From c1604d4383388aaee07a34c3e35680c43bc6f312 Mon Sep 17 00:00:00 2001 From: samwiseg0 Date: Thu, 1 Nov 2018 13:27:20 -0400 Subject: [PATCH 001/120] Add option to toggle SSL verification for tautulli --- configuration.example.py | 1 + tautulli.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/configuration.example.py b/configuration.example.py index a0df50a..492d500 100644 --- a/configuration.example.py +++ b/configuration.example.py @@ -41,6 +41,7 @@ tautulli_url = 'https://tautulli.domain.tld' tautulli_api_key = 'xxxxxxxxxxxxxxx' tautulli_failback_ip = '' tautulli_influxdb_db_name = 'plex' +tautulli_verify_ssl = True ########################## FIREWALL CONFIG ############################ asa_url = 'https://firewall.domain.tld' diff --git a/tautulli.py b/tautulli.py index 96f61ed..e92fb4f 100644 --- a/tautulli.py +++ b/tautulli.py @@ -12,8 +12,10 @@ CURRENT_TIME = datetime.now(timezone.utc).astimezone().isoformat() PAYLOAD = {'apikey': configuration.tautulli_api_key, 'cmd': 'get_activity'} -ACTIVITY = requests.get('{}/api/v2'.format(configuration.tautulli_url), - params=PAYLOAD).json()['response']['data'] +ACTIVITY = requests.get('{}/api/v2'.format(configuration.tautulli_url.rstrip('/')), + params=PAYLOAD, + verify=configuration.tautulli_verify_ssl + ).json()['response']['data'] SESSIONS = {d['session_id']: d for d in ACTIVITY['sessions']} From 6347e2da3ed0368cdded6fe37c74d229ea59e10e Mon Sep 17 00:00:00 2001 From: samwiseg0 Date: Thu, 1 Nov 2018 14:10:14 -0400 Subject: [PATCH 002/120] Update README, formatting --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 94aebb8..6a612a7 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ Repo for api scripts written (both pushing and pulling) to aggregate data into i Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), [Python3](https://www.python.org/downloads/), [InfluxDB](https://docs.influxdata.com/influxdb/v1.5/introduction/installation/) -
+

+ +

## Quick Setup 1. Install requirements `pip3 install -r requirements.txt` From 3dfae2d4bae7963949a99496041b894658e134fc Mon Sep 17 00:00:00 2001 From: samwiseg0 Date: Thu, 1 Nov 2018 14:58:34 -0400 Subject: [PATCH 003/120] Remove invalid character --- dashboard/panel_row_worldmap.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/panel_row_worldmap.json b/dashboard/panel_row_worldmap.json index 63b381d..52a00e0 100644 --- a/dashboard/panel_row_worldmap.json +++ b/dashboard/panel_row_worldmap.json @@ -1,4 +1,4 @@ -{ +{ "circleMaxSize": "2", "circleMinSize": "2", "colors": [ From bc7dea7ad6ed64f998ffde9fe705f5ebddee81df Mon Sep 17 00:00:00 2001 From: samwiseg0 Date: Thu, 1 Nov 2018 16:01:53 -0400 Subject: [PATCH 004/120] Add option to toggle SSL verification for other scripts --- configuration.example.py | 3 +++ ombi.py | 13 ++++++++++--- radarr.py | 17 ++++++++++++++--- sonarr.py | 21 ++++++++++++++------- 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/configuration.example.py b/configuration.example.py index 492d500..89e6097 100644 --- a/configuration.example.py +++ b/configuration.example.py @@ -22,6 +22,7 @@ sonarr_server_list = [ #('https://sonarr3.domain.tld', 'xxxxxxxxxxxxxxx', '3') ] sonarr_influxdb_db_name = 'plex' +sonarr_verify_ssl = True ############################ RADARR CONFIG ############################ radarr_server_list = [ @@ -30,11 +31,13 @@ radarr_server_list = [ #('https://radarr3.domain.tld', 'xxxxxxxxxxxxxxx', '3') ] radarr_influxdb_db_name = 'plex' +radarr_verify_ssl = True ############################ OMBI CONFIG ############################## ombi_url = 'https://ombi.domain.tld' ombi_api_key = 'xxxxxxxxxxxxxxx' ombi_influxdb_db_name = 'plex' +ombi_verify_ssl = True ########################## TAUTULLI CONFIG ############################ tautulli_url = 'https://tautulli.domain.tld' diff --git a/ombi.py b/ombi.py index 5aa1812..f7a1f91 100644 --- a/ombi.py +++ b/ombi.py @@ -19,8 +19,13 @@ def influx_sender(influx_payload): influx.write_points(influx_payload) def get_total_requests(): - get_tv_requests = requests.get('{}/api/v1/Request/tv'.format(configuration.ombi_url), headers=headers).json() - get_movie_requests = requests.get('{}/api/v1/Request/movie'.format(configuration.ombi_url), headers=headers).json() + get_tv_requests = requests.get('{}/api/v1/Request/tv'.format( + configuration.ombi_url), headers=headers, + verify=configuration.ombi_verify_ssl).json() + + get_movie_requests = requests.get('{}/api/v1/Request/movie'.format( + configuration.ombi_url), headers=headers, + verify=configuration.ombi_verify_ssl).json() count_movie_requests = 0 count_tv_requests = 0 @@ -46,7 +51,9 @@ def get_total_requests(): return influx_payload def get_request_counts(): - get_request_counts = requests.get('{}/api/v1/Request/count'.format(configuration.ombi_url), headers=headers).json() + get_request_counts = requests.get('{}/api/v1/Request/count'.format( + configuration.ombi_url), headers=headers, + verify=configuration.ombi_verify_ssl).json() influx_payload = [ { diff --git a/radarr.py b/radarr.py index 2358a73..0f95d0b 100644 --- a/radarr.py +++ b/radarr.py @@ -27,7 +27,11 @@ def get_missing_movies(): for radarr_url, radarr_api_key, server_id in configuration.radarr_server_list: headers = {'X-Api-Key': radarr_api_key} - get_movies = requests.get('{}/api/movie'.format(radarr_url), headers=headers).json() + + get_movies = requests.get('{}/api/movie'.format(radarr_url), + headers=headers, + verify=configuration.radarr_verify_ssl).json() + movies = {d['tmdbId']: d for d in get_movies} for movie in movies.keys(): @@ -64,7 +68,11 @@ def get_missing_avl(): for radarr_url, radarr_api_key, server_id in configuration.radarr_server_list: headers = {'X-Api-Key': radarr_api_key} - get_movies = requests.get('{}/api/movie'.format(radarr_url), headers=headers).json() + + get_movies = requests.get('{}/api/movie'.format(radarr_url), + headers=headers, + verify=configuration.radarr_verify_ssl).json() + movies = {d['tmdbId']: d for d in get_movies} for movie in movies.keys(): @@ -103,7 +111,10 @@ def get_queue_movies(): for radarr_url, radarr_api_key, server_id in configuration.radarr_server_list: headers = {'X-Api-Key': radarr_api_key} - get_movies = requests.get('{}/api/queue'.format(radarr_url), headers=headers).json() + get_movies = requests.get('{}/api/queue'.format(radarr_url), + headers=headers, + verify=configuration.radarr_verify_ssl).json() + queue_movies = {d['id']: d for d in get_movies} for movie in queue_movies.keys(): diff --git a/sonarr.py b/sonarr.py index 8be5f6c..6b463a3 100644 --- a/sonarr.py +++ b/sonarr.py @@ -32,7 +32,8 @@ def get_all_missing_shows(): headers = {'X-Api-Key': sonarr_api_key} get_tv_shows = requests.get('{}/api/wanted/missing/?pageSize=1000'.format(sonarr_url), - headers=headers).json()['records'] + headers=headers, + verify=configuration.sonarr_verify_ssl).json()['records'] tv_shows = {d['id']: d for d in get_tv_shows} @@ -81,8 +82,10 @@ def get_missing_shows(days_past): headers = {'X-Api-Key': sonarr_api_key} - get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=1000'.format(sonarr_url, last_days, today), - headers=headers).json() + get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=1000' + .format(sonarr_url, last_days, today), + headers=headers, + verify=configuration.sonarr_verify_ssl).json() tv_shows = {d['id']: d for d in get_tv_shows} @@ -130,7 +133,8 @@ def get_upcoming_shows(): headers = {'X-Api-Key': sonarr_api_key} get_upcoming_shows = requests.get('{}/api/calendar/'.format(sonarr_url), - headers=headers).json() + headers=headers, + verify=configuration.sonarr_verify_ssl).json() upcoming_shows = {d['id']: d for d in get_upcoming_shows} @@ -181,8 +185,10 @@ def get_future_shows(future_days): headers = {'X-Api-Key': sonarr_api_key} - get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=200'.format(sonarr_url, today, future), - headers=headers).json() + get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=200' + .format(sonarr_url, today, future), + headers=headers, + verify=configuration.sonarr_verify_ssl).json() tv_shows = {d['id']: d for d in get_tv_shows} @@ -232,7 +238,8 @@ def get_queue_shows(): headers = {'X-Api-Key': sonarr_api_key} get_tv_shows = requests.get('{}/api/queue'.format(sonarr_url), - headers=headers).json() + headers=headers, + verify=configuration.sonarr_verify_ssl).json() tv_shows = {d['id']: d for d in get_tv_shows} From 158f4f3eaf46f4ee4fe0e3cb41c727ec1d62fcb5 Mon Sep 17 00:00:00 2001 From: samwiseg0 Date: Thu, 1 Nov 2018 16:03:03 -0400 Subject: [PATCH 005/120] Clean up some formatting --- ombi.py | 26 ++++++++++----- radarr.py | 35 ++++++++++++-------- sonarr.py | 96 ++++++++++++++++++++++++++++++++++++------------------- 3 files changed, 104 insertions(+), 53 deletions(-) diff --git a/ombi.py b/ombi.py index f7a1f91..5adbd99 100644 --- a/ombi.py +++ b/ombi.py @@ -9,15 +9,22 @@ import configuration headers = {'Apikey': configuration.ombi_api_key} + def now_iso(): now_iso = datetime.now(timezone.utc).astimezone().isoformat() return now_iso + def influx_sender(influx_payload): - influx = InfluxDBClient(configuration.influxdb_url, configuration.influxdb_port, configuration.influxdb_username, - configuration.influxdb_password, configuration.ombi_influxdb_db_name) + influx = InfluxDBClient(configuration.influxdb_url, + configuration.influxdb_port, + configuration.influxdb_username, + configuration.influxdb_password, + configuration.ombi_influxdb_db_name) + influx.write_points(influx_payload) + def get_total_requests(): get_tv_requests = requests.get('{}/api/v1/Request/tv'.format( configuration.ombi_url), headers=headers, @@ -50,6 +57,7 @@ def get_total_requests(): ] return influx_payload + def get_request_counts(): get_request_counts = requests.get('{}/api/v1/Request/count'.format( configuration.ombi_url), headers=headers, @@ -57,7 +65,7 @@ def get_request_counts(): influx_payload = [ { - "measurement": "Ombi", + "measurement": "Ombi", "tags": { "type": "Request_Counts" }, @@ -71,15 +79,17 @@ def get_request_counts(): ] return influx_payload + if __name__ == "__main__": parser = argparse.ArgumentParser(prog='Ombi stats operations', - description='Script to aid in data gathering from Ombi', formatter_class=RawTextHelpFormatter) + description='Script to aid in data gathering from Ombi', + formatter_class=RawTextHelpFormatter) - parser.add_argument("--total", action='store_true', - help='Get the total count of all requests') + parser.add_argument("--total", action='store_true', + help='Get the total count of all requests') - parser.add_argument("--counts", action='store_true', - help='Get the count of pending, approved, and available requests') + parser.add_argument("--counts", action='store_true', + help='Get the count of pending, approved, and available requests') opts = parser.parse_args() diff --git a/radarr.py b/radarr.py index 0f95d0b..26bbcf2 100644 --- a/radarr.py +++ b/radarr.py @@ -14,8 +14,12 @@ def now_iso(): def influx_sender(influx_payload): - influx = InfluxDBClient(configuration.influxdb_url, configuration.influxdb_port, configuration.influxdb_username, - configuration.influxdb_password, configuration.radarr_influxdb_db_name) + influx = InfluxDBClient(configuration.influxdb_url, + configuration.influxdb_port, + configuration.influxdb_username, + configuration.influxdb_password, + configuration.radarr_influxdb_db_name) + influx.write_points(influx_payload) @@ -36,7 +40,8 @@ def get_missing_movies(): for movie in movies.keys(): if not movies[movie]['downloaded']: - movie_name = ('{} ({})'.format(movies[movie]['title'], movies[movie]['year'])) + movie_name = ('{} ({})'.format(movies[movie]['title'], + movies[movie]['year'])) missing.append((movie_name, movies[movie]['tmdbId'])) for movie, id in missing: @@ -78,10 +83,10 @@ def get_missing_avl(): for movie in movies.keys(): if not movies[movie]['downloaded']: if movies[movie]['isAvailable'] is True: - movie_name = ('{} ({})'.format(movies[movie]['title'], movies[movie]['year'])) + movie_name = ('{} ({})'.format(movies[movie]['title'], + movies[movie]['year'])) missing.append((movie_name, movies[movie]['tmdbId'])) - for movie, id in missing: influx_payload.append( { @@ -118,8 +123,11 @@ def get_queue_movies(): queue_movies = {d['id']: d for d in get_movies} for movie in queue_movies.keys(): - name = '{} ({})'.format(queue_movies[movie]['movie']['title'], queue_movies[movie]['movie']['year']) + name = '{} ({})'.format(queue_movies[movie]['movie']['title'], + queue_movies[movie]['movie']['year']) + quality = (queue_movies[movie]['quality']['quality']['name']) + protocol = (queue_movies[movie]['protocol'].upper()) if protocol == 'USENET': @@ -155,16 +163,17 @@ def get_queue_movies(): if __name__ == "__main__": parser = argparse.ArgumentParser(prog='Radarr stats operations', - description='Script to aid in data gathering from Radarr', formatter_class=RawTextHelpFormatter) + description='Script to aid in data gathering from Radarr', + formatter_class=RawTextHelpFormatter) - parser.add_argument("--missing", action='store_true', - help='Get missing movies') + parser.add_argument("--missing", action='store_true', + help='Get missing movies') - parser.add_argument("--missing_avl", action='store_true', - help='Get missing yet available movies') + parser.add_argument("--missing_avl", action='store_true', + help='Get missing yet available movies') - parser.add_argument("--queue", action='store_true', - help='Get movies in queue') + parser.add_argument("--queue", action='store_true', + help='Get movies in queue') opts = parser.parse_args() diff --git a/sonarr.py b/sonarr.py index 6b463a3..a9c50eb 100644 --- a/sonarr.py +++ b/sonarr.py @@ -14,8 +14,12 @@ def now_iso(): def influx_sender(influx_payload): - influx = InfluxDBClient(configuration.influxdb_url, configuration.influxdb_port, configuration.influxdb_username, - configuration.influxdb_password, configuration.sonarr_influxdb_db_name) + influx = InfluxDBClient(configuration.influxdb_url, + configuration.influxdb_port, + configuration.influxdb_username, + configuration.influxdb_password, + configuration.sonarr_influxdb_db_name) + influx.write_points(influx_payload) @@ -37,11 +41,14 @@ def get_all_missing_shows(): tv_shows = {d['id']: d for d in get_tv_shows} - for show in tv_shows.keys(): series_title = '{}'.format(tv_shows[show]['series']['title']) - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'],tv_shows[show]['episodeNumber']) - missing.append((series_title, sxe, tv_shows[show]['id'], tv_shows[show]['title'])) + + sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], + tv_shows[show]['episodeNumber']) + + missing.append((series_title, sxe, tv_shows[show]['id'], + tv_shows[show]['title'])) for series_title, sxe, id, episode_title in missing: influx_payload.append( @@ -92,8 +99,12 @@ def get_missing_shows(days_past): for show in tv_shows.keys(): if not (tv_shows[show]['hasFile']): series_title = '{}'.format(tv_shows[show]['series']['title']) - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) + + sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], + tv_shows[show]['episodeNumber']) + air_date = (tv_shows[show]['airDate']) + missing.append((series_title, sxe, air_date, tv_shows[show]['id'])) for series_title, sxe, air_date, id in missing: @@ -140,27 +151,34 @@ def get_upcoming_shows(): for show in upcoming_shows.keys(): series_title = '{}'.format(upcoming_shows[show]['series']['title']) - sxe = 'S{:0>2}E{:0>2}'.format(upcoming_shows[show]['seasonNumber'],upcoming_shows[show]['episodeNumber']) - upcoming.append((series_title, sxe, upcoming_shows[show]['id'], upcoming_shows[show]['title'], upcoming_shows[show]['airDate'])) + + sxe = 'S{:0>2}E{:0>2}'.format(upcoming_shows[show]['seasonNumber'], + upcoming_shows[show]['episodeNumber']) + + upcoming.append((series_title, sxe, + upcoming_shows[show]['id'], + upcoming_shows[show]['title'], + upcoming_shows[show]['airDate'])) for series_title, sxe, id, episode_title, air_date in upcoming: influx_payload.append( { "measurement": "Sonarr", "tags": { - "type": "Soon", - "sonarrId": id, - "server": server_id - }, - "time": now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "airs": air_date - } - } - ) + "type": "Soon", + "sonarrId": id, + "server": server_id + }, + "time": now, + "fields": { + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "airs": air_date + } + } + ) + # Empty upcoming or else things get foo bared upcoming = [] @@ -194,9 +212,16 @@ def get_future_shows(future_days): for show in tv_shows.keys(): series_title = '{}'.format(tv_shows[show]['series']['title']) + dl_status = int(tv_shows[show]['hasFile']) - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) - air_days.append((series_title, dl_status, sxe, tv_shows[show]['title'], tv_shows[show]['airDate'], tv_shows[show]['id'])) + + sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], + tv_shows[show]['episodeNumber']) + + air_days.append((series_title, dl_status, sxe, + tv_shows[show]['title'], + tv_shows[show]['airDate'], + tv_shows[show]['id'])) for series_title, dl_status, sxe, episode_title, air_date, id in air_days: influx_payload.append( @@ -245,15 +270,21 @@ def get_queue_shows(): for show in tv_shows.keys(): series_title = '{}'.format(tv_shows[show]['series']['title']) + episode_title = '{}'.format(tv_shows[show]['episode']['title']) + protocol = (tv_shows[show]['protocol'].upper()) - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['episode']['seasonNumber'], tv_shows[show]['episode']['episodeNumber']) + + sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['episode']['seasonNumber'], + tv_shows[show]['episode']['episodeNumber']) + if protocol == 'USENET': protocol_id = 1 else: protocol_id = 0 - queue.append((series_title, episode_title, protocol, protocol_id, sxe, tv_shows[show]['id'])) + queue.append((series_title, episode_title, protocol, + protocol_id, sxe, tv_shows[show]['id'])) for series_title, episode_title, protocol, protocol_id, sxe, id in queue: influx_payload.append( @@ -284,23 +315,24 @@ def get_queue_shows(): if __name__ == "__main__": parser = argparse.ArgumentParser(prog='Sonarr stats operations', - description='Script to aid in data gathering from Sonarr', formatter_class=RawTextHelpFormatter) + description='Script to aid in data gathering from Sonarr', + formatter_class=RawTextHelpFormatter) parser.add_argument("--missing", action='store_true', - help='Get all missing TV shows') + help='Get all missing TV shows') parser.add_argument("--missing_days", type=int, - help='Get missing TV shows in past X days') + help='Get missing TV shows in past X days') parser.add_argument("--upcoming", action='store_true', - help='Get upcoming TV shows') + help='Get upcoming TV shows') parser.add_argument("--future", type=int, - help='Get TV shows on X days into the future. Includes today.' - '\ni.e. --future 2 is Today and Tomorrow') + help='Get TV shows on X days into the future. Includes today.' + '\ni.e. --future 2 is Today and Tomorrow') parser.add_argument("--queue", action='store_true', - help='Get TV shows in queue') + help='Get TV shows in queue') opts = parser.parse_args() From 01800259a4e6badc8d1bc39ba93e125708022126 Mon Sep 17 00:00:00 2001 From: samwiseg0 Date: Tue, 13 Nov 2018 15:20:14 -0500 Subject: [PATCH 006/120] Add panel examples. --- .../example_panels/ombi_requests_pending.json | 107 ++++++++++++ .../example_panels/ombi_requests_pending.png | Bin 0 -> 3750 bytes .../piechart_tautulli_device_types.json | 122 +++++++++++++ .../piechart_tautulli_device_types.png | Bin 0 -> 35595 bytes .../piechart_tautulli_stream_types.json | 109 ++++++++++++ .../piechart_tautulli_stream_types.png | Bin 0 -> 30810 bytes .../table_radarr_missing_avail_movies.json | 109 ++++++++++++ .../table_radarr_missing_avail_movies.png | Bin 0 -> 6904 bytes .../example_panels/table_radarr_queue.json | 161 +++++++++++++++++ .../example_panels/table_radarr_queue.png | Bin 0 -> 5769 bytes .../example_panels/table_sonarr_missing.json | 149 ++++++++++++++++ .../example_panels/table_sonarr_missing.png | Bin 0 -> 14394 bytes .../example_panels/table_sonarr_on_today.json | 163 ++++++++++++++++++ .../example_panels/table_sonarr_on_today.png | Bin 0 -> 49745 bytes .../example_panels/table_sonarr_queue.json | 149 ++++++++++++++++ .../example_panels/table_sonarr_queue.png | Bin 0 -> 7369 bytes 16 files changed, 1069 insertions(+) create mode 100644 dashboard/example_panels/ombi_requests_pending.json create mode 100644 dashboard/example_panels/ombi_requests_pending.png create mode 100644 dashboard/example_panels/piechart_tautulli_device_types.json create mode 100644 dashboard/example_panels/piechart_tautulli_device_types.png create mode 100644 dashboard/example_panels/piechart_tautulli_stream_types.json create mode 100644 dashboard/example_panels/piechart_tautulli_stream_types.png create mode 100644 dashboard/example_panels/table_radarr_missing_avail_movies.json create mode 100644 dashboard/example_panels/table_radarr_missing_avail_movies.png create mode 100644 dashboard/example_panels/table_radarr_queue.json create mode 100644 dashboard/example_panels/table_radarr_queue.png create mode 100644 dashboard/example_panels/table_sonarr_missing.json create mode 100644 dashboard/example_panels/table_sonarr_missing.png create mode 100644 dashboard/example_panels/table_sonarr_on_today.json create mode 100644 dashboard/example_panels/table_sonarr_on_today.png create mode 100644 dashboard/example_panels/table_sonarr_queue.json create mode 100644 dashboard/example_panels/table_sonarr_queue.png diff --git a/dashboard/example_panels/ombi_requests_pending.json b/dashboard/example_panels/ombi_requests_pending.json new file mode 100644 index 0000000..0b09acf --- /dev/null +++ b/dashboard/example_panels/ombi_requests_pending.json @@ -0,0 +1,107 @@ +{ + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "rgba(212, 74, 58, 0.58)" + ], + "datasource": "influxdb", + "format": "none", + "gauge": { + "maxValue": 40, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 20, + "y": 0 + }, + "id": 8, + "interval": null, + "links": [ + { + "targetBlank": true, + "title": "Ombi", + "type": "absolute", + "url": "https://request.server.com/requests" + } + ], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "dsType": "influxdb", + "groupBy": [], + "measurement": "Ombi", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "pending" + ], + "type": "field" + } + ] + ], + "tags": [ + { + "key": "type", + "operator": "=", + "value": "Request_Counts" + } + ] + } + ], + "thresholds": ".5,5", + "title": "Req. Pending", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" +} diff --git a/dashboard/example_panels/ombi_requests_pending.png b/dashboard/example_panels/ombi_requests_pending.png new file mode 100644 index 0000000000000000000000000000000000000000..e89bc700a630a07605c6b0997153d2f475b070a1 GIT binary patch literal 3750 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 literal 0 HcmV?d00001 diff --git a/dashboard/example_panels/piechart_tautulli_device_types.json b/dashboard/example_panels/piechart_tautulli_device_types.json new file mode 100644 index 0000000..a4ec83b --- /dev/null +++ b/dashboard/example_panels/piechart_tautulli_device_types.json @@ -0,0 +1,122 @@ +{ + "aliasColors": { + "Android": "#a4ca39", + "Chrome": "#db3236", + "Chromecast": "#039BE5", + "NetCast": "#c8135c", + "Playstation 4": "#003791", + "Plex Media Player": "#e67817", + "Roku": "#6d3c97", + "Samsung": "#034ea2", + "Tizen": "#034ea2", + "Xbox One": "#107c10", + "iOS": "#858487", + "tvOS": "#858487" + }, + "breakPoint": "50%", + "cacheTimeout": null, + "combine": { + "label": "Others", + "threshold": ".04" + }, + "datasource": "influxdb", + "decimals": 0, + "fontSize": "110%", + "format": "none", + "gridPos": { + "h": 10, + "w": 5, + "x": 0, + "y": 6 + }, + "id": 24, + "interval": null, + "legend": { + "percentage": true, + "percentageDecimals": 0, + "show": true, + "sort": "total", + "sortDesc": true, + "values": true + }, + "legendType": "On graph", + "links": [], + "maxDataPoints": 3, + "nullPointMode": "connected", + "pieType": "donut", + "strokeWidth": "7", + "targets": [ + { + "alias": "$tag_device_type", + "application": { + "filter": "" + }, + "functions": [], + "group": { + "filter": "" + }, + "groupBy": [ + { + "params": [ + "device_type" + ], + "type": "tag" + } + ], + "host": { + "filter": "" + }, + "item": { + "filter": "" + }, + "measurement": "Tautulli", + "mode": 0, + "options": { + "showDisabledItems": false, + "skipEmptyValues": false + }, + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "session_key" + ], + "type": "field" + }, + { + "params": [], + "type": "distinct" + }, + { + "params": [], + "type": "count" + } + ] + ], + "table": { + "skipEmptyValues": false + }, + "tags": [ + { + "key": "type", + "operator": "=", + "value": "Session" + } + ], + "triggers": { + "acknowledged": 2, + "count": true, + "minSeverity": 3 + } + } + ], + "timeFrom": "1w", + "title": "Device Types", + "transparent": false, + "type": "grafana-piechart-panel", + "valueName": "total" +} diff --git a/dashboard/example_panels/piechart_tautulli_device_types.png b/dashboard/example_panels/piechart_tautulli_device_types.png new file mode 100644 index 0000000000000000000000000000000000000000..1f08275c3981d6e38ee178f1de73a9ae716cd959 GIT binary patch literal 35595 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 L_*1wFtb5GgwU`{x literal 0 HcmV?d00001 diff --git a/dashboard/example_panels/piechart_tautulli_stream_types.json b/dashboard/example_panels/piechart_tautulli_stream_types.json new file mode 100644 index 0000000..100281d --- /dev/null +++ b/dashboard/example_panels/piechart_tautulli_stream_types.json @@ -0,0 +1,109 @@ +{ + "aliasColors": {}, + "breakPoint": "50%", + "cacheTimeout": null, + "combine": { + "label": "Others", + "threshold": "0" + }, + "datasource": "influxdb", + "decimals": 0, + "fontSize": "110%", + "format": "none", + "gridPos": { + "h": 10, + "w": 5, + "x": 5, + "y": 6 + }, + "id": 25, + "interval": null, + "legend": { + "percentage": true, + "percentageDecimals": 0, + "show": true, + "sort": "total", + "sortDesc": true, + "values": true + }, + "legendType": "On graph", + "links": [], + "maxDataPoints": 3, + "nullPointMode": "connected", + "pieType": "donut", + "strokeWidth": "7", + "targets": [ + { + "alias": "$tag_video_decision", + "application": { + "filter": "" + }, + "functions": [], + "group": { + "filter": "" + }, + "groupBy": [ + { + "params": [ + "video_decision" + ], + "type": "tag" + } + ], + "host": { + "filter": "" + }, + "item": { + "filter": "" + }, + "measurement": "Tautulli", + "mode": 0, + "options": { + "showDisabledItems": false, + "skipEmptyValues": false + }, + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "session_key" + ], + "type": "field" + }, + { + "params": [], + "type": "distinct" + }, + { + "params": [], + "type": "count" + } + ] + ], + "table": { + "skipEmptyValues": false + }, + "tags": [ + { + "key": "type", + "operator": "=", + "value": "Session" + } + ], + "triggers": { + "acknowledged": 2, + "count": true, + "minSeverity": 3 + } + } + ], + "timeFrom": "1w", + "title": "Stream Types", + "transparent": false, + "type": "grafana-piechart-panel", + "valueName": "total" +} diff --git a/dashboard/example_panels/piechart_tautulli_stream_types.png b/dashboard/example_panels/piechart_tautulli_stream_types.png new file mode 100644 index 0000000000000000000000000000000000000000..75c088aa81e2257ad37042c97dd053c6313f2bc1 GIT binary patch literal 30810 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 literal 0 HcmV?d00001 diff --git a/dashboard/example_panels/table_radarr_queue.json b/dashboard/example_panels/table_radarr_queue.json new file mode 100644 index 0000000..427b00e --- /dev/null +++ b/dashboard/example_panels/table_radarr_queue.json @@ -0,0 +1,161 @@ +{ + "columns": [], + "datasource": "influxdb", + "fontSize": "90%", + "gridPos": { + "h": 5, + "w": 4, + "x": 20, + "y": 9 + }, + "hideTimeOverride": true, + "id": 20, + "interval": "32s", + "links": [ + { + "targetBlank": true, + "title": "Radarr", + "type": "absolute", + "url": "https://movies.server.com/wanted/missing" + } + ], + "minSpan": 6, + "pageSize": 3, + "scroll": true, + "showHeader": true, + "sort": { + "col": null, + "desc": false + }, + "styles": [ + { + "alias": "", + "colorMode": null, + "colors": [ + "rgba(245, 54, 54, 0.9)", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "pattern": "Time", + "thresholds": [], + "type": "hidden", + "unit": "short" + }, + { + "alias": "Name", + "colorMode": "row", + "colors": [ + "#0a437c", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 2, + "mappingType": 1, + "pattern": "name", + "preserveFormat": true, + "thresholds": [ + "20" + ], + "type": "string", + "unit": "short" + }, + { + "alias": "", + "colorMode": "row", + "colors": [ + "#0a437c", + "rgb(168, 147, 4)", + "#629e51" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 0, + "mappingType": 1, + "pattern": "T", + "thresholds": [ + "-1", + "1" + ], + "type": "string", + "unit": "none", + "valueMaps": [ + { + "text": "Tor", + "value": "0" + }, + { + "text": "Use", + "value": "1" + } + ] + } + ], + "targets": [ + { + "dsType": "influxdb", + "groupBy": [], + "measurement": "Radarr", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "name" + ], + "type": "field" + }, + { + "params": [ + "Movie" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "quality" + ], + "type": "field" + }, + { + "params": [ + "Quality" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "protocol_id" + ], + "type": "field" + }, + { + "params": [ + "T" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "type", + "operator": "=", + "value": "Queue" + } + ] + } + ], + "timeFrom": "33s", + "title": "Movies in Queue", + "transform": "table", + "type": "table" +} diff --git a/dashboard/example_panels/table_radarr_queue.png b/dashboard/example_panels/table_radarr_queue.png new file mode 100644 index 0000000000000000000000000000000000000000..58a853ba61760855f2d04b5da126b2480c39731f GIT binary patch literal 5769 zcmeHLXIoQUvxX3rUIL<&(5pyQilHMNDbkBHA%K8Hq=t?tNRy`U(m|w1FQJAaf}uC1 zNfi*OA%H+A37q}B*ZBkI)A{mz*?ZRB>zcJ@t(m#+nOGwOEgDKzN)i$h8XawQV-gZl zWk7BszYaWWyyED97paf2mKsU*DEk%(3G+i8byd?qQfw~L*G!`c)0O=|fE&z2?nC~U z36go!Ed;D3aX*S|l|%?clJi96Ib`K^$Ff(59Hk+LsaW2CN+c-o71K z0F$w!&G=`)L>kA*35IY*!gL~5Ed+Z;Rntf)j0mUH$s`n$Iv}Pl7>hq`7BF+Q&ou(9 zU0?tyIGq}(^%>_=0dYrikyFuVczQnJGV?GMR92?j7|XAEY$L-%3Md>T4^-gz!U#?} zUb6u~x>8f0F3ZUNY5_}1*2xyw`|E)CjAkg_hOPgxQ`Lz*#}D^DXJKM` zvKQx5dUn>@5_DYd1FaW?aFIgxxw%;EEiL7AE3ZQIR5J4sWEU-V9k(lbB>b$6jNbkk z$^NLH4KA)`lkr5oml>Nkh847Td}^_0ImkM zle=9w&FW~jsXOk_2Cexbjb?eP7R&h+)U zMks3Amq(8;t@M#*Pc#jg*L2xqUTQWab~;6Z#2labW&5=SVUcMa5WMS}v`VeVjF)K>r{t;ngb;Lh&Nvt7}?IYyQJ% z>c{d&$2&89C5hSZ@AXvOE!T)=c2CYHD=^P&7ojPvDKf#n_{1wWKwE?+!^dX+WL_aD#mv2QsUtmj^fZ^ zcec*5!G&h8ZAT2gvpG@ZuHb}8IM}i9^}U}LvaQ3e5J>-Kx+FKjEJO);`02RwSpp0< zg}!BQ83K{AW5zQkyVmsw6^{m_8y7vp1uR1!vvJgKIbMA#4Ug2zs17$03UN`CV_{*@ zu2a_q8x1B;a?jSo<7Wmgi5E_oVHUaYJq6>KlBav^twMM1)QA1_`|?FK%CsYfBaD0{ z-BNp6j?lfbZCzv6A@RdKV0*)48s&RCFY_n?GI>p0T*WS|6SA1W+-~YJD7q^6b+$m2 zjQ`1+U_xKiG_ve?$Zx3~3Ez%OIl<05ZH(p)<`L3b1sQ1&&&%ePm;U0x6{y&Ik85$g)JsM zTDMwIEzn%OwK*;n5}Eql0{3zh?hz_u0@t-|^Hps*KHTW+xM-)PhQgS#Wf7Rc4QT$VCLev;Y>xqJ*=h_Q9kq>1?%wLN)5_yh~*DUg~n|bmy|rc zx+J;}dNiku-QFn<7>GW{?SeYLhb&Asc=qM}pA4zN zgW6q2`dVJD>%KWgPAGuPlw+m$GrwApxn*U6t-`l?*}y!e^#)C0i$SN9-_wso?|7s& z)~I#~=lXs?yw7)3HVGWZwgG3Jug5iqrpY?Fdi!P5k54@NMv_&{CmvGm- zy%{v7eARA^uNWQmLh|x_A-hg%^y9?5%KG!xG2lc~um;ap{cVNL3Bt{#f!XH2Gn{?T z&aNdZK^Pgm=c}4OpQn<#=8s@Hi5L4P^9UP{Su6M9rO`pC=ln@AM8)RxHA8$bIbnNo z>F{Ck{s+OQORbeF&rdzc=9)YzWCWZQ$HaI%t85xEXuV~#%EVW#y7IPe#PkYPSIL*@Zn*b zBC+z_#vyGXc_ewP?eDA{d477}N*G1un|Ls3d6~a+^WsDq@Q2^>?SYGKl9I089C>p{ zJY0Z+kG{Dlh=uU%!I- zmPTu+j2>tLIQ*9a%oo-fUhaIEyva=gfUBuK7uP{W8{0I1(Ojef3+KmR|U2{-L=kC!`lWNobk_{I%hS=pJgi8rrbb1Cq!XZVPD9PdoqAT*MG za}4gy*oP+$WgO4Vz-D0%39A$_6 z(5$#WBUw%Zsr+H{nBHD4LBT$cF~m1}FLh1LkF~Y*0Fg2?I}WzTJb3wAdpJva2muvD zsH>|xoWwVz23H&7n$~kEhvHK7-|6i&jLrtf!AJJ?F1mrlZ zuo1to71h;oA-nK$NR%`{p4hszIaOitE(1`TTfC(r?i8xWQ+`Ab~@X zl~KT;<)_&9Fq;46Eag>w<@Btq=4R4MT?1eU1TqeQxN)|SopDO9E2j99Yxex73R zWXHi;pVQ>#W4GA?Zd!J^7q%DyEN3{uTN`s5db3Is*mCxEUsy`=M!ekyg^G zVfZEE9O_wA6L#+D*q)-0kkFS+$s$Rw1vx&H&X);Zt)WMmoX`uQy_PjvJ^_K%-dIMt z(DVBo$Rmb?P|v$C|J^b|ic(1YDKB0t;dZ3WN3ZUc${x4xpD5Y{{j^>fNnjL}Mt71> zJL7d^0tPv6gK-@eXFj{Lk_kw+1lZ{4C?+>xDV7!r`-R4JK5TFqk#L_Ba&~b!&${cg zfDS-NmjGz!I8D_s-e_a-dt==R+vkyzo*uooh<3uxHI`pV(IB5Pkq%78SKGbm<8?Q; z{?FM@KKFr*&nBe@-4~n=fN?X4ZU9R;J?P`Cd$FSCyS+dWg<}n|&9!Q9T{%5i3k+Ys za9e6`Z4;*(MqTBVbneG5S$_g+O?NSO*3ny3Kk2q#CWweVX^Ds=yYbQLhszxkSVkx< zkAEk8O4(xNS9g?qTT1}W?$LiFNDuT^4>0yiUyAiIBo4M>JEx`&7!4I7(*zAx#S!h1 zq-58H4pP{j&o`NciP^R>jTwZQQ?xsG-g6!8J@?zGTkW_Rc=%b6d*-Fj-|-rI<*DUG z{6gZzUY9+z^wTHP2gGz~C1HTpTef)$ml)*6m6ZwgXXw1Po;})pY^`PXTe+HB<-kY3 z3>0vbW~iiU8F<^+#H1r}b#2Y;kDyiD+W{@k&`-U+Hr=p)SgZQvC*scr*E;Ob^X;Ze z&dbZQWvsJx%4RqjF>4a3<&i#Kn&xcxgj#o$6aPetFY@86n(`I#-MmOFV^ z>;K3){2~e2@4g}Dx6Nc;{p52=dVS`KjXKlC(N>hYNKeShn79K@Hh2mfb%XXPK#xvW zX6bat`FB1@KVX4VOA60t`T<1ic}iBknV%v^Dve5ul4j23qhtWU>oRAAU_!uWfR5?p5y=6302a> zb5cJce^`CW;dC_K0ond~Pfoa%?2N7p>cH`cP_PPzLyj>vKoT0Fhw3?q)n zE_146p3{cxCmvrt^Goa7f|wY$Jav_<26!lpS~1E@5pt z%ki-z1FNi8<(%iw_G>krCwJ^}h&({m7sGPTRmf5CkjUWSj?9KU85rK>`TBSAv~+Su z3`l8pL2sOA_Z{!-*wi{i+KyagrsPFt*dtk7M{^<{Wm;5PM*3stkhlwu{ek=Pcy&ni)hIH zeRuY7?7-35ryKm{Uy29|>u_0ypR)Q}QgzEwz^ zYM74=wZb=O)P)42^Khl@9u}EnoqpfKSzI+b&!;O!y@9HDYOcsLqCYrM3d*cXGg-Pl z`_y)Wg6ZZ>9&zQaVb9mA8w#!`+8{oM+W?75@(7E*7P|+qmQ5hEfAnOU13eIGUX_D; zce#9Hz}hNwq;dHiqbRk1<^!T5X`wt&y zs{*v$){IznwN%-prAUt809cP=&NHCb@P0hiHv-pD@ea~;-6yDvG7Zs(|8Y5%eWW#ewG-BwP2aBdtL#LwI z%gW1hi;9Y(-(YjT`*%k|Onzc6PR?R3RD`NR3~oNh7k+XMc4rm}3JRN>)0L94vY`^K z^eb_0^A@f$yQ=3*DL*iA&@=x8e*07-jQy(%`yl>7NN{G?PBR<%YBc3dykey2?}VrH zqztk@-@g#2LK{uUh9{=yQL5hy3z;vf!ID~%zE?S~I1LpW@R(jd#d{`!8{oLoGI_o|EHSW{_DK<& zb~x#kMYxHp3A{&f(UIXIWS_O`=}VTgBK7bN6ez%w*|SYJ_OGVIiM(~8^#!cFdU1uV zI5FVLswv_)~ zx>HB4aoy)_O#&*>pTW<`Wl!4RFHV|fw}!TJ^76hntbOpcb95|wNe8BwGJ$|sxoJk2~S7qDqPYRrG)M-4(;Ld!=HR}zQs5+j48XW=fB=}v9? zybSGzR@4aixb!Y_eqjKdI#z7IAl_zYB=zF3ZrmfVS<^aSF7#JL#mF+-W}imB`y9Cg z#sfM#xxbmED{E7Du6pmr{#-3gHdA%#M0ixSD^WRPQ9ExD34)eI?|Nt*^Hx08MlS>Ag1q`MP~lRj|0YXtrV?>2+AszqfjST&N|DNRYZetTPaTmcqyocj~WS!KI=B zZ23W9FInMzDvv!qf_b}xe~DXS*za=kPwo78A zo)7P*13So_c9XY1Ej=^c=W?^u+x+jZ3N?fmn6v={%0oUEdot!NZn>9GOX;r` z{KeTG;+Y;W+l#&Ym{eC6E--=Q1))X?Ux3GatddA#;!ehLy=-?pi{`VoMW%dIY`)@@ zD+<|2?oRD_==^!hcloS8)#r{L7&~b!`t*?#WY&<$ic94oEX#X65zhZ?)|J0^&>W*4z0*iiW%I$eapyIH-{7A*L>fOn|En^J{;nC4hc~P^nocXyugMWe&O|l@vhgtG0InM)@WfFS?FPmf}=LNu&HeFd$EV5ZEq9Y(D>>B0aQs=?OrN{q!>jQVR)* zOrkO_rR)1oCHE5aG;{M~#1}`Jz~Es!p6$n{nGV{-rDf#Wy>Z%hWQxEou%y%up^91~+hWJVB45S2C7{YC`u z)aP_y(Xor}$2*t17I5b|r=Y9Sn@A21%a1946LjgrK#TkCjM(-R+fo*5# zbg(M86=50nV`A$lAh4bRpwE?Y_bRhFb9WM60&Z`*ftH;To&1rJzd1xWsW@?wzqi+! z2sFr3hj1k=Gd`Lni7)}1CW9GFn7X{S1~yF}sKF04!H1^$drVKzkoNJ^cu7T@@8|vz z9&g(_obwB#B1irpiu-OOj%v1;K%1ci_WCu@{UiixZm$?kfY_$bPp*FJAg_pZ?i1(W zpd2Sun(4W^5c#^OgMU`pIMPWgjSuv_hMAI{e+WMYdv-^O)?c1AT0GXV?M!hKm1OGl z1~>(`1l=;nWRUI(vttv`I4Eo2%FfMOVF|7mKCK^ZSW02vFm-CTE*h5ow&`VVpeVqv zkYM|(qJ0hurQCRXP*FFs2cOoG5(>;m>JPcO9xpeijdEHaHPja*@N_=7h+hq#sJ;pq%-Btj!J( zq7Pr_WcH27RR#pVp!c(o{g=8g@ljS>qAs3QD&su2=0Dbd6Y@VIUD6Vzqf&W zQQCPUhwW5>O7Ofj1&2tOdUB77A`8D=tOi4>Y>bps{epqi;jfw=lrMi{FLOcymGFjlbS)VlI>vl85v=&|hFzkDl;!~Ze!3MME}%UMvo zyA{Ar$t-IpYXUHLXCB?c@1t~Tdh>2)3g=@7879*=AolQ7oane)Aq#yp=rjA78AKouR5q_4uXuE$o-3XiMJlo4;V46nhBm z*`Jx#6tcIiouuC?34ApNZHvSz%+v*t-V#XNyqCjNOXAACzGX=uigS358?9H|VYhgS zygx~w%vZ0>_PJw+E#$KzZpXpGdS0oT!SR1MTaZloZZ+lLT9(FbL({3llh2gh3x0K@ zQ7!5+V87Ft{Vr)~W&XT&_?X1#fNGdQQ6J*&5?Is4ocP3*exx{?{bA_mABJJ_PVr3u zlNwu$)OiVg!>GvG^5N>RvgzFGZG$4WzSXqqw)5~a_JgXItQvwfq~Ust?9fq%)yeVX zgy+R@{%$5vin|9fNycKkaU8+cnBw>F9}Zp)?U!?c0hZ8#WGlw~vDV`P*Ksju+jj<$ zV@c1oPjmYDLkJ1@t%GWFByP6X!&8fEu>jD+Cedd>x|3@N>)?fvu$#OhbE=2=*RCgS zV`94#DZc$Fmwd)2cS~zX(!wmxc%M(l4>4tEg|?_MaFPBmbh##TbfNTHC<4aLc_5lvL*N z8Uo~VFxogGmfE4^XUl-bud9n<9F$ej<`j=NLNU(a+rWXm?2hM}dn5Zafp@0zB4avT zP~0DSiKW_OAC&*~bf@>OdECzxbl9{!*IXNG=+i}8B)G896=HQf;g!-`|3rPZF?`|q zZ&rrZJDsxl;`sH&t8@Y8W`gO@^zl1U@XLnUr&)>SUGG zizaFxo3n*+(;I|IXIQy$6Xc$Xk!uY3emn9X2t)+JYgf4ra@XBfE}^M9F#qdSGSOGj zdKTU-i0CC1i{?0d<^np*s3?5>VFc@%{7X%0VFs+H!MWTSF$^-LvDIxqg_;8tv!xdu zItKQ%i(EU~9~y(o+y=zxgBn=qQ?7pI+}KuDMY36k$$0p0U&hN~Q7gDDl9DGX7GCvH zTXU9isek83BzxjRO`RmeeM*8h-ySz@j&RD{3ODEZ<&7{*&@-ZG%tLZ~q@fj=p~X^c zskN8jOK_mH8CL80i*j|=pu@Q>CPuSV)y}g%%FiuJCZ=1|hQ^Gzyppt)87opva$sbqfj6>Qu znD6K&aqClYWxj_UYhmG_>5&AX%}b*1SW5wXWoTOU(+evc6r&9)7(_+5>u|?kH^5*^ zNJrxY?u18=&_pm?;hjY^FE1yrVSA9pBxAP_BC~>&Tj|k1^7>mYXOHN^o6y8$z-eRB z1wU%v^fKt(cBjBA)@?AsDH*BPon1aq*CH>Gm-APxXeUMXU863-MU;hSlq_iy84DP8 zvki5-R3=#RQ#BZ{%75~(@1HjXUcp(~e<;oNvgfdt6o?sOscrGR`>X?B$=!f8Bc|a4u+(uZkA3H4VUW>4 z3mE=IVmnfJBU*A%2vM{hD57EwhQ3da%^AZHgQ#FO$`>- z<=LJeYo&QkWzDu(LZy1m!eFB0<|2E#2-AI-odr--+QkYe51nnA(URIXzItBX`Ab7< znDfQrGgz5rqKr9NT~awX9bjr9x(fJQUIhz;PYPFX&yE@k_AXF)$fO4$LJ)xm*#1F4 zZ%D)iZGGWn#0af#Q}l4Fkwfavtl=;ytn2_n7m}%1C=Agwfa~)Q1JAb$?hwg<~g0xh730(H4RMKq-?cp=mo26|CMn4>2%K5->aJgdP4xf za6YgU-`@pmb3%MlIPW0%Kpr}3>y0CWYOc$Qu%VPenJ5*`>DUgt>*tzq%qHzf9(@BB ze*)TBSi6~X&x&(Emf<8LKFxdcJ;2Z_THz9q0qi!1fIBJ<1}K~!bn59PopBcazCBh1 z-pVgDY%Cm|12&<9qmy5j5$rip>!{fyvHh_0{i_t%bSCbPj-Pqu_QO2!7e*>q(Q48~ zR*4kc?b6qu9Vt_iaC+)nOp3%c6``}Nk)CtqG*{)`koD5i2C+({fk+0yoIxi?d!5~+ za+G7+KyyonO!>{-o6~b;LFUKOd0Zu?uEl5hom2|DD;+|-ODK2~!w>W6Sf#=w=OX7EhS{H<*r^&`nKT9f&kTp zX+~5IxH6xAv6|Am1YBO+P@hQ=Q*2#Z<@hU_6Ohg@+sQrM25U$t+fA0CWM|;${85oB7)pfLVx%9cN_3j>7OP{+#o+ogN~nurAj<DblX`8Jq#EL&!H$ezy0OL!Yq`!njJusVooh0i9U*Pi^O;ag$UJ)_gbxk3PdmKO~WjB|0j@5z>U)G<|{{{ zBLpWqzR6FDbi8;`r0;IXZ}(=xoPfA>wXht zLt^G9(q-)w?;C};GdvUeJEN2!&r~kUr}nWo-L{2=(+Tc$sLZ5q?Y)JJZk9?BleO)) z4*(humO}6r=rw0MJU|~Z98iB=B;W%5vhd-{Yug|s8nH-r_G(iY9rxJb1sNrpitCAmK+S$J_1Fg^BH+4nGy21J- zRhhsUs?~_%SbQ>Cy8^!i0vg#-N}vLBk4JG|jAbD)`BVO5y?!XO4P%k}OqT6@=hppMRnM6GG z_)$~oF-uV=h`F?_MZq1!I?WTwhgt7snn6O=J4jbD;jQaD2Xb8CHIcaw6KYZpW3*QJ ziGw){!0Re3fhBh`Z%kT}qh`Ukj}k9v-WfM5WwnQl`+`z#3|HMd(q1TEZWcs0ZQYR= z9ITl`O;#WxfR6zK<|4XEaYJTrDU!vCL6K#X?pS5=u!7k*Ez0C!3F(dT+Y5UV@N$>D zO?KQy2XlV9F7i}-csdrFjWCaXFxv?f0e%~z!c({H7>pbAb0k!2NYXr(-(MIpuw}qM z0xLRcPYE5=`t&AWO=m~H;YPHqbDe}3QzND=vNq9N-FxTTCL7{Z$fKZT6RF?Ll(vHx z)%3PUFTM!8s%A_R`-})Lpd1*zI0I!ht9K)7IxsqVFT&vdhduL1k*4p_ea&vAv?)ur z4pITS0o-&(Ld<}wZ(#snS+<|<<-^q7B=KdoR#}#1i^n#+eA{GrM`$as&QlBrtM_mirQsR!Qr5`Z>%WGknP=_{x$=leOYtTm-~4Z8>M%%^#8h)I4+YHksk(;y(aBue6! z-Ff$~)FD_5txD$+ihHof(}~5vp!dq z(KOBfXxM5=B$ll9HvQ~=spmdZ1hZ>~**32QS5AvweaP28kC@c=p4HR$m{2Z59dp)U z^Eg|1m8sKqsJbB3)LG7KYKApmH+zpC_7R(>zl7_T%ljBOKBFPWM9G?m zCQ7Zsi@wZ!c9hj%eBnINtFNGH&hyKX$4nPv_gn_vfdtcq3Wvk7Q)p5Igi8xA@ON6s zk!s;v3PCL4O*uQGhjwR{mN@x2NpHNwwp#{k5Alh;=|}y#VlM$nA}UPQyMES1?aW+Z zN7hy*^wYK=*qp#wSk%ZNdBV2%=$ZyAJmII1`dp&B>ZWi1V-38tw|V-gzDHmii^3$+ z(jx%5_;Bx3qb}5(&yk6E>c!{wj62BG?W9Zj&JaTJJ4oooC9k`Q)+ERz`rEmw3&`Ou zHzH)yt)cr!x0Ay^Z7hmYP{~IY0kh=>nAI=y_8>2P1^~y^<^8aJ)rGAAze}1##NKYg zx(@x3AEEm?erzH%lAp21q59E`;8nGSwW(6oZjs30iT!ZcPVaHCBm}JQ5?E9<>|504 zG8*=Lv+BB%lg!iwT%KUkMRJwTD3++X)VF=k9g_jxFQ2K(J+)W@oDupu3@M{zS6XEu5Rnj$(@f;L$t5Bj46B{52ovVQJ*S%og-u|AaOor_RX zb7rHa;4X~50BSc-h0Vi1_GCLq?Lnjb9Gc;j(6pQe>Ag;Ci30hhR4qGh-_@DF1StIc zqjA>R@0ZrD;pH1NW35f~9&eCBJd@UV%fjG{No3ENl|3LODmL;bO}we9@U@o;YjVR5 zz;qa`}q62l;UO5vH^-Q#-33*fj>2^Oy?G^~N7!1o3hTdtFD0aHri;cO^t( zTc_ED>WjYu< z4DnR7?_rab@6V@DRr|{|(!ZpipSjQldI(EB`%W~Po*w{Ktd=mc^U*Y5LT7>P+ z+uL?3NcsG(Qouhi^X+toK?d||3~Tm1f&is!`f6*U8#LhN(#P)~|&2z5g_ zS%sxyqv__%m6F;StIF9gu_Yc~uI#j!B-k3PG&}l~_K25Cs~#Mc59FUmZam;@+NMiK zv@$-sJgEYFh?aGpr#SZcioA>@mP7B|Om%>}05o$x@xZcbokuqEErXeNsd^01?7SPI zp4wBIN_!mjz|g>SSuVb4X>xo%*4ZG4k~O6rs?of!A7GGyQ) z(MP&=;Z*|PHAg2!&yp~aavPK)yS+`TkeApKe)Z4q0G%AOa@wR<=!jt>$~uL@_AapX zNa_*2%9Z(n1xHR1`lzw&)%VB2hWF@~44L|vlfHs-sJClcdDS(VdfCHP-xu-*XNomO@>nZh_w*@}uYLIog;WteW2`o+Y2!vpv^x&7mb9lp+PG^)`Bs!eC7lx+wj$sd+;qQ+ z=qGaJ*(?*IXq`-^c^>`2!|CW0t$u zS!rNm)?lJl2i`<`6GZDYD`k6&_;>CWf|D+EKXC&3cesm0lBx^RMJz;Ou)_gQ0!oL= z_cv0{LC*KYih)Nx_z?7BMiFSqLzO~iSP}Z2)r1sU6Y>A$!rsI+F5oE{@>$q9cf-z! zIi>m~ZHF2@e5YZ_Y|RoCOuGElVDeI+-AAuCY3bDS<(&4Buaiy^YTQj%udFSDqhT?6 z%DhE;bje9upks@n)OgRzYuS?>K&1m#QkPhRb>8WXXDrQj4UcaZVP$$Y>?H^knn2ckT8WT{u(Hr zQ-Jk`rt-aS=@fC9_X!aBh6M4%+0`QoIpDUxcohE8gliVYv0o<N1lz zhzvZOC4bXFT#T0-gvym%FOX@&r3pG>N^Rjz9Ph63 zQ;AEzdFf6iR9#7k*Hm+K_PlaJePEeL zu5z4?eUT{v`~p9c=5~W~$xH9*l3J+x!k?PlQr=olWSjL6>PPQ#FKXR#EvRK!R`Qn$ zMIvKtYh@aEDtNio6nH_p3W~RNRjHN|{M&GE(~CF0opq&~v>wrljL>prG~JA#73ODP zTe5Ox@wxqcOc6(^PJ>RT5q&bna8MfUbNn^zVZ0CIeBabV7$!D%`?W9eP`S)nZ>Rg)`VGCmGS<#VgWsq)S{<{qAHt;{TyrFjR5|;ko!nqj zULmAwo?kRA30kPVxwzZTILiD{)Fxu<3NY|&RQ*M2f2oTp=;pk=`IH42AgGC`Zi>d? z^qm@Ff2je|Kq+Gp89H<_&UPS*m}O)VIs=`M%=t&qZRdvT)ZHo74nmR(By?FF&LbXj zXXYcs-EpKiZOfl=;=5oVc%R%q@vwFZO>u?!PCBtqLpZI|Z%utTIc!&h9$I_PpMWhm zj6Kcaf#u$r;D|qJv(rV|nd-bLZK}|tlR2sE-zdlgpw&KC0|G6(6ku<*{s4oa$j| z?>CYF{rh86eKEgkDxw|Dn-{u;g>UKh#|a+BHEesJmS+lpuDU`Czo^->o>IH0j>$KjKo)HYgdj-A~^A96$ zpI?e!s+ZA7CnX^fI;x&WijTbtS|7xaXVjXbc)QbY>HVUOd4Br=iOk6}i(0SWf4n!U zRe#^kZeU;=tp^dhIxtu`c~BcK?h9JeYy1ILH7AhdYV4c7K$w|)J-Ac^|KXECWyfHhw`o2Z4{LthhPnclwCL0655c=@Jg-FGLj`Nf=Yy$6UzhM7 z@NDoCfHr?q$xwUbtf^6ga5}n`C@@2EaX+M5tFQ~~)13f)Q)Ae9Yf^BRfqxp$<*J};azu&S|XAH=B(oj#OERCdXbT4_=Zxt3byFP#V{9&_Q zAAT-R6NYH*#a2(^{YkR+kmi&5z{yfxrQe6w?W!p?M=~5l$HZ3ZFwciH{v-0-S*7DM z%nEiDp%aX2J=DCFi&K!o+h(Gc5-AH8KqX{o@W#)TwRk>wQ9wPC&cuuU%xIHti-GXZaz)2HN@ew6H^ZLh0J zoyWL9u%*fG>PP9MyuxZ2)^8Js=Ae4nu3(_PHBcq#kzurNl>yhRuvI@J-7pv4NmH0A z4LF6?QF@oWZ?yh!9Ve9#Uj1WuN*TYJ?3uUl*lsrFHcnOryIt9C>B$r;?If2FugQS@ zPe0xQfzQGP0<6Yb!{;q&TuKHGodVp5=@pi>%U-|vqH>0@_YN#>PBPuBFH2m)b77I3lhu0MF2`7?FRLCjS? zQe;(T4Rys(w!2?hbjK^b8*9K@*I*>Q$$5v?3J4X?`R_b2c&dD+(Du7RMbV2t^XE+c zSNg$dM^T|_Bbiw}7QG(m72r=2YV+hXhw738tv2@zsZPSxfPS%Eh`9}UwI`2v1~h_Z z7m`*^b$fjaCeyv#QmGFu9^XzU$af5(r9PcBQTzxrY5K;bs4)te?enxZDw9HcZ^Hdg zt{=WeLEjU&?ck43CC3QQQYb2#M>&wVH?P&PaO9I;=INC7N!?gsA9-8WaUlyxS$O1G zRr5YuAkxQPgh3VjA-&!UZ9@LcEGVK{~D(-E#w}zoXFJMaMO3!hp1KL-EE(> zVb?Mz;xGK^>(Zi6<$&pxB8PcP60)V@tbEeX{cr7sVgh}#{KRAKENb1`$A_prb00yM zt;F@`U=-e?!|MM2Cj2ht>z7H#(h?jZ%slJ+ava>#8x>m9G|vY0kDD7;_TEpGFZzPx z*uNK~mGbJ5jSbJ32vcH9pQbEmm#$~FxX`E!+2hOJFjI9d5Z1Ch&c0pDQ?1oBasL|= zWzPO`BT*wNp&}qDQHQ-cey&X{D$Q%RJ;O68OytL6TtStC$za;2mEG*qU{%PO!Sf{G z+2fT4`U&f7|I+)({`0$xBQ4_?$G^ULB{MVX2EGrWUsz1xaU*p><)`2B*99!}R{eSN z0fSDCf+G)$4i!SDmvrf|wTd=#;~|b8t75ycp&50+Ry{+#Rx81Vy`+QNQui!sgENg4 z(F00>TB2l5O9~}_MN8Qq$6nt;HnOouZ2|m`uKuzgg%4h-6w=R~`)cxQkjTb)9$7(+ zgk`&J>75!v*Sb6OQ`mQvn%Tj}>n{6aFd_PNuxsr@*7$iRl{%MW~Au%s2#gT3OFQu60*i`?{Du4td>IJ0vMS#&auhC>uC4cl&< z;K=u=1IIV^et!<6A=4JQiEp38U1nInb%&usB$P>^)>srQAV|E{(2S`f)#EAzqY$^r zd(vLARX2Pg(3P`}(N0~&xq;mKE_{GqcHMY!7lD-K0?aN;$4@B&Af`@?d}^eX~>z z@8S0QuNC(LV-7cdnk*Cxtw=~ARK7fbN~gy;W!NyEp+<8!kl_`wrD5pr3*&hW6)-W; z>hFhdxK=l)cgb+T>?>92c@$Ud`!YYvP;EB0jK}}3Gd+2v9u+_JGW0Qe!Fe?Q>%*3$ zdW@DVkf7Us8Co=7aDlaffwo;3J*Gy6p#T4_L-;}0 z2h$nj5gj2Vddz%I$S1V=uKAy>cXa&j{bi}2$U zOf(eNQ!!$`{5SX=D;j*~2gRvI(fv8hXh2U&$@y1wXrjUTSG>Du8XkI(8oB`Bek1b~ zZ9(R9Gz768d=(@^4@yF#vgya~#ORg8$uUv1%7D=N2Mq;w<`RpO6t;nPRhN^5 zs~#u)3kN3-rywn%=>>oE8P!))=53A}o?s|p!sLg%Tf)q%d^b(as6d3MsHhxDDlPF$ z3?^Ois8R-bjY9bvb;J*|lOM$Q6T~T@S${k)mr@&*T)J7s>K$*7e;>U#t{)v8{d(W> zgp5HO{1Fd6GEjPhQMUV;k^C3wPib)~O28X5c^L-!VA%fIEHx#;riK_Y4cu!%%}^H{ zc_bPH6%hn=fatY`I07Xg;kEEy;QwFP(uMNu;wsdj?SiJnx_n94=L*eXr8ScYq(Jj} z5fUl&2Fl5a)n(hCmdB(bw6}O$WDtez)&733;cO90k~Vo4m}TNr#GBgy`t^Y^+^NU_+^PB6l zGdPtMPS^Zn`{xJu?uiK%&Eu7Oij_{k2*>5$MR<+q(UmDMYo-z5r$apK1V1Ggi8~n^ ztL6TSCl}EyQ_dDfE>Xz|O9N&_3aWpL!_U(UW^%ibgw@jU7bNu}v}gj%&`(*-#6Oh@ zO`{1volkC~>8-iBXk zmnN8OwAK(FsHtJP#_x@{6ol7lC=?QZ!8OQER!!{ z?%z{EGc9HcPB*JjXXX5q%w})c2mKp6$(|$vg*4ixUtZ_=Ryvis9IZ~9!Co><0WVLc zLr&090_Pgzyx@%>4DfQ4;tU0eP9l{|Xi}%=X?ANCy$bo+`8iM40oww9mvrnvA_@jZ7qb4&VBw@u^}H@psaI%h-LXij#11;RL*u>u{&-k2nd_ zxrmhyAFihyzv8|;o{C9PIIRB!j;1o}g!{Fcx4U1`UF}c3-BK-cK3J@y#vlpSoGOsRG@oy> z)v=na4Js`-7Gm6xaKsL-&`G{MrupiN9-_uT&B4*5xOuxjC3O|{B>|6&%Li+v!;`4< z<-wNt<=koUmnU2M#R0Qwg*I@1RjF?F+AMLQI##^iu#^`U_QI5`kR1rqDD^^~0%nS< z!c$e5B)DAO>8$;9uTXvCemF-UDJ3cCl(=MCWH(ImxSQpe=bkms9?;|KRW)I|5xcC= z-1+V9s%&QRZR$1~WBDW< z_TFO8R~q{->YjDaRi8_6)?K~}Z7u(Fx8?UZD&4rBQ4wSGRaHxV46N)>5xqxxxcSRG zxLD>&Z-Sg74?Zg0{NA)xyBG4j6|*H0Crbn{XLgHdBmI;do9FJphjlDFELM z_4j`}7WBKcdJexoY8Rj4_ZOQxk*ys-ltWmJHxp0@F zaGrQAlZL}s*=G__B`CYxbHe6Hv^+r@P1G0of*3lmP^rgN(foj+F8bg|cyE5KQT1ub zG}&Ex^t5H&|LrxIpojRq$=TVaR31Mn4l&wHS0*b<63Lk4D3F@2z&}gamB{xD(xzg(1-{r?oJiOm!6;QhrNc7XKk;1p}i7; zHvqr~U%M|sQ+XkxQ$5&>5=+6;c^HhY^)!yFKZ_jGwru5mM=ih?w_)|m8Tp%n3kh6~R)ptPrIOQ}vO`1@ZSmaI+%jnSK9Q;%vbvpy%dzP>Stx7>8W=+o-!p3@HyDok|8vN)Pytt8@cw z+((DRrr;EjEqi|Rr66ZeU1~LDrj{le70_JfgY`@!z zX8(F)H=N1CJZYi;N~Z3FqygWi?di;P1^eD#q8G_U2sEyi=p;Sdp5kuP$H*PtXD^~O z$b5{(D*E&u+RR^-qhc}jiI<(ljRHGT#=1gP`M9q56lI%-)D$~8UwcN0Ez63oKCySK zE3g?RR#CCA{j?XYP!FI;yWg8reFXYR?PUB9-kFo!pI>;@Tmk-Xgm`B2pgMZ-Ix4lm zt9%%jqAaU&F2_|W#B*DVpb=1bl@!shl0q{$<1tU+@2l`}1<2P?r6^9eQ3USL!SvM^ zQ3OWyP<3>R7gV~huPTk7BIpaVWEs*A+|C^a*l{Zg!ibL_9qUPAU%H_+!_~kQY8|eD znPxo^>~`H6A#fd&IRo@cnS)t&(d?hpY3g7ag%aRof|O(qpey-zbzJ}x1CggqE?<4V z=5&sxw1fQ@3HstdOI-0O&|z^TR;`Pj5$H>uFRf+UBH0uhtI)VTL=Gq4DDKfLWx_?E z1qka+pIMj3V=wfwkIx$n#`S{h?DTQs7r+g^McL32R-K1r&ilcaLRr=cOW6>}$Y0c% zjnMk1+&a$nHc^4+shxAz-ZuZW_d{hPGcrZeY1$#*kL*|EI=<;d2ea6Gm>}biV z6{vGl+Rv$6MxJv2ZF-YI8^qjCA6dRCeA%ka9#kNoRSsYZ?F1A3UP0h{TR`xvPF^P0W)&uR0KE<2hop*0%tgZ+B+1Y># zUhJ`H4WwTMW-gwf^=aD#4GnH7%Qn}Fh}8>L-YiRK8M2V$8BT(hxzUdWv$Y1WM{&87 zFiCJ?~xC$J{TV9364g||wL2BPB758CWTsF@2&M% z_cDD&?DI!JP|i!v8_00Taz4@&*t*q_`7KegmH^1=`#jiHh*rnzowt?C@5WsZSwq6O zkrc0E)op`tS`Xo zu(~>ArP*eLyH@L5?3`_Mx{j0D&0KsQhq*fF!zwTe+k*7NO0?&z8#~`ETM5Tplh963 zOT`*nj&WI&lVD$(k&KUBXZqTL4x4RaTPM9ZfqN@BZCT}a84Cw(@OvacBWwFqlGuoh zK?4h%vWXw%XnGp9ZPOgA?|+Rj2iIY@@YK+Jgj^FcDK#AvgeSPi zbkZDuXCRu5nq#_iLf@Moc0R&OU@~9MZ>^%uAjgHxm znHO)~L(G9g%t|_q24x$ZElmSbwtYGAiD}mSmWH6}kv)~jluPC%@{x@!b!h9!hip09nXIDXBrkpek&B6AEtB{|=4y-3Uv$xD#ZrGaX+Zqmf z7)7=&QQMHoq+uKbIIB{S~=!PSd`S3^%KF425Mclc){E-c6&Jw zvgb~^*FSO^uqakMjw9-6(s0+h<;Kk^+nZlEmOT;LTzq`y$Yz`E7}n-fvxDVjv-&5d z#Ul%YWmj^EA*5l#t`4um89j;OAh?pmJW2NiG#Vvbq^!D0@jz+?X3?qpc4ZzLF&zg9 zM)XuEYrG19>hEFTqzwv^Kvo7x(i~x(ro-6Y00U>7vBm!PdKUx)Rt+BDYcf5E_0 zlc3XWZ3}Beb=-m99Gw`@$NT_-h7^P8ky^Gn_Y53=^W1ArZ13?J?1B$dR~Xj4R5oQ? z7a5G|glF5T@SMeFF`Ro`Qo+!S$FjM@vRIck3YPU|VG)tfuakxqL3@*od}FUzT8W%> z>0`x++JhNzeMN<=?(cNAudCSg7mIt}_{W}BhHL+0R5I@n~%0y{{{K<;W*6HFSH%+v>gb&3Tk z_S@rYM)48FR^~D!-18vMiO+C&*2-*Iv?tMGx)jf2Z}MWiL~WlpUcN(ZU7F4{^viWz z!Xiom*P|lOzN$-17$3`YtQyJ4(iv6akV}WzA*ijlhOTF2x)kk`l5Vz(=_r(7MACxbr08A#f=+ySO%^oLVU zOPK6mAu(YRMA(F@QN6yv!dn?6;Q4C=cBq^45@;v*He@@Lh5@W?@x;^k)EyWkT>^i{(k zDCNn6xqN$xG)H6FkiAB_&~UAcRe#1lo#xJdkt;vQ@m=B*3?bGQyWo>l7mV| zE+?7qb~<*L0y`f#yyEY$SEizT-3}70?*#W5*2uuQ? zvDIJgnCHE>-=tgJZ_%phy(Se;g4_y`RukCtQdn~6GkN_b@|8)8vSHQS@~J|X3xn?g zV!qqsnNpvK+CmG)2NgAGPnbI|^e0&#K{2Y%*&vpkH&$cE+^isa(wWR*5;^Qs%+hlK)7xTIRI$&zU75c4*)n%22`^CBG16%o7q+%flHg+;xe+@U6-c`}D0v^9&IICTs(x$q$s^dieDJcsj7%))Z%%hn zB)>EHcFbM9Rmi?ww{|>jJttUr%%@i%KAQfN@?F?Dng!3Hhq2CAZ8CN2deo0xZ11Ca zn{65IG!t`O9c^k-pOmfHuJzc&nyzl=AcPROkO5^d-_2{@!hGLGHzaRc-6CP5HQ*w! zKtlXFLOb&`E5!NoeOv87A@`ks3x_Me;$+yC7cl9#XyDWr^0p>0$@FD#yIFN2Yl*f} z4=(a9=Yn5Yc=9&nD@*`p9rBP=)vScO7DK_-jrB`OY_lsX;W_$M*-)42BpV_R2M#uU zY`L~HQ^hAtWCR~-uoQ=MU2WUyvVYZ7DQhNw>*P0o zkM(&C@X6|z-R@?XsHmgxyW(V6a5sMPq5wTjo_@OhC&{mZ>bPLWCeC&G3fz|8U*Hi& z@t?~12*!A#HS;V$X5(1)dxOm^IaNeWI%)nKxK-KJF~Z!SqHLHP3Iml{f>H}gcbn&7 zI%Q9me4cv+*HtJM^)_+HGzWFYs+}7I&GrMu4o<7nYO#If=VGfeQW&}IB@0;(RM^l! z#Fb0)!<^baqol<0o8qSz!Aym#2`592jwnUDepYCAes@xnje0wkSttze$;Y5&-)BE2 z(3ICdfsoz=K8d9J3ARpoZDErK8QN8Knoc4!PUG#3V8*7Ew<-|E0e$7T`?x`=Dge>i z-ILEXc@cv|XKEMPU55bjvwmm(U}oC;ViwQx_(_J$*)HBGc~^~+CeqU_dsZ%B6+0Gh zm9CromJH#$`(ySgj|?4ncOEJdq%pGP$-vO~iRo+|kp_TFqr(?_lm%^Ewf<%z0tT}0 ztp!wW1$~4c(Y@HIw6P$J9;BeS6y46_*CCLe#Qf>Opw94WTW1M?>!&^{+a_JHQ8jDp z(q_1sJKNy9I|>__R+Mv?U0TDT%P++h&yjIa%dwqxv@{^`)ROXj&BM)nKq1fDq!aFRYB za?M{NW@YTyPQ-`7b4iY{$U(|rV1rgvug>Pc1axyr0j2d_G;uy6Wn@0~m^Z4NCG?ZU zVhA$it zY!F94>RoXGl_t5Oc_?>TN`2bp;@I)`U>9U~PvQGd-JVFR!z9v_JYnU&rMS11AuO#2 z(?Q?fKcFGgGES^_)t*gqIwwaB3;GYRKxrP@{1tho`zI)vt10q&uZx*0qou^+iI}w2 zW865$o?W=rc%XgTT)Tw)(qi&4>U2utVvKE-8{)?)g(X>9Hw;wRbTp58*}VkcQsDYBf5y~= zqn<+UYr?+7=F8%^_fxa$2eg0enzl(o3bD2~2-(s}30!B#eTUJ_%7-vaq^)|x_ zXE$?wQ8A~u$L+hG24L#{hH1#f*bSP0G$2uvH$y2=?HsDSbUR&Ol7H7xXb&~!tC@bB z{r$(jXT@mWPpo2A&4ac_5q|f+avBW7*uShl@1E=LjigUZSGqo4w2IBUjBPKB{x<5% zvOR*b-wV z-b40T2n;5;!3`gGtw~EVP;dLb+X`3K=*D$`8Sg~XHL`@pSxM934rf)K@?vmqP*B0FHr9~n zkqjX^#?MpZro*NlqEd>jTSJPdr`=eFZ{PS6Njy+U+IV=oKVZI=te_TG z#9a5qcl5aU8fSgn(1P1v;cnL|duX#}pG-4J>`ixL@fW>c1`l43{l_djH(i;3=rv(4@)eRMUIY86hj#oV` z{IXfHH%=qOtXY$#H_&ARStKvs2+qiTFN>f>Zt#}c#z>>E;`VcT-^bdQFiv2iVMW6t z<}~8Q_cx!OoZh|cIYz5b>yfoL*Q3Yj*IPvKIp7#v0A57GRCY% z!*PnNNA=h7pp@U+sHsuVa4}~C?|PrU7>Vn=y~0mUv?N9x35*(V&TgDSEP9YXczRGQ z`tM+XJH3sL<@CTp*rP;&S+}Mp1255M?1q1qk%@*;QlprIB1@frhwyWp{%xqyXU$Pm zA&TF^Gd|D8%x5k9Vpyumf=k-vggH;4VR}Cq8_s`1ibU2bS5Zjez?C{vqE<+nkc@zR#j4%dLg!9_`VK zR7e!uYGar$)`3j+=YEoMOVA3K-Z-G?yZ@$4GbH=Y!;j=ViXL$DLZ)mOA7tdzzU?y? z*lFa**O(d>Bav&GM_TKp$+hUekI}z9fg0;yh2ncQhEu0H$DD}jPw|KJWzCzMDfKsY z$9aQ2pT&=T-31idTt9hOVm3f7rNCj8D5{o;X=nuTp!dSwau&W{R2_e_d)M4}ky*=l zWS8r#E4`xomN~B5GJq*)AiTri(^Jyan9ykcPaYOd4g5{aSVhSk4`}YD5M(Y)n0W87 zpUx?BsHw~%1-O{m|4gE{|-++nvi$z&y#S3&PWiBWS-kV%bpn7S2qf@un)8Nln8 znYV#2wee2A>W90gaM}&CC*fh!(`7oe-3?RTr*uR~1)1>xfR+C-zaJvT)*jFM(=lDY zmGZvCg@U;~>43}Qaf00Sow3@3N%K4RJ_XHgpz+f*`#G0G*u{h71p50;IM()zA6t{b z&s$?ZCw==#rug)q8Dl1#RX4$Jd~URpCYZ;(o+9;1I_0cua}cSHA)+57gdrkBsa1-1VM3E8JD^Tl zx;}fzd=qzdT%Ogli=fwmUkK)hI#|A7ArR%~=Lxd!J09L=;0IW)8MWVY z?+hNFWdTPQzX&1Z)P0^2>i+easV69dEKg5>c0YA&P**{v?=6(Nkx$j{`QbUeA43rp zerVV2Z3O(p&P_;*U}{9JXh8RI#5p@bs>v_VME8PirOr&9NO{Hb2C&uTrTGw?ErPUx z5Y?xznp;TWUT#X!iDHwu%`U3E@4%4!KAxk{8}-1o79X96I? zg-dbcnb!qvxoS53Y8rA(%pti+DhrPJm z$>BsQ;>9s==y-QB=SCdMKQ~2Q;=BA2QixFQCiYOdIphyKC`+Zlm!0Vv^V7VdCf9FptepCMwYzQEu(f#D)CLKD~cs zx72DGcodn4+&bQ@rU3HIl{|0IMyk>5L*gov6rWqmJ}!%<#XGk;0^kC&t#bG5i~9yd ztTAM8$?EwSqpO_hD%A^(+gh9~*D@^ITVKH&16VRRZ^)=mqcCq?9?w3q_Q%?ZS&)#m z;NM{uG-8f$%0pcLX5BkNG{Vzq4(seTgo9tNW>e9}e^V)#{+_KOKcqz=!}n~fp~zUZ z1Vi=m7+NkYN2Jzb_JIiG^RN^0*0U$#Q)q$j_^!r5RYePV;2HwUy9C7i+Hp0KKbTbp z27f(2+F**d9*=}9#@JvMYO&Leit`%%IxZ2|91vh9gh;RI-67`5W;gJ8 z82>6h@ie}&hjSm}e!=uO1#K87z|J3Tt+a>9L_|NXaTORu=;sj)3B#HC-6`tB9Go_B zfm|Og^$o!a{b9SAA8!w*BWxr7GJco62+_+_sMS`+`A0^;U~cL!vQ^d3YK*QwL;1ZA z2qWyQ-X*&qF-)mn%-*Bf!`CB>9a^4grm={CpeLIZ4Bls2VY7uk1Q^{nwaiW|2jtuh zVB@db%{L9{<=seE@YBrb6kCzN^NDExzMVp}CnwiDm;40=hN<#r>dtu=EL^iemU(Td zpDkQZ7_;3A)cZW>ncW{!F}hiOp}lGt@7fWv$u5Z8NSg>zw*RtsL`f|00n;4+7s#iB_e%b&fBD6% zriy0EJk&IEbvCL`>`|SiZX-P2|8jN^)uC6N6^*aXE3kBoTB!G(3~R8n!`z(y5ca27 zSxvX&F*r`pmDO4}#PTTcWK5}y?C!na1{A>+!9MY?z~4WW+($$9T_Uy#XgP`$c~Rd_ zs7T<+u4fE&IjRYy*S-OqKRv1}m)j;jLD*C|{Ni4kCH7rc@O|{!ZQjPz4E>f}ACLFq zceRrJ+SrL_GMLoobkc056k9Y+^L#q2Wfiup;e%T5m)bj7rslkfX{T3##upMtJ>M&K zCx}bta0Fg6B#p9L^h8g~B{*dGtI~`qBZkM8X4rYFN{ikb*<^g|+{D7hi4!F16(#gs z<>T}eF>V?}$2ZCV$$De7{T}X8%nmE!Oq^1mkd0W#kpkRwhb6bMj9uWP6Jc%j2eycc zg6^r_=-5rq_x5Tw{_`y7=M?dOcSB-NsA7JzNQSl=gu-g~*N?u*R#J}qzD@jC^S<*K z!cH7GBHoDpYi~?)Cn^!{Uv~3P8_+vRWFvjE3igh|pxdBcGB9MpaXlhC&3W;V8pttz z0}upOJ6k-lGgnc*bncVRlgTb3vX%KPg)f4FMsy_72p>(7w)$@Mbyye-6TMC9IR)C* z43$d)v-!ydpA?jA!ch56Ju7H%T&H{Ytc%?TaTAkd$&Bi`YM$gD0l|y6e8m48jFnv2RyD z>7YAUwDQjlJSJt?AFooFB=QS9f9=bhhzbUv%`Gc^ffirEvtLpbiIAwY$mKV&XzWsn zr^R&$$l(40(#ne{kCE1vAVubMh)#;=C|9=oVe z0WWCH$X?3TXt|zFAz={ZWq-&QSB0hJhx!^&iCRd372u?RR2V-=D@OSEgUV?0^v*|F zwrKl~lkGWOf$EHgI)PkN(i$K-!X+E`iRuiADuMhIpeS>Pq>L{>6WN^{1IGxod_*Jb zYP+K;U>tf~#vz>cANq+E2ZbslsMjaH!V?O-jJ2L}><=1sCP{jD#y_t_^e!Z@hpN$^ zfJ^H~LKM4Jtpe36sWrTzEP~dH_V%~x4%b~4{O*+cAQ<1f{9 zDHL!qqxd@tvdQ)j7Z{+|gaGaD4ZS zaT7U7#a(0!?VTg(^8%fCn@ziB>s>jCUrWaABpAPJvgP{0{&8In&WCN%mlf|(Ot$vj zcjHp}RU62Fb)x&^V>3h??8z@;+aH6ve=cB#W`7t6%BJ_Dx@Vh14;37UnfTEe+fe=Y z-a&V1y_*#dF{=oA?@#3y%ErxvUc)_?4+5A%0qv2Fv%G=niD3Yda*0nf5es?#9Q&)~ z2Iapb0Hd;9sseLYacJwu>gd^3Np7UuY(5K(?@<2$PG?sN65yl5xz!aIJ?x82=W#oi zC{u~_+E?;Gf7{Cnl_~0@h>e6`+LTzX6-ihh2sdx?T}#`r5Wkb+ThNbPx3HeiF`GR9 z-u)q6I|aQvNM%EN>^y-ZU=RE*s`fEnFGR|CDc8q_XKY4?o!nC@rgQjT^n+!H0Wpqi zUm@=qUU;5gwLhg@=902j_;CeFceC|`L{;t+d$l9*ZaW}W%m1&l#Kf(7%yA`!3VS%~ zC&;CJEm{sQg~=wV=p>eyFz7EovYD~FUz?!p<3*p9AvlZXxw z1CaG!iQGteZ4zio>1h-D7fTIa_A8e%Kl!ru@K(%OON_yp+!QhV#(vpiPCczFcAC_0 zYZQHPN#^2SPWchF-ebM01u=!d=iXb(9@FS#U8IWW;O!F6U0f9JAw4 z&-p#sXiY&WpM*UqagqZ>tcVf(c<~zwF8i_1PQa^YLbBjP2TmLmZf3-_pKW7UyH%nn zh+WG?FaI7@Y2049%5g1XZ}0{4Go3>yI~M)YDxT2Z^TDWMoOy@&d0wQo`Gc}<;9)D4vK!~{rE2E z&ol=x_j*$64WT!YKgBe$OD>f{|GVA2ZTJZseeA25`#wSKKyo0YI})?y1Ey%BUx|a7 z6w?yont`K=1FPmVX1Ew8favtwWa!-JPFVU-73#kuy3BS;o~{gVQ0*W z8xF2rr5~@_>V0}dumrUjYA8sBJTVP3>))+e5g~c2wYl4=B8;1CoZ1pQT($h;33_Vt zrNrq=&6fSWzghJS_s}tiHzbRgGCMf=ZD;dfBY{}-hr5g`J1tpXR-<(6prDy%aT?J; zzvUD}Pw;ifQhG#uz!KuWuXz~Yb|*|aR$SDjwdXJsIyPg{zv40XzbLjJJhB=UhMVY1 zkR9N=q?N2rr)*X0zL0;9@0q5r5jM5G%P{nWm!h5RmPx?pL#77RUUOIWBvhxsBAe6~ zK~@8Q-y(ZB#yXb?5XkaHh_dpS@YYJPw+s0RJ)Mb-P4C|9wsW&y9h!c7@8 z9*lRDb6b~F$Y~jx=RVO(2jOEKKk2pKH?kwG*{R7Q{p90oxb@9icK%}wa4{!wE%966 z2R>UxbjIYGW?m;TS6I|d+6a`o%Ag%w3K>LuBmFm1NCK9U6}0u_i-ONS#;=E76o?-a z0Mj=0iM#mG%zD)uYs)%p#Dz3fjm)sjTpvom66`N4FJlmm<&ZU#&Q_ARer260Uq#f} zj4qXzcX<3cU_AWPu@OCQ0^_btd(Ge!3)9~_YukLi;oEAHZL)woN(F#AsP32r>k%5* znjNB_o<39^L000}Z5yj7PJ0CuP!E-EL?&2tDl7pW)CsJHhArZT2p&t>IvwPz2CeG1 z65ko4O+Eb6r`V-8AN=HrN4$YeMlkj_sprGUhpDy#F{Zv`S z3OP5i{k-?i>l#uO-10IJD%!N6;tLyKR&enzT!co=De6Q7;7ifgBxuF&5^6kPyvbUD zX8GcWKe!9Q$m0^~nFf|@(u4I2_mh&XIvdMBBeAY~!8uN>cP}d$Dt&u@Bdg+ctB%y3(SRUniOuBDodMRSL`vuS$GJl?*IA+W< zcs5@$E4=;%Ao%!ekTnmjB`-K5DrjCntM7R0kuZSdKHVeCh(Iynq-Mnxo({W|n%u>~ zaXkKW2j;J-=_P?CAj`53;jU?|YbM-tR$4e}DV!xX27u-@-r=m7*^IyWG)HgT*CU_m zzCLf>GbCi)>aga=fR6@9g3guJH=R}sgCxDDf}T-8nvTXoDp*zq6x)Ey`Ar&73V7E< zY!}BKE8vtLS84L+Vho|C`rnkWY{|s6fw@iwrX*XA7VIyfwxW$F0e(hx?DcubGx;U6gatq1GqvA|6|e_Lay%xeopW%=Go&3zUa9sGRe=BG^`$+XrN{d z4KP#v`W3d~53aJB!4e*%E^~1&Awi03X1<&A#~hH zZ~rvJj*@zNMZ`NqrYT!k1z0fY$$ZP+6hL-&fqSgydF4Cv)Rc-vn3wDusv!2K*=K9# zCoD+1*)4M{F_yy|&9sS^d7f=0;4~Vpji#rQGF%hv+Rr4?U$u6*{X*bhKkj97GtobP zhVUTeAT!i!-e}mV=dW87*^GEIa5xn%`nl^ZCj3hrwESaoYiq22v2KjnQ=gIC#s?-u zMI)eEN=Swq4ajM|MiArp5UIB%JN_8<$;`GwjClZ#Y+rrU<=~!Zh?+`Cg z^;)Cz`PlNc$vcPJwahx~gAET@G!kmx$f+%}wfDE`QP?$af^BfzvODEe*oeFi#CLq>`)x`j%hFA%`eY`r4l{+1K3uy-0aoW z8F~{t%KNCn)Iw*I1E`+`yeGSq7VO3JfQuP>j9P{r*WyiZWd?YjQfj(GWkA+(igw+1 zO9AzCTFxmMDvFmJUE#eq*2lROtn)1ed&}ej($j$Gb;nL#}xbg zeFLqV_`g+rBxodO)an7g)#FJK)C6ML%C!U*q@VVbuH#|^7;^}R=?7WdbM41RN5;NQ zn-kHL_x@Nzh=!u@R7bx6_~V!Icdvlq3e%IkyP8@}73JCa%VyX%l`3hKwEEG5bOf!O zb2Kz8YWsbo4UwZDa*vG0zfr_kp=fi!z27C{W=mwNug+Bu#d8Fs!c`QiiHiOYrBd+z zY*JMMs`ELw#s}tRjuL3=@o8Mn2eSNx(+cbB-dcc|nOr34!*XABH^&t-uarPa`ziqo z*_8QUhcb3_-y1jRWOI%>$CCl>rb$?VCS09S_=~;2eK8(6?{Q4pGTBrF z|J~uSxGdu5_5O)z!=*@VKWt$zX1`1<&RZ!ag6{j`*3bI{KPH?jo~g(Sr8j!h6C;Di zaI@XcJ`XWdZ6=0f~aZ+5pk4KnSP(5>FeFg5Q^)*m(hI4+t@!P+@ zr<16+(m?tx&1}7%KG7$t8F!Vd0SckFoY>Yr*!TS5^ouY$g2;0-{_|c~m(#4uSCfHO z?KhFhl<+Nh&n5N*+>G~TD%I5FSRHh!@1SlXAS6&!LW6jMPPuy1euk ztG+E;XmCp&|EQqP=A8i!qKGr=Ww_p!r$KG4V{86*Oh>#{FRp~S=%h_>%J04iCkzZE~rrZ!`h=oW5hUhlN6jOH!g6J!g?FFV=)?}X$+Vih0Nwz+z6tbB56H%`(@ia3Bj~~~tX4ZRk zzWDEBHBM#^N>!^}-UuE|hzhqpy89v7$p$c1g$+%thATv;CYvwWxH92C%dadvUUha$ z6{7E-Y;xP`weqKcA~0vi{~muJ-@6OANi-1G#6da+`rFnN``G@ z1Pv!Y?5$`GIvghPw(ThL^j{FdK6-q^K94+4;Hfz8pmVK2>XLM7auaiH zni>6qy*rW*?$5}WR#e0-7D$}i3=f?u{@M>&UwEHC?FF(>scxe%plfr>l#dVv@4LTw z{<7!er|i}?mDI4uHP!OzAQMORWHaRQ&9i(88ue27nv1as|CU0Uitt7LTW?n?+kW!F z*?6J4bKbwdeOtlm?HQ~^ZCw7}Qk>3ZyJD-nt!u=t1S~HbMbeL__{-^|NmnPV*Ioo+ z%SoOKzjAwgo#0z7`42v$*euwFpkT4$z}DJi@#RxeZ6P5c;pbU+X!!aHuXIr$I-Jb^ z!l!caj!ux1op{#~XUzO3%Z|efWX=p=`-)pyb#rrYG}5$?y>pOC*`%b1uKqsXH5`w| z&_XmcQ3PD-ryi;8_H_g>xa+^TomZp6xaPAI(@AHbI#I$fwRiNJBNICi-<>Yx{mkOA z$og(|g3P`h#im&S+L-ZhJq-BH+^BL{I(BH?vg0RtM8;M2_L${>Fczjkgn{qKI`u~s z&HBjKa)ned@*H7tlZ2%Z--@TO{D&>2V5vdKAkR2IaUl(iMmMZwyNv;Y=xElkFZmH! zSju#m7zXL=W=?79unXXcUVn$hhA;!v>uq5?EB^Dt#`OOpv)lXjFD!szE2JG?UaIU2 zuh#or)EA4!9x84Rjyc+r4%Oq`zlfd(ALZTUX!4}HwcZSWt4V1a)lHtk)#(oDG(}A` zllB~;a=Y=2g_`aF#Q|&^c#Q$C60Q^t7G`oFvos` zhOCeX{h!VFMYLVEU=AxK##icAdi5Pf-LzuVe;8k>`v)LFwAT$qxBfN3YCr)a>ixnK zi=Xic!JN(p`s5lWSf#VoE^tI#!j6&f1JQfDvaSVQKk@Hm=_19|(Y(wU#&`)ZBUQn> z-qMW%93-DV$EesdtBF@w5Zlawh0j}#vQkDYON+f9On>Bu|0BS#{=j25#f~WFx4}C; z{NMb`1V?@T|L`x+9IjkAm=6&0`-?WoQ~}8>x1SdoJF(`eHdK9;HnYC~SeL0WCXM$~ z7=eI8*<-PVEKh$M@64_VPB$WtPY8rSkf?s=Pl@7@IYWTrS!P9yIpCB>JR{U~0(X3X zQhqzkK)l|;6gqK$b}8nNKR<)~in8-uGH-VN$vig%QIp8|v9o)s2W6iY5ihp|)7BEz zVY4Q2eVS@&V}Bys88oTlaQjD55#!M2n$a6q8j2QC`C^}z255iF7gUy zLJn-M3z8f5F^a8DpOLOIJFy>rFXC+P9*4vmRWS*^3%zt5+Jvy*>(EO6=-kU`*CYt= z5e1Ci7$TjH|EXHT_?=GRU`F#x!pK46@QUbCn<69#7A5%iFBU*bHpPDsl3l}9v(3&r zPDH${CpIzmWDo03yaxU@excM(*S^nf#{5nfu=)50(fzgK{qQjfu2_St7b zi*Cp4!nMW{P)(#U)g%_9!1eN2v;lC~>keT|7MQ;S9P(o_<#!VIUK~|D#yHoze06cn zOEP4C(!-+gkqafS(xzi;7j_W2vz$Nx+K3>Asf}FQ=n>vL-%(69Tg;B zvpfywA%=KJb}gyqC*Xa(@6-FWF;{<|r=N`HgwiMJZ^X&x_TmN%HEK(*^dFUd7vCLL zYE>E(O)0}%Xe^D%Zg-H0m@X~%e@G_NemGUn?M70EvNXqlki`*x`3?eJ@jpPB3;-D? zXOT`V9!j}*mlUsVjg^FbIJ1c62Zzlq4YbU zDxZ!v#kFY0MF_jGhicBc3{orkhk@LHN8>!SIoV>&Om0~CyO&GE|Lv$_FJvw^e}K~$ z(`*~%`d%uOAG}n7(kO*|eVw^YhXY69UW4GbY^}eUf8WrT7Ls1Il)>CSMRm;KOl{Eq z9+RfUZ`G9j%ZuTe%wer%Mg18slY}A{N3-MU`FoSTbB6txLmG=lTJ7HfaYY`bsJQ38>lm}740Bg(vw z>DiwL0*rF~h-51@m&}Og??R0nPaWIBEE0#Nutf~MO1rGz;KD4AZ^v*LI`%2A9QAS1 z@&fFhCLZlUk}J=_UhJNTMB_w5j&IJ3JU4>xv`a8X)I%S+qP}nwr$&X$F^^!_q+GlzjMw#eCGVD5_z)Km#L&y4@D^@ zRMg`e4JjiRMS?`uHB!wFyb8X2ZSwS%oMH_`3iKpu2tXAq_f?&)wt8}ba;tr<5=DH& zm26+oRBOXFKb*Z|tA^XUP^!q-n7%V#qwCk1dct{U1%{=x_U=?cFo@Ly#Ose9^D;)7 z#Bd#0YhMgre|+1_VdExR28e41186&3BfEV0P9AWinFV;zG0z*f95C2Q+sA- zpZN)whXe>&p*jD*H2$J$O}1u$Y+#AM_Sqqd)$KJ0+T9D!HY`Ozp#u37g(pD0(^gye z)U*Ubkix1MkV@R|vF_O7=s!P^SZOxhd~v`E_MtmuCx)cPMD^Y=B8<^lBxa>x2~nk> zUSo9kRXE#`D8y|tvT#RqKGOlSGf~0}$RZDX^@t1EK5#jllL%}N3vmX^CbsM%hYksK zNWX;k_jFLV624*H$lJR6cM7x)aE5<{HT+CB0tZ!r(7+tslR$r;h{p?*<&RL5*+Ujr z?Wd-Moi97jXD?GXHxfBGp^O4u;&n0i?oaHeeEY%>5i{uQEUwtZv(p!>-IR5V1XLUT zTn_1|?>#bHXa2~$Dm;7PXu@6P{2#`upc10*OR*-NCQ^$>z8c*W_W)CW9trg`K*et~O^^KLM)MiSkeMil0U}US~`$ zKR~r&X-yjL1SxEYu>emz;>p&4wNG2>nrg5f!#{PXpV>Nm!;Tmbe=v#wyx<;jzR{SD zCD2N@YGC}*x^(XTh}&#~2Ze_x#>LG7W2j99od2->{)T2{`%tw!uusW*&R~vlWDlY` zyrCCb{irtftB<@~G|Fiu)-+*IKL&6@#_!BHggi$60~6TqcVa{@U{U#qrtOTY#8qp~ zDqMyD&Hm$84Osdaftwjp_75oK1b`&UkOYA$9FX+kzB*(LPS*RBVSrOVF#eI0mT82Z z02S5c4^N}ycgSe-KU-;DaVX2aP8~TO;CSUd`H?i`V4^Ocnv3uAYRMQ1%Ky$`Zs84} zCS*fClZ&3u0HZd*#!K?0yWg_%IqR)mt!P$5|yJmM^T6T=9Be}sFbO$zqWBf>Im zzQXAqMf_V^VkI;=KX;|GBFe7Ai;%!uA%oEEkn2~_E$rR)B41`zF7fQ4O{)X8`KGSx ztQ3=$jJx`o1W|n^L2;bCiBD>JLVrtj{9XC(em{bIkqAEnrT)KZ$~JD}!2bx8hA+?! zp;rF2?3jO+Jv=GwAo{s%!%xAS4wP_82oTJRLIA8c9?CCgVfb+R8$2vsWurJhck5)3dfj^#jo4349tvhZ)O&~2sR3}}j1ZWUYZFiHmQNJM z!IeXpd~T)?f8y-wS08ImnKFQTOCE#`(eZ71JpSmE5pqAHJ3iHuz@z-y@$Lpeyj}@JX zX(vSb*WE^9WqzR7A(ieeK45fDRI+imws;?N&_N97ZarKoV`;w+O3b;mWH~>J-q3ya>FzvbKFp#Z zktYL0%(wCl#ky_pzG~0TIdHo%%ft=tayfWYf1mdUW&tE9>NYMNi8^Kn5NHB1a#qo4 z#x5PuDBH7t2q@Bezo&riqmrjX7Lee{F}9qE=8!R z9DRl@k7mnm_{2Zv9#rI3%Q(&OTeU+8y@hT949R9 zJhVE>tT23Cp`UG1RSG+k8lTaiUM&)zKP8uI-w3(;R6O?+q zo87iw@SwBVHcDxo2+i2+)hDUcD|TrdrT`LmdHS{f&TaKX98jENDA$%Mft-u%nzvoN zg}Jmy6t&Va5HeDr>O7nW#3b1Q@n7l##RluV5SXn=-Z=AUeKcW-Iy6-Mrt-=`^R=kKA?-=s-RD~csNkMAt zUPyA)DUDi7cC&HPgoXw>>eCdM`Kmiyyt;^3*P{j+s@tx&u#?|NFa@8`$T0Rt+yeE6 z8@kP|!{a!LzFEwQYlk`GfM^X^=1|kX*)b@ubhX-Xldb;znA3_R6t6z~+<_xJP=$~% z?cpZp(nw$q!Y-(?{=da-br^A*8Z?;-MK7TLC8XimWgZ5~2B?31#tM}z6~w5E|8MHn zyOich??1W1q2{e$M)^q3D%U%)uz`;FE+ANd$docnd(FdL#O%O7FJE#%33#*NR-1g% z)OB(`b=j73DVG5+`ppgtZ~=xW4Yae9$|2c8=X9&3tW)w*#qf)jAjy!SX z>$>%-8nY&2oILVz`v)q!xYZ2ytY9SI_2GN@?q{RxxcxC*TU~VgMq(iq&@)cD-R6)ni0!{;{-=Y6a{g${NueMM;I;|!SLtAdR7qJ#hW{(NRLtarbP;26{ z?FnR#V;G&ad)H@VSV(vB^4QyRI$uSsIAGuFo=**2K1AG9fs{kG&nlb$q*nU|1TndC zk%}elGR<=cDCbk~eJnj@KJzEZaODdw{qHz~TKN{A>6QqIND}Ef_S>>S6wpcy_!OgD36yfi{V44Ae>?jAy$y zNeYlhwuI3-k^M$mPFsp^=^xvb1eb8To3hU)B1sZ8(V@KCFY-d!?&;i*4pv{2t_;!K zTJQbF8*1oo;Iqy)aR5&h@0@W#>`@xfH{A%NZJs(bV`A$~gus`}CspnQRht;+ z421D{RoLHN@_y+_|6X+>j1 zs(*C&q=1o3HDu=m=spx!qlZ)*(S~Q#q9#O|f;;BmblbaK6dxR<@}~-<;%gD9h`e;T zVc%!l`~(#;hR#Uih>Se~FzhW9l~h0`-GkFQ0qMD2lzHD1VqK1VEeQ23=AIa)yATFW-DwAbV}RBxL5Zf{2(2%*bOGhwtMiHscn|_J5RmAcoN>B^|n5+9|ez7pXYly`YBof^+9%CQz0ANyS#g&^;h1*9H zuSumPvs_iOn81`q3Wd5UVx%jrCbwAX|02bmr%l>Xa>KpXM`=%?5E3!UwhOV)c13R{~k)clc^5Cp7|EoYR3-o;)j(h({ zpc9iWC1=(yf0=?TY$Q@6WpU{kfBP9awanQwCd2neF&KHFL0ab(HRb&=I54 zuo8aMG}zFS89zJymDSBS?5hB`uYl31PKlT#8-?}Ftv8!JQCQ;w5c5+IzM)v0uCpfRzk9n*_i3^gP0}u2O;3@$imATo%n(CN zbfERF*yJ!STW18ScZ|y8Gt142`t##Hbl&eyuB|@RJH+FrQ;{5|-~=R31!m`G$;^xX z@sH0gcC7%sN#|@3jD^EDpzj0& zF{ND{2$@MJZ3UB*F{HdSxVTF$quYxh7IK(;CS%|$U3O1ZJwLuCqxI`Cxeu z$bSC7CnG*8M6}For}cEV!0Pa2Vyeuj)Iy-u5= zJDHCtohJ;=Qs6eLdA@URN>|mzhc6DJV9@GU8l*!uBYg|BP7QO7Uxp}9`N_}?=}i`3 zdUBXwwG+Ff!`LKs?Ar9o3Xq@*@hd!5Y5GX|HM96jxACABG!T)m1;UUD8lgxo<)G8{ zTxoQaCF>B_X{RQWRYUf-f}_ojj@Dl38O>x2h0Nw+JSJSLB1AKKbD1va z;dD({ka8Mmy!2+V>Q=UklB>?*?-~!ppfRM^U|$aen!62<2*#Nj7cQ zI`*dYDr(L5G3xMgI-z}!r+vQ;>({1%_N|}gyQJQ@;oe8}_=BQSu&g!fE&OkyJE1)U3#HO)%h9c>;pp#uO5#TF+hD&BNu7GutXp! zt)pVo6FlI226weXaBh%IgUPm=SjoAFw;& z88IV$CKy73=F^by@{c^RedG^Q%@kR_1UOVu#Hq-wt*8y+UX%-#AuAmAJfN4jqi+@x zy*9kZU8={x+Rw&0T=sJbomJ$KWw%*@gZzc!|1^VS(@eRx)7E@%qU0#ou)>_`SuMP8 zcs4~+tB0Ea;t_A3YE&|)G-B|!%INA>f(jI zEUMVi?t8Viz}2)A{IC&0*=D0}=>1niNOggDg#o%pT?G&!^w+Ksn3FP;Ybce?Sfgy$-EIFyRGd&d6n{r<9FMcnS=(jpzwUxr;~m|529tJD zsCgBoW(9j&P4Lw}#KHKu@t8*lX|jPW*lB3CFVDFYi8QG?*80gFrszgmCztdgqni{I zXcdSb@61OK(S`zzGx02i<(6l0kgIS^Ww71z0P z-BPflP!lvg15AC$15Fcg4G)Vs`rG6oCs44763PU7czve>R4b;d-?PA+EcIcdzGPu2~{zv46mG()nVgdc;V!zP~!z zwN1~|Z;rkjOuk~AMTxF_zzQ~Xe*v4u_MiEV^{&5!-5t{;N9y9Te^tkkhb`>V1uL5h zJIxF*K}3T4>I0kFwaGTwn>Jnu^)=_o{>xEJs<8JQ)^#IuR+9}e%Dv|*P|sx?Ywc{3 zCZG08ox@bUwMadAA{_BsZ4|VlZifga?@a7x;4f(bdM`v?+7u8&8+KQF^3;f8#l|X$TiVkPQzUjLkN*iBK4xU{1rr4`ImjZ zGw4rwW9F{1E}sOJIYNW5GEh)yyx{i>7Nbr0^A#cy-g+d8P8Av(%SZ*<)#=3pZYg%|CupB^z$b-fVY>!9ewnp zBt2Pxon|h>TWpOqolPMpd8wiZ1#^~>E*aeQEpV64Z_nB#Z9g)k{>LE@2YfmNnH9TJ zvNVA2dYK1z*lO^S>_eo$Ia;$P*1G>5j*BUUbg{!|dUhUa|pX6b7H%pS-%|rn42{R zHd$6#O;74=Nuazi6yXrZF^;oG9SVzR22bsv`h(RZCHvtJPVc-#cdGANREFX_M{sf( za&)lXk8L_~>z{Z6=H=5xY!cXwYGz5#h%(S(*BIEBK_R?CbkEPcVZwARGZ}+k4L(5U zPB|Wu?DNX8eIv&16hAAX+{{Jh;KZB06ouZbO+@AP#C{5ji9O7p`F=Q<7jL)7*zogOCqMEHuBMwb` z#&3T5Z>;Zj`J?y0us*jc_gLNUh{(vmmj~Uh@%7#Xb0+{M#|Ag&iUrsPMuv->Y0>zZ zI5k4y3xSE3BJi>edu*v&8(s%?@Ip!-PL>t)!_{F^K*IG$s;nLcDcwCSPD?ObY?971 zm54O?Xq&*T3|U*&tNHk?YN&PUYPNo=h!+TuK|60#IL>#Bc!+?gK?C*k<56$RofFvA z!xAyPWhZtXm~+IU%Zd=dJC(o-!{TJy)ZP8=CHqeO3C1M>)Vn87iaCbiS20*DXsp@i zG39_Eh`hSPhvF+YfwvuAl@}s+KVt#unNNNFBji(G$LRf!VhNz6p_`!he*x+L#s4SZ zgzo{v5PaMdh6D@A!GWok9?~AH0#6my*XFvYZjn>tv)|9>lC}6H6Mh0m;(bnI)C_{7 z{JU2IV)k!Ov1gqUT~qnl3VFQyG5-nwSqMH|OdYb^wqHn7xWW@T~s|7zrrx##v zuw|>GvM`p~pk7&XNI`OQUamaDwE;B?GOm^^X589ufn9YxcTjgfl$`((g;Gd$I{6`R z7O1XM2w;3f(w^yGHP>Vdcsg{X$08`n9QDG=($b%syKOeyFx4$dzUgvVxgky0r(sRr z*7!(CNdYzx8^6Ep`kk`5j>3f*FHh83X`-qPPR?SP?C7mKJ{QPOVTI81PXD>r84^hT z349OdghB9wN(~i=fHsINe-S;w>$9gqQPW}WqXi>qh4DTLl%0ov&zL3#zm1H=*a3qAVFOXw$>}5tjuvY$oy$34;}v0tvhWTP7hf)P z)(*a+xitRZ42?iYW|h)Y+ok+Y-CNk?6C==IDfk_Clp5W)(H4SRa}@qHOY@cTH%YDL zrX>jVwqJ7nJ;<0-Bd6T}x3H?U4?_<)bDgWaQb-4?=!Q`@WuKAtHX8p+tR^fCTk4Z( z`RIFRJGpn|_VfGZ5-)-(zpxp6jIeMJN;RZTWqL!45hZ2F`)}jYx2I*5x`ca6((?9| zlKchPVp%bGkZQzWb>XNe3E>^ji8dx``pHqTzP){c^WlU2@wH~#i}Z^%mWMRki&W}_ z4`-lFGfrOv{!477#rY^p;{G4bNQV2a9>$u?@F#2Od4mDFI()de1XWrmEaIe=ku*DH z8&wYo380!?(WR`^Ilr7aru*ax#OzM_;u;#`9mJv*XIka$(^?&HV!wBU{V46Zq*M-~ zu-GF7l^UUeLosIwgv00Yyk%aWf?Y;zLCpjfD)D`?GHAo`sw|$|$ggWdX#nF&YsD!g zuIs;-Uauu>4Au=IaCz!+-9kT7*|7K%fVb|}^*Wd=V$lay-owygBgUj0&F^)_WsgyQ z(yU`UB>xqLDJaeTW!p4~pBBk;@_H-!ZfE@ElFreR_)+S*R|FnR^XXdb{Ft!bkZNF#`RtTcwz&)of=Xt@|746CA!JdsIWR4t)lZ zV#$n=EAdgscGD6Z*bo>^{>j$U_pRz@kN~?1|8OLn)aM)8;e82W`YMtutWnt@AE)A@ z7HP>q@NOGAACiC}+F#1>g6krEOa6gSgSo=f)-3+POv$@mX6iLlRM>LPmcOiHBWPKY zxSCTJa9<98sQQ04=^TwjR|AhJ&5+!L?k$+pZE$+8UiCM+mq5ZA!kcT3^$Y|4a` z)Egew8q*70$5|fdF7p!uzNZFsz~Y7~Mcu>vSW@=4VxEcRrYDKLU(VWBabYxX&$zhV zIHnUZp-%k$KlFC(x7zu|NDcUJhf9LTfOMAnsVe!pY6%5Q4-&}XgLwl>a>VrZS{mxZ z0f+PWggAY4A&Xc7X=vwM1%cKYmJr{mAxux(aWP$IhH&^zUnHCP{tDv(8b_g_acE{& zL$j4vgcNr1h~L5*DV}LNJu!&76$xgFe|)E!a$DGf)N*wD=aC=#BU5sjT4M;zVJ@tY zHKGNAqyUny?^opkGptlZ1(nxUgbojEusXO>X+NO4G_U?xwU943hGtwY>h|V$q*-I5 z;0`C_``_x)vcUS~EM^e=Vxu3XTD9d#Jr7!_mgwt!x7W(QSLz&oyoFALmu4~bBa9C_ zK>Tvdx>=0cXSr!ayi~9gw>=2f1qLDn=1~1Th-PzVb_d&tL0S?UT`ijsMGt6o)^=lV z?hZGiBjK=%kBVaFb9JY|OTY_nz=%x>%2!|PofQ(D=J`H*7VCY(=ENcB?_eo5?ImM& zBvil`v1Bx%SBZT3%0cw*52!|qiP<}`DZpFECg>NRImsU8{=w$%%t{F$YmBj$D^)SR z&*UWtwfMQmJj9#Qh4xUu)eI&@6DO#ZUsdDgIiNxDQid!U)*c9nG{#6sPx8)e zs3#ZZ+N)`!SnwM6z+)hHhGe`D-o>5LpUxNSf{Jp-%X_V>79!f!hny{&dtlDun>oSLfh-_B2VfooC5bdZykdc@Yy1#&W zbr-1q?)**fc2QHv;7s>K;>6yMaROPb=DsE~uX{tI(234ettA~YhvS=}1M{ezhTkza zL4I?zmpUl6yts&SD*%VJrm{{lONjfS5y)QN*E_+Js`~Yl=;@YpUWeJRD;t`DqeE8O zG<^)&;V?~*U(~9W{r0eFQP@=3ik4ShLbuE7-ll@aF7VSFOR00DLuWYhyS0-&zTLi9 za-|Il0t+lHwOG$Dwq>EDq=X(G7O5i5gJeQGpuY&iIGE^n*U0*Sl|4CL*_jY|*myLKR1-(|qAG>O7_fHCwGa631UAa`M1-wPO|6)IdJ6&xH` z5k*g7Zy5`)u#z8(k3`x}z)gF|z-4uH8bTyGe$Mtx0_~rVi-40>_G2O2K8p1dc>G0# zFMx{+SEU2OA2tv0mp|;1J8< z!Pvh8=WvyFkBYIIi82$2Y+}X>6j|s*Jp4+fs3z43{SGv1vj%6K!`-CvSTrnayNo~g+&G|9^OKE(XAd3>45aKEP`m(OnF_X={Cpt5z(A1rTJyBL zkUO{~2!OjGXn@H=r3oi9K7Rgq|X?$G0NPlDs)vuN4^fjUlDCS?JFVG5|Dv2{v(?|YDOpSH_<7lySUc_2Y zchyQW9YErCseu+N4ra zUyw1eUa_Fw#lR$3COPE8E}l92)QXtU>(9c`P4U|ezDZd8Lgnk!%0mCZ*b-4io%xfT zEoeoov4IIlR00YyrZ)e~(A%dZ<{+-)UBe}6^&B)AgamIn0Bc6zH>llcC`PMrrOMDZzj0dVS0_EQ-Z+r^kE%15{4j_#$`Nlkyf39R)%t49 zwm2PIZoSXRZ#>dA&Vn2-AUs)vlCw-LW;2siI}MI@&lhBOeCmh+*DcD*xj^qhI;wlj zw~Fr%??Ne=Orgv$k#A;w+u~efkFC8JVHvvL7uBfenD={Nz*jM<4AmdU2;QHj+~g_r4lHJA34)a0seuZC28S+_cZ z*op)ahRjVm#Utc{kT_Klna&AC%DmSK64yqv36!UykGfd}l8Ew;Th`=|L;b|z(Ux-= zJ(l6U!ApDd*uSbY?cOxLR=*!YLmlsh=l5;hgVIvh#DqK_F<5P~x-h`V7iES4)KzD+ z4n4~G*K6AxcE%b-EGVLdHgjT2?fHsOC831(Ip{(tcM~I3DI@}4+)Bdu=jleAUxHJQ zT70Wvt9|avsY8do6@B_5Jp+orx0uC}%-R)w*ks&Sw%P5I77fp%Y4)Zs&h9h73coin zbM*NdoH0lfknvFQBuvIcs&0J&bkMq6DKgtsU{NavwVw;HYn;tMb{UZ}_qG2&QMx*eLmx}1nJ*wbc4II6tK)ToGtL%7sm+vM@&`WB!fi%(u3Q<$ zb}_vtGkfufr`(_S)-U2v0V9)|&8PH%Uh#F{4Eco>q+*u)md^S6*zZ9-e)0!(-b-t& zKiK>&9SnOVanawoJD9M4En#I@%Ds(X7L0|fEiYq>f15-Qh3Jc*crM|;_V5qdTPDO_ zSpWW!T#FORIjlH|6zifbWcw& zqwM$jc5usH4^}(7kUnT3NC{WF?2Lvts@M)F#ZahC&qJ3OBg=WZ$A z*SElN>B<7cjkI^Ef_dm;HONYd74NgWC}yg~;4=J#4vA8+wmSFDWlH-~)AGP zV7e1XRFSTI2@^>@g{H3>KGvzi>&)DTuF*+{1w7MYSGr*(4$~~I@s3;CX{IPJNk>O)+B16%iRmzX5E36-s;m@HDwymWk4Ba(fc{?=q8FJ!8U~ESDfJ< zYv+qcZQP!Vg+vA`ixnjH*QpONGD#!g9G-!3HK}T8$86UIZ6c1oyK=2YaM~hyaNK5X zD=mPf4r)1ba z;)V1u=%QlNxjD>wr3~@y7!>c$e80LqZ?k06|4oJ|;YU%ye{M*g2k%iWanS2qC5Os4g2k zIFCePxQgW^N?g?TU@@I00BMRTb+V|25blYhm9(r0TV{`k6tnmC!wM0YV|16H=34N! z%=a4slSqib#30X>%bo^nj1R^_dqdn9f#-J!lTnMJv+(N`;q=UocyX|RcF&rZXp z6B|wkf0pxy!NjxtJRQC1cFF3&3rD|E<(k(BF<~Bf#M&6;3=%qaRN&M1^0x=N9ueno z4eStiq|Q{4D_RHeW%S#;V4u!AU#dXWg>xBL=>oC?PTSW2R+x#m_&^nGHL3NG@3C!c z(W;op4{#@&&ft;7o>*DI=&BGHEHhumSJQ`WqjdWUj)%|chD&>@GI@pEjTY1rQvKxM z`?t2Z-#S3t$G~9;RR+Q61Bh)IRF|vA^)zg)l6Ng!doFMR=Ma$N%UzSi2knZ8z&yGz zeVQ&(27OMV04{iERLftLRD?{j;&+~RE*#>nxttCek=;rm`_@?dLkx=?2C)s!HH3R+Q0%2~(JSr&td91_LuEnw+KZihxnyAIt@F|bZ}1qT zg39Q3BuQ!N%gz*{$5w4_L5W(w3x;FL_OwmVA84w`DSsM*-I~c@=?=}uCYm=-zj6Hd zL5_@KLnL^uhNhYErHtpQyLZmh7QcAT7)GwsjW8?4&P5RdI=vNvyq_>Nd?jo__nkX3 z*i(D1?JX#uC%RGh$az>SnAo4wR+TAwVEop?*dSHMxBC-JHWJY|XXO^ISGSsk^XdVBCFZ}rnCOc1^zWBQsmUhPA2d5~ek;F}W|SA?;50*<-N ze)`oaI0g19!iD4AH>sp5$le;i9kx)R#;)P!bt6A_pXSa?1qs~CqA`n0T-h4Yr*NNM zUE$zs6=B`v`%zkoJUXtdWr2jFy2?1kRQbVeTSqHpc(I+wNx>Srji33gUS-3lLnEEN z)z6*gRtDNqn9TO75QJ6-w8U#q4gPAqGA$b#i0?hcMNYQM{j`W+PGe$q{Q_D%4N;kS6F;Bwmm~)ngIl;r3h!|gWa9ig0xrNq;*y+tfXF3Ze zRZGBA%{+~~Dan(y30Z1J%hHlFBF^H#$0)LUz z_R7exgzKt3xbXcpXva-O*y4eZ@^a{y^-0+wuiM*jMFPAtiNW|n@0ZC;Fkn7y1X#F*iRfS z$apNoh|#T=d)5W0Y_d8b8Kt+_Tb1;gHfuF(c}6A^VEi*BL=9kDhsTzM$Id$9!im@& zQ9KfAL&X3P>82jv&Ei&b3)ll+@fnG-szQo(^1(p;SDC4sH?^{w3J?!BgK60`1EfF^ zeAI#xU>-JXI}*;jE7Xr0@aSf7JblsG zfGB+bWnV_Us7(GEB1oXp2zX8r37%r#Ochj}90p+T18(kHLk1`3;6Mp*!=Vd*S!KUt zqCZYjwZjOh_zg~QG?atT@Wc=xWuQP}uX$rC6%1@=Ro~bji5-GcC>RncdCNUbDRlgn zkHGOGKi9zk@Ql3h1a&Dd_~`fmyQl>B`8!qMzRSb|?w0m~2RKFaZCo|oa4)3(Pmh(r z^XWNRc}KG-|FuYje_We$B!4czwRx)va`%DR{?L+X`~3NDpFcl5rF5CKC#^1!g+pb& z*ywuBQ*u2<*vbL1d6bBQr(rlO`Da6=I?!Seo*0ozV(p!=H0Vn()3zXyTjguWT}Ir& zqOanfTwf#iRx%uk;HkH{J6R4zwEG#CD@LLYeN$L-)=VG80Uy7b_$gH=KrBAWJH|Oi z^S{!rT_ih|(njZ!@6%p^R1&O+DKkrn7x$hwfky7C&CLJ-}=PKQYRlA}dLch1iX0z4VwMIBw8`P}jSxxwbU zt=I)F`Z%^r4!!~e7Yu<;VZJ*}Nn>TnA?Nry?R|aA;YMA{?e_cVj+DE@z|=s%flp?< zK?fD!qpciw56rEu2Q`euUYA6*6WV-hS#{s`9EwgfB%OzHRx2CIyrKKf#_Aet9^vwj zZXYm)TPI)3Yrq&n;Qtg047GLldVocHCB0dE&4Zup9dsF>CS-M!GY>@)JgjHdrz4Av zv7^$5G$^7^&~y~onmodii77(Vr;5qwgL@c!Cl8vPMZX?e^I{#|PXbZbsKX?YFkz z@bj)C)h6z@ChzEY#p*Py1h`g=8ypO=xpn?tfKmCQUQXeGYtW{Ln6?qhW#PT)AgFVn zP+QVb#=a@LTCCUYA+|Qbv+GMw$X&`mS@sI9nDv$vofDs8H<%P#M?+zjvAGRIp9 z;@BiD#Nx>SQ#rXtkj_{kgw?jbfw-Lq5*o{k@yoO_1U)+5pl|%U)BgU*<9-+N5opAE ziem53$0~LDX0YIVer7{o_v|!S(V2K%iguE~?_~KAq`?MbESZkYN`%gff{%Iu;~IpE zSRZk5Nr6S`j|qjh>ml*JQ*{M*O~%C zY-(BWZ&aic>cly}?J7pxM$Rf1GGiQG>MM%H1y@xjtf))_De)AiD~%b<*zKSn#+`Ud zVr{hzv8GoUfA)Xyut~*+c(5xEUbOG{?gF~Go4J_Jp7oI8FUiRO5;R7~q036HSegKE z2RS!+SVk|y3U!3}10aWH+0Ylt7~z5zedKcQM8IdRhrA$*12ry+KV71bhh|nrmC?f+ zW`o8wE?{w(2EC42r_V~$jaY3E1#e3Trm4wB(Ke<-n~deIu8Sy*Zu%Ya;NNtFHba&x zG?f#UsL;SDmRw*OgG|}Xc%;U#=HNW`zG-+~(u{^u^H|qyl$YctYYLpcoFl&mK4$>J z0*mib5Oi+ODTgOE8t)Y%lS0V`_6+{~E?k5PK)eg0qBufQ>aj{|vy+8o>wv{N74Sblho&xK8} z$p~h2lA=FcK3Z;LjgT24uGG`)Q!o3M=6O)$?-)HcgHSXQ!N3@Q?fR(+x7+yV`Z$fX03Z2ql z%anareZawI;J^}qMD8KL_W%rn(l0f*(AklCzY9zcX|yfP!s$19G-F<=Tfu2PSW-53 zZRfQKQp5Il#5U5z?&ucic+!!HgG?smve&vM#RahS_p@}2$M9Yq?M`+H4q?lF8CMy# ziBLOlcpgS&p``90u7v1)Usj(gl!xZKV*)S4uao6N`Cb#Y%*qHj3hD$+TM%+=-b{ax zF>r$$+3@?%>Ade!qr}n_1JMa&Nip*u{9E*N!$MC2avc5Ewh(&3cbi=+X4-vKhmaP+J`ti6P}wf@j$-=X;{+)sBvfn^zSHS|Ma)e z83(=$;-#_`{3Vc8Xpt-qfcPTb8;s)D$sEgl-DgO;W$NuvOE=pNb`V7&=1A0IrrRQ; zGPbZ;n$i+{at$-rO)B%EzUq1~%6FD0-nzG#1_sdA!DjHB(eIC{7_^lx^~HQQvIyW) z|47xC`2@*@^{C|+@rBx8@w4P!^j)S9R5JbN%E3#5@v^r-;QFIqe6Ljfkd{PwSq0_k zc&igP(}zN_w17fVT-=$akcAfg_`CJsxW!vQPoJPSx7Jn|M-qKn!VtwV;a#$r40w`} z4xt|{+HN9tcb}tXkJ*b(lQZ#;*&@%89f8{M=up?u)|o~Ck}q-Jmqh5_>rioqTJ1dd zL$k?s#EI+~=v|CczBWq*=*ieooFYxxI9_rAqJOh^3E_?Q%&>RO3&vGWY#jhgHriF| zCN#}|{t115|Kr3AB3@@==l#5>SBdEn)hf(2!9ybc3r z*WH8GbY^4NXf6+NX7*ebm?(30Ul}Zd7mTX4d?x9Jlg4 zCUZfW)5_v-4~EMKjAghIsrUN%yeQL=?wXRzL+3=nZS+r@*+nOL2x{hK&FQad`$ofS z2+ZJ3un5K$LulBtg4qFDuPfBw%Va?*S8P_mj)6=Zi2$IHz(8y;fYN7X9I&#b{hHku z`JVt63J%BJ{QE2buYyk!UuMW$Bi|_Px9lUL_X)58l1xFx40h%;5b!O8zGn6F$T&~qf(&&1Ras3MHU;8c$jr( zM|-7^yYJOe80=i$}r^iNi~52E0J<+|9m&fUGchV&)j6R z8+)l`1DIx!y`TPBNcLYa|0vJpHa9p4=rlJ8ncPtF+^1I7^JR6`C8ce0 zzdEr#;&74SY7ZF5JB&cMpqZVG_=kEY6txBi9Pm4v=~c3#iI+LA2C+H+#olkO#{qI$ z@30Bk<{UC*!W-03B7~6lsyhxa=s0FZsTKY|K5AVy=xl)91XOGj_BcdG$bVIH6p2xa zAQE$0bYaZizAZ{N%8oJ4W=A%U0&tnx&wk0@>>oMwgm(nWG zUQ{?vlNNZ;kKzwP&QLMDPv!xA}Y0bIDEZhYi;F5mUC z=y!;4#?009H373*Kl?|uUCuj>D`R~6S-0LLMfNY;Rb*)dDh z>vK~8awZWwzx;m2F2RUjdNBU@vjGeev+_zZWbg{?SeWVQb*4k>dHjE3Wqkc5{~hle zA8It+00pE-kpgxg&WXPKLC%8&cv(@>muz2o}E92N}%JCfX26r?ZBV3M_@H z#SRu5AI>$AZ6wvmq1&IdB*Y-CXck(E9B8(s`js~BOgA_PkQM&qo#4k{T|$8n^`!sB zE!&so{HvcXG*)2A@tEpJZPyc;q)I(Nuv32Ty2|PRHtTU42>s2!?GbEE(#@~?2DVH0 z4jk%84HAQtlkx6AH>vdv_%GOH@2x?~ffF@pq1quVOn#1Ni4CfTg#7^{5S_DY12OA| zI|#JgqL;1CUU0o(v_piYT75zHO4_CiT@y*M7~j2}#PIXWW;utk5yIdiz3awfG7}+d z-xASSlY`t-2I2TIa&~cOu$Xu^rL4vcQ#vE24Vq9`R8BOWE@OsqU=9s%qFS|A+`kH`3jm(k0y;f^Nj zn#!e(+Get=AU9ycsQv1}Pkg%iiA633)ZJzU-->U;$-tpw2|4|sb$4X!r!0We%!__U z<@FZOp2aYLOG_59X4CAl0&pl6SG@hGk?6q!gsqw3UmWm~&k4Xjx-wg!`nMNTLG!Bx zbpaYrgVL@tm?ZKo)1%)GBwn)GxI7*?8|7(NTKYj@Q~c4U;)G(WB*1@q8P&dbl0G3Q z$q)vtNmF>*a#=wz>GASJ24>>#zw61FlnQ*^WLukT%0v4vO4PnGVLq&+T00+XW5%S3 z|FJQV>t9;&xnjI~I?{Et)_&EDtK#-uDm>tr%EgcFXEWB(&ls_FvPzw==tsAb>s@rGW}RI)T(f2u31MqoZ}h^yB3Clo;P+Mzr|NH!wH6?Q~VW;Q#BJv;==%zKeoL^C58%?o{7mNjp#SpLhQ4>dbO#*nKKAUnUCqr2HZ7jj+ zNMJ9NjfYobN#5;S0_f{4Q)6Upw{iv4ipHING8fV~BlYUEiP}(#T_Zpk0PKI5!z-*$ ze!uFtL60$P({rZ9FO~3^E%WB;iDnJU^_Ng_Ei1T2+~2vm$;j=l_ayL0Mr3fu^*QGZ z_L5r4`BA!{O6cPlQCV5R6nZ?;r`R_{11A>!njH`M-hgOi|aMVSJ;&P1eALgx9J8nE{S_ysmQXy zSk!e#2A+HX4N3T5zKE%GjI1Y3xnYh^_2Pv0p%*>6z7SG&A_3QY=(K4Ac#qPhZJVzn z&XNH`5e+B`12`7rDr%*G?F2|U!Io#o?hfW`009jF0}!Is;)2E~AJI(WylV14Ns9)7 z^`0P60 zp*b|=&lm;CqG<8LB*w4#2co@#nDq4~_fj&UpGr=1)_6&9kH#Y+8=u^VnK{|Yju}J# z|LR-XgwcPgDR|~xr2Y^t8+@o?V0mtj{Q`xw-`HF91bl@Tq+m(@vD9P_#$lF#Lb8%H zo|8g98Wg8dyE{b{0Bnjk$b#gmFh;EMklD9%JK!l=H&Z2Tdxwj9uXci}Zv6bMjaQ`6 zle+?Wceb6_?-DWvD~OT1$svO=YJ{{hf~lna4DN;&J9DRvRAqC6GzSADnq?H5SeQMW zNGM|5s*B>NeL6)@iU3iI))a1LqM1D6pcHU>`*qK5TGB|44*wIa6kFwJ_#@%jktH8? zu1saOvvbCgsX4rsALBfCo4VHIPpDO|5@qHuXrKTpr2N*UZg1JYsB0WT?(`H)ZO(J= zyEdKQeakO(a5PuJc&Bp`b!rdIE8@4Cadj4u(Mam`n8i+USz|OQm~DJ$;v>Rt*g8avHtoho8HkT{@&4*=(9}1=G9*pV#XYDR z#@Gg0M8~`^+WGuf(fMeJh2h5J7h_-%X!Ue{WK5UYl5)+YI-BssytXiRz*p`Kb;!V| z?vGpFuKC)5RNc9zYbCHzX;MLn$k|9$jXc^Vmnp&pJHX(2&2MADpdNoS-DZ$Ay?6~! zs3#eVUYv3&`Bpv2?39)$r{8{ceiMVrbZW{>77zW~gA)?jlR7DUkGWOWSvb9!NMbj$ zD6JQ5k-N`{8VhtK$cL%{R(T#E&on zuxHwzkB2Se*b9fSh>fqYzXA`I*6MpmpqJE~?_NLz_W9T6+J;Q4;j z%8^B@oKl%y6#I#-vgFSq+n!)-9VWtYCXTu_!|!SMg46YuBd=u&nf7Unw=qB16WFzg zc^9%n4+(hx9MOstet9Vt{29sSsX9NqxWHfrH5%2!YL_AOVTX!e6WRCetlOJPkw27g z#=>~*SRh(G(+LG_8p$kZ&G7?dnEYm)$0tV0dl|8JI?^UXE~J%@U=@gdiF?pn-N=%kQHWRh4q3Of8ORH+@9-uF`l7x=-8mAEf|K)ezm(#$zf4rb~JC95q!GB=9rZu~0>nG^6N-#a^ z*cGXHZwv;cus_9P?EZ`0y*0EbaDKHwM*7U|s%ZOP+z8dqYMgAnBHQVUgB@!BoR@x- z;)kZyUdcHO8;yF-448NpgVCXwiA6KQUDF%*>{ox?zM5bgcfEDyz4TjXOXPRTMeJm4 z&vu^_GB{n!r%>BfeU1>EN|Y?Pu&Rvph});#XJZ>v&h~#2vW> zU&iFqs8AsxaEO^AP3*Sd^T}C4+=+dl_Q1wm9SK|CXzT3;ReY#b5OdiCs8IT9J2l(i zp{ctsNPB*+dz~I#6#tSKg!*nFp9M3_3u3$EFDHi6)bNn`CLoZ z7Vdhs%jyu@Ur}?o29EqzISIwh`;_M8wNpvU!IuRbV+Y#E2h19Ac^7inEJ-olA7inU zP1X1#=!~fu8@qq6q%avtj6=%bygHbMb;n$}@Ee-7nV~mv;i?sZEV>F=E@CZaiLV{OX8W$$7c#xl)OZ;2P6pe1+7gfP*_$S>C zmKt}(^bE}=$xKj=LPBtf0_Iz+>6nj_D%VaeAYdHaABNls1ga{vGA8N)->o^ENte2< z_%GBhV4LcLMfhT(xI!$!LN$gQ=R*vY7@$O4eNcv}=}-9Eu&yyfq|ty6?Mh)l%9!xA zN2%J4+UcX1z)?g=f+Y(kq=vb9bGQPK8rV1tb_RP`Gtb|;7h25vI)uq0R0I($mV&`T zhPDAaiyEwy7QtIx(NHEEYu^G@;4s}3YKg@*E7%bdBJJVv7aefhZfwXA~2O6;6eUoX@O4qX^ zNI?Ys#vOuJs?+vR zJz`|W69rGKU)BPT9zg~MoqZN7Tc>kKIHYEt$I9qU_H)-WC#5Plz(V!sdETZUeH&JC z)jGgY0#8NaMvW#QCBJz6UDfudG${v(;xEWf(mr7M$+UU*MV`7%cs;uKW(#$Z*m~Tq zl^~&f|5C{~OU09jni6}S*~sVeXgLU2R2rnKv%;q|WAOx$*2vPEdcMtiEhjSnj!&sT zIZ)|DqvHeot_s{&iIXs*w*8?5{HPkB-Gfq*y@5OK=g==KW{){8+q{`8=rLqTQxn7 z7yJ1NR^G#!W}|_5$ei5#HGqd)*?kCD_j>yico8d&+sf^&7kype3xlIx7|N#vWU@>{mj4^h3flq{}k z)GeDC68&UoEex`YmRTbn4PG5_Louy0CDiX;EUDglyO^)WA=r++Y9+Pl#GPf_!de1W zczKg}yib4es%88{Ced7yIH@QUAY?UvZHyIHz!UTaSf~P_`6r#znQka9>OQr=Wb7Ib z=L>C;l4yPHdxEEw9+tL`0>VP^BBV;h!5F5TnT{YP^y#G3a966WbqSX= z%X__26I!ks1948U>B8#&N&<}}iQU4gTXnot2h%1>tEsi!on<$dRn8mEz6NcYZ*0y1;@3G>4LW?l-pV8pFZgMw;^fa%z zs*cL-d}~NiPd}IM0vREe&j`v)_ClBpU20zr|;L0i|;Q`R*06j}Pmm-k|M!lBxnM)M!HK%=92y z;S9LfjLBBv+Vfhf{@}&v!+m9Y3{hWgnm9O$Bv2F+?{6@caBxC&!hEf^{&3ObOC zj;eA84tS(K=tb1V#k}&CTa1v^KqBdiQKtd)(#8u5pyXg*)X& zipg>l^Q9Tw{xfsr9#wCL{Vz%`P2JR!cS+N)6DxA;6G0FQ8kenwr*IVcj(p@IIUF}n8|DfeAhdXh0>Rr&!Czuv+zkc&=3}Kuwgu)h;dKK% zW$4dx^QP`yU>+PKED7KMG3{Z8TMCh(>(}U^CV`zL3nA~g_{lvOxP7CbAM+|BU_rG! z1CACAIe*Nn|0?dhQPwWIUOKOwAPGb1-4q~{CpFua+e}ZlpdRffC z@FfHW-Q0hJZe%+t*_AL8vi&FNCNbn3qLwE_dhf$=N#xLBG~giOuO*XyXj?eX>La)w z%kLwzYkp+V@_YX@sUZ_@lu!A5rQQ?2wcYT?!w|DT@JR`QwVjy|o`@>=g#e;dfG6hn z#Wsu`Lg{(Ss{sYP45)iB|96IwmA-%dy95U@+cUYd-2&~!7s_5k-(dQ`(=XhAARnbL z!H&swtY|aS9l|I=`VbQ{JUNHGWzEZUJUU4k{$7j3mRm4K7SQNP#6MGK!MK~Hq}Kj# z+->`9b3qvgfXh{g;MF8)pvAEv$E;Qxq_t>VBX?Q z*n2Y(6?=5*2m;c!40Ec(TQGj1S4rvfN0v}#$4X+(Y;L8vbzJ!^F^_Lasnk8MHPbgK z-Wst~{Q_>6+VJCvc=ls8TqM?nhx1IEC9A(a8-yDcZkw4B>AG|0@5I`Zhh*nHdUA=c zt~vIT-ww`paCa*CnF|TPb)@*PuhYG``^d+3Qm8kLg$wb*5ZG*nK7coywQ)S}|Gn7^ z#8`L^4c!h;PwlN<*;&EoKXDPmWZ#X8qOH#IbC>b#ZodZI2l0R{rO}@YZ9}5B)|-1I zX>aMe(TX~2eIm3p=^k@1Ixh-HbMHpD5Q2;qADiYXFc}dFp?2rFH1RzsE%-eX>FLPLc-YTHY_HsuElQid} z@Iu?%-)8@m;3zlR-nBiIN70GG%nPRL;O~eu;Z- zP$mCeP!^{QLlbU2ZAyN1Oaab^G^ypS<&ebY13ZnLQv4;%1Zz+>FLl{mPSc%Nb~eR4 zM>ARtejTg_%@_Jto3bV@=?euvGHS$}u%NS8J<0HoPDRVt!{u@3VT|`fNPMr41cM^3qG2JHnr7x6Mc|NFce>eRBpM! zlCfjzb;1fPP1Ifgr|wjyE@xv?54`X?T4Ol!R|ew6!K%^;KjZ4))tlU8DP!%Z`~~DR z7U|>XhIp3|dYYfUrBQBLw-Qm}wsc)xf;`TPPaSsUp(Or``mB8)a@e7x{&?xTtI`1#+-hr?#S=0`UVx_M7bf&C_``pJH)J}bn=8$p^gXq>n!#V%vo<$ODz(HkJSM6#g{`AEtk zY0UTr)w>1#Dm9nz*VU-m5O8k?r0dRNB`JpSuYJOrad_?XN^3|T_Tmc=@2Su~BBz6E z@g`;SuCOHxR--E1o(_mLu`uhYe!H9fF1xqR>4;d2fm2Vyh17+|RVh;c^GLh{3^D4t zIQn1|_P?_}xg>a!ho^0_Zl(zK*)t^fwufHD>BE?F?{?m{!|Eg1h%oB9B59L;=$jju zXL=6%B;o>@QB}V%s!ql9;$N!ST2kc|?30JNy*Vo5CIW;_=y7^X^<|=zQfkEcPr~sZ zhVrbxK@VTJLWYR2v?lv$o7tGee zhP^H&`P$ir&+82_2~-krj|K9&D7 zJ}IaxFFK?zUV(dIEejnpDi`^}XZ$?Wc3_Cj+rHaWWQcovq<|(qio`hR_j5y7X0+0Yd!K%Ji%4FScBB6!#f zyXzd1YvMT_Vv%s4eGLLUS`NuJ-adAnh1{?X`$e%SO(>J9{WT2+)v!L%a7vYUl~k3G`UCRON)sTW6PtW;*g z)q~zDiH69#rslV5r`aQflhzQ0ag*TX1|dIc8#)>$HuVLi!I%~AY&j~FlH)QOV@FbG zF-P3oOe&tT>(eyGDOq17>?c^PUhg`!4Hl(~1>I6Q5Rw~@LXtLYSt%gJtLaKpDPYNH9}k* zgt2A*`V)<^H>@=O8iCVhEixdv$|X* zhYRYVhZzg18P@o#t9gpu?kH#3r$KQ&x&zi`=f+Same*PUMBtNxL;$ z$vRpqO++4b4Q%Dils)8U+l3m?Pf6;}*Twj~sJfJLn>?xydIO3ZZf+Pc& z_IUOrVX4BWlz*?Tm#=Lh%ACKI4|HHOTzo&PN&O{nsI@|NffTSR%4`f$q)zW*ZdTNirZbblcPD^E9Z*kPM zGHB9HJX^mdWvmlSa?-U9Le*W%{fJwf3*pwbzyrhLq*dye(s?C_X!jKxZZ^-lQ^nlY z-)M*98mx*GBIMY{g&1(*M(m&ZYTQ&O>fU9Z93uo~8MEVcG4ltMRWTt1b#C=+h1m2F zgBRfSODDb1%2zR_v1>g=%!iGZC|tuV)XH0ljqc7NTpUSSS+58*e}t7SL_tB%WU&tp zF`CHE@?eK0I|{pVf1v$z&2y;(PeRhRvipAK8hyg+{d=6~m>50fZpY~!8aiRskT7RX zYs#`BWdascZrZaRwNw__qJ#cfy;9P%N&;nrT^!jBH1nI6FAiPMAvma$`1W~qO-hdv z=|WK*lwX^u(n#O~VPSmotX4Lb=o$E=11EUc1VkebE@4CytqMfM z!0s^)vR*Ezh|1hc^u0ovU$EI3;9cY(q0 z^bP!W7AE+B4anuYZ&qT)(PO!<3!R85-jgD`B0`FMR{N5W;5r>^>wn)1gx>$pTNX&f z64TO&PQf;~+Mke7Q9WbUbC~HB3h*;CU)dq6f}={Wz{~ss6vQ_6$^VEd6_ZxO&;CA1 z8gM3vM9eJiN`g%a?2;P;%@IrqFEBxHzyT&Bp0iH%$t9%yk{*d@br^QYDbfEK_$l<% z%w@**P|kprAmNWZ5V5CkeVweEyI3WJaqoT{tRj*dU2~n4A&`8dCU`dTnO77cQkBoM zi5`Xfg5)$e6AsCQva$QjmqZ~{qHXq8MMQH~8Q7&mp?UYLimo3F1RXHN!+~-sGoH%8t0b z#=ewJpz=@dxXm*v>tj%`Z?S$Xg-M!1ut8>3+v)_~T)kWzPo1I=d1kO)v4%6#ll0+= zWNb^|*IrCpMWfzdE?S)JqpHO zauh@2Q&dEA5#LXp!45OiBe-+_aL->Fw&Gj!zz7plFvL!hzDIi2kPQ?^IQC}*wwSFWaiywZ_nOu1G*3l2y<+ z1;B!`V9RK3%cPcz2fdrtm|iu^+O`uQ=rka4H+74XsA_fXBh=~=TR{wq98MM_MPVQ#A;Ft)#<5>1ZPyr+$X z2_=K-&~+BkYx{_gw&IO>c%6DX54hp~9EKLNu`b3e%w(jBG)9CwBrQXr2(6-+8f8E2 zq+RMj(bDW%XRUB4`f~qWiO^A32Ofk{rsWJir?%{bvfw%+t8PbpPF2m-w5N=to3;s> z;{p>1p^J51o6eUM?WFrscm~n^Xo?l08bCDylvCkXo!8)=o`ACstoz%3K2(3++vY!U zBVdUlBy!PH9awpdYFbQm&`p5Vlnb6;+EYO>#sd}`B$TE3os zpwrk%#I3Ka;zLBkr<10lM&>baE|wxY>fQPV9#%4cJs`otZNxDlxxTYM5-{7MMp?kE zHT1O6ZMq6Z3fFCd`y64v{WQVAbYN>C2ObC?Ep{BZXHfB(HlMLHM6x7fdHzx`!cg*EnL5OM(*i~#kVj}Mw_D-eT5}Z} z>N99-Vn*-vw?wHQ3i8oRMhXVs8gWukshvzt;@vREML(4r`v zg&mGnHe_m7C>|qglwaWGMFyaVdCucr>a?-!#)zB!WlAkuoiY;qi<|qBICthd-M3`6 z<*OsX2eVbgm#%+8N&Aem_K3tHzK`2>+=pa*@uNVW0D;{b9wmG!QO;<1|F0>hgE;G* z0NJrJ|DeH_NnaB%YU7X5#@qq;=sTJ^|82%l_ltT92? zZ$yTBQ>hg4yRxYwO&etI5HpV^MeCUNWk{CYL2!X|@SbK43P`IrhP@7G9@t@n#yOa4 zR>5W-FN{z^*2ueP67@S2hUI{eDdBQ`eBlZn1w<-aqE-!+Kl4*)3hx;0V&q1K(S4;V zjh4cjsyxhR~9!I0>&vsDj5%~IMg?aUSuf3;|Nj1EJ3)%*)%SdGLqBoO&=aS7T5LFYq*3%?cVKIxsvdADx@b_9Sr}6s>W}C1@EsmPnH@x6!~(1DdAfL z*Gi@ckXPzFHUb=Yo|>foJ(@v82`R}qh~C%eAuHIrxyNReJ_cxnjwN9R?z+=o=8c^r zHc(WOmRwW%7)o1-EeZ_nLrHzP@fcr5npQsLZNRh~8Y5KoJ>DkquT^$<{Sxw4CkZ81 zkHEnFLqCi9S@0dG0Tu)o!Z~T@wYWjycJKd}oe+(*JM*sT7-+g1fwf~ASb!G~b9CL~R)pC=9POd~`xmcNq?a;k2^|91I!Cw` zjssWG@wRZ;O$A7{@xnogF&7>ThJyjJiq<_Sbot&_k7Fa8Vs;sO%&+V0C||!cv-e+% zd8)lccOKjoPGiBD%0e5!pbp3Q3leIS?l5&l)>dDU@%Vp}lcxclHQcES%LmizHm7-C zBGBS0>@w@T^;s3QfCHlQ964t|BCSh6*Ab=Qks>mKfRkF3|+blK^-n!`b87t&2YE+3C zKB*}oBk{D7H(}>?M)WkfWYiihnfCmEC7bqyJiIEJV~J8p*`^j*aSXJPT@aVt!Zm{M zQu^sfAR>xqJBKPs5fGF3vj0t8uy}62|3_SYD)dE%%7?!f0hw)ih+Fa@3N5J4c2!Eg zsR;{#{eQ*xv0PYLrk|B5t?TAdou%yV_pTW|KsE+T!5g?|0z-Bv0Zd>tJ`)(-0bncNl~A9*$tkM`taR1>6?G~phpBoM z(+w^i15Eki>S+QX3lG42mNTlnLlSg&~FEWaRfO86dlQ}Vu zHG&M5;lALqvteJ(!0%w36(SU@XknmNa4aBmo?nG>cnEOL)FzA_s5*m(d_MDU-$;&+ z^;w*cw6DfD=4QVy$vvs~Tap3O0z#w+SlkoYpp;io`|VD!*2z^{huMbx5L1bz1YWFl z9>~*il41$Rl#*Q)w%4-)`PPzToQ*;A#xdzcT7%tXLPs;-V7>TUMdeuT2Fh4$_B9D` z!wb1oS$TqjoO-7*UMeH^wym?s+;@b!0y~3EWGc03sOOA4O7QQDd|8{QN#4wfX)m5?4HcoF85)uxD)$w^~E`Ce!hZ+)&KxuAu>_Rp^&|T#cgrKumnl_#lij(LZNU%}Y~|?zcoHk+8y0Cpl6EzFPHVXCpN$hV#wJt{epdru+ZB8FZJ0<>>v@+HKgioABQdR46G@9j0(Zr~1{G7@( z3sSk3D<2Bk+JLIBkN}Cj%dJ9^{y`9UpWTc)zao5|0-&BN7fBupxiWT^DU&E0cMq_X zjakz|b6BMd&}b${TwZX5^a2Jo+w6w#GtuieX!$X1QZPWAG*DM;T-s+9jNSaIxC8wAxn^BESt zK-=M#O}k*@Q|=b<+~|?pXE34!)NturulEjKauy>k`9(uikWPZYy;|f9?HD#XW3P=1 z%AV}nPOU5EADucF2b-1wb2>KgaKrJ&J{m7Ld?<;Uht?k93k zK4gK-*e9U?z$Fy+v z5EN&mdpd(?nel`g_!$3A?}K{P4d|Eem!G~RTTHo4d~Ja*J;1poOwSj;m1xDT+1Kb` zaBc}(TA+^6*-Qny_K>fm`xP|&w|e0DDF6vOty{f;Cmaa*FtX-2FX>K~%WAR6x60(4 z!yOxJOIF_p_jV(Vy*tb>UUBSnJ7N#eo&6gQS*6bV@iB7>WC~FI$9y%iHu4#BysuH7@Sr zqOveWTK~^1wh>Bc;$XY{yge_O!T8-nHB6@9z@jpJT#;im zk0CwzUH=1~DY#0iB{MNop#Fu2iSvs_grwI)qfQT}Hce+2*5;VkK(Pgi;4hQhj``0p zU8A5lNh)b(csbLR8B;LG5v7geypZ5IEfc4{kFE{Gkk(XT|0Lj#ZLU_bTf9(-ED_vC zMSSS+dV_*IV8SC#EKhq1ps#n9`ZNTYTlj&kr)9J-pBzERy*|O3meIqBiv9s?gCs`j z7?4S#qLGhBYMAJRJ=JD83t#t)zfJ!a=G*yWfh;wc3JMxJS)W0OP+Gc0-A}h!2WemF zc+{st8gb0*V~9Qa+kP290<{6TL!jOkQbyYWVrA-RY+%HR;wIGz{isNK?9Q^L$~C%Z zEK?t&cm|F7z$X8jV?YCghQoC-o3F>-nItr*6(a1fGLtv&(xDhn4D3inomiA=*J;*e z;AdM@G;s>tAj+XY&`4vI(XhF}hNQA)vExX~UWXgDk;VKD(NdEe`nzXfwOQ$u`{;Hk zRqR97%n~>ATWv0CGh^~~O8bfp@OgU)u_Ihq=Wf{j_#PLZ&R%1EH`W_!xPDm$Z6;X( z_`uh@@e1%JC)=2uT(!6ZEquS`m*m&JFWR%Jsxp=2?#_-QY6I5(-3m$b4SDXh zWo>%RK)3r9GMTO(UG!zC7kNh$z3K7Tv>IpqH)tSp6h|?`nckHFwrx$tOo#Dc8MK=t zY$nr~yth?z;ei|y$Nm|Z6uAakZRzgVkY94UWx{|!&@l<5eSb*Zd7{3NhCX<&TxAYi$i#HHxgm+Oe=wmIbMpF+q)fG}_!Lgv5e>06Q@0NzUobt494nC> z%SA13fp18~VJ_v``5sX%g_~g%aH8Kg1;*018K76kV2RO2En{!E?iNK*4d$cQ1mc+` zB^Elw-h|fv6Xw-z1Vkn8n6=b?be00my|{8fWn6rGy#M+-*)%$hB}=PG@UYPii6S7n z00H*`LX*19laRC;B2*L&tb#nly%qd!?xBJ=!7krW1n|{RL?)FGSfg#h05WID=9^gy z09G8|17BU=zX=9_=>PuD1Y2|!9`2C?QLBZ&+13YBt<2$Z$YG$gI)|%)lIij-XDZFc z@(`4i2n>eOo9jLc+%XO?X?~NS8-{@iDHmMwI$&rqIzpl1J9&$~$A|Zo*7)8}su71p zibk`NY;!0hq^?c_olysm)x+_DaQ4`JAk^{+!bN{LrRbRF{?->GRiLFSaB_gj9ac ziwaV;&4biq)1_h1sg)pOC}ca1TFbiX9VXAd7iu?XD_R0Iwdnc`FX@7(2wr$u4CiNf z`9?;*n{MILxPui5dW8rCljv+~d7pG`i`3{*-b};B(bSKkCJXNPKHfX9ogDmbcc$VO|vkRIfwwws@U@nkDRvZxDTob=t}4+B{vPOB#P6wMS^VycFGo zgrEQe&}iQR$pD4=O^+Bjk6#T-s~^FARsdfuY6gKG_1q$C19?3q#@MP&_~YXaXKqyk zP9{v1umm0!K=bIhLi&+-uGm!634&!VEo4Wr9k}NhjMcOV{hX|M^m8>`x>@3eAnL!D8TP1;VEvrCcae_$!O5ucuB8In8YtYViK!GOD2lh!N0$xJ) zfJ^Fs{c#l!xT58+!oY73Yu7nA1Pm+L`-1N%A=y-L6-1K)%)J23!}%+CVL#B0WlvQUnF5vCym130(!GOD~~U5lj$}CLtgX zy-648Vh8~t2!s$qd5+KD@P2#!kabqhS!d3y*|TR~^P3oBLtU10Jm)|l5R1N^mI(+1 zh5CAn*&N zB3Pc-mrlQ0wbJN}wd5{HzKP|hyT%C%5!d;5o7=b-xgLA}atb*ZZW_y|cfaLLxcFG9 z9po_xq3yTVV`SdrLO&An_5LFRQWatO!tFcIQflUX_nSY4XV2m!(*N9n4|BJ!5W(V zY1g=FS#tnWkQsV8Tq2*6BsX`5)G$v}pqrA8-N&a)fpSa~Gne%XN})#qO@>Yh!1sO% z7fRqtOW9K{96@nXJsgh>6WH0=O9Or*{EhD3O+C;5{Vs8=Y-lTvQ!3+_#`<$YZl7nF zt7HibVf+^yfw(A@)G<+Qp}A*d`TgTJTV6$v6i?i|usI?mGSJ|a3MSe%mf#ntE~l*)U`_}y7cb}-d@z)Mtp7Y&RQR*sX5ymge%5x} z{oItZ203y}9!CA4 z&XV((do-+0bF*ml$U8b%>7!siZz(%}EsRj5%z0<;Yw#QSR3q^oU9lXIhfO>9fxEk_ zL$64kHnpM0NaxTCWqybC`He{a=in45vJoigyCtu zS5YjYH=2yeD1&TEW2x<2|R zF!&9hir-*8#`4qqR@E(g>UJk<89e>ENrgw=Jz4=;Y!m9{ss3ID{0-kCJ%&MO%zXCu ze163%!)>w6Mxf~wtn^%n#Ozh!Y8Y%d%Gvu{A*)(bfiixyQyjH8*S!0ZS=hPb;WC}R zkj$A&(NPUN=( zVo>WmUcPpBbt6H|Gc8ZWA5KCAq4ouXwlX2?kt!Rd&x| zW+L{4O2)VH5V~v!T9UR9MRy{<(UiIJa9cgAZLkQl@WtRdeQ9jHsm$ykWztDz7}Y3~ z3R%vKS*cgEz`A^5l4rr<2QuRXP8vMsEl(84o-^Gwo{Q?0rWJ`3?;j4({oF`JD0W;t z)~<+7Td@^z{Bp-UhO&q2saBY6^z<}KY+1i&XvpbK0bK6gzR;k|5UIgJfO78^>$wzCZpGvom@cn&#|wytO4JW|;d{tRTRIfmJ|~ zsRR0m1(r9^oxp3~9&$!@6B9kya#~NRpvS!#$J0o-%t&UgUcr;?vlmwnshZI&>09$H zX9@3E;UoEK_#+0wKnP{Q@eXou_u0MY8`chkS(jH3+|ce-%c z;1biTN5$+Fr&UqYb4uR$;y7tp36$UF_w32pTqW;Xmeo(E$f4X_l+25lsth@M5M_cZXSV5d~w`D_Bz;pLY%@{u^BR#E!rIaOwUBzkf?JzGqCegzkx*0|dCJPmJ=4{DHgB5$g19k=(@y#|;&$3e7-h8cu?<$L< z+q?YQ^$OZ1Pa{yX)j)c>_*Gh$d6nDvisq!Gq!8t8vWex-M%>3W5iE{s_Sw2f;;6}1a^Il_SPF0`jLe|s$Q^H^p*9~3;H^wb(Y=3UjI7hXI0ZKkyGx~h?650xb4m2R};fSg} z+LTUKjovTxVn)l7k80X;&pS=EW?S6uAz_kA2)ff~8;SMm+33^VBBebAqk{qch_4c` zFBqrEkp0}5)#rv`YlIP0f&25r&c#-3?`B|02G5n7l)+7)jIl!5HM>3toN@@4(9ByC+9-1RWNmxq$V2;uN zpRglK`JB2+~n*+vCe!cv&nlkS<}K(`nB#Z{Cd) zHn)9Ib`Pa)plmayf7LK6i$v%O^D|5$BQCS>Di7usOn7Je&jm(XR04Z+{ui1;f8yGW z``HGj^tm_AJA9G-o|GRwce3Ia=sb5<_Ig&dZP667dK?#2fns8qCDW11o3LP5LP`im zmaa8x=izK?X2Kqattn~sbWVpQ!LC%CS`D)6>vuFK)a~$HSk4qny6CxnT_`V11XGx za#-Fg1{JH(fAx%vQcF50zM-cEuquFCVjV`gC;Muu?w3S0yE7!+G%qX!KU&j#{Dm}l zT<-N`wPkEPh+&3J{0;1XQD?kH6ABNPfY6_ZeHlqlku{`a6MqMb__HA5VzkTQ;ddca z2BBqwi4wO2z)m_sBfvM#Y4)`-3N7Wp&|tlTW;1wq90(H0(R%}AlzU5nmgK_Opt-LL zFrW1W;9lJV2dUrgk{E7=_p@!$JQT z=$-v*@^38vCei=-?lGR5jJUaF3VoZFc7=(F$?Iotvgu;r8v&sDNsGcW-fEs@ja)0+ zHW&fg5a*fqWz;z+?#SdkzcRJ}ad2V0xCTP^_(@mn*At+$rW~bxggj|In{mxl=e&UGC0eVs{}Da`INJ0rEptjMDhQ3P zXwru2=|PW1|JNc-?|H(5w)?eq`hR$@-evOo)Mm^@v7ht)i|Ku-S9@$%B4`jkh>CS5 z(0cTJnzog|ogR~()!ibs?v|QuT7D!{9Co;dg&4pyrEQix1Ov9gKxbeOK{@p&y{lTP z$Pev*z1SldCV5QS@;s5WX?f7%Pm&Hf+^71pCXZ-DLYVrl+3VPCLfKp2W@V+3?en>A zM123`dN=YMM`;LcUp8zZ*!#^?sq@8A!5;vTqbMGqHx&TrLp`WAzCVSwLAm40H$9v2 zoTKT_;9jxW%Up=y%b#2(Q$qbIsS@a>FZDA3$NZt4^@&Om^7?ctejsDueenz-tS6D5H>tt(YfZ%7=B&B0|CU>Ko$rPj@r5P;`4t7B zDT(|l@K+2hc0hzoK3K{Vr#_&nAAJ-)5xJ@6G28GVdwc;KWWTneS>{|5ifO}P=D&zNNq4UlJ7>_k&&l}niY?4EduO&DPtD3cZVz`` z3Hi|{_J<^7{eC@io@h!w+OJ)7m~~vMDcU)vEDl(Cwuw$wnJbbu6oyW3>U&I_tbh6VnH)`p#i5+^ECAE-3uSlrk=cE&g@b$Ol!5Cm&I=9RtInt)^> zj#nf-_+awTHsFMwN+lGOkv2nC{1uZtv1Bvq3tH075i_*6iwoff(OZ0Hj>+dHlw04Jaa#)?LvVrw8+-!Ih~GR)ipN zG~vic0@2p`2W1W^Bxqk>U$KTU&y>q}Exs>gF!2aq4;+U+RKxS3-SONh4}|s6tvmhS z+}@jga4`#Su^fd_X^W>G5YYzB?LxPi+TxD#P0FtskvMgP-NMvv46DXo4Yg>RZH&XX zQ-@_}-EeU{?#iL>)?D*WH?OB2YOy}K7T*s~y=w5|vtq;x)dQ*Ojv@jGy5;k+^>}}_ z)|+4$xI3gc^rYN&>CK|trP)ueV!p4^S64IzG9My7$a-R|J{y{0Y^4_6kB48kphL-g zc?`d5?FZ~SqgZ6USML!o#ryZAbglk~WH5jFTTAKEUS-F-s@|?RuAVUKh8`F8c{w`W zBp!G56=Jf{-DWTRpw%^dLv&gR?f0Y&81i-gdzTLS$=;7|T=*%Cd6zpzi+}WuwI|j- zDhInA-_Ah~b8NrfsyT)2IhSKvkZvKjT}L$+LueFxB=oTKeu>VXesqN>po(H)td$9V4fLB3a= z)-1u;1Ft^O%-$cOckMd9&^u^9D$!xNm^OOsec1Ap8hZ~q{lVPa1GnPmJX);%i<~ZD z)-ABx?hd%WdDx|ciT;SpU-!rHZ4td$nAAvRD0G_{#*%*)A-JZF42nT>`o@JC80sq! zHut_BR$d zfA^k1{Ly$jiA9#cj6f1asZWVq9q%`%mtq!;1fkY7UtiZpSDWzqJc?C*30k zdjvg3@bQ9Jbgo=l@hmR}cX>peQcaDR5?rsyM_xZP`?Y~OJuZ(A3e2sxZkByipl#Qa z$iLdws8mPjeI0WVPg~-9inMHbfVQ5g&!!Mn16OAp?G9>|cSdyu``T}xF=+BQ@$-J3 z6`06mV^gH9$WoxOxbNsKF<9?BKDa45?bke2eWH`d$2~8#@7r-iFYB@=K7$QvTwC6q zs5C7LJwE(MSrJ?d>^0J$KG^H8&qqz9TkP!%s!h00wmg)q-h;%W8Fpvo-kw!g{@O%b zu0g*pF~?405Ktgh2QbVF{(3?tm0ptwoK+ZifR^mbVlfptmZ%(5I2v*n(0g=~1cuzX z>X2ipM~7med^~8{vw&C=*zP3b;4U~1J*irV%bdfw+ulOqU|i{C6z-bHuwyr<_keWD&0VuXbukvV(@21CE!T{#(NY!6@&5D5?G zg3zHyGLQN1kuQZn%w1i>KsikX%BiV*HZ!1< zzzYM4_8lC7L0TarQ)y|bUS|}cN2hdzx$YxGGM*kRVlGff0vs!w}!MeoYxjA!eIr Date: Tue, 13 Nov 2018 15:24:39 -0500 Subject: [PATCH 007/120] Rename files --- ...g.json => singlestat_ombi_requests_pending.json} | 0 ...ing.png => singlestat_ombi_requests_pending.png} | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename dashboard/example_panels/{ombi_requests_pending.json => singlestat_ombi_requests_pending.json} (100%) rename dashboard/example_panels/{ombi_requests_pending.png => singlestat_ombi_requests_pending.png} (100%) diff --git a/dashboard/example_panels/ombi_requests_pending.json b/dashboard/example_panels/singlestat_ombi_requests_pending.json similarity index 100% rename from dashboard/example_panels/ombi_requests_pending.json rename to dashboard/example_panels/singlestat_ombi_requests_pending.json diff --git a/dashboard/example_panels/ombi_requests_pending.png b/dashboard/example_panels/singlestat_ombi_requests_pending.png similarity index 100% rename from dashboard/example_panels/ombi_requests_pending.png rename to dashboard/example_panels/singlestat_ombi_requests_pending.png From 8306986ca329f2382aad92c1a88d3e51c6514bc0 Mon Sep 17 00:00:00 2001 From: samwiseg0 Date: Thu, 15 Nov 2018 14:21:17 -0500 Subject: [PATCH 008/120] Fix sonarr queue pannel --- .../example_panels/table_sonarr_queue.json | 81 ++++++++++--------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/dashboard/example_panels/table_sonarr_queue.json b/dashboard/example_panels/table_sonarr_queue.json index 018f181..a959a03 100644 --- a/dashboard/example_panels/table_sonarr_queue.json +++ b/dashboard/example_panels/table_sonarr_queue.json @@ -3,13 +3,14 @@ "datasource": "influxdb", "fontSize": "100%", "gridPos": { - "h": 7, + "h": 8, "w": 4, "x": 20, - "y": 14 + "y": 21 }, "hideTimeOverride": true, - "id": 16, + "id": 19, + "interval": "30s", "links": [ { "targetBlank": true, @@ -19,7 +20,7 @@ } ], "minSpan": 8, - "pageSize": 6, + "pageSize": 7, "scroll": true, "showHeader": true, "sort": { @@ -37,7 +38,7 @@ "alias": "Name", "colorMode": "cell", "colors": [ - "#e24d42", + "rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)" ], @@ -57,46 +58,36 @@ "alias": "", "colorMode": "row", "colors": [ - "#e24d42", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" + "#0a437c", + "rgb(168, 147, 4)", + "#629e51" ], "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, + "decimals": 0, "mappingType": 1, - "pattern": "TV Show", - "thresholds": [], + "pattern": "T", + "thresholds": [ + "-1", + "1" + ], "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "sonarrId", - "thresholds": [], - "type": "hidden", - "unit": "short" + "unit": "short", + "valueMaps": [ + { + "text": "Tor", + "value": "0" + }, + { + "text": "Use", + "value": "1" + } + ] } ], "targets": [ { "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "sonarrId" - ], - "type": "tag" - } - ], + "groupBy": [], "measurement": "Sonarr", "orderByTime": "ASC", "policy": "default", @@ -130,20 +121,34 @@ ], "type": "alias" } + ], + [ + { + "params": [ + "protocol_id" + ], + "type": "field" + }, + { + "params": [ + "T" + ], + "type": "alias" + } ] ], "tags": [ { "key": "type", "operator": "=", - "value": "Missing_Days" + "value": "Queue" } ] } ], "timeFrom": "33s", "timeShift": null, - "title": "Missing TV Shows (Last 7 Days)", + "title": "TV Shows in Queue", "transform": "table", "type": "table" } From cd56692834a9a228361ef126770e171c94127623 Mon Sep 17 00:00:00 2001 From: samwiseg0 Date: Thu, 15 Nov 2018 15:22:59 -0500 Subject: [PATCH 009/120] Fix US world map panel --- dashboard/panel_us_only_worldmap.json | 30 +++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/dashboard/panel_us_only_worldmap.json b/dashboard/panel_us_only_worldmap.json index e2984c4..99587c7 100644 --- a/dashboard/panel_us_only_worldmap.json +++ b/dashboard/panel_us_only_worldmap.json @@ -1,4 +1,4 @@ -{ +{ "circleMaxSize": 30, "circleMinSize": "1", "colors": [ @@ -6,13 +6,13 @@ "#c15c17", "#890f02" ], - "datasource": "plex", + "datasource": "influxdb", "decimals": 0, "esLocationName": "", "esMetric": "$tag_counter", "hideEmpty": false, "hideZero": false, - "id": 4, + "id": 41, "initialZoom": "4", "links": [], "locationData": "states", @@ -29,10 +29,10 @@ "dsType": "influxdb", "groupBy": [ { + "type": "tag", "params": [ "region_code" - ], - "type": "tag" + ] } ], "measurement": "Tautulli", @@ -43,20 +43,24 @@ "select": [ [ { + "type": "field", "params": [ - "location" - ], - "type": "field" + "session_key" + ] }, { - "params": [], - "type": "count" + "type": "distinct", + "params": [] }, { + "type": "count", + "params": [] + }, + { + "type": "alias", "params": [ "metric" - ], - "type": "alias" + ] } ] ], @@ -79,7 +83,7 @@ "valueName": "current", "gridPos": { "x": 16, - "y": 0, + "y": 7, "w": 8, "h": 8 }, From a312edabb8366b550a91fdbbe672b58827ce5541 Mon Sep 17 00:00:00 2001 From: samwiseg0 Date: Mon, 19 Nov 2018 17:40:12 -0500 Subject: [PATCH 010/120] Fix relay lookup failure using geoip --- tautulli.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tautulli.py b/tautulli.py index e92fb4f..9fc06be 100644 --- a/tautulli.py +++ b/tautulli.py @@ -2,12 +2,15 @@ import os import tarfile import urllib.request import time -from datetime import datetime, timezone import geoip2.database -from influxdb import InfluxDBClient import requests import configuration +from geoip2.errors import AddressNotFoundError +from influxdb import InfluxDBClient +from datetime import datetime, timezone + + CURRENT_TIME = datetime.now(timezone.utc).astimezone().isoformat() PAYLOAD = {'apikey': configuration.tautulli_api_key, 'cmd': 'get_activity'} @@ -74,7 +77,7 @@ INFLUX_PAYLOAD = [ for session in SESSIONS.keys(): try: geodata = geo_lookup(SESSIONS[session]['ip_address_public']) - except (ValueError, geoip2.errors.AddressNotFoundError): + except (ValueError, AddressNotFoundError): if configuration.tautulli_failback_ip: geodata = geo_lookup(configuration.tautulli_failback_ip) else: From 5b3e9d864908a3cbd4a161764ab206c57676c968 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 10:55:58 -0600 Subject: [PATCH 011/120] PEP8 Cleanup --- sonarr.py | 116 ++++++++++++++++++++---------------------------------- 1 file changed, 43 insertions(+), 73 deletions(-) diff --git a/sonarr.py b/sonarr.py index 8be5f6c..f504b74 100644 --- a/sonarr.py +++ b/sonarr.py @@ -9,8 +9,8 @@ import configuration def now_iso(): - now_iso = datetime.now(timezone.utc).astimezone().isoformat() - return now_iso + now = datetime.now(timezone.utc).astimezone().isoformat() + return now def influx_sender(influx_payload): @@ -22,10 +22,7 @@ def influx_sender(influx_payload): def get_all_missing_shows(): # Set the time here so we have one timestamp to work with now = now_iso() - - missing = [] - - influx_payload = [] + missing, influx_payload = [], [] for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: @@ -36,19 +33,18 @@ def get_all_missing_shows(): tv_shows = {d['id']: d for d in get_tv_shows} - for show in tv_shows.keys(): series_title = '{}'.format(tv_shows[show]['series']['title']) - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'],tv_shows[show]['episodeNumber']) + sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) missing.append((series_title, sxe, tv_shows[show]['id'], tv_shows[show]['title'])) - for series_title, sxe, id, episode_title in missing: + for series_title, sxe, sonarr_id, episode_title in missing: influx_payload.append( { "measurement": "Sonarr", "tags": { "type": "Missing", - "sonarrId": id, + "sonarrId": sonarr_id, "server": server_id }, "time": now, @@ -68,20 +64,16 @@ def get_all_missing_shows(): def get_missing_shows(days_past): # Set the time here so we have one timestamp to work with now = now_iso() - last_days = str(date.today()+timedelta(days=-days_past)) - today = str(date.today()) - - missing = [] - - influx_payload = [] + missing, influx_payload = [], [] for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: headers = {'X-Api-Key': sonarr_api_key} - get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=1000'.format(sonarr_url, last_days, today), + get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=1000'.format(sonarr_url, last_days, + today), headers=headers).json() tv_shows = {d['id']: d for d in get_tv_shows} @@ -93,13 +85,13 @@ def get_missing_shows(days_past): air_date = (tv_shows[show]['airDate']) missing.append((series_title, sxe, air_date, tv_shows[show]['id'])) - for series_title, sxe, air_date, id in missing: + for series_title, sxe, air_date, sonarr_id in missing: influx_payload.append( { "measurement": "Sonarr", "tags": { "type": "Missing_Days", - "sonarrId": id, + "sonarrId": sonarr_id, "server": server_id }, "time": now, @@ -120,41 +112,39 @@ def get_missing_shows(days_past): def get_upcoming_shows(): # Set the time here so we have one timestamp to work with now = now_iso() - upcoming = [] - influx_payload = [] for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: headers = {'X-Api-Key': sonarr_api_key} - get_upcoming_shows = requests.get('{}/api/calendar/'.format(sonarr_url), - headers=headers).json() + upcoming_shows_request = requests.get('{}/api/calendar/'.format(sonarr_url), headers=headers).json() - upcoming_shows = {d['id']: d for d in get_upcoming_shows} + upcoming_shows = {d['id']: d for d in upcoming_shows_request} for show in upcoming_shows.keys(): series_title = '{}'.format(upcoming_shows[show]['series']['title']) - sxe = 'S{:0>2}E{:0>2}'.format(upcoming_shows[show]['seasonNumber'],upcoming_shows[show]['episodeNumber']) - upcoming.append((series_title, sxe, upcoming_shows[show]['id'], upcoming_shows[show]['title'], upcoming_shows[show]['airDate'])) + sxe = 'S{:0>2}E{:0>2}'.format(upcoming_shows[show]['seasonNumber'], upcoming_shows[show]['episodeNumber']) + upcoming.append((series_title, sxe, upcoming_shows[show]['id'], upcoming_shows[show]['title'], + upcoming_shows[show]['airDate'])) - for series_title, sxe, id, episode_title, air_date in upcoming: + for series_title, sxe, sonarr_id, episode_title, air_date in upcoming: influx_payload.append( { "measurement": "Sonarr", "tags": { "type": "Soon", - "sonarrId": id, + "sonarrId": sonarr_id, "server": server_id - }, - "time": now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "airs": air_date - } + }, + "time": now, + "fields": { + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "airs": air_date + } } ) # Empty upcoming or else things get foo bared @@ -168,13 +158,8 @@ def get_future_shows(future_days): now = now_iso() today = str(date.today()) - future = str(date.today()+timedelta(days=future_days)) - air_days = [] - - downloaded = [] - influx_payload = [] for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: @@ -190,15 +175,16 @@ def get_future_shows(future_days): series_title = '{}'.format(tv_shows[show]['series']['title']) dl_status = int(tv_shows[show]['hasFile']) sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) - air_days.append((series_title, dl_status, sxe, tv_shows[show]['title'], tv_shows[show]['airDate'], tv_shows[show]['id'])) + air_days.append((series_title, dl_status, sxe, tv_shows[show]['title'], tv_shows[show]['airDate'], + tv_shows[show]['id'])) - for series_title, dl_status, sxe, episode_title, air_date, id in air_days: + for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: influx_payload.append( { "measurement": "Sonarr", "tags": { "type": "Future", - "sonarrId": id, + "sonarrId": sonarr_id, "server": server_id }, "time": now, @@ -220,11 +206,7 @@ def get_future_shows(future_days): def get_queue_shows(): # Set the time here so we have one timestamp to work with now = now_iso() - queue = [] - - downloaded = [] - influx_payload = [] for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: @@ -239,8 +221,9 @@ def get_queue_shows(): for show in tv_shows.keys(): series_title = '{}'.format(tv_shows[show]['series']['title']) episode_title = '{}'.format(tv_shows[show]['episode']['title']) - protocol = (tv_shows[show]['protocol'].upper()) - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['episode']['seasonNumber'], tv_shows[show]['episode']['episodeNumber']) + protocol = tv_shows[show]['protocol'].upper() + sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['episode']['seasonNumber'], + tv_shows[show]['episode']['episodeNumber']) if protocol == 'USENET': protocol_id = 1 else: @@ -248,13 +231,13 @@ def get_queue_shows(): queue.append((series_title, episode_title, protocol, protocol_id, sxe, tv_shows[show]['id'])) - for series_title, episode_title, protocol, protocol_id, sxe, id in queue: + for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue: influx_payload.append( { "measurement": "Sonarr", "tags": { "type": "Queue", - "sonarrId": id, + "sonarrId": sonarr_id, "server": server_id }, @@ -277,41 +260,28 @@ def get_queue_shows(): if __name__ == "__main__": parser = argparse.ArgumentParser(prog='Sonarr stats operations', - description='Script to aid in data gathering from Sonarr', formatter_class=RawTextHelpFormatter) + description='Script to aid in data gathering from Sonarr', + formatter_class=RawTextHelpFormatter) - parser.add_argument("--missing", action='store_true', - help='Get all missing TV shows') - - parser.add_argument("--missing_days", type=int, - help='Get missing TV shows in past X days') - - parser.add_argument("--upcoming", action='store_true', - help='Get upcoming TV shows') - - parser.add_argument("--future", type=int, - help='Get TV shows on X days into the future. Includes today.' - '\ni.e. --future 2 is Today and Tomorrow') - - parser.add_argument("--queue", action='store_true', - help='Get TV shows in queue') + parser.add_argument("--missing", action='store_true', help='Get all missing TV shows') + parser.add_argument("--missing_days", type=int, help='Get missing TV shows in past X days') + parser.add_argument("--upcoming", action='store_true', help='Get upcoming TV shows') + parser.add_argument("--future", type=int, help='Get TV shows on X days into the future. Includes today.' + '\ni.e. --future 2 is Today and Tomorrow') + parser.add_argument("--queue", action='store_true', help='Get TV shows in queue') opts = parser.parse_args() if opts.missing: influx_sender(get_all_missing_shows()) - elif opts.missing_days: influx_sender(get_missing_shows(opts.missing_days)) - elif opts.upcoming: influx_sender(get_upcoming_shows()) - elif opts.future: influx_sender(get_future_shows(opts.future)) - elif opts.queue: influx_sender(get_queue_shows()) - elif len(sys.argv) == 1: parser.print_help(sys.stderr) sys.exit(1) From 32b20c025c63eb1b3728b762c60df9c4b048b965 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 14:32:39 -0600 Subject: [PATCH 012/120] sonarr.py overhaul --- helpers.py | 42 ++++++ sonarr.py | 401 ++++++++++++++++++++++------------------------------- 2 files changed, 210 insertions(+), 233 deletions(-) create mode 100644 helpers.py diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..c30773f --- /dev/null +++ b/helpers.py @@ -0,0 +1,42 @@ +from typing import NamedTuple + + +class TVShow(NamedTuple): + seriesId: int + episodeFileId: int + seasonNumber: int + episodeNumber: int + title: str + airDate: str + airDateUtc: str + overview: str + episodeFile: dict + hasFile: bool + monitored: bool + unverifiedSceneNumbering: bool + absoluteEpisodeNumber: int + series: dict + id: int + + +class Queue(NamedTuple): + series: dict + episode: dict + quality: dict + size: float + title: str + sizeleft: float + timeleft: str + estimatedCompletionTime: str + status: str + trackedDownloadStatus: str + statusMessages: list + downloadId: str + protocol: str + id: int + + +class Server(NamedTuple): + url: str + api_key: str + id: int diff --git a/sonarr.py b/sonarr.py index f504b74..f62c0b4 100644 --- a/sonarr.py +++ b/sonarr.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # Do not edit this script. Edit configuration.py import sys import requests @@ -5,257 +6,183 @@ from datetime import datetime, timezone, date, timedelta from influxdb import InfluxDBClient import argparse from argparse import RawTextHelpFormatter -import configuration +import configuration as config +from helpers import Server, TVShow, Queue -def now_iso(): - now = datetime.now(timezone.utc).astimezone().isoformat() - return now +class SonarrAPI(object): + TVShow.__new__.__defaults__ = (None,) * len(TVShow._fields) + def __init__(self): + self.now = datetime.now(timezone.utc).astimezone().isoformat() + self.today = str(date.today()) + self.influx = InfluxDBClient(config.influxdb_url, config.influxdb_port, config.influxdb_username, + config.influxdb_password, config.sonarr_influxdb_db_name) + self.influx_payload = [] + self.servers = self.get_servers() + self.session = requests.Session() + self.session.params = {'pageSize': 1000} -def influx_sender(influx_payload): - influx = InfluxDBClient(configuration.influxdb_url, configuration.influxdb_port, configuration.influxdb_username, - configuration.influxdb_password, configuration.sonarr_influxdb_db_name) - influx.write_points(influx_payload) + @staticmethod + def get_servers(): + if not config.sonarr_server_list: + sys.exit("No Sonarr servers defined in config") + servers = [] + for url, api_key, server_id in config.sonarr_server_list: + servers.append(Server(url=url, api_key=api_key, id=server_id)) -def get_all_missing_shows(): - # Set the time here so we have one timestamp to work with - now = now_iso() - missing, influx_payload = [], [] + return servers - for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: + def get_missing(self, days_past): + endpoint = '/api/calendar' + last_days = str(date.today() + timedelta(days=-days_past)) + params = {'start': last_days, 'end': self.today} - headers = {'X-Api-Key': sonarr_api_key} + for server in self.servers: + missing = [] + headers = {'X-Api-Key': server.api_key} - get_tv_shows = requests.get('{}/api/wanted/missing/?pageSize=1000'.format(sonarr_url), - headers=headers).json()['records'] + get = self.session.get(server.url + endpoint, params=params, headers=headers).json() + tv_shows = [TVShow(**show) for show in get] - tv_shows = {d['id']: d for d in get_tv_shows} + for show in tv_shows: + if not show.hasFile: + sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) + missing.append((show.series['title'], sxe, show.airDate, show.title, show.id)) - for show in tv_shows.keys(): - series_title = '{}'.format(tv_shows[show]['series']['title']) - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) - missing.append((series_title, sxe, tv_shows[show]['id'], tv_shows[show]['title'])) - - for series_title, sxe, sonarr_id, episode_title in missing: - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Missing", - "sonarrId": sonarr_id, - "server": server_id - }, - "time": now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe + for series_title, sxe, air_date, episode_title, sonarr_id in missing: + self.influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Missing", + "sonarrId": sonarr_id, + "server": server.id + }, + "time": self.now, + "fields": { + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "airs": air_date + } } - } - ) - # Empty missing or else things get foo bared - missing = [] + ) - return influx_payload + def get_upcoming(self): + endpoint = '/api/calendar/' + for server in self.servers: + upcoming = [] + headers = {'X-Api-Key': server.api_key} -def get_missing_shows(days_past): - # Set the time here so we have one timestamp to work with - now = now_iso() - last_days = str(date.today()+timedelta(days=-days_past)) - today = str(date.today()) - missing, influx_payload = [], [] + get = self.session.get(server.url + endpoint, headers=headers).json() + tv_shows = [TVShow(**show) for show in get] - for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: + for show in tv_shows: + sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) + upcoming.append((show.series['title'], sxe, show.id, show.title, show.airDate)) - headers = {'X-Api-Key': sonarr_api_key} - - get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=1000'.format(sonarr_url, last_days, - today), - headers=headers).json() - - tv_shows = {d['id']: d for d in get_tv_shows} - - for show in tv_shows.keys(): - if not (tv_shows[show]['hasFile']): - series_title = '{}'.format(tv_shows[show]['series']['title']) - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) - air_date = (tv_shows[show]['airDate']) - missing.append((series_title, sxe, air_date, tv_shows[show]['id'])) - - for series_title, sxe, air_date, sonarr_id in missing: - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Missing_Days", - "sonarrId": sonarr_id, - "server": server_id - }, - "time": now, - "fields": { - "name": series_title, - "sxe": sxe, - "airs": air_date + for series_title, sxe, sonarr_id, episode_title, air_date in upcoming: + self.influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Soon", + "sonarrId": sonarr_id, + "server": server.id + }, + "time": self.now, + "fields": { + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "airs": air_date + } } - } - ) + ) - # Empty missing or else things get foo bared - missing = [] + def get_future(self, future_days): + endpoint = '/api/calendar/' + future = str(date.today() + timedelta(days=future_days)) - return influx_payload + for server in self.servers: + air_days = [] + headers = {'X-Api-Key': server.api_key} + params = {'start': self.today, 'end': future} + get = self.session.get(server.url + endpoint, params=params, headers=headers).json() + tv_shows = [TVShow(**show) for show in get] -def get_upcoming_shows(): - # Set the time here so we have one timestamp to work with - now = now_iso() - upcoming = [] - influx_payload = [] + for show in tv_shows: + sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) + air_days.append((show.series['title'], show.hasFile, sxe, show.title, show.airDate, show.id)) - for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: - - headers = {'X-Api-Key': sonarr_api_key} - - upcoming_shows_request = requests.get('{}/api/calendar/'.format(sonarr_url), headers=headers).json() - - upcoming_shows = {d['id']: d for d in upcoming_shows_request} - - for show in upcoming_shows.keys(): - series_title = '{}'.format(upcoming_shows[show]['series']['title']) - sxe = 'S{:0>2}E{:0>2}'.format(upcoming_shows[show]['seasonNumber'], upcoming_shows[show]['episodeNumber']) - upcoming.append((series_title, sxe, upcoming_shows[show]['id'], upcoming_shows[show]['title'], - upcoming_shows[show]['airDate'])) - - for series_title, sxe, sonarr_id, episode_title, air_date in upcoming: - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Soon", - "sonarrId": sonarr_id, - "server": server_id - }, - "time": now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "airs": air_date + for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: + self.influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Future", + "sonarrId": sonarr_id, + "server": server.id + }, + "time": self.now, + "fields": { + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "airs": air_date, + "downloaded": dl_status + } } - } - ) - # Empty upcoming or else things get foo bared - upcoming = [] + ) - return influx_payload + def get_queue(self): + endpoint = '/api/queue' + for server in self.servers: + queue = [] + headers = {'X-Api-Key': server.api_key} -def get_future_shows(future_days): - # Set the time here so we have one timestamp to work with - now = now_iso() + get = self.session.get(server.url + endpoint, headers=headers).json() + download_queue = [Queue(**show) for show in get] - today = str(date.today()) - future = str(date.today()+timedelta(days=future_days)) - air_days = [] - influx_payload = [] + for show in download_queue: + sxe = 'S{:0>2}E{:0>2}'.format(show.episode['seasonNumber'], show.episode['episodeNumber']) + if show.protocol.upper() == 'USENET': + protocol_id = 1 + else: + protocol_id = 0 - for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: + queue.append((show.series['title'], show.episode['title'], show.protocol.upper(), + protocol_id, sxe, show.id)) - headers = {'X-Api-Key': sonarr_api_key} + for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue: + self.influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Queue", + "sonarrId": sonarr_id, + "server": server.id - get_tv_shows = requests.get('{}/api/calendar/?start={}&end={}&pageSize=200'.format(sonarr_url, today, future), - headers=headers).json() - - tv_shows = {d['id']: d for d in get_tv_shows} - - for show in tv_shows.keys(): - series_title = '{}'.format(tv_shows[show]['series']['title']) - dl_status = int(tv_shows[show]['hasFile']) - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['seasonNumber'], tv_shows[show]['episodeNumber']) - air_days.append((series_title, dl_status, sxe, tv_shows[show]['title'], tv_shows[show]['airDate'], - tv_shows[show]['id'])) - - for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Future", - "sonarrId": sonarr_id, - "server": server_id - }, - "time": now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "airs": air_date, - "downloaded": dl_status + }, + "time": self.now, + "fields": { + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "protocol": protocol, + "protocol_id": protocol_id + } } - } - ) - # Empty air_days or else things get foo bared - air_days = [] + ) - return influx_payload - - -def get_queue_shows(): - # Set the time here so we have one timestamp to work with - now = now_iso() - queue = [] - influx_payload = [] - - for sonarr_url, sonarr_api_key, server_id in configuration.sonarr_server_list: - - headers = {'X-Api-Key': sonarr_api_key} - - get_tv_shows = requests.get('{}/api/queue'.format(sonarr_url), - headers=headers).json() - - tv_shows = {d['id']: d for d in get_tv_shows} - - for show in tv_shows.keys(): - series_title = '{}'.format(tv_shows[show]['series']['title']) - episode_title = '{}'.format(tv_shows[show]['episode']['title']) - protocol = tv_shows[show]['protocol'].upper() - sxe = 'S{:0>2}E{:0>2}'.format(tv_shows[show]['episode']['seasonNumber'], - tv_shows[show]['episode']['episodeNumber']) - if protocol == 'USENET': - protocol_id = 1 - else: - protocol_id = 0 - - queue.append((series_title, episode_title, protocol, protocol_id, sxe, tv_shows[show]['id'])) - - for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue: - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Queue", - "sonarrId": sonarr_id, - "server": server_id - - }, - "time": now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "protocol": protocol, - "protocol_id": protocol_id - } - } - ) - - # Empty queue or else things get foo bared - queue = [] - - return influx_payload + def influx_push(self): + # TODO: error handling for failed connection + self.influx.write_points(self.influx_payload) if __name__ == "__main__": @@ -263,25 +190,33 @@ if __name__ == "__main__": description='Script to aid in data gathering from Sonarr', formatter_class=RawTextHelpFormatter) - parser.add_argument("--missing", action='store_true', help='Get all missing TV shows') - parser.add_argument("--missing_days", type=int, help='Get missing TV shows in past X days') + parser.add_argument("--missing", metavar='$days', type=int, help='Get missing TV shows in past X days' + '\ni.e. --missing 7 is in the last week') + parser.add_argument("--missing_days", metavar='$days', type=int, help='legacy command. Deprecated in favor of' + ' --missing' + '\nfunctions identically to --missing' + '\nNote: Will be removed in a future release') parser.add_argument("--upcoming", action='store_true', help='Get upcoming TV shows') - parser.add_argument("--future", type=int, help='Get TV shows on X days into the future. Includes today.' - '\ni.e. --future 2 is Today and Tomorrow') + parser.add_argument("--future", metavar='$days', type=int, help='Get TV shows on X days into the future. ' + 'Includes today.' + '\ni.e. --future 2 is Today and Tomorrow') parser.add_argument("--queue", action='store_true', help='Get TV shows in queue') opts = parser.parse_args() + sonarr = SonarrAPI() - if opts.missing: - influx_sender(get_all_missing_shows()) - elif opts.missing_days: - influx_sender(get_missing_shows(opts.missing_days)) - elif opts.upcoming: - influx_sender(get_upcoming_shows()) - elif opts.future: - influx_sender(get_future_shows(opts.future)) - elif opts.queue: - influx_sender(get_queue_shows()) - elif len(sys.argv) == 1: + if len(sys.argv) == 1: parser.print_help(sys.stderr) sys.exit(1) + + if any([opts.missing, opts.missing_days]): + days = opts.missing if opts.missing else opts.missing_days + sonarr.get_missing(days) + if opts.upcoming: + sonarr.get_upcoming() + if opts.future: + sonarr.get_future(opts.future) + if opts.queue: + sonarr.get_queue() + + sonarr.influx_push() From 31464a89b4645e4ee06862a8596956c0aa1acfe8 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 18:08:59 -0600 Subject: [PATCH 013/120] formatting, and some notation --- sonarr.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/sonarr.py b/sonarr.py index f62c0b4..2655937 100644 --- a/sonarr.py +++ b/sonarr.py @@ -2,32 +2,37 @@ # Do not edit this script. Edit configuration.py import sys import requests -from datetime import datetime, timezone, date, timedelta -from influxdb import InfluxDBClient import argparse -from argparse import RawTextHelpFormatter +from influxdb import InfluxDBClient +from datetime import datetime, timezone, date, timedelta + import configuration as config from helpers import Server, TVShow, Queue class SonarrAPI(object): + # Sets None as default for all TVShow NamedTuples, because sonarr's response json is inconsistent TVShow.__new__.__defaults__ = (None,) * len(TVShow._fields) def __init__(self): + # Set Time of initialization self.now = datetime.now(timezone.utc).astimezone().isoformat() self.today = str(date.today()) self.influx = InfluxDBClient(config.influxdb_url, config.influxdb_port, config.influxdb_username, config.influxdb_password, config.sonarr_influxdb_db_name) self.influx_payload = [] self.servers = self.get_servers() + # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = requests.Session() self.session.params = {'pageSize': 1000} @staticmethod def get_servers(): + # Ensure sonarr servers have been defined if not config.sonarr_server_list: sys.exit("No Sonarr servers defined in config") + # Build Server Objects from config servers = [] for url, api_key, server_id in config.sonarr_server_list: servers.append(Server(url=url, api_key=api_key, id=server_id)) @@ -44,8 +49,10 @@ class SonarrAPI(object): headers = {'X-Api-Key': server.api_key} get = self.session.get(server.url + endpoint, params=params, headers=headers).json() + # Iteratively create a list of TVShow Objects from response json tv_shows = [TVShow(**show) for show in get] + # Add show to missing list if file does not exist for show in tv_shows: if not show.hasFile: sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) @@ -188,18 +195,19 @@ class SonarrAPI(object): if __name__ == "__main__": parser = argparse.ArgumentParser(prog='Sonarr stats operations', description='Script to aid in data gathering from Sonarr', - formatter_class=RawTextHelpFormatter) + formatter_class=argparse.RawTextHelpFormatter) - parser.add_argument("--missing", metavar='$days', type=int, help='Get missing TV shows in past X days' - '\ni.e. --missing 7 is in the last week') - parser.add_argument("--missing_days", metavar='$days', type=int, help='legacy command. Deprecated in favor of' - ' --missing' - '\nfunctions identically to --missing' - '\nNote: Will be removed in a future release') + parser.add_argument("--missing", metavar='$days', type=int, + help='Get missing TV shows in past X days' + '\ni.e. --missing 7 is in the last week') + parser.add_argument("--missing_days", metavar='$days', type=int, + help='legacy command. Deprecated in favor of --missing' + '\nfunctions identically to --missing' + '\nNote: Will be removed in a future release') parser.add_argument("--upcoming", action='store_true', help='Get upcoming TV shows') - parser.add_argument("--future", metavar='$days', type=int, help='Get TV shows on X days into the future. ' - 'Includes today.' - '\ni.e. --future 2 is Today and Tomorrow') + parser.add_argument("--future", metavar='$days', type=int, + help='Get TV shows on X days into the future. Includes today.' + '\ni.e. --future 2 is Today and Tomorrow') parser.add_argument("--queue", action='store_true', help='Get TV shows in queue') opts = parser.parse_args() From 472700397a88ad5c069a6b8afeb25c344fcc9207 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 18:29:36 -0600 Subject: [PATCH 014/120] formatting, and some notation --- .gitignore | 3 ++- cisco_asa.py => Varken/cisco_asa.py | 2 +- configuration.example.py => Varken/configuration.example.py | 0 helpers.py => Varken/helpers.py | 0 ombi.py => Varken/ombi.py | 2 +- radarr.py => Varken/radarr.py | 2 +- raid_init.py => Varken/raid_init.py | 0 san.py => Varken/san.py | 0 sonarr.py => Varken/sonarr.py | 4 ++-- tautulli.py => Varken/tautulli.py | 2 +- Varken/varken.py | 0 11 files changed, 8 insertions(+), 7 deletions(-) rename cisco_asa.py => Varken/cisco_asa.py (97%) rename configuration.example.py => Varken/configuration.example.py (100%) rename helpers.py => Varken/helpers.py (100%) rename ombi.py => Varken/ombi.py (98%) rename radarr.py => Varken/radarr.py (99%) rename raid_init.py => Varken/raid_init.py (100%) rename san.py => Varken/san.py (100%) rename sonarr.py => Varken/sonarr.py (99%) rename tautulli.py => Varken/tautulli.py (99%) create mode 100644 Varken/varken.py diff --git a/.gitignore b/.gitignore index 4427ab9..b7dbb72 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ .Trashes ehthumbs.db Thumbs.db -configuration.py +Varken/configuration.py __pycache__ GeoLite2-City.mmdb GeoLite2-City.tar.gz +.idea/ diff --git a/cisco_asa.py b/Varken/cisco_asa.py similarity index 97% rename from cisco_asa.py rename to Varken/cisco_asa.py index 26f439a..bb44c93 100644 --- a/cisco_asa.py +++ b/Varken/cisco_asa.py @@ -3,7 +3,7 @@ import requests from datetime import datetime, timezone from influxdb import InfluxDBClient -import configuration +from Varken import configuration current_time = datetime.now(timezone.utc).astimezone().isoformat() diff --git a/configuration.example.py b/Varken/configuration.example.py similarity index 100% rename from configuration.example.py rename to Varken/configuration.example.py diff --git a/helpers.py b/Varken/helpers.py similarity index 100% rename from helpers.py rename to Varken/helpers.py diff --git a/ombi.py b/Varken/ombi.py similarity index 98% rename from ombi.py rename to Varken/ombi.py index 5aa1812..898b8c8 100644 --- a/ombi.py +++ b/Varken/ombi.py @@ -5,7 +5,7 @@ from datetime import datetime, timezone from influxdb import InfluxDBClient import argparse from argparse import RawTextHelpFormatter -import configuration +from Varken import configuration headers = {'Apikey': configuration.ombi_api_key} diff --git a/radarr.py b/Varken/radarr.py similarity index 99% rename from radarr.py rename to Varken/radarr.py index 2358a73..25ddff9 100644 --- a/radarr.py +++ b/Varken/radarr.py @@ -5,7 +5,7 @@ from datetime import datetime, timezone from influxdb import InfluxDBClient import argparse from argparse import RawTextHelpFormatter -import configuration +from Varken import configuration def now_iso(): diff --git a/raid_init.py b/Varken/raid_init.py similarity index 100% rename from raid_init.py rename to Varken/raid_init.py diff --git a/san.py b/Varken/san.py similarity index 100% rename from san.py rename to Varken/san.py diff --git a/sonarr.py b/Varken/sonarr.py similarity index 99% rename from sonarr.py rename to Varken/sonarr.py index 2655937..203a30e 100644 --- a/sonarr.py +++ b/Varken/sonarr.py @@ -6,8 +6,8 @@ import argparse from influxdb import InfluxDBClient from datetime import datetime, timezone, date, timedelta -import configuration as config -from helpers import Server, TVShow, Queue +from Varken import configuration as config +from Varken.helpers import Server, TVShow, Queue class SonarrAPI(object): diff --git a/tautulli.py b/Varken/tautulli.py similarity index 99% rename from tautulli.py rename to Varken/tautulli.py index 96f61ed..70656df 100644 --- a/tautulli.py +++ b/Varken/tautulli.py @@ -6,7 +6,7 @@ from datetime import datetime, timezone import geoip2.database from influxdb import InfluxDBClient import requests -import configuration +from Varken import configuration CURRENT_TIME = datetime.now(timezone.utc).astimezone().isoformat() diff --git a/Varken/varken.py b/Varken/varken.py new file mode 100644 index 0000000..e69de29 From 7e01d026d875d8d2972d60d83c90a7d552036c8b Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 19:26:16 -0600 Subject: [PATCH 015/120] added example config.ini --- .gitignore | 1 + Varken/varken.example.ini | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 Varken/varken.example.ini diff --git a/.gitignore b/.gitignore index b7dbb72..5db3f8d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ ehthumbs.db Thumbs.db Varken/configuration.py +Varken/config.ini __pycache__ GeoLite2-City.mmdb GeoLite2-City.tar.gz diff --git a/Varken/varken.example.ini b/Varken/varken.example.ini new file mode 100644 index 0000000..4bfa5bf --- /dev/null +++ b/Varken/varken.example.ini @@ -0,0 +1,62 @@ +#Notes: +# - Sonarr + Radarr scripts support multiple servers. You can remove the second +# server by putting a # in front of the lines and section name, and removing +# that number from your server_ids list +# - tautulli_failback_ip, This is used when there is no IP listed in tautulli. +# This can happen when you are streaming locally. This is usually your public IP. + +[global] +sonarr_server_ids = 1,2 +radarr_server_ids = 1,2 +ombi = true +tautulli = true +asa = false + +[influxdb] +url = influxdb.domain.tld +port = 8086 +username = root +password = root + +[sonarr-1] +url = sonarr1.domain.tld +apikey = xxxxxxxxxxxxxxxx +#ssl = true +#verify_ssl = false + +[sonarr-2] +url = sonarr2.domain.tld +apikey = yyyyyyyyyyyyyyyy +#ssl = true +#verify_ssl = false + +[radarr-1] +url = radarr1.domain.tld +apikey = xxxxxxxxxxxxxxxx +#ssl = true +#verify_ssl = false + +[radarr-2] +url = radarr2.domain.tld +apikey = yyyyyyyyyyyyyyyy +#ssl = true +#verify_ssl = false + +[ombi] +url = ombi.domain.tld +apikey = xxxxxxxxxxxxxxxx +#ssl = true +#verify_ssl = false + +[tautulli] +url = tautulli.domain.tld +tautulli_failback_ip = 0.0.0.0 +apikey = xxxxxxxxxxxxxxxx +#ssl = true +#verify_ssl = false +influx_db = plex + +[asa] +username = cisco +password = cisco +influx_db = asa From 9d526cd79ce868d46ee95a2d48897b7c2c02b919 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 19:31:38 -0600 Subject: [PATCH 016/120] minor change in example --- Varken/varken.example.ini | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Varken/varken.example.ini b/Varken/varken.example.ini index 4bfa5bf..9d550f4 100644 --- a/Varken/varken.example.ini +++ b/Varken/varken.example.ini @@ -1,4 +1,4 @@ -#Notes: +# Notes: # - Sonarr + Radarr scripts support multiple servers. You can remove the second # server by putting a # in front of the lines and section name, and removing # that number from your server_ids list @@ -21,39 +21,39 @@ password = root [sonarr-1] url = sonarr1.domain.tld apikey = xxxxxxxxxxxxxxxx -#ssl = true -#verify_ssl = false +ssl = false +verify_ssl = true [sonarr-2] url = sonarr2.domain.tld apikey = yyyyyyyyyyyyyyyy -#ssl = true -#verify_ssl = false +ssl = false +verify_ssl = true [radarr-1] url = radarr1.domain.tld apikey = xxxxxxxxxxxxxxxx -#ssl = true -#verify_ssl = false +ssl = false +verify_ssl = true [radarr-2] url = radarr2.domain.tld apikey = yyyyyyyyyyyyyyyy -#ssl = true -#verify_ssl = false +ssl = false +verify_ssl = true [ombi] url = ombi.domain.tld apikey = xxxxxxxxxxxxxxxx -#ssl = true -#verify_ssl = false +ssl = false +verify_ssl = true [tautulli] url = tautulli.domain.tld tautulli_failback_ip = 0.0.0.0 apikey = xxxxxxxxxxxxxxxx -#ssl = true -#verify_ssl = false +ssl = false +verify_ssl = true influx_db = plex [asa] From c7c7e34bfef9273f674e29aee74f2247b3272070 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 21:20:32 -0600 Subject: [PATCH 017/120] added default values to helper classes --- .gitignore | 1 + Varken/__init__.py | 0 Varken/helpers.py | 79 +++++++++++++++++++++++++++------------------- 3 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 Varken/__init__.py diff --git a/.gitignore b/.gitignore index 5db3f8d..942e550 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ __pycache__ GeoLite2-City.mmdb GeoLite2-City.tar.gz .idea/ +Varken/varken.ini diff --git a/Varken/__init__.py b/Varken/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Varken/helpers.py b/Varken/helpers.py index c30773f..45157f2 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -2,41 +2,56 @@ from typing import NamedTuple class TVShow(NamedTuple): - seriesId: int - episodeFileId: int - seasonNumber: int - episodeNumber: int - title: str - airDate: str - airDateUtc: str - overview: str - episodeFile: dict - hasFile: bool - monitored: bool - unverifiedSceneNumbering: bool - absoluteEpisodeNumber: int - series: dict - id: int + seriesId: int = None + episodeFileId: int = None + seasonNumber: int = None + episodeNumber: int = None + title: str = None + airDate: str = None + airDateUtc: str = None + overview: str = None + episodeFile: dict = None + hasFile: bool = None + monitored: bool = None + unverifiedSceneNumbering: bool = None + absoluteEpisodeNumber: int = None + series: dict = None + id: int = None class Queue(NamedTuple): - series: dict - episode: dict - quality: dict - size: float - title: str - sizeleft: float - timeleft: str - estimatedCompletionTime: str - status: str - trackedDownloadStatus: str - statusMessages: list - downloadId: str - protocol: str - id: int + series: dict = None + episode: dict = None + quality: dict = None + size: float = None + title: str = None + sizeleft: float = None + timeleft: str = None + estimatedCompletionTime: str = None + status: str = None + trackedDownloadStatus: str = None + statusMessages: list = None + downloadId: str = None + protocol: str = None + id: int = None class Server(NamedTuple): - url: str - api_key: str - id: int + id: int = None + url: str = None + api_key: str = None + verify_ssl: bool = False + + +class TautulliServer(NamedTuple): + url: str = None + fallback_ip: str = None + apikey: str = None + verify_ssl: bool = None + influx_db: str = None + +class InfluxServer(NamedTuple): + url: str = 'localhost' + port: int = 8086 + username: str = 'root' + password: str = 'root' \ No newline at end of file From 0f06236117f5ea97fcaa9a21df80be99f10806f8 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 21:20:56 -0600 Subject: [PATCH 018/120] added missing keys to example.ini --- Varken/varken.example.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Varken/varken.example.ini b/Varken/varken.example.ini index 9d550f4..c037f55 100644 --- a/Varken/varken.example.ini +++ b/Varken/varken.example.ini @@ -50,13 +50,16 @@ verify_ssl = true [tautulli] url = tautulli.domain.tld -tautulli_failback_ip = 0.0.0.0 +fallback_ip = 0.0.0.0 apikey = xxxxxxxxxxxxxxxx ssl = false verify_ssl = true influx_db = plex [asa] +url = firewall.domain.tld username = cisco password = cisco influx_db = asa +ssl = false +verify_ssl = true \ No newline at end of file From fd450df774239ca1075cef24cf13970176f88f28 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 21:21:17 -0600 Subject: [PATCH 019/120] Created INIParser.py to read config file --- Varken/iniparser.py | 101 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 Varken/iniparser.py diff --git a/Varken/iniparser.py b/Varken/iniparser.py new file mode 100644 index 0000000..6706c53 --- /dev/null +++ b/Varken/iniparser.py @@ -0,0 +1,101 @@ +import sys +import configparser + +from Varken.helpers import Server, TautulliServer, InfluxServer + + +class INIParser(object): + def __init__(self): + self.config = configparser.ConfigParser() + + self.influx_server = InfluxServer() + self.sonarr_enabled = False + self.sonarr_servers = [] + self.radarr_enabled = False + self.radarr_servers = [] + self.ombi_enabled = False + self.ombi_server = None + self.tautulli_enabled = False + self.tautulli_server = None + self.read_file() + self.parse_opts() + + def read_file(self): + with open('varken.ini') as config_ini: + self.config.read_file(config_ini) + + def parse_opts(self): + # Parse InfluxDB options + url = self.config.get('influxdb', 'url') + port = self.config.getint('influxdb', 'port') + username = self.config.get('influxdb', 'username') + password = self.config.get('influxdb', 'password') + + self.influx_server = InfluxServer(url, port, username, password) + + # Parse Sonarr options + try: + if not self.config.getboolean('global', 'sonarr_server_ids'): + sys.exit('sonarr_server_ids must be either false, or a comma-separated list of server ids') + except ValueError: + self.sonarr_enabled = True + sids = self.config.get('global', 'sonarr_server_ids').strip(' ').split(',') + + for server_id in sids: + sonarr_section = 'sonarr-' + server_id + url = self.config.get(sonarr_section, 'url') + apikey = self.config.get(sonarr_section, 'apikey') + scheme = 'https://' if self.config.getboolean(sonarr_section, 'ssl') else 'http://' + verify_ssl = self.config.getboolean(sonarr_section, 'verify_ssl') + + self.sonarr_servers.append(Server(server_id, scheme + url, apikey, verify_ssl)) + + # Parse Radarr options + try: + if not self.config.getboolean('global', 'radarr_server_ids'): + sys.exit('radarr_server_ids must be either false, or a comma-separated list of server ids') + except ValueError: + self.radarr_enabled = True + sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') + for server_id in sids: + radarr_section = 'sonarr-' + server_id + url = self.config.get(radarr_section, 'url') + apikey = self.config.get(radarr_section, 'apikey') + scheme = 'https://' if self.config.getboolean(radarr_section, 'ssl') else 'http://' + verify_ssl = self.config.getboolean(radarr_section, 'verify_ssl') + + self.radarr_servers.append(Server(server_id, scheme + url, apikey, verify_ssl)) + + # Parse Tautulli options + if self.config.getboolean('global', 'tautulli'): + self.tautulli_enabled = True + url = self.config.get('tautulli', 'url') + fallback_ip = self.config.get('tautulli', 'fallback_ip') + apikey = self.config.get('tautulli', 'apikey') + scheme = 'https://' if self.config.getboolean('tautulli', 'ssl') else 'http://' + verify_ssl = self.config.getboolean('tautulli', 'verify_ssl') + db_name = self.config.get('tautulli', 'influx_db') + + self.tautulli_server = TautulliServer(scheme + url, fallback_ip, apikey, verify_ssl, db_name) + + # Parse Ombi Options + if self.config.getboolean('global', 'ombi'): + self.tautulli_enabled = True + url = self.config.get('ombi', 'url') + apikey = self.config.get('ombi', 'apikey') + scheme = 'https://' if self.config.getboolean('ombi', 'ssl') else 'http://' + verify_ssl = self.config.getboolean('ombi', 'verify_ssl') + + self.ombi_server = Server(url=scheme + url, api_key=apikey, verify_ssl=verify_ssl) + + # Parse ASA opts + if self.config.getboolean('global', 'asa'): + self.tautulli_enabled = True + url = self.config.get('asa', 'url') + username = self.config.get('asa', 'username') + password = self.config.get('asa', 'password') + scheme = 'https://' if self.config.getboolean('asa', 'ssl') else 'http://' + verify_ssl = self.config.getboolean('asa', 'verify_ssl') + db_name = self.config.get('asa', 'influx_db') + + self.ombi_server = (scheme + url, username, password, verify_ssl, db_name) From f934d80cc20ad5b120d2546b5c75f339feac81cf Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 21:23:33 -0600 Subject: [PATCH 020/120] the copy pasta was too strong --- Varken/iniparser.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 6706c53..915b576 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -17,6 +17,8 @@ class INIParser(object): self.ombi_server = None self.tautulli_enabled = False self.tautulli_server = None + self.asa_enabled = False + self.asa = None self.read_file() self.parse_opts() @@ -80,7 +82,7 @@ class INIParser(object): # Parse Ombi Options if self.config.getboolean('global', 'ombi'): - self.tautulli_enabled = True + self.ombi_enabled = True url = self.config.get('ombi', 'url') apikey = self.config.get('ombi', 'apikey') scheme = 'https://' if self.config.getboolean('ombi', 'ssl') else 'http://' @@ -90,7 +92,7 @@ class INIParser(object): # Parse ASA opts if self.config.getboolean('global', 'asa'): - self.tautulli_enabled = True + self.asa_enabled = True url = self.config.get('asa', 'url') username = self.config.get('asa', 'username') password = self.config.get('asa', 'password') @@ -98,4 +100,4 @@ class INIParser(object): verify_ssl = self.config.getboolean('asa', 'verify_ssl') db_name = self.config.get('asa', 'influx_db') - self.ombi_server = (scheme + url, username, password, verify_ssl, db_name) + self.asa = (scheme + url, username, password, verify_ssl, db_name) From 8f47246be156a330030bca358eb87d1890ad5f3d Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 22:19:42 -0600 Subject: [PATCH 021/120] split off sonarrserver as a class --- Varken/helpers.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Varken/helpers.py b/Varken/helpers.py index 45157f2..5d9e456 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -35,6 +35,14 @@ class Queue(NamedTuple): protocol: str = None id: int = None +class SonarrServer(NamedTuple): + id: int = None + url: str = None + api_key: str = None + verify_ssl: bool = False + missing_days: int = None + future_days: int = None + queue: bool = False class Server(NamedTuple): id: int = None From ad376fcfa8f18d74271b779ccd947358046d6ee4 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 22:20:09 -0600 Subject: [PATCH 022/120] var separation and args added to sonarr --- Varken/iniparser.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 915b576..b457343 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,25 +1,29 @@ import sys import configparser -from Varken.helpers import Server, TautulliServer, InfluxServer - +from Varken.helpers import Server, TautulliServer, SonarrServer, InfluxServer class INIParser(object): def __init__(self): self.config = configparser.ConfigParser() self.influx_server = InfluxServer() + self.sonarr_enabled = False self.sonarr_servers = [] + self.radarr_enabled = False self.radarr_servers = [] + self.ombi_enabled = False self.ombi_server = None + self.tautulli_enabled = False self.tautulli_server = None + self.asa_enabled = False self.asa = None - self.read_file() + self.parse_opts() def read_file(self): @@ -27,6 +31,7 @@ class INIParser(object): self.config.read_file(config_ini) def parse_opts(self): + self.read_file() # Parse InfluxDB options url = self.config.get('influxdb', 'url') port = self.config.getint('influxdb', 'port') @@ -49,8 +54,12 @@ class INIParser(object): apikey = self.config.get(sonarr_section, 'apikey') scheme = 'https://' if self.config.getboolean(sonarr_section, 'ssl') else 'http://' verify_ssl = self.config.getboolean(sonarr_section, 'verify_ssl') + queue = self.config.getboolean(sonarr_section, 'queue') + missing_days = self.config.getint(sonarr_section, 'missing_days') + future_days = self.config.getint(sonarr_section, 'future_days') - self.sonarr_servers.append(Server(server_id, scheme + url, apikey, verify_ssl)) + self.sonarr_servers.append(SonarrServer(server_id, scheme + url, apikey, verify_ssl, + missing_days, future_days, queue)) # Parse Radarr options try: @@ -60,7 +69,7 @@ class INIParser(object): self.radarr_enabled = True sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') for server_id in sids: - radarr_section = 'sonarr-' + server_id + radarr_section = 'radarr-' + server_id url = self.config.get(radarr_section, 'url') apikey = self.config.get(radarr_section, 'apikey') scheme = 'https://' if self.config.getboolean(radarr_section, 'ssl') else 'http://' From 5f782cc1adc5f9e06eb4d93134e965c234a50bf5 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 22:20:25 -0600 Subject: [PATCH 023/120] leaving template --- Varken/logger.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 Varken/logger.py diff --git a/Varken/logger.py b/Varken/logger.py new file mode 100644 index 0000000..b1b2b42 --- /dev/null +++ b/Varken/logger.py @@ -0,0 +1 @@ +import functools \ No newline at end of file From 1fce9df22a4141e25d2b5cb8a52a843b9afc5634 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 22:20:42 -0600 Subject: [PATCH 024/120] migrated sonarr.py to new style --- Varken/sonarr.py | 56 +++--------------------------------------------- 1 file changed, 3 insertions(+), 53 deletions(-) diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 203a30e..22eaeb8 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -7,38 +7,22 @@ from influxdb import InfluxDBClient from datetime import datetime, timezone, date, timedelta from Varken import configuration as config -from Varken.helpers import Server, TVShow, Queue +from Varken.helpers import TVShow, Queue class SonarrAPI(object): - # Sets None as default for all TVShow NamedTuples, because sonarr's response json is inconsistent - TVShow.__new__.__defaults__ = (None,) * len(TVShow._fields) - - def __init__(self): + def __init__(self, servers): # Set Time of initialization self.now = datetime.now(timezone.utc).astimezone().isoformat() self.today = str(date.today()) self.influx = InfluxDBClient(config.influxdb_url, config.influxdb_port, config.influxdb_username, config.influxdb_password, config.sonarr_influxdb_db_name) self.influx_payload = [] - self.servers = self.get_servers() + self.servers = servers # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = requests.Session() self.session.params = {'pageSize': 1000} - @staticmethod - def get_servers(): - # Ensure sonarr servers have been defined - if not config.sonarr_server_list: - sys.exit("No Sonarr servers defined in config") - - # Build Server Objects from config - servers = [] - for url, api_key, server_id in config.sonarr_server_list: - servers.append(Server(url=url, api_key=api_key, id=server_id)) - - return servers - def get_missing(self, days_past): endpoint = '/api/calendar' last_days = str(date.today() + timedelta(days=-days_past)) @@ -77,39 +61,6 @@ class SonarrAPI(object): } ) - def get_upcoming(self): - endpoint = '/api/calendar/' - - for server in self.servers: - upcoming = [] - headers = {'X-Api-Key': server.api_key} - - get = self.session.get(server.url + endpoint, headers=headers).json() - tv_shows = [TVShow(**show) for show in get] - - for show in tv_shows: - sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) - upcoming.append((show.series['title'], sxe, show.id, show.title, show.airDate)) - - for series_title, sxe, sonarr_id, episode_title, air_date in upcoming: - self.influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Soon", - "sonarrId": sonarr_id, - "server": server.id - }, - "time": self.now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "airs": air_date - } - } - ) - def get_future(self, future_days): endpoint = '/api/calendar/' future = str(date.today() + timedelta(days=future_days)) @@ -204,7 +155,6 @@ if __name__ == "__main__": help='legacy command. Deprecated in favor of --missing' '\nfunctions identically to --missing' '\nNote: Will be removed in a future release') - parser.add_argument("--upcoming", action='store_true', help='Get upcoming TV shows') parser.add_argument("--future", metavar='$days', type=int, help='Get TV shows on X days into the future. Includes today.' '\ni.e. --future 2 is Today and Tomorrow') From 90b3b70b25987eb4b4b4ef9003ef924a9d2c4144 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 22:20:55 -0600 Subject: [PATCH 025/120] added args to ini --- Varken/varken.example.ini | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Varken/varken.example.ini b/Varken/varken.example.ini index c037f55..39a6073 100644 --- a/Varken/varken.example.ini +++ b/Varken/varken.example.ini @@ -23,12 +23,18 @@ url = sonarr1.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false verify_ssl = true +missing_days = 7 +future_days = 1 +queue = true [sonarr-2] url = sonarr2.domain.tld apikey = yyyyyyyyyyyyyyyy ssl = false verify_ssl = true +missing_days = 7 +future_days = 1 +queue = true [radarr-1] url = radarr1.domain.tld From 01370d92dce511115b0beef2ff339ffddb48fb1a Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 22:21:16 -0600 Subject: [PATCH 026/120] initial scheduling test --- Varken/varken.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Varken/varken.py b/Varken/varken.py index e69de29..4569f95 100644 --- a/Varken/varken.py +++ b/Varken/varken.py @@ -0,0 +1,36 @@ +import schedule +import threading +import functools +from time import sleep + +from Varken.iniparser import INIParser +from Varken.sonarr import SonarrAPI + +def logging(function): + @functools.wraps(function) + def wrapper(*args, **kwargs): + print('LOG: Running job "%s"' % function.__name__) + result = function(*args, **kwargs) + print('LOG: Job "%s" completed' % function.__name__) + return result + + return wrapper + +@logging +def threaded(job): + thread = threading.Thread(target=job) + thread.start() + +if __name__ == "__main__": + CONFIG = INIParser() + + if CONFIG.sonarr_enabled: + SONARR = SonarrAPI(CONFIG.sonarr_servers) + for server in CONFIG.sonarr_servers: + if server.queue: + schedule.every().minute.do(threaded, SONARR.get_queue) + + while True: + schedule.run_pending() + sleep(1) + From 7910efc64134668b9a075a1e3956afed3b1206e1 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 22:26:00 -0600 Subject: [PATCH 027/120] passed influx_seraver to sonarrapi class --- Varken/sonarr.py | 9 ++++----- Varken/varken.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 22eaeb8..0ba4dcf 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -6,19 +6,18 @@ import argparse from influxdb import InfluxDBClient from datetime import datetime, timezone, date, timedelta -from Varken import configuration as config from Varken.helpers import TVShow, Queue class SonarrAPI(object): - def __init__(self, servers): + def __init__(self, sonarr_servers, influx_server): # Set Time of initialization self.now = datetime.now(timezone.utc).astimezone().isoformat() self.today = str(date.today()) - self.influx = InfluxDBClient(config.influxdb_url, config.influxdb_port, config.influxdb_username, - config.influxdb_password, config.sonarr_influxdb_db_name) + self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, + influx_server.password, 'plex') self.influx_payload = [] - self.servers = servers + self.servers = sonarr_servers # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = requests.Session() self.session.params = {'pageSize': 1000} diff --git a/Varken/varken.py b/Varken/varken.py index 4569f95..1b9dac4 100644 --- a/Varken/varken.py +++ b/Varken/varken.py @@ -25,7 +25,7 @@ if __name__ == "__main__": CONFIG = INIParser() if CONFIG.sonarr_enabled: - SONARR = SonarrAPI(CONFIG.sonarr_servers) + SONARR = SonarrAPI(CONFIG.sonarr_servers, CONFIG.influx_server) for server in CONFIG.sonarr_servers: if server.queue: schedule.every().minute.do(threaded, SONARR.get_queue) From b7605ebbcc6297d62a058f55ddc449aba06b6d73 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 22:57:10 -0600 Subject: [PATCH 028/120] bunch of tweaks. Scheduling is working --- Varken/iniparser.py | 4 +++ Varken/logger.py | 12 ++++++++- Varken/sonarr.py | 64 ++++++++++++++------------------------------- Varken/varken.py | 20 ++++++-------- 4 files changed, 42 insertions(+), 58 deletions(-) diff --git a/Varken/iniparser.py b/Varken/iniparser.py index b457343..233db54 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -44,8 +44,12 @@ class INIParser(object): try: if not self.config.getboolean('global', 'sonarr_server_ids'): sys.exit('sonarr_server_ids must be either false, or a comma-separated list of server ids') + elif self.config.getint('global', 'sonarr_server_ids'): + self.sonarr_enabled = True except ValueError: self.sonarr_enabled = True + + if self.sonarr_enabled: sids = self.config.get('global', 'sonarr_server_ids').strip(' ').split(',') for server_id in sids: diff --git a/Varken/logger.py b/Varken/logger.py index b1b2b42..689dd37 100644 --- a/Varken/logger.py +++ b/Varken/logger.py @@ -1 +1,11 @@ -import functools \ No newline at end of file +import functools + +def logging(function): + @functools.wraps(function) + def wrapper(*args, **kwargs): + print('LOG: Running job "%s"' % function.__name__) + result = function(*args, **kwargs) + print('LOG: Job "%s" completed' % function.__name__) + return result + + return wrapper \ No newline at end of file diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 0ba4dcf..21cf87c 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -1,11 +1,10 @@ #!/usr/bin/env python3 # Do not edit this script. Edit configuration.py -import sys import requests -import argparse from influxdb import InfluxDBClient from datetime import datetime, timezone, date, timedelta +from Varken.logger import logging from Varken.helpers import TVShow, Queue @@ -16,16 +15,17 @@ class SonarrAPI(object): self.today = str(date.today()) self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, influx_server.password, 'plex') - self.influx_payload = [] self.servers = sonarr_servers # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = requests.Session() self.session.params = {'pageSize': 1000} + @logging def get_missing(self, days_past): endpoint = '/api/calendar' last_days = str(date.today() + timedelta(days=-days_past)) params = {'start': last_days, 'end': self.today} + influx_payload = [] for server in self.servers: missing = [] @@ -42,7 +42,7 @@ class SonarrAPI(object): missing.append((show.series['title'], sxe, show.airDate, show.title, show.id)) for series_title, sxe, air_date, episode_title, sonarr_id in missing: - self.influx_payload.append( + influx_payload.append( { "measurement": "Sonarr", "tags": { @@ -60,12 +60,17 @@ class SonarrAPI(object): } ) + self.influx_push(influx_payload) + + @logging def get_future(self, future_days): endpoint = '/api/calendar/' future = str(date.today() + timedelta(days=future_days)) + influx_payload = [] for server in self.servers: air_days = [] + headers = {'X-Api-Key': server.api_key} params = {'start': self.today, 'end': future} @@ -77,7 +82,7 @@ class SonarrAPI(object): air_days.append((show.series['title'], show.hasFile, sxe, show.title, show.airDate, show.id)) for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: - self.influx_payload.append( + influx_payload.append( { "measurement": "Sonarr", "tags": { @@ -96,7 +101,11 @@ class SonarrAPI(object): } ) + self.influx_push(influx_payload) + + @logging def get_queue(self): + influx_payload = [] endpoint = '/api/queue' for server in self.servers: @@ -117,7 +126,7 @@ class SonarrAPI(object): protocol_id, sxe, show.id)) for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue: - self.influx_payload.append( + influx_payload.append( { "measurement": "Sonarr", "tags": { @@ -137,43 +146,8 @@ class SonarrAPI(object): } ) - def influx_push(self): + self.influx_push(influx_payload) + + def influx_push(self, payload): # TODO: error handling for failed connection - self.influx.write_points(self.influx_payload) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(prog='Sonarr stats operations', - description='Script to aid in data gathering from Sonarr', - formatter_class=argparse.RawTextHelpFormatter) - - parser.add_argument("--missing", metavar='$days', type=int, - help='Get missing TV shows in past X days' - '\ni.e. --missing 7 is in the last week') - parser.add_argument("--missing_days", metavar='$days', type=int, - help='legacy command. Deprecated in favor of --missing' - '\nfunctions identically to --missing' - '\nNote: Will be removed in a future release') - parser.add_argument("--future", metavar='$days', type=int, - help='Get TV shows on X days into the future. Includes today.' - '\ni.e. --future 2 is Today and Tomorrow') - parser.add_argument("--queue", action='store_true', help='Get TV shows in queue') - - opts = parser.parse_args() - sonarr = SonarrAPI() - - if len(sys.argv) == 1: - parser.print_help(sys.stderr) - sys.exit(1) - - if any([opts.missing, opts.missing_days]): - days = opts.missing if opts.missing else opts.missing_days - sonarr.get_missing(days) - if opts.upcoming: - sonarr.get_upcoming() - if opts.future: - sonarr.get_future(opts.future) - if opts.queue: - sonarr.get_queue() - - sonarr.influx_push() + self.influx.write_points(payload) diff --git a/Varken/varken.py b/Varken/varken.py index 1b9dac4..0af850d 100644 --- a/Varken/varken.py +++ b/Varken/varken.py @@ -1,23 +1,13 @@ import schedule import threading -import functools from time import sleep from Varken.iniparser import INIParser from Varken.sonarr import SonarrAPI -def logging(function): - @functools.wraps(function) - def wrapper(*args, **kwargs): - print('LOG: Running job "%s"' % function.__name__) - result = function(*args, **kwargs) - print('LOG: Job "%s" completed' % function.__name__) - return result - return wrapper - -@logging def threaded(job): + print('test') thread = threading.Thread(target=job) thread.start() @@ -26,9 +16,15 @@ if __name__ == "__main__": if CONFIG.sonarr_enabled: SONARR = SonarrAPI(CONFIG.sonarr_servers, CONFIG.influx_server) + for server in CONFIG.sonarr_servers: if server.queue: - schedule.every().minute.do(threaded, SONARR.get_queue) + schedule.every(1).minutes.do(threaded, SONARR.get_queue) + if server.missing_days > 0: + schedule.every(30).minutes.do(threaded, SONARR.get_missing, server.missing_days) + if server.future_days > 0: + schedule.every(30).minutes.do(threaded, SONARR.get_future, server.future_days) + while True: schedule.run_pending() From a5f138785dabd419e2b10931b4e0c24e77619ee3 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 28 Nov 2018 23:47:04 -0600 Subject: [PATCH 029/120] added config minutes setting --- Varken/helpers.py | 7 +++++-- Varken/iniparser.py | 8 ++++++-- Varken/sonarr.py | 10 ++++++---- Varken/varken.example.ini | 3 +++ Varken/varken.py | 14 +++++++------- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Varken/helpers.py b/Varken/helpers.py index 5d9e456..2551928 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -40,9 +40,12 @@ class SonarrServer(NamedTuple): url: str = None api_key: str = None verify_ssl: bool = False - missing_days: int = None - future_days: int = None + missing_days: int = 0 + missing_days_run_minutes: int = 30 + future_days: int = 0 + future_days_run_minutes: int = 30 queue: bool = False + queue_run_minutes: int = 1 class Server(NamedTuple): id: int = None diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 233db54..0ea8098 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -61,9 +61,13 @@ class INIParser(object): queue = self.config.getboolean(sonarr_section, 'queue') missing_days = self.config.getint(sonarr_section, 'missing_days') future_days = self.config.getint(sonarr_section, 'future_days') + missing_days_run_minutes = self.config.getint(sonarr_section, 'missing_days_run_minutes') + future_days_run_minutes = self.config.getint(sonarr_section, 'future_days_run_minutes') + queue_run_minutes = self.config.getint(sonarr_section, 'queue_run_minutes') - self.sonarr_servers.append(SonarrServer(server_id, scheme + url, apikey, verify_ssl, - missing_days, future_days, queue)) + self.sonarr_servers.append(SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days, + missing_days_run_minutes, future_days, + future_days_run_minutes, queue, queue_run_minutes)) # Parse Radarr options try: diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 21cf87c..c93a067 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -31,7 +31,8 @@ class SonarrAPI(object): missing = [] headers = {'X-Api-Key': server.api_key} - get = self.session.get(server.url + endpoint, params=params, headers=headers).json() + get = self.session.get(server.url + endpoint, params=params, headers=headers, + verify=server.verify_ssl).json() # Iteratively create a list of TVShow Objects from response json tv_shows = [TVShow(**show) for show in get] @@ -74,7 +75,8 @@ class SonarrAPI(object): headers = {'X-Api-Key': server.api_key} params = {'start': self.today, 'end': future} - get = self.session.get(server.url + endpoint, params=params, headers=headers).json() + get = self.session.get(server.url + endpoint, params=params, headers=headers, + verify=server.verify_ssl).json() tv_shows = [TVShow(**show) for show in get] for show in tv_shows: @@ -104,7 +106,7 @@ class SonarrAPI(object): self.influx_push(influx_payload) @logging - def get_queue(self): + def get_queue(self, notimplemented): influx_payload = [] endpoint = '/api/queue' @@ -112,7 +114,7 @@ class SonarrAPI(object): queue = [] headers = {'X-Api-Key': server.api_key} - get = self.session.get(server.url + endpoint, headers=headers).json() + get = self.session.get(server.url + endpoint, headers=headers, verify=server.verify_ssl).json() download_queue = [Queue(**show) for show in get] for show in download_queue: diff --git a/Varken/varken.example.ini b/Varken/varken.example.ini index 39a6073..e3022ed 100644 --- a/Varken/varken.example.ini +++ b/Varken/varken.example.ini @@ -24,8 +24,11 @@ apikey = xxxxxxxxxxxxxxxx ssl = false verify_ssl = true missing_days = 7 +missing_days_run_minutes = 30 future_days = 1 +future_days_run_minutes = 30 queue = true +queue_run_minutes = 1 [sonarr-2] url = sonarr2.domain.tld diff --git a/Varken/varken.py b/Varken/varken.py index 0af850d..2212e48 100644 --- a/Varken/varken.py +++ b/Varken/varken.py @@ -6,9 +6,8 @@ from Varken.iniparser import INIParser from Varken.sonarr import SonarrAPI -def threaded(job): - print('test') - thread = threading.Thread(target=job) +def threaded(job, days=None): + thread = threading.Thread(target=job, args=([days])) thread.start() if __name__ == "__main__": @@ -19,12 +18,13 @@ if __name__ == "__main__": for server in CONFIG.sonarr_servers: if server.queue: - schedule.every(1).minutes.do(threaded, SONARR.get_queue) + schedule.every(server.queue_run_minutes).minutes.do(threaded, SONARR.get_queue) if server.missing_days > 0: - schedule.every(30).minutes.do(threaded, SONARR.get_missing, server.missing_days) + schedule.every(server.missing_days_run_minutes).minutes.do(threaded, SONARR.get_missing, + server.missing_days) if server.future_days > 0: - schedule.every(30).minutes.do(threaded, SONARR.get_future, server.future_days) - + schedule.every(server.future_days_run_minutes).minutes.do(threaded, SONARR.get_future, + server.future_days) while True: schedule.run_pending() From dc3c7b2f5a29e61aa41277b7159c4f869177252a Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Thu, 29 Nov 2018 00:05:27 -0600 Subject: [PATCH 030/120] created systemd config example --- varken.service | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 varken.service diff --git a/varken.service b/varken.service new file mode 100644 index 0000000..d1db152 --- /dev/null +++ b/varken.service @@ -0,0 +1,12 @@ +[Unit] +Description=Varken - A data collection and graphing tool +After=network-online.target + +[Service] +Type=simple +WorkingDirectory=/opt/Varken/Varken +ExecStart=/usr/bin/python3 /opt/Varken/Varken/varken.py +Restart=always + +[Install] +WantedBy=multi-user.target From 3eb91d535259efe2c518a2e77bc36af8f3ca8553 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Thu, 29 Nov 2018 12:42:10 -0600 Subject: [PATCH 031/120] changed to seconds instead of minutes --- Varken/iniparser.py | 14 +++++++++----- Varken/varken.example.ini | 12 +++++++----- Varken/varken.py | 7 ++++--- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 0ea8098..9b24d4c 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -3,6 +3,7 @@ import configparser from Varken.helpers import Server, TautulliServer, SonarrServer, InfluxServer + class INIParser(object): def __init__(self): self.config = configparser.ConfigParser() @@ -61,21 +62,24 @@ class INIParser(object): queue = self.config.getboolean(sonarr_section, 'queue') missing_days = self.config.getint(sonarr_section, 'missing_days') future_days = self.config.getint(sonarr_section, 'future_days') - missing_days_run_minutes = self.config.getint(sonarr_section, 'missing_days_run_minutes') - future_days_run_minutes = self.config.getint(sonarr_section, 'future_days_run_minutes') - queue_run_minutes = self.config.getint(sonarr_section, 'queue_run_minutes') + missing_days_run_seconds = self.config.getint(sonarr_section, 'missing_days_run_seconds') + future_days_run_seconds = self.config.getint(sonarr_section, 'future_days_run_seconds') + queue_run_seconds = self.config.getint(sonarr_section, 'queue_run_seconds') self.sonarr_servers.append(SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days, - missing_days_run_minutes, future_days, - future_days_run_minutes, queue, queue_run_minutes)) + missing_days_run_seconds, future_days, + future_days_run_seconds, queue, queue_run_seconds)) # Parse Radarr options try: if not self.config.getboolean('global', 'radarr_server_ids'): sys.exit('radarr_server_ids must be either false, or a comma-separated list of server ids') + elif self.config.getint('global', 'radarr_server_ids'): + self.radarr_enabled = True except ValueError: self.radarr_enabled = True sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') + for server_id in sids: radarr_section = 'radarr-' + server_id url = self.config.get(radarr_section, 'url') diff --git a/Varken/varken.example.ini b/Varken/varken.example.ini index e3022ed..10b7c70 100644 --- a/Varken/varken.example.ini +++ b/Varken/varken.example.ini @@ -2,7 +2,7 @@ # - Sonarr + Radarr scripts support multiple servers. You can remove the second # server by putting a # in front of the lines and section name, and removing # that number from your server_ids list -# - tautulli_failback_ip, This is used when there is no IP listed in tautulli. +# - fallback_ip, This is used when there is no IP listed in tautulli. # This can happen when you are streaming locally. This is usually your public IP. [global] @@ -24,11 +24,11 @@ apikey = xxxxxxxxxxxxxxxx ssl = false verify_ssl = true missing_days = 7 -missing_days_run_minutes = 30 +missing_days_run_seconds = 300 future_days = 1 -future_days_run_minutes = 30 +future_days_run_seconds = 300 queue = true -queue_run_minutes = 1 +queue_run_seconds = 300 [sonarr-2] url = sonarr2.domain.tld @@ -36,8 +36,11 @@ apikey = yyyyyyyyyyyyyyyy ssl = false verify_ssl = true missing_days = 7 +missing_days_run_seconds = 300 future_days = 1 +future_days_run_seconds = 300 queue = true +queue_run_seconds = 300 [radarr-1] url = radarr1.domain.tld @@ -63,7 +66,6 @@ fallback_ip = 0.0.0.0 apikey = xxxxxxxxxxxxxxxx ssl = false verify_ssl = true -influx_db = plex [asa] url = firewall.domain.tld diff --git a/Varken/varken.py b/Varken/varken.py index 2212e48..c6e9349 100644 --- a/Varken/varken.py +++ b/Varken/varken.py @@ -10,6 +10,7 @@ def threaded(job, days=None): thread = threading.Thread(target=job, args=([days])) thread.start() + if __name__ == "__main__": CONFIG = INIParser() @@ -18,12 +19,12 @@ if __name__ == "__main__": for server in CONFIG.sonarr_servers: if server.queue: - schedule.every(server.queue_run_minutes).minutes.do(threaded, SONARR.get_queue) + schedule.every(server.queue_run_seconds).seconds.do(threaded, SONARR.get_queue) if server.missing_days > 0: - schedule.every(server.missing_days_run_minutes).minutes.do(threaded, SONARR.get_missing, + schedule.every(server.missing_days_run_seconds).seconds.do(threaded, SONARR.get_missing, server.missing_days) if server.future_days > 0: - schedule.every(server.future_days_run_minutes).minutes.do(threaded, SONARR.get_future, + schedule.every(server.future_days_run_seconds).seconds.do(threaded, SONARR.get_future, server.future_days) while True: From e89a2a75a8af81d089b1e5e811fb70d9e986437a Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Thu, 29 Nov 2018 12:55:13 -0600 Subject: [PATCH 032/120] folder restructure, dbmanager placeholder, iniparser file fullpath, and updated example --- {Varken => Legacy}/cisco_asa.py | 0 {Varken => Legacy}/configuration.example.py | 0 crontabs => Legacy/crontabs | 0 {Varken => Legacy}/ombi.py | 0 {Varken => Legacy}/radarr.py | 0 {Varken => Legacy}/raid_init.py | 0 {Varken => Legacy}/san.py | 0 {Varken => Legacy}/tautulli.py | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {Varken => Legacy}/cisco_asa.py (100%) rename {Varken => Legacy}/configuration.example.py (100%) rename crontabs => Legacy/crontabs (100%) rename {Varken => Legacy}/ombi.py (100%) rename {Varken => Legacy}/radarr.py (100%) rename {Varken => Legacy}/raid_init.py (100%) rename {Varken => Legacy}/san.py (100%) rename {Varken => Legacy}/tautulli.py (100%) diff --git a/Varken/cisco_asa.py b/Legacy/cisco_asa.py similarity index 100% rename from Varken/cisco_asa.py rename to Legacy/cisco_asa.py diff --git a/Varken/configuration.example.py b/Legacy/configuration.example.py similarity index 100% rename from Varken/configuration.example.py rename to Legacy/configuration.example.py diff --git a/crontabs b/Legacy/crontabs similarity index 100% rename from crontabs rename to Legacy/crontabs diff --git a/Varken/ombi.py b/Legacy/ombi.py similarity index 100% rename from Varken/ombi.py rename to Legacy/ombi.py diff --git a/Varken/radarr.py b/Legacy/radarr.py similarity index 100% rename from Varken/radarr.py rename to Legacy/radarr.py diff --git a/Varken/raid_init.py b/Legacy/raid_init.py similarity index 100% rename from Varken/raid_init.py rename to Legacy/raid_init.py diff --git a/Varken/san.py b/Legacy/san.py similarity index 100% rename from Varken/san.py rename to Legacy/san.py diff --git a/Varken/tautulli.py b/Legacy/tautulli.py similarity index 100% rename from Varken/tautulli.py rename to Legacy/tautulli.py From 32b965edf816cdd568889ad8b5532395b2e21402 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Thu, 29 Nov 2018 12:55:28 -0600 Subject: [PATCH 033/120] folder restructure, dbmanager placeholder, iniparser file fullpath, and updated example --- .gitignore | 1 + Varken/dbmanager.py | 0 Varken/iniparser.py | 5 +++-- Varken/varken.example.ini => varken.example.ini | 0 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 Varken/dbmanager.py rename Varken/varken.example.ini => varken.example.ini (100%) diff --git a/.gitignore b/.gitignore index 942e550..c51a719 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ __pycache__ GeoLite2-City.mmdb GeoLite2-City.tar.gz .idea/ +.idea/* Varken/varken.ini diff --git a/Varken/dbmanager.py b/Varken/dbmanager.py new file mode 100644 index 0000000..e69de29 diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 9b24d4c..6c6875f 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,6 +1,6 @@ import sys import configparser - +from os.path import abspath, dirname, join from Varken.helpers import Server, TautulliServer, SonarrServer, InfluxServer @@ -28,7 +28,8 @@ class INIParser(object): self.parse_opts() def read_file(self): - with open('varken.ini') as config_ini: + file_path = abspath(join(dirname(__file__), '..', 'varken.ini')) + with open(file_path) as config_ini: self.config.read_file(config_ini) def parse_opts(self): diff --git a/Varken/varken.example.ini b/varken.example.ini similarity index 100% rename from Varken/varken.example.ini rename to varken.example.ini From 7638cd937e2d1b626896fc723f77577d28752edf Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 16:30:41 -0600 Subject: [PATCH 034/120] Migrated tautulli.py and allowed for multiple servers --- .gitignore | 2 +- Legacy/tautulli.py | 179 -------------------------------- Varken/helpers.py | 242 +++++++++++++++++++++++++++++++++++++++++++- Varken/iniparser.py | 43 +++++--- Varken/sonarr.py | 4 +- Varken/tautulli.py | 146 ++++++++++++++++++++++++++ Varken/varken.py | 10 ++ varken.example.ini | 19 ++-- 8 files changed, 439 insertions(+), 206 deletions(-) delete mode 100644 Legacy/tautulli.py create mode 100644 Varken/tautulli.py diff --git a/.gitignore b/.gitignore index c51a719..185ff03 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ GeoLite2-City.mmdb GeoLite2-City.tar.gz .idea/ .idea/* -Varken/varken.ini +varken.ini diff --git a/Legacy/tautulli.py b/Legacy/tautulli.py deleted file mode 100644 index 70656df..0000000 --- a/Legacy/tautulli.py +++ /dev/null @@ -1,179 +0,0 @@ -import os -import tarfile -import urllib.request -import time -from datetime import datetime, timezone -import geoip2.database -from influxdb import InfluxDBClient -import requests -from Varken import configuration - -CURRENT_TIME = datetime.now(timezone.utc).astimezone().isoformat() - -PAYLOAD = {'apikey': configuration.tautulli_api_key, 'cmd': 'get_activity'} - -ACTIVITY = requests.get('{}/api/v2'.format(configuration.tautulli_url), - params=PAYLOAD).json()['response']['data'] - -SESSIONS = {d['session_id']: d for d in ACTIVITY['sessions']} - -TAR_DBFILE = '{}/GeoLite2-City.tar.gz'.format(os.path.dirname(os.path.realpath(__file__))) - -DBFILE = '{}/GeoLite2-City.mmdb'.format(os.path.dirname(os.path.realpath(__file__))) - -NOW = time.time() - -DB_AGE = NOW - (86400 * 35) - -#remove the running db file if it is older than 35 days -try: - t = os.stat(DBFILE) - c = t.st_ctime - if c < DB_AGE: - os.remove(DBFILE) -except FileNotFoundError: - pass - - -def geo_lookup(ipaddress): - """Lookup an IP using the local GeoLite2 DB""" - if not os.path.isfile(DBFILE): - urllib.request.urlretrieve( - 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz', - TAR_DBFILE) - - tar = tarfile.open(TAR_DBFILE, "r:gz") - for files in tar.getmembers(): - if 'GeoLite2-City.mmdb' in files.name: - files.name = os.path.basename(files.name) - tar.extract(files, '{}/'.format(os.path.dirname(os.path.realpath(__file__)))) - - reader = geoip2.database.Reader(DBFILE) - - return reader.city(ipaddress) - - -INFLUX_PAYLOAD = [ - { - "measurement": "Tautulli", - "tags": { - "type": "stream_count" - }, - "time": CURRENT_TIME, - "fields": { - "current_streams": int(ACTIVITY['stream_count']), - "transcode_streams": int(ACTIVITY['stream_count_transcode']), - "direct_play_streams": int(ACTIVITY['stream_count_direct_play']), - "direct_streams": int(ACTIVITY['stream_count_direct_stream']) - } - } -] - -for session in SESSIONS.keys(): - try: - geodata = geo_lookup(SESSIONS[session]['ip_address_public']) - except (ValueError, geoip2.errors.AddressNotFoundError): - if configuration.tautulli_failback_ip: - geodata = geo_lookup(configuration.tautulli_failback_ip) - else: - geodata = geo_lookup(requests.get('http://ip.42.pl/raw').text) - - latitude = geodata.location.latitude - - if not geodata.location.latitude: - latitude = 37.234332396 - else: - latitude = geodata.location.latitude - - if not geodata.location.longitude: - longitude = -115.80666344 - else: - longitude = geodata.location.longitude - - decision = SESSIONS[session]['transcode_decision'] - - if decision == 'copy': - decision = 'direct stream' - - video_decision = SESSIONS[session]['stream_video_decision'] - - if video_decision == 'copy': - video_decision = 'direct stream' - - elif video_decision == '': - video_decision = 'Music' - - quality = SESSIONS[session]['stream_video_resolution'] - - - # If the video resolution is empty. Asssume it's an audio stream - # and use the container for music - if not quality: - quality = SESSIONS[session]['container'].upper() - - elif quality in ('SD', 'sd'): - quality = SESSIONS[session]['stream_video_resolution'].upper() - - elif quality in '4k': - quality = SESSIONS[session]['stream_video_resolution'].upper() - - else: - quality = '{}p'.format(SESSIONS[session]['stream_video_resolution']) - - - # Translate player_state to integers so we can colorize the table - player_state = SESSIONS[session]['state'].lower() - - if player_state == 'playing': - player_state = 0 - - elif player_state == 'paused': - player_state = 1 - - elif player_state == 'buffering': - player_state = 3 - - - INFLUX_PAYLOAD.append( - { - "measurement": "Tautulli", - "tags": { - "type": "Session", - "session_id": SESSIONS[session]['session_id'], - "name": SESSIONS[session]['friendly_name'], - "title": SESSIONS[session]['full_title'], - "platform": SESSIONS[session]['platform'], - "product_version": SESSIONS[session]['product_version'], - "quality": quality, - "video_decision": video_decision.title(), - "transcode_decision": decision.title(), - "media_type": SESSIONS[session]['media_type'].title(), - "audio_codec": SESSIONS[session]['audio_codec'].upper(), - "audio_profile": SESSIONS[session]['audio_profile'].upper(), - "stream_audio_codec": SESSIONS[session]['stream_audio_codec'].upper(), - "quality_profile": SESSIONS[session]['quality_profile'], - "progress_percent": SESSIONS[session]['progress_percent'], - "region_code": geodata.subdivisions.most_specific.iso_code, - "location": geodata.city.name, - "full_location": '{} - {}'.format(geodata.subdivisions.most_specific.name, - geodata.city.name), - "latitude": latitude, - "longitude": longitude, - "player_state": player_state, - "device_type": SESSIONS[session]['platform'] - }, - "time": CURRENT_TIME, - "fields": { - "session_id": SESSIONS[session]['session_id'], - "session_key": SESSIONS[session]['session_key'] - } - } - ) - -INFLUX_SENDER = InfluxDBClient(configuration.influxdb_url, - configuration.influxdb_port, - configuration.influxdb_username, - configuration.influxdb_password, - configuration.tautulli_influxdb_db_name) - -INFLUX_SENDER.write_points(INFLUX_PAYLOAD) diff --git a/Varken/helpers.py b/Varken/helpers.py index 2551928..fea68b1 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -1,4 +1,10 @@ +import os +import time +import tarfile +import geoip2.database from typing import NamedTuple +from os.path import abspath, join +from urllib.request import urlretrieve class TVShow(NamedTuple): @@ -35,17 +41,19 @@ class Queue(NamedTuple): protocol: str = None id: int = None + class SonarrServer(NamedTuple): id: int = None url: str = None api_key: str = None verify_ssl: bool = False missing_days: int = 0 - missing_days_run_minutes: int = 30 + missing_days_run_seconds: int = 30 future_days: int = 0 - future_days_run_minutes: int = 30 + future_days_run_seconds: int = 30 queue: bool = False - queue_run_minutes: int = 1 + queue_run_seconds: int = 1 + class Server(NamedTuple): id: int = None @@ -55,14 +63,238 @@ class Server(NamedTuple): class TautulliServer(NamedTuple): + id: int = None url: str = None fallback_ip: str = None apikey: str = None verify_ssl: bool = None - influx_db: str = None + get_activity: bool = False + get_activity_run_seconds: int = 30 + get_sessions: bool = False + get_sessions_run_seconds: int = 30 + class InfluxServer(NamedTuple): url: str = 'localhost' port: int = 8086 username: str = 'root' - password: str = 'root' \ No newline at end of file + password: str = 'root' + + +class TautulliStream(NamedTuple): + rating: str + transcode_width: str + labels: list + stream_bitrate: str + bandwidth: str + optimized_version: int + video_language: str + parent_rating_key: str + rating_key: str + platform_version: str + transcode_hw_decoding: int + thumb: str + title: str + video_codec_level: str + tagline: str + last_viewed_at: str + audio_sample_rate: str + user_rating: str + platform: str + collections: list + location: str + transcode_container: str + audio_channel_layout: str + local: str + stream_subtitle_format: str + stream_video_ref_frames: str + transcode_hw_encode_title: str + stream_container_decision: str + audience_rating: str + full_title: str + ip_address: str + subtitles: int + stream_subtitle_language: str + channel_stream: int + video_bitrate: str + is_allow_sync: int + stream_video_bitrate: str + summary: str + stream_audio_decision: str + aspect_ratio: str + audio_bitrate_mode: str + transcode_hw_decode_title: str + stream_audio_channel_layout: str + deleted_user: int + library_name: str + art: str + stream_video_resolution: str + video_profile: str + sort_title: str + stream_video_codec_level: str + stream_video_height: str + year: str + stream_duration: str + stream_audio_channels: str + video_language_code: str + transcode_key: str + transcode_throttled: int + container: str + stream_audio_bitrate: str + user: str + selected: int + product_version: str + subtitle_location: str + transcode_hw_requested: int + video_height: str + state: str + is_restricted: int + email: str + stream_container: str + transcode_speed: str + video_bit_depth: str + stream_audio_sample_rate: str + grandparent_title: str + studio: str + transcode_decision: str + video_width: str + bitrate: str + machine_id: str + originally_available_at: str + video_frame_rate: str + synced_version_profile: str + friendly_name: str + audio_profile: str + optimized_version_title: str + platform_name: str + stream_video_language: str + keep_history: int + stream_audio_codec: str + stream_video_codec: str + grandparent_thumb: str + synced_version: int + transcode_hw_decode: str + user_thumb: str + stream_video_width: str + height: str + stream_subtitle_decision: str + audio_codec: str + parent_title: str + guid: str + audio_language_code: str + transcode_video_codec: str + transcode_audio_codec: str + stream_video_decision: str + user_id: int + transcode_height: str + transcode_hw_full_pipeline: int + throttled: str + quality_profile: str + width: str + live: int + stream_subtitle_forced: int + media_type: str + video_resolution: str + stream_subtitle_location: str + do_notify: int + video_ref_frames: str + stream_subtitle_language_code: str + audio_channels: str + stream_audio_language_code: str + optimized_version_profile: str + relay: int + duration: str + rating_image: str + is_home_user: int + is_admin: int + ip_address_public: str + allow_guest: int + transcode_audio_channels: str + stream_audio_channel_layout_: str + media_index: str + stream_video_framerate: str + transcode_hw_encode: str + grandparent_rating_key: str + original_title: str + added_at: str + banner: str + bif_thumb: str + parent_media_index: str + live_uuid: str + audio_language: str + stream_audio_bitrate_mode: str + username: str + subtitle_decision: str + children_count: str + updated_at: str + player: str + subtitle_format: str + file: str + file_size: str + session_key: str + id: str + subtitle_container: str + genres: list + stream_video_language_code: str + indexes: int + video_decision: str + stream_audio_language: str + writers: list + actors: list + progress_percent: str + audio_decision: str + subtitle_forced: int + profile: str + product: str + view_offset: str + type: str + audience_rating_image: str + audio_bitrate: str + section_id: str + stream_subtitle_codec: str + subtitle_codec: str + video_codec: str + device: str + stream_video_bit_depth: str + video_framerate: str + transcode_hw_encoding: int + transcode_protocol: str + shared_libraries: list + stream_aspect_ratio: str + content_rating: str + session_id: str + directors: list + parent_thumb: str + subtitle_language_code: str + transcode_progress: int + subtitle_language: str + stream_subtitle_container: str + +def geoip_download(): + tar_dbfile = abspath(join('..', 'data', 'GeoLite2-City.tar.gz')) + url = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz' + urlretrieve(url, tar_dbfile) + tar = tarfile.open(tar_dbfile, "r:gz") + for files in tar.getmembers(): + if 'GeoLite2-City.mmdb' in files.name: + files.name = os.path.basename(files.name) + tar.extract(files, '{}/'.format(os.path.dirname(os.path.realpath(__file__)))) + os.remove(tar_dbfile) + +def geo_lookup(ipaddress): + + dbfile = abspath(join('..', 'data', 'GeoLite2-City.mmdb')) + now = time.time() + + try: + dbinfo = os.stat(dbfile) + db_age = now - dbinfo.st_ctime + if db_age > (35 * 86400): + os.remove(dbfile) + geoip_download() + except FileNotFoundError: + geoip_download() + + reader = geoip2.database.Reader(dbfile) + + return reader.city(ipaddress) diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 6c6875f..449d052 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -20,7 +20,7 @@ class INIParser(object): self.ombi_server = None self.tautulli_enabled = False - self.tautulli_server = None + self.tautulli_servers = [] self.asa_enabled = False self.asa = None @@ -67,9 +67,10 @@ class INIParser(object): future_days_run_seconds = self.config.getint(sonarr_section, 'future_days_run_seconds') queue_run_seconds = self.config.getint(sonarr_section, 'queue_run_seconds') - self.sonarr_servers.append(SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days, - missing_days_run_seconds, future_days, - future_days_run_seconds, queue, queue_run_seconds)) + server = SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days, + missing_days_run_seconds, future_days, future_days_run_seconds, + queue, queue_run_seconds) + self.sonarr_servers.append(server) # Parse Radarr options try: @@ -79,6 +80,8 @@ class INIParser(object): self.radarr_enabled = True except ValueError: self.radarr_enabled = True + + if self.sonarr_enabled: sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') for server_id in sids: @@ -91,16 +94,32 @@ class INIParser(object): self.radarr_servers.append(Server(server_id, scheme + url, apikey, verify_ssl)) # Parse Tautulli options - if self.config.getboolean('global', 'tautulli'): + try: + if not self.config.getboolean('global', 'tautulli_server_ids'): + sys.exit('tautulli_server_ids must be either false, or a comma-separated list of server ids') + elif self.config.getint('global', 'tautulli_server_ids'): + self.tautulli_enabled = True + except ValueError: self.tautulli_enabled = True - url = self.config.get('tautulli', 'url') - fallback_ip = self.config.get('tautulli', 'fallback_ip') - apikey = self.config.get('tautulli', 'apikey') - scheme = 'https://' if self.config.getboolean('tautulli', 'ssl') else 'http://' - verify_ssl = self.config.getboolean('tautulli', 'verify_ssl') - db_name = self.config.get('tautulli', 'influx_db') - self.tautulli_server = TautulliServer(scheme + url, fallback_ip, apikey, verify_ssl, db_name) + if self.tautulli_enabled: + sids = self.config.get('global', 'tautulli_server_ids').strip(' ').split(',') + + for server_id in sids: + tautulli_section = 'tautulli-' + server_id + url = self.config.get(tautulli_section, 'url') + fallback_ip = self.config.get(tautulli_section, 'fallback_ip') + apikey = self.config.get(tautulli_section, 'apikey') + scheme = 'https://' if self.config.getboolean(tautulli_section, 'ssl') else 'http://' + verify_ssl = self.config.getboolean(tautulli_section, 'verify_ssl') + get_activity = self.config.getboolean(tautulli_section, 'get_activity') + get_activity_run_seconds = self.config.getint(tautulli_section, 'get_activity_run_seconds') + get_sessions = self.config.getboolean(tautulli_section, 'get_sessions') + get_sessions_run_seconds = self.config.getint(tautulli_section, 'get_sessions_run_seconds') + + server = TautulliServer(server_id, scheme + url, fallback_ip, apikey, verify_ssl, get_activity, + get_activity_run_seconds, get_sessions, get_sessions_run_seconds) + self.tautulli_servers.append(server) # Parse Ombi Options if self.config.getboolean('global', 'ombi'): diff --git a/Varken/sonarr.py b/Varken/sonarr.py index c93a067..65f5df0 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -9,13 +9,13 @@ from Varken.helpers import TVShow, Queue class SonarrAPI(object): - def __init__(self, sonarr_servers, influx_server): + def __init__(self, servers, influx_server): # Set Time of initialization self.now = datetime.now(timezone.utc).astimezone().isoformat() self.today = str(date.today()) self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, influx_server.password, 'plex') - self.servers = sonarr_servers + self.servers = servers # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = requests.Session() self.session.params = {'pageSize': 1000} diff --git a/Varken/tautulli.py b/Varken/tautulli.py new file mode 100644 index 0000000..6912c90 --- /dev/null +++ b/Varken/tautulli.py @@ -0,0 +1,146 @@ +from datetime import datetime, timezone +from geoip2.errors import AddressNotFoundError +from influxdb import InfluxDBClient +import requests +from Varken.helpers import TautulliStream, geo_lookup +from Varken.logger import logging + +class TautulliAPI(object): + def __init__(self, servers, influx_server): + # Set Time of initialization + self.now = datetime.now(timezone.utc).astimezone().isoformat() + self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, + influx_server.password, 'plex') + self.servers = servers + self.session = requests.Session() + self.endpoint = '/api/v2' + + def influx_push(self, payload): + # TODO: error handling for failed connection + self.influx.write_points(payload) + + @logging + def get_activity(self, notimplemented): + params = {'cmd': 'get_activity'} + influx_payload = [] + + for server in self.servers: + params['apikey'] = server.apikey + g = self.session.get(server.url + self.endpoint, params=params, verify=server.verify_ssl) + get = g.json()['response']['data'] + + influx_payload.append( + { + "measurement": "Tautulli", + "tags": { + "type": "current_stream_stats", + "server": server.id + }, + "time": self.now, + "fields": { + "stream_count": int(get['stream_count']), + "total_bandwidth": int(get['total_bandwidth']), + "wan_bandwidth": int(get['wan_bandwidth']), + "lan_bandwidth": int(get['lan_bandwidth']), + "transcode_streams": int(get['stream_count_transcode']), + "direct_play_streams": int(get['stream_count_direct_play']), + "direct_streams": int(get['stream_count_direct_stream']) + } + } + ) + + self.influx_push(influx_payload) + + @logging + def get_sessions(self, notimplemented): + params = {'cmd': 'get_activity'} + influx_payload = [] + + for server in self.servers: + params['apikey'] = server.apikey + g = self.session.get(server.url + self.endpoint, params=params, verify=server.verify_ssl) + get = g.json()['response']['data']['sessions'] + print(get) + sessions = [TautulliStream(**session) for session in get] + + for session in sessions: + try: + geodata = geo_lookup(session.ip_address_public) + except (ValueError, AddressNotFoundError): + if server.fallback_ip: + geodata = geo_lookup(server.fallback_ip) + else: + my_ip = requests.get('http://ip.42.pl/raw').text + geodata = geo_lookup(my_ip) + + if not all([geodata.location.latitude, geodata.location.longitude]): + latitude = 37.234332396 + longitude = -115.80666344 + else: + latitude = geodata.location.latitude + longitude = geodata.location.longitude + + decision = session.transcode_decision + if decision == 'copy': + decision = 'direct stream' + + video_decision = session.stream_video_decision + if video_decision == 'copy': + video_decision = 'direct stream' + elif video_decision == '': + video_decision = 'Music' + + quality = session.stream_video_resolution + if not quality: + quality = session.container.upper() + elif quality in ('SD', 'sd', '4k'): + quality = session.stream_video_resolution.upper() + else: + quality = session.stream_video_resolution + 'p' + + player_state = session.state.lower() + if player_state == 'playing': + player_state = 0 + elif player_state == 'paused': + player_state = 1 + elif player_state == 'buffering': + player_state = 3 + + influx_payload.append( + { + "measurement": "Tautulli", + "tags": { + "type": "Session", + "session_id": session.session_id, + "name": session.friendly_name, + "title": session.full_title, + "platform": session.platform, + "product_version": session.product_version, + "quality": quality, + "video_decision": video_decision.title(), + "transcode_decision": decision.title(), + "media_type": session.media_type.title(), + "audio_codec": session.audio_codec.upper(), + "audio_profile": session.audio_profile.upper(), + "stream_audio_codec": session.stream_audio_codec.upper(), + "quality_profile": session.quality_profile, + "progress_percent": session.progress_percent, + "region_code": geodata.subdivisions.most_specific.iso_code, + "location": geodata.city.name, + "full_location": '{} - {}'.format(geodata.subdivisions.most_specific.name, + geodata.city.name), + "latitude": latitude, + "longitude": longitude, + "player_state": player_state, + "device_type": session.platform, + "server": server.id + }, + "time": self.now, + "fields": { + "session_id": session.session_id, + "session_key": session.session_key + } + } + ) + + self.influx_push(influx_payload) diff --git a/Varken/varken.py b/Varken/varken.py index c6e9349..8be5b2c 100644 --- a/Varken/varken.py +++ b/Varken/varken.py @@ -4,6 +4,7 @@ from time import sleep from Varken.iniparser import INIParser from Varken.sonarr import SonarrAPI +from Varken.tautulli import TautulliAPI def threaded(job, days=None): @@ -27,6 +28,15 @@ if __name__ == "__main__": schedule.every(server.future_days_run_seconds).seconds.do(threaded, SONARR.get_future, server.future_days) + if CONFIG.tautulli_enabled: + TAUTULLI = TautulliAPI(CONFIG.tautulli_servers, CONFIG.influx_server) + + for server in CONFIG.tautulli_servers: + if server.get_activity: + schedule.every(server.get_activity_run_seconds).seconds.do(threaded, TAUTULLI.get_activity) + if server.get_sessions: + schedule.every(server.get_sessions_run_seconds).seconds.do(threaded, TAUTULLI.get_sessions) + while True: schedule.run_pending() sleep(1) diff --git a/varken.example.ini b/varken.example.ini index 10b7c70..aaa3bb6 100644 --- a/varken.example.ini +++ b/varken.example.ini @@ -8,8 +8,8 @@ [global] sonarr_server_ids = 1,2 radarr_server_ids = 1,2 +tautulli_server_ids = 1 ombi = true -tautulli = true asa = false [influxdb] @@ -18,6 +18,17 @@ port = 8086 username = root password = root +[tautulli-1] +url = tautulli.domain.tld +fallback_ip = 0.0.0.0 +apikey = xxxxxxxxxxxxxxxx +ssl = false +verify_ssl = true +get_activity = true +get_activity_run_seconds = 30 +get_sessions = true +get_sessions_run_seconds = 30 + [sonarr-1] url = sonarr1.domain.tld apikey = xxxxxxxxxxxxxxxx @@ -60,12 +71,6 @@ apikey = xxxxxxxxxxxxxxxx ssl = false verify_ssl = true -[tautulli] -url = tautulli.domain.tld -fallback_ip = 0.0.0.0 -apikey = xxxxxxxxxxxxxxxx -ssl = false -verify_ssl = true [asa] url = firewall.domain.tld From 98677892e21ce82d9e84892779b0327460679719 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 17:11:46 -0600 Subject: [PATCH 035/120] fixed time --- Varken/sonarr.py | 3 +++ Varken/tautulli.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 65f5df0..79cab8a 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -24,6 +24,7 @@ class SonarrAPI(object): def get_missing(self, days_past): endpoint = '/api/calendar' last_days = str(date.today() + timedelta(days=-days_past)) + self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'start': last_days, 'end': self.today} influx_payload = [] @@ -66,6 +67,7 @@ class SonarrAPI(object): @logging def get_future(self, future_days): endpoint = '/api/calendar/' + self.now = datetime.now(timezone.utc).astimezone().isoformat() future = str(date.today() + timedelta(days=future_days)) influx_payload = [] @@ -109,6 +111,7 @@ class SonarrAPI(object): def get_queue(self, notimplemented): influx_payload = [] endpoint = '/api/queue' + self.now = datetime.now(timezone.utc).astimezone().isoformat() for server in self.servers: queue = [] diff --git a/Varken/tautulli.py b/Varken/tautulli.py index 6912c90..da01739 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -5,12 +5,13 @@ import requests from Varken.helpers import TautulliStream, geo_lookup from Varken.logger import logging + class TautulliAPI(object): def __init__(self, servers, influx_server): # Set Time of initialization self.now = datetime.now(timezone.utc).astimezone().isoformat() self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, - influx_server.password, 'plex') + influx_server.password, 'plex2') self.servers = servers self.session = requests.Session() self.endpoint = '/api/v2' @@ -21,6 +22,7 @@ class TautulliAPI(object): @logging def get_activity(self, notimplemented): + self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} influx_payload = [] @@ -53,6 +55,7 @@ class TautulliAPI(object): @logging def get_sessions(self, notimplemented): + self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} influx_payload = [] @@ -60,7 +63,6 @@ class TautulliAPI(object): params['apikey'] = server.apikey g = self.session.get(server.url + self.endpoint, params=params, verify=server.verify_ssl) get = g.json()['response']['data']['sessions'] - print(get) sessions = [TautulliStream(**session) for session in get] for session in sessions: From ecb330206a70aa3a2c4b89241d6ecf9f528ec353 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 17:28:50 -0600 Subject: [PATCH 036/120] deleted extras and fixed downloaded --- .gitignore | 1 + Varken/helpers.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 185ff03..50ca5a6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ GeoLite2-City.tar.gz .idea/ .idea/* varken.ini +data/ diff --git a/Varken/helpers.py b/Varken/helpers.py index fea68b1..d655474 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -278,7 +278,7 @@ def geoip_download(): for files in tar.getmembers(): if 'GeoLite2-City.mmdb' in files.name: files.name = os.path.basename(files.name) - tar.extract(files, '{}/'.format(os.path.dirname(os.path.realpath(__file__)))) + tar.extract(files, abspath(join('..', 'data'))) os.remove(tar_dbfile) def geo_lookup(ipaddress): From 762e644d4638af3153eac486a64d399919b29562 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 17:46:56 -0600 Subject: [PATCH 037/120] moved files --- .gitignore | 9 ++-- Legacy/cisco_asa.py | 2 +- Legacy/ombi.py | 2 +- Legacy/radarr.py | 2 +- Varken/iniparser.py | 4 +- varken.example.ini | 81 ----------------------------------- Varken/varken.py => varken.py | 0 7 files changed, 9 insertions(+), 91 deletions(-) delete mode 100644 varken.example.ini rename Varken/varken.py => varken.py (100%) diff --git a/.gitignore b/.gitignore index 50ca5a6..a7e3e02 100644 --- a/.gitignore +++ b/.gitignore @@ -5,12 +5,11 @@ .Trashes ehthumbs.db Thumbs.db -Varken/configuration.py -Varken/config.ini __pycache__ GeoLite2-City.mmdb GeoLite2-City.tar.gz +data/varken.ini +data/GeoLite2-City.mmdb +data/GeoLite2-City.tar.gz .idea/ -.idea/* -varken.ini -data/ +Legacy/configuration.py diff --git a/Legacy/cisco_asa.py b/Legacy/cisco_asa.py index bb44c93..33ebe44 100644 --- a/Legacy/cisco_asa.py +++ b/Legacy/cisco_asa.py @@ -3,7 +3,7 @@ import requests from datetime import datetime, timezone from influxdb import InfluxDBClient -from Varken import configuration +from Legacy import configuration current_time = datetime.now(timezone.utc).astimezone().isoformat() diff --git a/Legacy/ombi.py b/Legacy/ombi.py index 898b8c8..bcea45f 100644 --- a/Legacy/ombi.py +++ b/Legacy/ombi.py @@ -5,7 +5,7 @@ from datetime import datetime, timezone from influxdb import InfluxDBClient import argparse from argparse import RawTextHelpFormatter -from Varken import configuration +from Legacy import configuration headers = {'Apikey': configuration.ombi_api_key} diff --git a/Legacy/radarr.py b/Legacy/radarr.py index 25ddff9..eea3499 100644 --- a/Legacy/radarr.py +++ b/Legacy/radarr.py @@ -5,7 +5,7 @@ from datetime import datetime, timezone from influxdb import InfluxDBClient import argparse from argparse import RawTextHelpFormatter -from Varken import configuration +from Legacy import configuration def now_iso(): diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 449d052..ec7f82b 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,6 +1,6 @@ import sys import configparser -from os.path import abspath, dirname, join +from os.path import abspath, join from Varken.helpers import Server, TautulliServer, SonarrServer, InfluxServer @@ -28,7 +28,7 @@ class INIParser(object): self.parse_opts() def read_file(self): - file_path = abspath(join(dirname(__file__), '..', 'varken.ini')) + file_path = abspath(join('..', 'data', 'varken.ini')) with open(file_path) as config_ini: self.config.read_file(config_ini) diff --git a/varken.example.ini b/varken.example.ini deleted file mode 100644 index aaa3bb6..0000000 --- a/varken.example.ini +++ /dev/null @@ -1,81 +0,0 @@ -# Notes: -# - Sonarr + Radarr scripts support multiple servers. You can remove the second -# server by putting a # in front of the lines and section name, and removing -# that number from your server_ids list -# - fallback_ip, This is used when there is no IP listed in tautulli. -# This can happen when you are streaming locally. This is usually your public IP. - -[global] -sonarr_server_ids = 1,2 -radarr_server_ids = 1,2 -tautulli_server_ids = 1 -ombi = true -asa = false - -[influxdb] -url = influxdb.domain.tld -port = 8086 -username = root -password = root - -[tautulli-1] -url = tautulli.domain.tld -fallback_ip = 0.0.0.0 -apikey = xxxxxxxxxxxxxxxx -ssl = false -verify_ssl = true -get_activity = true -get_activity_run_seconds = 30 -get_sessions = true -get_sessions_run_seconds = 30 - -[sonarr-1] -url = sonarr1.domain.tld -apikey = xxxxxxxxxxxxxxxx -ssl = false -verify_ssl = true -missing_days = 7 -missing_days_run_seconds = 300 -future_days = 1 -future_days_run_seconds = 300 -queue = true -queue_run_seconds = 300 - -[sonarr-2] -url = sonarr2.domain.tld -apikey = yyyyyyyyyyyyyyyy -ssl = false -verify_ssl = true -missing_days = 7 -missing_days_run_seconds = 300 -future_days = 1 -future_days_run_seconds = 300 -queue = true -queue_run_seconds = 300 - -[radarr-1] -url = radarr1.domain.tld -apikey = xxxxxxxxxxxxxxxx -ssl = false -verify_ssl = true - -[radarr-2] -url = radarr2.domain.tld -apikey = yyyyyyyyyyyyyyyy -ssl = false -verify_ssl = true - -[ombi] -url = ombi.domain.tld -apikey = xxxxxxxxxxxxxxxx -ssl = false -verify_ssl = true - - -[asa] -url = firewall.domain.tld -username = cisco -password = cisco -influx_db = asa -ssl = false -verify_ssl = true \ No newline at end of file diff --git a/Varken/varken.py b/varken.py similarity index 100% rename from Varken/varken.py rename to varken.py From 40f6d524297ea7866521473166ae6eea91731dea Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 17:47:31 -0600 Subject: [PATCH 038/120] moved files --- data/varken.example.ini | 81 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 data/varken.example.ini diff --git a/data/varken.example.ini b/data/varken.example.ini new file mode 100644 index 0000000..aaa3bb6 --- /dev/null +++ b/data/varken.example.ini @@ -0,0 +1,81 @@ +# Notes: +# - Sonarr + Radarr scripts support multiple servers. You can remove the second +# server by putting a # in front of the lines and section name, and removing +# that number from your server_ids list +# - fallback_ip, This is used when there is no IP listed in tautulli. +# This can happen when you are streaming locally. This is usually your public IP. + +[global] +sonarr_server_ids = 1,2 +radarr_server_ids = 1,2 +tautulli_server_ids = 1 +ombi = true +asa = false + +[influxdb] +url = influxdb.domain.tld +port = 8086 +username = root +password = root + +[tautulli-1] +url = tautulli.domain.tld +fallback_ip = 0.0.0.0 +apikey = xxxxxxxxxxxxxxxx +ssl = false +verify_ssl = true +get_activity = true +get_activity_run_seconds = 30 +get_sessions = true +get_sessions_run_seconds = 30 + +[sonarr-1] +url = sonarr1.domain.tld +apikey = xxxxxxxxxxxxxxxx +ssl = false +verify_ssl = true +missing_days = 7 +missing_days_run_seconds = 300 +future_days = 1 +future_days_run_seconds = 300 +queue = true +queue_run_seconds = 300 + +[sonarr-2] +url = sonarr2.domain.tld +apikey = yyyyyyyyyyyyyyyy +ssl = false +verify_ssl = true +missing_days = 7 +missing_days_run_seconds = 300 +future_days = 1 +future_days_run_seconds = 300 +queue = true +queue_run_seconds = 300 + +[radarr-1] +url = radarr1.domain.tld +apikey = xxxxxxxxxxxxxxxx +ssl = false +verify_ssl = true + +[radarr-2] +url = radarr2.domain.tld +apikey = yyyyyyyyyyyyyyyy +ssl = false +verify_ssl = true + +[ombi] +url = ombi.domain.tld +apikey = xxxxxxxxxxxxxxxx +ssl = false +verify_ssl = true + + +[asa] +url = firewall.domain.tld +username = cisco +password = cisco +influx_db = asa +ssl = false +verify_ssl = true \ No newline at end of file From 7422d0a029b55a1cf0f2f1c2cafff2e5bd851ac7 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 17:47:37 -0600 Subject: [PATCH 039/120] moved files --- Legacy/configuration.py | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Legacy/configuration.py diff --git a/Legacy/configuration.py b/Legacy/configuration.py new file mode 100644 index 0000000..a0df50a --- /dev/null +++ b/Legacy/configuration.py @@ -0,0 +1,49 @@ +''' +Notes: + - Domains should be either http(s)://subdomain.domain.com or http(s)://domain.com/url_suffix + + - Sonarr + Radarr scripts support multiple servers. You can remove the second + server by putting a # in front of the line. + + - tautulli_failback_ip, This is used when there is no IP listed in tautulli. + This can happen when you are streaming locally. This is usually your public IP. +''' + +########################### INFLUXDB CONFIG ########################### +influxdb_url = 'influxdb.domain.tld' +influxdb_port = 8086 +influxdb_username = '' +influxdb_password = '' + +############################ SONARR CONFIG ############################ +sonarr_server_list = [ + ('https://sonarr1.domain.tld', 'xxxxxxxxxxxxxxx', '1'), + ('https://sonarr2.domain.tld', 'xxxxxxxxxxxxxxx', '2'), + #('https://sonarr3.domain.tld', 'xxxxxxxxxxxxxxx', '3') + ] +sonarr_influxdb_db_name = 'plex' + +############################ RADARR CONFIG ############################ +radarr_server_list = [ + ('https://radarr1.domain.tld', 'xxxxxxxxxxxxxxx', '1'), + ('https://radarr2.domain.tld', 'xxxxxxxxxxxxxxx', '2'), + #('https://radarr3.domain.tld', 'xxxxxxxxxxxxxxx', '3') + ] +radarr_influxdb_db_name = 'plex' + +############################ OMBI CONFIG ############################## +ombi_url = 'https://ombi.domain.tld' +ombi_api_key = 'xxxxxxxxxxxxxxx' +ombi_influxdb_db_name = 'plex' + +########################## TAUTULLI CONFIG ############################ +tautulli_url = 'https://tautulli.domain.tld' +tautulli_api_key = 'xxxxxxxxxxxxxxx' +tautulli_failback_ip = '' +tautulli_influxdb_db_name = 'plex' + +########################## FIREWALL CONFIG ############################ +asa_url = 'https://firewall.domain.tld' +asa_username = 'cisco' +asa_password = 'cisco' +asa_influxdb_db_name = 'asa' From e68dfbe9bbeb0893ca3a4dcca40234b03925feaa Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 17:51:28 -0600 Subject: [PATCH 040/120] moved files --- Varken/helpers.py | 2 +- Varken/iniparser.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Varken/helpers.py b/Varken/helpers.py index d655474..9ffbf5a 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -278,7 +278,7 @@ def geoip_download(): for files in tar.getmembers(): if 'GeoLite2-City.mmdb' in files.name: files.name = os.path.basename(files.name) - tar.extract(files, abspath(join('..', 'data'))) + tar.extract(files, abspath(join('.', 'data'))) os.remove(tar_dbfile) def geo_lookup(ipaddress): diff --git a/Varken/iniparser.py b/Varken/iniparser.py index ec7f82b..7793161 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -28,7 +28,7 @@ class INIParser(object): self.parse_opts() def read_file(self): - file_path = abspath(join('..', 'data', 'varken.ini')) + file_path = abspath(join('.', 'data', 'varken.ini')) with open(file_path) as config_ini: self.config.read_file(config_ini) From d2829608bdfaa9d80853954774b7fbbd4fd9c178 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 17:53:03 -0600 Subject: [PATCH 041/120] forgot to delete period --- Varken/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Varken/helpers.py b/Varken/helpers.py index 9ffbf5a..5939032 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -271,7 +271,7 @@ class TautulliStream(NamedTuple): stream_subtitle_container: str def geoip_download(): - tar_dbfile = abspath(join('..', 'data', 'GeoLite2-City.tar.gz')) + tar_dbfile = abspath(join('.', 'data', 'GeoLite2-City.tar.gz')) url = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz' urlretrieve(url, tar_dbfile) tar = tarfile.open(tar_dbfile, "r:gz") From d1079ab949ea1b6e1835cf802f9e7334682fea14 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 17:54:47 -0600 Subject: [PATCH 042/120] forgot to delete period again --- Varken/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Varken/helpers.py b/Varken/helpers.py index 5939032..f84689b 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -283,7 +283,7 @@ def geoip_download(): def geo_lookup(ipaddress): - dbfile = abspath(join('..', 'data', 'GeoLite2-City.mmdb')) + dbfile = abspath(join('.', 'data', 'GeoLite2-City.mmdb')) now = time.time() try: From bf1db64b82e86cf1f72c91c554514996acbbaf1b Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 20:33:33 -0600 Subject: [PATCH 043/120] migrated radarr --- .gitignore | 2 - Legacy/radarr.py | 171 ---------------- Varken/helpers.py | 426 ++++++++++++++++++++++------------------ Varken/iniparser.py | 12 +- Varken/radarr.py | 106 ++++++++++ Varken/sonarr.py | 2 - data/varken.example.ini | 5 + varken.py | 12 ++ 8 files changed, 371 insertions(+), 365 deletions(-) delete mode 100644 Legacy/radarr.py create mode 100644 Varken/radarr.py diff --git a/.gitignore b/.gitignore index a7e3e02..f238ac1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,5 @@ __pycache__ GeoLite2-City.mmdb GeoLite2-City.tar.gz data/varken.ini -data/GeoLite2-City.mmdb -data/GeoLite2-City.tar.gz .idea/ Legacy/configuration.py diff --git a/Legacy/radarr.py b/Legacy/radarr.py deleted file mode 100644 index eea3499..0000000 --- a/Legacy/radarr.py +++ /dev/null @@ -1,171 +0,0 @@ -# Do not edit this script. Edit configuration.py -import sys -import requests -from datetime import datetime, timezone -from influxdb import InfluxDBClient -import argparse -from argparse import RawTextHelpFormatter -from Legacy import configuration - - -def now_iso(): - now_iso = datetime.now(timezone.utc).astimezone().isoformat() - return now_iso - - -def influx_sender(influx_payload): - influx = InfluxDBClient(configuration.influxdb_url, configuration.influxdb_port, configuration.influxdb_username, - configuration.influxdb_password, configuration.radarr_influxdb_db_name) - influx.write_points(influx_payload) - - -def get_missing_movies(): - # Set the time here so we have one timestamp to work with - now = now_iso() - missing = [] - influx_payload = [] - - for radarr_url, radarr_api_key, server_id in configuration.radarr_server_list: - headers = {'X-Api-Key': radarr_api_key} - get_movies = requests.get('{}/api/movie'.format(radarr_url), headers=headers).json() - movies = {d['tmdbId']: d for d in get_movies} - - for movie in movies.keys(): - if not movies[movie]['downloaded']: - movie_name = ('{} ({})'.format(movies[movie]['title'], movies[movie]['year'])) - missing.append((movie_name, movies[movie]['tmdbId'])) - - for movie, id in missing: - influx_payload.append( - { - "measurement": "Radarr", - "tags": { - "type": "Missing", - "tmdbId": id, - "server": server_id - }, - "time": now, - "fields": { - "name": movie - } - } - ) - # Empty missing or else things get foo bared - missing = [] - - return influx_payload - - -def get_missing_avl(): - # Set the time here so we have one timestamp to work with - now = now_iso() - missing = [] - influx_payload = [] - - for radarr_url, radarr_api_key, server_id in configuration.radarr_server_list: - headers = {'X-Api-Key': radarr_api_key} - get_movies = requests.get('{}/api/movie'.format(radarr_url), headers=headers).json() - movies = {d['tmdbId']: d for d in get_movies} - - for movie in movies.keys(): - if not movies[movie]['downloaded']: - if movies[movie]['isAvailable'] is True: - movie_name = ('{} ({})'.format(movies[movie]['title'], movies[movie]['year'])) - missing.append((movie_name, movies[movie]['tmdbId'])) - - - for movie, id in missing: - influx_payload.append( - { - "measurement": "Radarr", - "tags": { - "type": "Missing_Available", - "tmdbId": id, - "server": server_id - }, - "time": now, - "fields": { - "name": movie, - } - } - ) - # Empty missing or else things get foo bared - missing = [] - - return influx_payload - - -def get_queue_movies(): - # Set the time here so we have one timestamp to work with - now = now_iso() - influx_payload = [] - queue = [] - - for radarr_url, radarr_api_key, server_id in configuration.radarr_server_list: - headers = {'X-Api-Key': radarr_api_key} - get_movies = requests.get('{}/api/queue'.format(radarr_url), headers=headers).json() - queue_movies = {d['id']: d for d in get_movies} - - for movie in queue_movies.keys(): - name = '{} ({})'.format(queue_movies[movie]['movie']['title'], queue_movies[movie]['movie']['year']) - quality = (queue_movies[movie]['quality']['quality']['name']) - protocol = (queue_movies[movie]['protocol'].upper()) - - if protocol == 'USENET': - protocol_id = 1 - else: - protocol_id = 0 - - queue.append((name, queue_movies[movie]['id'])) - - for movie, id in queue: - influx_payload.append( - { - "measurement": "Radarr", - "tags": { - "type": "Queue", - "tmdbId": id, - "server": server_id - }, - "time": now, - "fields": { - "name": movie, - "quality": quality, - "protocol": protocol, - "protocol_id": protocol_id - } - } - ) - # Empty queue or else things get foo bared - queue = [] - - return influx_payload - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(prog='Radarr stats operations', - description='Script to aid in data gathering from Radarr', formatter_class=RawTextHelpFormatter) - - parser.add_argument("--missing", action='store_true', - help='Get missing movies') - - parser.add_argument("--missing_avl", action='store_true', - help='Get missing yet available movies') - - parser.add_argument("--queue", action='store_true', - help='Get movies in queue') - - opts = parser.parse_args() - - if opts.missing: - influx_sender(get_missing_movies()) - - elif opts.missing_avl: - influx_sender(get_missing_avl()) - - elif opts.queue: - influx_sender(get_queue_movies()) - - elif len(sys.argv) == 1: - parser.print_help(sys.stderr) - sys.exit(1) diff --git a/Varken/helpers.py b/Varken/helpers.py index f84689b..434f2ee 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -25,7 +25,48 @@ class TVShow(NamedTuple): id: int = None +class Movie(NamedTuple): + title: str = None + alternativeTitles: list = None + secondaryYearSourceId: int = None + sortTitle: str = None + sizeOnDisk: int = None + status: str = None + overview: str = None + inCinemas: str = None + images: list = None + downloaded: bool = None + year: int = None + secondaryYear: str = None + hasFile: bool = None + youTubeTrailerId: str = None + studio: str = None + path: str = None + profileId: int = None + pathState: str = None + monitored: bool = None + minimumAvailability: str = None + isAvailable: bool = None + folderName: str = None + runtime: int = None + lastInfoSync: str = None + cleanTitle: str = None + imdbId: str = None + tmdbId: int = None + titleSlug: str = None + genres: list = None + tags: list = None + added: str = None + ratings: dict = None + movieFile: dict = None + qualityProfileId: int = None + physicalRelease: str = None + physicalReleaseNote: str = None + website: str = None + id: int = None + class Queue(NamedTuple): + movie: dict = None series: dict = None episode: dict = None quality: dict = None @@ -54,6 +95,15 @@ class SonarrServer(NamedTuple): queue: bool = False queue_run_seconds: int = 1 +class RadarrServer(NamedTuple): + id: int = None + url: str = None + api_key: str = None + verify_ssl: bool = False + queue: bool = False + queue_run_seconds: int = 1 + get_missing: bool = False + get_missing_run_seconds: int = 30 class Server(NamedTuple): id: int = None @@ -82,193 +132,195 @@ class InfluxServer(NamedTuple): class TautulliStream(NamedTuple): - rating: str - transcode_width: str - labels: list - stream_bitrate: str - bandwidth: str - optimized_version: int - video_language: str - parent_rating_key: str - rating_key: str - platform_version: str - transcode_hw_decoding: int - thumb: str - title: str - video_codec_level: str - tagline: str - last_viewed_at: str - audio_sample_rate: str - user_rating: str - platform: str - collections: list - location: str - transcode_container: str - audio_channel_layout: str - local: str - stream_subtitle_format: str - stream_video_ref_frames: str - transcode_hw_encode_title: str - stream_container_decision: str - audience_rating: str - full_title: str - ip_address: str - subtitles: int - stream_subtitle_language: str - channel_stream: int - video_bitrate: str - is_allow_sync: int - stream_video_bitrate: str - summary: str - stream_audio_decision: str - aspect_ratio: str - audio_bitrate_mode: str - transcode_hw_decode_title: str - stream_audio_channel_layout: str - deleted_user: int - library_name: str - art: str - stream_video_resolution: str - video_profile: str - sort_title: str - stream_video_codec_level: str - stream_video_height: str - year: str - stream_duration: str - stream_audio_channels: str - video_language_code: str - transcode_key: str - transcode_throttled: int - container: str - stream_audio_bitrate: str - user: str - selected: int - product_version: str - subtitle_location: str - transcode_hw_requested: int - video_height: str - state: str - is_restricted: int - email: str - stream_container: str - transcode_speed: str - video_bit_depth: str - stream_audio_sample_rate: str - grandparent_title: str - studio: str - transcode_decision: str - video_width: str - bitrate: str - machine_id: str - originally_available_at: str - video_frame_rate: str - synced_version_profile: str - friendly_name: str - audio_profile: str - optimized_version_title: str - platform_name: str - stream_video_language: str - keep_history: int - stream_audio_codec: str - stream_video_codec: str - grandparent_thumb: str - synced_version: int - transcode_hw_decode: str - user_thumb: str - stream_video_width: str - height: str - stream_subtitle_decision: str - audio_codec: str - parent_title: str - guid: str - audio_language_code: str - transcode_video_codec: str - transcode_audio_codec: str - stream_video_decision: str - user_id: int - transcode_height: str - transcode_hw_full_pipeline: int - throttled: str - quality_profile: str - width: str - live: int - stream_subtitle_forced: int - media_type: str - video_resolution: str - stream_subtitle_location: str - do_notify: int - video_ref_frames: str - stream_subtitle_language_code: str - audio_channels: str - stream_audio_language_code: str - optimized_version_profile: str - relay: int - duration: str - rating_image: str - is_home_user: int - is_admin: int - ip_address_public: str - allow_guest: int - transcode_audio_channels: str - stream_audio_channel_layout_: str - media_index: str - stream_video_framerate: str - transcode_hw_encode: str - grandparent_rating_key: str - original_title: str - added_at: str - banner: str - bif_thumb: str - parent_media_index: str - live_uuid: str - audio_language: str - stream_audio_bitrate_mode: str - username: str - subtitle_decision: str - children_count: str - updated_at: str - player: str - subtitle_format: str - file: str - file_size: str - session_key: str - id: str - subtitle_container: str - genres: list - stream_video_language_code: str - indexes: int - video_decision: str - stream_audio_language: str - writers: list - actors: list - progress_percent: str - audio_decision: str - subtitle_forced: int - profile: str - product: str - view_offset: str - type: str - audience_rating_image: str - audio_bitrate: str - section_id: str - stream_subtitle_codec: str - subtitle_codec: str - video_codec: str - device: str - stream_video_bit_depth: str - video_framerate: str - transcode_hw_encoding: int - transcode_protocol: str - shared_libraries: list - stream_aspect_ratio: str - content_rating: str - session_id: str - directors: list - parent_thumb: str - subtitle_language_code: str - transcode_progress: int - subtitle_language: str - stream_subtitle_container: str + rating: str = None + transcode_width: str = None + labels: list = None + stream_bitrate: str = None + bandwidth: str = None + optimized_version: int = None + video_language: str = None + parent_rating_key: str = None + rating_key: str = None + platform_version: str = None + transcode_hw_decoding: int = None + thumb: str = None + title: str = None + video_codec_level: str = None + tagline: str = None + last_viewed_at: str = None + audio_sample_rate: str = None + user_rating: str = None + platform: str = None + collections: list = None + location: str = None + transcode_container: str = None + audio_channel_layout: str = None + local: str = None + stream_subtitle_format: str = None + stream_video_ref_frames: str = None + transcode_hw_encode_title: str = None + stream_container_decision: str = None + audience_rating: str = None + full_title: str = None + ip_address: str = None + subtitles: int = None + stream_subtitle_language: str = None + channel_stream: int = None + video_bitrate: str = None + is_allow_sync: int = None + stream_video_bitrate: str = None + summary: str = None + stream_audio_decision: str = None + aspect_ratio: str = None + audio_bitrate_mode: str = None + transcode_hw_decode_title: str = None + stream_audio_channel_layout: str = None + deleted_user: int = None + library_name: str = None + art: str = None + stream_video_resolution: str = None + video_profile: str = None + sort_title: str = None + stream_video_codec_level: str = None + stream_video_height: str = None + year: str = None + stream_duration: str = None + stream_audio_channels: str = None + video_language_code: str = None + transcode_key: str = None + transcode_throttled: int = None + container: str = None + stream_audio_bitrate: str = None + user: str = None + selected: int = None + product_version: str = None + subtitle_location: str = None + transcode_hw_requested: int = None + video_height: str = None + state: str = None + is_restricted: int = None + email: str = None + stream_container: str = None + transcode_speed: str = None + video_bit_depth: str = None + stream_audio_sample_rate: str = None + grandparent_title: str = None + studio: str = None + transcode_decision: str = None + video_width: str = None + bitrate: str = None + machine_id: str = None + originally_available_at: str = None + video_frame_rate: str = None + synced_version_profile: str = None + friendly_name: str = None + audio_profile: str = None + optimized_version_title: str = None + platform_name: str = None + stream_video_language: str = None + keep_history: int = None + stream_audio_codec: str = None + stream_video_codec: str = None + grandparent_thumb: str = None + synced_version: int = None + transcode_hw_decode: str = None + user_thumb: str = None + stream_video_width: str = None + height: str = None + stream_subtitle_decision: str = None + audio_codec: str = None + parent_title: str = None + guid: str = None + audio_language_code: str = None + transcode_video_codec: str = None + transcode_audio_codec: str = None + stream_video_decision: str = None + user_id: int = None + transcode_height: str = None + transcode_hw_full_pipeline: int = None + throttled: str = None + quality_profile: str = None + width: str = None + live: int = None + stream_subtitle_forced: int = None + media_type: str = None + video_resolution: str = None + stream_subtitle_location: str = None + do_notify: int = None + video_ref_frames: str = None + stream_subtitle_language_code: str = None + audio_channels: str = None + stream_audio_language_code: str = None + optimized_version_profile: str = None + relay: int = None + duration: str = None + rating_image: str = None + is_home_user: int = None + is_admin: int = None + ip_address_public: str = None + allow_guest: int = None + transcode_audio_channels: str = None + stream_audio_channel_layout_: str = None + media_index: str = None + stream_video_framerate: str = None + transcode_hw_encode: str = None + grandparent_rating_key: str = None + original_title: str = None + added_at: str = None + banner: str = None + bif_thumb: str = None + parent_media_index: str = None + live_uuid: str = None + audio_language: str = None + stream_audio_bitrate_mode: str = None + username: str = None + subtitle_decision: str = None + children_count: str = None + updated_at: str = None + player: str = None + subtitle_format: str = None + file: str = None + file_size: str = None + session_key: str = None + id: str = None + subtitle_container: str = None + genres: list = None + stream_video_language_code: str = None + indexes: int = None + video_decision: str = None + stream_audio_language: str = None + writers: list = None + actors: list = None + progress_percent: str = None + audio_decision: str = None + subtitle_forced: int = None + profile: str = None + product: str = None + view_offset: str = None + type: str = None + audience_rating_image: str = None + audio_bitrate: str = None + section_id: str = None + stream_subtitle_codec: str = None + subtitle_codec: str = None + video_codec: str = None + device: str = None + stream_video_bit_depth: str = None + video_framerate: str = None + transcode_hw_encoding: int = None + transcode_protocol: str = None + shared_libraries: list = None + stream_aspect_ratio: str = None + content_rating: str = None + session_id: str = None + directors: list = None + parent_thumb: str = None + subtitle_language_code: str = None + transcode_progress: int = None + subtitle_language: str = None + stream_subtitle_container: str = None + _cache_time: int = None + def geoip_download(): tar_dbfile = abspath(join('.', 'data', 'GeoLite2-City.tar.gz')) diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 7793161..89d56fc 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,7 +1,7 @@ import sys import configparser from os.path import abspath, join -from Varken.helpers import Server, TautulliServer, SonarrServer, InfluxServer +from Varken.helpers import Server, TautulliServer, SonarrServer, InfluxServer, RadarrServer class INIParser(object): @@ -81,7 +81,7 @@ class INIParser(object): except ValueError: self.radarr_enabled = True - if self.sonarr_enabled: + if self.radarr_enabled: sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') for server_id in sids: @@ -90,8 +90,14 @@ class INIParser(object): apikey = self.config.get(radarr_section, 'apikey') scheme = 'https://' if self.config.getboolean(radarr_section, 'ssl') else 'http://' verify_ssl = self.config.getboolean(radarr_section, 'verify_ssl') + queue = self.config.getboolean(radarr_section, 'queue') + queue_run_seconds = self.config.getint(radarr_section, 'queue_run_seconds') + get_missing = self.config.getboolean(radarr_section, 'get_missing') + get_missing_run_seconds = self.config.getint(radarr_section, 'get_missing_run_seconds') - self.radarr_servers.append(Server(server_id, scheme + url, apikey, verify_ssl)) + server = RadarrServer(server_id, scheme + url, apikey, verify_ssl, queue, queue_run_seconds, + get_missing, get_missing_run_seconds) + self.radarr_servers.append(server) # Parse Tautulli options try: diff --git a/Varken/radarr.py b/Varken/radarr.py new file mode 100644 index 0000000..33f7c0b --- /dev/null +++ b/Varken/radarr.py @@ -0,0 +1,106 @@ +import requests +from datetime import datetime, timezone +from influxdb import InfluxDBClient + +from Varken.logger import logging +from Varken.helpers import Movie, Queue + + +class RadarrAPI(object): + def __init__(self, servers, influx_server): + self.now = datetime.now(timezone.utc).astimezone().isoformat() + self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, + influx_server.password, 'plex2') + self.servers = servers + # Create session to reduce server web thread load, and globally define pageSize for all requests + self.session = requests.Session() + + def influx_push(self, payload): + # TODO: error handling for failed connection + self.influx.write_points(payload) + + @logging + def get_missing(self, notimplemented): + endpoint = '/api/movie' + self.now = datetime.now(timezone.utc).astimezone().isoformat() + influx_payload = [] + + for server in self.servers: + missing = [] + headers = {'X-Api-Key': server.api_key} + get = self.session.get(server.url + endpoint, headers=headers, verify=server.verify_ssl).json() + movies = [Movie(**movie) for movie in get] + + for movie in movies: + if server.get_missing: + if not movie.downloaded and movie.isAvailable: + ma = True + else: + ma = False + movie_name = '{} ({})'.format(movie.title, movie.year) + missing.append((movie_name, ma, movie.tmdbId)) + + for title, ma, mid in missing: + influx_payload.append( + { + "measurement": "Radarr", + "tags": { + "Missing": True, + "Missing_Available": ma, + "tmdbId": mid, + "server": server.id + }, + "time": self.now, + "fields": { + "name": title + } + } + ) + + self.influx_push(influx_payload) + + @logging + def get_queue(self, notimplemented): + endpoint = '/api/queue' + self.now = datetime.now(timezone.utc).astimezone().isoformat() + influx_payload = [] + + for server in self.servers: + queue = [] + headers = {'X-Api-Key': server.api_key} + get = self.session.get(server.url + endpoint, headers=headers, verify=server.verify_ssl).json() + for movie in get: + movie['movie'] = Movie(**movie['movie']) + download_queue = [Queue(**movie) for movie in get] + + for queue_item in download_queue: + name = '{} ({})'.format(queue_item.movie.title, queue_item.movie.year) + + if queue_item.protocol.upper() == 'USENET': + protocol_id = 1 + else: + protocol_id = 0 + + queue.append((name, queue_item.quality['quality']['name'], queue_item.protocol.upper(), + protocol_id, queue_item.id)) + + for movie, quality, protocol, protocol_id, qid in queue: + influx_payload.append( + { + "measurement": "Radarr", + "tags": { + "type": "Queue", + "tmdbId": qid, + "server": server.id + }, + "time": self.now, + "fields": { + "name": movie, + "quality": quality, + "protocol": protocol, + "protocol_id": protocol_id + } + } + ) + + self.influx_push(influx_payload) diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 79cab8a..9ee89c2 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# Do not edit this script. Edit configuration.py import requests from influxdb import InfluxDBClient from datetime import datetime, timezone, date, timedelta diff --git a/data/varken.example.ini b/data/varken.example.ini index aaa3bb6..0ebd511 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -58,6 +58,11 @@ url = radarr1.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false verify_ssl = true +queue = true +queue_run_seconds = 300 +get_missing = true +get_missing_available = true +get_missing_run_seconds = 300 [radarr-2] url = radarr2.domain.tld diff --git a/varken.py b/varken.py index 8be5b2c..482e788 100644 --- a/varken.py +++ b/varken.py @@ -5,6 +5,7 @@ from time import sleep from Varken.iniparser import INIParser from Varken.sonarr import SonarrAPI from Varken.tautulli import TautulliAPI +from Varken.radarr import RadarrAPI def threaded(job, days=None): @@ -37,6 +38,17 @@ if __name__ == "__main__": if server.get_sessions: schedule.every(server.get_sessions_run_seconds).seconds.do(threaded, TAUTULLI.get_sessions) + if CONFIG.radarr_enabled: + RADARR = RadarrAPI(CONFIG.radarr_servers, CONFIG.influx_server) + + for server in CONFIG.radarr_servers: + if any([server.get_missing, server.get_missing_available]): + schedule.every(server.get_missing_run_seconds).seconds.do(threaded, RADARR.get_missing) + if server.queue: + schedule.every(server.queue_run_seconds).seconds.do(threaded, RADARR.get_queue) + + + while True: schedule.run_pending() sleep(1) From 7be718751bfe3118013bfdef233e04319eaebc9b Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 21:31:58 -0600 Subject: [PATCH 044/120] reworked scheduler to pass server to instance to remove duplication --- Varken/helpers.py | 3 +- Varken/radarr.py | 126 ++++++++++++----------- Varken/sonarr.py | 192 +++++++++++++++++------------------ Varken/tautulli.py | 216 ++++++++++++++++++++-------------------- data/varken.example.ini | 1 - varken.py | 23 ++--- 6 files changed, 270 insertions(+), 291 deletions(-) diff --git a/Varken/helpers.py b/Varken/helpers.py index 434f2ee..c3d8d2e 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -116,7 +116,7 @@ class TautulliServer(NamedTuple): id: int = None url: str = None fallback_ip: str = None - apikey: str = None + api_key: str = None verify_ssl: bool = None get_activity: bool = False get_activity_run_seconds: int = 30 @@ -319,7 +319,6 @@ class TautulliStream(NamedTuple): transcode_progress: int = None subtitle_language: str = None stream_subtitle_container: str = None - _cache_time: int = None def geoip_download(): diff --git a/Varken/radarr.py b/Varken/radarr.py index 33f7c0b..c31e2e0 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -7,11 +7,11 @@ from Varken.helpers import Movie, Queue class RadarrAPI(object): - def __init__(self, servers, influx_server): + def __init__(self, server, influx_server): self.now = datetime.now(timezone.utc).astimezone().isoformat() self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, influx_server.password, 'plex2') - self.servers = servers + self.server = server # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = requests.Session() @@ -20,87 +20,85 @@ class RadarrAPI(object): self.influx.write_points(payload) @logging - def get_missing(self, notimplemented): + def get_missing(self): endpoint = '/api/movie' self.now = datetime.now(timezone.utc).astimezone().isoformat() influx_payload = [] - for server in self.servers: - missing = [] - headers = {'X-Api-Key': server.api_key} - get = self.session.get(server.url + endpoint, headers=headers, verify=server.verify_ssl).json() - movies = [Movie(**movie) for movie in get] + missing = [] + headers = {'X-Api-Key': self.server.api_key} + get = self.session.get(self.server.url + endpoint, headers=headers, verify=self.server.verify_ssl).json() + movies = [Movie(**movie) for movie in get] - for movie in movies: - if server.get_missing: - if not movie.downloaded and movie.isAvailable: - ma = True - else: - ma = False - movie_name = '{} ({})'.format(movie.title, movie.year) - missing.append((movie_name, ma, movie.tmdbId)) + for movie in movies: + if self.server.get_missing: + if not movie.downloaded and movie.isAvailable: + ma = True + else: + ma = False + movie_name = '{} ({})'.format(movie.title, movie.year) + missing.append((movie_name, ma, movie.tmdbId)) - for title, ma, mid in missing: - influx_payload.append( - { - "measurement": "Radarr", - "tags": { - "Missing": True, - "Missing_Available": ma, - "tmdbId": mid, - "server": server.id - }, - "time": self.now, - "fields": { - "name": title - } + for title, ma, mid in missing: + influx_payload.append( + { + "measurement": "Radarr", + "tags": { + "Missing": True, + "Missing_Available": ma, + "tmdbId": mid, + "server": self.server.id + }, + "time": self.now, + "fields": { + "name": title } - ) + } + ) self.influx_push(influx_payload) @logging - def get_queue(self, notimplemented): + def get_queue(self): endpoint = '/api/queue' self.now = datetime.now(timezone.utc).astimezone().isoformat() influx_payload = [] - for server in self.servers: - queue = [] - headers = {'X-Api-Key': server.api_key} - get = self.session.get(server.url + endpoint, headers=headers, verify=server.verify_ssl).json() - for movie in get: - movie['movie'] = Movie(**movie['movie']) - download_queue = [Queue(**movie) for movie in get] + queue = [] + headers = {'X-Api-Key': self.server.api_key} + get = self.session.get(self.server.url + endpoint, headers=headers, verify=self.server.verify_ssl).json() + for movie in get: + movie['movie'] = Movie(**movie['movie']) + download_queue = [Queue(**movie) for movie in get] - for queue_item in download_queue: - name = '{} ({})'.format(queue_item.movie.title, queue_item.movie.year) + for queue_item in download_queue: + name = '{} ({})'.format(queue_item.movie.title, queue_item.movie.year) - if queue_item.protocol.upper() == 'USENET': - protocol_id = 1 - else: - protocol_id = 0 + if queue_item.protocol.upper() == 'USENET': + protocol_id = 1 + else: + protocol_id = 0 - queue.append((name, queue_item.quality['quality']['name'], queue_item.protocol.upper(), - protocol_id, queue_item.id)) + queue.append((name, queue_item.quality['quality']['name'], queue_item.protocol.upper(), + protocol_id, queue_item.id)) - for movie, quality, protocol, protocol_id, qid in queue: - influx_payload.append( - { - "measurement": "Radarr", - "tags": { - "type": "Queue", - "tmdbId": qid, - "server": server.id - }, - "time": self.now, - "fields": { - "name": movie, - "quality": quality, - "protocol": protocol, - "protocol_id": protocol_id - } + for movie, quality, protocol, protocol_id, qid in queue: + influx_payload.append( + { + "measurement": "Radarr", + "tags": { + "type": "Queue", + "tmdbId": qid, + "server": self.server.id + }, + "time": self.now, + "fields": { + "name": movie, + "quality": quality, + "protocol": protocol, + "protocol_id": protocol_id } - ) + } + ) self.influx_push(influx_payload) diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 9ee89c2..7fb7a04 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -7,147 +7,141 @@ from Varken.helpers import TVShow, Queue class SonarrAPI(object): - def __init__(self, servers, influx_server): + def __init__(self, server, influx_server): # Set Time of initialization self.now = datetime.now(timezone.utc).astimezone().isoformat() self.today = str(date.today()) self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, influx_server.password, 'plex') - self.servers = servers + self.server = server # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = requests.Session() self.session.params = {'pageSize': 1000} @logging - def get_missing(self, days_past): + def get_missing(self): endpoint = '/api/calendar' - last_days = str(date.today() + timedelta(days=-days_past)) + last_days = str(date.today() + timedelta(days=-self.server.missing_days)) self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'start': last_days, 'end': self.today} influx_payload = [] + missing = [] + headers = {'X-Api-Key': self.server.api_key} - for server in self.servers: - missing = [] - headers = {'X-Api-Key': server.api_key} + get = self.session.get(self.server.url + endpoint, params=params, headers=headers, + verify=self.server.verify_ssl).json() + # Iteratively create a list of TVShow Objects from response json + tv_shows = [TVShow(**show) for show in get] - get = self.session.get(server.url + endpoint, params=params, headers=headers, - verify=server.verify_ssl).json() - # Iteratively create a list of TVShow Objects from response json - tv_shows = [TVShow(**show) for show in get] + # Add show to missing list if file does not exist + for show in tv_shows: + if not show.hasFile: + sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) + missing.append((show.series['title'], sxe, show.airDate, show.title, show.id)) - # Add show to missing list if file does not exist - for show in tv_shows: - if not show.hasFile: - sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) - missing.append((show.series['title'], sxe, show.airDate, show.title, show.id)) - - for series_title, sxe, air_date, episode_title, sonarr_id in missing: - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Missing", - "sonarrId": sonarr_id, - "server": server.id - }, - "time": self.now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "airs": air_date - } + for series_title, sxe, air_date, episode_title, sonarr_id in missing: + influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Missing", + "sonarrId": sonarr_id, + "server": self.server.id + }, + "time": self.now, + "fields": { + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "airs": air_date } - ) + } + ) self.influx_push(influx_payload) + @logging - def get_future(self, future_days): + def get_future(self): endpoint = '/api/calendar/' self.now = datetime.now(timezone.utc).astimezone().isoformat() - future = str(date.today() + timedelta(days=future_days)) + future = str(date.today() + timedelta(days=self.server.future_days)) influx_payload = [] + air_days = [] + headers = {'X-Api-Key': self.server.api_key} + params = {'start': self.today, 'end': future} - for server in self.servers: - air_days = [] + get = self.session.get(self.server.url + endpoint, params=params, headers=headers, + verify=self.server.verify_ssl).json() + tv_shows = [TVShow(**show) for show in get] - headers = {'X-Api-Key': server.api_key} - params = {'start': self.today, 'end': future} + for show in tv_shows: + sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) + air_days.append((show.series['title'], show.hasFile, sxe, show.title, show.airDate, show.id)) - get = self.session.get(server.url + endpoint, params=params, headers=headers, - verify=server.verify_ssl).json() - tv_shows = [TVShow(**show) for show in get] - - for show in tv_shows: - sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) - air_days.append((show.series['title'], show.hasFile, sxe, show.title, show.airDate, show.id)) - - for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Future", - "sonarrId": sonarr_id, - "server": server.id - }, - "time": self.now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "airs": air_date, - "downloaded": dl_status - } + for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: + influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Future", + "sonarrId": sonarr_id, + "server": self.server.id + }, + "time": self.now, + "fields": { + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "airs": air_date, + "downloaded": dl_status } - ) + } + ) self.influx_push(influx_payload) @logging - def get_queue(self, notimplemented): + def get_queue(self): influx_payload = [] endpoint = '/api/queue' self.now = datetime.now(timezone.utc).astimezone().isoformat() + queue = [] + headers = {'X-Api-Key': self.server.api_key} - for server in self.servers: - queue = [] - headers = {'X-Api-Key': server.api_key} + get = self.session.get(self.server.url + endpoint, headers=headers, verify=self.server.verify_ssl).json() + download_queue = [Queue(**show) for show in get] - get = self.session.get(server.url + endpoint, headers=headers, verify=server.verify_ssl).json() - download_queue = [Queue(**show) for show in get] + for show in download_queue: + sxe = 'S{:0>2}E{:0>2}'.format(show.episode['seasonNumber'], show.episode['episodeNumber']) + if show.protocol.upper() == 'USENET': + protocol_id = 1 + else: + protocol_id = 0 - for show in download_queue: - sxe = 'S{:0>2}E{:0>2}'.format(show.episode['seasonNumber'], show.episode['episodeNumber']) - if show.protocol.upper() == 'USENET': - protocol_id = 1 - else: - protocol_id = 0 + queue.append((show.series['title'], show.episode['title'], show.protocol.upper(), + protocol_id, sxe, show.id)) - queue.append((show.series['title'], show.episode['title'], show.protocol.upper(), - protocol_id, sxe, show.id)) + for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue: + influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Queue", + "sonarrId": sonarr_id, + "server": self.server.id - for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue: - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Queue", - "sonarrId": sonarr_id, - "server": server.id - - }, - "time": self.now, - "fields": { - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "protocol": protocol, - "protocol_id": protocol_id - } + }, + "time": self.now, + "fields": { + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "protocol": protocol, + "protocol_id": protocol_id } - ) + } + ) self.influx_push(influx_payload) diff --git a/Varken/tautulli.py b/Varken/tautulli.py index da01739..ee23e08 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -7,12 +7,12 @@ from Varken.logger import logging class TautulliAPI(object): - def __init__(self, servers, influx_server): + def __init__(self, server, influx_server): # Set Time of initialization self.now = datetime.now(timezone.utc).astimezone().isoformat() self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, influx_server.password, 'plex2') - self.servers = servers + self.server = server self.session = requests.Session() self.endpoint = '/api/v2' @@ -21,128 +21,124 @@ class TautulliAPI(object): self.influx.write_points(payload) @logging - def get_activity(self, notimplemented): + def get_activity(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} influx_payload = [] + params['apikey'] = self.server.api_key + g = self.session.get(self.server.url + self.endpoint, params=params, verify=self.server.verify_ssl) + get = g.json()['response']['data'] - for server in self.servers: - params['apikey'] = server.apikey - g = self.session.get(server.url + self.endpoint, params=params, verify=server.verify_ssl) - get = g.json()['response']['data'] + influx_payload.append( + { + "measurement": "Tautulli", + "tags": { + "type": "current_stream_stats", + "server": self.server.id + }, + "time": self.now, + "fields": { + "stream_count": int(get['stream_count']), + "total_bandwidth": int(get['total_bandwidth']), + "wan_bandwidth": int(get['wan_bandwidth']), + "lan_bandwidth": int(get['lan_bandwidth']), + "transcode_streams": int(get['stream_count_transcode']), + "direct_play_streams": int(get['stream_count_direct_play']), + "direct_streams": int(get['stream_count_direct_stream']) + } + } + ) + + self.influx_push(influx_payload) + + @logging + def get_sessions(self): + self.now = datetime.now(timezone.utc).astimezone().isoformat() + params = {'cmd': 'get_activity'} + influx_payload = [] + params['apikey'] = self.server.api_key + g = self.session.get(self.server.url + self.endpoint, params=params, verify=self.server.verify_ssl) + get = g.json()['response']['data']['sessions'] + sessions = [TautulliStream(**session) for session in get] + + for session in sessions: + try: + geodata = geo_lookup(session.ip_address_public) + except (ValueError, AddressNotFoundError): + if self.server.fallback_ip: + geodata = geo_lookup(self.server.fallback_ip) + else: + my_ip = requests.get('http://ip.42.pl/raw').text + geodata = geo_lookup(my_ip) + + if not all([geodata.location.latitude, geodata.location.longitude]): + latitude = 37.234332396 + longitude = -115.80666344 + else: + latitude = geodata.location.latitude + longitude = geodata.location.longitude + + decision = session.transcode_decision + if decision == 'copy': + decision = 'direct stream' + + video_decision = session.stream_video_decision + if video_decision == 'copy': + video_decision = 'direct stream' + elif video_decision == '': + video_decision = 'Music' + + quality = session.stream_video_resolution + if not quality: + quality = session.container.upper() + elif quality in ('SD', 'sd', '4k'): + quality = session.stream_video_resolution.upper() + else: + quality = session.stream_video_resolution + 'p' + + player_state = session.state.lower() + if player_state == 'playing': + player_state = 0 + elif player_state == 'paused': + player_state = 1 + elif player_state == 'buffering': + player_state = 3 influx_payload.append( { "measurement": "Tautulli", "tags": { - "type": "current_stream_stats", - "server": server.id + "type": "Session", + "session_id": session.session_id, + "name": session.friendly_name, + "title": session.full_title, + "platform": session.platform, + "product_version": session.product_version, + "quality": quality, + "video_decision": video_decision.title(), + "transcode_decision": decision.title(), + "media_type": session.media_type.title(), + "audio_codec": session.audio_codec.upper(), + "audio_profile": session.audio_profile.upper(), + "stream_audio_codec": session.stream_audio_codec.upper(), + "quality_profile": session.quality_profile, + "progress_percent": session.progress_percent, + "region_code": geodata.subdivisions.most_specific.iso_code, + "location": geodata.city.name, + "full_location": '{} - {}'.format(geodata.subdivisions.most_specific.name, + geodata.city.name), + "latitude": latitude, + "longitude": longitude, + "player_state": player_state, + "device_type": session.platform, + "server": self.server.id }, "time": self.now, "fields": { - "stream_count": int(get['stream_count']), - "total_bandwidth": int(get['total_bandwidth']), - "wan_bandwidth": int(get['wan_bandwidth']), - "lan_bandwidth": int(get['lan_bandwidth']), - "transcode_streams": int(get['stream_count_transcode']), - "direct_play_streams": int(get['stream_count_direct_play']), - "direct_streams": int(get['stream_count_direct_stream']) + "session_id": session.session_id, + "session_key": session.session_key } } ) self.influx_push(influx_payload) - - @logging - def get_sessions(self, notimplemented): - self.now = datetime.now(timezone.utc).astimezone().isoformat() - params = {'cmd': 'get_activity'} - influx_payload = [] - - for server in self.servers: - params['apikey'] = server.apikey - g = self.session.get(server.url + self.endpoint, params=params, verify=server.verify_ssl) - get = g.json()['response']['data']['sessions'] - sessions = [TautulliStream(**session) for session in get] - - for session in sessions: - try: - geodata = geo_lookup(session.ip_address_public) - except (ValueError, AddressNotFoundError): - if server.fallback_ip: - geodata = geo_lookup(server.fallback_ip) - else: - my_ip = requests.get('http://ip.42.pl/raw').text - geodata = geo_lookup(my_ip) - - if not all([geodata.location.latitude, geodata.location.longitude]): - latitude = 37.234332396 - longitude = -115.80666344 - else: - latitude = geodata.location.latitude - longitude = geodata.location.longitude - - decision = session.transcode_decision - if decision == 'copy': - decision = 'direct stream' - - video_decision = session.stream_video_decision - if video_decision == 'copy': - video_decision = 'direct stream' - elif video_decision == '': - video_decision = 'Music' - - quality = session.stream_video_resolution - if not quality: - quality = session.container.upper() - elif quality in ('SD', 'sd', '4k'): - quality = session.stream_video_resolution.upper() - else: - quality = session.stream_video_resolution + 'p' - - player_state = session.state.lower() - if player_state == 'playing': - player_state = 0 - elif player_state == 'paused': - player_state = 1 - elif player_state == 'buffering': - player_state = 3 - - influx_payload.append( - { - "measurement": "Tautulli", - "tags": { - "type": "Session", - "session_id": session.session_id, - "name": session.friendly_name, - "title": session.full_title, - "platform": session.platform, - "product_version": session.product_version, - "quality": quality, - "video_decision": video_decision.title(), - "transcode_decision": decision.title(), - "media_type": session.media_type.title(), - "audio_codec": session.audio_codec.upper(), - "audio_profile": session.audio_profile.upper(), - "stream_audio_codec": session.stream_audio_codec.upper(), - "quality_profile": session.quality_profile, - "progress_percent": session.progress_percent, - "region_code": geodata.subdivisions.most_specific.iso_code, - "location": geodata.city.name, - "full_location": '{} - {}'.format(geodata.subdivisions.most_specific.name, - geodata.city.name), - "latitude": latitude, - "longitude": longitude, - "player_state": player_state, - "device_type": session.platform, - "server": server.id - }, - "time": self.now, - "fields": { - "session_id": session.session_id, - "session_key": session.session_key - } - } - ) - - self.influx_push(influx_payload) diff --git a/data/varken.example.ini b/data/varken.example.ini index 0ebd511..4d28054 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -61,7 +61,6 @@ verify_ssl = true queue = true queue_run_seconds = 300 get_missing = true -get_missing_available = true get_missing_run_seconds = 300 [radarr-2] diff --git a/varken.py b/varken.py index 482e788..921b8fb 100644 --- a/varken.py +++ b/varken.py @@ -8,8 +8,8 @@ from Varken.tautulli import TautulliAPI from Varken.radarr import RadarrAPI -def threaded(job, days=None): - thread = threading.Thread(target=job, args=([days])) +def threaded(job): + thread = threading.Thread(target=job) thread.start() @@ -17,38 +17,31 @@ if __name__ == "__main__": CONFIG = INIParser() if CONFIG.sonarr_enabled: - SONARR = SonarrAPI(CONFIG.sonarr_servers, CONFIG.influx_server) - for server in CONFIG.sonarr_servers: + SONARR = SonarrAPI(server, CONFIG.influx_server) if server.queue: schedule.every(server.queue_run_seconds).seconds.do(threaded, SONARR.get_queue) if server.missing_days > 0: - schedule.every(server.missing_days_run_seconds).seconds.do(threaded, SONARR.get_missing, - server.missing_days) + schedule.every(server.missing_days_run_seconds).seconds.do(threaded, SONARR.get_missing) if server.future_days > 0: - schedule.every(server.future_days_run_seconds).seconds.do(threaded, SONARR.get_future, - server.future_days) + schedule.every(server.future_days_run_seconds).seconds.do(threaded, SONARR.get_future) if CONFIG.tautulli_enabled: - TAUTULLI = TautulliAPI(CONFIG.tautulli_servers, CONFIG.influx_server) - for server in CONFIG.tautulli_servers: + TAUTULLI = TautulliAPI(server, CONFIG.influx_server) if server.get_activity: schedule.every(server.get_activity_run_seconds).seconds.do(threaded, TAUTULLI.get_activity) if server.get_sessions: schedule.every(server.get_sessions_run_seconds).seconds.do(threaded, TAUTULLI.get_sessions) if CONFIG.radarr_enabled: - RADARR = RadarrAPI(CONFIG.radarr_servers, CONFIG.influx_server) - for server in CONFIG.radarr_servers: - if any([server.get_missing, server.get_missing_available]): + RADARR = RadarrAPI(server, CONFIG.influx_server) + if server.get_missing: schedule.every(server.get_missing_run_seconds).seconds.do(threaded, RADARR.get_missing) if server.queue: schedule.every(server.queue_run_seconds).seconds.do(threaded, RADARR.get_queue) - - while True: schedule.run_pending() sleep(1) From c3c0381fe13c41196328bb018102a786581f035a Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 21:36:42 -0600 Subject: [PATCH 045/120] truncate roku product version --- Varken/tautulli.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Varken/tautulli.py b/Varken/tautulli.py index ee23e08..a2789d0 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -104,6 +104,10 @@ class TautulliAPI(object): elif player_state == 'buffering': player_state = 3 + product_version = session.product_version + if session.platform == 'Roku': + product_version = session.product_version.split('-')[0] + influx_payload.append( { "measurement": "Tautulli", @@ -113,7 +117,7 @@ class TautulliAPI(object): "name": session.friendly_name, "title": session.full_title, "platform": session.platform, - "product_version": session.product_version, + "product_version": product_version, "quality": quality, "video_decision": video_decision.title(), "transcode_decision": decision.title(), From 818f4bea7cfd663055f336c74a777437a2b7a53e Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 22:26:44 -0600 Subject: [PATCH 046/120] ported ombi --- Legacy/configuration.example.py | 49 ------------------- Legacy/configuration.py | 49 ------------------- Legacy/crontabs | 11 ----- Legacy/ombi.py | 87 --------------------------------- Varken/helpers.py | 17 +++++-- Varken/iniparser.py | 38 +++++++++----- Varken/ombi.py | 70 ++++++++++++++++++++++++++ Varken/radarr.py | 5 +- Varken/sonarr.py | 6 +-- Varken/tautulli.py | 7 ++- data/varken.example.ini | 13 +++-- varken.py | 10 +++- 12 files changed, 137 insertions(+), 225 deletions(-) delete mode 100644 Legacy/configuration.example.py delete mode 100644 Legacy/configuration.py delete mode 100644 Legacy/crontabs delete mode 100644 Legacy/ombi.py create mode 100644 Varken/ombi.py diff --git a/Legacy/configuration.example.py b/Legacy/configuration.example.py deleted file mode 100644 index a0df50a..0000000 --- a/Legacy/configuration.example.py +++ /dev/null @@ -1,49 +0,0 @@ -''' -Notes: - - Domains should be either http(s)://subdomain.domain.com or http(s)://domain.com/url_suffix - - - Sonarr + Radarr scripts support multiple servers. You can remove the second - server by putting a # in front of the line. - - - tautulli_failback_ip, This is used when there is no IP listed in tautulli. - This can happen when you are streaming locally. This is usually your public IP. -''' - -########################### INFLUXDB CONFIG ########################### -influxdb_url = 'influxdb.domain.tld' -influxdb_port = 8086 -influxdb_username = '' -influxdb_password = '' - -############################ SONARR CONFIG ############################ -sonarr_server_list = [ - ('https://sonarr1.domain.tld', 'xxxxxxxxxxxxxxx', '1'), - ('https://sonarr2.domain.tld', 'xxxxxxxxxxxxxxx', '2'), - #('https://sonarr3.domain.tld', 'xxxxxxxxxxxxxxx', '3') - ] -sonarr_influxdb_db_name = 'plex' - -############################ RADARR CONFIG ############################ -radarr_server_list = [ - ('https://radarr1.domain.tld', 'xxxxxxxxxxxxxxx', '1'), - ('https://radarr2.domain.tld', 'xxxxxxxxxxxxxxx', '2'), - #('https://radarr3.domain.tld', 'xxxxxxxxxxxxxxx', '3') - ] -radarr_influxdb_db_name = 'plex' - -############################ OMBI CONFIG ############################## -ombi_url = 'https://ombi.domain.tld' -ombi_api_key = 'xxxxxxxxxxxxxxx' -ombi_influxdb_db_name = 'plex' - -########################## TAUTULLI CONFIG ############################ -tautulli_url = 'https://tautulli.domain.tld' -tautulli_api_key = 'xxxxxxxxxxxxxxx' -tautulli_failback_ip = '' -tautulli_influxdb_db_name = 'plex' - -########################## FIREWALL CONFIG ############################ -asa_url = 'https://firewall.domain.tld' -asa_username = 'cisco' -asa_password = 'cisco' -asa_influxdb_db_name = 'asa' diff --git a/Legacy/configuration.py b/Legacy/configuration.py deleted file mode 100644 index a0df50a..0000000 --- a/Legacy/configuration.py +++ /dev/null @@ -1,49 +0,0 @@ -''' -Notes: - - Domains should be either http(s)://subdomain.domain.com or http(s)://domain.com/url_suffix - - - Sonarr + Radarr scripts support multiple servers. You can remove the second - server by putting a # in front of the line. - - - tautulli_failback_ip, This is used when there is no IP listed in tautulli. - This can happen when you are streaming locally. This is usually your public IP. -''' - -########################### INFLUXDB CONFIG ########################### -influxdb_url = 'influxdb.domain.tld' -influxdb_port = 8086 -influxdb_username = '' -influxdb_password = '' - -############################ SONARR CONFIG ############################ -sonarr_server_list = [ - ('https://sonarr1.domain.tld', 'xxxxxxxxxxxxxxx', '1'), - ('https://sonarr2.domain.tld', 'xxxxxxxxxxxxxxx', '2'), - #('https://sonarr3.domain.tld', 'xxxxxxxxxxxxxxx', '3') - ] -sonarr_influxdb_db_name = 'plex' - -############################ RADARR CONFIG ############################ -radarr_server_list = [ - ('https://radarr1.domain.tld', 'xxxxxxxxxxxxxxx', '1'), - ('https://radarr2.domain.tld', 'xxxxxxxxxxxxxxx', '2'), - #('https://radarr3.domain.tld', 'xxxxxxxxxxxxxxx', '3') - ] -radarr_influxdb_db_name = 'plex' - -############################ OMBI CONFIG ############################## -ombi_url = 'https://ombi.domain.tld' -ombi_api_key = 'xxxxxxxxxxxxxxx' -ombi_influxdb_db_name = 'plex' - -########################## TAUTULLI CONFIG ############################ -tautulli_url = 'https://tautulli.domain.tld' -tautulli_api_key = 'xxxxxxxxxxxxxxx' -tautulli_failback_ip = '' -tautulli_influxdb_db_name = 'plex' - -########################## FIREWALL CONFIG ############################ -asa_url = 'https://firewall.domain.tld' -asa_username = 'cisco' -asa_password = 'cisco' -asa_influxdb_db_name = 'asa' diff --git a/Legacy/crontabs b/Legacy/crontabs deleted file mode 100644 index 413be4e..0000000 --- a/Legacy/crontabs +++ /dev/null @@ -1,11 +0,0 @@ -### Modify paths as appropriate. python3 is located in different places for different users. (`which python3` will give you the path) -### to edit your crontab entry, do not modify /var/spool/cron/crontabs/ directly, use `crontab -e` -### Crontabs require an empty line at the end or they WILL not run. Make sure to have 2 lines to be safe -### -* * * * * /usr/bin/python3 /path-to-grafana-scripts/ombi.py -* * * * * ( sleep 30 ; /usr/bin/python3 /path-to-grafana-scripts/ombi.py ) -* * * * * /usr/bin/python3 /path-to-grafana-scripts/taurulli.py -* * * * * ( sleep 30 ; /usr/bin/python3 /path-to-grafana-scripts/tautulli.py ) -*/30 * * * * /usr/bin/python3 /path-to-grafana-scripts/radarr.py -*/30 * * * * /usr/bin/python3 /path-to-grafana-scripts/sonarr.py -#*/30 * * * * /usr/bin/python3 /path-to-grafana-scripts/sickrage.py diff --git a/Legacy/ombi.py b/Legacy/ombi.py deleted file mode 100644 index bcea45f..0000000 --- a/Legacy/ombi.py +++ /dev/null @@ -1,87 +0,0 @@ -# Do not edit this script. Edit configuration.py -import sys -import requests -from datetime import datetime, timezone -from influxdb import InfluxDBClient -import argparse -from argparse import RawTextHelpFormatter -from Legacy import configuration - -headers = {'Apikey': configuration.ombi_api_key} - -def now_iso(): - now_iso = datetime.now(timezone.utc).astimezone().isoformat() - return now_iso - -def influx_sender(influx_payload): - influx = InfluxDBClient(configuration.influxdb_url, configuration.influxdb_port, configuration.influxdb_username, - configuration.influxdb_password, configuration.ombi_influxdb_db_name) - influx.write_points(influx_payload) - -def get_total_requests(): - get_tv_requests = requests.get('{}/api/v1/Request/tv'.format(configuration.ombi_url), headers=headers).json() - get_movie_requests = requests.get('{}/api/v1/Request/movie'.format(configuration.ombi_url), headers=headers).json() - - count_movie_requests = 0 - count_tv_requests = 0 - - for show in get_tv_requests: - count_tv_requests += 1 - - for movie in get_movie_requests: - count_movie_requests += 1 - - influx_payload = [ - { - "measurement": "Ombi", - "tags": { - "type": "Request_Total" - }, - "time": now_iso(), - "fields": { - "total": count_movie_requests + count_tv_requests - } - } - ] - return influx_payload - -def get_request_counts(): - get_request_counts = requests.get('{}/api/v1/Request/count'.format(configuration.ombi_url), headers=headers).json() - - influx_payload = [ - { - "measurement": "Ombi", - "tags": { - "type": "Request_Counts" - }, - "time": now_iso(), - "fields": { - "pending": int(get_request_counts['pending']), - "approved": int(get_request_counts['approved']), - "available": int(get_request_counts['available']) - } - } - ] - return influx_payload - -if __name__ == "__main__": - parser = argparse.ArgumentParser(prog='Ombi stats operations', - description='Script to aid in data gathering from Ombi', formatter_class=RawTextHelpFormatter) - - parser.add_argument("--total", action='store_true', - help='Get the total count of all requests') - - parser.add_argument("--counts", action='store_true', - help='Get the count of pending, approved, and available requests') - - opts = parser.parse_args() - - if opts.total: - influx_sender(get_total_requests()) - - elif opts.counts: - influx_sender(get_request_counts()) - - elif len(sys.argv) == 1: - parser.print_help(sys.stderr) - sys.exit(1) diff --git a/Varken/helpers.py b/Varken/helpers.py index c3d8d2e..e464b89 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -93,7 +93,7 @@ class SonarrServer(NamedTuple): future_days: int = 0 future_days_run_seconds: int = 30 queue: bool = False - queue_run_seconds: int = 1 + queue_run_seconds: int = 30 class RadarrServer(NamedTuple): id: int = None @@ -101,15 +101,20 @@ class RadarrServer(NamedTuple): api_key: str = None verify_ssl: bool = False queue: bool = False - queue_run_seconds: int = 1 + queue_run_seconds: int = 30 get_missing: bool = False get_missing_run_seconds: int = 30 -class Server(NamedTuple): + +class OmbiServer(NamedTuple): id: int = None url: str = None api_key: str = None verify_ssl: bool = False + request_type_counts: bool = False + request_type_run_seconds: int = 30 + request_total_counts: bool = False + request_total_run_seconds: int = 30 class TautulliServer(NamedTuple): @@ -131,6 +136,12 @@ class InfluxServer(NamedTuple): password: str = 'root' +class OmbiRequestCounts(NamedTuple): + pending: int = 0 + approved: int = 0 + available: int = 0 + + class TautulliStream(NamedTuple): rating: str = None transcode_width: str = None diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 89d56fc..9a21e11 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,7 +1,7 @@ import sys import configparser from os.path import abspath, join -from Varken.helpers import Server, TautulliServer, SonarrServer, InfluxServer, RadarrServer +from Varken.helpers import OmbiServer, TautulliServer, SonarrServer, InfluxServer, RadarrServer class INIParser(object): @@ -17,7 +17,7 @@ class INIParser(object): self.radarr_servers = [] self.ombi_enabled = False - self.ombi_server = None + self.ombi_servers = [] self.tautulli_enabled = False self.tautulli_servers = [] @@ -45,7 +45,7 @@ class INIParser(object): # Parse Sonarr options try: if not self.config.getboolean('global', 'sonarr_server_ids'): - sys.exit('sonarr_server_ids must be either false, or a comma-separated list of server ids') + sys.exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'sonarr_server_ids'): self.sonarr_enabled = True except ValueError: @@ -75,7 +75,7 @@ class INIParser(object): # Parse Radarr options try: if not self.config.getboolean('global', 'radarr_server_ids'): - sys.exit('radarr_server_ids must be either false, or a comma-separated list of server ids') + sys.exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'radarr_server_ids'): self.radarr_enabled = True except ValueError: @@ -102,7 +102,7 @@ class INIParser(object): # Parse Tautulli options try: if not self.config.getboolean('global', 'tautulli_server_ids'): - sys.exit('tautulli_server_ids must be either false, or a comma-separated list of server ids') + sys.exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'tautulli_server_ids'): self.tautulli_enabled = True except ValueError: @@ -128,14 +128,30 @@ class INIParser(object): self.tautulli_servers.append(server) # Parse Ombi Options - if self.config.getboolean('global', 'ombi'): + try: + if not self.config.getboolean('global', 'ombi_server_ids'): + sys.exit('server_ids must be either false, or a comma-separated list of server ids') + elif self.config.getint('global', 'ombi_server_ids'): + self.ombi_enabled = True + except ValueError: self.ombi_enabled = True - url = self.config.get('ombi', 'url') - apikey = self.config.get('ombi', 'apikey') - scheme = 'https://' if self.config.getboolean('ombi', 'ssl') else 'http://' - verify_ssl = self.config.getboolean('ombi', 'verify_ssl') - self.ombi_server = Server(url=scheme + url, api_key=apikey, verify_ssl=verify_ssl) + if self.ombi_enabled: + sids = self.config.get('global', 'ombi_server_ids').strip(' ').split(',') + for server_id in sids: + ombi_section = 'ombi-' + server_id + url = self.config.get(ombi_section, 'url') + apikey = self.config.get(ombi_section, 'apikey') + scheme = 'https://' if self.config.getboolean(ombi_section, 'ssl') else 'http://' + verify_ssl = self.config.getboolean(ombi_section, 'verify_ssl') + request_type_counts = self.config.getboolean(ombi_section, 'get_request_type_counts') + request_type_run_seconds = self.config.getint(ombi_section, 'request_type_run_seconds') + request_total_counts = self.config.getboolean(ombi_section, 'get_request_total_counts') + request_total_run_seconds = self.config.getint(ombi_section, 'request_total_run_seconds') + + server = OmbiServer(server_id, scheme + url, apikey, verify_ssl, request_type_counts, + request_type_run_seconds, request_total_counts, request_total_run_seconds) + self.ombi_servers.append(server) # Parse ASA opts if self.config.getboolean('global', 'asa'): diff --git a/Varken/ombi.py b/Varken/ombi.py new file mode 100644 index 0000000..47c620c --- /dev/null +++ b/Varken/ombi.py @@ -0,0 +1,70 @@ +from requests import Session +from datetime import datetime, timezone +from influxdb import InfluxDBClient + +from Varken.helpers import OmbiRequestCounts +from Varken.logger import logging + +class OmbiAPI(object): + def __init__(self, server, influx_server): + self.now = datetime.now(timezone.utc).astimezone().isoformat() + self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, + influx_server.password, 'plex2') + self.server = server + # Create session to reduce server web thread load, and globally define pageSize for all requests + self.session = Session() + self.session.headers = {'Apikey': self.server.api_key} + + def influx_push(self, payload): + self.influx.write_points(payload) + + @logging + def get_total_requests(self): + self.now = datetime.now(timezone.utc).astimezone().isoformat() + tv_endpoint = '/api/v1/Request/tv' + movie_endpoint = "/api/v1/Request/movie" + get_tv = self.session.get(self.server.url + tv_endpoint, verify=self.server.verify_ssl).json() + get_movie = self.session.get(self.server.url + movie_endpoint, verify=self.server.verify_ssl).json() + + movie_requests = len(get_movie) + tv_requests = len(get_tv) + + influx_payload = [ + { + "measurement": "Ombi", + "tags": { + "type": "Request_Total" + }, + "time": self.now, + "fields": { + "total": movie_requests + tv_requests, + "movies": movie_requests, + "tv_shows": tv_requests + } + } + ] + + self.influx_push(influx_payload) + + @logging + def get_request_counts(self): + self.now = datetime.now(timezone.utc).astimezone().isoformat() + endpoint = '/api/v1/Request/count' + get = self.session.get(self.server.url + endpoint, verify=self.server.verify_ssl).json() + requests = OmbiRequestCounts(**get) + influx_payload = [ + { + "measurement": "Ombi", + "tags": { + "type": "Request_Counts" + }, + "time": self.now, + "fields": { + "pending": requests.pending, + "approved": requests.approved, + "available": requests.available + } + } + ] + + self.influx_push(influx_payload) diff --git a/Varken/radarr.py b/Varken/radarr.py index c31e2e0..d385ed7 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -1,4 +1,4 @@ -import requests +from requests import Session from datetime import datetime, timezone from influxdb import InfluxDBClient @@ -13,10 +13,9 @@ class RadarrAPI(object): influx_server.password, 'plex2') self.server = server # Create session to reduce server web thread load, and globally define pageSize for all requests - self.session = requests.Session() + self.session = Session() def influx_push(self, payload): - # TODO: error handling for failed connection self.influx.write_points(payload) @logging diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 7fb7a04..8967564 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -1,4 +1,4 @@ -import requests +from requests import Session from influxdb import InfluxDBClient from datetime import datetime, timezone, date, timedelta @@ -15,7 +15,7 @@ class SonarrAPI(object): influx_server.password, 'plex') self.server = server # Create session to reduce server web thread load, and globally define pageSize for all requests - self.session = requests.Session() + self.session = Session() self.session.params = {'pageSize': 1000} @logging @@ -60,7 +60,6 @@ class SonarrAPI(object): self.influx_push(influx_payload) - @logging def get_future(self): endpoint = '/api/calendar/' @@ -146,5 +145,4 @@ class SonarrAPI(object): self.influx_push(influx_payload) def influx_push(self, payload): - # TODO: error handling for failed connection self.influx.write_points(payload) diff --git a/Varken/tautulli.py b/Varken/tautulli.py index a2789d0..03aaa6d 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -1,7 +1,7 @@ from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError from influxdb import InfluxDBClient -import requests +from requests import Session from Varken.helpers import TautulliStream, geo_lookup from Varken.logger import logging @@ -13,11 +13,10 @@ class TautulliAPI(object): self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, influx_server.password, 'plex2') self.server = server - self.session = requests.Session() + self.session = Session() self.endpoint = '/api/v2' def influx_push(self, payload): - # TODO: error handling for failed connection self.influx.write_points(payload) @logging @@ -68,7 +67,7 @@ class TautulliAPI(object): if self.server.fallback_ip: geodata = geo_lookup(self.server.fallback_ip) else: - my_ip = requests.get('http://ip.42.pl/raw').text + my_ip = self.session.get('http://ip.42.pl/raw').text geodata = geo_lookup(my_ip) if not all([geodata.location.latitude, geodata.location.longitude]): diff --git a/data/varken.example.ini b/data/varken.example.ini index 4d28054..c51272b 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -9,7 +9,7 @@ sonarr_server_ids = 1,2 radarr_server_ids = 1,2 tautulli_server_ids = 1 -ombi = true +ombi_server_ids = 1 asa = false [influxdb] @@ -68,13 +68,20 @@ url = radarr2.domain.tld apikey = yyyyyyyyyyyyyyyy ssl = false verify_ssl = true +queue = true +queue_run_seconds = 300 +get_missing = true +get_missing_run_seconds = 300 -[ombi] +[ombi-1] url = ombi.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false verify_ssl = true - +get_request_type_counts = true +request_type_run_seconds = 300 +get_request_total_counts = true +request_total_run_seconds = 300 [asa] url = firewall.domain.tld diff --git a/varken.py b/varken.py index 921b8fb..dbe72b7 100644 --- a/varken.py +++ b/varken.py @@ -6,7 +6,7 @@ from Varken.iniparser import INIParser from Varken.sonarr import SonarrAPI from Varken.tautulli import TautulliAPI from Varken.radarr import RadarrAPI - +from Varken.ombi import OmbiAPI def threaded(job): thread = threading.Thread(target=job) @@ -42,6 +42,14 @@ if __name__ == "__main__": if server.queue: schedule.every(server.queue_run_seconds).seconds.do(threaded, RADARR.get_queue) + if CONFIG.ombi_enabled: + for server in CONFIG.ombi_servers: + OMBI = OmbiAPI(server, CONFIG.influx_server) + if server.request_type_counts: + schedule.every(server.request_type_run_seconds).seconds.do(threaded, OMBI.get_request_counts) + if server.request_total_counts: + schedule.every(server.request_total_run_seconds).seconds.do(threaded, OMBI.get_total_requests) + while True: schedule.run_pending() sleep(1) From 0249f582471aca1b7a7c90f4e29d5254a9b047e9 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 23:15:12 -0600 Subject: [PATCH 047/120] created and assigned basic dbmanager --- README.md | 11 +++++++---- Varken/dbmanager.py | 15 +++++++++++++++ Varken/ombi.py | 12 ++++-------- Varken/radarr.py | 12 ++++-------- Varken/sonarr.py | 15 +++++---------- Varken/tautulli.py | 12 ++++-------- requirements.txt | 1 + varken.py | 10 ++++++---- 8 files changed, 46 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 94aebb8..c6e7198 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ -# Grafana Scripts -Repo for api scripts written (both pushing and pulling) to aggregate data into influxdb for grafana +# Varken +Dutch for PIG. PIG is an Acronym for PlexDB/Influx/Grafana + +Varken is a standalone commmandline utility that will aggregate date +from the plex ecosystem into influxdb to be displayed in grafana Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), [Python3](https://www.python.org/downloads/), [InfluxDB](https://docs.influxdata.com/influxdb/v1.5/introduction/installation/) @@ -7,8 +10,8 @@ Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), ## Quick Setup 1. Install requirements `pip3 install -r requirements.txt` -1. Make a copy of `configuration.example.py` to `configuration.py` -2. Make the appropriate changes to `configuration.py` +1. Make a copy of `varken.example.ini` to `varken.ini` in the `data` folder +2. Make the appropriate changes to `varken.ini` 1. Create your plex database in influx ```sh user@server: ~$ influx diff --git a/Varken/dbmanager.py b/Varken/dbmanager.py index e69de29..a7c7109 100644 --- a/Varken/dbmanager.py +++ b/Varken/dbmanager.py @@ -0,0 +1,15 @@ +from influxdb import InfluxDBClient + +class DBManager(object): + def __init__(self, server): + self.server = server + self.influx = InfluxDBClient(self.server.url, self.server.port, self.server.username, self.server.password, + 'plex2') + databases = [db['name'] for db in self.influx.get_list_database()] + + if 'varken' not in databases: + self.influx.create_database('varken') + self.influx.create_retention_policy('Varken 30d/1h', '30d', '1', 'varken', False, '1h') + + def write_points(self, data): + self.influx.write_points(data) \ No newline at end of file diff --git a/Varken/ombi.py b/Varken/ombi.py index 47c620c..e1a632d 100644 --- a/Varken/ombi.py +++ b/Varken/ombi.py @@ -6,18 +6,14 @@ from Varken.helpers import OmbiRequestCounts from Varken.logger import logging class OmbiAPI(object): - def __init__(self, server, influx_server): + def __init__(self, server, dbmanager): self.now = datetime.now(timezone.utc).astimezone().isoformat() - self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, - influx_server.password, 'plex2') + self.dbmanager = dbmanager self.server = server # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = Session() self.session.headers = {'Apikey': self.server.api_key} - def influx_push(self, payload): - self.influx.write_points(payload) - @logging def get_total_requests(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() @@ -44,7 +40,7 @@ class OmbiAPI(object): } ] - self.influx_push(influx_payload) + self.dbmanager.write_points(influx_payload) @logging def get_request_counts(self): @@ -67,4 +63,4 @@ class OmbiAPI(object): } ] - self.influx_push(influx_payload) + self.dbmanager.write_points(influx_payload) diff --git a/Varken/radarr.py b/Varken/radarr.py index d385ed7..2582197 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -7,17 +7,13 @@ from Varken.helpers import Movie, Queue class RadarrAPI(object): - def __init__(self, server, influx_server): + def __init__(self, server, dbmanager): self.now = datetime.now(timezone.utc).astimezone().isoformat() - self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, - influx_server.password, 'plex2') + self.dbmanager = dbmanager self.server = server # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = Session() - def influx_push(self, payload): - self.influx.write_points(payload) - @logging def get_missing(self): endpoint = '/api/movie' @@ -55,7 +51,7 @@ class RadarrAPI(object): } ) - self.influx_push(influx_payload) + self.dbmanager.write_points(influx_payload) @logging def get_queue(self): @@ -100,4 +96,4 @@ class RadarrAPI(object): } ) - self.influx_push(influx_payload) + self.dbmanager.write_points(influx_payload) diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 8967564..ae09c2e 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -1,5 +1,4 @@ from requests import Session -from influxdb import InfluxDBClient from datetime import datetime, timezone, date, timedelta from Varken.logger import logging @@ -7,12 +6,11 @@ from Varken.helpers import TVShow, Queue class SonarrAPI(object): - def __init__(self, server, influx_server): + def __init__(self, server, dbmanager): # Set Time of initialization self.now = datetime.now(timezone.utc).astimezone().isoformat() + self.dbmanager = dbmanager self.today = str(date.today()) - self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, - influx_server.password, 'plex') self.server = server # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = Session() @@ -58,7 +56,7 @@ class SonarrAPI(object): } ) - self.influx_push(influx_payload) + self.dbmanager.write_points(influx_payload) @logging def get_future(self): @@ -98,7 +96,7 @@ class SonarrAPI(object): } ) - self.influx_push(influx_payload) + self.dbmanager.write_points(influx_payload) @logging def get_queue(self): @@ -142,7 +140,4 @@ class SonarrAPI(object): } ) - self.influx_push(influx_payload) - - def influx_push(self, payload): - self.influx.write_points(payload) + self.dbmanager.write_points(influx_payload) diff --git a/Varken/tautulli.py b/Varken/tautulli.py index 03aaa6d..6ec6d7e 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -7,18 +7,14 @@ from Varken.logger import logging class TautulliAPI(object): - def __init__(self, server, influx_server): + def __init__(self, server, dbmanager): # Set Time of initialization self.now = datetime.now(timezone.utc).astimezone().isoformat() - self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username, - influx_server.password, 'plex2') + self.dbmanager = dbmanager self.server = server self.session = Session() self.endpoint = '/api/v2' - def influx_push(self, payload): - self.influx.write_points(payload) - @logging def get_activity(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() @@ -48,7 +44,7 @@ class TautulliAPI(object): } ) - self.influx_push(influx_payload) + self.dbmanager.write_points(influx_payload) @logging def get_sessions(self): @@ -144,4 +140,4 @@ class TautulliAPI(object): } ) - self.influx_push(influx_payload) + self.dbmanager.write_points(influx_payload) diff --git a/requirements.txt b/requirements.txt index ceb78c6..4a24667 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ requests geoip2 influxdb +schedule \ No newline at end of file diff --git a/varken.py b/varken.py index dbe72b7..2d68b86 100644 --- a/varken.py +++ b/varken.py @@ -7,6 +7,7 @@ from Varken.sonarr import SonarrAPI from Varken.tautulli import TautulliAPI from Varken.radarr import RadarrAPI from Varken.ombi import OmbiAPI +from Varken.dbmanager import DBManager def threaded(job): thread = threading.Thread(target=job) @@ -15,10 +16,11 @@ def threaded(job): if __name__ == "__main__": CONFIG = INIParser() + DBMANAGER = DBManager(CONFIG.influx_server) if CONFIG.sonarr_enabled: for server in CONFIG.sonarr_servers: - SONARR = SonarrAPI(server, CONFIG.influx_server) + SONARR = SonarrAPI(server, DBMANAGER) if server.queue: schedule.every(server.queue_run_seconds).seconds.do(threaded, SONARR.get_queue) if server.missing_days > 0: @@ -28,7 +30,7 @@ if __name__ == "__main__": if CONFIG.tautulli_enabled: for server in CONFIG.tautulli_servers: - TAUTULLI = TautulliAPI(server, CONFIG.influx_server) + TAUTULLI = TautulliAPI(server, DBMANAGER) if server.get_activity: schedule.every(server.get_activity_run_seconds).seconds.do(threaded, TAUTULLI.get_activity) if server.get_sessions: @@ -36,7 +38,7 @@ if __name__ == "__main__": if CONFIG.radarr_enabled: for server in CONFIG.radarr_servers: - RADARR = RadarrAPI(server, CONFIG.influx_server) + RADARR = RadarrAPI(server, DBMANAGER) if server.get_missing: schedule.every(server.get_missing_run_seconds).seconds.do(threaded, RADARR.get_missing) if server.queue: @@ -44,7 +46,7 @@ if __name__ == "__main__": if CONFIG.ombi_enabled: for server in CONFIG.ombi_servers: - OMBI = OmbiAPI(server, CONFIG.influx_server) + OMBI = OmbiAPI(server, DBMANAGER) if server.request_type_counts: schedule.every(server.request_type_run_seconds).seconds.do(threaded, OMBI.get_request_counts) if server.request_total_counts: From d2307324b341f5a07bccbc0b76dc70caaa5b7e76 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 23:16:56 -0600 Subject: [PATCH 048/120] cleared extra imports --- Varken/ombi.py | 1 - Varken/radarr.py | 1 - Varken/tautulli.py | 1 - 3 files changed, 3 deletions(-) diff --git a/Varken/ombi.py b/Varken/ombi.py index e1a632d..3981250 100644 --- a/Varken/ombi.py +++ b/Varken/ombi.py @@ -1,6 +1,5 @@ from requests import Session from datetime import datetime, timezone -from influxdb import InfluxDBClient from Varken.helpers import OmbiRequestCounts from Varken.logger import logging diff --git a/Varken/radarr.py b/Varken/radarr.py index 2582197..091bb77 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -1,6 +1,5 @@ from requests import Session from datetime import datetime, timezone -from influxdb import InfluxDBClient from Varken.logger import logging from Varken.helpers import Movie, Queue diff --git a/Varken/tautulli.py b/Varken/tautulli.py index 6ec6d7e..24ceeaf 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -1,6 +1,5 @@ from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError -from influxdb import InfluxDBClient from requests import Session from Varken.helpers import TautulliStream, geo_lookup from Varken.logger import logging From bdcbdefa7537794432ec5171c892c7796607f4c7 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 23:19:13 -0600 Subject: [PATCH 049/120] ammended servicefile --- varken.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/varken.service b/varken.service index d1db152..ba56950 100644 --- a/varken.service +++ b/varken.service @@ -4,8 +4,8 @@ After=network-online.target [Service] Type=simple -WorkingDirectory=/opt/Varken/Varken -ExecStart=/usr/bin/python3 /opt/Varken/Varken/varken.py +WorkingDirectory=/opt/Varken +ExecStart=/usr/bin/python3 /opt/Varken/varken.py Restart=always [Install] From b593235311858cf1f4c8b76755848867f7e56b7b Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 23:38:02 -0600 Subject: [PATCH 050/120] accept dbchange and add to readme --- README.md | 40 ++++++++++++---------------------------- Varken/dbmanager.py | 2 +- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index c6e7198..cb53fa2 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,18 @@ Requirements /w install links: [Grafana](http://docs.grafana.org/installation/),
-## Quick Setup -1. Install requirements `pip3 install -r requirements.txt` -1. Make a copy of `varken.example.ini` to `varken.ini` in the `data` folder -2. Make the appropriate changes to `varken.ini` -1. Create your plex database in influx - ```sh - user@server: ~$ influx - > CREATE DATABASE plex - > quit - ``` -1. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. At a minimum, you will need the plex database. -1. Install `grafana-cli plugins install grafana-worldmap-panel` -1. Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. +## Quick Setup (Varken Alpha) +1. Clone the repository `git clone https://github.com/DirtyCajunRice/grafana-scripts.git /opt/Varken` +1. Switch to the testing branch `cd /opt/Varken && git checkout refactor-project` +1. Install requirements `/usr/bin/python -m pip install -r requirements.txt` +2. Make a copy of `varken.example.ini` to `varken.ini` in the `data` folder + `cp data/varken.example.ini data/varken.ini` +3. Make the appropriate changes to `varken.ini` + `nano data/varken.ini` +4. Copy the systemd file `cp varken.service /etc/systemd/system/` +5. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. At a minimum, you will need the plex database. +6. Install `grafana-cli plugins install grafana-worldmap-panel` +7. Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. @@ -101,18 +100,3 @@ optional arguments: ### `tautulli.py` Gathers data from Tautulli and pushes it to influxdb. On initial run it will download the geoip2 DB and use it for locations. -## Notes -To run the python scripts crontab is currently leveraged. Examples: -```sh -### Modify paths as appropriate. python3 is located in different places for different users. (`which python3` will give you the path) -### to edit your crontab entry, do not modify /var/spool/cron/crontabs/ directly, use `crontab -e` -### Crontabs require an empty line at the end or they WILL not run. Make sure to have 2 lines to be safe -### It is bad practice to run any cronjob more than once a minute. For timing help: https://crontab.guru/ -* * * * * /usr/bin/python3 /path-to-grafana-scripts/ombi.py --total -* * * * * /usr/bin/python3 /path-to-grafana-scripts/tautulli.py -* * * * * /usr/bin/python3 /path-to-grafana-scripts/radarr.py --queue -* * * * * /usr/bin/python3 /path-to-grafana-scripts/sonarr.py --queue -*/30 * * * * /usr/bin/python3 /path-to-grafana-scripts/radarr.py --missing -*/30 * * * * /usr/bin/python3 /path-to-grafana-scripts/sonarr.py --missing -*/30 * * * * /usr/bin/python3 /path-to-grafana-scripts/sickrage.py -``` diff --git a/Varken/dbmanager.py b/Varken/dbmanager.py index a7c7109..de5dc1d 100644 --- a/Varken/dbmanager.py +++ b/Varken/dbmanager.py @@ -4,7 +4,7 @@ class DBManager(object): def __init__(self, server): self.server = server self.influx = InfluxDBClient(self.server.url, self.server.port, self.server.username, self.server.password, - 'plex2') + 'varken') databases = [db['name'] for db in self.influx.get_list_database()] if 'varken' not in databases: From 6a8352b0b8a0d09dc98a403eb8c52a3a4aee5602 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 23:47:59 -0600 Subject: [PATCH 051/120] readme update --- README.md | 86 +++---------------------------------------------------- 1 file changed, 4 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index cb53fa2..359e01b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Varken -Dutch for PIG. PIG is an Acronym for PlexDB/Influx/Grafana +Dutch for PIG. PIG is an Acronym for Plex/InfluxDB/Grafana -Varken is a standalone commmandline utility that will aggregate date +Varken is a standalone commmand-line utility that will aggregate date from the plex ecosystem into influxdb to be displayed in grafana Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), [Python3](https://www.python.org/downloads/), [InfluxDB](https://docs.influxdata.com/influxdb/v1.5/introduction/installation/) @@ -17,86 +17,8 @@ Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), 3. Make the appropriate changes to `varken.ini` `nano data/varken.ini` 4. Copy the systemd file `cp varken.service /etc/systemd/system/` +5. start the service and enable it `systemctl start varken && systemctl enable varken` 5. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. At a minimum, you will need the plex database. 6. Install `grafana-cli plugins install grafana-worldmap-panel` -7. Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. - - - -### Docker - -Repo is included in [si0972/grafana-scripts](https://github.com/si0972/grafana-scripts-docker) - -
Example -

- -``` -docker create \ - --name=grafana-scripts \ - -v :/Scripts \ - -e plex=true \ - -e PGID= -e PUID= \ - si0972/grafana-scripts:latest -``` -

-
- - - - -## Scripts -### `sonarr.py` -Gathers data from Sonarr and pushes it to influxdb. - -``` -Script to aid in data gathering from Sonarr - -optional arguments: - -h, --help show this help message and exit - --missing Get all missing TV shows - --missing_days MISSING_DAYS - Get missing TV shows in past X days - --upcoming Get upcoming TV shows - --future FUTURE Get TV shows on X days into the future. Includes today. - i.e. --future 2 is Today and Tomorrow - --queue Get TV shows in queue -``` -- Notes: - - You cannot stack the arguments. ie. `sonarr.py --missing --queue` - - One argument must be supplied - -### `radarr.py` -Gathers data from Radarr and pushes it to influxdb - -``` -Script to aid in data gathering from Radarr - -optional arguments: - -h, --help show this help message and exit - --missing Get missing movies - --missing_avl Get missing available movies - --queue Get movies in queue -``` -- Notes: - - You cannot stack the arguments. ie. `radarr.py --missing --queue` - - One argument must be supplied - - `--missing_avl` Refers to how Radarr has determined if the movie should be available to download. The easy way to determine if the movie will appear on this list is if the movie has a RED "Missing" tag associated with that movie. BLUE "Missing" tag refers to a movie that is missing but is not available for download yet. These tags are determined by your "Minimum Availability" settings for that movie. - -### `ombi.py` -Gathers data from Ombi and pushes it to influxdb - -``` -Script to aid in data gathering from Ombi - -optional arguments: - -h, --help show this help message and exit - --total Get the total count of all requests - --counts Get the count of pending, approved, and available requests -``` -- Notes: - - You cannot stack the arguments. ie. `ombi.py --total --counts` - - One argument must be supplied - -### `tautulli.py` -Gathers data from Tautulli and pushes it to influxdb. On initial run it will download the geoip2 DB and use it for locations. +7. TODO:: Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. From f34e3bd8b34de52ac570a38f0883124eb7c5b552 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 23:49:17 -0600 Subject: [PATCH 052/120] temporary root in systemd file --- varken.service | 1 + 1 file changed, 1 insertion(+) diff --git a/varken.service b/varken.service index ba56950..0b181a2 100644 --- a/varken.service +++ b/varken.service @@ -4,6 +4,7 @@ After=network-online.target [Service] Type=simple +User=root WorkingDirectory=/opt/Varken ExecStart=/usr/bin/python3 /opt/Varken/varken.py Restart=always From 0e2ffdbbb13d0a6bc020f97c63e4c362e49f854e Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sun, 2 Dec 2018 01:33:06 -0600 Subject: [PATCH 053/120] added min requirements and split friendly name with username --- Varken/tautulli.py | 3 ++- requirements.txt | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Varken/tautulli.py b/Varken/tautulli.py index 24ceeaf..62f42a1 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -108,7 +108,8 @@ class TautulliAPI(object): "tags": { "type": "Session", "session_id": session.session_id, - "name": session.friendly_name, + "friendly_name": session.friendly_name, + "username": session.username, "title": session.full_title, "platform": session.platform, "product_version": product_version, diff --git a/requirements.txt b/requirements.txt index 4a24667..760382e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # Potential requirements. # pip3 install -r requirements.txt #--------------------------------------------------------- -requests -geoip2 -influxdb -schedule \ No newline at end of file +requests>=2.20.1 +geoip2>=2.9.0 +influxdb>=5.2.0 +schedule>=0.5.0 \ No newline at end of file From ef9022c6eb2631ba1596f1b42e30e6c4881d1260 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sun, 2 Dec 2018 13:38:37 -0600 Subject: [PATCH 054/120] added forced package imports --- Varken/dbmanager.py | 5 + Varken/helpers.py | 16 +- Varken/iniparser.py | 18 +- Varken/ombi.py | 4 + Varken/radarr.py | 4 + Varken/sonarr.py | 4 + Varken/tautulli.py | 7 +- lib/DateTime/DateTime.py | 1940 ++++ lib/DateTime/DateTime.txt | 785 ++ lib/DateTime/__init__.py | 17 + lib/DateTime/interfaces.py | 375 + lib/DateTime/pytz.txt | 192 + lib/DateTime/pytz_support.py | 259 + lib/DateTime/tests/__init__.py | 15 + lib/DateTime/tests/julian_testdata.txt | 57 + lib/DateTime/tests/test_datetime.py | 686 ++ lib/backports/configparser/__init__.py | 1390 +++ lib/backports/configparser/helpers.py | 171 + lib/bin/chardetect.exe | Bin 0 -> 93027 bytes lib/bin/easy_install-3.7.exe | Bin 0 -> 93036 bytes lib/bin/easy_install.exe | Bin 0 -> 93036 bytes lib/certifi/__init__.py | 3 + lib/certifi/__main__.py | 2 + lib/certifi/cacert.pem | 4512 +++++++++ lib/certifi/core.py | 20 + lib/chardet/__init__.py | 39 + lib/chardet/big5freq.py | 386 + lib/chardet/big5prober.py | 47 + lib/chardet/chardistribution.py | 233 + lib/chardet/charsetgroupprober.py | 106 + lib/chardet/charsetprober.py | 145 + lib/chardet/cli/__init__.py | 1 + lib/chardet/cli/chardetect.py | 85 + lib/chardet/codingstatemachine.py | 88 + lib/chardet/compat.py | 34 + lib/chardet/cp949prober.py | 49 + lib/chardet/enums.py | 76 + lib/chardet/escprober.py | 101 + lib/chardet/escsm.py | 246 + lib/chardet/eucjpprober.py | 92 + lib/chardet/euckrfreq.py | 195 + lib/chardet/euckrprober.py | 47 + lib/chardet/euctwfreq.py | 387 + lib/chardet/euctwprober.py | 46 + lib/chardet/gb2312freq.py | 283 + lib/chardet/gb2312prober.py | 46 + lib/chardet/hebrewprober.py | 292 + lib/chardet/jisfreq.py | 325 + lib/chardet/jpcntx.py | 233 + lib/chardet/langbulgarianmodel.py | 228 + lib/chardet/langcyrillicmodel.py | 333 + lib/chardet/langgreekmodel.py | 225 + lib/chardet/langhebrewmodel.py | 200 + lib/chardet/langhungarianmodel.py | 225 + lib/chardet/langthaimodel.py | 199 + lib/chardet/langturkishmodel.py | 193 + lib/chardet/latin1prober.py | 145 + lib/chardet/mbcharsetprober.py | 91 + lib/chardet/mbcsgroupprober.py | 54 + lib/chardet/mbcssm.py | 572 ++ lib/chardet/sbcharsetprober.py | 132 + lib/chardet/sbcsgroupprober.py | 73 + lib/chardet/sjisprober.py | 92 + lib/chardet/universaldetector.py | 286 + lib/chardet/utf8prober.py | 82 + lib/chardet/version.py | 9 + lib/configparser-3.5.0-py3.7-nspkg.pth | 1 + lib/dateutil/__init__.py | 8 + lib/dateutil/_common.py | 43 + lib/dateutil/_version.py | 4 + lib/dateutil/easter.py | 89 + lib/dateutil/parser/__init__.py | 60 + lib/dateutil/parser/_parser.py | 1578 ++++ lib/dateutil/parser/isoparser.py | 406 + lib/dateutil/relativedelta.py | 590 ++ lib/dateutil/rrule.py | 1672 ++++ lib/dateutil/tz/__init__.py | 17 + lib/dateutil/tz/_common.py | 415 + lib/dateutil/tz/_factories.py | 49 + lib/dateutil/tz/tz.py | 1785 ++++ lib/dateutil/tz/win.py | 331 + lib/dateutil/tzwin.py | 2 + lib/dateutil/utils.py | 71 + lib/dateutil/zoneinfo/__init__.py | 167 + .../zoneinfo/dateutil-zoneinfo.tar.gz | Bin 0 -> 154226 bytes lib/dateutil/zoneinfo/rebuild.py | 53 + lib/easy_install.py | 5 + lib/geoip2/__init__.py | 7 + lib/geoip2/compat.py | 19 + lib/geoip2/database.py | 214 + lib/geoip2/errors.py | 51 + lib/geoip2/mixins.py | 16 + lib/geoip2/models.py | 502 + lib/geoip2/records.py | 675 ++ lib/geoip2/webservice.py | 235 + lib/idna/__init__.py | 2 + lib/idna/codec.py | 118 + lib/idna/compat.py | 12 + lib/idna/core.py | 399 + lib/idna/idnadata.py | 1893 ++++ lib/idna/intranges.py | 53 + lib/idna/package_data.py | 2 + lib/idna/uts46data.py | 8179 +++++++++++++++++ lib/influxdb/__init__.py | 21 + lib/influxdb/_dataframe_client.py | 452 + lib/influxdb/chunked_json.py | 27 + lib/influxdb/client.py | 980 ++ lib/influxdb/dataframe_client.py | 28 + lib/influxdb/exceptions.py | 35 + lib/influxdb/helper.py | 184 + lib/influxdb/influxdb08/__init__.py | 18 + lib/influxdb/influxdb08/chunked_json.py | 27 + lib/influxdb/influxdb08/client.py | 843 ++ lib/influxdb/influxdb08/dataframe_client.py | 177 + lib/influxdb/influxdb08/helper.py | 153 + lib/influxdb/line_protocol.py | 172 + lib/influxdb/resultset.py | 206 + lib/influxdb/tests/__init__.py | 21 + lib/influxdb/tests/chunked_json_test.py | 51 + lib/influxdb/tests/client_test.py | 1094 +++ lib/influxdb/tests/dataframe_client_test.py | 711 ++ lib/influxdb/tests/helper_test.py | 367 + lib/influxdb/tests/influxdb08/__init__.py | 2 + lib/influxdb/tests/influxdb08/client_test.py | 904 ++ .../tests/influxdb08/dataframe_client_test.py | 331 + lib/influxdb/tests/influxdb08/helper_test.py | 228 + lib/influxdb/tests/misc.py | 50 + lib/influxdb/tests/resultset_test.py | 202 + lib/influxdb/tests/server_tests/__init__.py | 1 + lib/influxdb/tests/server_tests/base.py | 84 + .../server_tests/client_test_with_server.py | 825 ++ .../tests/server_tests/influxdb_instance.py | 198 + lib/influxdb/tests/test_line_protocol.py | 147 + lib/maxminddb/__init__.py | 54 + lib/maxminddb/compat.py | 43 + lib/maxminddb/const.py | 8 + lib/maxminddb/decoder.py | 172 + lib/maxminddb/errors.py | 10 + lib/maxminddb/extension/maxminddb.c | 602 ++ lib/maxminddb/file.py | 65 + lib/maxminddb/reader.py | 309 + lib/pkg_resources/__init__.py | 3171 +++++++ lib/pkg_resources/_vendor/__init__.py | 0 lib/pkg_resources/_vendor/appdirs.py | 608 ++ .../_vendor/packaging/__about__.py | 21 + .../_vendor/packaging/__init__.py | 14 + .../_vendor/packaging/_compat.py | 30 + .../_vendor/packaging/_structures.py | 68 + .../_vendor/packaging/markers.py | 301 + .../_vendor/packaging/requirements.py | 127 + .../_vendor/packaging/specifiers.py | 774 ++ lib/pkg_resources/_vendor/packaging/utils.py | 14 + .../_vendor/packaging/version.py | 393 + lib/pkg_resources/_vendor/pyparsing.py | 5742 ++++++++++++ lib/pkg_resources/_vendor/six.py | 868 ++ lib/pkg_resources/extern/__init__.py | 73 + lib/pkg_resources/py31compat.py | 23 + lib/pytz/__init__.py | 1527 +++ lib/pytz/exceptions.py | 48 + lib/pytz/lazy.py | 172 + lib/pytz/reference.py | 140 + lib/pytz/tzfile.py | 134 + lib/pytz/tzinfo.py | 577 ++ lib/pytz/zoneinfo/Africa/Abidjan | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Accra | Bin 0 -> 828 bytes lib/pytz/zoneinfo/Africa/Addis_Ababa | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Africa/Algiers | Bin 0 -> 751 bytes lib/pytz/zoneinfo/Africa/Asmara | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Africa/Asmera | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Africa/Bamako | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Bangui | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Banjul | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Bissau | Bin 0 -> 194 bytes lib/pytz/zoneinfo/Africa/Blantyre | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Brazzaville | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Bujumbura | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Cairo | Bin 0 -> 1963 bytes lib/pytz/zoneinfo/Africa/Casablanca | Bin 0 -> 969 bytes lib/pytz/zoneinfo/Africa/Ceuta | Bin 0 -> 2050 bytes lib/pytz/zoneinfo/Africa/Conakry | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Dakar | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Dar_es_Salaam | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Africa/Djibouti | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Africa/Douala | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/El_Aaiun | Bin 0 -> 839 bytes lib/pytz/zoneinfo/Africa/Freetown | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Gaborone | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Harare | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Johannesburg | Bin 0 -> 262 bytes lib/pytz/zoneinfo/Africa/Juba | Bin 0 -> 669 bytes lib/pytz/zoneinfo/Africa/Kampala | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Africa/Khartoum | Bin 0 -> 699 bytes lib/pytz/zoneinfo/Africa/Kigali | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Kinshasa | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Lagos | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Libreville | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Lome | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Luanda | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Lubumbashi | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Lusaka | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Malabo | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Maputo | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Maseru | Bin 0 -> 262 bytes lib/pytz/zoneinfo/Africa/Mbabane | Bin 0 -> 262 bytes lib/pytz/zoneinfo/Africa/Mogadishu | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Africa/Monrovia | Bin 0 -> 224 bytes lib/pytz/zoneinfo/Africa/Nairobi | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Africa/Ndjamena | Bin 0 -> 211 bytes lib/pytz/zoneinfo/Africa/Niamey | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Nouakchott | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Ouagadougou | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Porto-Novo | Bin 0 -> 157 bytes lib/pytz/zoneinfo/Africa/Sao_Tome | Bin 0 -> 225 bytes lib/pytz/zoneinfo/Africa/Timbuktu | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Africa/Tripoli | Bin 0 -> 641 bytes lib/pytz/zoneinfo/Africa/Tunis | Bin 0 -> 701 bytes lib/pytz/zoneinfo/Africa/Windhoek | Bin 0 -> 979 bytes lib/pytz/zoneinfo/America/Adak | Bin 0 -> 2356 bytes lib/pytz/zoneinfo/America/Anchorage | Bin 0 -> 2371 bytes lib/pytz/zoneinfo/America/Anguilla | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Antigua | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Araguaina | Bin 0 -> 896 bytes .../zoneinfo/America/Argentina/Buenos_Aires | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Argentina/Catamarca | Bin 0 -> 1100 bytes .../zoneinfo/America/Argentina/ComodRivadavia | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Argentina/Cordoba | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Argentina/Jujuy | Bin 0 -> 1072 bytes lib/pytz/zoneinfo/America/Argentina/La_Rioja | Bin 0 -> 1114 bytes lib/pytz/zoneinfo/America/Argentina/Mendoza | Bin 0 -> 1100 bytes .../zoneinfo/America/Argentina/Rio_Gallegos | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Argentina/Salta | Bin 0 -> 1072 bytes lib/pytz/zoneinfo/America/Argentina/San_Juan | Bin 0 -> 1114 bytes lib/pytz/zoneinfo/America/Argentina/San_Luis | Bin 0 -> 1130 bytes lib/pytz/zoneinfo/America/Argentina/Tucuman | Bin 0 -> 1128 bytes lib/pytz/zoneinfo/America/Argentina/Ushuaia | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Aruba | Bin 0 -> 198 bytes lib/pytz/zoneinfo/America/Asuncion | Bin 0 -> 2068 bytes lib/pytz/zoneinfo/America/Atikokan | Bin 0 -> 336 bytes lib/pytz/zoneinfo/America/Atka | Bin 0 -> 2356 bytes lib/pytz/zoneinfo/America/Bahia | Bin 0 -> 1036 bytes lib/pytz/zoneinfo/America/Bahia_Banderas | Bin 0 -> 1574 bytes lib/pytz/zoneinfo/America/Barbados | Bin 0 -> 330 bytes lib/pytz/zoneinfo/America/Belem | Bin 0 -> 588 bytes lib/pytz/zoneinfo/America/Belize | Bin 0 -> 964 bytes lib/pytz/zoneinfo/America/Blanc-Sablon | Bin 0 -> 298 bytes lib/pytz/zoneinfo/America/Boa_Vista | Bin 0 -> 644 bytes lib/pytz/zoneinfo/America/Bogota | Bin 0 -> 262 bytes lib/pytz/zoneinfo/America/Boise | Bin 0 -> 2394 bytes lib/pytz/zoneinfo/America/Buenos_Aires | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Cambridge_Bay | Bin 0 -> 2084 bytes lib/pytz/zoneinfo/America/Campo_Grande | Bin 0 -> 2002 bytes lib/pytz/zoneinfo/America/Cancun | Bin 0 -> 802 bytes lib/pytz/zoneinfo/America/Caracas | Bin 0 -> 280 bytes lib/pytz/zoneinfo/America/Catamarca | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Cayenne | Bin 0 -> 210 bytes lib/pytz/zoneinfo/America/Cayman | Bin 0 -> 194 bytes lib/pytz/zoneinfo/America/Chicago | Bin 0 -> 3576 bytes lib/pytz/zoneinfo/America/Chihuahua | Bin 0 -> 1508 bytes lib/pytz/zoneinfo/America/Coral_Harbour | Bin 0 -> 336 bytes lib/pytz/zoneinfo/America/Cordoba | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Costa_Rica | Bin 0 -> 332 bytes lib/pytz/zoneinfo/America/Creston | Bin 0 -> 224 bytes lib/pytz/zoneinfo/America/Cuiaba | Bin 0 -> 1974 bytes lib/pytz/zoneinfo/America/Curacao | Bin 0 -> 198 bytes lib/pytz/zoneinfo/America/Danmarkshavn | Bin 0 -> 698 bytes lib/pytz/zoneinfo/America/Dawson | Bin 0 -> 2084 bytes lib/pytz/zoneinfo/America/Dawson_Creek | Bin 0 -> 1050 bytes lib/pytz/zoneinfo/America/Denver | Bin 0 -> 2444 bytes lib/pytz/zoneinfo/America/Detroit | Bin 0 -> 2174 bytes lib/pytz/zoneinfo/America/Dominica | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Edmonton | Bin 0 -> 2388 bytes lib/pytz/zoneinfo/America/Eirunepe | Bin 0 -> 676 bytes lib/pytz/zoneinfo/America/El_Salvador | Bin 0 -> 236 bytes lib/pytz/zoneinfo/America/Ensenada | Bin 0 -> 2342 bytes lib/pytz/zoneinfo/America/Fort_Nelson | Bin 0 -> 2240 bytes lib/pytz/zoneinfo/America/Fort_Wayne | Bin 0 -> 1666 bytes lib/pytz/zoneinfo/America/Fortaleza | Bin 0 -> 728 bytes lib/pytz/zoneinfo/America/Glace_Bay | Bin 0 -> 2192 bytes lib/pytz/zoneinfo/America/Godthab | Bin 0 -> 1878 bytes lib/pytz/zoneinfo/America/Goose_Bay | Bin 0 -> 3210 bytes lib/pytz/zoneinfo/America/Grand_Turk | Bin 0 -> 1872 bytes lib/pytz/zoneinfo/America/Grenada | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Guadeloupe | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Guatemala | Bin 0 -> 292 bytes lib/pytz/zoneinfo/America/Guayaquil | Bin 0 -> 262 bytes lib/pytz/zoneinfo/America/Guyana | Bin 0 -> 252 bytes lib/pytz/zoneinfo/America/Halifax | Bin 0 -> 3424 bytes lib/pytz/zoneinfo/America/Havana | Bin 0 -> 2428 bytes lib/pytz/zoneinfo/America/Hermosillo | Bin 0 -> 440 bytes .../zoneinfo/America/Indiana/Indianapolis | Bin 0 -> 1666 bytes lib/pytz/zoneinfo/America/Indiana/Knox | Bin 0 -> 2428 bytes lib/pytz/zoneinfo/America/Indiana/Marengo | Bin 0 -> 1722 bytes lib/pytz/zoneinfo/America/Indiana/Petersburg | Bin 0 -> 1904 bytes lib/pytz/zoneinfo/America/Indiana/Tell_City | Bin 0 -> 1726 bytes lib/pytz/zoneinfo/America/Indiana/Vevay | Bin 0 -> 1414 bytes lib/pytz/zoneinfo/America/Indiana/Vincennes | Bin 0 -> 1694 bytes lib/pytz/zoneinfo/America/Indiana/Winamac | Bin 0 -> 1778 bytes lib/pytz/zoneinfo/America/Indianapolis | Bin 0 -> 1666 bytes lib/pytz/zoneinfo/America/Inuvik | Bin 0 -> 1914 bytes lib/pytz/zoneinfo/America/Iqaluit | Bin 0 -> 2032 bytes lib/pytz/zoneinfo/America/Jamaica | Bin 0 -> 498 bytes lib/pytz/zoneinfo/America/Jujuy | Bin 0 -> 1072 bytes lib/pytz/zoneinfo/America/Juneau | Bin 0 -> 2353 bytes lib/pytz/zoneinfo/America/Kentucky/Louisville | Bin 0 -> 2772 bytes lib/pytz/zoneinfo/America/Kentucky/Monticello | Bin 0 -> 2352 bytes lib/pytz/zoneinfo/America/Knox_IN | Bin 0 -> 2428 bytes lib/pytz/zoneinfo/America/Kralendijk | Bin 0 -> 198 bytes lib/pytz/zoneinfo/America/La_Paz | Bin 0 -> 248 bytes lib/pytz/zoneinfo/America/Lima | Bin 0 -> 422 bytes lib/pytz/zoneinfo/America/Los_Angeles | Bin 0 -> 2836 bytes lib/pytz/zoneinfo/America/Louisville | Bin 0 -> 2772 bytes lib/pytz/zoneinfo/America/Lower_Princes | Bin 0 -> 198 bytes lib/pytz/zoneinfo/America/Maceio | Bin 0 -> 756 bytes lib/pytz/zoneinfo/America/Managua | Bin 0 -> 454 bytes lib/pytz/zoneinfo/America/Manaus | Bin 0 -> 616 bytes lib/pytz/zoneinfo/America/Marigot | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Martinique | Bin 0 -> 248 bytes lib/pytz/zoneinfo/America/Matamoros | Bin 0 -> 1402 bytes lib/pytz/zoneinfo/America/Mazatlan | Bin 0 -> 1550 bytes lib/pytz/zoneinfo/America/Mendoza | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Menominee | Bin 0 -> 2274 bytes lib/pytz/zoneinfo/America/Merida | Bin 0 -> 1442 bytes lib/pytz/zoneinfo/America/Metlakatla | Bin 0 -> 1409 bytes lib/pytz/zoneinfo/America/Mexico_City | Bin 0 -> 1604 bytes lib/pytz/zoneinfo/America/Miquelon | Bin 0 -> 1682 bytes lib/pytz/zoneinfo/America/Moncton | Bin 0 -> 3154 bytes lib/pytz/zoneinfo/America/Monterrey | Bin 0 -> 1402 bytes lib/pytz/zoneinfo/America/Montevideo | Bin 0 -> 1550 bytes lib/pytz/zoneinfo/America/Montreal | Bin 0 -> 3494 bytes lib/pytz/zoneinfo/America/Montserrat | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Nassau | Bin 0 -> 2270 bytes lib/pytz/zoneinfo/America/New_York | Bin 0 -> 3536 bytes lib/pytz/zoneinfo/America/Nipigon | Bin 0 -> 2122 bytes lib/pytz/zoneinfo/America/Nome | Bin 0 -> 2367 bytes lib/pytz/zoneinfo/America/Noronha | Bin 0 -> 728 bytes lib/pytz/zoneinfo/America/North_Dakota/Beulah | Bin 0 -> 2380 bytes lib/pytz/zoneinfo/America/North_Dakota/Center | Bin 0 -> 2380 bytes .../zoneinfo/America/North_Dakota/New_Salem | Bin 0 -> 2380 bytes lib/pytz/zoneinfo/America/Ojinaga | Bin 0 -> 1508 bytes lib/pytz/zoneinfo/America/Panama | Bin 0 -> 194 bytes lib/pytz/zoneinfo/America/Pangnirtung | Bin 0 -> 2094 bytes lib/pytz/zoneinfo/America/Paramaribo | Bin 0 -> 282 bytes lib/pytz/zoneinfo/America/Phoenix | Bin 0 -> 344 bytes lib/pytz/zoneinfo/America/Port-au-Prince | Bin 0 -> 1446 bytes lib/pytz/zoneinfo/America/Port_of_Spain | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Porto_Acre | Bin 0 -> 648 bytes lib/pytz/zoneinfo/America/Porto_Velho | Bin 0 -> 588 bytes lib/pytz/zoneinfo/America/Puerto_Rico | Bin 0 -> 246 bytes lib/pytz/zoneinfo/America/Punta_Arenas | Bin 0 -> 1902 bytes lib/pytz/zoneinfo/America/Rainy_River | Bin 0 -> 2122 bytes lib/pytz/zoneinfo/America/Rankin_Inlet | Bin 0 -> 1916 bytes lib/pytz/zoneinfo/America/Recife | Bin 0 -> 728 bytes lib/pytz/zoneinfo/America/Regina | Bin 0 -> 980 bytes lib/pytz/zoneinfo/America/Resolute | Bin 0 -> 1916 bytes lib/pytz/zoneinfo/America/Rio_Branco | Bin 0 -> 648 bytes lib/pytz/zoneinfo/America/Rosario | Bin 0 -> 1100 bytes lib/pytz/zoneinfo/America/Santa_Isabel | Bin 0 -> 2342 bytes lib/pytz/zoneinfo/America/Santarem | Bin 0 -> 618 bytes lib/pytz/zoneinfo/America/Santiago | Bin 0 -> 2529 bytes lib/pytz/zoneinfo/America/Santo_Domingo | Bin 0 -> 482 bytes lib/pytz/zoneinfo/America/Sao_Paulo | Bin 0 -> 2002 bytes lib/pytz/zoneinfo/America/Scoresbysund | Bin 0 -> 1916 bytes lib/pytz/zoneinfo/America/Shiprock | Bin 0 -> 2444 bytes lib/pytz/zoneinfo/America/Sitka | Bin 0 -> 2329 bytes lib/pytz/zoneinfo/America/St_Barthelemy | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/St_Johns | Bin 0 -> 3655 bytes lib/pytz/zoneinfo/America/St_Kitts | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/St_Lucia | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/St_Thomas | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/St_Vincent | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Swift_Current | Bin 0 -> 560 bytes lib/pytz/zoneinfo/America/Tegucigalpa | Bin 0 -> 264 bytes lib/pytz/zoneinfo/America/Thule | Bin 0 -> 1514 bytes lib/pytz/zoneinfo/America/Thunder_Bay | Bin 0 -> 2202 bytes lib/pytz/zoneinfo/America/Tijuana | Bin 0 -> 2342 bytes lib/pytz/zoneinfo/America/Toronto | Bin 0 -> 3494 bytes lib/pytz/zoneinfo/America/Tortola | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Vancouver | Bin 0 -> 2892 bytes lib/pytz/zoneinfo/America/Virgin | Bin 0 -> 156 bytes lib/pytz/zoneinfo/America/Whitehorse | Bin 0 -> 2084 bytes lib/pytz/zoneinfo/America/Winnipeg | Bin 0 -> 2882 bytes lib/pytz/zoneinfo/America/Yakutat | Bin 0 -> 2305 bytes lib/pytz/zoneinfo/America/Yellowknife | Bin 0 -> 1966 bytes lib/pytz/zoneinfo/Antarctica/Casey | Bin 0 -> 297 bytes lib/pytz/zoneinfo/Antarctica/Davis | Bin 0 -> 297 bytes lib/pytz/zoneinfo/Antarctica/DumontDUrville | Bin 0 -> 202 bytes lib/pytz/zoneinfo/Antarctica/Macquarie | Bin 0 -> 1534 bytes lib/pytz/zoneinfo/Antarctica/Mawson | Bin 0 -> 211 bytes lib/pytz/zoneinfo/Antarctica/McMurdo | Bin 0 -> 2451 bytes lib/pytz/zoneinfo/Antarctica/Palmer | Bin 0 -> 1418 bytes lib/pytz/zoneinfo/Antarctica/Rothera | Bin 0 -> 172 bytes lib/pytz/zoneinfo/Antarctica/South_Pole | Bin 0 -> 2451 bytes lib/pytz/zoneinfo/Antarctica/Syowa | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Antarctica/Troll | Bin 0 -> 1162 bytes lib/pytz/zoneinfo/Antarctica/Vostok | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Arctic/Longyearbyen | Bin 0 -> 2242 bytes lib/pytz/zoneinfo/Asia/Aden | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Asia/Almaty | Bin 0 -> 1017 bytes lib/pytz/zoneinfo/Asia/Amman | Bin 0 -> 1863 bytes lib/pytz/zoneinfo/Asia/Anadyr | Bin 0 -> 1208 bytes lib/pytz/zoneinfo/Asia/Aqtau | Bin 0 -> 1003 bytes lib/pytz/zoneinfo/Asia/Aqtobe | Bin 0 -> 1033 bytes lib/pytz/zoneinfo/Asia/Ashgabat | Bin 0 -> 637 bytes lib/pytz/zoneinfo/Asia/Ashkhabad | Bin 0 -> 637 bytes lib/pytz/zoneinfo/Asia/Atyrau | Bin 0 -> 1011 bytes lib/pytz/zoneinfo/Asia/Baghdad | Bin 0 -> 995 bytes lib/pytz/zoneinfo/Asia/Bahrain | Bin 0 -> 211 bytes lib/pytz/zoneinfo/Asia/Baku | Bin 0 -> 1255 bytes lib/pytz/zoneinfo/Asia/Bangkok | Bin 0 -> 211 bytes lib/pytz/zoneinfo/Asia/Barnaul | Bin 0 -> 1241 bytes lib/pytz/zoneinfo/Asia/Beirut | Bin 0 -> 2166 bytes lib/pytz/zoneinfo/Asia/Bishkek | Bin 0 -> 999 bytes lib/pytz/zoneinfo/Asia/Brunei | Bin 0 -> 215 bytes lib/pytz/zoneinfo/Asia/Calcutta | Bin 0 -> 303 bytes lib/pytz/zoneinfo/Asia/Chita | Bin 0 -> 1243 bytes lib/pytz/zoneinfo/Asia/Choibalsan | Bin 0 -> 977 bytes lib/pytz/zoneinfo/Asia/Chongqing | Bin 0 -> 545 bytes lib/pytz/zoneinfo/Asia/Chungking | Bin 0 -> 545 bytes lib/pytz/zoneinfo/Asia/Colombo | Bin 0 -> 404 bytes lib/pytz/zoneinfo/Asia/Dacca | Bin 0 -> 361 bytes lib/pytz/zoneinfo/Asia/Damascus | Bin 0 -> 2306 bytes lib/pytz/zoneinfo/Asia/Dhaka | Bin 0 -> 361 bytes lib/pytz/zoneinfo/Asia/Dili | Bin 0 -> 239 bytes lib/pytz/zoneinfo/Asia/Dubai | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Asia/Dushanbe | Bin 0 -> 607 bytes lib/pytz/zoneinfo/Asia/Famagusta | Bin 0 -> 2028 bytes lib/pytz/zoneinfo/Asia/Gaza | Bin 0 -> 2286 bytes lib/pytz/zoneinfo/Asia/Harbin | Bin 0 -> 545 bytes lib/pytz/zoneinfo/Asia/Hebron | Bin 0 -> 2314 bytes lib/pytz/zoneinfo/Asia/Ho_Chi_Minh | Bin 0 -> 375 bytes lib/pytz/zoneinfo/Asia/Hong_Kong | Bin 0 -> 1175 bytes lib/pytz/zoneinfo/Asia/Hovd | Bin 0 -> 907 bytes lib/pytz/zoneinfo/Asia/Irkutsk | Bin 0 -> 1267 bytes lib/pytz/zoneinfo/Asia/Istanbul | Bin 0 -> 2157 bytes lib/pytz/zoneinfo/Asia/Jakarta | Bin 0 -> 383 bytes lib/pytz/zoneinfo/Asia/Jayapura | Bin 0 -> 237 bytes lib/pytz/zoneinfo/Asia/Jerusalem | Bin 0 -> 2256 bytes lib/pytz/zoneinfo/Asia/Kabul | Bin 0 -> 220 bytes lib/pytz/zoneinfo/Asia/Kamchatka | Bin 0 -> 1184 bytes lib/pytz/zoneinfo/Asia/Karachi | Bin 0 -> 403 bytes lib/pytz/zoneinfo/Asia/Kashgar | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Asia/Kathmandu | Bin 0 -> 224 bytes lib/pytz/zoneinfo/Asia/Katmandu | Bin 0 -> 224 bytes lib/pytz/zoneinfo/Asia/Khandyga | Bin 0 -> 1297 bytes lib/pytz/zoneinfo/Asia/Kolkata | Bin 0 -> 303 bytes lib/pytz/zoneinfo/Asia/Krasnoyarsk | Bin 0 -> 1229 bytes lib/pytz/zoneinfo/Asia/Kuala_Lumpur | Bin 0 -> 415 bytes lib/pytz/zoneinfo/Asia/Kuching | Bin 0 -> 507 bytes lib/pytz/zoneinfo/Asia/Kuwait | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Asia/Macao | Bin 0 -> 1241 bytes lib/pytz/zoneinfo/Asia/Macau | Bin 0 -> 1241 bytes lib/pytz/zoneinfo/Asia/Magadan | Bin 0 -> 1244 bytes lib/pytz/zoneinfo/Asia/Makassar | Bin 0 -> 274 bytes lib/pytz/zoneinfo/Asia/Manila | Bin 0 -> 350 bytes lib/pytz/zoneinfo/Asia/Muscat | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Asia/Nicosia | Bin 0 -> 2002 bytes lib/pytz/zoneinfo/Asia/Novokuznetsk | Bin 0 -> 1183 bytes lib/pytz/zoneinfo/Asia/Novosibirsk | Bin 0 -> 1241 bytes lib/pytz/zoneinfo/Asia/Omsk | Bin 0 -> 1229 bytes lib/pytz/zoneinfo/Asia/Oral | Bin 0 -> 1025 bytes lib/pytz/zoneinfo/Asia/Phnom_Penh | Bin 0 -> 211 bytes lib/pytz/zoneinfo/Asia/Pontianak | Bin 0 -> 381 bytes lib/pytz/zoneinfo/Asia/Pyongyang | Bin 0 -> 253 bytes lib/pytz/zoneinfo/Asia/Qatar | Bin 0 -> 211 bytes lib/pytz/zoneinfo/Asia/Qyzylorda | Bin 0 -> 1017 bytes lib/pytz/zoneinfo/Asia/Rangoon | Bin 0 -> 288 bytes lib/pytz/zoneinfo/Asia/Riyadh | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Asia/Saigon | Bin 0 -> 375 bytes lib/pytz/zoneinfo/Asia/Sakhalin | Bin 0 -> 1220 bytes lib/pytz/zoneinfo/Asia/Samarkand | Bin 0 -> 605 bytes lib/pytz/zoneinfo/Asia/Seoul | Bin 0 -> 517 bytes lib/pytz/zoneinfo/Asia/Shanghai | Bin 0 -> 545 bytes lib/pytz/zoneinfo/Asia/Singapore | Bin 0 -> 415 bytes lib/pytz/zoneinfo/Asia/Srednekolymsk | Bin 0 -> 1230 bytes lib/pytz/zoneinfo/Asia/Taipei | Bin 0 -> 781 bytes lib/pytz/zoneinfo/Asia/Tashkent | Bin 0 -> 621 bytes lib/pytz/zoneinfo/Asia/Tbilisi | Bin 0 -> 1071 bytes lib/pytz/zoneinfo/Asia/Tehran | Bin 0 -> 1704 bytes lib/pytz/zoneinfo/Asia/Tel_Aviv | Bin 0 -> 2256 bytes lib/pytz/zoneinfo/Asia/Thimbu | Bin 0 -> 215 bytes lib/pytz/zoneinfo/Asia/Thimphu | Bin 0 -> 215 bytes lib/pytz/zoneinfo/Asia/Tokyo | Bin 0 -> 309 bytes lib/pytz/zoneinfo/Asia/Tomsk | Bin 0 -> 1241 bytes lib/pytz/zoneinfo/Asia/Ujung_Pandang | Bin 0 -> 274 bytes lib/pytz/zoneinfo/Asia/Ulaanbaatar | Bin 0 -> 907 bytes lib/pytz/zoneinfo/Asia/Ulan_Bator | Bin 0 -> 907 bytes lib/pytz/zoneinfo/Asia/Urumqi | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Asia/Ust-Nera | Bin 0 -> 1276 bytes lib/pytz/zoneinfo/Asia/Vientiane | Bin 0 -> 211 bytes lib/pytz/zoneinfo/Asia/Vladivostok | Bin 0 -> 1230 bytes lib/pytz/zoneinfo/Asia/Yakutsk | Bin 0 -> 1229 bytes lib/pytz/zoneinfo/Asia/Yangon | Bin 0 -> 288 bytes lib/pytz/zoneinfo/Asia/Yekaterinburg | Bin 0 -> 1267 bytes lib/pytz/zoneinfo/Asia/Yerevan | Bin 0 -> 1199 bytes lib/pytz/zoneinfo/Atlantic/Azores | Bin 0 -> 3484 bytes lib/pytz/zoneinfo/Atlantic/Bermuda | Bin 0 -> 1990 bytes lib/pytz/zoneinfo/Atlantic/Canary | Bin 0 -> 1897 bytes lib/pytz/zoneinfo/Atlantic/Cape_Verde | Bin 0 -> 270 bytes lib/pytz/zoneinfo/Atlantic/Faeroe | Bin 0 -> 1815 bytes lib/pytz/zoneinfo/Atlantic/Faroe | Bin 0 -> 1815 bytes lib/pytz/zoneinfo/Atlantic/Jan_Mayen | Bin 0 -> 2242 bytes lib/pytz/zoneinfo/Atlantic/Madeira | Bin 0 -> 3475 bytes lib/pytz/zoneinfo/Atlantic/Reykjavik | Bin 0 -> 1174 bytes lib/pytz/zoneinfo/Atlantic/South_Georgia | Bin 0 -> 172 bytes lib/pytz/zoneinfo/Atlantic/St_Helena | Bin 0 -> 156 bytes lib/pytz/zoneinfo/Atlantic/Stanley | Bin 0 -> 1242 bytes lib/pytz/zoneinfo/Australia/ACT | Bin 0 -> 2214 bytes lib/pytz/zoneinfo/Australia/Adelaide | Bin 0 -> 2233 bytes lib/pytz/zoneinfo/Australia/Brisbane | Bin 0 -> 443 bytes lib/pytz/zoneinfo/Australia/Broken_Hill | Bin 0 -> 2269 bytes lib/pytz/zoneinfo/Australia/Canberra | Bin 0 -> 2214 bytes lib/pytz/zoneinfo/Australia/Currie | Bin 0 -> 2214 bytes lib/pytz/zoneinfo/Australia/Darwin | Bin 0 -> 318 bytes lib/pytz/zoneinfo/Australia/Eucla | Bin 0 -> 494 bytes lib/pytz/zoneinfo/Australia/Hobart | Bin 0 -> 2326 bytes lib/pytz/zoneinfo/Australia/LHI | Bin 0 -> 1880 bytes lib/pytz/zoneinfo/Australia/Lindeman | Bin 0 -> 513 bytes lib/pytz/zoneinfo/Australia/Lord_Howe | Bin 0 -> 1880 bytes lib/pytz/zoneinfo/Australia/Melbourne | Bin 0 -> 2214 bytes lib/pytz/zoneinfo/Australia/NSW | Bin 0 -> 2214 bytes lib/pytz/zoneinfo/Australia/North | Bin 0 -> 318 bytes lib/pytz/zoneinfo/Australia/Perth | Bin 0 -> 470 bytes lib/pytz/zoneinfo/Australia/Queensland | Bin 0 -> 443 bytes lib/pytz/zoneinfo/Australia/South | Bin 0 -> 2233 bytes lib/pytz/zoneinfo/Australia/Sydney | Bin 0 -> 2214 bytes lib/pytz/zoneinfo/Australia/Tasmania | Bin 0 -> 2326 bytes lib/pytz/zoneinfo/Australia/Victoria | Bin 0 -> 2214 bytes lib/pytz/zoneinfo/Australia/West | Bin 0 -> 470 bytes lib/pytz/zoneinfo/Australia/Yancowinna | Bin 0 -> 2269 bytes lib/pytz/zoneinfo/Brazil/Acre | Bin 0 -> 648 bytes lib/pytz/zoneinfo/Brazil/DeNoronha | Bin 0 -> 728 bytes lib/pytz/zoneinfo/Brazil/East | Bin 0 -> 2002 bytes lib/pytz/zoneinfo/Brazil/West | Bin 0 -> 616 bytes lib/pytz/zoneinfo/CET | Bin 0 -> 2102 bytes lib/pytz/zoneinfo/CST6CDT | Bin 0 -> 2294 bytes lib/pytz/zoneinfo/Canada/Atlantic | Bin 0 -> 3424 bytes lib/pytz/zoneinfo/Canada/Central | Bin 0 -> 2882 bytes lib/pytz/zoneinfo/Canada/Eastern | Bin 0 -> 3494 bytes lib/pytz/zoneinfo/Canada/Mountain | Bin 0 -> 2388 bytes lib/pytz/zoneinfo/Canada/Newfoundland | Bin 0 -> 3655 bytes lib/pytz/zoneinfo/Canada/Pacific | Bin 0 -> 2892 bytes lib/pytz/zoneinfo/Canada/Saskatchewan | Bin 0 -> 980 bytes lib/pytz/zoneinfo/Canada/Yukon | Bin 0 -> 2084 bytes lib/pytz/zoneinfo/Chile/Continental | Bin 0 -> 2529 bytes lib/pytz/zoneinfo/Chile/EasterIsland | Bin 0 -> 2233 bytes lib/pytz/zoneinfo/Cuba | Bin 0 -> 2428 bytes lib/pytz/zoneinfo/EET | Bin 0 -> 1876 bytes lib/pytz/zoneinfo/EST | Bin 0 -> 118 bytes lib/pytz/zoneinfo/EST5EDT | Bin 0 -> 2294 bytes lib/pytz/zoneinfo/Egypt | Bin 0 -> 1963 bytes lib/pytz/zoneinfo/Eire | Bin 0 -> 3522 bytes lib/pytz/zoneinfo/Etc/GMT | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Etc/GMT+0 | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Etc/GMT+1 | Bin 0 -> 120 bytes lib/pytz/zoneinfo/Etc/GMT+10 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT+11 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT+12 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT+2 | Bin 0 -> 120 bytes lib/pytz/zoneinfo/Etc/GMT+3 | Bin 0 -> 120 bytes lib/pytz/zoneinfo/Etc/GMT+4 | Bin 0 -> 120 bytes lib/pytz/zoneinfo/Etc/GMT+5 | Bin 0 -> 120 bytes lib/pytz/zoneinfo/Etc/GMT+6 | Bin 0 -> 120 bytes lib/pytz/zoneinfo/Etc/GMT+7 | Bin 0 -> 120 bytes lib/pytz/zoneinfo/Etc/GMT+8 | Bin 0 -> 120 bytes lib/pytz/zoneinfo/Etc/GMT+9 | Bin 0 -> 120 bytes lib/pytz/zoneinfo/Etc/GMT-0 | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Etc/GMT-1 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT-10 | Bin 0 -> 122 bytes lib/pytz/zoneinfo/Etc/GMT-11 | Bin 0 -> 122 bytes lib/pytz/zoneinfo/Etc/GMT-12 | Bin 0 -> 122 bytes lib/pytz/zoneinfo/Etc/GMT-13 | Bin 0 -> 122 bytes lib/pytz/zoneinfo/Etc/GMT-14 | Bin 0 -> 122 bytes lib/pytz/zoneinfo/Etc/GMT-2 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT-3 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT-4 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT-5 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT-6 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT-7 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT-8 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT-9 | Bin 0 -> 121 bytes lib/pytz/zoneinfo/Etc/GMT0 | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Etc/Greenwich | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Etc/UCT | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Etc/UTC | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Etc/Universal | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Etc/Zulu | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Europe/Amsterdam | Bin 0 -> 2940 bytes lib/pytz/zoneinfo/Europe/Andorra | Bin 0 -> 1742 bytes lib/pytz/zoneinfo/Europe/Astrakhan | Bin 0 -> 1183 bytes lib/pytz/zoneinfo/Europe/Athens | Bin 0 -> 2262 bytes lib/pytz/zoneinfo/Europe/Belfast | Bin 0 -> 3678 bytes lib/pytz/zoneinfo/Europe/Belgrade | Bin 0 -> 1948 bytes lib/pytz/zoneinfo/Europe/Berlin | Bin 0 -> 2326 bytes lib/pytz/zoneinfo/Europe/Bratislava | Bin 0 -> 2329 bytes lib/pytz/zoneinfo/Europe/Brussels | Bin 0 -> 2961 bytes lib/pytz/zoneinfo/Europe/Bucharest | Bin 0 -> 2212 bytes lib/pytz/zoneinfo/Europe/Budapest | Bin 0 -> 2396 bytes lib/pytz/zoneinfo/Europe/Busingen | Bin 0 -> 1909 bytes lib/pytz/zoneinfo/Europe/Chisinau | Bin 0 -> 2436 bytes lib/pytz/zoneinfo/Europe/Copenhagen | Bin 0 -> 2151 bytes lib/pytz/zoneinfo/Europe/Dublin | Bin 0 -> 3522 bytes lib/pytz/zoneinfo/Europe/Gibraltar | Bin 0 -> 3052 bytes lib/pytz/zoneinfo/Europe/Guernsey | Bin 0 -> 3678 bytes lib/pytz/zoneinfo/Europe/Helsinki | Bin 0 -> 1900 bytes lib/pytz/zoneinfo/Europe/Isle_of_Man | Bin 0 -> 3678 bytes lib/pytz/zoneinfo/Europe/Istanbul | Bin 0 -> 2157 bytes lib/pytz/zoneinfo/Europe/Jersey | Bin 0 -> 3678 bytes lib/pytz/zoneinfo/Europe/Kaliningrad | Bin 0 -> 1509 bytes lib/pytz/zoneinfo/Europe/Kiev | Bin 0 -> 2088 bytes lib/pytz/zoneinfo/Europe/Kirov | Bin 0 -> 1153 bytes lib/pytz/zoneinfo/Europe/Lisbon | Bin 0 -> 3469 bytes lib/pytz/zoneinfo/Europe/Ljubljana | Bin 0 -> 1948 bytes lib/pytz/zoneinfo/Europe/London | Bin 0 -> 3678 bytes lib/pytz/zoneinfo/Europe/Luxembourg | Bin 0 -> 2960 bytes lib/pytz/zoneinfo/Europe/Madrid | Bin 0 -> 2628 bytes lib/pytz/zoneinfo/Europe/Malta | Bin 0 -> 2620 bytes lib/pytz/zoneinfo/Europe/Mariehamn | Bin 0 -> 1900 bytes lib/pytz/zoneinfo/Europe/Minsk | Bin 0 -> 1361 bytes lib/pytz/zoneinfo/Europe/Monaco | Bin 0 -> 2944 bytes lib/pytz/zoneinfo/Europe/Moscow | Bin 0 -> 1535 bytes lib/pytz/zoneinfo/Europe/Nicosia | Bin 0 -> 2002 bytes lib/pytz/zoneinfo/Europe/Oslo | Bin 0 -> 2242 bytes lib/pytz/zoneinfo/Europe/Paris | Bin 0 -> 2962 bytes lib/pytz/zoneinfo/Europe/Podgorica | Bin 0 -> 1948 bytes lib/pytz/zoneinfo/Europe/Prague | Bin 0 -> 2329 bytes lib/pytz/zoneinfo/Europe/Riga | Bin 0 -> 2226 bytes lib/pytz/zoneinfo/Europe/Rome | Bin 0 -> 2683 bytes lib/pytz/zoneinfo/Europe/Samara | Bin 0 -> 1215 bytes lib/pytz/zoneinfo/Europe/San_Marino | Bin 0 -> 2683 bytes lib/pytz/zoneinfo/Europe/Sarajevo | Bin 0 -> 1948 bytes lib/pytz/zoneinfo/Europe/Saratov | Bin 0 -> 1183 bytes lib/pytz/zoneinfo/Europe/Simferopol | Bin 0 -> 1481 bytes lib/pytz/zoneinfo/Europe/Skopje | Bin 0 -> 1948 bytes lib/pytz/zoneinfo/Europe/Sofia | Bin 0 -> 2121 bytes lib/pytz/zoneinfo/Europe/Stockholm | Bin 0 -> 1909 bytes lib/pytz/zoneinfo/Europe/Tallinn | Bin 0 -> 2178 bytes lib/pytz/zoneinfo/Europe/Tirane | Bin 0 -> 2084 bytes lib/pytz/zoneinfo/Europe/Tiraspol | Bin 0 -> 2436 bytes lib/pytz/zoneinfo/Europe/Ulyanovsk | Bin 0 -> 1267 bytes lib/pytz/zoneinfo/Europe/Uzhgorod | Bin 0 -> 2094 bytes lib/pytz/zoneinfo/Europe/Vaduz | Bin 0 -> 1909 bytes lib/pytz/zoneinfo/Europe/Vatican | Bin 0 -> 2683 bytes lib/pytz/zoneinfo/Europe/Vienna | Bin 0 -> 2228 bytes lib/pytz/zoneinfo/Europe/Vilnius | Bin 0 -> 2190 bytes lib/pytz/zoneinfo/Europe/Volgograd | Bin 0 -> 1183 bytes lib/pytz/zoneinfo/Europe/Warsaw | Bin 0 -> 2696 bytes lib/pytz/zoneinfo/Europe/Zagreb | Bin 0 -> 1948 bytes lib/pytz/zoneinfo/Europe/Zaporozhye | Bin 0 -> 2106 bytes lib/pytz/zoneinfo/Europe/Zurich | Bin 0 -> 1909 bytes lib/pytz/zoneinfo/Factory | Bin 0 -> 120 bytes lib/pytz/zoneinfo/GB | Bin 0 -> 3678 bytes lib/pytz/zoneinfo/GB-Eire | Bin 0 -> 3678 bytes lib/pytz/zoneinfo/GMT | Bin 0 -> 118 bytes lib/pytz/zoneinfo/GMT+0 | Bin 0 -> 118 bytes lib/pytz/zoneinfo/GMT-0 | Bin 0 -> 118 bytes lib/pytz/zoneinfo/GMT0 | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Greenwich | Bin 0 -> 118 bytes lib/pytz/zoneinfo/HST | Bin 0 -> 119 bytes lib/pytz/zoneinfo/Hongkong | Bin 0 -> 1175 bytes lib/pytz/zoneinfo/Iceland | Bin 0 -> 1174 bytes lib/pytz/zoneinfo/Indian/Antananarivo | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Indian/Chagos | Bin 0 -> 211 bytes lib/pytz/zoneinfo/Indian/Christmas | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Indian/Cocos | Bin 0 -> 182 bytes lib/pytz/zoneinfo/Indian/Comoro | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Indian/Kerguelen | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Indian/Mahe | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Indian/Maldives | Bin 0 -> 211 bytes lib/pytz/zoneinfo/Indian/Mauritius | Bin 0 -> 253 bytes lib/pytz/zoneinfo/Indian/Mayotte | Bin 0 -> 271 bytes lib/pytz/zoneinfo/Indian/Reunion | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Iran | Bin 0 -> 1704 bytes lib/pytz/zoneinfo/Israel | Bin 0 -> 2256 bytes lib/pytz/zoneinfo/Jamaica | Bin 0 -> 498 bytes lib/pytz/zoneinfo/Japan | Bin 0 -> 309 bytes lib/pytz/zoneinfo/Kwajalein | Bin 0 -> 250 bytes lib/pytz/zoneinfo/Libya | Bin 0 -> 641 bytes lib/pytz/zoneinfo/MET | Bin 0 -> 2102 bytes lib/pytz/zoneinfo/MST | Bin 0 -> 118 bytes lib/pytz/zoneinfo/MST7MDT | Bin 0 -> 2294 bytes lib/pytz/zoneinfo/Mexico/BajaNorte | Bin 0 -> 2342 bytes lib/pytz/zoneinfo/Mexico/BajaSur | Bin 0 -> 1550 bytes lib/pytz/zoneinfo/Mexico/General | Bin 0 -> 1604 bytes lib/pytz/zoneinfo/NZ | Bin 0 -> 2451 bytes lib/pytz/zoneinfo/NZ-CHAT | Bin 0 -> 2078 bytes lib/pytz/zoneinfo/Navajo | Bin 0 -> 2444 bytes lib/pytz/zoneinfo/PRC | Bin 0 -> 545 bytes lib/pytz/zoneinfo/PST8PDT | Bin 0 -> 2294 bytes lib/pytz/zoneinfo/Pacific/Apia | Bin 0 -> 1125 bytes lib/pytz/zoneinfo/Pacific/Auckland | Bin 0 -> 2451 bytes lib/pytz/zoneinfo/Pacific/Bougainville | Bin 0 -> 286 bytes lib/pytz/zoneinfo/Pacific/Chatham | Bin 0 -> 2078 bytes lib/pytz/zoneinfo/Pacific/Chuuk | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Pacific/Easter | Bin 0 -> 2233 bytes lib/pytz/zoneinfo/Pacific/Efate | Bin 0 -> 478 bytes lib/pytz/zoneinfo/Pacific/Enderbury | Bin 0 -> 250 bytes lib/pytz/zoneinfo/Pacific/Fakaofo | Bin 0 -> 212 bytes lib/pytz/zoneinfo/Pacific/Fiji | Bin 0 -> 1090 bytes lib/pytz/zoneinfo/Pacific/Funafuti | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Pacific/Galapagos | Bin 0 -> 254 bytes lib/pytz/zoneinfo/Pacific/Gambier | Bin 0 -> 172 bytes lib/pytz/zoneinfo/Pacific/Guadalcanal | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Pacific/Guam | Bin 0 -> 216 bytes lib/pytz/zoneinfo/Pacific/Honolulu | Bin 0 -> 329 bytes lib/pytz/zoneinfo/Pacific/Johnston | Bin 0 -> 329 bytes lib/pytz/zoneinfo/Pacific/Kiritimati | Bin 0 -> 254 bytes lib/pytz/zoneinfo/Pacific/Kosrae | Bin 0 -> 242 bytes lib/pytz/zoneinfo/Pacific/Kwajalein | Bin 0 -> 250 bytes lib/pytz/zoneinfo/Pacific/Majuro | Bin 0 -> 212 bytes lib/pytz/zoneinfo/Pacific/Marquesas | Bin 0 -> 181 bytes lib/pytz/zoneinfo/Pacific/Midway | Bin 0 -> 187 bytes lib/pytz/zoneinfo/Pacific/Nauru | Bin 0 -> 268 bytes lib/pytz/zoneinfo/Pacific/Niue | Bin 0 -> 257 bytes lib/pytz/zoneinfo/Pacific/Norfolk | Bin 0 -> 314 bytes lib/pytz/zoneinfo/Pacific/Noumea | Bin 0 -> 314 bytes lib/pytz/zoneinfo/Pacific/Pago_Pago | Bin 0 -> 187 bytes lib/pytz/zoneinfo/Pacific/Palau | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Pacific/Pitcairn | Bin 0 -> 214 bytes lib/pytz/zoneinfo/Pacific/Pohnpei | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Pacific/Ponape | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Pacific/Port_Moresby | Bin 0 -> 196 bytes lib/pytz/zoneinfo/Pacific/Rarotonga | Bin 0 -> 593 bytes lib/pytz/zoneinfo/Pacific/Saipan | Bin 0 -> 216 bytes lib/pytz/zoneinfo/Pacific/Samoa | Bin 0 -> 187 bytes lib/pytz/zoneinfo/Pacific/Tahiti | Bin 0 -> 173 bytes lib/pytz/zoneinfo/Pacific/Tarawa | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Pacific/Tongatapu | Bin 0 -> 384 bytes lib/pytz/zoneinfo/Pacific/Truk | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Pacific/Wake | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Pacific/Wallis | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Pacific/Yap | Bin 0 -> 174 bytes lib/pytz/zoneinfo/Poland | Bin 0 -> 2696 bytes lib/pytz/zoneinfo/Portugal | Bin 0 -> 3469 bytes lib/pytz/zoneinfo/ROC | Bin 0 -> 781 bytes lib/pytz/zoneinfo/ROK | Bin 0 -> 517 bytes lib/pytz/zoneinfo/Singapore | Bin 0 -> 415 bytes lib/pytz/zoneinfo/Turkey | Bin 0 -> 2157 bytes lib/pytz/zoneinfo/UCT | Bin 0 -> 118 bytes lib/pytz/zoneinfo/US/Alaska | Bin 0 -> 2371 bytes lib/pytz/zoneinfo/US/Aleutian | Bin 0 -> 2356 bytes lib/pytz/zoneinfo/US/Arizona | Bin 0 -> 344 bytes lib/pytz/zoneinfo/US/Central | Bin 0 -> 3576 bytes lib/pytz/zoneinfo/US/East-Indiana | Bin 0 -> 1666 bytes lib/pytz/zoneinfo/US/Eastern | Bin 0 -> 3536 bytes lib/pytz/zoneinfo/US/Hawaii | Bin 0 -> 329 bytes lib/pytz/zoneinfo/US/Indiana-Starke | Bin 0 -> 2428 bytes lib/pytz/zoneinfo/US/Michigan | Bin 0 -> 2174 bytes lib/pytz/zoneinfo/US/Mountain | Bin 0 -> 2444 bytes lib/pytz/zoneinfo/US/Pacific | Bin 0 -> 2836 bytes lib/pytz/zoneinfo/US/Samoa | Bin 0 -> 187 bytes lib/pytz/zoneinfo/UTC | Bin 0 -> 118 bytes lib/pytz/zoneinfo/Universal | Bin 0 -> 118 bytes lib/pytz/zoneinfo/W-SU | Bin 0 -> 1535 bytes lib/pytz/zoneinfo/WET | Bin 0 -> 1873 bytes lib/pytz/zoneinfo/Zulu | Bin 0 -> 118 bytes lib/pytz/zoneinfo/iso3166.tab | 274 + lib/pytz/zoneinfo/leapseconds | 66 + lib/pytz/zoneinfo/posixrules | Bin 0 -> 3536 bytes lib/pytz/zoneinfo/tzdata.zi | 4177 +++++++++ lib/pytz/zoneinfo/zone.tab | 448 + lib/pytz/zoneinfo/zone1970.tab | 382 + lib/requests/__init__.py | 131 + lib/requests/__version__.py | 14 + lib/requests/_internal_utils.py | 42 + lib/requests/adapters.py | 533 ++ lib/requests/api.py | 158 + lib/requests/auth.py | 305 + lib/requests/certs.py | 18 + lib/requests/compat.py | 70 + lib/requests/cookies.py | 549 ++ lib/requests/exceptions.py | 126 + lib/requests/help.py | 119 + lib/requests/hooks.py | 34 + lib/requests/models.py | 953 ++ lib/requests/packages.py | 14 + lib/requests/sessions.py | 770 ++ lib/requests/status_codes.py | 120 + lib/requests/structures.py | 103 + lib/requests/utils.py | 977 ++ lib/schedule/__init__.py | 528 ++ lib/setuptools/__init__.py | 195 + lib/setuptools/_deprecation_warning.py | 7 + lib/setuptools/_vendor/__init__.py | 0 lib/setuptools/_vendor/packaging/__about__.py | 21 + lib/setuptools/_vendor/packaging/__init__.py | 14 + lib/setuptools/_vendor/packaging/_compat.py | 30 + .../_vendor/packaging/_structures.py | 68 + lib/setuptools/_vendor/packaging/markers.py | 301 + .../_vendor/packaging/requirements.py | 127 + .../_vendor/packaging/specifiers.py | 774 ++ lib/setuptools/_vendor/packaging/utils.py | 14 + lib/setuptools/_vendor/packaging/version.py | 393 + lib/setuptools/_vendor/pyparsing.py | 5742 ++++++++++++ lib/setuptools/_vendor/six.py | 868 ++ lib/setuptools/archive_util.py | 173 + lib/setuptools/build_meta.py | 182 + lib/setuptools/cli-32.exe | Bin 0 -> 65536 bytes lib/setuptools/cli-64.exe | Bin 0 -> 74752 bytes lib/setuptools/cli.exe | Bin 0 -> 65536 bytes lib/setuptools/command/__init__.py | 18 + lib/setuptools/command/alias.py | 80 + lib/setuptools/command/bdist_egg.py | 502 + lib/setuptools/command/bdist_rpm.py | 43 + lib/setuptools/command/bdist_wininst.py | 21 + lib/setuptools/command/build_clib.py | 98 + lib/setuptools/command/build_ext.py | 321 + lib/setuptools/command/build_py.py | 270 + lib/setuptools/command/develop.py | 218 + lib/setuptools/command/dist_info.py | 36 + lib/setuptools/command/easy_install.py | 2342 +++++ lib/setuptools/command/egg_info.py | 716 ++ lib/setuptools/command/install.py | 125 + lib/setuptools/command/install_egg_info.py | 62 + lib/setuptools/command/install_lib.py | 121 + lib/setuptools/command/install_scripts.py | 65 + lib/setuptools/command/launcher manifest.xml | 15 + lib/setuptools/command/py36compat.py | 136 + lib/setuptools/command/register.py | 18 + lib/setuptools/command/rotate.py | 66 + lib/setuptools/command/saveopts.py | 22 + lib/setuptools/command/sdist.py | 200 + lib/setuptools/command/setopt.py | 149 + lib/setuptools/command/test.py | 270 + lib/setuptools/command/upload.py | 196 + lib/setuptools/command/upload_docs.py | 206 + lib/setuptools/config.py | 635 ++ lib/setuptools/dep_util.py | 23 + lib/setuptools/depends.py | 186 + lib/setuptools/dist.py | 1147 +++ lib/setuptools/extension.py | 57 + lib/setuptools/extern/__init__.py | 73 + lib/setuptools/glibc.py | 86 + lib/setuptools/glob.py | 174 + lib/setuptools/gui-32.exe | Bin 0 -> 65536 bytes lib/setuptools/gui-64.exe | Bin 0 -> 75264 bytes lib/setuptools/gui.exe | Bin 0 -> 65536 bytes lib/setuptools/launch.py | 35 + lib/setuptools/lib2to3_ex.py | 62 + lib/setuptools/monkey.py | 179 + lib/setuptools/msvc.py | 1301 +++ lib/setuptools/namespaces.py | 107 + lib/setuptools/package_index.py | 1128 +++ lib/setuptools/pep425tags.py | 319 + lib/setuptools/py27compat.py | 28 + lib/setuptools/py31compat.py | 32 + lib/setuptools/py33compat.py | 55 + lib/setuptools/py36compat.py | 82 + lib/setuptools/sandbox.py | 491 + lib/setuptools/script (dev).tmpl | 6 + lib/setuptools/script.tmpl | 3 + lib/setuptools/site-patch.py | 74 + lib/setuptools/ssl_support.py | 260 + lib/setuptools/unicode_utils.py | 44 + lib/setuptools/version.py | 6 + lib/setuptools/wheel.py | 210 + lib/setuptools/windows_support.py | 29 + lib/six.py | 891 ++ lib/typing.py | 2413 +++++ lib/urllib3/__init__.py | 92 + lib/urllib3/_collections.py | 329 + lib/urllib3/connection.py | 391 + lib/urllib3/connectionpool.py | 896 ++ lib/urllib3/contrib/__init__.py | 0 lib/urllib3/contrib/_appengine_environ.py | 30 + .../contrib/_securetransport/__init__.py | 0 .../contrib/_securetransport/bindings.py | 593 ++ .../contrib/_securetransport/low_level.py | 346 + lib/urllib3/contrib/appengine.py | 289 + lib/urllib3/contrib/ntlmpool.py | 111 + lib/urllib3/contrib/pyopenssl.py | 466 + lib/urllib3/contrib/securetransport.py | 804 ++ lib/urllib3/contrib/socks.py | 192 + lib/urllib3/exceptions.py | 246 + lib/urllib3/fields.py | 178 + lib/urllib3/filepost.py | 98 + lib/urllib3/packages/__init__.py | 5 + lib/urllib3/packages/backports/__init__.py | 0 lib/urllib3/packages/backports/makefile.py | 53 + lib/urllib3/packages/six.py | 868 ++ .../packages/ssl_match_hostname/__init__.py | 19 + .../ssl_match_hostname/_implementation.py | 156 + lib/urllib3/poolmanager.py | 450 + lib/urllib3/request.py | 150 + lib/urllib3/response.py | 705 ++ lib/urllib3/util/__init__.py | 54 + lib/urllib3/util/connection.py | 134 + lib/urllib3/util/queue.py | 21 + lib/urllib3/util/request.py | 118 + lib/urllib3/util/response.py | 87 + lib/urllib3/util/retry.py | 411 + lib/urllib3/util/ssl_.py | 381 + lib/urllib3/util/timeout.py | 242 + lib/urllib3/util/url.py | 230 + lib/urllib3/util/wait.py | 150 + lib/zope.interface-4.6.0-py3.7-nspkg.pth | 1 + lib/zope/interface/__init__.py | 90 + lib/zope/interface/_compat.py | 58 + lib/zope/interface/_flatten.py | 35 + .../_zope_interface_coptimizations.c | 1726 ++++ ...pe_interface_coptimizations.cp37-win32.pyd | Bin 0 -> 22528 bytes lib/zope/interface/adapter.py | 712 ++ lib/zope/interface/advice.py | 205 + lib/zope/interface/common/__init__.py | 2 + lib/zope/interface/common/idatetime.py | 606 ++ lib/zope/interface/common/interfaces.py | 212 + lib/zope/interface/common/mapping.py | 150 + lib/zope/interface/common/sequence.py | 165 + lib/zope/interface/common/tests/__init__.py | 2 + .../interface/common/tests/basemapping.py | 107 + .../interface/common/tests/test_idatetime.py | 37 + .../common/tests/test_import_interfaces.py | 20 + lib/zope/interface/declarations.py | 929 ++ lib/zope/interface/document.py | 120 + lib/zope/interface/exceptions.py | 67 + lib/zope/interface/interface.py | 687 ++ lib/zope/interface/interfaces.py | 1282 +++ lib/zope/interface/registry.py | 654 ++ lib/zope/interface/ro.py | 64 + lib/zope/interface/tests/__init__.py | 1 + lib/zope/interface/tests/advisory_testing.py | 42 + lib/zope/interface/tests/dummy.py | 23 + lib/zope/interface/tests/idummy.py | 23 + lib/zope/interface/tests/ifoo.py | 26 + lib/zope/interface/tests/ifoo_other.py | 26 + lib/zope/interface/tests/m1.py | 21 + lib/zope/interface/tests/m2.py | 15 + lib/zope/interface/tests/odd.py | 128 + lib/zope/interface/tests/test_adapter.py | 1419 +++ lib/zope/interface/tests/test_advice.py | 355 + lib/zope/interface/tests/test_declarations.py | 1658 ++++ lib/zope/interface/tests/test_document.py | 505 + lib/zope/interface/tests/test_element.py | 31 + lib/zope/interface/tests/test_exceptions.py | 72 + lib/zope/interface/tests/test_interface.py | 2123 +++++ lib/zope/interface/tests/test_interfaces.py | 95 + .../interface/tests/test_odd_declarations.py | 268 + lib/zope/interface/tests/test_registry.py | 2788 ++++++ lib/zope/interface/tests/test_ro.py | 115 + lib/zope/interface/tests/test_sorting.py | 47 + lib/zope/interface/tests/test_verify.py | 582 ++ lib/zope/interface/verify.py | 123 + requirements.txt | 5 +- varken.py | 4 + 943 files changed, 125530 insertions(+), 16 deletions(-) create mode 100644 lib/DateTime/DateTime.py create mode 100644 lib/DateTime/DateTime.txt create mode 100644 lib/DateTime/__init__.py create mode 100644 lib/DateTime/interfaces.py create mode 100644 lib/DateTime/pytz.txt create mode 100644 lib/DateTime/pytz_support.py create mode 100644 lib/DateTime/tests/__init__.py create mode 100644 lib/DateTime/tests/julian_testdata.txt create mode 100644 lib/DateTime/tests/test_datetime.py create mode 100644 lib/backports/configparser/__init__.py create mode 100644 lib/backports/configparser/helpers.py create mode 100644 lib/bin/chardetect.exe create mode 100644 lib/bin/easy_install-3.7.exe create mode 100644 lib/bin/easy_install.exe create mode 100644 lib/certifi/__init__.py create mode 100644 lib/certifi/__main__.py create mode 100644 lib/certifi/cacert.pem create mode 100644 lib/certifi/core.py create mode 100644 lib/chardet/__init__.py create mode 100644 lib/chardet/big5freq.py create mode 100644 lib/chardet/big5prober.py create mode 100644 lib/chardet/chardistribution.py create mode 100644 lib/chardet/charsetgroupprober.py create mode 100644 lib/chardet/charsetprober.py create mode 100644 lib/chardet/cli/__init__.py create mode 100644 lib/chardet/cli/chardetect.py create mode 100644 lib/chardet/codingstatemachine.py create mode 100644 lib/chardet/compat.py create mode 100644 lib/chardet/cp949prober.py create mode 100644 lib/chardet/enums.py create mode 100644 lib/chardet/escprober.py create mode 100644 lib/chardet/escsm.py create mode 100644 lib/chardet/eucjpprober.py create mode 100644 lib/chardet/euckrfreq.py create mode 100644 lib/chardet/euckrprober.py create mode 100644 lib/chardet/euctwfreq.py create mode 100644 lib/chardet/euctwprober.py create mode 100644 lib/chardet/gb2312freq.py create mode 100644 lib/chardet/gb2312prober.py create mode 100644 lib/chardet/hebrewprober.py create mode 100644 lib/chardet/jisfreq.py create mode 100644 lib/chardet/jpcntx.py create mode 100644 lib/chardet/langbulgarianmodel.py create mode 100644 lib/chardet/langcyrillicmodel.py create mode 100644 lib/chardet/langgreekmodel.py create mode 100644 lib/chardet/langhebrewmodel.py create mode 100644 lib/chardet/langhungarianmodel.py create mode 100644 lib/chardet/langthaimodel.py create mode 100644 lib/chardet/langturkishmodel.py create mode 100644 lib/chardet/latin1prober.py create mode 100644 lib/chardet/mbcharsetprober.py create mode 100644 lib/chardet/mbcsgroupprober.py create mode 100644 lib/chardet/mbcssm.py create mode 100644 lib/chardet/sbcharsetprober.py create mode 100644 lib/chardet/sbcsgroupprober.py create mode 100644 lib/chardet/sjisprober.py create mode 100644 lib/chardet/universaldetector.py create mode 100644 lib/chardet/utf8prober.py create mode 100644 lib/chardet/version.py create mode 100644 lib/configparser-3.5.0-py3.7-nspkg.pth create mode 100644 lib/dateutil/__init__.py create mode 100644 lib/dateutil/_common.py create mode 100644 lib/dateutil/_version.py create mode 100644 lib/dateutil/easter.py create mode 100644 lib/dateutil/parser/__init__.py create mode 100644 lib/dateutil/parser/_parser.py create mode 100644 lib/dateutil/parser/isoparser.py create mode 100644 lib/dateutil/relativedelta.py create mode 100644 lib/dateutil/rrule.py create mode 100644 lib/dateutil/tz/__init__.py create mode 100644 lib/dateutil/tz/_common.py create mode 100644 lib/dateutil/tz/_factories.py create mode 100644 lib/dateutil/tz/tz.py create mode 100644 lib/dateutil/tz/win.py create mode 100644 lib/dateutil/tzwin.py create mode 100644 lib/dateutil/utils.py create mode 100644 lib/dateutil/zoneinfo/__init__.py create mode 100644 lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz create mode 100644 lib/dateutil/zoneinfo/rebuild.py create mode 100644 lib/easy_install.py create mode 100644 lib/geoip2/__init__.py create mode 100644 lib/geoip2/compat.py create mode 100644 lib/geoip2/database.py create mode 100644 lib/geoip2/errors.py create mode 100644 lib/geoip2/mixins.py create mode 100644 lib/geoip2/models.py create mode 100644 lib/geoip2/records.py create mode 100644 lib/geoip2/webservice.py create mode 100644 lib/idna/__init__.py create mode 100644 lib/idna/codec.py create mode 100644 lib/idna/compat.py create mode 100644 lib/idna/core.py create mode 100644 lib/idna/idnadata.py create mode 100644 lib/idna/intranges.py create mode 100644 lib/idna/package_data.py create mode 100644 lib/idna/uts46data.py create mode 100644 lib/influxdb/__init__.py create mode 100644 lib/influxdb/_dataframe_client.py create mode 100644 lib/influxdb/chunked_json.py create mode 100644 lib/influxdb/client.py create mode 100644 lib/influxdb/dataframe_client.py create mode 100644 lib/influxdb/exceptions.py create mode 100644 lib/influxdb/helper.py create mode 100644 lib/influxdb/influxdb08/__init__.py create mode 100644 lib/influxdb/influxdb08/chunked_json.py create mode 100644 lib/influxdb/influxdb08/client.py create mode 100644 lib/influxdb/influxdb08/dataframe_client.py create mode 100644 lib/influxdb/influxdb08/helper.py create mode 100644 lib/influxdb/line_protocol.py create mode 100644 lib/influxdb/resultset.py create mode 100644 lib/influxdb/tests/__init__.py create mode 100644 lib/influxdb/tests/chunked_json_test.py create mode 100644 lib/influxdb/tests/client_test.py create mode 100644 lib/influxdb/tests/dataframe_client_test.py create mode 100644 lib/influxdb/tests/helper_test.py create mode 100644 lib/influxdb/tests/influxdb08/__init__.py create mode 100644 lib/influxdb/tests/influxdb08/client_test.py create mode 100644 lib/influxdb/tests/influxdb08/dataframe_client_test.py create mode 100644 lib/influxdb/tests/influxdb08/helper_test.py create mode 100644 lib/influxdb/tests/misc.py create mode 100644 lib/influxdb/tests/resultset_test.py create mode 100644 lib/influxdb/tests/server_tests/__init__.py create mode 100644 lib/influxdb/tests/server_tests/base.py create mode 100644 lib/influxdb/tests/server_tests/client_test_with_server.py create mode 100644 lib/influxdb/tests/server_tests/influxdb_instance.py create mode 100644 lib/influxdb/tests/test_line_protocol.py create mode 100644 lib/maxminddb/__init__.py create mode 100644 lib/maxminddb/compat.py create mode 100644 lib/maxminddb/const.py create mode 100644 lib/maxminddb/decoder.py create mode 100644 lib/maxminddb/errors.py create mode 100644 lib/maxminddb/extension/maxminddb.c create mode 100644 lib/maxminddb/file.py create mode 100644 lib/maxminddb/reader.py create mode 100644 lib/pkg_resources/__init__.py create mode 100644 lib/pkg_resources/_vendor/__init__.py create mode 100644 lib/pkg_resources/_vendor/appdirs.py create mode 100644 lib/pkg_resources/_vendor/packaging/__about__.py create mode 100644 lib/pkg_resources/_vendor/packaging/__init__.py create mode 100644 lib/pkg_resources/_vendor/packaging/_compat.py create mode 100644 lib/pkg_resources/_vendor/packaging/_structures.py create mode 100644 lib/pkg_resources/_vendor/packaging/markers.py create mode 100644 lib/pkg_resources/_vendor/packaging/requirements.py create mode 100644 lib/pkg_resources/_vendor/packaging/specifiers.py create mode 100644 lib/pkg_resources/_vendor/packaging/utils.py create mode 100644 lib/pkg_resources/_vendor/packaging/version.py create mode 100644 lib/pkg_resources/_vendor/pyparsing.py create mode 100644 lib/pkg_resources/_vendor/six.py create mode 100644 lib/pkg_resources/extern/__init__.py create mode 100644 lib/pkg_resources/py31compat.py create mode 100644 lib/pytz/__init__.py create mode 100644 lib/pytz/exceptions.py create mode 100644 lib/pytz/lazy.py create mode 100644 lib/pytz/reference.py create mode 100644 lib/pytz/tzfile.py create mode 100644 lib/pytz/tzinfo.py create mode 100644 lib/pytz/zoneinfo/Africa/Abidjan create mode 100644 lib/pytz/zoneinfo/Africa/Accra create mode 100644 lib/pytz/zoneinfo/Africa/Addis_Ababa create mode 100644 lib/pytz/zoneinfo/Africa/Algiers create mode 100644 lib/pytz/zoneinfo/Africa/Asmara create mode 100644 lib/pytz/zoneinfo/Africa/Asmera create mode 100644 lib/pytz/zoneinfo/Africa/Bamako create mode 100644 lib/pytz/zoneinfo/Africa/Bangui create mode 100644 lib/pytz/zoneinfo/Africa/Banjul create mode 100644 lib/pytz/zoneinfo/Africa/Bissau create mode 100644 lib/pytz/zoneinfo/Africa/Blantyre create mode 100644 lib/pytz/zoneinfo/Africa/Brazzaville create mode 100644 lib/pytz/zoneinfo/Africa/Bujumbura create mode 100644 lib/pytz/zoneinfo/Africa/Cairo create mode 100644 lib/pytz/zoneinfo/Africa/Casablanca create mode 100644 lib/pytz/zoneinfo/Africa/Ceuta create mode 100644 lib/pytz/zoneinfo/Africa/Conakry create mode 100644 lib/pytz/zoneinfo/Africa/Dakar create mode 100644 lib/pytz/zoneinfo/Africa/Dar_es_Salaam create mode 100644 lib/pytz/zoneinfo/Africa/Djibouti create mode 100644 lib/pytz/zoneinfo/Africa/Douala create mode 100644 lib/pytz/zoneinfo/Africa/El_Aaiun create mode 100644 lib/pytz/zoneinfo/Africa/Freetown create mode 100644 lib/pytz/zoneinfo/Africa/Gaborone create mode 100644 lib/pytz/zoneinfo/Africa/Harare create mode 100644 lib/pytz/zoneinfo/Africa/Johannesburg create mode 100644 lib/pytz/zoneinfo/Africa/Juba create mode 100644 lib/pytz/zoneinfo/Africa/Kampala create mode 100644 lib/pytz/zoneinfo/Africa/Khartoum create mode 100644 lib/pytz/zoneinfo/Africa/Kigali create mode 100644 lib/pytz/zoneinfo/Africa/Kinshasa create mode 100644 lib/pytz/zoneinfo/Africa/Lagos create mode 100644 lib/pytz/zoneinfo/Africa/Libreville create mode 100644 lib/pytz/zoneinfo/Africa/Lome create mode 100644 lib/pytz/zoneinfo/Africa/Luanda create mode 100644 lib/pytz/zoneinfo/Africa/Lubumbashi create mode 100644 lib/pytz/zoneinfo/Africa/Lusaka create mode 100644 lib/pytz/zoneinfo/Africa/Malabo create mode 100644 lib/pytz/zoneinfo/Africa/Maputo create mode 100644 lib/pytz/zoneinfo/Africa/Maseru create mode 100644 lib/pytz/zoneinfo/Africa/Mbabane create mode 100644 lib/pytz/zoneinfo/Africa/Mogadishu create mode 100644 lib/pytz/zoneinfo/Africa/Monrovia create mode 100644 lib/pytz/zoneinfo/Africa/Nairobi create mode 100644 lib/pytz/zoneinfo/Africa/Ndjamena create mode 100644 lib/pytz/zoneinfo/Africa/Niamey create mode 100644 lib/pytz/zoneinfo/Africa/Nouakchott create mode 100644 lib/pytz/zoneinfo/Africa/Ouagadougou create mode 100644 lib/pytz/zoneinfo/Africa/Porto-Novo create mode 100644 lib/pytz/zoneinfo/Africa/Sao_Tome create mode 100644 lib/pytz/zoneinfo/Africa/Timbuktu create mode 100644 lib/pytz/zoneinfo/Africa/Tripoli create mode 100644 lib/pytz/zoneinfo/Africa/Tunis create mode 100644 lib/pytz/zoneinfo/Africa/Windhoek create mode 100644 lib/pytz/zoneinfo/America/Adak create mode 100644 lib/pytz/zoneinfo/America/Anchorage create mode 100644 lib/pytz/zoneinfo/America/Anguilla create mode 100644 lib/pytz/zoneinfo/America/Antigua create mode 100644 lib/pytz/zoneinfo/America/Araguaina create mode 100644 lib/pytz/zoneinfo/America/Argentina/Buenos_Aires create mode 100644 lib/pytz/zoneinfo/America/Argentina/Catamarca create mode 100644 lib/pytz/zoneinfo/America/Argentina/ComodRivadavia create mode 100644 lib/pytz/zoneinfo/America/Argentina/Cordoba create mode 100644 lib/pytz/zoneinfo/America/Argentina/Jujuy create mode 100644 lib/pytz/zoneinfo/America/Argentina/La_Rioja create mode 100644 lib/pytz/zoneinfo/America/Argentina/Mendoza create mode 100644 lib/pytz/zoneinfo/America/Argentina/Rio_Gallegos create mode 100644 lib/pytz/zoneinfo/America/Argentina/Salta create mode 100644 lib/pytz/zoneinfo/America/Argentina/San_Juan create mode 100644 lib/pytz/zoneinfo/America/Argentina/San_Luis create mode 100644 lib/pytz/zoneinfo/America/Argentina/Tucuman create mode 100644 lib/pytz/zoneinfo/America/Argentina/Ushuaia create mode 100644 lib/pytz/zoneinfo/America/Aruba create mode 100644 lib/pytz/zoneinfo/America/Asuncion create mode 100644 lib/pytz/zoneinfo/America/Atikokan create mode 100644 lib/pytz/zoneinfo/America/Atka create mode 100644 lib/pytz/zoneinfo/America/Bahia create mode 100644 lib/pytz/zoneinfo/America/Bahia_Banderas create mode 100644 lib/pytz/zoneinfo/America/Barbados create mode 100644 lib/pytz/zoneinfo/America/Belem create mode 100644 lib/pytz/zoneinfo/America/Belize create mode 100644 lib/pytz/zoneinfo/America/Blanc-Sablon create mode 100644 lib/pytz/zoneinfo/America/Boa_Vista create mode 100644 lib/pytz/zoneinfo/America/Bogota create mode 100644 lib/pytz/zoneinfo/America/Boise create mode 100644 lib/pytz/zoneinfo/America/Buenos_Aires create mode 100644 lib/pytz/zoneinfo/America/Cambridge_Bay create mode 100644 lib/pytz/zoneinfo/America/Campo_Grande create mode 100644 lib/pytz/zoneinfo/America/Cancun create mode 100644 lib/pytz/zoneinfo/America/Caracas create mode 100644 lib/pytz/zoneinfo/America/Catamarca create mode 100644 lib/pytz/zoneinfo/America/Cayenne create mode 100644 lib/pytz/zoneinfo/America/Cayman create mode 100644 lib/pytz/zoneinfo/America/Chicago create mode 100644 lib/pytz/zoneinfo/America/Chihuahua create mode 100644 lib/pytz/zoneinfo/America/Coral_Harbour create mode 100644 lib/pytz/zoneinfo/America/Cordoba create mode 100644 lib/pytz/zoneinfo/America/Costa_Rica create mode 100644 lib/pytz/zoneinfo/America/Creston create mode 100644 lib/pytz/zoneinfo/America/Cuiaba create mode 100644 lib/pytz/zoneinfo/America/Curacao create mode 100644 lib/pytz/zoneinfo/America/Danmarkshavn create mode 100644 lib/pytz/zoneinfo/America/Dawson create mode 100644 lib/pytz/zoneinfo/America/Dawson_Creek create mode 100644 lib/pytz/zoneinfo/America/Denver create mode 100644 lib/pytz/zoneinfo/America/Detroit create mode 100644 lib/pytz/zoneinfo/America/Dominica create mode 100644 lib/pytz/zoneinfo/America/Edmonton create mode 100644 lib/pytz/zoneinfo/America/Eirunepe create mode 100644 lib/pytz/zoneinfo/America/El_Salvador create mode 100644 lib/pytz/zoneinfo/America/Ensenada create mode 100644 lib/pytz/zoneinfo/America/Fort_Nelson create mode 100644 lib/pytz/zoneinfo/America/Fort_Wayne create mode 100644 lib/pytz/zoneinfo/America/Fortaleza create mode 100644 lib/pytz/zoneinfo/America/Glace_Bay create mode 100644 lib/pytz/zoneinfo/America/Godthab create mode 100644 lib/pytz/zoneinfo/America/Goose_Bay create mode 100644 lib/pytz/zoneinfo/America/Grand_Turk create mode 100644 lib/pytz/zoneinfo/America/Grenada create mode 100644 lib/pytz/zoneinfo/America/Guadeloupe create mode 100644 lib/pytz/zoneinfo/America/Guatemala create mode 100644 lib/pytz/zoneinfo/America/Guayaquil create mode 100644 lib/pytz/zoneinfo/America/Guyana create mode 100644 lib/pytz/zoneinfo/America/Halifax create mode 100644 lib/pytz/zoneinfo/America/Havana create mode 100644 lib/pytz/zoneinfo/America/Hermosillo create mode 100644 lib/pytz/zoneinfo/America/Indiana/Indianapolis create mode 100644 lib/pytz/zoneinfo/America/Indiana/Knox create mode 100644 lib/pytz/zoneinfo/America/Indiana/Marengo create mode 100644 lib/pytz/zoneinfo/America/Indiana/Petersburg create mode 100644 lib/pytz/zoneinfo/America/Indiana/Tell_City create mode 100644 lib/pytz/zoneinfo/America/Indiana/Vevay create mode 100644 lib/pytz/zoneinfo/America/Indiana/Vincennes create mode 100644 lib/pytz/zoneinfo/America/Indiana/Winamac create mode 100644 lib/pytz/zoneinfo/America/Indianapolis create mode 100644 lib/pytz/zoneinfo/America/Inuvik create mode 100644 lib/pytz/zoneinfo/America/Iqaluit create mode 100644 lib/pytz/zoneinfo/America/Jamaica create mode 100644 lib/pytz/zoneinfo/America/Jujuy create mode 100644 lib/pytz/zoneinfo/America/Juneau create mode 100644 lib/pytz/zoneinfo/America/Kentucky/Louisville create mode 100644 lib/pytz/zoneinfo/America/Kentucky/Monticello create mode 100644 lib/pytz/zoneinfo/America/Knox_IN create mode 100644 lib/pytz/zoneinfo/America/Kralendijk create mode 100644 lib/pytz/zoneinfo/America/La_Paz create mode 100644 lib/pytz/zoneinfo/America/Lima create mode 100644 lib/pytz/zoneinfo/America/Los_Angeles create mode 100644 lib/pytz/zoneinfo/America/Louisville create mode 100644 lib/pytz/zoneinfo/America/Lower_Princes create mode 100644 lib/pytz/zoneinfo/America/Maceio create mode 100644 lib/pytz/zoneinfo/America/Managua create mode 100644 lib/pytz/zoneinfo/America/Manaus create mode 100644 lib/pytz/zoneinfo/America/Marigot create mode 100644 lib/pytz/zoneinfo/America/Martinique create mode 100644 lib/pytz/zoneinfo/America/Matamoros create mode 100644 lib/pytz/zoneinfo/America/Mazatlan create mode 100644 lib/pytz/zoneinfo/America/Mendoza create mode 100644 lib/pytz/zoneinfo/America/Menominee create mode 100644 lib/pytz/zoneinfo/America/Merida create mode 100644 lib/pytz/zoneinfo/America/Metlakatla create mode 100644 lib/pytz/zoneinfo/America/Mexico_City create mode 100644 lib/pytz/zoneinfo/America/Miquelon create mode 100644 lib/pytz/zoneinfo/America/Moncton create mode 100644 lib/pytz/zoneinfo/America/Monterrey create mode 100644 lib/pytz/zoneinfo/America/Montevideo create mode 100644 lib/pytz/zoneinfo/America/Montreal create mode 100644 lib/pytz/zoneinfo/America/Montserrat create mode 100644 lib/pytz/zoneinfo/America/Nassau create mode 100644 lib/pytz/zoneinfo/America/New_York create mode 100644 lib/pytz/zoneinfo/America/Nipigon create mode 100644 lib/pytz/zoneinfo/America/Nome create mode 100644 lib/pytz/zoneinfo/America/Noronha create mode 100644 lib/pytz/zoneinfo/America/North_Dakota/Beulah create mode 100644 lib/pytz/zoneinfo/America/North_Dakota/Center create mode 100644 lib/pytz/zoneinfo/America/North_Dakota/New_Salem create mode 100644 lib/pytz/zoneinfo/America/Ojinaga create mode 100644 lib/pytz/zoneinfo/America/Panama create mode 100644 lib/pytz/zoneinfo/America/Pangnirtung create mode 100644 lib/pytz/zoneinfo/America/Paramaribo create mode 100644 lib/pytz/zoneinfo/America/Phoenix create mode 100644 lib/pytz/zoneinfo/America/Port-au-Prince create mode 100644 lib/pytz/zoneinfo/America/Port_of_Spain create mode 100644 lib/pytz/zoneinfo/America/Porto_Acre create mode 100644 lib/pytz/zoneinfo/America/Porto_Velho create mode 100644 lib/pytz/zoneinfo/America/Puerto_Rico create mode 100644 lib/pytz/zoneinfo/America/Punta_Arenas create mode 100644 lib/pytz/zoneinfo/America/Rainy_River create mode 100644 lib/pytz/zoneinfo/America/Rankin_Inlet create mode 100644 lib/pytz/zoneinfo/America/Recife create mode 100644 lib/pytz/zoneinfo/America/Regina create mode 100644 lib/pytz/zoneinfo/America/Resolute create mode 100644 lib/pytz/zoneinfo/America/Rio_Branco create mode 100644 lib/pytz/zoneinfo/America/Rosario create mode 100644 lib/pytz/zoneinfo/America/Santa_Isabel create mode 100644 lib/pytz/zoneinfo/America/Santarem create mode 100644 lib/pytz/zoneinfo/America/Santiago create mode 100644 lib/pytz/zoneinfo/America/Santo_Domingo create mode 100644 lib/pytz/zoneinfo/America/Sao_Paulo create mode 100644 lib/pytz/zoneinfo/America/Scoresbysund create mode 100644 lib/pytz/zoneinfo/America/Shiprock create mode 100644 lib/pytz/zoneinfo/America/Sitka create mode 100644 lib/pytz/zoneinfo/America/St_Barthelemy create mode 100644 lib/pytz/zoneinfo/America/St_Johns create mode 100644 lib/pytz/zoneinfo/America/St_Kitts create mode 100644 lib/pytz/zoneinfo/America/St_Lucia create mode 100644 lib/pytz/zoneinfo/America/St_Thomas create mode 100644 lib/pytz/zoneinfo/America/St_Vincent create mode 100644 lib/pytz/zoneinfo/America/Swift_Current create mode 100644 lib/pytz/zoneinfo/America/Tegucigalpa create mode 100644 lib/pytz/zoneinfo/America/Thule create mode 100644 lib/pytz/zoneinfo/America/Thunder_Bay create mode 100644 lib/pytz/zoneinfo/America/Tijuana create mode 100644 lib/pytz/zoneinfo/America/Toronto create mode 100644 lib/pytz/zoneinfo/America/Tortola create mode 100644 lib/pytz/zoneinfo/America/Vancouver create mode 100644 lib/pytz/zoneinfo/America/Virgin create mode 100644 lib/pytz/zoneinfo/America/Whitehorse create mode 100644 lib/pytz/zoneinfo/America/Winnipeg create mode 100644 lib/pytz/zoneinfo/America/Yakutat create mode 100644 lib/pytz/zoneinfo/America/Yellowknife create mode 100644 lib/pytz/zoneinfo/Antarctica/Casey create mode 100644 lib/pytz/zoneinfo/Antarctica/Davis create mode 100644 lib/pytz/zoneinfo/Antarctica/DumontDUrville create mode 100644 lib/pytz/zoneinfo/Antarctica/Macquarie create mode 100644 lib/pytz/zoneinfo/Antarctica/Mawson create mode 100644 lib/pytz/zoneinfo/Antarctica/McMurdo create mode 100644 lib/pytz/zoneinfo/Antarctica/Palmer create mode 100644 lib/pytz/zoneinfo/Antarctica/Rothera create mode 100644 lib/pytz/zoneinfo/Antarctica/South_Pole create mode 100644 lib/pytz/zoneinfo/Antarctica/Syowa create mode 100644 lib/pytz/zoneinfo/Antarctica/Troll create mode 100644 lib/pytz/zoneinfo/Antarctica/Vostok create mode 100644 lib/pytz/zoneinfo/Arctic/Longyearbyen create mode 100644 lib/pytz/zoneinfo/Asia/Aden create mode 100644 lib/pytz/zoneinfo/Asia/Almaty create mode 100644 lib/pytz/zoneinfo/Asia/Amman create mode 100644 lib/pytz/zoneinfo/Asia/Anadyr create mode 100644 lib/pytz/zoneinfo/Asia/Aqtau create mode 100644 lib/pytz/zoneinfo/Asia/Aqtobe create mode 100644 lib/pytz/zoneinfo/Asia/Ashgabat create mode 100644 lib/pytz/zoneinfo/Asia/Ashkhabad create mode 100644 lib/pytz/zoneinfo/Asia/Atyrau create mode 100644 lib/pytz/zoneinfo/Asia/Baghdad create mode 100644 lib/pytz/zoneinfo/Asia/Bahrain create mode 100644 lib/pytz/zoneinfo/Asia/Baku create mode 100644 lib/pytz/zoneinfo/Asia/Bangkok create mode 100644 lib/pytz/zoneinfo/Asia/Barnaul create mode 100644 lib/pytz/zoneinfo/Asia/Beirut create mode 100644 lib/pytz/zoneinfo/Asia/Bishkek create mode 100644 lib/pytz/zoneinfo/Asia/Brunei create mode 100644 lib/pytz/zoneinfo/Asia/Calcutta create mode 100644 lib/pytz/zoneinfo/Asia/Chita create mode 100644 lib/pytz/zoneinfo/Asia/Choibalsan create mode 100644 lib/pytz/zoneinfo/Asia/Chongqing create mode 100644 lib/pytz/zoneinfo/Asia/Chungking create mode 100644 lib/pytz/zoneinfo/Asia/Colombo create mode 100644 lib/pytz/zoneinfo/Asia/Dacca create mode 100644 lib/pytz/zoneinfo/Asia/Damascus create mode 100644 lib/pytz/zoneinfo/Asia/Dhaka create mode 100644 lib/pytz/zoneinfo/Asia/Dili create mode 100644 lib/pytz/zoneinfo/Asia/Dubai create mode 100644 lib/pytz/zoneinfo/Asia/Dushanbe create mode 100644 lib/pytz/zoneinfo/Asia/Famagusta create mode 100644 lib/pytz/zoneinfo/Asia/Gaza create mode 100644 lib/pytz/zoneinfo/Asia/Harbin create mode 100644 lib/pytz/zoneinfo/Asia/Hebron create mode 100644 lib/pytz/zoneinfo/Asia/Ho_Chi_Minh create mode 100644 lib/pytz/zoneinfo/Asia/Hong_Kong create mode 100644 lib/pytz/zoneinfo/Asia/Hovd create mode 100644 lib/pytz/zoneinfo/Asia/Irkutsk create mode 100644 lib/pytz/zoneinfo/Asia/Istanbul create mode 100644 lib/pytz/zoneinfo/Asia/Jakarta create mode 100644 lib/pytz/zoneinfo/Asia/Jayapura create mode 100644 lib/pytz/zoneinfo/Asia/Jerusalem create mode 100644 lib/pytz/zoneinfo/Asia/Kabul create mode 100644 lib/pytz/zoneinfo/Asia/Kamchatka create mode 100644 lib/pytz/zoneinfo/Asia/Karachi create mode 100644 lib/pytz/zoneinfo/Asia/Kashgar create mode 100644 lib/pytz/zoneinfo/Asia/Kathmandu create mode 100644 lib/pytz/zoneinfo/Asia/Katmandu create mode 100644 lib/pytz/zoneinfo/Asia/Khandyga create mode 100644 lib/pytz/zoneinfo/Asia/Kolkata create mode 100644 lib/pytz/zoneinfo/Asia/Krasnoyarsk create mode 100644 lib/pytz/zoneinfo/Asia/Kuala_Lumpur create mode 100644 lib/pytz/zoneinfo/Asia/Kuching create mode 100644 lib/pytz/zoneinfo/Asia/Kuwait create mode 100644 lib/pytz/zoneinfo/Asia/Macao create mode 100644 lib/pytz/zoneinfo/Asia/Macau create mode 100644 lib/pytz/zoneinfo/Asia/Magadan create mode 100644 lib/pytz/zoneinfo/Asia/Makassar create mode 100644 lib/pytz/zoneinfo/Asia/Manila create mode 100644 lib/pytz/zoneinfo/Asia/Muscat create mode 100644 lib/pytz/zoneinfo/Asia/Nicosia create mode 100644 lib/pytz/zoneinfo/Asia/Novokuznetsk create mode 100644 lib/pytz/zoneinfo/Asia/Novosibirsk create mode 100644 lib/pytz/zoneinfo/Asia/Omsk create mode 100644 lib/pytz/zoneinfo/Asia/Oral create mode 100644 lib/pytz/zoneinfo/Asia/Phnom_Penh create mode 100644 lib/pytz/zoneinfo/Asia/Pontianak create mode 100644 lib/pytz/zoneinfo/Asia/Pyongyang create mode 100644 lib/pytz/zoneinfo/Asia/Qatar create mode 100644 lib/pytz/zoneinfo/Asia/Qyzylorda create mode 100644 lib/pytz/zoneinfo/Asia/Rangoon create mode 100644 lib/pytz/zoneinfo/Asia/Riyadh create mode 100644 lib/pytz/zoneinfo/Asia/Saigon create mode 100644 lib/pytz/zoneinfo/Asia/Sakhalin create mode 100644 lib/pytz/zoneinfo/Asia/Samarkand create mode 100644 lib/pytz/zoneinfo/Asia/Seoul create mode 100644 lib/pytz/zoneinfo/Asia/Shanghai create mode 100644 lib/pytz/zoneinfo/Asia/Singapore create mode 100644 lib/pytz/zoneinfo/Asia/Srednekolymsk create mode 100644 lib/pytz/zoneinfo/Asia/Taipei create mode 100644 lib/pytz/zoneinfo/Asia/Tashkent create mode 100644 lib/pytz/zoneinfo/Asia/Tbilisi create mode 100644 lib/pytz/zoneinfo/Asia/Tehran create mode 100644 lib/pytz/zoneinfo/Asia/Tel_Aviv create mode 100644 lib/pytz/zoneinfo/Asia/Thimbu create mode 100644 lib/pytz/zoneinfo/Asia/Thimphu create mode 100644 lib/pytz/zoneinfo/Asia/Tokyo create mode 100644 lib/pytz/zoneinfo/Asia/Tomsk create mode 100644 lib/pytz/zoneinfo/Asia/Ujung_Pandang create mode 100644 lib/pytz/zoneinfo/Asia/Ulaanbaatar create mode 100644 lib/pytz/zoneinfo/Asia/Ulan_Bator create mode 100644 lib/pytz/zoneinfo/Asia/Urumqi create mode 100644 lib/pytz/zoneinfo/Asia/Ust-Nera create mode 100644 lib/pytz/zoneinfo/Asia/Vientiane create mode 100644 lib/pytz/zoneinfo/Asia/Vladivostok create mode 100644 lib/pytz/zoneinfo/Asia/Yakutsk create mode 100644 lib/pytz/zoneinfo/Asia/Yangon create mode 100644 lib/pytz/zoneinfo/Asia/Yekaterinburg create mode 100644 lib/pytz/zoneinfo/Asia/Yerevan create mode 100644 lib/pytz/zoneinfo/Atlantic/Azores create mode 100644 lib/pytz/zoneinfo/Atlantic/Bermuda create mode 100644 lib/pytz/zoneinfo/Atlantic/Canary create mode 100644 lib/pytz/zoneinfo/Atlantic/Cape_Verde create mode 100644 lib/pytz/zoneinfo/Atlantic/Faeroe create mode 100644 lib/pytz/zoneinfo/Atlantic/Faroe create mode 100644 lib/pytz/zoneinfo/Atlantic/Jan_Mayen create mode 100644 lib/pytz/zoneinfo/Atlantic/Madeira create mode 100644 lib/pytz/zoneinfo/Atlantic/Reykjavik create mode 100644 lib/pytz/zoneinfo/Atlantic/South_Georgia create mode 100644 lib/pytz/zoneinfo/Atlantic/St_Helena create mode 100644 lib/pytz/zoneinfo/Atlantic/Stanley create mode 100644 lib/pytz/zoneinfo/Australia/ACT create mode 100644 lib/pytz/zoneinfo/Australia/Adelaide create mode 100644 lib/pytz/zoneinfo/Australia/Brisbane create mode 100644 lib/pytz/zoneinfo/Australia/Broken_Hill create mode 100644 lib/pytz/zoneinfo/Australia/Canberra create mode 100644 lib/pytz/zoneinfo/Australia/Currie create mode 100644 lib/pytz/zoneinfo/Australia/Darwin create mode 100644 lib/pytz/zoneinfo/Australia/Eucla create mode 100644 lib/pytz/zoneinfo/Australia/Hobart create mode 100644 lib/pytz/zoneinfo/Australia/LHI create mode 100644 lib/pytz/zoneinfo/Australia/Lindeman create mode 100644 lib/pytz/zoneinfo/Australia/Lord_Howe create mode 100644 lib/pytz/zoneinfo/Australia/Melbourne create mode 100644 lib/pytz/zoneinfo/Australia/NSW create mode 100644 lib/pytz/zoneinfo/Australia/North create mode 100644 lib/pytz/zoneinfo/Australia/Perth create mode 100644 lib/pytz/zoneinfo/Australia/Queensland create mode 100644 lib/pytz/zoneinfo/Australia/South create mode 100644 lib/pytz/zoneinfo/Australia/Sydney create mode 100644 lib/pytz/zoneinfo/Australia/Tasmania create mode 100644 lib/pytz/zoneinfo/Australia/Victoria create mode 100644 lib/pytz/zoneinfo/Australia/West create mode 100644 lib/pytz/zoneinfo/Australia/Yancowinna create mode 100644 lib/pytz/zoneinfo/Brazil/Acre create mode 100644 lib/pytz/zoneinfo/Brazil/DeNoronha create mode 100644 lib/pytz/zoneinfo/Brazil/East create mode 100644 lib/pytz/zoneinfo/Brazil/West create mode 100644 lib/pytz/zoneinfo/CET create mode 100644 lib/pytz/zoneinfo/CST6CDT create mode 100644 lib/pytz/zoneinfo/Canada/Atlantic create mode 100644 lib/pytz/zoneinfo/Canada/Central create mode 100644 lib/pytz/zoneinfo/Canada/Eastern create mode 100644 lib/pytz/zoneinfo/Canada/Mountain create mode 100644 lib/pytz/zoneinfo/Canada/Newfoundland create mode 100644 lib/pytz/zoneinfo/Canada/Pacific create mode 100644 lib/pytz/zoneinfo/Canada/Saskatchewan create mode 100644 lib/pytz/zoneinfo/Canada/Yukon create mode 100644 lib/pytz/zoneinfo/Chile/Continental create mode 100644 lib/pytz/zoneinfo/Chile/EasterIsland create mode 100644 lib/pytz/zoneinfo/Cuba create mode 100644 lib/pytz/zoneinfo/EET create mode 100644 lib/pytz/zoneinfo/EST create mode 100644 lib/pytz/zoneinfo/EST5EDT create mode 100644 lib/pytz/zoneinfo/Egypt create mode 100644 lib/pytz/zoneinfo/Eire create mode 100644 lib/pytz/zoneinfo/Etc/GMT create mode 100644 lib/pytz/zoneinfo/Etc/GMT+0 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+1 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+10 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+11 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+12 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+2 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+3 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+4 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+5 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+6 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+7 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+8 create mode 100644 lib/pytz/zoneinfo/Etc/GMT+9 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-0 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-1 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-10 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-11 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-12 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-13 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-14 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-2 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-3 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-4 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-5 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-6 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-7 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-8 create mode 100644 lib/pytz/zoneinfo/Etc/GMT-9 create mode 100644 lib/pytz/zoneinfo/Etc/GMT0 create mode 100644 lib/pytz/zoneinfo/Etc/Greenwich create mode 100644 lib/pytz/zoneinfo/Etc/UCT create mode 100644 lib/pytz/zoneinfo/Etc/UTC create mode 100644 lib/pytz/zoneinfo/Etc/Universal create mode 100644 lib/pytz/zoneinfo/Etc/Zulu create mode 100644 lib/pytz/zoneinfo/Europe/Amsterdam create mode 100644 lib/pytz/zoneinfo/Europe/Andorra create mode 100644 lib/pytz/zoneinfo/Europe/Astrakhan create mode 100644 lib/pytz/zoneinfo/Europe/Athens create mode 100644 lib/pytz/zoneinfo/Europe/Belfast create mode 100644 lib/pytz/zoneinfo/Europe/Belgrade create mode 100644 lib/pytz/zoneinfo/Europe/Berlin create mode 100644 lib/pytz/zoneinfo/Europe/Bratislava create mode 100644 lib/pytz/zoneinfo/Europe/Brussels create mode 100644 lib/pytz/zoneinfo/Europe/Bucharest create mode 100644 lib/pytz/zoneinfo/Europe/Budapest create mode 100644 lib/pytz/zoneinfo/Europe/Busingen create mode 100644 lib/pytz/zoneinfo/Europe/Chisinau create mode 100644 lib/pytz/zoneinfo/Europe/Copenhagen create mode 100644 lib/pytz/zoneinfo/Europe/Dublin create mode 100644 lib/pytz/zoneinfo/Europe/Gibraltar create mode 100644 lib/pytz/zoneinfo/Europe/Guernsey create mode 100644 lib/pytz/zoneinfo/Europe/Helsinki create mode 100644 lib/pytz/zoneinfo/Europe/Isle_of_Man create mode 100644 lib/pytz/zoneinfo/Europe/Istanbul create mode 100644 lib/pytz/zoneinfo/Europe/Jersey create mode 100644 lib/pytz/zoneinfo/Europe/Kaliningrad create mode 100644 lib/pytz/zoneinfo/Europe/Kiev create mode 100644 lib/pytz/zoneinfo/Europe/Kirov create mode 100644 lib/pytz/zoneinfo/Europe/Lisbon create mode 100644 lib/pytz/zoneinfo/Europe/Ljubljana create mode 100644 lib/pytz/zoneinfo/Europe/London create mode 100644 lib/pytz/zoneinfo/Europe/Luxembourg create mode 100644 lib/pytz/zoneinfo/Europe/Madrid create mode 100644 lib/pytz/zoneinfo/Europe/Malta create mode 100644 lib/pytz/zoneinfo/Europe/Mariehamn create mode 100644 lib/pytz/zoneinfo/Europe/Minsk create mode 100644 lib/pytz/zoneinfo/Europe/Monaco create mode 100644 lib/pytz/zoneinfo/Europe/Moscow create mode 100644 lib/pytz/zoneinfo/Europe/Nicosia create mode 100644 lib/pytz/zoneinfo/Europe/Oslo create mode 100644 lib/pytz/zoneinfo/Europe/Paris create mode 100644 lib/pytz/zoneinfo/Europe/Podgorica create mode 100644 lib/pytz/zoneinfo/Europe/Prague create mode 100644 lib/pytz/zoneinfo/Europe/Riga create mode 100644 lib/pytz/zoneinfo/Europe/Rome create mode 100644 lib/pytz/zoneinfo/Europe/Samara create mode 100644 lib/pytz/zoneinfo/Europe/San_Marino create mode 100644 lib/pytz/zoneinfo/Europe/Sarajevo create mode 100644 lib/pytz/zoneinfo/Europe/Saratov create mode 100644 lib/pytz/zoneinfo/Europe/Simferopol create mode 100644 lib/pytz/zoneinfo/Europe/Skopje create mode 100644 lib/pytz/zoneinfo/Europe/Sofia create mode 100644 lib/pytz/zoneinfo/Europe/Stockholm create mode 100644 lib/pytz/zoneinfo/Europe/Tallinn create mode 100644 lib/pytz/zoneinfo/Europe/Tirane create mode 100644 lib/pytz/zoneinfo/Europe/Tiraspol create mode 100644 lib/pytz/zoneinfo/Europe/Ulyanovsk create mode 100644 lib/pytz/zoneinfo/Europe/Uzhgorod create mode 100644 lib/pytz/zoneinfo/Europe/Vaduz create mode 100644 lib/pytz/zoneinfo/Europe/Vatican create mode 100644 lib/pytz/zoneinfo/Europe/Vienna create mode 100644 lib/pytz/zoneinfo/Europe/Vilnius create mode 100644 lib/pytz/zoneinfo/Europe/Volgograd create mode 100644 lib/pytz/zoneinfo/Europe/Warsaw create mode 100644 lib/pytz/zoneinfo/Europe/Zagreb create mode 100644 lib/pytz/zoneinfo/Europe/Zaporozhye create mode 100644 lib/pytz/zoneinfo/Europe/Zurich create mode 100644 lib/pytz/zoneinfo/Factory create mode 100644 lib/pytz/zoneinfo/GB create mode 100644 lib/pytz/zoneinfo/GB-Eire create mode 100644 lib/pytz/zoneinfo/GMT create mode 100644 lib/pytz/zoneinfo/GMT+0 create mode 100644 lib/pytz/zoneinfo/GMT-0 create mode 100644 lib/pytz/zoneinfo/GMT0 create mode 100644 lib/pytz/zoneinfo/Greenwich create mode 100644 lib/pytz/zoneinfo/HST create mode 100644 lib/pytz/zoneinfo/Hongkong create mode 100644 lib/pytz/zoneinfo/Iceland create mode 100644 lib/pytz/zoneinfo/Indian/Antananarivo create mode 100644 lib/pytz/zoneinfo/Indian/Chagos create mode 100644 lib/pytz/zoneinfo/Indian/Christmas create mode 100644 lib/pytz/zoneinfo/Indian/Cocos create mode 100644 lib/pytz/zoneinfo/Indian/Comoro create mode 100644 lib/pytz/zoneinfo/Indian/Kerguelen create mode 100644 lib/pytz/zoneinfo/Indian/Mahe create mode 100644 lib/pytz/zoneinfo/Indian/Maldives create mode 100644 lib/pytz/zoneinfo/Indian/Mauritius create mode 100644 lib/pytz/zoneinfo/Indian/Mayotte create mode 100644 lib/pytz/zoneinfo/Indian/Reunion create mode 100644 lib/pytz/zoneinfo/Iran create mode 100644 lib/pytz/zoneinfo/Israel create mode 100644 lib/pytz/zoneinfo/Jamaica create mode 100644 lib/pytz/zoneinfo/Japan create mode 100644 lib/pytz/zoneinfo/Kwajalein create mode 100644 lib/pytz/zoneinfo/Libya create mode 100644 lib/pytz/zoneinfo/MET create mode 100644 lib/pytz/zoneinfo/MST create mode 100644 lib/pytz/zoneinfo/MST7MDT create mode 100644 lib/pytz/zoneinfo/Mexico/BajaNorte create mode 100644 lib/pytz/zoneinfo/Mexico/BajaSur create mode 100644 lib/pytz/zoneinfo/Mexico/General create mode 100644 lib/pytz/zoneinfo/NZ create mode 100644 lib/pytz/zoneinfo/NZ-CHAT create mode 100644 lib/pytz/zoneinfo/Navajo create mode 100644 lib/pytz/zoneinfo/PRC create mode 100644 lib/pytz/zoneinfo/PST8PDT create mode 100644 lib/pytz/zoneinfo/Pacific/Apia create mode 100644 lib/pytz/zoneinfo/Pacific/Auckland create mode 100644 lib/pytz/zoneinfo/Pacific/Bougainville create mode 100644 lib/pytz/zoneinfo/Pacific/Chatham create mode 100644 lib/pytz/zoneinfo/Pacific/Chuuk create mode 100644 lib/pytz/zoneinfo/Pacific/Easter create mode 100644 lib/pytz/zoneinfo/Pacific/Efate create mode 100644 lib/pytz/zoneinfo/Pacific/Enderbury create mode 100644 lib/pytz/zoneinfo/Pacific/Fakaofo create mode 100644 lib/pytz/zoneinfo/Pacific/Fiji create mode 100644 lib/pytz/zoneinfo/Pacific/Funafuti create mode 100644 lib/pytz/zoneinfo/Pacific/Galapagos create mode 100644 lib/pytz/zoneinfo/Pacific/Gambier create mode 100644 lib/pytz/zoneinfo/Pacific/Guadalcanal create mode 100644 lib/pytz/zoneinfo/Pacific/Guam create mode 100644 lib/pytz/zoneinfo/Pacific/Honolulu create mode 100644 lib/pytz/zoneinfo/Pacific/Johnston create mode 100644 lib/pytz/zoneinfo/Pacific/Kiritimati create mode 100644 lib/pytz/zoneinfo/Pacific/Kosrae create mode 100644 lib/pytz/zoneinfo/Pacific/Kwajalein create mode 100644 lib/pytz/zoneinfo/Pacific/Majuro create mode 100644 lib/pytz/zoneinfo/Pacific/Marquesas create mode 100644 lib/pytz/zoneinfo/Pacific/Midway create mode 100644 lib/pytz/zoneinfo/Pacific/Nauru create mode 100644 lib/pytz/zoneinfo/Pacific/Niue create mode 100644 lib/pytz/zoneinfo/Pacific/Norfolk create mode 100644 lib/pytz/zoneinfo/Pacific/Noumea create mode 100644 lib/pytz/zoneinfo/Pacific/Pago_Pago create mode 100644 lib/pytz/zoneinfo/Pacific/Palau create mode 100644 lib/pytz/zoneinfo/Pacific/Pitcairn create mode 100644 lib/pytz/zoneinfo/Pacific/Pohnpei create mode 100644 lib/pytz/zoneinfo/Pacific/Ponape create mode 100644 lib/pytz/zoneinfo/Pacific/Port_Moresby create mode 100644 lib/pytz/zoneinfo/Pacific/Rarotonga create mode 100644 lib/pytz/zoneinfo/Pacific/Saipan create mode 100644 lib/pytz/zoneinfo/Pacific/Samoa create mode 100644 lib/pytz/zoneinfo/Pacific/Tahiti create mode 100644 lib/pytz/zoneinfo/Pacific/Tarawa create mode 100644 lib/pytz/zoneinfo/Pacific/Tongatapu create mode 100644 lib/pytz/zoneinfo/Pacific/Truk create mode 100644 lib/pytz/zoneinfo/Pacific/Wake create mode 100644 lib/pytz/zoneinfo/Pacific/Wallis create mode 100644 lib/pytz/zoneinfo/Pacific/Yap create mode 100644 lib/pytz/zoneinfo/Poland create mode 100644 lib/pytz/zoneinfo/Portugal create mode 100644 lib/pytz/zoneinfo/ROC create mode 100644 lib/pytz/zoneinfo/ROK create mode 100644 lib/pytz/zoneinfo/Singapore create mode 100644 lib/pytz/zoneinfo/Turkey create mode 100644 lib/pytz/zoneinfo/UCT create mode 100644 lib/pytz/zoneinfo/US/Alaska create mode 100644 lib/pytz/zoneinfo/US/Aleutian create mode 100644 lib/pytz/zoneinfo/US/Arizona create mode 100644 lib/pytz/zoneinfo/US/Central create mode 100644 lib/pytz/zoneinfo/US/East-Indiana create mode 100644 lib/pytz/zoneinfo/US/Eastern create mode 100644 lib/pytz/zoneinfo/US/Hawaii create mode 100644 lib/pytz/zoneinfo/US/Indiana-Starke create mode 100644 lib/pytz/zoneinfo/US/Michigan create mode 100644 lib/pytz/zoneinfo/US/Mountain create mode 100644 lib/pytz/zoneinfo/US/Pacific create mode 100644 lib/pytz/zoneinfo/US/Samoa create mode 100644 lib/pytz/zoneinfo/UTC create mode 100644 lib/pytz/zoneinfo/Universal create mode 100644 lib/pytz/zoneinfo/W-SU create mode 100644 lib/pytz/zoneinfo/WET create mode 100644 lib/pytz/zoneinfo/Zulu create mode 100644 lib/pytz/zoneinfo/iso3166.tab create mode 100644 lib/pytz/zoneinfo/leapseconds create mode 100644 lib/pytz/zoneinfo/posixrules create mode 100644 lib/pytz/zoneinfo/tzdata.zi create mode 100644 lib/pytz/zoneinfo/zone.tab create mode 100644 lib/pytz/zoneinfo/zone1970.tab create mode 100644 lib/requests/__init__.py create mode 100644 lib/requests/__version__.py create mode 100644 lib/requests/_internal_utils.py create mode 100644 lib/requests/adapters.py create mode 100644 lib/requests/api.py create mode 100644 lib/requests/auth.py create mode 100644 lib/requests/certs.py create mode 100644 lib/requests/compat.py create mode 100644 lib/requests/cookies.py create mode 100644 lib/requests/exceptions.py create mode 100644 lib/requests/help.py create mode 100644 lib/requests/hooks.py create mode 100644 lib/requests/models.py create mode 100644 lib/requests/packages.py create mode 100644 lib/requests/sessions.py create mode 100644 lib/requests/status_codes.py create mode 100644 lib/requests/structures.py create mode 100644 lib/requests/utils.py create mode 100644 lib/schedule/__init__.py create mode 100644 lib/setuptools/__init__.py create mode 100644 lib/setuptools/_deprecation_warning.py create mode 100644 lib/setuptools/_vendor/__init__.py create mode 100644 lib/setuptools/_vendor/packaging/__about__.py create mode 100644 lib/setuptools/_vendor/packaging/__init__.py create mode 100644 lib/setuptools/_vendor/packaging/_compat.py create mode 100644 lib/setuptools/_vendor/packaging/_structures.py create mode 100644 lib/setuptools/_vendor/packaging/markers.py create mode 100644 lib/setuptools/_vendor/packaging/requirements.py create mode 100644 lib/setuptools/_vendor/packaging/specifiers.py create mode 100644 lib/setuptools/_vendor/packaging/utils.py create mode 100644 lib/setuptools/_vendor/packaging/version.py create mode 100644 lib/setuptools/_vendor/pyparsing.py create mode 100644 lib/setuptools/_vendor/six.py create mode 100644 lib/setuptools/archive_util.py create mode 100644 lib/setuptools/build_meta.py create mode 100644 lib/setuptools/cli-32.exe create mode 100644 lib/setuptools/cli-64.exe create mode 100644 lib/setuptools/cli.exe create mode 100644 lib/setuptools/command/__init__.py create mode 100644 lib/setuptools/command/alias.py create mode 100644 lib/setuptools/command/bdist_egg.py create mode 100644 lib/setuptools/command/bdist_rpm.py create mode 100644 lib/setuptools/command/bdist_wininst.py create mode 100644 lib/setuptools/command/build_clib.py create mode 100644 lib/setuptools/command/build_ext.py create mode 100644 lib/setuptools/command/build_py.py create mode 100644 lib/setuptools/command/develop.py create mode 100644 lib/setuptools/command/dist_info.py create mode 100644 lib/setuptools/command/easy_install.py create mode 100644 lib/setuptools/command/egg_info.py create mode 100644 lib/setuptools/command/install.py create mode 100644 lib/setuptools/command/install_egg_info.py create mode 100644 lib/setuptools/command/install_lib.py create mode 100644 lib/setuptools/command/install_scripts.py create mode 100644 lib/setuptools/command/launcher manifest.xml create mode 100644 lib/setuptools/command/py36compat.py create mode 100644 lib/setuptools/command/register.py create mode 100644 lib/setuptools/command/rotate.py create mode 100644 lib/setuptools/command/saveopts.py create mode 100644 lib/setuptools/command/sdist.py create mode 100644 lib/setuptools/command/setopt.py create mode 100644 lib/setuptools/command/test.py create mode 100644 lib/setuptools/command/upload.py create mode 100644 lib/setuptools/command/upload_docs.py create mode 100644 lib/setuptools/config.py create mode 100644 lib/setuptools/dep_util.py create mode 100644 lib/setuptools/depends.py create mode 100644 lib/setuptools/dist.py create mode 100644 lib/setuptools/extension.py create mode 100644 lib/setuptools/extern/__init__.py create mode 100644 lib/setuptools/glibc.py create mode 100644 lib/setuptools/glob.py create mode 100644 lib/setuptools/gui-32.exe create mode 100644 lib/setuptools/gui-64.exe create mode 100644 lib/setuptools/gui.exe create mode 100644 lib/setuptools/launch.py create mode 100644 lib/setuptools/lib2to3_ex.py create mode 100644 lib/setuptools/monkey.py create mode 100644 lib/setuptools/msvc.py create mode 100644 lib/setuptools/namespaces.py create mode 100644 lib/setuptools/package_index.py create mode 100644 lib/setuptools/pep425tags.py create mode 100644 lib/setuptools/py27compat.py create mode 100644 lib/setuptools/py31compat.py create mode 100644 lib/setuptools/py33compat.py create mode 100644 lib/setuptools/py36compat.py create mode 100644 lib/setuptools/sandbox.py create mode 100644 lib/setuptools/script (dev).tmpl create mode 100644 lib/setuptools/script.tmpl create mode 100644 lib/setuptools/site-patch.py create mode 100644 lib/setuptools/ssl_support.py create mode 100644 lib/setuptools/unicode_utils.py create mode 100644 lib/setuptools/version.py create mode 100644 lib/setuptools/wheel.py create mode 100644 lib/setuptools/windows_support.py create mode 100644 lib/six.py create mode 100644 lib/typing.py create mode 100644 lib/urllib3/__init__.py create mode 100644 lib/urllib3/_collections.py create mode 100644 lib/urllib3/connection.py create mode 100644 lib/urllib3/connectionpool.py create mode 100644 lib/urllib3/contrib/__init__.py create mode 100644 lib/urllib3/contrib/_appengine_environ.py create mode 100644 lib/urllib3/contrib/_securetransport/__init__.py create mode 100644 lib/urllib3/contrib/_securetransport/bindings.py create mode 100644 lib/urllib3/contrib/_securetransport/low_level.py create mode 100644 lib/urllib3/contrib/appengine.py create mode 100644 lib/urllib3/contrib/ntlmpool.py create mode 100644 lib/urllib3/contrib/pyopenssl.py create mode 100644 lib/urllib3/contrib/securetransport.py create mode 100644 lib/urllib3/contrib/socks.py create mode 100644 lib/urllib3/exceptions.py create mode 100644 lib/urllib3/fields.py create mode 100644 lib/urllib3/filepost.py create mode 100644 lib/urllib3/packages/__init__.py create mode 100644 lib/urllib3/packages/backports/__init__.py create mode 100644 lib/urllib3/packages/backports/makefile.py create mode 100644 lib/urllib3/packages/six.py create mode 100644 lib/urllib3/packages/ssl_match_hostname/__init__.py create mode 100644 lib/urllib3/packages/ssl_match_hostname/_implementation.py create mode 100644 lib/urllib3/poolmanager.py create mode 100644 lib/urllib3/request.py create mode 100644 lib/urllib3/response.py create mode 100644 lib/urllib3/util/__init__.py create mode 100644 lib/urllib3/util/connection.py create mode 100644 lib/urllib3/util/queue.py create mode 100644 lib/urllib3/util/request.py create mode 100644 lib/urllib3/util/response.py create mode 100644 lib/urllib3/util/retry.py create mode 100644 lib/urllib3/util/ssl_.py create mode 100644 lib/urllib3/util/timeout.py create mode 100644 lib/urllib3/util/url.py create mode 100644 lib/urllib3/util/wait.py create mode 100644 lib/zope.interface-4.6.0-py3.7-nspkg.pth create mode 100644 lib/zope/interface/__init__.py create mode 100644 lib/zope/interface/_compat.py create mode 100644 lib/zope/interface/_flatten.py create mode 100644 lib/zope/interface/_zope_interface_coptimizations.c create mode 100644 lib/zope/interface/_zope_interface_coptimizations.cp37-win32.pyd create mode 100644 lib/zope/interface/adapter.py create mode 100644 lib/zope/interface/advice.py create mode 100644 lib/zope/interface/common/__init__.py create mode 100644 lib/zope/interface/common/idatetime.py create mode 100644 lib/zope/interface/common/interfaces.py create mode 100644 lib/zope/interface/common/mapping.py create mode 100644 lib/zope/interface/common/sequence.py create mode 100644 lib/zope/interface/common/tests/__init__.py create mode 100644 lib/zope/interface/common/tests/basemapping.py create mode 100644 lib/zope/interface/common/tests/test_idatetime.py create mode 100644 lib/zope/interface/common/tests/test_import_interfaces.py create mode 100644 lib/zope/interface/declarations.py create mode 100644 lib/zope/interface/document.py create mode 100644 lib/zope/interface/exceptions.py create mode 100644 lib/zope/interface/interface.py create mode 100644 lib/zope/interface/interfaces.py create mode 100644 lib/zope/interface/registry.py create mode 100644 lib/zope/interface/ro.py create mode 100644 lib/zope/interface/tests/__init__.py create mode 100644 lib/zope/interface/tests/advisory_testing.py create mode 100644 lib/zope/interface/tests/dummy.py create mode 100644 lib/zope/interface/tests/idummy.py create mode 100644 lib/zope/interface/tests/ifoo.py create mode 100644 lib/zope/interface/tests/ifoo_other.py create mode 100644 lib/zope/interface/tests/m1.py create mode 100644 lib/zope/interface/tests/m2.py create mode 100644 lib/zope/interface/tests/odd.py create mode 100644 lib/zope/interface/tests/test_adapter.py create mode 100644 lib/zope/interface/tests/test_advice.py create mode 100644 lib/zope/interface/tests/test_declarations.py create mode 100644 lib/zope/interface/tests/test_document.py create mode 100644 lib/zope/interface/tests/test_element.py create mode 100644 lib/zope/interface/tests/test_exceptions.py create mode 100644 lib/zope/interface/tests/test_interface.py create mode 100644 lib/zope/interface/tests/test_interfaces.py create mode 100644 lib/zope/interface/tests/test_odd_declarations.py create mode 100644 lib/zope/interface/tests/test_registry.py create mode 100644 lib/zope/interface/tests/test_ro.py create mode 100644 lib/zope/interface/tests/test_sorting.py create mode 100644 lib/zope/interface/tests/test_verify.py create mode 100644 lib/zope/interface/verify.py diff --git a/Varken/dbmanager.py b/Varken/dbmanager.py index de5dc1d..8f081d6 100644 --- a/Varken/dbmanager.py +++ b/Varken/dbmanager.py @@ -1,5 +1,10 @@ +from sys import path +from os.path import abspath, dirname, join +path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) + from influxdb import InfluxDBClient + class DBManager(object): def __init__(self, server): self.server = server diff --git a/Varken/helpers.py b/Varken/helpers.py index e464b89..1560073 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -1,9 +1,12 @@ -import os +from sys import path +from os.path import abspath, basename, join, dirname +path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) + import time import tarfile import geoip2.database +from os import stat, remove from typing import NamedTuple -from os.path import abspath, join from urllib.request import urlretrieve @@ -339,9 +342,10 @@ def geoip_download(): tar = tarfile.open(tar_dbfile, "r:gz") for files in tar.getmembers(): if 'GeoLite2-City.mmdb' in files.name: - files.name = os.path.basename(files.name) + files.name = basename(files.name) tar.extract(files, abspath(join('.', 'data'))) - os.remove(tar_dbfile) + remove(tar_dbfile) + def geo_lookup(ipaddress): @@ -349,10 +353,10 @@ def geo_lookup(ipaddress): now = time.time() try: - dbinfo = os.stat(dbfile) + dbinfo = stat(dbfile) db_age = now - dbinfo.st_ctime if db_age > (35 * 86400): - os.remove(dbfile) + remove(dbfile) geoip_download() except FileNotFoundError: geoip_download() diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 9a21e11..79ec04d 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,12 +1,14 @@ -import sys -import configparser -from os.path import abspath, join +from sys import path, exit +from os.path import abspath, dirname, join +path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) + +from configparser import ConfigParser from Varken.helpers import OmbiServer, TautulliServer, SonarrServer, InfluxServer, RadarrServer class INIParser(object): def __init__(self): - self.config = configparser.ConfigParser() + self.config = ConfigParser() self.influx_server = InfluxServer() @@ -45,7 +47,7 @@ class INIParser(object): # Parse Sonarr options try: if not self.config.getboolean('global', 'sonarr_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') + exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'sonarr_server_ids'): self.sonarr_enabled = True except ValueError: @@ -75,7 +77,7 @@ class INIParser(object): # Parse Radarr options try: if not self.config.getboolean('global', 'radarr_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') + exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'radarr_server_ids'): self.radarr_enabled = True except ValueError: @@ -102,7 +104,7 @@ class INIParser(object): # Parse Tautulli options try: if not self.config.getboolean('global', 'tautulli_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') + exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'tautulli_server_ids'): self.tautulli_enabled = True except ValueError: @@ -130,7 +132,7 @@ class INIParser(object): # Parse Ombi Options try: if not self.config.getboolean('global', 'ombi_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') + exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'ombi_server_ids'): self.ombi_enabled = True except ValueError: diff --git a/Varken/ombi.py b/Varken/ombi.py index 3981250..3e703bb 100644 --- a/Varken/ombi.py +++ b/Varken/ombi.py @@ -1,3 +1,7 @@ +from sys import path +from os.path import abspath, dirname, join +path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) + from requests import Session from datetime import datetime, timezone diff --git a/Varken/radarr.py b/Varken/radarr.py index 091bb77..e81cfdf 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -1,3 +1,7 @@ +from sys import path +from os.path import abspath, dirname, join +path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) + from requests import Session from datetime import datetime, timezone diff --git a/Varken/sonarr.py b/Varken/sonarr.py index ae09c2e..1e9ae7c 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -1,3 +1,7 @@ +from sys import path +from os.path import abspath, dirname, join +path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) + from requests import Session from datetime import datetime, timezone, date, timedelta diff --git a/Varken/tautulli.py b/Varken/tautulli.py index 62f42a1..b77b2e3 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -1,6 +1,11 @@ +from sys import path +from os.path import abspath, dirname, join +path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) + +from requests import Session from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError -from requests import Session + from Varken.helpers import TautulliStream, geo_lookup from Varken.logger import logging diff --git a/lib/DateTime/DateTime.py b/lib/DateTime/DateTime.py new file mode 100644 index 0000000..cc6ca78 --- /dev/null +++ b/lib/DateTime/DateTime.py @@ -0,0 +1,1940 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Foundation and Contributors. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +import math +import re +import sys +from time import altzone +from time import daylight +from time import gmtime +from time import localtime +from time import time +from time import timezone +from time import tzname +from datetime import datetime + +from zope.interface import implementer + +from .interfaces import IDateTime +from .interfaces import DateTimeError +from .interfaces import SyntaxError +from .interfaces import DateError +from .interfaces import TimeError +from .pytz_support import PytzCache + +if sys.version_info > (3, ): + import copyreg as copy_reg + basestring = str + long = int + explicit_unicode_type = type(None) +else: + import copy_reg + explicit_unicode_type = unicode + +default_datefmt = None + + +def getDefaultDateFormat(): + global default_datefmt + if default_datefmt is None: + try: + from App.config import getConfiguration + default_datefmt = getConfiguration().datetime_format + return default_datefmt + except Exception: + return 'us' + else: + return default_datefmt + +# To control rounding errors, we round system time to the nearest +# microsecond. Then delicate calculations can rely on that the +# maximum precision that needs to be preserved is known. +_system_time = time + + +def time(): + return round(_system_time(), 6) + +# Determine machine epoch +tm = ((0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334), + (0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335)) +yr, mo, dy, hr, mn, sc = gmtime(0)[:6] +i = int(yr - 1) +to_year = int(i * 365 + i // 4 - i // 100 + i // 400 - 693960.0) +to_month = tm[yr % 4 == 0 and (yr % 100 != 0 or yr % 400 == 0)][mo] +EPOCH = ((to_year + to_month + dy + + (hr / 24.0 + mn / 1440.0 + sc / 86400.0)) * 86400) +jd1901 = 2415385 + +_TZINFO = PytzCache() + +INT_PATTERN = re.compile(r'([0-9]+)') +FLT_PATTERN = re.compile(r':([0-9]+\.[0-9]+)') +NAME_PATTERN = re.compile(r'([a-zA-Z]+)', re.I) +SPACE_CHARS = ' \t\n' +DELIMITERS = '-/.:,+' + +_MONTH_LEN = ((0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31), + (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)) +_MONTHS = ('', 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December') +_MONTHS_A = ('', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') +_MONTHS_P = ('', 'Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June', + 'July', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.') +_MONTHMAP = {'january': 1, 'jan': 1, + 'february': 2, 'feb': 2, + 'march': 3, 'mar': 3, + 'april': 4, 'apr': 4, + 'may': 5, + 'june': 6, 'jun': 6, + 'july': 7, 'jul': 7, + 'august': 8, 'aug': 8, + 'september': 9, 'sep': 9, 'sept': 9, + 'october': 10, 'oct': 10, + 'november': 11, 'nov': 11, + 'december': 12, 'dec': 12} +_DAYS = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', + 'Thursday', 'Friday', 'Saturday') +_DAYS_A = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat') +_DAYS_P = ('Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.') +_DAYMAP = {'sunday': 1, 'sun': 1, + 'monday': 2, 'mon': 2, + 'tuesday': 3, 'tues': 3, 'tue': 3, + 'wednesday': 4, 'wed': 4, + 'thursday': 5, 'thurs': 5, 'thur': 5, 'thu': 5, + 'friday': 6, 'fri': 6, + 'saturday': 7, 'sat': 7} + +numericTimeZoneMatch = re.compile(r'[+-][0-9][0-9][0-9][0-9]').match +iso8601Match = re.compile(r''' + (?P\d\d\d\d) # four digits year + (?:-? # one optional dash + (?: # followed by: + (?P\d\d\d # three digits year day + (?!\d)) # when there is no fourth digit + | # or: + W # one W + (?P\d\d) # two digits week + (?:-? # one optional dash + (?P\d) # one digit week day + )? # week day is optional + | # or: + (?P\d\d)? # two digits month + (?:-? # one optional dash + (?P\d\d)? # two digits day + )? # after day is optional + ) # + )? # after year is optional + (?:[T ] # one T or one whitespace + (?P\d\d) # two digits hour + (?::? # one optional colon + (?P\d\d)? # two digits minute + (?::? # one optional colon + (?P\d\d)? # two digits second + (?:[.,] # one dot or one comma + (?P\d+) # n digits fraction + )? # after second is optional + )? # after minute is optional + )? # after hour is optional + (?: # timezone: + (?PZ) # one Z + | # or: + (?P[-+]) # one plus or one minus as signal + (?P\d # one digit for hour offset... + (?:\d(?!\d$) # ...or two, if not the last two digits + )?) # second hour offset digit is optional + (?::? # one optional colon + (?P\d\d) # two digits minute offset + )? # after hour offset is optional + )? # timezone is optional + )? # time is optional + (?P.*) # store the extra garbage +''', re.VERBOSE).match + + +def _findLocalTimeZoneName(isDST): + if not daylight: + # Daylight savings does not occur in this time zone. + isDST = 0 + try: + # Get the name of the current time zone depending + # on DST. + _localzone = PytzCache._zmap[tzname[isDST].lower()] + except: + try: + # Generate a GMT-offset zone name. + if isDST: + localzone = altzone + else: + localzone = timezone + offset = (-localzone / 3600.0) + majorOffset = int(offset) + if majorOffset != 0: + minorOffset = abs(int((offset % majorOffset) * 60.0)) + else: + minorOffset = 0 + m = majorOffset >= 0 and '+' or '' + lz = '%s%0.02d%0.02d' % (m, majorOffset, minorOffset) + _localzone = PytzCache._zmap[('GMT%s' % lz).lower()] + except: + _localzone = '' + return _localzone + +_localzone0 = _findLocalTimeZoneName(0) +_localzone1 = _findLocalTimeZoneName(1) +_multipleZones = (_localzone0 != _localzone1) + +# Some utility functions for calculating dates: + + +def _calcSD(t): + # Returns timezone-independent days since epoch and the fractional + # part of the days. + dd = t + EPOCH - 86400.0 + d = dd / 86400.0 + s = d - math.floor(d) + return s, d + + +def _calcDependentSecond(tz, t): + # Calculates the timezone-dependent second (integer part only) + # from the timezone-independent second. + fset = _tzoffset(tz, t) + return fset + long(math.floor(t)) + long(EPOCH) - 86400 + + +def _calcDependentSecond2(yr, mo, dy, hr, mn, sc): + # Calculates the timezone-dependent second (integer part only) + # from the date given. + ss = int(hr) * 3600 + int(mn) * 60 + int(sc) + x = long(_julianday(yr, mo, dy) - jd1901) * 86400 + ss + return x + + +def _calcIndependentSecondEtc(tz, x, ms): + # Derive the timezone-independent second from the timezone + # dependent second. + fsetAtEpoch = _tzoffset(tz, 0.0) + nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms + # nearTime is now within an hour of being correct. + # Recalculate t according to DST. + fset = long(_tzoffset(tz, nearTime)) + d = (x - fset) / 86400.0 + (ms / 86400.0) + t = x - fset - long(EPOCH) + 86400 + ms + micros = (x + 86400 - fset) * 1000000 + \ + long(round(ms * 1000000.0)) - long(EPOCH * 1000000.0) + s = d - math.floor(d) + return (s, d, t, micros) + + +def _calcHMS(x, ms): + # hours, minutes, seconds from integer and float. + hr = x // 3600 + x = x - hr * 3600 + mn = x // 60 + sc = x - mn * 60 + ms + return (hr, mn, sc) + + +def _calcYMDHMS(x, ms): + # x is a timezone-dependent integer of seconds. + # Produces yr,mo,dy,hr,mn,sc. + yr, mo, dy = _calendarday(x // 86400 + jd1901) + x = int(x - (x // 86400) * 86400) + hr = x // 3600 + x = x - hr * 3600 + mn = x // 60 + sc = x - mn * 60 + ms + return (yr, mo, dy, hr, mn, sc) + + +def _julianday(yr, mo, dy): + y, m, d = long(yr), long(mo), long(dy) + if m > 12: + y = y + m // 12 + m = m % 12 + elif m < 1: + m = -m + y = y - m // 12 - 1 + m = 12 - m % 12 + if y > 0: + yr_correct = 0 + else: + yr_correct = 3 + if m < 3: + y, m = y - 1, m + 12 + if y * 10000 + m * 100 + d > 15821014: + b = 2 - y // 100 + y // 400 + else: + b = 0 + return ((1461 * y - yr_correct) // 4 + + 306001 * (m + 1) // 10000 + d + 1720994 + b) + + +def _calendarday(j): + j = long(j) + if (j < 2299160): + b = j + 1525 + else: + a = (4 * j - 7468861) // 146097 + b = j + 1526 + a - a // 4 + c = (20 * b - 2442) // 7305 + d = 1461 * c // 4 + e = 10000 * (b - d) // 306001 + dy = int(b - d - 306001 * e // 10000) + mo = (e < 14) and int(e - 1) or int(e - 13) + yr = (mo > 2) and (c - 4716) or (c - 4715) + return (int(yr), int(mo), int(dy)) + + +def _tzoffset(tz, t): + """Returns the offset in seconds to GMT from a specific timezone (tz) at + a specific time (t). NB! The _tzoffset result is the same same sign as + the time zone, i.e. GMT+2 has a 7200 second offset. This is the opposite + sign of time.timezone which (confusingly) is -7200 for GMT+2.""" + try: + return _TZINFO[tz].info(t)[0] + except Exception: + if numericTimeZoneMatch(tz) is not None: + return int(tz[0:3]) * 3600 + int(tz[0] + tz[3:5]) * 60 + else: + return 0 # ?? + + +def _correctYear(year): + # Y2K patch. + if year >= 0 and year < 100: + # 00-69 means 2000-2069, 70-99 means 1970-1999. + if year < 70: + year = 2000 + year + else: + year = 1900 + year + return year + + +def safegmtime(t): + '''gmtime with a safety zone.''' + try: + return gmtime(t) + except (ValueError, OverflowError): + raise TimeError('The time %f is beyond the range of this Python ' + 'implementation.' % float(t)) + + +def safelocaltime(t): + '''localtime with a safety zone.''' + try: + return localtime(t) + except (ValueError, OverflowError): + raise TimeError('The time %f is beyond the range of this Python ' + 'implementation.' % float(t)) + + +def _tzoffset2rfc822zone(seconds): + """Takes an offset, such as from _tzoffset(), and returns an rfc822 + compliant zone specification. Please note that the result of + _tzoffset() is the negative of what time.localzone and time.altzone is. + """ + return "%+03d%02d" % divmod((seconds // 60), 60) + + +def _tzoffset2iso8601zone(seconds): + """Takes an offset, such as from _tzoffset(), and returns an ISO 8601 + compliant zone specification. Please note that the result of + _tzoffset() is the negative of what time.localzone and time.altzone is. + """ + return "%+03d:%02d" % divmod((seconds // 60), 60) + + +def Timezones(): + """Return the list of recognized timezone names""" + return sorted(list(PytzCache._zmap.values())) + + +class strftimeFormatter(object): + + def __init__(self, dt, format): + self.dt = dt + self.format = format + + def __call__(self): + return self.dt.strftime(self.format) + + +@implementer(IDateTime) +class DateTime(object): + """DateTime objects represent instants in time and provide + interfaces for controlling its representation without + affecting the absolute value of the object. + + DateTime objects may be created from a wide variety of string + or numeric data, or may be computed from other DateTime objects. + DateTimes support the ability to convert their representations + to many major timezones, as well as the ablility to create a + DateTime object in the context of a given timezone. + + DateTime objects provide partial numerical behavior: + + - Two date-time objects can be subtracted to obtain a time, + in days between the two. + + - A date-time object and a positive or negative number may + be added to obtain a new date-time object that is the given + number of days later than the input date-time object. + + - A positive or negative number and a date-time object may + be added to obtain a new date-time object that is the given + number of days later than the input date-time object. + + - A positive or negative number may be subtracted from a + date-time object to obtain a new date-time object that is + the given number of days earlier than the input date-time + object. + + DateTime objects may be converted to integer, long, or float + numbers of days since January 1, 1901, using the standard int, + long, and float functions (Compatibility Note: int, long and + float return the number of days since 1901 in GMT rather than + local machine timezone). DateTime objects also provide access + to their value in a float format usable with the python time + module, provided that the value of the object falls in the + range of the epoch-based time module, and as a datetime.datetime + object. + + A DateTime object should be considered immutable; all conversion + and numeric operations return a new DateTime object rather than + modify the current object.""" + + # For security machinery: + __roles__ = None + __allow_access_to_unprotected_subobjects__ = 1 + + # Limit the amount of instance attributes + __slots__ = ( + '_timezone_naive', + '_tz', + '_dayoffset', + '_year', + '_month', + '_day', + '_hour', + '_minute', + '_second', + '_nearsec', + '_d', + '_micros', + 'time', + ) + + def __init__(self, *args, **kw): + """Return a new date-time object""" + try: + return self._parse_args(*args, **kw) + except (DateError, TimeError, DateTimeError): + raise + except Exception: + raise SyntaxError('Unable to parse %s, %s' % (args, kw)) + + def __getstate__(self): + # We store a float of _micros, instead of the _micros long, as we most + # often don't have any sub-second resolution and can save those bytes + return (self._micros / 1000000.0, + getattr(self, '_timezone_naive', False), + self._tz) + + def __setstate__(self, value): + if isinstance(value, tuple): + self._parse_args(value[0], value[2]) + self._micros = long(value[0] * 1000000) + self._timezone_naive = value[1] + else: + for k, v in value.items(): + if k in self.__slots__: + setattr(self, k, v) + # BBB: support for very old DateTime pickles + if '_micros' not in value: + self._micros = long(value['_t'] * 1000000) + if '_timezone_naive' not in value: + self._timezone_naive = False + + def _parse_args(self, *args, **kw): + """Return a new date-time object. + + A DateTime object always maintains its value as an absolute + UTC time, and is represented in the context of some timezone + based on the arguments used to create the object. A DateTime + object's methods return values based on the timezone context. + + Note that in all cases the local machine timezone is used for + representation if no timezone is specified. + + DateTimes may be created with from zero to seven arguments. + + - If the function is called with no arguments or with None, + then the current date/time is returned, represented in the + timezone of the local machine. + + - If the function is invoked with a single string argument + which is a recognized timezone name, an object representing + the current time is returned, represented in the specified + timezone. + + - If the function is invoked with a single string argument + representing a valid date/time, an object representing + that date/time will be returned. + + As a general rule, any date-time representation that is + recognized and unambigous to a resident of North America + is acceptable. The reason for this qualification is that + in North America, a date like: 2/1/1994 is interpreted + as February 1, 1994, while in some parts of the world, + it is interpreted as January 2, 1994. + + A date/time string consists of two components, a date + component and an optional time component, separated by one + or more spaces. If the time component is omited, 12:00am is + assumed. Any recognized timezone name specified as the final + element of the date/time string will be used for computing + the date/time value. If you create a DateTime with the + string 'Mar 9, 1997 1:45pm US/Pacific', the value will + essentially be the same as if you had captured time.time() + at the specified date and time on a machine in that timezone: + +
+            e=DateTime('US/Eastern')
+            # returns current date/time, represented in US/Eastern.
+
+            x=DateTime('1997/3/9 1:45pm')
+            # returns specified time, represented in local machine zone.
+
+            y=DateTime('Mar 9, 1997 13:45:00')
+            # y is equal to x
+            
+ + The date component consists of year, month, and day + values. The year value must be a one-, two-, or + four-digit integer. If a one- or two-digit year is + used, the year is assumed to be in the twentieth + century. The month may be an integer, from 1 to 12, a + month name, or a month abreviation, where a period may + optionally follow the abreviation. The day must be an + integer from 1 to the number of days in the month. The + year, month, and day values may be separated by + periods, hyphens, forward, shashes, or spaces. Extra + spaces are permitted around the delimiters. Year, + month, and day values may be given in any order as long + as it is possible to distinguish the components. If all + three components are numbers that are less than 13, + then a a month-day-year ordering is assumed. + + The time component consists of hour, minute, and second + values separated by colons. The hour value must be an + integer between 0 and 23 inclusively. The minute value + must be an integer between 0 and 59 inclusively. The + second value may be an integer value between 0 and + 59.999 inclusively. The second value or both the minute + and second values may be ommitted. The time may be + followed by am or pm in upper or lower case, in which + case a 12-hour clock is assumed. + + New in Zope 2.4: + The DateTime constructor automatically detects and handles + ISO8601 compliant dates (YYYY-MM-DDThh:ss:mmTZD). + + New in Zope 2.9.6: + The existing ISO8601 parser was extended to support almost + the whole ISO8601 specification. New formats includes: + +
+            y=DateTime('1993-045')
+            # returns the 45th day from 1993, which is 14th February
+
+            w=DateTime('1993-W06-7')
+            # returns the 7th day from the 6th week from 1993, which
+            # is also 14th February
+            
+ + See http://en.wikipedia.org/wiki/ISO_8601 for full specs. + + Note that the Zope DateTime parser assumes timezone naive ISO + strings to be in UTC rather than local time as specified. + + - If the DateTime function is invoked with a single Numeric + argument, the number is assumed to be a floating point value + such as that returned by time.time(). + + A DateTime object is returned that represents the GMT value + of the time.time() float represented in the local machine's + timezone. + + - If the DateTime function is invoked with a single argument + that is a DateTime instane, a copy of the passed object will + be created. + + - New in 2.11: + The DateTime function may now be invoked with a single argument + that is a datetime.datetime instance. DateTimes may be converted + back to datetime.datetime objects with asdatetime(). + DateTime instances may be converted to a timezone naive + datetime.datetime in UTC with utcdatetime(). + + - If the function is invoked with two numeric arguments, then + the first is taken to be an integer year and the second + argument is taken to be an offset in days from the beginning + of the year, in the context of the local machine timezone. + + The date-time value returned is the given offset number of + days from the beginning of the given year, represented in + the timezone of the local machine. The offset may be positive + or negative. + + Two-digit years are assumed to be in the twentieth + century. + + - If the function is invoked with two arguments, the first + a float representing a number of seconds past the epoch + in gmt (such as those returned by time.time()) and the + second a string naming a recognized timezone, a DateTime + with a value of that gmt time will be returned, represented + in the given timezone. + +
+            import time
+            t=time.time()
+
+            now_east=DateTime(t,'US/Eastern')
+            # Time t represented as US/Eastern
+
+            now_west=DateTime(t,'US/Pacific')
+            # Time t represented as US/Pacific
+
+            # now_east == now_west
+            # only their representations are different
+            
+ + - If the function is invoked with three or more numeric + arguments, then the first is taken to be an integer + year, the second is taken to be an integer month, and + the third is taken to be an integer day. If the + combination of values is not valid, then a + DateError is raised. Two-digit years are assumed + to be in the twentieth century. The fourth, fifth, and + sixth arguments specify a time in hours, minutes, and + seconds; hours and minutes should be positive integers + and seconds is a positive floating point value, all of + these default to zero if not given. An optional string may + be given as the final argument to indicate timezone (the + effect of this is as if you had taken the value of time.time() + at that time on a machine in the specified timezone). + + New in Zope 2.7: + A new keyword parameter "datefmt" can be passed to the + constructor. If set to "international", the constructor + is forced to treat ambigious dates as "days before month + before year". This useful if you need to parse non-US + dates in a reliable way + + In any case that a floating point number of seconds is given + or derived, it's rounded to the nearest millisecond. + + If a string argument passed to the DateTime constructor cannot be + parsed, it will raise DateTime.SyntaxError. Invalid date components + will raise a DateError, while invalid time or timezone components + will raise a DateTimeError. + + The module function Timezones() will return a list of the (common) + timezones recognized by the DateTime module. Recognition of + timezone names is case-insensitive. + """ + + datefmt = kw.get('datefmt', getDefaultDateFormat()) + d = t = s = None + ac = len(args) + microsecs = None + + if ac == 10: + # Internal format called only by DateTime + yr, mo, dy, hr, mn, sc, tz, t, d, s = args + elif ac == 11: + # Internal format that includes milliseconds (from the epoch) + yr, mo, dy, hr, mn, sc, tz, t, d, s, millisecs = args + microsecs = millisecs * 1000 + + elif ac == 12: + # Internal format that includes microseconds (from the epoch) and a + # flag indicating whether this was constructed in a timezone naive + # manner + yr, mo, dy, hr, mn, sc, tz, t, d, s, microsecs, tznaive = args + if tznaive is not None: # preserve this information + self._timezone_naive = tznaive + + elif not args or (ac and args[0] is None): + # Current time, to be displayed in local timezone + t = time() + lt = safelocaltime(t) + tz = self.localZone(lt) + ms = (t - math.floor(t)) + s, d = _calcSD(t) + yr, mo, dy, hr, mn, sc = lt[:6] + sc = sc + ms + self._timezone_naive = False + + elif ac == 1: + arg = args[0] + + if arg == '': + raise SyntaxError(arg) + + if isinstance(arg, DateTime): + """Construct a new DateTime instance from a given + DateTime instance. + """ + t = arg.timeTime() + s, d = _calcSD(t) + yr, mo, dy, hr, mn, sc, tz = arg.parts() + + elif isinstance(arg, datetime): + yr, mo, dy, hr, mn, sc, numerictz, tznaive = \ + self._parse_iso8601_preserving_tznaive(arg.isoformat()) + if arg.tzinfo is None: + self._timezone_naive = True + tz = None + else: + self._timezone_naive = False + # if we have a pytz tzinfo, use the `zone` attribute + # as a key + tz = getattr(arg.tzinfo, 'zone', numerictz) + ms = sc - math.floor(sc) + x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc) + + if tz: + try: + zone = _TZINFO[tz] + except DateTimeError: + try: + zone = _TZINFO[numerictz] + except DateTimeError: + raise DateTimeError( + 'Unknown time zone in date: %s' % arg) + tz = zone.tzinfo.zone + else: + tz = self._calcTimezoneName(x, ms) + s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms) + + elif (isinstance(arg, basestring) and + arg.lower() in _TZINFO._zidx): + # Current time, to be displayed in specified timezone + t, tz = time(), _TZINFO._zmap[arg.lower()] + ms = (t - math.floor(t)) + # Use integer arithmetic as much as possible. + s, d = _calcSD(t) + x = _calcDependentSecond(tz, t) + yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms) + + elif isinstance(arg, basestring): + # Date/time string + iso8601 = iso8601Match(arg.strip()) + fields_iso8601 = iso8601 and iso8601.groupdict() or {} + if fields_iso8601 and not fields_iso8601.get('garbage'): + yr, mo, dy, hr, mn, sc, tz, tznaive = \ + self._parse_iso8601_preserving_tznaive(arg) + self._timezone_naive = tznaive + else: + yr, mo, dy, hr, mn, sc, tz = self._parse(arg, datefmt) + + if not self._validDate(yr, mo, dy): + raise DateError('Invalid date: %s' % arg) + if not self._validTime(hr, mn, int(sc)): + raise TimeError('Invalid time: %s' % arg) + ms = sc - math.floor(sc) + x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc) + + if tz: + try: + tz = _TZINFO._zmap[tz.lower()] + except KeyError: + if numericTimeZoneMatch(tz) is None: + raise DateTimeError( + 'Unknown time zone in date: %s' % arg) + else: + tz = self._calcTimezoneName(x, ms) + s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms) + + else: + # Seconds from epoch, gmt + t = arg + lt = safelocaltime(t) + tz = self.localZone(lt) + ms = (t - math.floor(t)) + s, d = _calcSD(t) + yr, mo, dy, hr, mn, sc = lt[:6] + sc = sc + ms + + elif ac == 2: + if isinstance(args[1], basestring): + # Seconds from epoch (gmt) and timezone + t, tz = args + ms = (t - math.floor(t)) + try: + tz = _TZINFO._zmap[tz.lower()] + except KeyError: + if numericTimeZoneMatch(tz) is None: + raise DateTimeError('Unknown time zone: %s' % tz) + # Use integer arithmetic as much as possible. + s, d = _calcSD(t) + x = _calcDependentSecond(tz, t) + yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms) + else: + # Year, julian expressed in local zone + t = time() + lt = safelocaltime(t) + tz = self.localZone(lt) + yr, jul = args + yr = _correctYear(yr) + d = (_julianday(yr, 1, 0) - jd1901) + jul + x_float = d * 86400.0 + x_floor = math.floor(x_float) + ms = x_float - x_floor + x = long(x_floor) + yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms) + s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms) + else: + # Explicit format + yr, mo, dy = args[:3] + hr, mn, sc, tz = 0, 0, 0, 0 + yr = _correctYear(yr) + if not self._validDate(yr, mo, dy): + raise DateError('Invalid date: %s' % (args, )) + args = args[3:] + if args: + hr, args = args[0], args[1:] + if args: + mn, args = args[0], args[1:] + if args: + sc, args = args[0], args[1:] + if args: + tz, args = args[0], args[1:] + if args: + raise DateTimeError('Too many arguments') + if not self._validTime(hr, mn, sc): + raise TimeError('Invalid time: %s' % repr(args)) + + x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc) + ms = sc - math.floor(sc) + if tz: + try: + tz = _TZINFO._zmap[tz.lower()] + except KeyError: + if numericTimeZoneMatch(tz) is None: + raise DateTimeError('Unknown time zone: %s' % tz) + else: + # Get local time zone name + tz = self._calcTimezoneName(x, ms) + s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms) + + self._dayoffset = int((_julianday(yr, mo, dy) + 2) % 7) + # Round to nearest microsecond in platform-independent way. You + # cannot rely on C sprintf (Python '%') formatting to round + # consistently; doing it ourselves ensures that all but truly + # horrid C sprintf implementations will yield the same result + # x-platform, provided the format asks for exactly 6 digits after + # the decimal point. + sc = round(sc, 6) + if sc >= 60.0: # can happen if, e.g., orig sc was 59.9999999 + sc = 59.999999 + self._nearsec = math.floor(sc) + self._year, self._month, self._day = yr, mo, dy + self._hour, self._minute, self._second = hr, mn, sc + self.time, self._d, self._tz = s, d, tz + # self._micros is the time since the epoch + # in long integer microseconds. + if microsecs is None: + microsecs = long(math.floor(t * 1000000.0)) + self._micros = microsecs + + def localZone(self, ltm=None): + '''Returns the time zone on the given date. The time zone + can change according to daylight savings.''' + if not _multipleZones: + return _localzone0 + if ltm is None: + ltm = localtime(time()) + isDST = ltm[8] + lz = isDST and _localzone1 or _localzone0 + return lz + + def _calcTimezoneName(self, x, ms): + # Derive the name of the local time zone at the given + # timezone-dependent second. + if not _multipleZones: + return _localzone0 + fsetAtEpoch = _tzoffset(_localzone0, 0.0) + nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms + # nearTime is within an hour of being correct. + try: + ltm = safelocaltime(nearTime) + except: + # We are beyond the range of Python's date support. + # Hopefully we can assume that daylight savings schedules + # repeat every 28 years. Calculate the name of the + # time zone using a supported range of years. + yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, 0) + yr = ((yr - 1970) % 28) + 1970 + x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc) + nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms + + # nearTime might still be negative if we are east of Greenwich. + # But we can asume on 1969/12/31 were no timezone changes. + nearTime = max(0, nearTime) + + ltm = safelocaltime(nearTime) + tz = self.localZone(ltm) + return tz + + def _parse(self, st, datefmt=getDefaultDateFormat()): + # Parse date-time components from a string + month = year = tz = tm = None + ValidZones = _TZINFO._zidx + TimeModifiers = ['am', 'pm'] + + # Find timezone first, since it should always be the last + # element, and may contain a slash, confusing the parser. + st = st.strip() + sp = st.split() + tz = sp[-1] + if tz and (tz.lower() in ValidZones): + self._timezone_naive = False + st = ' '.join(sp[:-1]) + else: + self._timezone_naive = True + tz = None # Decide later, since the default time zone + # could depend on the date. + + ints = [] + i = 0 + l = len(st) + while i < l: + while i < l and st[i] in SPACE_CHARS: + i += 1 + if i < l and st[i] in DELIMITERS: + d = st[i] + i += 1 + else: + d = '' + while i < l and st[i] in SPACE_CHARS: + i += 1 + + # The float pattern needs to look back 1 character, because it + # actually looks for a preceding colon like ':33.33'. This is + # needed to avoid accidentally matching the date part of a + # dot-separated date string such as '1999.12.31'. + if i > 0: + b = i - 1 + else: + b = i + + ts_results = FLT_PATTERN.match(st, b) + if ts_results: + s = ts_results.group(1) + i = i + len(s) + ints.append(float(s)) + continue + + #AJ + ts_results = INT_PATTERN.match(st, i) + if ts_results: + s = ts_results.group(0) + + ls = len(s) + i = i + ls + if (ls == 4 and d and d in '+-' and + (len(ints) + (not not month) >= 3)): + tz = '%s%s' % (d, s) + else: + v = int(s) + ints.append(v) + continue + + ts_results = NAME_PATTERN.match(st, i) + if ts_results: + s = ts_results.group(0).lower() + i = i + len(s) + if i < l and st[i] == '.': + i += 1 + # Check for month name: + _v = _MONTHMAP.get(s) + if _v is not None: + if month is None: + month = _v + else: + raise SyntaxError(st) + continue + # Check for time modifier: + if s in TimeModifiers: + if tm is None: + tm = s + else: + raise SyntaxError(st) + continue + # Check for and skip day of week: + if s in _DAYMAP: + continue + + raise SyntaxError(st) + + day = None + if ints[-1] > 60 and d not in ('.', ':', '/') and len(ints) > 2: + year = ints[-1] + del ints[-1] + if month: + day = ints[0] + del ints[:1] + else: + if datefmt == "us": + month = ints[0] + day = ints[1] + else: + month = ints[1] + day = ints[0] + del ints[:2] + elif month: + if len(ints) > 1: + if ints[0] > 31: + year = ints[0] + day = ints[1] + else: + year = ints[1] + day = ints[0] + del ints[:2] + elif len(ints) > 2: + if ints[0] > 31: + year = ints[0] + if ints[1] > 12: + day = ints[1] + month = ints[2] + else: + day = ints[2] + month = ints[1] + if ints[1] > 31: + year = ints[1] + if ints[0] > 12 and ints[2] <= 12: + day = ints[0] + month = ints[2] + elif ints[2] > 12 and ints[0] <= 12: + day = ints[2] + month = ints[0] + elif ints[2] > 31: + year = ints[2] + if ints[0] > 12: + day = ints[0] + month = ints[1] + else: + if datefmt == "us": + day = ints[1] + month = ints[0] + else: + day = ints[0] + month = ints[1] + + elif ints[0] <= 12: + month = ints[0] + day = ints[1] + year = ints[2] + del ints[:3] + + if day is None: + # Use today's date. + year, month, day = localtime(time())[:3] + + year = _correctYear(year) + if year < 1000: + raise SyntaxError(st) + + leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + try: + if not day or day > _MONTH_LEN[leap][month]: + raise DateError(st) + except IndexError: + raise DateError(st) + + tod = 0 + if ints: + i = ints[0] + # Modify hour to reflect am/pm + if tm and (tm == 'pm') and i < 12: + i += 12 + if tm and (tm == 'am') and i == 12: + i = 0 + if i > 24: + raise TimeError(st) + tod = tod + int(i) * 3600 + del ints[0] + if ints: + i = ints[0] + if i > 60: + raise TimeError(st) + tod = tod + int(i) * 60 + del ints[0] + if ints: + i = ints[0] + if i > 60: + raise TimeError(st) + tod = tod + i + del ints[0] + if ints: + raise SyntaxError(st) + + tod_int = int(math.floor(tod)) + ms = tod - tod_int + hr, mn, sc = _calcHMS(tod_int, ms) + if not tz: + # Figure out what time zone it is in the local area + # on the given date. + x = _calcDependentSecond2(year, month, day, hr, mn, sc) + tz = self._calcTimezoneName(x, ms) + + return year, month, day, hr, mn, sc, tz + + # Internal methods + def _validDate(self, y, m, d): + if m < 1 or m > 12 or y < 0 or d < 1 or d > 31: + return 0 + return d <= _MONTH_LEN[ + (y % 4 == 0 and (y % 100 != 0 or y % 400 == 0))][m] + + def _validTime(self, h, m, s): + return h >= 0 and h <= 23 and m >= 0 and m <= 59 and s >= 0 and s < 60 + + def __getattr__(self, name): + if '%' in name: + return strftimeFormatter(self, name) + raise AttributeError(name) + + # Conversion and comparison methods + + def timeTime(self): + """Return the date/time as a floating-point number in UTC, + in the format used by the python time module. + + Note that it is possible to create date/time values with + DateTime that have no meaningful value to the time module. + """ + return self._micros / 1000000.0 + + def toZone(self, z): + """Return a DateTime with the value as the current + object, represented in the indicated timezone. + """ + t, tz = self._t, _TZINFO._zmap[z.lower()] + micros = self.micros() + tznaive = False # you're performing a timzone change, can't be naive + + try: + # Try to use time module for speed. + yr, mo, dy, hr, mn, sc = safegmtime(t + _tzoffset(tz, t))[:6] + sc = self._second + return self.__class__(yr, mo, dy, hr, mn, sc, tz, t, + self._d, self.time, micros, tznaive) + except Exception: + # gmtime can't perform the calculation in the given range. + # Calculate the difference between the two time zones. + tzdiff = _tzoffset(tz, t) - _tzoffset(self._tz, t) + if tzdiff == 0: + return self + sc = self._second + ms = sc - math.floor(sc) + x = _calcDependentSecond2(self._year, self._month, self._day, + self._hour, self._minute, sc) + x_new = x + tzdiff + yr, mo, dy, hr, mn, sc = _calcYMDHMS(x_new, ms) + return self.__class__(yr, mo, dy, hr, mn, sc, tz, t, + self._d, self.time, micros, tznaive) + + def isFuture(self): + """Return true if this object represents a date/time + later than the time of the call. + """ + return (self._t > time()) + + def isPast(self): + """Return true if this object represents a date/time + earlier than the time of the call. + """ + return (self._t < time()) + + def isCurrentYear(self): + """Return true if this object represents a date/time + that falls within the current year, in the context + of this object\'s timezone representation. + """ + t = time() + return safegmtime(t + _tzoffset(self._tz, t))[0] == self._year + + def isCurrentMonth(self): + """Return true if this object represents a date/time + that falls within the current month, in the context + of this object\'s timezone representation. + """ + t = time() + gmt = safegmtime(t + _tzoffset(self._tz, t)) + return gmt[0] == self._year and gmt[1] == self._month + + def isCurrentDay(self): + """Return true if this object represents a date/time + that falls within the current day, in the context + of this object\'s timezone representation. + """ + t = time() + gmt = safegmtime(t + _tzoffset(self._tz, t)) + return (gmt[0] == self._year and gmt[1] == self._month and + gmt[2] == self._day) + + def isCurrentHour(self): + """Return true if this object represents a date/time + that falls within the current hour, in the context + of this object\'s timezone representation. + """ + t = time() + gmt = safegmtime(t + _tzoffset(self._tz, t)) + return (gmt[0] == self._year and gmt[1] == self._month and + gmt[2] == self._day and gmt[3] == self._hour) + + def isCurrentMinute(self): + """Return true if this object represents a date/time + that falls within the current minute, in the context + of this object\'s timezone representation. + """ + t = time() + gmt = safegmtime(t + _tzoffset(self._tz, t)) + return (gmt[0] == self._year and gmt[1] == self._month and + gmt[2] == self._day and gmt[3] == self._hour and + gmt[4] == self._minute) + + def earliestTime(self): + """Return a new DateTime object that represents the earliest + possible time (in whole seconds) that still falls within + the current object\'s day, in the object\'s timezone context. + """ + return self.__class__( + self._year, self._month, self._day, 0, 0, 0, self._tz) + + def latestTime(self): + """Return a new DateTime object that represents the latest + possible time (in whole seconds) that still falls within + the current object\'s day, in the object\'s timezone context. + """ + return self.__class__( + self._year, self._month, self._day, 23, 59, 59, self._tz) + + def greaterThan(self, t): + """Compare this DateTime object to another DateTime object + OR a floating point number such as that which is returned + by the python time module. + + Returns true if the object represents a date/time greater + than the specified DateTime or time module style time. + + Revised to give more correct results through comparison of + long integer microseconds. + """ + if t is None: + t = 0 + if isinstance(t, float): + return self._micros > long(t * 1000000) + try: + return self._micros > t._micros + except AttributeError: + return self._micros > t + + __gt__ = greaterThan + + def greaterThanEqualTo(self, t): + """Compare this DateTime object to another DateTime object + OR a floating point number such as that which is returned + by the python time module. + + Returns true if the object represents a date/time greater + than or equal to the specified DateTime or time module style + time. + + Revised to give more correct results through comparison of + long integer microseconds. + """ + if t is None: + t = 0 + if isinstance(t, float): + return self._micros >= long(t * 1000000) + try: + return self._micros >= t._micros + except AttributeError: + return self._micros >= t + + __ge__ = greaterThanEqualTo + + def equalTo(self, t): + """Compare this DateTime object to another DateTime object + OR a floating point number such as that which is returned + by the python time module. + + Returns true if the object represents a date/time equal to + the specified DateTime or time module style time. + + Revised to give more correct results through comparison of + long integer microseconds. + """ + if t is None: + t = 0 + if isinstance(t, float): + return self._micros == long(t * 1000000) + try: + return self._micros == t._micros + except AttributeError: + return self._micros == t + + def notEqualTo(self, t): + """Compare this DateTime object to another DateTime object + OR a floating point number such as that which is returned + by the python time module. + + Returns true if the object represents a date/time not equal + to the specified DateTime or time module style time. + + Revised to give more correct results through comparison of + long integer microseconds. + """ + return not self.equalTo(t) + + def __eq__(self, t): + """Compare this DateTime object to another DateTime object. + Return True if their internal state is the same. Two objects + representing the same time in different timezones are regared as + unequal. Use the equalTo method if you are only interested in them + refering to the same moment in time. + """ + if not isinstance(t, DateTime): + return False + return (self._micros, self._tz) == (t._micros, t._tz) + + def __ne__(self, t): + return not self.__eq__(t) + + def lessThan(self, t): + """Compare this DateTime object to another DateTime object + OR a floating point number such as that which is returned + by the python time module. + + Returns true if the object represents a date/time less than + the specified DateTime or time module style time. + + Revised to give more correct results through comparison of + long integer microseconds. + """ + if t is None: + t = 0 + if isinstance(t, float): + return self._micros < long(t * 1000000) + try: + return self._micros < t._micros + except AttributeError: + return self._micros < t + + __lt__ = lessThan + + def lessThanEqualTo(self, t): + """Compare this DateTime object to another DateTime object + OR a floating point number such as that which is returned + by the python time module. + + Returns true if the object represents a date/time less than + or equal to the specified DateTime or time module style time. + + Revised to give more correct results through comparison of + long integer microseconds. + """ + if t is None: + t = 0 + if isinstance(t, float): + return self._micros <= long(t * 1000000) + try: + return self._micros <= t._micros + except AttributeError: + return self._micros <= t + + __le__ = lessThanEqualTo + + def isLeapYear(self): + """Return true if the current year (in the context of the + object\'s timezone) is a leap year. + """ + return (self._year % 4 == 0 and + (self._year % 100 != 0 or self._year % 400 == 0)) + + def dayOfYear(self): + """Return the day of the year, in context of the timezone + representation of the object. + """ + d = int(self._d + (_tzoffset(self._tz, self._t) / 86400.0)) + return int((d + jd1901) - _julianday(self._year, 1, 0)) + + # Component access + def parts(self): + """Return a tuple containing the calendar year, month, + day, hour, minute second and timezone of the object. + """ + return (self._year, self._month, self._day, self._hour, + self._minute, self._second, self._tz) + + def timezone(self): + """Return the timezone in which the object is represented.""" + return self._tz + + def tzoffset(self): + """Return the timezone offset for the objects timezone.""" + return _tzoffset(self._tz, self._t) + + def year(self): + """Return the calendar year of the object.""" + return self._year + + def month(self): + """Return the month of the object as an integer.""" + return self._month + + @property + def _fmon(self): + return _MONTHS[self._month] + + def Month(self): + """Return the full month name.""" + return self._fmon + + @property + def _amon(self): + return _MONTHS_A[self._month] + + def aMonth(self): + """Return the abreviated month name.""" + return self._amon + + def Mon(self): + """Compatibility: see aMonth.""" + return self._amon + + @property + def _pmon(self): + return _MONTHS_P[self._month] + + def pMonth(self): + """Return the abreviated (with period) month name.""" + return self._pmon + + def Mon_(self): + """Compatibility: see pMonth.""" + return self._pmon + + def day(self): + """Return the integer day.""" + return self._day + + @property + def _fday(self): + return _DAYS[self._dayoffset] + + def Day(self): + """Return the full name of the day of the week.""" + return self._fday + + def DayOfWeek(self): + """Compatibility: see Day.""" + return self._fday + + @property + def _aday(self): + return _DAYS_A[self._dayoffset] + + def aDay(self): + """Return the abreviated name of the day of the week.""" + return self._aday + + @property + def _pday(self): + return _DAYS_P[self._dayoffset] + + def pDay(self): + """Return the abreviated (with period) name of the day of the week.""" + return self._pday + + def Day_(self): + """Compatibility: see pDay.""" + return self._pday + + def dow(self): + """Return the integer day of the week, where sunday is 0.""" + return self._dayoffset + + def dow_1(self): + """Return the integer day of the week, where sunday is 1.""" + return self._dayoffset + 1 + + @property + def _pmhour(self): + hr = self._hour + if hr > 12: + return hr - 12 + return hr or 12 + + def h_12(self): + """Return the 12-hour clock representation of the hour.""" + return self._pmhour + + def h_24(self): + """Return the 24-hour clock representation of the hour.""" + return self._hour + + @property + def _pm(self): + hr = self._hour + if hr >= 12: + return 'pm' + return 'am' + + def ampm(self): + """Return the appropriate time modifier (am or pm).""" + return self._pm + + def hour(self): + """Return the 24-hour clock representation of the hour.""" + return self._hour + + def minute(self): + """Return the minute.""" + return self._minute + + def second(self): + """Return the second.""" + return self._second + + def millis(self): + """Return the millisecond since the epoch in GMT.""" + return self._micros // 1000 + + def micros(self): + """Return the microsecond since the epoch in GMT.""" + return self._micros + + def timezoneNaive(self): + """The python datetime module introduces the idea of distinguishing + between timezone aware and timezone naive datetime values. For lossless + conversion to and from datetime.datetime record if we record this + information using True / False. DateTime makes no distinction, when we + don't have any information we return None here. + """ + try: + return self._timezone_naive + except AttributeError: + return None + + def strftime(self, format): + """Format the date/time using the *current timezone representation*.""" + x = _calcDependentSecond2(self._year, self._month, self._day, + self._hour, self._minute, self._second) + ltz = self._calcTimezoneName(x, 0) + tzdiff = _tzoffset(ltz, self._t) - _tzoffset(self._tz, self._t) + zself = self + tzdiff / 86400.0 + microseconds = int((zself._second - zself._nearsec) * 1000000) + unicode_format = False + if isinstance(format, explicit_unicode_type): + format = format.encode('utf-8') + unicode_format = True + ds = datetime(zself._year, zself._month, zself._day, zself._hour, + zself._minute, int(zself._nearsec), + microseconds).strftime(format) + if unicode_format: + return ds.decode('utf-8') + return ds + + # General formats from previous DateTime + def Date(self): + """Return the date string for the object.""" + return "%s/%2.2d/%2.2d" % (self._year, self._month, self._day) + + def Time(self): + """Return the time string for an object to the nearest second.""" + return '%2.2d:%2.2d:%2.2d' % (self._hour, self._minute, self._nearsec) + + def TimeMinutes(self): + """Return the time string for an object not showing seconds.""" + return '%2.2d:%2.2d' % (self._hour, self._minute) + + def AMPM(self): + """Return the time string for an object to the nearest second.""" + return '%2.2d:%2.2d:%2.2d %s' % ( + self._pmhour, self._minute, self._nearsec, self._pm) + + def AMPMMinutes(self): + """Return the time string for an object not showing seconds.""" + return '%2.2d:%2.2d %s' % (self._pmhour, self._minute, self._pm) + + def PreciseTime(self): + """Return the time string for the object.""" + return '%2.2d:%2.2d:%06.3f' % (self._hour, self._minute, self._second) + + def PreciseAMPM(self): + """Return the time string for the object.""" + return '%2.2d:%2.2d:%06.3f %s' % ( + self._pmhour, self._minute, self._second, self._pm) + + def yy(self): + """Return calendar year as a 2 digit string.""" + return str(self._year)[-2:] + + def mm(self): + """Return month as a 2 digit string.""" + return '%02d' % self._month + + def dd(self): + """Return day as a 2 digit string.""" + return '%02d' % self._day + + def rfc822(self): + """Return the date in RFC 822 format.""" + tzoffset = _tzoffset2rfc822zone(_tzoffset(self._tz, self._t)) + return '%s, %2.2d %s %d %2.2d:%2.2d:%2.2d %s' % ( + self._aday, self._day, self._amon, self._year, + self._hour, self._minute, self._nearsec, tzoffset) + + # New formats + def fCommon(self): + """Return a string representing the object\'s value + in the format: March 1, 1997 1:45 pm. + """ + return '%s %s, %4.4d %s:%2.2d %s' % ( + self._fmon, self._day, self._year, self._pmhour, + self._minute, self._pm) + + def fCommonZ(self): + """Return a string representing the object\'s value + in the format: March 1, 1997 1:45 pm US/Eastern. + """ + return '%s %s, %4.4d %d:%2.2d %s %s' % ( + self._fmon, self._day, self._year, self._pmhour, + self._minute, self._pm, self._tz) + + def aCommon(self): + """Return a string representing the object\'s value + in the format: Mar 1, 1997 1:45 pm. + """ + return '%s %s, %4.4d %s:%2.2d %s' % ( + self._amon, self._day, self._year, self._pmhour, + self._minute, self._pm) + + def aCommonZ(self): + """Return a string representing the object\'s value + in the format: Mar 1, 1997 1:45 pm US/Eastern. + """ + return '%s %s, %4.4d %d:%2.2d %s %s' % ( + self._amon, self._day, self._year, self._pmhour, + self._minute, self._pm, self._tz) + + def pCommon(self): + """Return a string representing the object\'s value + in the format: Mar. 1, 1997 1:45 pm. + """ + return '%s %s, %4.4d %s:%2.2d %s' % ( + self._pmon, self._day, self._year, self._pmhour, + self._minute, self._pm) + + def pCommonZ(self): + """Return a string representing the object\'s value + in the format: Mar. 1, 1997 1:45 pm US/Eastern. + """ + return '%s %s, %4.4d %d:%2.2d %s %s' % ( + self._pmon, self._day, self._year, self._pmhour, + self._minute, self._pm, self._tz) + + def ISO(self): + """Return the object in ISO standard format. + + Note: this is *not* ISO 8601-format! See the ISO8601 and + HTML4 methods below for ISO 8601-compliant output. + + Dates are output as: YYYY-MM-DD HH:MM:SS + """ + return "%.4d-%.2d-%.2d %.2d:%.2d:%.2d" % ( + self._year, self._month, self._day, + self._hour, self._minute, self._second) + + def ISO8601(self): + """Return the object in ISO 8601-compatible format containing the + date, time with seconds-precision and the time zone identifier. + + See: http://www.w3.org/TR/NOTE-datetime + + Dates are output as: YYYY-MM-DDTHH:MM:SSTZD + T is a literal character. + TZD is Time Zone Designator, format +HH:MM or -HH:MM + + If the instance is timezone naive (it was not specified with a timezone + when it was constructed) then the timezone is ommitted. + + The HTML4 method below offers the same formatting, but converts + to UTC before returning the value and sets the TZD "Z". + """ + if self.timezoneNaive(): + return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d" % ( + self._year, self._month, self._day, + self._hour, self._minute, self._second) + tzoffset = _tzoffset2iso8601zone(_tzoffset(self._tz, self._t)) + return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d%s" % ( + self._year, self._month, self._day, + self._hour, self._minute, self._second, tzoffset) + + def HTML4(self): + """Return the object in the format used in the HTML4.0 specification, + one of the standard forms in ISO8601. + + See: http://www.w3.org/TR/NOTE-datetime + + Dates are output as: YYYY-MM-DDTHH:MM:SSZ + T, Z are literal characters. + The time is in UTC. + """ + newdate = self.toZone('UTC') + return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2dZ" % ( + newdate._year, newdate._month, newdate._day, + newdate._hour, newdate._minute, newdate._second) + + def asdatetime(self): + """Return a standard libary datetime.datetime + """ + tznaive = self.timezoneNaive() + if tznaive: + tzinfo = None + else: + tzinfo = _TZINFO[self._tz].tzinfo + second = int(self._second) + microsec = self.micros() % 1000000 + dt = datetime(self._year, self._month, self._day, self._hour, + self._minute, second, microsec, tzinfo) + return dt + + def utcdatetime(self): + """Convert the time to UTC then return a timezone naive datetime object + """ + utc = self.toZone('UTC') + second = int(utc._second) + microsec = utc.micros() % 1000000 + dt = datetime(utc._year, utc._month, utc._day, utc._hour, + utc._minute, second, microsec) + return dt + + def __add__(self, other): + """A DateTime may be added to a number and a number may be + added to a DateTime; two DateTimes cannot be added. + """ + if hasattr(other, '_t'): + raise DateTimeError('Cannot add two DateTimes') + o = float(other) + tz = self._tz + omicros = round(o * 86400000000) + tmicros = self.micros() + omicros + t = tmicros / 1000000.0 + d = (tmicros + long(EPOCH * 1000000)) / 86400000000.0 + s = d - math.floor(d) + ms = t - math.floor(t) + x = _calcDependentSecond(tz, t) + yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms) + return self.__class__(yr, mo, dy, hr, mn, sc, self._tz, + t, d, s, None, self.timezoneNaive()) + + __radd__ = __add__ + + def __sub__(self, other): + """Either a DateTime or a number may be subtracted from a + DateTime, however, a DateTime may not be subtracted from + a number. + """ + if hasattr(other, '_d'): + return (self.micros() - other.micros()) / 86400000000.0 + else: + return self.__add__(-(other)) + + def __repr__(self): + """Convert a DateTime to a string that looks like a Python + expression. + """ + return '%s(\'%s\')' % (self.__class__.__name__, str(self)) + + def __str__(self): + """Convert a DateTime to a string.""" + y, m, d = self._year, self._month, self._day + h, mn, s, t = self._hour, self._minute, self._second, self._tz + if s == int(s): + # A whole number of seconds -- suppress milliseconds. + return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d %s' % ( + y, m, d, h, mn, s, t) + else: + # s is already rounded to the nearest microsecond, and + # it's not a whole number of seconds. Be sure to print + # 2 digits before the decimal point. + return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%06.6f %s' % ( + y, m, d, h, mn, s, t) + + def __hash__(self): + """Compute a hash value for a DateTime.""" + return int(((self._year % 100 * 12 + self._month) * 31 + + self._day + self.time) * 100) + + def __int__(self): + """Convert to an integer number of seconds since the epoch (gmt).""" + return int(self.micros() // 1000000) + + def __long__(self): + """Convert to a long-int number of seconds since the epoch (gmt).""" + return long(self.micros() // 1000000) + + def __float__(self): + """Convert to floating-point number of seconds since the epoch (gmt). + """ + return self.micros() / 1000000.0 + + @property + def _t(self): + return self._micros / 1000000.0 + + def _parse_iso8601(self, s): + # preserve the previously implied contract + # who know where this could be used... + return self._parse_iso8601_preserving_tznaive(s)[:7] + + def _parse_iso8601_preserving_tznaive(self, s): + try: + return self.__parse_iso8601(s) + except IndexError: + raise SyntaxError( + 'Not an ISO 8601 compliant date string: "%s"' % s) + + def __parse_iso8601(self, s): + """Parse an ISO 8601 compliant date. + + See: http://en.wikipedia.org/wiki/ISO_8601 + """ + month = day = week_day = 1 + year = hour = minute = seconds = hour_off = min_off = 0 + tznaive = True + + iso8601 = iso8601Match(s.strip()) + fields = iso8601 and iso8601.groupdict() or {} + if not iso8601 or fields.get('garbage'): + raise IndexError + + if fields['year']: + year = int(fields['year']) + if fields['month']: + month = int(fields['month']) + if fields['day']: + day = int(fields['day']) + + if fields['year_day']: + d = DateTime('%s-01-01' % year) + int(fields['year_day']) - 1 + month = d.month() + day = d.day() + + if fields['week']: + week = int(fields['week']) + if fields['week_day']: + week_day = int(fields['week_day']) + d = DateTime('%s-01-04' % year) + d = d - (d.dow() + 6) % 7 + week * 7 + week_day - 8 + month = d.month() + day = d.day() + + if fields['hour']: + hour = int(fields['hour']) + + if fields['minute']: + minute = int(fields['minute']) + elif fields['fraction']: + minute = 60.0 * float('0.%s' % fields['fraction']) + seconds, minute = math.modf(minute) + minute = int(minute) + seconds = 60.0 * seconds + # Avoid reprocess when handling seconds, bellow + fields['fraction'] = None + + if fields['second']: + seconds = int(fields['second']) + if fields['fraction']: + seconds = seconds + float('0.%s' % fields['fraction']) + elif fields['fraction']: + seconds = 60.0 * float('0.%s' % fields['fraction']) + + if fields['hour_off']: + hour_off = int(fields['hour_off']) + if fields['signal'] == '-': + hour_off *= -1 + + if fields['min_off']: + min_off = int(fields['min_off']) + + if fields['signal'] or fields['Z']: + tznaive = False + else: + tznaive = True + + # Differ from the specification here. To preserve backwards + # compatibility assume a default timezone == UTC. + tz = 'GMT%+03d%02d' % (hour_off, min_off) + + return year, month, day, hour, minute, seconds, tz, tznaive + + def JulianDay(self): + """Return the Julian day. + + See: http://www.tondering.dk/claus/cal/node3.html#sec-calcjd + """ + a = (14 - self._month) // 12 + y = self._year + 4800 - a + m = self._month + (12 * a) - 3 + return (self._day + (153 * m + 2) // 5 + 365 * y + + y // 4 - y // 100 + y // 400 - 32045) + + def week(self): + """Return the week number according to ISO. + + See: http://www.tondering.dk/claus/cal/node6.html + """ + J = self.JulianDay() + d4 = (J + 31741 - (J % 7)) % 146097 % 36524 % 1461 + L = d4 // 1460 + d1 = ((d4 - L) % 365) + L + return d1 // 7 + 1 + + def encode(self, out): + """Encode value for XML-RPC.""" + out.write('') + out.write(self.ISO8601()) + out.write('\n') + + +# Provide the _dt_reconstructor function here, in case something +# accidentally creates a reference to this function + +orig_reconstructor = copy_reg._reconstructor + + +def _dt_reconstructor(cls, base, state): + if cls is DateTime: + return cls(state) + return orig_reconstructor(cls, base, state) diff --git a/lib/DateTime/DateTime.txt b/lib/DateTime/DateTime.txt new file mode 100644 index 0000000..5467047 --- /dev/null +++ b/lib/DateTime/DateTime.txt @@ -0,0 +1,785 @@ +The DateTime package +==================== + +Encapsulation of date/time values. + + +Function Timezones() +-------------------- + +Returns the list of recognized timezone names: + + >>> from DateTime import Timezones + >>> zones = set(Timezones()) + +Almost all of the standard pytz timezones are included, with the exception +of some commonly-used but ambiguous abbreviations, where historical Zope +usage conflicts with the name used by pytz: + + >>> import pytz + >>> [x for x in pytz.all_timezones if x not in zones] + ['CET', 'EET', 'EST', 'MET', 'MST', 'WET'] + +Class DateTime +-------------- + +DateTime objects represent instants in time and provide interfaces for +controlling its representation without affecting the absolute value of +the object. + +DateTime objects may be created from a wide variety of string or +numeric data, or may be computed from other DateTime objects. +DateTimes support the ability to convert their representations to many +major timezones, as well as the ablility to create a DateTime object +in the context of a given timezone. + +DateTime objects provide partial numerical behavior: + +* Two date-time objects can be subtracted to obtain a time, in days + between the two. + +* A date-time object and a positive or negative number may be added to + obtain a new date-time object that is the given number of days later + than the input date-time object. + +* A positive or negative number and a date-time object may be added to + obtain a new date-time object that is the given number of days later + than the input date-time object. + +* A positive or negative number may be subtracted from a date-time + object to obtain a new date-time object that is the given number of + days earlier than the input date-time object. + +DateTime objects may be converted to integer, long, or float numbers +of days since January 1, 1901, using the standard int, long, and float +functions (Compatibility Note: int, long and float return the number +of days since 1901 in GMT rather than local machine timezone). +DateTime objects also provide access to their value in a float format +usable with the python time module, provided that the value of the +object falls in the range of the epoch-based time module. + +A DateTime object should be considered immutable; all conversion and numeric +operations return a new DateTime object rather than modify the current object. + +A DateTime object always maintains its value as an absolute UTC time, +and is represented in the context of some timezone based on the +arguments used to create the object. A DateTime object's methods +return values based on the timezone context. + +Note that in all cases the local machine timezone is used for +representation if no timezone is specified. + +Constructor for DateTime +------------------------ + +DateTime() returns a new date-time object. DateTimes may be created +with from zero to seven arguments: + +* If the function is called with no arguments, then the current date/ + time is returned, represented in the timezone of the local machine. + +* If the function is invoked with a single string argument which is a + recognized timezone name, an object representing the current time is + returned, represented in the specified timezone. + +* If the function is invoked with a single string argument + representing a valid date/time, an object representing that date/ + time will be returned. + + As a general rule, any date-time representation that is recognized + and unambigous to a resident of North America is acceptable. (The + reason for this qualification is that in North America, a date like: + 2/1/1994 is interpreted as February 1, 1994, while in some parts of + the world, it is interpreted as January 2, 1994.) A date/ time + string consists of two components, a date component and an optional + time component, separated by one or more spaces. If the time + component is omited, 12:00am is assumed. + + Any recognized timezone name specified as the final element of the + date/time string will be used for computing the date/time value. + (If you create a DateTime with the string, + "Mar 9, 1997 1:45pm US/Pacific", the value will essentially be the + same as if you had captured time.time() at the specified date and + time on a machine in that timezone). If no timezone is passed, then + the timezone configured on the local machine will be used, **except** + that if the date format matches ISO 8601 ('YYYY-MM-DD'), the instance + will use UTC / CMT+0 as the timezone. + + o Returns current date/time, represented in US/Eastern: + + >>> from DateTime import DateTime + >>> e = DateTime('US/Eastern') + >>> e.timezone() + 'US/Eastern' + + o Returns specified time, represented in local machine zone: + + >>> x = DateTime('1997/3/9 1:45pm') + >>> x.parts() # doctest: +ELLIPSIS + (1997, 3, 9, 13, 45, ...) + + o Specified time in local machine zone, verbose format: + + >>> y = DateTime('Mar 9, 1997 13:45:00') + >>> y.parts() # doctest: +ELLIPSIS + (1997, 3, 9, 13, 45, ...) + >>> y == x + True + + o Specified time in UTC via ISO 8601 rule: + + >>> z = DateTime('2014-03-24') + >>> z.parts() # doctest: +ELLIPSIS + (2014, 3, 24, 0, 0, ...) + >>> z.timezone() + 'GMT+0' + + The date component consists of year, month, and day values. The + year value must be a one-, two-, or four-digit integer. If a one- + or two-digit year is used, the year is assumed to be in the + twentieth century. The month may an integer, from 1 to 12, a month + name, or a month abreviation, where a period may optionally follow + the abreviation. The day must be an integer from 1 to the number of + days in the month. The year, month, and day values may be separated + by periods, hyphens, forward, shashes, or spaces. Extra spaces are + permitted around the delimiters. Year, month, and day values may be + given in any order as long as it is possible to distinguish the + components. If all three components are numbers that are less than + 13, then a a month-day-year ordering is assumed. + + The time component consists of hour, minute, and second values + separated by colons. The hour value must be an integer between 0 + and 23 inclusively. The minute value must be an integer between 0 + and 59 inclusively. The second value may be an integer value + between 0 and 59.999 inclusively. The second value or both the + minute and second values may be ommitted. The time may be followed + by am or pm in upper or lower case, in which case a 12-hour clock is + assumed. + +* If the DateTime function is invoked with a single Numeric argument, + the number is assumed to be either a floating point value such as + that returned by time.time() , or a number of days after January 1, + 1901 00:00:00 UTC. + + A DateTime object is returned that represents either the gmt value + of the time.time() float represented in the local machine's + timezone, or that number of days after January 1, 1901. Note that + the number of days after 1901 need to be expressed from the + viewpoint of the local machine's timezone. A negative argument will + yield a date-time value before 1901. + +* If the function is invoked with two numeric arguments, then the + first is taken to be an integer year and the second argument is + taken to be an offset in days from the beginning of the year, in the + context of the local machine timezone. The date-time value returned + is the given offset number of days from the beginning of the given + year, represented in the timezone of the local machine. The offset + may be positive or negative. Two-digit years are assumed to be in + the twentieth century. + +* If the function is invoked with two arguments, the first a float + representing a number of seconds past the epoch in gmt (such as + those returned by time.time()) and the second a string naming a + recognized timezone, a DateTime with a value of that gmt time will + be returned, represented in the given timezone. + + >>> import time + >>> t = time.time() + + Time t represented as US/Eastern: + + >>> now_east = DateTime(t, 'US/Eastern') + + Time t represented as US/Pacific: + + >>> now_west = DateTime(t, 'US/Pacific') + + Only their representations are different: + + >>> now_east.equalTo(now_west) + True + +* If the function is invoked with three or more numeric arguments, + then the first is taken to be an integer year, the second is taken + to be an integer month, and the third is taken to be an integer day. + If the combination of values is not valid, then a DateTimeError is + raised. One- or two-digit years up to 69 are assumed to be in the + 21st century, whereas values 70-99 are assumed to be 20th century. + The fourth, fifth, and sixth arguments are floating point, positive + or negative offsets in units of hours, minutes, and days, and + default to zero if not given. An optional string may be given as + the final argument to indicate timezone (the effect of this is as if + you had taken the value of time.time() at that time on a machine in + the specified timezone). + +If a string argument passed to the DateTime constructor cannot be +parsed, it will raise SyntaxError. Invalid date, time, or +timezone components will raise a DateTimeError. + +The module function Timezones() will return a list of the timezones +recognized by the DateTime module. Recognition of timezone names is +case-insensitive. + +Instance Methods for DateTime (IDateTime interface) +--------------------------------------------------- + +Conversion and comparison methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* ``timeTime()`` returns the date/time as a floating-point number in + UTC, in the format used by the python time module. Note that it is + possible to create date /time values with DateTime that have no + meaningful value to the time module, and in such cases a + DateTimeError is raised. A DateTime object's value must generally + be between Jan 1, 1970 (or your local machine epoch) and Jan 2038 to + produce a valid time.time() style value. + + >>> dt = DateTime('Mar 9, 1997 13:45:00 US/Eastern') + >>> dt.timeTime() + 857933100.0 + + >>> DateTime('2040/01/01 UTC').timeTime() + 2208988800.0 + + >>> DateTime('1900/01/01 UTC').timeTime() + -2208988800.0 + +* ``toZone(z)`` returns a DateTime with the value as the current + object, represented in the indicated timezone: + + >>> dt.toZone('UTC') + DateTime('1997/03/09 18:45:00 UTC') + + >>> dt.toZone('UTC').equalTo(dt) + True + +* ``isFuture()`` returns true if this object represents a date/time + later than the time of the call: + + >>> dt.isFuture() + False + >>> DateTime('Jan 1 3000').isFuture() # not time-machine safe! + True + +* ``isPast()`` returns true if this object represents a date/time + earlier than the time of the call: + + >>> dt.isPast() + True + >>> DateTime('Jan 1 3000').isPast() # not time-machine safe! + False + +* ``isCurrentYear()`` returns true if this object represents a + date/time that falls within the current year, in the context of this + object's timezone representation: + + >>> dt.isCurrentYear() + False + >>> DateTime().isCurrentYear() + True + +* ``isCurrentMonth()`` returns true if this object represents a + date/time that falls within the current month, in the context of + this object's timezone representation: + + >>> dt.isCurrentMonth() + False + >>> DateTime().isCurrentMonth() + True + +* ``isCurrentDay()`` returns true if this object represents a + date/time that falls within the current day, in the context of this + object's timezone representation: + + >>> dt.isCurrentDay() + False + >>> DateTime().isCurrentDay() + True + +* ``isCurrentHour()`` returns true if this object represents a + date/time that falls within the current hour, in the context of this + object's timezone representation: + + >>> dt.isCurrentHour() + False + + >>> DateTime().isCurrentHour() + True + +* ``isCurrentMinute()`` returns true if this object represents a + date/time that falls within the current minute, in the context of + this object's timezone representation: + + >>> dt.isCurrentMinute() + False + >>> DateTime().isCurrentMinute() + True + +* ``isLeapYear()`` returns true if the current year (in the context of + the object's timezone) is a leap year: + + >>> dt.isLeapYear() + False + >>> DateTime('Mar 8 2004').isLeapYear() + True + +* ``earliestTime()`` returns a new DateTime object that represents the + earliest possible time (in whole seconds) that still falls within + the current object's day, in the object's timezone context: + + >>> dt.earliestTime() + DateTime('1997/03/09 00:00:00 US/Eastern') + +* ``latestTime()`` return a new DateTime object that represents the + latest possible time (in whole seconds) that still falls within the + current object's day, in the object's timezone context + + >>> dt.latestTime() + DateTime('1997/03/09 23:59:59 US/Eastern') + +Component access +~~~~~~~~~~~~~~~~ + +* ``parts()`` returns a tuple containing the calendar year, month, + day, hour, minute second and timezone of the object + + >>> dt.parts() # doctest: +ELLIPSIS + (1997, 3, 9, 13, 45, ... 'US/Eastern') + +* ``timezone()`` returns the timezone in which the object is represented: + + >>> dt.timezone() in Timezones() + True + +* ``tzoffset()`` returns the timezone offset for the objects timezone: + + >>> dt.tzoffset() + -18000 + +* ``year()`` returns the calendar year of the object: + + >>> dt.year() + 1997 + +* ``month()`` retursn the month of the object as an integer: + + >>> dt.month() + 3 + +* ``Month()`` returns the full month name: + + >>> dt.Month() + 'March' + +* ``aMonth()`` returns the abreviated month name: + + >>> dt.aMonth() + 'Mar' + +* ``pMonth()`` returns the abreviated (with period) month name: + + >>> dt.pMonth() + 'Mar.' + +* ``day()`` returns the integer day: + + >>> dt.day() + 9 + +* ``Day()`` returns the full name of the day of the week: + + >>> dt.Day() + 'Sunday' + +* ``dayOfYear()`` returns the day of the year, in context of the + timezone representation of the object: + + >>> dt.dayOfYear() + 68 + +* ``aDay()`` returns the abreviated name of the day of the week: + + >>> dt.aDay() + 'Sun' + +* ``pDay()`` returns the abreviated (with period) name of the day of + the week: + + >>> dt.pDay() + 'Sun.' + +* ``dow()`` returns the integer day of the week, where Sunday is 0: + + >>> dt.dow() + 0 + +* ``dow_1()`` returns the integer day of the week, where sunday is 1: + + >>> dt.dow_1() + 1 + +* ``h_12()`` returns the 12-hour clock representation of the hour: + + >>> dt.h_12() + 1 + +* ``h_24()`` returns the 24-hour clock representation of the hour: + + >>> dt.h_24() + 13 + +* ``ampm()`` returns the appropriate time modifier (am or pm): + + >>> dt.ampm() + 'pm' + +* ``hour()`` returns the 24-hour clock representation of the hour: + + >>> dt.hour() + 13 + +* ``minute()`` returns the minute: + + >>> dt.minute() + 45 + +* ``second()`` returns the second: + + >>> dt.second() == 0 + True + +* ``millis()`` returns the milliseconds since the epoch in GMT. + + >>> dt.millis() == 857933100000 + True + +strftime() +~~~~~~~~~~ + +See ``tests/test_datetime.py``. + +General formats from previous DateTime +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* ``Date()`` return the date string for the object: + + >>> dt.Date() + '1997/03/09' + +* ``Time()`` returns the time string for an object to the nearest + second: + + >>> dt.Time() + '13:45:00' + +* ``TimeMinutes()`` returns the time string for an object not showing + seconds: + + >>> dt.TimeMinutes() + '13:45' + +* ``AMPM()`` returns the time string for an object to the nearest second: + + >>> dt.AMPM() + '01:45:00 pm' + +* ``AMPMMinutes()`` returns the time string for an object not showing + seconds: + + >>> dt.AMPMMinutes() + '01:45 pm' + +* ``PreciseTime()`` returns the time string for the object: + + >>> dt.PreciseTime() + '13:45:00.000' + +* ``PreciseAMPM()`` returns the time string for the object: + + >>> dt.PreciseAMPM() + '01:45:00.000 pm' + +* ``yy()`` returns the calendar year as a 2 digit string + + >>> dt.yy() + '97' + +* ``mm()`` returns the month as a 2 digit string + + >>> dt.mm() + '03' + +* ``dd()`` returns the day as a 2 digit string: + + >>> dt.dd() + '09' + +* ``rfc822()`` returns the date in RFC 822 format: + + >>> dt.rfc822() + 'Sun, 09 Mar 1997 13:45:00 -0500' + +New formats +~~~~~~~~~~~ + +* ``fCommon()`` returns a string representing the object's value in + the format: March 9, 1997 1:45 pm: + + >>> dt.fCommon() + 'March 9, 1997 1:45 pm' + +* ``fCommonZ()`` returns a string representing the object's value in + the format: March 9, 1997 1:45 pm US/Eastern: + + >>> dt.fCommonZ() + 'March 9, 1997 1:45 pm US/Eastern' + +* ``aCommon()`` returns a string representing the object's value in + the format: Mar 9, 1997 1:45 pm: + + >>> dt.aCommon() + 'Mar 9, 1997 1:45 pm' + +* ``aCommonZ()`` return a string representing the object's value in + the format: Mar 9, 1997 1:45 pm US/Eastern: + + >>> dt.aCommonZ() + 'Mar 9, 1997 1:45 pm US/Eastern' + +* ``pCommon()`` returns a string representing the object's value in + the format Mar. 9, 1997 1:45 pm: + + >>> dt.pCommon() + 'Mar. 9, 1997 1:45 pm' + +* ``pCommonZ()`` returns a string representing the object's value in + the format: Mar. 9, 1997 1:45 pm US/Eastern: + + >>> dt.pCommonZ() + 'Mar. 9, 1997 1:45 pm US/Eastern' + +* ``ISO()`` returns a string with the date/time in ISO format. Note: + this is not ISO 8601-format! See the ISO8601 and HTML4 methods below + for ISO 8601-compliant output. Dates are output as: YYYY-MM-DD HH:MM:SS + + >>> dt.ISO() + '1997-03-09 13:45:00' + +* ``ISO8601()`` returns the object in ISO 8601-compatible format + containing the date, time with seconds-precision and the time zone + identifier - see http://www.w3.org/TR/NOTE-datetime. Dates are + output as: YYYY-MM-DDTHH:MM:SSTZD (T is a literal character, TZD is + Time Zone Designator, format +HH:MM or -HH:MM). + + The ``HTML4()`` method below offers the same formatting, but + converts to UTC before returning the value and sets the TZD"Z" + + >>> dt.ISO8601() + '1997-03-09T13:45:00-05:00' + + +* ``HTML4()`` returns the object in the format used in the HTML4.0 + specification, one of the standard forms in ISO8601. See + http://www.w3.org/TR/NOTE-datetime. Dates are output as: + YYYY-MM-DDTHH:MM:SSZ (T, Z are literal characters, the time is in + UTC.): + + >>> dt.HTML4() + '1997-03-09T18:45:00Z' + +* ``JulianDay()`` returns the Julian day according to + http://www.tondering.dk/claus/cal/node3.html#sec-calcjd + + >>> dt.JulianDay() + 2450517 + +* ``week()`` returns the week number according to ISO + see http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 + + >>> dt.week() + 10 + +Deprecated API +~~~~~~~~~~~~~~ + +* DayOfWeek(): see Day() + +* Day_(): see pDay() + +* Mon(): see aMonth() + +* Mon_(): see pMonth + +General Services Provided by DateTime +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +DateTimes can be repr()'ed; the result will be a string indicating how +to make a DateTime object like this: + + >>> repr(dt) + "DateTime('1997/03/09 13:45:00 US/Eastern')" + +When we convert them into a string, we get a nicer string that could +actually be shown to a user: + + >>> str(dt) + '1997/03/09 13:45:00 US/Eastern' + +The hash value of a DateTime is based on the date and time and is +equal for different representations of the DateTime: + + >>> hash(dt) + 3618678 + >>> hash(dt.toZone('UTC')) + 3618678 + +DateTime objects can be compared to other DateTime objects OR floating +point numbers such as the ones which are returned by the python time +module by using the equalTo method. Using this API, True is returned if the +object represents a date/time equal to the specified DateTime or time module +style time: + + >>> dt.equalTo(dt) + True + >>> dt.equalTo(dt.toZone('UTC')) + True + >>> dt.equalTo(dt.timeTime()) + True + >>> dt.equalTo(DateTime()) + False + +Same goes for inequalities: + + >>> dt.notEqualTo(dt) + False + >>> dt.notEqualTo(dt.toZone('UTC')) + False + >>> dt.notEqualTo(dt.timeTime()) + False + >>> dt.notEqualTo(DateTime()) + True + +Normal equality operations only work with datetime objects and take the +timezone setting into account: + + >>> dt == dt + True + >>> dt == dt.toZone('UTC') + False + >>> dt == DateTime() + False + + >>> dt != dt + False + >>> dt != dt.toZone('UTC') + True + >>> dt != DateTime() + True + +But the other comparison operations compare the referenced moment in time and +not the representation itself: + + >>> dt > dt + False + >>> DateTime() > dt + True + >>> dt > DateTime().timeTime() + False + >>> DateTime().timeTime() > dt + True + + >>> dt.greaterThan(dt) + False + >>> DateTime().greaterThan(dt) + True + >>> dt.greaterThan(DateTime().timeTime()) + False + + >>> dt >= dt + True + >>> DateTime() >= dt + True + >>> dt >= DateTime().timeTime() + False + >>> DateTime().timeTime() >= dt + True + + >>> dt.greaterThanEqualTo(dt) + True + >>> DateTime().greaterThanEqualTo(dt) + True + >>> dt.greaterThanEqualTo(DateTime().timeTime()) + False + + >>> dt < dt + False + >>> DateTime() < dt + False + >>> dt < DateTime().timeTime() + True + >>> DateTime().timeTime() < dt + False + + >>> dt.lessThan(dt) + False + >>> DateTime().lessThan(dt) + False + >>> dt.lessThan(DateTime().timeTime()) + True + + >>> dt <= dt + True + >>> DateTime() <= dt + False + >>> dt <= DateTime().timeTime() + True + >>> DateTime().timeTime() <= dt + False + + >>> dt.lessThanEqualTo(dt) + True + >>> DateTime().lessThanEqualTo(dt) + False + >>> dt.lessThanEqualTo(DateTime().timeTime()) + True + +Numeric Services Provided by DateTime +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A DateTime may be added to a number and a number may be added to a +DateTime: + + >>> dt + 5 + DateTime('1997/03/14 13:45:00 US/Eastern') + >>> 5 + dt + DateTime('1997/03/14 13:45:00 US/Eastern') + +Two DateTimes cannot be added: + + >>> from DateTime.interfaces import DateTimeError + >>> try: + ... dt + dt + ... print('fail') + ... except DateTimeError: + ... print('ok') + ok + +Either a DateTime or a number may be subtracted from a DateTime, +however, a DateTime may not be subtracted from a number: + + >>> DateTime('1997/03/10 13:45 US/Eastern') - dt + 1.0 + >>> dt - 1 + DateTime('1997/03/08 13:45:00 US/Eastern') + >>> 1 - dt + Traceback (most recent call last): + ... + TypeError: unsupported operand type(s) for -: 'int' and 'DateTime' + +DateTimes can also be converted to integers (number of seconds since +the epoch) and floats: + + >>> int(dt) + 857933100 + >>> float(dt) + 857933100.0 diff --git a/lib/DateTime/__init__.py b/lib/DateTime/__init__.py new file mode 100644 index 0000000..b4181ad --- /dev/null +++ b/lib/DateTime/__init__.py @@ -0,0 +1,17 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Foundation and Contributors. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +from .DateTime import DateTime +from .DateTime import Timezones + +__all__ = ('DateTime', 'Timezones') diff --git a/lib/DateTime/interfaces.py b/lib/DateTime/interfaces.py new file mode 100644 index 0000000..5f29cff --- /dev/null +++ b/lib/DateTime/interfaces.py @@ -0,0 +1,375 @@ +############################################################################## +# +# Copyright (c) 2005 Zope Foundation and Contributors. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +from zope.interface import Interface + + +class DateTimeError(Exception): + pass + + +class SyntaxError(DateTimeError): + pass + + +class DateError(DateTimeError): + pass + + +class TimeError(DateTimeError): + pass + + +class IDateTime(Interface): + # Conversion and comparison methods + + #TODO determine whether this method really is part of the public API + def localZone(ltm=None): + '''Returns the time zone on the given date. The time zone + can change according to daylight savings.''' + + def timeTime(): + """Return the date/time as a floating-point number in UTC, in + the format used by the python time module. Note that it is + possible to create date/time values with DateTime that have no + meaningful value to the time module.""" + + def toZone(z): + """Return a DateTime with the value as the current object, + represented in the indicated timezone.""" + + def isFuture(): + """Return true if this object represents a date/time later + than the time of the call""" + + def isPast(): + """Return true if this object represents a date/time earlier + than the time of the call""" + + def isCurrentYear(): + """Return true if this object represents a date/time that + falls within the current year, in the context of this + object's timezone representation""" + + def isCurrentMonth(): + """Return true if this object represents a date/time that + falls within the current month, in the context of this + object's timezone representation""" + + def isCurrentDay(): + """Return true if this object represents a date/time that + falls within the current day, in the context of this object's + timezone representation""" + + def isCurrentHour(): + """Return true if this object represents a date/time that + falls within the current hour, in the context of this object's + timezone representation""" + + def isCurrentMinute(): + """Return true if this object represents a date/time that + falls within the current minute, in the context of this + object's timezone representation""" + + def isLeapYear(): + """Return true if the current year (in the context of the + object's timezone) is a leap year""" + + def earliestTime(): + """Return a new DateTime object that represents the earliest + possible time (in whole seconds) that still falls within the + current object's day, in the object's timezone context""" + + def latestTime(): + """Return a new DateTime object that represents the latest + possible time (in whole seconds) that still falls within the + current object's day, in the object's timezone context""" + + def greaterThan(t): + """Compare this DateTime object to another DateTime object OR + a floating point number such as that which is returned by the + python time module. Returns true if the object represents a + date/time greater than the specified DateTime or time module + style time. Revised to give more correct results through + comparison of long integer milliseconds.""" + + __gt__ = greaterThan + + def greaterThanEqualTo(t): + """Compare this DateTime object to another DateTime object OR + a floating point number such as that which is returned by the + python time module. Returns true if the object represents a + date/time greater than or equal to the specified DateTime or + time module style time. Revised to give more correct results + through comparison of long integer milliseconds.""" + + __ge__ = greaterThanEqualTo + + def equalTo(t): + """Compare this DateTime object to another DateTime object OR + a floating point number such as that which is returned by the + python time module. Returns true if the object represents a + date/time equal to the specified DateTime or time module style + time. Revised to give more correct results through comparison + of long integer milliseconds.""" + + __eq__ = equalTo + + def notEqualTo(t): + """Compare this DateTime object to another DateTime object OR + a floating point number such as that which is returned by the + python time module. Returns true if the object represents a + date/time not equal to the specified DateTime or time module + style time. Revised to give more correct results through + comparison of long integer milliseconds.""" + + __ne__ = notEqualTo + + def lessThan(t): + """Compare this DateTime object to another DateTime object OR + a floating point number such as that which is returned by the + python time module. Returns true if the object represents a + date/time less than the specified DateTime or time module + style time. Revised to give more correct results through + comparison of long integer milliseconds.""" + + __lt__ = lessThan + + def lessThanEqualTo(t): + """Compare this DateTime object to another DateTime object OR + a floating point number such as that which is returned by the + python time module. Returns true if the object represents a + date/time less than or equal to the specified DateTime or time + module style time. Revised to give more correct results + through comparison of long integer milliseconds.""" + + __le__ = lessThanEqualTo + + # Component access + + def parts(): + """Return a tuple containing the calendar year, month, day, + hour, minute second and timezone of the object""" + + def timezone(): + """Return the timezone in which the object is represented.""" + + def tzoffset(): + """Return the timezone offset for the objects timezone.""" + + def year(): + """Return the calendar year of the object""" + + def month(): + """Return the month of the object as an integer""" + + def Month(): + """Return the full month name""" + + def aMonth(): + """Return the abreviated month name.""" + + def Mon(): + """Compatibility: see aMonth""" + + def pMonth(): + """Return the abreviated (with period) month name.""" + + def Mon_(): + """Compatibility: see pMonth""" + + def day(): + """Return the integer day""" + + def Day(): + """Return the full name of the day of the week""" + + def DayOfWeek(): + """Compatibility: see Day""" + + def dayOfYear(): + """Return the day of the year, in context of the timezone + representation of the object""" + + def aDay(): + """Return the abreviated name of the day of the week""" + + def pDay(): + """Return the abreviated (with period) name of the day of the + week""" + + def Day_(): + """Compatibility: see pDay""" + + def dow(): + """Return the integer day of the week, where sunday is 0""" + + def dow_1(): + """Return the integer day of the week, where sunday is 1""" + + def h_12(): + """Return the 12-hour clock representation of the hour""" + + def h_24(): + """Return the 24-hour clock representation of the hour""" + + def ampm(): + """Return the appropriate time modifier (am or pm)""" + + def hour(): + """Return the 24-hour clock representation of the hour""" + + def minute(): + """Return the minute""" + + def second(): + """Return the second""" + + def millis(): + """Return the millisecond since the epoch in GMT.""" + + def strftime(format): + """Format the date/time using the *current timezone representation*.""" + + # General formats from previous DateTime + + def Date(): + """Return the date string for the object.""" + + def Time(): + """Return the time string for an object to the nearest second.""" + + def TimeMinutes(): + """Return the time string for an object not showing seconds.""" + + def AMPM(): + """Return the time string for an object to the nearest second.""" + + def AMPMMinutes(): + """Return the time string for an object not showing seconds.""" + + def PreciseTime(): + """Return the time string for the object.""" + + def PreciseAMPM(): + """Return the time string for the object.""" + + def yy(): + """Return calendar year as a 2 digit string""" + + def mm(): + """Return month as a 2 digit string""" + + def dd(): + """Return day as a 2 digit string""" + + def rfc822(): + """Return the date in RFC 822 format""" + + # New formats + + def fCommon(): + """Return a string representing the object's value in the + format: March 1, 1997 1:45 pm""" + + def fCommonZ(): + """Return a string representing the object's value in the + format: March 1, 1997 1:45 pm US/Eastern""" + + def aCommon(): + """Return a string representing the object's value in the + format: Mar 1, 1997 1:45 pm""" + + def aCommonZ(): + """Return a string representing the object's value in the + format: Mar 1, 1997 1:45 pm US/Eastern""" + + def pCommon(): + """Return a string representing the object's value in the + format: Mar. 1, 1997 1:45 pm""" + + def pCommonZ(): + """Return a string representing the object's value + in the format: Mar. 1, 1997 1:45 pm US/Eastern""" + + def ISO(): + """Return the object in ISO standard format. Note: this is + *not* ISO 8601-format! See the ISO8601 and HTML4 methods below + for ISO 8601-compliant output + + Dates are output as: YYYY-MM-DD HH:MM:SS + """ + + def ISO8601(): + """Return the object in ISO 8601-compatible format containing + the date, time with seconds-precision and the time zone + identifier - see http://www.w3.org/TR/NOTE-datetime + + Dates are output as: YYYY-MM-DDTHH:MM:SSTZD + T is a literal character. + TZD is Time Zone Designator, format +HH:MM or -HH:MM + + The HTML4 method below offers the same formatting, but + converts to UTC before returning the value and sets the TZD"Z" + """ + + def HTML4(): + """Return the object in the format used in the HTML4.0 + specification, one of the standard forms in ISO8601. See + http://www.w3.org/TR/NOTE-datetime + + Dates are output as: YYYY-MM-DDTHH:MM:SSZ + T, Z are literal characters. + The time is in UTC. + """ + + def JulianDay(): + """Return the Julian day according to + http://www.tondering.dk/claus/cal/node3.html#sec-calcjd + """ + + def week(): + """Return the week number according to ISO + see http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 + """ + + # Python operator and conversion API + + def __add__(other): + """A DateTime may be added to a number and a number may be + added to a DateTime; two DateTimes cannot be added.""" + + __radd__ = __add__ + + def __sub__(other): + """Either a DateTime or a number may be subtracted from a + DateTime, however, a DateTime may not be subtracted from a + number.""" + + def __repr__(): + """Convert a DateTime to a string that looks like a Python + expression.""" + + def __str__(): + """Convert a DateTime to a string.""" + + def __hash__(): + """Compute a hash value for a DateTime""" + + def __int__(): + """Convert to an integer number of seconds since the epoch (gmt)""" + + def __long__(): + """Convert to a long-int number of seconds since the epoch (gmt)""" + + def __float__(): + """Convert to floating-point number of seconds since the epoch (gmt)""" diff --git a/lib/DateTime/pytz.txt b/lib/DateTime/pytz.txt new file mode 100644 index 0000000..33de811 --- /dev/null +++ b/lib/DateTime/pytz.txt @@ -0,0 +1,192 @@ +Pytz Support +============ + +Allows the pytz package to be used for time zone information. The +advantage of using pytz is that it has a more complete and up to date +time zone and daylight savings time database. + +Usage +----- +You don't have to do anything special to make it work. + + >>> from DateTime import DateTime, Timezones + >>> d = DateTime('March 11, 2007 US/Eastern') + +Daylight Savings +---------------- +In 2007 daylight savings time in the US was changed. The Energy Policy +Act of 2005 mandates that DST will start on the second Sunday in March +and end on the first Sunday in November. + +In 2007, the start and stop dates are March 11 and November 4, +respectively. These dates are different from previous DST start and +stop dates. In 2006, the dates were the first Sunday in April (April +2, 2006) and the last Sunday in October (October 29, 2006). + +Let's make sure that DateTime can deal with this, since the primary +motivation to use pytz for time zone information is the fact that it +is kept up to date with daylight savings changes. + + >>> DateTime('March 11, 2007 US/Eastern').tzoffset() + -18000 + >>> DateTime('March 12, 2007 US/Eastern').tzoffset() + -14400 + >>> DateTime('November 4, 2007 US/Eastern').tzoffset() + -14400 + >>> DateTime('November 5, 2007 US/Eastern').tzoffset() + -18000 + +Let's compare this to 2006. + + >>> DateTime('April 2, 2006 US/Eastern').tzoffset() + -18000 + >>> DateTime('April 3, 2006 US/Eastern').tzoffset() + -14400 + >>> DateTime('October 29, 2006 US/Eastern').tzoffset() + -14400 + >>> DateTime('October 30, 2006 US/Eastern').tzoffset() + -18000 + +Time Zones +--------- +DateTime can use pytz's large database of time zones. Here are some +examples: + + >>> d = DateTime('Pacific/Kwajalein') + >>> d = DateTime('America/Shiprock') + >>> d = DateTime('Africa/Ouagadougou') + +Of course pytz doesn't know about everything. + + >>> from DateTime.interfaces import SyntaxError + >>> try: + ... d = DateTime('July 21, 1969 Moon/Eastern') + ... print('fail') + ... except SyntaxError: + ... print('ok') + ok + +You can still use zone names that DateTime defines that aren't part of +the pytz database. + + >>> d = DateTime('eet') + >>> d = DateTime('iceland') + +These time zones use DateTimes database. So it's preferable to use the +official time zone name. + +One trickiness is that DateTime supports some zone name +abbreviations. Some of these map to pytz names, so these abbreviations +will give you time zone date from pytz. Notable among abbreviations +that work this way are 'est', 'cst', 'mst', and 'pst'. + +Let's verify that 'est' picks up the 2007 daylight savings time changes. + + >>> DateTime('March 11, 2007 est').tzoffset() + -18000 + >>> DateTime('March 12, 2007 est').tzoffset() + -14400 + >>> DateTime('November 4, 2007 est').tzoffset() + -14400 + >>> DateTime('November 5, 2007 est').tzoffset() + -18000 + +You can get a list of time zones supported by calling the Timezones() function. + + >>> Timezones() #doctest: +ELLIPSIS + ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', ...] + +Note that you can mess with this list without hurting things. + + >>> t = Timezones() + >>> t.remove('US/Eastern') + >>> d = DateTime('US/Eastern') + + +Internal Components +------------------- + +The following are tests of internal components. + +Cache +~~~~~ + +The DateTime class uses a new time zone cache. + + >>> from DateTime.DateTime import _TZINFO + >>> _TZINFO #doctest: +ELLIPSIS + + +The cache maps time zone names to time zone instances. + + >>> cache = _TZINFO + >>> tz = cache['GMT+730'] + >>> tz = cache['US/Mountain'] + +The cache also must provide a few attributes for use by the DateTime +class. + +The _zlst attribute is a list of supported time zone names. + + >>> cache._zlst #doctest: +ELLIPSIS + ['Africa/Abidjan'... 'Africa/Accra'... 'IDLE'... 'NZST'... 'NZT'...] + +The _zidx attribute is a list of lower-case and possibly abbreviated +time zone names that can be mapped to offical zone names. + + >>> 'australia/yancowinna' in cache._zidx + True + >>> 'europe/isle_of_man' in cache._zidx + True + >>> 'gmt+0500' in cache._zidx + True + +Note that there are more items in _zidx than in _zlst since there are +multiple names for some time zones. + + >>> len(cache._zidx) > len(cache._zlst) + True + +Each entry in _zlst should also be present in _zidx in lower case form. + + >>> for name in cache._zlst: + ... if not name.lower() in cache._zidx: + ... print("Error %s not in _zidx" % name.lower()) + +The _zmap attribute maps the names in _zidx to official names in _zlst. + + >>> cache._zmap['africa/abidjan'] + 'Africa/Abidjan' + >>> cache._zmap['gmt+1'] + 'GMT+1' + >>> cache._zmap['gmt+0100'] + 'GMT+1' + >>> cache._zmap['utc'] + 'UTC' + +Let's make sure that _zmap and _zidx agree. + + >>> idx = set(cache._zidx) + >>> keys = set(cache._zmap.keys()) + >>> idx == keys + True + +Timezone objects +~~~~~~~~~~~~~~~~ +The timezone instances have only one public method info(). It returns +a tuple of (offset, is_dst, name). The method takes a timestamp, which +is used to determine dst information. + + >>> t1 = DateTime('November 4, 00:00 2007 US/Mountain').timeTime() + >>> t2 = DateTime('November 4, 02:00 2007 US/Mountain').timeTime() + >>> tz.info(t1) + (-21600, 1, 'MDT') + >>> tz.info(t2) + (-25200, 0, 'MST') + +If you don't pass any arguments to info it provides daylight savings +time information as of today. + + >>> tz.info() in ((-21600, 1, 'MDT'), (-25200, 0, 'MST')) + True + diff --git a/lib/DateTime/pytz_support.py b/lib/DateTime/pytz_support.py new file mode 100644 index 0000000..8cfbfc5 --- /dev/null +++ b/lib/DateTime/pytz_support.py @@ -0,0 +1,259 @@ +############################################################################## +# +# Copyright (c) 2007 Zope Foundation and Contributors. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +from datetime import datetime, timedelta + +import pytz +import pytz.reference +from pytz.tzinfo import StaticTzInfo, memorized_timedelta + +from .interfaces import DateTimeError + +EPOCH = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc) + +_numeric_timezone_data = { + 'GMT': ('GMT', 0, 1, [], '', [(0, 0, 0)], 'GMT\000'), + 'GMT+0': ('GMT+0', 0, 1, [], '', [(0, 0, 0)], 'GMT+0000\000'), + 'GMT+1': ('GMT+1', 0, 1, [], '', [(3600, 0, 0)], 'GMT+0100\000'), + 'GMT+2': ('GMT+2', 0, 1, [], '', [(7200, 0, 0)], 'GMT+0200\000'), + 'GMT+3': ('GMT+3', 0, 1, [], '', [(10800, 0, 0)], 'GMT+0300\000'), + 'GMT+4': ('GMT+4', 0, 1, [], '', [(14400, 0, 0)], 'GMT+0400\000'), + 'GMT+5': ('GMT+5', 0, 1, [], '', [(18000, 0, 0)], 'GMT+0500\000'), + 'GMT+6': ('GMT+6', 0, 1, [], '', [(21600, 0, 0)], 'GMT+0600\000'), + 'GMT+7': ('GMT+7', 0, 1, [], '', [(25200, 0, 0)], 'GMT+0700\000'), + 'GMT+8': ('GMT+8', 0, 1, [], '', [(28800, 0, 0)], 'GMT+0800\000'), + 'GMT+9': ('GMT+9', 0, 1, [], '', [(32400, 0, 0)], 'GMT+0900\000'), + 'GMT+10': ('GMT+10', 0, 1, [], '', [(36000, 0, 0)], 'GMT+1000\000'), + 'GMT+11': ('GMT+11', 0, 1, [], '', [(39600, 0, 0)], 'GMT+1100\000'), + 'GMT+12': ('GMT+12', 0, 1, [], '', [(43200, 0, 0)], 'GMT+1200\000'), + 'GMT+13': ('GMT+13', 0, 1, [], '', [(46800, 0, 0)], 'GMT+1300\000'), + + 'GMT-1': ('GMT-1', 0, 1, [], '', [(-3600, 0, 0)], 'GMT-0100\000'), + 'GMT-2': ('GMT-2', 0, 1, [], '', [(-7200, 0, 0)], 'GMT-0200\000'), + 'GMT-3': ('GMT-3', 0, 1, [], '', [(-10800, 0, 0)], 'GMT-0300\000'), + 'GMT-4': ('GMT-4', 0, 1, [], '', [(-14400, 0, 0)], 'GMT-0400\000'), + 'GMT-5': ('GMT-5', 0, 1, [], '', [(-18000, 0, 0)], 'GMT-0500\000'), + 'GMT-6': ('GMT-6', 0, 1, [], '', [(-21600, 0, 0)], 'GMT-0600\000'), + 'GMT-7': ('GMT-7', 0, 1, [], '', [(-25200, 0, 0)], 'GMT-0700\000'), + 'GMT-8': ('GMT-8', 0, 1, [], '', [(-28800, 0, 0)], 'GMT-0800\000'), + 'GMT-9': ('GMT-9', 0, 1, [], '', [(-32400, 0, 0)], 'GMT-0900\000'), + 'GMT-10': ('GMT-10', 0, 1, [], '', [(-36000, 0, 0)], 'GMT-1000\000'), + 'GMT-11': ('GMT-11', 0, 1, [], '', [(-39600, 0, 0)], 'GMT-1100\000'), + 'GMT-12': ('GMT-12', 0, 1, [], '', [(-43200, 0, 0)], 'GMT-1200\000'), + + 'GMT+0130': ('GMT+0130', 0, 1, [], '', [(5400, 0, 0)], 'GMT+0130\000'), + 'GMT+0230': ('GMT+0230', 0, 1, [], '', [(9000, 0, 0)], 'GMT+0230\000'), + 'GMT+0330': ('GMT+0330', 0, 1, [], '', [(12600, 0, 0)], 'GMT+0330\000'), + 'GMT+0430': ('GMT+0430', 0, 1, [], '', [(16200, 0, 0)], 'GMT+0430\000'), + 'GMT+0530': ('GMT+0530', 0, 1, [], '', [(19800, 0, 0)], 'GMT+0530\000'), + 'GMT+0630': ('GMT+0630', 0, 1, [], '', [(23400, 0, 0)], 'GMT+0630\000'), + 'GMT+0730': ('GMT+0730', 0, 1, [], '', [(27000, 0, 0)], 'GMT+0730\000'), + 'GMT+0830': ('GMT+0830', 0, 1, [], '', [(30600, 0, 0)], 'GMT+0830\000'), + 'GMT+0930': ('GMT+0930', 0, 1, [], '', [(34200, 0, 0)], 'GMT+0930\000'), + 'GMT+1030': ('GMT+1030', 0, 1, [], '', [(37800, 0, 0)], 'GMT+1030\000'), + 'GMT+1130': ('GMT+1130', 0, 1, [], '', [(41400, 0, 0)], 'GMT+1130\000'), + 'GMT+1230': ('GMT+1230', 0, 1, [], '', [(45000, 0, 0)], 'GMT+1230\000'), + + 'GMT-0130': ('GMT-0130', 0, 1, [], '', [(-5400, 0, 0)], 'GMT-0130\000'), + 'GMT-0230': ('GMT-0230', 0, 1, [], '', [(-9000, 0, 0)], 'GMT-0230\000'), + 'GMT-0330': ('GMT-0330', 0, 1, [], '', [(-12600, 0, 0)], 'GMT-0330\000'), + 'GMT-0430': ('GMT-0430', 0, 1, [], '', [(-16200, 0, 0)], 'GMT-0430\000'), + 'GMT-0530': ('GMT-0530', 0, 1, [], '', [(-19800, 0, 0)], 'GMT-0530\000'), + 'GMT-0630': ('GMT-0630', 0, 1, [], '', [(-23400, 0, 0)], 'GMT-0630\000'), + 'GMT-0730': ('GMT-0730', 0, 1, [], '', [(-27000, 0, 0)], 'GMT-0730\000'), + 'GMT-0830': ('GMT-0830', 0, 1, [], '', [(-30600, 0, 0)], 'GMT-0830\000'), + 'GMT-0930': ('GMT-0930', 0, 1, [], '', [(-34200, 0, 0)], 'GMT-0930\000'), + 'GMT-1030': ('GMT-1030', 0, 1, [], '', [(-37800, 0, 0)], 'GMT-1030\000'), + 'GMT-1130': ('GMT-1130', 0, 1, [], '', [(-41400, 0, 0)], 'GMT-1130\000'), + 'GMT-1230': ('GMT-1230', 0, 1, [], '', [(-45000, 0, 0)], 'GMT-1230\000'), +} + +# These are the timezones not in pytz.common_timezones +_old_zlst = [ + 'AST', 'AT', 'BST', 'BT', 'CCT', + 'CET', 'CST', 'Cuba', 'EADT', 'EAST', + 'EEST', 'EET', 'EST', 'Egypt', 'FST', + 'FWT', 'GB-Eire', 'GMT+0100', 'GMT+0130', 'GMT+0200', + 'GMT+0230', 'GMT+0300', 'GMT+0330', 'GMT+0400', 'GMT+0430', + 'GMT+0500', 'GMT+0530', 'GMT+0600', 'GMT+0630', 'GMT+0700', + 'GMT+0730', 'GMT+0800', 'GMT+0830', 'GMT+0900', 'GMT+0930', + 'GMT+1', 'GMT+1000', 'GMT+1030', 'GMT+1100', 'GMT+1130', + 'GMT+1200', 'GMT+1230', 'GMT+1300', 'GMT-0100', 'GMT-0130', + 'GMT-0200', 'GMT-0300', 'GMT-0400', 'GMT-0500', 'GMT-0600', + 'GMT-0630', 'GMT-0700', 'GMT-0730', 'GMT-0800', 'GMT-0830', + 'GMT-0900', 'GMT-0930', 'GMT-1000', 'GMT-1030', 'GMT-1100', + 'GMT-1130', 'GMT-1200', 'GMT-1230', 'GST', 'Greenwich', + 'Hongkong', 'IDLE', 'IDLW', 'Iceland', 'Iran', + 'Israel', 'JST', 'Jamaica', 'Japan', 'MEST', + 'MET', 'MEWT', 'MST', 'NT', 'NZDT', + 'NZST', 'NZT', 'PST', 'Poland', 'SST', + 'SWT', 'Singapore', 'Turkey', 'UCT', 'UT', + 'Universal', 'WADT', 'WAST', 'WAT', 'WET', + 'ZP4', 'ZP5', 'ZP6', +] + +_old_zmap = { + 'aest': 'GMT+10', 'aedt': 'GMT+11', + 'aus eastern standard time': 'GMT+10', + 'sydney standard time': 'GMT+10', + 'tasmania standard time': 'GMT+10', + 'e. australia standard time': 'GMT+10', + 'aus central standard time': 'GMT+0930', + 'cen. australia standard time': 'GMT+0930', + 'w. australia standard time': 'GMT+8', + + 'central europe standard time': 'GMT+1', + 'eastern standard time': 'US/Eastern', + 'us eastern standard time': 'US/Eastern', + 'central standard time': 'US/Central', + 'mountain standard time': 'US/Mountain', + 'pacific standard time': 'US/Pacific', + 'mst': 'US/Mountain', 'pst': 'US/Pacific', + 'cst': 'US/Central', 'est': 'US/Eastern', + + 'gmt+0000': 'GMT+0', 'gmt+0': 'GMT+0', + + 'gmt+0100': 'GMT+1', 'gmt+0200': 'GMT+2', 'gmt+0300': 'GMT+3', + 'gmt+0400': 'GMT+4', 'gmt+0500': 'GMT+5', 'gmt+0600': 'GMT+6', + 'gmt+0700': 'GMT+7', 'gmt+0800': 'GMT+8', 'gmt+0900': 'GMT+9', + 'gmt+1000': 'GMT+10', 'gmt+1100': 'GMT+11', 'gmt+1200': 'GMT+12', + 'gmt+1300': 'GMT+13', + 'gmt-0100': 'GMT-1', 'gmt-0200': 'GMT-2', 'gmt-0300': 'GMT-3', + 'gmt-0400': 'GMT-4', 'gmt-0500': 'GMT-5', 'gmt-0600': 'GMT-6', + 'gmt-0700': 'GMT-7', 'gmt-0800': 'GMT-8', 'gmt-0900': 'GMT-9', + 'gmt-1000': 'GMT-10', 'gmt-1100': 'GMT-11', 'gmt-1200': 'GMT-12', + + 'gmt+1': 'GMT+1', 'gmt+2': 'GMT+2', 'gmt+3': 'GMT+3', + 'gmt+4': 'GMT+4', 'gmt+5': 'GMT+5', 'gmt+6': 'GMT+6', + 'gmt+7': 'GMT+7', 'gmt+8': 'GMT+8', 'gmt+9': 'GMT+9', + 'gmt+10': 'GMT+10', 'gmt+11': 'GMT+11', 'gmt+12': 'GMT+12', + 'gmt+13': 'GMT+13', + 'gmt-1': 'GMT-1', 'gmt-2': 'GMT-2', 'gmt-3': 'GMT-3', + 'gmt-4': 'GMT-4', 'gmt-5': 'GMT-5', 'gmt-6': 'GMT-6', + 'gmt-7': 'GMT-7', 'gmt-8': 'GMT-8', 'gmt-9': 'GMT-9', + 'gmt-10': 'GMT-10', 'gmt-11': 'GMT-11', 'gmt-12': 'GMT-12', + + 'gmt+130': 'GMT+0130', 'gmt+0130': 'GMT+0130', + 'gmt+230': 'GMT+0230', 'gmt+0230': 'GMT+0230', + 'gmt+330': 'GMT+0330', 'gmt+0330': 'GMT+0330', + 'gmt+430': 'GMT+0430', 'gmt+0430': 'GMT+0430', + 'gmt+530': 'GMT+0530', 'gmt+0530': 'GMT+0530', + 'gmt+630': 'GMT+0630', 'gmt+0630': 'GMT+0630', + 'gmt+730': 'GMT+0730', 'gmt+0730': 'GMT+0730', + 'gmt+830': 'GMT+0830', 'gmt+0830': 'GMT+0830', + 'gmt+930': 'GMT+0930', 'gmt+0930': 'GMT+0930', + 'gmt+1030': 'GMT+1030', + 'gmt+1130': 'GMT+1130', + 'gmt+1230': 'GMT+1230', + + 'gmt-130': 'GMT-0130', 'gmt-0130': 'GMT-0130', + 'gmt-230': 'GMT-0230', 'gmt-0230': 'GMT-0230', + 'gmt-330': 'GMT-0330', 'gmt-0330': 'GMT-0330', + 'gmt-430': 'GMT-0430', 'gmt-0430': 'GMT-0430', + 'gmt-530': 'GMT-0530', 'gmt-0530': 'GMT-0530', + 'gmt-630': 'GMT-0630', 'gmt-0630': 'GMT-0630', + 'gmt-730': 'GMT-0730', 'gmt-0730': 'GMT-0730', + 'gmt-830': 'GMT-0830', 'gmt-0830': 'GMT-0830', + 'gmt-930': 'GMT-0930', 'gmt-0930': 'GMT-0930', + 'gmt-1030': 'GMT-1030', + 'gmt-1130': 'GMT-1130', + 'gmt-1230': 'GMT-1230', + + 'ut': 'Universal', + 'bst': 'GMT+1', 'mest': 'GMT+2', 'sst': 'GMT+2', + 'fst': 'GMT+2', 'wadt': 'GMT+8', 'eadt': 'GMT+11', 'nzdt': 'GMT+13', + 'wet': 'GMT', 'wat': 'GMT-1', 'at': 'GMT-2', 'ast': 'GMT-4', + 'nt': 'GMT-11', 'idlw': 'GMT-12', 'cet': 'GMT+1', 'cest': 'GMT+2', + 'met': 'GMT+1', + 'mewt': 'GMT+1', 'swt': 'GMT+1', 'fwt': 'GMT+1', 'eet': 'GMT+2', + 'eest': 'GMT+3', + 'bt': 'GMT+3', 'zp4': 'GMT+4', 'zp5': 'GMT+5', 'zp6': 'GMT+6', + 'wast': 'GMT+7', 'cct': 'GMT+8', 'jst': 'GMT+9', 'east': 'GMT+10', + 'gst': 'GMT+10', 'nzt': 'GMT+12', 'nzst': 'GMT+12', 'idle': 'GMT+12', + 'ret': 'GMT+4', 'ist': 'GMT+0530', 'edt': 'GMT-4', + +} + + +# some timezone definitions of the "-0400" are not working +# when upgrading +for hour in range(0, 13): + hour = hour + fhour = str(hour) + if len(fhour) == 1: + fhour = '0' + fhour + _old_zmap['-%s00' % fhour] = 'GMT-%i' % hour + _old_zmap['+%s00' % fhour] = 'GMT+%i' % hour + + +def _static_timezone_factory(data): + zone = data[0] + cls = type(zone, (StaticTzInfo,), dict( + zone=zone, + _utcoffset=memorized_timedelta(data[5][0][0]), + _tzname=data[6][:-1])) # strip the trailing null + return cls() + +_numeric_timezones = dict((key, _static_timezone_factory(data)) + for key, data in _numeric_timezone_data.items()) + + +class Timezone: + """ + Timezone information returned by PytzCache.__getitem__ + Adapts datetime.tzinfo object to DateTime._timezone interface + """ + def __init__(self, tzinfo): + self.tzinfo = tzinfo + + def info(self, t=None): + if t is None: + dt = datetime.utcnow().replace(tzinfo=pytz.utc) + else: + # can't use utcfromtimestamp past 2038 + dt = EPOCH + timedelta(0, t) + + # need to normalize tzinfo for the datetime to deal with + # daylight savings time. + normalized_dt = self.tzinfo.normalize(dt.astimezone(self.tzinfo)) + normalized_tzinfo = normalized_dt.tzinfo + + offset = normalized_tzinfo.utcoffset(normalized_dt) + secs = offset.days * 24 * 60 * 60 + offset.seconds + dst = normalized_tzinfo.dst(normalized_dt) + if dst == timedelta(0): + is_dst = 0 + else: + is_dst = 1 + return secs, is_dst, normalized_tzinfo.tzname(normalized_dt) + + +class PytzCache: + """ + Reimplementation of the DateTime._cache class that uses for timezone info + """ + + _zlst = pytz.common_timezones + _old_zlst # used by DateTime.TimeZones + _zmap = dict((name.lower(), name) for name in pytz.all_timezones) + _zmap.update(_old_zmap) # These must take priority + _zidx = _zmap.keys() + + def __getitem__(self, key): + name = self._zmap.get(key.lower(), key) # fallback to key + try: + return Timezone(pytz.timezone(name)) + except pytz.UnknownTimeZoneError: + try: + return Timezone(_numeric_timezones[name]) + except KeyError: + raise DateTimeError('Unrecognized timezone: %s' % key) diff --git a/lib/DateTime/tests/__init__.py b/lib/DateTime/tests/__init__.py new file mode 100644 index 0000000..e67bcb6 --- /dev/null +++ b/lib/DateTime/tests/__init__.py @@ -0,0 +1,15 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## + +# This file is needed to make this a package. diff --git a/lib/DateTime/tests/julian_testdata.txt b/lib/DateTime/tests/julian_testdata.txt new file mode 100644 index 0000000..386c3da --- /dev/null +++ b/lib/DateTime/tests/julian_testdata.txt @@ -0,0 +1,57 @@ +1970-01-01 (1970, 1, 4) +1970-01-02 (1970, 1, 5) +1970-01-30 (1970, 5, 5) +1970-01-31 (1970, 5, 6) +1970-02-01 (1970, 5, 7) +1970-02-02 (1970, 6, 1) +1970-02-28 (1970, 9, 6) +1970-03-01 (1970, 9, 7) +1970-03-30 (1970, 14, 1) +1970-03-31 (1970, 14, 2) +1970-04-01 (1970, 14, 3) +1970-09-30 (1970, 40, 3) +1970-10-01 (1970, 40, 4) +1970-10-02 (1970, 40, 5) +1970-10-03 (1970, 40, 6) +1970-10-04 (1970, 40, 7) +1970-10-05 (1970, 41, 1) +1971-01-02 (1970, 53, 6) +1971-01-03 (1970, 53, 7) +1971-01-04 (1971, 1, 1) +1971-01-05 (1971, 1, 2) +1971-12-31 (1971, 52, 5) +1972-01-01 (1971, 52, 6) +1972-01-02 (1971, 52, 7) +1972-01-03 (1972, 1, 1) +1972-01-04 (1972, 1, 2) +1972-12-30 (1972, 52, 6) +1972-12-31 (1972, 52, 7) +1973-01-01 (1973, 1, 1) +1973-01-02 (1973, 1, 2) +1973-12-29 (1973, 52, 6) +1973-12-30 (1973, 52, 7) +1973-12-31 (1974, 1, 1) +1974-01-01 (1974, 1, 2) +1998-12-30 (1998, 53, 3) +1998-12-31 (1998, 53, 4) +1999-01-01 (1998, 53, 5) +1999-01-02 (1998, 53, 6) +1999-01-03 (1998, 53, 7) +1999-01-04 (1999, 1, 1) +1999-01-05 (1999, 1, 2) +1999-12-30 (1999, 52, 4) +1999-12-31 (1999, 52, 5) +2000-01-01 (1999, 52, 6) +2000-01-02 (1999, 52, 7) +2000-01-03 (2000, 1, 1) +2000-01-04 (2000, 1, 2) +2000-01-05 (2000, 1, 3) +2000-01-06 (2000, 1, 4) +2000-01-07 (2000, 1, 5) +2000-01-08 (2000, 1, 6) +2000-01-09 (2000, 1, 7) +2000-01-10 (2000, 2, 1) +2019-12-28 (2019, 52, 6) +2019-12-29 (2019, 52, 7) +2019-12-30 (2020, 1, 1) +2019-12-31 (2020, 1, 2) diff --git a/lib/DateTime/tests/test_datetime.py b/lib/DateTime/tests/test_datetime.py new file mode 100644 index 0000000..7172adb --- /dev/null +++ b/lib/DateTime/tests/test_datetime.py @@ -0,0 +1,686 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## + +from datetime import date, datetime, tzinfo, timedelta +import math +import platform +import os +import sys +import time +import unittest + +import pytz + +from DateTime.DateTime import _findLocalTimeZoneName +from DateTime import DateTime + +if sys.version_info > (3, ): + import pickle + unicode = str + PY3K = True +else: + import cPickle as pickle + PY3K = False + +try: + __file__ +except NameError: + f = sys.argv[0] +else: + f = __file__ + +IS_PYPY = getattr(platform, 'python_implementation', lambda: None)() == 'PyPy' + +DATADIR = os.path.dirname(os.path.abspath(f)) +del f + +ZERO = timedelta(0) + + +class FixedOffset(tzinfo): + """Fixed offset in minutes east from UTC.""" + + def __init__(self, offset, name): + self.__offset = timedelta(minutes=offset) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return ZERO + + +class DateTimeTests(unittest.TestCase): + + def _compare(self, dt1, dt2): + '''Compares the internal representation of dt1 with + the representation in dt2. Allows sub-millisecond variations. + Primarily for testing.''' + self.assertEqual(round(dt1._t, 3), round(dt2._t, 3)) + self.assertEqual(round(dt1._d, 9), round(dt2._d, 9)) + self.assertEqual(round(dt1.time, 9), round(dt2.time, 9)) + self.assertEqual(dt1.millis(), dt2.millis()) + self.assertEqual(dt1._micros, dt2._micros) + + def testBug1203(self): + # 01:59:60 occurred in old DateTime + dt = DateTime(7200, 'GMT') + self.assertTrue(str(dt).find('60') < 0, dt) + + def testDSTInEffect(self): + # Checks GMT offset for a DST date in the US/Eastern time zone + dt = DateTime(2000, 5, 9, 15, 0, 0, 'US/Eastern') + self.assertEqual(dt.toZone('GMT').hour(), 19, + (dt, dt.toZone('GMT'))) + + def testDSTNotInEffect(self): + # Checks GMT offset for a non-DST date in the US/Eastern time zone + dt = DateTime(2000, 11, 9, 15, 0, 0, 'US/Eastern') + self.assertEqual(dt.toZone('GMT').hour(), 20, + (dt, dt.toZone('GMT'))) + + def testAddPrecision(self): + # Precision of serial additions + dt = DateTime() + self.assertEqual(str(dt + 0.10 + 3.14 + 6.76 - 10), str(dt), + dt) + + def testConstructor3(self): + # Constructor from date/time string + dt = DateTime() + dt1s = '%d/%d/%d %d:%d:%f %s' % ( + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second(), + dt.timezone()) + dt1 = DateTime(dt1s) + # Compare representations as it's the + # only way to compare the dates to the same accuracy + self.assertEqual(repr(dt), repr(dt1)) + + def testConstructor4(self): + # Constructor from time float + dt = DateTime() + dt1 = DateTime(float(dt)) + self._compare(dt, dt1) + + def testConstructor5(self): + # Constructor from time float and timezone + dt = DateTime() + dt1 = DateTime(float(dt), dt.timezone()) + self.assertEqual(str(dt), str(dt1), (dt, dt1)) + dt1 = DateTime(float(dt), unicode(dt.timezone())) + self.assertEqual(str(dt), str(dt1), (dt, dt1)) + + def testConstructor6(self): + # Constructor from year and julian date + # This test must normalize the time zone, or it *will* break when + # DST changes! + dt1 = DateTime(2000, 5.500000578705) + dt = DateTime('2000/1/5 12:00:00.050 pm %s' % dt1.localZone()) + self._compare(dt, dt1) + + def testConstructor7(self): + # Constructor from parts + dt = DateTime() + dt1 = DateTime( + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second(), + dt.timezone()) + # Compare representations as it's the + # only way to compare the dates to the same accuracy + self.assertEqual(repr(dt), repr(dt1)) + + def testDayOfWeek(self): + # Compare to the datetime.date value to make it locale independent + expected = date(2000, 6, 16).strftime('%A') + # strftime() used to always be passed a day of week of 0 + dt = DateTime('2000/6/16') + s = dt.strftime('%A') + self.assertEqual(s, expected, (dt, s)) + + def testOldDate(self): + # Fails when an 1800 date is displayed with negative signs + dt = DateTime('1830/5/6 12:31:46.213 pm') + dt1 = dt.toZone('GMT+6') + self.assertTrue(str(dt1).find('-') < 0, (dt, dt1)) + + def testSubtraction(self): + # Reconstruction of a DateTime from its parts, with subtraction + # this also tests the accuracy of addition and reconstruction + dt = DateTime() + dt1 = dt - 3.141592653 + dt2 = DateTime( + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second()) + dt3 = dt2 - 3.141592653 + self.assertEqual(dt1, dt3, (dt, dt1, dt2, dt3)) + + def testTZ1add(self): + # Time zone manipulation: add to a date + dt = DateTime('1997/3/8 1:45am GMT-4') + dt1 = DateTime('1997/3/9 1:45pm GMT+8') + self.assertTrue((dt + 1.0).equalTo(dt1)) + + def testTZ1sub(self): + # Time zone manipulation: subtract from a date + dt = DateTime('1997/3/8 1:45am GMT-4') + dt1 = DateTime('1997/3/9 1:45pm GMT+8') + self.assertTrue((dt1 - 1.0).equalTo(dt)) + + def testTZ1diff(self): + # Time zone manipulation: diff two dates + dt = DateTime('1997/3/8 1:45am GMT-4') + dt1 = DateTime('1997/3/9 1:45pm GMT+8') + self.assertEqual(dt1 - dt, 1.0, (dt, dt1)) + + def test_compare_methods(self): + # Compare two dates using several methods + dt = DateTime('1997/1/1') + dt1 = DateTime('1997/2/2') + self.assertTrue(dt1.greaterThan(dt)) + self.assertTrue(dt1.greaterThanEqualTo(dt)) + self.assertTrue(dt.lessThan(dt1)) + self.assertTrue(dt.lessThanEqualTo(dt1)) + self.assertTrue(dt.notEqualTo(dt1)) + self.assertFalse(dt.equalTo(dt1)) + + def test_compare_methods_none(self): + # Compare a date to None + dt = DateTime('1997/1/1') + self.assertTrue(dt.greaterThan(None)) + self.assertTrue(dt.greaterThanEqualTo(None)) + self.assertFalse(dt.lessThan(None)) + self.assertFalse(dt.lessThanEqualTo(None)) + self.assertTrue(dt.notEqualTo(None)) + self.assertFalse(dt.equalTo(None)) + + def test_pickle(self): + dt = DateTime() + data = pickle.dumps(dt, 1) + new = pickle.loads(data) + for key in DateTime.__slots__: + self.assertEqual(getattr(dt, key), getattr(new, key)) + + def test_pickle_with_tz(self): + dt = DateTime('2002/5/2 8:00am GMT+8') + data = pickle.dumps(dt, 1) + new = pickle.loads(data) + for key in DateTime.__slots__: + self.assertEqual(getattr(dt, key), getattr(new, key)) + + def test_pickle_with_numerical_tz(self): + for dt_str in ('2007/01/02 12:34:56.789 +0300', + '2007/01/02 12:34:56.789 +0430', + '2007/01/02 12:34:56.789 -1234'): + dt = DateTime(dt_str) + data = pickle.dumps(dt, 1) + new = pickle.loads(data) + for key in DateTime.__slots__: + self.assertEqual(getattr(dt, key), getattr(new, key)) + + def test_pickle_with_micros(self): + dt = DateTime('2002/5/2 8:00:14.123 GMT+8') + data = pickle.dumps(dt, 1) + new = pickle.loads(data) + for key in DateTime.__slots__: + self.assertEqual(getattr(dt, key), getattr(new, key)) + + def test_pickle_old(self): + dt = DateTime('2002/5/2 8:00am GMT+0') + data = ('(cDateTime.DateTime\nDateTime\nq\x01Noq\x02}q\x03(U\x05' + '_amonq\x04U\x03Mayq\x05U\x05_adayq\x06U\x03Thuq\x07U\x05_pmonq' + '\x08h\x05U\x05_hourq\tK\x08U\x05_fmonq\nh\x05U\x05_pdayq\x0bU' + '\x04Thu.q\x0cU\x05_fdayq\rU\x08Thursdayq\x0eU\x03_pmq\x0fU\x02amq' + '\x10U\x02_tq\x11GA\xcehy\x00\x00\x00\x00U\x07_minuteq\x12K\x00U' + '\x07_microsq\x13L1020326400000000L\nU\x02_dq\x14G@\xe2\x12j\xaa' + '\xaa\xaa\xabU\x07_secondq\x15G\x00\x00\x00\x00\x00\x00\x00\x00U' + '\x03_tzq\x16U\x05GMT+0q\x17U\x06_monthq\x18K\x05U' + '\x0f_timezone_naiveq\x19I00\nU\x04_dayq\x1aK\x02U\x05_yearq' + '\x1bM\xd2\x07U\x08_nearsecq\x1cG\x00\x00\x00\x00\x00\x00\x00' + '\x00U\x07_pmhourq\x1dK\x08U\n_dayoffsetq\x1eK\x04U\x04timeq' + '\x1fG?\xd5UUUV\x00\x00ub.') + if PY3K: + data = data.encode('latin-1') + new = pickle.loads(data) + for key in DateTime.__slots__: + self.assertEqual(getattr(dt, key), getattr(new, key)) + + def test_pickle_old_without_micros(self): + dt = DateTime('2002/5/2 8:00am GMT+0') + data = ('(cDateTime.DateTime\nDateTime\nq\x01Noq\x02}q\x03(U\x05' + '_amonq\x04U\x03Mayq\x05U\x05_adayq\x06U\x03Thuq\x07U\x05_pmonq' + '\x08h\x05U\x05_hourq\tK\x08U\x05_fmonq\nh\x05U\x05_pdayq\x0bU' + '\x04Thu.q\x0cU\x05_fdayq\rU\x08Thursdayq\x0eU\x03_pmq\x0fU' + '\x02amq\x10U\x02_tq\x11GA\xcehy\x00\x00\x00\x00U\x07_minuteq' + '\x12K\x00U\x02_dq\x13G@\xe2\x12j\xaa\xaa\xaa\xabU\x07_secondq' + '\x14G\x00\x00\x00\x00\x00\x00\x00\x00U\x03_tzq\x15U\x05GMT+0q' + '\x16U\x06_monthq\x17K\x05U\x0f_timezone_naiveq\x18I00\nU' + '\x04_dayq\x19K\x02U\x05_yearq\x1aM\xd2\x07U\x08_nearsecq' + '\x1bG\x00\x00\x00\x00\x00\x00\x00\x00U\x07_pmhourq\x1cK\x08U' + '\n_dayoffsetq\x1dK\x04U\x04timeq\x1eG?\xd5UUUV\x00\x00ub.') + if PY3K: + data = data.encode('latin-1') + new = pickle.loads(data) + for key in DateTime.__slots__: + self.assertEqual(getattr(dt, key), getattr(new, key)) + + def testTZ2(self): + # Time zone manipulation test 2 + dt = DateTime() + dt1 = dt.toZone('GMT') + s = dt.second() + s1 = dt1.second() + self.assertEqual(s, s1, (dt, dt1, s, s1)) + + def testTZDiffDaylight(self): + # Diff dates across daylight savings dates + dt = DateTime('2000/6/8 1:45am US/Eastern') + dt1 = DateTime('2000/12/8 12:45am US/Eastern') + self.assertEqual(dt1 - dt, 183, (dt, dt1, dt1 - dt)) + + def testY10KDate(self): + # Comparison of a Y10K date and a Y2K date + dt = DateTime('10213/09/21') + dt1 = DateTime(2000, 1, 1) + + dsec = (dt.millis() - dt1.millis()) / 1000.0 + ddays = math.floor((dsec / 86400.0) + 0.5) + + self.assertEqual(ddays, 3000000, ddays) + + def test_tzoffset(self): + # Test time-zone given as an offset + + # GMT + dt = DateTime('Tue, 10 Sep 2001 09:41:03 GMT') + self.assertEqual(dt.tzoffset(), 0) + + # Timezone by name, a timezone that hasn't got daylightsaving. + dt = DateTime('Tue, 2 Mar 2001 09:41:03 GMT+3') + self.assertEqual(dt.tzoffset(), 10800) + + # Timezone by name, has daylightsaving but is not in effect. + dt = DateTime('Tue, 21 Jan 2001 09:41:03 PST') + self.assertEqual(dt.tzoffset(), -28800) + + # Timezone by name, with daylightsaving in effect + dt = DateTime('Tue, 24 Aug 2001 09:41:03 PST') + self.assertEqual(dt.tzoffset(), -25200) + + # A negative numerical timezone + dt = DateTime('Tue, 24 Jul 2001 09:41:03 -0400') + self.assertEqual(dt.tzoffset(), -14400) + + # A positive numerical timzone + dt = DateTime('Tue, 6 Dec 1966 01:41:03 +0200') + self.assertEqual(dt.tzoffset(), 7200) + + # A negative numerical timezone with minutes. + dt = DateTime('Tue, 24 Jul 2001 09:41:03 -0637') + self.assertEqual(dt.tzoffset(), -23820) + + # A positive numerical timezone with minutes. + dt = DateTime('Tue, 24 Jul 2001 09:41:03 +0425') + self.assertEqual(dt.tzoffset(), 15900) + + def testISO8601(self): + # ISO8601 reference dates + ref0 = DateTime('2002/5/2 8:00am GMT') + ref1 = DateTime('2002/5/2 8:00am US/Eastern') + ref2 = DateTime('2006/11/6 10:30 GMT') + ref3 = DateTime('2004/06/14 14:30:15 GMT-3') + ref4 = DateTime('2006/01/01 GMT') + + # Basic tests + # Though this is timezone naive and according to specification should + # be interpreted in the local timezone, to preserve backwards + # compatibility with previously expected behaviour. + isoDt = DateTime('2002-05-02T08:00:00') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2002-05-02T08:00:00Z') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2002-05-02T08:00:00+00:00') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2002-05-02T08:00:00-04:00') + self.assertTrue(ref1.equalTo(isoDt)) + isoDt = DateTime('2002-05-02 08:00:00-04:00') + self.assertTrue(ref1.equalTo(isoDt)) + + # Bug 1386: the colon in the timezone offset is optional + isoDt = DateTime('2002-05-02T08:00:00-0400') + self.assertTrue(ref1.equalTo(isoDt)) + + # Bug 2191: date reduced formats + isoDt = DateTime('2006-01-01') + self.assertTrue(ref4.equalTo(isoDt)) + isoDt = DateTime('200601-01') + self.assertTrue(ref4.equalTo(isoDt)) + isoDt = DateTime('20060101') + self.assertTrue(ref4.equalTo(isoDt)) + isoDt = DateTime('2006-01') + self.assertTrue(ref4.equalTo(isoDt)) + isoDt = DateTime('200601') + self.assertTrue(ref4.equalTo(isoDt)) + isoDt = DateTime('2006') + self.assertTrue(ref4.equalTo(isoDt)) + + # Bug 2191: date/time separators are also optional + isoDt = DateTime('20020502T08:00:00') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2002-05-02T080000') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('20020502T080000') + self.assertTrue(ref0.equalTo(isoDt)) + + # Bug 2191: timezones with only one digit for hour + isoDt = DateTime('20020502T080000+0') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('20020502 080000-4') + self.assertTrue(ref1.equalTo(isoDt)) + isoDt = DateTime('20020502T080000-400') + self.assertTrue(ref1.equalTo(isoDt)) + isoDt = DateTime('20020502T080000-4:00') + self.assertTrue(ref1.equalTo(isoDt)) + + # Bug 2191: optional seconds/minutes + isoDt = DateTime('2002-05-02T0800') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2002-05-02T08') + self.assertTrue(ref0.equalTo(isoDt)) + + # Bug 2191: week format + isoDt = DateTime('2002-W18-4T0800') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2002-W184T0800') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2002W18-4T0800') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2002W184T08') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2004-W25-1T14:30:15-03:00') + self.assertTrue(ref3.equalTo(isoDt)) + isoDt = DateTime('2004-W25T14:30:15-03:00') + self.assertTrue(ref3.equalTo(isoDt)) + + # Bug 2191: day of year format + isoDt = DateTime('2002-122T0800') + self.assertTrue(ref0.equalTo(isoDt)) + isoDt = DateTime('2002122T0800') + self.assertTrue(ref0.equalTo(isoDt)) + + # Bug 2191: hours/minutes fractions + isoDt = DateTime('2006-11-06T10.5') + self.assertTrue(ref2.equalTo(isoDt)) + isoDt = DateTime('2006-11-06T10,5') + self.assertTrue(ref2.equalTo(isoDt)) + isoDt = DateTime('20040614T1430.25-3') + self.assertTrue(ref3.equalTo(isoDt)) + isoDt = DateTime('2004-06-14T1430,25-3') + self.assertTrue(ref3.equalTo(isoDt)) + isoDt = DateTime('2004-06-14T14:30.25-3') + self.assertTrue(ref3.equalTo(isoDt)) + isoDt = DateTime('20040614T14:30,25-3') + self.assertTrue(ref3.equalTo(isoDt)) + + # ISO8601 standard format + iso8601_string = '2002-05-02T08:00:00-04:00' + iso8601DT = DateTime(iso8601_string) + self.assertEqual(iso8601_string, iso8601DT.ISO8601()) + + # ISO format with no timezone + isoDt = DateTime('2006-01-01 00:00:00') + self.assertTrue(ref4.equalTo(isoDt)) + + def testJulianWeek(self): + # Check JulianDayWeek function + fn = os.path.join(DATADIR, 'julian_testdata.txt') + with open(fn, 'r') as fd: + lines = fd.readlines() + for line in lines: + d = DateTime(line[:10]) + result_from_mx = tuple(map(int, line[12:-2].split(','))) + self.assertEqual(result_from_mx[1], d.week()) + + def testCopyConstructor(self): + d = DateTime('2004/04/04') + self.assertEqual(DateTime(d), d) + self.assertEqual(str(DateTime(d)), str(d)) + d2 = DateTime('1999/04/12 01:00:00') + self.assertEqual(DateTime(d2), d2) + self.assertEqual(str(DateTime(d2)), str(d2)) + + def testCopyConstructorPreservesTimezone(self): + # test for https://bugs.launchpad.net/zope2/+bug/200007 + # This always worked in the local timezone, so we need at least + # two tests with different zones to be sure at least one of them + # is not local. + d = DateTime('2004/04/04') + self.assertEqual(DateTime(d).timezone(), d.timezone()) + d2 = DateTime('2008/04/25 12:00:00 EST') + self.assertEqual(DateTime(d2).timezone(), d2.timezone()) + self.assertEqual(str(DateTime(d2)), str(d2)) + d3 = DateTime('2008/04/25 12:00:00 PST') + self.assertEqual(DateTime(d3).timezone(), d3.timezone()) + self.assertEqual(str(DateTime(d3)), str(d3)) + + def testRFC822(self): + # rfc822 conversion + dt = DateTime('2002-05-02T08:00:00+00:00') + self.assertEqual(dt.rfc822(), 'Thu, 02 May 2002 08:00:00 +0000') + + dt = DateTime('2002-05-02T08:00:00+02:00') + self.assertEqual(dt.rfc822(), 'Thu, 02 May 2002 08:00:00 +0200') + + dt = DateTime('2002-05-02T08:00:00-02:00') + self.assertEqual(dt.rfc822(), 'Thu, 02 May 2002 08:00:00 -0200') + + # Checking that conversion from local time is working. + dt = DateTime() + dts = dt.rfc822().split(' ') + times = dts[4].split(':') + _isDST = time.localtime(time.time())[8] + if _isDST: + offset = time.altzone + else: + offset = time.timezone + self.assertEqual(dts[0], dt.aDay() + ',') + self.assertEqual(int(dts[1]), dt.day()) + self.assertEqual(dts[2], dt.aMonth()) + self.assertEqual(int(dts[3]), dt.year()) + self.assertEqual(int(times[0]), dt.h_24()) + self.assertEqual(int(times[1]), dt.minute()) + self.assertEqual(int(times[2]), int(dt.second())) + self.assertEqual(dts[5], "%+03d%02d" % divmod((-offset / 60), 60)) + + def testInternationalDateformat(self): + for year in (1990, 2001, 2020): + for month in (1, 12): + for day in (1, 12, 28, 31): + try: + d_us = DateTime("%d/%d/%d" % (year, month, day)) + except Exception: + continue + + d_int = DateTime("%d.%d.%d" % (day, month, year), + datefmt="international") + self.assertEqual(d_us, d_int) + + d_int = DateTime("%d/%d/%d" % (day, month, year), + datefmt="international") + self.assertEqual(d_us, d_int) + + def test_intl_format_hyphen(self): + d_jan = DateTime('2011-01-11 GMT') + d_nov = DateTime('2011-11-01 GMT') + d_us = DateTime('11-01-2011 GMT') + d_int = DateTime('11-01-2011 GMT', datefmt="international") + self.assertNotEqual(d_us, d_int) + self.assertEqual(d_us, d_nov) + self.assertEqual(d_int, d_jan) + + def test_calcTimezoneName(self): + from DateTime.interfaces import TimeError + timezone_dependent_epoch = 2177452800 + try: + DateTime()._calcTimezoneName(timezone_dependent_epoch, 0) + except TimeError: + self.fail('Zope Collector issue #484 (negative time bug): ' + 'TimeError raised') + + def testStrftimeTZhandling(self): + # strftime timezone testing + # This is a test for collector issue #1127 + format = '%Y-%m-%d %H:%M %Z' + dt = DateTime('Wed, 19 Nov 2003 18:32:07 -0215') + dt_string = dt.strftime(format) + dt_local = dt.toZone(_findLocalTimeZoneName(0)) + dt_localstring = dt_local.strftime(format) + self.assertEqual(dt_string, dt_localstring) + + def testStrftimeFarDates(self): + # Checks strftime in dates <= 1900 or >= 2038 + dt = DateTime('1900/01/30') + self.assertEqual(dt.strftime('%d/%m/%Y'), '30/01/1900') + dt = DateTime('2040/01/30') + self.assertEqual(dt.strftime('%d/%m/%Y'), '30/01/2040') + + def testZoneInFarDates(self): + # Checks time zone in dates <= 1900 or >= 2038 + dt1 = DateTime('2040/01/30 14:33 GMT+1') + dt2 = DateTime('2040/01/30 11:33 GMT-2') + self.assertEqual(dt1.strftime('%d/%m/%Y %H:%M'), + dt2.strftime('%d/%m/%Y %H:%M')) + + def testStrftimeUnicode(self): + if IS_PYPY: + # Using Non-Ascii characters for strftime doesn't work in PyPy + # https://bitbucket.org/pypy/pypy/issues/2161/pypy3-strftime-does-not-accept-unicode + return + dt = DateTime('2002-05-02T08:00:00+00:00') + uchar = b'\xc3\xa0'.decode('utf-8') + ok = dt.strftime('Le %d/%m/%Y a %Hh%M').replace('a', uchar) + ustr = b'Le %d/%m/%Y \xc3\xa0 %Hh%M'.decode('utf-8') + self.assertEqual(dt.strftime(ustr), ok) + + def testTimezoneNaiveHandling(self): + # checks that we assign timezone naivity correctly + dt = DateTime('2007-10-04T08:00:00+00:00') + self.assertFalse(dt.timezoneNaive(), + 'error with naivity handling in __parse_iso8601') + dt = DateTime('2007-10-04T08:00:00Z') + self.assertFalse(dt.timezoneNaive(), + 'error with naivity handling in __parse_iso8601') + dt = DateTime('2007-10-04T08:00:00') + self.assertTrue(dt.timezoneNaive(), + 'error with naivity handling in __parse_iso8601') + dt = DateTime('2007/10/04 15:12:33.487618 GMT+1') + self.assertFalse(dt.timezoneNaive(), + 'error with naivity handling in _parse') + dt = DateTime('2007/10/04 15:12:33.487618') + self.assertTrue(dt.timezoneNaive(), + 'error with naivity handling in _parse') + dt = DateTime() + self.assertFalse(dt.timezoneNaive(), + 'error with naivity for current time') + s = '2007-10-04T08:00:00' + dt = DateTime(s) + self.assertEqual(s, dt.ISO8601()) + s = '2007-10-04T08:00:00+00:00' + dt = DateTime(s) + self.assertEqual(s, dt.ISO8601()) + + def testConversions(self): + sdt0 = datetime.now() # this is a timezone naive datetime + dt0 = DateTime(sdt0) + self.assertTrue(dt0.timezoneNaive(), (sdt0, dt0)) + sdt1 = datetime(2007, 10, 4, 18, 14, 42, 580, pytz.utc) + dt1 = DateTime(sdt1) + self.assertFalse(dt1.timezoneNaive(), (sdt1, dt1)) + + # convert back + sdt2 = dt0.asdatetime() + self.assertEqual(sdt0, sdt2) + sdt3 = dt1.utcdatetime() # this returns a timezone naive datetime + self.assertEqual(sdt1.hour, sdt3.hour) + + dt4 = DateTime('2007-10-04T10:00:00+05:00') + sdt4 = datetime(2007, 10, 4, 5, 0) + self.assertEqual(dt4.utcdatetime(), sdt4) + self.assertEqual(dt4.asdatetime(), sdt4.replace(tzinfo=pytz.utc)) + + dt5 = DateTime('2007-10-23 10:00:00 US/Eastern') + tz = pytz.timezone('US/Eastern') + sdt5 = datetime(2007, 10, 23, 10, 0, tzinfo=tz) + dt6 = DateTime(sdt5) + self.assertEqual(dt5.asdatetime(), sdt5) + self.assertEqual(dt6.asdatetime(), sdt5) + self.assertEqual(dt5, dt6) + self.assertEqual(dt5.asdatetime().tzinfo, tz) + self.assertEqual(dt6.asdatetime().tzinfo, tz) + + def testBasicTZ(self): + # psycopg2 supplies it's own tzinfo instances, with no `zone` attribute + tz = FixedOffset(60, 'GMT+1') + dt1 = datetime(2008, 8, 5, 12, 0, tzinfo=tz) + DT = DateTime(dt1) + dt2 = DT.asdatetime() + offset1 = dt1.tzinfo.utcoffset(dt1) + offset2 = dt2.tzinfo.utcoffset(dt2) + self.assertEqual(offset1, offset2) + + def testEDTTimezone(self): + # should be able to parse EDT timezones: see lp:599856. + dt = DateTime("Mon, 28 Jun 2010 10:12:25 EDT") + self.assertEqual(dt.Day(), 'Monday') + self.assertEqual(dt.day(), 28) + self.assertEqual(dt.Month(), 'June') + self.assertEqual(dt.timezone(), 'GMT-4') + + def testParseISO8601(self): + parsed = DateTime()._parse_iso8601('2010-10-10') + self.assertEqual(parsed, (2010, 10, 10, 0, 0, 0, 'GMT+0000')) + + def test_interface(self): + from DateTime.interfaces import IDateTime + self.assertTrue(IDateTime.providedBy(DateTime())) + + def test_security(self): + dt = DateTime() + self.assertEqual(dt.__roles__, None) + self.assertEqual(dt.__allow_access_to_unprotected_subobjects__, 1) + + +def test_suite(): + import doctest + return unittest.TestSuite([ + unittest.makeSuite(DateTimeTests), + doctest.DocFileSuite('DateTime.txt', package='DateTime'), + doctest.DocFileSuite('pytz.txt', package='DateTime'), + ]) diff --git a/lib/backports/configparser/__init__.py b/lib/backports/configparser/__init__.py new file mode 100644 index 0000000..06d7a08 --- /dev/null +++ b/lib/backports/configparser/__init__.py @@ -0,0 +1,1390 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Configuration file parser. + +A configuration file consists of sections, lead by a "[section]" header, +and followed by "name: value" entries, with continuations and such in +the style of RFC 822. + +Intrinsic defaults can be specified by passing them into the +ConfigParser constructor as a dictionary. + +class: + +ConfigParser -- responsible for parsing a list of + configuration files, and managing the parsed database. + + methods: + + __init__(defaults=None, dict_type=_default_dict, allow_no_value=False, + delimiters=('=', ':'), comment_prefixes=('#', ';'), + inline_comment_prefixes=None, strict=True, + empty_lines_in_values=True, default_section='DEFAULT', + interpolation=, converters=): + Create the parser. When `defaults' is given, it is initialized into the + dictionary or intrinsic defaults. The keys must be strings, the values + must be appropriate for %()s string interpolation. + + When `dict_type' is given, it will be used to create the dictionary + objects for the list of sections, for the options within a section, and + for the default values. + + When `delimiters' is given, it will be used as the set of substrings + that divide keys from values. + + When `comment_prefixes' is given, it will be used as the set of + substrings that prefix comments in empty lines. Comments can be + indented. + + When `inline_comment_prefixes' is given, it will be used as the set of + substrings that prefix comments in non-empty lines. + + When `strict` is True, the parser won't allow for any section or option + duplicates while reading from a single source (file, string or + dictionary). Default is True. + + When `empty_lines_in_values' is False (default: True), each empty line + marks the end of an option. Otherwise, internal empty lines of + a multiline option are kept as part of the value. + + When `allow_no_value' is True (default: False), options without + values are accepted; the value presented for these is None. + + sections() + Return all the configuration section names, sans DEFAULT. + + has_section(section) + Return whether the given section exists. + + has_option(section, option) + Return whether the given option exists in the given section. + + options(section) + Return list of configuration options for the named section. + + read(filenames, encoding=None) + Read and parse the list of named configuration files, given by + name. A single filename is also allowed. Non-existing files + are ignored. Return list of successfully read files. + + read_file(f, filename=None) + Read and parse one configuration file, given as a file object. + The filename defaults to f.name; it is only used in error + messages (if f has no `name' attribute, the string `' is used). + + read_string(string) + Read configuration from a given string. + + read_dict(dictionary) + Read configuration from a dictionary. Keys are section names, + values are dictionaries with keys and values that should be present + in the section. If the used dictionary type preserves order, sections + and their keys will be added in order. Values are automatically + converted to strings. + + get(section, option, raw=False, vars=None, fallback=_UNSET) + Return a string value for the named option. All % interpolations are + expanded in the return values, based on the defaults passed into the + constructor and the DEFAULT section. Additional substitutions may be + provided using the `vars' argument, which must be a dictionary whose + contents override any pre-existing defaults. If `option' is a key in + `vars', the value from `vars' is used. + + getint(section, options, raw=False, vars=None, fallback=_UNSET) + Like get(), but convert value to an integer. + + getfloat(section, options, raw=False, vars=None, fallback=_UNSET) + Like get(), but convert value to a float. + + getboolean(section, options, raw=False, vars=None, fallback=_UNSET) + Like get(), but convert value to a boolean (currently case + insensitively defined as 0, false, no, off for False, and 1, true, + yes, on for True). Returns False or True. + + items(section=_UNSET, raw=False, vars=None) + If section is given, return a list of tuples with (name, value) for + each option in the section. Otherwise, return a list of tuples with + (section_name, section_proxy) for each section, including DEFAULTSECT. + + remove_section(section) + Remove the given file section and all its options. + + remove_option(section, option) + Remove the given option from the given section. + + set(section, option, value) + Set the given option. + + write(fp, space_around_delimiters=True) + Write the configuration state in .ini format. If + `space_around_delimiters' is True (the default), delimiters + between keys and values are surrounded by spaces. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from collections import MutableMapping +import functools +import io +import itertools +import re +import sys +import warnings + +from backports.configparser.helpers import OrderedDict as _default_dict +from backports.configparser.helpers import ChainMap as _ChainMap +from backports.configparser.helpers import from_none, open, str, PY2 + +__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", + "NoOptionError", "InterpolationError", "InterpolationDepthError", + "InterpolationMissingOptionError", "InterpolationSyntaxError", + "ParsingError", "MissingSectionHeaderError", + "ConfigParser", "SafeConfigParser", "RawConfigParser", + "Interpolation", "BasicInterpolation", "ExtendedInterpolation", + "LegacyInterpolation", "SectionProxy", "ConverterMapping", + "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] + +DEFAULTSECT = "DEFAULT" + +MAX_INTERPOLATION_DEPTH = 10 + + +# exception classes +class Error(Exception): + """Base class for ConfigParser exceptions.""" + + def __init__(self, msg=''): + self.message = msg + Exception.__init__(self, msg) + + def __repr__(self): + return self.message + + __str__ = __repr__ + + +class NoSectionError(Error): + """Raised when no section matches a requested option.""" + + def __init__(self, section): + Error.__init__(self, 'No section: %r' % (section,)) + self.section = section + self.args = (section, ) + + +class DuplicateSectionError(Error): + """Raised when a section is repeated in an input source. + + Possible repetitions that raise this exception are: multiple creation + using the API or in strict parsers when a section is found more than once + in a single input file, string or dictionary. + """ + + def __init__(self, section, source=None, lineno=None): + msg = [repr(section), " already exists"] + if source is not None: + message = ["While reading from ", repr(source)] + if lineno is not None: + message.append(" [line {0:2d}]".format(lineno)) + message.append(": section ") + message.extend(msg) + msg = message + else: + msg.insert(0, "Section ") + Error.__init__(self, "".join(msg)) + self.section = section + self.source = source + self.lineno = lineno + self.args = (section, source, lineno) + + +class DuplicateOptionError(Error): + """Raised by strict parsers when an option is repeated in an input source. + + Current implementation raises this exception only when an option is found + more than once in a single file, string or dictionary. + """ + + def __init__(self, section, option, source=None, lineno=None): + msg = [repr(option), " in section ", repr(section), + " already exists"] + if source is not None: + message = ["While reading from ", repr(source)] + if lineno is not None: + message.append(" [line {0:2d}]".format(lineno)) + message.append(": option ") + message.extend(msg) + msg = message + else: + msg.insert(0, "Option ") + Error.__init__(self, "".join(msg)) + self.section = section + self.option = option + self.source = source + self.lineno = lineno + self.args = (section, option, source, lineno) + + +class NoOptionError(Error): + """A requested option was not found.""" + + def __init__(self, option, section): + Error.__init__(self, "No option %r in section: %r" % + (option, section)) + self.option = option + self.section = section + self.args = (option, section) + + +class InterpolationError(Error): + """Base class for interpolation-related exceptions.""" + + def __init__(self, option, section, msg): + Error.__init__(self, msg) + self.option = option + self.section = section + self.args = (option, section, msg) + + +class InterpolationMissingOptionError(InterpolationError): + """A string substitution required a setting which was not available.""" + + def __init__(self, option, section, rawval, reference): + msg = ("Bad value substitution: option {0!r} in section {1!r} contains " + "an interpolation key {2!r} which is not a valid option name. " + "Raw value: {3!r}".format(option, section, reference, rawval)) + InterpolationError.__init__(self, option, section, msg) + self.reference = reference + self.args = (option, section, rawval, reference) + + +class InterpolationSyntaxError(InterpolationError): + """Raised when the source text contains invalid syntax. + + Current implementation raises this exception when the source text into + which substitutions are made does not conform to the required syntax. + """ + + +class InterpolationDepthError(InterpolationError): + """Raised when substitutions are nested too deeply.""" + + def __init__(self, option, section, rawval): + msg = ("Recursion limit exceeded in value substitution: option {0!r} " + "in section {1!r} contains an interpolation key which " + "cannot be substituted in {2} steps. Raw value: {3!r}" + "".format(option, section, MAX_INTERPOLATION_DEPTH, + rawval)) + InterpolationError.__init__(self, option, section, msg) + self.args = (option, section, rawval) + + +class ParsingError(Error): + """Raised when a configuration file does not follow legal syntax.""" + + def __init__(self, source=None, filename=None): + # Exactly one of `source'/`filename' arguments has to be given. + # `filename' kept for compatibility. + if filename and source: + raise ValueError("Cannot specify both `filename' and `source'. " + "Use `source'.") + elif not filename and not source: + raise ValueError("Required argument `source' not given.") + elif filename: + source = filename + Error.__init__(self, 'Source contains parsing errors: %r' % source) + self.source = source + self.errors = [] + self.args = (source, ) + + @property + def filename(self): + """Deprecated, use `source'.""" + warnings.warn( + "The 'filename' attribute will be removed in future versions. " + "Use 'source' instead.", + DeprecationWarning, stacklevel=2 + ) + return self.source + + @filename.setter + def filename(self, value): + """Deprecated, user `source'.""" + warnings.warn( + "The 'filename' attribute will be removed in future versions. " + "Use 'source' instead.", + DeprecationWarning, stacklevel=2 + ) + self.source = value + + def append(self, lineno, line): + self.errors.append((lineno, line)) + self.message += '\n\t[line %2d]: %s' % (lineno, line) + + +class MissingSectionHeaderError(ParsingError): + """Raised when a key-value pair is found before any section header.""" + + def __init__(self, filename, lineno, line): + Error.__init__( + self, + 'File contains no section headers.\nfile: %r, line: %d\n%r' % + (filename, lineno, line)) + self.source = filename + self.lineno = lineno + self.line = line + self.args = (filename, lineno, line) + + +# Used in parser getters to indicate the default behaviour when a specific +# option is not found it to raise an exception. Created to enable `None' as +# a valid fallback value. +_UNSET = object() + + +class Interpolation(object): + """Dummy interpolation that passes the value through with no changes.""" + + def before_get(self, parser, section, option, value, defaults): + return value + + def before_set(self, parser, section, option, value): + return value + + def before_read(self, parser, section, option, value): + return value + + def before_write(self, parser, section, option, value): + return value + + +class BasicInterpolation(Interpolation): + """Interpolation as implemented in the classic ConfigParser. + + The option values can contain format strings which refer to other values in + the same section, or values in the special default section. + + For example: + + something: %(dir)s/whatever + + would resolve the "%(dir)s" to the value of dir. All reference + expansions are done late, on demand. If a user needs to use a bare % in + a configuration file, she can escape it by writing %%. Other % usage + is considered a user error and raises `InterpolationSyntaxError'.""" + + _KEYCRE = re.compile(r"%\(([^)]+)\)s") + + def before_get(self, parser, section, option, value, defaults): + L = [] + self._interpolate_some(parser, option, L, value, section, defaults, 1) + return ''.join(L) + + def before_set(self, parser, section, option, value): + tmp_value = value.replace('%%', '') # escaped percent signs + tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax + if '%' in tmp_value: + raise ValueError("invalid interpolation syntax in %r at " + "position %d" % (value, tmp_value.find('%'))) + return value + + def _interpolate_some(self, parser, option, accum, rest, section, map, + depth): + rawval = parser.get(section, option, raw=True, fallback=rest) + if depth > MAX_INTERPOLATION_DEPTH: + raise InterpolationDepthError(option, section, rawval) + while rest: + p = rest.find("%") + if p < 0: + accum.append(rest) + return + if p > 0: + accum.append(rest[:p]) + rest = rest[p:] + # p is no longer used + c = rest[1:2] + if c == "%": + accum.append("%") + rest = rest[2:] + elif c == "(": + m = self._KEYCRE.match(rest) + if m is None: + raise InterpolationSyntaxError(option, section, + "bad interpolation variable reference %r" % rest) + var = parser.optionxform(m.group(1)) + rest = rest[m.end():] + try: + v = map[var] + except KeyError: + raise from_none(InterpolationMissingOptionError( + option, section, rawval, var)) + if "%" in v: + self._interpolate_some(parser, option, accum, v, + section, map, depth + 1) + else: + accum.append(v) + else: + raise InterpolationSyntaxError( + option, section, + "'%%' must be followed by '%%' or '(', " + "found: %r" % (rest,)) + + +class ExtendedInterpolation(Interpolation): + """Advanced variant of interpolation, supports the syntax used by + `zc.buildout'. Enables interpolation between sections.""" + + _KEYCRE = re.compile(r"\$\{([^}]+)\}") + + def before_get(self, parser, section, option, value, defaults): + L = [] + self._interpolate_some(parser, option, L, value, section, defaults, 1) + return ''.join(L) + + def before_set(self, parser, section, option, value): + tmp_value = value.replace('$$', '') # escaped dollar signs + tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax + if '$' in tmp_value: + raise ValueError("invalid interpolation syntax in %r at " + "position %d" % (value, tmp_value.find('$'))) + return value + + def _interpolate_some(self, parser, option, accum, rest, section, map, + depth): + rawval = parser.get(section, option, raw=True, fallback=rest) + if depth > MAX_INTERPOLATION_DEPTH: + raise InterpolationDepthError(option, section, rawval) + while rest: + p = rest.find("$") + if p < 0: + accum.append(rest) + return + if p > 0: + accum.append(rest[:p]) + rest = rest[p:] + # p is no longer used + c = rest[1:2] + if c == "$": + accum.append("$") + rest = rest[2:] + elif c == "{": + m = self._KEYCRE.match(rest) + if m is None: + raise InterpolationSyntaxError(option, section, + "bad interpolation variable reference %r" % rest) + path = m.group(1).split(':') + rest = rest[m.end():] + sect = section + opt = option + try: + if len(path) == 1: + opt = parser.optionxform(path[0]) + v = map[opt] + elif len(path) == 2: + sect = path[0] + opt = parser.optionxform(path[1]) + v = parser.get(sect, opt, raw=True) + else: + raise InterpolationSyntaxError( + option, section, + "More than one ':' found: %r" % (rest,)) + except (KeyError, NoSectionError, NoOptionError): + raise from_none(InterpolationMissingOptionError( + option, section, rawval, ":".join(path))) + if "$" in v: + self._interpolate_some(parser, opt, accum, v, sect, + dict(parser.items(sect, raw=True)), + depth + 1) + else: + accum.append(v) + else: + raise InterpolationSyntaxError( + option, section, + "'$' must be followed by '$' or '{', " + "found: %r" % (rest,)) + + +class LegacyInterpolation(Interpolation): + """Deprecated interpolation used in old versions of ConfigParser. + Use BasicInterpolation or ExtendedInterpolation instead.""" + + _KEYCRE = re.compile(r"%\(([^)]*)\)s|.") + + def before_get(self, parser, section, option, value, vars): + rawval = value + depth = MAX_INTERPOLATION_DEPTH + while depth: # Loop through this until it's done + depth -= 1 + if value and "%(" in value: + replace = functools.partial(self._interpolation_replace, + parser=parser) + value = self._KEYCRE.sub(replace, value) + try: + value = value % vars + except KeyError as e: + raise from_none(InterpolationMissingOptionError( + option, section, rawval, e.args[0])) + else: + break + if value and "%(" in value: + raise InterpolationDepthError(option, section, rawval) + return value + + def before_set(self, parser, section, option, value): + return value + + @staticmethod + def _interpolation_replace(match, parser): + s = match.group(1) + if s is None: + return match.group() + else: + return "%%(%s)s" % parser.optionxform(s) + + +class RawConfigParser(MutableMapping): + """ConfigParser that does not do interpolation.""" + + # Regular expressions for parsing section headers and options + _SECT_TMPL = r""" + \[ # [ + (?P
[^]]+) # very permissive! + \] # ] + """ + _OPT_TMPL = r""" + (?PCbk((@9-7tXbJf|5eCh;duD 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 z zZ?S>7+Ew2O(mhBL66cLV>4`6K%NBWTj<-0^T-~5=vckk>+3_>d%G`Z&AeQ9rN66QA6~NOA-2yK zui2p~w2u&9F-NfO8pQo%`R)$*XS||ie`Ek+d;?*vf$YZvPg@qu%!`XdY+tSy2d;2D zW?$E7*$8AHy(D^?@-dbbn026}Qk}K+>5&oe_Dh{NKhaK-%fv&9?vbE;`3l63FX>UW z(fPKKYZ%1(j>YOhE?S!mwu)aBQt^+jDl+=I`CKjlf4hd($+ z&R^QFfcQ+RK^+$h_J-TShkIRB(ONa9-x@IIOQJVb$6O0_T~pQPL)E0h?WwFI_h4*@3^D& z6wCu5ajon0yekM8=^=K=C~2DHBmEo9cN_b&H&Hp_(T5h>($WcwNp(iM8~E|Gcbn`_ zV)LLPcvQ;qkd%OX_&9ddIOdN<(}RxEgKPv5_NbzX@f>2E!GYDEsW1|YU6Y?G)TPau zbr$a(>)z@9nRS$l*B*H}uxqIbF~BU8d0@aqHJT5jnPs?dj+#WT=p9 zb(=F5Y}FNgcEYf>YGCEQG&J+ssikaohw=7D1SugO%Iq>a6Ci&TO3CwP;$0J=hH)ry3~bZ<}8`*iG*!!H%J*V2?j(` z_R+9ApXRCr{kDjYU_b4>0iBGBE54_a%nC>B@w1@}C=4dOn{Yzmt@mGq?BSBL-C#!K zwf%Q#+dD5YP4ynaxhkJ~iPe3!J^*P1APonk19oWzyDl+{{Certy|-{;@8L(}i2`?N zvw+|{P-Dw29K{oAX>Wx=7}FRUCI4(_gqFunMqPZD+3Thmtl0&>WPstpY*p9#FL^0!Rl=`CY#U)II_gMZ)}BVj@*?sk&wPdlDZU=An?S2|D?+nXHR|q0TRE+x+=?rL5Az+i0@VkF7iMXg zbMFg@>fbX-5R!0MnRq%?1>RhwJ=W7^%dzvXHjUm|De3lHQ4QU0O-KANpMy`I&d;2) z>S?d%DZxcqlwHSnbi=LNWA`#1RplRy)q3bned>}biC`Z(*g7yBw2a~0hfSQMD(P55lPaf9>;#&lz!=%(z5Dw+p4c5sy4Zp)P_E-rl?LOZLeM_ zg-}aI$i6O#XQ4;oHZr1Y<(EXrB_%H&{dZ-QZ(O#9eIkn}NzcoRpBo<56bh<~$}Cnv zAr^gW^{PzvU79ZsMH2JxL#;W+@1#*N& zp*Hj{5y&prZxkN1%1$ zsb~4JXQY)rvAnQ^R@ER^AWY(%X()O)>VNt%OO}=1`(Fa>o|AM^oFgk42I7S@{x$tPxzQF-&xUo7lyeFbZYH) zhMM~o4K)cQX@~k*+Q4`N%k7pBX83Hda6~{cSZdHtFplx1yT?fO4Swyw*q{{+< zD_ZW#HfM01qjHQT1h&ZP7yZjHU3wmE)7B2us95b$c!b$JCh4X|c!Ka2H9x&Qrqpwg zDRneVc+q)bud18!7X4f)hUq^++O=*<4A)>aq9_QdBc2q0h#D1R4?RZqCU*Gr`OjWU zH}O5rJ?vgv;7`;l;;?-n0Wn{k$P!xATlp1<5O(y_u9BV7eiT0 z>z!HQInI9)bAF}vc~L|WcffmPqKFKD_^I%m0GT=IQcu2bXU*2iQ)!^^l;}SiLCRJ# z^E@+5q0ar%C^O6i!93aT1mT=4hg3_w`AI-Pr<*X|Bp^FKU3f~aHdI>3KR?&(Lqar+ zw9G#m8UgY2RCa7@gDKV8aA_?d;QbcB0RkF8!15nW7FD@M=dTD(`@}{QXiFwQvqN}F zuQs$K!V;$v=eNLj4-_g{iS$$_e4xezsL2LIq9oGYqTcu%0Ds;L(-T>++UB?H_oXPH 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 Rs}CAN=Sk9x^OX$ee*qyL&5r;8 literal 0 HcmV?d00001 diff --git a/lib/dateutil/zoneinfo/rebuild.py b/lib/dateutil/zoneinfo/rebuild.py new file mode 100644 index 0000000..78f0d1a --- /dev/null +++ b/lib/dateutil/zoneinfo/rebuild.py @@ -0,0 +1,53 @@ +import logging +import os +import tempfile +import shutil +import json +from subprocess import check_call +from tarfile import TarFile + +from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME + + +def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): + """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* + + filename is the timezone tarball from ``ftp.iana.org/tz``. + + """ + tmpdir = tempfile.mkdtemp() + zonedir = os.path.join(tmpdir, "zoneinfo") + moduledir = os.path.dirname(__file__) + try: + with TarFile.open(filename) as tf: + for name in zonegroups: + tf.extract(name, tmpdir) + filepaths = [os.path.join(tmpdir, n) for n in zonegroups] + try: + check_call(["zic", "-d", zonedir] + filepaths) + except OSError as e: + _print_on_nosuchfile(e) + raise + # write metadata file + with open(os.path.join(zonedir, METADATA_FN), 'w') as f: + json.dump(metadata, f, indent=4, sort_keys=True) + target = os.path.join(moduledir, ZONEFILENAME) + with TarFile.open(target, "w:%s" % format) as tf: + for entry in os.listdir(zonedir): + entrypath = os.path.join(zonedir, entry) + tf.add(entrypath, entry) + finally: + shutil.rmtree(tmpdir) + + +def _print_on_nosuchfile(e): + """Print helpful troubleshooting message + + e is an exception raised by subprocess.check_call() + + """ + if e.errno == 2: + logging.error( + "Could not find zic. Perhaps you need to install " + "libc-bin or some other package that provides it, " + "or it's not in your PATH?") diff --git a/lib/easy_install.py b/lib/easy_install.py new file mode 100644 index 0000000..d87e984 --- /dev/null +++ b/lib/easy_install.py @@ -0,0 +1,5 @@ +"""Run the EasyInstall command""" + +if __name__ == '__main__': + from setuptools.command.easy_install import main + main() diff --git a/lib/geoip2/__init__.py b/lib/geoip2/__init__.py new file mode 100644 index 0000000..81776ec --- /dev/null +++ b/lib/geoip2/__init__.py @@ -0,0 +1,7 @@ +# pylint:disable=C0111 + +__title__ = 'geoip2' +__version__ = '2.9.0' +__author__ = 'Gregory Oschwald' +__license__ = 'Apache License, Version 2.0' +__copyright__ = 'Copyright (c) 2013-2018 Maxmind, Inc.' diff --git a/lib/geoip2/compat.py b/lib/geoip2/compat.py new file mode 100644 index 0000000..228198c --- /dev/null +++ b/lib/geoip2/compat.py @@ -0,0 +1,19 @@ +"""Intended for internal use only.""" +import sys + +import ipaddress + +# pylint: skip-file + +if sys.version_info[0] == 2: + + def compat_ip_address(address): + """Intended for internal use only.""" + if isinstance(address, bytes): + address = address.decode() + return ipaddress.ip_address(address) +else: + + def compat_ip_address(address): + """Intended for internal use only.""" + return ipaddress.ip_address(address) diff --git a/lib/geoip2/database.py b/lib/geoip2/database.py new file mode 100644 index 0000000..f6c68b8 --- /dev/null +++ b/lib/geoip2/database.py @@ -0,0 +1,214 @@ +""" +====================== +GeoIP2 Database Reader +====================== + +""" +import inspect + +import maxminddb +# pylint: disable=unused-import +from maxminddb import (MODE_AUTO, MODE_MMAP, MODE_MMAP_EXT, MODE_FILE, + MODE_MEMORY, MODE_FD) + +import geoip2 +import geoip2.models +import geoip2.errors + + +class Reader(object): + """GeoIP2 database Reader object. + + Instances of this class provide a reader for the GeoIP2 database format. + IP addresses can be looked up using the ``country`` and ``city`` methods. + + The basic API for this class is the same for every database. First, you + create a reader object, specifying a file name or file descriptor. + You then call the method corresponding to the specific database, passing + it the IP address you want to look up. + + If the request succeeds, the method call will return a model class for the + method you called. This model in turn contains multiple record classes, + each of which represents part of the data returned by the database. If the + database does not contain the requested information, the attributes on the + record class will have a ``None`` value. + + If the address is not in the database, an + ``geoip2.errors.AddressNotFoundError`` exception will be thrown. If the + database is corrupt or invalid, a ``maxminddb.InvalidDatabaseError`` will + be thrown. + +""" + + def __init__(self, fileish, locales=None, mode=MODE_AUTO): + """Create GeoIP2 Reader. + + :param fileish: The string path to the GeoIP2 database, or an existing + file descriptor pointing to the database. Note that this latter + usage is only valid when mode is MODE_FD. + :param locales: This is list of locale codes. This argument will be + passed on to record classes to use when their name properties are + called. The default value is ['en']. + + The order of the locales is significant. When a record class has + multiple names (country, city, etc.), its name property will return + the name in the first locale that has one. + + Note that the only locale which is always present in the GeoIP2 + data is "en". If you do not include this locale, the name property + may end up returning None even when the record has an English name. + + Currently, the valid locale codes are: + + * de -- German + * en -- English names may still include accented characters if that + is the accepted spelling in English. In other words, English does + not mean ASCII. + * es -- Spanish + * fr -- French + * ja -- Japanese + * pt-BR -- Brazilian Portuguese + * ru -- Russian + * zh-CN -- Simplified Chinese. + :param mode: The mode to open the database with. Valid mode are: + * MODE_MMAP_EXT - use the C extension with memory map. + * MODE_MMAP - read from memory map. Pure Python. + * MODE_FILE - read database as standard file. Pure Python. + * MODE_MEMORY - load database into memory. Pure Python. + * MODE_FD - the param passed via fileish is a file descriptor, not a + path. This mode implies MODE_MEMORY. Pure Python. + * MODE_AUTO - try MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that order. + Default. + + """ + if locales is None: + locales = ['en'] + self._db_reader = maxminddb.open_database(fileish, mode) + self._locales = locales + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def country(self, ip_address): + """Get the Country object for the IP address. + + :param ip_address: IPv4 or IPv6 address as a string. + + :returns: :py:class:`geoip2.models.Country` object + + """ + + return self._model_for(geoip2.models.Country, 'Country', ip_address) + + def city(self, ip_address): + """Get the City object for the IP address. + + :param ip_address: IPv4 or IPv6 address as a string. + + :returns: :py:class:`geoip2.models.City` object + + """ + return self._model_for(geoip2.models.City, 'City', ip_address) + + def anonymous_ip(self, ip_address): + """Get the AnonymousIP object for the IP address. + + :param ip_address: IPv4 or IPv6 address as a string. + + :returns: :py:class:`geoip2.models.AnonymousIP` object + + """ + return self._flat_model_for(geoip2.models.AnonymousIP, + 'GeoIP2-Anonymous-IP', ip_address) + + def asn(self, ip_address): + """Get the ASN object for the IP address. + + :param ip_address: IPv4 or IPv6 address as a string. + + :returns: :py:class:`geoip2.models.ASN` object + + """ + return self._flat_model_for(geoip2.models.ASN, 'GeoLite2-ASN', + ip_address) + + def connection_type(self, ip_address): + """Get the ConnectionType object for the IP address. + + :param ip_address: IPv4 or IPv6 address as a string. + + :returns: :py:class:`geoip2.models.ConnectionType` object + + """ + return self._flat_model_for(geoip2.models.ConnectionType, + 'GeoIP2-Connection-Type', ip_address) + + def domain(self, ip_address): + """Get the Domain object for the IP address. + + :param ip_address: IPv4 or IPv6 address as a string. + + :returns: :py:class:`geoip2.models.Domain` object + + """ + return self._flat_model_for(geoip2.models.Domain, 'GeoIP2-Domain', + ip_address) + + def enterprise(self, ip_address): + """Get the Enterprise object for the IP address. + + :param ip_address: IPv4 or IPv6 address as a string. + + :returns: :py:class:`geoip2.models.Enterprise` object + + """ + return self._model_for(geoip2.models.Enterprise, 'Enterprise', + ip_address) + + def isp(self, ip_address): + """Get the ISP object for the IP address. + + :param ip_address: IPv4 or IPv6 address as a string. + + :returns: :py:class:`geoip2.models.ISP` object + + """ + return self._flat_model_for(geoip2.models.ISP, 'GeoIP2-ISP', + ip_address) + + def _get(self, database_type, ip_address): + if database_type not in self.metadata().database_type: + caller = inspect.stack()[2][3] + raise TypeError("The %s method cannot be used with the " + "%s database" % (caller, + self.metadata().database_type)) + record = self._db_reader.get(ip_address) + if record is None: + raise geoip2.errors.AddressNotFoundError( + "The address %s is not in the database." % ip_address) + return record + + def _model_for(self, model_class, types, ip_address): + record = self._get(types, ip_address) + record.setdefault('traits', {})['ip_address'] = ip_address + return model_class(record, locales=self._locales) + + def _flat_model_for(self, model_class, types, ip_address): + record = self._get(types, ip_address) + record['ip_address'] = ip_address + return model_class(record) + + def metadata(self): + """The metadata for the open database. + + :returns: :py:class:`maxminddb.reader.Metadata` object + """ + return self._db_reader.metadata() + + def close(self): + """Closes the GeoIP2 database.""" + + self._db_reader.close() diff --git a/lib/geoip2/errors.py b/lib/geoip2/errors.py new file mode 100644 index 0000000..468b585 --- /dev/null +++ b/lib/geoip2/errors.py @@ -0,0 +1,51 @@ +""" +Errors +====== + +""" + + +class GeoIP2Error(RuntimeError): + """There was a generic error in GeoIP2. + + This class represents a generic error. It extends :py:exc:`RuntimeError` + and does not add any additional attributes. + + """ + + +class AddressNotFoundError(GeoIP2Error): + """The address you were looking up was not found.""" + + +class AuthenticationError(GeoIP2Error): + """There was a problem authenticating the request.""" + + +class HTTPError(GeoIP2Error): + """There was an error when making your HTTP request. + + This class represents an HTTP transport error. It extends + :py:exc:`GeoIP2Error` and adds attributes of its own. + + :ivar http_status: The HTTP status code returned + :ivar uri: The URI queried + + """ + + def __init__(self, message, http_status=None, uri=None): + super(HTTPError, self).__init__(message) + self.http_status = http_status + self.uri = uri + + +class InvalidRequestError(GeoIP2Error): + """The request was invalid.""" + + +class OutOfQueriesError(GeoIP2Error): + """Your account is out of funds for the service queried.""" + + +class PermissionRequiredError(GeoIP2Error): + """Your account does not have permission to access this service.""" diff --git a/lib/geoip2/mixins.py b/lib/geoip2/mixins.py new file mode 100644 index 0000000..4afc5a0 --- /dev/null +++ b/lib/geoip2/mixins.py @@ -0,0 +1,16 @@ +"""This package contains utility mixins""" +# pylint: disable=too-few-public-methods +from abc import ABCMeta + + +class SimpleEquality(object): + """Naive __dict__ equality mixin""" + + __metaclass__ = ABCMeta + + def __eq__(self, other): + return (isinstance(other, self.__class__) + and self.__dict__ == other.__dict__) + + def __ne__(self, other): + return not self.__eq__(other) diff --git a/lib/geoip2/models.py b/lib/geoip2/models.py new file mode 100644 index 0000000..d50fb91 --- /dev/null +++ b/lib/geoip2/models.py @@ -0,0 +1,502 @@ +""" +Models +====== + +These classes provide models for the data returned by the GeoIP2 +web service and databases. + +The only difference between the City and Insights model classes is which +fields in each record may be populated. See +http://dev.maxmind.com/geoip/geoip2/web-services for more details. + +""" +# pylint: disable=too-many-instance-attributes,too-few-public-methods +from abc import ABCMeta + +import geoip2.records +from geoip2.mixins import SimpleEquality + + +class Country(SimpleEquality): + """Model for the GeoIP2 Precision: Country and the GeoIP2 Country database. + + This class provides the following attributes: + + .. attribute:: continent + + Continent object for the requested IP address. + + :type: :py:class:`geoip2.records.Continent` + + .. attribute:: country + + Country object for the requested IP address. This record represents the + country where MaxMind believes the IP is located. + + :type: :py:class:`geoip2.records.Country` + + .. attribute:: maxmind + + Information related to your MaxMind account. + + :type: :py:class:`geoip2.records.MaxMind` + + .. attribute:: registered_country + + The registered country object for the requested IP address. This record + represents the country where the ISP has registered a given IP block in + and may differ from the user's country. + + :type: :py:class:`geoip2.records.Country` + + .. attribute:: represented_country + + Object for the country represented by the users of the IP address + when that country is different than the country in ``country``. For + instance, the country represented by an overseas military base. + + :type: :py:class:`geoip2.records.RepresentedCountry` + + .. attribute:: traits + + Object with the traits of the requested IP address. + + :type: :py:class:`geoip2.records.Traits` + + """ + + def __init__(self, raw_response, locales=None): + if locales is None: + locales = ['en'] + self._locales = locales + self.continent = \ + geoip2.records.Continent(locales, + **raw_response.get('continent', {})) + self.country = \ + geoip2.records.Country(locales, + **raw_response.get('country', {})) + self.registered_country = \ + geoip2.records.Country(locales, + **raw_response.get('registered_country', + {})) + self.represented_country \ + = geoip2.records.RepresentedCountry(locales, + **raw_response.get( + 'represented_country', {})) + + self.maxmind = \ + geoip2.records.MaxMind(**raw_response.get('maxmind', {})) + + self.traits = geoip2.records.Traits(**raw_response.get('traits', {})) + self.raw = raw_response + + def __repr__(self): + return '{module}.{class_name}({data}, {locales})'.format( + module=self.__module__, + class_name=self.__class__.__name__, + data=self.raw, + locales=self._locales) + + +class City(Country): + """Model for the GeoIP2 Precision: City and the GeoIP2 City database. + + .. attribute:: city + + City object for the requested IP address. + + :type: :py:class:`geoip2.records.City` + + .. attribute:: continent + + Continent object for the requested IP address. + + :type: :py:class:`geoip2.records.Continent` + + .. attribute:: country + + Country object for the requested IP address. This record represents the + country where MaxMind believes the IP is located. + + :type: :py:class:`geoip2.records.Country` + + .. attribute:: location + + Location object for the requested IP address. + + .. attribute:: maxmind + + Information related to your MaxMind account. + + :type: :py:class:`geoip2.records.MaxMind` + + .. attribute:: registered_country + + The registered country object for the requested IP address. This record + represents the country where the ISP has registered a given IP block in + and may differ from the user's country. + + :type: :py:class:`geoip2.records.Country` + + .. attribute:: represented_country + + Object for the country represented by the users of the IP address + when that country is different than the country in ``country``. For + instance, the country represented by an overseas military base. + + :type: :py:class:`geoip2.records.RepresentedCountry` + + .. attribute:: subdivisions + + Object (tuple) representing the subdivisions of the country to which + the location of the requested IP address belongs. + + :type: :py:class:`geoip2.records.Subdivisions` + + .. attribute:: traits + + Object with the traits of the requested IP address. + + :type: :py:class:`geoip2.records.Traits` + + """ + + def __init__(self, raw_response, locales=None): + super(City, self).__init__(raw_response, locales) + self.city = \ + geoip2.records.City(locales, **raw_response.get('city', {})) + self.location = \ + geoip2.records.Location(**raw_response.get('location', {})) + self.postal = \ + geoip2.records.Postal(**raw_response.get('postal', {})) + self.subdivisions = \ + geoip2.records.Subdivisions(locales, + *raw_response.get('subdivisions', [])) + + +class Insights(City): + """Model for the GeoIP2 Precision: Insights web service endpoint. + + .. attribute:: city + + City object for the requested IP address. + + :type: :py:class:`geoip2.records.City` + + .. attribute:: continent + + Continent object for the requested IP address. + + :type: :py:class:`geoip2.records.Continent` + + .. attribute:: country + + Country object for the requested IP address. This record represents the + country where MaxMind believes the IP is located. + + :type: :py:class:`geoip2.records.Country` + + .. attribute:: location + + Location object for the requested IP address. + + .. attribute:: maxmind + + Information related to your MaxMind account. + + :type: :py:class:`geoip2.records.MaxMind` + + .. attribute:: registered_country + + The registered country object for the requested IP address. This record + represents the country where the ISP has registered a given IP block in + and may differ from the user's country. + + :type: :py:class:`geoip2.records.Country` + + .. attribute:: represented_country + + Object for the country represented by the users of the IP address + when that country is different than the country in ``country``. For + instance, the country represented by an overseas military base. + + :type: :py:class:`geoip2.records.RepresentedCountry` + + .. attribute:: subdivisions + + Object (tuple) representing the subdivisions of the country to which + the location of the requested IP address belongs. + + :type: :py:class:`geoip2.records.Subdivisions` + + .. attribute:: traits + + Object with the traits of the requested IP address. + + :type: :py:class:`geoip2.records.Traits` + + """ + + +class Enterprise(City): + """Model for the GeoIP2 Enterprise database. + + .. attribute:: city + + City object for the requested IP address. + + :type: :py:class:`geoip2.records.City` + + .. attribute:: continent + + Continent object for the requested IP address. + + :type: :py:class:`geoip2.records.Continent` + + .. attribute:: country + + Country object for the requested IP address. This record represents the + country where MaxMind believes the IP is located. + + :type: :py:class:`geoip2.records.Country` + + .. attribute:: location + + Location object for the requested IP address. + + .. attribute:: maxmind + + Information related to your MaxMind account. + + :type: :py:class:`geoip2.records.MaxMind` + + .. attribute:: registered_country + + The registered country object for the requested IP address. This record + represents the country where the ISP has registered a given IP block in + and may differ from the user's country. + + :type: :py:class:`geoip2.records.Country` + + .. attribute:: represented_country + + Object for the country represented by the users of the IP address + when that country is different than the country in ``country``. For + instance, the country represented by an overseas military base. + + :type: :py:class:`geoip2.records.RepresentedCountry` + + .. attribute:: subdivisions + + Object (tuple) representing the subdivisions of the country to which + the location of the requested IP address belongs. + + :type: :py:class:`geoip2.records.Subdivisions` + + .. attribute:: traits + + Object with the traits of the requested IP address. + + :type: :py:class:`geoip2.records.Traits` + + """ + + +class SimpleModel(SimpleEquality): + """Provides basic methods for non-location models""" + + __metaclass__ = ABCMeta + + def __repr__(self): + # pylint: disable=no-member + return '{module}.{class_name}({data})'.format( + module=self.__module__, + class_name=self.__class__.__name__, + data=str(self.raw)) + + +class AnonymousIP(SimpleModel): + """Model class for the GeoIP2 Anonymous IP. + + This class provides the following attribute: + + .. attribute:: is_anonymous + + This is true if the IP address belongs to any sort of anonymous network. + + :type: bool + + .. attribute:: is_anonymous_vpn + + This is true if the IP address belongs to an anonymous VPN system. + + :type: bool + + .. attribute:: is_hosting_provider + + This is true if the IP address belongs to a hosting provider. + + :type: bool + + .. attribute:: is_public_proxy + + This is true if the IP address belongs to a public proxy. + + :type: bool + + .. attribute:: is_tor_exit_node + + This is true if the IP address is a Tor exit node. + + :type: bool + + .. attribute:: ip_address + + The IP address used in the lookup. + + :type: unicode + """ + + def __init__(self, raw): + self.is_anonymous = raw.get('is_anonymous', False) + self.is_anonymous_vpn = raw.get('is_anonymous_vpn', False) + self.is_hosting_provider = raw.get('is_hosting_provider', False) + self.is_public_proxy = raw.get('is_public_proxy', False) + self.is_tor_exit_node = raw.get('is_tor_exit_node', False) + + self.ip_address = raw.get('ip_address') + self.raw = raw + + +class ASN(SimpleModel): + """Model class for the GeoLite2 ASN. + + This class provides the following attribute: + + .. attribute:: autonomous_system_number + + The autonomous system number associated with the IP address. + + :type: int + + .. attribute:: autonomous_system_organization + + The organization associated with the registered autonomous system number + for the IP address. + + :type: unicode + + .. attribute:: ip_address + + The IP address used in the lookup. + + :type: unicode + """ + + # pylint:disable=too-many-arguments + def __init__(self, raw): + self.autonomous_system_number = raw.get('autonomous_system_number') + self.autonomous_system_organization = raw.get( + 'autonomous_system_organization') + self.ip_address = raw.get('ip_address') + self.raw = raw + + +class ConnectionType(SimpleModel): + """Model class for the GeoIP2 Connection-Type. + + This class provides the following attribute: + + .. attribute:: connection_type + + The connection type may take the following values: + + - Dialup + - Cable/DSL + - Corporate + - Cellular + + Additional values may be added in the future. + + :type: unicode + + .. attribute:: ip_address + + The IP address used in the lookup. + + :type: unicode + """ + + def __init__(self, raw): + self.connection_type = raw.get('connection_type') + self.ip_address = raw.get('ip_address') + self.raw = raw + + +class Domain(SimpleModel): + """Model class for the GeoIP2 Domain. + + This class provides the following attribute: + + .. attribute:: domain + + The domain associated with the IP address. + + :type: unicode + + .. attribute:: ip_address + + The IP address used in the lookup. + + :type: unicode + + """ + + def __init__(self, raw): + self.domain = raw.get('domain') + self.ip_address = raw.get('ip_address') + self.raw = raw + + +class ISP(ASN): + """Model class for the GeoIP2 ISP. + + This class provides the following attribute: + + .. attribute:: autonomous_system_number + + The autonomous system number associated with the IP address. + + :type: int + + .. attribute:: autonomous_system_organization + + The organization associated with the registered autonomous system number + for the IP address. + + :type: unicode + + .. attribute:: isp + + The name of the ISP associated with the IP address. + + :type: unicode + + .. attribute:: organization + + The name of the organization associated with the IP address. + + :type: unicode + + .. attribute:: ip_address + + The IP address used in the lookup. + + :type: unicode + """ + + # pylint:disable=too-many-arguments + def __init__(self, raw): + super(ISP, self).__init__(raw) + self.isp = raw.get('isp') + self.organization = raw.get('organization') diff --git a/lib/geoip2/records.py b/lib/geoip2/records.py new file mode 100644 index 0000000..9e04960 --- /dev/null +++ b/lib/geoip2/records.py @@ -0,0 +1,675 @@ +""" + +Records +======= + +""" + +# pylint:disable=R0903 +from abc import ABCMeta + +from geoip2.mixins import SimpleEquality + + +class Record(SimpleEquality): + """All records are subclasses of the abstract class ``Record``.""" + + __metaclass__ = ABCMeta + + _valid_attributes = set() + + def __init__(self, **kwargs): + valid_args = dict((k, kwargs.get(k)) for k in self._valid_attributes) + self.__dict__.update(valid_args) + + def __setattr__(self, name, value): + raise AttributeError("can't set attribute") + + def __repr__(self): + args = ', '.join('%s=%r' % x for x in self.__dict__.items()) + return '{module}.{class_name}({data})'.format( + module=self.__module__, + class_name=self.__class__.__name__, + data=args) + + +class PlaceRecord(Record): + """All records with :py:attr:`names` subclass :py:class:`PlaceRecord`.""" + + __metaclass__ = ABCMeta + + def __init__(self, locales=None, **kwargs): + if locales is None: + locales = ['en'] + if kwargs.get('names') is None: + kwargs['names'] = {} + object.__setattr__(self, '_locales', locales) + super(PlaceRecord, self).__init__(**kwargs) + + @property + def name(self): + """Dict with locale codes as keys and localized name as value.""" + # pylint:disable=E1101 + return next((self.names.get(x) for x in self._locales + if x in self.names), None) + + +class City(PlaceRecord): + """Contains data for the city record associated with an IP address. + + This class contains the city-level data associated with an IP address. + + This record is returned by ``city``, ``enterprise``, and ``insights``. + + Attributes: + + .. attribute:: confidence + + A value from 0-100 indicating MaxMind's + confidence that the city is correct. This attribute is only available + from the Insights end point and the GeoIP2 Enterprise database. + + :type: int + + .. attribute:: geoname_id + + The GeoName ID for the city. + + :type: int + + .. attribute:: name + + The name of the city based on the locales list passed to the + constructor. + + :type: unicode + + .. attribute:: names + + A dictionary where the keys are locale codes + and the values are names. + + :type: dict + + """ + + _valid_attributes = set(['confidence', 'geoname_id', 'names']) + + +class Continent(PlaceRecord): + """Contains data for the continent record associated with an IP address. + + This class contains the continent-level data associated with an IP + address. + + Attributes: + + + .. attribute:: code + + A two character continent code like "NA" (North America) + or "OC" (Oceania). + + :type: unicode + + .. attribute:: geoname_id + + The GeoName ID for the continent. + + :type: int + + .. attribute:: name + + Returns the name of the continent based on the locales list passed to + the constructor. + + :type: unicode + + .. attribute:: names + + A dictionary where the keys are locale codes + and the values are names. + + :type: dict + + """ + + _valid_attributes = set(['code', 'geoname_id', 'names']) + + +class Country(PlaceRecord): + """Contains data for the country record associated with an IP address. + + This class contains the country-level data associated with an IP address. + + Attributes: + + + .. attribute:: confidence + + A value from 0-100 indicating MaxMind's confidence that + the country is correct. This attribute is only available from the + Insights end point and the GeoIP2 Enterprise database. + + :type: int + + .. attribute:: geoname_id + + The GeoName ID for the country. + + :type: int + + .. attribute:: is_in_european_union + + This is true if the country is a member state of the European Union. + + :type: bool + + .. attribute:: iso_code + + The two-character `ISO 3166-1 + `_ alpha code for the + country. + + :type: unicode + + .. attribute:: name + + The name of the country based on the locales list passed to the + constructor. + + :type: unicode + + .. attribute:: names + + A dictionary where the keys are locale codes and the values + are names. + + :type: dict + + """ + + _valid_attributes = set([ + 'confidence', 'geoname_id', 'is_in_european_union', 'iso_code', 'names' + ]) + + def __init__(self, locales=None, **kwargs): + if 'is_in_european_union' not in kwargs: + kwargs['is_in_european_union'] = False + super(Country, self).__init__(locales, **kwargs) + + +class RepresentedCountry(Country): + """Contains data for the represented country associated with an IP address. + + This class contains the country-level data associated with an IP address + for the IP's represented country. The represented country is the country + represented by something like a military base. + + Attributes: + + + .. attribute:: confidence + + A value from 0-100 indicating MaxMind's confidence that + the country is correct. This attribute is only available from the + Insights end point and the GeoIP2 Enterprise database. + + :type: int + + .. attribute:: geoname_id + + The GeoName ID for the country. + + :type: int + + .. attribute:: is_in_european_union + + This is true if the country is a member state of the European Union. + + :type: bool + + .. attribute:: iso_code + + The two-character `ISO 3166-1 + `_ alpha code for the country. + + :type: unicode + + .. attribute:: name + + The name of the country based on the locales list passed to the + constructor. + + :type: unicode + + .. attribute:: names + + A dictionary where the keys are locale codes and the values + are names. + + :type: dict + + + .. attribute:: type + + A string indicating the type of entity that is representing the + country. Currently we only return ``military`` but this could expand to + include other types in the future. + + :type: unicode + + """ + + _valid_attributes = set([ + 'confidence', 'geoname_id', 'is_in_european_union', 'iso_code', + 'names', 'type' + ]) + + +class Location(Record): + """Contains data for the location record associated with an IP address. + + This class contains the location data associated with an IP address. + + This record is returned by ``city``, ``enterprise``, and ``insights``. + + Attributes: + + .. attribute:: average_income + + The average income in US dollars associated with the requested IP + address. This attribute is only available from the Insights end point. + + :type: int + + .. attribute:: accuracy_radius + + The approximate accuracy radius in kilometers around the latitude and + longitude for the IP address. This is the radius where we have a 67% + confidence that the device using the IP address resides within the + circle centered at the latitude and longitude with the provided radius. + + :type: int + + .. attribute:: latitude + + The approximate latitude of the location associated with the IP + address. This value is not precise and should not be used to identify a + particular address or household. + + :type: float + + .. attribute:: longitude + + The approximate longitude of the location associated with the IP + address. This value is not precise and should not be used to identify a + particular address or household. + + :type: float + + .. attribute:: metro_code + + The metro code of the location if the + location is in the US. MaxMind returns the same metro codes as the + `Google AdWords API + `_. + + :type: int + + .. attribute:: population_density + + The estimated population per square kilometer associated with the IP + address. This attribute is only available from the Insights end point. + + :type: int + + .. attribute:: time_zone + + The time zone associated with location, as specified by the `IANA Time + Zone Database `_, e.g., + "America/New_York". + + :type: unicode + + """ + + _valid_attributes = set([ + 'average_income', 'accuracy_radius', 'latitude', 'longitude', + 'metro_code', 'population_density', 'postal_code', 'postal_confidence', + 'time_zone' + ]) + + +class MaxMind(Record): + """Contains data related to your MaxMind account. + + Attributes: + + .. attribute:: queries_remaining + + The number of remaining queries you have + for the end point you are calling. + + :type: int + + """ + + _valid_attributes = set(['queries_remaining']) + + +class Postal(Record): + """Contains data for the postal record associated with an IP address. + + This class contains the postal data associated with an IP address. + + This attribute is returned by ``city``, ``enterprise``, and ``insights``. + + Attributes: + + .. attribute:: code + + The postal code of the location. Postal + codes are not available for all countries. In some countries, this will + only contain part of the postal code. + + :type: unicode + + .. attribute:: confidence + + A value from 0-100 indicating + MaxMind's confidence that the postal code is correct. This attribute is + only available from the Insights end point and the GeoIP2 Enterprise + database. + + :type: int + + """ + + _valid_attributes = set(['code', 'confidence']) + + +class Subdivision(PlaceRecord): + """Contains data for the subdivisions associated with an IP address. + + This class contains the subdivision data associated with an IP address. + + This attribute is returned by ``city``, ``enterprise``, and ``insights``. + + Attributes: + + .. attribute:: confidence + + This is a value from 0-100 indicating MaxMind's + confidence that the subdivision is correct. This attribute is only + available from the Insights end point and the GeoIP2 Enterprise + database. + + :type: int + + .. attribute:: geoname_id + + This is a GeoName ID for the subdivision. + + :type: int + + .. attribute:: iso_code + + This is a string up to three characters long + contain the subdivision portion of the `ISO 3166-2 code + `_. + + :type: unicode + + .. attribute:: name + + The name of the subdivision based on the locales list passed to the + constructor. + + :type: unicode + + .. attribute:: names + + A dictionary where the keys are locale codes and the + values are names + + :type: dict + + """ + + _valid_attributes = set(['confidence', 'geoname_id', 'iso_code', 'names']) + + +class Subdivisions(tuple): + """A tuple-like collection of subdivisions associated with an IP address. + + This class contains the subdivisions of the country associated with the + IP address from largest to smallest. + + For instance, the response for Oxford in the United Kingdom would have + England as the first element and Oxfordshire as the second element. + + This attribute is returned by ``city``, ``enterprise``, and ``insights``. + """ + + def __new__(cls, locales, *subdivisions): + subdivisions = [Subdivision(locales, **x) for x in subdivisions] + obj = super(cls, Subdivisions).__new__(cls, subdivisions) + return obj + + def __init__(self, locales, *subdivisions): # pylint:disable=W0613 + self._locales = locales + super(Subdivisions, self).__init__() + + @property + def most_specific(self): + """The most specific (smallest) subdivision available. + + If there are no :py:class:`Subdivision` objects for the response, + this returns an empty :py:class:`Subdivision`. + + :type: :py:class:`Subdivision` + """ + try: + return self[-1] + except IndexError: + return Subdivision(self._locales) + + +class Traits(Record): + """Contains data for the traits record associated with an IP address. + + This class contains the traits data associated with an IP address. + + This class has the following attributes: + + + .. attribute:: autonomous_system_number + + The `autonomous system + number `_ + associated with the IP address. This attribute is only available from + the City and Insights web service end points and the GeoIP2 Enterprise + database. + + :type: int + + .. attribute:: autonomous_system_organization + + The organization associated with the registered `autonomous system + number `_ for + the IP address. This attribute is only available from the City and + Insights web service end points and the GeoIP2 Enterprise database. + + :type: unicode + + .. attribute:: connection_type + + The connection type may take the following values: + + - Dialup + - Cable/DSL + - Corporate + - Cellular + + Additional values may be added in the future. + + This attribute is only available in the GeoIP2 Enterprise database. + + :type: unicode + + .. attribute:: domain + + The second level domain associated with the + IP address. This will be something like "example.com" or + "example.co.uk", not "foo.example.com". This attribute is only available + from the City and Insights web service end points and the GeoIP2 + Enterprise database. + + :type: unicode + + .. attribute:: ip_address + + The IP address that the data in the model + is for. If you performed a "me" lookup against the web service, this + will be the externally routable IP address for the system the code is + running on. If the system is behind a NAT, this may differ from the IP + address locally assigned to it. + + :type: unicode + + .. attribute:: is_anonymous + + This is true if the IP address belongs to any sort of anonymous network. + This attribute is only available from GeoIP2 Precision Insights. + + :type: bool + + .. attribute:: is_anonymous_proxy + + This is true if the IP is an anonymous + proxy. See http://dev.maxmind.com/faq/geoip#anonproxy for further + details. + + :type: bool + + .. deprecated:: 2.2.0 + Use our our `GeoIP2 Anonymous IP database + `_ + instead. + + .. attribute:: is_anonymous_vpn + + This is true if the IP address belongs to an anonymous VPN system. + This attribute is only available from GeoIP2 Precision Insights. + + :type: bool + + .. attribute:: is_hosting_provider + + This is true if the IP address belongs to a hosting provider. + This attribute is only available from GeoIP2 Precision Insights. + + :type: bool + + .. attribute:: is_legitimate_proxy + + This attribute is true if MaxMind believes this IP address to be a + legitimate proxy, such as an internal VPN used by a corporation. This + attribute is only available in the GeoIP2 Enterprise database. + + :type: bool + + .. attribute:: is_public_proxy + + This is true if the IP address belongs to a public proxy. This attribute + is only available from GeoIP2 Precision Insights. + + :type: bool + + .. attribute:: is_satellite_provider + + This is true if the IP address is from a satellite provider that + provides service to multiple countries. + + :type: bool + + .. deprecated:: 2.2.0 + Due to the increased coverage by mobile carriers, very few + satellite providers now serve multiple countries. As a result, the + output does not provide sufficiently relevant data for us to maintain + it. + + .. attribute:: is_tor_exit_node + + This is true if the IP address is a Tor exit node. This attribute is + only available from GeoIP2 Precision Insights. + + :type: bool + + .. attribute:: isp + + The name of the ISP associated with the IP address. This attribute is + only available from the City and Insights web service end points and the + GeoIP2 Enterprise database. + + :type: unicode + + .. attribute:: organization + + The name of the organization associated with the IP address. This + attribute is only available from the City and Insights web service end + points and the GeoIP2 Enterprise database. + + :type: unicode + + .. attribute:: user_type + + The user type associated with the IP + address. This can be one of the following values: + + * business + * cafe + * cellular + * college + * content_delivery_network + * dialup + * government + * hosting + * library + * military + * residential + * router + * school + * search_engine_spider + * traveler + + This attribute is only available from the Insights end point and the + GeoIP2 Enterprise database. + + :type: unicode + + """ + + _valid_attributes = set([ + 'autonomous_system_number', 'autonomous_system_organization', + 'connection_type', 'domain', 'is_anonymous', 'is_anonymous_proxy', + 'is_anonymous_vpn', 'is_hosting_provider', 'is_legitimate_proxy', + 'is_public_proxy', 'is_satellite_provider', 'is_tor_exit_node', + 'is_satellite_provider', 'isp', 'ip_address', 'organization', + 'user_type' + ]) + + def __init__(self, **kwargs): + for k in [ + 'is_anonymous', + 'is_anonymous_proxy', + 'is_anonymous_vpn', + 'is_hosting_provider', + 'is_legitimate_proxy', + 'is_public_proxy', + 'is_satellite_provider', + 'is_tor_exit_node', + ]: + kwargs[k] = bool(kwargs.get(k, False)) + super(Traits, self).__init__(**kwargs) diff --git a/lib/geoip2/webservice.py b/lib/geoip2/webservice.py new file mode 100644 index 0000000..3e238a3 --- /dev/null +++ b/lib/geoip2/webservice.py @@ -0,0 +1,235 @@ +""" +============================ +WebServices Client API +============================ + +This class provides a client API for all the GeoIP2 Precision web service end +points. The end points are Country, City, and Insights. Each end point returns +a different set of data about an IP address, with Country returning the least +data and Insights the most. + +Each web service end point is represented by a different model class, and +these model classes in turn contain multiple record classes. The record +classes have attributes which contain data about the IP address. + +If the web service does not return a particular piece of data for an IP +address, the associated attribute is not populated. + +The web service may not return any information for an entire record, in which +case all of the attributes for that record class will be empty. + +SSL +--- + +Requests to the GeoIP2 Precision web service are always made with SSL. + +""" + +import requests + +from requests.utils import default_user_agent + +import geoip2 +import geoip2.models + +from .compat import compat_ip_address + +from .errors import (AddressNotFoundError, AuthenticationError, GeoIP2Error, + HTTPError, InvalidRequestError, OutOfQueriesError, + PermissionRequiredError) + + +class Client(object): + """Creates a new client object. + + It accepts the following required arguments: + + :param account_id: Your MaxMind account ID. + :param license_key: Your MaxMind license key. + + Go to https://www.maxmind.com/en/my_license_key to see your MaxMind + account ID and license key. + + The following keyword arguments are also accepted: + + :param host: The hostname to make a request against. This defaults to + "geoip.maxmind.com". In most cases, you should not need to set this + explicitly. + :param locales: This is list of locale codes. This argument will be + passed on to record classes to use when their name properties are + called. The default value is ['en']. + + The order of the locales is significant. When a record class has + multiple names (country, city, etc.), its name property will return + the name in the first locale that has one. + + Note that the only locale which is always present in the GeoIP2 + data is "en". If you do not include this locale, the name property + may end up returning None even when the record has an English name. + + Currently, the valid locale codes are: + + * de -- German + * en -- English names may still include accented characters if that is + the accepted spelling in English. In other words, English does not + mean ASCII. + * es -- Spanish + * fr -- French + * ja -- Japanese + * pt-BR -- Brazilian Portuguese + * ru -- Russian + * zh-CN -- Simplified Chinese. + + """ + + def __init__( + self, + account_id=None, + license_key=None, + host='geoip.maxmind.com', + locales=None, + timeout=None, + + # This is deprecated and not documented for that reason. + # It can be removed if we do a major release in the future. + user_id=None): + """Construct a Client.""" + # pylint: disable=too-many-arguments + if locales is None: + locales = ['en'] + if account_id is None: + account_id = user_id + + if account_id is None: + raise TypeError('The account_id is a required parameter') + if license_key is None: + raise TypeError('The license_key is a required parameter') + + self._locales = locales + # requests 2.12.2 requires that the username passed to auth be bytes + # or a string, with the former being preferred. + self._account_id = account_id if isinstance(account_id, + bytes) else str(account_id) + self._license_key = license_key + self._base_uri = 'https://%s/geoip/v2.1' % host + self._timeout = timeout + + def city(self, ip_address='me'): + """Call GeoIP2 Precision City endpoint with the specified IP. + + :param ip_address: IPv4 or IPv6 address as a string. If no + address is provided, the address that the web service is + called from will be used. + + :returns: :py:class:`geoip2.models.City` object + + """ + return self._response_for('city', geoip2.models.City, ip_address) + + def country(self, ip_address='me'): + """Call the GeoIP2 Country endpoint with the specified IP. + + :param ip_address: IPv4 or IPv6 address as a string. If no address + is provided, the address that the web service is called from will + be used. + + :returns: :py:class:`geoip2.models.Country` object + + """ + return self._response_for('country', geoip2.models.Country, ip_address) + + def insights(self, ip_address='me'): + """Call the GeoIP2 Precision: Insights endpoint with the specified IP. + + :param ip_address: IPv4 or IPv6 address as a string. If no address + is provided, the address that the web service is called from will + be used. + + :returns: :py:class:`geoip2.models.Insights` object + + """ + return self._response_for('insights', geoip2.models.Insights, + ip_address) + + def _response_for(self, path, model_class, ip_address): + if ip_address != 'me': + ip_address = str(compat_ip_address(ip_address)) + uri = '/'.join([self._base_uri, path, ip_address]) + response = requests.get( + uri, + auth=(self._account_id, self._license_key), + headers={ + 'Accept': 'application/json', + 'User-Agent': self._user_agent() + }, + timeout=self._timeout) + if response.status_code != 200: + raise self._exception_for_error(response, uri) + body = self._handle_success(response, uri) + return model_class(body, locales=self._locales) + + def _user_agent(self): + return 'GeoIP2 Python Client v%s (%s)' % (geoip2.__version__, + default_user_agent()) + + def _handle_success(self, response, uri): + try: + return response.json() + except ValueError as ex: + raise GeoIP2Error('Received a 200 response for %(uri)s' + ' but could not decode the response as ' + 'JSON: ' % locals() + ', '.join(ex.args), 200, + uri) + + def _exception_for_error(self, response, uri): + status = response.status_code + + if 400 <= status < 500: + return self._exception_for_4xx_status(response, status, uri) + elif 500 <= status < 600: + return self._exception_for_5xx_status(status, uri) + return self._exception_for_non_200_status(status, uri) + + def _exception_for_4xx_status(self, response, status, uri): + if not response.content: + return HTTPError('Received a %(status)i error for %(uri)s ' + 'with no body.' % locals(), status, uri) + elif response.headers['Content-Type'].find('json') == -1: + return HTTPError('Received a %i for %s with the following ' + 'body: %s' % (status, uri, response.content), + status, uri) + try: + body = response.json() + except ValueError as ex: + return HTTPError( + 'Received a %(status)i error for %(uri)s but it did' + ' not include the expected JSON body: ' % locals() + + ', '.join(ex.args), status, uri) + else: + if 'code' in body and 'error' in body: + return self._exception_for_web_service_error( + body.get('error'), body.get('code'), status, uri) + return HTTPError('Response contains JSON but it does not specify ' + 'code or error keys', status, uri) + + def _exception_for_web_service_error(self, message, code, status, uri): + if code in ('IP_ADDRESS_NOT_FOUND', 'IP_ADDRESS_RESERVED'): + return AddressNotFoundError(message) + elif code in ('ACCOUNT_ID_REQUIRED', 'ACCOUNT_ID_UNKNOWN', + 'AUTHORIZATION_INVALID', 'LICENSE_KEY_REQUIRED', + 'USER_ID_REQUIRED', 'USER_ID_UNKNOWN'): + return AuthenticationError(message) + elif code in ('INSUFFICIENT_FUNDS', 'OUT_OF_QUERIES'): + return OutOfQueriesError(message) + elif code == 'PERMISSION_REQUIRED': + return PermissionRequiredError(message) + + return InvalidRequestError(message, code, status, uri) + + def _exception_for_5xx_status(self, status, uri): + return HTTPError('Received a server error (%(status)i) for ' + '%(uri)s' % locals(), status, uri) + + def _exception_for_non_200_status(self, status, uri): + return HTTPError('Received a very surprising HTTP status ' + '(%(status)i) for %(uri)s' % locals(), status, uri) diff --git a/lib/idna/__init__.py b/lib/idna/__init__.py new file mode 100644 index 0000000..847bf93 --- /dev/null +++ b/lib/idna/__init__.py @@ -0,0 +1,2 @@ +from .package_data import __version__ +from .core import * diff --git a/lib/idna/codec.py b/lib/idna/codec.py new file mode 100644 index 0000000..98c65ea --- /dev/null +++ b/lib/idna/codec.py @@ -0,0 +1,118 @@ +from .core import encode, decode, alabel, ulabel, IDNAError +import codecs +import re + +_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') + +class Codec(codecs.Codec): + + def encode(self, data, errors='strict'): + + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return "", 0 + + return encode(data), len(data) + + def decode(self, data, errors='strict'): + + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return u"", 0 + + return decode(data), len(data) + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + def _buffer_encode(self, data, errors, final): + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return ("", 0) + + labels = _unicode_dots_re.split(data) + trailing_dot = u'' + if labels: + if not labels[-1]: + trailing_dot = '.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = '.' + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result = ".".join(result) + trailing_dot + size += len(trailing_dot) + return (result, size) + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + def _buffer_decode(self, data, errors, final): + if errors != 'strict': + raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) + + if not data: + return (u"", 0) + + # IDNA allows decoding to operate on Unicode strings, too. + if isinstance(data, unicode): + labels = _unicode_dots_re.split(data) + else: + # Must be ASCII string + data = str(data) + unicode(data, "ascii") + labels = data.split(".") + + trailing_dot = u'' + if labels: + if not labels[-1]: + trailing_dot = u'.' + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = u'.' + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result = u".".join(result) + trailing_dot + size += len(trailing_dot) + return (result, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + +class StreamReader(Codec, codecs.StreamReader): + pass + +def getregentry(): + return codecs.CodecInfo( + name='idna', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) diff --git a/lib/idna/compat.py b/lib/idna/compat.py new file mode 100644 index 0000000..4d47f33 --- /dev/null +++ b/lib/idna/compat.py @@ -0,0 +1,12 @@ +from .core import * +from .codec import * + +def ToASCII(label): + return encode(label) + +def ToUnicode(label): + return decode(label) + +def nameprep(s): + raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") + diff --git a/lib/idna/core.py b/lib/idna/core.py new file mode 100644 index 0000000..090c2c1 --- /dev/null +++ b/lib/idna/core.py @@ -0,0 +1,399 @@ +from . import idnadata +import bisect +import unicodedata +import re +import sys +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b'xn--' +_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') + +if sys.version_info[0] == 3: + unicode = str + unichr = chr + +class IDNAError(UnicodeError): + """ Base exception for all IDNA-encoding related problems """ + pass + + +class IDNABidiError(IDNAError): + """ Exception when bidirectional requirements are not satisfied """ + pass + + +class InvalidCodepoint(IDNAError): + """ Exception when a disallowed or unallocated codepoint is used """ + pass + + +class InvalidCodepointContext(IDNAError): + """ Exception when the codepoint is not valid in the context it is used """ + pass + + +def _combining_class(cp): + v = unicodedata.combining(unichr(cp)) + if v == 0: + if not unicodedata.name(unichr(cp)): + raise ValueError("Unknown character in unicodedata") + return v + +def _is_script(cp, script): + return intranges_contain(ord(cp), idnadata.scripts[script]) + +def _punycode(s): + return s.encode('punycode') + +def _unot(s): + return 'U+{0:04X}'.format(s) + + +def valid_label_length(label): + + if len(label) > 63: + return False + return True + + +def valid_string_length(label, trailing_dot): + + if len(label) > (254 if trailing_dot else 253): + return False + return True + + +def check_bidi(label, check_ltr=False): + + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == '': + # String likely comes from a newer version of Unicode + raise IDNABidiError('Unknown directionality in label {0} at position {1}'.format(repr(label), idx)) + if direction in ['R', 'AL', 'AN']: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in ['R', 'AL']: + rtl = True + elif direction == 'L': + rtl = False + else: + raise IDNABidiError('First codepoint in label {0} must be directionality L, R or AL'.format(repr(label))) + + valid_ending = False + number_type = False + for (idx, cp) in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {0} in a right-to-left label'.format(idx)) + # Bidi rule 3 + if direction in ['R', 'AL', 'EN', 'AN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + # Bidi rule 4 + if direction in ['AN', 'EN']: + if not number_type: + number_type = direction + else: + if number_type != direction: + raise IDNABidiError('Can not mix numeral types in a right-to-left label') + else: + # Bidi rule 5 + if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: + raise IDNABidiError('Invalid direction for codepoint at position {0} in a left-to-right label'.format(idx)) + # Bidi rule 6 + if direction in ['L', 'EN']: + valid_ending = True + elif direction != 'NSM': + valid_ending = False + + if not valid_ending: + raise IDNABidiError('Label ends with illegal codepoint directionality') + + return True + + +def check_initial_combiner(label): + + if unicodedata.category(label[0])[0] == 'M': + raise IDNAError('Label begins with an illegal combining character') + return True + + +def check_hyphen_ok(label): + + if label[2:4] == '--': + raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') + if label[0] == '-' or label[-1] == '-': + raise IDNAError('Label must not start or end with a hyphen') + return True + + +def check_nfc(label): + + if unicodedata.normalize('NFC', label) != label: + raise IDNAError('Label must be in Normalization Form C') + + +def valid_contextj(label, pos): + + cp_value = ord(label[pos]) + + if cp_value == 0x200c: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos-1, -1, -1): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('L'), ord('D')]: + ok = True + break + + if not ok: + return False + + ok = False + for i in range(pos+1, len(label)): + joining_type = idnadata.joining_types.get(ord(label[i])) + if joining_type == ord('T'): + continue + if joining_type in [ord('R'), ord('D')]: + ok = True + break + return ok + + if cp_value == 0x200d: + + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + return False + + else: + + return False + + +def valid_contexto(label, pos, exception=False): + + cp_value = ord(label[pos]) + + if cp_value == 0x00b7: + if 0 < pos < len(label)-1: + if ord(label[pos - 1]) == 0x006c and ord(label[pos + 1]) == 0x006c: + return True + return False + + elif cp_value == 0x0375: + if pos < len(label)-1 and len(label) > 1: + return _is_script(label[pos + 1], 'Greek') + return False + + elif cp_value == 0x05f3 or cp_value == 0x05f4: + if pos > 0: + return _is_script(label[pos - 1], 'Hebrew') + return False + + elif cp_value == 0x30fb: + for cp in label: + if cp == u'\u30fb': + continue + if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): + return True + return False + + elif 0x660 <= cp_value <= 0x669: + for cp in label: + if 0x6f0 <= ord(cp) <= 0x06f9: + return False + return True + + elif 0x6f0 <= cp_value <= 0x6f9: + for cp in label: + if 0x660 <= ord(cp) <= 0x0669: + return False + return True + + +def check_label(label): + + if isinstance(label, (bytes, bytearray)): + label = label.decode('utf-8') + if len(label) == 0: + raise IDNAError('Empty Label') + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for (pos, cp) in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']): + continue + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format( + _unot(cp_value), pos+1, repr(label))) + except ValueError: + raise IDNAError('Unknown codepoint adjacent to joiner {0} at position {1} in {2}'.format( + _unot(cp_value), pos+1, repr(label))) + elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): + if not valid_contexto(label, pos): + raise InvalidCodepointContext('Codepoint {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label))) + else: + raise InvalidCodepoint('Codepoint {0} at position {1} of {2} not allowed'.format(_unot(cp_value), pos+1, repr(label))) + + check_bidi(label) + + +def alabel(label): + + try: + label = label.encode('ascii') + try: + ulabel(label) + except IDNAError: + raise IDNAError('The label {0} is not a valid A-label'.format(label)) + if not valid_label_length(label): + raise IDNAError('Label too long') + return label + except UnicodeEncodeError: + pass + + if not label: + raise IDNAError('No Input') + + label = unicode(label) + check_label(label) + label = _punycode(label) + label = _alabel_prefix + label + + if not valid_label_length(label): + raise IDNAError('Label too long') + + return label + + +def ulabel(label): + + if not isinstance(label, (bytes, bytearray)): + try: + label = label.encode('ascii') + except UnicodeEncodeError: + check_label(label) + return label + + label = label.lower() + if label.startswith(_alabel_prefix): + label = label[len(_alabel_prefix):] + else: + check_label(label) + return label.decode('ascii') + + label = label.decode('punycode') + check_label(label) + return label + + +def uts46_remap(domain, std3_rules=True, transitional=False): + """Re-map the characters in the string according to UTS46 processing.""" + from .uts46data import uts46data + output = u"" + try: + for pos, char in enumerate(domain): + code_point = ord(char) + uts46row = uts46data[code_point if code_point < 256 else + bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + status = uts46row[1] + replacement = uts46row[2] if len(uts46row) == 3 else None + if (status == "V" or + (status == "D" and not transitional) or + (status == "3" and not std3_rules and replacement is None)): + output += char + elif replacement is not None and (status == "M" or + (status == "3" and not std3_rules) or + (status == "D" and transitional)): + output += replacement + elif status != "I": + raise IndexError() + return unicodedata.normalize("NFC", output) + except IndexError: + raise InvalidCodepoint( + "Codepoint {0} not allowed at position {1} in {2}".format( + _unot(code_point), pos + 1, repr(domain))) + + +def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False): + + if isinstance(s, (bytes, bytearray)): + s = s.decode("ascii") + if uts46: + s = uts46_remap(s, std3_rules, transitional) + trailing_dot = False + result = [] + if strict: + labels = s.split('.') + else: + labels = _unicode_dots_re.split(s) + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if labels[-1] == '': + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(b'') + s = b'.'.join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError('Domain too long') + return s + + +def decode(s, strict=False, uts46=False, std3_rules=False): + + if isinstance(s, (bytes, bytearray)): + s = s.decode("ascii") + if uts46: + s = uts46_remap(s, std3_rules, False) + trailing_dot = False + result = [] + if not strict: + labels = _unicode_dots_re.split(s) + else: + labels = s.split(u'.') + if not labels or labels == ['']: + raise IDNAError('Empty domain') + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError('Empty label') + if trailing_dot: + result.append(u'') + return u'.'.join(result) diff --git a/lib/idna/idnadata.py b/lib/idna/idnadata.py new file mode 100644 index 0000000..17974e2 --- /dev/null +++ b/lib/idna/idnadata.py @@ -0,0 +1,1893 @@ +# This file is automatically generated by tools/idna-data + +__version__ = "10.0.0" +scripts = { + 'Greek': ( + 0x37000000374, + 0x37500000378, + 0x37a0000037e, + 0x37f00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038b, + 0x38c0000038d, + 0x38e000003a2, + 0x3a3000003e2, + 0x3f000000400, + 0x1d2600001d2b, + 0x1d5d00001d62, + 0x1d6600001d6b, + 0x1dbf00001dc0, + 0x1f0000001f16, + 0x1f1800001f1e, + 0x1f2000001f46, + 0x1f4800001f4e, + 0x1f5000001f58, + 0x1f5900001f5a, + 0x1f5b00001f5c, + 0x1f5d00001f5e, + 0x1f5f00001f7e, + 0x1f8000001fb5, + 0x1fb600001fc5, + 0x1fc600001fd4, + 0x1fd600001fdc, + 0x1fdd00001ff0, + 0x1ff200001ff5, + 0x1ff600001fff, + 0x212600002127, + 0xab650000ab66, + 0x101400001018f, + 0x101a0000101a1, + 0x1d2000001d246, + ), + 'Han': ( + 0x2e8000002e9a, + 0x2e9b00002ef4, + 0x2f0000002fd6, + 0x300500003006, + 0x300700003008, + 0x30210000302a, + 0x30380000303c, + 0x340000004db6, + 0x4e0000009feb, + 0xf9000000fa6e, + 0xfa700000fada, + 0x200000002a6d7, + 0x2a7000002b735, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + 0x2f8000002fa1e, + ), + 'Hebrew': ( + 0x591000005c8, + 0x5d0000005eb, + 0x5f0000005f5, + 0xfb1d0000fb37, + 0xfb380000fb3d, + 0xfb3e0000fb3f, + 0xfb400000fb42, + 0xfb430000fb45, + 0xfb460000fb50, + ), + 'Hiragana': ( + 0x304100003097, + 0x309d000030a0, + 0x1b0010001b11f, + 0x1f2000001f201, + ), + 'Katakana': ( + 0x30a1000030fb, + 0x30fd00003100, + 0x31f000003200, + 0x32d0000032ff, + 0x330000003358, + 0xff660000ff70, + 0xff710000ff9e, + 0x1b0000001b001, + ), +} +joining_types = { + 0x600: 85, + 0x601: 85, + 0x602: 85, + 0x603: 85, + 0x604: 85, + 0x605: 85, + 0x608: 85, + 0x60b: 85, + 0x620: 68, + 0x621: 85, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62a: 68, + 0x62b: 68, + 0x62c: 68, + 0x62d: 68, + 0x62e: 68, + 0x62f: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63a: 68, + 0x63b: 68, + 0x63c: 68, + 0x63d: 68, + 0x63e: 68, + 0x63f: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64a: 68, + 0x66e: 68, + 0x66f: 68, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x674: 85, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67a: 68, + 0x67b: 68, + 0x67c: 68, + 0x67d: 68, + 0x67e: 68, + 0x67f: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68a: 82, + 0x68b: 82, + 0x68c: 82, + 0x68d: 82, + 0x68e: 82, + 0x68f: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69a: 68, + 0x69b: 68, + 0x69c: 68, + 0x69d: 68, + 0x69e: 68, + 0x69f: 68, + 0x6a0: 68, + 0x6a1: 68, + 0x6a2: 68, + 0x6a3: 68, + 0x6a4: 68, + 0x6a5: 68, + 0x6a6: 68, + 0x6a7: 68, + 0x6a8: 68, + 0x6a9: 68, + 0x6aa: 68, + 0x6ab: 68, + 0x6ac: 68, + 0x6ad: 68, + 0x6ae: 68, + 0x6af: 68, + 0x6b0: 68, + 0x6b1: 68, + 0x6b2: 68, + 0x6b3: 68, + 0x6b4: 68, + 0x6b5: 68, + 0x6b6: 68, + 0x6b7: 68, + 0x6b8: 68, + 0x6b9: 68, + 0x6ba: 68, + 0x6bb: 68, + 0x6bc: 68, + 0x6bd: 68, + 0x6be: 68, + 0x6bf: 68, + 0x6c0: 82, + 0x6c1: 68, + 0x6c2: 68, + 0x6c3: 82, + 0x6c4: 82, + 0x6c5: 82, + 0x6c6: 82, + 0x6c7: 82, + 0x6c8: 82, + 0x6c9: 82, + 0x6ca: 82, + 0x6cb: 82, + 0x6cc: 68, + 0x6cd: 82, + 0x6ce: 68, + 0x6cf: 82, + 0x6d0: 68, + 0x6d1: 68, + 0x6d2: 82, + 0x6d3: 82, + 0x6d5: 82, + 0x6dd: 85, + 0x6ee: 82, + 0x6ef: 82, + 0x6fa: 68, + 0x6fb: 68, + 0x6fc: 68, + 0x6ff: 68, + 0x710: 82, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71a: 68, + 0x71b: 68, + 0x71c: 68, + 0x71d: 68, + 0x71e: 82, + 0x71f: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72a: 82, + 0x72b: 68, + 0x72c: 82, + 0x72d: 68, + 0x72e: 68, + 0x72f: 82, + 0x74d: 82, + 0x74e: 68, + 0x74f: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75a: 82, + 0x75b: 82, + 0x75c: 68, + 0x75d: 68, + 0x75e: 68, + 0x75f: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76a: 68, + 0x76b: 82, + 0x76c: 82, + 0x76d: 68, + 0x76e: 68, + 0x76f: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77a: 68, + 0x77b: 68, + 0x77c: 68, + 0x77d: 68, + 0x77e: 68, + 0x77f: 68, + 0x7ca: 68, + 0x7cb: 68, + 0x7cc: 68, + 0x7cd: 68, + 0x7ce: 68, + 0x7cf: 68, + 0x7d0: 68, + 0x7d1: 68, + 0x7d2: 68, + 0x7d3: 68, + 0x7d4: 68, + 0x7d5: 68, + 0x7d6: 68, + 0x7d7: 68, + 0x7d8: 68, + 0x7d9: 68, + 0x7da: 68, + 0x7db: 68, + 0x7dc: 68, + 0x7dd: 68, + 0x7de: 68, + 0x7df: 68, + 0x7e0: 68, + 0x7e1: 68, + 0x7e2: 68, + 0x7e3: 68, + 0x7e4: 68, + 0x7e5: 68, + 0x7e6: 68, + 0x7e7: 68, + 0x7e8: 68, + 0x7e9: 68, + 0x7ea: 68, + 0x7fa: 67, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84a: 68, + 0x84b: 68, + 0x84c: 68, + 0x84d: 68, + 0x84e: 68, + 0x84f: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 85, + 0x857: 85, + 0x858: 85, + 0x860: 68, + 0x861: 85, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x866: 85, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86a: 82, + 0x8a0: 68, + 0x8a1: 68, + 0x8a2: 68, + 0x8a3: 68, + 0x8a4: 68, + 0x8a5: 68, + 0x8a6: 68, + 0x8a7: 68, + 0x8a8: 68, + 0x8a9: 68, + 0x8aa: 82, + 0x8ab: 82, + 0x8ac: 82, + 0x8ad: 85, + 0x8ae: 82, + 0x8af: 68, + 0x8b0: 68, + 0x8b1: 82, + 0x8b2: 82, + 0x8b3: 68, + 0x8b4: 68, + 0x8b6: 68, + 0x8b7: 68, + 0x8b8: 68, + 0x8b9: 82, + 0x8ba: 68, + 0x8bb: 68, + 0x8bc: 68, + 0x8bd: 68, + 0x8e2: 85, + 0x1806: 85, + 0x1807: 68, + 0x180a: 67, + 0x180e: 85, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182a: 68, + 0x182b: 68, + 0x182c: 68, + 0x182d: 68, + 0x182e: 68, + 0x182f: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183a: 68, + 0x183b: 68, + 0x183c: 68, + 0x183d: 68, + 0x183e: 68, + 0x183f: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184a: 68, + 0x184b: 68, + 0x184c: 68, + 0x184d: 68, + 0x184e: 68, + 0x184f: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185a: 68, + 0x185b: 68, + 0x185c: 68, + 0x185d: 68, + 0x185e: 68, + 0x185f: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186a: 68, + 0x186b: 68, + 0x186c: 68, + 0x186d: 68, + 0x186e: 68, + 0x186f: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1880: 85, + 0x1881: 85, + 0x1882: 85, + 0x1883: 85, + 0x1884: 85, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188a: 68, + 0x188b: 68, + 0x188c: 68, + 0x188d: 68, + 0x188e: 68, + 0x188f: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189a: 68, + 0x189b: 68, + 0x189c: 68, + 0x189d: 68, + 0x189e: 68, + 0x189f: 68, + 0x18a0: 68, + 0x18a1: 68, + 0x18a2: 68, + 0x18a3: 68, + 0x18a4: 68, + 0x18a5: 68, + 0x18a6: 68, + 0x18a7: 68, + 0x18a8: 68, + 0x18aa: 68, + 0x200c: 85, + 0x200d: 67, + 0x202f: 85, + 0x2066: 85, + 0x2067: 85, + 0x2068: 85, + 0x2069: 85, + 0xa840: 68, + 0xa841: 68, + 0xa842: 68, + 0xa843: 68, + 0xa844: 68, + 0xa845: 68, + 0xa846: 68, + 0xa847: 68, + 0xa848: 68, + 0xa849: 68, + 0xa84a: 68, + 0xa84b: 68, + 0xa84c: 68, + 0xa84d: 68, + 0xa84e: 68, + 0xa84f: 68, + 0xa850: 68, + 0xa851: 68, + 0xa852: 68, + 0xa853: 68, + 0xa854: 68, + 0xa855: 68, + 0xa856: 68, + 0xa857: 68, + 0xa858: 68, + 0xa859: 68, + 0xa85a: 68, + 0xa85b: 68, + 0xa85c: 68, + 0xa85d: 68, + 0xa85e: 68, + 0xa85f: 68, + 0xa860: 68, + 0xa861: 68, + 0xa862: 68, + 0xa863: 68, + 0xa864: 68, + 0xa865: 68, + 0xa866: 68, + 0xa867: 68, + 0xa868: 68, + 0xa869: 68, + 0xa86a: 68, + 0xa86b: 68, + 0xa86c: 68, + 0xa86d: 68, + 0xa86e: 68, + 0xa86f: 68, + 0xa870: 68, + 0xa871: 68, + 0xa872: 76, + 0xa873: 85, + 0x10ac0: 68, + 0x10ac1: 68, + 0x10ac2: 68, + 0x10ac3: 68, + 0x10ac4: 68, + 0x10ac5: 82, + 0x10ac6: 85, + 0x10ac7: 82, + 0x10ac8: 85, + 0x10ac9: 82, + 0x10aca: 82, + 0x10acb: 85, + 0x10acc: 85, + 0x10acd: 76, + 0x10ace: 82, + 0x10acf: 82, + 0x10ad0: 82, + 0x10ad1: 82, + 0x10ad2: 82, + 0x10ad3: 68, + 0x10ad4: 68, + 0x10ad5: 68, + 0x10ad6: 68, + 0x10ad7: 76, + 0x10ad8: 68, + 0x10ad9: 68, + 0x10ada: 68, + 0x10adb: 68, + 0x10adc: 68, + 0x10add: 82, + 0x10ade: 68, + 0x10adf: 68, + 0x10ae0: 68, + 0x10ae1: 82, + 0x10ae2: 85, + 0x10ae3: 85, + 0x10ae4: 82, + 0x10aeb: 68, + 0x10aec: 68, + 0x10aed: 68, + 0x10aee: 68, + 0x10aef: 82, + 0x10b80: 68, + 0x10b81: 82, + 0x10b82: 68, + 0x10b83: 82, + 0x10b84: 82, + 0x10b85: 82, + 0x10b86: 68, + 0x10b87: 68, + 0x10b88: 68, + 0x10b89: 82, + 0x10b8a: 68, + 0x10b8b: 68, + 0x10b8c: 82, + 0x10b8d: 68, + 0x10b8e: 82, + 0x10b8f: 82, + 0x10b90: 68, + 0x10b91: 82, + 0x10ba9: 82, + 0x10baa: 82, + 0x10bab: 82, + 0x10bac: 82, + 0x10bad: 68, + 0x10bae: 68, + 0x10baf: 85, + 0x1e900: 68, + 0x1e901: 68, + 0x1e902: 68, + 0x1e903: 68, + 0x1e904: 68, + 0x1e905: 68, + 0x1e906: 68, + 0x1e907: 68, + 0x1e908: 68, + 0x1e909: 68, + 0x1e90a: 68, + 0x1e90b: 68, + 0x1e90c: 68, + 0x1e90d: 68, + 0x1e90e: 68, + 0x1e90f: 68, + 0x1e910: 68, + 0x1e911: 68, + 0x1e912: 68, + 0x1e913: 68, + 0x1e914: 68, + 0x1e915: 68, + 0x1e916: 68, + 0x1e917: 68, + 0x1e918: 68, + 0x1e919: 68, + 0x1e91a: 68, + 0x1e91b: 68, + 0x1e91c: 68, + 0x1e91d: 68, + 0x1e91e: 68, + 0x1e91f: 68, + 0x1e920: 68, + 0x1e921: 68, + 0x1e922: 68, + 0x1e923: 68, + 0x1e924: 68, + 0x1e925: 68, + 0x1e926: 68, + 0x1e927: 68, + 0x1e928: 68, + 0x1e929: 68, + 0x1e92a: 68, + 0x1e92b: 68, + 0x1e92c: 68, + 0x1e92d: 68, + 0x1e92e: 68, + 0x1e92f: 68, + 0x1e930: 68, + 0x1e931: 68, + 0x1e932: 68, + 0x1e933: 68, + 0x1e934: 68, + 0x1e935: 68, + 0x1e936: 68, + 0x1e937: 68, + 0x1e938: 68, + 0x1e939: 68, + 0x1e93a: 68, + 0x1e93b: 68, + 0x1e93c: 68, + 0x1e93d: 68, + 0x1e93e: 68, + 0x1e93f: 68, + 0x1e940: 68, + 0x1e941: 68, + 0x1e942: 68, + 0x1e943: 68, +} +codepoint_classes = { + 'PVALID': ( + 0x2d0000002e, + 0x300000003a, + 0x610000007b, + 0xdf000000f7, + 0xf800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010a, + 0x10b0000010c, + 0x10d0000010e, + 0x10f00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011a, + 0x11b0000011c, + 0x11d0000011e, + 0x11f00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012a, + 0x12b0000012c, + 0x12d0000012e, + 0x12f00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13a0000013b, + 0x13c0000013d, + 0x13e0000013f, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14b0000014c, + 0x14d0000014e, + 0x14f00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015a, + 0x15b0000015c, + 0x15d0000015e, + 0x15f00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016a, + 0x16b0000016c, + 0x16d0000016e, + 0x16f00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17a0000017b, + 0x17c0000017d, + 0x17e0000017f, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18c0000018e, + 0x19200000193, + 0x19500000196, + 0x1990000019c, + 0x19e0000019f, + 0x1a1000001a2, + 0x1a3000001a4, + 0x1a5000001a6, + 0x1a8000001a9, + 0x1aa000001ac, + 0x1ad000001ae, + 0x1b0000001b1, + 0x1b4000001b5, + 0x1b6000001b7, + 0x1b9000001bc, + 0x1bd000001c4, + 0x1ce000001cf, + 0x1d0000001d1, + 0x1d2000001d3, + 0x1d4000001d5, + 0x1d6000001d7, + 0x1d8000001d9, + 0x1da000001db, + 0x1dc000001de, + 0x1df000001e0, + 0x1e1000001e2, + 0x1e3000001e4, + 0x1e5000001e6, + 0x1e7000001e8, + 0x1e9000001ea, + 0x1eb000001ec, + 0x1ed000001ee, + 0x1ef000001f1, + 0x1f5000001f6, + 0x1f9000001fa, + 0x1fb000001fc, + 0x1fd000001fe, + 0x1ff00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020a, + 0x20b0000020c, + 0x20d0000020e, + 0x20f00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021a, + 0x21b0000021c, + 0x21d0000021e, + 0x21f00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022a, + 0x22b0000022c, + 0x22d0000022e, + 0x22f00000230, + 0x23100000232, + 0x2330000023a, + 0x23c0000023d, + 0x23f00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024a, + 0x24b0000024c, + 0x24d0000024e, + 0x24f000002b0, + 0x2b9000002c2, + 0x2c6000002d2, + 0x2ec000002ed, + 0x2ee000002ef, + 0x30000000340, + 0x34200000343, + 0x3460000034f, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37b0000037e, + 0x39000000391, + 0x3ac000003cf, + 0x3d7000003d8, + 0x3d9000003da, + 0x3db000003dc, + 0x3dd000003de, + 0x3df000003e0, + 0x3e1000003e2, + 0x3e3000003e4, + 0x3e5000003e6, + 0x3e7000003e8, + 0x3e9000003ea, + 0x3eb000003ec, + 0x3ed000003ee, + 0x3ef000003f0, + 0x3f3000003f4, + 0x3f8000003f9, + 0x3fb000003fd, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046a, + 0x46b0000046c, + 0x46d0000046e, + 0x46f00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047a, + 0x47b0000047c, + 0x47d0000047e, + 0x47f00000480, + 0x48100000482, + 0x48300000488, + 0x48b0000048c, + 0x48d0000048e, + 0x48f00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049a, + 0x49b0000049c, + 0x49d0000049e, + 0x49f000004a0, + 0x4a1000004a2, + 0x4a3000004a4, + 0x4a5000004a6, + 0x4a7000004a8, + 0x4a9000004aa, + 0x4ab000004ac, + 0x4ad000004ae, + 0x4af000004b0, + 0x4b1000004b2, + 0x4b3000004b4, + 0x4b5000004b6, + 0x4b7000004b8, + 0x4b9000004ba, + 0x4bb000004bc, + 0x4bd000004be, + 0x4bf000004c0, + 0x4c2000004c3, + 0x4c4000004c5, + 0x4c6000004c7, + 0x4c8000004c9, + 0x4ca000004cb, + 0x4cc000004cd, + 0x4ce000004d0, + 0x4d1000004d2, + 0x4d3000004d4, + 0x4d5000004d6, + 0x4d7000004d8, + 0x4d9000004da, + 0x4db000004dc, + 0x4dd000004de, + 0x4df000004e0, + 0x4e1000004e2, + 0x4e3000004e4, + 0x4e5000004e6, + 0x4e7000004e8, + 0x4e9000004ea, + 0x4eb000004ec, + 0x4ed000004ee, + 0x4ef000004f0, + 0x4f1000004f2, + 0x4f3000004f4, + 0x4f5000004f6, + 0x4f7000004f8, + 0x4f9000004fa, + 0x4fb000004fc, + 0x4fd000004fe, + 0x4ff00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050a, + 0x50b0000050c, + 0x50d0000050e, + 0x50f00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051a, + 0x51b0000051c, + 0x51d0000051e, + 0x51f00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052a, + 0x52b0000052c, + 0x52d0000052e, + 0x52f00000530, + 0x5590000055a, + 0x56100000587, + 0x591000005be, + 0x5bf000005c0, + 0x5c1000005c3, + 0x5c4000005c6, + 0x5c7000005c8, + 0x5d0000005eb, + 0x5f0000005f3, + 0x6100000061b, + 0x62000000640, + 0x64100000660, + 0x66e00000675, + 0x679000006d4, + 0x6d5000006dd, + 0x6df000006e9, + 0x6ea000006f0, + 0x6fa00000700, + 0x7100000074b, + 0x74d000007b2, + 0x7c0000007f6, + 0x8000000082e, + 0x8400000085c, + 0x8600000086b, + 0x8a0000008b5, + 0x8b6000008be, + 0x8d4000008e2, + 0x8e300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098d, + 0x98f00000991, + 0x993000009a9, + 0x9aa000009b1, + 0x9b2000009b3, + 0x9b6000009ba, + 0x9bc000009c5, + 0x9c7000009c9, + 0x9cb000009cf, + 0x9d7000009d8, + 0x9e0000009e4, + 0x9e6000009f2, + 0x9fc000009fd, + 0xa0100000a04, + 0xa0500000a0b, + 0xa0f00000a11, + 0xa1300000a29, + 0xa2a00000a31, + 0xa3200000a33, + 0xa3500000a36, + 0xa3800000a3a, + 0xa3c00000a3d, + 0xa3e00000a43, + 0xa4700000a49, + 0xa4b00000a4e, + 0xa5100000a52, + 0xa5c00000a5d, + 0xa6600000a76, + 0xa8100000a84, + 0xa8500000a8e, + 0xa8f00000a92, + 0xa9300000aa9, + 0xaaa00000ab1, + 0xab200000ab4, + 0xab500000aba, + 0xabc00000ac6, + 0xac700000aca, + 0xacb00000ace, + 0xad000000ad1, + 0xae000000ae4, + 0xae600000af0, + 0xaf900000b00, + 0xb0100000b04, + 0xb0500000b0d, + 0xb0f00000b11, + 0xb1300000b29, + 0xb2a00000b31, + 0xb3200000b34, + 0xb3500000b3a, + 0xb3c00000b45, + 0xb4700000b49, + 0xb4b00000b4e, + 0xb5600000b58, + 0xb5f00000b64, + 0xb6600000b70, + 0xb7100000b72, + 0xb8200000b84, + 0xb8500000b8b, + 0xb8e00000b91, + 0xb9200000b96, + 0xb9900000b9b, + 0xb9c00000b9d, + 0xb9e00000ba0, + 0xba300000ba5, + 0xba800000bab, + 0xbae00000bba, + 0xbbe00000bc3, + 0xbc600000bc9, + 0xbca00000bce, + 0xbd000000bd1, + 0xbd700000bd8, + 0xbe600000bf0, + 0xc0000000c04, + 0xc0500000c0d, + 0xc0e00000c11, + 0xc1200000c29, + 0xc2a00000c3a, + 0xc3d00000c45, + 0xc4600000c49, + 0xc4a00000c4e, + 0xc5500000c57, + 0xc5800000c5b, + 0xc6000000c64, + 0xc6600000c70, + 0xc8000000c84, + 0xc8500000c8d, + 0xc8e00000c91, + 0xc9200000ca9, + 0xcaa00000cb4, + 0xcb500000cba, + 0xcbc00000cc5, + 0xcc600000cc9, + 0xcca00000cce, + 0xcd500000cd7, + 0xcde00000cdf, + 0xce000000ce4, + 0xce600000cf0, + 0xcf100000cf3, + 0xd0000000d04, + 0xd0500000d0d, + 0xd0e00000d11, + 0xd1200000d45, + 0xd4600000d49, + 0xd4a00000d4f, + 0xd5400000d58, + 0xd5f00000d64, + 0xd6600000d70, + 0xd7a00000d80, + 0xd8200000d84, + 0xd8500000d97, + 0xd9a00000db2, + 0xdb300000dbc, + 0xdbd00000dbe, + 0xdc000000dc7, + 0xdca00000dcb, + 0xdcf00000dd5, + 0xdd600000dd7, + 0xdd800000de0, + 0xde600000df0, + 0xdf200000df4, + 0xe0100000e33, + 0xe3400000e3b, + 0xe4000000e4f, + 0xe5000000e5a, + 0xe8100000e83, + 0xe8400000e85, + 0xe8700000e89, + 0xe8a00000e8b, + 0xe8d00000e8e, + 0xe9400000e98, + 0xe9900000ea0, + 0xea100000ea4, + 0xea500000ea6, + 0xea700000ea8, + 0xeaa00000eac, + 0xead00000eb3, + 0xeb400000eba, + 0xebb00000ebe, + 0xec000000ec5, + 0xec600000ec7, + 0xec800000ece, + 0xed000000eda, + 0xede00000ee0, + 0xf0000000f01, + 0xf0b00000f0c, + 0xf1800000f1a, + 0xf2000000f2a, + 0xf3500000f36, + 0xf3700000f38, + 0xf3900000f3a, + 0xf3e00000f43, + 0xf4400000f48, + 0xf4900000f4d, + 0xf4e00000f52, + 0xf5300000f57, + 0xf5800000f5c, + 0xf5d00000f69, + 0xf6a00000f6d, + 0xf7100000f73, + 0xf7400000f75, + 0xf7a00000f81, + 0xf8200000f85, + 0xf8600000f93, + 0xf9400000f98, + 0xf9900000f9d, + 0xf9e00000fa2, + 0xfa300000fa7, + 0xfa800000fac, + 0xfad00000fb9, + 0xfba00000fbd, + 0xfc600000fc7, + 0x10000000104a, + 0x10500000109e, + 0x10d0000010fb, + 0x10fd00001100, + 0x120000001249, + 0x124a0000124e, + 0x125000001257, + 0x125800001259, + 0x125a0000125e, + 0x126000001289, + 0x128a0000128e, + 0x1290000012b1, + 0x12b2000012b6, + 0x12b8000012bf, + 0x12c0000012c1, + 0x12c2000012c6, + 0x12c8000012d7, + 0x12d800001311, + 0x131200001316, + 0x13180000135b, + 0x135d00001360, + 0x138000001390, + 0x13a0000013f6, + 0x14010000166d, + 0x166f00001680, + 0x16810000169b, + 0x16a0000016eb, + 0x16f1000016f9, + 0x17000000170d, + 0x170e00001715, + 0x172000001735, + 0x174000001754, + 0x17600000176d, + 0x176e00001771, + 0x177200001774, + 0x1780000017b4, + 0x17b6000017d4, + 0x17d7000017d8, + 0x17dc000017de, + 0x17e0000017ea, + 0x18100000181a, + 0x182000001878, + 0x1880000018ab, + 0x18b0000018f6, + 0x19000000191f, + 0x19200000192c, + 0x19300000193c, + 0x19460000196e, + 0x197000001975, + 0x1980000019ac, + 0x19b0000019ca, + 0x19d0000019da, + 0x1a0000001a1c, + 0x1a2000001a5f, + 0x1a6000001a7d, + 0x1a7f00001a8a, + 0x1a9000001a9a, + 0x1aa700001aa8, + 0x1ab000001abe, + 0x1b0000001b4c, + 0x1b5000001b5a, + 0x1b6b00001b74, + 0x1b8000001bf4, + 0x1c0000001c38, + 0x1c4000001c4a, + 0x1c4d00001c7e, + 0x1cd000001cd3, + 0x1cd400001cfa, + 0x1d0000001d2c, + 0x1d2f00001d30, + 0x1d3b00001d3c, + 0x1d4e00001d4f, + 0x1d6b00001d78, + 0x1d7900001d9b, + 0x1dc000001dfa, + 0x1dfb00001e00, + 0x1e0100001e02, + 0x1e0300001e04, + 0x1e0500001e06, + 0x1e0700001e08, + 0x1e0900001e0a, + 0x1e0b00001e0c, + 0x1e0d00001e0e, + 0x1e0f00001e10, + 0x1e1100001e12, + 0x1e1300001e14, + 0x1e1500001e16, + 0x1e1700001e18, + 0x1e1900001e1a, + 0x1e1b00001e1c, + 0x1e1d00001e1e, + 0x1e1f00001e20, + 0x1e2100001e22, + 0x1e2300001e24, + 0x1e2500001e26, + 0x1e2700001e28, + 0x1e2900001e2a, + 0x1e2b00001e2c, + 0x1e2d00001e2e, + 0x1e2f00001e30, + 0x1e3100001e32, + 0x1e3300001e34, + 0x1e3500001e36, + 0x1e3700001e38, + 0x1e3900001e3a, + 0x1e3b00001e3c, + 0x1e3d00001e3e, + 0x1e3f00001e40, + 0x1e4100001e42, + 0x1e4300001e44, + 0x1e4500001e46, + 0x1e4700001e48, + 0x1e4900001e4a, + 0x1e4b00001e4c, + 0x1e4d00001e4e, + 0x1e4f00001e50, + 0x1e5100001e52, + 0x1e5300001e54, + 0x1e5500001e56, + 0x1e5700001e58, + 0x1e5900001e5a, + 0x1e5b00001e5c, + 0x1e5d00001e5e, + 0x1e5f00001e60, + 0x1e6100001e62, + 0x1e6300001e64, + 0x1e6500001e66, + 0x1e6700001e68, + 0x1e6900001e6a, + 0x1e6b00001e6c, + 0x1e6d00001e6e, + 0x1e6f00001e70, + 0x1e7100001e72, + 0x1e7300001e74, + 0x1e7500001e76, + 0x1e7700001e78, + 0x1e7900001e7a, + 0x1e7b00001e7c, + 0x1e7d00001e7e, + 0x1e7f00001e80, + 0x1e8100001e82, + 0x1e8300001e84, + 0x1e8500001e86, + 0x1e8700001e88, + 0x1e8900001e8a, + 0x1e8b00001e8c, + 0x1e8d00001e8e, + 0x1e8f00001e90, + 0x1e9100001e92, + 0x1e9300001e94, + 0x1e9500001e9a, + 0x1e9c00001e9e, + 0x1e9f00001ea0, + 0x1ea100001ea2, + 0x1ea300001ea4, + 0x1ea500001ea6, + 0x1ea700001ea8, + 0x1ea900001eaa, + 0x1eab00001eac, + 0x1ead00001eae, + 0x1eaf00001eb0, + 0x1eb100001eb2, + 0x1eb300001eb4, + 0x1eb500001eb6, + 0x1eb700001eb8, + 0x1eb900001eba, + 0x1ebb00001ebc, + 0x1ebd00001ebe, + 0x1ebf00001ec0, + 0x1ec100001ec2, + 0x1ec300001ec4, + 0x1ec500001ec6, + 0x1ec700001ec8, + 0x1ec900001eca, + 0x1ecb00001ecc, + 0x1ecd00001ece, + 0x1ecf00001ed0, + 0x1ed100001ed2, + 0x1ed300001ed4, + 0x1ed500001ed6, + 0x1ed700001ed8, + 0x1ed900001eda, + 0x1edb00001edc, + 0x1edd00001ede, + 0x1edf00001ee0, + 0x1ee100001ee2, + 0x1ee300001ee4, + 0x1ee500001ee6, + 0x1ee700001ee8, + 0x1ee900001eea, + 0x1eeb00001eec, + 0x1eed00001eee, + 0x1eef00001ef0, + 0x1ef100001ef2, + 0x1ef300001ef4, + 0x1ef500001ef6, + 0x1ef700001ef8, + 0x1ef900001efa, + 0x1efb00001efc, + 0x1efd00001efe, + 0x1eff00001f08, + 0x1f1000001f16, + 0x1f2000001f28, + 0x1f3000001f38, + 0x1f4000001f46, + 0x1f5000001f58, + 0x1f6000001f68, + 0x1f7000001f71, + 0x1f7200001f73, + 0x1f7400001f75, + 0x1f7600001f77, + 0x1f7800001f79, + 0x1f7a00001f7b, + 0x1f7c00001f7d, + 0x1fb000001fb2, + 0x1fb600001fb7, + 0x1fc600001fc7, + 0x1fd000001fd3, + 0x1fd600001fd8, + 0x1fe000001fe3, + 0x1fe400001fe8, + 0x1ff600001ff7, + 0x214e0000214f, + 0x218400002185, + 0x2c3000002c5f, + 0x2c6100002c62, + 0x2c6500002c67, + 0x2c6800002c69, + 0x2c6a00002c6b, + 0x2c6c00002c6d, + 0x2c7100002c72, + 0x2c7300002c75, + 0x2c7600002c7c, + 0x2c8100002c82, + 0x2c8300002c84, + 0x2c8500002c86, + 0x2c8700002c88, + 0x2c8900002c8a, + 0x2c8b00002c8c, + 0x2c8d00002c8e, + 0x2c8f00002c90, + 0x2c9100002c92, + 0x2c9300002c94, + 0x2c9500002c96, + 0x2c9700002c98, + 0x2c9900002c9a, + 0x2c9b00002c9c, + 0x2c9d00002c9e, + 0x2c9f00002ca0, + 0x2ca100002ca2, + 0x2ca300002ca4, + 0x2ca500002ca6, + 0x2ca700002ca8, + 0x2ca900002caa, + 0x2cab00002cac, + 0x2cad00002cae, + 0x2caf00002cb0, + 0x2cb100002cb2, + 0x2cb300002cb4, + 0x2cb500002cb6, + 0x2cb700002cb8, + 0x2cb900002cba, + 0x2cbb00002cbc, + 0x2cbd00002cbe, + 0x2cbf00002cc0, + 0x2cc100002cc2, + 0x2cc300002cc4, + 0x2cc500002cc6, + 0x2cc700002cc8, + 0x2cc900002cca, + 0x2ccb00002ccc, + 0x2ccd00002cce, + 0x2ccf00002cd0, + 0x2cd100002cd2, + 0x2cd300002cd4, + 0x2cd500002cd6, + 0x2cd700002cd8, + 0x2cd900002cda, + 0x2cdb00002cdc, + 0x2cdd00002cde, + 0x2cdf00002ce0, + 0x2ce100002ce2, + 0x2ce300002ce5, + 0x2cec00002ced, + 0x2cee00002cf2, + 0x2cf300002cf4, + 0x2d0000002d26, + 0x2d2700002d28, + 0x2d2d00002d2e, + 0x2d3000002d68, + 0x2d7f00002d97, + 0x2da000002da7, + 0x2da800002daf, + 0x2db000002db7, + 0x2db800002dbf, + 0x2dc000002dc7, + 0x2dc800002dcf, + 0x2dd000002dd7, + 0x2dd800002ddf, + 0x2de000002e00, + 0x2e2f00002e30, + 0x300500003008, + 0x302a0000302e, + 0x303c0000303d, + 0x304100003097, + 0x30990000309b, + 0x309d0000309f, + 0x30a1000030fb, + 0x30fc000030ff, + 0x31050000312f, + 0x31a0000031bb, + 0x31f000003200, + 0x340000004db6, + 0x4e0000009feb, + 0xa0000000a48d, + 0xa4d00000a4fe, + 0xa5000000a60d, + 0xa6100000a62c, + 0xa6410000a642, + 0xa6430000a644, + 0xa6450000a646, + 0xa6470000a648, + 0xa6490000a64a, + 0xa64b0000a64c, + 0xa64d0000a64e, + 0xa64f0000a650, + 0xa6510000a652, + 0xa6530000a654, + 0xa6550000a656, + 0xa6570000a658, + 0xa6590000a65a, + 0xa65b0000a65c, + 0xa65d0000a65e, + 0xa65f0000a660, + 0xa6610000a662, + 0xa6630000a664, + 0xa6650000a666, + 0xa6670000a668, + 0xa6690000a66a, + 0xa66b0000a66c, + 0xa66d0000a670, + 0xa6740000a67e, + 0xa67f0000a680, + 0xa6810000a682, + 0xa6830000a684, + 0xa6850000a686, + 0xa6870000a688, + 0xa6890000a68a, + 0xa68b0000a68c, + 0xa68d0000a68e, + 0xa68f0000a690, + 0xa6910000a692, + 0xa6930000a694, + 0xa6950000a696, + 0xa6970000a698, + 0xa6990000a69a, + 0xa69b0000a69c, + 0xa69e0000a6e6, + 0xa6f00000a6f2, + 0xa7170000a720, + 0xa7230000a724, + 0xa7250000a726, + 0xa7270000a728, + 0xa7290000a72a, + 0xa72b0000a72c, + 0xa72d0000a72e, + 0xa72f0000a732, + 0xa7330000a734, + 0xa7350000a736, + 0xa7370000a738, + 0xa7390000a73a, + 0xa73b0000a73c, + 0xa73d0000a73e, + 0xa73f0000a740, + 0xa7410000a742, + 0xa7430000a744, + 0xa7450000a746, + 0xa7470000a748, + 0xa7490000a74a, + 0xa74b0000a74c, + 0xa74d0000a74e, + 0xa74f0000a750, + 0xa7510000a752, + 0xa7530000a754, + 0xa7550000a756, + 0xa7570000a758, + 0xa7590000a75a, + 0xa75b0000a75c, + 0xa75d0000a75e, + 0xa75f0000a760, + 0xa7610000a762, + 0xa7630000a764, + 0xa7650000a766, + 0xa7670000a768, + 0xa7690000a76a, + 0xa76b0000a76c, + 0xa76d0000a76e, + 0xa76f0000a770, + 0xa7710000a779, + 0xa77a0000a77b, + 0xa77c0000a77d, + 0xa77f0000a780, + 0xa7810000a782, + 0xa7830000a784, + 0xa7850000a786, + 0xa7870000a789, + 0xa78c0000a78d, + 0xa78e0000a790, + 0xa7910000a792, + 0xa7930000a796, + 0xa7970000a798, + 0xa7990000a79a, + 0xa79b0000a79c, + 0xa79d0000a79e, + 0xa79f0000a7a0, + 0xa7a10000a7a2, + 0xa7a30000a7a4, + 0xa7a50000a7a6, + 0xa7a70000a7a8, + 0xa7a90000a7aa, + 0xa7b50000a7b6, + 0xa7b70000a7b8, + 0xa7f70000a7f8, + 0xa7fa0000a828, + 0xa8400000a874, + 0xa8800000a8c6, + 0xa8d00000a8da, + 0xa8e00000a8f8, + 0xa8fb0000a8fc, + 0xa8fd0000a8fe, + 0xa9000000a92e, + 0xa9300000a954, + 0xa9800000a9c1, + 0xa9cf0000a9da, + 0xa9e00000a9ff, + 0xaa000000aa37, + 0xaa400000aa4e, + 0xaa500000aa5a, + 0xaa600000aa77, + 0xaa7a0000aac3, + 0xaadb0000aade, + 0xaae00000aaf0, + 0xaaf20000aaf7, + 0xab010000ab07, + 0xab090000ab0f, + 0xab110000ab17, + 0xab200000ab27, + 0xab280000ab2f, + 0xab300000ab5b, + 0xab600000ab66, + 0xabc00000abeb, + 0xabec0000abee, + 0xabf00000abfa, + 0xac000000d7a4, + 0xfa0e0000fa10, + 0xfa110000fa12, + 0xfa130000fa15, + 0xfa1f0000fa20, + 0xfa210000fa22, + 0xfa230000fa25, + 0xfa270000fa2a, + 0xfb1e0000fb1f, + 0xfe200000fe30, + 0xfe730000fe74, + 0x100000001000c, + 0x1000d00010027, + 0x100280001003b, + 0x1003c0001003e, + 0x1003f0001004e, + 0x100500001005e, + 0x10080000100fb, + 0x101fd000101fe, + 0x102800001029d, + 0x102a0000102d1, + 0x102e0000102e1, + 0x1030000010320, + 0x1032d00010341, + 0x103420001034a, + 0x103500001037b, + 0x103800001039e, + 0x103a0000103c4, + 0x103c8000103d0, + 0x104280001049e, + 0x104a0000104aa, + 0x104d8000104fc, + 0x1050000010528, + 0x1053000010564, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1080000010806, + 0x1080800010809, + 0x1080a00010836, + 0x1083700010839, + 0x1083c0001083d, + 0x1083f00010856, + 0x1086000010877, + 0x108800001089f, + 0x108e0000108f3, + 0x108f4000108f6, + 0x1090000010916, + 0x109200001093a, + 0x10980000109b8, + 0x109be000109c0, + 0x10a0000010a04, + 0x10a0500010a07, + 0x10a0c00010a14, + 0x10a1500010a18, + 0x10a1900010a34, + 0x10a3800010a3b, + 0x10a3f00010a40, + 0x10a6000010a7d, + 0x10a8000010a9d, + 0x10ac000010ac8, + 0x10ac900010ae7, + 0x10b0000010b36, + 0x10b4000010b56, + 0x10b6000010b73, + 0x10b8000010b92, + 0x10c0000010c49, + 0x10cc000010cf3, + 0x1100000011047, + 0x1106600011070, + 0x1107f000110bb, + 0x110d0000110e9, + 0x110f0000110fa, + 0x1110000011135, + 0x1113600011140, + 0x1115000011174, + 0x1117600011177, + 0x11180000111c5, + 0x111ca000111cd, + 0x111d0000111db, + 0x111dc000111dd, + 0x1120000011212, + 0x1121300011238, + 0x1123e0001123f, + 0x1128000011287, + 0x1128800011289, + 0x1128a0001128e, + 0x1128f0001129e, + 0x1129f000112a9, + 0x112b0000112eb, + 0x112f0000112fa, + 0x1130000011304, + 0x113050001130d, + 0x1130f00011311, + 0x1131300011329, + 0x1132a00011331, + 0x1133200011334, + 0x113350001133a, + 0x1133c00011345, + 0x1134700011349, + 0x1134b0001134e, + 0x1135000011351, + 0x1135700011358, + 0x1135d00011364, + 0x113660001136d, + 0x1137000011375, + 0x114000001144b, + 0x114500001145a, + 0x11480000114c6, + 0x114c7000114c8, + 0x114d0000114da, + 0x11580000115b6, + 0x115b8000115c1, + 0x115d8000115de, + 0x1160000011641, + 0x1164400011645, + 0x116500001165a, + 0x11680000116b8, + 0x116c0000116ca, + 0x117000001171a, + 0x1171d0001172c, + 0x117300001173a, + 0x118c0000118ea, + 0x118ff00011900, + 0x11a0000011a3f, + 0x11a4700011a48, + 0x11a5000011a84, + 0x11a8600011a9a, + 0x11ac000011af9, + 0x11c0000011c09, + 0x11c0a00011c37, + 0x11c3800011c41, + 0x11c5000011c5a, + 0x11c7200011c90, + 0x11c9200011ca8, + 0x11ca900011cb7, + 0x11d0000011d07, + 0x11d0800011d0a, + 0x11d0b00011d37, + 0x11d3a00011d3b, + 0x11d3c00011d3e, + 0x11d3f00011d48, + 0x11d5000011d5a, + 0x120000001239a, + 0x1248000012544, + 0x130000001342f, + 0x1440000014647, + 0x1680000016a39, + 0x16a4000016a5f, + 0x16a6000016a6a, + 0x16ad000016aee, + 0x16af000016af5, + 0x16b0000016b37, + 0x16b4000016b44, + 0x16b5000016b5a, + 0x16b6300016b78, + 0x16b7d00016b90, + 0x16f0000016f45, + 0x16f5000016f7f, + 0x16f8f00016fa0, + 0x16fe000016fe2, + 0x17000000187ed, + 0x1880000018af3, + 0x1b0000001b11f, + 0x1b1700001b2fc, + 0x1bc000001bc6b, + 0x1bc700001bc7d, + 0x1bc800001bc89, + 0x1bc900001bc9a, + 0x1bc9d0001bc9f, + 0x1da000001da37, + 0x1da3b0001da6d, + 0x1da750001da76, + 0x1da840001da85, + 0x1da9b0001daa0, + 0x1daa10001dab0, + 0x1e0000001e007, + 0x1e0080001e019, + 0x1e01b0001e022, + 0x1e0230001e025, + 0x1e0260001e02b, + 0x1e8000001e8c5, + 0x1e8d00001e8d7, + 0x1e9220001e94b, + 0x1e9500001e95a, + 0x200000002a6d7, + 0x2a7000002b735, + 0x2b7400002b81e, + 0x2b8200002cea2, + 0x2ceb00002ebe1, + ), + 'CONTEXTJ': ( + 0x200c0000200e, + ), + 'CONTEXTO': ( + 0xb7000000b8, + 0x37500000376, + 0x5f3000005f5, + 0x6600000066a, + 0x6f0000006fa, + 0x30fb000030fc, + ), +} diff --git a/lib/idna/intranges.py b/lib/idna/intranges.py new file mode 100644 index 0000000..fa8a735 --- /dev/null +++ b/lib/idna/intranges.py @@ -0,0 +1,53 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect + +def intranges_from_list(list_): + """Represent a list of integers as a sequence of ranges: + ((start_0, end_0), (start_1, end_1), ...), such that the original + integers are exactly those x such that start_i <= x < end_i for some i. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i+1 < len(sorted_list): + if sorted_list[i] == sorted_list[i+1]-1: + continue + current_range = sorted_list[last_write+1:i+1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + +def _encode_range(start, end): + return (start << 32) | end + +def _decode_range(r): + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_, ranges): + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos-1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/lib/idna/package_data.py b/lib/idna/package_data.py new file mode 100644 index 0000000..39c192b --- /dev/null +++ b/lib/idna/package_data.py @@ -0,0 +1,2 @@ +__version__ = '2.7' + diff --git a/lib/idna/uts46data.py b/lib/idna/uts46data.py new file mode 100644 index 0000000..79731cb --- /dev/null +++ b/lib/idna/uts46data.py @@ -0,0 +1,8179 @@ +# This file is automatically generated by tools/idna-data +# vim: set fileencoding=utf-8 : + +"""IDNA Mapping Table from UTS46.""" + + +__version__ = "10.0.0" +def _seg_0(): + return [ + (0x0, '3'), + (0x1, '3'), + (0x2, '3'), + (0x3, '3'), + (0x4, '3'), + (0x5, '3'), + (0x6, '3'), + (0x7, '3'), + (0x8, '3'), + (0x9, '3'), + (0xA, '3'), + (0xB, '3'), + (0xC, '3'), + (0xD, '3'), + (0xE, '3'), + (0xF, '3'), + (0x10, '3'), + (0x11, '3'), + (0x12, '3'), + (0x13, '3'), + (0x14, '3'), + (0x15, '3'), + (0x16, '3'), + (0x17, '3'), + (0x18, '3'), + (0x19, '3'), + (0x1A, '3'), + (0x1B, '3'), + (0x1C, '3'), + (0x1D, '3'), + (0x1E, '3'), + (0x1F, '3'), + (0x20, '3'), + (0x21, '3'), + (0x22, '3'), + (0x23, '3'), + (0x24, '3'), + (0x25, '3'), + (0x26, '3'), + (0x27, '3'), + (0x28, '3'), + (0x29, '3'), + (0x2A, '3'), + (0x2B, '3'), + (0x2C, '3'), + (0x2D, 'V'), + (0x2E, 'V'), + (0x2F, '3'), + (0x30, 'V'), + (0x31, 'V'), + (0x32, 'V'), + (0x33, 'V'), + (0x34, 'V'), + (0x35, 'V'), + (0x36, 'V'), + (0x37, 'V'), + (0x38, 'V'), + (0x39, 'V'), + (0x3A, '3'), + (0x3B, '3'), + (0x3C, '3'), + (0x3D, '3'), + (0x3E, '3'), + (0x3F, '3'), + (0x40, '3'), + (0x41, 'M', u'a'), + (0x42, 'M', u'b'), + (0x43, 'M', u'c'), + (0x44, 'M', u'd'), + (0x45, 'M', u'e'), + (0x46, 'M', u'f'), + (0x47, 'M', u'g'), + (0x48, 'M', u'h'), + (0x49, 'M', u'i'), + (0x4A, 'M', u'j'), + (0x4B, 'M', u'k'), + (0x4C, 'M', u'l'), + (0x4D, 'M', u'm'), + (0x4E, 'M', u'n'), + (0x4F, 'M', u'o'), + (0x50, 'M', u'p'), + (0x51, 'M', u'q'), + (0x52, 'M', u'r'), + (0x53, 'M', u's'), + (0x54, 'M', u't'), + (0x55, 'M', u'u'), + (0x56, 'M', u'v'), + (0x57, 'M', u'w'), + (0x58, 'M', u'x'), + (0x59, 'M', u'y'), + (0x5A, 'M', u'z'), + (0x5B, '3'), + (0x5C, '3'), + (0x5D, '3'), + (0x5E, '3'), + (0x5F, '3'), + (0x60, '3'), + (0x61, 'V'), + (0x62, 'V'), + (0x63, 'V'), + ] + +def _seg_1(): + return [ + (0x64, 'V'), + (0x65, 'V'), + (0x66, 'V'), + (0x67, 'V'), + (0x68, 'V'), + (0x69, 'V'), + (0x6A, 'V'), + (0x6B, 'V'), + (0x6C, 'V'), + (0x6D, 'V'), + (0x6E, 'V'), + (0x6F, 'V'), + (0x70, 'V'), + (0x71, 'V'), + (0x72, 'V'), + (0x73, 'V'), + (0x74, 'V'), + (0x75, 'V'), + (0x76, 'V'), + (0x77, 'V'), + (0x78, 'V'), + (0x79, 'V'), + (0x7A, 'V'), + (0x7B, '3'), + (0x7C, '3'), + (0x7D, '3'), + (0x7E, '3'), + (0x7F, '3'), + (0x80, 'X'), + (0x81, 'X'), + (0x82, 'X'), + (0x83, 'X'), + (0x84, 'X'), + (0x85, 'X'), + (0x86, 'X'), + (0x87, 'X'), + (0x88, 'X'), + (0x89, 'X'), + (0x8A, 'X'), + (0x8B, 'X'), + (0x8C, 'X'), + (0x8D, 'X'), + (0x8E, 'X'), + (0x8F, 'X'), + (0x90, 'X'), + (0x91, 'X'), + (0x92, 'X'), + (0x93, 'X'), + (0x94, 'X'), + (0x95, 'X'), + (0x96, 'X'), + (0x97, 'X'), + (0x98, 'X'), + (0x99, 'X'), + (0x9A, 'X'), + (0x9B, 'X'), + (0x9C, 'X'), + (0x9D, 'X'), + (0x9E, 'X'), + (0x9F, 'X'), + (0xA0, '3', u' '), + (0xA1, 'V'), + (0xA2, 'V'), + (0xA3, 'V'), + (0xA4, 'V'), + (0xA5, 'V'), + (0xA6, 'V'), + (0xA7, 'V'), + (0xA8, '3', u' ̈'), + (0xA9, 'V'), + (0xAA, 'M', u'a'), + (0xAB, 'V'), + (0xAC, 'V'), + (0xAD, 'I'), + (0xAE, 'V'), + (0xAF, '3', u' ̄'), + (0xB0, 'V'), + (0xB1, 'V'), + (0xB2, 'M', u'2'), + (0xB3, 'M', u'3'), + (0xB4, '3', u' ́'), + (0xB5, 'M', u'μ'), + (0xB6, 'V'), + (0xB7, 'V'), + (0xB8, '3', u' ̧'), + (0xB9, 'M', u'1'), + (0xBA, 'M', u'o'), + (0xBB, 'V'), + (0xBC, 'M', u'1⁄4'), + (0xBD, 'M', u'1⁄2'), + (0xBE, 'M', u'3⁄4'), + (0xBF, 'V'), + (0xC0, 'M', u'à'), + (0xC1, 'M', u'á'), + (0xC2, 'M', u'â'), + (0xC3, 'M', u'ã'), + (0xC4, 'M', u'ä'), + (0xC5, 'M', u'å'), + (0xC6, 'M', u'æ'), + (0xC7, 'M', u'ç'), + ] + +def _seg_2(): + return [ + (0xC8, 'M', u'è'), + (0xC9, 'M', u'é'), + (0xCA, 'M', u'ê'), + (0xCB, 'M', u'ë'), + (0xCC, 'M', u'ì'), + (0xCD, 'M', u'í'), + (0xCE, 'M', u'î'), + (0xCF, 'M', u'ï'), + (0xD0, 'M', u'ð'), + (0xD1, 'M', u'ñ'), + (0xD2, 'M', u'ò'), + (0xD3, 'M', u'ó'), + (0xD4, 'M', u'ô'), + (0xD5, 'M', u'õ'), + (0xD6, 'M', u'ö'), + (0xD7, 'V'), + (0xD8, 'M', u'ø'), + (0xD9, 'M', u'ù'), + (0xDA, 'M', u'ú'), + (0xDB, 'M', u'û'), + (0xDC, 'M', u'ü'), + (0xDD, 'M', u'ý'), + (0xDE, 'M', u'þ'), + (0xDF, 'D', u'ss'), + (0xE0, 'V'), + (0xE1, 'V'), + (0xE2, 'V'), + (0xE3, 'V'), + (0xE4, 'V'), + (0xE5, 'V'), + (0xE6, 'V'), + (0xE7, 'V'), + (0xE8, 'V'), + (0xE9, 'V'), + (0xEA, 'V'), + (0xEB, 'V'), + (0xEC, 'V'), + (0xED, 'V'), + (0xEE, 'V'), + (0xEF, 'V'), + (0xF0, 'V'), + (0xF1, 'V'), + (0xF2, 'V'), + (0xF3, 'V'), + (0xF4, 'V'), + (0xF5, 'V'), + (0xF6, 'V'), + (0xF7, 'V'), + (0xF8, 'V'), + (0xF9, 'V'), + (0xFA, 'V'), + (0xFB, 'V'), + (0xFC, 'V'), + (0xFD, 'V'), + (0xFE, 'V'), + (0xFF, 'V'), + (0x100, 'M', u'ā'), + (0x101, 'V'), + (0x102, 'M', u'ă'), + (0x103, 'V'), + (0x104, 'M', u'ą'), + (0x105, 'V'), + (0x106, 'M', u'ć'), + (0x107, 'V'), + (0x108, 'M', u'ĉ'), + (0x109, 'V'), + (0x10A, 'M', u'ċ'), + (0x10B, 'V'), + (0x10C, 'M', u'č'), + (0x10D, 'V'), + (0x10E, 'M', u'ď'), + (0x10F, 'V'), + (0x110, 'M', u'đ'), + (0x111, 'V'), + (0x112, 'M', u'ē'), + (0x113, 'V'), + (0x114, 'M', u'ĕ'), + (0x115, 'V'), + (0x116, 'M', u'ė'), + (0x117, 'V'), + (0x118, 'M', u'ę'), + (0x119, 'V'), + (0x11A, 'M', u'ě'), + (0x11B, 'V'), + (0x11C, 'M', u'ĝ'), + (0x11D, 'V'), + (0x11E, 'M', u'ğ'), + (0x11F, 'V'), + (0x120, 'M', u'ġ'), + (0x121, 'V'), + (0x122, 'M', u'ģ'), + (0x123, 'V'), + (0x124, 'M', u'ĥ'), + (0x125, 'V'), + (0x126, 'M', u'ħ'), + (0x127, 'V'), + (0x128, 'M', u'ĩ'), + (0x129, 'V'), + (0x12A, 'M', u'ī'), + (0x12B, 'V'), + ] + +def _seg_3(): + return [ + (0x12C, 'M', u'ĭ'), + (0x12D, 'V'), + (0x12E, 'M', u'į'), + (0x12F, 'V'), + (0x130, 'M', u'i̇'), + (0x131, 'V'), + (0x132, 'M', u'ij'), + (0x134, 'M', u'ĵ'), + (0x135, 'V'), + (0x136, 'M', u'ķ'), + (0x137, 'V'), + (0x139, 'M', u'ĺ'), + (0x13A, 'V'), + (0x13B, 'M', u'ļ'), + (0x13C, 'V'), + (0x13D, 'M', u'ľ'), + (0x13E, 'V'), + (0x13F, 'M', u'l·'), + (0x141, 'M', u'ł'), + (0x142, 'V'), + (0x143, 'M', u'ń'), + (0x144, 'V'), + (0x145, 'M', u'ņ'), + (0x146, 'V'), + (0x147, 'M', u'ň'), + (0x148, 'V'), + (0x149, 'M', u'ʼn'), + (0x14A, 'M', u'ŋ'), + (0x14B, 'V'), + (0x14C, 'M', u'ō'), + (0x14D, 'V'), + (0x14E, 'M', u'ŏ'), + (0x14F, 'V'), + (0x150, 'M', u'ő'), + (0x151, 'V'), + (0x152, 'M', u'œ'), + (0x153, 'V'), + (0x154, 'M', u'ŕ'), + (0x155, 'V'), + (0x156, 'M', u'ŗ'), + (0x157, 'V'), + (0x158, 'M', u'ř'), + (0x159, 'V'), + (0x15A, 'M', u'ś'), + (0x15B, 'V'), + (0x15C, 'M', u'ŝ'), + (0x15D, 'V'), + (0x15E, 'M', u'ş'), + (0x15F, 'V'), + (0x160, 'M', u'š'), + (0x161, 'V'), + (0x162, 'M', u'ţ'), + (0x163, 'V'), + (0x164, 'M', u'ť'), + (0x165, 'V'), + (0x166, 'M', u'ŧ'), + (0x167, 'V'), + (0x168, 'M', u'ũ'), + (0x169, 'V'), + (0x16A, 'M', u'ū'), + (0x16B, 'V'), + (0x16C, 'M', u'ŭ'), + (0x16D, 'V'), + (0x16E, 'M', u'ů'), + (0x16F, 'V'), + (0x170, 'M', u'ű'), + (0x171, 'V'), + (0x172, 'M', u'ų'), + (0x173, 'V'), + (0x174, 'M', u'ŵ'), + (0x175, 'V'), + (0x176, 'M', u'ŷ'), + (0x177, 'V'), + (0x178, 'M', u'ÿ'), + (0x179, 'M', u'ź'), + (0x17A, 'V'), + (0x17B, 'M', u'ż'), + (0x17C, 'V'), + (0x17D, 'M', u'ž'), + (0x17E, 'V'), + (0x17F, 'M', u's'), + (0x180, 'V'), + (0x181, 'M', u'ɓ'), + (0x182, 'M', u'ƃ'), + (0x183, 'V'), + (0x184, 'M', u'ƅ'), + (0x185, 'V'), + (0x186, 'M', u'ɔ'), + (0x187, 'M', u'ƈ'), + (0x188, 'V'), + (0x189, 'M', u'ɖ'), + (0x18A, 'M', u'ɗ'), + (0x18B, 'M', u'ƌ'), + (0x18C, 'V'), + (0x18E, 'M', u'ǝ'), + (0x18F, 'M', u'ə'), + (0x190, 'M', u'ɛ'), + (0x191, 'M', u'ƒ'), + (0x192, 'V'), + (0x193, 'M', u'ɠ'), + ] + +def _seg_4(): + return [ + (0x194, 'M', u'ɣ'), + (0x195, 'V'), + (0x196, 'M', u'ɩ'), + (0x197, 'M', u'ɨ'), + (0x198, 'M', u'ƙ'), + (0x199, 'V'), + (0x19C, 'M', u'ɯ'), + (0x19D, 'M', u'ɲ'), + (0x19E, 'V'), + (0x19F, 'M', u'ɵ'), + (0x1A0, 'M', u'ơ'), + (0x1A1, 'V'), + (0x1A2, 'M', u'ƣ'), + (0x1A3, 'V'), + (0x1A4, 'M', u'ƥ'), + (0x1A5, 'V'), + (0x1A6, 'M', u'ʀ'), + (0x1A7, 'M', u'ƨ'), + (0x1A8, 'V'), + (0x1A9, 'M', u'ʃ'), + (0x1AA, 'V'), + (0x1AC, 'M', u'ƭ'), + (0x1AD, 'V'), + (0x1AE, 'M', u'ʈ'), + (0x1AF, 'M', u'ư'), + (0x1B0, 'V'), + (0x1B1, 'M', u'ʊ'), + (0x1B2, 'M', u'ʋ'), + (0x1B3, 'M', u'ƴ'), + (0x1B4, 'V'), + (0x1B5, 'M', u'ƶ'), + (0x1B6, 'V'), + (0x1B7, 'M', u'ʒ'), + (0x1B8, 'M', u'ƹ'), + (0x1B9, 'V'), + (0x1BC, 'M', u'ƽ'), + (0x1BD, 'V'), + (0x1C4, 'M', u'dž'), + (0x1C7, 'M', u'lj'), + (0x1CA, 'M', u'nj'), + (0x1CD, 'M', u'ǎ'), + (0x1CE, 'V'), + (0x1CF, 'M', u'ǐ'), + (0x1D0, 'V'), + (0x1D1, 'M', u'ǒ'), + (0x1D2, 'V'), + (0x1D3, 'M', u'ǔ'), + (0x1D4, 'V'), + (0x1D5, 'M', u'ǖ'), + (0x1D6, 'V'), + (0x1D7, 'M', u'ǘ'), + (0x1D8, 'V'), + (0x1D9, 'M', u'ǚ'), + (0x1DA, 'V'), + (0x1DB, 'M', u'ǜ'), + (0x1DC, 'V'), + (0x1DE, 'M', u'ǟ'), + (0x1DF, 'V'), + (0x1E0, 'M', u'ǡ'), + (0x1E1, 'V'), + (0x1E2, 'M', u'ǣ'), + (0x1E3, 'V'), + (0x1E4, 'M', u'ǥ'), + (0x1E5, 'V'), + (0x1E6, 'M', u'ǧ'), + (0x1E7, 'V'), + (0x1E8, 'M', u'ǩ'), + (0x1E9, 'V'), + (0x1EA, 'M', u'ǫ'), + (0x1EB, 'V'), + (0x1EC, 'M', u'ǭ'), + (0x1ED, 'V'), + (0x1EE, 'M', u'ǯ'), + (0x1EF, 'V'), + (0x1F1, 'M', u'dz'), + (0x1F4, 'M', u'ǵ'), + (0x1F5, 'V'), + (0x1F6, 'M', u'ƕ'), + (0x1F7, 'M', u'ƿ'), + (0x1F8, 'M', u'ǹ'), + (0x1F9, 'V'), + (0x1FA, 'M', u'ǻ'), + (0x1FB, 'V'), + (0x1FC, 'M', u'ǽ'), + (0x1FD, 'V'), + (0x1FE, 'M', u'ǿ'), + (0x1FF, 'V'), + (0x200, 'M', u'ȁ'), + (0x201, 'V'), + (0x202, 'M', u'ȃ'), + (0x203, 'V'), + (0x204, 'M', u'ȅ'), + (0x205, 'V'), + (0x206, 'M', u'ȇ'), + (0x207, 'V'), + (0x208, 'M', u'ȉ'), + (0x209, 'V'), + (0x20A, 'M', u'ȋ'), + (0x20B, 'V'), + (0x20C, 'M', u'ȍ'), + ] + +def _seg_5(): + return [ + (0x20D, 'V'), + (0x20E, 'M', u'ȏ'), + (0x20F, 'V'), + (0x210, 'M', u'ȑ'), + (0x211, 'V'), + (0x212, 'M', u'ȓ'), + (0x213, 'V'), + (0x214, 'M', u'ȕ'), + (0x215, 'V'), + (0x216, 'M', u'ȗ'), + (0x217, 'V'), + (0x218, 'M', u'ș'), + (0x219, 'V'), + (0x21A, 'M', u'ț'), + (0x21B, 'V'), + (0x21C, 'M', u'ȝ'), + (0x21D, 'V'), + (0x21E, 'M', u'ȟ'), + (0x21F, 'V'), + (0x220, 'M', u'ƞ'), + (0x221, 'V'), + (0x222, 'M', u'ȣ'), + (0x223, 'V'), + (0x224, 'M', u'ȥ'), + (0x225, 'V'), + (0x226, 'M', u'ȧ'), + (0x227, 'V'), + (0x228, 'M', u'ȩ'), + (0x229, 'V'), + (0x22A, 'M', u'ȫ'), + (0x22B, 'V'), + (0x22C, 'M', u'ȭ'), + (0x22D, 'V'), + (0x22E, 'M', u'ȯ'), + (0x22F, 'V'), + (0x230, 'M', u'ȱ'), + (0x231, 'V'), + (0x232, 'M', u'ȳ'), + (0x233, 'V'), + (0x23A, 'M', u'ⱥ'), + (0x23B, 'M', u'ȼ'), + (0x23C, 'V'), + (0x23D, 'M', u'ƚ'), + (0x23E, 'M', u'ⱦ'), + (0x23F, 'V'), + (0x241, 'M', u'ɂ'), + (0x242, 'V'), + (0x243, 'M', u'ƀ'), + (0x244, 'M', u'ʉ'), + (0x245, 'M', u'ʌ'), + (0x246, 'M', u'ɇ'), + (0x247, 'V'), + (0x248, 'M', u'ɉ'), + (0x249, 'V'), + (0x24A, 'M', u'ɋ'), + (0x24B, 'V'), + (0x24C, 'M', u'ɍ'), + (0x24D, 'V'), + (0x24E, 'M', u'ɏ'), + (0x24F, 'V'), + (0x2B0, 'M', u'h'), + (0x2B1, 'M', u'ɦ'), + (0x2B2, 'M', u'j'), + (0x2B3, 'M', u'r'), + (0x2B4, 'M', u'ɹ'), + (0x2B5, 'M', u'ɻ'), + (0x2B6, 'M', u'ʁ'), + (0x2B7, 'M', u'w'), + (0x2B8, 'M', u'y'), + (0x2B9, 'V'), + (0x2D8, '3', u' ̆'), + (0x2D9, '3', u' ̇'), + (0x2DA, '3', u' ̊'), + (0x2DB, '3', u' ̨'), + (0x2DC, '3', u' ̃'), + (0x2DD, '3', u' ̋'), + (0x2DE, 'V'), + (0x2E0, 'M', u'ɣ'), + (0x2E1, 'M', u'l'), + (0x2E2, 'M', u's'), + (0x2E3, 'M', u'x'), + (0x2E4, 'M', u'ʕ'), + (0x2E5, 'V'), + (0x340, 'M', u'̀'), + (0x341, 'M', u'́'), + (0x342, 'V'), + (0x343, 'M', u'̓'), + (0x344, 'M', u'̈́'), + (0x345, 'M', u'ι'), + (0x346, 'V'), + (0x34F, 'I'), + (0x350, 'V'), + (0x370, 'M', u'ͱ'), + (0x371, 'V'), + (0x372, 'M', u'ͳ'), + (0x373, 'V'), + (0x374, 'M', u'ʹ'), + (0x375, 'V'), + (0x376, 'M', u'ͷ'), + (0x377, 'V'), + ] + +def _seg_6(): + return [ + (0x378, 'X'), + (0x37A, '3', u' ι'), + (0x37B, 'V'), + (0x37E, '3', u';'), + (0x37F, 'M', u'ϳ'), + (0x380, 'X'), + (0x384, '3', u' ́'), + (0x385, '3', u' ̈́'), + (0x386, 'M', u'ά'), + (0x387, 'M', u'·'), + (0x388, 'M', u'έ'), + (0x389, 'M', u'ή'), + (0x38A, 'M', u'ί'), + (0x38B, 'X'), + (0x38C, 'M', u'ό'), + (0x38D, 'X'), + (0x38E, 'M', u'ύ'), + (0x38F, 'M', u'ώ'), + (0x390, 'V'), + (0x391, 'M', u'α'), + (0x392, 'M', u'β'), + (0x393, 'M', u'γ'), + (0x394, 'M', u'δ'), + (0x395, 'M', u'ε'), + (0x396, 'M', u'ζ'), + (0x397, 'M', u'η'), + (0x398, 'M', u'θ'), + (0x399, 'M', u'ι'), + (0x39A, 'M', u'κ'), + (0x39B, 'M', u'λ'), + (0x39C, 'M', u'μ'), + (0x39D, 'M', u'ν'), + (0x39E, 'M', u'ξ'), + (0x39F, 'M', u'ο'), + (0x3A0, 'M', u'π'), + (0x3A1, 'M', u'ρ'), + (0x3A2, 'X'), + (0x3A3, 'M', u'σ'), + (0x3A4, 'M', u'τ'), + (0x3A5, 'M', u'υ'), + (0x3A6, 'M', u'φ'), + (0x3A7, 'M', u'χ'), + (0x3A8, 'M', u'ψ'), + (0x3A9, 'M', u'ω'), + (0x3AA, 'M', u'ϊ'), + (0x3AB, 'M', u'ϋ'), + (0x3AC, 'V'), + (0x3C2, 'D', u'σ'), + (0x3C3, 'V'), + (0x3CF, 'M', u'ϗ'), + (0x3D0, 'M', u'β'), + (0x3D1, 'M', u'θ'), + (0x3D2, 'M', u'υ'), + (0x3D3, 'M', u'ύ'), + (0x3D4, 'M', u'ϋ'), + (0x3D5, 'M', u'φ'), + (0x3D6, 'M', u'π'), + (0x3D7, 'V'), + (0x3D8, 'M', u'ϙ'), + (0x3D9, 'V'), + (0x3DA, 'M', u'ϛ'), + (0x3DB, 'V'), + (0x3DC, 'M', u'ϝ'), + (0x3DD, 'V'), + (0x3DE, 'M', u'ϟ'), + (0x3DF, 'V'), + (0x3E0, 'M', u'ϡ'), + (0x3E1, 'V'), + (0x3E2, 'M', u'ϣ'), + (0x3E3, 'V'), + (0x3E4, 'M', u'ϥ'), + (0x3E5, 'V'), + (0x3E6, 'M', u'ϧ'), + (0x3E7, 'V'), + (0x3E8, 'M', u'ϩ'), + (0x3E9, 'V'), + (0x3EA, 'M', u'ϫ'), + (0x3EB, 'V'), + (0x3EC, 'M', u'ϭ'), + (0x3ED, 'V'), + (0x3EE, 'M', u'ϯ'), + (0x3EF, 'V'), + (0x3F0, 'M', u'κ'), + (0x3F1, 'M', u'ρ'), + (0x3F2, 'M', u'σ'), + (0x3F3, 'V'), + (0x3F4, 'M', u'θ'), + (0x3F5, 'M', u'ε'), + (0x3F6, 'V'), + (0x3F7, 'M', u'ϸ'), + (0x3F8, 'V'), + (0x3F9, 'M', u'σ'), + (0x3FA, 'M', u'ϻ'), + (0x3FB, 'V'), + (0x3FD, 'M', u'ͻ'), + (0x3FE, 'M', u'ͼ'), + (0x3FF, 'M', u'ͽ'), + (0x400, 'M', u'ѐ'), + (0x401, 'M', u'ё'), + (0x402, 'M', u'ђ'), + ] + +def _seg_7(): + return [ + (0x403, 'M', u'ѓ'), + (0x404, 'M', u'є'), + (0x405, 'M', u'ѕ'), + (0x406, 'M', u'і'), + (0x407, 'M', u'ї'), + (0x408, 'M', u'ј'), + (0x409, 'M', u'љ'), + (0x40A, 'M', u'њ'), + (0x40B, 'M', u'ћ'), + (0x40C, 'M', u'ќ'), + (0x40D, 'M', u'ѝ'), + (0x40E, 'M', u'ў'), + (0x40F, 'M', u'џ'), + (0x410, 'M', u'а'), + (0x411, 'M', u'б'), + (0x412, 'M', u'в'), + (0x413, 'M', u'г'), + (0x414, 'M', u'д'), + (0x415, 'M', u'е'), + (0x416, 'M', u'ж'), + (0x417, 'M', u'з'), + (0x418, 'M', u'и'), + (0x419, 'M', u'й'), + (0x41A, 'M', u'к'), + (0x41B, 'M', u'л'), + (0x41C, 'M', u'м'), + (0x41D, 'M', u'н'), + (0x41E, 'M', u'о'), + (0x41F, 'M', u'п'), + (0x420, 'M', u'р'), + (0x421, 'M', u'с'), + (0x422, 'M', u'т'), + (0x423, 'M', u'у'), + (0x424, 'M', u'ф'), + (0x425, 'M', u'х'), + (0x426, 'M', u'ц'), + (0x427, 'M', u'ч'), + (0x428, 'M', u'ш'), + (0x429, 'M', u'щ'), + (0x42A, 'M', u'ъ'), + (0x42B, 'M', u'ы'), + (0x42C, 'M', u'ь'), + (0x42D, 'M', u'э'), + (0x42E, 'M', u'ю'), + (0x42F, 'M', u'я'), + (0x430, 'V'), + (0x460, 'M', u'ѡ'), + (0x461, 'V'), + (0x462, 'M', u'ѣ'), + (0x463, 'V'), + (0x464, 'M', u'ѥ'), + (0x465, 'V'), + (0x466, 'M', u'ѧ'), + (0x467, 'V'), + (0x468, 'M', u'ѩ'), + (0x469, 'V'), + (0x46A, 'M', u'ѫ'), + (0x46B, 'V'), + (0x46C, 'M', u'ѭ'), + (0x46D, 'V'), + (0x46E, 'M', u'ѯ'), + (0x46F, 'V'), + (0x470, 'M', u'ѱ'), + (0x471, 'V'), + (0x472, 'M', u'ѳ'), + (0x473, 'V'), + (0x474, 'M', u'ѵ'), + (0x475, 'V'), + (0x476, 'M', u'ѷ'), + (0x477, 'V'), + (0x478, 'M', u'ѹ'), + (0x479, 'V'), + (0x47A, 'M', u'ѻ'), + (0x47B, 'V'), + (0x47C, 'M', u'ѽ'), + (0x47D, 'V'), + (0x47E, 'M', u'ѿ'), + (0x47F, 'V'), + (0x480, 'M', u'ҁ'), + (0x481, 'V'), + (0x48A, 'M', u'ҋ'), + (0x48B, 'V'), + (0x48C, 'M', u'ҍ'), + (0x48D, 'V'), + (0x48E, 'M', u'ҏ'), + (0x48F, 'V'), + (0x490, 'M', u'ґ'), + (0x491, 'V'), + (0x492, 'M', u'ғ'), + (0x493, 'V'), + (0x494, 'M', u'ҕ'), + (0x495, 'V'), + (0x496, 'M', u'җ'), + (0x497, 'V'), + (0x498, 'M', u'ҙ'), + (0x499, 'V'), + (0x49A, 'M', u'қ'), + (0x49B, 'V'), + (0x49C, 'M', u'ҝ'), + (0x49D, 'V'), + ] + +def _seg_8(): + return [ + (0x49E, 'M', u'ҟ'), + (0x49F, 'V'), + (0x4A0, 'M', u'ҡ'), + (0x4A1, 'V'), + (0x4A2, 'M', u'ң'), + (0x4A3, 'V'), + (0x4A4, 'M', u'ҥ'), + (0x4A5, 'V'), + (0x4A6, 'M', u'ҧ'), + (0x4A7, 'V'), + (0x4A8, 'M', u'ҩ'), + (0x4A9, 'V'), + (0x4AA, 'M', u'ҫ'), + (0x4AB, 'V'), + (0x4AC, 'M', u'ҭ'), + (0x4AD, 'V'), + (0x4AE, 'M', u'ү'), + (0x4AF, 'V'), + (0x4B0, 'M', u'ұ'), + (0x4B1, 'V'), + (0x4B2, 'M', u'ҳ'), + (0x4B3, 'V'), + (0x4B4, 'M', u'ҵ'), + (0x4B5, 'V'), + (0x4B6, 'M', u'ҷ'), + (0x4B7, 'V'), + (0x4B8, 'M', u'ҹ'), + (0x4B9, 'V'), + (0x4BA, 'M', u'һ'), + (0x4BB, 'V'), + (0x4BC, 'M', u'ҽ'), + (0x4BD, 'V'), + (0x4BE, 'M', u'ҿ'), + (0x4BF, 'V'), + (0x4C0, 'X'), + (0x4C1, 'M', u'ӂ'), + (0x4C2, 'V'), + (0x4C3, 'M', u'ӄ'), + (0x4C4, 'V'), + (0x4C5, 'M', u'ӆ'), + (0x4C6, 'V'), + (0x4C7, 'M', u'ӈ'), + (0x4C8, 'V'), + (0x4C9, 'M', u'ӊ'), + (0x4CA, 'V'), + (0x4CB, 'M', u'ӌ'), + (0x4CC, 'V'), + (0x4CD, 'M', u'ӎ'), + (0x4CE, 'V'), + (0x4D0, 'M', u'ӑ'), + (0x4D1, 'V'), + (0x4D2, 'M', u'ӓ'), + (0x4D3, 'V'), + (0x4D4, 'M', u'ӕ'), + (0x4D5, 'V'), + (0x4D6, 'M', u'ӗ'), + (0x4D7, 'V'), + (0x4D8, 'M', u'ә'), + (0x4D9, 'V'), + (0x4DA, 'M', u'ӛ'), + (0x4DB, 'V'), + (0x4DC, 'M', u'ӝ'), + (0x4DD, 'V'), + (0x4DE, 'M', u'ӟ'), + (0x4DF, 'V'), + (0x4E0, 'M', u'ӡ'), + (0x4E1, 'V'), + (0x4E2, 'M', u'ӣ'), + (0x4E3, 'V'), + (0x4E4, 'M', u'ӥ'), + (0x4E5, 'V'), + (0x4E6, 'M', u'ӧ'), + (0x4E7, 'V'), + (0x4E8, 'M', u'ө'), + (0x4E9, 'V'), + (0x4EA, 'M', u'ӫ'), + (0x4EB, 'V'), + (0x4EC, 'M', u'ӭ'), + (0x4ED, 'V'), + (0x4EE, 'M', u'ӯ'), + (0x4EF, 'V'), + (0x4F0, 'M', u'ӱ'), + (0x4F1, 'V'), + (0x4F2, 'M', u'ӳ'), + (0x4F3, 'V'), + (0x4F4, 'M', u'ӵ'), + (0x4F5, 'V'), + (0x4F6, 'M', u'ӷ'), + (0x4F7, 'V'), + (0x4F8, 'M', u'ӹ'), + (0x4F9, 'V'), + (0x4FA, 'M', u'ӻ'), + (0x4FB, 'V'), + (0x4FC, 'M', u'ӽ'), + (0x4FD, 'V'), + (0x4FE, 'M', u'ӿ'), + (0x4FF, 'V'), + (0x500, 'M', u'ԁ'), + (0x501, 'V'), + (0x502, 'M', u'ԃ'), + ] + +def _seg_9(): + return [ + (0x503, 'V'), + (0x504, 'M', u'ԅ'), + (0x505, 'V'), + (0x506, 'M', u'ԇ'), + (0x507, 'V'), + (0x508, 'M', u'ԉ'), + (0x509, 'V'), + (0x50A, 'M', u'ԋ'), + (0x50B, 'V'), + (0x50C, 'M', u'ԍ'), + (0x50D, 'V'), + (0x50E, 'M', u'ԏ'), + (0x50F, 'V'), + (0x510, 'M', u'ԑ'), + (0x511, 'V'), + (0x512, 'M', u'ԓ'), + (0x513, 'V'), + (0x514, 'M', u'ԕ'), + (0x515, 'V'), + (0x516, 'M', u'ԗ'), + (0x517, 'V'), + (0x518, 'M', u'ԙ'), + (0x519, 'V'), + (0x51A, 'M', u'ԛ'), + (0x51B, 'V'), + (0x51C, 'M', u'ԝ'), + (0x51D, 'V'), + (0x51E, 'M', u'ԟ'), + (0x51F, 'V'), + (0x520, 'M', u'ԡ'), + (0x521, 'V'), + (0x522, 'M', u'ԣ'), + (0x523, 'V'), + (0x524, 'M', u'ԥ'), + (0x525, 'V'), + (0x526, 'M', u'ԧ'), + (0x527, 'V'), + (0x528, 'M', u'ԩ'), + (0x529, 'V'), + (0x52A, 'M', u'ԫ'), + (0x52B, 'V'), + (0x52C, 'M', u'ԭ'), + (0x52D, 'V'), + (0x52E, 'M', u'ԯ'), + (0x52F, 'V'), + (0x530, 'X'), + (0x531, 'M', u'ա'), + (0x532, 'M', u'բ'), + (0x533, 'M', u'գ'), + (0x534, 'M', u'դ'), + (0x535, 'M', u'ե'), + (0x536, 'M', u'զ'), + (0x537, 'M', u'է'), + (0x538, 'M', u'ը'), + (0x539, 'M', u'թ'), + (0x53A, 'M', u'ժ'), + (0x53B, 'M', u'ի'), + (0x53C, 'M', u'լ'), + (0x53D, 'M', u'խ'), + (0x53E, 'M', u'ծ'), + (0x53F, 'M', u'կ'), + (0x540, 'M', u'հ'), + (0x541, 'M', u'ձ'), + (0x542, 'M', u'ղ'), + (0x543, 'M', u'ճ'), + (0x544, 'M', u'մ'), + (0x545, 'M', u'յ'), + (0x546, 'M', u'ն'), + (0x547, 'M', u'շ'), + (0x548, 'M', u'ո'), + (0x549, 'M', u'չ'), + (0x54A, 'M', u'պ'), + (0x54B, 'M', u'ջ'), + (0x54C, 'M', u'ռ'), + (0x54D, 'M', u'ս'), + (0x54E, 'M', u'վ'), + (0x54F, 'M', u'տ'), + (0x550, 'M', u'ր'), + (0x551, 'M', u'ց'), + (0x552, 'M', u'ւ'), + (0x553, 'M', u'փ'), + (0x554, 'M', u'ք'), + (0x555, 'M', u'օ'), + (0x556, 'M', u'ֆ'), + (0x557, 'X'), + (0x559, 'V'), + (0x560, 'X'), + (0x561, 'V'), + (0x587, 'M', u'եւ'), + (0x588, 'X'), + (0x589, 'V'), + (0x58B, 'X'), + (0x58D, 'V'), + (0x590, 'X'), + (0x591, 'V'), + (0x5C8, 'X'), + (0x5D0, 'V'), + (0x5EB, 'X'), + (0x5F0, 'V'), + (0x5F5, 'X'), + ] + +def _seg_10(): + return [ + (0x606, 'V'), + (0x61C, 'X'), + (0x61E, 'V'), + (0x675, 'M', u'اٴ'), + (0x676, 'M', u'وٴ'), + (0x677, 'M', u'ۇٴ'), + (0x678, 'M', u'يٴ'), + (0x679, 'V'), + (0x6DD, 'X'), + (0x6DE, 'V'), + (0x70E, 'X'), + (0x710, 'V'), + (0x74B, 'X'), + (0x74D, 'V'), + (0x7B2, 'X'), + (0x7C0, 'V'), + (0x7FB, 'X'), + (0x800, 'V'), + (0x82E, 'X'), + (0x830, 'V'), + (0x83F, 'X'), + (0x840, 'V'), + (0x85C, 'X'), + (0x85E, 'V'), + (0x85F, 'X'), + (0x860, 'V'), + (0x86B, 'X'), + (0x8A0, 'V'), + (0x8B5, 'X'), + (0x8B6, 'V'), + (0x8BE, 'X'), + (0x8D4, 'V'), + (0x8E2, 'X'), + (0x8E3, 'V'), + (0x958, 'M', u'क़'), + (0x959, 'M', u'ख़'), + (0x95A, 'M', u'ग़'), + (0x95B, 'M', u'ज़'), + (0x95C, 'M', u'ड़'), + (0x95D, 'M', u'ढ़'), + (0x95E, 'M', u'फ़'), + (0x95F, 'M', u'य़'), + (0x960, 'V'), + (0x984, 'X'), + (0x985, 'V'), + (0x98D, 'X'), + (0x98F, 'V'), + (0x991, 'X'), + (0x993, 'V'), + (0x9A9, 'X'), + (0x9AA, 'V'), + (0x9B1, 'X'), + (0x9B2, 'V'), + (0x9B3, 'X'), + (0x9B6, 'V'), + (0x9BA, 'X'), + (0x9BC, 'V'), + (0x9C5, 'X'), + (0x9C7, 'V'), + (0x9C9, 'X'), + (0x9CB, 'V'), + (0x9CF, 'X'), + (0x9D7, 'V'), + (0x9D8, 'X'), + (0x9DC, 'M', u'ড়'), + (0x9DD, 'M', u'ঢ়'), + (0x9DE, 'X'), + (0x9DF, 'M', u'য়'), + (0x9E0, 'V'), + (0x9E4, 'X'), + (0x9E6, 'V'), + (0x9FE, 'X'), + (0xA01, 'V'), + (0xA04, 'X'), + (0xA05, 'V'), + (0xA0B, 'X'), + (0xA0F, 'V'), + (0xA11, 'X'), + (0xA13, 'V'), + (0xA29, 'X'), + (0xA2A, 'V'), + (0xA31, 'X'), + (0xA32, 'V'), + (0xA33, 'M', u'ਲ਼'), + (0xA34, 'X'), + (0xA35, 'V'), + (0xA36, 'M', u'ਸ਼'), + (0xA37, 'X'), + (0xA38, 'V'), + (0xA3A, 'X'), + (0xA3C, 'V'), + (0xA3D, 'X'), + (0xA3E, 'V'), + (0xA43, 'X'), + (0xA47, 'V'), + (0xA49, 'X'), + (0xA4B, 'V'), + (0xA4E, 'X'), + (0xA51, 'V'), + (0xA52, 'X'), + ] + +def _seg_11(): + return [ + (0xA59, 'M', u'ਖ਼'), + (0xA5A, 'M', u'ਗ਼'), + (0xA5B, 'M', u'ਜ਼'), + (0xA5C, 'V'), + (0xA5D, 'X'), + (0xA5E, 'M', u'ਫ਼'), + (0xA5F, 'X'), + (0xA66, 'V'), + (0xA76, 'X'), + (0xA81, 'V'), + (0xA84, 'X'), + (0xA85, 'V'), + (0xA8E, 'X'), + (0xA8F, 'V'), + (0xA92, 'X'), + (0xA93, 'V'), + (0xAA9, 'X'), + (0xAAA, 'V'), + (0xAB1, 'X'), + (0xAB2, 'V'), + (0xAB4, 'X'), + (0xAB5, 'V'), + (0xABA, 'X'), + (0xABC, 'V'), + (0xAC6, 'X'), + (0xAC7, 'V'), + (0xACA, 'X'), + (0xACB, 'V'), + (0xACE, 'X'), + (0xAD0, 'V'), + (0xAD1, 'X'), + (0xAE0, 'V'), + (0xAE4, 'X'), + (0xAE6, 'V'), + (0xAF2, 'X'), + (0xAF9, 'V'), + (0xB00, 'X'), + (0xB01, 'V'), + (0xB04, 'X'), + (0xB05, 'V'), + (0xB0D, 'X'), + (0xB0F, 'V'), + (0xB11, 'X'), + (0xB13, 'V'), + (0xB29, 'X'), + (0xB2A, 'V'), + (0xB31, 'X'), + (0xB32, 'V'), + (0xB34, 'X'), + (0xB35, 'V'), + (0xB3A, 'X'), + (0xB3C, 'V'), + (0xB45, 'X'), + (0xB47, 'V'), + (0xB49, 'X'), + (0xB4B, 'V'), + (0xB4E, 'X'), + (0xB56, 'V'), + (0xB58, 'X'), + (0xB5C, 'M', u'ଡ଼'), + (0xB5D, 'M', u'ଢ଼'), + (0xB5E, 'X'), + (0xB5F, 'V'), + (0xB64, 'X'), + (0xB66, 'V'), + (0xB78, 'X'), + (0xB82, 'V'), + (0xB84, 'X'), + (0xB85, 'V'), + (0xB8B, 'X'), + (0xB8E, 'V'), + (0xB91, 'X'), + (0xB92, 'V'), + (0xB96, 'X'), + (0xB99, 'V'), + (0xB9B, 'X'), + (0xB9C, 'V'), + (0xB9D, 'X'), + (0xB9E, 'V'), + (0xBA0, 'X'), + (0xBA3, 'V'), + (0xBA5, 'X'), + (0xBA8, 'V'), + (0xBAB, 'X'), + (0xBAE, 'V'), + (0xBBA, 'X'), + (0xBBE, 'V'), + (0xBC3, 'X'), + (0xBC6, 'V'), + (0xBC9, 'X'), + (0xBCA, 'V'), + (0xBCE, 'X'), + (0xBD0, 'V'), + (0xBD1, 'X'), + (0xBD7, 'V'), + (0xBD8, 'X'), + (0xBE6, 'V'), + (0xBFB, 'X'), + (0xC00, 'V'), + (0xC04, 'X'), + ] + +def _seg_12(): + return [ + (0xC05, 'V'), + (0xC0D, 'X'), + (0xC0E, 'V'), + (0xC11, 'X'), + (0xC12, 'V'), + (0xC29, 'X'), + (0xC2A, 'V'), + (0xC3A, 'X'), + (0xC3D, 'V'), + (0xC45, 'X'), + (0xC46, 'V'), + (0xC49, 'X'), + (0xC4A, 'V'), + (0xC4E, 'X'), + (0xC55, 'V'), + (0xC57, 'X'), + (0xC58, 'V'), + (0xC5B, 'X'), + (0xC60, 'V'), + (0xC64, 'X'), + (0xC66, 'V'), + (0xC70, 'X'), + (0xC78, 'V'), + (0xC84, 'X'), + (0xC85, 'V'), + (0xC8D, 'X'), + (0xC8E, 'V'), + (0xC91, 'X'), + (0xC92, 'V'), + (0xCA9, 'X'), + (0xCAA, 'V'), + (0xCB4, 'X'), + (0xCB5, 'V'), + (0xCBA, 'X'), + (0xCBC, 'V'), + (0xCC5, 'X'), + (0xCC6, 'V'), + (0xCC9, 'X'), + (0xCCA, 'V'), + (0xCCE, 'X'), + (0xCD5, 'V'), + (0xCD7, 'X'), + (0xCDE, 'V'), + (0xCDF, 'X'), + (0xCE0, 'V'), + (0xCE4, 'X'), + (0xCE6, 'V'), + (0xCF0, 'X'), + (0xCF1, 'V'), + (0xCF3, 'X'), + (0xD00, 'V'), + (0xD04, 'X'), + (0xD05, 'V'), + (0xD0D, 'X'), + (0xD0E, 'V'), + (0xD11, 'X'), + (0xD12, 'V'), + (0xD45, 'X'), + (0xD46, 'V'), + (0xD49, 'X'), + (0xD4A, 'V'), + (0xD50, 'X'), + (0xD54, 'V'), + (0xD64, 'X'), + (0xD66, 'V'), + (0xD80, 'X'), + (0xD82, 'V'), + (0xD84, 'X'), + (0xD85, 'V'), + (0xD97, 'X'), + (0xD9A, 'V'), + (0xDB2, 'X'), + (0xDB3, 'V'), + (0xDBC, 'X'), + (0xDBD, 'V'), + (0xDBE, 'X'), + (0xDC0, 'V'), + (0xDC7, 'X'), + (0xDCA, 'V'), + (0xDCB, 'X'), + (0xDCF, 'V'), + (0xDD5, 'X'), + (0xDD6, 'V'), + (0xDD7, 'X'), + (0xDD8, 'V'), + (0xDE0, 'X'), + (0xDE6, 'V'), + (0xDF0, 'X'), + (0xDF2, 'V'), + (0xDF5, 'X'), + (0xE01, 'V'), + (0xE33, 'M', u'ํา'), + (0xE34, 'V'), + (0xE3B, 'X'), + (0xE3F, 'V'), + (0xE5C, 'X'), + (0xE81, 'V'), + (0xE83, 'X'), + (0xE84, 'V'), + (0xE85, 'X'), + ] + +def _seg_13(): + return [ + (0xE87, 'V'), + (0xE89, 'X'), + (0xE8A, 'V'), + (0xE8B, 'X'), + (0xE8D, 'V'), + (0xE8E, 'X'), + (0xE94, 'V'), + (0xE98, 'X'), + (0xE99, 'V'), + (0xEA0, 'X'), + (0xEA1, 'V'), + (0xEA4, 'X'), + (0xEA5, 'V'), + (0xEA6, 'X'), + (0xEA7, 'V'), + (0xEA8, 'X'), + (0xEAA, 'V'), + (0xEAC, 'X'), + (0xEAD, 'V'), + (0xEB3, 'M', u'ໍາ'), + (0xEB4, 'V'), + (0xEBA, 'X'), + (0xEBB, 'V'), + (0xEBE, 'X'), + (0xEC0, 'V'), + (0xEC5, 'X'), + (0xEC6, 'V'), + (0xEC7, 'X'), + (0xEC8, 'V'), + (0xECE, 'X'), + (0xED0, 'V'), + (0xEDA, 'X'), + (0xEDC, 'M', u'ຫນ'), + (0xEDD, 'M', u'ຫມ'), + (0xEDE, 'V'), + (0xEE0, 'X'), + (0xF00, 'V'), + (0xF0C, 'M', u'་'), + (0xF0D, 'V'), + (0xF43, 'M', u'གྷ'), + (0xF44, 'V'), + (0xF48, 'X'), + (0xF49, 'V'), + (0xF4D, 'M', u'ཌྷ'), + (0xF4E, 'V'), + (0xF52, 'M', u'དྷ'), + (0xF53, 'V'), + (0xF57, 'M', u'བྷ'), + (0xF58, 'V'), + (0xF5C, 'M', u'ཛྷ'), + (0xF5D, 'V'), + (0xF69, 'M', u'ཀྵ'), + (0xF6A, 'V'), + (0xF6D, 'X'), + (0xF71, 'V'), + (0xF73, 'M', u'ཱི'), + (0xF74, 'V'), + (0xF75, 'M', u'ཱུ'), + (0xF76, 'M', u'ྲྀ'), + (0xF77, 'M', u'ྲཱྀ'), + (0xF78, 'M', u'ླྀ'), + (0xF79, 'M', u'ླཱྀ'), + (0xF7A, 'V'), + (0xF81, 'M', u'ཱྀ'), + (0xF82, 'V'), + (0xF93, 'M', u'ྒྷ'), + (0xF94, 'V'), + (0xF98, 'X'), + (0xF99, 'V'), + (0xF9D, 'M', u'ྜྷ'), + (0xF9E, 'V'), + (0xFA2, 'M', u'ྡྷ'), + (0xFA3, 'V'), + (0xFA7, 'M', u'ྦྷ'), + (0xFA8, 'V'), + (0xFAC, 'M', u'ྫྷ'), + (0xFAD, 'V'), + (0xFB9, 'M', u'ྐྵ'), + (0xFBA, 'V'), + (0xFBD, 'X'), + (0xFBE, 'V'), + (0xFCD, 'X'), + (0xFCE, 'V'), + (0xFDB, 'X'), + (0x1000, 'V'), + (0x10A0, 'X'), + (0x10C7, 'M', u'ⴧ'), + (0x10C8, 'X'), + (0x10CD, 'M', u'ⴭ'), + (0x10CE, 'X'), + (0x10D0, 'V'), + (0x10FC, 'M', u'ნ'), + (0x10FD, 'V'), + (0x115F, 'X'), + (0x1161, 'V'), + (0x1249, 'X'), + (0x124A, 'V'), + (0x124E, 'X'), + (0x1250, 'V'), + (0x1257, 'X'), + ] + +def _seg_14(): + return [ + (0x1258, 'V'), + (0x1259, 'X'), + (0x125A, 'V'), + (0x125E, 'X'), + (0x1260, 'V'), + (0x1289, 'X'), + (0x128A, 'V'), + (0x128E, 'X'), + (0x1290, 'V'), + (0x12B1, 'X'), + (0x12B2, 'V'), + (0x12B6, 'X'), + (0x12B8, 'V'), + (0x12BF, 'X'), + (0x12C0, 'V'), + (0x12C1, 'X'), + (0x12C2, 'V'), + (0x12C6, 'X'), + (0x12C8, 'V'), + (0x12D7, 'X'), + (0x12D8, 'V'), + (0x1311, 'X'), + (0x1312, 'V'), + (0x1316, 'X'), + (0x1318, 'V'), + (0x135B, 'X'), + (0x135D, 'V'), + (0x137D, 'X'), + (0x1380, 'V'), + (0x139A, 'X'), + (0x13A0, 'V'), + (0x13F6, 'X'), + (0x13F8, 'M', u'Ᏸ'), + (0x13F9, 'M', u'Ᏹ'), + (0x13FA, 'M', u'Ᏺ'), + (0x13FB, 'M', u'Ᏻ'), + (0x13FC, 'M', u'Ᏼ'), + (0x13FD, 'M', u'Ᏽ'), + (0x13FE, 'X'), + (0x1400, 'V'), + (0x1680, 'X'), + (0x1681, 'V'), + (0x169D, 'X'), + (0x16A0, 'V'), + (0x16F9, 'X'), + (0x1700, 'V'), + (0x170D, 'X'), + (0x170E, 'V'), + (0x1715, 'X'), + (0x1720, 'V'), + (0x1737, 'X'), + (0x1740, 'V'), + (0x1754, 'X'), + (0x1760, 'V'), + (0x176D, 'X'), + (0x176E, 'V'), + (0x1771, 'X'), + (0x1772, 'V'), + (0x1774, 'X'), + (0x1780, 'V'), + (0x17B4, 'X'), + (0x17B6, 'V'), + (0x17DE, 'X'), + (0x17E0, 'V'), + (0x17EA, 'X'), + (0x17F0, 'V'), + (0x17FA, 'X'), + (0x1800, 'V'), + (0x1806, 'X'), + (0x1807, 'V'), + (0x180B, 'I'), + (0x180E, 'X'), + (0x1810, 'V'), + (0x181A, 'X'), + (0x1820, 'V'), + (0x1878, 'X'), + (0x1880, 'V'), + (0x18AB, 'X'), + (0x18B0, 'V'), + (0x18F6, 'X'), + (0x1900, 'V'), + (0x191F, 'X'), + (0x1920, 'V'), + (0x192C, 'X'), + (0x1930, 'V'), + (0x193C, 'X'), + (0x1940, 'V'), + (0x1941, 'X'), + (0x1944, 'V'), + (0x196E, 'X'), + (0x1970, 'V'), + (0x1975, 'X'), + (0x1980, 'V'), + (0x19AC, 'X'), + (0x19B0, 'V'), + (0x19CA, 'X'), + (0x19D0, 'V'), + (0x19DB, 'X'), + (0x19DE, 'V'), + (0x1A1C, 'X'), + ] + +def _seg_15(): + return [ + (0x1A1E, 'V'), + (0x1A5F, 'X'), + (0x1A60, 'V'), + (0x1A7D, 'X'), + (0x1A7F, 'V'), + (0x1A8A, 'X'), + (0x1A90, 'V'), + (0x1A9A, 'X'), + (0x1AA0, 'V'), + (0x1AAE, 'X'), + (0x1AB0, 'V'), + (0x1ABF, 'X'), + (0x1B00, 'V'), + (0x1B4C, 'X'), + (0x1B50, 'V'), + (0x1B7D, 'X'), + (0x1B80, 'V'), + (0x1BF4, 'X'), + (0x1BFC, 'V'), + (0x1C38, 'X'), + (0x1C3B, 'V'), + (0x1C4A, 'X'), + (0x1C4D, 'V'), + (0x1C80, 'M', u'в'), + (0x1C81, 'M', u'д'), + (0x1C82, 'M', u'о'), + (0x1C83, 'M', u'с'), + (0x1C84, 'M', u'т'), + (0x1C86, 'M', u'ъ'), + (0x1C87, 'M', u'ѣ'), + (0x1C88, 'M', u'ꙋ'), + (0x1C89, 'X'), + (0x1CC0, 'V'), + (0x1CC8, 'X'), + (0x1CD0, 'V'), + (0x1CFA, 'X'), + (0x1D00, 'V'), + (0x1D2C, 'M', u'a'), + (0x1D2D, 'M', u'æ'), + (0x1D2E, 'M', u'b'), + (0x1D2F, 'V'), + (0x1D30, 'M', u'd'), + (0x1D31, 'M', u'e'), + (0x1D32, 'M', u'ǝ'), + (0x1D33, 'M', u'g'), + (0x1D34, 'M', u'h'), + (0x1D35, 'M', u'i'), + (0x1D36, 'M', u'j'), + (0x1D37, 'M', u'k'), + (0x1D38, 'M', u'l'), + (0x1D39, 'M', u'm'), + (0x1D3A, 'M', u'n'), + (0x1D3B, 'V'), + (0x1D3C, 'M', u'o'), + (0x1D3D, 'M', u'ȣ'), + (0x1D3E, 'M', u'p'), + (0x1D3F, 'M', u'r'), + (0x1D40, 'M', u't'), + (0x1D41, 'M', u'u'), + (0x1D42, 'M', u'w'), + (0x1D43, 'M', u'a'), + (0x1D44, 'M', u'ɐ'), + (0x1D45, 'M', u'ɑ'), + (0x1D46, 'M', u'ᴂ'), + (0x1D47, 'M', u'b'), + (0x1D48, 'M', u'd'), + (0x1D49, 'M', u'e'), + (0x1D4A, 'M', u'ə'), + (0x1D4B, 'M', u'ɛ'), + (0x1D4C, 'M', u'ɜ'), + (0x1D4D, 'M', u'g'), + (0x1D4E, 'V'), + (0x1D4F, 'M', u'k'), + (0x1D50, 'M', u'm'), + (0x1D51, 'M', u'ŋ'), + (0x1D52, 'M', u'o'), + (0x1D53, 'M', u'ɔ'), + (0x1D54, 'M', u'ᴖ'), + (0x1D55, 'M', u'ᴗ'), + (0x1D56, 'M', u'p'), + (0x1D57, 'M', u't'), + (0x1D58, 'M', u'u'), + (0x1D59, 'M', u'ᴝ'), + (0x1D5A, 'M', u'ɯ'), + (0x1D5B, 'M', u'v'), + (0x1D5C, 'M', u'ᴥ'), + (0x1D5D, 'M', u'β'), + (0x1D5E, 'M', u'γ'), + (0x1D5F, 'M', u'δ'), + (0x1D60, 'M', u'φ'), + (0x1D61, 'M', u'χ'), + (0x1D62, 'M', u'i'), + (0x1D63, 'M', u'r'), + (0x1D64, 'M', u'u'), + (0x1D65, 'M', u'v'), + (0x1D66, 'M', u'β'), + (0x1D67, 'M', u'γ'), + (0x1D68, 'M', u'ρ'), + (0x1D69, 'M', u'φ'), + (0x1D6A, 'M', u'χ'), + ] + +def _seg_16(): + return [ + (0x1D6B, 'V'), + (0x1D78, 'M', u'н'), + (0x1D79, 'V'), + (0x1D9B, 'M', u'ɒ'), + (0x1D9C, 'M', u'c'), + (0x1D9D, 'M', u'ɕ'), + (0x1D9E, 'M', u'ð'), + (0x1D9F, 'M', u'ɜ'), + (0x1DA0, 'M', u'f'), + (0x1DA1, 'M', u'ɟ'), + (0x1DA2, 'M', u'ɡ'), + (0x1DA3, 'M', u'ɥ'), + (0x1DA4, 'M', u'ɨ'), + (0x1DA5, 'M', u'ɩ'), + (0x1DA6, 'M', u'ɪ'), + (0x1DA7, 'M', u'ᵻ'), + (0x1DA8, 'M', u'ʝ'), + (0x1DA9, 'M', u'ɭ'), + (0x1DAA, 'M', u'ᶅ'), + (0x1DAB, 'M', u'ʟ'), + (0x1DAC, 'M', u'ɱ'), + (0x1DAD, 'M', u'ɰ'), + (0x1DAE, 'M', u'ɲ'), + (0x1DAF, 'M', u'ɳ'), + (0x1DB0, 'M', u'ɴ'), + (0x1DB1, 'M', u'ɵ'), + (0x1DB2, 'M', u'ɸ'), + (0x1DB3, 'M', u'ʂ'), + (0x1DB4, 'M', u'ʃ'), + (0x1DB5, 'M', u'ƫ'), + (0x1DB6, 'M', u'ʉ'), + (0x1DB7, 'M', u'ʊ'), + (0x1DB8, 'M', u'ᴜ'), + (0x1DB9, 'M', u'ʋ'), + (0x1DBA, 'M', u'ʌ'), + (0x1DBB, 'M', u'z'), + (0x1DBC, 'M', u'ʐ'), + (0x1DBD, 'M', u'ʑ'), + (0x1DBE, 'M', u'ʒ'), + (0x1DBF, 'M', u'θ'), + (0x1DC0, 'V'), + (0x1DFA, 'X'), + (0x1DFB, 'V'), + (0x1E00, 'M', u'ḁ'), + (0x1E01, 'V'), + (0x1E02, 'M', u'ḃ'), + (0x1E03, 'V'), + (0x1E04, 'M', u'ḅ'), + (0x1E05, 'V'), + (0x1E06, 'M', u'ḇ'), + (0x1E07, 'V'), + (0x1E08, 'M', u'ḉ'), + (0x1E09, 'V'), + (0x1E0A, 'M', u'ḋ'), + (0x1E0B, 'V'), + (0x1E0C, 'M', u'ḍ'), + (0x1E0D, 'V'), + (0x1E0E, 'M', u'ḏ'), + (0x1E0F, 'V'), + (0x1E10, 'M', u'ḑ'), + (0x1E11, 'V'), + (0x1E12, 'M', u'ḓ'), + (0x1E13, 'V'), + (0x1E14, 'M', u'ḕ'), + (0x1E15, 'V'), + (0x1E16, 'M', u'ḗ'), + (0x1E17, 'V'), + (0x1E18, 'M', u'ḙ'), + (0x1E19, 'V'), + (0x1E1A, 'M', u'ḛ'), + (0x1E1B, 'V'), + (0x1E1C, 'M', u'ḝ'), + (0x1E1D, 'V'), + (0x1E1E, 'M', u'ḟ'), + (0x1E1F, 'V'), + (0x1E20, 'M', u'ḡ'), + (0x1E21, 'V'), + (0x1E22, 'M', u'ḣ'), + (0x1E23, 'V'), + (0x1E24, 'M', u'ḥ'), + (0x1E25, 'V'), + (0x1E26, 'M', u'ḧ'), + (0x1E27, 'V'), + (0x1E28, 'M', u'ḩ'), + (0x1E29, 'V'), + (0x1E2A, 'M', u'ḫ'), + (0x1E2B, 'V'), + (0x1E2C, 'M', u'ḭ'), + (0x1E2D, 'V'), + (0x1E2E, 'M', u'ḯ'), + (0x1E2F, 'V'), + (0x1E30, 'M', u'ḱ'), + (0x1E31, 'V'), + (0x1E32, 'M', u'ḳ'), + (0x1E33, 'V'), + (0x1E34, 'M', u'ḵ'), + (0x1E35, 'V'), + (0x1E36, 'M', u'ḷ'), + (0x1E37, 'V'), + (0x1E38, 'M', u'ḹ'), + ] + +def _seg_17(): + return [ + (0x1E39, 'V'), + (0x1E3A, 'M', u'ḻ'), + (0x1E3B, 'V'), + (0x1E3C, 'M', u'ḽ'), + (0x1E3D, 'V'), + (0x1E3E, 'M', u'ḿ'), + (0x1E3F, 'V'), + (0x1E40, 'M', u'ṁ'), + (0x1E41, 'V'), + (0x1E42, 'M', u'ṃ'), + (0x1E43, 'V'), + (0x1E44, 'M', u'ṅ'), + (0x1E45, 'V'), + (0x1E46, 'M', u'ṇ'), + (0x1E47, 'V'), + (0x1E48, 'M', u'ṉ'), + (0x1E49, 'V'), + (0x1E4A, 'M', u'ṋ'), + (0x1E4B, 'V'), + (0x1E4C, 'M', u'ṍ'), + (0x1E4D, 'V'), + (0x1E4E, 'M', u'ṏ'), + (0x1E4F, 'V'), + (0x1E50, 'M', u'ṑ'), + (0x1E51, 'V'), + (0x1E52, 'M', u'ṓ'), + (0x1E53, 'V'), + (0x1E54, 'M', u'ṕ'), + (0x1E55, 'V'), + (0x1E56, 'M', u'ṗ'), + (0x1E57, 'V'), + (0x1E58, 'M', u'ṙ'), + (0x1E59, 'V'), + (0x1E5A, 'M', u'ṛ'), + (0x1E5B, 'V'), + (0x1E5C, 'M', u'ṝ'), + (0x1E5D, 'V'), + (0x1E5E, 'M', u'ṟ'), + (0x1E5F, 'V'), + (0x1E60, 'M', u'ṡ'), + (0x1E61, 'V'), + (0x1E62, 'M', u'ṣ'), + (0x1E63, 'V'), + (0x1E64, 'M', u'ṥ'), + (0x1E65, 'V'), + (0x1E66, 'M', u'ṧ'), + (0x1E67, 'V'), + (0x1E68, 'M', u'ṩ'), + (0x1E69, 'V'), + (0x1E6A, 'M', u'ṫ'), + (0x1E6B, 'V'), + (0x1E6C, 'M', u'ṭ'), + (0x1E6D, 'V'), + (0x1E6E, 'M', u'ṯ'), + (0x1E6F, 'V'), + (0x1E70, 'M', u'ṱ'), + (0x1E71, 'V'), + (0x1E72, 'M', u'ṳ'), + (0x1E73, 'V'), + (0x1E74, 'M', u'ṵ'), + (0x1E75, 'V'), + (0x1E76, 'M', u'ṷ'), + (0x1E77, 'V'), + (0x1E78, 'M', u'ṹ'), + (0x1E79, 'V'), + (0x1E7A, 'M', u'ṻ'), + (0x1E7B, 'V'), + (0x1E7C, 'M', u'ṽ'), + (0x1E7D, 'V'), + (0x1E7E, 'M', u'ṿ'), + (0x1E7F, 'V'), + (0x1E80, 'M', u'ẁ'), + (0x1E81, 'V'), + (0x1E82, 'M', u'ẃ'), + (0x1E83, 'V'), + (0x1E84, 'M', u'ẅ'), + (0x1E85, 'V'), + (0x1E86, 'M', u'ẇ'), + (0x1E87, 'V'), + (0x1E88, 'M', u'ẉ'), + (0x1E89, 'V'), + (0x1E8A, 'M', u'ẋ'), + (0x1E8B, 'V'), + (0x1E8C, 'M', u'ẍ'), + (0x1E8D, 'V'), + (0x1E8E, 'M', u'ẏ'), + (0x1E8F, 'V'), + (0x1E90, 'M', u'ẑ'), + (0x1E91, 'V'), + (0x1E92, 'M', u'ẓ'), + (0x1E93, 'V'), + (0x1E94, 'M', u'ẕ'), + (0x1E95, 'V'), + (0x1E9A, 'M', u'aʾ'), + (0x1E9B, 'M', u'ṡ'), + (0x1E9C, 'V'), + (0x1E9E, 'M', u'ss'), + (0x1E9F, 'V'), + (0x1EA0, 'M', u'ạ'), + (0x1EA1, 'V'), + ] + +def _seg_18(): + return [ + (0x1EA2, 'M', u'ả'), + (0x1EA3, 'V'), + (0x1EA4, 'M', u'ấ'), + (0x1EA5, 'V'), + (0x1EA6, 'M', u'ầ'), + (0x1EA7, 'V'), + (0x1EA8, 'M', u'ẩ'), + (0x1EA9, 'V'), + (0x1EAA, 'M', u'ẫ'), + (0x1EAB, 'V'), + (0x1EAC, 'M', u'ậ'), + (0x1EAD, 'V'), + (0x1EAE, 'M', u'ắ'), + (0x1EAF, 'V'), + (0x1EB0, 'M', u'ằ'), + (0x1EB1, 'V'), + (0x1EB2, 'M', u'ẳ'), + (0x1EB3, 'V'), + (0x1EB4, 'M', u'ẵ'), + (0x1EB5, 'V'), + (0x1EB6, 'M', u'ặ'), + (0x1EB7, 'V'), + (0x1EB8, 'M', u'ẹ'), + (0x1EB9, 'V'), + (0x1EBA, 'M', u'ẻ'), + (0x1EBB, 'V'), + (0x1EBC, 'M', u'ẽ'), + (0x1EBD, 'V'), + (0x1EBE, 'M', u'ế'), + (0x1EBF, 'V'), + (0x1EC0, 'M', u'ề'), + (0x1EC1, 'V'), + (0x1EC2, 'M', u'ể'), + (0x1EC3, 'V'), + (0x1EC4, 'M', u'ễ'), + (0x1EC5, 'V'), + (0x1EC6, 'M', u'ệ'), + (0x1EC7, 'V'), + (0x1EC8, 'M', u'ỉ'), + (0x1EC9, 'V'), + (0x1ECA, 'M', u'ị'), + (0x1ECB, 'V'), + (0x1ECC, 'M', u'ọ'), + (0x1ECD, 'V'), + (0x1ECE, 'M', u'ỏ'), + (0x1ECF, 'V'), + (0x1ED0, 'M', u'ố'), + (0x1ED1, 'V'), + (0x1ED2, 'M', u'ồ'), + (0x1ED3, 'V'), + (0x1ED4, 'M', u'ổ'), + (0x1ED5, 'V'), + (0x1ED6, 'M', u'ỗ'), + (0x1ED7, 'V'), + (0x1ED8, 'M', u'ộ'), + (0x1ED9, 'V'), + (0x1EDA, 'M', u'ớ'), + (0x1EDB, 'V'), + (0x1EDC, 'M', u'ờ'), + (0x1EDD, 'V'), + (0x1EDE, 'M', u'ở'), + (0x1EDF, 'V'), + (0x1EE0, 'M', u'ỡ'), + (0x1EE1, 'V'), + (0x1EE2, 'M', u'ợ'), + (0x1EE3, 'V'), + (0x1EE4, 'M', u'ụ'), + (0x1EE5, 'V'), + (0x1EE6, 'M', u'ủ'), + (0x1EE7, 'V'), + (0x1EE8, 'M', u'ứ'), + (0x1EE9, 'V'), + (0x1EEA, 'M', u'ừ'), + (0x1EEB, 'V'), + (0x1EEC, 'M', u'ử'), + (0x1EED, 'V'), + (0x1EEE, 'M', u'ữ'), + (0x1EEF, 'V'), + (0x1EF0, 'M', u'ự'), + (0x1EF1, 'V'), + (0x1EF2, 'M', u'ỳ'), + (0x1EF3, 'V'), + (0x1EF4, 'M', u'ỵ'), + (0x1EF5, 'V'), + (0x1EF6, 'M', u'ỷ'), + (0x1EF7, 'V'), + (0x1EF8, 'M', u'ỹ'), + (0x1EF9, 'V'), + (0x1EFA, 'M', u'ỻ'), + (0x1EFB, 'V'), + (0x1EFC, 'M', u'ỽ'), + (0x1EFD, 'V'), + (0x1EFE, 'M', u'ỿ'), + (0x1EFF, 'V'), + (0x1F08, 'M', u'ἀ'), + (0x1F09, 'M', u'ἁ'), + (0x1F0A, 'M', u'ἂ'), + (0x1F0B, 'M', u'ἃ'), + (0x1F0C, 'M', u'ἄ'), + (0x1F0D, 'M', u'ἅ'), + ] + +def _seg_19(): + return [ + (0x1F0E, 'M', u'ἆ'), + (0x1F0F, 'M', u'ἇ'), + (0x1F10, 'V'), + (0x1F16, 'X'), + (0x1F18, 'M', u'ἐ'), + (0x1F19, 'M', u'ἑ'), + (0x1F1A, 'M', u'ἒ'), + (0x1F1B, 'M', u'ἓ'), + (0x1F1C, 'M', u'ἔ'), + (0x1F1D, 'M', u'ἕ'), + (0x1F1E, 'X'), + (0x1F20, 'V'), + (0x1F28, 'M', u'ἠ'), + (0x1F29, 'M', u'ἡ'), + (0x1F2A, 'M', u'ἢ'), + (0x1F2B, 'M', u'ἣ'), + (0x1F2C, 'M', u'ἤ'), + (0x1F2D, 'M', u'ἥ'), + (0x1F2E, 'M', u'ἦ'), + (0x1F2F, 'M', u'ἧ'), + (0x1F30, 'V'), + (0x1F38, 'M', u'ἰ'), + (0x1F39, 'M', u'ἱ'), + (0x1F3A, 'M', u'ἲ'), + (0x1F3B, 'M', u'ἳ'), + (0x1F3C, 'M', u'ἴ'), + (0x1F3D, 'M', u'ἵ'), + (0x1F3E, 'M', u'ἶ'), + (0x1F3F, 'M', u'ἷ'), + (0x1F40, 'V'), + (0x1F46, 'X'), + (0x1F48, 'M', u'ὀ'), + (0x1F49, 'M', u'ὁ'), + (0x1F4A, 'M', u'ὂ'), + (0x1F4B, 'M', u'ὃ'), + (0x1F4C, 'M', u'ὄ'), + (0x1F4D, 'M', u'ὅ'), + (0x1F4E, 'X'), + (0x1F50, 'V'), + (0x1F58, 'X'), + (0x1F59, 'M', u'ὑ'), + (0x1F5A, 'X'), + (0x1F5B, 'M', u'ὓ'), + (0x1F5C, 'X'), + (0x1F5D, 'M', u'ὕ'), + (0x1F5E, 'X'), + (0x1F5F, 'M', u'ὗ'), + (0x1F60, 'V'), + (0x1F68, 'M', u'ὠ'), + (0x1F69, 'M', u'ὡ'), + (0x1F6A, 'M', u'ὢ'), + (0x1F6B, 'M', u'ὣ'), + (0x1F6C, 'M', u'ὤ'), + (0x1F6D, 'M', u'ὥ'), + (0x1F6E, 'M', u'ὦ'), + (0x1F6F, 'M', u'ὧ'), + (0x1F70, 'V'), + (0x1F71, 'M', u'ά'), + (0x1F72, 'V'), + (0x1F73, 'M', u'έ'), + (0x1F74, 'V'), + (0x1F75, 'M', u'ή'), + (0x1F76, 'V'), + (0x1F77, 'M', u'ί'), + (0x1F78, 'V'), + (0x1F79, 'M', u'ό'), + (0x1F7A, 'V'), + (0x1F7B, 'M', u'ύ'), + (0x1F7C, 'V'), + (0x1F7D, 'M', u'ώ'), + (0x1F7E, 'X'), + (0x1F80, 'M', u'ἀι'), + (0x1F81, 'M', u'ἁι'), + (0x1F82, 'M', u'ἂι'), + (0x1F83, 'M', u'ἃι'), + (0x1F84, 'M', u'ἄι'), + (0x1F85, 'M', u'ἅι'), + (0x1F86, 'M', u'ἆι'), + (0x1F87, 'M', u'ἇι'), + (0x1F88, 'M', u'ἀι'), + (0x1F89, 'M', u'ἁι'), + (0x1F8A, 'M', u'ἂι'), + (0x1F8B, 'M', u'ἃι'), + (0x1F8C, 'M', u'ἄι'), + (0x1F8D, 'M', u'ἅι'), + (0x1F8E, 'M', u'ἆι'), + (0x1F8F, 'M', u'ἇι'), + (0x1F90, 'M', u'ἠι'), + (0x1F91, 'M', u'ἡι'), + (0x1F92, 'M', u'ἢι'), + (0x1F93, 'M', u'ἣι'), + (0x1F94, 'M', u'ἤι'), + (0x1F95, 'M', u'ἥι'), + (0x1F96, 'M', u'ἦι'), + (0x1F97, 'M', u'ἧι'), + (0x1F98, 'M', u'ἠι'), + (0x1F99, 'M', u'ἡι'), + (0x1F9A, 'M', u'ἢι'), + (0x1F9B, 'M', u'ἣι'), + (0x1F9C, 'M', u'ἤι'), + ] + +def _seg_20(): + return [ + (0x1F9D, 'M', u'ἥι'), + (0x1F9E, 'M', u'ἦι'), + (0x1F9F, 'M', u'ἧι'), + (0x1FA0, 'M', u'ὠι'), + (0x1FA1, 'M', u'ὡι'), + (0x1FA2, 'M', u'ὢι'), + (0x1FA3, 'M', u'ὣι'), + (0x1FA4, 'M', u'ὤι'), + (0x1FA5, 'M', u'ὥι'), + (0x1FA6, 'M', u'ὦι'), + (0x1FA7, 'M', u'ὧι'), + (0x1FA8, 'M', u'ὠι'), + (0x1FA9, 'M', u'ὡι'), + (0x1FAA, 'M', u'ὢι'), + (0x1FAB, 'M', u'ὣι'), + (0x1FAC, 'M', u'ὤι'), + (0x1FAD, 'M', u'ὥι'), + (0x1FAE, 'M', u'ὦι'), + (0x1FAF, 'M', u'ὧι'), + (0x1FB0, 'V'), + (0x1FB2, 'M', u'ὰι'), + (0x1FB3, 'M', u'αι'), + (0x1FB4, 'M', u'άι'), + (0x1FB5, 'X'), + (0x1FB6, 'V'), + (0x1FB7, 'M', u'ᾶι'), + (0x1FB8, 'M', u'ᾰ'), + (0x1FB9, 'M', u'ᾱ'), + (0x1FBA, 'M', u'ὰ'), + (0x1FBB, 'M', u'ά'), + (0x1FBC, 'M', u'αι'), + (0x1FBD, '3', u' ̓'), + (0x1FBE, 'M', u'ι'), + (0x1FBF, '3', u' ̓'), + (0x1FC0, '3', u' ͂'), + (0x1FC1, '3', u' ̈͂'), + (0x1FC2, 'M', u'ὴι'), + (0x1FC3, 'M', u'ηι'), + (0x1FC4, 'M', u'ήι'), + (0x1FC5, 'X'), + (0x1FC6, 'V'), + (0x1FC7, 'M', u'ῆι'), + (0x1FC8, 'M', u'ὲ'), + (0x1FC9, 'M', u'έ'), + (0x1FCA, 'M', u'ὴ'), + (0x1FCB, 'M', u'ή'), + (0x1FCC, 'M', u'ηι'), + (0x1FCD, '3', u' ̓̀'), + (0x1FCE, '3', u' ̓́'), + (0x1FCF, '3', u' ̓͂'), + (0x1FD0, 'V'), + (0x1FD3, 'M', u'ΐ'), + (0x1FD4, 'X'), + (0x1FD6, 'V'), + (0x1FD8, 'M', u'ῐ'), + (0x1FD9, 'M', u'ῑ'), + (0x1FDA, 'M', u'ὶ'), + (0x1FDB, 'M', u'ί'), + (0x1FDC, 'X'), + (0x1FDD, '3', u' ̔̀'), + (0x1FDE, '3', u' ̔́'), + (0x1FDF, '3', u' ̔͂'), + (0x1FE0, 'V'), + (0x1FE3, 'M', u'ΰ'), + (0x1FE4, 'V'), + (0x1FE8, 'M', u'ῠ'), + (0x1FE9, 'M', u'ῡ'), + (0x1FEA, 'M', u'ὺ'), + (0x1FEB, 'M', u'ύ'), + (0x1FEC, 'M', u'ῥ'), + (0x1FED, '3', u' ̈̀'), + (0x1FEE, '3', u' ̈́'), + (0x1FEF, '3', u'`'), + (0x1FF0, 'X'), + (0x1FF2, 'M', u'ὼι'), + (0x1FF3, 'M', u'ωι'), + (0x1FF4, 'M', u'ώι'), + (0x1FF5, 'X'), + (0x1FF6, 'V'), + (0x1FF7, 'M', u'ῶι'), + (0x1FF8, 'M', u'ὸ'), + (0x1FF9, 'M', u'ό'), + (0x1FFA, 'M', u'ὼ'), + (0x1FFB, 'M', u'ώ'), + (0x1FFC, 'M', u'ωι'), + (0x1FFD, '3', u' ́'), + (0x1FFE, '3', u' ̔'), + (0x1FFF, 'X'), + (0x2000, '3', u' '), + (0x200B, 'I'), + (0x200C, 'D', u''), + (0x200E, 'X'), + (0x2010, 'V'), + (0x2011, 'M', u'‐'), + (0x2012, 'V'), + (0x2017, '3', u' ̳'), + (0x2018, 'V'), + (0x2024, 'X'), + (0x2027, 'V'), + (0x2028, 'X'), + ] + +def _seg_21(): + return [ + (0x202F, '3', u' '), + (0x2030, 'V'), + (0x2033, 'M', u'′′'), + (0x2034, 'M', u'′′′'), + (0x2035, 'V'), + (0x2036, 'M', u'‵‵'), + (0x2037, 'M', u'‵‵‵'), + (0x2038, 'V'), + (0x203C, '3', u'!!'), + (0x203D, 'V'), + (0x203E, '3', u' ̅'), + (0x203F, 'V'), + (0x2047, '3', u'??'), + (0x2048, '3', u'?!'), + (0x2049, '3', u'!?'), + (0x204A, 'V'), + (0x2057, 'M', u'′′′′'), + (0x2058, 'V'), + (0x205F, '3', u' '), + (0x2060, 'I'), + (0x2061, 'X'), + (0x2064, 'I'), + (0x2065, 'X'), + (0x2070, 'M', u'0'), + (0x2071, 'M', u'i'), + (0x2072, 'X'), + (0x2074, 'M', u'4'), + (0x2075, 'M', u'5'), + (0x2076, 'M', u'6'), + (0x2077, 'M', u'7'), + (0x2078, 'M', u'8'), + (0x2079, 'M', u'9'), + (0x207A, '3', u'+'), + (0x207B, 'M', u'−'), + (0x207C, '3', u'='), + (0x207D, '3', u'('), + (0x207E, '3', u')'), + (0x207F, 'M', u'n'), + (0x2080, 'M', u'0'), + (0x2081, 'M', u'1'), + (0x2082, 'M', u'2'), + (0x2083, 'M', u'3'), + (0x2084, 'M', u'4'), + (0x2085, 'M', u'5'), + (0x2086, 'M', u'6'), + (0x2087, 'M', u'7'), + (0x2088, 'M', u'8'), + (0x2089, 'M', u'9'), + (0x208A, '3', u'+'), + (0x208B, 'M', u'−'), + (0x208C, '3', u'='), + (0x208D, '3', u'('), + (0x208E, '3', u')'), + (0x208F, 'X'), + (0x2090, 'M', u'a'), + (0x2091, 'M', u'e'), + (0x2092, 'M', u'o'), + (0x2093, 'M', u'x'), + (0x2094, 'M', u'ə'), + (0x2095, 'M', u'h'), + (0x2096, 'M', u'k'), + (0x2097, 'M', u'l'), + (0x2098, 'M', u'm'), + (0x2099, 'M', u'n'), + (0x209A, 'M', u'p'), + (0x209B, 'M', u's'), + (0x209C, 'M', u't'), + (0x209D, 'X'), + (0x20A0, 'V'), + (0x20A8, 'M', u'rs'), + (0x20A9, 'V'), + (0x20C0, 'X'), + (0x20D0, 'V'), + (0x20F1, 'X'), + (0x2100, '3', u'a/c'), + (0x2101, '3', u'a/s'), + (0x2102, 'M', u'c'), + (0x2103, 'M', u'°c'), + (0x2104, 'V'), + (0x2105, '3', u'c/o'), + (0x2106, '3', u'c/u'), + (0x2107, 'M', u'ɛ'), + (0x2108, 'V'), + (0x2109, 'M', u'°f'), + (0x210A, 'M', u'g'), + (0x210B, 'M', u'h'), + (0x210F, 'M', u'ħ'), + (0x2110, 'M', u'i'), + (0x2112, 'M', u'l'), + (0x2114, 'V'), + (0x2115, 'M', u'n'), + (0x2116, 'M', u'no'), + (0x2117, 'V'), + (0x2119, 'M', u'p'), + (0x211A, 'M', u'q'), + (0x211B, 'M', u'r'), + (0x211E, 'V'), + (0x2120, 'M', u'sm'), + (0x2121, 'M', u'tel'), + (0x2122, 'M', u'tm'), + ] + +def _seg_22(): + return [ + (0x2123, 'V'), + (0x2124, 'M', u'z'), + (0x2125, 'V'), + (0x2126, 'M', u'ω'), + (0x2127, 'V'), + (0x2128, 'M', u'z'), + (0x2129, 'V'), + (0x212A, 'M', u'k'), + (0x212B, 'M', u'å'), + (0x212C, 'M', u'b'), + (0x212D, 'M', u'c'), + (0x212E, 'V'), + (0x212F, 'M', u'e'), + (0x2131, 'M', u'f'), + (0x2132, 'X'), + (0x2133, 'M', u'm'), + (0x2134, 'M', u'o'), + (0x2135, 'M', u'א'), + (0x2136, 'M', u'ב'), + (0x2137, 'M', u'ג'), + (0x2138, 'M', u'ד'), + (0x2139, 'M', u'i'), + (0x213A, 'V'), + (0x213B, 'M', u'fax'), + (0x213C, 'M', u'π'), + (0x213D, 'M', u'γ'), + (0x213F, 'M', u'π'), + (0x2140, 'M', u'∑'), + (0x2141, 'V'), + (0x2145, 'M', u'd'), + (0x2147, 'M', u'e'), + (0x2148, 'M', u'i'), + (0x2149, 'M', u'j'), + (0x214A, 'V'), + (0x2150, 'M', u'1⁄7'), + (0x2151, 'M', u'1⁄9'), + (0x2152, 'M', u'1⁄10'), + (0x2153, 'M', u'1⁄3'), + (0x2154, 'M', u'2⁄3'), + (0x2155, 'M', u'1⁄5'), + (0x2156, 'M', u'2⁄5'), + (0x2157, 'M', u'3⁄5'), + (0x2158, 'M', u'4⁄5'), + (0x2159, 'M', u'1⁄6'), + (0x215A, 'M', u'5⁄6'), + (0x215B, 'M', u'1⁄8'), + (0x215C, 'M', u'3⁄8'), + (0x215D, 'M', u'5⁄8'), + (0x215E, 'M', u'7⁄8'), + (0x215F, 'M', u'1⁄'), + (0x2160, 'M', u'i'), + (0x2161, 'M', u'ii'), + (0x2162, 'M', u'iii'), + (0x2163, 'M', u'iv'), + (0x2164, 'M', u'v'), + (0x2165, 'M', u'vi'), + (0x2166, 'M', u'vii'), + (0x2167, 'M', u'viii'), + (0x2168, 'M', u'ix'), + (0x2169, 'M', u'x'), + (0x216A, 'M', u'xi'), + (0x216B, 'M', u'xii'), + (0x216C, 'M', u'l'), + (0x216D, 'M', u'c'), + (0x216E, 'M', u'd'), + (0x216F, 'M', u'm'), + (0x2170, 'M', u'i'), + (0x2171, 'M', u'ii'), + (0x2172, 'M', u'iii'), + (0x2173, 'M', u'iv'), + (0x2174, 'M', u'v'), + (0x2175, 'M', u'vi'), + (0x2176, 'M', u'vii'), + (0x2177, 'M', u'viii'), + (0x2178, 'M', u'ix'), + (0x2179, 'M', u'x'), + (0x217A, 'M', u'xi'), + (0x217B, 'M', u'xii'), + (0x217C, 'M', u'l'), + (0x217D, 'M', u'c'), + (0x217E, 'M', u'd'), + (0x217F, 'M', u'm'), + (0x2180, 'V'), + (0x2183, 'X'), + (0x2184, 'V'), + (0x2189, 'M', u'0⁄3'), + (0x218A, 'V'), + (0x218C, 'X'), + (0x2190, 'V'), + (0x222C, 'M', u'∫∫'), + (0x222D, 'M', u'∫∫∫'), + (0x222E, 'V'), + (0x222F, 'M', u'∮∮'), + (0x2230, 'M', u'∮∮∮'), + (0x2231, 'V'), + (0x2260, '3'), + (0x2261, 'V'), + (0x226E, '3'), + (0x2270, 'V'), + (0x2329, 'M', u'〈'), + ] + +def _seg_23(): + return [ + (0x232A, 'M', u'〉'), + (0x232B, 'V'), + (0x2427, 'X'), + (0x2440, 'V'), + (0x244B, 'X'), + (0x2460, 'M', u'1'), + (0x2461, 'M', u'2'), + (0x2462, 'M', u'3'), + (0x2463, 'M', u'4'), + (0x2464, 'M', u'5'), + (0x2465, 'M', u'6'), + (0x2466, 'M', u'7'), + (0x2467, 'M', u'8'), + (0x2468, 'M', u'9'), + (0x2469, 'M', u'10'), + (0x246A, 'M', u'11'), + (0x246B, 'M', u'12'), + (0x246C, 'M', u'13'), + (0x246D, 'M', u'14'), + (0x246E, 'M', u'15'), + (0x246F, 'M', u'16'), + (0x2470, 'M', u'17'), + (0x2471, 'M', u'18'), + (0x2472, 'M', u'19'), + (0x2473, 'M', u'20'), + (0x2474, '3', u'(1)'), + (0x2475, '3', u'(2)'), + (0x2476, '3', u'(3)'), + (0x2477, '3', u'(4)'), + (0x2478, '3', u'(5)'), + (0x2479, '3', u'(6)'), + (0x247A, '3', u'(7)'), + (0x247B, '3', u'(8)'), + (0x247C, '3', u'(9)'), + (0x247D, '3', u'(10)'), + (0x247E, '3', u'(11)'), + (0x247F, '3', u'(12)'), + (0x2480, '3', u'(13)'), + (0x2481, '3', u'(14)'), + (0x2482, '3', u'(15)'), + (0x2483, '3', u'(16)'), + (0x2484, '3', u'(17)'), + (0x2485, '3', u'(18)'), + (0x2486, '3', u'(19)'), + (0x2487, '3', u'(20)'), + (0x2488, 'X'), + (0x249C, '3', u'(a)'), + (0x249D, '3', u'(b)'), + (0x249E, '3', u'(c)'), + (0x249F, '3', u'(d)'), + (0x24A0, '3', u'(e)'), + (0x24A1, '3', u'(f)'), + (0x24A2, '3', u'(g)'), + (0x24A3, '3', u'(h)'), + (0x24A4, '3', u'(i)'), + (0x24A5, '3', u'(j)'), + (0x24A6, '3', u'(k)'), + (0x24A7, '3', u'(l)'), + (0x24A8, '3', u'(m)'), + (0x24A9, '3', u'(n)'), + (0x24AA, '3', u'(o)'), + (0x24AB, '3', u'(p)'), + (0x24AC, '3', u'(q)'), + (0x24AD, '3', u'(r)'), + (0x24AE, '3', u'(s)'), + (0x24AF, '3', u'(t)'), + (0x24B0, '3', u'(u)'), + (0x24B1, '3', u'(v)'), + (0x24B2, '3', u'(w)'), + (0x24B3, '3', u'(x)'), + (0x24B4, '3', u'(y)'), + (0x24B5, '3', u'(z)'), + (0x24B6, 'M', u'a'), + (0x24B7, 'M', u'b'), + (0x24B8, 'M', u'c'), + (0x24B9, 'M', u'd'), + (0x24BA, 'M', u'e'), + (0x24BB, 'M', u'f'), + (0x24BC, 'M', u'g'), + (0x24BD, 'M', u'h'), + (0x24BE, 'M', u'i'), + (0x24BF, 'M', u'j'), + (0x24C0, 'M', u'k'), + (0x24C1, 'M', u'l'), + (0x24C2, 'M', u'm'), + (0x24C3, 'M', u'n'), + (0x24C4, 'M', u'o'), + (0x24C5, 'M', u'p'), + (0x24C6, 'M', u'q'), + (0x24C7, 'M', u'r'), + (0x24C8, 'M', u's'), + (0x24C9, 'M', u't'), + (0x24CA, 'M', u'u'), + (0x24CB, 'M', u'v'), + (0x24CC, 'M', u'w'), + (0x24CD, 'M', u'x'), + (0x24CE, 'M', u'y'), + (0x24CF, 'M', u'z'), + (0x24D0, 'M', u'a'), + (0x24D1, 'M', u'b'), + ] + +def _seg_24(): + return [ + (0x24D2, 'M', u'c'), + (0x24D3, 'M', u'd'), + (0x24D4, 'M', u'e'), + (0x24D5, 'M', u'f'), + (0x24D6, 'M', u'g'), + (0x24D7, 'M', u'h'), + (0x24D8, 'M', u'i'), + (0x24D9, 'M', u'j'), + (0x24DA, 'M', u'k'), + (0x24DB, 'M', u'l'), + (0x24DC, 'M', u'm'), + (0x24DD, 'M', u'n'), + (0x24DE, 'M', u'o'), + (0x24DF, 'M', u'p'), + (0x24E0, 'M', u'q'), + (0x24E1, 'M', u'r'), + (0x24E2, 'M', u's'), + (0x24E3, 'M', u't'), + (0x24E4, 'M', u'u'), + (0x24E5, 'M', u'v'), + (0x24E6, 'M', u'w'), + (0x24E7, 'M', u'x'), + (0x24E8, 'M', u'y'), + (0x24E9, 'M', u'z'), + (0x24EA, 'M', u'0'), + (0x24EB, 'V'), + (0x2A0C, 'M', u'∫∫∫∫'), + (0x2A0D, 'V'), + (0x2A74, '3', u'::='), + (0x2A75, '3', u'=='), + (0x2A76, '3', u'==='), + (0x2A77, 'V'), + (0x2ADC, 'M', u'⫝̸'), + (0x2ADD, 'V'), + (0x2B74, 'X'), + (0x2B76, 'V'), + (0x2B96, 'X'), + (0x2B98, 'V'), + (0x2BBA, 'X'), + (0x2BBD, 'V'), + (0x2BC9, 'X'), + (0x2BCA, 'V'), + (0x2BD3, 'X'), + (0x2BEC, 'V'), + (0x2BF0, 'X'), + (0x2C00, 'M', u'ⰰ'), + (0x2C01, 'M', u'ⰱ'), + (0x2C02, 'M', u'ⰲ'), + (0x2C03, 'M', u'ⰳ'), + (0x2C04, 'M', u'ⰴ'), + (0x2C05, 'M', u'ⰵ'), + (0x2C06, 'M', u'ⰶ'), + (0x2C07, 'M', u'ⰷ'), + (0x2C08, 'M', u'ⰸ'), + (0x2C09, 'M', u'ⰹ'), + (0x2C0A, 'M', u'ⰺ'), + (0x2C0B, 'M', u'ⰻ'), + (0x2C0C, 'M', u'ⰼ'), + (0x2C0D, 'M', u'ⰽ'), + (0x2C0E, 'M', u'ⰾ'), + (0x2C0F, 'M', u'ⰿ'), + (0x2C10, 'M', u'ⱀ'), + (0x2C11, 'M', u'ⱁ'), + (0x2C12, 'M', u'ⱂ'), + (0x2C13, 'M', u'ⱃ'), + (0x2C14, 'M', u'ⱄ'), + (0x2C15, 'M', u'ⱅ'), + (0x2C16, 'M', u'ⱆ'), + (0x2C17, 'M', u'ⱇ'), + (0x2C18, 'M', u'ⱈ'), + (0x2C19, 'M', u'ⱉ'), + (0x2C1A, 'M', u'ⱊ'), + (0x2C1B, 'M', u'ⱋ'), + (0x2C1C, 'M', u'ⱌ'), + (0x2C1D, 'M', u'ⱍ'), + (0x2C1E, 'M', u'ⱎ'), + (0x2C1F, 'M', u'ⱏ'), + (0x2C20, 'M', u'ⱐ'), + (0x2C21, 'M', u'ⱑ'), + (0x2C22, 'M', u'ⱒ'), + (0x2C23, 'M', u'ⱓ'), + (0x2C24, 'M', u'ⱔ'), + (0x2C25, 'M', u'ⱕ'), + (0x2C26, 'M', u'ⱖ'), + (0x2C27, 'M', u'ⱗ'), + (0x2C28, 'M', u'ⱘ'), + (0x2C29, 'M', u'ⱙ'), + (0x2C2A, 'M', u'ⱚ'), + (0x2C2B, 'M', u'ⱛ'), + (0x2C2C, 'M', u'ⱜ'), + (0x2C2D, 'M', u'ⱝ'), + (0x2C2E, 'M', u'ⱞ'), + (0x2C2F, 'X'), + (0x2C30, 'V'), + (0x2C5F, 'X'), + (0x2C60, 'M', u'ⱡ'), + (0x2C61, 'V'), + (0x2C62, 'M', u'ɫ'), + (0x2C63, 'M', u'ᵽ'), + (0x2C64, 'M', u'ɽ'), + ] + +def _seg_25(): + return [ + (0x2C65, 'V'), + (0x2C67, 'M', u'ⱨ'), + (0x2C68, 'V'), + (0x2C69, 'M', u'ⱪ'), + (0x2C6A, 'V'), + (0x2C6B, 'M', u'ⱬ'), + (0x2C6C, 'V'), + (0x2C6D, 'M', u'ɑ'), + (0x2C6E, 'M', u'ɱ'), + (0x2C6F, 'M', u'ɐ'), + (0x2C70, 'M', u'ɒ'), + (0x2C71, 'V'), + (0x2C72, 'M', u'ⱳ'), + (0x2C73, 'V'), + (0x2C75, 'M', u'ⱶ'), + (0x2C76, 'V'), + (0x2C7C, 'M', u'j'), + (0x2C7D, 'M', u'v'), + (0x2C7E, 'M', u'ȿ'), + (0x2C7F, 'M', u'ɀ'), + (0x2C80, 'M', u'ⲁ'), + (0x2C81, 'V'), + (0x2C82, 'M', u'ⲃ'), + (0x2C83, 'V'), + (0x2C84, 'M', u'ⲅ'), + (0x2C85, 'V'), + (0x2C86, 'M', u'ⲇ'), + (0x2C87, 'V'), + (0x2C88, 'M', u'ⲉ'), + (0x2C89, 'V'), + (0x2C8A, 'M', u'ⲋ'), + (0x2C8B, 'V'), + (0x2C8C, 'M', u'ⲍ'), + (0x2C8D, 'V'), + (0x2C8E, 'M', u'ⲏ'), + (0x2C8F, 'V'), + (0x2C90, 'M', u'ⲑ'), + (0x2C91, 'V'), + (0x2C92, 'M', u'ⲓ'), + (0x2C93, 'V'), + (0x2C94, 'M', u'ⲕ'), + (0x2C95, 'V'), + (0x2C96, 'M', u'ⲗ'), + (0x2C97, 'V'), + (0x2C98, 'M', u'ⲙ'), + (0x2C99, 'V'), + (0x2C9A, 'M', u'ⲛ'), + (0x2C9B, 'V'), + (0x2C9C, 'M', u'ⲝ'), + (0x2C9D, 'V'), + (0x2C9E, 'M', u'ⲟ'), + (0x2C9F, 'V'), + (0x2CA0, 'M', u'ⲡ'), + (0x2CA1, 'V'), + (0x2CA2, 'M', u'ⲣ'), + (0x2CA3, 'V'), + (0x2CA4, 'M', u'ⲥ'), + (0x2CA5, 'V'), + (0x2CA6, 'M', u'ⲧ'), + (0x2CA7, 'V'), + (0x2CA8, 'M', u'ⲩ'), + (0x2CA9, 'V'), + (0x2CAA, 'M', u'ⲫ'), + (0x2CAB, 'V'), + (0x2CAC, 'M', u'ⲭ'), + (0x2CAD, 'V'), + (0x2CAE, 'M', u'ⲯ'), + (0x2CAF, 'V'), + (0x2CB0, 'M', u'ⲱ'), + (0x2CB1, 'V'), + (0x2CB2, 'M', u'ⲳ'), + (0x2CB3, 'V'), + (0x2CB4, 'M', u'ⲵ'), + (0x2CB5, 'V'), + (0x2CB6, 'M', u'ⲷ'), + (0x2CB7, 'V'), + (0x2CB8, 'M', u'ⲹ'), + (0x2CB9, 'V'), + (0x2CBA, 'M', u'ⲻ'), + (0x2CBB, 'V'), + (0x2CBC, 'M', u'ⲽ'), + (0x2CBD, 'V'), + (0x2CBE, 'M', u'ⲿ'), + (0x2CBF, 'V'), + (0x2CC0, 'M', u'ⳁ'), + (0x2CC1, 'V'), + (0x2CC2, 'M', u'ⳃ'), + (0x2CC3, 'V'), + (0x2CC4, 'M', u'ⳅ'), + (0x2CC5, 'V'), + (0x2CC6, 'M', u'ⳇ'), + (0x2CC7, 'V'), + (0x2CC8, 'M', u'ⳉ'), + (0x2CC9, 'V'), + (0x2CCA, 'M', u'ⳋ'), + (0x2CCB, 'V'), + (0x2CCC, 'M', u'ⳍ'), + (0x2CCD, 'V'), + (0x2CCE, 'M', u'ⳏ'), + (0x2CCF, 'V'), + ] + +def _seg_26(): + return [ + (0x2CD0, 'M', u'ⳑ'), + (0x2CD1, 'V'), + (0x2CD2, 'M', u'ⳓ'), + (0x2CD3, 'V'), + (0x2CD4, 'M', u'ⳕ'), + (0x2CD5, 'V'), + (0x2CD6, 'M', u'ⳗ'), + (0x2CD7, 'V'), + (0x2CD8, 'M', u'ⳙ'), + (0x2CD9, 'V'), + (0x2CDA, 'M', u'ⳛ'), + (0x2CDB, 'V'), + (0x2CDC, 'M', u'ⳝ'), + (0x2CDD, 'V'), + (0x2CDE, 'M', u'ⳟ'), + (0x2CDF, 'V'), + (0x2CE0, 'M', u'ⳡ'), + (0x2CE1, 'V'), + (0x2CE2, 'M', u'ⳣ'), + (0x2CE3, 'V'), + (0x2CEB, 'M', u'ⳬ'), + (0x2CEC, 'V'), + (0x2CED, 'M', u'ⳮ'), + (0x2CEE, 'V'), + (0x2CF2, 'M', u'ⳳ'), + (0x2CF3, 'V'), + (0x2CF4, 'X'), + (0x2CF9, 'V'), + (0x2D26, 'X'), + (0x2D27, 'V'), + (0x2D28, 'X'), + (0x2D2D, 'V'), + (0x2D2E, 'X'), + (0x2D30, 'V'), + (0x2D68, 'X'), + (0x2D6F, 'M', u'ⵡ'), + (0x2D70, 'V'), + (0x2D71, 'X'), + (0x2D7F, 'V'), + (0x2D97, 'X'), + (0x2DA0, 'V'), + (0x2DA7, 'X'), + (0x2DA8, 'V'), + (0x2DAF, 'X'), + (0x2DB0, 'V'), + (0x2DB7, 'X'), + (0x2DB8, 'V'), + (0x2DBF, 'X'), + (0x2DC0, 'V'), + (0x2DC7, 'X'), + (0x2DC8, 'V'), + (0x2DCF, 'X'), + (0x2DD0, 'V'), + (0x2DD7, 'X'), + (0x2DD8, 'V'), + (0x2DDF, 'X'), + (0x2DE0, 'V'), + (0x2E4A, 'X'), + (0x2E80, 'V'), + (0x2E9A, 'X'), + (0x2E9B, 'V'), + (0x2E9F, 'M', u'母'), + (0x2EA0, 'V'), + (0x2EF3, 'M', u'龟'), + (0x2EF4, 'X'), + (0x2F00, 'M', u'一'), + (0x2F01, 'M', u'丨'), + (0x2F02, 'M', u'丶'), + (0x2F03, 'M', u'丿'), + (0x2F04, 'M', u'乙'), + (0x2F05, 'M', u'亅'), + (0x2F06, 'M', u'二'), + (0x2F07, 'M', u'亠'), + (0x2F08, 'M', u'人'), + (0x2F09, 'M', u'儿'), + (0x2F0A, 'M', u'入'), + (0x2F0B, 'M', u'八'), + (0x2F0C, 'M', u'冂'), + (0x2F0D, 'M', u'冖'), + (0x2F0E, 'M', u'冫'), + (0x2F0F, 'M', u'几'), + (0x2F10, 'M', u'凵'), + (0x2F11, 'M', u'刀'), + (0x2F12, 'M', u'力'), + (0x2F13, 'M', u'勹'), + (0x2F14, 'M', u'匕'), + (0x2F15, 'M', u'匚'), + (0x2F16, 'M', u'匸'), + (0x2F17, 'M', u'十'), + (0x2F18, 'M', u'卜'), + (0x2F19, 'M', u'卩'), + (0x2F1A, 'M', u'厂'), + (0x2F1B, 'M', u'厶'), + (0x2F1C, 'M', u'又'), + (0x2F1D, 'M', u'口'), + (0x2F1E, 'M', u'囗'), + (0x2F1F, 'M', u'土'), + (0x2F20, 'M', u'士'), + (0x2F21, 'M', u'夂'), + (0x2F22, 'M', u'夊'), + ] + +def _seg_27(): + return [ + (0x2F23, 'M', u'夕'), + (0x2F24, 'M', u'大'), + (0x2F25, 'M', u'女'), + (0x2F26, 'M', u'子'), + (0x2F27, 'M', u'宀'), + (0x2F28, 'M', u'寸'), + (0x2F29, 'M', u'小'), + (0x2F2A, 'M', u'尢'), + (0x2F2B, 'M', u'尸'), + (0x2F2C, 'M', u'屮'), + (0x2F2D, 'M', u'山'), + (0x2F2E, 'M', u'巛'), + (0x2F2F, 'M', u'工'), + (0x2F30, 'M', u'己'), + (0x2F31, 'M', u'巾'), + (0x2F32, 'M', u'干'), + (0x2F33, 'M', u'幺'), + (0x2F34, 'M', u'广'), + (0x2F35, 'M', u'廴'), + (0x2F36, 'M', u'廾'), + (0x2F37, 'M', u'弋'), + (0x2F38, 'M', u'弓'), + (0x2F39, 'M', u'彐'), + (0x2F3A, 'M', u'彡'), + (0x2F3B, 'M', u'彳'), + (0x2F3C, 'M', u'心'), + (0x2F3D, 'M', u'戈'), + (0x2F3E, 'M', u'戶'), + (0x2F3F, 'M', u'手'), + (0x2F40, 'M', u'支'), + (0x2F41, 'M', u'攴'), + (0x2F42, 'M', u'文'), + (0x2F43, 'M', u'斗'), + (0x2F44, 'M', u'斤'), + (0x2F45, 'M', u'方'), + (0x2F46, 'M', u'无'), + (0x2F47, 'M', u'日'), + (0x2F48, 'M', u'曰'), + (0x2F49, 'M', u'月'), + (0x2F4A, 'M', u'木'), + (0x2F4B, 'M', u'欠'), + (0x2F4C, 'M', u'止'), + (0x2F4D, 'M', u'歹'), + (0x2F4E, 'M', u'殳'), + (0x2F4F, 'M', u'毋'), + (0x2F50, 'M', u'比'), + (0x2F51, 'M', u'毛'), + (0x2F52, 'M', u'氏'), + (0x2F53, 'M', u'气'), + (0x2F54, 'M', u'水'), + (0x2F55, 'M', u'火'), + (0x2F56, 'M', u'爪'), + (0x2F57, 'M', u'父'), + (0x2F58, 'M', u'爻'), + (0x2F59, 'M', u'爿'), + (0x2F5A, 'M', u'片'), + (0x2F5B, 'M', u'牙'), + (0x2F5C, 'M', u'牛'), + (0x2F5D, 'M', u'犬'), + (0x2F5E, 'M', u'玄'), + (0x2F5F, 'M', u'玉'), + (0x2F60, 'M', u'瓜'), + (0x2F61, 'M', u'瓦'), + (0x2F62, 'M', u'甘'), + (0x2F63, 'M', u'生'), + (0x2F64, 'M', u'用'), + (0x2F65, 'M', u'田'), + (0x2F66, 'M', u'疋'), + (0x2F67, 'M', u'疒'), + (0x2F68, 'M', u'癶'), + (0x2F69, 'M', u'白'), + (0x2F6A, 'M', u'皮'), + (0x2F6B, 'M', u'皿'), + (0x2F6C, 'M', u'目'), + (0x2F6D, 'M', u'矛'), + (0x2F6E, 'M', u'矢'), + (0x2F6F, 'M', u'石'), + (0x2F70, 'M', u'示'), + (0x2F71, 'M', u'禸'), + (0x2F72, 'M', u'禾'), + (0x2F73, 'M', u'穴'), + (0x2F74, 'M', u'立'), + (0x2F75, 'M', u'竹'), + (0x2F76, 'M', u'米'), + (0x2F77, 'M', u'糸'), + (0x2F78, 'M', u'缶'), + (0x2F79, 'M', u'网'), + (0x2F7A, 'M', u'羊'), + (0x2F7B, 'M', u'羽'), + (0x2F7C, 'M', u'老'), + (0x2F7D, 'M', u'而'), + (0x2F7E, 'M', u'耒'), + (0x2F7F, 'M', u'耳'), + (0x2F80, 'M', u'聿'), + (0x2F81, 'M', u'肉'), + (0x2F82, 'M', u'臣'), + (0x2F83, 'M', u'自'), + (0x2F84, 'M', u'至'), + (0x2F85, 'M', u'臼'), + (0x2F86, 'M', u'舌'), + ] + +def _seg_28(): + return [ + (0x2F87, 'M', u'舛'), + (0x2F88, 'M', u'舟'), + (0x2F89, 'M', u'艮'), + (0x2F8A, 'M', u'色'), + (0x2F8B, 'M', u'艸'), + (0x2F8C, 'M', u'虍'), + (0x2F8D, 'M', u'虫'), + (0x2F8E, 'M', u'血'), + (0x2F8F, 'M', u'行'), + (0x2F90, 'M', u'衣'), + (0x2F91, 'M', u'襾'), + (0x2F92, 'M', u'見'), + (0x2F93, 'M', u'角'), + (0x2F94, 'M', u'言'), + (0x2F95, 'M', u'谷'), + (0x2F96, 'M', u'豆'), + (0x2F97, 'M', u'豕'), + (0x2F98, 'M', u'豸'), + (0x2F99, 'M', u'貝'), + (0x2F9A, 'M', u'赤'), + (0x2F9B, 'M', u'走'), + (0x2F9C, 'M', u'足'), + (0x2F9D, 'M', u'身'), + (0x2F9E, 'M', u'車'), + (0x2F9F, 'M', u'辛'), + (0x2FA0, 'M', u'辰'), + (0x2FA1, 'M', u'辵'), + (0x2FA2, 'M', u'邑'), + (0x2FA3, 'M', u'酉'), + (0x2FA4, 'M', u'釆'), + (0x2FA5, 'M', u'里'), + (0x2FA6, 'M', u'金'), + (0x2FA7, 'M', u'長'), + (0x2FA8, 'M', u'門'), + (0x2FA9, 'M', u'阜'), + (0x2FAA, 'M', u'隶'), + (0x2FAB, 'M', u'隹'), + (0x2FAC, 'M', u'雨'), + (0x2FAD, 'M', u'靑'), + (0x2FAE, 'M', u'非'), + (0x2FAF, 'M', u'面'), + (0x2FB0, 'M', u'革'), + (0x2FB1, 'M', u'韋'), + (0x2FB2, 'M', u'韭'), + (0x2FB3, 'M', u'音'), + (0x2FB4, 'M', u'頁'), + (0x2FB5, 'M', u'風'), + (0x2FB6, 'M', u'飛'), + (0x2FB7, 'M', u'食'), + (0x2FB8, 'M', u'首'), + (0x2FB9, 'M', u'香'), + (0x2FBA, 'M', u'馬'), + (0x2FBB, 'M', u'骨'), + (0x2FBC, 'M', u'高'), + (0x2FBD, 'M', u'髟'), + (0x2FBE, 'M', u'鬥'), + (0x2FBF, 'M', u'鬯'), + (0x2FC0, 'M', u'鬲'), + (0x2FC1, 'M', u'鬼'), + (0x2FC2, 'M', u'魚'), + (0x2FC3, 'M', u'鳥'), + (0x2FC4, 'M', u'鹵'), + (0x2FC5, 'M', u'鹿'), + (0x2FC6, 'M', u'麥'), + (0x2FC7, 'M', u'麻'), + (0x2FC8, 'M', u'黃'), + (0x2FC9, 'M', u'黍'), + (0x2FCA, 'M', u'黑'), + (0x2FCB, 'M', u'黹'), + (0x2FCC, 'M', u'黽'), + (0x2FCD, 'M', u'鼎'), + (0x2FCE, 'M', u'鼓'), + (0x2FCF, 'M', u'鼠'), + (0x2FD0, 'M', u'鼻'), + (0x2FD1, 'M', u'齊'), + (0x2FD2, 'M', u'齒'), + (0x2FD3, 'M', u'龍'), + (0x2FD4, 'M', u'龜'), + (0x2FD5, 'M', u'龠'), + (0x2FD6, 'X'), + (0x3000, '3', u' '), + (0x3001, 'V'), + (0x3002, 'M', u'.'), + (0x3003, 'V'), + (0x3036, 'M', u'〒'), + (0x3037, 'V'), + (0x3038, 'M', u'十'), + (0x3039, 'M', u'卄'), + (0x303A, 'M', u'卅'), + (0x303B, 'V'), + (0x3040, 'X'), + (0x3041, 'V'), + (0x3097, 'X'), + (0x3099, 'V'), + (0x309B, '3', u' ゙'), + (0x309C, '3', u' ゚'), + (0x309D, 'V'), + (0x309F, 'M', u'より'), + (0x30A0, 'V'), + (0x30FF, 'M', u'コト'), + ] + +def _seg_29(): + return [ + (0x3100, 'X'), + (0x3105, 'V'), + (0x312F, 'X'), + (0x3131, 'M', u'ᄀ'), + (0x3132, 'M', u'ᄁ'), + (0x3133, 'M', u'ᆪ'), + (0x3134, 'M', u'ᄂ'), + (0x3135, 'M', u'ᆬ'), + (0x3136, 'M', u'ᆭ'), + (0x3137, 'M', u'ᄃ'), + (0x3138, 'M', u'ᄄ'), + (0x3139, 'M', u'ᄅ'), + (0x313A, 'M', u'ᆰ'), + (0x313B, 'M', u'ᆱ'), + (0x313C, 'M', u'ᆲ'), + (0x313D, 'M', u'ᆳ'), + (0x313E, 'M', u'ᆴ'), + (0x313F, 'M', u'ᆵ'), + (0x3140, 'M', u'ᄚ'), + (0x3141, 'M', u'ᄆ'), + (0x3142, 'M', u'ᄇ'), + (0x3143, 'M', u'ᄈ'), + (0x3144, 'M', u'ᄡ'), + (0x3145, 'M', u'ᄉ'), + (0x3146, 'M', u'ᄊ'), + (0x3147, 'M', u'ᄋ'), + (0x3148, 'M', u'ᄌ'), + (0x3149, 'M', u'ᄍ'), + (0x314A, 'M', u'ᄎ'), + (0x314B, 'M', u'ᄏ'), + (0x314C, 'M', u'ᄐ'), + (0x314D, 'M', u'ᄑ'), + (0x314E, 'M', u'ᄒ'), + (0x314F, 'M', u'ᅡ'), + (0x3150, 'M', u'ᅢ'), + (0x3151, 'M', u'ᅣ'), + (0x3152, 'M', u'ᅤ'), + (0x3153, 'M', u'ᅥ'), + (0x3154, 'M', u'ᅦ'), + (0x3155, 'M', u'ᅧ'), + (0x3156, 'M', u'ᅨ'), + (0x3157, 'M', u'ᅩ'), + (0x3158, 'M', u'ᅪ'), + (0x3159, 'M', u'ᅫ'), + (0x315A, 'M', u'ᅬ'), + (0x315B, 'M', u'ᅭ'), + (0x315C, 'M', u'ᅮ'), + (0x315D, 'M', u'ᅯ'), + (0x315E, 'M', u'ᅰ'), + (0x315F, 'M', u'ᅱ'), + (0x3160, 'M', u'ᅲ'), + (0x3161, 'M', u'ᅳ'), + (0x3162, 'M', u'ᅴ'), + (0x3163, 'M', u'ᅵ'), + (0x3164, 'X'), + (0x3165, 'M', u'ᄔ'), + (0x3166, 'M', u'ᄕ'), + (0x3167, 'M', u'ᇇ'), + (0x3168, 'M', u'ᇈ'), + (0x3169, 'M', u'ᇌ'), + (0x316A, 'M', u'ᇎ'), + (0x316B, 'M', u'ᇓ'), + (0x316C, 'M', u'ᇗ'), + (0x316D, 'M', u'ᇙ'), + (0x316E, 'M', u'ᄜ'), + (0x316F, 'M', u'ᇝ'), + (0x3170, 'M', u'ᇟ'), + (0x3171, 'M', u'ᄝ'), + (0x3172, 'M', u'ᄞ'), + (0x3173, 'M', u'ᄠ'), + (0x3174, 'M', u'ᄢ'), + (0x3175, 'M', u'ᄣ'), + (0x3176, 'M', u'ᄧ'), + (0x3177, 'M', u'ᄩ'), + (0x3178, 'M', u'ᄫ'), + (0x3179, 'M', u'ᄬ'), + (0x317A, 'M', u'ᄭ'), + (0x317B, 'M', u'ᄮ'), + (0x317C, 'M', u'ᄯ'), + (0x317D, 'M', u'ᄲ'), + (0x317E, 'M', u'ᄶ'), + (0x317F, 'M', u'ᅀ'), + (0x3180, 'M', u'ᅇ'), + (0x3181, 'M', u'ᅌ'), + (0x3182, 'M', u'ᇱ'), + (0x3183, 'M', u'ᇲ'), + (0x3184, 'M', u'ᅗ'), + (0x3185, 'M', u'ᅘ'), + (0x3186, 'M', u'ᅙ'), + (0x3187, 'M', u'ᆄ'), + (0x3188, 'M', u'ᆅ'), + (0x3189, 'M', u'ᆈ'), + (0x318A, 'M', u'ᆑ'), + (0x318B, 'M', u'ᆒ'), + (0x318C, 'M', u'ᆔ'), + (0x318D, 'M', u'ᆞ'), + (0x318E, 'M', u'ᆡ'), + (0x318F, 'X'), + (0x3190, 'V'), + (0x3192, 'M', u'一'), + ] + +def _seg_30(): + return [ + (0x3193, 'M', u'二'), + (0x3194, 'M', u'三'), + (0x3195, 'M', u'四'), + (0x3196, 'M', u'上'), + (0x3197, 'M', u'中'), + (0x3198, 'M', u'下'), + (0x3199, 'M', u'甲'), + (0x319A, 'M', u'乙'), + (0x319B, 'M', u'丙'), + (0x319C, 'M', u'丁'), + (0x319D, 'M', u'天'), + (0x319E, 'M', u'地'), + (0x319F, 'M', u'人'), + (0x31A0, 'V'), + (0x31BB, 'X'), + (0x31C0, 'V'), + (0x31E4, 'X'), + (0x31F0, 'V'), + (0x3200, '3', u'(ᄀ)'), + (0x3201, '3', u'(ᄂ)'), + (0x3202, '3', u'(ᄃ)'), + (0x3203, '3', u'(ᄅ)'), + (0x3204, '3', u'(ᄆ)'), + (0x3205, '3', u'(ᄇ)'), + (0x3206, '3', u'(ᄉ)'), + (0x3207, '3', u'(ᄋ)'), + (0x3208, '3', u'(ᄌ)'), + (0x3209, '3', u'(ᄎ)'), + (0x320A, '3', u'(ᄏ)'), + (0x320B, '3', u'(ᄐ)'), + (0x320C, '3', u'(ᄑ)'), + (0x320D, '3', u'(ᄒ)'), + (0x320E, '3', u'(가)'), + (0x320F, '3', u'(나)'), + (0x3210, '3', u'(다)'), + (0x3211, '3', u'(라)'), + (0x3212, '3', u'(마)'), + (0x3213, '3', u'(바)'), + (0x3214, '3', u'(사)'), + (0x3215, '3', u'(아)'), + (0x3216, '3', u'(자)'), + (0x3217, '3', u'(차)'), + (0x3218, '3', u'(카)'), + (0x3219, '3', u'(타)'), + (0x321A, '3', u'(파)'), + (0x321B, '3', u'(하)'), + (0x321C, '3', u'(주)'), + (0x321D, '3', u'(오전)'), + (0x321E, '3', u'(오후)'), + (0x321F, 'X'), + (0x3220, '3', u'(一)'), + (0x3221, '3', u'(二)'), + (0x3222, '3', u'(三)'), + (0x3223, '3', u'(四)'), + (0x3224, '3', u'(五)'), + (0x3225, '3', u'(六)'), + (0x3226, '3', u'(七)'), + (0x3227, '3', u'(八)'), + (0x3228, '3', u'(九)'), + (0x3229, '3', u'(十)'), + (0x322A, '3', u'(月)'), + (0x322B, '3', u'(火)'), + (0x322C, '3', u'(水)'), + (0x322D, '3', u'(木)'), + (0x322E, '3', u'(金)'), + (0x322F, '3', u'(土)'), + (0x3230, '3', u'(日)'), + (0x3231, '3', u'(株)'), + (0x3232, '3', u'(有)'), + (0x3233, '3', u'(社)'), + (0x3234, '3', u'(名)'), + (0x3235, '3', u'(特)'), + (0x3236, '3', u'(財)'), + (0x3237, '3', u'(祝)'), + (0x3238, '3', u'(労)'), + (0x3239, '3', u'(代)'), + (0x323A, '3', u'(呼)'), + (0x323B, '3', u'(学)'), + (0x323C, '3', u'(監)'), + (0x323D, '3', u'(企)'), + (0x323E, '3', u'(資)'), + (0x323F, '3', u'(協)'), + (0x3240, '3', u'(祭)'), + (0x3241, '3', u'(休)'), + (0x3242, '3', u'(自)'), + (0x3243, '3', u'(至)'), + (0x3244, 'M', u'問'), + (0x3245, 'M', u'幼'), + (0x3246, 'M', u'文'), + (0x3247, 'M', u'箏'), + (0x3248, 'V'), + (0x3250, 'M', u'pte'), + (0x3251, 'M', u'21'), + (0x3252, 'M', u'22'), + (0x3253, 'M', u'23'), + (0x3254, 'M', u'24'), + (0x3255, 'M', u'25'), + (0x3256, 'M', u'26'), + (0x3257, 'M', u'27'), + (0x3258, 'M', u'28'), + ] + +def _seg_31(): + return [ + (0x3259, 'M', u'29'), + (0x325A, 'M', u'30'), + (0x325B, 'M', u'31'), + (0x325C, 'M', u'32'), + (0x325D, 'M', u'33'), + (0x325E, 'M', u'34'), + (0x325F, 'M', u'35'), + (0x3260, 'M', u'ᄀ'), + (0x3261, 'M', u'ᄂ'), + (0x3262, 'M', u'ᄃ'), + (0x3263, 'M', u'ᄅ'), + (0x3264, 'M', u'ᄆ'), + (0x3265, 'M', u'ᄇ'), + (0x3266, 'M', u'ᄉ'), + (0x3267, 'M', u'ᄋ'), + (0x3268, 'M', u'ᄌ'), + (0x3269, 'M', u'ᄎ'), + (0x326A, 'M', u'ᄏ'), + (0x326B, 'M', u'ᄐ'), + (0x326C, 'M', u'ᄑ'), + (0x326D, 'M', u'ᄒ'), + (0x326E, 'M', u'가'), + (0x326F, 'M', u'나'), + (0x3270, 'M', u'다'), + (0x3271, 'M', u'라'), + (0x3272, 'M', u'마'), + (0x3273, 'M', u'바'), + (0x3274, 'M', u'사'), + (0x3275, 'M', u'아'), + (0x3276, 'M', u'자'), + (0x3277, 'M', u'차'), + (0x3278, 'M', u'카'), + (0x3279, 'M', u'타'), + (0x327A, 'M', u'파'), + (0x327B, 'M', u'하'), + (0x327C, 'M', u'참고'), + (0x327D, 'M', u'주의'), + (0x327E, 'M', u'우'), + (0x327F, 'V'), + (0x3280, 'M', u'一'), + (0x3281, 'M', u'二'), + (0x3282, 'M', u'三'), + (0x3283, 'M', u'四'), + (0x3284, 'M', u'五'), + (0x3285, 'M', u'六'), + (0x3286, 'M', u'七'), + (0x3287, 'M', u'八'), + (0x3288, 'M', u'九'), + (0x3289, 'M', u'十'), + (0x328A, 'M', u'月'), + (0x328B, 'M', u'火'), + (0x328C, 'M', u'水'), + (0x328D, 'M', u'木'), + (0x328E, 'M', u'金'), + (0x328F, 'M', u'土'), + (0x3290, 'M', u'日'), + (0x3291, 'M', u'株'), + (0x3292, 'M', u'有'), + (0x3293, 'M', u'社'), + (0x3294, 'M', u'名'), + (0x3295, 'M', u'特'), + (0x3296, 'M', u'財'), + (0x3297, 'M', u'祝'), + (0x3298, 'M', u'労'), + (0x3299, 'M', u'秘'), + (0x329A, 'M', u'男'), + (0x329B, 'M', u'女'), + (0x329C, 'M', u'適'), + (0x329D, 'M', u'優'), + (0x329E, 'M', u'印'), + (0x329F, 'M', u'注'), + (0x32A0, 'M', u'項'), + (0x32A1, 'M', u'休'), + (0x32A2, 'M', u'写'), + (0x32A3, 'M', u'正'), + (0x32A4, 'M', u'上'), + (0x32A5, 'M', u'中'), + (0x32A6, 'M', u'下'), + (0x32A7, 'M', u'左'), + (0x32A8, 'M', u'右'), + (0x32A9, 'M', u'医'), + (0x32AA, 'M', u'宗'), + (0x32AB, 'M', u'学'), + (0x32AC, 'M', u'監'), + (0x32AD, 'M', u'企'), + (0x32AE, 'M', u'資'), + (0x32AF, 'M', u'協'), + (0x32B0, 'M', u'夜'), + (0x32B1, 'M', u'36'), + (0x32B2, 'M', u'37'), + (0x32B3, 'M', u'38'), + (0x32B4, 'M', u'39'), + (0x32B5, 'M', u'40'), + (0x32B6, 'M', u'41'), + (0x32B7, 'M', u'42'), + (0x32B8, 'M', u'43'), + (0x32B9, 'M', u'44'), + (0x32BA, 'M', u'45'), + (0x32BB, 'M', u'46'), + (0x32BC, 'M', u'47'), + ] + +def _seg_32(): + return [ + (0x32BD, 'M', u'48'), + (0x32BE, 'M', u'49'), + (0x32BF, 'M', u'50'), + (0x32C0, 'M', u'1月'), + (0x32C1, 'M', u'2月'), + (0x32C2, 'M', u'3月'), + (0x32C3, 'M', u'4月'), + (0x32C4, 'M', u'5月'), + (0x32C5, 'M', u'6月'), + (0x32C6, 'M', u'7月'), + (0x32C7, 'M', u'8月'), + (0x32C8, 'M', u'9月'), + (0x32C9, 'M', u'10月'), + (0x32CA, 'M', u'11月'), + (0x32CB, 'M', u'12月'), + (0x32CC, 'M', u'hg'), + (0x32CD, 'M', u'erg'), + (0x32CE, 'M', u'ev'), + (0x32CF, 'M', u'ltd'), + (0x32D0, 'M', u'ア'), + (0x32D1, 'M', u'イ'), + (0x32D2, 'M', u'ウ'), + (0x32D3, 'M', u'エ'), + (0x32D4, 'M', u'オ'), + (0x32D5, 'M', u'カ'), + (0x32D6, 'M', u'キ'), + (0x32D7, 'M', u'ク'), + (0x32D8, 'M', u'ケ'), + (0x32D9, 'M', u'コ'), + (0x32DA, 'M', u'サ'), + (0x32DB, 'M', u'シ'), + (0x32DC, 'M', u'ス'), + (0x32DD, 'M', u'セ'), + (0x32DE, 'M', u'ソ'), + (0x32DF, 'M', u'タ'), + (0x32E0, 'M', u'チ'), + (0x32E1, 'M', u'ツ'), + (0x32E2, 'M', u'テ'), + (0x32E3, 'M', u'ト'), + (0x32E4, 'M', u'ナ'), + (0x32E5, 'M', u'ニ'), + (0x32E6, 'M', u'ヌ'), + (0x32E7, 'M', u'ネ'), + (0x32E8, 'M', u'ノ'), + (0x32E9, 'M', u'ハ'), + (0x32EA, 'M', u'ヒ'), + (0x32EB, 'M', u'フ'), + (0x32EC, 'M', u'ヘ'), + (0x32ED, 'M', u'ホ'), + (0x32EE, 'M', u'マ'), + (0x32EF, 'M', u'ミ'), + (0x32F0, 'M', u'ム'), + (0x32F1, 'M', u'メ'), + (0x32F2, 'M', u'モ'), + (0x32F3, 'M', u'ヤ'), + (0x32F4, 'M', u'ユ'), + (0x32F5, 'M', u'ヨ'), + (0x32F6, 'M', u'ラ'), + (0x32F7, 'M', u'リ'), + (0x32F8, 'M', u'ル'), + (0x32F9, 'M', u'レ'), + (0x32FA, 'M', u'ロ'), + (0x32FB, 'M', u'ワ'), + (0x32FC, 'M', u'ヰ'), + (0x32FD, 'M', u'ヱ'), + (0x32FE, 'M', u'ヲ'), + (0x32FF, 'X'), + (0x3300, 'M', u'アパート'), + (0x3301, 'M', u'アルファ'), + (0x3302, 'M', u'アンペア'), + (0x3303, 'M', u'アール'), + (0x3304, 'M', u'イニング'), + (0x3305, 'M', u'インチ'), + (0x3306, 'M', u'ウォン'), + (0x3307, 'M', u'エスクード'), + (0x3308, 'M', u'エーカー'), + (0x3309, 'M', u'オンス'), + (0x330A, 'M', u'オーム'), + (0x330B, 'M', u'カイリ'), + (0x330C, 'M', u'カラット'), + (0x330D, 'M', u'カロリー'), + (0x330E, 'M', u'ガロン'), + (0x330F, 'M', u'ガンマ'), + (0x3310, 'M', u'ギガ'), + (0x3311, 'M', u'ギニー'), + (0x3312, 'M', u'キュリー'), + (0x3313, 'M', u'ギルダー'), + (0x3314, 'M', u'キロ'), + (0x3315, 'M', u'キログラム'), + (0x3316, 'M', u'キロメートル'), + (0x3317, 'M', u'キロワット'), + (0x3318, 'M', u'グラム'), + (0x3319, 'M', u'グラムトン'), + (0x331A, 'M', u'クルゼイロ'), + (0x331B, 'M', u'クローネ'), + (0x331C, 'M', u'ケース'), + (0x331D, 'M', u'コルナ'), + (0x331E, 'M', u'コーポ'), + (0x331F, 'M', u'サイクル'), + (0x3320, 'M', u'サンチーム'), + ] + +def _seg_33(): + return [ + (0x3321, 'M', u'シリング'), + (0x3322, 'M', u'センチ'), + (0x3323, 'M', u'セント'), + (0x3324, 'M', u'ダース'), + (0x3325, 'M', u'デシ'), + (0x3326, 'M', u'ドル'), + (0x3327, 'M', u'トン'), + (0x3328, 'M', u'ナノ'), + (0x3329, 'M', u'ノット'), + (0x332A, 'M', u'ハイツ'), + (0x332B, 'M', u'パーセント'), + (0x332C, 'M', u'パーツ'), + (0x332D, 'M', u'バーレル'), + (0x332E, 'M', u'ピアストル'), + (0x332F, 'M', u'ピクル'), + (0x3330, 'M', u'ピコ'), + (0x3331, 'M', u'ビル'), + (0x3332, 'M', u'ファラッド'), + (0x3333, 'M', u'フィート'), + (0x3334, 'M', u'ブッシェル'), + (0x3335, 'M', u'フラン'), + (0x3336, 'M', u'ヘクタール'), + (0x3337, 'M', u'ペソ'), + (0x3338, 'M', u'ペニヒ'), + (0x3339, 'M', u'ヘルツ'), + (0x333A, 'M', u'ペンス'), + (0x333B, 'M', u'ページ'), + (0x333C, 'M', u'ベータ'), + (0x333D, 'M', u'ポイント'), + (0x333E, 'M', u'ボルト'), + (0x333F, 'M', u'ホン'), + (0x3340, 'M', u'ポンド'), + (0x3341, 'M', u'ホール'), + (0x3342, 'M', u'ホーン'), + (0x3343, 'M', u'マイクロ'), + (0x3344, 'M', u'マイル'), + (0x3345, 'M', u'マッハ'), + (0x3346, 'M', u'マルク'), + (0x3347, 'M', u'マンション'), + (0x3348, 'M', u'ミクロン'), + (0x3349, 'M', u'ミリ'), + (0x334A, 'M', u'ミリバール'), + (0x334B, 'M', u'メガ'), + (0x334C, 'M', u'メガトン'), + (0x334D, 'M', u'メートル'), + (0x334E, 'M', u'ヤード'), + (0x334F, 'M', u'ヤール'), + (0x3350, 'M', u'ユアン'), + (0x3351, 'M', u'リットル'), + (0x3352, 'M', u'リラ'), + (0x3353, 'M', u'ルピー'), + (0x3354, 'M', u'ルーブル'), + (0x3355, 'M', u'レム'), + (0x3356, 'M', u'レントゲン'), + (0x3357, 'M', u'ワット'), + (0x3358, 'M', u'0点'), + (0x3359, 'M', u'1点'), + (0x335A, 'M', u'2点'), + (0x335B, 'M', u'3点'), + (0x335C, 'M', u'4点'), + (0x335D, 'M', u'5点'), + (0x335E, 'M', u'6点'), + (0x335F, 'M', u'7点'), + (0x3360, 'M', u'8点'), + (0x3361, 'M', u'9点'), + (0x3362, 'M', u'10点'), + (0x3363, 'M', u'11点'), + (0x3364, 'M', u'12点'), + (0x3365, 'M', u'13点'), + (0x3366, 'M', u'14点'), + (0x3367, 'M', u'15点'), + (0x3368, 'M', u'16点'), + (0x3369, 'M', u'17点'), + (0x336A, 'M', u'18点'), + (0x336B, 'M', u'19点'), + (0x336C, 'M', u'20点'), + (0x336D, 'M', u'21点'), + (0x336E, 'M', u'22点'), + (0x336F, 'M', u'23点'), + (0x3370, 'M', u'24点'), + (0x3371, 'M', u'hpa'), + (0x3372, 'M', u'da'), + (0x3373, 'M', u'au'), + (0x3374, 'M', u'bar'), + (0x3375, 'M', u'ov'), + (0x3376, 'M', u'pc'), + (0x3377, 'M', u'dm'), + (0x3378, 'M', u'dm2'), + (0x3379, 'M', u'dm3'), + (0x337A, 'M', u'iu'), + (0x337B, 'M', u'平成'), + (0x337C, 'M', u'昭和'), + (0x337D, 'M', u'大正'), + (0x337E, 'M', u'明治'), + (0x337F, 'M', u'株式会社'), + (0x3380, 'M', u'pa'), + (0x3381, 'M', u'na'), + (0x3382, 'M', u'μa'), + (0x3383, 'M', u'ma'), + (0x3384, 'M', u'ka'), + ] + +def _seg_34(): + return [ + (0x3385, 'M', u'kb'), + (0x3386, 'M', u'mb'), + (0x3387, 'M', u'gb'), + (0x3388, 'M', u'cal'), + (0x3389, 'M', u'kcal'), + (0x338A, 'M', u'pf'), + (0x338B, 'M', u'nf'), + (0x338C, 'M', u'μf'), + (0x338D, 'M', u'μg'), + (0x338E, 'M', u'mg'), + (0x338F, 'M', u'kg'), + (0x3390, 'M', u'hz'), + (0x3391, 'M', u'khz'), + (0x3392, 'M', u'mhz'), + (0x3393, 'M', u'ghz'), + (0x3394, 'M', u'thz'), + (0x3395, 'M', u'μl'), + (0x3396, 'M', u'ml'), + (0x3397, 'M', u'dl'), + (0x3398, 'M', u'kl'), + (0x3399, 'M', u'fm'), + (0x339A, 'M', u'nm'), + (0x339B, 'M', u'μm'), + (0x339C, 'M', u'mm'), + (0x339D, 'M', u'cm'), + (0x339E, 'M', u'km'), + (0x339F, 'M', u'mm2'), + (0x33A0, 'M', u'cm2'), + (0x33A1, 'M', u'm2'), + (0x33A2, 'M', u'km2'), + (0x33A3, 'M', u'mm3'), + (0x33A4, 'M', u'cm3'), + (0x33A5, 'M', u'm3'), + (0x33A6, 'M', u'km3'), + (0x33A7, 'M', u'm∕s'), + (0x33A8, 'M', u'm∕s2'), + (0x33A9, 'M', u'pa'), + (0x33AA, 'M', u'kpa'), + (0x33AB, 'M', u'mpa'), + (0x33AC, 'M', u'gpa'), + (0x33AD, 'M', u'rad'), + (0x33AE, 'M', u'rad∕s'), + (0x33AF, 'M', u'rad∕s2'), + (0x33B0, 'M', u'ps'), + (0x33B1, 'M', u'ns'), + (0x33B2, 'M', u'μs'), + (0x33B3, 'M', u'ms'), + (0x33B4, 'M', u'pv'), + (0x33B5, 'M', u'nv'), + (0x33B6, 'M', u'μv'), + (0x33B7, 'M', u'mv'), + (0x33B8, 'M', u'kv'), + (0x33B9, 'M', u'mv'), + (0x33BA, 'M', u'pw'), + (0x33BB, 'M', u'nw'), + (0x33BC, 'M', u'μw'), + (0x33BD, 'M', u'mw'), + (0x33BE, 'M', u'kw'), + (0x33BF, 'M', u'mw'), + (0x33C0, 'M', u'kω'), + (0x33C1, 'M', u'mω'), + (0x33C2, 'X'), + (0x33C3, 'M', u'bq'), + (0x33C4, 'M', u'cc'), + (0x33C5, 'M', u'cd'), + (0x33C6, 'M', u'c∕kg'), + (0x33C7, 'X'), + (0x33C8, 'M', u'db'), + (0x33C9, 'M', u'gy'), + (0x33CA, 'M', u'ha'), + (0x33CB, 'M', u'hp'), + (0x33CC, 'M', u'in'), + (0x33CD, 'M', u'kk'), + (0x33CE, 'M', u'km'), + (0x33CF, 'M', u'kt'), + (0x33D0, 'M', u'lm'), + (0x33D1, 'M', u'ln'), + (0x33D2, 'M', u'log'), + (0x33D3, 'M', u'lx'), + (0x33D4, 'M', u'mb'), + (0x33D5, 'M', u'mil'), + (0x33D6, 'M', u'mol'), + (0x33D7, 'M', u'ph'), + (0x33D8, 'X'), + (0x33D9, 'M', u'ppm'), + (0x33DA, 'M', u'pr'), + (0x33DB, 'M', u'sr'), + (0x33DC, 'M', u'sv'), + (0x33DD, 'M', u'wb'), + (0x33DE, 'M', u'v∕m'), + (0x33DF, 'M', u'a∕m'), + (0x33E0, 'M', u'1日'), + (0x33E1, 'M', u'2日'), + (0x33E2, 'M', u'3日'), + (0x33E3, 'M', u'4日'), + (0x33E4, 'M', u'5日'), + (0x33E5, 'M', u'6日'), + (0x33E6, 'M', u'7日'), + (0x33E7, 'M', u'8日'), + (0x33E8, 'M', u'9日'), + ] + +def _seg_35(): + return [ + (0x33E9, 'M', u'10日'), + (0x33EA, 'M', u'11日'), + (0x33EB, 'M', u'12日'), + (0x33EC, 'M', u'13日'), + (0x33ED, 'M', u'14日'), + (0x33EE, 'M', u'15日'), + (0x33EF, 'M', u'16日'), + (0x33F0, 'M', u'17日'), + (0x33F1, 'M', u'18日'), + (0x33F2, 'M', u'19日'), + (0x33F3, 'M', u'20日'), + (0x33F4, 'M', u'21日'), + (0x33F5, 'M', u'22日'), + (0x33F6, 'M', u'23日'), + (0x33F7, 'M', u'24日'), + (0x33F8, 'M', u'25日'), + (0x33F9, 'M', u'26日'), + (0x33FA, 'M', u'27日'), + (0x33FB, 'M', u'28日'), + (0x33FC, 'M', u'29日'), + (0x33FD, 'M', u'30日'), + (0x33FE, 'M', u'31日'), + (0x33FF, 'M', u'gal'), + (0x3400, 'V'), + (0x4DB6, 'X'), + (0x4DC0, 'V'), + (0x9FEB, 'X'), + (0xA000, 'V'), + (0xA48D, 'X'), + (0xA490, 'V'), + (0xA4C7, 'X'), + (0xA4D0, 'V'), + (0xA62C, 'X'), + (0xA640, 'M', u'ꙁ'), + (0xA641, 'V'), + (0xA642, 'M', u'ꙃ'), + (0xA643, 'V'), + (0xA644, 'M', u'ꙅ'), + (0xA645, 'V'), + (0xA646, 'M', u'ꙇ'), + (0xA647, 'V'), + (0xA648, 'M', u'ꙉ'), + (0xA649, 'V'), + (0xA64A, 'M', u'ꙋ'), + (0xA64B, 'V'), + (0xA64C, 'M', u'ꙍ'), + (0xA64D, 'V'), + (0xA64E, 'M', u'ꙏ'), + (0xA64F, 'V'), + (0xA650, 'M', u'ꙑ'), + (0xA651, 'V'), + (0xA652, 'M', u'ꙓ'), + (0xA653, 'V'), + (0xA654, 'M', u'ꙕ'), + (0xA655, 'V'), + (0xA656, 'M', u'ꙗ'), + (0xA657, 'V'), + (0xA658, 'M', u'ꙙ'), + (0xA659, 'V'), + (0xA65A, 'M', u'ꙛ'), + (0xA65B, 'V'), + (0xA65C, 'M', u'ꙝ'), + (0xA65D, 'V'), + (0xA65E, 'M', u'ꙟ'), + (0xA65F, 'V'), + (0xA660, 'M', u'ꙡ'), + (0xA661, 'V'), + (0xA662, 'M', u'ꙣ'), + (0xA663, 'V'), + (0xA664, 'M', u'ꙥ'), + (0xA665, 'V'), + (0xA666, 'M', u'ꙧ'), + (0xA667, 'V'), + (0xA668, 'M', u'ꙩ'), + (0xA669, 'V'), + (0xA66A, 'M', u'ꙫ'), + (0xA66B, 'V'), + (0xA66C, 'M', u'ꙭ'), + (0xA66D, 'V'), + (0xA680, 'M', u'ꚁ'), + (0xA681, 'V'), + (0xA682, 'M', u'ꚃ'), + (0xA683, 'V'), + (0xA684, 'M', u'ꚅ'), + (0xA685, 'V'), + (0xA686, 'M', u'ꚇ'), + (0xA687, 'V'), + (0xA688, 'M', u'ꚉ'), + (0xA689, 'V'), + (0xA68A, 'M', u'ꚋ'), + (0xA68B, 'V'), + (0xA68C, 'M', u'ꚍ'), + (0xA68D, 'V'), + (0xA68E, 'M', u'ꚏ'), + (0xA68F, 'V'), + (0xA690, 'M', u'ꚑ'), + (0xA691, 'V'), + (0xA692, 'M', u'ꚓ'), + (0xA693, 'V'), + (0xA694, 'M', u'ꚕ'), + ] + +def _seg_36(): + return [ + (0xA695, 'V'), + (0xA696, 'M', u'ꚗ'), + (0xA697, 'V'), + (0xA698, 'M', u'ꚙ'), + (0xA699, 'V'), + (0xA69A, 'M', u'ꚛ'), + (0xA69B, 'V'), + (0xA69C, 'M', u'ъ'), + (0xA69D, 'M', u'ь'), + (0xA69E, 'V'), + (0xA6F8, 'X'), + (0xA700, 'V'), + (0xA722, 'M', u'ꜣ'), + (0xA723, 'V'), + (0xA724, 'M', u'ꜥ'), + (0xA725, 'V'), + (0xA726, 'M', u'ꜧ'), + (0xA727, 'V'), + (0xA728, 'M', u'ꜩ'), + (0xA729, 'V'), + (0xA72A, 'M', u'ꜫ'), + (0xA72B, 'V'), + (0xA72C, 'M', u'ꜭ'), + (0xA72D, 'V'), + (0xA72E, 'M', u'ꜯ'), + (0xA72F, 'V'), + (0xA732, 'M', u'ꜳ'), + (0xA733, 'V'), + (0xA734, 'M', u'ꜵ'), + (0xA735, 'V'), + (0xA736, 'M', u'ꜷ'), + (0xA737, 'V'), + (0xA738, 'M', u'ꜹ'), + (0xA739, 'V'), + (0xA73A, 'M', u'ꜻ'), + (0xA73B, 'V'), + (0xA73C, 'M', u'ꜽ'), + (0xA73D, 'V'), + (0xA73E, 'M', u'ꜿ'), + (0xA73F, 'V'), + (0xA740, 'M', u'ꝁ'), + (0xA741, 'V'), + (0xA742, 'M', u'ꝃ'), + (0xA743, 'V'), + (0xA744, 'M', u'ꝅ'), + (0xA745, 'V'), + (0xA746, 'M', u'ꝇ'), + (0xA747, 'V'), + (0xA748, 'M', u'ꝉ'), + (0xA749, 'V'), + (0xA74A, 'M', u'ꝋ'), + (0xA74B, 'V'), + (0xA74C, 'M', u'ꝍ'), + (0xA74D, 'V'), + (0xA74E, 'M', u'ꝏ'), + (0xA74F, 'V'), + (0xA750, 'M', u'ꝑ'), + (0xA751, 'V'), + (0xA752, 'M', u'ꝓ'), + (0xA753, 'V'), + (0xA754, 'M', u'ꝕ'), + (0xA755, 'V'), + (0xA756, 'M', u'ꝗ'), + (0xA757, 'V'), + (0xA758, 'M', u'ꝙ'), + (0xA759, 'V'), + (0xA75A, 'M', u'ꝛ'), + (0xA75B, 'V'), + (0xA75C, 'M', u'ꝝ'), + (0xA75D, 'V'), + (0xA75E, 'M', u'ꝟ'), + (0xA75F, 'V'), + (0xA760, 'M', u'ꝡ'), + (0xA761, 'V'), + (0xA762, 'M', u'ꝣ'), + (0xA763, 'V'), + (0xA764, 'M', u'ꝥ'), + (0xA765, 'V'), + (0xA766, 'M', u'ꝧ'), + (0xA767, 'V'), + (0xA768, 'M', u'ꝩ'), + (0xA769, 'V'), + (0xA76A, 'M', u'ꝫ'), + (0xA76B, 'V'), + (0xA76C, 'M', u'ꝭ'), + (0xA76D, 'V'), + (0xA76E, 'M', u'ꝯ'), + (0xA76F, 'V'), + (0xA770, 'M', u'ꝯ'), + (0xA771, 'V'), + (0xA779, 'M', u'ꝺ'), + (0xA77A, 'V'), + (0xA77B, 'M', u'ꝼ'), + (0xA77C, 'V'), + (0xA77D, 'M', u'ᵹ'), + (0xA77E, 'M', u'ꝿ'), + (0xA77F, 'V'), + (0xA780, 'M', u'ꞁ'), + (0xA781, 'V'), + (0xA782, 'M', u'ꞃ'), + ] + +def _seg_37(): + return [ + (0xA783, 'V'), + (0xA784, 'M', u'ꞅ'), + (0xA785, 'V'), + (0xA786, 'M', u'ꞇ'), + (0xA787, 'V'), + (0xA78B, 'M', u'ꞌ'), + (0xA78C, 'V'), + (0xA78D, 'M', u'ɥ'), + (0xA78E, 'V'), + (0xA790, 'M', u'ꞑ'), + (0xA791, 'V'), + (0xA792, 'M', u'ꞓ'), + (0xA793, 'V'), + (0xA796, 'M', u'ꞗ'), + (0xA797, 'V'), + (0xA798, 'M', u'ꞙ'), + (0xA799, 'V'), + (0xA79A, 'M', u'ꞛ'), + (0xA79B, 'V'), + (0xA79C, 'M', u'ꞝ'), + (0xA79D, 'V'), + (0xA79E, 'M', u'ꞟ'), + (0xA79F, 'V'), + (0xA7A0, 'M', u'ꞡ'), + (0xA7A1, 'V'), + (0xA7A2, 'M', u'ꞣ'), + (0xA7A3, 'V'), + (0xA7A4, 'M', u'ꞥ'), + (0xA7A5, 'V'), + (0xA7A6, 'M', u'ꞧ'), + (0xA7A7, 'V'), + (0xA7A8, 'M', u'ꞩ'), + (0xA7A9, 'V'), + (0xA7AA, 'M', u'ɦ'), + (0xA7AB, 'M', u'ɜ'), + (0xA7AC, 'M', u'ɡ'), + (0xA7AD, 'M', u'ɬ'), + (0xA7AE, 'M', u'ɪ'), + (0xA7AF, 'X'), + (0xA7B0, 'M', u'ʞ'), + (0xA7B1, 'M', u'ʇ'), + (0xA7B2, 'M', u'ʝ'), + (0xA7B3, 'M', u'ꭓ'), + (0xA7B4, 'M', u'ꞵ'), + (0xA7B5, 'V'), + (0xA7B6, 'M', u'ꞷ'), + (0xA7B7, 'V'), + (0xA7B8, 'X'), + (0xA7F7, 'V'), + (0xA7F8, 'M', u'ħ'), + (0xA7F9, 'M', u'œ'), + (0xA7FA, 'V'), + (0xA82C, 'X'), + (0xA830, 'V'), + (0xA83A, 'X'), + (0xA840, 'V'), + (0xA878, 'X'), + (0xA880, 'V'), + (0xA8C6, 'X'), + (0xA8CE, 'V'), + (0xA8DA, 'X'), + (0xA8E0, 'V'), + (0xA8FE, 'X'), + (0xA900, 'V'), + (0xA954, 'X'), + (0xA95F, 'V'), + (0xA97D, 'X'), + (0xA980, 'V'), + (0xA9CE, 'X'), + (0xA9CF, 'V'), + (0xA9DA, 'X'), + (0xA9DE, 'V'), + (0xA9FF, 'X'), + (0xAA00, 'V'), + (0xAA37, 'X'), + (0xAA40, 'V'), + (0xAA4E, 'X'), + (0xAA50, 'V'), + (0xAA5A, 'X'), + (0xAA5C, 'V'), + (0xAAC3, 'X'), + (0xAADB, 'V'), + (0xAAF7, 'X'), + (0xAB01, 'V'), + (0xAB07, 'X'), + (0xAB09, 'V'), + (0xAB0F, 'X'), + (0xAB11, 'V'), + (0xAB17, 'X'), + (0xAB20, 'V'), + (0xAB27, 'X'), + (0xAB28, 'V'), + (0xAB2F, 'X'), + (0xAB30, 'V'), + (0xAB5C, 'M', u'ꜧ'), + (0xAB5D, 'M', u'ꬷ'), + (0xAB5E, 'M', u'ɫ'), + (0xAB5F, 'M', u'ꭒ'), + (0xAB60, 'V'), + (0xAB66, 'X'), + ] + +def _seg_38(): + return [ + (0xAB70, 'M', u'Ꭰ'), + (0xAB71, 'M', u'Ꭱ'), + (0xAB72, 'M', u'Ꭲ'), + (0xAB73, 'M', u'Ꭳ'), + (0xAB74, 'M', u'Ꭴ'), + (0xAB75, 'M', u'Ꭵ'), + (0xAB76, 'M', u'Ꭶ'), + (0xAB77, 'M', u'Ꭷ'), + (0xAB78, 'M', u'Ꭸ'), + (0xAB79, 'M', u'Ꭹ'), + (0xAB7A, 'M', u'Ꭺ'), + (0xAB7B, 'M', u'Ꭻ'), + (0xAB7C, 'M', u'Ꭼ'), + (0xAB7D, 'M', u'Ꭽ'), + (0xAB7E, 'M', u'Ꭾ'), + (0xAB7F, 'M', u'Ꭿ'), + (0xAB80, 'M', u'Ꮀ'), + (0xAB81, 'M', u'Ꮁ'), + (0xAB82, 'M', u'Ꮂ'), + (0xAB83, 'M', u'Ꮃ'), + (0xAB84, 'M', u'Ꮄ'), + (0xAB85, 'M', u'Ꮅ'), + (0xAB86, 'M', u'Ꮆ'), + (0xAB87, 'M', u'Ꮇ'), + (0xAB88, 'M', u'Ꮈ'), + (0xAB89, 'M', u'Ꮉ'), + (0xAB8A, 'M', u'Ꮊ'), + (0xAB8B, 'M', u'Ꮋ'), + (0xAB8C, 'M', u'Ꮌ'), + (0xAB8D, 'M', u'Ꮍ'), + (0xAB8E, 'M', u'Ꮎ'), + (0xAB8F, 'M', u'Ꮏ'), + (0xAB90, 'M', u'Ꮐ'), + (0xAB91, 'M', u'Ꮑ'), + (0xAB92, 'M', u'Ꮒ'), + (0xAB93, 'M', u'Ꮓ'), + (0xAB94, 'M', u'Ꮔ'), + (0xAB95, 'M', u'Ꮕ'), + (0xAB96, 'M', u'Ꮖ'), + (0xAB97, 'M', u'Ꮗ'), + (0xAB98, 'M', u'Ꮘ'), + (0xAB99, 'M', u'Ꮙ'), + (0xAB9A, 'M', u'Ꮚ'), + (0xAB9B, 'M', u'Ꮛ'), + (0xAB9C, 'M', u'Ꮜ'), + (0xAB9D, 'M', u'Ꮝ'), + (0xAB9E, 'M', u'Ꮞ'), + (0xAB9F, 'M', u'Ꮟ'), + (0xABA0, 'M', u'Ꮠ'), + (0xABA1, 'M', u'Ꮡ'), + (0xABA2, 'M', u'Ꮢ'), + (0xABA3, 'M', u'Ꮣ'), + (0xABA4, 'M', u'Ꮤ'), + (0xABA5, 'M', u'Ꮥ'), + (0xABA6, 'M', u'Ꮦ'), + (0xABA7, 'M', u'Ꮧ'), + (0xABA8, 'M', u'Ꮨ'), + (0xABA9, 'M', u'Ꮩ'), + (0xABAA, 'M', u'Ꮪ'), + (0xABAB, 'M', u'Ꮫ'), + (0xABAC, 'M', u'Ꮬ'), + (0xABAD, 'M', u'Ꮭ'), + (0xABAE, 'M', u'Ꮮ'), + (0xABAF, 'M', u'Ꮯ'), + (0xABB0, 'M', u'Ꮰ'), + (0xABB1, 'M', u'Ꮱ'), + (0xABB2, 'M', u'Ꮲ'), + (0xABB3, 'M', u'Ꮳ'), + (0xABB4, 'M', u'Ꮴ'), + (0xABB5, 'M', u'Ꮵ'), + (0xABB6, 'M', u'Ꮶ'), + (0xABB7, 'M', u'Ꮷ'), + (0xABB8, 'M', u'Ꮸ'), + (0xABB9, 'M', u'Ꮹ'), + (0xABBA, 'M', u'Ꮺ'), + (0xABBB, 'M', u'Ꮻ'), + (0xABBC, 'M', u'Ꮼ'), + (0xABBD, 'M', u'Ꮽ'), + (0xABBE, 'M', u'Ꮾ'), + (0xABBF, 'M', u'Ꮿ'), + (0xABC0, 'V'), + (0xABEE, 'X'), + (0xABF0, 'V'), + (0xABFA, 'X'), + (0xAC00, 'V'), + (0xD7A4, 'X'), + (0xD7B0, 'V'), + (0xD7C7, 'X'), + (0xD7CB, 'V'), + (0xD7FC, 'X'), + (0xF900, 'M', u'豈'), + (0xF901, 'M', u'更'), + (0xF902, 'M', u'車'), + (0xF903, 'M', u'賈'), + (0xF904, 'M', u'滑'), + (0xF905, 'M', u'串'), + (0xF906, 'M', u'句'), + (0xF907, 'M', u'龜'), + (0xF909, 'M', u'契'), + (0xF90A, 'M', u'金'), + ] + +def _seg_39(): + return [ + (0xF90B, 'M', u'喇'), + (0xF90C, 'M', u'奈'), + (0xF90D, 'M', u'懶'), + (0xF90E, 'M', u'癩'), + (0xF90F, 'M', u'羅'), + (0xF910, 'M', u'蘿'), + (0xF911, 'M', u'螺'), + (0xF912, 'M', u'裸'), + (0xF913, 'M', u'邏'), + (0xF914, 'M', u'樂'), + (0xF915, 'M', u'洛'), + (0xF916, 'M', u'烙'), + (0xF917, 'M', u'珞'), + (0xF918, 'M', u'落'), + (0xF919, 'M', u'酪'), + (0xF91A, 'M', u'駱'), + (0xF91B, 'M', u'亂'), + (0xF91C, 'M', u'卵'), + (0xF91D, 'M', u'欄'), + (0xF91E, 'M', u'爛'), + (0xF91F, 'M', u'蘭'), + (0xF920, 'M', u'鸞'), + (0xF921, 'M', u'嵐'), + (0xF922, 'M', u'濫'), + (0xF923, 'M', u'藍'), + (0xF924, 'M', u'襤'), + (0xF925, 'M', u'拉'), + (0xF926, 'M', u'臘'), + (0xF927, 'M', u'蠟'), + (0xF928, 'M', u'廊'), + (0xF929, 'M', u'朗'), + (0xF92A, 'M', u'浪'), + (0xF92B, 'M', u'狼'), + (0xF92C, 'M', u'郎'), + (0xF92D, 'M', u'來'), + (0xF92E, 'M', u'冷'), + (0xF92F, 'M', u'勞'), + (0xF930, 'M', u'擄'), + (0xF931, 'M', u'櫓'), + (0xF932, 'M', u'爐'), + (0xF933, 'M', u'盧'), + (0xF934, 'M', u'老'), + (0xF935, 'M', u'蘆'), + (0xF936, 'M', u'虜'), + (0xF937, 'M', u'路'), + (0xF938, 'M', u'露'), + (0xF939, 'M', u'魯'), + (0xF93A, 'M', u'鷺'), + (0xF93B, 'M', u'碌'), + (0xF93C, 'M', u'祿'), + (0xF93D, 'M', u'綠'), + (0xF93E, 'M', u'菉'), + (0xF93F, 'M', u'錄'), + (0xF940, 'M', u'鹿'), + (0xF941, 'M', u'論'), + (0xF942, 'M', u'壟'), + (0xF943, 'M', u'弄'), + (0xF944, 'M', u'籠'), + (0xF945, 'M', u'聾'), + (0xF946, 'M', u'牢'), + (0xF947, 'M', u'磊'), + (0xF948, 'M', u'賂'), + (0xF949, 'M', u'雷'), + (0xF94A, 'M', u'壘'), + (0xF94B, 'M', u'屢'), + (0xF94C, 'M', u'樓'), + (0xF94D, 'M', u'淚'), + (0xF94E, 'M', u'漏'), + (0xF94F, 'M', u'累'), + (0xF950, 'M', u'縷'), + (0xF951, 'M', u'陋'), + (0xF952, 'M', u'勒'), + (0xF953, 'M', u'肋'), + (0xF954, 'M', u'凜'), + (0xF955, 'M', u'凌'), + (0xF956, 'M', u'稜'), + (0xF957, 'M', u'綾'), + (0xF958, 'M', u'菱'), + (0xF959, 'M', u'陵'), + (0xF95A, 'M', u'讀'), + (0xF95B, 'M', u'拏'), + (0xF95C, 'M', u'樂'), + (0xF95D, 'M', u'諾'), + (0xF95E, 'M', u'丹'), + (0xF95F, 'M', u'寧'), + (0xF960, 'M', u'怒'), + (0xF961, 'M', u'率'), + (0xF962, 'M', u'異'), + (0xF963, 'M', u'北'), + (0xF964, 'M', u'磻'), + (0xF965, 'M', u'便'), + (0xF966, 'M', u'復'), + (0xF967, 'M', u'不'), + (0xF968, 'M', u'泌'), + (0xF969, 'M', u'數'), + (0xF96A, 'M', u'索'), + (0xF96B, 'M', u'參'), + (0xF96C, 'M', u'塞'), + (0xF96D, 'M', u'省'), + (0xF96E, 'M', u'葉'), + ] + +def _seg_40(): + return [ + (0xF96F, 'M', u'說'), + (0xF970, 'M', u'殺'), + (0xF971, 'M', u'辰'), + (0xF972, 'M', u'沈'), + (0xF973, 'M', u'拾'), + (0xF974, 'M', u'若'), + (0xF975, 'M', u'掠'), + (0xF976, 'M', u'略'), + (0xF977, 'M', u'亮'), + (0xF978, 'M', u'兩'), + (0xF979, 'M', u'凉'), + (0xF97A, 'M', u'梁'), + (0xF97B, 'M', u'糧'), + (0xF97C, 'M', u'良'), + (0xF97D, 'M', u'諒'), + (0xF97E, 'M', u'量'), + (0xF97F, 'M', u'勵'), + (0xF980, 'M', u'呂'), + (0xF981, 'M', u'女'), + (0xF982, 'M', u'廬'), + (0xF983, 'M', u'旅'), + (0xF984, 'M', u'濾'), + (0xF985, 'M', u'礪'), + (0xF986, 'M', u'閭'), + (0xF987, 'M', u'驪'), + (0xF988, 'M', u'麗'), + (0xF989, 'M', u'黎'), + (0xF98A, 'M', u'力'), + (0xF98B, 'M', u'曆'), + (0xF98C, 'M', u'歷'), + (0xF98D, 'M', u'轢'), + (0xF98E, 'M', u'年'), + (0xF98F, 'M', u'憐'), + (0xF990, 'M', u'戀'), + (0xF991, 'M', u'撚'), + (0xF992, 'M', u'漣'), + (0xF993, 'M', u'煉'), + (0xF994, 'M', u'璉'), + (0xF995, 'M', u'秊'), + (0xF996, 'M', u'練'), + (0xF997, 'M', u'聯'), + (0xF998, 'M', u'輦'), + (0xF999, 'M', u'蓮'), + (0xF99A, 'M', u'連'), + (0xF99B, 'M', u'鍊'), + (0xF99C, 'M', u'列'), + (0xF99D, 'M', u'劣'), + (0xF99E, 'M', u'咽'), + (0xF99F, 'M', u'烈'), + (0xF9A0, 'M', u'裂'), + (0xF9A1, 'M', u'說'), + (0xF9A2, 'M', u'廉'), + (0xF9A3, 'M', u'念'), + (0xF9A4, 'M', u'捻'), + (0xF9A5, 'M', u'殮'), + (0xF9A6, 'M', u'簾'), + (0xF9A7, 'M', u'獵'), + (0xF9A8, 'M', u'令'), + (0xF9A9, 'M', u'囹'), + (0xF9AA, 'M', u'寧'), + (0xF9AB, 'M', u'嶺'), + (0xF9AC, 'M', u'怜'), + (0xF9AD, 'M', u'玲'), + (0xF9AE, 'M', u'瑩'), + (0xF9AF, 'M', u'羚'), + (0xF9B0, 'M', u'聆'), + (0xF9B1, 'M', u'鈴'), + (0xF9B2, 'M', u'零'), + (0xF9B3, 'M', u'靈'), + (0xF9B4, 'M', u'領'), + (0xF9B5, 'M', u'例'), + (0xF9B6, 'M', u'禮'), + (0xF9B7, 'M', u'醴'), + (0xF9B8, 'M', u'隸'), + (0xF9B9, 'M', u'惡'), + (0xF9BA, 'M', u'了'), + (0xF9BB, 'M', u'僚'), + (0xF9BC, 'M', u'寮'), + (0xF9BD, 'M', u'尿'), + (0xF9BE, 'M', u'料'), + (0xF9BF, 'M', u'樂'), + (0xF9C0, 'M', u'燎'), + (0xF9C1, 'M', u'療'), + (0xF9C2, 'M', u'蓼'), + (0xF9C3, 'M', u'遼'), + (0xF9C4, 'M', u'龍'), + (0xF9C5, 'M', u'暈'), + (0xF9C6, 'M', u'阮'), + (0xF9C7, 'M', u'劉'), + (0xF9C8, 'M', u'杻'), + (0xF9C9, 'M', u'柳'), + (0xF9CA, 'M', u'流'), + (0xF9CB, 'M', u'溜'), + (0xF9CC, 'M', u'琉'), + (0xF9CD, 'M', u'留'), + (0xF9CE, 'M', u'硫'), + (0xF9CF, 'M', u'紐'), + (0xF9D0, 'M', u'類'), + (0xF9D1, 'M', u'六'), + (0xF9D2, 'M', u'戮'), + ] + +def _seg_41(): + return [ + (0xF9D3, 'M', u'陸'), + (0xF9D4, 'M', u'倫'), + (0xF9D5, 'M', u'崙'), + (0xF9D6, 'M', u'淪'), + (0xF9D7, 'M', u'輪'), + (0xF9D8, 'M', u'律'), + (0xF9D9, 'M', u'慄'), + (0xF9DA, 'M', u'栗'), + (0xF9DB, 'M', u'率'), + (0xF9DC, 'M', u'隆'), + (0xF9DD, 'M', u'利'), + (0xF9DE, 'M', u'吏'), + (0xF9DF, 'M', u'履'), + (0xF9E0, 'M', u'易'), + (0xF9E1, 'M', u'李'), + (0xF9E2, 'M', u'梨'), + (0xF9E3, 'M', u'泥'), + (0xF9E4, 'M', u'理'), + (0xF9E5, 'M', u'痢'), + (0xF9E6, 'M', u'罹'), + (0xF9E7, 'M', u'裏'), + (0xF9E8, 'M', u'裡'), + (0xF9E9, 'M', u'里'), + (0xF9EA, 'M', u'離'), + (0xF9EB, 'M', u'匿'), + (0xF9EC, 'M', u'溺'), + (0xF9ED, 'M', u'吝'), + (0xF9EE, 'M', u'燐'), + (0xF9EF, 'M', u'璘'), + (0xF9F0, 'M', u'藺'), + (0xF9F1, 'M', u'隣'), + (0xF9F2, 'M', u'鱗'), + (0xF9F3, 'M', u'麟'), + (0xF9F4, 'M', u'林'), + (0xF9F5, 'M', u'淋'), + (0xF9F6, 'M', u'臨'), + (0xF9F7, 'M', u'立'), + (0xF9F8, 'M', u'笠'), + (0xF9F9, 'M', u'粒'), + (0xF9FA, 'M', u'狀'), + (0xF9FB, 'M', u'炙'), + (0xF9FC, 'M', u'識'), + (0xF9FD, 'M', u'什'), + (0xF9FE, 'M', u'茶'), + (0xF9FF, 'M', u'刺'), + (0xFA00, 'M', u'切'), + (0xFA01, 'M', u'度'), + (0xFA02, 'M', u'拓'), + (0xFA03, 'M', u'糖'), + (0xFA04, 'M', u'宅'), + (0xFA05, 'M', u'洞'), + (0xFA06, 'M', u'暴'), + (0xFA07, 'M', u'輻'), + (0xFA08, 'M', u'行'), + (0xFA09, 'M', u'降'), + (0xFA0A, 'M', u'見'), + (0xFA0B, 'M', u'廓'), + (0xFA0C, 'M', u'兀'), + (0xFA0D, 'M', u'嗀'), + (0xFA0E, 'V'), + (0xFA10, 'M', u'塚'), + (0xFA11, 'V'), + (0xFA12, 'M', u'晴'), + (0xFA13, 'V'), + (0xFA15, 'M', u'凞'), + (0xFA16, 'M', u'猪'), + (0xFA17, 'M', u'益'), + (0xFA18, 'M', u'礼'), + (0xFA19, 'M', u'神'), + (0xFA1A, 'M', u'祥'), + (0xFA1B, 'M', u'福'), + (0xFA1C, 'M', u'靖'), + (0xFA1D, 'M', u'精'), + (0xFA1E, 'M', u'羽'), + (0xFA1F, 'V'), + (0xFA20, 'M', u'蘒'), + (0xFA21, 'V'), + (0xFA22, 'M', u'諸'), + (0xFA23, 'V'), + (0xFA25, 'M', u'逸'), + (0xFA26, 'M', u'都'), + (0xFA27, 'V'), + (0xFA2A, 'M', u'飯'), + (0xFA2B, 'M', u'飼'), + (0xFA2C, 'M', u'館'), + (0xFA2D, 'M', u'鶴'), + (0xFA2E, 'M', u'郞'), + (0xFA2F, 'M', u'隷'), + (0xFA30, 'M', u'侮'), + (0xFA31, 'M', u'僧'), + (0xFA32, 'M', u'免'), + (0xFA33, 'M', u'勉'), + (0xFA34, 'M', u'勤'), + (0xFA35, 'M', u'卑'), + (0xFA36, 'M', u'喝'), + (0xFA37, 'M', u'嘆'), + (0xFA38, 'M', u'器'), + (0xFA39, 'M', u'塀'), + (0xFA3A, 'M', u'墨'), + (0xFA3B, 'M', u'層'), + ] + +def _seg_42(): + return [ + (0xFA3C, 'M', u'屮'), + (0xFA3D, 'M', u'悔'), + (0xFA3E, 'M', u'慨'), + (0xFA3F, 'M', u'憎'), + (0xFA40, 'M', u'懲'), + (0xFA41, 'M', u'敏'), + (0xFA42, 'M', u'既'), + (0xFA43, 'M', u'暑'), + (0xFA44, 'M', u'梅'), + (0xFA45, 'M', u'海'), + (0xFA46, 'M', u'渚'), + (0xFA47, 'M', u'漢'), + (0xFA48, 'M', u'煮'), + (0xFA49, 'M', u'爫'), + (0xFA4A, 'M', u'琢'), + (0xFA4B, 'M', u'碑'), + (0xFA4C, 'M', u'社'), + (0xFA4D, 'M', u'祉'), + (0xFA4E, 'M', u'祈'), + (0xFA4F, 'M', u'祐'), + (0xFA50, 'M', u'祖'), + (0xFA51, 'M', u'祝'), + (0xFA52, 'M', u'禍'), + (0xFA53, 'M', u'禎'), + (0xFA54, 'M', u'穀'), + (0xFA55, 'M', u'突'), + (0xFA56, 'M', u'節'), + (0xFA57, 'M', u'練'), + (0xFA58, 'M', u'縉'), + (0xFA59, 'M', u'繁'), + (0xFA5A, 'M', u'署'), + (0xFA5B, 'M', u'者'), + (0xFA5C, 'M', u'臭'), + (0xFA5D, 'M', u'艹'), + (0xFA5F, 'M', u'著'), + (0xFA60, 'M', u'褐'), + (0xFA61, 'M', u'視'), + (0xFA62, 'M', u'謁'), + (0xFA63, 'M', u'謹'), + (0xFA64, 'M', u'賓'), + (0xFA65, 'M', u'贈'), + (0xFA66, 'M', u'辶'), + (0xFA67, 'M', u'逸'), + (0xFA68, 'M', u'難'), + (0xFA69, 'M', u'響'), + (0xFA6A, 'M', u'頻'), + (0xFA6B, 'M', u'恵'), + (0xFA6C, 'M', u'𤋮'), + (0xFA6D, 'M', u'舘'), + (0xFA6E, 'X'), + (0xFA70, 'M', u'並'), + (0xFA71, 'M', u'况'), + (0xFA72, 'M', u'全'), + (0xFA73, 'M', u'侀'), + (0xFA74, 'M', u'充'), + (0xFA75, 'M', u'冀'), + (0xFA76, 'M', u'勇'), + (0xFA77, 'M', u'勺'), + (0xFA78, 'M', u'喝'), + (0xFA79, 'M', u'啕'), + (0xFA7A, 'M', u'喙'), + (0xFA7B, 'M', u'嗢'), + (0xFA7C, 'M', u'塚'), + (0xFA7D, 'M', u'墳'), + (0xFA7E, 'M', u'奄'), + (0xFA7F, 'M', u'奔'), + (0xFA80, 'M', u'婢'), + (0xFA81, 'M', u'嬨'), + (0xFA82, 'M', u'廒'), + (0xFA83, 'M', u'廙'), + (0xFA84, 'M', u'彩'), + (0xFA85, 'M', u'徭'), + (0xFA86, 'M', u'惘'), + (0xFA87, 'M', u'慎'), + (0xFA88, 'M', u'愈'), + (0xFA89, 'M', u'憎'), + (0xFA8A, 'M', u'慠'), + (0xFA8B, 'M', u'懲'), + (0xFA8C, 'M', u'戴'), + (0xFA8D, 'M', u'揄'), + (0xFA8E, 'M', u'搜'), + (0xFA8F, 'M', u'摒'), + (0xFA90, 'M', u'敖'), + (0xFA91, 'M', u'晴'), + (0xFA92, 'M', u'朗'), + (0xFA93, 'M', u'望'), + (0xFA94, 'M', u'杖'), + (0xFA95, 'M', u'歹'), + (0xFA96, 'M', u'殺'), + (0xFA97, 'M', u'流'), + (0xFA98, 'M', u'滛'), + (0xFA99, 'M', u'滋'), + (0xFA9A, 'M', u'漢'), + (0xFA9B, 'M', u'瀞'), + (0xFA9C, 'M', u'煮'), + (0xFA9D, 'M', u'瞧'), + (0xFA9E, 'M', u'爵'), + (0xFA9F, 'M', u'犯'), + (0xFAA0, 'M', u'猪'), + (0xFAA1, 'M', u'瑱'), + ] + +def _seg_43(): + return [ + (0xFAA2, 'M', u'甆'), + (0xFAA3, 'M', u'画'), + (0xFAA4, 'M', u'瘝'), + (0xFAA5, 'M', u'瘟'), + (0xFAA6, 'M', u'益'), + (0xFAA7, 'M', u'盛'), + (0xFAA8, 'M', u'直'), + (0xFAA9, 'M', u'睊'), + (0xFAAA, 'M', u'着'), + (0xFAAB, 'M', u'磌'), + (0xFAAC, 'M', u'窱'), + (0xFAAD, 'M', u'節'), + (0xFAAE, 'M', u'类'), + (0xFAAF, 'M', u'絛'), + (0xFAB0, 'M', u'練'), + (0xFAB1, 'M', u'缾'), + (0xFAB2, 'M', u'者'), + (0xFAB3, 'M', u'荒'), + (0xFAB4, 'M', u'華'), + (0xFAB5, 'M', u'蝹'), + (0xFAB6, 'M', u'襁'), + (0xFAB7, 'M', u'覆'), + (0xFAB8, 'M', u'視'), + (0xFAB9, 'M', u'調'), + (0xFABA, 'M', u'諸'), + (0xFABB, 'M', u'請'), + (0xFABC, 'M', u'謁'), + (0xFABD, 'M', u'諾'), + (0xFABE, 'M', u'諭'), + (0xFABF, 'M', u'謹'), + (0xFAC0, 'M', u'變'), + (0xFAC1, 'M', u'贈'), + (0xFAC2, 'M', u'輸'), + (0xFAC3, 'M', u'遲'), + (0xFAC4, 'M', u'醙'), + (0xFAC5, 'M', u'鉶'), + (0xFAC6, 'M', u'陼'), + (0xFAC7, 'M', u'難'), + (0xFAC8, 'M', u'靖'), + (0xFAC9, 'M', u'韛'), + (0xFACA, 'M', u'響'), + (0xFACB, 'M', u'頋'), + (0xFACC, 'M', u'頻'), + (0xFACD, 'M', u'鬒'), + (0xFACE, 'M', u'龜'), + (0xFACF, 'M', u'𢡊'), + (0xFAD0, 'M', u'𢡄'), + (0xFAD1, 'M', u'𣏕'), + (0xFAD2, 'M', u'㮝'), + (0xFAD3, 'M', u'䀘'), + (0xFAD4, 'M', u'䀹'), + (0xFAD5, 'M', u'𥉉'), + (0xFAD6, 'M', u'𥳐'), + (0xFAD7, 'M', u'𧻓'), + (0xFAD8, 'M', u'齃'), + (0xFAD9, 'M', u'龎'), + (0xFADA, 'X'), + (0xFB00, 'M', u'ff'), + (0xFB01, 'M', u'fi'), + (0xFB02, 'M', u'fl'), + (0xFB03, 'M', u'ffi'), + (0xFB04, 'M', u'ffl'), + (0xFB05, 'M', u'st'), + (0xFB07, 'X'), + (0xFB13, 'M', u'մն'), + (0xFB14, 'M', u'մե'), + (0xFB15, 'M', u'մի'), + (0xFB16, 'M', u'վն'), + (0xFB17, 'M', u'մխ'), + (0xFB18, 'X'), + (0xFB1D, 'M', u'יִ'), + (0xFB1E, 'V'), + (0xFB1F, 'M', u'ײַ'), + (0xFB20, 'M', u'ע'), + (0xFB21, 'M', u'א'), + (0xFB22, 'M', u'ד'), + (0xFB23, 'M', u'ה'), + (0xFB24, 'M', u'כ'), + (0xFB25, 'M', u'ל'), + (0xFB26, 'M', u'ם'), + (0xFB27, 'M', u'ר'), + (0xFB28, 'M', u'ת'), + (0xFB29, '3', u'+'), + (0xFB2A, 'M', u'שׁ'), + (0xFB2B, 'M', u'שׂ'), + (0xFB2C, 'M', u'שּׁ'), + (0xFB2D, 'M', u'שּׂ'), + (0xFB2E, 'M', u'אַ'), + (0xFB2F, 'M', u'אָ'), + (0xFB30, 'M', u'אּ'), + (0xFB31, 'M', u'בּ'), + (0xFB32, 'M', u'גּ'), + (0xFB33, 'M', u'דּ'), + (0xFB34, 'M', u'הּ'), + (0xFB35, 'M', u'וּ'), + (0xFB36, 'M', u'זּ'), + (0xFB37, 'X'), + (0xFB38, 'M', u'טּ'), + (0xFB39, 'M', u'יּ'), + (0xFB3A, 'M', u'ךּ'), + ] + +def _seg_44(): + return [ + (0xFB3B, 'M', u'כּ'), + (0xFB3C, 'M', u'לּ'), + (0xFB3D, 'X'), + (0xFB3E, 'M', u'מּ'), + (0xFB3F, 'X'), + (0xFB40, 'M', u'נּ'), + (0xFB41, 'M', u'סּ'), + (0xFB42, 'X'), + (0xFB43, 'M', u'ףּ'), + (0xFB44, 'M', u'פּ'), + (0xFB45, 'X'), + (0xFB46, 'M', u'צּ'), + (0xFB47, 'M', u'קּ'), + (0xFB48, 'M', u'רּ'), + (0xFB49, 'M', u'שּ'), + (0xFB4A, 'M', u'תּ'), + (0xFB4B, 'M', u'וֹ'), + (0xFB4C, 'M', u'בֿ'), + (0xFB4D, 'M', u'כֿ'), + (0xFB4E, 'M', u'פֿ'), + (0xFB4F, 'M', u'אל'), + (0xFB50, 'M', u'ٱ'), + (0xFB52, 'M', u'ٻ'), + (0xFB56, 'M', u'پ'), + (0xFB5A, 'M', u'ڀ'), + (0xFB5E, 'M', u'ٺ'), + (0xFB62, 'M', u'ٿ'), + (0xFB66, 'M', u'ٹ'), + (0xFB6A, 'M', u'ڤ'), + (0xFB6E, 'M', u'ڦ'), + (0xFB72, 'M', u'ڄ'), + (0xFB76, 'M', u'ڃ'), + (0xFB7A, 'M', u'چ'), + (0xFB7E, 'M', u'ڇ'), + (0xFB82, 'M', u'ڍ'), + (0xFB84, 'M', u'ڌ'), + (0xFB86, 'M', u'ڎ'), + (0xFB88, 'M', u'ڈ'), + (0xFB8A, 'M', u'ژ'), + (0xFB8C, 'M', u'ڑ'), + (0xFB8E, 'M', u'ک'), + (0xFB92, 'M', u'گ'), + (0xFB96, 'M', u'ڳ'), + (0xFB9A, 'M', u'ڱ'), + (0xFB9E, 'M', u'ں'), + (0xFBA0, 'M', u'ڻ'), + (0xFBA4, 'M', u'ۀ'), + (0xFBA6, 'M', u'ہ'), + (0xFBAA, 'M', u'ھ'), + (0xFBAE, 'M', u'ے'), + (0xFBB0, 'M', u'ۓ'), + (0xFBB2, 'V'), + (0xFBC2, 'X'), + (0xFBD3, 'M', u'ڭ'), + (0xFBD7, 'M', u'ۇ'), + (0xFBD9, 'M', u'ۆ'), + (0xFBDB, 'M', u'ۈ'), + (0xFBDD, 'M', u'ۇٴ'), + (0xFBDE, 'M', u'ۋ'), + (0xFBE0, 'M', u'ۅ'), + (0xFBE2, 'M', u'ۉ'), + (0xFBE4, 'M', u'ې'), + (0xFBE8, 'M', u'ى'), + (0xFBEA, 'M', u'ئا'), + (0xFBEC, 'M', u'ئە'), + (0xFBEE, 'M', u'ئو'), + (0xFBF0, 'M', u'ئۇ'), + (0xFBF2, 'M', u'ئۆ'), + (0xFBF4, 'M', u'ئۈ'), + (0xFBF6, 'M', u'ئې'), + (0xFBF9, 'M', u'ئى'), + (0xFBFC, 'M', u'ی'), + (0xFC00, 'M', u'ئج'), + (0xFC01, 'M', u'ئح'), + (0xFC02, 'M', u'ئم'), + (0xFC03, 'M', u'ئى'), + (0xFC04, 'M', u'ئي'), + (0xFC05, 'M', u'بج'), + (0xFC06, 'M', u'بح'), + (0xFC07, 'M', u'بخ'), + (0xFC08, 'M', u'بم'), + (0xFC09, 'M', u'بى'), + (0xFC0A, 'M', u'بي'), + (0xFC0B, 'M', u'تج'), + (0xFC0C, 'M', u'تح'), + (0xFC0D, 'M', u'تخ'), + (0xFC0E, 'M', u'تم'), + (0xFC0F, 'M', u'تى'), + (0xFC10, 'M', u'تي'), + (0xFC11, 'M', u'ثج'), + (0xFC12, 'M', u'ثم'), + (0xFC13, 'M', u'ثى'), + (0xFC14, 'M', u'ثي'), + (0xFC15, 'M', u'جح'), + (0xFC16, 'M', u'جم'), + (0xFC17, 'M', u'حج'), + (0xFC18, 'M', u'حم'), + (0xFC19, 'M', u'خج'), + (0xFC1A, 'M', u'خح'), + (0xFC1B, 'M', u'خم'), + ] + +def _seg_45(): + return [ + (0xFC1C, 'M', u'سج'), + (0xFC1D, 'M', u'سح'), + (0xFC1E, 'M', u'سخ'), + (0xFC1F, 'M', u'سم'), + (0xFC20, 'M', u'صح'), + (0xFC21, 'M', u'صم'), + (0xFC22, 'M', u'ضج'), + (0xFC23, 'M', u'ضح'), + (0xFC24, 'M', u'ضخ'), + (0xFC25, 'M', u'ضم'), + (0xFC26, 'M', u'طح'), + (0xFC27, 'M', u'طم'), + (0xFC28, 'M', u'ظم'), + (0xFC29, 'M', u'عج'), + (0xFC2A, 'M', u'عم'), + (0xFC2B, 'M', u'غج'), + (0xFC2C, 'M', u'غم'), + (0xFC2D, 'M', u'فج'), + (0xFC2E, 'M', u'فح'), + (0xFC2F, 'M', u'فخ'), + (0xFC30, 'M', u'فم'), + (0xFC31, 'M', u'فى'), + (0xFC32, 'M', u'في'), + (0xFC33, 'M', u'قح'), + (0xFC34, 'M', u'قم'), + (0xFC35, 'M', u'قى'), + (0xFC36, 'M', u'قي'), + (0xFC37, 'M', u'كا'), + (0xFC38, 'M', u'كج'), + (0xFC39, 'M', u'كح'), + (0xFC3A, 'M', u'كخ'), + (0xFC3B, 'M', u'كل'), + (0xFC3C, 'M', u'كم'), + (0xFC3D, 'M', u'كى'), + (0xFC3E, 'M', u'كي'), + (0xFC3F, 'M', u'لج'), + (0xFC40, 'M', u'لح'), + (0xFC41, 'M', u'لخ'), + (0xFC42, 'M', u'لم'), + (0xFC43, 'M', u'لى'), + (0xFC44, 'M', u'لي'), + (0xFC45, 'M', u'مج'), + (0xFC46, 'M', u'مح'), + (0xFC47, 'M', u'مخ'), + (0xFC48, 'M', u'مم'), + (0xFC49, 'M', u'مى'), + (0xFC4A, 'M', u'مي'), + (0xFC4B, 'M', u'نج'), + (0xFC4C, 'M', u'نح'), + (0xFC4D, 'M', u'نخ'), + (0xFC4E, 'M', u'نم'), + (0xFC4F, 'M', u'نى'), + (0xFC50, 'M', u'ني'), + (0xFC51, 'M', u'هج'), + (0xFC52, 'M', u'هم'), + (0xFC53, 'M', u'هى'), + (0xFC54, 'M', u'هي'), + (0xFC55, 'M', u'يج'), + (0xFC56, 'M', u'يح'), + (0xFC57, 'M', u'يخ'), + (0xFC58, 'M', u'يم'), + (0xFC59, 'M', u'يى'), + (0xFC5A, 'M', u'يي'), + (0xFC5B, 'M', u'ذٰ'), + (0xFC5C, 'M', u'رٰ'), + (0xFC5D, 'M', u'ىٰ'), + (0xFC5E, '3', u' ٌّ'), + (0xFC5F, '3', u' ٍّ'), + (0xFC60, '3', u' َّ'), + (0xFC61, '3', u' ُّ'), + (0xFC62, '3', u' ِّ'), + (0xFC63, '3', u' ّٰ'), + (0xFC64, 'M', u'ئر'), + (0xFC65, 'M', u'ئز'), + (0xFC66, 'M', u'ئم'), + (0xFC67, 'M', u'ئن'), + (0xFC68, 'M', u'ئى'), + (0xFC69, 'M', u'ئي'), + (0xFC6A, 'M', u'بر'), + (0xFC6B, 'M', u'بز'), + (0xFC6C, 'M', u'بم'), + (0xFC6D, 'M', u'بن'), + (0xFC6E, 'M', u'بى'), + (0xFC6F, 'M', u'بي'), + (0xFC70, 'M', u'تر'), + (0xFC71, 'M', u'تز'), + (0xFC72, 'M', u'تم'), + (0xFC73, 'M', u'تن'), + (0xFC74, 'M', u'تى'), + (0xFC75, 'M', u'تي'), + (0xFC76, 'M', u'ثر'), + (0xFC77, 'M', u'ثز'), + (0xFC78, 'M', u'ثم'), + (0xFC79, 'M', u'ثن'), + (0xFC7A, 'M', u'ثى'), + (0xFC7B, 'M', u'ثي'), + (0xFC7C, 'M', u'فى'), + (0xFC7D, 'M', u'في'), + (0xFC7E, 'M', u'قى'), + (0xFC7F, 'M', u'قي'), + ] + +def _seg_46(): + return [ + (0xFC80, 'M', u'كا'), + (0xFC81, 'M', u'كل'), + (0xFC82, 'M', u'كم'), + (0xFC83, 'M', u'كى'), + (0xFC84, 'M', u'كي'), + (0xFC85, 'M', u'لم'), + (0xFC86, 'M', u'لى'), + (0xFC87, 'M', u'لي'), + (0xFC88, 'M', u'ما'), + (0xFC89, 'M', u'مم'), + (0xFC8A, 'M', u'نر'), + (0xFC8B, 'M', u'نز'), + (0xFC8C, 'M', u'نم'), + (0xFC8D, 'M', u'نن'), + (0xFC8E, 'M', u'نى'), + (0xFC8F, 'M', u'ني'), + (0xFC90, 'M', u'ىٰ'), + (0xFC91, 'M', u'ير'), + (0xFC92, 'M', u'يز'), + (0xFC93, 'M', u'يم'), + (0xFC94, 'M', u'ين'), + (0xFC95, 'M', u'يى'), + (0xFC96, 'M', u'يي'), + (0xFC97, 'M', u'ئج'), + (0xFC98, 'M', u'ئح'), + (0xFC99, 'M', u'ئخ'), + (0xFC9A, 'M', u'ئم'), + (0xFC9B, 'M', u'ئه'), + (0xFC9C, 'M', u'بج'), + (0xFC9D, 'M', u'بح'), + (0xFC9E, 'M', u'بخ'), + (0xFC9F, 'M', u'بم'), + (0xFCA0, 'M', u'به'), + (0xFCA1, 'M', u'تج'), + (0xFCA2, 'M', u'تح'), + (0xFCA3, 'M', u'تخ'), + (0xFCA4, 'M', u'تم'), + (0xFCA5, 'M', u'ته'), + (0xFCA6, 'M', u'ثم'), + (0xFCA7, 'M', u'جح'), + (0xFCA8, 'M', u'جم'), + (0xFCA9, 'M', u'حج'), + (0xFCAA, 'M', u'حم'), + (0xFCAB, 'M', u'خج'), + (0xFCAC, 'M', u'خم'), + (0xFCAD, 'M', u'سج'), + (0xFCAE, 'M', u'سح'), + (0xFCAF, 'M', u'سخ'), + (0xFCB0, 'M', u'سم'), + (0xFCB1, 'M', u'صح'), + (0xFCB2, 'M', u'صخ'), + (0xFCB3, 'M', u'صم'), + (0xFCB4, 'M', u'ضج'), + (0xFCB5, 'M', u'ضح'), + (0xFCB6, 'M', u'ضخ'), + (0xFCB7, 'M', u'ضم'), + (0xFCB8, 'M', u'طح'), + (0xFCB9, 'M', u'ظم'), + (0xFCBA, 'M', u'عج'), + (0xFCBB, 'M', u'عم'), + (0xFCBC, 'M', u'غج'), + (0xFCBD, 'M', u'غم'), + (0xFCBE, 'M', u'فج'), + (0xFCBF, 'M', u'فح'), + (0xFCC0, 'M', u'فخ'), + (0xFCC1, 'M', u'فم'), + (0xFCC2, 'M', u'قح'), + (0xFCC3, 'M', u'قم'), + (0xFCC4, 'M', u'كج'), + (0xFCC5, 'M', u'كح'), + (0xFCC6, 'M', u'كخ'), + (0xFCC7, 'M', u'كل'), + (0xFCC8, 'M', u'كم'), + (0xFCC9, 'M', u'لج'), + (0xFCCA, 'M', u'لح'), + (0xFCCB, 'M', u'لخ'), + (0xFCCC, 'M', u'لم'), + (0xFCCD, 'M', u'له'), + (0xFCCE, 'M', u'مج'), + (0xFCCF, 'M', u'مح'), + (0xFCD0, 'M', u'مخ'), + (0xFCD1, 'M', u'مم'), + (0xFCD2, 'M', u'نج'), + (0xFCD3, 'M', u'نح'), + (0xFCD4, 'M', u'نخ'), + (0xFCD5, 'M', u'نم'), + (0xFCD6, 'M', u'نه'), + (0xFCD7, 'M', u'هج'), + (0xFCD8, 'M', u'هم'), + (0xFCD9, 'M', u'هٰ'), + (0xFCDA, 'M', u'يج'), + (0xFCDB, 'M', u'يح'), + (0xFCDC, 'M', u'يخ'), + (0xFCDD, 'M', u'يم'), + (0xFCDE, 'M', u'يه'), + (0xFCDF, 'M', u'ئم'), + (0xFCE0, 'M', u'ئه'), + (0xFCE1, 'M', u'بم'), + (0xFCE2, 'M', u'به'), + (0xFCE3, 'M', u'تم'), + ] + +def _seg_47(): + return [ + (0xFCE4, 'M', u'ته'), + (0xFCE5, 'M', u'ثم'), + (0xFCE6, 'M', u'ثه'), + (0xFCE7, 'M', u'سم'), + (0xFCE8, 'M', u'سه'), + (0xFCE9, 'M', u'شم'), + (0xFCEA, 'M', u'شه'), + (0xFCEB, 'M', u'كل'), + (0xFCEC, 'M', u'كم'), + (0xFCED, 'M', u'لم'), + (0xFCEE, 'M', u'نم'), + (0xFCEF, 'M', u'نه'), + (0xFCF0, 'M', u'يم'), + (0xFCF1, 'M', u'يه'), + (0xFCF2, 'M', u'ـَّ'), + (0xFCF3, 'M', u'ـُّ'), + (0xFCF4, 'M', u'ـِّ'), + (0xFCF5, 'M', u'طى'), + (0xFCF6, 'M', u'طي'), + (0xFCF7, 'M', u'عى'), + (0xFCF8, 'M', u'عي'), + (0xFCF9, 'M', u'غى'), + (0xFCFA, 'M', u'غي'), + (0xFCFB, 'M', u'سى'), + (0xFCFC, 'M', u'سي'), + (0xFCFD, 'M', u'شى'), + (0xFCFE, 'M', u'شي'), + (0xFCFF, 'M', u'حى'), + (0xFD00, 'M', u'حي'), + (0xFD01, 'M', u'جى'), + (0xFD02, 'M', u'جي'), + (0xFD03, 'M', u'خى'), + (0xFD04, 'M', u'خي'), + (0xFD05, 'M', u'صى'), + (0xFD06, 'M', u'صي'), + (0xFD07, 'M', u'ضى'), + (0xFD08, 'M', u'ضي'), + (0xFD09, 'M', u'شج'), + (0xFD0A, 'M', u'شح'), + (0xFD0B, 'M', u'شخ'), + (0xFD0C, 'M', u'شم'), + (0xFD0D, 'M', u'شر'), + (0xFD0E, 'M', u'سر'), + (0xFD0F, 'M', u'صر'), + (0xFD10, 'M', u'ضر'), + (0xFD11, 'M', u'طى'), + (0xFD12, 'M', u'طي'), + (0xFD13, 'M', u'عى'), + (0xFD14, 'M', u'عي'), + (0xFD15, 'M', u'غى'), + (0xFD16, 'M', u'غي'), + (0xFD17, 'M', u'سى'), + (0xFD18, 'M', u'سي'), + (0xFD19, 'M', u'شى'), + (0xFD1A, 'M', u'شي'), + (0xFD1B, 'M', u'حى'), + (0xFD1C, 'M', u'حي'), + (0xFD1D, 'M', u'جى'), + (0xFD1E, 'M', u'جي'), + (0xFD1F, 'M', u'خى'), + (0xFD20, 'M', u'خي'), + (0xFD21, 'M', u'صى'), + (0xFD22, 'M', u'صي'), + (0xFD23, 'M', u'ضى'), + (0xFD24, 'M', u'ضي'), + (0xFD25, 'M', u'شج'), + (0xFD26, 'M', u'شح'), + (0xFD27, 'M', u'شخ'), + (0xFD28, 'M', u'شم'), + (0xFD29, 'M', u'شر'), + (0xFD2A, 'M', u'سر'), + (0xFD2B, 'M', u'صر'), + (0xFD2C, 'M', u'ضر'), + (0xFD2D, 'M', u'شج'), + (0xFD2E, 'M', u'شح'), + (0xFD2F, 'M', u'شخ'), + (0xFD30, 'M', u'شم'), + (0xFD31, 'M', u'سه'), + (0xFD32, 'M', u'شه'), + (0xFD33, 'M', u'طم'), + (0xFD34, 'M', u'سج'), + (0xFD35, 'M', u'سح'), + (0xFD36, 'M', u'سخ'), + (0xFD37, 'M', u'شج'), + (0xFD38, 'M', u'شح'), + (0xFD39, 'M', u'شخ'), + (0xFD3A, 'M', u'طم'), + (0xFD3B, 'M', u'ظم'), + (0xFD3C, 'M', u'اً'), + (0xFD3E, 'V'), + (0xFD40, 'X'), + (0xFD50, 'M', u'تجم'), + (0xFD51, 'M', u'تحج'), + (0xFD53, 'M', u'تحم'), + (0xFD54, 'M', u'تخم'), + (0xFD55, 'M', u'تمج'), + (0xFD56, 'M', u'تمح'), + (0xFD57, 'M', u'تمخ'), + (0xFD58, 'M', u'جمح'), + (0xFD5A, 'M', u'حمي'), + ] + +def _seg_48(): + return [ + (0xFD5B, 'M', u'حمى'), + (0xFD5C, 'M', u'سحج'), + (0xFD5D, 'M', u'سجح'), + (0xFD5E, 'M', u'سجى'), + (0xFD5F, 'M', u'سمح'), + (0xFD61, 'M', u'سمج'), + (0xFD62, 'M', u'سمم'), + (0xFD64, 'M', u'صحح'), + (0xFD66, 'M', u'صمم'), + (0xFD67, 'M', u'شحم'), + (0xFD69, 'M', u'شجي'), + (0xFD6A, 'M', u'شمخ'), + (0xFD6C, 'M', u'شمم'), + (0xFD6E, 'M', u'ضحى'), + (0xFD6F, 'M', u'ضخم'), + (0xFD71, 'M', u'طمح'), + (0xFD73, 'M', u'طمم'), + (0xFD74, 'M', u'طمي'), + (0xFD75, 'M', u'عجم'), + (0xFD76, 'M', u'عمم'), + (0xFD78, 'M', u'عمى'), + (0xFD79, 'M', u'غمم'), + (0xFD7A, 'M', u'غمي'), + (0xFD7B, 'M', u'غمى'), + (0xFD7C, 'M', u'فخم'), + (0xFD7E, 'M', u'قمح'), + (0xFD7F, 'M', u'قمم'), + (0xFD80, 'M', u'لحم'), + (0xFD81, 'M', u'لحي'), + (0xFD82, 'M', u'لحى'), + (0xFD83, 'M', u'لجج'), + (0xFD85, 'M', u'لخم'), + (0xFD87, 'M', u'لمح'), + (0xFD89, 'M', u'محج'), + (0xFD8A, 'M', u'محم'), + (0xFD8B, 'M', u'محي'), + (0xFD8C, 'M', u'مجح'), + (0xFD8D, 'M', u'مجم'), + (0xFD8E, 'M', u'مخج'), + (0xFD8F, 'M', u'مخم'), + (0xFD90, 'X'), + (0xFD92, 'M', u'مجخ'), + (0xFD93, 'M', u'همج'), + (0xFD94, 'M', u'همم'), + (0xFD95, 'M', u'نحم'), + (0xFD96, 'M', u'نحى'), + (0xFD97, 'M', u'نجم'), + (0xFD99, 'M', u'نجى'), + (0xFD9A, 'M', u'نمي'), + (0xFD9B, 'M', u'نمى'), + (0xFD9C, 'M', u'يمم'), + (0xFD9E, 'M', u'بخي'), + (0xFD9F, 'M', u'تجي'), + (0xFDA0, 'M', u'تجى'), + (0xFDA1, 'M', u'تخي'), + (0xFDA2, 'M', u'تخى'), + (0xFDA3, 'M', u'تمي'), + (0xFDA4, 'M', u'تمى'), + (0xFDA5, 'M', u'جمي'), + (0xFDA6, 'M', u'جحى'), + (0xFDA7, 'M', u'جمى'), + (0xFDA8, 'M', u'سخى'), + (0xFDA9, 'M', u'صحي'), + (0xFDAA, 'M', u'شحي'), + (0xFDAB, 'M', u'ضحي'), + (0xFDAC, 'M', u'لجي'), + (0xFDAD, 'M', u'لمي'), + (0xFDAE, 'M', u'يحي'), + (0xFDAF, 'M', u'يجي'), + (0xFDB0, 'M', u'يمي'), + (0xFDB1, 'M', u'ممي'), + (0xFDB2, 'M', u'قمي'), + (0xFDB3, 'M', u'نحي'), + (0xFDB4, 'M', u'قمح'), + (0xFDB5, 'M', u'لحم'), + (0xFDB6, 'M', u'عمي'), + (0xFDB7, 'M', u'كمي'), + (0xFDB8, 'M', u'نجح'), + (0xFDB9, 'M', u'مخي'), + (0xFDBA, 'M', u'لجم'), + (0xFDBB, 'M', u'كمم'), + (0xFDBC, 'M', u'لجم'), + (0xFDBD, 'M', u'نجح'), + (0xFDBE, 'M', u'جحي'), + (0xFDBF, 'M', u'حجي'), + (0xFDC0, 'M', u'مجي'), + (0xFDC1, 'M', u'فمي'), + (0xFDC2, 'M', u'بحي'), + (0xFDC3, 'M', u'كمم'), + (0xFDC4, 'M', u'عجم'), + (0xFDC5, 'M', u'صمم'), + (0xFDC6, 'M', u'سخي'), + (0xFDC7, 'M', u'نجي'), + (0xFDC8, 'X'), + (0xFDF0, 'M', u'صلے'), + (0xFDF1, 'M', u'قلے'), + (0xFDF2, 'M', u'الله'), + (0xFDF3, 'M', u'اكبر'), + (0xFDF4, 'M', u'محمد'), + (0xFDF5, 'M', u'صلعم'), + ] + +def _seg_49(): + return [ + (0xFDF6, 'M', u'رسول'), + (0xFDF7, 'M', u'عليه'), + (0xFDF8, 'M', u'وسلم'), + (0xFDF9, 'M', u'صلى'), + (0xFDFA, '3', u'صلى الله عليه وسلم'), + (0xFDFB, '3', u'جل جلاله'), + (0xFDFC, 'M', u'ریال'), + (0xFDFD, 'V'), + (0xFDFE, 'X'), + (0xFE00, 'I'), + (0xFE10, '3', u','), + (0xFE11, 'M', u'、'), + (0xFE12, 'X'), + (0xFE13, '3', u':'), + (0xFE14, '3', u';'), + (0xFE15, '3', u'!'), + (0xFE16, '3', u'?'), + (0xFE17, 'M', u'〖'), + (0xFE18, 'M', u'〗'), + (0xFE19, 'X'), + (0xFE20, 'V'), + (0xFE30, 'X'), + (0xFE31, 'M', u'—'), + (0xFE32, 'M', u'–'), + (0xFE33, '3', u'_'), + (0xFE35, '3', u'('), + (0xFE36, '3', u')'), + (0xFE37, '3', u'{'), + (0xFE38, '3', u'}'), + (0xFE39, 'M', u'〔'), + (0xFE3A, 'M', u'〕'), + (0xFE3B, 'M', u'【'), + (0xFE3C, 'M', u'】'), + (0xFE3D, 'M', u'《'), + (0xFE3E, 'M', u'》'), + (0xFE3F, 'M', u'〈'), + (0xFE40, 'M', u'〉'), + (0xFE41, 'M', u'「'), + (0xFE42, 'M', u'」'), + (0xFE43, 'M', u'『'), + (0xFE44, 'M', u'』'), + (0xFE45, 'V'), + (0xFE47, '3', u'['), + (0xFE48, '3', u']'), + (0xFE49, '3', u' ̅'), + (0xFE4D, '3', u'_'), + (0xFE50, '3', u','), + (0xFE51, 'M', u'、'), + (0xFE52, 'X'), + (0xFE54, '3', u';'), + (0xFE55, '3', u':'), + (0xFE56, '3', u'?'), + (0xFE57, '3', u'!'), + (0xFE58, 'M', u'—'), + (0xFE59, '3', u'('), + (0xFE5A, '3', u')'), + (0xFE5B, '3', u'{'), + (0xFE5C, '3', u'}'), + (0xFE5D, 'M', u'〔'), + (0xFE5E, 'M', u'〕'), + (0xFE5F, '3', u'#'), + (0xFE60, '3', u'&'), + (0xFE61, '3', u'*'), + (0xFE62, '3', u'+'), + (0xFE63, 'M', u'-'), + (0xFE64, '3', u'<'), + (0xFE65, '3', u'>'), + (0xFE66, '3', u'='), + (0xFE67, 'X'), + (0xFE68, '3', u'\\'), + (0xFE69, '3', u'$'), + (0xFE6A, '3', u'%'), + (0xFE6B, '3', u'@'), + (0xFE6C, 'X'), + (0xFE70, '3', u' ً'), + (0xFE71, 'M', u'ـً'), + (0xFE72, '3', u' ٌ'), + (0xFE73, 'V'), + (0xFE74, '3', u' ٍ'), + (0xFE75, 'X'), + (0xFE76, '3', u' َ'), + (0xFE77, 'M', u'ـَ'), + (0xFE78, '3', u' ُ'), + (0xFE79, 'M', u'ـُ'), + (0xFE7A, '3', u' ِ'), + (0xFE7B, 'M', u'ـِ'), + (0xFE7C, '3', u' ّ'), + (0xFE7D, 'M', u'ـّ'), + (0xFE7E, '3', u' ْ'), + (0xFE7F, 'M', u'ـْ'), + (0xFE80, 'M', u'ء'), + (0xFE81, 'M', u'آ'), + (0xFE83, 'M', u'أ'), + (0xFE85, 'M', u'ؤ'), + (0xFE87, 'M', u'إ'), + (0xFE89, 'M', u'ئ'), + (0xFE8D, 'M', u'ا'), + (0xFE8F, 'M', u'ب'), + (0xFE93, 'M', u'ة'), + (0xFE95, 'M', u'ت'), + ] + +def _seg_50(): + return [ + (0xFE99, 'M', u'ث'), + (0xFE9D, 'M', u'ج'), + (0xFEA1, 'M', u'ح'), + (0xFEA5, 'M', u'خ'), + (0xFEA9, 'M', u'د'), + (0xFEAB, 'M', u'ذ'), + (0xFEAD, 'M', u'ر'), + (0xFEAF, 'M', u'ز'), + (0xFEB1, 'M', u'س'), + (0xFEB5, 'M', u'ش'), + (0xFEB9, 'M', u'ص'), + (0xFEBD, 'M', u'ض'), + (0xFEC1, 'M', u'ط'), + (0xFEC5, 'M', u'ظ'), + (0xFEC9, 'M', u'ع'), + (0xFECD, 'M', u'غ'), + (0xFED1, 'M', u'ف'), + (0xFED5, 'M', u'ق'), + (0xFED9, 'M', u'ك'), + (0xFEDD, 'M', u'ل'), + (0xFEE1, 'M', u'م'), + (0xFEE5, 'M', u'ن'), + (0xFEE9, 'M', u'ه'), + (0xFEED, 'M', u'و'), + (0xFEEF, 'M', u'ى'), + (0xFEF1, 'M', u'ي'), + (0xFEF5, 'M', u'لآ'), + (0xFEF7, 'M', u'لأ'), + (0xFEF9, 'M', u'لإ'), + (0xFEFB, 'M', u'لا'), + (0xFEFD, 'X'), + (0xFEFF, 'I'), + (0xFF00, 'X'), + (0xFF01, '3', u'!'), + (0xFF02, '3', u'"'), + (0xFF03, '3', u'#'), + (0xFF04, '3', u'$'), + (0xFF05, '3', u'%'), + (0xFF06, '3', u'&'), + (0xFF07, '3', u'\''), + (0xFF08, '3', u'('), + (0xFF09, '3', u')'), + (0xFF0A, '3', u'*'), + (0xFF0B, '3', u'+'), + (0xFF0C, '3', u','), + (0xFF0D, 'M', u'-'), + (0xFF0E, 'M', u'.'), + (0xFF0F, '3', u'/'), + (0xFF10, 'M', u'0'), + (0xFF11, 'M', u'1'), + (0xFF12, 'M', u'2'), + (0xFF13, 'M', u'3'), + (0xFF14, 'M', u'4'), + (0xFF15, 'M', u'5'), + (0xFF16, 'M', u'6'), + (0xFF17, 'M', u'7'), + (0xFF18, 'M', u'8'), + (0xFF19, 'M', u'9'), + (0xFF1A, '3', u':'), + (0xFF1B, '3', u';'), + (0xFF1C, '3', u'<'), + (0xFF1D, '3', u'='), + (0xFF1E, '3', u'>'), + (0xFF1F, '3', u'?'), + (0xFF20, '3', u'@'), + (0xFF21, 'M', u'a'), + (0xFF22, 'M', u'b'), + (0xFF23, 'M', u'c'), + (0xFF24, 'M', u'd'), + (0xFF25, 'M', u'e'), + (0xFF26, 'M', u'f'), + (0xFF27, 'M', u'g'), + (0xFF28, 'M', u'h'), + (0xFF29, 'M', u'i'), + (0xFF2A, 'M', u'j'), + (0xFF2B, 'M', u'k'), + (0xFF2C, 'M', u'l'), + (0xFF2D, 'M', u'm'), + (0xFF2E, 'M', u'n'), + (0xFF2F, 'M', u'o'), + (0xFF30, 'M', u'p'), + (0xFF31, 'M', u'q'), + (0xFF32, 'M', u'r'), + (0xFF33, 'M', u's'), + (0xFF34, 'M', u't'), + (0xFF35, 'M', u'u'), + (0xFF36, 'M', u'v'), + (0xFF37, 'M', u'w'), + (0xFF38, 'M', u'x'), + (0xFF39, 'M', u'y'), + (0xFF3A, 'M', u'z'), + (0xFF3B, '3', u'['), + (0xFF3C, '3', u'\\'), + (0xFF3D, '3', u']'), + (0xFF3E, '3', u'^'), + (0xFF3F, '3', u'_'), + (0xFF40, '3', u'`'), + (0xFF41, 'M', u'a'), + (0xFF42, 'M', u'b'), + (0xFF43, 'M', u'c'), + ] + +def _seg_51(): + return [ + (0xFF44, 'M', u'd'), + (0xFF45, 'M', u'e'), + (0xFF46, 'M', u'f'), + (0xFF47, 'M', u'g'), + (0xFF48, 'M', u'h'), + (0xFF49, 'M', u'i'), + (0xFF4A, 'M', u'j'), + (0xFF4B, 'M', u'k'), + (0xFF4C, 'M', u'l'), + (0xFF4D, 'M', u'm'), + (0xFF4E, 'M', u'n'), + (0xFF4F, 'M', u'o'), + (0xFF50, 'M', u'p'), + (0xFF51, 'M', u'q'), + (0xFF52, 'M', u'r'), + (0xFF53, 'M', u's'), + (0xFF54, 'M', u't'), + (0xFF55, 'M', u'u'), + (0xFF56, 'M', u'v'), + (0xFF57, 'M', u'w'), + (0xFF58, 'M', u'x'), + (0xFF59, 'M', u'y'), + (0xFF5A, 'M', u'z'), + (0xFF5B, '3', u'{'), + (0xFF5C, '3', u'|'), + (0xFF5D, '3', u'}'), + (0xFF5E, '3', u'~'), + (0xFF5F, 'M', u'⦅'), + (0xFF60, 'M', u'⦆'), + (0xFF61, 'M', u'.'), + (0xFF62, 'M', u'「'), + (0xFF63, 'M', u'」'), + (0xFF64, 'M', u'、'), + (0xFF65, 'M', u'・'), + (0xFF66, 'M', u'ヲ'), + (0xFF67, 'M', u'ァ'), + (0xFF68, 'M', u'ィ'), + (0xFF69, 'M', u'ゥ'), + (0xFF6A, 'M', u'ェ'), + (0xFF6B, 'M', u'ォ'), + (0xFF6C, 'M', u'ャ'), + (0xFF6D, 'M', u'ュ'), + (0xFF6E, 'M', u'ョ'), + (0xFF6F, 'M', u'ッ'), + (0xFF70, 'M', u'ー'), + (0xFF71, 'M', u'ア'), + (0xFF72, 'M', u'イ'), + (0xFF73, 'M', u'ウ'), + (0xFF74, 'M', u'エ'), + (0xFF75, 'M', u'オ'), + (0xFF76, 'M', u'カ'), + (0xFF77, 'M', u'キ'), + (0xFF78, 'M', u'ク'), + (0xFF79, 'M', u'ケ'), + (0xFF7A, 'M', u'コ'), + (0xFF7B, 'M', u'サ'), + (0xFF7C, 'M', u'シ'), + (0xFF7D, 'M', u'ス'), + (0xFF7E, 'M', u'セ'), + (0xFF7F, 'M', u'ソ'), + (0xFF80, 'M', u'タ'), + (0xFF81, 'M', u'チ'), + (0xFF82, 'M', u'ツ'), + (0xFF83, 'M', u'テ'), + (0xFF84, 'M', u'ト'), + (0xFF85, 'M', u'ナ'), + (0xFF86, 'M', u'ニ'), + (0xFF87, 'M', u'ヌ'), + (0xFF88, 'M', u'ネ'), + (0xFF89, 'M', u'ノ'), + (0xFF8A, 'M', u'ハ'), + (0xFF8B, 'M', u'ヒ'), + (0xFF8C, 'M', u'フ'), + (0xFF8D, 'M', u'ヘ'), + (0xFF8E, 'M', u'ホ'), + (0xFF8F, 'M', u'マ'), + (0xFF90, 'M', u'ミ'), + (0xFF91, 'M', u'ム'), + (0xFF92, 'M', u'メ'), + (0xFF93, 'M', u'モ'), + (0xFF94, 'M', u'ヤ'), + (0xFF95, 'M', u'ユ'), + (0xFF96, 'M', u'ヨ'), + (0xFF97, 'M', u'ラ'), + (0xFF98, 'M', u'リ'), + (0xFF99, 'M', u'ル'), + (0xFF9A, 'M', u'レ'), + (0xFF9B, 'M', u'ロ'), + (0xFF9C, 'M', u'ワ'), + (0xFF9D, 'M', u'ン'), + (0xFF9E, 'M', u'゙'), + (0xFF9F, 'M', u'゚'), + (0xFFA0, 'X'), + (0xFFA1, 'M', u'ᄀ'), + (0xFFA2, 'M', u'ᄁ'), + (0xFFA3, 'M', u'ᆪ'), + (0xFFA4, 'M', u'ᄂ'), + (0xFFA5, 'M', u'ᆬ'), + (0xFFA6, 'M', u'ᆭ'), + (0xFFA7, 'M', u'ᄃ'), + ] + +def _seg_52(): + return [ + (0xFFA8, 'M', u'ᄄ'), + (0xFFA9, 'M', u'ᄅ'), + (0xFFAA, 'M', u'ᆰ'), + (0xFFAB, 'M', u'ᆱ'), + (0xFFAC, 'M', u'ᆲ'), + (0xFFAD, 'M', u'ᆳ'), + (0xFFAE, 'M', u'ᆴ'), + (0xFFAF, 'M', u'ᆵ'), + (0xFFB0, 'M', u'ᄚ'), + (0xFFB1, 'M', u'ᄆ'), + (0xFFB2, 'M', u'ᄇ'), + (0xFFB3, 'M', u'ᄈ'), + (0xFFB4, 'M', u'ᄡ'), + (0xFFB5, 'M', u'ᄉ'), + (0xFFB6, 'M', u'ᄊ'), + (0xFFB7, 'M', u'ᄋ'), + (0xFFB8, 'M', u'ᄌ'), + (0xFFB9, 'M', u'ᄍ'), + (0xFFBA, 'M', u'ᄎ'), + (0xFFBB, 'M', u'ᄏ'), + (0xFFBC, 'M', u'ᄐ'), + (0xFFBD, 'M', u'ᄑ'), + (0xFFBE, 'M', u'ᄒ'), + (0xFFBF, 'X'), + (0xFFC2, 'M', u'ᅡ'), + (0xFFC3, 'M', u'ᅢ'), + (0xFFC4, 'M', u'ᅣ'), + (0xFFC5, 'M', u'ᅤ'), + (0xFFC6, 'M', u'ᅥ'), + (0xFFC7, 'M', u'ᅦ'), + (0xFFC8, 'X'), + (0xFFCA, 'M', u'ᅧ'), + (0xFFCB, 'M', u'ᅨ'), + (0xFFCC, 'M', u'ᅩ'), + (0xFFCD, 'M', u'ᅪ'), + (0xFFCE, 'M', u'ᅫ'), + (0xFFCF, 'M', u'ᅬ'), + (0xFFD0, 'X'), + (0xFFD2, 'M', u'ᅭ'), + (0xFFD3, 'M', u'ᅮ'), + (0xFFD4, 'M', u'ᅯ'), + (0xFFD5, 'M', u'ᅰ'), + (0xFFD6, 'M', u'ᅱ'), + (0xFFD7, 'M', u'ᅲ'), + (0xFFD8, 'X'), + (0xFFDA, 'M', u'ᅳ'), + (0xFFDB, 'M', u'ᅴ'), + (0xFFDC, 'M', u'ᅵ'), + (0xFFDD, 'X'), + (0xFFE0, 'M', u'¢'), + (0xFFE1, 'M', u'£'), + (0xFFE2, 'M', u'¬'), + (0xFFE3, '3', u' ̄'), + (0xFFE4, 'M', u'¦'), + (0xFFE5, 'M', u'¥'), + (0xFFE6, 'M', u'₩'), + (0xFFE7, 'X'), + (0xFFE8, 'M', u'│'), + (0xFFE9, 'M', u'←'), + (0xFFEA, 'M', u'↑'), + (0xFFEB, 'M', u'→'), + (0xFFEC, 'M', u'↓'), + (0xFFED, 'M', u'■'), + (0xFFEE, 'M', u'○'), + (0xFFEF, 'X'), + (0x10000, 'V'), + (0x1000C, 'X'), + (0x1000D, 'V'), + (0x10027, 'X'), + (0x10028, 'V'), + (0x1003B, 'X'), + (0x1003C, 'V'), + (0x1003E, 'X'), + (0x1003F, 'V'), + (0x1004E, 'X'), + (0x10050, 'V'), + (0x1005E, 'X'), + (0x10080, 'V'), + (0x100FB, 'X'), + (0x10100, 'V'), + (0x10103, 'X'), + (0x10107, 'V'), + (0x10134, 'X'), + (0x10137, 'V'), + (0x1018F, 'X'), + (0x10190, 'V'), + (0x1019C, 'X'), + (0x101A0, 'V'), + (0x101A1, 'X'), + (0x101D0, 'V'), + (0x101FE, 'X'), + (0x10280, 'V'), + (0x1029D, 'X'), + (0x102A0, 'V'), + (0x102D1, 'X'), + (0x102E0, 'V'), + (0x102FC, 'X'), + (0x10300, 'V'), + (0x10324, 'X'), + (0x1032D, 'V'), + ] + +def _seg_53(): + return [ + (0x1034B, 'X'), + (0x10350, 'V'), + (0x1037B, 'X'), + (0x10380, 'V'), + (0x1039E, 'X'), + (0x1039F, 'V'), + (0x103C4, 'X'), + (0x103C8, 'V'), + (0x103D6, 'X'), + (0x10400, 'M', u'𐐨'), + (0x10401, 'M', u'𐐩'), + (0x10402, 'M', u'𐐪'), + (0x10403, 'M', u'𐐫'), + (0x10404, 'M', u'𐐬'), + (0x10405, 'M', u'𐐭'), + (0x10406, 'M', u'𐐮'), + (0x10407, 'M', u'𐐯'), + (0x10408, 'M', u'𐐰'), + (0x10409, 'M', u'𐐱'), + (0x1040A, 'M', u'𐐲'), + (0x1040B, 'M', u'𐐳'), + (0x1040C, 'M', u'𐐴'), + (0x1040D, 'M', u'𐐵'), + (0x1040E, 'M', u'𐐶'), + (0x1040F, 'M', u'𐐷'), + (0x10410, 'M', u'𐐸'), + (0x10411, 'M', u'𐐹'), + (0x10412, 'M', u'𐐺'), + (0x10413, 'M', u'𐐻'), + (0x10414, 'M', u'𐐼'), + (0x10415, 'M', u'𐐽'), + (0x10416, 'M', u'𐐾'), + (0x10417, 'M', u'𐐿'), + (0x10418, 'M', u'𐑀'), + (0x10419, 'M', u'𐑁'), + (0x1041A, 'M', u'𐑂'), + (0x1041B, 'M', u'𐑃'), + (0x1041C, 'M', u'𐑄'), + (0x1041D, 'M', u'𐑅'), + (0x1041E, 'M', u'𐑆'), + (0x1041F, 'M', u'𐑇'), + (0x10420, 'M', u'𐑈'), + (0x10421, 'M', u'𐑉'), + (0x10422, 'M', u'𐑊'), + (0x10423, 'M', u'𐑋'), + (0x10424, 'M', u'𐑌'), + (0x10425, 'M', u'𐑍'), + (0x10426, 'M', u'𐑎'), + (0x10427, 'M', u'𐑏'), + (0x10428, 'V'), + (0x1049E, 'X'), + (0x104A0, 'V'), + (0x104AA, 'X'), + (0x104B0, 'M', u'𐓘'), + (0x104B1, 'M', u'𐓙'), + (0x104B2, 'M', u'𐓚'), + (0x104B3, 'M', u'𐓛'), + (0x104B4, 'M', u'𐓜'), + (0x104B5, 'M', u'𐓝'), + (0x104B6, 'M', u'𐓞'), + (0x104B7, 'M', u'𐓟'), + (0x104B8, 'M', u'𐓠'), + (0x104B9, 'M', u'𐓡'), + (0x104BA, 'M', u'𐓢'), + (0x104BB, 'M', u'𐓣'), + (0x104BC, 'M', u'𐓤'), + (0x104BD, 'M', u'𐓥'), + (0x104BE, 'M', u'𐓦'), + (0x104BF, 'M', u'𐓧'), + (0x104C0, 'M', u'𐓨'), + (0x104C1, 'M', u'𐓩'), + (0x104C2, 'M', u'𐓪'), + (0x104C3, 'M', u'𐓫'), + (0x104C4, 'M', u'𐓬'), + (0x104C5, 'M', u'𐓭'), + (0x104C6, 'M', u'𐓮'), + (0x104C7, 'M', u'𐓯'), + (0x104C8, 'M', u'𐓰'), + (0x104C9, 'M', u'𐓱'), + (0x104CA, 'M', u'𐓲'), + (0x104CB, 'M', u'𐓳'), + (0x104CC, 'M', u'𐓴'), + (0x104CD, 'M', u'𐓵'), + (0x104CE, 'M', u'𐓶'), + (0x104CF, 'M', u'𐓷'), + (0x104D0, 'M', u'𐓸'), + (0x104D1, 'M', u'𐓹'), + (0x104D2, 'M', u'𐓺'), + (0x104D3, 'M', u'𐓻'), + (0x104D4, 'X'), + (0x104D8, 'V'), + (0x104FC, 'X'), + (0x10500, 'V'), + (0x10528, 'X'), + (0x10530, 'V'), + (0x10564, 'X'), + (0x1056F, 'V'), + (0x10570, 'X'), + (0x10600, 'V'), + (0x10737, 'X'), + ] + +def _seg_54(): + return [ + (0x10740, 'V'), + (0x10756, 'X'), + (0x10760, 'V'), + (0x10768, 'X'), + (0x10800, 'V'), + (0x10806, 'X'), + (0x10808, 'V'), + (0x10809, 'X'), + (0x1080A, 'V'), + (0x10836, 'X'), + (0x10837, 'V'), + (0x10839, 'X'), + (0x1083C, 'V'), + (0x1083D, 'X'), + (0x1083F, 'V'), + (0x10856, 'X'), + (0x10857, 'V'), + (0x1089F, 'X'), + (0x108A7, 'V'), + (0x108B0, 'X'), + (0x108E0, 'V'), + (0x108F3, 'X'), + (0x108F4, 'V'), + (0x108F6, 'X'), + (0x108FB, 'V'), + (0x1091C, 'X'), + (0x1091F, 'V'), + (0x1093A, 'X'), + (0x1093F, 'V'), + (0x10940, 'X'), + (0x10980, 'V'), + (0x109B8, 'X'), + (0x109BC, 'V'), + (0x109D0, 'X'), + (0x109D2, 'V'), + (0x10A04, 'X'), + (0x10A05, 'V'), + (0x10A07, 'X'), + (0x10A0C, 'V'), + (0x10A14, 'X'), + (0x10A15, 'V'), + (0x10A18, 'X'), + (0x10A19, 'V'), + (0x10A34, 'X'), + (0x10A38, 'V'), + (0x10A3B, 'X'), + (0x10A3F, 'V'), + (0x10A48, 'X'), + (0x10A50, 'V'), + (0x10A59, 'X'), + (0x10A60, 'V'), + (0x10AA0, 'X'), + (0x10AC0, 'V'), + (0x10AE7, 'X'), + (0x10AEB, 'V'), + (0x10AF7, 'X'), + (0x10B00, 'V'), + (0x10B36, 'X'), + (0x10B39, 'V'), + (0x10B56, 'X'), + (0x10B58, 'V'), + (0x10B73, 'X'), + (0x10B78, 'V'), + (0x10B92, 'X'), + (0x10B99, 'V'), + (0x10B9D, 'X'), + (0x10BA9, 'V'), + (0x10BB0, 'X'), + (0x10C00, 'V'), + (0x10C49, 'X'), + (0x10C80, 'M', u'𐳀'), + (0x10C81, 'M', u'𐳁'), + (0x10C82, 'M', u'𐳂'), + (0x10C83, 'M', u'𐳃'), + (0x10C84, 'M', u'𐳄'), + (0x10C85, 'M', u'𐳅'), + (0x10C86, 'M', u'𐳆'), + (0x10C87, 'M', u'𐳇'), + (0x10C88, 'M', u'𐳈'), + (0x10C89, 'M', u'𐳉'), + (0x10C8A, 'M', u'𐳊'), + (0x10C8B, 'M', u'𐳋'), + (0x10C8C, 'M', u'𐳌'), + (0x10C8D, 'M', u'𐳍'), + (0x10C8E, 'M', u'𐳎'), + (0x10C8F, 'M', u'𐳏'), + (0x10C90, 'M', u'𐳐'), + (0x10C91, 'M', u'𐳑'), + (0x10C92, 'M', u'𐳒'), + (0x10C93, 'M', u'𐳓'), + (0x10C94, 'M', u'𐳔'), + (0x10C95, 'M', u'𐳕'), + (0x10C96, 'M', u'𐳖'), + (0x10C97, 'M', u'𐳗'), + (0x10C98, 'M', u'𐳘'), + (0x10C99, 'M', u'𐳙'), + (0x10C9A, 'M', u'𐳚'), + (0x10C9B, 'M', u'𐳛'), + (0x10C9C, 'M', u'𐳜'), + (0x10C9D, 'M', u'𐳝'), + ] + +def _seg_55(): + return [ + (0x10C9E, 'M', u'𐳞'), + (0x10C9F, 'M', u'𐳟'), + (0x10CA0, 'M', u'𐳠'), + (0x10CA1, 'M', u'𐳡'), + (0x10CA2, 'M', u'𐳢'), + (0x10CA3, 'M', u'𐳣'), + (0x10CA4, 'M', u'𐳤'), + (0x10CA5, 'M', u'𐳥'), + (0x10CA6, 'M', u'𐳦'), + (0x10CA7, 'M', u'𐳧'), + (0x10CA8, 'M', u'𐳨'), + (0x10CA9, 'M', u'𐳩'), + (0x10CAA, 'M', u'𐳪'), + (0x10CAB, 'M', u'𐳫'), + (0x10CAC, 'M', u'𐳬'), + (0x10CAD, 'M', u'𐳭'), + (0x10CAE, 'M', u'𐳮'), + (0x10CAF, 'M', u'𐳯'), + (0x10CB0, 'M', u'𐳰'), + (0x10CB1, 'M', u'𐳱'), + (0x10CB2, 'M', u'𐳲'), + (0x10CB3, 'X'), + (0x10CC0, 'V'), + (0x10CF3, 'X'), + (0x10CFA, 'V'), + (0x10D00, 'X'), + (0x10E60, 'V'), + (0x10E7F, 'X'), + (0x11000, 'V'), + (0x1104E, 'X'), + (0x11052, 'V'), + (0x11070, 'X'), + (0x1107F, 'V'), + (0x110BD, 'X'), + (0x110BE, 'V'), + (0x110C2, 'X'), + (0x110D0, 'V'), + (0x110E9, 'X'), + (0x110F0, 'V'), + (0x110FA, 'X'), + (0x11100, 'V'), + (0x11135, 'X'), + (0x11136, 'V'), + (0x11144, 'X'), + (0x11150, 'V'), + (0x11177, 'X'), + (0x11180, 'V'), + (0x111CE, 'X'), + (0x111D0, 'V'), + (0x111E0, 'X'), + (0x111E1, 'V'), + (0x111F5, 'X'), + (0x11200, 'V'), + (0x11212, 'X'), + (0x11213, 'V'), + (0x1123F, 'X'), + (0x11280, 'V'), + (0x11287, 'X'), + (0x11288, 'V'), + (0x11289, 'X'), + (0x1128A, 'V'), + (0x1128E, 'X'), + (0x1128F, 'V'), + (0x1129E, 'X'), + (0x1129F, 'V'), + (0x112AA, 'X'), + (0x112B0, 'V'), + (0x112EB, 'X'), + (0x112F0, 'V'), + (0x112FA, 'X'), + (0x11300, 'V'), + (0x11304, 'X'), + (0x11305, 'V'), + (0x1130D, 'X'), + (0x1130F, 'V'), + (0x11311, 'X'), + (0x11313, 'V'), + (0x11329, 'X'), + (0x1132A, 'V'), + (0x11331, 'X'), + (0x11332, 'V'), + (0x11334, 'X'), + (0x11335, 'V'), + (0x1133A, 'X'), + (0x1133C, 'V'), + (0x11345, 'X'), + (0x11347, 'V'), + (0x11349, 'X'), + (0x1134B, 'V'), + (0x1134E, 'X'), + (0x11350, 'V'), + (0x11351, 'X'), + (0x11357, 'V'), + (0x11358, 'X'), + (0x1135D, 'V'), + (0x11364, 'X'), + (0x11366, 'V'), + (0x1136D, 'X'), + (0x11370, 'V'), + (0x11375, 'X'), + ] + +def _seg_56(): + return [ + (0x11400, 'V'), + (0x1145A, 'X'), + (0x1145B, 'V'), + (0x1145C, 'X'), + (0x1145D, 'V'), + (0x1145E, 'X'), + (0x11480, 'V'), + (0x114C8, 'X'), + (0x114D0, 'V'), + (0x114DA, 'X'), + (0x11580, 'V'), + (0x115B6, 'X'), + (0x115B8, 'V'), + (0x115DE, 'X'), + (0x11600, 'V'), + (0x11645, 'X'), + (0x11650, 'V'), + (0x1165A, 'X'), + (0x11660, 'V'), + (0x1166D, 'X'), + (0x11680, 'V'), + (0x116B8, 'X'), + (0x116C0, 'V'), + (0x116CA, 'X'), + (0x11700, 'V'), + (0x1171A, 'X'), + (0x1171D, 'V'), + (0x1172C, 'X'), + (0x11730, 'V'), + (0x11740, 'X'), + (0x118A0, 'M', u'𑣀'), + (0x118A1, 'M', u'𑣁'), + (0x118A2, 'M', u'𑣂'), + (0x118A3, 'M', u'𑣃'), + (0x118A4, 'M', u'𑣄'), + (0x118A5, 'M', u'𑣅'), + (0x118A6, 'M', u'𑣆'), + (0x118A7, 'M', u'𑣇'), + (0x118A8, 'M', u'𑣈'), + (0x118A9, 'M', u'𑣉'), + (0x118AA, 'M', u'𑣊'), + (0x118AB, 'M', u'𑣋'), + (0x118AC, 'M', u'𑣌'), + (0x118AD, 'M', u'𑣍'), + (0x118AE, 'M', u'𑣎'), + (0x118AF, 'M', u'𑣏'), + (0x118B0, 'M', u'𑣐'), + (0x118B1, 'M', u'𑣑'), + (0x118B2, 'M', u'𑣒'), + (0x118B3, 'M', u'𑣓'), + (0x118B4, 'M', u'𑣔'), + (0x118B5, 'M', u'𑣕'), + (0x118B6, 'M', u'𑣖'), + (0x118B7, 'M', u'𑣗'), + (0x118B8, 'M', u'𑣘'), + (0x118B9, 'M', u'𑣙'), + (0x118BA, 'M', u'𑣚'), + (0x118BB, 'M', u'𑣛'), + (0x118BC, 'M', u'𑣜'), + (0x118BD, 'M', u'𑣝'), + (0x118BE, 'M', u'𑣞'), + (0x118BF, 'M', u'𑣟'), + (0x118C0, 'V'), + (0x118F3, 'X'), + (0x118FF, 'V'), + (0x11900, 'X'), + (0x11A00, 'V'), + (0x11A48, 'X'), + (0x11A50, 'V'), + (0x11A84, 'X'), + (0x11A86, 'V'), + (0x11A9D, 'X'), + (0x11A9E, 'V'), + (0x11AA3, 'X'), + (0x11AC0, 'V'), + (0x11AF9, 'X'), + (0x11C00, 'V'), + (0x11C09, 'X'), + (0x11C0A, 'V'), + (0x11C37, 'X'), + (0x11C38, 'V'), + (0x11C46, 'X'), + (0x11C50, 'V'), + (0x11C6D, 'X'), + (0x11C70, 'V'), + (0x11C90, 'X'), + (0x11C92, 'V'), + (0x11CA8, 'X'), + (0x11CA9, 'V'), + (0x11CB7, 'X'), + (0x11D00, 'V'), + (0x11D07, 'X'), + (0x11D08, 'V'), + (0x11D0A, 'X'), + (0x11D0B, 'V'), + (0x11D37, 'X'), + (0x11D3A, 'V'), + (0x11D3B, 'X'), + (0x11D3C, 'V'), + (0x11D3E, 'X'), + ] + +def _seg_57(): + return [ + (0x11D3F, 'V'), + (0x11D48, 'X'), + (0x11D50, 'V'), + (0x11D5A, 'X'), + (0x12000, 'V'), + (0x1239A, 'X'), + (0x12400, 'V'), + (0x1246F, 'X'), + (0x12470, 'V'), + (0x12475, 'X'), + (0x12480, 'V'), + (0x12544, 'X'), + (0x13000, 'V'), + (0x1342F, 'X'), + (0x14400, 'V'), + (0x14647, 'X'), + (0x16800, 'V'), + (0x16A39, 'X'), + (0x16A40, 'V'), + (0x16A5F, 'X'), + (0x16A60, 'V'), + (0x16A6A, 'X'), + (0x16A6E, 'V'), + (0x16A70, 'X'), + (0x16AD0, 'V'), + (0x16AEE, 'X'), + (0x16AF0, 'V'), + (0x16AF6, 'X'), + (0x16B00, 'V'), + (0x16B46, 'X'), + (0x16B50, 'V'), + (0x16B5A, 'X'), + (0x16B5B, 'V'), + (0x16B62, 'X'), + (0x16B63, 'V'), + (0x16B78, 'X'), + (0x16B7D, 'V'), + (0x16B90, 'X'), + (0x16F00, 'V'), + (0x16F45, 'X'), + (0x16F50, 'V'), + (0x16F7F, 'X'), + (0x16F8F, 'V'), + (0x16FA0, 'X'), + (0x16FE0, 'V'), + (0x16FE2, 'X'), + (0x17000, 'V'), + (0x187ED, 'X'), + (0x18800, 'V'), + (0x18AF3, 'X'), + (0x1B000, 'V'), + (0x1B11F, 'X'), + (0x1B170, 'V'), + (0x1B2FC, 'X'), + (0x1BC00, 'V'), + (0x1BC6B, 'X'), + (0x1BC70, 'V'), + (0x1BC7D, 'X'), + (0x1BC80, 'V'), + (0x1BC89, 'X'), + (0x1BC90, 'V'), + (0x1BC9A, 'X'), + (0x1BC9C, 'V'), + (0x1BCA0, 'I'), + (0x1BCA4, 'X'), + (0x1D000, 'V'), + (0x1D0F6, 'X'), + (0x1D100, 'V'), + (0x1D127, 'X'), + (0x1D129, 'V'), + (0x1D15E, 'M', u'𝅗𝅥'), + (0x1D15F, 'M', u'𝅘𝅥'), + (0x1D160, 'M', u'𝅘𝅥𝅮'), + (0x1D161, 'M', u'𝅘𝅥𝅯'), + (0x1D162, 'M', u'𝅘𝅥𝅰'), + (0x1D163, 'M', u'𝅘𝅥𝅱'), + (0x1D164, 'M', u'𝅘𝅥𝅲'), + (0x1D165, 'V'), + (0x1D173, 'X'), + (0x1D17B, 'V'), + (0x1D1BB, 'M', u'𝆹𝅥'), + (0x1D1BC, 'M', u'𝆺𝅥'), + (0x1D1BD, 'M', u'𝆹𝅥𝅮'), + (0x1D1BE, 'M', u'𝆺𝅥𝅮'), + (0x1D1BF, 'M', u'𝆹𝅥𝅯'), + (0x1D1C0, 'M', u'𝆺𝅥𝅯'), + (0x1D1C1, 'V'), + (0x1D1E9, 'X'), + (0x1D200, 'V'), + (0x1D246, 'X'), + (0x1D300, 'V'), + (0x1D357, 'X'), + (0x1D360, 'V'), + (0x1D372, 'X'), + (0x1D400, 'M', u'a'), + (0x1D401, 'M', u'b'), + (0x1D402, 'M', u'c'), + (0x1D403, 'M', u'd'), + (0x1D404, 'M', u'e'), + (0x1D405, 'M', u'f'), + ] + +def _seg_58(): + return [ + (0x1D406, 'M', u'g'), + (0x1D407, 'M', u'h'), + (0x1D408, 'M', u'i'), + (0x1D409, 'M', u'j'), + (0x1D40A, 'M', u'k'), + (0x1D40B, 'M', u'l'), + (0x1D40C, 'M', u'm'), + (0x1D40D, 'M', u'n'), + (0x1D40E, 'M', u'o'), + (0x1D40F, 'M', u'p'), + (0x1D410, 'M', u'q'), + (0x1D411, 'M', u'r'), + (0x1D412, 'M', u's'), + (0x1D413, 'M', u't'), + (0x1D414, 'M', u'u'), + (0x1D415, 'M', u'v'), + (0x1D416, 'M', u'w'), + (0x1D417, 'M', u'x'), + (0x1D418, 'M', u'y'), + (0x1D419, 'M', u'z'), + (0x1D41A, 'M', u'a'), + (0x1D41B, 'M', u'b'), + (0x1D41C, 'M', u'c'), + (0x1D41D, 'M', u'd'), + (0x1D41E, 'M', u'e'), + (0x1D41F, 'M', u'f'), + (0x1D420, 'M', u'g'), + (0x1D421, 'M', u'h'), + (0x1D422, 'M', u'i'), + (0x1D423, 'M', u'j'), + (0x1D424, 'M', u'k'), + (0x1D425, 'M', u'l'), + (0x1D426, 'M', u'm'), + (0x1D427, 'M', u'n'), + (0x1D428, 'M', u'o'), + (0x1D429, 'M', u'p'), + (0x1D42A, 'M', u'q'), + (0x1D42B, 'M', u'r'), + (0x1D42C, 'M', u's'), + (0x1D42D, 'M', u't'), + (0x1D42E, 'M', u'u'), + (0x1D42F, 'M', u'v'), + (0x1D430, 'M', u'w'), + (0x1D431, 'M', u'x'), + (0x1D432, 'M', u'y'), + (0x1D433, 'M', u'z'), + (0x1D434, 'M', u'a'), + (0x1D435, 'M', u'b'), + (0x1D436, 'M', u'c'), + (0x1D437, 'M', u'd'), + (0x1D438, 'M', u'e'), + (0x1D439, 'M', u'f'), + (0x1D43A, 'M', u'g'), + (0x1D43B, 'M', u'h'), + (0x1D43C, 'M', u'i'), + (0x1D43D, 'M', u'j'), + (0x1D43E, 'M', u'k'), + (0x1D43F, 'M', u'l'), + (0x1D440, 'M', u'm'), + (0x1D441, 'M', u'n'), + (0x1D442, 'M', u'o'), + (0x1D443, 'M', u'p'), + (0x1D444, 'M', u'q'), + (0x1D445, 'M', u'r'), + (0x1D446, 'M', u's'), + (0x1D447, 'M', u't'), + (0x1D448, 'M', u'u'), + (0x1D449, 'M', u'v'), + (0x1D44A, 'M', u'w'), + (0x1D44B, 'M', u'x'), + (0x1D44C, 'M', u'y'), + (0x1D44D, 'M', u'z'), + (0x1D44E, 'M', u'a'), + (0x1D44F, 'M', u'b'), + (0x1D450, 'M', u'c'), + (0x1D451, 'M', u'd'), + (0x1D452, 'M', u'e'), + (0x1D453, 'M', u'f'), + (0x1D454, 'M', u'g'), + (0x1D455, 'X'), + (0x1D456, 'M', u'i'), + (0x1D457, 'M', u'j'), + (0x1D458, 'M', u'k'), + (0x1D459, 'M', u'l'), + (0x1D45A, 'M', u'm'), + (0x1D45B, 'M', u'n'), + (0x1D45C, 'M', u'o'), + (0x1D45D, 'M', u'p'), + (0x1D45E, 'M', u'q'), + (0x1D45F, 'M', u'r'), + (0x1D460, 'M', u's'), + (0x1D461, 'M', u't'), + (0x1D462, 'M', u'u'), + (0x1D463, 'M', u'v'), + (0x1D464, 'M', u'w'), + (0x1D465, 'M', u'x'), + (0x1D466, 'M', u'y'), + (0x1D467, 'M', u'z'), + (0x1D468, 'M', u'a'), + (0x1D469, 'M', u'b'), + ] + +def _seg_59(): + return [ + (0x1D46A, 'M', u'c'), + (0x1D46B, 'M', u'd'), + (0x1D46C, 'M', u'e'), + (0x1D46D, 'M', u'f'), + (0x1D46E, 'M', u'g'), + (0x1D46F, 'M', u'h'), + (0x1D470, 'M', u'i'), + (0x1D471, 'M', u'j'), + (0x1D472, 'M', u'k'), + (0x1D473, 'M', u'l'), + (0x1D474, 'M', u'm'), + (0x1D475, 'M', u'n'), + (0x1D476, 'M', u'o'), + (0x1D477, 'M', u'p'), + (0x1D478, 'M', u'q'), + (0x1D479, 'M', u'r'), + (0x1D47A, 'M', u's'), + (0x1D47B, 'M', u't'), + (0x1D47C, 'M', u'u'), + (0x1D47D, 'M', u'v'), + (0x1D47E, 'M', u'w'), + (0x1D47F, 'M', u'x'), + (0x1D480, 'M', u'y'), + (0x1D481, 'M', u'z'), + (0x1D482, 'M', u'a'), + (0x1D483, 'M', u'b'), + (0x1D484, 'M', u'c'), + (0x1D485, 'M', u'd'), + (0x1D486, 'M', u'e'), + (0x1D487, 'M', u'f'), + (0x1D488, 'M', u'g'), + (0x1D489, 'M', u'h'), + (0x1D48A, 'M', u'i'), + (0x1D48B, 'M', u'j'), + (0x1D48C, 'M', u'k'), + (0x1D48D, 'M', u'l'), + (0x1D48E, 'M', u'm'), + (0x1D48F, 'M', u'n'), + (0x1D490, 'M', u'o'), + (0x1D491, 'M', u'p'), + (0x1D492, 'M', u'q'), + (0x1D493, 'M', u'r'), + (0x1D494, 'M', u's'), + (0x1D495, 'M', u't'), + (0x1D496, 'M', u'u'), + (0x1D497, 'M', u'v'), + (0x1D498, 'M', u'w'), + (0x1D499, 'M', u'x'), + (0x1D49A, 'M', u'y'), + (0x1D49B, 'M', u'z'), + (0x1D49C, 'M', u'a'), + (0x1D49D, 'X'), + (0x1D49E, 'M', u'c'), + (0x1D49F, 'M', u'd'), + (0x1D4A0, 'X'), + (0x1D4A2, 'M', u'g'), + (0x1D4A3, 'X'), + (0x1D4A5, 'M', u'j'), + (0x1D4A6, 'M', u'k'), + (0x1D4A7, 'X'), + (0x1D4A9, 'M', u'n'), + (0x1D4AA, 'M', u'o'), + (0x1D4AB, 'M', u'p'), + (0x1D4AC, 'M', u'q'), + (0x1D4AD, 'X'), + (0x1D4AE, 'M', u's'), + (0x1D4AF, 'M', u't'), + (0x1D4B0, 'M', u'u'), + (0x1D4B1, 'M', u'v'), + (0x1D4B2, 'M', u'w'), + (0x1D4B3, 'M', u'x'), + (0x1D4B4, 'M', u'y'), + (0x1D4B5, 'M', u'z'), + (0x1D4B6, 'M', u'a'), + (0x1D4B7, 'M', u'b'), + (0x1D4B8, 'M', u'c'), + (0x1D4B9, 'M', u'd'), + (0x1D4BA, 'X'), + (0x1D4BB, 'M', u'f'), + (0x1D4BC, 'X'), + (0x1D4BD, 'M', u'h'), + (0x1D4BE, 'M', u'i'), + (0x1D4BF, 'M', u'j'), + (0x1D4C0, 'M', u'k'), + (0x1D4C1, 'M', u'l'), + (0x1D4C2, 'M', u'm'), + (0x1D4C3, 'M', u'n'), + (0x1D4C4, 'X'), + (0x1D4C5, 'M', u'p'), + (0x1D4C6, 'M', u'q'), + (0x1D4C7, 'M', u'r'), + (0x1D4C8, 'M', u's'), + (0x1D4C9, 'M', u't'), + (0x1D4CA, 'M', u'u'), + (0x1D4CB, 'M', u'v'), + (0x1D4CC, 'M', u'w'), + (0x1D4CD, 'M', u'x'), + (0x1D4CE, 'M', u'y'), + (0x1D4CF, 'M', u'z'), + (0x1D4D0, 'M', u'a'), + ] + +def _seg_60(): + return [ + (0x1D4D1, 'M', u'b'), + (0x1D4D2, 'M', u'c'), + (0x1D4D3, 'M', u'd'), + (0x1D4D4, 'M', u'e'), + (0x1D4D5, 'M', u'f'), + (0x1D4D6, 'M', u'g'), + (0x1D4D7, 'M', u'h'), + (0x1D4D8, 'M', u'i'), + (0x1D4D9, 'M', u'j'), + (0x1D4DA, 'M', u'k'), + (0x1D4DB, 'M', u'l'), + (0x1D4DC, 'M', u'm'), + (0x1D4DD, 'M', u'n'), + (0x1D4DE, 'M', u'o'), + (0x1D4DF, 'M', u'p'), + (0x1D4E0, 'M', u'q'), + (0x1D4E1, 'M', u'r'), + (0x1D4E2, 'M', u's'), + (0x1D4E3, 'M', u't'), + (0x1D4E4, 'M', u'u'), + (0x1D4E5, 'M', u'v'), + (0x1D4E6, 'M', u'w'), + (0x1D4E7, 'M', u'x'), + (0x1D4E8, 'M', u'y'), + (0x1D4E9, 'M', u'z'), + (0x1D4EA, 'M', u'a'), + (0x1D4EB, 'M', u'b'), + (0x1D4EC, 'M', u'c'), + (0x1D4ED, 'M', u'd'), + (0x1D4EE, 'M', u'e'), + (0x1D4EF, 'M', u'f'), + (0x1D4F0, 'M', u'g'), + (0x1D4F1, 'M', u'h'), + (0x1D4F2, 'M', u'i'), + (0x1D4F3, 'M', u'j'), + (0x1D4F4, 'M', u'k'), + (0x1D4F5, 'M', u'l'), + (0x1D4F6, 'M', u'm'), + (0x1D4F7, 'M', u'n'), + (0x1D4F8, 'M', u'o'), + (0x1D4F9, 'M', u'p'), + (0x1D4FA, 'M', u'q'), + (0x1D4FB, 'M', u'r'), + (0x1D4FC, 'M', u's'), + (0x1D4FD, 'M', u't'), + (0x1D4FE, 'M', u'u'), + (0x1D4FF, 'M', u'v'), + (0x1D500, 'M', u'w'), + (0x1D501, 'M', u'x'), + (0x1D502, 'M', u'y'), + (0x1D503, 'M', u'z'), + (0x1D504, 'M', u'a'), + (0x1D505, 'M', u'b'), + (0x1D506, 'X'), + (0x1D507, 'M', u'd'), + (0x1D508, 'M', u'e'), + (0x1D509, 'M', u'f'), + (0x1D50A, 'M', u'g'), + (0x1D50B, 'X'), + (0x1D50D, 'M', u'j'), + (0x1D50E, 'M', u'k'), + (0x1D50F, 'M', u'l'), + (0x1D510, 'M', u'm'), + (0x1D511, 'M', u'n'), + (0x1D512, 'M', u'o'), + (0x1D513, 'M', u'p'), + (0x1D514, 'M', u'q'), + (0x1D515, 'X'), + (0x1D516, 'M', u's'), + (0x1D517, 'M', u't'), + (0x1D518, 'M', u'u'), + (0x1D519, 'M', u'v'), + (0x1D51A, 'M', u'w'), + (0x1D51B, 'M', u'x'), + (0x1D51C, 'M', u'y'), + (0x1D51D, 'X'), + (0x1D51E, 'M', u'a'), + (0x1D51F, 'M', u'b'), + (0x1D520, 'M', u'c'), + (0x1D521, 'M', u'd'), + (0x1D522, 'M', u'e'), + (0x1D523, 'M', u'f'), + (0x1D524, 'M', u'g'), + (0x1D525, 'M', u'h'), + (0x1D526, 'M', u'i'), + (0x1D527, 'M', u'j'), + (0x1D528, 'M', u'k'), + (0x1D529, 'M', u'l'), + (0x1D52A, 'M', u'm'), + (0x1D52B, 'M', u'n'), + (0x1D52C, 'M', u'o'), + (0x1D52D, 'M', u'p'), + (0x1D52E, 'M', u'q'), + (0x1D52F, 'M', u'r'), + (0x1D530, 'M', u's'), + (0x1D531, 'M', u't'), + (0x1D532, 'M', u'u'), + (0x1D533, 'M', u'v'), + (0x1D534, 'M', u'w'), + (0x1D535, 'M', u'x'), + ] + +def _seg_61(): + return [ + (0x1D536, 'M', u'y'), + (0x1D537, 'M', u'z'), + (0x1D538, 'M', u'a'), + (0x1D539, 'M', u'b'), + (0x1D53A, 'X'), + (0x1D53B, 'M', u'd'), + (0x1D53C, 'M', u'e'), + (0x1D53D, 'M', u'f'), + (0x1D53E, 'M', u'g'), + (0x1D53F, 'X'), + (0x1D540, 'M', u'i'), + (0x1D541, 'M', u'j'), + (0x1D542, 'M', u'k'), + (0x1D543, 'M', u'l'), + (0x1D544, 'M', u'm'), + (0x1D545, 'X'), + (0x1D546, 'M', u'o'), + (0x1D547, 'X'), + (0x1D54A, 'M', u's'), + (0x1D54B, 'M', u't'), + (0x1D54C, 'M', u'u'), + (0x1D54D, 'M', u'v'), + (0x1D54E, 'M', u'w'), + (0x1D54F, 'M', u'x'), + (0x1D550, 'M', u'y'), + (0x1D551, 'X'), + (0x1D552, 'M', u'a'), + (0x1D553, 'M', u'b'), + (0x1D554, 'M', u'c'), + (0x1D555, 'M', u'd'), + (0x1D556, 'M', u'e'), + (0x1D557, 'M', u'f'), + (0x1D558, 'M', u'g'), + (0x1D559, 'M', u'h'), + (0x1D55A, 'M', u'i'), + (0x1D55B, 'M', u'j'), + (0x1D55C, 'M', u'k'), + (0x1D55D, 'M', u'l'), + (0x1D55E, 'M', u'm'), + (0x1D55F, 'M', u'n'), + (0x1D560, 'M', u'o'), + (0x1D561, 'M', u'p'), + (0x1D562, 'M', u'q'), + (0x1D563, 'M', u'r'), + (0x1D564, 'M', u's'), + (0x1D565, 'M', u't'), + (0x1D566, 'M', u'u'), + (0x1D567, 'M', u'v'), + (0x1D568, 'M', u'w'), + (0x1D569, 'M', u'x'), + (0x1D56A, 'M', u'y'), + (0x1D56B, 'M', u'z'), + (0x1D56C, 'M', u'a'), + (0x1D56D, 'M', u'b'), + (0x1D56E, 'M', u'c'), + (0x1D56F, 'M', u'd'), + (0x1D570, 'M', u'e'), + (0x1D571, 'M', u'f'), + (0x1D572, 'M', u'g'), + (0x1D573, 'M', u'h'), + (0x1D574, 'M', u'i'), + (0x1D575, 'M', u'j'), + (0x1D576, 'M', u'k'), + (0x1D577, 'M', u'l'), + (0x1D578, 'M', u'm'), + (0x1D579, 'M', u'n'), + (0x1D57A, 'M', u'o'), + (0x1D57B, 'M', u'p'), + (0x1D57C, 'M', u'q'), + (0x1D57D, 'M', u'r'), + (0x1D57E, 'M', u's'), + (0x1D57F, 'M', u't'), + (0x1D580, 'M', u'u'), + (0x1D581, 'M', u'v'), + (0x1D582, 'M', u'w'), + (0x1D583, 'M', u'x'), + (0x1D584, 'M', u'y'), + (0x1D585, 'M', u'z'), + (0x1D586, 'M', u'a'), + (0x1D587, 'M', u'b'), + (0x1D588, 'M', u'c'), + (0x1D589, 'M', u'd'), + (0x1D58A, 'M', u'e'), + (0x1D58B, 'M', u'f'), + (0x1D58C, 'M', u'g'), + (0x1D58D, 'M', u'h'), + (0x1D58E, 'M', u'i'), + (0x1D58F, 'M', u'j'), + (0x1D590, 'M', u'k'), + (0x1D591, 'M', u'l'), + (0x1D592, 'M', u'm'), + (0x1D593, 'M', u'n'), + (0x1D594, 'M', u'o'), + (0x1D595, 'M', u'p'), + (0x1D596, 'M', u'q'), + (0x1D597, 'M', u'r'), + (0x1D598, 'M', u's'), + (0x1D599, 'M', u't'), + (0x1D59A, 'M', u'u'), + (0x1D59B, 'M', u'v'), + ] + +def _seg_62(): + return [ + (0x1D59C, 'M', u'w'), + (0x1D59D, 'M', u'x'), + (0x1D59E, 'M', u'y'), + (0x1D59F, 'M', u'z'), + (0x1D5A0, 'M', u'a'), + (0x1D5A1, 'M', u'b'), + (0x1D5A2, 'M', u'c'), + (0x1D5A3, 'M', u'd'), + (0x1D5A4, 'M', u'e'), + (0x1D5A5, 'M', u'f'), + (0x1D5A6, 'M', u'g'), + (0x1D5A7, 'M', u'h'), + (0x1D5A8, 'M', u'i'), + (0x1D5A9, 'M', u'j'), + (0x1D5AA, 'M', u'k'), + (0x1D5AB, 'M', u'l'), + (0x1D5AC, 'M', u'm'), + (0x1D5AD, 'M', u'n'), + (0x1D5AE, 'M', u'o'), + (0x1D5AF, 'M', u'p'), + (0x1D5B0, 'M', u'q'), + (0x1D5B1, 'M', u'r'), + (0x1D5B2, 'M', u's'), + (0x1D5B3, 'M', u't'), + (0x1D5B4, 'M', u'u'), + (0x1D5B5, 'M', u'v'), + (0x1D5B6, 'M', u'w'), + (0x1D5B7, 'M', u'x'), + (0x1D5B8, 'M', u'y'), + (0x1D5B9, 'M', u'z'), + (0x1D5BA, 'M', u'a'), + (0x1D5BB, 'M', u'b'), + (0x1D5BC, 'M', u'c'), + (0x1D5BD, 'M', u'd'), + (0x1D5BE, 'M', u'e'), + (0x1D5BF, 'M', u'f'), + (0x1D5C0, 'M', u'g'), + (0x1D5C1, 'M', u'h'), + (0x1D5C2, 'M', u'i'), + (0x1D5C3, 'M', u'j'), + (0x1D5C4, 'M', u'k'), + (0x1D5C5, 'M', u'l'), + (0x1D5C6, 'M', u'm'), + (0x1D5C7, 'M', u'n'), + (0x1D5C8, 'M', u'o'), + (0x1D5C9, 'M', u'p'), + (0x1D5CA, 'M', u'q'), + (0x1D5CB, 'M', u'r'), + (0x1D5CC, 'M', u's'), + (0x1D5CD, 'M', u't'), + (0x1D5CE, 'M', u'u'), + (0x1D5CF, 'M', u'v'), + (0x1D5D0, 'M', u'w'), + (0x1D5D1, 'M', u'x'), + (0x1D5D2, 'M', u'y'), + (0x1D5D3, 'M', u'z'), + (0x1D5D4, 'M', u'a'), + (0x1D5D5, 'M', u'b'), + (0x1D5D6, 'M', u'c'), + (0x1D5D7, 'M', u'd'), + (0x1D5D8, 'M', u'e'), + (0x1D5D9, 'M', u'f'), + (0x1D5DA, 'M', u'g'), + (0x1D5DB, 'M', u'h'), + (0x1D5DC, 'M', u'i'), + (0x1D5DD, 'M', u'j'), + (0x1D5DE, 'M', u'k'), + (0x1D5DF, 'M', u'l'), + (0x1D5E0, 'M', u'm'), + (0x1D5E1, 'M', u'n'), + (0x1D5E2, 'M', u'o'), + (0x1D5E3, 'M', u'p'), + (0x1D5E4, 'M', u'q'), + (0x1D5E5, 'M', u'r'), + (0x1D5E6, 'M', u's'), + (0x1D5E7, 'M', u't'), + (0x1D5E8, 'M', u'u'), + (0x1D5E9, 'M', u'v'), + (0x1D5EA, 'M', u'w'), + (0x1D5EB, 'M', u'x'), + (0x1D5EC, 'M', u'y'), + (0x1D5ED, 'M', u'z'), + (0x1D5EE, 'M', u'a'), + (0x1D5EF, 'M', u'b'), + (0x1D5F0, 'M', u'c'), + (0x1D5F1, 'M', u'd'), + (0x1D5F2, 'M', u'e'), + (0x1D5F3, 'M', u'f'), + (0x1D5F4, 'M', u'g'), + (0x1D5F5, 'M', u'h'), + (0x1D5F6, 'M', u'i'), + (0x1D5F7, 'M', u'j'), + (0x1D5F8, 'M', u'k'), + (0x1D5F9, 'M', u'l'), + (0x1D5FA, 'M', u'm'), + (0x1D5FB, 'M', u'n'), + (0x1D5FC, 'M', u'o'), + (0x1D5FD, 'M', u'p'), + (0x1D5FE, 'M', u'q'), + (0x1D5FF, 'M', u'r'), + ] + +def _seg_63(): + return [ + (0x1D600, 'M', u's'), + (0x1D601, 'M', u't'), + (0x1D602, 'M', u'u'), + (0x1D603, 'M', u'v'), + (0x1D604, 'M', u'w'), + (0x1D605, 'M', u'x'), + (0x1D606, 'M', u'y'), + (0x1D607, 'M', u'z'), + (0x1D608, 'M', u'a'), + (0x1D609, 'M', u'b'), + (0x1D60A, 'M', u'c'), + (0x1D60B, 'M', u'd'), + (0x1D60C, 'M', u'e'), + (0x1D60D, 'M', u'f'), + (0x1D60E, 'M', u'g'), + (0x1D60F, 'M', u'h'), + (0x1D610, 'M', u'i'), + (0x1D611, 'M', u'j'), + (0x1D612, 'M', u'k'), + (0x1D613, 'M', u'l'), + (0x1D614, 'M', u'm'), + (0x1D615, 'M', u'n'), + (0x1D616, 'M', u'o'), + (0x1D617, 'M', u'p'), + (0x1D618, 'M', u'q'), + (0x1D619, 'M', u'r'), + (0x1D61A, 'M', u's'), + (0x1D61B, 'M', u't'), + (0x1D61C, 'M', u'u'), + (0x1D61D, 'M', u'v'), + (0x1D61E, 'M', u'w'), + (0x1D61F, 'M', u'x'), + (0x1D620, 'M', u'y'), + (0x1D621, 'M', u'z'), + (0x1D622, 'M', u'a'), + (0x1D623, 'M', u'b'), + (0x1D624, 'M', u'c'), + (0x1D625, 'M', u'd'), + (0x1D626, 'M', u'e'), + (0x1D627, 'M', u'f'), + (0x1D628, 'M', u'g'), + (0x1D629, 'M', u'h'), + (0x1D62A, 'M', u'i'), + (0x1D62B, 'M', u'j'), + (0x1D62C, 'M', u'k'), + (0x1D62D, 'M', u'l'), + (0x1D62E, 'M', u'm'), + (0x1D62F, 'M', u'n'), + (0x1D630, 'M', u'o'), + (0x1D631, 'M', u'p'), + (0x1D632, 'M', u'q'), + (0x1D633, 'M', u'r'), + (0x1D634, 'M', u's'), + (0x1D635, 'M', u't'), + (0x1D636, 'M', u'u'), + (0x1D637, 'M', u'v'), + (0x1D638, 'M', u'w'), + (0x1D639, 'M', u'x'), + (0x1D63A, 'M', u'y'), + (0x1D63B, 'M', u'z'), + (0x1D63C, 'M', u'a'), + (0x1D63D, 'M', u'b'), + (0x1D63E, 'M', u'c'), + (0x1D63F, 'M', u'd'), + (0x1D640, 'M', u'e'), + (0x1D641, 'M', u'f'), + (0x1D642, 'M', u'g'), + (0x1D643, 'M', u'h'), + (0x1D644, 'M', u'i'), + (0x1D645, 'M', u'j'), + (0x1D646, 'M', u'k'), + (0x1D647, 'M', u'l'), + (0x1D648, 'M', u'm'), + (0x1D649, 'M', u'n'), + (0x1D64A, 'M', u'o'), + (0x1D64B, 'M', u'p'), + (0x1D64C, 'M', u'q'), + (0x1D64D, 'M', u'r'), + (0x1D64E, 'M', u's'), + (0x1D64F, 'M', u't'), + (0x1D650, 'M', u'u'), + (0x1D651, 'M', u'v'), + (0x1D652, 'M', u'w'), + (0x1D653, 'M', u'x'), + (0x1D654, 'M', u'y'), + (0x1D655, 'M', u'z'), + (0x1D656, 'M', u'a'), + (0x1D657, 'M', u'b'), + (0x1D658, 'M', u'c'), + (0x1D659, 'M', u'd'), + (0x1D65A, 'M', u'e'), + (0x1D65B, 'M', u'f'), + (0x1D65C, 'M', u'g'), + (0x1D65D, 'M', u'h'), + (0x1D65E, 'M', u'i'), + (0x1D65F, 'M', u'j'), + (0x1D660, 'M', u'k'), + (0x1D661, 'M', u'l'), + (0x1D662, 'M', u'm'), + (0x1D663, 'M', u'n'), + ] + +def _seg_64(): + return [ + (0x1D664, 'M', u'o'), + (0x1D665, 'M', u'p'), + (0x1D666, 'M', u'q'), + (0x1D667, 'M', u'r'), + (0x1D668, 'M', u's'), + (0x1D669, 'M', u't'), + (0x1D66A, 'M', u'u'), + (0x1D66B, 'M', u'v'), + (0x1D66C, 'M', u'w'), + (0x1D66D, 'M', u'x'), + (0x1D66E, 'M', u'y'), + (0x1D66F, 'M', u'z'), + (0x1D670, 'M', u'a'), + (0x1D671, 'M', u'b'), + (0x1D672, 'M', u'c'), + (0x1D673, 'M', u'd'), + (0x1D674, 'M', u'e'), + (0x1D675, 'M', u'f'), + (0x1D676, 'M', u'g'), + (0x1D677, 'M', u'h'), + (0x1D678, 'M', u'i'), + (0x1D679, 'M', u'j'), + (0x1D67A, 'M', u'k'), + (0x1D67B, 'M', u'l'), + (0x1D67C, 'M', u'm'), + (0x1D67D, 'M', u'n'), + (0x1D67E, 'M', u'o'), + (0x1D67F, 'M', u'p'), + (0x1D680, 'M', u'q'), + (0x1D681, 'M', u'r'), + (0x1D682, 'M', u's'), + (0x1D683, 'M', u't'), + (0x1D684, 'M', u'u'), + (0x1D685, 'M', u'v'), + (0x1D686, 'M', u'w'), + (0x1D687, 'M', u'x'), + (0x1D688, 'M', u'y'), + (0x1D689, 'M', u'z'), + (0x1D68A, 'M', u'a'), + (0x1D68B, 'M', u'b'), + (0x1D68C, 'M', u'c'), + (0x1D68D, 'M', u'd'), + (0x1D68E, 'M', u'e'), + (0x1D68F, 'M', u'f'), + (0x1D690, 'M', u'g'), + (0x1D691, 'M', u'h'), + (0x1D692, 'M', u'i'), + (0x1D693, 'M', u'j'), + (0x1D694, 'M', u'k'), + (0x1D695, 'M', u'l'), + (0x1D696, 'M', u'm'), + (0x1D697, 'M', u'n'), + (0x1D698, 'M', u'o'), + (0x1D699, 'M', u'p'), + (0x1D69A, 'M', u'q'), + (0x1D69B, 'M', u'r'), + (0x1D69C, 'M', u's'), + (0x1D69D, 'M', u't'), + (0x1D69E, 'M', u'u'), + (0x1D69F, 'M', u'v'), + (0x1D6A0, 'M', u'w'), + (0x1D6A1, 'M', u'x'), + (0x1D6A2, 'M', u'y'), + (0x1D6A3, 'M', u'z'), + (0x1D6A4, 'M', u'ı'), + (0x1D6A5, 'M', u'ȷ'), + (0x1D6A6, 'X'), + (0x1D6A8, 'M', u'α'), + (0x1D6A9, 'M', u'β'), + (0x1D6AA, 'M', u'γ'), + (0x1D6AB, 'M', u'δ'), + (0x1D6AC, 'M', u'ε'), + (0x1D6AD, 'M', u'ζ'), + (0x1D6AE, 'M', u'η'), + (0x1D6AF, 'M', u'θ'), + (0x1D6B0, 'M', u'ι'), + (0x1D6B1, 'M', u'κ'), + (0x1D6B2, 'M', u'λ'), + (0x1D6B3, 'M', u'μ'), + (0x1D6B4, 'M', u'ν'), + (0x1D6B5, 'M', u'ξ'), + (0x1D6B6, 'M', u'ο'), + (0x1D6B7, 'M', u'π'), + (0x1D6B8, 'M', u'ρ'), + (0x1D6B9, 'M', u'θ'), + (0x1D6BA, 'M', u'σ'), + (0x1D6BB, 'M', u'τ'), + (0x1D6BC, 'M', u'υ'), + (0x1D6BD, 'M', u'φ'), + (0x1D6BE, 'M', u'χ'), + (0x1D6BF, 'M', u'ψ'), + (0x1D6C0, 'M', u'ω'), + (0x1D6C1, 'M', u'∇'), + (0x1D6C2, 'M', u'α'), + (0x1D6C3, 'M', u'β'), + (0x1D6C4, 'M', u'γ'), + (0x1D6C5, 'M', u'δ'), + (0x1D6C6, 'M', u'ε'), + (0x1D6C7, 'M', u'ζ'), + (0x1D6C8, 'M', u'η'), + ] + +def _seg_65(): + return [ + (0x1D6C9, 'M', u'θ'), + (0x1D6CA, 'M', u'ι'), + (0x1D6CB, 'M', u'κ'), + (0x1D6CC, 'M', u'λ'), + (0x1D6CD, 'M', u'μ'), + (0x1D6CE, 'M', u'ν'), + (0x1D6CF, 'M', u'ξ'), + (0x1D6D0, 'M', u'ο'), + (0x1D6D1, 'M', u'π'), + (0x1D6D2, 'M', u'ρ'), + (0x1D6D3, 'M', u'σ'), + (0x1D6D5, 'M', u'τ'), + (0x1D6D6, 'M', u'υ'), + (0x1D6D7, 'M', u'φ'), + (0x1D6D8, 'M', u'χ'), + (0x1D6D9, 'M', u'ψ'), + (0x1D6DA, 'M', u'ω'), + (0x1D6DB, 'M', u'∂'), + (0x1D6DC, 'M', u'ε'), + (0x1D6DD, 'M', u'θ'), + (0x1D6DE, 'M', u'κ'), + (0x1D6DF, 'M', u'φ'), + (0x1D6E0, 'M', u'ρ'), + (0x1D6E1, 'M', u'π'), + (0x1D6E2, 'M', u'α'), + (0x1D6E3, 'M', u'β'), + (0x1D6E4, 'M', u'γ'), + (0x1D6E5, 'M', u'δ'), + (0x1D6E6, 'M', u'ε'), + (0x1D6E7, 'M', u'ζ'), + (0x1D6E8, 'M', u'η'), + (0x1D6E9, 'M', u'θ'), + (0x1D6EA, 'M', u'ι'), + (0x1D6EB, 'M', u'κ'), + (0x1D6EC, 'M', u'λ'), + (0x1D6ED, 'M', u'μ'), + (0x1D6EE, 'M', u'ν'), + (0x1D6EF, 'M', u'ξ'), + (0x1D6F0, 'M', u'ο'), + (0x1D6F1, 'M', u'π'), + (0x1D6F2, 'M', u'ρ'), + (0x1D6F3, 'M', u'θ'), + (0x1D6F4, 'M', u'σ'), + (0x1D6F5, 'M', u'τ'), + (0x1D6F6, 'M', u'υ'), + (0x1D6F7, 'M', u'φ'), + (0x1D6F8, 'M', u'χ'), + (0x1D6F9, 'M', u'ψ'), + (0x1D6FA, 'M', u'ω'), + (0x1D6FB, 'M', u'∇'), + (0x1D6FC, 'M', u'α'), + (0x1D6FD, 'M', u'β'), + (0x1D6FE, 'M', u'γ'), + (0x1D6FF, 'M', u'δ'), + (0x1D700, 'M', u'ε'), + (0x1D701, 'M', u'ζ'), + (0x1D702, 'M', u'η'), + (0x1D703, 'M', u'θ'), + (0x1D704, 'M', u'ι'), + (0x1D705, 'M', u'κ'), + (0x1D706, 'M', u'λ'), + (0x1D707, 'M', u'μ'), + (0x1D708, 'M', u'ν'), + (0x1D709, 'M', u'ξ'), + (0x1D70A, 'M', u'ο'), + (0x1D70B, 'M', u'π'), + (0x1D70C, 'M', u'ρ'), + (0x1D70D, 'M', u'σ'), + (0x1D70F, 'M', u'τ'), + (0x1D710, 'M', u'υ'), + (0x1D711, 'M', u'φ'), + (0x1D712, 'M', u'χ'), + (0x1D713, 'M', u'ψ'), + (0x1D714, 'M', u'ω'), + (0x1D715, 'M', u'∂'), + (0x1D716, 'M', u'ε'), + (0x1D717, 'M', u'θ'), + (0x1D718, 'M', u'κ'), + (0x1D719, 'M', u'φ'), + (0x1D71A, 'M', u'ρ'), + (0x1D71B, 'M', u'π'), + (0x1D71C, 'M', u'α'), + (0x1D71D, 'M', u'β'), + (0x1D71E, 'M', u'γ'), + (0x1D71F, 'M', u'δ'), + (0x1D720, 'M', u'ε'), + (0x1D721, 'M', u'ζ'), + (0x1D722, 'M', u'η'), + (0x1D723, 'M', u'θ'), + (0x1D724, 'M', u'ι'), + (0x1D725, 'M', u'κ'), + (0x1D726, 'M', u'λ'), + (0x1D727, 'M', u'μ'), + (0x1D728, 'M', u'ν'), + (0x1D729, 'M', u'ξ'), + (0x1D72A, 'M', u'ο'), + (0x1D72B, 'M', u'π'), + (0x1D72C, 'M', u'ρ'), + (0x1D72D, 'M', u'θ'), + (0x1D72E, 'M', u'σ'), + ] + +def _seg_66(): + return [ + (0x1D72F, 'M', u'τ'), + (0x1D730, 'M', u'υ'), + (0x1D731, 'M', u'φ'), + (0x1D732, 'M', u'χ'), + (0x1D733, 'M', u'ψ'), + (0x1D734, 'M', u'ω'), + (0x1D735, 'M', u'∇'), + (0x1D736, 'M', u'α'), + (0x1D737, 'M', u'β'), + (0x1D738, 'M', u'γ'), + (0x1D739, 'M', u'δ'), + (0x1D73A, 'M', u'ε'), + (0x1D73B, 'M', u'ζ'), + (0x1D73C, 'M', u'η'), + (0x1D73D, 'M', u'θ'), + (0x1D73E, 'M', u'ι'), + (0x1D73F, 'M', u'κ'), + (0x1D740, 'M', u'λ'), + (0x1D741, 'M', u'μ'), + (0x1D742, 'M', u'ν'), + (0x1D743, 'M', u'ξ'), + (0x1D744, 'M', u'ο'), + (0x1D745, 'M', u'π'), + (0x1D746, 'M', u'ρ'), + (0x1D747, 'M', u'σ'), + (0x1D749, 'M', u'τ'), + (0x1D74A, 'M', u'υ'), + (0x1D74B, 'M', u'φ'), + (0x1D74C, 'M', u'χ'), + (0x1D74D, 'M', u'ψ'), + (0x1D74E, 'M', u'ω'), + (0x1D74F, 'M', u'∂'), + (0x1D750, 'M', u'ε'), + (0x1D751, 'M', u'θ'), + (0x1D752, 'M', u'κ'), + (0x1D753, 'M', u'φ'), + (0x1D754, 'M', u'ρ'), + (0x1D755, 'M', u'π'), + (0x1D756, 'M', u'α'), + (0x1D757, 'M', u'β'), + (0x1D758, 'M', u'γ'), + (0x1D759, 'M', u'δ'), + (0x1D75A, 'M', u'ε'), + (0x1D75B, 'M', u'ζ'), + (0x1D75C, 'M', u'η'), + (0x1D75D, 'M', u'θ'), + (0x1D75E, 'M', u'ι'), + (0x1D75F, 'M', u'κ'), + (0x1D760, 'M', u'λ'), + (0x1D761, 'M', u'μ'), + (0x1D762, 'M', u'ν'), + (0x1D763, 'M', u'ξ'), + (0x1D764, 'M', u'ο'), + (0x1D765, 'M', u'π'), + (0x1D766, 'M', u'ρ'), + (0x1D767, 'M', u'θ'), + (0x1D768, 'M', u'σ'), + (0x1D769, 'M', u'τ'), + (0x1D76A, 'M', u'υ'), + (0x1D76B, 'M', u'φ'), + (0x1D76C, 'M', u'χ'), + (0x1D76D, 'M', u'ψ'), + (0x1D76E, 'M', u'ω'), + (0x1D76F, 'M', u'∇'), + (0x1D770, 'M', u'α'), + (0x1D771, 'M', u'β'), + (0x1D772, 'M', u'γ'), + (0x1D773, 'M', u'δ'), + (0x1D774, 'M', u'ε'), + (0x1D775, 'M', u'ζ'), + (0x1D776, 'M', u'η'), + (0x1D777, 'M', u'θ'), + (0x1D778, 'M', u'ι'), + (0x1D779, 'M', u'κ'), + (0x1D77A, 'M', u'λ'), + (0x1D77B, 'M', u'μ'), + (0x1D77C, 'M', u'ν'), + (0x1D77D, 'M', u'ξ'), + (0x1D77E, 'M', u'ο'), + (0x1D77F, 'M', u'π'), + (0x1D780, 'M', u'ρ'), + (0x1D781, 'M', u'σ'), + (0x1D783, 'M', u'τ'), + (0x1D784, 'M', u'υ'), + (0x1D785, 'M', u'φ'), + (0x1D786, 'M', u'χ'), + (0x1D787, 'M', u'ψ'), + (0x1D788, 'M', u'ω'), + (0x1D789, 'M', u'∂'), + (0x1D78A, 'M', u'ε'), + (0x1D78B, 'M', u'θ'), + (0x1D78C, 'M', u'κ'), + (0x1D78D, 'M', u'φ'), + (0x1D78E, 'M', u'ρ'), + (0x1D78F, 'M', u'π'), + (0x1D790, 'M', u'α'), + (0x1D791, 'M', u'β'), + (0x1D792, 'M', u'γ'), + (0x1D793, 'M', u'δ'), + (0x1D794, 'M', u'ε'), + ] + +def _seg_67(): + return [ + (0x1D795, 'M', u'ζ'), + (0x1D796, 'M', u'η'), + (0x1D797, 'M', u'θ'), + (0x1D798, 'M', u'ι'), + (0x1D799, 'M', u'κ'), + (0x1D79A, 'M', u'λ'), + (0x1D79B, 'M', u'μ'), + (0x1D79C, 'M', u'ν'), + (0x1D79D, 'M', u'ξ'), + (0x1D79E, 'M', u'ο'), + (0x1D79F, 'M', u'π'), + (0x1D7A0, 'M', u'ρ'), + (0x1D7A1, 'M', u'θ'), + (0x1D7A2, 'M', u'σ'), + (0x1D7A3, 'M', u'τ'), + (0x1D7A4, 'M', u'υ'), + (0x1D7A5, 'M', u'φ'), + (0x1D7A6, 'M', u'χ'), + (0x1D7A7, 'M', u'ψ'), + (0x1D7A8, 'M', u'ω'), + (0x1D7A9, 'M', u'∇'), + (0x1D7AA, 'M', u'α'), + (0x1D7AB, 'M', u'β'), + (0x1D7AC, 'M', u'γ'), + (0x1D7AD, 'M', u'δ'), + (0x1D7AE, 'M', u'ε'), + (0x1D7AF, 'M', u'ζ'), + (0x1D7B0, 'M', u'η'), + (0x1D7B1, 'M', u'θ'), + (0x1D7B2, 'M', u'ι'), + (0x1D7B3, 'M', u'κ'), + (0x1D7B4, 'M', u'λ'), + (0x1D7B5, 'M', u'μ'), + (0x1D7B6, 'M', u'ν'), + (0x1D7B7, 'M', u'ξ'), + (0x1D7B8, 'M', u'ο'), + (0x1D7B9, 'M', u'π'), + (0x1D7BA, 'M', u'ρ'), + (0x1D7BB, 'M', u'σ'), + (0x1D7BD, 'M', u'τ'), + (0x1D7BE, 'M', u'υ'), + (0x1D7BF, 'M', u'φ'), + (0x1D7C0, 'M', u'χ'), + (0x1D7C1, 'M', u'ψ'), + (0x1D7C2, 'M', u'ω'), + (0x1D7C3, 'M', u'∂'), + (0x1D7C4, 'M', u'ε'), + (0x1D7C5, 'M', u'θ'), + (0x1D7C6, 'M', u'κ'), + (0x1D7C7, 'M', u'φ'), + (0x1D7C8, 'M', u'ρ'), + (0x1D7C9, 'M', u'π'), + (0x1D7CA, 'M', u'ϝ'), + (0x1D7CC, 'X'), + (0x1D7CE, 'M', u'0'), + (0x1D7CF, 'M', u'1'), + (0x1D7D0, 'M', u'2'), + (0x1D7D1, 'M', u'3'), + (0x1D7D2, 'M', u'4'), + (0x1D7D3, 'M', u'5'), + (0x1D7D4, 'M', u'6'), + (0x1D7D5, 'M', u'7'), + (0x1D7D6, 'M', u'8'), + (0x1D7D7, 'M', u'9'), + (0x1D7D8, 'M', u'0'), + (0x1D7D9, 'M', u'1'), + (0x1D7DA, 'M', u'2'), + (0x1D7DB, 'M', u'3'), + (0x1D7DC, 'M', u'4'), + (0x1D7DD, 'M', u'5'), + (0x1D7DE, 'M', u'6'), + (0x1D7DF, 'M', u'7'), + (0x1D7E0, 'M', u'8'), + (0x1D7E1, 'M', u'9'), + (0x1D7E2, 'M', u'0'), + (0x1D7E3, 'M', u'1'), + (0x1D7E4, 'M', u'2'), + (0x1D7E5, 'M', u'3'), + (0x1D7E6, 'M', u'4'), + (0x1D7E7, 'M', u'5'), + (0x1D7E8, 'M', u'6'), + (0x1D7E9, 'M', u'7'), + (0x1D7EA, 'M', u'8'), + (0x1D7EB, 'M', u'9'), + (0x1D7EC, 'M', u'0'), + (0x1D7ED, 'M', u'1'), + (0x1D7EE, 'M', u'2'), + (0x1D7EF, 'M', u'3'), + (0x1D7F0, 'M', u'4'), + (0x1D7F1, 'M', u'5'), + (0x1D7F2, 'M', u'6'), + (0x1D7F3, 'M', u'7'), + (0x1D7F4, 'M', u'8'), + (0x1D7F5, 'M', u'9'), + (0x1D7F6, 'M', u'0'), + (0x1D7F7, 'M', u'1'), + (0x1D7F8, 'M', u'2'), + (0x1D7F9, 'M', u'3'), + (0x1D7FA, 'M', u'4'), + (0x1D7FB, 'M', u'5'), + ] + +def _seg_68(): + return [ + (0x1D7FC, 'M', u'6'), + (0x1D7FD, 'M', u'7'), + (0x1D7FE, 'M', u'8'), + (0x1D7FF, 'M', u'9'), + (0x1D800, 'V'), + (0x1DA8C, 'X'), + (0x1DA9B, 'V'), + (0x1DAA0, 'X'), + (0x1DAA1, 'V'), + (0x1DAB0, 'X'), + (0x1E000, 'V'), + (0x1E007, 'X'), + (0x1E008, 'V'), + (0x1E019, 'X'), + (0x1E01B, 'V'), + (0x1E022, 'X'), + (0x1E023, 'V'), + (0x1E025, 'X'), + (0x1E026, 'V'), + (0x1E02B, 'X'), + (0x1E800, 'V'), + (0x1E8C5, 'X'), + (0x1E8C7, 'V'), + (0x1E8D7, 'X'), + (0x1E900, 'M', u'𞤢'), + (0x1E901, 'M', u'𞤣'), + (0x1E902, 'M', u'𞤤'), + (0x1E903, 'M', u'𞤥'), + (0x1E904, 'M', u'𞤦'), + (0x1E905, 'M', u'𞤧'), + (0x1E906, 'M', u'𞤨'), + (0x1E907, 'M', u'𞤩'), + (0x1E908, 'M', u'𞤪'), + (0x1E909, 'M', u'𞤫'), + (0x1E90A, 'M', u'𞤬'), + (0x1E90B, 'M', u'𞤭'), + (0x1E90C, 'M', u'𞤮'), + (0x1E90D, 'M', u'𞤯'), + (0x1E90E, 'M', u'𞤰'), + (0x1E90F, 'M', u'𞤱'), + (0x1E910, 'M', u'𞤲'), + (0x1E911, 'M', u'𞤳'), + (0x1E912, 'M', u'𞤴'), + (0x1E913, 'M', u'𞤵'), + (0x1E914, 'M', u'𞤶'), + (0x1E915, 'M', u'𞤷'), + (0x1E916, 'M', u'𞤸'), + (0x1E917, 'M', u'𞤹'), + (0x1E918, 'M', u'𞤺'), + (0x1E919, 'M', u'𞤻'), + (0x1E91A, 'M', u'𞤼'), + (0x1E91B, 'M', u'𞤽'), + (0x1E91C, 'M', u'𞤾'), + (0x1E91D, 'M', u'𞤿'), + (0x1E91E, 'M', u'𞥀'), + (0x1E91F, 'M', u'𞥁'), + (0x1E920, 'M', u'𞥂'), + (0x1E921, 'M', u'𞥃'), + (0x1E922, 'V'), + (0x1E94B, 'X'), + (0x1E950, 'V'), + (0x1E95A, 'X'), + (0x1E95E, 'V'), + (0x1E960, 'X'), + (0x1EE00, 'M', u'ا'), + (0x1EE01, 'M', u'ب'), + (0x1EE02, 'M', u'ج'), + (0x1EE03, 'M', u'د'), + (0x1EE04, 'X'), + (0x1EE05, 'M', u'و'), + (0x1EE06, 'M', u'ز'), + (0x1EE07, 'M', u'ح'), + (0x1EE08, 'M', u'ط'), + (0x1EE09, 'M', u'ي'), + (0x1EE0A, 'M', u'ك'), + (0x1EE0B, 'M', u'ل'), + (0x1EE0C, 'M', u'م'), + (0x1EE0D, 'M', u'ن'), + (0x1EE0E, 'M', u'س'), + (0x1EE0F, 'M', u'ع'), + (0x1EE10, 'M', u'ف'), + (0x1EE11, 'M', u'ص'), + (0x1EE12, 'M', u'ق'), + (0x1EE13, 'M', u'ر'), + (0x1EE14, 'M', u'ش'), + (0x1EE15, 'M', u'ت'), + (0x1EE16, 'M', u'ث'), + (0x1EE17, 'M', u'خ'), + (0x1EE18, 'M', u'ذ'), + (0x1EE19, 'M', u'ض'), + (0x1EE1A, 'M', u'ظ'), + (0x1EE1B, 'M', u'غ'), + (0x1EE1C, 'M', u'ٮ'), + (0x1EE1D, 'M', u'ں'), + (0x1EE1E, 'M', u'ڡ'), + (0x1EE1F, 'M', u'ٯ'), + (0x1EE20, 'X'), + (0x1EE21, 'M', u'ب'), + (0x1EE22, 'M', u'ج'), + (0x1EE23, 'X'), + ] + +def _seg_69(): + return [ + (0x1EE24, 'M', u'ه'), + (0x1EE25, 'X'), + (0x1EE27, 'M', u'ح'), + (0x1EE28, 'X'), + (0x1EE29, 'M', u'ي'), + (0x1EE2A, 'M', u'ك'), + (0x1EE2B, 'M', u'ل'), + (0x1EE2C, 'M', u'م'), + (0x1EE2D, 'M', u'ن'), + (0x1EE2E, 'M', u'س'), + (0x1EE2F, 'M', u'ع'), + (0x1EE30, 'M', u'ف'), + (0x1EE31, 'M', u'ص'), + (0x1EE32, 'M', u'ق'), + (0x1EE33, 'X'), + (0x1EE34, 'M', u'ش'), + (0x1EE35, 'M', u'ت'), + (0x1EE36, 'M', u'ث'), + (0x1EE37, 'M', u'خ'), + (0x1EE38, 'X'), + (0x1EE39, 'M', u'ض'), + (0x1EE3A, 'X'), + (0x1EE3B, 'M', u'غ'), + (0x1EE3C, 'X'), + (0x1EE42, 'M', u'ج'), + (0x1EE43, 'X'), + (0x1EE47, 'M', u'ح'), + (0x1EE48, 'X'), + (0x1EE49, 'M', u'ي'), + (0x1EE4A, 'X'), + (0x1EE4B, 'M', u'ل'), + (0x1EE4C, 'X'), + (0x1EE4D, 'M', u'ن'), + (0x1EE4E, 'M', u'س'), + (0x1EE4F, 'M', u'ع'), + (0x1EE50, 'X'), + (0x1EE51, 'M', u'ص'), + (0x1EE52, 'M', u'ق'), + (0x1EE53, 'X'), + (0x1EE54, 'M', u'ش'), + (0x1EE55, 'X'), + (0x1EE57, 'M', u'خ'), + (0x1EE58, 'X'), + (0x1EE59, 'M', u'ض'), + (0x1EE5A, 'X'), + (0x1EE5B, 'M', u'غ'), + (0x1EE5C, 'X'), + (0x1EE5D, 'M', u'ں'), + (0x1EE5E, 'X'), + (0x1EE5F, 'M', u'ٯ'), + (0x1EE60, 'X'), + (0x1EE61, 'M', u'ب'), + (0x1EE62, 'M', u'ج'), + (0x1EE63, 'X'), + (0x1EE64, 'M', u'ه'), + (0x1EE65, 'X'), + (0x1EE67, 'M', u'ح'), + (0x1EE68, 'M', u'ط'), + (0x1EE69, 'M', u'ي'), + (0x1EE6A, 'M', u'ك'), + (0x1EE6B, 'X'), + (0x1EE6C, 'M', u'م'), + (0x1EE6D, 'M', u'ن'), + (0x1EE6E, 'M', u'س'), + (0x1EE6F, 'M', u'ع'), + (0x1EE70, 'M', u'ف'), + (0x1EE71, 'M', u'ص'), + (0x1EE72, 'M', u'ق'), + (0x1EE73, 'X'), + (0x1EE74, 'M', u'ش'), + (0x1EE75, 'M', u'ت'), + (0x1EE76, 'M', u'ث'), + (0x1EE77, 'M', u'خ'), + (0x1EE78, 'X'), + (0x1EE79, 'M', u'ض'), + (0x1EE7A, 'M', u'ظ'), + (0x1EE7B, 'M', u'غ'), + (0x1EE7C, 'M', u'ٮ'), + (0x1EE7D, 'X'), + (0x1EE7E, 'M', u'ڡ'), + (0x1EE7F, 'X'), + (0x1EE80, 'M', u'ا'), + (0x1EE81, 'M', u'ب'), + (0x1EE82, 'M', u'ج'), + (0x1EE83, 'M', u'د'), + (0x1EE84, 'M', u'ه'), + (0x1EE85, 'M', u'و'), + (0x1EE86, 'M', u'ز'), + (0x1EE87, 'M', u'ح'), + (0x1EE88, 'M', u'ط'), + (0x1EE89, 'M', u'ي'), + (0x1EE8A, 'X'), + (0x1EE8B, 'M', u'ل'), + (0x1EE8C, 'M', u'م'), + (0x1EE8D, 'M', u'ن'), + (0x1EE8E, 'M', u'س'), + (0x1EE8F, 'M', u'ع'), + (0x1EE90, 'M', u'ف'), + (0x1EE91, 'M', u'ص'), + (0x1EE92, 'M', u'ق'), + ] + +def _seg_70(): + return [ + (0x1EE93, 'M', u'ر'), + (0x1EE94, 'M', u'ش'), + (0x1EE95, 'M', u'ت'), + (0x1EE96, 'M', u'ث'), + (0x1EE97, 'M', u'خ'), + (0x1EE98, 'M', u'ذ'), + (0x1EE99, 'M', u'ض'), + (0x1EE9A, 'M', u'ظ'), + (0x1EE9B, 'M', u'غ'), + (0x1EE9C, 'X'), + (0x1EEA1, 'M', u'ب'), + (0x1EEA2, 'M', u'ج'), + (0x1EEA3, 'M', u'د'), + (0x1EEA4, 'X'), + (0x1EEA5, 'M', u'و'), + (0x1EEA6, 'M', u'ز'), + (0x1EEA7, 'M', u'ح'), + (0x1EEA8, 'M', u'ط'), + (0x1EEA9, 'M', u'ي'), + (0x1EEAA, 'X'), + (0x1EEAB, 'M', u'ل'), + (0x1EEAC, 'M', u'م'), + (0x1EEAD, 'M', u'ن'), + (0x1EEAE, 'M', u'س'), + (0x1EEAF, 'M', u'ع'), + (0x1EEB0, 'M', u'ف'), + (0x1EEB1, 'M', u'ص'), + (0x1EEB2, 'M', u'ق'), + (0x1EEB3, 'M', u'ر'), + (0x1EEB4, 'M', u'ش'), + (0x1EEB5, 'M', u'ت'), + (0x1EEB6, 'M', u'ث'), + (0x1EEB7, 'M', u'خ'), + (0x1EEB8, 'M', u'ذ'), + (0x1EEB9, 'M', u'ض'), + (0x1EEBA, 'M', u'ظ'), + (0x1EEBB, 'M', u'غ'), + (0x1EEBC, 'X'), + (0x1EEF0, 'V'), + (0x1EEF2, 'X'), + (0x1F000, 'V'), + (0x1F02C, 'X'), + (0x1F030, 'V'), + (0x1F094, 'X'), + (0x1F0A0, 'V'), + (0x1F0AF, 'X'), + (0x1F0B1, 'V'), + (0x1F0C0, 'X'), + (0x1F0C1, 'V'), + (0x1F0D0, 'X'), + (0x1F0D1, 'V'), + (0x1F0F6, 'X'), + (0x1F101, '3', u'0,'), + (0x1F102, '3', u'1,'), + (0x1F103, '3', u'2,'), + (0x1F104, '3', u'3,'), + (0x1F105, '3', u'4,'), + (0x1F106, '3', u'5,'), + (0x1F107, '3', u'6,'), + (0x1F108, '3', u'7,'), + (0x1F109, '3', u'8,'), + (0x1F10A, '3', u'9,'), + (0x1F10B, 'V'), + (0x1F10D, 'X'), + (0x1F110, '3', u'(a)'), + (0x1F111, '3', u'(b)'), + (0x1F112, '3', u'(c)'), + (0x1F113, '3', u'(d)'), + (0x1F114, '3', u'(e)'), + (0x1F115, '3', u'(f)'), + (0x1F116, '3', u'(g)'), + (0x1F117, '3', u'(h)'), + (0x1F118, '3', u'(i)'), + (0x1F119, '3', u'(j)'), + (0x1F11A, '3', u'(k)'), + (0x1F11B, '3', u'(l)'), + (0x1F11C, '3', u'(m)'), + (0x1F11D, '3', u'(n)'), + (0x1F11E, '3', u'(o)'), + (0x1F11F, '3', u'(p)'), + (0x1F120, '3', u'(q)'), + (0x1F121, '3', u'(r)'), + (0x1F122, '3', u'(s)'), + (0x1F123, '3', u'(t)'), + (0x1F124, '3', u'(u)'), + (0x1F125, '3', u'(v)'), + (0x1F126, '3', u'(w)'), + (0x1F127, '3', u'(x)'), + (0x1F128, '3', u'(y)'), + (0x1F129, '3', u'(z)'), + (0x1F12A, 'M', u'〔s〕'), + (0x1F12B, 'M', u'c'), + (0x1F12C, 'M', u'r'), + (0x1F12D, 'M', u'cd'), + (0x1F12E, 'M', u'wz'), + (0x1F12F, 'X'), + (0x1F130, 'M', u'a'), + (0x1F131, 'M', u'b'), + (0x1F132, 'M', u'c'), + (0x1F133, 'M', u'd'), + ] + +def _seg_71(): + return [ + (0x1F134, 'M', u'e'), + (0x1F135, 'M', u'f'), + (0x1F136, 'M', u'g'), + (0x1F137, 'M', u'h'), + (0x1F138, 'M', u'i'), + (0x1F139, 'M', u'j'), + (0x1F13A, 'M', u'k'), + (0x1F13B, 'M', u'l'), + (0x1F13C, 'M', u'm'), + (0x1F13D, 'M', u'n'), + (0x1F13E, 'M', u'o'), + (0x1F13F, 'M', u'p'), + (0x1F140, 'M', u'q'), + (0x1F141, 'M', u'r'), + (0x1F142, 'M', u's'), + (0x1F143, 'M', u't'), + (0x1F144, 'M', u'u'), + (0x1F145, 'M', u'v'), + (0x1F146, 'M', u'w'), + (0x1F147, 'M', u'x'), + (0x1F148, 'M', u'y'), + (0x1F149, 'M', u'z'), + (0x1F14A, 'M', u'hv'), + (0x1F14B, 'M', u'mv'), + (0x1F14C, 'M', u'sd'), + (0x1F14D, 'M', u'ss'), + (0x1F14E, 'M', u'ppv'), + (0x1F14F, 'M', u'wc'), + (0x1F150, 'V'), + (0x1F16A, 'M', u'mc'), + (0x1F16B, 'M', u'md'), + (0x1F16C, 'X'), + (0x1F170, 'V'), + (0x1F190, 'M', u'dj'), + (0x1F191, 'V'), + (0x1F1AD, 'X'), + (0x1F1E6, 'V'), + (0x1F200, 'M', u'ほか'), + (0x1F201, 'M', u'ココ'), + (0x1F202, 'M', u'サ'), + (0x1F203, 'X'), + (0x1F210, 'M', u'手'), + (0x1F211, 'M', u'字'), + (0x1F212, 'M', u'双'), + (0x1F213, 'M', u'デ'), + (0x1F214, 'M', u'二'), + (0x1F215, 'M', u'多'), + (0x1F216, 'M', u'解'), + (0x1F217, 'M', u'天'), + (0x1F218, 'M', u'交'), + (0x1F219, 'M', u'映'), + (0x1F21A, 'M', u'無'), + (0x1F21B, 'M', u'料'), + (0x1F21C, 'M', u'前'), + (0x1F21D, 'M', u'後'), + (0x1F21E, 'M', u'再'), + (0x1F21F, 'M', u'新'), + (0x1F220, 'M', u'初'), + (0x1F221, 'M', u'終'), + (0x1F222, 'M', u'生'), + (0x1F223, 'M', u'販'), + (0x1F224, 'M', u'声'), + (0x1F225, 'M', u'吹'), + (0x1F226, 'M', u'演'), + (0x1F227, 'M', u'投'), + (0x1F228, 'M', u'捕'), + (0x1F229, 'M', u'一'), + (0x1F22A, 'M', u'三'), + (0x1F22B, 'M', u'遊'), + (0x1F22C, 'M', u'左'), + (0x1F22D, 'M', u'中'), + (0x1F22E, 'M', u'右'), + (0x1F22F, 'M', u'指'), + (0x1F230, 'M', u'走'), + (0x1F231, 'M', u'打'), + (0x1F232, 'M', u'禁'), + (0x1F233, 'M', u'空'), + (0x1F234, 'M', u'合'), + (0x1F235, 'M', u'満'), + (0x1F236, 'M', u'有'), + (0x1F237, 'M', u'月'), + (0x1F238, 'M', u'申'), + (0x1F239, 'M', u'割'), + (0x1F23A, 'M', u'営'), + (0x1F23B, 'M', u'配'), + (0x1F23C, 'X'), + (0x1F240, 'M', u'〔本〕'), + (0x1F241, 'M', u'〔三〕'), + (0x1F242, 'M', u'〔二〕'), + (0x1F243, 'M', u'〔安〕'), + (0x1F244, 'M', u'〔点〕'), + (0x1F245, 'M', u'〔打〕'), + (0x1F246, 'M', u'〔盗〕'), + (0x1F247, 'M', u'〔勝〕'), + (0x1F248, 'M', u'〔敗〕'), + (0x1F249, 'X'), + (0x1F250, 'M', u'得'), + (0x1F251, 'M', u'可'), + (0x1F252, 'X'), + (0x1F260, 'V'), + ] + +def _seg_72(): + return [ + (0x1F266, 'X'), + (0x1F300, 'V'), + (0x1F6D5, 'X'), + (0x1F6E0, 'V'), + (0x1F6ED, 'X'), + (0x1F6F0, 'V'), + (0x1F6F9, 'X'), + (0x1F700, 'V'), + (0x1F774, 'X'), + (0x1F780, 'V'), + (0x1F7D5, 'X'), + (0x1F800, 'V'), + (0x1F80C, 'X'), + (0x1F810, 'V'), + (0x1F848, 'X'), + (0x1F850, 'V'), + (0x1F85A, 'X'), + (0x1F860, 'V'), + (0x1F888, 'X'), + (0x1F890, 'V'), + (0x1F8AE, 'X'), + (0x1F900, 'V'), + (0x1F90C, 'X'), + (0x1F910, 'V'), + (0x1F93F, 'X'), + (0x1F940, 'V'), + (0x1F94D, 'X'), + (0x1F950, 'V'), + (0x1F96C, 'X'), + (0x1F980, 'V'), + (0x1F998, 'X'), + (0x1F9C0, 'V'), + (0x1F9C1, 'X'), + (0x1F9D0, 'V'), + (0x1F9E7, 'X'), + (0x20000, 'V'), + (0x2A6D7, 'X'), + (0x2A700, 'V'), + (0x2B735, 'X'), + (0x2B740, 'V'), + (0x2B81E, 'X'), + (0x2B820, 'V'), + (0x2CEA2, 'X'), + (0x2CEB0, 'V'), + (0x2EBE1, 'X'), + (0x2F800, 'M', u'丽'), + (0x2F801, 'M', u'丸'), + (0x2F802, 'M', u'乁'), + (0x2F803, 'M', u'𠄢'), + (0x2F804, 'M', u'你'), + (0x2F805, 'M', u'侮'), + (0x2F806, 'M', u'侻'), + (0x2F807, 'M', u'倂'), + (0x2F808, 'M', u'偺'), + (0x2F809, 'M', u'備'), + (0x2F80A, 'M', u'僧'), + (0x2F80B, 'M', u'像'), + (0x2F80C, 'M', u'㒞'), + (0x2F80D, 'M', u'𠘺'), + (0x2F80E, 'M', u'免'), + (0x2F80F, 'M', u'兔'), + (0x2F810, 'M', u'兤'), + (0x2F811, 'M', u'具'), + (0x2F812, 'M', u'𠔜'), + (0x2F813, 'M', u'㒹'), + (0x2F814, 'M', u'內'), + (0x2F815, 'M', u'再'), + (0x2F816, 'M', u'𠕋'), + (0x2F817, 'M', u'冗'), + (0x2F818, 'M', u'冤'), + (0x2F819, 'M', u'仌'), + (0x2F81A, 'M', u'冬'), + (0x2F81B, 'M', u'况'), + (0x2F81C, 'M', u'𩇟'), + (0x2F81D, 'M', u'凵'), + (0x2F81E, 'M', u'刃'), + (0x2F81F, 'M', u'㓟'), + (0x2F820, 'M', u'刻'), + (0x2F821, 'M', u'剆'), + (0x2F822, 'M', u'割'), + (0x2F823, 'M', u'剷'), + (0x2F824, 'M', u'㔕'), + (0x2F825, 'M', u'勇'), + (0x2F826, 'M', u'勉'), + (0x2F827, 'M', u'勤'), + (0x2F828, 'M', u'勺'), + (0x2F829, 'M', u'包'), + (0x2F82A, 'M', u'匆'), + (0x2F82B, 'M', u'北'), + (0x2F82C, 'M', u'卉'), + (0x2F82D, 'M', u'卑'), + (0x2F82E, 'M', u'博'), + (0x2F82F, 'M', u'即'), + (0x2F830, 'M', u'卽'), + (0x2F831, 'M', u'卿'), + (0x2F834, 'M', u'𠨬'), + (0x2F835, 'M', u'灰'), + (0x2F836, 'M', u'及'), + (0x2F837, 'M', u'叟'), + (0x2F838, 'M', u'𠭣'), + ] + +def _seg_73(): + return [ + (0x2F839, 'M', u'叫'), + (0x2F83A, 'M', u'叱'), + (0x2F83B, 'M', u'吆'), + (0x2F83C, 'M', u'咞'), + (0x2F83D, 'M', u'吸'), + (0x2F83E, 'M', u'呈'), + (0x2F83F, 'M', u'周'), + (0x2F840, 'M', u'咢'), + (0x2F841, 'M', u'哶'), + (0x2F842, 'M', u'唐'), + (0x2F843, 'M', u'啓'), + (0x2F844, 'M', u'啣'), + (0x2F845, 'M', u'善'), + (0x2F847, 'M', u'喙'), + (0x2F848, 'M', u'喫'), + (0x2F849, 'M', u'喳'), + (0x2F84A, 'M', u'嗂'), + (0x2F84B, 'M', u'圖'), + (0x2F84C, 'M', u'嘆'), + (0x2F84D, 'M', u'圗'), + (0x2F84E, 'M', u'噑'), + (0x2F84F, 'M', u'噴'), + (0x2F850, 'M', u'切'), + (0x2F851, 'M', u'壮'), + (0x2F852, 'M', u'城'), + (0x2F853, 'M', u'埴'), + (0x2F854, 'M', u'堍'), + (0x2F855, 'M', u'型'), + (0x2F856, 'M', u'堲'), + (0x2F857, 'M', u'報'), + (0x2F858, 'M', u'墬'), + (0x2F859, 'M', u'𡓤'), + (0x2F85A, 'M', u'売'), + (0x2F85B, 'M', u'壷'), + (0x2F85C, 'M', u'夆'), + (0x2F85D, 'M', u'多'), + (0x2F85E, 'M', u'夢'), + (0x2F85F, 'M', u'奢'), + (0x2F860, 'M', u'𡚨'), + (0x2F861, 'M', u'𡛪'), + (0x2F862, 'M', u'姬'), + (0x2F863, 'M', u'娛'), + (0x2F864, 'M', u'娧'), + (0x2F865, 'M', u'姘'), + (0x2F866, 'M', u'婦'), + (0x2F867, 'M', u'㛮'), + (0x2F868, 'X'), + (0x2F869, 'M', u'嬈'), + (0x2F86A, 'M', u'嬾'), + (0x2F86C, 'M', u'𡧈'), + (0x2F86D, 'M', u'寃'), + (0x2F86E, 'M', u'寘'), + (0x2F86F, 'M', u'寧'), + (0x2F870, 'M', u'寳'), + (0x2F871, 'M', u'𡬘'), + (0x2F872, 'M', u'寿'), + (0x2F873, 'M', u'将'), + (0x2F874, 'X'), + (0x2F875, 'M', u'尢'), + (0x2F876, 'M', u'㞁'), + (0x2F877, 'M', u'屠'), + (0x2F878, 'M', u'屮'), + (0x2F879, 'M', u'峀'), + (0x2F87A, 'M', u'岍'), + (0x2F87B, 'M', u'𡷤'), + (0x2F87C, 'M', u'嵃'), + (0x2F87D, 'M', u'𡷦'), + (0x2F87E, 'M', u'嵮'), + (0x2F87F, 'M', u'嵫'), + (0x2F880, 'M', u'嵼'), + (0x2F881, 'M', u'巡'), + (0x2F882, 'M', u'巢'), + (0x2F883, 'M', u'㠯'), + (0x2F884, 'M', u'巽'), + (0x2F885, 'M', u'帨'), + (0x2F886, 'M', u'帽'), + (0x2F887, 'M', u'幩'), + (0x2F888, 'M', u'㡢'), + (0x2F889, 'M', u'𢆃'), + (0x2F88A, 'M', u'㡼'), + (0x2F88B, 'M', u'庰'), + (0x2F88C, 'M', u'庳'), + (0x2F88D, 'M', u'庶'), + (0x2F88E, 'M', u'廊'), + (0x2F88F, 'M', u'𪎒'), + (0x2F890, 'M', u'廾'), + (0x2F891, 'M', u'𢌱'), + (0x2F893, 'M', u'舁'), + (0x2F894, 'M', u'弢'), + (0x2F896, 'M', u'㣇'), + (0x2F897, 'M', u'𣊸'), + (0x2F898, 'M', u'𦇚'), + (0x2F899, 'M', u'形'), + (0x2F89A, 'M', u'彫'), + (0x2F89B, 'M', u'㣣'), + (0x2F89C, 'M', u'徚'), + (0x2F89D, 'M', u'忍'), + (0x2F89E, 'M', u'志'), + (0x2F89F, 'M', u'忹'), + (0x2F8A0, 'M', u'悁'), + ] + +def _seg_74(): + return [ + (0x2F8A1, 'M', u'㤺'), + (0x2F8A2, 'M', u'㤜'), + (0x2F8A3, 'M', u'悔'), + (0x2F8A4, 'M', u'𢛔'), + (0x2F8A5, 'M', u'惇'), + (0x2F8A6, 'M', u'慈'), + (0x2F8A7, 'M', u'慌'), + (0x2F8A8, 'M', u'慎'), + (0x2F8A9, 'M', u'慌'), + (0x2F8AA, 'M', u'慺'), + (0x2F8AB, 'M', u'憎'), + (0x2F8AC, 'M', u'憲'), + (0x2F8AD, 'M', u'憤'), + (0x2F8AE, 'M', u'憯'), + (0x2F8AF, 'M', u'懞'), + (0x2F8B0, 'M', u'懲'), + (0x2F8B1, 'M', u'懶'), + (0x2F8B2, 'M', u'成'), + (0x2F8B3, 'M', u'戛'), + (0x2F8B4, 'M', u'扝'), + (0x2F8B5, 'M', u'抱'), + (0x2F8B6, 'M', u'拔'), + (0x2F8B7, 'M', u'捐'), + (0x2F8B8, 'M', u'𢬌'), + (0x2F8B9, 'M', u'挽'), + (0x2F8BA, 'M', u'拼'), + (0x2F8BB, 'M', u'捨'), + (0x2F8BC, 'M', u'掃'), + (0x2F8BD, 'M', u'揤'), + (0x2F8BE, 'M', u'𢯱'), + (0x2F8BF, 'M', u'搢'), + (0x2F8C0, 'M', u'揅'), + (0x2F8C1, 'M', u'掩'), + (0x2F8C2, 'M', u'㨮'), + (0x2F8C3, 'M', u'摩'), + (0x2F8C4, 'M', u'摾'), + (0x2F8C5, 'M', u'撝'), + (0x2F8C6, 'M', u'摷'), + (0x2F8C7, 'M', u'㩬'), + (0x2F8C8, 'M', u'敏'), + (0x2F8C9, 'M', u'敬'), + (0x2F8CA, 'M', u'𣀊'), + (0x2F8CB, 'M', u'旣'), + (0x2F8CC, 'M', u'書'), + (0x2F8CD, 'M', u'晉'), + (0x2F8CE, 'M', u'㬙'), + (0x2F8CF, 'M', u'暑'), + (0x2F8D0, 'M', u'㬈'), + (0x2F8D1, 'M', u'㫤'), + (0x2F8D2, 'M', u'冒'), + (0x2F8D3, 'M', u'冕'), + (0x2F8D4, 'M', u'最'), + (0x2F8D5, 'M', u'暜'), + (0x2F8D6, 'M', u'肭'), + (0x2F8D7, 'M', u'䏙'), + (0x2F8D8, 'M', u'朗'), + (0x2F8D9, 'M', u'望'), + (0x2F8DA, 'M', u'朡'), + (0x2F8DB, 'M', u'杞'), + (0x2F8DC, 'M', u'杓'), + (0x2F8DD, 'M', u'𣏃'), + (0x2F8DE, 'M', u'㭉'), + (0x2F8DF, 'M', u'柺'), + (0x2F8E0, 'M', u'枅'), + (0x2F8E1, 'M', u'桒'), + (0x2F8E2, 'M', u'梅'), + (0x2F8E3, 'M', u'𣑭'), + (0x2F8E4, 'M', u'梎'), + (0x2F8E5, 'M', u'栟'), + (0x2F8E6, 'M', u'椔'), + (0x2F8E7, 'M', u'㮝'), + (0x2F8E8, 'M', u'楂'), + (0x2F8E9, 'M', u'榣'), + (0x2F8EA, 'M', u'槪'), + (0x2F8EB, 'M', u'檨'), + (0x2F8EC, 'M', u'𣚣'), + (0x2F8ED, 'M', u'櫛'), + (0x2F8EE, 'M', u'㰘'), + (0x2F8EF, 'M', u'次'), + (0x2F8F0, 'M', u'𣢧'), + (0x2F8F1, 'M', u'歔'), + (0x2F8F2, 'M', u'㱎'), + (0x2F8F3, 'M', u'歲'), + (0x2F8F4, 'M', u'殟'), + (0x2F8F5, 'M', u'殺'), + (0x2F8F6, 'M', u'殻'), + (0x2F8F7, 'M', u'𣪍'), + (0x2F8F8, 'M', u'𡴋'), + (0x2F8F9, 'M', u'𣫺'), + (0x2F8FA, 'M', u'汎'), + (0x2F8FB, 'M', u'𣲼'), + (0x2F8FC, 'M', u'沿'), + (0x2F8FD, 'M', u'泍'), + (0x2F8FE, 'M', u'汧'), + (0x2F8FF, 'M', u'洖'), + (0x2F900, 'M', u'派'), + (0x2F901, 'M', u'海'), + (0x2F902, 'M', u'流'), + (0x2F903, 'M', u'浩'), + (0x2F904, 'M', u'浸'), + ] + +def _seg_75(): + return [ + (0x2F905, 'M', u'涅'), + (0x2F906, 'M', u'𣴞'), + (0x2F907, 'M', u'洴'), + (0x2F908, 'M', u'港'), + (0x2F909, 'M', u'湮'), + (0x2F90A, 'M', u'㴳'), + (0x2F90B, 'M', u'滋'), + (0x2F90C, 'M', u'滇'), + (0x2F90D, 'M', u'𣻑'), + (0x2F90E, 'M', u'淹'), + (0x2F90F, 'M', u'潮'), + (0x2F910, 'M', u'𣽞'), + (0x2F911, 'M', u'𣾎'), + (0x2F912, 'M', u'濆'), + (0x2F913, 'M', u'瀹'), + (0x2F914, 'M', u'瀞'), + (0x2F915, 'M', u'瀛'), + (0x2F916, 'M', u'㶖'), + (0x2F917, 'M', u'灊'), + (0x2F918, 'M', u'災'), + (0x2F919, 'M', u'灷'), + (0x2F91A, 'M', u'炭'), + (0x2F91B, 'M', u'𠔥'), + (0x2F91C, 'M', u'煅'), + (0x2F91D, 'M', u'𤉣'), + (0x2F91E, 'M', u'熜'), + (0x2F91F, 'X'), + (0x2F920, 'M', u'爨'), + (0x2F921, 'M', u'爵'), + (0x2F922, 'M', u'牐'), + (0x2F923, 'M', u'𤘈'), + (0x2F924, 'M', u'犀'), + (0x2F925, 'M', u'犕'), + (0x2F926, 'M', u'𤜵'), + (0x2F927, 'M', u'𤠔'), + (0x2F928, 'M', u'獺'), + (0x2F929, 'M', u'王'), + (0x2F92A, 'M', u'㺬'), + (0x2F92B, 'M', u'玥'), + (0x2F92C, 'M', u'㺸'), + (0x2F92E, 'M', u'瑇'), + (0x2F92F, 'M', u'瑜'), + (0x2F930, 'M', u'瑱'), + (0x2F931, 'M', u'璅'), + (0x2F932, 'M', u'瓊'), + (0x2F933, 'M', u'㼛'), + (0x2F934, 'M', u'甤'), + (0x2F935, 'M', u'𤰶'), + (0x2F936, 'M', u'甾'), + (0x2F937, 'M', u'𤲒'), + (0x2F938, 'M', u'異'), + (0x2F939, 'M', u'𢆟'), + (0x2F93A, 'M', u'瘐'), + (0x2F93B, 'M', u'𤾡'), + (0x2F93C, 'M', u'𤾸'), + (0x2F93D, 'M', u'𥁄'), + (0x2F93E, 'M', u'㿼'), + (0x2F93F, 'M', u'䀈'), + (0x2F940, 'M', u'直'), + (0x2F941, 'M', u'𥃳'), + (0x2F942, 'M', u'𥃲'), + (0x2F943, 'M', u'𥄙'), + (0x2F944, 'M', u'𥄳'), + (0x2F945, 'M', u'眞'), + (0x2F946, 'M', u'真'), + (0x2F948, 'M', u'睊'), + (0x2F949, 'M', u'䀹'), + (0x2F94A, 'M', u'瞋'), + (0x2F94B, 'M', u'䁆'), + (0x2F94C, 'M', u'䂖'), + (0x2F94D, 'M', u'𥐝'), + (0x2F94E, 'M', u'硎'), + (0x2F94F, 'M', u'碌'), + (0x2F950, 'M', u'磌'), + (0x2F951, 'M', u'䃣'), + (0x2F952, 'M', u'𥘦'), + (0x2F953, 'M', u'祖'), + (0x2F954, 'M', u'𥚚'), + (0x2F955, 'M', u'𥛅'), + (0x2F956, 'M', u'福'), + (0x2F957, 'M', u'秫'), + (0x2F958, 'M', u'䄯'), + (0x2F959, 'M', u'穀'), + (0x2F95A, 'M', u'穊'), + (0x2F95B, 'M', u'穏'), + (0x2F95C, 'M', u'𥥼'), + (0x2F95D, 'M', u'𥪧'), + (0x2F95F, 'X'), + (0x2F960, 'M', u'䈂'), + (0x2F961, 'M', u'𥮫'), + (0x2F962, 'M', u'篆'), + (0x2F963, 'M', u'築'), + (0x2F964, 'M', u'䈧'), + (0x2F965, 'M', u'𥲀'), + (0x2F966, 'M', u'糒'), + (0x2F967, 'M', u'䊠'), + (0x2F968, 'M', u'糨'), + (0x2F969, 'M', u'糣'), + (0x2F96A, 'M', u'紀'), + (0x2F96B, 'M', u'𥾆'), + ] + +def _seg_76(): + return [ + (0x2F96C, 'M', u'絣'), + (0x2F96D, 'M', u'䌁'), + (0x2F96E, 'M', u'緇'), + (0x2F96F, 'M', u'縂'), + (0x2F970, 'M', u'繅'), + (0x2F971, 'M', u'䌴'), + (0x2F972, 'M', u'𦈨'), + (0x2F973, 'M', u'𦉇'), + (0x2F974, 'M', u'䍙'), + (0x2F975, 'M', u'𦋙'), + (0x2F976, 'M', u'罺'), + (0x2F977, 'M', u'𦌾'), + (0x2F978, 'M', u'羕'), + (0x2F979, 'M', u'翺'), + (0x2F97A, 'M', u'者'), + (0x2F97B, 'M', u'𦓚'), + (0x2F97C, 'M', u'𦔣'), + (0x2F97D, 'M', u'聠'), + (0x2F97E, 'M', u'𦖨'), + (0x2F97F, 'M', u'聰'), + (0x2F980, 'M', u'𣍟'), + (0x2F981, 'M', u'䏕'), + (0x2F982, 'M', u'育'), + (0x2F983, 'M', u'脃'), + (0x2F984, 'M', u'䐋'), + (0x2F985, 'M', u'脾'), + (0x2F986, 'M', u'媵'), + (0x2F987, 'M', u'𦞧'), + (0x2F988, 'M', u'𦞵'), + (0x2F989, 'M', u'𣎓'), + (0x2F98A, 'M', u'𣎜'), + (0x2F98B, 'M', u'舁'), + (0x2F98C, 'M', u'舄'), + (0x2F98D, 'M', u'辞'), + (0x2F98E, 'M', u'䑫'), + (0x2F98F, 'M', u'芑'), + (0x2F990, 'M', u'芋'), + (0x2F991, 'M', u'芝'), + (0x2F992, 'M', u'劳'), + (0x2F993, 'M', u'花'), + (0x2F994, 'M', u'芳'), + (0x2F995, 'M', u'芽'), + (0x2F996, 'M', u'苦'), + (0x2F997, 'M', u'𦬼'), + (0x2F998, 'M', u'若'), + (0x2F999, 'M', u'茝'), + (0x2F99A, 'M', u'荣'), + (0x2F99B, 'M', u'莭'), + (0x2F99C, 'M', u'茣'), + (0x2F99D, 'M', u'莽'), + (0x2F99E, 'M', u'菧'), + (0x2F99F, 'M', u'著'), + (0x2F9A0, 'M', u'荓'), + (0x2F9A1, 'M', u'菊'), + (0x2F9A2, 'M', u'菌'), + (0x2F9A3, 'M', u'菜'), + (0x2F9A4, 'M', u'𦰶'), + (0x2F9A5, 'M', u'𦵫'), + (0x2F9A6, 'M', u'𦳕'), + (0x2F9A7, 'M', u'䔫'), + (0x2F9A8, 'M', u'蓱'), + (0x2F9A9, 'M', u'蓳'), + (0x2F9AA, 'M', u'蔖'), + (0x2F9AB, 'M', u'𧏊'), + (0x2F9AC, 'M', u'蕤'), + (0x2F9AD, 'M', u'𦼬'), + (0x2F9AE, 'M', u'䕝'), + (0x2F9AF, 'M', u'䕡'), + (0x2F9B0, 'M', u'𦾱'), + (0x2F9B1, 'M', u'𧃒'), + (0x2F9B2, 'M', u'䕫'), + (0x2F9B3, 'M', u'虐'), + (0x2F9B4, 'M', u'虜'), + (0x2F9B5, 'M', u'虧'), + (0x2F9B6, 'M', u'虩'), + (0x2F9B7, 'M', u'蚩'), + (0x2F9B8, 'M', u'蚈'), + (0x2F9B9, 'M', u'蜎'), + (0x2F9BA, 'M', u'蛢'), + (0x2F9BB, 'M', u'蝹'), + (0x2F9BC, 'M', u'蜨'), + (0x2F9BD, 'M', u'蝫'), + (0x2F9BE, 'M', u'螆'), + (0x2F9BF, 'X'), + (0x2F9C0, 'M', u'蟡'), + (0x2F9C1, 'M', u'蠁'), + (0x2F9C2, 'M', u'䗹'), + (0x2F9C3, 'M', u'衠'), + (0x2F9C4, 'M', u'衣'), + (0x2F9C5, 'M', u'𧙧'), + (0x2F9C6, 'M', u'裗'), + (0x2F9C7, 'M', u'裞'), + (0x2F9C8, 'M', u'䘵'), + (0x2F9C9, 'M', u'裺'), + (0x2F9CA, 'M', u'㒻'), + (0x2F9CB, 'M', u'𧢮'), + (0x2F9CC, 'M', u'𧥦'), + (0x2F9CD, 'M', u'䚾'), + (0x2F9CE, 'M', u'䛇'), + (0x2F9CF, 'M', u'誠'), + ] + +def _seg_77(): + return [ + (0x2F9D0, 'M', u'諭'), + (0x2F9D1, 'M', u'變'), + (0x2F9D2, 'M', u'豕'), + (0x2F9D3, 'M', u'𧲨'), + (0x2F9D4, 'M', u'貫'), + (0x2F9D5, 'M', u'賁'), + (0x2F9D6, 'M', u'贛'), + (0x2F9D7, 'M', u'起'), + (0x2F9D8, 'M', u'𧼯'), + (0x2F9D9, 'M', u'𠠄'), + (0x2F9DA, 'M', u'跋'), + (0x2F9DB, 'M', u'趼'), + (0x2F9DC, 'M', u'跰'), + (0x2F9DD, 'M', u'𠣞'), + (0x2F9DE, 'M', u'軔'), + (0x2F9DF, 'M', u'輸'), + (0x2F9E0, 'M', u'𨗒'), + (0x2F9E1, 'M', u'𨗭'), + (0x2F9E2, 'M', u'邔'), + (0x2F9E3, 'M', u'郱'), + (0x2F9E4, 'M', u'鄑'), + (0x2F9E5, 'M', u'𨜮'), + (0x2F9E6, 'M', u'鄛'), + (0x2F9E7, 'M', u'鈸'), + (0x2F9E8, 'M', u'鋗'), + (0x2F9E9, 'M', u'鋘'), + (0x2F9EA, 'M', u'鉼'), + (0x2F9EB, 'M', u'鏹'), + (0x2F9EC, 'M', u'鐕'), + (0x2F9ED, 'M', u'𨯺'), + (0x2F9EE, 'M', u'開'), + (0x2F9EF, 'M', u'䦕'), + (0x2F9F0, 'M', u'閷'), + (0x2F9F1, 'M', u'𨵷'), + (0x2F9F2, 'M', u'䧦'), + (0x2F9F3, 'M', u'雃'), + (0x2F9F4, 'M', u'嶲'), + (0x2F9F5, 'M', u'霣'), + (0x2F9F6, 'M', u'𩅅'), + (0x2F9F7, 'M', u'𩈚'), + (0x2F9F8, 'M', u'䩮'), + (0x2F9F9, 'M', u'䩶'), + (0x2F9FA, 'M', u'韠'), + (0x2F9FB, 'M', u'𩐊'), + (0x2F9FC, 'M', u'䪲'), + (0x2F9FD, 'M', u'𩒖'), + (0x2F9FE, 'M', u'頋'), + (0x2FA00, 'M', u'頩'), + (0x2FA01, 'M', u'𩖶'), + (0x2FA02, 'M', u'飢'), + (0x2FA03, 'M', u'䬳'), + (0x2FA04, 'M', u'餩'), + (0x2FA05, 'M', u'馧'), + (0x2FA06, 'M', u'駂'), + (0x2FA07, 'M', u'駾'), + (0x2FA08, 'M', u'䯎'), + (0x2FA09, 'M', u'𩬰'), + (0x2FA0A, 'M', u'鬒'), + (0x2FA0B, 'M', u'鱀'), + (0x2FA0C, 'M', u'鳽'), + (0x2FA0D, 'M', u'䳎'), + (0x2FA0E, 'M', u'䳭'), + (0x2FA0F, 'M', u'鵧'), + (0x2FA10, 'M', u'𪃎'), + (0x2FA11, 'M', u'䳸'), + (0x2FA12, 'M', u'𪄅'), + (0x2FA13, 'M', u'𪈎'), + (0x2FA14, 'M', u'𪊑'), + (0x2FA15, 'M', u'麻'), + (0x2FA16, 'M', u'䵖'), + (0x2FA17, 'M', u'黹'), + (0x2FA18, 'M', u'黾'), + (0x2FA19, 'M', u'鼅'), + (0x2FA1A, 'M', u'鼏'), + (0x2FA1B, 'M', u'鼖'), + (0x2FA1C, 'M', u'鼻'), + (0x2FA1D, 'M', u'𪘀'), + (0x2FA1E, 'X'), + (0xE0100, 'I'), + (0xE01F0, 'X'), + ] + +uts46data = tuple( + _seg_0() + + _seg_1() + + _seg_2() + + _seg_3() + + _seg_4() + + _seg_5() + + _seg_6() + + _seg_7() + + _seg_8() + + _seg_9() + + _seg_10() + + _seg_11() + + _seg_12() + + _seg_13() + + _seg_14() + + _seg_15() + + _seg_16() + + _seg_17() + + _seg_18() + + _seg_19() + + _seg_20() + + _seg_21() + + _seg_22() + + _seg_23() + + _seg_24() + + _seg_25() + + _seg_26() + + _seg_27() + + _seg_28() + + _seg_29() + + _seg_30() + + _seg_31() + + _seg_32() + + _seg_33() + + _seg_34() + + _seg_35() + + _seg_36() + + _seg_37() + + _seg_38() + + _seg_39() + + _seg_40() + + _seg_41() + + _seg_42() + + _seg_43() + + _seg_44() + + _seg_45() + + _seg_46() + + _seg_47() + + _seg_48() + + _seg_49() + + _seg_50() + + _seg_51() + + _seg_52() + + _seg_53() + + _seg_54() + + _seg_55() + + _seg_56() + + _seg_57() + + _seg_58() + + _seg_59() + + _seg_60() + + _seg_61() + + _seg_62() + + _seg_63() + + _seg_64() + + _seg_65() + + _seg_66() + + _seg_67() + + _seg_68() + + _seg_69() + + _seg_70() + + _seg_71() + + _seg_72() + + _seg_73() + + _seg_74() + + _seg_75() + + _seg_76() + + _seg_77() +) diff --git a/lib/influxdb/__init__.py b/lib/influxdb/__init__.py new file mode 100644 index 0000000..03f7458 --- /dev/null +++ b/lib/influxdb/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +"""Initialize the influxdb package.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from .client import InfluxDBClient +from .dataframe_client import DataFrameClient +from .helper import SeriesHelper + + +__all__ = [ + 'InfluxDBClient', + 'DataFrameClient', + 'SeriesHelper', +] + + +__version__ = '5.2.0' diff --git a/lib/influxdb/_dataframe_client.py b/lib/influxdb/_dataframe_client.py new file mode 100644 index 0000000..646f298 --- /dev/null +++ b/lib/influxdb/_dataframe_client.py @@ -0,0 +1,452 @@ +# -*- coding: utf-8 -*- +"""DataFrame client for InfluxDB.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import math +from collections import defaultdict + +import pandas as pd +import numpy as np + +from .client import InfluxDBClient +from .line_protocol import _escape_tag + + +def _pandas_time_unit(time_precision): + unit = time_precision + if time_precision == 'm': + unit = 'ms' + elif time_precision == 'u': + unit = 'us' + elif time_precision == 'n': + unit = 'ns' + assert unit in ('s', 'ms', 'us', 'ns') + return unit + + +def _escape_pandas_series(s): + return s.apply(lambda v: _escape_tag(v)) + + +class DataFrameClient(InfluxDBClient): + """DataFrameClient instantiates InfluxDBClient to connect to the backend. + + The ``DataFrameClient`` object holds information necessary to connect + to InfluxDB. Requests can be made to InfluxDB directly through the client. + The client reads and writes from pandas DataFrames. + """ + + EPOCH = pd.Timestamp('1970-01-01 00:00:00.000+00:00') + + def write_points(self, + dataframe, + measurement, + tags=None, + tag_columns=None, + field_columns=None, + time_precision=None, + database=None, + retention_policy=None, + batch_size=None, + protocol='line', + numeric_precision=None): + """Write to multiple time series names. + + :param dataframe: data points in a DataFrame + :param measurement: name of measurement + :param tags: dictionary of tags, with string key-values + :param time_precision: [Optional, default None] Either 's', 'ms', 'u' + or 'n'. + :param batch_size: [Optional] Value to write the points in batches + instead of all at one time. Useful for when doing data dumps from + one database to another or when doing a massive write operation + :type batch_size: int + :param protocol: Protocol for writing data. Either 'line' or 'json'. + :param numeric_precision: Precision for floating point values. + Either None, 'full' or some int, where int is the desired decimal + precision. 'full' preserves full precision for int and float + datatypes. Defaults to None, which preserves 14-15 significant + figures for float and all significant figures for int datatypes. + """ + if tag_columns is None: + tag_columns = [] + + if field_columns is None: + field_columns = [] + + if batch_size: + number_batches = int(math.ceil(len(dataframe) / float(batch_size))) + + for batch in range(number_batches): + start_index = batch * batch_size + end_index = (batch + 1) * batch_size + + if protocol == 'line': + points = self._convert_dataframe_to_lines( + dataframe.iloc[start_index:end_index].copy(), + measurement=measurement, + global_tags=tags, + time_precision=time_precision, + tag_columns=tag_columns, + field_columns=field_columns, + numeric_precision=numeric_precision) + else: + points = self._convert_dataframe_to_json( + dataframe.iloc[start_index:end_index].copy(), + measurement=measurement, + tags=tags, + time_precision=time_precision, + tag_columns=tag_columns, + field_columns=field_columns) + + super(DataFrameClient, self).write_points( + points, + time_precision, + database, + retention_policy, + protocol=protocol) + + return True + + if protocol == 'line': + points = self._convert_dataframe_to_lines( + dataframe, + measurement=measurement, + global_tags=tags, + tag_columns=tag_columns, + field_columns=field_columns, + time_precision=time_precision, + numeric_precision=numeric_precision) + else: + points = self._convert_dataframe_to_json( + dataframe, + measurement=measurement, + tags=tags, + time_precision=time_precision, + tag_columns=tag_columns, + field_columns=field_columns) + + super(DataFrameClient, self).write_points( + points, + time_precision, + database, + retention_policy, + protocol=protocol) + + return True + + def query(self, + query, + params=None, + epoch=None, + expected_response_code=200, + database=None, + raise_errors=True, + chunked=False, + chunk_size=0, + dropna=True): + """ + Quering data into a DataFrame. + + :param query: the actual query string + :param params: additional parameters for the request, defaults to {} + :param epoch: response timestamps to be in epoch format either 'h', + 'm', 's', 'ms', 'u', or 'ns',defaults to `None` which is + RFC3339 UTC format with nanosecond precision + :param expected_response_code: the expected status code of response, + defaults to 200 + :param database: database to query, defaults to None + :param raise_errors: Whether or not to raise exceptions when InfluxDB + returns errors, defaults to True + :param chunked: Enable to use chunked responses from InfluxDB. + With ``chunked`` enabled, one ResultSet is returned per chunk + containing all results within that chunk + :param chunk_size: Size of each chunk to tell InfluxDB to use. + :param dropna: drop columns where all values are missing + :returns: the queried data + :rtype: :class:`~.ResultSet` + """ + query_args = dict(params=params, + epoch=epoch, + expected_response_code=expected_response_code, + raise_errors=raise_errors, + chunked=chunked, + database=database, + chunk_size=chunk_size) + results = super(DataFrameClient, self).query(query, **query_args) + if query.strip().upper().startswith("SELECT"): + if len(results) > 0: + return self._to_dataframe(results, dropna) + else: + return {} + else: + return results + + def _to_dataframe(self, rs, dropna=True): + result = defaultdict(list) + if isinstance(rs, list): + return map(self._to_dataframe, rs) + + for key, data in rs.items(): + name, tags = key + if tags is None: + key = name + else: + key = (name, tuple(sorted(tags.items()))) + df = pd.DataFrame(data) + df.time = pd.to_datetime(df.time) + df.set_index('time', inplace=True) + df.index = df.index.tz_localize('UTC') + df.index.name = None + result[key].append(df) + for key, data in result.items(): + df = pd.concat(data).sort_index() + if dropna: + df.dropna(how='all', axis=1, inplace=True) + result[key] = df + + return result + + @staticmethod + def _convert_dataframe_to_json(dataframe, + measurement, + tags=None, + tag_columns=None, + field_columns=None, + time_precision=None): + + if not isinstance(dataframe, pd.DataFrame): + raise TypeError('Must be DataFrame, but type was: {0}.' + .format(type(dataframe))) + if not (isinstance(dataframe.index, pd.PeriodIndex) or + isinstance(dataframe.index, pd.DatetimeIndex)): + raise TypeError('Must be DataFrame with DatetimeIndex or ' + 'PeriodIndex.') + + # Make sure tags and tag columns are correctly typed + tag_columns = tag_columns if tag_columns is not None else [] + field_columns = field_columns if field_columns is not None else [] + tags = tags if tags is not None else {} + # Assume field columns are all columns not included in tag columns + if not field_columns: + field_columns = list( + set(dataframe.columns).difference(set(tag_columns))) + + dataframe.index = pd.to_datetime(dataframe.index) + if dataframe.index.tzinfo is None: + dataframe.index = dataframe.index.tz_localize('UTC') + + # Convert column to strings + dataframe.columns = dataframe.columns.astype('str') + + # Convert dtype for json serialization + dataframe = dataframe.astype('object') + + precision_factor = { + "n": 1, + "u": 1e3, + "ms": 1e6, + "s": 1e9, + "m": 1e9 * 60, + "h": 1e9 * 3600, + }.get(time_precision, 1) + + points = [ + {'measurement': measurement, + 'tags': dict(list(tag.items()) + list(tags.items())), + 'fields': rec, + 'time': np.int64(ts.value / precision_factor)} + for ts, tag, rec in zip(dataframe.index, + dataframe[tag_columns].to_dict('record'), + dataframe[field_columns].to_dict('record')) + ] + + return points + + def _convert_dataframe_to_lines(self, + dataframe, + measurement, + field_columns=None, + tag_columns=None, + global_tags=None, + time_precision=None, + numeric_precision=None): + + dataframe = dataframe.dropna(how='all').copy() + if len(dataframe) == 0: + return [] + + if not isinstance(dataframe, pd.DataFrame): + raise TypeError('Must be DataFrame, but type was: {0}.' + .format(type(dataframe))) + if not (isinstance(dataframe.index, pd.PeriodIndex) or + isinstance(dataframe.index, pd.DatetimeIndex)): + raise TypeError('Must be DataFrame with DatetimeIndex or ' + 'PeriodIndex.') + + dataframe = dataframe.rename( + columns={item: _escape_tag(item) for item in dataframe.columns}) + # Create a Series of columns for easier indexing + column_series = pd.Series(dataframe.columns) + + if field_columns is None: + field_columns = [] + + if tag_columns is None: + tag_columns = [] + + if global_tags is None: + global_tags = {} + + # Make sure field_columns and tag_columns are lists + field_columns = list(field_columns) if list(field_columns) else [] + tag_columns = list(tag_columns) if list(tag_columns) else [] + + # If field columns but no tag columns, assume rest of columns are tags + if field_columns and (not tag_columns): + tag_columns = list(column_series[~column_series.isin( + field_columns)]) + + # If no field columns, assume non-tag columns are fields + if not field_columns: + field_columns = list(column_series[~column_series.isin( + tag_columns)]) + + precision_factor = { + "n": 1, + "u": 1e3, + "ms": 1e6, + "s": 1e9, + "m": 1e9 * 60, + "h": 1e9 * 3600, + }.get(time_precision, 1) + + # Make array of timestamp ints + if isinstance(dataframe.index, pd.PeriodIndex): + time = ((dataframe.index.to_timestamp().values.astype(np.int64) / + precision_factor).astype(np.int64).astype(str)) + else: + time = ((pd.to_datetime(dataframe.index).values.astype(np.int64) / + precision_factor).astype(np.int64).astype(str)) + + # If tag columns exist, make an array of formatted tag keys and values + if tag_columns: + + # Make global_tags as tag_columns + if global_tags: + for tag in global_tags: + dataframe[tag] = global_tags[tag] + tag_columns.append(tag) + + tag_df = dataframe[tag_columns] + tag_df = tag_df.fillna('') # replace NA with empty string + tag_df = tag_df.sort_index(axis=1) + tag_df = self._stringify_dataframe( + tag_df, numeric_precision, datatype='tag') + + # join preprendded tags, leaving None values out + tags = tag_df.apply( + lambda s: [',' + s.name + '=' + v if v else '' for v in s]) + tags = tags.sum(axis=1) + + del tag_df + elif global_tags: + tag_string = ''.join( + [",{}={}".format(k, _escape_tag(v)) if v else '' + for k, v in sorted(global_tags.items())] + ) + tags = pd.Series(tag_string, index=dataframe.index) + else: + tags = '' + + # Make an array of formatted field keys and values + field_df = dataframe[field_columns] + # Keep the positions where Null values are found + mask_null = field_df.isnull().values + + field_df = self._stringify_dataframe(field_df, + numeric_precision, + datatype='field') + + field_df = (field_df.columns.values + '=').tolist() + field_df + field_df[field_df.columns[1:]] = ',' + field_df[ + field_df.columns[1:]] + field_df = field_df.where(~mask_null, '') # drop Null entries + fields = field_df.sum(axis=1) + del field_df + + # Generate line protocol string + measurement = _escape_tag(measurement) + points = (measurement + tags + ' ' + fields + ' ' + time).tolist() + return points + + @staticmethod + def _stringify_dataframe(dframe, numeric_precision, datatype='field'): + + # Prevent modification of input dataframe + dframe = dframe.copy() + + # Find int and string columns for field-type data + int_columns = dframe.select_dtypes(include=['integer']).columns + string_columns = dframe.select_dtypes(include=['object']).columns + + # Convert dframe to string + if numeric_precision is None: + # If no precision specified, convert directly to string (fast) + dframe = dframe.astype(str) + elif numeric_precision == 'full': + # If full precision, use repr to get full float precision + float_columns = (dframe.select_dtypes( + include=['floating']).columns) + nonfloat_columns = dframe.columns[~dframe.columns.isin( + float_columns)] + dframe[float_columns] = dframe[float_columns].applymap(repr) + dframe[nonfloat_columns] = (dframe[nonfloat_columns].astype(str)) + elif isinstance(numeric_precision, int): + # If precision is specified, round to appropriate precision + float_columns = (dframe.select_dtypes( + include=['floating']).columns) + nonfloat_columns = dframe.columns[~dframe.columns.isin( + float_columns)] + dframe[float_columns] = (dframe[float_columns].round( + numeric_precision)) + + # If desired precision is > 10 decimal places, need to use repr + if numeric_precision > 10: + dframe[float_columns] = (dframe[float_columns].applymap(repr)) + dframe[nonfloat_columns] = (dframe[nonfloat_columns] + .astype(str)) + else: + dframe = dframe.astype(str) + else: + raise ValueError('Invalid numeric precision.') + + if datatype == 'field': + # If dealing with fields, format ints and strings correctly + dframe[int_columns] += 'i' + dframe[string_columns] = '"' + dframe[string_columns] + '"' + elif datatype == 'tag': + dframe = dframe.apply(_escape_pandas_series) + + dframe.columns = dframe.columns.astype(str) + + return dframe + + def _datetime_to_epoch(self, datetime, time_precision='s'): + seconds = (datetime - self.EPOCH).total_seconds() + if time_precision == 'h': + return seconds / 3600 + elif time_precision == 'm': + return seconds / 60 + elif time_precision == 's': + return seconds + elif time_precision == 'ms': + return seconds * 1e3 + elif time_precision == 'u': + return seconds * 1e6 + elif time_precision == 'n': + return seconds * 1e9 diff --git a/lib/influxdb/chunked_json.py b/lib/influxdb/chunked_json.py new file mode 100644 index 0000000..4e40f01 --- /dev/null +++ b/lib/influxdb/chunked_json.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +"""Module to generate chunked JSON replies.""" + +# +# Author: Adrian Sampson +# Source: https://gist.github.com/sampsyo/920215 +# + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import json + + +def loads(s): + """Generate a sequence of JSON values from a string.""" + _decoder = json.JSONDecoder() + + while s: + s = s.strip() + obj, pos = _decoder.raw_decode(s) + if not pos: + raise ValueError('no JSON object found at %i' % pos) + yield obj + s = s[pos:] diff --git a/lib/influxdb/client.py b/lib/influxdb/client.py new file mode 100644 index 0000000..8f8b14a --- /dev/null +++ b/lib/influxdb/client.py @@ -0,0 +1,980 @@ +# -*- coding: utf-8 -*- +"""Python client for InfluxDB.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import time +import random + +import json +import socket +import requests +import requests.exceptions +from six.moves import xrange +from six.moves.urllib.parse import urlparse + +from influxdb.line_protocol import make_lines, quote_ident, quote_literal +from influxdb.resultset import ResultSet +from .exceptions import InfluxDBClientError +from .exceptions import InfluxDBServerError + + +class InfluxDBClient(object): + """InfluxDBClient primary client object to connect InfluxDB. + + The :class:`~.InfluxDBClient` object holds information necessary to + connect to InfluxDB. Requests can be made to InfluxDB directly through + the client. + + :param host: hostname to connect to InfluxDB, defaults to 'localhost' + :type host: str + :param port: port to connect to InfluxDB, defaults to 8086 + :type port: int + :param username: user to connect, defaults to 'root' + :type username: str + :param password: password of the user, defaults to 'root' + :type password: str + :param pool_size: urllib3 connection pool size, defaults to 10. + :type pool_size: int + :param database: database name to connect to, defaults to None + :type database: str + :param ssl: use https instead of http to connect to InfluxDB, defaults to + False + :type ssl: bool + :param verify_ssl: verify SSL certificates for HTTPS requests, defaults to + False + :type verify_ssl: bool + :param timeout: number of seconds Requests will wait for your client to + establish a connection, defaults to None + :type timeout: int + :param retries: number of retries your client will try before aborting, + defaults to 3. 0 indicates try until success + :type retries: int + :param use_udp: use UDP to connect to InfluxDB, defaults to False + :type use_udp: bool + :param udp_port: UDP port to connect to InfluxDB, defaults to 4444 + :type udp_port: int + :param proxies: HTTP(S) proxy to use for Requests, defaults to {} + :type proxies: dict + :param path: path of InfluxDB on the server to connect, defaults to '' + :type path: str + """ + + def __init__(self, + host='localhost', + port=8086, + username='root', + password='root', + database=None, + ssl=False, + verify_ssl=False, + timeout=None, + retries=3, + use_udp=False, + udp_port=4444, + proxies=None, + pool_size=10, + path='', + ): + """Construct a new InfluxDBClient object.""" + self.__host = host + self.__port = int(port) + self._username = username + self._password = password + self._database = database + self._timeout = timeout + self._retries = retries + + self._verify_ssl = verify_ssl + + self.__use_udp = use_udp + self.__udp_port = udp_port + self._session = requests.Session() + adapter = requests.adapters.HTTPAdapter( + pool_connections=int(pool_size), + pool_maxsize=int(pool_size) + ) + + if use_udp: + self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + if not path: + self.__path = '' + elif path[0] == '/': + self.__path = path + else: + self.__path = '/' + path + + self._scheme = "http" + + if ssl is True: + self._scheme = "https" + + self._session.mount(self._scheme + '://', adapter) + + if proxies is None: + self._proxies = {} + else: + self._proxies = proxies + + self.__baseurl = "{0}://{1}:{2}{3}".format( + self._scheme, + self._host, + self._port, + self._path) + + self._headers = { + 'Content-Type': 'application/json', + 'Accept': 'text/plain' + } + + @property + def _baseurl(self): + return self.__baseurl + + @property + def _host(self): + return self.__host + + @property + def _port(self): + return self.__port + + @property + def _path(self): + return self.__path + + @property + def _udp_port(self): + return self.__udp_port + + @property + def _use_udp(self): + return self.__use_udp + + @classmethod + def from_dsn(cls, dsn, **kwargs): + r"""Generate an instance of InfluxDBClient from given data source name. + + Return an instance of :class:`~.InfluxDBClient` from the provided + data source name. Supported schemes are "influxdb", "https+influxdb" + and "udp+influxdb". Parameters for the :class:`~.InfluxDBClient` + constructor may also be passed to this method. + + :param dsn: data source name + :type dsn: string + :param kwargs: additional parameters for `InfluxDBClient` + :type kwargs: dict + :raises ValueError: if the provided DSN has any unexpected values + + :Example: + + :: + + >> cli = InfluxDBClient.from_dsn('influxdb://username:password@\ + localhost:8086/databasename', timeout=5) + >> type(cli) + + >> cli = InfluxDBClient.from_dsn('udp+influxdb://username:pass@\ + localhost:8086/databasename', timeout=5, udp_port=159) + >> print('{0._baseurl} - {0.use_udp} {0.udp_port}'.format(cli)) + http://localhost:8086 - True 159 + + .. note:: parameters provided in `**kwargs` may override dsn parameters + .. note:: when using "udp+influxdb" the specified port (if any) will + be used for the TCP connection; specify the UDP port with the + additional `udp_port` parameter (cf. examples). + """ + init_args = _parse_dsn(dsn) + host, port = init_args.pop('hosts')[0] + init_args['host'] = host + init_args['port'] = port + init_args.update(kwargs) + + return cls(**init_args) + + def switch_database(self, database): + """Change the client's database. + + :param database: the name of the database to switch to + :type database: str + """ + self._database = database + + def switch_user(self, username, password): + """Change the client's username. + + :param username: the username to switch to + :type username: str + :param password: the password for the username + :type password: str + """ + self._username = username + self._password = password + + def request(self, url, method='GET', params=None, data=None, + expected_response_code=200, headers=None): + """Make a HTTP request to the InfluxDB API. + + :param url: the path of the HTTP request, e.g. write, query, etc. + :type url: str + :param method: the HTTP method for the request, defaults to GET + :type method: str + :param params: additional parameters for the request, defaults to None + :type params: dict + :param data: the data of the request, defaults to None + :type data: str + :param expected_response_code: the expected response code of + the request, defaults to 200 + :type expected_response_code: int + :param headers: headers to add to the request + :type headers: dict + :returns: the response from the request + :rtype: :class:`requests.Response` + :raises InfluxDBServerError: if the response code is any server error + code (5xx) + :raises InfluxDBClientError: if the response code is not the + same as `expected_response_code` and is not a server error code + """ + url = "{0}/{1}".format(self._baseurl, url) + + if headers is None: + headers = self._headers + + if params is None: + params = {} + + if isinstance(data, (dict, list)): + data = json.dumps(data) + + # Try to send the request more than once by default (see #103) + retry = True + _try = 0 + while retry: + try: + response = self._session.request( + method=method, + url=url, + auth=(self._username, self._password), + params=params, + data=data, + headers=headers, + proxies=self._proxies, + verify=self._verify_ssl, + timeout=self._timeout + ) + break + except (requests.exceptions.ConnectionError, + requests.exceptions.HTTPError, + requests.exceptions.Timeout): + _try += 1 + if self._retries != 0: + retry = _try < self._retries + if method == "POST": + time.sleep((2 ** _try) * random.random() / 100.0) + if not retry: + raise + # if there's not an error, there must have been a successful response + if 500 <= response.status_code < 600: + raise InfluxDBServerError(response.content) + elif response.status_code == expected_response_code: + return response + else: + raise InfluxDBClientError(response.content, response.status_code) + + def write(self, data, params=None, expected_response_code=204, + protocol='json'): + """Write data to InfluxDB. + + :param data: the data to be written + :type data: (if protocol is 'json') dict + (if protocol is 'line') sequence of line protocol strings + or single string + :param params: additional parameters for the request, defaults to None + :type params: dict + :param expected_response_code: the expected response code of the write + operation, defaults to 204 + :type expected_response_code: int + :param protocol: protocol of input data, either 'json' or 'line' + :type protocol: str + :returns: True, if the write operation is successful + :rtype: bool + """ + headers = self._headers + headers['Content-Type'] = 'application/octet-stream' + + if params: + precision = params.get('precision') + else: + precision = None + + if protocol == 'json': + data = make_lines(data, precision).encode('utf-8') + elif protocol == 'line': + if isinstance(data, str): + data = [data] + data = ('\n'.join(data) + '\n').encode('utf-8') + + self.request( + url="write", + method='POST', + params=params, + data=data, + expected_response_code=expected_response_code, + headers=headers + ) + return True + + @staticmethod + def _read_chunked_response(response, raise_errors=True): + result_set = {} + for line in response.iter_lines(): + if isinstance(line, bytes): + line = line.decode('utf-8') + data = json.loads(line) + for result in data.get('results', []): + for _key in result: + if isinstance(result[_key], list): + result_set.setdefault( + _key, []).extend(result[_key]) + return ResultSet(result_set, raise_errors=raise_errors) + + def query(self, + query, + params=None, + epoch=None, + expected_response_code=200, + database=None, + raise_errors=True, + chunked=False, + chunk_size=0, + method="GET"): + """Send a query to InfluxDB. + + :param query: the actual query string + :type query: str + + :param params: additional parameters for the request, + defaults to {} + :type params: dict + + :param epoch: response timestamps to be in epoch format either 'h', + 'm', 's', 'ms', 'u', or 'ns',defaults to `None` which is + RFC3339 UTC format with nanosecond precision + :type epoch: str + + :param expected_response_code: the expected status code of response, + defaults to 200 + :type expected_response_code: int + + :param database: database to query, defaults to None + :type database: str + + :param raise_errors: Whether or not to raise exceptions when InfluxDB + returns errors, defaults to True + :type raise_errors: bool + + :param chunked: Enable to use chunked responses from InfluxDB. + With ``chunked`` enabled, one ResultSet is returned per chunk + containing all results within that chunk + :type chunked: bool + + :param chunk_size: Size of each chunk to tell InfluxDB to use. + :type chunk_size: int + + :param method: the HTTP method for the request, defaults to GET + :type method: str + + :returns: the queried data + :rtype: :class:`~.ResultSet` + """ + if params is None: + params = {} + + params['q'] = query + params['db'] = database or self._database + + if epoch is not None: + params['epoch'] = epoch + + if chunked: + params['chunked'] = 'true' + if chunk_size > 0: + params['chunk_size'] = chunk_size + + if query.lower().startswith("select ") and " into " in query.lower(): + method = "POST" + + response = self.request( + url="query", + method=method, + params=params, + data=None, + expected_response_code=expected_response_code + ) + + if chunked: + return self._read_chunked_response(response) + + data = response.json() + + results = [ + ResultSet(result, raise_errors=raise_errors) + for result + in data.get('results', []) + ] + + # TODO(aviau): Always return a list. (This would be a breaking change) + if len(results) == 1: + return results[0] + + return results + + def write_points(self, + points, + time_precision=None, + database=None, + retention_policy=None, + tags=None, + batch_size=None, + protocol='json' + ): + """Write to multiple time series names. + + :param points: the list of points to be written in the database + :type points: list of dictionaries, each dictionary represents a point + :type points: (if protocol is 'json') list of dicts, where each dict + represents a point. + (if protocol is 'line') sequence of line protocol strings. + :param time_precision: Either 's', 'm', 'ms' or 'u', defaults to None + :type time_precision: str + :param database: the database to write the points to. Defaults to + the client's current database + :type database: str + :param tags: a set of key-value pairs associated with each point. Both + keys and values must be strings. These are shared tags and will be + merged with point-specific tags, defaults to None + :type tags: dict + :param retention_policy: the retention policy for the points. Defaults + to None + :type retention_policy: str + :param batch_size: value to write the points in batches + instead of all at one time. Useful for when doing data dumps from + one database to another or when doing a massive write operation, + defaults to None + :type batch_size: int + :param protocol: Protocol for writing data. Either 'line' or 'json'. + :type protocol: str + :returns: True, if the operation is successful + :rtype: bool + + .. note:: if no retention policy is specified, the default retention + policy for the database is used + """ + if batch_size and batch_size > 0: + for batch in self._batches(points, batch_size): + self._write_points(points=batch, + time_precision=time_precision, + database=database, + retention_policy=retention_policy, + tags=tags, protocol=protocol) + return True + + return self._write_points(points=points, + time_precision=time_precision, + database=database, + retention_policy=retention_policy, + tags=tags, protocol=protocol) + + def ping(self): + """Check connectivity to InfluxDB. + + :returns: The version of the InfluxDB the client is connected to + """ + response = self.request( + url="ping", + method='GET', + expected_response_code=204 + ) + + return response.headers['X-Influxdb-Version'] + + @staticmethod + def _batches(iterable, size): + for i in xrange(0, len(iterable), size): + yield iterable[i:i + size] + + def _write_points(self, + points, + time_precision, + database, + retention_policy, + tags, + protocol='json'): + if time_precision not in ['n', 'u', 'ms', 's', 'm', 'h', None]: + raise ValueError( + "Invalid time precision is given. " + "(use 'n', 'u', 'ms', 's', 'm' or 'h')") + + if protocol == 'json': + data = { + 'points': points + } + + if tags is not None: + data['tags'] = tags + else: + data = points + + params = { + 'db': database or self._database + } + + if time_precision is not None: + params['precision'] = time_precision + + if retention_policy is not None: + params['rp'] = retention_policy + + if self._use_udp: + self.send_packet( + data, protocol=protocol, time_precision=time_precision + ) + else: + self.write( + data=data, + params=params, + expected_response_code=204, + protocol=protocol + ) + + return True + + def get_list_database(self): + """Get the list of databases in InfluxDB. + + :returns: all databases in InfluxDB + :rtype: list of dictionaries + + :Example: + + :: + + >> dbs = client.get_list_database() + >> dbs + [{u'name': u'db1'}, {u'name': u'db2'}, {u'name': u'db3'}] + """ + return list(self.query("SHOW DATABASES").get_points()) + + def create_database(self, dbname): + """Create a new database in InfluxDB. + + :param dbname: the name of the database to create + :type dbname: str + """ + self.query("CREATE DATABASE {0}".format(quote_ident(dbname)), + method="POST") + + def drop_database(self, dbname): + """Drop a database from InfluxDB. + + :param dbname: the name of the database to drop + :type dbname: str + """ + self.query("DROP DATABASE {0}".format(quote_ident(dbname)), + method="POST") + + def get_list_measurements(self): + """Get the list of measurements in InfluxDB. + + :returns: all measurements in InfluxDB + :rtype: list of dictionaries + + :Example: + + :: + + >> dbs = client.get_list_measurements() + >> dbs + [{u'name': u'measurements1'}, + {u'name': u'measurements2'}, + {u'name': u'measurements3'}] + """ + return list(self.query("SHOW MEASUREMENTS").get_points()) + + def drop_measurement(self, measurement): + """Drop a measurement from InfluxDB. + + :param measurement: the name of the measurement to drop + :type measurement: str + """ + self.query("DROP MEASUREMENT {0}".format(quote_ident(measurement)), + method="POST") + + def create_retention_policy(self, name, duration, replication, + database=None, + default=False, shard_duration="0s"): + """Create a retention policy for a database. + + :param name: the name of the new retention policy + :type name: str + :param duration: the duration of the new retention policy. + Durations such as 1h, 90m, 12h, 7d, and 4w, are all supported + and mean 1 hour, 90 minutes, 12 hours, 7 day, and 4 weeks, + respectively. For infinite retention - meaning the data will + never be deleted - use 'INF' for duration. + The minimum retention period is 1 hour. + :type duration: str + :param replication: the replication of the retention policy + :type replication: str + :param database: the database for which the retention policy is + created. Defaults to current client's database + :type database: str + :param default: whether or not to set the policy as default + :type default: bool + :param shard_duration: the shard duration of the retention policy. + Durations such as 1h, 90m, 12h, 7d, and 4w, are all supported and + mean 1 hour, 90 minutes, 12 hours, 7 day, and 4 weeks, + respectively. Infinite retention is not supported. As a workaround, + specify a "1000w" duration to achieve an extremely long shard group + duration. Defaults to "0s", which is interpreted by the database + to mean the default value given the duration. + The minimum shard group duration is 1 hour. + :type shard_duration: str + """ + query_string = \ + "CREATE RETENTION POLICY {0} ON {1} " \ + "DURATION {2} REPLICATION {3} SHARD DURATION {4}".format( + quote_ident(name), quote_ident(database or self._database), + duration, replication, shard_duration) + + if default is True: + query_string += " DEFAULT" + + self.query(query_string, method="POST") + + def alter_retention_policy(self, name, database=None, + duration=None, replication=None, + default=None, shard_duration=None): + """Modify an existing retention policy for a database. + + :param name: the name of the retention policy to modify + :type name: str + :param database: the database for which the retention policy is + modified. Defaults to current client's database + :type database: str + :param duration: the new duration of the existing retention policy. + Durations such as 1h, 90m, 12h, 7d, and 4w, are all supported + and mean 1 hour, 90 minutes, 12 hours, 7 day, and 4 weeks, + respectively. For infinite retention, meaning the data will + never be deleted, use 'INF' for duration. + The minimum retention period is 1 hour. + :type duration: str + :param replication: the new replication of the existing + retention policy + :type replication: int + :param default: whether or not to set the modified policy as default + :type default: bool + :param shard_duration: the shard duration of the retention policy. + Durations such as 1h, 90m, 12h, 7d, and 4w, are all supported and + mean 1 hour, 90 minutes, 12 hours, 7 day, and 4 weeks, + respectively. Infinite retention is not supported. As a workaround, + specify a "1000w" duration to achieve an extremely long shard group + duration. + The minimum shard group duration is 1 hour. + :type shard_duration: str + + .. note:: at least one of duration, replication, or default flag + should be set. Otherwise the operation will fail. + """ + query_string = ( + "ALTER RETENTION POLICY {0} ON {1}" + ).format(quote_ident(name), + quote_ident(database or self._database), shard_duration) + if duration: + query_string += " DURATION {0}".format(duration) + if shard_duration: + query_string += " SHARD DURATION {0}".format(shard_duration) + if replication: + query_string += " REPLICATION {0}".format(replication) + if default is True: + query_string += " DEFAULT" + + self.query(query_string, method="POST") + + def drop_retention_policy(self, name, database=None): + """Drop an existing retention policy for a database. + + :param name: the name of the retention policy to drop + :type name: str + :param database: the database for which the retention policy is + dropped. Defaults to current client's database + :type database: str + """ + query_string = ( + "DROP RETENTION POLICY {0} ON {1}" + ).format(quote_ident(name), quote_ident(database or self._database)) + self.query(query_string, method="POST") + + def get_list_retention_policies(self, database=None): + """Get the list of retention policies for a database. + + :param database: the name of the database, defaults to the client's + current database + :type database: str + :returns: all retention policies for the database + :rtype: list of dictionaries + + :Example: + + :: + + >> ret_policies = client.get_list_retention_policies('my_db') + >> ret_policies + [{u'default': True, + u'duration': u'0', + u'name': u'default', + u'replicaN': 1}] + """ + if not (database or self._database): + raise InfluxDBClientError( + "get_list_retention_policies() requires a database as a " + "parameter or the client to be using a database") + + rsp = self.query( + "SHOW RETENTION POLICIES ON {0}".format( + quote_ident(database or self._database)) + ) + return list(rsp.get_points()) + + def get_list_users(self): + """Get the list of all users in InfluxDB. + + :returns: all users in InfluxDB + :rtype: list of dictionaries + + :Example: + + :: + + >> users = client.get_list_users() + >> users + [{u'admin': True, u'user': u'user1'}, + {u'admin': False, u'user': u'user2'}, + {u'admin': False, u'user': u'user3'}] + """ + return list(self.query("SHOW USERS").get_points()) + + def create_user(self, username, password, admin=False): + """Create a new user in InfluxDB. + + :param username: the new username to create + :type username: str + :param password: the password for the new user + :type password: str + :param admin: whether the user should have cluster administration + privileges or not + :type admin: boolean + """ + text = "CREATE USER {0} WITH PASSWORD {1}".format( + quote_ident(username), quote_literal(password)) + if admin: + text += ' WITH ALL PRIVILEGES' + self.query(text, method="POST") + + def drop_user(self, username): + """Drop a user from InfluxDB. + + :param username: the username to drop + :type username: str + """ + text = "DROP USER {0}".format(quote_ident(username), method="POST") + self.query(text, method="POST") + + def set_user_password(self, username, password): + """Change the password of an existing user. + + :param username: the username who's password is being changed + :type username: str + :param password: the new password for the user + :type password: str + """ + text = "SET PASSWORD FOR {0} = {1}".format( + quote_ident(username), quote_literal(password)) + self.query(text) + + def delete_series(self, database=None, measurement=None, tags=None): + """Delete series from a database. + + Series can be filtered by measurement and tags. + + :param database: the database from which the series should be + deleted, defaults to client's current database + :type database: str + :param measurement: Delete all series from a measurement + :type measurement: str + :param tags: Delete all series that match given tags + :type tags: dict + """ + database = database or self._database + query_str = 'DROP SERIES' + if measurement: + query_str += ' FROM {0}'.format(quote_ident(measurement)) + + if tags: + tag_eq_list = ["{0}={1}".format(quote_ident(k), quote_literal(v)) + for k, v in tags.items()] + query_str += ' WHERE ' + ' AND '.join(tag_eq_list) + self.query(query_str, database=database, method="POST") + + def grant_admin_privileges(self, username): + """Grant cluster administration privileges to a user. + + :param username: the username to grant privileges to + :type username: str + + .. note:: Only a cluster administrator can create/drop databases + and manage users. + """ + text = "GRANT ALL PRIVILEGES TO {0}".format(quote_ident(username)) + self.query(text, method="POST") + + def revoke_admin_privileges(self, username): + """Revoke cluster administration privileges from a user. + + :param username: the username to revoke privileges from + :type username: str + + .. note:: Only a cluster administrator can create/ drop databases + and manage users. + """ + text = "REVOKE ALL PRIVILEGES FROM {0}".format(quote_ident(username)) + self.query(text, method="POST") + + def grant_privilege(self, privilege, database, username): + """Grant a privilege on a database to a user. + + :param privilege: the privilege to grant, one of 'read', 'write' + or 'all'. The string is case-insensitive + :type privilege: str + :param database: the database to grant the privilege on + :type database: str + :param username: the username to grant the privilege to + :type username: str + """ + text = "GRANT {0} ON {1} TO {2}".format(privilege, + quote_ident(database), + quote_ident(username)) + self.query(text, method="POST") + + def revoke_privilege(self, privilege, database, username): + """Revoke a privilege on a database from a user. + + :param privilege: the privilege to revoke, one of 'read', 'write' + or 'all'. The string is case-insensitive + :type privilege: str + :param database: the database to revoke the privilege on + :type database: str + :param username: the username to revoke the privilege from + :type username: str + """ + text = "REVOKE {0} ON {1} FROM {2}".format(privilege, + quote_ident(database), + quote_ident(username)) + self.query(text, method="POST") + + def get_list_privileges(self, username): + """Get the list of all privileges granted to given user. + + :param username: the username to get privileges of + :type username: str + + :returns: all privileges granted to given user + :rtype: list of dictionaries + + :Example: + + :: + + >> privileges = client.get_list_privileges('user1') + >> privileges + [{u'privilege': u'WRITE', u'database': u'db1'}, + {u'privilege': u'ALL PRIVILEGES', u'database': u'db2'}, + {u'privilege': u'NO PRIVILEGES', u'database': u'db3'}] + """ + text = "SHOW GRANTS FOR {0}".format(quote_ident(username)) + return list(self.query(text).get_points()) + + def send_packet(self, packet, protocol='json', time_precision=None): + """Send an UDP packet. + + :param packet: the packet to be sent + :type packet: (if protocol is 'json') dict + (if protocol is 'line') list of line protocol strings + :param protocol: protocol of input data, either 'json' or 'line' + :type protocol: str + :param time_precision: Either 's', 'm', 'ms' or 'u', defaults to None + :type time_precision: str + """ + if protocol == 'json': + data = make_lines(packet, time_precision).encode('utf-8') + elif protocol == 'line': + data = ('\n'.join(packet) + '\n').encode('utf-8') + self.udp_socket.sendto(data, (self._host, self._udp_port)) + + def close(self): + """Close http session.""" + if isinstance(self._session, requests.Session): + self._session.close() + + +def _parse_dsn(dsn): + """Parse data source name. + + This is a helper function to split the data source name provided in + the from_dsn classmethod + """ + conn_params = urlparse(dsn) + init_args = {} + scheme_info = conn_params.scheme.split('+') + if len(scheme_info) == 1: + scheme = scheme_info[0] + modifier = None + else: + modifier, scheme = scheme_info + + if scheme != 'influxdb': + raise ValueError('Unknown scheme "{0}".'.format(scheme)) + + if modifier: + if modifier == 'udp': + init_args['use_udp'] = True + elif modifier == 'https': + init_args['ssl'] = True + else: + raise ValueError('Unknown modifier "{0}".'.format(modifier)) + + netlocs = conn_params.netloc.split(',') + + init_args['hosts'] = [] + for netloc in netlocs: + parsed = _parse_netloc(netloc) + init_args['hosts'].append((parsed['host'], int(parsed['port']))) + init_args['username'] = parsed['username'] + init_args['password'] = parsed['password'] + + if conn_params.path and len(conn_params.path) > 1: + init_args['database'] = conn_params.path[1:] + + return init_args + + +def _parse_netloc(netloc): + info = urlparse("http://{0}".format(netloc)) + return {'username': info.username or None, + 'password': info.password or None, + 'host': info.hostname or 'localhost', + 'port': info.port or 8086} diff --git a/lib/influxdb/dataframe_client.py b/lib/influxdb/dataframe_client.py new file mode 100644 index 0000000..9725864 --- /dev/null +++ b/lib/influxdb/dataframe_client.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +"""DataFrame client for InfluxDB.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +__all__ = ['DataFrameClient'] + +try: + import pandas + del pandas +except ImportError as err: + from .client import InfluxDBClient + + class DataFrameClient(InfluxDBClient): + """DataFrameClient default class instantiation.""" + + err = err + + def __init__(self, *a, **kw): + """Initialize the default DataFrameClient.""" + super(DataFrameClient, self).__init__() + raise ImportError("DataFrameClient requires Pandas " + "which couldn't be imported: %s" % self.err) +else: + from ._dataframe_client import DataFrameClient diff --git a/lib/influxdb/exceptions.py b/lib/influxdb/exceptions.py new file mode 100644 index 0000000..bd71d30 --- /dev/null +++ b/lib/influxdb/exceptions.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +"""Exception handler for InfluxDBClient.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + + +class InfluxDBClientError(Exception): + """Raised when an error occurs in the request.""" + + def __init__(self, content, code=None): + """Initialize the InfluxDBClientError handler.""" + if isinstance(content, type(b'')): + content = content.decode('UTF-8', 'replace') + + if code is not None: + message = "%s: %s" % (code, content) + else: + message = content + + super(InfluxDBClientError, self).__init__( + message + ) + self.content = content + self.code = code + + +class InfluxDBServerError(Exception): + """Raised when a server error occurs.""" + + def __init__(self, content): + """Initialize the InfluxDBServerError handler.""" + super(InfluxDBServerError, self).__init__(content) diff --git a/lib/influxdb/helper.py b/lib/influxdb/helper.py new file mode 100644 index 0000000..e622526 --- /dev/null +++ b/lib/influxdb/helper.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +"""Helper class for InfluxDB.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from collections import namedtuple, defaultdict +from datetime import datetime +from warnings import warn + +import six + + +class SeriesHelper(object): + """Subclass this helper eases writing data points in bulk. + + All data points are immutable, ensuring they do not get overwritten. + Each subclass can write to its own database. + The time series names can also be based on one or more defined fields. + The field "time" can be specified when creating a point, and may be any of + the time types supported by the client (i.e. str, datetime, int). + If the time is not specified, the current system time (utc) will be used. + + Annotated example:: + + class MySeriesHelper(SeriesHelper): + class Meta: + # Meta class stores time series helper configuration. + series_name = 'events.stats.{server_name}' + # Series name must be a string, curly brackets for dynamic use. + fields = ['time', 'server_name'] + # Defines all the fields in this time series. + ### Following attributes are optional. ### + client = TestSeriesHelper.client + # Client should be an instance of InfluxDBClient. + :warning: Only used if autocommit is True. + bulk_size = 5 + # Defines the number of data points to write simultaneously. + # Only applicable if autocommit is True. + autocommit = True + # If True and no bulk_size, then will set bulk_size to 1. + + """ + + __initialized__ = False + + def __new__(cls, *args, **kwargs): + """Initialize class attributes for subsequent constructor calls. + + :note: *args and **kwargs are not explicitly used in this function, + but needed for Python 2 compatibility. + """ + if not cls.__initialized__: + cls.__initialized__ = True + try: + _meta = getattr(cls, 'Meta') + except AttributeError: + raise AttributeError( + 'Missing Meta class in {0}.'.format( + cls.__name__)) + + for attr in ['series_name', 'fields', 'tags']: + try: + setattr(cls, '_' + attr, getattr(_meta, attr)) + except AttributeError: + raise AttributeError( + 'Missing {0} in {1} Meta class.'.format( + attr, + cls.__name__)) + + cls._autocommit = getattr(_meta, 'autocommit', False) + + cls._client = getattr(_meta, 'client', None) + if cls._autocommit and not cls._client: + raise AttributeError( + 'In {0}, autocommit is set to True, but no client is set.' + .format(cls.__name__)) + + try: + cls._bulk_size = getattr(_meta, 'bulk_size') + if cls._bulk_size < 1 and cls._autocommit: + warn( + 'Definition of bulk_size in {0} forced to 1, ' + 'was less than 1.'.format(cls.__name__)) + cls._bulk_size = 1 + except AttributeError: + cls._bulk_size = -1 + else: + if not cls._autocommit: + warn( + 'Definition of bulk_size in {0} has no affect because' + ' autocommit is false.'.format(cls.__name__)) + + cls._datapoints = defaultdict(list) + + if 'time' in cls._fields: + cls._fields.remove('time') + cls._type = namedtuple(cls.__name__, + ['time'] + cls._tags + cls._fields) + cls._type.__new__.__defaults__ = (None,) * len(cls._fields) + + return super(SeriesHelper, cls).__new__(cls) + + def __init__(self, **kw): + """Call to constructor creates a new data point. + + :note: Data points written when `bulk_size` is reached per Helper. + :warning: Data points are *immutable* (`namedtuples`). + """ + cls = self.__class__ + timestamp = kw.pop('time', self._current_timestamp()) + tags = set(cls._tags) + fields = set(cls._fields) + keys = set(kw.keys()) + + # all tags should be passed, and keys - tags should be a subset of keys + if not(tags <= keys): + raise NameError( + 'Expected arguments to contain all tags {0}, instead got {1}.' + .format(cls._tags, kw.keys())) + if not(keys - tags <= fields): + raise NameError('Got arguments not in tags or fields: {0}' + .format(keys - tags - fields)) + + cls._datapoints[cls._series_name.format(**kw)].append( + cls._type(time=timestamp, **kw) + ) + + if cls._autocommit and \ + sum(len(series) for series in cls._datapoints.values()) \ + >= cls._bulk_size: + cls.commit() + + @classmethod + def commit(cls, client=None): + """Commit everything from datapoints via the client. + + :param client: InfluxDBClient instance for writing points to InfluxDB. + :attention: any provided client will supersede the class client. + :return: result of client.write_points. + """ + if not client: + client = cls._client + rtn = client.write_points(cls._json_body_()) + cls._reset_() + return rtn + + @classmethod + def _json_body_(cls): + """Return the JSON body of given datapoints. + + :return: JSON body of these datapoints. + """ + json = [] + for series_name, data in six.iteritems(cls._datapoints): + for point in data: + json_point = { + "measurement": series_name, + "fields": {}, + "tags": {}, + "time": getattr(point, "time") + } + + for field in cls._fields: + value = getattr(point, field) + if value is not None: + json_point['fields'][field] = value + + for tag in cls._tags: + json_point['tags'][tag] = getattr(point, tag) + + json.append(json_point) + return json + + @classmethod + def _reset_(cls): + """Reset data storage.""" + cls._datapoints = defaultdict(list) + + @staticmethod + def _current_timestamp(): + return datetime.utcnow() diff --git a/lib/influxdb/influxdb08/__init__.py b/lib/influxdb/influxdb08/__init__.py new file mode 100644 index 0000000..f4e6c08 --- /dev/null +++ b/lib/influxdb/influxdb08/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +"""Define the influxdb08 package.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from .client import InfluxDBClient +from .dataframe_client import DataFrameClient +from .helper import SeriesHelper + + +__all__ = [ + 'InfluxDBClient', + 'DataFrameClient', + 'SeriesHelper', +] diff --git a/lib/influxdb/influxdb08/chunked_json.py b/lib/influxdb/influxdb08/chunked_json.py new file mode 100644 index 0000000..d6847de --- /dev/null +++ b/lib/influxdb/influxdb08/chunked_json.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +"""Module to generate chunked JSON replies for influxdb08.""" + +# +# Author: Adrian Sampson +# Source: https://gist.github.com/sampsyo/920215 +# + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import json + + +def loads(s): + """Generate a sequence of JSON values from a string.""" + _decoder = json.JSONDecoder() + + while s: + s = s.strip() + obj, pos = _decoder.raw_decode(s) + if not pos: + raise ValueError('no JSON object found at %i' % pos) + yield obj + s = s[pos:] diff --git a/lib/influxdb/influxdb08/client.py b/lib/influxdb/influxdb08/client.py new file mode 100644 index 0000000..965a91d --- /dev/null +++ b/lib/influxdb/influxdb08/client.py @@ -0,0 +1,843 @@ +# -*- coding: utf-8 -*- +"""Python client for InfluxDB v0.8.""" + +import warnings + +import json +import socket +import requests +import requests.exceptions +from six.moves import xrange +from six.moves.urllib.parse import urlparse + +from influxdb import chunked_json + +session = requests.Session() + + +class InfluxDBClientError(Exception): + """Raised when an error occurs in the request.""" + + def __init__(self, content, code=-1): + """Initialize an InfluxDBClientError handler.""" + super(InfluxDBClientError, self).__init__( + "{0}: {1}".format(code, content)) + self.content = content + self.code = code + + +class InfluxDBClient(object): + """Define the standard InfluxDBClient for influxdb v0.8. + + The ``InfluxDBClient`` object holds information necessary to connect + to InfluxDB. Requests can be made to InfluxDB directly through the client. + + :param host: hostname to connect to InfluxDB, defaults to 'localhost' + :type host: string + :param port: port to connect to InfluxDB, defaults to 'localhost' + :type port: int + :param username: user to connect, defaults to 'root' + :type username: string + :param password: password of the user, defaults to 'root' + :type password: string + :param database: database name to connect to, defaults is None + :type database: string + :param ssl: use https instead of http to connect to InfluxDB, defaults is + False + :type ssl: boolean + :param verify_ssl: verify SSL certificates for HTTPS requests, defaults is + False + :type verify_ssl: boolean + :param retries: number of retries your client will try before aborting, + defaults to 3. 0 indicates try until success + :type retries: int + :param timeout: number of seconds Requests will wait for your client to + establish a connection, defaults to None + :type timeout: int + :param use_udp: use UDP to connect to InfluxDB, defaults is False + :type use_udp: int + :param udp_port: UDP port to connect to InfluxDB, defaults is 4444 + :type udp_port: int + """ + + def __init__(self, + host='localhost', + port=8086, + username='root', + password='root', + database=None, + ssl=False, + verify_ssl=False, + timeout=None, + retries=3, + use_udp=False, + udp_port=4444): + """Construct a new InfluxDBClient object.""" + self._host = host + self._port = port + self._username = username + self._password = password + self._database = database + self._timeout = timeout + self._retries = retries + + self._verify_ssl = verify_ssl + + self._use_udp = use_udp + self._udp_port = udp_port + if use_udp: + self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + self._scheme = "http" + + if ssl is True: + self._scheme = "https" + + self._baseurl = "{0}://{1}:{2}".format( + self._scheme, + self._host, + self._port) + + self._headers = { + 'Content-type': 'application/json', + 'Accept': 'text/plain'} + + @staticmethod + def from_dsn(dsn, **kwargs): + r"""Return an instaance of InfluxDBClient from given data source name. + + Returns an instance of InfluxDBClient from the provided data source + name. Supported schemes are "influxdb", "https+influxdb", + "udp+influxdb". Parameters for the InfluxDBClient constructor may be + also be passed to this function. + + Examples: + >> cli = InfluxDBClient.from_dsn('influxdb://username:password@\ + ... localhost:8086/databasename', timeout=5) + >> type(cli) + + >> cli = InfluxDBClient.from_dsn('udp+influxdb://username:pass@\ + ... localhost:8086/databasename', timeout=5, udp_port=159) + >> print('{0._baseurl} - {0.use_udp} {0.udp_port}'.format(cli)) + http://localhost:8086 - True 159 + + :param dsn: data source name + :type dsn: string + :param **kwargs: additional parameters for InfluxDBClient. + :type **kwargs: dict + :note: parameters provided in **kwargs may override dsn parameters. + :note: when using "udp+influxdb" the specified port (if any) will be + used for the TCP connection; specify the udp port with the additional + udp_port parameter (cf. examples). + :raise ValueError: if the provided DSN has any unexpected value. + + """ + init_args = {} + conn_params = urlparse(dsn) + scheme_info = conn_params.scheme.split('+') + + if len(scheme_info) == 1: + scheme = scheme_info[0] + modifier = None + else: + modifier, scheme = scheme_info + + if scheme != 'influxdb': + raise ValueError('Unknown scheme "{0}".'.format(scheme)) + + if modifier: + if modifier == 'udp': + init_args['use_udp'] = True + elif modifier == 'https': + init_args['ssl'] = True + else: + raise ValueError('Unknown modifier "{0}".'.format(modifier)) + + if conn_params.hostname: + init_args['host'] = conn_params.hostname + if conn_params.port: + init_args['port'] = conn_params.port + if conn_params.username: + init_args['username'] = conn_params.username + if conn_params.password: + init_args['password'] = conn_params.password + if conn_params.path and len(conn_params.path) > 1: + init_args['database'] = conn_params.path[1:] + + init_args.update(kwargs) + + return InfluxDBClient(**init_args) + + # Change member variables + + def switch_database(self, database): + """Change client database. + + :param database: the new database name to switch to + :type database: string + """ + self._database = database + + def switch_db(self, database): + """Change client database. + + DEPRECATED. + """ + warnings.warn( + "switch_db is deprecated, and will be removed " + "in future versions. Please use " + "``InfluxDBClient.switch_database(database)`` instead.", + FutureWarning) + return self.switch_database(database) + + def switch_user(self, username, password): + """Change client username. + + :param username: the new username to switch to + :type username: string + :param password: the new password to switch to + :type password: string + """ + self._username = username + self._password = password + + def request(self, url, method='GET', params=None, data=None, + expected_response_code=200): + """Make a http request to API.""" + url = "{0}/{1}".format(self._baseurl, url) + + if params is None: + params = {} + + auth = { + 'u': self._username, + 'p': self._password + } + + params.update(auth) + + if data is not None and not isinstance(data, str): + data = json.dumps(data) + + retry = True + _try = 0 + # Try to send the request more than once by default (see #103) + while retry: + try: + response = session.request( + method=method, + url=url, + params=params, + data=data, + headers=self._headers, + verify=self._verify_ssl, + timeout=self._timeout + ) + break + except (requests.exceptions.ConnectionError, + requests.exceptions.Timeout): + _try += 1 + if self._retries != 0: + retry = _try < self._retries + else: + raise requests.exceptions.ConnectionError + + if response.status_code == expected_response_code: + return response + else: + raise InfluxDBClientError(response.content, response.status_code) + + def write(self, data): + """Provide as convenience for influxdb v0.9.0, this may change.""" + self.request( + url="write", + method='POST', + params=None, + data=data, + expected_response_code=200 + ) + return True + + # Writing Data + # + # Assuming you have a database named foo_production you can write data + # by doing a POST to /db/foo_production/series?u=some_user&p=some_password + # with a JSON body of points. + + def write_points(self, data, time_precision='s', *args, **kwargs): + """Write to multiple time series names. + + An example data blob is: + + data = [ + { + "points": [ + [ + 12 + ] + ], + "name": "cpu_load_short", + "columns": [ + "value" + ] + } + ] + + :param data: A list of dicts in InfluxDB 0.8.x data format. + :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms' + or 'u'. + :param batch_size: [Optional] Value to write the points in batches + instead of all at one time. Useful for when doing data dumps from + one database to another or when doing a massive write operation + :type batch_size: int + + """ + def list_chunks(l, n): + """Yield successive n-sized chunks from l.""" + for i in xrange(0, len(l), n): + yield l[i:i + n] + + batch_size = kwargs.get('batch_size') + if batch_size and batch_size > 0: + for item in data: + name = item.get('name') + columns = item.get('columns') + point_list = item.get('points', []) + + for batch in list_chunks(point_list, batch_size): + item = [{ + "points": batch, + "name": name, + "columns": columns + }] + self._write_points( + data=item, + time_precision=time_precision) + return True + + return self._write_points(data=data, + time_precision=time_precision) + + def write_points_with_precision(self, data, time_precision='s'): + """Write to multiple time series names. + + DEPRECATED. + """ + warnings.warn( + "write_points_with_precision is deprecated, and will be removed " + "in future versions. Please use " + "``InfluxDBClient.write_points(time_precision='..')`` instead.", + FutureWarning) + return self._write_points(data=data, time_precision=time_precision) + + def _write_points(self, data, time_precision): + if time_precision not in ['s', 'm', 'ms', 'u']: + raise Exception( + "Invalid time precision is given. (use 's', 'm', 'ms' or 'u')") + + if self._use_udp and time_precision != 's': + raise Exception( + "InfluxDB only supports seconds precision for udp writes" + ) + + url = "db/{0}/series".format(self._database) + + params = { + 'time_precision': time_precision + } + + if self._use_udp: + self.send_packet(data) + else: + self.request( + url=url, + method='POST', + params=params, + data=data, + expected_response_code=200 + ) + + return True + + # One Time Deletes + + def delete_points(self, name): + """Delete an entire series.""" + url = "db/{0}/series/{1}".format(self._database, name) + + self.request( + url=url, + method='DELETE', + expected_response_code=204 + ) + + return True + + # Regularly Scheduled Deletes + + def create_scheduled_delete(self, json_body): + """Create schedule delete from database. + + 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, + but it is documented in http://influxdb.org/docs/api/http.html. + See also: src/api/http/api.go:l57 + + """ + raise NotImplementedError() + + # get list of deletes + # curl http://localhost:8086/db/site_dev/scheduled_deletes + # + # remove a regularly scheduled delete + # curl -X DELETE http://localhost:8086/db/site_dev/scheduled_deletes/:id + + def get_list_scheduled_delete(self): + """Get list of scheduled deletes. + + 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, + but it is documented in http://influxdb.org/docs/api/http.html. + See also: src/api/http/api.go:l57 + + """ + raise NotImplementedError() + + def remove_scheduled_delete(self, delete_id): + """Remove scheduled delete. + + 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, + but it is documented in http://influxdb.org/docs/api/http.html. + See also: src/api/http/api.go:l57 + + """ + raise NotImplementedError() + + def query(self, query, time_precision='s', chunked=False): + """Query data from the influxdb v0.8 database. + + :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms' + or 'u'. + :param chunked: [Optional, default=False] True if the data shall be + retrieved in chunks, False otherwise. + """ + return self._query(query, time_precision=time_precision, + chunked=chunked) + + # Querying Data + # + # GET db/:name/series. It takes five parameters + def _query(self, query, time_precision='s', chunked=False): + if time_precision not in ['s', 'm', 'ms', 'u']: + raise Exception( + "Invalid time precision is given. (use 's', 'm', 'ms' or 'u')") + + if chunked is True: + chunked_param = 'true' + else: + chunked_param = 'false' + + # Build the URL of the series to query + url = "db/{0}/series".format(self._database) + + params = { + 'q': query, + 'time_precision': time_precision, + 'chunked': chunked_param + } + + response = self.request( + url=url, + method='GET', + params=params, + expected_response_code=200 + ) + + if chunked: + try: + decoded = chunked_json.loads(response.content.decode()) + except UnicodeDecodeError: + decoded = chunked_json.loads(response.content.decode('utf-8')) + + return list(decoded) + + return response.json() + + # Creating and Dropping Databases + # + # ### create a database + # curl -X POST http://localhost:8086/db -d '{"name": "site_development"}' + # + # ### drop a database + # curl -X DELETE http://localhost:8086/db/site_development + + def create_database(self, database): + """Create a database on the InfluxDB server. + + :param database: the name of the database to create + :type database: string + :rtype: boolean + """ + url = "db" + + data = {'name': database} + + self.request( + url=url, + method='POST', + data=data, + expected_response_code=201 + ) + + return True + + def delete_database(self, database): + """Drop a database on the InfluxDB server. + + :param database: the name of the database to delete + :type database: string + :rtype: boolean + """ + url = "db/{0}".format(database) + + self.request( + url=url, + method='DELETE', + expected_response_code=204 + ) + + return True + + # ### get list of databases + # curl -X GET http://localhost:8086/db + + def get_list_database(self): + """Get the list of databases.""" + url = "db" + + response = self.request( + url=url, + method='GET', + expected_response_code=200 + ) + + return response.json() + + def get_database_list(self): + """Get the list of databases. + + DEPRECATED. + """ + warnings.warn( + "get_database_list is deprecated, and will be removed " + "in future versions. Please use " + "``InfluxDBClient.get_list_database`` instead.", + FutureWarning) + return self.get_list_database() + + def delete_series(self, series): + """Drop a series on the InfluxDB server. + + :param series: the name of the series to delete + :type series: string + :rtype: boolean + """ + url = "db/{0}/series/{1}".format( + self._database, + series + ) + + self.request( + url=url, + method='DELETE', + expected_response_code=204 + ) + + return True + + def get_list_series(self): + """Get a list of all time series in a database.""" + response = self._query('list series') + return [series[1] for series in response[0]['points']] + + def get_list_continuous_queries(self): + """Get a list of continuous queries.""" + response = self._query('list continuous queries') + return [query[2] for query in response[0]['points']] + + # Security + # get list of cluster admins + # curl http://localhost:8086/cluster_admins?u=root&p=root + + # add cluster admin + # curl -X POST http://localhost:8086/cluster_admins?u=root&p=root \ + # -d '{"name": "paul", "password": "i write teh docz"}' + + # update cluster admin password + # curl -X POST http://localhost:8086/cluster_admins/paul?u=root&p=root \ + # -d '{"password": "new pass"}' + + # delete cluster admin + # curl -X DELETE http://localhost:8086/cluster_admins/paul?u=root&p=root + + # Database admins, with a database name of site_dev + # get list of database admins + # curl http://localhost:8086/db/site_dev/admins?u=root&p=root + + # add database admin + # curl -X POST http://localhost:8086/db/site_dev/admins?u=root&p=root \ + # -d '{"name": "paul", "password": "i write teh docz"}' + + # update database admin password + # curl -X POST http://localhost:8086/db/site_dev/admins/paul?u=root&p=root\ + # -d '{"password": "new pass"}' + + # delete database admin + # curl -X DELETE \ + # http://localhost:8086/db/site_dev/admins/paul?u=root&p=root + + def get_list_cluster_admins(self): + """Get list of cluster admins.""" + response = self.request( + url="cluster_admins", + method='GET', + expected_response_code=200 + ) + + return response.json() + + def add_cluster_admin(self, new_username, new_password): + """Add cluster admin.""" + data = { + 'name': new_username, + 'password': new_password + } + + self.request( + url="cluster_admins", + method='POST', + data=data, + expected_response_code=200 + ) + + return True + + def update_cluster_admin_password(self, username, new_password): + """Update cluster admin password.""" + url = "cluster_admins/{0}".format(username) + + data = { + 'password': new_password + } + + self.request( + url=url, + method='POST', + data=data, + expected_response_code=200 + ) + + return True + + def delete_cluster_admin(self, username): + """Delete cluster admin.""" + url = "cluster_admins/{0}".format(username) + + self.request( + url=url, + method='DELETE', + expected_response_code=200 + ) + + return True + + def set_database_admin(self, username): + """Set user as database admin.""" + return self.alter_database_admin(username, True) + + def unset_database_admin(self, username): + """Unset user as database admin.""" + return self.alter_database_admin(username, False) + + def alter_database_admin(self, username, is_admin): + """Alter the database admin.""" + url = "db/{0}/users/{1}".format(self._database, username) + + data = {'admin': is_admin} + + self.request( + url=url, + method='POST', + data=data, + expected_response_code=200 + ) + + return True + + def get_list_database_admins(self): + """Get list of database admins. + + 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, + but it is documented in http://influxdb.org/docs/api/http.html. + See also: src/api/http/api.go:l57 + + """ + raise NotImplementedError() + + def add_database_admin(self, new_username, new_password): + """Add cluster admin. + + 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, + but it is documented in http://influxdb.org/docs/api/http.html. + See also: src/api/http/api.go:l57 + + """ + raise NotImplementedError() + + def update_database_admin_password(self, username, new_password): + """Update database admin password. + + 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, + but it is documented in http://influxdb.org/docs/api/http.html. + See also: src/api/http/api.go:l57 + + """ + raise NotImplementedError() + + def delete_database_admin(self, username): + """Delete database admin. + + 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, + but it is documented in http://influxdb.org/docs/api/http.html. + See also: src/api/http/api.go:l57 + + """ + raise NotImplementedError() + + ### + # Limiting User Access + + # Database users + # get list of database users + # curl http://localhost:8086/db/site_dev/users?u=root&p=root + + # add database user + # curl -X POST http://localhost:8086/db/site_dev/users?u=root&p=root \ + # -d '{"name": "paul", "password": "i write teh docz"}' + + # update database user password + # curl -X POST http://localhost:8086/db/site_dev/users/paul?u=root&p=root \ + # -d '{"password": "new pass"}' + + # delete database user + # curl -X DELETE http://localhost:8086/db/site_dev/users/paul?u=root&p=root + + def get_database_users(self): + """Get list of database users.""" + url = "db/{0}/users".format(self._database) + + response = self.request( + url=url, + method='GET', + expected_response_code=200 + ) + + return response.json() + + def add_database_user(self, new_username, new_password, permissions=None): + """Add database user. + + :param permissions: A ``(readFrom, writeTo)`` tuple + """ + url = "db/{0}/users".format(self._database) + + data = { + 'name': new_username, + 'password': new_password + } + + if permissions: + try: + data['readFrom'], data['writeTo'] = permissions + except (ValueError, TypeError): + raise TypeError( + "'permissions' must be (readFrom, writeTo) tuple" + ) + + self.request( + url=url, + method='POST', + data=data, + expected_response_code=200 + ) + + return True + + def update_database_user_password(self, username, new_password): + """Update password.""" + return self.alter_database_user(username, new_password) + + def alter_database_user(self, username, password=None, permissions=None): + """Alter a database user and/or their permissions. + + :param permissions: A ``(readFrom, writeTo)`` tuple + :raise TypeError: if permissions cannot be read. + :raise ValueError: if neither password nor permissions provided. + """ + url = "db/{0}/users/{1}".format(self._database, username) + + if not password and not permissions: + raise ValueError("Nothing to alter for user {0}.".format(username)) + + data = {} + + if password: + data['password'] = password + + if permissions: + try: + data['readFrom'], data['writeTo'] = permissions + except (ValueError, TypeError): + raise TypeError( + "'permissions' must be (readFrom, writeTo) tuple" + ) + + self.request( + url=url, + method='POST', + data=data, + expected_response_code=200 + ) + + if username == self._username: + self._password = password + + return True + + def delete_database_user(self, username): + """Delete database user.""" + url = "db/{0}/users/{1}".format(self._database, username) + + self.request( + url=url, + method='DELETE', + expected_response_code=200 + ) + + return True + + # update the user by POSTing to db/site_dev/users/paul + + def update_permission(self, username, json_body): + """Update read/write permission. + + 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, + but it is documented in http://influxdb.org/docs/api/http.html. + See also: src/api/http/api.go:l57 + + """ + raise NotImplementedError() + + def send_packet(self, packet): + """Send a UDP packet along the wire.""" + data = json.dumps(packet) + byte = data.encode('utf-8') + self.udp_socket.sendto(byte, (self._host, self._udp_port)) diff --git a/lib/influxdb/influxdb08/dataframe_client.py b/lib/influxdb/influxdb08/dataframe_client.py new file mode 100644 index 0000000..2867125 --- /dev/null +++ b/lib/influxdb/influxdb08/dataframe_client.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +"""DataFrame client for InfluxDB v0.8.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import math +import warnings + +from .client import InfluxDBClient + + +class DataFrameClient(InfluxDBClient): + """Primary defintion of the DataFrameClient for v0.8. + + The ``DataFrameClient`` object holds information necessary to connect + to InfluxDB. Requests can be made to InfluxDB directly through the client. + The client reads and writes from pandas DataFrames. + """ + + def __init__(self, ignore_nan=True, *args, **kwargs): + """Initialize an instance of the DataFrameClient.""" + super(DataFrameClient, self).__init__(*args, **kwargs) + + try: + global pd + import pandas as pd + except ImportError as ex: + raise ImportError('DataFrameClient requires Pandas, ' + '"{ex}" problem importing'.format(ex=str(ex))) + + self.EPOCH = pd.Timestamp('1970-01-01 00:00:00.000+00:00') + self.ignore_nan = ignore_nan + + def write_points(self, data, *args, **kwargs): + """Write to multiple time series names. + + :param data: A dictionary mapping series names to pandas DataFrames + :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms' + or 'u'. + :param batch_size: [Optional] Value to write the points in batches + instead of all at one time. Useful for when doing data dumps from + one database to another or when doing a massive write operation + :type batch_size: int + """ + batch_size = kwargs.get('batch_size') + time_precision = kwargs.get('time_precision', 's') + if batch_size: + kwargs.pop('batch_size') # don't hand over to InfluxDBClient + for key, data_frame in data.items(): + number_batches = int(math.ceil( + len(data_frame) / float(batch_size))) + for batch in range(number_batches): + start_index = batch * batch_size + end_index = (batch + 1) * batch_size + outdata = [ + self._convert_dataframe_to_json( + name=key, + dataframe=data_frame + .iloc[start_index:end_index].copy(), + time_precision=time_precision)] + InfluxDBClient.write_points(self, outdata, *args, **kwargs) + return True + + outdata = [ + self._convert_dataframe_to_json(name=key, dataframe=dataframe, + time_precision=time_precision) + for key, dataframe in data.items()] + return InfluxDBClient.write_points(self, outdata, *args, **kwargs) + + def write_points_with_precision(self, data, time_precision='s'): + """Write to multiple time series names. + + DEPRECATED + """ + warnings.warn( + "write_points_with_precision is deprecated, and will be removed " + "in future versions. Please use " + "``DataFrameClient.write_points(time_precision='..')`` instead.", + FutureWarning) + return self.write_points(data, time_precision='s') + + def query(self, query, time_precision='s', chunked=False): + """Query data into DataFrames. + + Returns a DataFrame for a single time series and a map for multiple + time series with the time series as value and its name as key. + + :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms' + or 'u'. + :param chunked: [Optional, default=False] True if the data shall be + retrieved in chunks, False otherwise. + """ + result = InfluxDBClient.query(self, query=query, + time_precision=time_precision, + chunked=chunked) + if len(result) == 0: + return result + elif len(result) == 1: + return self._to_dataframe(result[0], time_precision) + else: + ret = {} + for time_series in result: + ret[time_series['name']] = self._to_dataframe(time_series, + time_precision) + return ret + + @staticmethod + def _to_dataframe(json_result, time_precision): + dataframe = pd.DataFrame(data=json_result['points'], + columns=json_result['columns']) + if 'sequence_number' in dataframe.keys(): + dataframe.sort_values(['time', 'sequence_number'], inplace=True) + else: + dataframe.sort_values(['time'], inplace=True) + + pandas_time_unit = time_precision + if time_precision == 'm': + pandas_time_unit = 'ms' + elif time_precision == 'u': + pandas_time_unit = 'us' + + dataframe.index = pd.to_datetime(list(dataframe['time']), + unit=pandas_time_unit, + utc=True) + del dataframe['time'] + return dataframe + + def _convert_dataframe_to_json(self, dataframe, name, time_precision='s'): + if not isinstance(dataframe, pd.DataFrame): + raise TypeError('Must be DataFrame, but type was: {0}.' + .format(type(dataframe))) + if not (isinstance(dataframe.index, pd.PeriodIndex) or + isinstance(dataframe.index, pd.DatetimeIndex)): + raise TypeError('Must be DataFrame with DatetimeIndex or \ + PeriodIndex.') + + if isinstance(dataframe.index, pd.PeriodIndex): + dataframe.index = dataframe.index.to_timestamp() + else: + dataframe.index = pd.to_datetime(dataframe.index) + + if dataframe.index.tzinfo is None: + dataframe.index = dataframe.index.tz_localize('UTC') + dataframe['time'] = [self._datetime_to_epoch(dt, time_precision) + for dt in dataframe.index] + data = {'name': name, + 'columns': [str(column) for column in dataframe.columns], + 'points': [self._convert_array(x) for x in dataframe.values]} + return data + + def _convert_array(self, array): + try: + global np + import numpy as np + except ImportError as ex: + raise ImportError('DataFrameClient requires Numpy, ' + '"{ex}" problem importing'.format(ex=str(ex))) + + if self.ignore_nan: + number_types = (int, float, np.number) + condition = (all(isinstance(el, number_types) for el in array) and + np.isnan(array)) + return list(np.where(condition, None, array)) + + return list(array) + + def _datetime_to_epoch(self, datetime, time_precision='s'): + seconds = (datetime - self.EPOCH).total_seconds() + if time_precision == 's': + return seconds + elif time_precision == 'm' or time_precision == 'ms': + return seconds * 1000 + elif time_precision == 'u': + return seconds * 1000000 diff --git a/lib/influxdb/influxdb08/helper.py b/lib/influxdb/influxdb08/helper.py new file mode 100644 index 0000000..f3dec33 --- /dev/null +++ b/lib/influxdb/influxdb08/helper.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +"""Helper class for InfluxDB for v0.8.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from collections import namedtuple, defaultdict +from warnings import warn + +import six + + +class SeriesHelper(object): + """Define the SeriesHelper object for InfluxDB v0.8. + + Subclassing this helper eases writing data points in bulk. + All data points are immutable, ensuring they do not get overwritten. + Each subclass can write to its own database. + The time series names can also be based on one or more defined fields. + + Annotated example:: + + class MySeriesHelper(SeriesHelper): + class Meta: + # Meta class stores time series helper configuration. + series_name = 'events.stats.{server_name}' + # Series name must be a string, curly brackets for dynamic use. + fields = ['time', 'server_name'] + # Defines all the fields in this time series. + ### Following attributes are optional. ### + client = TestSeriesHelper.client + # Client should be an instance of InfluxDBClient. + :warning: Only used if autocommit is True. + bulk_size = 5 + # Defines the number of data points to write simultaneously. + # Only applicable if autocommit is True. + autocommit = True + # If True and no bulk_size, then will set bulk_size to 1. + + """ + + __initialized__ = False + + def __new__(cls, *args, **kwargs): + """Initialize class attributes for subsequent constructor calls. + + :note: *args and **kwargs are not explicitly used in this function, + but needed for Python 2 compatibility. + """ + if not cls.__initialized__: + cls.__initialized__ = True + try: + _meta = getattr(cls, 'Meta') + except AttributeError: + raise AttributeError( + 'Missing Meta class in {0}.'.format( + cls.__name__)) + + for attr in ['series_name', 'fields']: + try: + setattr(cls, '_' + attr, getattr(_meta, attr)) + except AttributeError: + raise AttributeError( + 'Missing {0} in {1} Meta class.'.format( + attr, + cls.__name__)) + + cls._autocommit = getattr(_meta, 'autocommit', False) + + cls._client = getattr(_meta, 'client', None) + if cls._autocommit and not cls._client: + raise AttributeError( + 'In {0}, autocommit is set to True, but no client is set.' + .format(cls.__name__)) + + try: + cls._bulk_size = getattr(_meta, 'bulk_size') + if cls._bulk_size < 1 and cls._autocommit: + warn( + 'Definition of bulk_size in {0} forced to 1, ' + 'was less than 1.'.format(cls.__name__)) + cls._bulk_size = 1 + except AttributeError: + cls._bulk_size = -1 + else: + if not cls._autocommit: + warn( + 'Definition of bulk_size in {0} has no affect because' + ' autocommit is false.'.format(cls.__name__)) + + cls._datapoints = defaultdict(list) + cls._type = namedtuple(cls.__name__, cls._fields) + + return super(SeriesHelper, cls).__new__(cls) + + def __init__(self, **kw): + """Create a new data point. + + All fields must be present. + + :note: Data points written when `bulk_size` is reached per Helper. + :warning: Data points are *immutable* (`namedtuples`). + """ + cls = self.__class__ + + if sorted(cls._fields) != sorted(kw.keys()): + raise NameError( + 'Expected {0}, got {1}.'.format( + cls._fields, + kw.keys())) + + cls._datapoints[cls._series_name.format(**kw)].append(cls._type(**kw)) + + if cls._autocommit and \ + sum(len(series) for series in cls._datapoints.values()) \ + >= cls._bulk_size: + cls.commit() + + @classmethod + def commit(cls, client=None): + """Commit everything from datapoints via the client. + + :param client: InfluxDBClient instance for writing points to InfluxDB. + :attention: any provided client will supersede the class client. + :return: result of client.write_points. + """ + if not client: + client = cls._client + rtn = client.write_points(cls._json_body_()) + cls._reset_() + return rtn + + @classmethod + def _json_body_(cls): + """Return JSON body of the datapoints. + + :return: JSON body of the datapoints. + """ + json = [] + for series_name, data in six.iteritems(cls._datapoints): + json.append({'name': series_name, + 'columns': cls._fields, + 'points': [[getattr(point, k) for k in cls._fields] + for point in data] + }) + return json + + @classmethod + def _reset_(cls): + """Reset data storage.""" + cls._datapoints = defaultdict(list) diff --git a/lib/influxdb/line_protocol.py b/lib/influxdb/line_protocol.py new file mode 100644 index 0000000..e8816fc --- /dev/null +++ b/lib/influxdb/line_protocol.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +"""Define the line_protocol handler.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from datetime import datetime +from numbers import Integral + +from pytz import UTC +from dateutil.parser import parse +from six import iteritems, binary_type, text_type, integer_types, PY2 + +EPOCH = UTC.localize(datetime.utcfromtimestamp(0)) + + +def _convert_timestamp(timestamp, precision=None): + if isinstance(timestamp, Integral): + return timestamp # assume precision is correct if timestamp is int + + if isinstance(_get_unicode(timestamp), text_type): + timestamp = parse(timestamp) + + if isinstance(timestamp, datetime): + if not timestamp.tzinfo: + timestamp = UTC.localize(timestamp) + + ns = (timestamp - EPOCH).total_seconds() * 1e9 + if precision is None or precision == 'n': + return ns + elif precision == 'u': + return ns / 1e3 + elif precision == 'ms': + return ns / 1e6 + elif precision == 's': + return ns / 1e9 + elif precision == 'm': + return ns / 1e9 / 60 + elif precision == 'h': + return ns / 1e9 / 3600 + + raise ValueError(timestamp) + + +def _escape_tag(tag): + tag = _get_unicode(tag, force=True) + return tag.replace( + "\\", "\\\\" + ).replace( + " ", "\\ " + ).replace( + ",", "\\," + ).replace( + "=", "\\=" + ) + + +def _escape_tag_value(value): + ret = _escape_tag(value) + if ret.endswith('\\'): + ret += ' ' + return ret + + +def quote_ident(value): + """Indent the quotes.""" + return "\"{}\"".format(value + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n")) + + +def quote_literal(value): + """Quote provided literal.""" + return "'{}'".format(value + .replace("\\", "\\\\") + .replace("'", "\\'")) + + +def _is_float(value): + try: + float(value) + except (TypeError, ValueError): + return False + + return True + + +def _escape_value(value): + value = _get_unicode(value) + + if isinstance(value, text_type) and value != '': + return quote_ident(value) + elif isinstance(value, integer_types) and not isinstance(value, bool): + return str(value) + 'i' + elif _is_float(value): + return repr(value) + + return str(value) + + +def _get_unicode(data, force=False): + """Try to return a text aka unicode object from the given data.""" + if isinstance(data, binary_type): + return data.decode('utf-8') + elif data is None: + return '' + elif force: + if PY2: + return unicode(data) + else: + return str(data) + else: + return data + + +def make_lines(data, precision=None): + """Extract points from given dict. + + Extracts the points from the given dict and returns a Unicode string + matching the line protocol introduced in InfluxDB 0.9.0. + """ + lines = [] + static_tags = data.get('tags') + for point in data['points']: + elements = [] + + # add measurement name + measurement = _escape_tag(_get_unicode( + point.get('measurement', data.get('measurement')))) + key_values = [measurement] + + # add tags + if static_tags: + tags = dict(static_tags) # make a copy, since we'll modify + tags.update(point.get('tags') or {}) + else: + tags = point.get('tags') or {} + + # tags should be sorted client-side to take load off server + for tag_key, tag_value in sorted(iteritems(tags)): + key = _escape_tag(tag_key) + value = _escape_tag_value(tag_value) + + if key != '' and value != '': + key_values.append(key + "=" + value) + + elements.append(','.join(key_values)) + + # add fields + field_values = [] + for field_key, field_value in sorted(iteritems(point['fields'])): + key = _escape_tag(field_key) + value = _escape_value(field_value) + + if key != '' and value != '': + field_values.append(key + "=" + value) + + elements.append(','.join(field_values)) + + # add timestamp + if 'time' in point: + timestamp = _get_unicode(str(int( + _convert_timestamp(point['time'], precision)))) + elements.append(timestamp) + + line = ' '.join(elements) + lines.append(line) + + return '\n'.join(lines) + '\n' diff --git a/lib/influxdb/resultset.py b/lib/influxdb/resultset.py new file mode 100644 index 0000000..ba4f3c1 --- /dev/null +++ b/lib/influxdb/resultset.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +"""Module to prepare the resultset.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import warnings + +from influxdb.exceptions import InfluxDBClientError + +_sentinel = object() + + +class ResultSet(object): + """A wrapper around a single InfluxDB query result.""" + + def __init__(self, series, raise_errors=True): + """Initialize the ResultSet.""" + self._raw = series + self._error = self._raw.get('error', None) + + if self.error is not None and raise_errors is True: + raise InfluxDBClientError(self.error) + + @property + def raw(self): + """Raw JSON from InfluxDB.""" + return self._raw + + @raw.setter + def raw(self, value): + self._raw = value + + @property + def error(self): + """Error returned by InfluxDB.""" + return self._error + + def __getitem__(self, key): + """Retrieve the series name or specific set based on key. + + :param key: Either a series name, or a tags_dict, or + a 2-tuple(series_name, tags_dict). + If the series name is None (or not given) then any serie + matching the eventual given tags will be given its points + one after the other. + To get the points of every series in this resultset then + you have to provide None as key. + :return: A generator yielding `Point`s matching the given key. + NB: + The order in which the points are yielded is actually undefined but + it might change.. + """ + warnings.warn( + ("ResultSet's ``__getitem__`` method will be deprecated. Use" + "``get_points`` instead."), + DeprecationWarning + ) + + if isinstance(key, tuple): + if len(key) != 2: + raise TypeError('only 2-tuples allowed') + + name = key[0] + tags = key[1] + + if not isinstance(tags, dict) and tags is not None: + raise TypeError('tags should be a dict') + elif isinstance(key, dict): + name = None + tags = key + else: + name = key + tags = None + + return self.get_points(name, tags) + + def get_points(self, measurement=None, tags=None): + """Return a generator for all the points that match the given filters. + + :param measurement: The measurement name + :type measurement: str + + :param tags: Tags to look for + :type tags: dict + + :return: Points generator + """ + # Raise error if measurement is not str or bytes + if not isinstance(measurement, + (bytes, type(b''.decode()), type(None))): + raise TypeError('measurement must be an str or None') + + for series in self._get_series(): + series_name = series.get('measurement', + series.get('name', 'results')) + if series_name is None: + # this is a "system" query or a query which + # doesn't return a name attribute. + # like 'show retention policies' .. + if tags is None: + for item in self._get_points_for_series(series): + yield item + + elif measurement in (None, series_name): + # by default if no tags was provided then + # we will matches every returned series + series_tags = series.get('tags', {}) + for item in self._get_points_for_series(series): + if tags is None or \ + self._tag_matches(item, tags) or \ + self._tag_matches(series_tags, tags): + yield item + + def __repr__(self): + """Representation of ResultSet object.""" + items = [] + + for item in self.items(): + items.append("'%s': %s" % (item[0], list(item[1]))) + + return "ResultSet({%s})" % ", ".join(items) + + def __iter__(self): + """Yield one dict instance per series result.""" + for key in self.keys(): + yield list(self.__getitem__(key)) + + @staticmethod + def _tag_matches(tags, filter): + """Check if all key/values in filter match in tags.""" + for tag_name, tag_value in filter.items(): + # using _sentinel as I'm not sure that "None" + # could be used, because it could be a valid + # series_tags value : when a series has no such tag + # then I think it's set to /null/None/.. TBC.. + series_tag_value = tags.get(tag_name, _sentinel) + if series_tag_value != tag_value: + return False + + return True + + def _get_series(self): + """Return all series.""" + return self.raw.get('series', []) + + def __len__(self): + """Return the len of the keys in the ResultSet.""" + return len(self.keys()) + + def keys(self): + """Return the list of keys in the ResultSet. + + :return: List of keys. Keys are tuples (series_name, tags) + """ + keys = [] + for series in self._get_series(): + keys.append( + (series.get('measurement', + series.get('name', 'results')), + series.get('tags', None)) + ) + return keys + + def items(self): + """Return the set of items from the ResultSet. + + :return: List of tuples, (key, generator) + """ + items = [] + for series in self._get_series(): + series_key = (series.get('measurement', + series.get('name', 'results')), + series.get('tags', None)) + items.append( + (series_key, self._get_points_for_series(series)) + ) + return items + + def _get_points_for_series(self, series): + """Return generator of dict from columns and values of a series. + + :param series: One series + :return: Generator of dicts + """ + for point in series.get('values', []): + yield self.point_from_cols_vals( + series['columns'], + point + ) + + @staticmethod + def point_from_cols_vals(cols, vals): + """Create a dict from columns and values lists. + + :param cols: List of columns + :param vals: List of values + :return: Dict where keys are columns. + """ + point = {} + for col_index, col_name in enumerate(cols): + point[col_name] = vals[col_index] + + return point diff --git a/lib/influxdb/tests/__init__.py b/lib/influxdb/tests/__init__.py new file mode 100644 index 0000000..adf2f20 --- /dev/null +++ b/lib/influxdb/tests/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +"""Configure the tests package for InfluxDBClient.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import sys +import os + +import unittest + +using_pypy = hasattr(sys, "pypy_version_info") +skipIfPYpy = unittest.skipIf(using_pypy, "Skipping this test on pypy.") + +_skip_server_tests = os.environ.get( + 'INFLUXDB_PYTHON_SKIP_SERVER_TESTS', + None) == 'True' +skipServerTests = unittest.skipIf(_skip_server_tests, + "Skipping server tests...") diff --git a/lib/influxdb/tests/chunked_json_test.py b/lib/influxdb/tests/chunked_json_test.py new file mode 100644 index 0000000..f633bcb --- /dev/null +++ b/lib/influxdb/tests/chunked_json_test.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +"""Chunked JSON test.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest + +from influxdb import chunked_json + + +class TestChunkJson(unittest.TestCase): + """Set up the TestChunkJson object.""" + + @classmethod + def setUpClass(cls): + """Initialize the TestChunkJson object.""" + super(TestChunkJson, cls).setUpClass() + + def test_load(self): + """Test reading a sequence of JSON values from a string.""" + example_response = \ + '{"results": [{"series": [{"measurement": "sdfsdfsdf", ' \ + '"columns": ["time", "value"], "values": ' \ + '[["2009-11-10T23:00:00Z", 0.64]]}]}, {"series": ' \ + '[{"measurement": "cpu_load_short", "columns": ["time", "value"],'\ + '"values": [["2009-11-10T23:00:00Z", 0.64]]}]}]}' + + res = list(chunked_json.loads(example_response)) + # import ipdb; ipdb.set_trace() + + self.assertListEqual( + [ + { + 'results': [ + {'series': [{ + 'values': [['2009-11-10T23:00:00Z', 0.64]], + 'measurement': 'sdfsdfsdf', + 'columns': + ['time', 'value']}]}, + {'series': [{ + 'values': [['2009-11-10T23:00:00Z', 0.64]], + 'measurement': 'cpu_load_short', + 'columns': ['time', 'value']}]} + ] + } + ], + res + ) diff --git a/lib/influxdb/tests/client_test.py b/lib/influxdb/tests/client_test.py new file mode 100644 index 0000000..e27eef1 --- /dev/null +++ b/lib/influxdb/tests/client_test.py @@ -0,0 +1,1094 @@ +# -*- coding: utf-8 -*- +"""Unit tests for the InfluxDBClient. + +NB/WARNING: +This module implements tests for the InfluxDBClient class +but does so + + without any server instance running + + by mocking all the expected responses. + +So any change of (response format from) the server will **NOT** be +detected by this module. + +See client_test_with_server.py for tests against a running server instance. + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import random +import socket +import unittest +import warnings + +import json +import mock +import requests +import requests.exceptions +import requests_mock + +from nose.tools import raises + +from influxdb import InfluxDBClient +from influxdb.resultset import ResultSet + + +def _build_response_object(status_code=200, content=""): + resp = requests.Response() + resp.status_code = status_code + resp._content = content.encode("utf8") + return resp + + +def _mocked_session(cli, method="GET", status_code=200, content=""): + method = method.upper() + + def request(*args, **kwargs): + """Request content from the mocked session.""" + c = content + + # Check method + assert method == kwargs.get('method', 'GET') + + if method == 'POST': + data = kwargs.get('data', None) + + if data is not None: + # Data must be a string + assert isinstance(data, str) + + # Data must be a JSON string + assert c == json.loads(data, strict=True) + + c = data + + # Anyway, Content must be a JSON string (or empty string) + if not isinstance(c, str): + c = json.dumps(c) + + return _build_response_object(status_code=status_code, content=c) + + return mock.patch.object(cli._session, 'request', side_effect=request) + + +class TestInfluxDBClient(unittest.TestCase): + """Set up the TestInfluxDBClient object.""" + + def setUp(self): + """Initialize an instance of TestInfluxDBClient object.""" + # By default, raise exceptions on warnings + warnings.simplefilter('error', FutureWarning) + + self.cli = InfluxDBClient('localhost', 8086, 'username', 'password') + self.dummy_points = [ + { + "measurement": "cpu_load_short", + "tags": { + "host": "server01", + "region": "us-west" + }, + "time": "2009-11-10T23:00:00.123456Z", + "fields": { + "value": 0.64 + } + } + ] + + self.dsn_string = 'influxdb://uSr:pWd@my.host.fr:1886/db' + + def test_scheme(self): + """Set up the test schema for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') + self.assertEqual('http://host:8086', cli._baseurl) + + cli = InfluxDBClient( + 'host', 8086, 'username', 'password', 'database', ssl=True + ) + self.assertEqual('https://host:8086', cli._baseurl) + + cli = InfluxDBClient( + 'host', 8086, 'username', 'password', 'database', ssl=True, + path="somepath" + ) + self.assertEqual('https://host:8086/somepath', cli._baseurl) + + cli = InfluxDBClient( + 'host', 8086, 'username', 'password', 'database', ssl=True, + path=None + ) + self.assertEqual('https://host:8086', cli._baseurl) + + cli = InfluxDBClient( + 'host', 8086, 'username', 'password', 'database', ssl=True, + path="/somepath" + ) + self.assertEqual('https://host:8086/somepath', cli._baseurl) + + def test_dsn(self): + """Set up the test datasource name for TestInfluxDBClient object.""" + cli = InfluxDBClient.from_dsn('influxdb://192.168.0.1:1886') + self.assertEqual('http://192.168.0.1:1886', cli._baseurl) + + cli = InfluxDBClient.from_dsn(self.dsn_string) + self.assertEqual('http://my.host.fr:1886', cli._baseurl) + self.assertEqual('uSr', cli._username) + self.assertEqual('pWd', cli._password) + self.assertEqual('db', cli._database) + self.assertFalse(cli._use_udp) + + cli = InfluxDBClient.from_dsn('udp+' + self.dsn_string) + self.assertTrue(cli._use_udp) + + cli = InfluxDBClient.from_dsn('https+' + self.dsn_string) + self.assertEqual('https://my.host.fr:1886', cli._baseurl) + + cli = InfluxDBClient.from_dsn('https+' + self.dsn_string, + **{'ssl': False}) + self.assertEqual('http://my.host.fr:1886', cli._baseurl) + + def test_switch_database(self): + """Test switch database in TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') + cli.switch_database('another_database') + self.assertEqual('another_database', cli._database) + + def test_switch_user(self): + """Test switch user in TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') + cli.switch_user('another_username', 'another_password') + self.assertEqual('another_username', cli._username) + self.assertEqual('another_password', cli._password) + + def test_write(self): + """Test write in TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/write", + status_code=204 + ) + cli = InfluxDBClient(database='db') + cli.write( + {"database": "mydb", + "retentionPolicy": "mypolicy", + "points": [{"measurement": "cpu_load_short", + "tags": {"host": "server01", + "region": "us-west"}, + "time": "2009-11-10T23:00:00Z", + "fields": {"value": 0.64}}]} + ) + + self.assertEqual( + m.last_request.body, + b"cpu_load_short,host=server01,region=us-west " + b"value=0.64 1257894000000000000\n", + ) + + def test_write_points(self): + """Test write points for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/write", + status_code=204 + ) + + cli = InfluxDBClient(database='db') + cli.write_points( + self.dummy_points, + ) + self.assertEqual( + 'cpu_load_short,host=server01,region=us-west ' + 'value=0.64 1257894000123456000\n', + m.last_request.body.decode('utf-8'), + ) + + def test_write_points_toplevel_attributes(self): + """Test write points attrs for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/write", + status_code=204 + ) + + cli = InfluxDBClient(database='db') + cli.write_points( + self.dummy_points, + database='testdb', + tags={"tag": "hello"}, + retention_policy="somepolicy" + ) + self.assertEqual( + 'cpu_load_short,host=server01,region=us-west,tag=hello ' + 'value=0.64 1257894000123456000\n', + m.last_request.body.decode('utf-8'), + ) + + def test_write_points_batch(self): + """Test write points batch for TestInfluxDBClient object.""" + dummy_points = [ + {"measurement": "cpu_usage", "tags": {"unit": "percent"}, + "time": "2009-11-10T23:00:00Z", "fields": {"value": 12.34}}, + {"measurement": "network", "tags": {"direction": "in"}, + "time": "2009-11-10T23:00:00Z", "fields": {"value": 123.00}}, + {"measurement": "network", "tags": {"direction": "out"}, + "time": "2009-11-10T23:00:00Z", "fields": {"value": 12.00}} + ] + expected_last_body = ( + "network,direction=out,host=server01,region=us-west " + "value=12.0 1257894000000000000\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + cli = InfluxDBClient(database='db') + cli.write_points(points=dummy_points, + database='db', + tags={"host": "server01", + "region": "us-west"}, + batch_size=2) + self.assertEqual(m.call_count, 2) + self.assertEqual(expected_last_body, + m.last_request.body.decode('utf-8')) + + def test_write_points_udp(self): + """Test write points UDP for TestInfluxDBClient object.""" + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + port = random.randint(4000, 8000) + s.bind(('0.0.0.0', port)) + + cli = InfluxDBClient( + 'localhost', 8086, 'root', 'root', + 'test', use_udp=True, udp_port=port + ) + cli.write_points(self.dummy_points) + + received_data, addr = s.recvfrom(1024) + + self.assertEqual( + 'cpu_load_short,host=server01,region=us-west ' + 'value=0.64 1257894000123456000\n', + received_data.decode() + ) + + @raises(Exception) + def test_write_points_fails(self): + """Test write points fail for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + with _mocked_session(cli, 'post', 500): + cli.write_points([]) + + def test_write_points_with_precision(self): + """Test write points with precision for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/write", + status_code=204 + ) + + cli = InfluxDBClient(database='db') + + cli.write_points(self.dummy_points, time_precision='n') + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 1257894000123456000\n', + m.last_request.body, + ) + + cli.write_points(self.dummy_points, time_precision='u') + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 1257894000123456\n', + m.last_request.body, + ) + + cli.write_points(self.dummy_points, time_precision='ms') + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 1257894000123\n', + m.last_request.body, + ) + + cli.write_points(self.dummy_points, time_precision='s') + self.assertEqual( + b"cpu_load_short,host=server01,region=us-west " + b"value=0.64 1257894000\n", + m.last_request.body, + ) + + cli.write_points(self.dummy_points, time_precision='m') + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 20964900\n', + m.last_request.body, + ) + + cli.write_points(self.dummy_points, time_precision='h') + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 349415\n', + m.last_request.body, + ) + + def test_write_points_with_precision_udp(self): + """Test write points with precision for TestInfluxDBClient object.""" + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + port = random.randint(4000, 8000) + s.bind(('0.0.0.0', port)) + + cli = InfluxDBClient( + 'localhost', 8086, 'root', 'root', + 'test', use_udp=True, udp_port=port + ) + + cli.write_points(self.dummy_points, time_precision='n') + received_data, addr = s.recvfrom(1024) + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 1257894000123456000\n', + received_data, + ) + + cli.write_points(self.dummy_points, time_precision='u') + received_data, addr = s.recvfrom(1024) + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 1257894000123456\n', + received_data, + ) + + cli.write_points(self.dummy_points, time_precision='ms') + received_data, addr = s.recvfrom(1024) + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 1257894000123\n', + received_data, + ) + + cli.write_points(self.dummy_points, time_precision='s') + received_data, addr = s.recvfrom(1024) + self.assertEqual( + b"cpu_load_short,host=server01,region=us-west " + b"value=0.64 1257894000\n", + received_data, + ) + + cli.write_points(self.dummy_points, time_precision='m') + received_data, addr = s.recvfrom(1024) + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 20964900\n', + received_data, + ) + + cli.write_points(self.dummy_points, time_precision='h') + received_data, addr = s.recvfrom(1024) + self.assertEqual( + b'cpu_load_short,host=server01,region=us-west ' + b'value=0.64 349415\n', + received_data, + ) + + def test_write_points_bad_precision(self): + """Test write points w/bad precision TestInfluxDBClient object.""" + cli = InfluxDBClient() + with self.assertRaisesRegexp( + Exception, + "Invalid time precision is given. " + "\(use 'n', 'u', 'ms', 's', 'm' or 'h'\)" + ): + cli.write_points( + self.dummy_points, + time_precision='g' + ) + + @raises(Exception) + def test_write_points_with_precision_fails(self): + """Test write points w/precision fail for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + with _mocked_session(cli, 'post', 500): + cli.write_points_with_precision([]) + + def test_query(self): + """Test query method for TestInfluxDBClient object.""" + example_response = ( + '{"results": [{"series": [{"measurement": "sdfsdfsdf", ' + '"columns": ["time", "value"], "values": ' + '[["2009-11-10T23:00:00Z", 0.64]]}]}, {"series": ' + '[{"measurement": "cpu_load_short", "columns": ["time", "value"], ' + '"values": [["2009-11-10T23:00:00Z", 0.64]]}]}]}' + ) + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/query", + text=example_response + ) + rs = self.cli.query('select * from foo') + + self.assertListEqual( + list(rs[0].get_points()), + [{'value': 0.64, 'time': '2009-11-10T23:00:00Z'}] + ) + + def test_select_into_post(self): + """Test SELECT.*INTO is POSTed.""" + example_response = ( + '{"results": [{"series": [{"measurement": "sdfsdfsdf", ' + '"columns": ["time", "value"], "values": ' + '[["2009-11-10T23:00:00Z", 0.64]]}]}, {"series": ' + '[{"measurement": "cpu_load_short", "columns": ["time", "value"], ' + '"values": [["2009-11-10T23:00:00Z", 0.64]]}]}]}' + ) + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text=example_response + ) + rs = self.cli.query('select * INTO newmeas from foo') + + self.assertListEqual( + list(rs[0].get_points()), + [{'value': 0.64, 'time': '2009-11-10T23:00:00Z'}] + ) + + @unittest.skip('Not implemented for 0.9') + def test_query_chunked(self): + """Test chunked query for TestInfluxDBClient object.""" + cli = InfluxDBClient(database='db') + example_object = { + 'points': [ + [1415206250119, 40001, 667], + [1415206244555, 30001, 7], + [1415206228241, 20001, 788], + [1415206212980, 10001, 555], + [1415197271586, 10001, 23] + ], + 'measurement': 'foo', + 'columns': [ + 'time', + 'sequence_number', + 'val' + ] + } + example_response = \ + json.dumps(example_object) + json.dumps(example_object) + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/db/db/series", + text=example_response + ) + + self.assertListEqual( + cli.query('select * from foo', chunked=True), + [example_object, example_object] + ) + + @raises(Exception) + def test_query_fail(self): + """Test query failed for TestInfluxDBClient object.""" + with _mocked_session(self.cli, 'get', 401): + self.cli.query('select column_one from foo;') + + def test_ping(self): + """Test ping querying InfluxDB version.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/ping", + status_code=204, + headers={'X-Influxdb-Version': '1.2.3'} + ) + version = self.cli.ping() + self.assertEqual(version, '1.2.3') + + def test_create_database(self): + """Test create database for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text='{"results":[{}]}' + ) + self.cli.create_database('new_db') + self.assertEqual( + m.last_request.qs['q'][0], + 'create database "new_db"' + ) + + def test_create_numeric_named_database(self): + """Test create db w/numeric name for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text='{"results":[{}]}' + ) + self.cli.create_database('123') + self.assertEqual( + m.last_request.qs['q'][0], + 'create database "123"' + ) + + @raises(Exception) + def test_create_database_fails(self): + """Test create database fail for TestInfluxDBClient object.""" + with _mocked_session(self.cli, 'post', 401): + self.cli.create_database('new_db') + + def test_drop_database(self): + """Test drop database for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text='{"results":[{}]}' + ) + self.cli.drop_database('new_db') + self.assertEqual( + m.last_request.qs['q'][0], + 'drop database "new_db"' + ) + + def test_drop_measurement(self): + """Test drop measurement for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text='{"results":[{}]}' + ) + self.cli.drop_measurement('new_measurement') + self.assertEqual( + m.last_request.qs['q'][0], + 'drop measurement "new_measurement"' + ) + + def test_drop_numeric_named_database(self): + """Test drop numeric db for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text='{"results":[{}]}' + ) + self.cli.drop_database('123') + self.assertEqual( + m.last_request.qs['q'][0], + 'drop database "123"' + ) + + def test_get_list_database(self): + """Test get list of databases for TestInfluxDBClient object.""" + data = {'results': [ + {'series': [ + {'name': 'databases', + 'values': [ + ['new_db_1'], + ['new_db_2']], + 'columns': ['name']}]} + ]} + + with _mocked_session(self.cli, 'get', 200, json.dumps(data)): + self.assertListEqual( + self.cli.get_list_database(), + [{'name': 'new_db_1'}, {'name': 'new_db_2'}] + ) + + @raises(Exception) + def test_get_list_database_fails(self): + """Test get list of dbs fail for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password') + with _mocked_session(cli, 'get', 401): + cli.get_list_database() + + def test_get_list_measurements(self): + """Test get list of measurements for TestInfluxDBClient object.""" + data = { + "results": [{ + "series": [ + {"name": "measurements", + "columns": ["name"], + "values": [["cpu"], ["disk"] + ]}]} + ] + } + + with _mocked_session(self.cli, 'get', 200, json.dumps(data)): + self.assertListEqual( + self.cli.get_list_measurements(), + [{'name': 'cpu'}, {'name': 'disk'}] + ) + + def test_create_retention_policy_default(self): + """Test create default ret policy for TestInfluxDBClient object.""" + example_response = '{"results":[{}]}' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text=example_response + ) + self.cli.create_retention_policy( + 'somename', '1d', 4, default=True, database='db' + ) + + self.assertEqual( + m.last_request.qs['q'][0], + 'create retention policy "somename" on ' + '"db" duration 1d replication 4 shard duration 0s default' + ) + + def test_create_retention_policy(self): + """Test create retention policy for TestInfluxDBClient object.""" + example_response = '{"results":[{}]}' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text=example_response + ) + self.cli.create_retention_policy( + 'somename', '1d', 4, database='db' + ) + + self.assertEqual( + m.last_request.qs['q'][0], + 'create retention policy "somename" on ' + '"db" duration 1d replication 4 shard duration 0s' + ) + + def test_alter_retention_policy(self): + """Test alter retention policy for TestInfluxDBClient object.""" + example_response = '{"results":[{}]}' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text=example_response + ) + # Test alter duration + self.cli.alter_retention_policy('somename', 'db', + duration='4d') + self.assertEqual( + m.last_request.qs['q'][0], + 'alter retention policy "somename" on "db" duration 4d' + ) + # Test alter replication + self.cli.alter_retention_policy('somename', 'db', + replication=4) + self.assertEqual( + m.last_request.qs['q'][0], + 'alter retention policy "somename" on "db" replication 4' + ) + + # Test alter shard duration + self.cli.alter_retention_policy('somename', 'db', + shard_duration='1h') + self.assertEqual( + m.last_request.qs['q'][0], + 'alter retention policy "somename" on "db" shard duration 1h' + ) + + # Test alter default + self.cli.alter_retention_policy('somename', 'db', + default=True) + self.assertEqual( + m.last_request.qs['q'][0], + 'alter retention policy "somename" on "db" default' + ) + + @raises(Exception) + def test_alter_retention_policy_invalid(self): + """Test invalid alter ret policy for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password') + with _mocked_session(cli, 'get', 400): + self.cli.alter_retention_policy('somename', 'db') + + def test_drop_retention_policy(self): + """Test drop retention policy for TestInfluxDBClient object.""" + example_response = '{"results":[{}]}' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text=example_response + ) + self.cli.drop_retention_policy('somename', 'db') + self.assertEqual( + m.last_request.qs['q'][0], + 'drop retention policy "somename" on "db"' + ) + + @raises(Exception) + def test_drop_retention_policy_fails(self): + """Test failed drop ret policy for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password') + with _mocked_session(cli, 'delete', 401): + cli.drop_retention_policy('default', 'db') + + def test_get_list_retention_policies(self): + """Test get retention policies for TestInfluxDBClient object.""" + example_response = \ + '{"results": [{"series": [{"values": [["fsfdsdf", "24h0m0s", 2]],'\ + ' "columns": ["name", "duration", "replicaN"]}]}]}' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/query", + text=example_response + ) + self.assertListEqual( + self.cli.get_list_retention_policies("db"), + [{'duration': '24h0m0s', + 'name': 'fsfdsdf', 'replicaN': 2}] + ) + + @mock.patch('requests.Session.request') + def test_request_retry(self, mock_request): + """Test that two connection errors will be handled.""" + class CustomMock(object): + """Create custom mock object for test.""" + + def __init__(self): + self.i = 0 + + def connection_error(self, *args, **kwargs): + """Handle a connection error for the CustomMock object.""" + self.i += 1 + + if self.i < 3: + raise requests.exceptions.ConnectionError + + r = requests.Response() + r.status_code = 204 + return r + + mock_request.side_effect = CustomMock().connection_error + + cli = InfluxDBClient(database='db') + cli.write_points( + self.dummy_points + ) + + @mock.patch('requests.Session.request') + def test_request_retry_raises(self, mock_request): + """Test that three requests errors will not be handled.""" + class CustomMock(object): + """Create custom mock object for test.""" + + def __init__(self): + self.i = 0 + + def connection_error(self, *args, **kwargs): + """Handle a connection error for the CustomMock object.""" + self.i += 1 + + if self.i < 4: + raise requests.exceptions.HTTPError + else: + r = requests.Response() + r.status_code = 200 + return r + + mock_request.side_effect = CustomMock().connection_error + + cli = InfluxDBClient(database='db') + + with self.assertRaises(requests.exceptions.HTTPError): + cli.write_points(self.dummy_points) + + @mock.patch('requests.Session.request') + def test_random_request_retry(self, mock_request): + """Test that a random number of connection errors will be handled.""" + class CustomMock(object): + """Create custom mock object for test.""" + + def __init__(self, retries): + self.i = 0 + self.retries = retries + + def connection_error(self, *args, **kwargs): + """Handle a connection error for the CustomMock object.""" + self.i += 1 + + if self.i < self.retries: + raise requests.exceptions.ConnectionError + else: + r = requests.Response() + r.status_code = 204 + return r + + retries = random.randint(1, 5) + mock_request.side_effect = CustomMock(retries).connection_error + + cli = InfluxDBClient(database='db', retries=retries) + cli.write_points(self.dummy_points) + + @mock.patch('requests.Session.request') + def test_random_request_retry_raises(self, mock_request): + """Test a random number of conn errors plus one will not be handled.""" + class CustomMock(object): + """Create custom mock object for test.""" + + def __init__(self, retries): + self.i = 0 + self.retries = retries + + def connection_error(self, *args, **kwargs): + """Handle a connection error for the CustomMock object.""" + self.i += 1 + + if self.i < self.retries + 1: + raise requests.exceptions.ConnectionError + else: + r = requests.Response() + r.status_code = 200 + return r + + retries = random.randint(1, 5) + mock_request.side_effect = CustomMock(retries).connection_error + + cli = InfluxDBClient(database='db', retries=retries) + + with self.assertRaises(requests.exceptions.ConnectionError): + cli.write_points(self.dummy_points) + + def test_get_list_users(self): + """Test get users for TestInfluxDBClient object.""" + example_response = ( + '{"results":[{"series":[{"columns":["user","admin"],' + '"values":[["test",false]]}]}]}' + ) + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/query", + text=example_response + ) + + self.assertListEqual( + self.cli.get_list_users(), + [{'user': 'test', 'admin': False}] + ) + + def test_get_list_users_empty(self): + """Test get empty userlist for TestInfluxDBClient object.""" + example_response = ( + '{"results":[{"series":[{"columns":["user","admin"]}]}]}' + ) + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/query", + text=example_response + ) + + self.assertListEqual(self.cli.get_list_users(), []) + + def test_grant_admin_privileges(self): + """Test grant admin privs for TestInfluxDBClient object.""" + example_response = '{"results":[{}]}' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text=example_response + ) + self.cli.grant_admin_privileges('test') + + self.assertEqual( + m.last_request.qs['q'][0], + 'grant all privileges to "test"' + ) + + @raises(Exception) + def test_grant_admin_privileges_invalid(self): + """Test grant invalid admin privs for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password') + with _mocked_session(cli, 'get', 400): + self.cli.grant_admin_privileges('') + + def test_revoke_admin_privileges(self): + """Test revoke admin privs for TestInfluxDBClient object.""" + example_response = '{"results":[{}]}' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text=example_response + ) + self.cli.revoke_admin_privileges('test') + + self.assertEqual( + m.last_request.qs['q'][0], + 'revoke all privileges from "test"' + ) + + @raises(Exception) + def test_revoke_admin_privileges_invalid(self): + """Test revoke invalid admin privs for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password') + with _mocked_session(cli, 'get', 400): + self.cli.revoke_admin_privileges('') + + def test_grant_privilege(self): + """Test grant privs for TestInfluxDBClient object.""" + example_response = '{"results":[{}]}' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text=example_response + ) + self.cli.grant_privilege('read', 'testdb', 'test') + + self.assertEqual( + m.last_request.qs['q'][0], + 'grant read on "testdb" to "test"' + ) + + @raises(Exception) + def test_grant_privilege_invalid(self): + """Test grant invalid privs for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password') + with _mocked_session(cli, 'get', 400): + self.cli.grant_privilege('', 'testdb', 'test') + + def test_revoke_privilege(self): + """Test revoke privs for TestInfluxDBClient object.""" + example_response = '{"results":[{}]}' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/query", + text=example_response + ) + self.cli.revoke_privilege('read', 'testdb', 'test') + + self.assertEqual( + m.last_request.qs['q'][0], + 'revoke read on "testdb" from "test"' + ) + + @raises(Exception) + def test_revoke_privilege_invalid(self): + """Test revoke invalid privs for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password') + with _mocked_session(cli, 'get', 400): + self.cli.revoke_privilege('', 'testdb', 'test') + + def test_get_list_privileges(self): + """Tst get list of privs for TestInfluxDBClient object.""" + data = {'results': [ + {'series': [ + {'columns': ['database', 'privilege'], + 'values': [ + ['db1', 'READ'], + ['db2', 'ALL PRIVILEGES'], + ['db3', 'NO PRIVILEGES']]} + ]} + ]} + + with _mocked_session(self.cli, 'get', 200, json.dumps(data)): + self.assertListEqual( + self.cli.get_list_privileges('test'), + [{'database': 'db1', 'privilege': 'READ'}, + {'database': 'db2', 'privilege': 'ALL PRIVILEGES'}, + {'database': 'db3', 'privilege': 'NO PRIVILEGES'}] + ) + + @raises(Exception) + def test_get_list_privileges_fails(self): + """Test failed get list of privs for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password') + with _mocked_session(cli, 'get', 401): + cli.get_list_privileges('test') + + def test_invalid_port_fails(self): + """Test invalid port fail for TestInfluxDBClient object.""" + with self.assertRaises(ValueError): + InfluxDBClient('host', '80/redir', 'username', 'password') + + def test_chunked_response(self): + """Test chunked reponse for TestInfluxDBClient object.""" + example_response = \ + u'{"results":[{"statement_id":0,"series":' \ + '[{"name":"cpu","columns":["fieldKey","fieldType"],"values":' \ + '[["value","integer"]]}],"partial":true}]}\n{"results":' \ + '[{"statement_id":0,"series":[{"name":"iops","columns":' \ + '["fieldKey","fieldType"],"values":[["value","integer"]]}],' \ + '"partial":true}]}\n{"results":[{"statement_id":0,"series":' \ + '[{"name":"load","columns":["fieldKey","fieldType"],"values":' \ + '[["value","integer"]]}],"partial":true}]}\n{"results":' \ + '[{"statement_id":0,"series":[{"name":"memory","columns":' \ + '["fieldKey","fieldType"],"values":[["value","integer"]]}]}]}\n' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/query", + text=example_response + ) + response = self.cli.query('show series limit 4 offset 0', + chunked=True, chunk_size=4) + self.assertTrue(len(response) == 4) + self.assertEqual(response.__repr__(), ResultSet( + {'series': [{'values': [['value', 'integer']], + 'name': 'cpu', + 'columns': ['fieldKey', 'fieldType']}, + {'values': [['value', 'integer']], + 'name': 'iops', + 'columns': ['fieldKey', 'fieldType']}, + {'values': [['value', 'integer']], + 'name': 'load', + 'columns': ['fieldKey', 'fieldType']}, + {'values': [['value', 'integer']], + 'name': 'memory', + 'columns': ['fieldKey', 'fieldType']}]} + ).__repr__()) + + +class FakeClient(InfluxDBClient): + """Set up a fake client instance of InfluxDBClient.""" + + def __init__(self, *args, **kwargs): + """Initialize an instance of the FakeClient object.""" + super(FakeClient, self).__init__(*args, **kwargs) + + def query(self, + query, + params=None, + expected_response_code=200, + database=None): + """Query data from the FakeClient object.""" + if query == 'Fail': + raise Exception("Fail") + elif query == 'Fail once' and self._host == 'host1': + raise Exception("Fail Once") + elif query == 'Fail twice' and self._host in 'host1 host2': + raise Exception("Fail Twice") + else: + return "Success" diff --git a/lib/influxdb/tests/dataframe_client_test.py b/lib/influxdb/tests/dataframe_client_test.py new file mode 100644 index 0000000..78f5437 --- /dev/null +++ b/lib/influxdb/tests/dataframe_client_test.py @@ -0,0 +1,711 @@ +# -*- coding: utf-8 -*- +"""Unit tests for misc module.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from datetime import timedelta + +import json +import unittest +import warnings +import requests_mock + +from influxdb.tests import skipIfPYpy, using_pypy +from nose.tools import raises + +from .client_test import _mocked_session + +if not using_pypy: + import pandas as pd + from pandas.util.testing import assert_frame_equal + from influxdb import DataFrameClient + + +@skipIfPYpy +class TestDataFrameClient(unittest.TestCase): + """Set up a test DataFrameClient object.""" + + def setUp(self): + """Instantiate a TestDataFrameClient object.""" + # By default, raise exceptions on warnings + warnings.simplefilter('error', FutureWarning) + + def test_write_points_from_dataframe(self): + """Test write points from df in TestDataFrameClient object.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + expected = ( + b"foo column_one=\"1\",column_two=1i,column_three=1.0 0\n" + b"foo column_one=\"2\",column_two=2i,column_three=2.0 " + b"3600000000000\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + + cli.write_points(dataframe, 'foo') + self.assertEqual(m.last_request.body, expected) + + cli.write_points(dataframe, 'foo', tags=None) + self.assertEqual(m.last_request.body, expected) + + def test_dataframe_write_points_with_whitespace_measurement(self): + """write_points should escape white space in measurements.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + expected = ( + b"meas\\ with\\ space " + b"column_one=\"1\",column_two=1i,column_three=1.0 0\n" + b"meas\\ with\\ space " + b"column_one=\"2\",column_two=2i,column_three=2.0 " + b"3600000000000\n" + ) + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + cli = DataFrameClient(database='db') + cli.write_points(dataframe, 'meas with space') + self.assertEqual(m.last_request.body, expected) + + def test_dataframe_write_points_with_whitespace_in_column_names(self): + """write_points should escape white space in column names.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column one", "column two", + "column three"]) + expected = ( + b"foo column\\ one=\"1\",column\\ two=1i,column\\ three=1.0 0\n" + b"foo column\\ one=\"2\",column\\ two=2i,column\\ three=2.0 " + b"3600000000000\n" + ) + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + cli = DataFrameClient(database='db') + cli.write_points(dataframe, 'foo') + self.assertEqual(m.last_request.body, expected) + + def test_write_points_from_dataframe_with_none(self): + """Test write points from df in TestDataFrameClient object.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[["1", None, 1.0], ["2", 2.0, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + expected = ( + b"foo column_one=\"1\",column_three=1.0 0\n" + b"foo column_one=\"2\",column_two=2.0,column_three=2.0 " + b"3600000000000\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + + cli.write_points(dataframe, 'foo') + self.assertEqual(m.last_request.body, expected) + + cli.write_points(dataframe, 'foo', tags=None) + self.assertEqual(m.last_request.body, expected) + + def test_write_points_from_dataframe_with_line_of_none(self): + """Test write points from df in TestDataFrameClient object.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[[None, None, None], ["2", 2.0, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + expected = ( + b"foo column_one=\"2\",column_two=2.0,column_three=2.0 " + b"3600000000000\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + + cli.write_points(dataframe, 'foo') + self.assertEqual(m.last_request.body, expected) + + cli.write_points(dataframe, 'foo', tags=None) + self.assertEqual(m.last_request.body, expected) + + def test_write_points_from_dataframe_with_all_none(self): + """Test write points from df in TestDataFrameClient object.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[[None, None, None], [None, None, None]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + expected = ( + b"\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + + cli.write_points(dataframe, 'foo') + self.assertEqual(m.last_request.body, expected) + + cli.write_points(dataframe, 'foo', tags=None) + self.assertEqual(m.last_request.body, expected) + + def test_write_points_from_dataframe_in_batches(self): + """Test write points in batch from df in TestDataFrameClient object.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + self.assertTrue(cli.write_points(dataframe, "foo", batch_size=1)) + + def test_write_points_from_dataframe_with_tag_columns(self): + """Test write points from df w/tag in TestDataFrameClient object.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[['blue', 1, "1", 1, 1.0], + ['red', 0, "2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["tag_one", "tag_two", "column_one", + "column_two", "column_three"]) + expected = ( + b"foo,tag_one=blue,tag_two=1 " + b"column_one=\"1\",column_two=1i,column_three=1.0 " + b"0\n" + b"foo,tag_one=red,tag_two=0 " + b"column_one=\"2\",column_two=2i,column_three=2.0 " + b"3600000000000\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + + cli.write_points(dataframe, 'foo', + tag_columns=['tag_one', 'tag_two']) + self.assertEqual(m.last_request.body, expected) + + cli.write_points(dataframe, 'foo', + tag_columns=['tag_one', 'tag_two'], tags=None) + self.assertEqual(m.last_request.body, expected) + + def test_write_points_from_dataframe_with_tag_cols_and_global_tags(self): + """Test write points from df w/tag + cols in TestDataFrameClient.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[['blue', 1, "1", 1, 1.0], + ['red', 0, "2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["tag_one", "tag_two", "column_one", + "column_two", "column_three"]) + expected = ( + b"foo,global_tag=value,tag_one=blue,tag_two=1 " + b"column_one=\"1\",column_two=1i,column_three=1.0 " + b"0\n" + b"foo,global_tag=value,tag_one=red,tag_two=0 " + b"column_one=\"2\",column_two=2i,column_three=2.0 " + b"3600000000000\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + + cli.write_points(dataframe, 'foo', + tag_columns=['tag_one', 'tag_two'], + tags={'global_tag': 'value'}) + self.assertEqual(m.last_request.body, expected) + + def test_write_points_from_dataframe_with_tag_cols_and_defaults(self): + """Test default write points from df w/tag in TestDataFrameClient.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[['blue', 1, "1", 1, 1.0, 'hot'], + ['red', 0, "2", 2, 2.0, 'cold']], + index=[now, now + timedelta(hours=1)], + columns=["tag_one", "tag_two", "column_one", + "column_two", "column_three", + "tag_three"]) + expected_tags_and_fields = ( + b"foo,tag_one=blue " + b"column_one=\"1\",column_two=1i " + b"0\n" + b"foo,tag_one=red " + b"column_one=\"2\",column_two=2i " + b"3600000000000\n" + ) + + expected_tags_no_fields = ( + b"foo,tag_one=blue,tag_two=1 " + b"column_one=\"1\",column_two=1i,column_three=1.0," + b"tag_three=\"hot\" 0\n" + b"foo,tag_one=red,tag_two=0 " + b"column_one=\"2\",column_two=2i,column_three=2.0," + b"tag_three=\"cold\" 3600000000000\n" + ) + + expected_fields_no_tags = ( + b"foo,tag_one=blue,tag_three=hot,tag_two=1 " + b"column_one=\"1\",column_two=1i,column_three=1.0 " + b"0\n" + b"foo,tag_one=red,tag_three=cold,tag_two=0 " + b"column_one=\"2\",column_two=2i,column_three=2.0 " + b"3600000000000\n" + ) + + expected_no_tags_no_fields = ( + b"foo " + b"tag_one=\"blue\",tag_two=1i,column_one=\"1\"," + b"column_two=1i,column_three=1.0,tag_three=\"hot\" " + b"0\n" + b"foo " + b"tag_one=\"red\",tag_two=0i,column_one=\"2\"," + b"column_two=2i,column_three=2.0,tag_three=\"cold\" " + b"3600000000000\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + + cli.write_points(dataframe, 'foo', + field_columns=['column_one', 'column_two'], + tag_columns=['tag_one']) + self.assertEqual(m.last_request.body, expected_tags_and_fields) + + cli.write_points(dataframe, 'foo', + tag_columns=['tag_one', 'tag_two']) + self.assertEqual(m.last_request.body, expected_tags_no_fields) + + cli.write_points(dataframe, 'foo', + field_columns=['column_one', 'column_two', + 'column_three']) + self.assertEqual(m.last_request.body, expected_fields_no_tags) + + cli.write_points(dataframe, 'foo') + self.assertEqual(m.last_request.body, expected_no_tags_no_fields) + + def test_write_points_from_dataframe_with_tag_escaped(self): + """Test write points from df w/escaped tag in TestDataFrameClient.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame( + data=[ + ['blue orange', "1", 1, 'hot=cold'], # space, equal + ['red,green', "2", 2, r'cold\fire'], # comma, backslash + ['some', "2", 2, ''], # skip empty + ['some', "2", 2, None], # skip None + ['', "2", 2, None], # all tags empty + ], + index=pd.period_range(now, freq='H', periods=5), + columns=["tag_one", "column_one", "column_two", "tag_three"] + ) + + expected_escaped_tags = ( + b"foo,tag_one=blue\\ orange,tag_three=hot\\=cold " + b"column_one=\"1\",column_two=1i " + b"0\n" + b"foo,tag_one=red\\,green,tag_three=cold\\\\fire " + b"column_one=\"2\",column_two=2i " + b"3600000000000\n" + b"foo,tag_one=some " + b"column_one=\"2\",column_two=2i " + b"7200000000000\n" + b"foo,tag_one=some " + b"column_one=\"2\",column_two=2i " + b"10800000000000\n" + b"foo " + b"column_one=\"2\",column_two=2i " + b"14400000000000\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + cli = DataFrameClient(database='db') + cli.write_points(dataframe, 'foo', + field_columns=['column_one', 'column_two'], + tag_columns=['tag_one', 'tag_three']) + self.assertEqual(m.last_request.body, expected_escaped_tags) + + def test_write_points_from_dataframe_with_numeric_column_names(self): + """Test write points from df with numeric cols.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + # df with numeric column names + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)]) + + expected = ( + b'foo,hello=there 0=\"1\",1=1i,2=1.0 0\n' + b'foo,hello=there 0=\"2\",1=2i,2=2.0 3600000000000\n' + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + cli.write_points(dataframe, "foo", {"hello": "there"}) + + self.assertEqual(m.last_request.body, expected) + + def test_write_points_from_dataframe_with_numeric_precision(self): + """Test write points from df with numeric precision.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + # df with numeric column names + dataframe = pd.DataFrame(data=[["1", 1, 1.1111111111111], + ["2", 2, 2.2222222222222]], + index=[now, now + timedelta(hours=1)]) + + expected_default_precision = ( + b'foo,hello=there 0=\"1\",1=1i,2=1.11111111111 0\n' + b'foo,hello=there 0=\"2\",1=2i,2=2.22222222222 3600000000000\n' + ) + + expected_specified_precision = ( + b'foo,hello=there 0=\"1\",1=1i,2=1.1111 0\n' + b'foo,hello=there 0=\"2\",1=2i,2=2.2222 3600000000000\n' + ) + + expected_full_precision = ( + b'foo,hello=there 0=\"1\",1=1i,2=1.1111111111111 0\n' + b'foo,hello=there 0=\"2\",1=2i,2=2.2222222222222 3600000000000\n' + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + cli.write_points(dataframe, "foo", {"hello": "there"}) + + self.assertEqual(m.last_request.body, expected_default_precision) + + cli = DataFrameClient(database='db') + cli.write_points(dataframe, "foo", {"hello": "there"}, + numeric_precision=4) + + self.assertEqual(m.last_request.body, expected_specified_precision) + + cli = DataFrameClient(database='db') + cli.write_points(dataframe, "foo", {"hello": "there"}, + numeric_precision='full') + + self.assertEqual(m.last_request.body, expected_full_precision) + + def test_write_points_from_dataframe_with_period_index(self): + """Test write points from df with period index.""" + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[pd.Period('1970-01-01'), + pd.Period('1970-01-02')], + columns=["column_one", "column_two", + "column_three"]) + + expected = ( + b"foo column_one=\"1\",column_two=1i,column_three=1.0 0\n" + b"foo column_one=\"2\",column_two=2i,column_three=2.0 " + b"86400000000000\n" + ) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + cli.write_points(dataframe, "foo") + + self.assertEqual(m.last_request.body, expected) + + def test_write_points_from_dataframe_with_time_precision(self): + """Test write points from df with time precision.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/write", + status_code=204) + + cli = DataFrameClient(database='db') + measurement = "foo" + + cli.write_points(dataframe, measurement, time_precision='h') + self.assertEqual(m.last_request.qs['precision'], ['h']) + self.assertEqual( + b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' + b'column_one="2",column_two=2i,column_three=2.0 1\n', + m.last_request.body, + ) + + cli.write_points(dataframe, measurement, time_precision='m') + self.assertEqual(m.last_request.qs['precision'], ['m']) + self.assertEqual( + b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' + b'column_one="2",column_two=2i,column_three=2.0 60\n', + m.last_request.body, + ) + + cli.write_points(dataframe, measurement, time_precision='s') + self.assertEqual(m.last_request.qs['precision'], ['s']) + self.assertEqual( + b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' + b'column_one="2",column_two=2i,column_three=2.0 3600\n', + m.last_request.body, + ) + + cli.write_points(dataframe, measurement, time_precision='ms') + self.assertEqual(m.last_request.qs['precision'], ['ms']) + self.assertEqual( + b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' + b'column_one="2",column_two=2i,column_three=2.0 3600000\n', + m.last_request.body, + ) + + cli.write_points(dataframe, measurement, time_precision='u') + self.assertEqual(m.last_request.qs['precision'], ['u']) + self.assertEqual( + b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' + b'column_one="2",column_two=2i,column_three=2.0 3600000000\n', + m.last_request.body, + ) + + cli.write_points(dataframe, measurement, time_precision='n') + self.assertEqual(m.last_request.qs['precision'], ['n']) + self.assertEqual( + b'foo column_one="1",column_two=1i,column_three=1.0 0\n' + b'foo column_one="2",column_two=2i,column_three=2.0 ' + b'3600000000000\n', + m.last_request.body, + ) + + @raises(TypeError) + def test_write_points_from_dataframe_fails_without_time_index(self): + """Test failed write points from df without time index.""" + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + columns=["column_one", "column_two", + "column_three"]) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series", + status_code=204) + + cli = DataFrameClient(database='db') + cli.write_points(dataframe, "foo") + + @raises(TypeError) + def test_write_points_from_dataframe_fails_with_series(self): + """Test failed write points from df with series.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.Series(data=[1.0, 2.0], + index=[now, now + timedelta(hours=1)]) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series", + status_code=204) + + cli = DataFrameClient(database='db') + cli.write_points(dataframe, "foo") + + def test_query_into_dataframe(self): + """Test query into df for TestDataFrameClient object.""" + data = { + "results": [{ + "series": [ + {"measurement": "network", + "tags": {"direction": ""}, + "columns": ["time", "value"], + "values":[["2009-11-10T23:00:00Z", 23422]] + }, + {"measurement": "network", + "tags": {"direction": "in"}, + "columns": ["time", "value"], + "values": [["2009-11-10T23:00:00Z", 23422], + ["2009-11-10T23:00:00Z", 23422], + ["2009-11-10T23:00:00Z", 23422]] + } + ] + }] + } + + pd1 = pd.DataFrame( + [[23422]], columns=['value'], + index=pd.to_datetime(["2009-11-10T23:00:00Z"])) + pd1.index = pd1.index.tz_localize('UTC') + pd2 = pd.DataFrame( + [[23422], [23422], [23422]], columns=['value'], + index=pd.to_datetime(["2009-11-10T23:00:00Z", + "2009-11-10T23:00:00Z", + "2009-11-10T23:00:00Z"])) + pd2.index = pd2.index.tz_localize('UTC') + expected = { + ('network', (('direction', ''),)): pd1, + ('network', (('direction', 'in'),)): pd2 + } + + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + with _mocked_session(cli, 'GET', 200, data): + result = cli.query('select value from network group by direction;') + for k in expected: + assert_frame_equal(expected[k], result[k]) + + def test_multiquery_into_dataframe(self): + """Test multiquyer into df for TestDataFrameClient object.""" + data = { + "results": [ + { + "series": [ + { + "name": "cpu_load_short", + "columns": ["time", "value"], + "values": [ + ["2015-01-29T21:55:43.702900257Z", 0.55], + ["2015-01-29T21:55:43.702900257Z", 23422], + ["2015-06-11T20:46:02Z", 0.64] + ] + } + ] + }, { + "series": [ + { + "name": "cpu_load_short", + "columns": ["time", "count"], + "values": [ + ["1970-01-01T00:00:00Z", 3] + ] + } + ] + } + ] + } + + pd1 = pd.DataFrame( + [[0.55], [23422.0], [0.64]], columns=['value'], + index=pd.to_datetime([ + "2015-01-29 21:55:43.702900257+0000", + "2015-01-29 21:55:43.702900257+0000", + "2015-06-11 20:46:02+0000"])).tz_localize('UTC') + pd2 = pd.DataFrame( + [[3]], columns=['count'], + index=pd.to_datetime(["1970-01-01 00:00:00+00:00"]))\ + .tz_localize('UTC') + expected = [{'cpu_load_short': pd1}, {'cpu_load_short': pd2}] + + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + iql = "SELECT value FROM cpu_load_short WHERE region='us-west';"\ + "SELECT count(value) FROM cpu_load_short WHERE region='us-west'" + with _mocked_session(cli, 'GET', 200, data): + result = cli.query(iql) + for r, e in zip(result, expected): + for k in e: + assert_frame_equal(e[k], r[k]) + + def test_query_with_empty_result(self): + """Test query with empty results in TestDataFrameClient object.""" + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + with _mocked_session(cli, 'GET', 200, {"results": [{}]}): + result = cli.query('select column_one from foo;') + self.assertEqual(result, {}) + + def test_get_list_database(self): + """Test get list of databases in TestDataFrameClient object.""" + data = {'results': [ + {'series': [ + {'measurement': 'databases', + 'values': [ + ['new_db_1'], + ['new_db_2']], + 'columns': ['name']}]} + ]} + + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + with _mocked_session(cli, 'get', 200, json.dumps(data)): + self.assertListEqual( + cli.get_list_database(), + [{'name': 'new_db_1'}, {'name': 'new_db_2'}] + ) + + def test_datetime_to_epoch(self): + """Test convert datetime to epoch in TestDataFrameClient object.""" + timestamp = pd.Timestamp('2013-01-01 00:00:00.000+00:00') + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + + self.assertEqual( + cli._datetime_to_epoch(timestamp), + 1356998400.0 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='h'), + 1356998400.0 / 3600 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='m'), + 1356998400.0 / 60 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='s'), + 1356998400.0 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='ms'), + 1356998400000.0 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='u'), + 1356998400000000.0 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='n'), + 1356998400000000000.0 + ) + + def test_dsn_constructor(self): + """Test data source name deconstructor in TestDataFrameClient.""" + client = DataFrameClient.from_dsn('influxdb://localhost:8086') + self.assertIsInstance(client, DataFrameClient) + self.assertEqual('http://localhost:8086', client._baseurl) diff --git a/lib/influxdb/tests/helper_test.py b/lib/influxdb/tests/helper_test.py new file mode 100644 index 0000000..6f24e85 --- /dev/null +++ b/lib/influxdb/tests/helper_test.py @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- +"""Set of series helper functions for test.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from datetime import datetime, timedelta + +import unittest +import warnings + +import mock +from influxdb import SeriesHelper, InfluxDBClient +from requests.exceptions import ConnectionError + + +class TestSeriesHelper(unittest.TestCase): + """Define the SeriesHelper test object.""" + + @classmethod + def setUpClass(cls): + """Set up the TestSeriesHelper object.""" + super(TestSeriesHelper, cls).setUpClass() + + TestSeriesHelper.client = InfluxDBClient( + 'host', + 8086, + 'username', + 'password', + 'database' + ) + + class MySeriesHelper(SeriesHelper): + """Define a SeriesHelper object.""" + + class Meta: + """Define metadata for the SeriesHelper object.""" + + client = TestSeriesHelper.client + series_name = 'events.stats.{server_name}' + fields = ['some_stat'] + tags = ['server_name', 'other_tag'] + bulk_size = 5 + autocommit = True + + TestSeriesHelper.MySeriesHelper = MySeriesHelper + + def tearDown(self): + """Deconstruct the TestSeriesHelper object.""" + super(TestSeriesHelper, self).tearDown() + TestSeriesHelper.MySeriesHelper._reset_() + self.assertEqual( + TestSeriesHelper.MySeriesHelper._json_body_(), + [], + 'Resetting helper did not empty datapoints.') + + def test_auto_commit(self): + """Test write_points called after valid number of events.""" + class AutoCommitTest(SeriesHelper): + """Define a SeriesHelper instance to test autocommit.""" + + class Meta: + """Define metadata for AutoCommitTest.""" + + series_name = 'events.stats.{server_name}' + fields = ['some_stat'] + tags = ['server_name', 'other_tag'] + bulk_size = 5 + client = InfluxDBClient() + autocommit = True + + fake_write_points = mock.MagicMock() + AutoCommitTest(server_name='us.east-1', some_stat=159, other_tag='gg') + AutoCommitTest._client.write_points = fake_write_points + AutoCommitTest(server_name='us.east-1', some_stat=158, other_tag='gg') + AutoCommitTest(server_name='us.east-1', some_stat=157, other_tag='gg') + AutoCommitTest(server_name='us.east-1', some_stat=156, other_tag='gg') + self.assertFalse(fake_write_points.called) + AutoCommitTest(server_name='us.east-1', some_stat=3443, other_tag='gg') + self.assertTrue(fake_write_points.called) + + @mock.patch('influxdb.helper.SeriesHelper._current_timestamp') + def testSingleSeriesName(self, current_timestamp): + """Test JSON conversion when there is only one series name.""" + current_timestamp.return_value = current_date = datetime.today() + TestSeriesHelper.MySeriesHelper( + server_name='us.east-1', other_tag='ello', some_stat=159) + TestSeriesHelper.MySeriesHelper( + server_name='us.east-1', other_tag='ello', some_stat=158) + TestSeriesHelper.MySeriesHelper( + server_name='us.east-1', other_tag='ello', some_stat=157) + TestSeriesHelper.MySeriesHelper( + server_name='us.east-1', other_tag='ello', some_stat=156) + expectation = [ + { + "measurement": "events.stats.us.east-1", + "tags": { + "other_tag": "ello", + "server_name": "us.east-1" + }, + "fields": { + "some_stat": 159 + }, + "time": current_date, + }, + { + "measurement": "events.stats.us.east-1", + "tags": { + "other_tag": "ello", + "server_name": "us.east-1" + }, + "fields": { + "some_stat": 158 + }, + "time": current_date, + }, + { + "measurement": "events.stats.us.east-1", + "tags": { + "other_tag": "ello", + "server_name": "us.east-1" + }, + "fields": { + "some_stat": 157 + }, + "time": current_date, + }, + { + "measurement": "events.stats.us.east-1", + "tags": { + "other_tag": "ello", + "server_name": "us.east-1" + }, + "fields": { + "some_stat": 156 + }, + "time": current_date, + } + ] + + rcvd = TestSeriesHelper.MySeriesHelper._json_body_() + self.assertTrue(all([el in expectation for el in rcvd]) and + all([el in rcvd for el in expectation]), + 'Invalid JSON body of time series returned from ' + '_json_body_ for one series name: {0}.'.format(rcvd)) + + @mock.patch('influxdb.helper.SeriesHelper._current_timestamp') + def testSeveralSeriesNames(self, current_timestamp): + """Test JSON conversion when there are multiple series names.""" + current_timestamp.return_value = current_date = datetime.today() + TestSeriesHelper.MySeriesHelper( + server_name='us.east-1', some_stat=159, other_tag='ello') + TestSeriesHelper.MySeriesHelper( + server_name='fr.paris-10', some_stat=158, other_tag='ello') + TestSeriesHelper.MySeriesHelper( + server_name='lu.lux', some_stat=157, other_tag='ello') + TestSeriesHelper.MySeriesHelper( + server_name='uk.london', some_stat=156, other_tag='ello') + expectation = [ + { + 'fields': { + 'some_stat': 157 + }, + 'measurement': 'events.stats.lu.lux', + 'tags': { + 'other_tag': 'ello', + 'server_name': 'lu.lux' + }, + "time": current_date, + }, + { + 'fields': { + 'some_stat': 156 + }, + 'measurement': 'events.stats.uk.london', + 'tags': { + 'other_tag': 'ello', + 'server_name': 'uk.london' + }, + "time": current_date, + }, + { + 'fields': { + 'some_stat': 158 + }, + 'measurement': 'events.stats.fr.paris-10', + 'tags': { + 'other_tag': 'ello', + 'server_name': 'fr.paris-10' + }, + "time": current_date, + }, + { + 'fields': { + 'some_stat': 159 + }, + 'measurement': 'events.stats.us.east-1', + 'tags': { + 'other_tag': 'ello', + 'server_name': 'us.east-1' + }, + "time": current_date, + } + ] + + rcvd = TestSeriesHelper.MySeriesHelper._json_body_() + self.assertTrue(all([el in expectation for el in rcvd]) and + all([el in rcvd for el in expectation]), + 'Invalid JSON body of time series returned from ' + '_json_body_ for several series names: {0}.' + .format(rcvd)) + + @mock.patch('influxdb.helper.SeriesHelper._current_timestamp') + def testSeriesWithoutTimeField(self, current_timestamp): + """Test that time is optional on a series without a time field.""" + current_date = datetime.today() + yesterday = current_date - timedelta(days=1) + current_timestamp.return_value = yesterday + TestSeriesHelper.MySeriesHelper( + server_name='us.east-1', other_tag='ello', + some_stat=159, time=current_date + ) + TestSeriesHelper.MySeriesHelper( + server_name='us.east-1', other_tag='ello', + some_stat=158, + ) + point1, point2 = TestSeriesHelper.MySeriesHelper._json_body_() + self.assertTrue('time' in point1 and 'time' in point2) + self.assertEqual(point1['time'], current_date) + self.assertEqual(point2['time'], yesterday) + + def testSeriesWithoutAllTags(self): + """Test that creating a data point without a tag throws an error.""" + class MyTimeFieldSeriesHelper(SeriesHelper): + + class Meta: + client = TestSeriesHelper.client + series_name = 'events.stats.{server_name}' + fields = ['some_stat', 'time'] + tags = ['server_name', 'other_tag'] + bulk_size = 5 + autocommit = True + + self.assertRaises(NameError, MyTimeFieldSeriesHelper, + **{"server_name": 'us.east-1', + "some_stat": 158}) + + @mock.patch('influxdb.helper.SeriesHelper._current_timestamp') + def testSeriesWithTimeField(self, current_timestamp): + """Test that time is optional on a series with a time field.""" + current_date = datetime.today() + yesterday = current_date - timedelta(days=1) + current_timestamp.return_value = yesterday + + class MyTimeFieldSeriesHelper(SeriesHelper): + + class Meta: + client = TestSeriesHelper.client + series_name = 'events.stats.{server_name}' + fields = ['some_stat', 'time'] + tags = ['server_name', 'other_tag'] + bulk_size = 5 + autocommit = True + + MyTimeFieldSeriesHelper( + server_name='us.east-1', other_tag='ello', + some_stat=159, time=current_date + ) + MyTimeFieldSeriesHelper( + server_name='us.east-1', other_tag='ello', + some_stat=158, + ) + point1, point2 = MyTimeFieldSeriesHelper._json_body_() + self.assertTrue('time' in point1 and 'time' in point2) + self.assertEqual(point1['time'], current_date) + self.assertEqual(point2['time'], yesterday) + + def testInvalidHelpers(self): + """Test errors in invalid helpers.""" + class MissingMeta(SeriesHelper): + """Define instance of SeriesHelper for missing meta.""" + + pass + + class MissingClient(SeriesHelper): + """Define SeriesHelper for missing client data.""" + + class Meta: + """Define metadat for MissingClient.""" + + series_name = 'events.stats.{server_name}' + fields = ['time', 'server_name'] + autocommit = True + + class MissingSeriesName(SeriesHelper): + """Define instance of SeriesHelper for missing series.""" + + class Meta: + """Define metadata for MissingSeriesName.""" + + fields = ['time', 'server_name'] + + class MissingFields(SeriesHelper): + """Define instance of SeriesHelper for missing fields.""" + + class Meta: + """Define metadata for MissingFields.""" + + series_name = 'events.stats.{server_name}' + + for cls in [MissingMeta, MissingClient, MissingFields, + MissingSeriesName]: + self.assertRaises( + AttributeError, cls, **{'time': 159, + 'server_name': 'us.east-1'}) + + @unittest.skip("Fails on py32") + def testWarnBulkSizeZero(self): + """Test warning for an invalid bulk size.""" + class WarnBulkSizeZero(SeriesHelper): + + class Meta: + client = TestSeriesHelper.client + series_name = 'events.stats.{server_name}' + fields = ['time', 'server_name'] + tags = [] + bulk_size = 0 + autocommit = True + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + try: + WarnBulkSizeZero(time=159, server_name='us.east-1') + except ConnectionError: + # Server defined in the client is invalid, we're testing + # the warning only. + pass + self.assertEqual(len(w), 1, + '{0} call should have generated one warning.' + .format(WarnBulkSizeZero)) + self.assertIn('forced to 1', str(w[-1].message), + 'Warning message did not contain "forced to 1".') + + def testWarnBulkSizeNoEffect(self): + """Test warning for a set bulk size but autocommit False.""" + class WarnBulkSizeNoEffect(SeriesHelper): + """Define SeriesHelper for warning on bulk size.""" + + class Meta: + """Define metadat for WarnBulkSizeNoEffect.""" + + series_name = 'events.stats.{server_name}' + fields = ['time', 'server_name'] + bulk_size = 5 + tags = [] + autocommit = False + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + WarnBulkSizeNoEffect(time=159, server_name='us.east-1') + self.assertEqual(len(w), 1, + '{0} call should have generated one warning.' + .format(WarnBulkSizeNoEffect)) + self.assertIn('has no affect', str(w[-1].message), + 'Warning message did not contain "has not affect".') diff --git a/lib/influxdb/tests/influxdb08/__init__.py b/lib/influxdb/tests/influxdb08/__init__.py new file mode 100644 index 0000000..0e79ed1 --- /dev/null +++ b/lib/influxdb/tests/influxdb08/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +"""Define the influxdb08 test package.""" diff --git a/lib/influxdb/tests/influxdb08/client_test.py b/lib/influxdb/tests/influxdb08/client_test.py new file mode 100644 index 0000000..39ab52d --- /dev/null +++ b/lib/influxdb/tests/influxdb08/client_test.py @@ -0,0 +1,904 @@ +# -*- coding: utf-8 -*- +"""Client unit tests.""" + +import json +import socket +import sys +import unittest +import random +import warnings + +import mock +import requests +import requests.exceptions +import requests_mock + +from nose.tools import raises +from mock import patch + +from influxdb.influxdb08 import InfluxDBClient +from influxdb.influxdb08.client import session + +if sys.version < '3': + import codecs + + def u(x): + """Test codec.""" + return codecs.unicode_escape_decode(x)[0] +else: + def u(x): + """Test codec.""" + return x + + +def _build_response_object(status_code=200, content=""): + resp = requests.Response() + resp.status_code = status_code + resp._content = content.encode("utf8") + return resp + + +def _mocked_session(method="GET", status_code=200, content=""): + method = method.upper() + + def request(*args, **kwargs): + """Define a request for the _mocked_session.""" + c = content + + # Check method + assert method == kwargs.get('method', 'GET') + + if method == 'POST': + data = kwargs.get('data', None) + + if data is not None: + # Data must be a string + assert isinstance(data, str) + + # Data must be a JSON string + assert c == json.loads(data, strict=True) + + c = data + + # Anyway, Content must be a JSON string (or empty string) + if not isinstance(c, str): + c = json.dumps(c) + + return _build_response_object(status_code=status_code, content=c) + + mocked = patch.object( + session, + 'request', + side_effect=request + ) + + return mocked + + +class TestInfluxDBClient(unittest.TestCase): + """Define a TestInfluxDBClient object.""" + + def setUp(self): + """Set up a TestInfluxDBClient object.""" + # By default, raise exceptions on warnings + warnings.simplefilter('error', FutureWarning) + + self.dummy_points = [ + { + "points": [ + ["1", 1, 1.0], + ["2", 2, 2.0] + ], + "name": "foo", + "columns": ["column_one", "column_two", "column_three"] + } + ] + + self.dsn_string = 'influxdb://uSr:pWd@host:1886/db' + + def test_scheme(self): + """Test database scheme for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') + self.assertEqual(cli._baseurl, 'http://host:8086') + + cli = InfluxDBClient( + 'host', 8086, 'username', 'password', 'database', ssl=True + ) + self.assertEqual(cli._baseurl, 'https://host:8086') + + def test_dsn(self): + """Test datasource name for TestInfluxDBClient object.""" + cli = InfluxDBClient.from_dsn(self.dsn_string) + self.assertEqual('http://host:1886', cli._baseurl) + self.assertEqual('uSr', cli._username) + self.assertEqual('pWd', cli._password) + self.assertEqual('db', cli._database) + self.assertFalse(cli._use_udp) + + cli = InfluxDBClient.from_dsn('udp+' + self.dsn_string) + self.assertTrue(cli._use_udp) + + cli = InfluxDBClient.from_dsn('https+' + self.dsn_string) + self.assertEqual('https://host:1886', cli._baseurl) + + cli = InfluxDBClient.from_dsn('https+' + self.dsn_string, + **{'ssl': False}) + self.assertEqual('http://host:1886', cli._baseurl) + + def test_switch_database(self): + """Test switch database for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') + cli.switch_database('another_database') + self.assertEqual(cli._database, 'another_database') + + @raises(FutureWarning) + def test_switch_db_deprecated(self): + """Test deprecated switch database for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') + cli.switch_db('another_database') + self.assertEqual(cli._database, 'another_database') + + def test_switch_user(self): + """Test switch user for TestInfluxDBClient object.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') + cli.switch_user('another_username', 'another_password') + self.assertEqual(cli._username, 'another_username') + self.assertEqual(cli._password, 'another_password') + + def test_write(self): + """Test write to database for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/write" + ) + cli = InfluxDBClient(database='db') + cli.write( + {"database": "mydb", + "retentionPolicy": "mypolicy", + "points": [{"name": "cpu_load_short", + "tags": {"host": "server01", + "region": "us-west"}, + "timestamp": "2009-11-10T23:00:00Z", + "values": {"value": 0.64}}]} + ) + + self.assertEqual( + json.loads(m.last_request.body), + {"database": "mydb", + "retentionPolicy": "mypolicy", + "points": [{"name": "cpu_load_short", + "tags": {"host": "server01", + "region": "us-west"}, + "timestamp": "2009-11-10T23:00:00Z", + "values": {"value": 0.64}}]} + ) + + def test_write_points(self): + """Test write points for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/db/db/series" + ) + + cli = InfluxDBClient(database='db') + cli.write_points( + self.dummy_points + ) + + self.assertListEqual( + json.loads(m.last_request.body), + self.dummy_points + ) + + def test_write_points_string(self): + """Test write string points for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/db/db/series" + ) + + cli = InfluxDBClient(database='db') + cli.write_points( + str(json.dumps(self.dummy_points)) + ) + + self.assertListEqual( + json.loads(m.last_request.body), + self.dummy_points + ) + + def test_write_points_batch(self): + """Test write batch points for TestInfluxDBClient object.""" + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + cli = InfluxDBClient('localhost', 8086, + 'username', 'password', 'db') + cli.write_points(data=self.dummy_points, batch_size=2) + self.assertEqual(1, m.call_count) + + def test_write_points_batch_invalid_size(self): + """Test write batch points invalid size for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + cli = InfluxDBClient('localhost', 8086, + 'username', 'password', 'db') + cli.write_points(data=self.dummy_points, batch_size=-2) + self.assertEqual(1, m.call_count) + + def test_write_points_batch_multiple_series(self): + """Test write points batch multiple series.""" + dummy_points = [ + {"points": [["1", 1, 1.0], ["2", 2, 2.0], ["3", 3, 3.0], + ["4", 4, 4.0], ["5", 5, 5.0]], + "name": "foo", + "columns": ["val1", "val2", "val3"]}, + {"points": [["1", 1, 1.0], ["2", 2, 2.0], ["3", 3, 3.0], + ["4", 4, 4.0], ["5", 5, 5.0], ["6", 6, 6.0], + ["7", 7, 7.0], ["8", 8, 8.0]], + "name": "bar", + "columns": ["val1", "val2", "val3"]}, + ] + expected_last_body = [{'points': [['7', 7, 7.0], ['8', 8, 8.0]], + 'name': 'bar', + 'columns': ['val1', 'val2', 'val3']}] + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + cli = InfluxDBClient('localhost', 8086, + 'username', 'password', 'db') + cli.write_points(data=dummy_points, batch_size=3) + self.assertEqual(m.call_count, 5) + self.assertEqual(expected_last_body, m.request_history[4].json()) + + def test_write_points_udp(self): + """Test write points UDP for TestInfluxDBClient object.""" + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + port = random.randint(4000, 8000) + s.bind(('0.0.0.0', port)) + + cli = InfluxDBClient( + 'localhost', 8086, 'root', 'root', + 'test', use_udp=True, udp_port=port + ) + cli.write_points(self.dummy_points) + + received_data, addr = s.recvfrom(1024) + + self.assertEqual(self.dummy_points, + json.loads(received_data.decode(), strict=True)) + + def test_write_bad_precision_udp(self): + """Test write UDP w/bad precision.""" + cli = InfluxDBClient( + 'localhost', 8086, 'root', 'root', + 'test', use_udp=True, udp_port=4444 + ) + + with self.assertRaisesRegexp( + Exception, + "InfluxDB only supports seconds precision for udp writes" + ): + cli.write_points( + self.dummy_points, + time_precision='ms' + ) + + @raises(Exception) + def test_write_points_fails(self): + """Test failed write points for TestInfluxDBClient object.""" + with _mocked_session('post', 500): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.write_points([]) + + def test_write_points_with_precision(self): + """Test write points with precision.""" + with _mocked_session('post', 200, self.dummy_points): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + self.assertTrue(cli.write_points(self.dummy_points)) + + def test_write_points_bad_precision(self): + """Test write points with bad precision.""" + cli = InfluxDBClient() + with self.assertRaisesRegexp( + Exception, + "Invalid time precision is given. \(use 's', 'm', 'ms' or 'u'\)" + ): + cli.write_points( + self.dummy_points, + time_precision='g' + ) + + @raises(Exception) + def test_write_points_with_precision_fails(self): + """Test write points where precision fails.""" + with _mocked_session('post', 500): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.write_points_with_precision([]) + + def test_delete_points(self): + """Test delete points for TestInfluxDBClient object.""" + with _mocked_session('delete', 204) as mocked: + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + self.assertTrue(cli.delete_points("foo")) + + self.assertEqual(len(mocked.call_args_list), 1) + args, kwds = mocked.call_args_list[0] + + self.assertEqual(kwds['params'], + {'u': 'username', 'p': 'password'}) + self.assertEqual(kwds['url'], 'http://host:8086/db/db/series/foo') + + @raises(Exception) + def test_delete_points_with_wrong_name(self): + """Test delete points with wrong name.""" + with _mocked_session('delete', 400): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.delete_points("nonexist") + + @raises(NotImplementedError) + def test_create_scheduled_delete(self): + """Test create scheduled deletes.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.create_scheduled_delete([]) + + @raises(NotImplementedError) + def test_get_list_scheduled_delete(self): + """Test get schedule list of deletes TestInfluxDBClient.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.get_list_scheduled_delete() + + @raises(NotImplementedError) + def test_remove_scheduled_delete(self): + """Test remove scheduled delete TestInfluxDBClient.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.remove_scheduled_delete(1) + + def test_query(self): + """Test query for TestInfluxDBClient object.""" + data = [ + { + "name": "foo", + "columns": ["time", "sequence_number", "column_one"], + "points": [ + [1383876043, 16, "2"], [1383876043, 15, "1"], + [1383876035, 14, "2"], [1383876035, 13, "1"] + ] + } + ] + with _mocked_session('get', 200, data): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + result = cli.query('select column_one from foo;') + self.assertEqual(len(result[0]['points']), 4) + + def test_query_chunked(self): + """Test chunked query for TestInfluxDBClient object.""" + cli = InfluxDBClient(database='db') + example_object = { + 'points': [ + [1415206250119, 40001, 667], + [1415206244555, 30001, 7], + [1415206228241, 20001, 788], + [1415206212980, 10001, 555], + [1415197271586, 10001, 23] + ], + 'name': 'foo', + 'columns': [ + 'time', + 'sequence_number', + 'val' + ] + } + example_response = \ + json.dumps(example_object) + json.dumps(example_object) + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/db/db/series", + text=example_response + ) + + self.assertListEqual( + cli.query('select * from foo', chunked=True), + [example_object, example_object] + ) + + def test_query_chunked_unicode(self): + """Test unicode chunked query for TestInfluxDBClient object.""" + cli = InfluxDBClient(database='db') + example_object = { + 'points': [ + [1415206212980, 10001, u('unicode-\xcf\x89')], + [1415197271586, 10001, u('more-unicode-\xcf\x90')] + ], + 'name': 'foo', + 'columns': [ + 'time', + 'sequence_number', + 'val' + ] + } + example_response = \ + json.dumps(example_object) + json.dumps(example_object) + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/db/db/series", + text=example_response + ) + + self.assertListEqual( + cli.query('select * from foo', chunked=True), + [example_object, example_object] + ) + + @raises(Exception) + def test_query_fail(self): + """Test failed query for TestInfluxDBClient.""" + with _mocked_session('get', 401): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.query('select column_one from foo;') + + def test_query_bad_precision(self): + """Test query with bad precision for TestInfluxDBClient.""" + cli = InfluxDBClient() + with self.assertRaisesRegexp( + Exception, + "Invalid time precision is given. \(use 's', 'm', 'ms' or 'u'\)" + ): + cli.query('select column_one from foo', time_precision='g') + + def test_create_database(self): + """Test create database for TestInfluxDBClient.""" + with _mocked_session('post', 201, {"name": "new_db"}): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + self.assertTrue(cli.create_database('new_db')) + + @raises(Exception) + def test_create_database_fails(self): + """Test failed create database for TestInfluxDBClient.""" + with _mocked_session('post', 401): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.create_database('new_db') + + def test_delete_database(self): + """Test delete database for TestInfluxDBClient.""" + with _mocked_session('delete', 204): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + self.assertTrue(cli.delete_database('old_db')) + + @raises(Exception) + def test_delete_database_fails(self): + """Test failed delete database for TestInfluxDBClient.""" + with _mocked_session('delete', 401): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.delete_database('old_db') + + def test_get_list_database(self): + """Test get list of databases for TestInfluxDBClient.""" + data = [ + {"name": "a_db"} + ] + with _mocked_session('get', 200, data): + cli = InfluxDBClient('host', 8086, 'username', 'password') + self.assertEqual(len(cli.get_list_database()), 1) + self.assertEqual(cli.get_list_database()[0]['name'], 'a_db') + + @raises(Exception) + def test_get_list_database_fails(self): + """Test failed get list of databases for TestInfluxDBClient.""" + with _mocked_session('get', 401): + cli = InfluxDBClient('host', 8086, 'username', 'password') + cli.get_list_database() + + @raises(FutureWarning) + def test_get_database_list_deprecated(self): + """Test deprecated get database list for TestInfluxDBClient.""" + data = [ + {"name": "a_db"} + ] + with _mocked_session('get', 200, data): + cli = InfluxDBClient('host', 8086, 'username', 'password') + self.assertEqual(len(cli.get_database_list()), 1) + self.assertEqual(cli.get_database_list()[0]['name'], 'a_db') + + def test_delete_series(self): + """Test delete series for TestInfluxDBClient.""" + with _mocked_session('delete', 204): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.delete_series('old_series') + + @raises(Exception) + def test_delete_series_fails(self): + """Test failed delete series for TestInfluxDBClient.""" + with _mocked_session('delete', 401): + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.delete_series('old_series') + + def test_get_series_list(self): + """Test get list of series for TestInfluxDBClient.""" + cli = InfluxDBClient(database='db') + + with requests_mock.Mocker() as m: + example_response = \ + '[{"name":"list_series_result","columns":' \ + '["time","name"],"points":[[0,"foo"],[0,"bar"]]}]' + + m.register_uri( + requests_mock.GET, + "http://localhost:8086/db/db/series", + text=example_response + ) + + self.assertListEqual( + cli.get_list_series(), + ['foo', 'bar'] + ) + + def test_get_continuous_queries(self): + """Test get continuous queries for TestInfluxDBClient.""" + cli = InfluxDBClient(database='db') + + with requests_mock.Mocker() as m: + + # Tip: put this in a json linter! + example_response = '[ { "name": "continuous queries", "columns"' \ + ': [ "time", "id", "query" ], "points": [ [ ' \ + '0, 1, "select foo(bar,95) from \\"foo_bar' \ + 's\\" group by time(5m) into response_times.' \ + 'percentiles.5m.95" ], [ 0, 2, "select perce' \ + 'ntile(value,95) from \\"response_times\\" g' \ + 'roup by time(5m) into response_times.percen' \ + 'tiles.5m.95" ] ] } ]' + + m.register_uri( + requests_mock.GET, + "http://localhost:8086/db/db/series", + text=example_response + ) + + self.assertListEqual( + cli.get_list_continuous_queries(), + [ + 'select foo(bar,95) from "foo_bars" group ' + 'by time(5m) into response_times.percentiles.5m.95', + + 'select percentile(value,95) from "response_times" group ' + 'by time(5m) into response_times.percentiles.5m.95' + ] + ) + + def test_get_list_cluster_admins(self): + """Test get list of cluster admins, not implemented.""" + pass + + def test_add_cluster_admin(self): + """Test add cluster admin for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/cluster_admins" + ) + + cli = InfluxDBClient(database='db') + cli.add_cluster_admin( + new_username='paul', + new_password='laup' + ) + + self.assertDictEqual( + json.loads(m.last_request.body), + { + 'name': 'paul', + 'password': 'laup' + } + ) + + def test_update_cluster_admin_password(self): + """Test update cluster admin pass for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/cluster_admins/paul" + ) + + cli = InfluxDBClient(database='db') + cli.update_cluster_admin_password( + username='paul', + new_password='laup' + ) + + self.assertDictEqual( + json.loads(m.last_request.body), + {'password': 'laup'} + ) + + def test_delete_cluster_admin(self): + """Test delete cluster admin for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.DELETE, + "http://localhost:8086/cluster_admins/paul", + status_code=200, + ) + + cli = InfluxDBClient(database='db') + cli.delete_cluster_admin(username='paul') + + self.assertIsNone(m.last_request.body) + + def test_set_database_admin(self): + """Test set database admin for TestInfluxDBClient.""" + pass + + def test_unset_database_admin(self): + """Test unset database admin for TestInfluxDBClient.""" + pass + + def test_alter_database_admin(self): + """Test alter database admin for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/db/db/users/paul" + ) + + cli = InfluxDBClient(database='db') + cli.alter_database_admin( + username='paul', + is_admin=False + ) + + self.assertDictEqual( + json.loads(m.last_request.body), + { + 'admin': False + } + ) + + @raises(NotImplementedError) + def test_get_list_database_admins(self): + """Test get list of database admins for TestInfluxDBClient.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.get_list_database_admins() + + @raises(NotImplementedError) + def test_add_database_admin(self): + """Test add database admins for TestInfluxDBClient.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.add_database_admin('admin', 'admin_secret_password') + + @raises(NotImplementedError) + def test_update_database_admin_password(self): + """Test update database admin pass for TestInfluxDBClient.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.update_database_admin_password('admin', 'admin_secret_password') + + @raises(NotImplementedError) + def test_delete_database_admin(self): + """Test delete database admin for TestInfluxDBClient.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.delete_database_admin('admin') + + def test_get_database_users(self): + """Test get database users for TestInfluxDBClient.""" + cli = InfluxDBClient('localhost', 8086, 'username', 'password', 'db') + + example_response = \ + '[{"name":"paul","isAdmin":false,"writeTo":".*","readFrom":".*"},'\ + '{"name":"bobby","isAdmin":false,"writeTo":".*","readFrom":".*"}]' + + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.GET, + "http://localhost:8086/db/db/users", + text=example_response + ) + users = cli.get_database_users() + + self.assertEqual(json.loads(example_response), users) + + def test_add_database_user(self): + """Test add database user for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/db/db/users" + ) + cli = InfluxDBClient(database='db') + cli.add_database_user( + new_username='paul', + new_password='laup', + permissions=('.*', '.*') + ) + + self.assertDictEqual( + json.loads(m.last_request.body), + { + 'writeTo': '.*', + 'password': 'laup', + 'readFrom': '.*', + 'name': 'paul' + } + ) + + def test_add_database_user_bad_permissions(self): + """Test add database user with bad perms for TestInfluxDBClient.""" + cli = InfluxDBClient() + + with self.assertRaisesRegexp( + Exception, + "'permissions' must be \(readFrom, writeTo\) tuple" + ): + cli.add_database_user( + new_password='paul', + new_username='paul', + permissions=('hello', 'hello', 'hello') + ) + + def test_alter_database_user_password(self): + """Test alter database user pass for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/db/db/users/paul" + ) + + cli = InfluxDBClient(database='db') + cli.alter_database_user( + username='paul', + password='n3wp4ss!' + ) + + self.assertDictEqual( + json.loads(m.last_request.body), + { + 'password': 'n3wp4ss!' + } + ) + + def test_alter_database_user_permissions(self): + """Test alter database user perms for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/db/db/users/paul" + ) + + cli = InfluxDBClient(database='db') + cli.alter_database_user( + username='paul', + permissions=('^$', '.*') + ) + + self.assertDictEqual( + json.loads(m.last_request.body), + { + 'readFrom': '^$', + 'writeTo': '.*' + } + ) + + def test_alter_database_user_password_and_permissions(self): + """Test alter database user pass and perms for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/db/db/users/paul" + ) + + cli = InfluxDBClient(database='db') + cli.alter_database_user( + username='paul', + password='n3wp4ss!', + permissions=('^$', '.*') + ) + + self.assertDictEqual( + json.loads(m.last_request.body), + { + 'password': 'n3wp4ss!', + 'readFrom': '^$', + 'writeTo': '.*' + } + ) + + def test_update_database_user_password_current_user(self): + """Test update database user pass for TestInfluxDBClient.""" + cli = InfluxDBClient( + username='root', + password='hello', + database='database' + ) + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.POST, + "http://localhost:8086/db/database/users/root" + ) + + cli.update_database_user_password( + username='root', + new_password='bye' + ) + + self.assertEqual(cli._password, 'bye') + + def test_delete_database_user(self): + """Test delete database user for TestInfluxDBClient.""" + with requests_mock.Mocker() as m: + m.register_uri( + requests_mock.DELETE, + "http://localhost:8086/db/db/users/paul" + ) + + cli = InfluxDBClient(database='db') + cli.delete_database_user(username='paul') + + self.assertIsNone(m.last_request.body) + + @raises(NotImplementedError) + def test_update_permission(self): + """Test update permission for TestInfluxDBClient.""" + cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') + cli.update_permission('admin', []) + + @mock.patch('requests.Session.request') + def test_request_retry(self, mock_request): + """Test that two connection errors will be handled.""" + class CustomMock(object): + """Define CustomMock object.""" + + def __init__(self): + self.i = 0 + + def connection_error(self, *args, **kwargs): + """Test connection error in CustomMock.""" + self.i += 1 + + if self.i < 3: + raise requests.exceptions.ConnectionError + else: + r = requests.Response() + r.status_code = 200 + return r + + mock_request.side_effect = CustomMock().connection_error + + cli = InfluxDBClient(database='db') + cli.write_points( + self.dummy_points + ) + + @mock.patch('requests.Session.request') + def test_request_retry_raises(self, mock_request): + """Test that three connection errors will not be handled.""" + class CustomMock(object): + """Define CustomMock object.""" + + def __init__(self): + """Initialize the object.""" + self.i = 0 + + def connection_error(self, *args, **kwargs): + """Test the connection error for CustomMock.""" + self.i += 1 + + if self.i < 4: + raise requests.exceptions.ConnectionError + else: + r = requests.Response() + r.status_code = 200 + return r + + mock_request.side_effect = CustomMock().connection_error + + cli = InfluxDBClient(database='db') + + with self.assertRaises(requests.exceptions.ConnectionError): + cli.write_points(self.dummy_points) diff --git a/lib/influxdb/tests/influxdb08/dataframe_client_test.py b/lib/influxdb/tests/influxdb08/dataframe_client_test.py new file mode 100644 index 0000000..6e6fa2c --- /dev/null +++ b/lib/influxdb/tests/influxdb08/dataframe_client_test.py @@ -0,0 +1,331 @@ +# -*- coding: utf-8 -*- +"""Unit tests for misc module.""" + +from datetime import timedelta + +import copy +import json +import unittest +import warnings + +import requests_mock + +from nose.tools import raises + +from influxdb.tests import skipIfPYpy, using_pypy + +from .client_test import _mocked_session + +if not using_pypy: + import pandas as pd + from pandas.util.testing import assert_frame_equal + from influxdb.influxdb08 import DataFrameClient + + +@skipIfPYpy +class TestDataFrameClient(unittest.TestCase): + """Define the DataFramClient test object.""" + + def setUp(self): + """Set up an instance of TestDataFrameClient object.""" + # By default, raise exceptions on warnings + warnings.simplefilter('error', FutureWarning) + + def test_write_points_from_dataframe(self): + """Test write points from dataframe.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + points = [ + { + "points": [ + ["1", 1, 1.0, 0], + ["2", 2, 2.0, 3600] + ], + "name": "foo", + "columns": ["column_one", "column_two", "column_three", "time"] + } + ] + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + + cli = DataFrameClient(database='db') + cli.write_points({"foo": dataframe}) + + self.assertListEqual(json.loads(m.last_request.body), points) + + def test_write_points_from_dataframe_with_float_nan(self): + """Test write points from dataframe with NaN float.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[[1, float("NaN"), 1.0], [2, 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + points = [ + { + "points": [ + [1, None, 1.0, 0], + [2, 2, 2.0, 3600] + ], + "name": "foo", + "columns": ["column_one", "column_two", "column_three", "time"] + } + ] + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + + cli = DataFrameClient(database='db') + cli.write_points({"foo": dataframe}) + + self.assertListEqual(json.loads(m.last_request.body), points) + + def test_write_points_from_dataframe_in_batches(self): + """Test write points from dataframe in batches.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + + cli = DataFrameClient(database='db') + self.assertTrue(cli.write_points({"foo": dataframe}, batch_size=1)) + + def test_write_points_from_dataframe_with_numeric_column_names(self): + """Test write points from dataframe with numeric columns.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + # df with numeric column names + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)]) + points = [ + { + "points": [ + ["1", 1, 1.0, 0], + ["2", 2, 2.0, 3600] + ], + "name": "foo", + "columns": ['0', '1', '2', "time"] + } + ] + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + + cli = DataFrameClient(database='db') + cli.write_points({"foo": dataframe}) + + self.assertListEqual(json.loads(m.last_request.body), points) + + def test_write_points_from_dataframe_with_period_index(self): + """Test write points from dataframe with period index.""" + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[pd.Period('1970-01-01'), + pd.Period('1970-01-02')], + columns=["column_one", "column_two", + "column_three"]) + points = [ + { + "points": [ + ["1", 1, 1.0, 0], + ["2", 2, 2.0, 86400] + ], + "name": "foo", + "columns": ["column_one", "column_two", "column_three", "time"] + } + ] + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + + cli = DataFrameClient(database='db') + cli.write_points({"foo": dataframe}) + + self.assertListEqual(json.loads(m.last_request.body), points) + + def test_write_points_from_dataframe_with_time_precision(self): + """Test write points from dataframe with time precision.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + index=[now, now + timedelta(hours=1)], + columns=["column_one", "column_two", + "column_three"]) + points = [ + { + "points": [ + ["1", 1, 1.0, 0], + ["2", 2, 2.0, 3600] + ], + "name": "foo", + "columns": ["column_one", "column_two", "column_three", "time"] + } + ] + + points_ms = copy.deepcopy(points) + points_ms[0]["points"][1][-1] = 3600 * 1000 + + points_us = copy.deepcopy(points) + points_us[0]["points"][1][-1] = 3600 * 1000000 + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + + cli = DataFrameClient(database='db') + + cli.write_points({"foo": dataframe}, time_precision='s') + self.assertListEqual(json.loads(m.last_request.body), points) + + cli.write_points({"foo": dataframe}, time_precision='m') + self.assertListEqual(json.loads(m.last_request.body), points_ms) + + cli.write_points({"foo": dataframe}, time_precision='u') + self.assertListEqual(json.loads(m.last_request.body), points_us) + + @raises(TypeError) + def test_write_points_from_dataframe_fails_without_time_index(self): + """Test write points from dataframe that fails without time index.""" + dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], + columns=["column_one", "column_two", + "column_three"]) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + + cli = DataFrameClient(database='db') + cli.write_points({"foo": dataframe}) + + @raises(TypeError) + def test_write_points_from_dataframe_fails_with_series(self): + """Test failed write points from dataframe with series.""" + now = pd.Timestamp('1970-01-01 00:00+00:00') + dataframe = pd.Series(data=[1.0, 2.0], + index=[now, now + timedelta(hours=1)]) + + with requests_mock.Mocker() as m: + m.register_uri(requests_mock.POST, + "http://localhost:8086/db/db/series") + + cli = DataFrameClient(database='db') + cli.write_points({"foo": dataframe}) + + def test_query_into_dataframe(self): + """Test query into a dataframe.""" + data = [ + { + "name": "foo", + "columns": ["time", "sequence_number", "column_one"], + "points": [ + [3600, 16, 2], [3600, 15, 1], + [0, 14, 2], [0, 13, 1] + ] + } + ] + # dataframe sorted ascending by time first, then sequence_number + dataframe = pd.DataFrame(data=[[13, 1], [14, 2], [15, 1], [16, 2]], + index=pd.to_datetime([0, 0, + 3600, 3600], + unit='s', utc=True), + columns=['sequence_number', 'column_one']) + with _mocked_session('get', 200, data): + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + result = cli.query('select column_one from foo;') + assert_frame_equal(dataframe, result) + + def test_query_multiple_time_series(self): + """Test query for multiple time series.""" + data = [ + { + "name": "series1", + "columns": ["time", "mean", "min", "max", "stddev"], + "points": [[0, 323048, 323048, 323048, 0]] + }, + { + "name": "series2", + "columns": ["time", "mean", "min", "max", "stddev"], + "points": [[0, -2.8233, -2.8503, -2.7832, 0.0173]] + }, + { + "name": "series3", + "columns": ["time", "mean", "min", "max", "stddev"], + "points": [[0, -0.01220, -0.01220, -0.01220, 0]] + } + ] + dataframes = { + 'series1': pd.DataFrame(data=[[323048, 323048, 323048, 0]], + index=pd.to_datetime([0], unit='s', + utc=True), + columns=['mean', 'min', 'max', 'stddev']), + 'series2': pd.DataFrame(data=[[-2.8233, -2.8503, -2.7832, 0.0173]], + index=pd.to_datetime([0], unit='s', + utc=True), + columns=['mean', 'min', 'max', 'stddev']), + 'series3': pd.DataFrame(data=[[-0.01220, -0.01220, -0.01220, 0]], + index=pd.to_datetime([0], unit='s', + utc=True), + columns=['mean', 'min', 'max', 'stddev']) + } + with _mocked_session('get', 200, data): + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + result = cli.query("""select mean(value), min(value), max(value), + stddev(value) from series1, series2, series3""") + self.assertEqual(dataframes.keys(), result.keys()) + for key in dataframes.keys(): + assert_frame_equal(dataframes[key], result[key]) + + def test_query_with_empty_result(self): + """Test query with empty results.""" + with _mocked_session('get', 200, []): + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + result = cli.query('select column_one from foo;') + self.assertEqual(result, []) + + def test_list_series(self): + """Test list of series for dataframe object.""" + response = [ + { + 'columns': ['time', 'name'], + 'name': 'list_series_result', + 'points': [[0, 'seriesA'], [0, 'seriesB']] + } + ] + with _mocked_session('get', 200, response): + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + series_list = cli.get_list_series() + self.assertEqual(series_list, ['seriesA', 'seriesB']) + + def test_datetime_to_epoch(self): + """Test convert datetime to epoch.""" + timestamp = pd.Timestamp('2013-01-01 00:00:00.000+00:00') + cli = DataFrameClient('host', 8086, 'username', 'password', 'db') + + self.assertEqual( + cli._datetime_to_epoch(timestamp), + 1356998400.0 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='s'), + 1356998400.0 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='m'), + 1356998400000.0 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='ms'), + 1356998400000.0 + ) + self.assertEqual( + cli._datetime_to_epoch(timestamp, time_precision='u'), + 1356998400000000.0 + ) diff --git a/lib/influxdb/tests/influxdb08/helper_test.py b/lib/influxdb/tests/influxdb08/helper_test.py new file mode 100644 index 0000000..2e305f3 --- /dev/null +++ b/lib/influxdb/tests/influxdb08/helper_test.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +"""Define set of helper functions for the dataframe.""" + +import unittest +import warnings + +import mock +from influxdb.influxdb08 import SeriesHelper, InfluxDBClient +from requests.exceptions import ConnectionError + + +class TestSeriesHelper(unittest.TestCase): + """Define the SeriesHelper for test.""" + + @classmethod + def setUpClass(cls): + """Set up an instance of the TestSerisHelper object.""" + super(TestSeriesHelper, cls).setUpClass() + + TestSeriesHelper.client = InfluxDBClient( + 'host', + 8086, + 'username', + 'password', + 'database' + ) + + class MySeriesHelper(SeriesHelper): + """Define a subset SeriesHelper instance.""" + + class Meta: + """Define metadata for the TestSeriesHelper object.""" + + client = TestSeriesHelper.client + series_name = 'events.stats.{server_name}' + fields = ['time', 'server_name'] + bulk_size = 5 + autocommit = True + + TestSeriesHelper.MySeriesHelper = MySeriesHelper + + def test_auto_commit(self): + """Test that write_points called after the right number of events.""" + class AutoCommitTest(SeriesHelper): + """Define an instance of SeriesHelper for AutoCommit test.""" + + class Meta: + """Define metadata AutoCommitTest object.""" + + series_name = 'events.stats.{server_name}' + fields = ['time', 'server_name'] + bulk_size = 5 + client = InfluxDBClient() + autocommit = True + + fake_write_points = mock.MagicMock() + AutoCommitTest(server_name='us.east-1', time=159) + AutoCommitTest._client.write_points = fake_write_points + AutoCommitTest(server_name='us.east-1', time=158) + AutoCommitTest(server_name='us.east-1', time=157) + AutoCommitTest(server_name='us.east-1', time=156) + self.assertFalse(fake_write_points.called) + AutoCommitTest(server_name='us.east-1', time=3443) + self.assertTrue(fake_write_points.called) + + def testSingleSeriesName(self): + """Test JSON conversion when there is only one series name.""" + TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=159) + TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=158) + TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=157) + TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=156) + expectation = [{'points': [[159, 'us.east-1'], + [158, 'us.east-1'], + [157, 'us.east-1'], + [156, 'us.east-1']], + 'name': 'events.stats.us.east-1', + 'columns': ['time', 'server_name']}] + + rcvd = TestSeriesHelper.MySeriesHelper._json_body_() + self.assertTrue(all([el in expectation for el in rcvd]) and + all([el in rcvd for el in expectation]), + 'Invalid JSON body of time series returned from ' + '_json_body_ for one series name: {0}.'.format(rcvd)) + TestSeriesHelper.MySeriesHelper._reset_() + self.assertEqual( + TestSeriesHelper.MySeriesHelper._json_body_(), + [], + 'Resetting helper did not empty datapoints.') + + def testSeveralSeriesNames(self): + """Test JSON conversion when there is only one series name.""" + TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=159) + TestSeriesHelper.MySeriesHelper(server_name='fr.paris-10', time=158) + TestSeriesHelper.MySeriesHelper(server_name='lu.lux', time=157) + TestSeriesHelper.MySeriesHelper(server_name='uk.london', time=156) + expectation = [{'points': [[157, 'lu.lux']], + 'name': 'events.stats.lu.lux', + 'columns': ['time', 'server_name']}, + {'points': [[156, 'uk.london']], + 'name': 'events.stats.uk.london', + 'columns': ['time', 'server_name']}, + {'points': [[158, 'fr.paris-10']], + 'name': 'events.stats.fr.paris-10', + 'columns': ['time', 'server_name']}, + {'points': [[159, 'us.east-1']], + 'name': 'events.stats.us.east-1', + 'columns': ['time', 'server_name']}] + + rcvd = TestSeriesHelper.MySeriesHelper._json_body_() + self.assertTrue(all([el in expectation for el in rcvd]) and + all([el in rcvd for el in expectation]), + 'Invalid JSON body of time series returned from ' + '_json_body_ for several series names: {0}.' + .format(rcvd)) + TestSeriesHelper.MySeriesHelper._reset_() + self.assertEqual( + TestSeriesHelper.MySeriesHelper._json_body_(), + [], + 'Resetting helper did not empty datapoints.') + + def testInvalidHelpers(self): + """Test errors in invalid helpers.""" + class MissingMeta(SeriesHelper): + """Define SeriesHelper object for MissingMeta test.""" + + pass + + class MissingClient(SeriesHelper): + """Define SeriesHelper object for MissingClient test.""" + + class Meta: + """Define metadata for MissingClient object.""" + + series_name = 'events.stats.{server_name}' + fields = ['time', 'server_name'] + autocommit = True + + class MissingSeriesName(SeriesHelper): + """Define SeriesHelper object for MissingSeries test.""" + + class Meta: + """Define metadata for MissingSeriesName object.""" + + fields = ['time', 'server_name'] + + class MissingFields(SeriesHelper): + """Define SeriesHelper for MissingFields test.""" + + class Meta: + """Define metadata for MissingFields object.""" + + series_name = 'events.stats.{server_name}' + + for cls in [MissingMeta, MissingClient, MissingFields, + MissingSeriesName]: + self.assertRaises( + AttributeError, cls, **{'time': 159, + 'server_name': 'us.east-1'}) + + def testWarnBulkSizeZero(self): + """Test warning for an invalid bulk size.""" + class WarnBulkSizeZero(SeriesHelper): + """Define SeriesHelper for WarnBulkSizeZero test.""" + + class Meta: + """Define metadata for WarnBulkSizeZero object.""" + + client = TestSeriesHelper.client + series_name = 'events.stats.{server_name}' + fields = ['time', 'server_name'] + bulk_size = 0 + autocommit = True + + with warnings.catch_warnings(record=True) as rec_warnings: + warnings.simplefilter("always") + # Server defined in the client is invalid, we're testing + # the warning only. + with self.assertRaises(ConnectionError): + WarnBulkSizeZero(time=159, server_name='us.east-1') + + self.assertGreaterEqual( + len(rec_warnings), 1, + '{0} call should have generated one warning.' + 'Actual generated warnings: {1}'.format( + WarnBulkSizeZero, '\n'.join(map(str, rec_warnings)))) + + expected_msg = ( + 'Definition of bulk_size in WarnBulkSizeZero forced to 1, ' + 'was less than 1.') + + self.assertIn(expected_msg, list(w.message.args[0] + for w in rec_warnings), + 'Warning message did not contain "forced to 1".') + + def testWarnBulkSizeNoEffect(self): + """Test warning for a set bulk size but autocommit False.""" + class WarnBulkSizeNoEffect(SeriesHelper): + """Define SeriesHelper for WarnBulkSizeNoEffect object.""" + + class Meta: + """Define metadata for WarnBulkSizeNoEffect object.""" + + series_name = 'events.stats.{server_name}' + fields = ['time', 'server_name'] + bulk_size = 5 + autocommit = False + + with warnings.catch_warnings(record=True) as rec_warnings: + warnings.simplefilter("always") + WarnBulkSizeNoEffect(time=159, server_name='us.east-1') + + self.assertGreaterEqual( + len(rec_warnings), 1, + '{0} call should have generated one warning.' + 'Actual generated warnings: {1}'.format( + WarnBulkSizeNoEffect, '\n'.join(map(str, rec_warnings)))) + + expected_msg = ( + 'Definition of bulk_size in WarnBulkSizeNoEffect has no affect ' + 'because autocommit is false.') + + self.assertIn(expected_msg, list(w.message.args[0] + for w in rec_warnings), + 'Warning message did not contain the expected_msg.') + + +if __name__ == '__main__': + unittest.main() diff --git a/lib/influxdb/tests/misc.py b/lib/influxdb/tests/misc.py new file mode 100644 index 0000000..324d13c --- /dev/null +++ b/lib/influxdb/tests/misc.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +"""Define the misc handler for InfluxDBClient test.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import socket + + +def get_free_ports(num_ports, ip='127.0.0.1'): + """Determine free ports on provided interface. + + Get `num_ports` free/available ports on the interface linked to the `ip` + :param int num_ports: The number of free ports to get + :param str ip: The ip on which the ports have to be taken + :return: a set of ports number + """ + sock_ports = [] + ports = set() + try: + for _ in range(num_ports): + sock = socket.socket() + cur = [sock, -1] + # append the socket directly, + # so that it'll be also closed (no leaked resource) + # in the finally here after. + sock_ports.append(cur) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((ip, 0)) + cur[1] = sock.getsockname()[1] + finally: + for sock, port in sock_ports: + sock.close() + ports.add(port) + assert num_ports == len(ports) + return ports + + +def is_port_open(port, ip='127.0.0.1'): + """Check if given TCP port is open for connection.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + result = sock.connect_ex((ip, port)) + if not result: + sock.shutdown(socket.SHUT_RDWR) + return result == 0 + finally: + sock.close() diff --git a/lib/influxdb/tests/resultset_test.py b/lib/influxdb/tests/resultset_test.py new file mode 100644 index 0000000..83faa4d --- /dev/null +++ b/lib/influxdb/tests/resultset_test.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +"""Define the resultset test package.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest + +from influxdb.exceptions import InfluxDBClientError +from influxdb.resultset import ResultSet + + +class TestResultSet(unittest.TestCase): + """Define the ResultSet test object.""" + + def setUp(self): + """Set up an instance of TestResultSet.""" + self.query_response = { + "results": [ + {"series": [{"name": "cpu_load_short", + "columns": ["time", "value", "host", "region"], + "values": [ + ["2015-01-29T21:51:28.968422294Z", + 0.64, + "server01", + "us-west"], + ["2015-01-29T21:51:28.968422294Z", + 0.65, + "server02", + "us-west"], + ]}, + {"name": "other_series", + "columns": ["time", "value", "host", "region"], + "values": [ + ["2015-01-29T21:51:28.968422294Z", + 0.66, + "server01", + "us-west"], + ]}]} + ] + } + + self.rs = ResultSet(self.query_response['results'][0]) + + def test_filter_by_name(self): + """Test filtering by name in TestResultSet object.""" + expected = [ + {'value': 0.64, + 'time': '2015-01-29T21:51:28.968422294Z', + 'host': 'server01', + 'region': 'us-west'}, + {'value': 0.65, + 'time': '2015-01-29T21:51:28.968422294Z', + 'host': 'server02', + 'region': 'us-west'}, + ] + + self.assertEqual(expected, list(self.rs['cpu_load_short'])) + self.assertEqual(expected, + list(self.rs.get_points( + measurement='cpu_load_short'))) + + def test_filter_by_tags(self): + """Test filter by tags in TestResultSet object.""" + expected = [ + {'value': 0.64, + 'time': '2015-01-29T21:51:28.968422294Z', + 'host': 'server01', + 'region': 'us-west'}, + {'value': 0.66, + 'time': '2015-01-29T21:51:28.968422294Z', + 'host': 'server01', + 'region': 'us-west'}, + ] + + self.assertEqual( + expected, + list(self.rs[{"host": "server01"}]) + ) + + self.assertEqual( + expected, + list(self.rs.get_points(tags={'host': 'server01'})) + ) + + def test_filter_by_name_and_tags(self): + """Test filter by name and tags in TestResultSet object.""" + self.assertEqual( + list(self.rs[('cpu_load_short', {"host": "server01"})]), + [{'value': 0.64, + 'time': '2015-01-29T21:51:28.968422294Z', + 'host': 'server01', + 'region': 'us-west'}] + ) + + self.assertEqual( + list(self.rs[('cpu_load_short', {"region": "us-west"})]), + [ + {'value': 0.64, + 'time': '2015-01-29T21:51:28.968422294Z', + 'host': 'server01', + 'region': 'us-west'}, + {'value': 0.65, + 'time': '2015-01-29T21:51:28.968422294Z', + 'host': 'server02', + 'region': 'us-west'}, + ] + ) + + def test_keys(self): + """Test keys in TestResultSet object.""" + self.assertEqual( + self.rs.keys(), + [ + ('cpu_load_short', None), + ('other_series', None), + ] + ) + + def test_len(self): + """Test length in TestResultSet object.""" + self.assertEqual( + len(self.rs), + 2 + ) + + def test_items(self): + """Test items in TestResultSet object.""" + items = list(self.rs.items()) + items_lists = [(item[0], list(item[1])) for item in items] + + self.assertEqual( + items_lists, + [ + ( + ('cpu_load_short', None), + [ + {'time': '2015-01-29T21:51:28.968422294Z', + 'value': 0.64, + 'host': 'server01', + 'region': 'us-west'}, + {'time': '2015-01-29T21:51:28.968422294Z', + 'value': 0.65, + 'host': 'server02', + 'region': 'us-west'}]), + ( + ('other_series', None), + [ + {'time': '2015-01-29T21:51:28.968422294Z', + 'value': 0.66, + 'host': 'server01', + 'region': 'us-west'}])] + ) + + def test_point_from_cols_vals(self): + """Test points from columns in TestResultSet object.""" + cols = ['col1', 'col2'] + vals = [1, '2'] + + point = ResultSet.point_from_cols_vals(cols, vals) + self.assertDictEqual( + point, + {'col1': 1, 'col2': '2'} + ) + + def test_system_query(self): + """Test system query capabilities in TestResultSet object.""" + rs = ResultSet( + {'series': [ + {'values': [['another', '48h0m0s', 3, False], + ['default', '0', 1, False], + ['somename', '24h0m0s', 4, True]], + 'columns': ['name', 'duration', + 'replicaN', 'default']}]} + ) + + self.assertEqual( + rs.keys(), + [('results', None)] + ) + + self.assertEqual( + list(rs['results']), + [ + {'duration': '48h0m0s', 'default': False, 'replicaN': 3, + 'name': 'another'}, + {'duration': '0', 'default': False, 'replicaN': 1, + 'name': 'default'}, + {'duration': '24h0m0s', 'default': True, 'replicaN': 4, + 'name': 'somename'} + ] + ) + + def test_resultset_error(self): + """Test returning error in TestResultSet object.""" + with self.assertRaises(InfluxDBClientError): + ResultSet({ + "series": [], + "error": "Big error, many problems." + }) diff --git a/lib/influxdb/tests/server_tests/__init__.py b/lib/influxdb/tests/server_tests/__init__.py new file mode 100644 index 0000000..ce149ab --- /dev/null +++ b/lib/influxdb/tests/server_tests/__init__.py @@ -0,0 +1 @@ +"""Define the server tests package.""" diff --git a/lib/influxdb/tests/server_tests/base.py b/lib/influxdb/tests/server_tests/base.py new file mode 100644 index 0000000..f4bd3ff --- /dev/null +++ b/lib/influxdb/tests/server_tests/base.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +"""Define the base module for server test.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import sys + +from influxdb.tests import using_pypy +from influxdb.tests.server_tests.influxdb_instance import InfluxDbInstance + +from influxdb.client import InfluxDBClient + +if not using_pypy: + from influxdb.dataframe_client import DataFrameClient + + +def _setup_influxdb_server(inst): + inst.influxd_inst = InfluxDbInstance( + inst.influxdb_template_conf, + udp_enabled=getattr(inst, 'influxdb_udp_enabled', False), + ) + + inst.cli = InfluxDBClient('localhost', + inst.influxd_inst.http_port, + 'root', + '', + database='db') + if not using_pypy: + inst.cliDF = DataFrameClient('localhost', + inst.influxd_inst.http_port, + 'root', + '', + database='db') + + +def _teardown_influxdb_server(inst): + remove_tree = sys.exc_info() == (None, None, None) + inst.influxd_inst.close(remove_tree=remove_tree) + + +class SingleTestCaseWithServerMixin(object): + """Define the single testcase with server mixin. + + A mixin for unittest.TestCase to start an influxdb server instance + in a temporary directory **for each test function/case** + """ + + # 'influxdb_template_conf' attribute must be set + # on the TestCase class or instance. + + setUp = _setup_influxdb_server + tearDown = _teardown_influxdb_server + + +class ManyTestCasesWithServerMixin(object): + """Define the many testcase with server mixin. + + Same as the SingleTestCaseWithServerMixin but this module creates + a single instance for the whole class. Also pre-creates a fresh + database: 'db'. + """ + + # 'influxdb_template_conf' attribute must be set on the class itself ! + + @classmethod + def setUpClass(cls): + """Set up an instance of the ManyTestCasesWithServerMixin.""" + _setup_influxdb_server(cls) + + def setUp(self): + """Set up an instance of the ManyTestCasesWithServerMixin.""" + self.cli.create_database('db') + + @classmethod + def tearDownClass(cls): + """Deconstruct an instance of ManyTestCasesWithServerMixin.""" + _teardown_influxdb_server(cls) + + def tearDown(self): + """Deconstruct an instance of ManyTestCasesWithServerMixin.""" + self.cli.drop_database('db') diff --git a/lib/influxdb/tests/server_tests/client_test_with_server.py b/lib/influxdb/tests/server_tests/client_test_with_server.py new file mode 100644 index 0000000..2f8a209 --- /dev/null +++ b/lib/influxdb/tests/server_tests/client_test_with_server.py @@ -0,0 +1,825 @@ +# -*- coding: utf-8 -*- +"""Unit tests for checking the InfluxDB server. + +The good/expected interaction between: + ++ the python client.. (obviously) ++ and a *_real_* server instance running. + +This basically duplicates what's in client_test.py +but without mocking around every call. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from functools import partial +import os +import time +import unittest +import warnings + +from influxdb import InfluxDBClient +from influxdb.exceptions import InfluxDBClientError + +from influxdb.tests import skipIfPYpy, using_pypy, skipServerTests +from influxdb.tests.server_tests.base import ManyTestCasesWithServerMixin +from influxdb.tests.server_tests.base import SingleTestCaseWithServerMixin + +# By default, raise exceptions on warnings +warnings.simplefilter('error', FutureWarning) + +if not using_pypy: + import pandas as pd + from pandas.util.testing import assert_frame_equal + + +THIS_DIR = os.path.abspath(os.path.dirname(__file__)) + + +def point(series_name, timestamp=None, tags=None, **fields): + """Define what a point looks like.""" + res = {'measurement': series_name} + + if timestamp: + res['time'] = timestamp + + if tags: + res['tags'] = tags + + res['fields'] = fields + return res + + +dummy_point = [ # some dummy points + { + "measurement": "cpu_load_short", + "tags": { + "host": "server01", + "region": "us-west" + }, + "time": "2009-11-10T23:00:00Z", + "fields": { + "value": 0.64 + } + } +] + +dummy_points = [ # some dummy points + dummy_point[0], + { + "measurement": "memory", + "tags": { + "host": "server01", + "region": "us-west" + }, + "time": "2009-11-10T23:01:35Z", + "fields": { + "value": 33.0 + } + } +] + +if not using_pypy: + dummy_pointDF = { + "measurement": "cpu_load_short", + "tags": {"host": "server01", + "region": "us-west"}, + "dataframe": pd.DataFrame( + [[0.64]], columns=['value'], + index=pd.to_datetime(["2009-11-10T23:00:00Z"])) + } + dummy_pointsDF = [{ + "measurement": "cpu_load_short", + "tags": {"host": "server01", "region": "us-west"}, + "dataframe": pd.DataFrame( + [[0.64]], columns=['value'], + index=pd.to_datetime(["2009-11-10T23:00:00Z"])), + }, { + "measurement": "memory", + "tags": {"host": "server01", "region": "us-west"}, + "dataframe": pd.DataFrame( + [[33]], columns=['value'], + index=pd.to_datetime(["2009-11-10T23:01:35Z"]) + ) + }] + + +dummy_point_without_timestamp = [ + { + "measurement": "cpu_load_short", + "tags": { + "host": "server02", + "region": "us-west" + }, + "fields": { + "value": 0.64 + } + } +] + + +@skipServerTests +class SimpleTests(SingleTestCaseWithServerMixin, unittest.TestCase): + """Define the class of simple tests.""" + + influxdb_template_conf = os.path.join(THIS_DIR, 'influxdb.conf.template') + + def test_fresh_server_no_db(self): + """Test a fresh server without database.""" + self.assertEqual([], self.cli.get_list_database()) + + def test_create_database(self): + """Test create a database.""" + self.assertIsNone(self.cli.create_database('new_db_1')) + self.assertIsNone(self.cli.create_database('new_db_2')) + self.assertEqual( + self.cli.get_list_database(), + [{'name': 'new_db_1'}, {'name': 'new_db_2'}] + ) + + def test_drop_database(self): + """Test drop a database.""" + self.test_create_database() + self.assertIsNone(self.cli.drop_database('new_db_1')) + self.assertEqual([{'name': 'new_db_2'}], self.cli.get_list_database()) + + def test_query_fail(self): + """Test that a query failed.""" + with self.assertRaises(InfluxDBClientError) as ctx: + self.cli.query('select column_one from foo') + self.assertIn('database not found: db', + ctx.exception.content) + + def test_query_fail_ignore_errors(self): + """Test query failed but ignore errors.""" + result = self.cli.query('select column_one from foo', + raise_errors=False) + self.assertEqual(result.error, 'database not found: db') + + def test_create_user(self): + """Test create user.""" + self.cli.create_user('test_user', 'secret_password') + rsp = list(self.cli.query("SHOW USERS")['results']) + self.assertIn({'user': 'test_user', 'admin': False}, + rsp) + + def test_create_user_admin(self): + """Test create admin user.""" + self.cli.create_user('test_user', 'secret_password', True) + rsp = list(self.cli.query("SHOW USERS")['results']) + self.assertIn({'user': 'test_user', 'admin': True}, + rsp) + + def test_create_user_blank_password(self): + """Test create user with a blank pass.""" + self.cli.create_user('test_user', '') + rsp = list(self.cli.query("SHOW USERS")['results']) + self.assertIn({'user': 'test_user', 'admin': False}, + rsp) + + def test_get_list_users_empty(self): + """Test get list of users, but empty.""" + rsp = self.cli.get_list_users() + self.assertEqual([], rsp) + + def test_get_list_users(self): + """Test get list of users.""" + self.cli.query("CREATE USER test WITH PASSWORD 'test'") + rsp = self.cli.get_list_users() + + self.assertEqual( + [{'user': 'test', 'admin': False}], + rsp + ) + + def test_create_user_blank_username(self): + """Test create blank username.""" + with self.assertRaises(InfluxDBClientError) as ctx: + self.cli.create_user('', 'secret_password') + self.assertIn('username required', + ctx.exception.content) + rsp = list(self.cli.query("SHOW USERS")['results']) + self.assertEqual(rsp, []) + + def test_drop_user(self): + """Test drop a user.""" + self.cli.query("CREATE USER test WITH PASSWORD 'test'") + self.cli.drop_user('test') + users = list(self.cli.query("SHOW USERS")['results']) + self.assertEqual(users, []) + + def test_drop_user_nonexisting(self): + """Test dropping a nonexistent user.""" + with self.assertRaises(InfluxDBClientError) as ctx: + self.cli.drop_user('test') + self.assertIn('user not found', + ctx.exception.content) + + @unittest.skip("Broken as of 0.9.0") + def test_revoke_admin_privileges(self): + """Test revoking admin privs, deprecated as of v0.9.0.""" + self.cli.create_user('test', 'test', admin=True) + self.assertEqual([{'user': 'test', 'admin': True}], + self.cli.get_list_users()) + self.cli.revoke_admin_privileges('test') + self.assertEqual([{'user': 'test', 'admin': False}], + self.cli.get_list_users()) + + def test_grant_privilege(self): + """Test grant privs to user.""" + self.cli.create_user('test', 'test') + self.cli.create_database('testdb') + self.cli.grant_privilege('all', 'testdb', 'test') + # TODO: when supported by InfluxDB, check if privileges are granted + + def test_grant_privilege_invalid(self): + """Test grant invalid privs to user.""" + self.cli.create_user('test', 'test') + self.cli.create_database('testdb') + with self.assertRaises(InfluxDBClientError) as ctx: + self.cli.grant_privilege('', 'testdb', 'test') + self.assertEqual(400, ctx.exception.code) + self.assertIn('{"error":"error parsing query: ', + ctx.exception.content) + + def test_revoke_privilege(self): + """Test revoke privs from user.""" + self.cli.create_user('test', 'test') + self.cli.create_database('testdb') + self.cli.revoke_privilege('all', 'testdb', 'test') + # TODO: when supported by InfluxDB, check if privileges are revoked + + def test_revoke_privilege_invalid(self): + """Test revoke invalid privs from user.""" + self.cli.create_user('test', 'test') + self.cli.create_database('testdb') + with self.assertRaises(InfluxDBClientError) as ctx: + self.cli.revoke_privilege('', 'testdb', 'test') + self.assertEqual(400, ctx.exception.code) + self.assertIn('{"error":"error parsing query: ', + ctx.exception.content) + + def test_invalid_port_fails(self): + """Test invalid port access fails.""" + with self.assertRaises(ValueError): + InfluxDBClient('host', '80/redir', 'username', 'password') + + +@skipServerTests +class CommonTests(ManyTestCasesWithServerMixin, unittest.TestCase): + """Define a class to handle common tests for the server.""" + + influxdb_template_conf = os.path.join(THIS_DIR, 'influxdb.conf.template') + + def test_write(self): + """Test write to the server.""" + self.assertIs(True, self.cli.write( + {'points': dummy_point}, + params={'db': 'db'}, + )) + + def test_write_check_read(self): + """Test write and check read of data to server.""" + self.test_write() + time.sleep(1) + rsp = self.cli.query('SELECT * FROM cpu_load_short', database='db') + self.assertListEqual([{'value': 0.64, 'time': '2009-11-10T23:00:00Z', + "host": "server01", "region": "us-west"}], + list(rsp.get_points())) + + def test_write_points(self): + """Test writing points to the server.""" + self.assertIs(True, self.cli.write_points(dummy_point)) + + @skipIfPYpy + def test_write_points_DF(self): + """Test writing points with dataframe.""" + self.assertIs( + True, + self.cliDF.write_points( + dummy_pointDF['dataframe'], + dummy_pointDF['measurement'], + dummy_pointDF['tags'] + ) + ) + + def test_write_points_check_read(self): + """Test writing points and check read back.""" + self.test_write_points() + time.sleep(1) # same as test_write_check_read() + rsp = self.cli.query('SELECT * FROM cpu_load_short') + + self.assertEqual( + list(rsp), + [[ + {'value': 0.64, + 'time': '2009-11-10T23:00:00Z', + "host": "server01", + "region": "us-west"} + ]] + ) + + rsp2 = list(rsp.get_points()) + self.assertEqual(len(rsp2), 1) + pt = rsp2[0] + + self.assertEqual( + pt, + {'time': '2009-11-10T23:00:00Z', + 'value': 0.64, + "host": "server01", + "region": "us-west"} + ) + + @unittest.skip("Broken as of 0.9.0") + def test_write_points_check_read_DF(self): + """Test write points and check back with dataframe.""" + self.test_write_points_DF() + time.sleep(1) # same as test_write_check_read() + + rsp = self.cliDF.query('SELECT * FROM cpu_load_short') + assert_frame_equal( + rsp['cpu_load_short'], + dummy_pointDF['dataframe'] + ) + + # Query with Tags + rsp = self.cliDF.query( + "SELECT * FROM cpu_load_short GROUP BY *") + assert_frame_equal( + rsp[('cpu_load_short', + (('host', 'server01'), ('region', 'us-west')))], + dummy_pointDF['dataframe'] + ) + + def test_write_multiple_points_different_series(self): + """Test write multiple points to different series.""" + self.assertIs(True, self.cli.write_points(dummy_points)) + time.sleep(1) + rsp = self.cli.query('SELECT * FROM cpu_load_short') + lrsp = list(rsp) + + self.assertEqual( + [[ + {'value': 0.64, + 'time': '2009-11-10T23:00:00Z', + "host": "server01", + "region": "us-west"} + ]], + lrsp + ) + + rsp = list(self.cli.query('SELECT * FROM memory')) + + self.assertEqual( + rsp, + [[ + {'value': 33, + 'time': '2009-11-10T23:01:35Z', + "host": "server01", + "region": "us-west"} + ]] + ) + + def test_select_into_as_post(self): + """Test SELECT INTO is POSTed.""" + self.assertIs(True, self.cli.write_points(dummy_points)) + time.sleep(1) + rsp = self.cli.query('SELECT * INTO "newmeas" FROM "memory"') + rsp = self.cli.query('SELECT * FROM "newmeas"') + lrsp = list(rsp) + + self.assertEqual( + lrsp, + [[ + {'value': 33, + 'time': '2009-11-10T23:01:35Z', + "host": "server01", + "region": "us-west"} + ]] + ) + + @unittest.skip("Broken as of 0.9.0") + def test_write_multiple_points_different_series_DF(self): + """Test write multiple points using dataframe to different series.""" + for i in range(2): + self.assertIs( + True, self.cliDF.write_points( + dummy_pointsDF[i]['dataframe'], + dummy_pointsDF[i]['measurement'], + dummy_pointsDF[i]['tags'])) + time.sleep(1) + rsp = self.cliDF.query('SELECT * FROM cpu_load_short') + + assert_frame_equal( + rsp['cpu_load_short'], + dummy_pointsDF[0]['dataframe'] + ) + + rsp = self.cliDF.query('SELECT * FROM memory') + assert_frame_equal( + rsp['memory'], + dummy_pointsDF[1]['dataframe'] + ) + + def test_write_points_batch(self): + """Test writing points in a batch.""" + dummy_points = [ + {"measurement": "cpu_usage", "tags": {"unit": "percent"}, + "time": "2009-11-10T23:00:00Z", "fields": {"value": 12.34}}, + {"measurement": "network", "tags": {"direction": "in"}, + "time": "2009-11-10T23:00:00Z", "fields": {"value": 123.00}}, + {"measurement": "network", "tags": {"direction": "out"}, + "time": "2009-11-10T23:00:00Z", "fields": {"value": 12.00}} + ] + self.cli.write_points(points=dummy_points, + tags={"host": "server01", + "region": "us-west"}, + batch_size=2) + time.sleep(5) + net_in = self.cli.query("SELECT value FROM network " + "WHERE direction='in'").raw + net_out = self.cli.query("SELECT value FROM network " + "WHERE direction='out'").raw + cpu = self.cli.query("SELECT value FROM cpu_usage").raw + self.assertIn(123, net_in['series'][0]['values'][0]) + self.assertIn(12, net_out['series'][0]['values'][0]) + self.assertIn(12.34, cpu['series'][0]['values'][0]) + + def test_query(self): + """Test querying data back from server.""" + self.assertIs(True, self.cli.write_points(dummy_point)) + + @unittest.skip('Not implemented for 0.9') + def test_query_chunked(self): + """Test query for chunked response from server.""" + cli = InfluxDBClient(database='db') + example_object = { + 'points': [ + [1415206250119, 40001, 667], + [1415206244555, 30001, 7], + [1415206228241, 20001, 788], + [1415206212980, 10001, 555], + [1415197271586, 10001, 23] + ], + 'name': 'foo', + 'columns': [ + 'time', + 'sequence_number', + 'val' + ] + } + del cli + del example_object + # TODO ? + + def test_delete_series_invalid(self): + """Test delete invalid series.""" + with self.assertRaises(InfluxDBClientError): + self.cli.delete_series() + + def test_default_retention_policy(self): + """Test add default retention policy.""" + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'name': 'autogen', + 'duration': '0s', + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'default': True} + ], + rsp + ) + + def test_create_retention_policy_default(self): + """Test create a new default retention policy.""" + self.cli.create_retention_policy('somename', '1d', 1, default=True) + self.cli.create_retention_policy('another', '2d', 1, default=False) + rsp = self.cli.get_list_retention_policies() + + self.assertEqual( + [ + {'duration': '0s', + 'default': False, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'}, + {'duration': '24h0m0s', + 'default': True, + 'replicaN': 1, + 'shardGroupDuration': u'1h0m0s', + 'name': 'somename'}, + {'duration': '48h0m0s', + 'default': False, + 'replicaN': 1, + 'shardGroupDuration': u'24h0m0s', + 'name': 'another'} + ], + rsp + ) + + def test_create_retention_policy(self): + """Test creating a new retention policy, not default.""" + self.cli.create_retention_policy('somename', '1d', 1) + # NB: creating a retention policy without specifying + # shard group duration + # leads to a shard group duration of 1 hour + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'duration': '0s', + 'default': True, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'}, + {'duration': '24h0m0s', + 'default': False, + 'replicaN': 1, + 'shardGroupDuration': u'1h0m0s', + 'name': 'somename'} + ], + rsp + ) + + self.cli.drop_retention_policy('somename', 'db') + # recreate the RP + self.cli.create_retention_policy('somename', '1w', 1, + shard_duration='1h') + + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'duration': '0s', + 'default': True, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'}, + {'duration': '168h0m0s', + 'default': False, + 'replicaN': 1, + 'shardGroupDuration': u'1h0m0s', + 'name': 'somename'} + ], + rsp + ) + + self.cli.drop_retention_policy('somename', 'db') + # recreate the RP + self.cli.create_retention_policy('somename', '1w', 1) + + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'duration': '0s', + 'default': True, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'}, + {'duration': '168h0m0s', + 'default': False, + 'replicaN': 1, + 'shardGroupDuration': u'24h0m0s', + 'name': 'somename'} + ], + rsp + ) + + def test_alter_retention_policy(self): + """Test alter a retention policy, not default.""" + self.cli.create_retention_policy('somename', '1d', 1) + + # Test alter duration + self.cli.alter_retention_policy('somename', 'db', + duration='4d', + shard_duration='2h') + # NB: altering retention policy doesn't change shard group duration + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'duration': '0s', + 'default': True, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'}, + {'duration': '96h0m0s', + 'default': False, + 'replicaN': 1, + 'shardGroupDuration': u'2h0m0s', + 'name': 'somename'} + ], + rsp + ) + + # Test alter replication + self.cli.alter_retention_policy('somename', 'db', + replication=4) + + # NB: altering retention policy doesn't change shard group duration + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'duration': '0s', + 'default': True, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'}, + {'duration': '96h0m0s', + 'default': False, + 'replicaN': 4, + 'shardGroupDuration': u'2h0m0s', + 'name': 'somename'} + ], + rsp + ) + + # Test alter default + self.cli.alter_retention_policy('somename', 'db', + default=True) + # NB: altering retention policy doesn't change shard group duration + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'duration': '0s', + 'default': False, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'}, + {'duration': '96h0m0s', + 'default': True, + 'replicaN': 4, + 'shardGroupDuration': u'2h0m0s', + 'name': 'somename'} + ], + rsp + ) + + # Test alter shard_duration + self.cli.alter_retention_policy('somename', 'db', + shard_duration='4h') + + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'duration': '0s', + 'default': False, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'}, + {'duration': '96h0m0s', + 'default': True, + 'replicaN': 4, + 'shardGroupDuration': u'4h0m0s', + 'name': 'somename'} + ], + rsp + ) + + def test_alter_retention_policy_invalid(self): + """Test invalid alter retention policy.""" + self.cli.create_retention_policy('somename', '1d', 1) + with self.assertRaises(InfluxDBClientError) as ctx: + self.cli.alter_retention_policy('somename', 'db') + self.assertEqual(400, ctx.exception.code) + self.assertIn('{"error":"error parsing query: ', + ctx.exception.content) + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'duration': '0s', + 'default': True, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'}, + {'duration': '24h0m0s', + 'default': False, + 'replicaN': 1, + 'shardGroupDuration': u'1h0m0s', + 'name': 'somename'} + ], + rsp + ) + + def test_drop_retention_policy(self): + """Test drop a retention policy.""" + self.cli.create_retention_policy('somename', '1d', 1) + + # Test drop retention + self.cli.drop_retention_policy('somename', 'db') + rsp = self.cli.get_list_retention_policies() + self.assertEqual( + [ + {'duration': '0s', + 'default': True, + 'replicaN': 1, + 'shardGroupDuration': u'168h0m0s', + 'name': 'autogen'} + ], + rsp + ) + + def test_issue_143(self): + """Test for PR#143 from repo.""" + pt = partial(point, 'a_series_name', timestamp='2015-03-30T16:16:37Z') + pts = [ + pt(value=15), + pt(tags={'tag_1': 'value1'}, value=5), + pt(tags={'tag_1': 'value2'}, value=10), + ] + self.cli.write_points(pts) + time.sleep(1) + rsp = list(self.cli.query('SELECT * FROM a_series_name \ +GROUP BY tag_1').get_points()) + + self.assertEqual( + [ + {'time': '2015-03-30T16:16:37Z', 'value': 15}, + {'time': '2015-03-30T16:16:37Z', 'value': 5}, + {'time': '2015-03-30T16:16:37Z', 'value': 10} + ], + rsp + ) + + # a slightly more complex one with 2 tags values: + pt = partial(point, 'series2', timestamp='2015-03-30T16:16:37Z') + pts = [ + pt(tags={'tag1': 'value1', 'tag2': 'v1'}, value=0), + pt(tags={'tag1': 'value1', 'tag2': 'v2'}, value=5), + pt(tags={'tag1': 'value2', 'tag2': 'v1'}, value=10), + ] + self.cli.write_points(pts) + time.sleep(1) + rsp = self.cli.query('SELECT * FROM series2 GROUP BY tag1,tag2') + + self.assertEqual( + [ + {'value': 0, 'time': '2015-03-30T16:16:37Z'}, + {'value': 5, 'time': '2015-03-30T16:16:37Z'}, + {'value': 10, 'time': '2015-03-30T16:16:37Z'} + ], + list(rsp['series2']) + ) + + all_tag2_equal_v1 = list(rsp.get_points(tags={'tag2': 'v1'})) + + self.assertEqual( + [{'value': 0, 'time': '2015-03-30T16:16:37Z'}, + {'value': 10, 'time': '2015-03-30T16:16:37Z'}], + all_tag2_equal_v1, + ) + + def test_query_multiple_series(self): + """Test query for multiple series.""" + pt = partial(point, 'series1', timestamp='2015-03-30T16:16:37Z') + pts = [ + pt(tags={'tag1': 'value1', 'tag2': 'v1'}, value=0), + ] + self.cli.write_points(pts) + + pt = partial(point, 'series2', timestamp='1970-03-30T16:16:37Z') + pts = [ + pt(tags={'tag1': 'value1', 'tag2': 'v1'}, + value=0, data1=33, data2="bla"), + ] + self.cli.write_points(pts) + + +@skipServerTests +class UdpTests(ManyTestCasesWithServerMixin, unittest.TestCase): + """Define a class to test UDP series.""" + + influxdb_udp_enabled = True + influxdb_template_conf = os.path.join(THIS_DIR, + 'influxdb.conf.template') + + def test_write_points_udp(self): + """Test write points UDP.""" + cli = InfluxDBClient( + 'localhost', + self.influxd_inst.http_port, + 'root', + '', + database='db', + use_udp=True, + udp_port=self.influxd_inst.udp_port + ) + cli.write_points(dummy_point) + + # The points are not immediately available after write_points. + # This is to be expected because we are using udp (no response !). + # So we have to wait some time, + time.sleep(3) # 3 sec seems to be a good choice. + rsp = self.cli.query('SELECT * FROM cpu_load_short') + + self.assertEqual( + # this is dummy_points : + [ + {'value': 0.64, + 'time': '2009-11-10T23:00:00Z', + "host": "server01", + "region": "us-west"} + ], + list(rsp['cpu_load_short']) + ) diff --git a/lib/influxdb/tests/server_tests/influxdb_instance.py b/lib/influxdb/tests/server_tests/influxdb_instance.py new file mode 100644 index 0000000..1dcd756 --- /dev/null +++ b/lib/influxdb/tests/server_tests/influxdb_instance.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +"""Define the test module for an influxdb instance.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import datetime +import distutils +import os +import tempfile +import shutil +import subprocess +import sys +import time +import unittest + +from influxdb.tests.misc import is_port_open, get_free_ports + +# hack in check_output if it's not defined, like for python 2.6 +if "check_output" not in dir(subprocess): + def f(*popenargs, **kwargs): + """Check for output.""" + if 'stdout' in kwargs: + raise ValueError( + 'stdout argument not allowed, it will be overridden.' + ) + process = subprocess.Popen(stdout=subprocess.PIPE, + *popenargs, + **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise subprocess.CalledProcessError(retcode, cmd) + return output + subprocess.check_output = f + + +class InfluxDbInstance(object): + """Define an instance of InfluxDB. + + A class to launch of fresh influxdb server instance + in a temporary place, using a config file template. + """ + + def __init__(self, conf_template, udp_enabled=False): + """Initialize an instance of InfluxDbInstance.""" + if os.environ.get("INFLUXDB_PYTHON_SKIP_SERVER_TESTS", None) == 'True': + raise unittest.SkipTest( + "Skipping server test (INFLUXDB_PYTHON_SKIP_SERVER_TESTS)" + ) + + self.influxd_path = self.find_influxd_path() + + errors = 0 + while True: + try: + self._start_server(conf_template, udp_enabled) + break + # Happens when the ports are already in use. + except RuntimeError as e: + errors += 1 + if errors > 2: + raise e + + def _start_server(self, conf_template, udp_enabled): + # create a temporary dir to store all needed files + # for the influxdb server instance : + self.temp_dir_base = tempfile.mkdtemp() + + # "temp_dir_base" will be used for conf file and logs, + # while "temp_dir_influxdb" is for the databases files/dirs : + tempdir = self.temp_dir_influxdb = tempfile.mkdtemp( + dir=self.temp_dir_base) + + # find a couple free ports : + free_ports = get_free_ports(4) + ports = {} + for service in 'http', 'global', 'meta', 'udp': + ports[service + '_port'] = free_ports.pop() + if not udp_enabled: + ports['udp_port'] = -1 + + conf_data = dict( + meta_dir=os.path.join(tempdir, 'meta'), + data_dir=os.path.join(tempdir, 'data'), + wal_dir=os.path.join(tempdir, 'wal'), + cluster_dir=os.path.join(tempdir, 'state'), + handoff_dir=os.path.join(tempdir, 'handoff'), + logs_file=os.path.join(self.temp_dir_base, 'logs.txt'), + udp_enabled='true' if udp_enabled else 'false', + ) + conf_data.update(ports) + self.__dict__.update(conf_data) + + conf_file = os.path.join(self.temp_dir_base, 'influxdb.conf') + with open(conf_file, "w") as fh: + with open(conf_template) as fh_template: + fh.write(fh_template.read().format(**conf_data)) + + # now start the server instance: + self.proc = subprocess.Popen( + [self.influxd_path, '-config', conf_file], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + print( + "%s > Started influxdb bin in %r with ports %s and %s.." % ( + datetime.datetime.now(), + self.temp_dir_base, + self.global_port, + self.http_port + ) + ) + + # wait for it to listen on the broker and admin ports: + # usually a fresh instance is ready in less than 1 sec .. + timeout = time.time() + 10 # so 10 secs should be enough, + # otherwise either your system load is high, + # or you run a 286 @ 1Mhz ? + try: + while time.time() < timeout: + if (is_port_open(self.http_port) and + is_port_open(self.global_port)): + # it's hard to check if a UDP port is open.. + if udp_enabled: + # so let's just sleep 0.5 sec in this case + # to be sure that the server has open the port + time.sleep(0.5) + break + time.sleep(0.5) + if self.proc.poll() is not None: + raise RuntimeError('influxdb prematurely exited') + else: + self.proc.terminate() + self.proc.wait() + raise RuntimeError('Timeout waiting for influxdb to listen' + ' on its ports (%s)' % ports) + except RuntimeError as err: + data = self.get_logs_and_output() + data['reason'] = str(err) + data['now'] = datetime.datetime.now() + raise RuntimeError("%(now)s > %(reason)s. RC=%(rc)s\n" + "stdout=%(out)s\nstderr=%(err)s\nlogs=%(logs)r" + % data) + + def find_influxd_path(self): + """Find the path for InfluxDB.""" + influxdb_bin_path = os.environ.get( + 'INFLUXDB_PYTHON_INFLUXD_PATH', + None + ) + + if influxdb_bin_path is None: + influxdb_bin_path = distutils.spawn.find_executable('influxd') + if not influxdb_bin_path: + try: + influxdb_bin_path = subprocess.check_output( + ['which', 'influxd'] + ).strip() + except subprocess.CalledProcessError: + # fallback on : + influxdb_bin_path = '/opt/influxdb/influxd' + + if not os.path.isfile(influxdb_bin_path): + raise unittest.SkipTest("Could not find influxd binary") + + version = subprocess.check_output([influxdb_bin_path, 'version']) + print("InfluxDB version: %s" % version, file=sys.stderr) + + return influxdb_bin_path + + def get_logs_and_output(self): + """Query for logs and output.""" + proc = self.proc + try: + with open(self.logs_file) as fh: + logs = fh.read() + except IOError as err: + logs = "Couldn't read logs: %s" % err + return { + 'rc': proc.returncode, + 'out': proc.stdout.read(), + 'err': proc.stderr.read(), + 'logs': logs + } + + def close(self, remove_tree=True): + """Close an instance of InfluxDB.""" + self.proc.terminate() + self.proc.wait() + if remove_tree: + shutil.rmtree(self.temp_dir_base) diff --git a/lib/influxdb/tests/test_line_protocol.py b/lib/influxdb/tests/test_line_protocol.py new file mode 100644 index 0000000..a3d8479 --- /dev/null +++ b/lib/influxdb/tests/test_line_protocol.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +"""Define the line protocol test module.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from datetime import datetime +import unittest +from pytz import UTC, timezone + +from influxdb import line_protocol + + +class TestLineProtocol(unittest.TestCase): + """Define the LineProtocol test object.""" + + def test_make_lines(self): + """Test make new lines in TestLineProtocol object.""" + data = { + "tags": { + "empty_tag": "", + "none_tag": None, + "backslash_tag": "C:\\", + "integer_tag": 2, + "string_tag": "hello" + }, + "points": [ + { + "measurement": "test", + "fields": { + "string_val": "hello!", + "int_val": 1, + "float_val": 1.1, + "none_field": None, + "bool_val": True, + } + } + ] + } + + self.assertEqual( + line_protocol.make_lines(data), + 'test,backslash_tag=C:\\\\ ,integer_tag=2,string_tag=hello ' + 'bool_val=True,float_val=1.1,int_val=1i,string_val="hello!"\n' + ) + + def test_timezone(self): + """Test timezone in TestLineProtocol object.""" + dt = datetime(2009, 11, 10, 23, 0, 0, 123456) + utc = UTC.localize(dt) + berlin = timezone('Europe/Berlin').localize(dt) + eastern = berlin.astimezone(timezone('US/Eastern')) + data = { + "points": [ + {"measurement": "A", "fields": {"val": 1}, + "time": 0}, + {"measurement": "A", "fields": {"val": 1}, + "time": "2009-11-10T23:00:00.123456Z"}, + {"measurement": "A", "fields": {"val": 1}, "time": dt}, + {"measurement": "A", "fields": {"val": 1}, "time": utc}, + {"measurement": "A", "fields": {"val": 1}, "time": berlin}, + {"measurement": "A", "fields": {"val": 1}, "time": eastern}, + ] + } + self.assertEqual( + line_protocol.make_lines(data), + '\n'.join([ + 'A val=1i 0', + 'A val=1i 1257894000123456000', + 'A val=1i 1257894000123456000', + 'A val=1i 1257894000123456000', + 'A val=1i 1257890400123456000', + 'A val=1i 1257890400123456000', + ]) + '\n' + ) + + def test_string_val_newline(self): + """Test string value with newline in TestLineProtocol object.""" + data = { + "points": [ + { + "measurement": "m1", + "fields": { + "multi_line": "line1\nline1\nline3" + } + } + ] + } + + self.assertEqual( + line_protocol.make_lines(data), + 'm1 multi_line="line1\\nline1\\nline3"\n' + ) + + def test_make_lines_unicode(self): + """Test make unicode lines in TestLineProtocol object.""" + data = { + "tags": { + "unicode_tag": "\'Привет!\'" # Hello! in Russian + }, + "points": [ + { + "measurement": "test", + "fields": { + "unicode_val": "Привет!", # Hello! in Russian + } + } + ] + } + + self.assertEqual( + line_protocol.make_lines(data), + 'test,unicode_tag=\'Привет!\' unicode_val="Привет!"\n' + ) + + def test_quote_ident(self): + """Test quote indentation in TestLineProtocol object.""" + self.assertEqual( + line_protocol.quote_ident(r"""\foo ' bar " Örf"""), + r'''"\\foo ' bar \" Örf"''' + ) + + def test_quote_literal(self): + """Test quote literal in TestLineProtocol object.""" + self.assertEqual( + line_protocol.quote_literal(r"""\foo ' bar " Örf"""), + r"""'\\foo \' bar " Örf'""" + ) + + def test_float_with_long_decimal_fraction(self): + """Ensure precision is preserved when casting floats into strings.""" + data = { + "points": [ + { + "measurement": "test", + "fields": { + "float_val": 1.0000000000000009, + } + } + ] + } + self.assertEqual( + line_protocol.make_lines(data), + 'test float_val=1.0000000000000009\n' + ) diff --git a/lib/maxminddb/__init__.py b/lib/maxminddb/__init__.py new file mode 100644 index 0000000..cb89c0f --- /dev/null +++ b/lib/maxminddb/__init__.py @@ -0,0 +1,54 @@ +# pylint:disable=C0111 +import os + +import maxminddb.reader + +try: + import maxminddb.extension +except ImportError: + maxminddb.extension = None + +from maxminddb.const import (MODE_AUTO, MODE_MMAP, MODE_MMAP_EXT, MODE_FILE, + MODE_MEMORY, MODE_FD) +from maxminddb.decoder import InvalidDatabaseError + + +def open_database(database, mode=MODE_AUTO): + """Open a Maxmind DB database + + Arguments: + database -- A path to a valid MaxMind DB file such as a GeoIP2 database + file, or a file descriptor in the case of MODE_FD. + mode -- mode to open the database with. Valid mode are: + * MODE_MMAP_EXT - use the C extension with memory map. + * MODE_MMAP - read from memory map. Pure Python. + * MODE_FILE - read database as standard file. Pure Python. + * MODE_MEMORY - load database into memory. Pure Python. + * MODE_FD - the param passed via database is a file descriptor, not + a path. This mode implies MODE_MEMORY. + * MODE_AUTO - tries MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that + order. Default mode. + """ + has_extension = maxminddb.extension and hasattr(maxminddb.extension, + 'Reader') + if (mode == MODE_AUTO and has_extension) or mode == MODE_MMAP_EXT: + if not has_extension: + raise ValueError( + "MODE_MMAP_EXT requires the maxminddb.extension module to be available" + ) + return maxminddb.extension.Reader(database) + elif mode in (MODE_AUTO, MODE_MMAP, MODE_FILE, MODE_MEMORY, MODE_FD): + return maxminddb.reader.Reader(database, mode) + raise ValueError('Unsupported open mode: {0}'.format(mode)) + + +def Reader(database): # pylint: disable=invalid-name + """This exists for backwards compatibility. Use open_database instead""" + return open_database(database) + + +__title__ = 'maxminddb' +__version__ = '1.4.1' +__author__ = 'Gregory Oschwald' +__license__ = 'Apache License, Version 2.0' +__copyright__ = 'Copyright 2013-2018 Maxmind, Inc.' diff --git a/lib/maxminddb/compat.py b/lib/maxminddb/compat.py new file mode 100644 index 0000000..4542e75 --- /dev/null +++ b/lib/maxminddb/compat.py @@ -0,0 +1,43 @@ +import sys + +import ipaddress + +# pylint: skip-file + +if sys.version_info[0] == 2: + + def compat_ip_address(address): + if isinstance(address, bytes): + address = address.decode() + return ipaddress.ip_address(address) + + int_from_byte = ord + + FileNotFoundError = IOError + + def int_from_bytes(b): + if b: + return int(b.encode("hex"), 16) + return 0 + + byte_from_int = chr + + string_type = basestring + + string_type_name = 'string' +else: + + def compat_ip_address(address): + return ipaddress.ip_address(address) + + int_from_byte = lambda x: x + + FileNotFoundError = FileNotFoundError + + int_from_bytes = lambda x: int.from_bytes(x, 'big') + + byte_from_int = lambda x: bytes([x]) + + string_type = str + + string_type_name = string_type.__name__ diff --git a/lib/maxminddb/const.py b/lib/maxminddb/const.py new file mode 100644 index 0000000..45222c2 --- /dev/null +++ b/lib/maxminddb/const.py @@ -0,0 +1,8 @@ +"""Constants used in the API""" + +MODE_AUTO = 0 +MODE_MMAP_EXT = 1 +MODE_MMAP = 2 +MODE_FILE = 4 +MODE_MEMORY = 8 +MODE_FD = 16 diff --git a/lib/maxminddb/decoder.py b/lib/maxminddb/decoder.py new file mode 100644 index 0000000..00f8bb1 --- /dev/null +++ b/lib/maxminddb/decoder.py @@ -0,0 +1,172 @@ +""" +maxminddb.decoder +~~~~~~~~~~~~~~~~~ + +This package contains code for decoding the MaxMind DB data section. + +""" +from __future__ import unicode_literals + +import struct + +from maxminddb.compat import byte_from_int, int_from_bytes +from maxminddb.errors import InvalidDatabaseError + + +class Decoder(object): # pylint: disable=too-few-public-methods + """Decoder for the data section of the MaxMind DB""" + + def __init__(self, database_buffer, pointer_base=0, pointer_test=False): + """Created a Decoder for a MaxMind DB + + Arguments: + database_buffer -- an mmap'd MaxMind DB file. + pointer_base -- the base number to use when decoding a pointer + pointer_test -- used for internal unit testing of pointer code + """ + self._pointer_test = pointer_test + self._buffer = database_buffer + self._pointer_base = pointer_base + + def _decode_array(self, size, offset): + array = [] + for _ in range(size): + (value, offset) = self.decode(offset) + array.append(value) + return array, offset + + def _decode_boolean(self, size, offset): + return size != 0, offset + + def _decode_bytes(self, size, offset): + new_offset = offset + size + return self._buffer[offset:new_offset], new_offset + + # pylint: disable=no-self-argument + # |-> I am open to better ways of doing this as long as it doesn't involve + # lots of code duplication. + def _decode_packed_type(type_code, type_size, pad=False): + # pylint: disable=protected-access, missing-docstring + def unpack_type(self, size, offset): + if not pad: + self._verify_size(size, type_size) + new_offset = offset + size + packed_bytes = self._buffer[offset:new_offset] + if pad: + packed_bytes = packed_bytes.rjust(type_size, b'\x00') + (value, ) = struct.unpack(type_code, packed_bytes) + return value, new_offset + + return unpack_type + + def _decode_map(self, size, offset): + container = {} + for _ in range(size): + (key, offset) = self.decode(offset) + (value, offset) = self.decode(offset) + container[key] = value + return container, offset + + _pointer_value_offset = { + 1: 0, + 2: 2048, + 3: 526336, + 4: 0, + } + + def _decode_pointer(self, size, offset): + pointer_size = ((size >> 3) & 0x3) + 1 + new_offset = offset + pointer_size + pointer_bytes = self._buffer[offset:new_offset] + packed = pointer_bytes if pointer_size == 4 else struct.pack( + b'!c', byte_from_int(size & 0x7)) + pointer_bytes + unpacked = int_from_bytes(packed) + pointer = unpacked + self._pointer_base + \ + self._pointer_value_offset[pointer_size] + if self._pointer_test: + return pointer, new_offset + (value, _) = self.decode(pointer) + return value, new_offset + + def _decode_uint(self, size, offset): + new_offset = offset + size + uint_bytes = self._buffer[offset:new_offset] + return int_from_bytes(uint_bytes), new_offset + + def _decode_utf8_string(self, size, offset): + new_offset = offset + size + return self._buffer[offset:new_offset].decode('utf-8'), new_offset + + _type_decoder = { + 1: _decode_pointer, + 2: _decode_utf8_string, + 3: _decode_packed_type(b'!d', 8), # double, + 4: _decode_bytes, + 5: _decode_uint, # uint16 + 6: _decode_uint, # uint32 + 7: _decode_map, + 8: _decode_packed_type(b'!i', 4, pad=True), # int32 + 9: _decode_uint, # uint64 + 10: _decode_uint, # uint128 + 11: _decode_array, + 14: _decode_boolean, + 15: _decode_packed_type(b'!f', 4), # float, + } + + def decode(self, offset): + """Decode a section of the data section starting at offset + + Arguments: + offset -- the location of the data structure to decode + """ + new_offset = offset + 1 + (ctrl_byte, ) = struct.unpack(b'!B', self._buffer[offset:new_offset]) + type_num = ctrl_byte >> 5 + # Extended type + if not type_num: + (type_num, new_offset) = self._read_extended(new_offset) + + if type_num not in self._type_decoder: + raise InvalidDatabaseError('Unexpected type number ({type}) ' + 'encountered'.format(type=type_num)) + + (size, new_offset) = self._size_from_ctrl_byte(ctrl_byte, new_offset, + type_num) + return self._type_decoder[type_num](self, size, new_offset) + + def _read_extended(self, offset): + (next_byte, ) = struct.unpack(b'!B', self._buffer[offset:offset + 1]) + type_num = next_byte + 7 + if type_num < 7: + raise InvalidDatabaseError( + 'Something went horribly wrong in the decoder. An ' + 'extended type resolved to a type number < 8 ' + '({type})'.format(type=type_num)) + return type_num, offset + 1 + + def _verify_size(self, expected, actual): + if expected != actual: + raise InvalidDatabaseError( + 'The MaxMind DB file\'s data section contains bad data ' + '(unknown data type or corrupt data)') + + def _size_from_ctrl_byte(self, ctrl_byte, offset, type_num): + size = ctrl_byte & 0x1f + if type_num == 1: + return size, offset + bytes_to_read = 0 if size < 29 else size - 28 + + new_offset = offset + bytes_to_read + size_bytes = self._buffer[offset:new_offset] + + # Using unpack rather than int_from_bytes as it is about 200 lookups + # per second faster here. + if size == 29: + size = 29 + struct.unpack(b'!B', size_bytes)[0] + elif size == 30: + size = 285 + struct.unpack(b'!H', size_bytes)[0] + elif size > 30: + size = struct.unpack(b'!I', size_bytes.rjust(4, + b'\x00'))[0] + 65821 + + return size, new_offset diff --git a/lib/maxminddb/errors.py b/lib/maxminddb/errors.py new file mode 100644 index 0000000..c996b96 --- /dev/null +++ b/lib/maxminddb/errors.py @@ -0,0 +1,10 @@ +""" +maxminddb.errors +~~~~~~~~~~~~~~~~ + +This module contains custom errors for the MaxMind DB reader +""" + + +class InvalidDatabaseError(RuntimeError): + """This error is thrown when unexpected data is found in the database.""" diff --git a/lib/maxminddb/extension/maxminddb.c b/lib/maxminddb/extension/maxminddb.c new file mode 100644 index 0000000..ac193ee --- /dev/null +++ b/lib/maxminddb/extension/maxminddb.c @@ -0,0 +1,602 @@ +#include +#include +#include "structmember.h" + +#define __STDC_FORMAT_MACROS +#include + +static PyTypeObject Reader_Type; +static PyTypeObject Metadata_Type; +static PyObject *MaxMindDB_error; + +typedef struct { + PyObject_HEAD /* no semicolon */ + MMDB_s *mmdb; + PyObject *closed; +} Reader_obj; + +typedef struct { + PyObject_HEAD /* no semicolon */ + PyObject *binary_format_major_version; + PyObject *binary_format_minor_version; + PyObject *build_epoch; + PyObject *database_type; + PyObject *description; + PyObject *ip_version; + PyObject *languages; + PyObject *node_count; + PyObject *record_size; +} Metadata_obj; + +static PyObject *from_entry_data_list(MMDB_entry_data_list_s **entry_data_list); +static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list); +static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list); +static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list); + +#if PY_MAJOR_VERSION >= 3 + #define MOD_INIT(name) PyMODINIT_FUNC PyInit_ ## name(void) + #define RETURN_MOD_INIT(m) return (m) + #define FILE_NOT_FOUND_ERROR PyExc_FileNotFoundError +#else + #define MOD_INIT(name) PyMODINIT_FUNC init ## name(void) + #define RETURN_MOD_INIT(m) return + #define PyInt_FromLong PyLong_FromLong + #define FILE_NOT_FOUND_ERROR PyExc_IOError +#endif + +#ifdef __GNUC__ + # define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) +#else + # define UNUSED(x) UNUSED_ ## x +#endif + +static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + char *filename; + int mode = 0; + + static char *kwlist[] = {"database", "mode", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &filename, &mode)) { + return -1; + } + + if (mode != 0 && mode != 1) { + PyErr_Format(PyExc_ValueError, "Unsupported open mode (%i). Only " + "MODE_AUTO and MODE_MMAP_EXT are supported by this extension.", + mode); + return -1; + } + + if (0 != access(filename, R_OK)) { + PyErr_Format(FILE_NOT_FOUND_ERROR, + "No such file or directory: '%s'", + filename); + return -1; + } + + MMDB_s *mmdb = (MMDB_s *)malloc(sizeof(MMDB_s)); + if (NULL == mmdb) { + PyErr_NoMemory(); + return -1; + } + + Reader_obj *mmdb_obj = (Reader_obj *)self; + if (!mmdb_obj) { + free(mmdb); + PyErr_NoMemory(); + return -1; + } + + uint16_t status = MMDB_open(filename, MMDB_MODE_MMAP, mmdb); + + if (MMDB_SUCCESS != status) { + free(mmdb); + PyErr_Format( + MaxMindDB_error, + "Error opening database file (%s). Is this a valid MaxMind DB file?", + filename + ); + return -1; + } + + mmdb_obj->mmdb = mmdb; + mmdb_obj->closed = Py_False; + return 0; +} + +static PyObject *Reader_get(PyObject *self, PyObject *args) +{ + char *ip_address = NULL; + + Reader_obj *mmdb_obj = (Reader_obj *)self; + if (!PyArg_ParseTuple(args, "s", &ip_address)) { + return NULL; + } + + MMDB_s *mmdb = mmdb_obj->mmdb; + + if (NULL == mmdb) { + PyErr_SetString(PyExc_ValueError, + "Attempt to read from a closed MaxMind DB."); + return NULL; + } + + int gai_error = 0; + int mmdb_error = MMDB_SUCCESS; + MMDB_lookup_result_s result = + MMDB_lookup_string(mmdb, ip_address, &gai_error, + &mmdb_error); + + if (0 != gai_error) { + PyErr_Format(PyExc_ValueError, + "'%s' does not appear to be an IPv4 or IPv6 address.", + ip_address); + return NULL; + } + + if (MMDB_SUCCESS != mmdb_error) { + PyObject *exception; + if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) { + exception = PyExc_ValueError; + } else { + exception = MaxMindDB_error; + } + PyErr_Format(exception, "Error looking up %s. %s", + ip_address, MMDB_strerror(mmdb_error)); + return NULL; + } + + if (!result.found_entry) { + Py_RETURN_NONE; + } + + MMDB_entry_data_list_s *entry_data_list = NULL; + int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list); + if (MMDB_SUCCESS != status) { + PyErr_Format(MaxMindDB_error, + "Error while looking up data for %s. %s", + ip_address, MMDB_strerror(status)); + MMDB_free_entry_data_list(entry_data_list); + return NULL; + } + + MMDB_entry_data_list_s *original_entry_data_list = entry_data_list; + PyObject *py_obj = from_entry_data_list(&entry_data_list); + MMDB_free_entry_data_list(original_entry_data_list); + return py_obj; +} + +static PyObject *Reader_metadata(PyObject *self, PyObject *UNUSED(args)) +{ + Reader_obj *mmdb_obj = (Reader_obj *)self; + + if (NULL == mmdb_obj->mmdb) { + PyErr_SetString(PyExc_IOError, + "Attempt to read from a closed MaxMind DB."); + return NULL; + } + + MMDB_entry_data_list_s *entry_data_list; + MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list); + MMDB_entry_data_list_s *original_entry_data_list = entry_data_list; + + PyObject *metadata_dict = from_entry_data_list(&entry_data_list); + MMDB_free_entry_data_list(original_entry_data_list); + if (NULL == metadata_dict || !PyDict_Check(metadata_dict)) { + PyErr_SetString(MaxMindDB_error, + "Error decoding metadata."); + return NULL; + } + + PyObject *args = PyTuple_New(0); + if (NULL == args) { + Py_DECREF(metadata_dict); + return NULL; + } + + PyObject *metadata = PyObject_Call((PyObject *)&Metadata_Type, args, + metadata_dict); + + Py_DECREF(metadata_dict); + return metadata; +} + +static PyObject *Reader_close(PyObject *self, PyObject *UNUSED(args)) +{ + Reader_obj *mmdb_obj = (Reader_obj *)self; + + if (NULL != mmdb_obj->mmdb) { + MMDB_close(mmdb_obj->mmdb); + free(mmdb_obj->mmdb); + mmdb_obj->mmdb = NULL; + } + + mmdb_obj->closed = Py_True; + + Py_RETURN_NONE; +} + +static PyObject *Reader__enter__(PyObject *self, PyObject *UNUSED(args)) +{ + Reader_obj *mmdb_obj = (Reader_obj *)self; + + if(mmdb_obj->closed == Py_True) { + PyErr_SetString(PyExc_ValueError, + "Attempt to reopen a closed MaxMind DB."); + return NULL; + } + + Py_INCREF(self); + return (PyObject *)self; +} + +static PyObject *Reader__exit__(PyObject *self, PyObject *UNUSED(args)) +{ + Reader_close(self, NULL); + Py_RETURN_NONE; +} + +static void Reader_dealloc(PyObject *self) +{ + Reader_obj *obj = (Reader_obj *)self; + if (NULL != obj->mmdb) { + Reader_close(self, NULL); + } + + PyObject_Del(self); +} + +static int Metadata_init(PyObject *self, PyObject *args, PyObject *kwds) +{ + + PyObject + *binary_format_major_version, + *binary_format_minor_version, + *build_epoch, + *database_type, + *description, + *ip_version, + *languages, + *node_count, + *record_size; + + static char *kwlist[] = { + "binary_format_major_version", + "binary_format_minor_version", + "build_epoch", + "database_type", + "description", + "ip_version", + "languages", + "node_count", + "record_size", + NULL + }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOOOOO", kwlist, + &binary_format_major_version, + &binary_format_minor_version, + &build_epoch, + &database_type, + &description, + &ip_version, + &languages, + &node_count, + &record_size)) { + return -1; + } + + Metadata_obj *obj = (Metadata_obj *)self; + + obj->binary_format_major_version = binary_format_major_version; + obj->binary_format_minor_version = binary_format_minor_version; + obj->build_epoch = build_epoch; + obj->database_type = database_type; + obj->description = description; + obj->ip_version = ip_version; + obj->languages = languages; + obj->node_count = node_count; + obj->record_size = record_size; + + Py_INCREF(obj->binary_format_major_version); + Py_INCREF(obj->binary_format_minor_version); + Py_INCREF(obj->build_epoch); + Py_INCREF(obj->database_type); + Py_INCREF(obj->description); + Py_INCREF(obj->ip_version); + Py_INCREF(obj->languages); + Py_INCREF(obj->node_count); + Py_INCREF(obj->record_size); + + return 0; +} + +static void Metadata_dealloc(PyObject *self) +{ + Metadata_obj *obj = (Metadata_obj *)self; + Py_DECREF(obj->binary_format_major_version); + Py_DECREF(obj->binary_format_minor_version); + Py_DECREF(obj->build_epoch); + Py_DECREF(obj->database_type); + Py_DECREF(obj->description); + Py_DECREF(obj->ip_version); + Py_DECREF(obj->languages); + Py_DECREF(obj->node_count); + Py_DECREF(obj->record_size); + PyObject_Del(self); +} + +static PyObject *from_entry_data_list(MMDB_entry_data_list_s **entry_data_list) +{ + if (NULL == entry_data_list || NULL == *entry_data_list) { + PyErr_SetString( + MaxMindDB_error, + "Error while looking up data. Your database may be corrupt or you have found a bug in libmaxminddb." + ); + return NULL; + } + + switch ((*entry_data_list)->entry_data.type) { + case MMDB_DATA_TYPE_MAP: + return from_map(entry_data_list); + case MMDB_DATA_TYPE_ARRAY: + return from_array(entry_data_list); + case MMDB_DATA_TYPE_UTF8_STRING: + return PyUnicode_FromStringAndSize( + (*entry_data_list)->entry_data.utf8_string, + (*entry_data_list)->entry_data.data_size + ); + case MMDB_DATA_TYPE_BYTES: + return PyByteArray_FromStringAndSize( + (const char *)(*entry_data_list)->entry_data.bytes, + (Py_ssize_t)(*entry_data_list)->entry_data.data_size); + case MMDB_DATA_TYPE_DOUBLE: + return PyFloat_FromDouble((*entry_data_list)->entry_data.double_value); + case MMDB_DATA_TYPE_FLOAT: + return PyFloat_FromDouble((*entry_data_list)->entry_data.float_value); + case MMDB_DATA_TYPE_UINT16: + return PyLong_FromLong( (*entry_data_list)->entry_data.uint16); + case MMDB_DATA_TYPE_UINT32: + return PyLong_FromLong((*entry_data_list)->entry_data.uint32); + case MMDB_DATA_TYPE_BOOLEAN: + return PyBool_FromLong((*entry_data_list)->entry_data.boolean); + case MMDB_DATA_TYPE_UINT64: + return PyLong_FromUnsignedLongLong( + (*entry_data_list)->entry_data.uint64); + case MMDB_DATA_TYPE_UINT128: + return from_uint128(*entry_data_list); + case MMDB_DATA_TYPE_INT32: + return PyLong_FromLong((*entry_data_list)->entry_data.int32); + default: + PyErr_Format(MaxMindDB_error, + "Invalid data type arguments: %d", + (*entry_data_list)->entry_data.type); + return NULL; + } + return NULL; +} + +static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) +{ + PyObject *py_obj = PyDict_New(); + if (NULL == py_obj) { + PyErr_NoMemory(); + return NULL; + } + + const uint32_t map_size = (*entry_data_list)->entry_data.data_size; + + uint i; + // entry_data_list cannot start out NULL (see from_entry_data_list). We + // check it in the loop because it may become NULL. + // coverity[check_after_deref] + for (i = 0; i < map_size && entry_data_list; i++) { + *entry_data_list = (*entry_data_list)->next; + + PyObject *key = PyUnicode_FromStringAndSize( + (char *)(*entry_data_list)->entry_data.utf8_string, + (*entry_data_list)->entry_data.data_size + ); + + *entry_data_list = (*entry_data_list)->next; + + PyObject *value = from_entry_data_list(entry_data_list); + if (NULL == value) { + Py_DECREF(key); + Py_DECREF(py_obj); + return NULL; + } + PyDict_SetItem(py_obj, key, value); + Py_DECREF(value); + Py_DECREF(key); + } + + return py_obj; +} + +static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list) +{ + const uint32_t size = (*entry_data_list)->entry_data.data_size; + + PyObject *py_obj = PyList_New(size); + if (NULL == py_obj) { + PyErr_NoMemory(); + return NULL; + } + + uint i; + // entry_data_list cannot start out NULL (see from_entry_data_list). We + // check it in the loop because it may become NULL. + // coverity[check_after_deref] + for (i = 0; i < size && entry_data_list; i++) { + *entry_data_list = (*entry_data_list)->next; + PyObject *value = from_entry_data_list(entry_data_list); + if (NULL == value) { + Py_DECREF(py_obj); + return NULL; + } + // PyList_SetItem 'steals' the reference + PyList_SetItem(py_obj, i, value); + } + return py_obj; +} + +static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list) +{ + uint64_t high = 0; + uint64_t low = 0; +#if MMDB_UINT128_IS_BYTE_ARRAY + int i; + for (i = 0; i < 8; i++) { + high = (high << 8) | entry_data_list->entry_data.uint128[i]; + } + + for (i = 8; i < 16; i++) { + low = (low << 8) | entry_data_list->entry_data.uint128[i]; + } +#else + high = entry_data_list->entry_data.uint128 >> 64; + low = (uint64_t)entry_data_list->entry_data.uint128; +#endif + + char *num_str = malloc(33); + if (NULL == num_str) { + PyErr_NoMemory(); + return NULL; + } + + snprintf(num_str, 33, "%016" PRIX64 "%016" PRIX64, high, low); + + PyObject *py_obj = PyLong_FromString(num_str, NULL, 16); + + free(num_str); + return py_obj; +} + +static PyMethodDef Reader_methods[] = { + { "get", Reader_get, METH_VARARGS, + "Get record for IP address" }, + { "metadata", Reader_metadata, METH_NOARGS, + "Returns metadata object for database" }, + { "close", Reader_close, METH_NOARGS, "Closes database"}, + { "__exit__", Reader__exit__, METH_VARARGS, "Called when exiting a with-context. Calls close"}, + { "__enter__", Reader__enter__, METH_NOARGS, "Called when entering a with-context."}, + { NULL, NULL, 0, NULL } +}; + +static PyMemberDef Reader_members[] = { + { "closed", T_OBJECT, offsetof(Reader_obj, closed), READONLY, NULL }, + { NULL, 0, 0, 0, NULL } +}; + +static PyTypeObject Reader_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_basicsize = sizeof(Reader_obj), + .tp_dealloc = Reader_dealloc, + .tp_doc = "Reader object", + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = Reader_methods, + .tp_members = Reader_members, + .tp_name = "Reader", + .tp_init = Reader_init, +}; + +static PyMethodDef Metadata_methods[] = { + { NULL, NULL, 0, NULL } +}; + +/* *INDENT-OFF* */ +static PyMemberDef Metadata_members[] = { + { "binary_format_major_version", T_OBJECT, offsetof( + Metadata_obj, binary_format_major_version), READONLY, NULL }, + { "binary_format_minor_version", T_OBJECT, offsetof( + Metadata_obj, binary_format_minor_version), READONLY, NULL }, + { "build_epoch", T_OBJECT, offsetof(Metadata_obj, build_epoch), + READONLY, NULL }, + { "database_type", T_OBJECT, offsetof(Metadata_obj, database_type), + READONLY, NULL }, + { "description", T_OBJECT, offsetof(Metadata_obj, description), + READONLY, NULL }, + { "ip_version", T_OBJECT, offsetof(Metadata_obj, ip_version), + READONLY, NULL }, + { "languages", T_OBJECT, offsetof(Metadata_obj, languages), READONLY, + NULL }, + { "node_count", T_OBJECT, offsetof(Metadata_obj, node_count), + READONLY, NULL }, + { "record_size", T_OBJECT, offsetof(Metadata_obj, record_size), + READONLY, NULL }, + { NULL, 0, 0, 0, NULL } +}; +/* *INDENT-ON* */ + +static PyTypeObject Metadata_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_basicsize = sizeof(Metadata_obj), + .tp_dealloc = Metadata_dealloc, + .tp_doc = "Metadata object", + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_members = Metadata_members, + .tp_methods = Metadata_methods, + .tp_name = "Metadata", + .tp_init = Metadata_init +}; + +static PyMethodDef MaxMindDB_methods[] = { + { NULL, NULL, 0, NULL } +}; + + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef MaxMindDB_module = { + PyModuleDef_HEAD_INIT, + .m_name = "extension", + .m_doc = "This is a C extension to read MaxMind DB file format", + .m_methods = MaxMindDB_methods, +}; +#endif + +MOD_INIT(extension){ + PyObject *m; + +#if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&MaxMindDB_module); +#else + m = Py_InitModule("extension", MaxMindDB_methods); +#endif + + if (!m) { + RETURN_MOD_INIT(NULL); + } + + Reader_Type.tp_new = PyType_GenericNew; + if (PyType_Ready(&Reader_Type)) { + RETURN_MOD_INIT(NULL); + } + Py_INCREF(&Reader_Type); + PyModule_AddObject(m, "Reader", (PyObject *)&Reader_Type); + + Metadata_Type.tp_new = PyType_GenericNew; + if (PyType_Ready(&Metadata_Type)) { + RETURN_MOD_INIT(NULL); + } + PyModule_AddObject(m, "extension", (PyObject *)&Metadata_Type); + + PyObject* error_mod = PyImport_ImportModule("maxminddb.errors"); + if (error_mod == NULL) { + RETURN_MOD_INIT(NULL); + } + + MaxMindDB_error = PyObject_GetAttrString(error_mod, "InvalidDatabaseError"); + Py_DECREF(error_mod); + + if (MaxMindDB_error == NULL) { + RETURN_MOD_INIT(NULL); + } + + Py_INCREF(MaxMindDB_error); + + /* We primarily add it to the module for backwards compatibility */ + PyModule_AddObject(m, "InvalidDatabaseError", MaxMindDB_error); + + RETURN_MOD_INIT(m); +} diff --git a/lib/maxminddb/file.py b/lib/maxminddb/file.py new file mode 100644 index 0000000..92b180b --- /dev/null +++ b/lib/maxminddb/file.py @@ -0,0 +1,65 @@ +"""For internal use only. It provides a slice-like file reader.""" + +import os + +try: + # pylint: disable=no-name-in-module + from multiprocessing import Lock +except ImportError: + from threading import Lock + + +class FileBuffer(object): + """A slice-able file reader""" + + def __init__(self, database): + self._handle = open(database, 'rb') + self._size = os.fstat(self._handle.fileno()).st_size + if not hasattr(os, 'pread'): + self._lock = Lock() + + def __getitem__(self, key): + if isinstance(key, slice): + return self._read(key.stop - key.start, key.start) + elif isinstance(key, int): + return self._read(1, key) + else: + raise TypeError("Invalid argument type.") + + def rfind(self, needle, start): + """Reverse find needle from start""" + pos = self._read(self._size - start - 1, start).rfind(needle) + if pos == -1: + return pos + return start + pos + + def size(self): + """Size of file""" + return self._size + + def close(self): + """Close file""" + self._handle.close() + + if hasattr(os, 'pread'): + + def _read(self, buffersize, offset): + """read that uses pread""" + # pylint: disable=no-member + return os.pread(self._handle.fileno(), buffersize, offset) + + else: + + def _read(self, buffersize, offset): + """read with a lock + + This lock is necessary as after a fork, the different processes + will share the same file table entry, even if we dup the fd, and + as such the same offsets. There does not appear to be a way to + duplicate the file table entry and we cannot re-open based on the + original path as that file may have replaced with another or + unlinked. + """ + with self._lock: + self._handle.seek(offset) + return self._handle.read(buffersize) diff --git a/lib/maxminddb/reader.py b/lib/maxminddb/reader.py new file mode 100644 index 0000000..3761b7d --- /dev/null +++ b/lib/maxminddb/reader.py @@ -0,0 +1,309 @@ +""" +maxminddb.reader +~~~~~~~~~~~~~~~~ + +This module contains the pure Python database reader and related classes. + +""" +from __future__ import unicode_literals + +try: + import mmap +except ImportError: + # pylint: disable=invalid-name + mmap = None + +import struct + +from maxminddb.compat import (byte_from_int, compat_ip_address, string_type, + string_type_name) +from maxminddb.const import MODE_AUTO, MODE_MMAP, MODE_FILE, MODE_MEMORY, MODE_FD +from maxminddb.decoder import Decoder +from maxminddb.errors import InvalidDatabaseError +from maxminddb.file import FileBuffer + + +class Reader(object): + """ + Instances of this class provide a reader for the MaxMind DB format. IP + addresses can be looked up using the ``get`` method. + """ + + _DATA_SECTION_SEPARATOR_SIZE = 16 + _METADATA_START_MARKER = b"\xAB\xCD\xEFMaxMind.com" + + _ipv4_start = None + + def __init__(self, database, mode=MODE_AUTO): + """Reader for the MaxMind DB file format + + Arguments: + database -- A path to a valid MaxMind DB file such as a GeoIP2 database + file, or a file descriptor in the case of MODE_FD. + mode -- mode to open the database with. Valid mode are: + * MODE_MMAP - read from memory map. + * MODE_FILE - read database as standard file. + * MODE_MEMORY - load database into memory. + * MODE_AUTO - tries MODE_MMAP and then MODE_FILE. Default. + * MODE_FD - the param passed via database is a file descriptor, not + a path. This mode implies MODE_MEMORY. + """ + if (mode == MODE_AUTO and mmap) or mode == MODE_MMAP: + with open(database, 'rb') as db_file: + self._buffer = mmap.mmap( + db_file.fileno(), 0, access=mmap.ACCESS_READ) + self._buffer_size = self._buffer.size() + filename = database + elif mode in (MODE_AUTO, MODE_FILE): + self._buffer = FileBuffer(database) + self._buffer_size = self._buffer.size() + filename = database + elif mode == MODE_MEMORY: + with open(database, 'rb') as db_file: + self._buffer = db_file.read() + self._buffer_size = len(self._buffer) + filename = database + elif mode == MODE_FD: + self._buffer = database.read() + self._buffer_size = len(self._buffer) + filename = database.name + else: + raise ValueError( + 'Unsupported open mode ({0}). Only MODE_AUTO, MODE_FILE, ' + 'MODE_MEMORY and MODE_FD are supported by the pure Python ' + 'Reader'.format(mode)) + + metadata_start = self._buffer.rfind( + self._METADATA_START_MARKER, max(0, + self._buffer_size - 128 * 1024)) + + if metadata_start == -1: + self.close() + raise InvalidDatabaseError('Error opening database file ({0}). ' + 'Is this a valid MaxMind DB file?' + ''.format(filename)) + + metadata_start += len(self._METADATA_START_MARKER) + metadata_decoder = Decoder(self._buffer, metadata_start) + (metadata, _) = metadata_decoder.decode(metadata_start) + self._metadata = Metadata(**metadata) # pylint: disable=bad-option-value + + self._decoder = Decoder(self._buffer, self._metadata.search_tree_size + + self._DATA_SECTION_SEPARATOR_SIZE) + self.closed = False + + def metadata(self): + """Return the metadata associated with the MaxMind DB file""" + return self._metadata + + def get(self, ip_address): + """Return the record for the ip_address in the MaxMind DB + + + Arguments: + ip_address -- an IP address in the standard string notation + """ + if not isinstance(ip_address, string_type): + raise TypeError('argument 1 must be %s, not %s' % + (string_type_name, type(ip_address).__name__)) + + address = compat_ip_address(ip_address) + + if address.version == 6 and self._metadata.ip_version == 4: + raise ValueError( + 'Error looking up {0}. You attempted to look up ' + 'an IPv6 address in an IPv4-only database.'.format(ip_address)) + pointer = self._find_address_in_tree(address) + + return self._resolve_data_pointer(pointer) if pointer else None + + def _find_address_in_tree(self, ip_address): + packed = bytearray(ip_address.packed) + + bit_count = len(packed) * 8 + node = self._start_node(bit_count) + + for i in range(bit_count): + if node >= self._metadata.node_count: + break + bit = 1 & (packed[i >> 3] >> 7 - (i % 8)) + node = self._read_node(node, bit) + if node == self._metadata.node_count: + # Record is empty + return 0 + elif node > self._metadata.node_count: + return node + + raise InvalidDatabaseError('Invalid node in search tree') + + def _start_node(self, length): + if self._metadata.ip_version != 6 or length == 128: + return 0 + + # We are looking up an IPv4 address in an IPv6 tree. Skip over the + # first 96 nodes. + if self._ipv4_start: + return self._ipv4_start + + node = 0 + for _ in range(96): + if node >= self._metadata.node_count: + break + node = self._read_node(node, 0) + self._ipv4_start = node + return node + + def _read_node(self, node_number, index): + base_offset = node_number * self._metadata.node_byte_size + + record_size = self._metadata.record_size + if record_size == 24: + offset = base_offset + index * 3 + node_bytes = b'\x00' + self._buffer[offset:offset + 3] + elif record_size == 28: + (middle, ) = struct.unpack( + b'!B', self._buffer[base_offset + 3:base_offset + 4]) + if index: + middle &= 0x0F + else: + middle = (0xF0 & middle) >> 4 + offset = base_offset + index * 4 + node_bytes = byte_from_int(middle) + self._buffer[offset:offset + 3] + elif record_size == 32: + offset = base_offset + index * 4 + node_bytes = self._buffer[offset:offset + 4] + else: + raise InvalidDatabaseError( + 'Unknown record size: {0}'.format(record_size)) + return struct.unpack(b'!I', node_bytes)[0] + + def _resolve_data_pointer(self, pointer): + resolved = pointer - self._metadata.node_count + \ + self._metadata.search_tree_size + + if resolved > self._buffer_size: + raise InvalidDatabaseError( + "The MaxMind DB file's search tree is corrupt") + + (data, _) = self._decoder.decode(resolved) + return data + + def close(self): + """Closes the MaxMind DB file and returns the resources to the system""" + # pylint: disable=unidiomatic-typecheck + if type(self._buffer) not in (str, bytes): + self._buffer.close() + self.closed = True + + def __exit__(self, *args): + self.close() + + def __enter__(self): + if self.closed: + raise ValueError('Attempt to reopen a closed MaxMind DB') + return self + + +class Metadata(object): + """Metadata for the MaxMind DB reader + + + .. attribute:: binary_format_major_version + + The major version number of the binary format used when creating the + database. + + :type: int + + .. attribute:: binary_format_minor_version + + The minor version number of the binary format used when creating the + database. + + :type: int + + .. attribute:: build_epoch + + The Unix epoch for the build time of the database. + + :type: int + + .. attribute:: database_type + + A string identifying the database type, e.g., "GeoIP2-City". + + :type: str + + .. attribute:: description + + A map from locales to text descriptions of the database. + + :type: dict(str, str) + + .. attribute:: ip_version + + The IP version of the data in a database. A value of "4" means the + database only supports IPv4. A database with a value of "6" may support + both IPv4 and IPv6 lookups. + + :type: int + + .. attribute:: languages + + A list of locale codes supported by the databse. + + :type: list(str) + + .. attribute:: node_count + + The number of nodes in the database. + + :type: int + + .. attribute:: record_size + + The bit size of a record in the search tree. + + :type: int + + """ + + # pylint: disable=too-many-instance-attributes + def __init__(self, **kwargs): + """Creates new Metadata object. kwargs are key/value pairs from spec""" + # Although I could just update __dict__, that is less obvious and it + # doesn't work well with static analysis tools and some IDEs + self.node_count = kwargs['node_count'] + self.record_size = kwargs['record_size'] + self.ip_version = kwargs['ip_version'] + self.database_type = kwargs['database_type'] + self.languages = kwargs['languages'] + self.binary_format_major_version = kwargs[ + 'binary_format_major_version'] + self.binary_format_minor_version = kwargs[ + 'binary_format_minor_version'] + self.build_epoch = kwargs['build_epoch'] + self.description = kwargs['description'] + + @property + def node_byte_size(self): + """The size of a node in bytes + + :type: int + """ + return self.record_size // 4 + + @property + def search_tree_size(self): + """The size of the search tree + + :type: int + """ + return self.node_count * self.node_byte_size + + def __repr__(self): + args = ', '.join('%s=%r' % x for x in self.__dict__.items()) + return '{module}.{class_name}({data})'.format( + module=self.__module__, + class_name=self.__class__.__name__, + data=args) diff --git a/lib/pkg_resources/__init__.py b/lib/pkg_resources/__init__.py new file mode 100644 index 0000000..6ca68da --- /dev/null +++ b/lib/pkg_resources/__init__.py @@ -0,0 +1,3171 @@ +# coding: utf-8 +""" +Package resource API +-------------------- + +A resource is a logical file contained within a package, or a logical +subdirectory thereof. The package resource API expects resource names +to have their path parts separated with ``/``, *not* whatever the local +path separator is. Do not use os.path operations to manipulate resource +names being passed into the API. + +The package resource API is designed to work with normal filesystem packages, +.egg files, and unpacked .egg files. It can also work in a limited way with +.zip files and with custom PEP 302 loaders that support the ``get_data()`` +method. +""" + +from __future__ import absolute_import + +import sys +import os +import io +import time +import re +import types +import zipfile +import zipimport +import warnings +import stat +import functools +import pkgutil +import operator +import platform +import collections +import plistlib +import email.parser +import errno +import tempfile +import textwrap +import itertools +import inspect +from pkgutil import get_importer + +try: + import _imp +except ImportError: + # Python 3.2 compatibility + import imp as _imp + +try: + FileExistsError +except NameError: + FileExistsError = OSError + +from pkg_resources.extern import six +from pkg_resources.extern.six.moves import urllib, map, filter + +# capture these to bypass sandboxing +from os import utime +try: + from os import mkdir, rename, unlink + WRITE_SUPPORT = True +except ImportError: + # no write support, probably under GAE + WRITE_SUPPORT = False + +from os import open as os_open +from os.path import isdir, split + +try: + import importlib.machinery as importlib_machinery + # access attribute to force import under delayed import mechanisms. + importlib_machinery.__name__ +except ImportError: + importlib_machinery = None + +from . import py31compat +from pkg_resources.extern import appdirs +from pkg_resources.extern import packaging +__import__('pkg_resources.extern.packaging.version') +__import__('pkg_resources.extern.packaging.specifiers') +__import__('pkg_resources.extern.packaging.requirements') +__import__('pkg_resources.extern.packaging.markers') + + +__metaclass__ = type + + +if (3, 0) < sys.version_info < (3, 4): + raise RuntimeError("Python 3.4 or later is required") + +if six.PY2: + # Those builtin exceptions are only defined in Python 3 + PermissionError = None + NotADirectoryError = None + +# declare some globals that will be defined later to +# satisfy the linters. +require = None +working_set = None +add_activation_listener = None +resources_stream = None +cleanup_resources = None +resource_dir = None +resource_stream = None +set_extraction_path = None +resource_isdir = None +resource_string = None +iter_entry_points = None +resource_listdir = None +resource_filename = None +resource_exists = None +_distribution_finders = None +_namespace_handlers = None +_namespace_packages = None + + +class PEP440Warning(RuntimeWarning): + """ + Used when there is an issue with a version or specifier not complying with + PEP 440. + """ + + +def parse_version(v): + try: + return packaging.version.Version(v) + except packaging.version.InvalidVersion: + return packaging.version.LegacyVersion(v) + + +_state_vars = {} + + +def _declare_state(vartype, **kw): + globals().update(kw) + _state_vars.update(dict.fromkeys(kw, vartype)) + + +def __getstate__(): + state = {} + g = globals() + for k, v in _state_vars.items(): + state[k] = g['_sget_' + v](g[k]) + return state + + +def __setstate__(state): + g = globals() + for k, v in state.items(): + g['_sset_' + _state_vars[k]](k, g[k], v) + return state + + +def _sget_dict(val): + return val.copy() + + +def _sset_dict(key, ob, state): + ob.clear() + ob.update(state) + + +def _sget_object(val): + return val.__getstate__() + + +def _sset_object(key, ob, state): + ob.__setstate__(state) + + +_sget_none = _sset_none = lambda *args: None + + +def get_supported_platform(): + """Return this platform's maximum compatible version. + + distutils.util.get_platform() normally reports the minimum version + of Mac OS X that would be required to *use* extensions produced by + distutils. But what we want when checking compatibility is to know the + version of Mac OS X that we are *running*. To allow usage of packages that + explicitly require a newer version of Mac OS X, we must also know the + current version of the OS. + + If this condition occurs for any other platform with a version in its + platform strings, this function should be extended accordingly. + """ + plat = get_build_platform() + m = macosVersionString.match(plat) + if m is not None and sys.platform == "darwin": + try: + plat = 'macosx-%s-%s' % ('.'.join(_macosx_vers()[:2]), m.group(3)) + except ValueError: + # not Mac OS X + pass + return plat + + +__all__ = [ + # Basic resource access and distribution/entry point discovery + 'require', 'run_script', 'get_provider', 'get_distribution', + 'load_entry_point', 'get_entry_map', 'get_entry_info', + 'iter_entry_points', + 'resource_string', 'resource_stream', 'resource_filename', + 'resource_listdir', 'resource_exists', 'resource_isdir', + + # Environmental control + 'declare_namespace', 'working_set', 'add_activation_listener', + 'find_distributions', 'set_extraction_path', 'cleanup_resources', + 'get_default_cache', + + # Primary implementation classes + 'Environment', 'WorkingSet', 'ResourceManager', + 'Distribution', 'Requirement', 'EntryPoint', + + # Exceptions + 'ResolutionError', 'VersionConflict', 'DistributionNotFound', + 'UnknownExtra', 'ExtractionError', + + # Warnings + 'PEP440Warning', + + # Parsing functions and string utilities + 'parse_requirements', 'parse_version', 'safe_name', 'safe_version', + 'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections', + 'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker', + + # filesystem utilities + 'ensure_directory', 'normalize_path', + + # Distribution "precedence" constants + 'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST', + + # "Provider" interfaces, implementations, and registration/lookup APIs + 'IMetadataProvider', 'IResourceProvider', 'FileMetadata', + 'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider', + 'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider', + 'register_finder', 'register_namespace_handler', 'register_loader_type', + 'fixup_namespace_packages', 'get_importer', + + # Warnings + 'PkgResourcesDeprecationWarning', + + # Deprecated/backward compatibility only + 'run_main', 'AvailableDistributions', +] + + +class ResolutionError(Exception): + """Abstract base for dependency resolution errors""" + + def __repr__(self): + return self.__class__.__name__ + repr(self.args) + + +class VersionConflict(ResolutionError): + """ + An already-installed version conflicts with the requested version. + + Should be initialized with the installed Distribution and the requested + Requirement. + """ + + _template = "{self.dist} is installed but {self.req} is required" + + @property + def dist(self): + return self.args[0] + + @property + def req(self): + return self.args[1] + + def report(self): + return self._template.format(**locals()) + + def with_context(self, required_by): + """ + If required_by is non-empty, return a version of self that is a + ContextualVersionConflict. + """ + if not required_by: + return self + args = self.args + (required_by,) + return ContextualVersionConflict(*args) + + +class ContextualVersionConflict(VersionConflict): + """ + A VersionConflict that accepts a third parameter, the set of the + requirements that required the installed Distribution. + """ + + _template = VersionConflict._template + ' by {self.required_by}' + + @property + def required_by(self): + return self.args[2] + + +class DistributionNotFound(ResolutionError): + """A requested distribution was not found""" + + _template = ("The '{self.req}' distribution was not found " + "and is required by {self.requirers_str}") + + @property + def req(self): + return self.args[0] + + @property + def requirers(self): + return self.args[1] + + @property + def requirers_str(self): + if not self.requirers: + return 'the application' + return ', '.join(self.requirers) + + def report(self): + return self._template.format(**locals()) + + def __str__(self): + return self.report() + + +class UnknownExtra(ResolutionError): + """Distribution doesn't have an "extra feature" of the given name""" + + +_provider_factories = {} + +PY_MAJOR = sys.version[:3] +EGG_DIST = 3 +BINARY_DIST = 2 +SOURCE_DIST = 1 +CHECKOUT_DIST = 0 +DEVELOP_DIST = -1 + + +def register_loader_type(loader_type, provider_factory): + """Register `provider_factory` to make providers for `loader_type` + + `loader_type` is the type or class of a PEP 302 ``module.__loader__``, + and `provider_factory` is a function that, passed a *module* object, + returns an ``IResourceProvider`` for that module. + """ + _provider_factories[loader_type] = provider_factory + + +def get_provider(moduleOrReq): + """Return an IResourceProvider for the named module or requirement""" + if isinstance(moduleOrReq, Requirement): + return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0] + try: + module = sys.modules[moduleOrReq] + except KeyError: + __import__(moduleOrReq) + module = sys.modules[moduleOrReq] + loader = getattr(module, '__loader__', None) + return _find_adapter(_provider_factories, loader)(module) + + +def _macosx_vers(_cache=[]): + if not _cache: + version = platform.mac_ver()[0] + # fallback for MacPorts + if version == '': + plist = '/System/Library/CoreServices/SystemVersion.plist' + if os.path.exists(plist): + if hasattr(plistlib, 'readPlist'): + plist_content = plistlib.readPlist(plist) + if 'ProductVersion' in plist_content: + version = plist_content['ProductVersion'] + + _cache.append(version.split('.')) + return _cache[0] + + +def _macosx_arch(machine): + return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine) + + +def get_build_platform(): + """Return this platform's string for platform-specific distributions + + XXX Currently this is the same as ``distutils.util.get_platform()``, but it + needs some hacks for Linux and Mac OS X. + """ + from sysconfig import get_platform + + plat = get_platform() + if sys.platform == "darwin" and not plat.startswith('macosx-'): + try: + version = _macosx_vers() + machine = os.uname()[4].replace(" ", "_") + return "macosx-%d.%d-%s" % ( + int(version[0]), int(version[1]), + _macosx_arch(machine), + ) + except ValueError: + # if someone is running a non-Mac darwin system, this will fall + # through to the default implementation + pass + return plat + + +macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)") +darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)") +# XXX backward compat +get_platform = get_build_platform + + +def compatible_platforms(provided, required): + """Can code for the `provided` platform run on the `required` platform? + + Returns true if either platform is ``None``, or the platforms are equal. + + XXX Needs compatibility checks for Linux and other unixy OSes. + """ + if provided is None or required is None or provided == required: + # easy case + return True + + # Mac OS X special cases + reqMac = macosVersionString.match(required) + if reqMac: + provMac = macosVersionString.match(provided) + + # is this a Mac package? + if not provMac: + # this is backwards compatibility for packages built before + # setuptools 0.6. All packages built after this point will + # use the new macosx designation. + provDarwin = darwinVersionString.match(provided) + if provDarwin: + dversion = int(provDarwin.group(1)) + macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2)) + if dversion == 7 and macosversion >= "10.3" or \ + dversion == 8 and macosversion >= "10.4": + return True + # egg isn't macosx or legacy darwin + return False + + # are they the same major version and machine type? + if provMac.group(1) != reqMac.group(1) or \ + provMac.group(3) != reqMac.group(3): + return False + + # is the required OS major update >= the provided one? + if int(provMac.group(2)) > int(reqMac.group(2)): + return False + + return True + + # XXX Linux and other platforms' special cases should go here + return False + + +def run_script(dist_spec, script_name): + """Locate distribution `dist_spec` and run its `script_name` script""" + ns = sys._getframe(1).f_globals + name = ns['__name__'] + ns.clear() + ns['__name__'] = name + require(dist_spec)[0].run_script(script_name, ns) + + +# backward compatibility +run_main = run_script + + +def get_distribution(dist): + """Return a current distribution object for a Requirement or string""" + if isinstance(dist, six.string_types): + dist = Requirement.parse(dist) + if isinstance(dist, Requirement): + dist = get_provider(dist) + if not isinstance(dist, Distribution): + raise TypeError("Expected string, Requirement, or Distribution", dist) + return dist + + +def load_entry_point(dist, group, name): + """Return `name` entry point of `group` for `dist` or raise ImportError""" + return get_distribution(dist).load_entry_point(group, name) + + +def get_entry_map(dist, group=None): + """Return the entry point map for `group`, or the full entry map""" + return get_distribution(dist).get_entry_map(group) + + +def get_entry_info(dist, group, name): + """Return the EntryPoint object for `group`+`name`, or ``None``""" + return get_distribution(dist).get_entry_info(group, name) + + +class IMetadataProvider: + def has_metadata(name): + """Does the package's distribution contain the named metadata?""" + + def get_metadata(name): + """The named metadata resource as a string""" + + def get_metadata_lines(name): + """Yield named metadata resource as list of non-blank non-comment lines + + Leading and trailing whitespace is stripped from each line, and lines + with ``#`` as the first non-blank character are omitted.""" + + def metadata_isdir(name): + """Is the named metadata a directory? (like ``os.path.isdir()``)""" + + def metadata_listdir(name): + """List of metadata names in the directory (like ``os.listdir()``)""" + + def run_script(script_name, namespace): + """Execute the named script in the supplied namespace dictionary""" + + +class IResourceProvider(IMetadataProvider): + """An object that provides access to package resources""" + + def get_resource_filename(manager, resource_name): + """Return a true filesystem path for `resource_name` + + `manager` must be an ``IResourceManager``""" + + def get_resource_stream(manager, resource_name): + """Return a readable file-like object for `resource_name` + + `manager` must be an ``IResourceManager``""" + + def get_resource_string(manager, resource_name): + """Return a string containing the contents of `resource_name` + + `manager` must be an ``IResourceManager``""" + + def has_resource(resource_name): + """Does the package contain the named resource?""" + + def resource_isdir(resource_name): + """Is the named resource a directory? (like ``os.path.isdir()``)""" + + def resource_listdir(resource_name): + """List of resource names in the directory (like ``os.listdir()``)""" + + +class WorkingSet: + """A collection of active distributions on sys.path (or a similar list)""" + + def __init__(self, entries=None): + """Create working set from list of path entries (default=sys.path)""" + self.entries = [] + self.entry_keys = {} + self.by_key = {} + self.callbacks = [] + + if entries is None: + entries = sys.path + + for entry in entries: + self.add_entry(entry) + + @classmethod + def _build_master(cls): + """ + Prepare the master working set. + """ + ws = cls() + try: + from __main__ import __requires__ + except ImportError: + # The main program does not list any requirements + return ws + + # ensure the requirements are met + try: + ws.require(__requires__) + except VersionConflict: + return cls._build_from_requirements(__requires__) + + return ws + + @classmethod + def _build_from_requirements(cls, req_spec): + """ + Build a working set from a requirement spec. Rewrites sys.path. + """ + # try it without defaults already on sys.path + # by starting with an empty path + ws = cls([]) + reqs = parse_requirements(req_spec) + dists = ws.resolve(reqs, Environment()) + for dist in dists: + ws.add(dist) + + # add any missing entries from sys.path + for entry in sys.path: + if entry not in ws.entries: + ws.add_entry(entry) + + # then copy back to sys.path + sys.path[:] = ws.entries + return ws + + def add_entry(self, entry): + """Add a path item to ``.entries``, finding any distributions on it + + ``find_distributions(entry, True)`` is used to find distributions + corresponding to the path entry, and they are added. `entry` is + always appended to ``.entries``, even if it is already present. + (This is because ``sys.path`` can contain the same value more than + once, and the ``.entries`` of the ``sys.path`` WorkingSet should always + equal ``sys.path``.) + """ + self.entry_keys.setdefault(entry, []) + self.entries.append(entry) + for dist in find_distributions(entry, True): + self.add(dist, entry, False) + + def __contains__(self, dist): + """True if `dist` is the active distribution for its project""" + return self.by_key.get(dist.key) == dist + + def find(self, req): + """Find a distribution matching requirement `req` + + If there is an active distribution for the requested project, this + returns it as long as it meets the version requirement specified by + `req`. But, if there is an active distribution for the project and it + does *not* meet the `req` requirement, ``VersionConflict`` is raised. + If there is no active distribution for the requested project, ``None`` + is returned. + """ + dist = self.by_key.get(req.key) + if dist is not None and dist not in req: + # XXX add more info + raise VersionConflict(dist, req) + return dist + + def iter_entry_points(self, group, name=None): + """Yield entry point objects from `group` matching `name` + + If `name` is None, yields all entry points in `group` from all + distributions in the working set, otherwise only ones matching + both `group` and `name` are yielded (in distribution order). + """ + return ( + entry + for dist in self + for entry in dist.get_entry_map(group).values() + if name is None or name == entry.name + ) + + def run_script(self, requires, script_name): + """Locate distribution for `requires` and run `script_name` script""" + ns = sys._getframe(1).f_globals + name = ns['__name__'] + ns.clear() + ns['__name__'] = name + self.require(requires)[0].run_script(script_name, ns) + + def __iter__(self): + """Yield distributions for non-duplicate projects in the working set + + The yield order is the order in which the items' path entries were + added to the working set. + """ + seen = {} + for item in self.entries: + if item not in self.entry_keys: + # workaround a cache issue + continue + + for key in self.entry_keys[item]: + if key not in seen: + seen[key] = 1 + yield self.by_key[key] + + def add(self, dist, entry=None, insert=True, replace=False): + """Add `dist` to working set, associated with `entry` + + If `entry` is unspecified, it defaults to the ``.location`` of `dist`. + On exit from this routine, `entry` is added to the end of the working + set's ``.entries`` (if it wasn't already present). + + `dist` is only added to the working set if it's for a project that + doesn't already have a distribution in the set, unless `replace=True`. + If it's added, any callbacks registered with the ``subscribe()`` method + will be called. + """ + if insert: + dist.insert_on(self.entries, entry, replace=replace) + + if entry is None: + entry = dist.location + keys = self.entry_keys.setdefault(entry, []) + keys2 = self.entry_keys.setdefault(dist.location, []) + if not replace and dist.key in self.by_key: + # ignore hidden distros + return + + self.by_key[dist.key] = dist + if dist.key not in keys: + keys.append(dist.key) + if dist.key not in keys2: + keys2.append(dist.key) + self._added_new(dist) + + def resolve(self, requirements, env=None, installer=None, + replace_conflicting=False, extras=None): + """List all distributions needed to (recursively) meet `requirements` + + `requirements` must be a sequence of ``Requirement`` objects. `env`, + if supplied, should be an ``Environment`` instance. If + not supplied, it defaults to all distributions available within any + entry or distribution in the working set. `installer`, if supplied, + will be invoked with each requirement that cannot be met by an + already-installed distribution; it should return a ``Distribution`` or + ``None``. + + Unless `replace_conflicting=True`, raises a VersionConflict exception + if + any requirements are found on the path that have the correct name but + the wrong version. Otherwise, if an `installer` is supplied it will be + invoked to obtain the correct version of the requirement and activate + it. + + `extras` is a list of the extras to be used with these requirements. + This is important because extra requirements may look like `my_req; + extra = "my_extra"`, which would otherwise be interpreted as a purely + optional requirement. Instead, we want to be able to assert that these + requirements are truly required. + """ + + # set up the stack + requirements = list(requirements)[::-1] + # set of processed requirements + processed = {} + # key -> dist + best = {} + to_activate = [] + + req_extras = _ReqExtras() + + # Mapping of requirement to set of distributions that required it; + # useful for reporting info about conflicts. + required_by = collections.defaultdict(set) + + while requirements: + # process dependencies breadth-first + req = requirements.pop(0) + if req in processed: + # Ignore cyclic or redundant dependencies + continue + + if not req_extras.markers_pass(req, extras): + continue + + dist = best.get(req.key) + if dist is None: + # Find the best distribution and add it to the map + dist = self.by_key.get(req.key) + if dist is None or (dist not in req and replace_conflicting): + ws = self + if env is None: + if dist is None: + env = Environment(self.entries) + else: + # Use an empty environment and workingset to avoid + # any further conflicts with the conflicting + # distribution + env = Environment([]) + ws = WorkingSet([]) + dist = best[req.key] = env.best_match( + req, ws, installer, + replace_conflicting=replace_conflicting + ) + if dist is None: + requirers = required_by.get(req, None) + raise DistributionNotFound(req, requirers) + to_activate.append(dist) + if dist not in req: + # Oops, the "best" so far conflicts with a dependency + dependent_req = required_by[req] + raise VersionConflict(dist, req).with_context(dependent_req) + + # push the new requirements onto the stack + new_requirements = dist.requires(req.extras)[::-1] + requirements.extend(new_requirements) + + # Register the new requirements needed by req + for new_requirement in new_requirements: + required_by[new_requirement].add(req.project_name) + req_extras[new_requirement] = req.extras + + processed[req] = True + + # return list of distros to activate + return to_activate + + def find_plugins( + self, plugin_env, full_env=None, installer=None, fallback=True): + """Find all activatable distributions in `plugin_env` + + Example usage:: + + distributions, errors = working_set.find_plugins( + Environment(plugin_dirlist) + ) + # add plugins+libs to sys.path + map(working_set.add, distributions) + # display errors + print('Could not load', errors) + + The `plugin_env` should be an ``Environment`` instance that contains + only distributions that are in the project's "plugin directory" or + directories. The `full_env`, if supplied, should be an ``Environment`` + contains all currently-available distributions. If `full_env` is not + supplied, one is created automatically from the ``WorkingSet`` this + method is called on, which will typically mean that every directory on + ``sys.path`` will be scanned for distributions. + + `installer` is a standard installer callback as used by the + ``resolve()`` method. The `fallback` flag indicates whether we should + attempt to resolve older versions of a plugin if the newest version + cannot be resolved. + + This method returns a 2-tuple: (`distributions`, `error_info`), where + `distributions` is a list of the distributions found in `plugin_env` + that were loadable, along with any other distributions that are needed + to resolve their dependencies. `error_info` is a dictionary mapping + unloadable plugin distributions to an exception instance describing the + error that occurred. Usually this will be a ``DistributionNotFound`` or + ``VersionConflict`` instance. + """ + + plugin_projects = list(plugin_env) + # scan project names in alphabetic order + plugin_projects.sort() + + error_info = {} + distributions = {} + + if full_env is None: + env = Environment(self.entries) + env += plugin_env + else: + env = full_env + plugin_env + + shadow_set = self.__class__([]) + # put all our entries in shadow_set + list(map(shadow_set.add, self)) + + for project_name in plugin_projects: + + for dist in plugin_env[project_name]: + + req = [dist.as_requirement()] + + try: + resolvees = shadow_set.resolve(req, env, installer) + + except ResolutionError as v: + # save error info + error_info[dist] = v + if fallback: + # try the next older version of project + continue + else: + # give up on this project, keep going + break + + else: + list(map(shadow_set.add, resolvees)) + distributions.update(dict.fromkeys(resolvees)) + + # success, no need to try any more versions of this project + break + + distributions = list(distributions) + distributions.sort() + + return distributions, error_info + + def require(self, *requirements): + """Ensure that distributions matching `requirements` are activated + + `requirements` must be a string or a (possibly-nested) sequence + thereof, specifying the distributions and versions required. The + return value is a sequence of the distributions that needed to be + activated to fulfill the requirements; all relevant distributions are + included, even if they were already activated in this working set. + """ + needed = self.resolve(parse_requirements(requirements)) + + for dist in needed: + self.add(dist) + + return needed + + def subscribe(self, callback, existing=True): + """Invoke `callback` for all distributions + + If `existing=True` (default), + call on all existing ones, as well. + """ + if callback in self.callbacks: + return + self.callbacks.append(callback) + if not existing: + return + for dist in self: + callback(dist) + + def _added_new(self, dist): + for callback in self.callbacks: + callback(dist) + + def __getstate__(self): + return ( + self.entries[:], self.entry_keys.copy(), self.by_key.copy(), + self.callbacks[:] + ) + + def __setstate__(self, e_k_b_c): + entries, keys, by_key, callbacks = e_k_b_c + self.entries = entries[:] + self.entry_keys = keys.copy() + self.by_key = by_key.copy() + self.callbacks = callbacks[:] + + +class _ReqExtras(dict): + """ + Map each requirement to the extras that demanded it. + """ + + def markers_pass(self, req, extras=None): + """ + Evaluate markers for req against each extra that + demanded it. + + Return False if the req has a marker and fails + evaluation. Otherwise, return True. + """ + extra_evals = ( + req.marker.evaluate({'extra': extra}) + for extra in self.get(req, ()) + (extras or (None,)) + ) + return not req.marker or any(extra_evals) + + +class Environment: + """Searchable snapshot of distributions on a search path""" + + def __init__( + self, search_path=None, platform=get_supported_platform(), + python=PY_MAJOR): + """Snapshot distributions available on a search path + + Any distributions found on `search_path` are added to the environment. + `search_path` should be a sequence of ``sys.path`` items. If not + supplied, ``sys.path`` is used. + + `platform` is an optional string specifying the name of the platform + that platform-specific distributions must be compatible with. If + unspecified, it defaults to the current platform. `python` is an + optional string naming the desired version of Python (e.g. ``'3.6'``); + it defaults to the current version. + + You may explicitly set `platform` (and/or `python`) to ``None`` if you + wish to map *all* distributions, not just those compatible with the + running platform or Python version. + """ + self._distmap = {} + self.platform = platform + self.python = python + self.scan(search_path) + + def can_add(self, dist): + """Is distribution `dist` acceptable for this environment? + + The distribution must match the platform and python version + requirements specified when this environment was created, or False + is returned. + """ + py_compat = ( + self.python is None + or dist.py_version is None + or dist.py_version == self.python + ) + return py_compat and compatible_platforms(dist.platform, self.platform) + + def remove(self, dist): + """Remove `dist` from the environment""" + self._distmap[dist.key].remove(dist) + + def scan(self, search_path=None): + """Scan `search_path` for distributions usable in this environment + + Any distributions found are added to the environment. + `search_path` should be a sequence of ``sys.path`` items. If not + supplied, ``sys.path`` is used. Only distributions conforming to + the platform/python version defined at initialization are added. + """ + if search_path is None: + search_path = sys.path + + for item in search_path: + for dist in find_distributions(item): + self.add(dist) + + def __getitem__(self, project_name): + """Return a newest-to-oldest list of distributions for `project_name` + + Uses case-insensitive `project_name` comparison, assuming all the + project's distributions use their project's name converted to all + lowercase as their key. + + """ + distribution_key = project_name.lower() + return self._distmap.get(distribution_key, []) + + def add(self, dist): + """Add `dist` if we ``can_add()`` it and it has not already been added + """ + if self.can_add(dist) and dist.has_version(): + dists = self._distmap.setdefault(dist.key, []) + if dist not in dists: + dists.append(dist) + dists.sort(key=operator.attrgetter('hashcmp'), reverse=True) + + def best_match( + self, req, working_set, installer=None, replace_conflicting=False): + """Find distribution best matching `req` and usable on `working_set` + + This calls the ``find(req)`` method of the `working_set` to see if a + suitable distribution is already active. (This may raise + ``VersionConflict`` if an unsuitable version of the project is already + active in the specified `working_set`.) If a suitable distribution + isn't active, this method returns the newest distribution in the + environment that meets the ``Requirement`` in `req`. If no suitable + distribution is found, and `installer` is supplied, then the result of + calling the environment's ``obtain(req, installer)`` method will be + returned. + """ + try: + dist = working_set.find(req) + except VersionConflict: + if not replace_conflicting: + raise + dist = None + if dist is not None: + return dist + for dist in self[req.key]: + if dist in req: + return dist + # try to download/install + return self.obtain(req, installer) + + def obtain(self, requirement, installer=None): + """Obtain a distribution matching `requirement` (e.g. via download) + + Obtain a distro that matches requirement (e.g. via download). In the + base ``Environment`` class, this routine just returns + ``installer(requirement)``, unless `installer` is None, in which case + None is returned instead. This method is a hook that allows subclasses + to attempt other ways of obtaining a distribution before falling back + to the `installer` argument.""" + if installer is not None: + return installer(requirement) + + def __iter__(self): + """Yield the unique project names of the available distributions""" + for key in self._distmap.keys(): + if self[key]: + yield key + + def __iadd__(self, other): + """In-place addition of a distribution or environment""" + if isinstance(other, Distribution): + self.add(other) + elif isinstance(other, Environment): + for project in other: + for dist in other[project]: + self.add(dist) + else: + raise TypeError("Can't add %r to environment" % (other,)) + return self + + def __add__(self, other): + """Add an environment or distribution to an environment""" + new = self.__class__([], platform=None, python=None) + for env in self, other: + new += env + return new + + +# XXX backward compatibility +AvailableDistributions = Environment + + +class ExtractionError(RuntimeError): + """An error occurred extracting a resource + + The following attributes are available from instances of this exception: + + manager + The resource manager that raised this exception + + cache_path + The base directory for resource extraction + + original_error + The exception instance that caused extraction to fail + """ + + +class ResourceManager: + """Manage resource extraction and packages""" + extraction_path = None + + def __init__(self): + self.cached_files = {} + + def resource_exists(self, package_or_requirement, resource_name): + """Does the named resource exist?""" + return get_provider(package_or_requirement).has_resource(resource_name) + + def resource_isdir(self, package_or_requirement, resource_name): + """Is the named resource an existing directory?""" + return get_provider(package_or_requirement).resource_isdir( + resource_name + ) + + def resource_filename(self, package_or_requirement, resource_name): + """Return a true filesystem path for specified resource""" + return get_provider(package_or_requirement).get_resource_filename( + self, resource_name + ) + + def resource_stream(self, package_or_requirement, resource_name): + """Return a readable file-like object for specified resource""" + return get_provider(package_or_requirement).get_resource_stream( + self, resource_name + ) + + def resource_string(self, package_or_requirement, resource_name): + """Return specified resource as a string""" + return get_provider(package_or_requirement).get_resource_string( + self, resource_name + ) + + def resource_listdir(self, package_or_requirement, resource_name): + """List the contents of the named resource directory""" + return get_provider(package_or_requirement).resource_listdir( + resource_name + ) + + def extraction_error(self): + """Give an error message for problems extracting file(s)""" + + old_exc = sys.exc_info()[1] + cache_path = self.extraction_path or get_default_cache() + + tmpl = textwrap.dedent(""" + Can't extract file(s) to egg cache + + The following error occurred while trying to extract file(s) + to the Python egg cache: + + {old_exc} + + The Python egg cache directory is currently set to: + + {cache_path} + + Perhaps your account does not have write access to this directory? + You can change the cache directory by setting the PYTHON_EGG_CACHE + environment variable to point to an accessible directory. + """).lstrip() + err = ExtractionError(tmpl.format(**locals())) + err.manager = self + err.cache_path = cache_path + err.original_error = old_exc + raise err + + def get_cache_path(self, archive_name, names=()): + """Return absolute location in cache for `archive_name` and `names` + + The parent directory of the resulting path will be created if it does + not already exist. `archive_name` should be the base filename of the + enclosing egg (which may not be the name of the enclosing zipfile!), + including its ".egg" extension. `names`, if provided, should be a + sequence of path name parts "under" the egg's extraction location. + + This method should only be called by resource providers that need to + obtain an extraction location, and only for names they intend to + extract, as it tracks the generated names for possible cleanup later. + """ + extract_path = self.extraction_path or get_default_cache() + target_path = os.path.join(extract_path, archive_name + '-tmp', *names) + try: + _bypass_ensure_directory(target_path) + except Exception: + self.extraction_error() + + self._warn_unsafe_extraction_path(extract_path) + + self.cached_files[target_path] = 1 + return target_path + + @staticmethod + def _warn_unsafe_extraction_path(path): + """ + If the default extraction path is overridden and set to an insecure + location, such as /tmp, it opens up an opportunity for an attacker to + replace an extracted file with an unauthorized payload. Warn the user + if a known insecure location is used. + + See Distribute #375 for more details. + """ + if os.name == 'nt' and not path.startswith(os.environ['windir']): + # On Windows, permissions are generally restrictive by default + # and temp directories are not writable by other users, so + # bypass the warning. + return + mode = os.stat(path).st_mode + if mode & stat.S_IWOTH or mode & stat.S_IWGRP: + msg = ( + "%s is writable by group/others and vulnerable to attack " + "when " + "used with get_resource_filename. Consider a more secure " + "location (set with .set_extraction_path or the " + "PYTHON_EGG_CACHE environment variable)." % path + ) + warnings.warn(msg, UserWarning) + + def postprocess(self, tempname, filename): + """Perform any platform-specific postprocessing of `tempname` + + This is where Mac header rewrites should be done; other platforms don't + have anything special they should do. + + Resource providers should call this method ONLY after successfully + extracting a compressed resource. They must NOT call it on resources + that are already in the filesystem. + + `tempname` is the current (temporary) name of the file, and `filename` + is the name it will be renamed to by the caller after this routine + returns. + """ + + if os.name == 'posix': + # Make the resource executable + mode = ((os.stat(tempname).st_mode) | 0o555) & 0o7777 + os.chmod(tempname, mode) + + def set_extraction_path(self, path): + """Set the base path where resources will be extracted to, if needed. + + If you do not call this routine before any extractions take place, the + path defaults to the return value of ``get_default_cache()``. (Which + is based on the ``PYTHON_EGG_CACHE`` environment variable, with various + platform-specific fallbacks. See that routine's documentation for more + details.) + + Resources are extracted to subdirectories of this path based upon + information given by the ``IResourceProvider``. You may set this to a + temporary directory, but then you must call ``cleanup_resources()`` to + delete the extracted files when done. There is no guarantee that + ``cleanup_resources()`` will be able to remove all extracted files. + + (Note: you may not change the extraction path for a given resource + manager once resources have been extracted, unless you first call + ``cleanup_resources()``.) + """ + if self.cached_files: + raise ValueError( + "Can't change extraction path, files already extracted" + ) + + self.extraction_path = path + + def cleanup_resources(self, force=False): + """ + Delete all extracted resource files and directories, returning a list + of the file and directory names that could not be successfully removed. + This function does not have any concurrency protection, so it should + generally only be called when the extraction path is a temporary + directory exclusive to a single process. This method is not + automatically called; you must call it explicitly or register it as an + ``atexit`` function if you wish to ensure cleanup of a temporary + directory used for extractions. + """ + # XXX + + +def get_default_cache(): + """ + Return the ``PYTHON_EGG_CACHE`` environment variable + or a platform-relevant user cache dir for an app + named "Python-Eggs". + """ + return ( + os.environ.get('PYTHON_EGG_CACHE') + or appdirs.user_cache_dir(appname='Python-Eggs') + ) + + +def safe_name(name): + """Convert an arbitrary string to a standard distribution name + + Any runs of non-alphanumeric/. characters are replaced with a single '-'. + """ + return re.sub('[^A-Za-z0-9.]+', '-', name) + + +def safe_version(version): + """ + Convert an arbitrary string to a standard version string + """ + try: + # normalize the version + return str(packaging.version.Version(version)) + except packaging.version.InvalidVersion: + version = version.replace(' ', '.') + return re.sub('[^A-Za-z0-9.]+', '-', version) + + +def safe_extra(extra): + """Convert an arbitrary string to a standard 'extra' name + + Any runs of non-alphanumeric characters are replaced with a single '_', + and the result is always lowercased. + """ + return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower() + + +def to_filename(name): + """Convert a project or version name to its filename-escaped form + + Any '-' characters are currently replaced with '_'. + """ + return name.replace('-', '_') + + +def invalid_marker(text): + """ + Validate text as a PEP 508 environment marker; return an exception + if invalid or False otherwise. + """ + try: + evaluate_marker(text) + except SyntaxError as e: + e.filename = None + e.lineno = None + return e + return False + + +def evaluate_marker(text, extra=None): + """ + Evaluate a PEP 508 environment marker. + Return a boolean indicating the marker result in this environment. + Raise SyntaxError if marker is invalid. + + This implementation uses the 'pyparsing' module. + """ + try: + marker = packaging.markers.Marker(text) + return marker.evaluate() + except packaging.markers.InvalidMarker as e: + raise SyntaxError(e) + + +class NullProvider: + """Try to implement resources and metadata for arbitrary PEP 302 loaders""" + + egg_name = None + egg_info = None + loader = None + + def __init__(self, module): + self.loader = getattr(module, '__loader__', None) + self.module_path = os.path.dirname(getattr(module, '__file__', '')) + + def get_resource_filename(self, manager, resource_name): + return self._fn(self.module_path, resource_name) + + def get_resource_stream(self, manager, resource_name): + return io.BytesIO(self.get_resource_string(manager, resource_name)) + + def get_resource_string(self, manager, resource_name): + return self._get(self._fn(self.module_path, resource_name)) + + def has_resource(self, resource_name): + return self._has(self._fn(self.module_path, resource_name)) + + def has_metadata(self, name): + return self.egg_info and self._has(self._fn(self.egg_info, name)) + + def get_metadata(self, name): + if not self.egg_info: + return "" + value = self._get(self._fn(self.egg_info, name)) + return value.decode('utf-8') if six.PY3 else value + + def get_metadata_lines(self, name): + return yield_lines(self.get_metadata(name)) + + def resource_isdir(self, resource_name): + return self._isdir(self._fn(self.module_path, resource_name)) + + def metadata_isdir(self, name): + return self.egg_info and self._isdir(self._fn(self.egg_info, name)) + + def resource_listdir(self, resource_name): + return self._listdir(self._fn(self.module_path, resource_name)) + + def metadata_listdir(self, name): + if self.egg_info: + return self._listdir(self._fn(self.egg_info, name)) + return [] + + def run_script(self, script_name, namespace): + script = 'scripts/' + script_name + if not self.has_metadata(script): + raise ResolutionError( + "Script {script!r} not found in metadata at {self.egg_info!r}" + .format(**locals()), + ) + script_text = self.get_metadata(script).replace('\r\n', '\n') + script_text = script_text.replace('\r', '\n') + script_filename = self._fn(self.egg_info, script) + namespace['__file__'] = script_filename + if os.path.exists(script_filename): + source = open(script_filename).read() + code = compile(source, script_filename, 'exec') + exec(code, namespace, namespace) + else: + from linecache import cache + cache[script_filename] = ( + len(script_text), 0, script_text.split('\n'), script_filename + ) + script_code = compile(script_text, script_filename, 'exec') + exec(script_code, namespace, namespace) + + def _has(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _isdir(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _listdir(self, path): + raise NotImplementedError( + "Can't perform this operation for unregistered loader type" + ) + + def _fn(self, base, resource_name): + if resource_name: + return os.path.join(base, *resource_name.split('/')) + return base + + def _get(self, path): + if hasattr(self.loader, 'get_data'): + return self.loader.get_data(path) + raise NotImplementedError( + "Can't perform this operation for loaders without 'get_data()'" + ) + + +register_loader_type(object, NullProvider) + + +class EggProvider(NullProvider): + """Provider based on a virtual filesystem""" + + def __init__(self, module): + NullProvider.__init__(self, module) + self._setup_prefix() + + def _setup_prefix(self): + # we assume here that our metadata may be nested inside a "basket" + # of multiple eggs; that's why we use module_path instead of .archive + path = self.module_path + old = None + while path != old: + if _is_egg_path(path): + self.egg_name = os.path.basename(path) + self.egg_info = os.path.join(path, 'EGG-INFO') + self.egg_root = path + break + old = path + path, base = os.path.split(path) + + +class DefaultProvider(EggProvider): + """Provides access to package resources in the filesystem""" + + def _has(self, path): + return os.path.exists(path) + + def _isdir(self, path): + return os.path.isdir(path) + + def _listdir(self, path): + return os.listdir(path) + + def get_resource_stream(self, manager, resource_name): + return open(self._fn(self.module_path, resource_name), 'rb') + + def _get(self, path): + with open(path, 'rb') as stream: + return stream.read() + + @classmethod + def _register(cls): + loader_names = 'SourceFileLoader', 'SourcelessFileLoader', + for name in loader_names: + loader_cls = getattr(importlib_machinery, name, type(None)) + register_loader_type(loader_cls, cls) + + +DefaultProvider._register() + + +class EmptyProvider(NullProvider): + """Provider that returns nothing for all requests""" + + module_path = None + + _isdir = _has = lambda self, path: False + + def _get(self, path): + return '' + + def _listdir(self, path): + return [] + + def __init__(self): + pass + + +empty_provider = EmptyProvider() + + +class ZipManifests(dict): + """ + zip manifest builder + """ + + @classmethod + def build(cls, path): + """ + Build a dictionary similar to the zipimport directory + caches, except instead of tuples, store ZipInfo objects. + + Use a platform-specific path separator (os.sep) for the path keys + for compatibility with pypy on Windows. + """ + with zipfile.ZipFile(path) as zfile: + items = ( + ( + name.replace('/', os.sep), + zfile.getinfo(name), + ) + for name in zfile.namelist() + ) + return dict(items) + + load = build + + +class MemoizedZipManifests(ZipManifests): + """ + Memoized zipfile manifests. + """ + manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime') + + def load(self, path): + """ + Load a manifest at path or return a suitable manifest already loaded. + """ + path = os.path.normpath(path) + mtime = os.stat(path).st_mtime + + if path not in self or self[path].mtime != mtime: + manifest = self.build(path) + self[path] = self.manifest_mod(manifest, mtime) + + return self[path].manifest + + +class ZipProvider(EggProvider): + """Resource support for zips and eggs""" + + eagers = None + _zip_manifests = MemoizedZipManifests() + + def __init__(self, module): + EggProvider.__init__(self, module) + self.zip_pre = self.loader.archive + os.sep + + def _zipinfo_name(self, fspath): + # Convert a virtual filename (full path to file) into a zipfile subpath + # usable with the zipimport directory cache for our target archive + fspath = fspath.rstrip(os.sep) + if fspath == self.loader.archive: + return '' + if fspath.startswith(self.zip_pre): + return fspath[len(self.zip_pre):] + raise AssertionError( + "%s is not a subpath of %s" % (fspath, self.zip_pre) + ) + + def _parts(self, zip_path): + # Convert a zipfile subpath into an egg-relative path part list. + # pseudo-fs path + fspath = self.zip_pre + zip_path + if fspath.startswith(self.egg_root + os.sep): + return fspath[len(self.egg_root) + 1:].split(os.sep) + raise AssertionError( + "%s is not a subpath of %s" % (fspath, self.egg_root) + ) + + @property + def zipinfo(self): + return self._zip_manifests.load(self.loader.archive) + + def get_resource_filename(self, manager, resource_name): + if not self.egg_name: + raise NotImplementedError( + "resource_filename() only supported for .egg, not .zip" + ) + # no need to lock for extraction, since we use temp names + zip_path = self._resource_to_zip(resource_name) + eagers = self._get_eager_resources() + if '/'.join(self._parts(zip_path)) in eagers: + for name in eagers: + self._extract_resource(manager, self._eager_to_zip(name)) + return self._extract_resource(manager, zip_path) + + @staticmethod + def _get_date_and_size(zip_stat): + size = zip_stat.file_size + # ymdhms+wday, yday, dst + date_time = zip_stat.date_time + (0, 0, -1) + # 1980 offset already done + timestamp = time.mktime(date_time) + return timestamp, size + + def _extract_resource(self, manager, zip_path): + + if zip_path in self._index(): + for name in self._index()[zip_path]: + last = self._extract_resource( + manager, os.path.join(zip_path, name) + ) + # return the extracted directory name + return os.path.dirname(last) + + timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) + + if not WRITE_SUPPORT: + raise IOError('"os.rename" and "os.unlink" are not supported ' + 'on this platform') + try: + + real_path = manager.get_cache_path( + self.egg_name, self._parts(zip_path) + ) + + if self._is_current(real_path, zip_path): + return real_path + + outf, tmpnam = _mkstemp( + ".$extract", + dir=os.path.dirname(real_path), + ) + os.write(outf, self.loader.get_data(zip_path)) + os.close(outf) + utime(tmpnam, (timestamp, timestamp)) + manager.postprocess(tmpnam, real_path) + + try: + rename(tmpnam, real_path) + + except os.error: + if os.path.isfile(real_path): + if self._is_current(real_path, zip_path): + # the file became current since it was checked above, + # so proceed. + return real_path + # Windows, del old file and retry + elif os.name == 'nt': + unlink(real_path) + rename(tmpnam, real_path) + return real_path + raise + + except os.error: + # report a user-friendly error + manager.extraction_error() + + return real_path + + def _is_current(self, file_path, zip_path): + """ + Return True if the file_path is current for this zip_path + """ + timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) + if not os.path.isfile(file_path): + return False + stat = os.stat(file_path) + if stat.st_size != size or stat.st_mtime != timestamp: + return False + # check that the contents match + zip_contents = self.loader.get_data(zip_path) + with open(file_path, 'rb') as f: + file_contents = f.read() + return zip_contents == file_contents + + def _get_eager_resources(self): + if self.eagers is None: + eagers = [] + for name in ('native_libs.txt', 'eager_resources.txt'): + if self.has_metadata(name): + eagers.extend(self.get_metadata_lines(name)) + self.eagers = eagers + return self.eagers + + def _index(self): + try: + return self._dirindex + except AttributeError: + ind = {} + for path in self.zipinfo: + parts = path.split(os.sep) + while parts: + parent = os.sep.join(parts[:-1]) + if parent in ind: + ind[parent].append(parts[-1]) + break + else: + ind[parent] = [parts.pop()] + self._dirindex = ind + return ind + + def _has(self, fspath): + zip_path = self._zipinfo_name(fspath) + return zip_path in self.zipinfo or zip_path in self._index() + + def _isdir(self, fspath): + return self._zipinfo_name(fspath) in self._index() + + def _listdir(self, fspath): + return list(self._index().get(self._zipinfo_name(fspath), ())) + + def _eager_to_zip(self, resource_name): + return self._zipinfo_name(self._fn(self.egg_root, resource_name)) + + def _resource_to_zip(self, resource_name): + return self._zipinfo_name(self._fn(self.module_path, resource_name)) + + +register_loader_type(zipimport.zipimporter, ZipProvider) + + +class FileMetadata(EmptyProvider): + """Metadata handler for standalone PKG-INFO files + + Usage:: + + metadata = FileMetadata("/path/to/PKG-INFO") + + This provider rejects all data and metadata requests except for PKG-INFO, + which is treated as existing, and will be the contents of the file at + the provided location. + """ + + def __init__(self, path): + self.path = path + + def has_metadata(self, name): + return name == 'PKG-INFO' and os.path.isfile(self.path) + + def get_metadata(self, name): + if name != 'PKG-INFO': + raise KeyError("No metadata except PKG-INFO is available") + + with io.open(self.path, encoding='utf-8', errors="replace") as f: + metadata = f.read() + self._warn_on_replacement(metadata) + return metadata + + def _warn_on_replacement(self, metadata): + # Python 2.7 compat for: replacement_char = '�' + replacement_char = b'\xef\xbf\xbd'.decode('utf-8') + if replacement_char in metadata: + tmpl = "{self.path} could not be properly decoded in UTF-8" + msg = tmpl.format(**locals()) + warnings.warn(msg) + + def get_metadata_lines(self, name): + return yield_lines(self.get_metadata(name)) + + +class PathMetadata(DefaultProvider): + """Metadata provider for egg directories + + Usage:: + + # Development eggs: + + egg_info = "/path/to/PackageName.egg-info" + base_dir = os.path.dirname(egg_info) + metadata = PathMetadata(base_dir, egg_info) + dist_name = os.path.splitext(os.path.basename(egg_info))[0] + dist = Distribution(basedir, project_name=dist_name, metadata=metadata) + + # Unpacked egg directories: + + egg_path = "/path/to/PackageName-ver-pyver-etc.egg" + metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO')) + dist = Distribution.from_filename(egg_path, metadata=metadata) + """ + + def __init__(self, path, egg_info): + self.module_path = path + self.egg_info = egg_info + + +class EggMetadata(ZipProvider): + """Metadata provider for .egg files""" + + def __init__(self, importer): + """Create a metadata provider from a zipimporter""" + + self.zip_pre = importer.archive + os.sep + self.loader = importer + if importer.prefix: + self.module_path = os.path.join(importer.archive, importer.prefix) + else: + self.module_path = importer.archive + self._setup_prefix() + + +_declare_state('dict', _distribution_finders={}) + + +def register_finder(importer_type, distribution_finder): + """Register `distribution_finder` to find distributions in sys.path items + + `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item + handler), and `distribution_finder` is a callable that, passed a path + item and the importer instance, yields ``Distribution`` instances found on + that path item. See ``pkg_resources.find_on_path`` for an example.""" + _distribution_finders[importer_type] = distribution_finder + + +def find_distributions(path_item, only=False): + """Yield distributions accessible via `path_item`""" + importer = get_importer(path_item) + finder = _find_adapter(_distribution_finders, importer) + return finder(importer, path_item, only) + + +def find_eggs_in_zip(importer, path_item, only=False): + """ + Find eggs in zip files; possibly multiple nested eggs. + """ + if importer.archive.endswith('.whl'): + # wheels are not supported with this finder + # they don't have PKG-INFO metadata, and won't ever contain eggs + return + metadata = EggMetadata(importer) + if metadata.has_metadata('PKG-INFO'): + yield Distribution.from_filename(path_item, metadata=metadata) + if only: + # don't yield nested distros + return + for subitem in metadata.resource_listdir('/'): + if _is_egg_path(subitem): + subpath = os.path.join(path_item, subitem) + dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) + for dist in dists: + yield dist + elif subitem.lower().endswith('.dist-info'): + subpath = os.path.join(path_item, subitem) + submeta = EggMetadata(zipimport.zipimporter(subpath)) + submeta.egg_info = subpath + yield Distribution.from_location(path_item, subitem, submeta) + + +register_finder(zipimport.zipimporter, find_eggs_in_zip) + + +def find_nothing(importer, path_item, only=False): + return () + + +register_finder(object, find_nothing) + + +def _by_version_descending(names): + """ + Given a list of filenames, return them in descending order + by version number. + + >>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg' + >>> _by_version_descending(names) + ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'foo', 'bar'] + >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg' + >>> _by_version_descending(names) + ['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg'] + >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg' + >>> _by_version_descending(names) + ['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg'] + """ + def _by_version(name): + """ + Parse each component of the filename + """ + name, ext = os.path.splitext(name) + parts = itertools.chain(name.split('-'), [ext]) + return [packaging.version.parse(part) for part in parts] + + return sorted(names, key=_by_version, reverse=True) + + +def find_on_path(importer, path_item, only=False): + """Yield distributions accessible on a sys.path directory""" + path_item = _normalize_cached(path_item) + + if _is_unpacked_egg(path_item): + yield Distribution.from_filename( + path_item, metadata=PathMetadata( + path_item, os.path.join(path_item, 'EGG-INFO') + ) + ) + return + + entries = safe_listdir(path_item) + + # for performance, before sorting by version, + # screen entries for only those that will yield + # distributions + filtered = ( + entry + for entry in entries + if dist_factory(path_item, entry, only) + ) + + # scan for .egg and .egg-info in directory + path_item_entries = _by_version_descending(filtered) + for entry in path_item_entries: + fullpath = os.path.join(path_item, entry) + factory = dist_factory(path_item, entry, only) + for dist in factory(fullpath): + yield dist + + +def dist_factory(path_item, entry, only): + """ + Return a dist_factory for a path_item and entry + """ + lower = entry.lower() + is_meta = any(map(lower.endswith, ('.egg-info', '.dist-info'))) + return ( + distributions_from_metadata + if is_meta else + find_distributions + if not only and _is_egg_path(entry) else + resolve_egg_link + if not only and lower.endswith('.egg-link') else + NoDists() + ) + + +class NoDists: + """ + >>> bool(NoDists()) + False + + >>> list(NoDists()('anything')) + [] + """ + def __bool__(self): + return False + if six.PY2: + __nonzero__ = __bool__ + + def __call__(self, fullpath): + return iter(()) + + +def safe_listdir(path): + """ + Attempt to list contents of path, but suppress some exceptions. + """ + try: + return os.listdir(path) + except (PermissionError, NotADirectoryError): + pass + except OSError as e: + # Ignore the directory if does not exist, not a directory or + # permission denied + ignorable = ( + e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT) + # Python 2 on Windows needs to be handled this way :( + or getattr(e, "winerror", None) == 267 + ) + if not ignorable: + raise + return () + + +def distributions_from_metadata(path): + root = os.path.dirname(path) + if os.path.isdir(path): + if len(os.listdir(path)) == 0: + # empty metadata dir; skip + return + metadata = PathMetadata(root, path) + else: + metadata = FileMetadata(path) + entry = os.path.basename(path) + yield Distribution.from_location( + root, entry, metadata, precedence=DEVELOP_DIST, + ) + + +def non_empty_lines(path): + """ + Yield non-empty lines from file at path + """ + with open(path) as f: + for line in f: + line = line.strip() + if line: + yield line + + +def resolve_egg_link(path): + """ + Given a path to an .egg-link, resolve distributions + present in the referenced path. + """ + referenced_paths = non_empty_lines(path) + resolved_paths = ( + os.path.join(os.path.dirname(path), ref) + for ref in referenced_paths + ) + dist_groups = map(find_distributions, resolved_paths) + return next(dist_groups, ()) + + +register_finder(pkgutil.ImpImporter, find_on_path) + +if hasattr(importlib_machinery, 'FileFinder'): + register_finder(importlib_machinery.FileFinder, find_on_path) + +_declare_state('dict', _namespace_handlers={}) +_declare_state('dict', _namespace_packages={}) + + +def register_namespace_handler(importer_type, namespace_handler): + """Register `namespace_handler` to declare namespace packages + + `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item + handler), and `namespace_handler` is a callable like this:: + + def namespace_handler(importer, path_entry, moduleName, module): + # return a path_entry to use for child packages + + Namespace handlers are only called if the importer object has already + agreed that it can handle the relevant path item, and they should only + return a subpath if the module __path__ does not already contain an + equivalent subpath. For an example namespace handler, see + ``pkg_resources.file_ns_handler``. + """ + _namespace_handlers[importer_type] = namespace_handler + + +def _handle_ns(packageName, path_item): + """Ensure that named package includes a subpath of path_item (if needed)""" + + importer = get_importer(path_item) + if importer is None: + return None + + # capture warnings due to #1111 + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + loader = importer.find_module(packageName) + + if loader is None: + return None + module = sys.modules.get(packageName) + if module is None: + module = sys.modules[packageName] = types.ModuleType(packageName) + module.__path__ = [] + _set_parent_ns(packageName) + elif not hasattr(module, '__path__'): + raise TypeError("Not a package:", packageName) + handler = _find_adapter(_namespace_handlers, importer) + subpath = handler(importer, path_item, packageName, module) + if subpath is not None: + path = module.__path__ + path.append(subpath) + loader.load_module(packageName) + _rebuild_mod_path(path, packageName, module) + return subpath + + +def _rebuild_mod_path(orig_path, package_name, module): + """ + Rebuild module.__path__ ensuring that all entries are ordered + corresponding to their sys.path order + """ + sys_path = [_normalize_cached(p) for p in sys.path] + + def safe_sys_path_index(entry): + """ + Workaround for #520 and #513. + """ + try: + return sys_path.index(entry) + except ValueError: + return float('inf') + + def position_in_sys_path(path): + """ + Return the ordinal of the path based on its position in sys.path + """ + path_parts = path.split(os.sep) + module_parts = package_name.count('.') + 1 + parts = path_parts[:-module_parts] + return safe_sys_path_index(_normalize_cached(os.sep.join(parts))) + + new_path = sorted(orig_path, key=position_in_sys_path) + new_path = [_normalize_cached(p) for p in new_path] + + if isinstance(module.__path__, list): + module.__path__[:] = new_path + else: + module.__path__ = new_path + + +def declare_namespace(packageName): + """Declare that package 'packageName' is a namespace package""" + + _imp.acquire_lock() + try: + if packageName in _namespace_packages: + return + + path = sys.path + parent, _, _ = packageName.rpartition('.') + + if parent: + declare_namespace(parent) + if parent not in _namespace_packages: + __import__(parent) + try: + path = sys.modules[parent].__path__ + except AttributeError: + raise TypeError("Not a package:", parent) + + # Track what packages are namespaces, so when new path items are added, + # they can be updated + _namespace_packages.setdefault(parent or None, []).append(packageName) + _namespace_packages.setdefault(packageName, []) + + for path_item in path: + # Ensure all the parent's path items are reflected in the child, + # if they apply + _handle_ns(packageName, path_item) + + finally: + _imp.release_lock() + + +def fixup_namespace_packages(path_item, parent=None): + """Ensure that previously-declared namespace packages include path_item""" + _imp.acquire_lock() + try: + for package in _namespace_packages.get(parent, ()): + subpath = _handle_ns(package, path_item) + if subpath: + fixup_namespace_packages(subpath, package) + finally: + _imp.release_lock() + + +def file_ns_handler(importer, path_item, packageName, module): + """Compute an ns-package subpath for a filesystem or zipfile importer""" + + subpath = os.path.join(path_item, packageName.split('.')[-1]) + normalized = _normalize_cached(subpath) + for item in module.__path__: + if _normalize_cached(item) == normalized: + break + else: + # Only return the path if it's not already there + return subpath + + +register_namespace_handler(pkgutil.ImpImporter, file_ns_handler) +register_namespace_handler(zipimport.zipimporter, file_ns_handler) + +if hasattr(importlib_machinery, 'FileFinder'): + register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler) + + +def null_ns_handler(importer, path_item, packageName, module): + return None + + +register_namespace_handler(object, null_ns_handler) + + +def normalize_path(filename): + """Normalize a file/dir name for comparison purposes""" + return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename)))) + + +def _cygwin_patch(filename): # pragma: nocover + """ + Contrary to POSIX 2008, on Cygwin, getcwd (3) contains + symlink components. Using + os.path.abspath() works around this limitation. A fix in os.getcwd() + would probably better, in Cygwin even more so, except + that this seems to be by design... + """ + return os.path.abspath(filename) if sys.platform == 'cygwin' else filename + + +def _normalize_cached(filename, _cache={}): + try: + return _cache[filename] + except KeyError: + _cache[filename] = result = normalize_path(filename) + return result + + +def _is_egg_path(path): + """ + Determine if given path appears to be an egg. + """ + return path.lower().endswith('.egg') + + +def _is_unpacked_egg(path): + """ + Determine if given path appears to be an unpacked egg. + """ + return ( + _is_egg_path(path) and + os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO')) + ) + + +def _set_parent_ns(packageName): + parts = packageName.split('.') + name = parts.pop() + if parts: + parent = '.'.join(parts) + setattr(sys.modules[parent], name, sys.modules[packageName]) + + +def yield_lines(strs): + """Yield non-empty/non-comment lines of a string or sequence""" + if isinstance(strs, six.string_types): + for s in strs.splitlines(): + s = s.strip() + # skip blank lines/comments + if s and not s.startswith('#'): + yield s + else: + for ss in strs: + for s in yield_lines(ss): + yield s + + +MODULE = re.compile(r"\w+(\.\w+)*$").match +EGG_NAME = re.compile( + r""" + (?P[^-]+) ( + -(?P[^-]+) ( + -py(?P[^-]+) ( + -(?P.+) + )? + )? + )? + """, + re.VERBOSE | re.IGNORECASE, +).match + + +class EntryPoint: + """Object representing an advertised importable object""" + + def __init__(self, name, module_name, attrs=(), extras=(), dist=None): + if not MODULE(module_name): + raise ValueError("Invalid module name", module_name) + self.name = name + self.module_name = module_name + self.attrs = tuple(attrs) + self.extras = tuple(extras) + self.dist = dist + + def __str__(self): + s = "%s = %s" % (self.name, self.module_name) + if self.attrs: + s += ':' + '.'.join(self.attrs) + if self.extras: + s += ' [%s]' % ','.join(self.extras) + return s + + def __repr__(self): + return "EntryPoint.parse(%r)" % str(self) + + def load(self, require=True, *args, **kwargs): + """ + Require packages for this EntryPoint, then resolve it. + """ + if not require or args or kwargs: + warnings.warn( + "Parameters to load are deprecated. Call .resolve and " + ".require separately.", + PkgResourcesDeprecationWarning, + stacklevel=2, + ) + if require: + self.require(*args, **kwargs) + return self.resolve() + + def resolve(self): + """ + Resolve the entry point from its module and attrs. + """ + module = __import__(self.module_name, fromlist=['__name__'], level=0) + try: + return functools.reduce(getattr, self.attrs, module) + except AttributeError as exc: + raise ImportError(str(exc)) + + def require(self, env=None, installer=None): + if self.extras and not self.dist: + raise UnknownExtra("Can't require() without a distribution", self) + + # Get the requirements for this entry point with all its extras and + # then resolve them. We have to pass `extras` along when resolving so + # that the working set knows what extras we want. Otherwise, for + # dist-info distributions, the working set will assume that the + # requirements for that extra are purely optional and skip over them. + reqs = self.dist.requires(self.extras) + items = working_set.resolve(reqs, env, installer, extras=self.extras) + list(map(working_set.add, items)) + + pattern = re.compile( + r'\s*' + r'(?P.+?)\s*' + r'=\s*' + r'(?P[\w.]+)\s*' + r'(:\s*(?P[\w.]+))?\s*' + r'(?P\[.*\])?\s*$' + ) + + @classmethod + def parse(cls, src, dist=None): + """Parse a single entry point from string `src` + + Entry point syntax follows the form:: + + name = some.module:some.attr [extra1, extra2] + + The entry name and module name are required, but the ``:attrs`` and + ``[extras]`` parts are optional + """ + m = cls.pattern.match(src) + if not m: + msg = "EntryPoint must be in 'name=module:attrs [extras]' format" + raise ValueError(msg, src) + res = m.groupdict() + extras = cls._parse_extras(res['extras']) + attrs = res['attr'].split('.') if res['attr'] else () + return cls(res['name'], res['module'], attrs, extras, dist) + + @classmethod + def _parse_extras(cls, extras_spec): + if not extras_spec: + return () + req = Requirement.parse('x' + extras_spec) + if req.specs: + raise ValueError() + return req.extras + + @classmethod + def parse_group(cls, group, lines, dist=None): + """Parse an entry point group""" + if not MODULE(group): + raise ValueError("Invalid group name", group) + this = {} + for line in yield_lines(lines): + ep = cls.parse(line, dist) + if ep.name in this: + raise ValueError("Duplicate entry point", group, ep.name) + this[ep.name] = ep + return this + + @classmethod + def parse_map(cls, data, dist=None): + """Parse a map of entry point groups""" + if isinstance(data, dict): + data = data.items() + else: + data = split_sections(data) + maps = {} + for group, lines in data: + if group is None: + if not lines: + continue + raise ValueError("Entry points must be listed in groups") + group = group.strip() + if group in maps: + raise ValueError("Duplicate group name", group) + maps[group] = cls.parse_group(group, lines, dist) + return maps + + +def _remove_md5_fragment(location): + if not location: + return '' + parsed = urllib.parse.urlparse(location) + if parsed[-1].startswith('md5='): + return urllib.parse.urlunparse(parsed[:-1] + ('',)) + return location + + +def _version_from_file(lines): + """ + Given an iterable of lines from a Metadata file, return + the value of the Version field, if present, or None otherwise. + """ + def is_version_line(line): + return line.lower().startswith('version:') + version_lines = filter(is_version_line, lines) + line = next(iter(version_lines), '') + _, _, value = line.partition(':') + return safe_version(value.strip()) or None + + +class Distribution: + """Wrap an actual or potential sys.path entry w/metadata""" + PKG_INFO = 'PKG-INFO' + + def __init__( + self, location=None, metadata=None, project_name=None, + version=None, py_version=PY_MAJOR, platform=None, + precedence=EGG_DIST): + self.project_name = safe_name(project_name or 'Unknown') + if version is not None: + self._version = safe_version(version) + self.py_version = py_version + self.platform = platform + self.location = location + self.precedence = precedence + self._provider = metadata or empty_provider + + @classmethod + def from_location(cls, location, basename, metadata=None, **kw): + project_name, version, py_version, platform = [None] * 4 + basename, ext = os.path.splitext(basename) + if ext.lower() in _distributionImpl: + cls = _distributionImpl[ext.lower()] + + match = EGG_NAME(basename) + if match: + project_name, version, py_version, platform = match.group( + 'name', 'ver', 'pyver', 'plat' + ) + return cls( + location, metadata, project_name=project_name, version=version, + py_version=py_version, platform=platform, **kw + )._reload_version() + + def _reload_version(self): + return self + + @property + def hashcmp(self): + return ( + self.parsed_version, + self.precedence, + self.key, + _remove_md5_fragment(self.location), + self.py_version or '', + self.platform or '', + ) + + def __hash__(self): + return hash(self.hashcmp) + + def __lt__(self, other): + return self.hashcmp < other.hashcmp + + def __le__(self, other): + return self.hashcmp <= other.hashcmp + + def __gt__(self, other): + return self.hashcmp > other.hashcmp + + def __ge__(self, other): + return self.hashcmp >= other.hashcmp + + def __eq__(self, other): + if not isinstance(other, self.__class__): + # It's not a Distribution, so they are not equal + return False + return self.hashcmp == other.hashcmp + + def __ne__(self, other): + return not self == other + + # These properties have to be lazy so that we don't have to load any + # metadata until/unless it's actually needed. (i.e., some distributions + # may not know their name or version without loading PKG-INFO) + + @property + def key(self): + try: + return self._key + except AttributeError: + self._key = key = self.project_name.lower() + return key + + @property + def parsed_version(self): + if not hasattr(self, "_parsed_version"): + self._parsed_version = parse_version(self.version) + + return self._parsed_version + + def _warn_legacy_version(self): + LV = packaging.version.LegacyVersion + is_legacy = isinstance(self._parsed_version, LV) + if not is_legacy: + return + + # While an empty version is technically a legacy version and + # is not a valid PEP 440 version, it's also unlikely to + # actually come from someone and instead it is more likely that + # it comes from setuptools attempting to parse a filename and + # including it in the list. So for that we'll gate this warning + # on if the version is anything at all or not. + if not self.version: + return + + tmpl = textwrap.dedent(""" + '{project_name} ({version})' is being parsed as a legacy, + non PEP 440, + version. You may find odd behavior and sort order. + In particular it will be sorted as less than 0.0. It + is recommended to migrate to PEP 440 compatible + versions. + """).strip().replace('\n', ' ') + + warnings.warn(tmpl.format(**vars(self)), PEP440Warning) + + @property + def version(self): + try: + return self._version + except AttributeError: + version = _version_from_file(self._get_metadata(self.PKG_INFO)) + if version is None: + tmpl = "Missing 'Version:' header and/or %s file" + raise ValueError(tmpl % self.PKG_INFO, self) + return version + + @property + def _dep_map(self): + """ + A map of extra to its list of (direct) requirements + for this distribution, including the null extra. + """ + try: + return self.__dep_map + except AttributeError: + self.__dep_map = self._filter_extras(self._build_dep_map()) + return self.__dep_map + + @staticmethod + def _filter_extras(dm): + """ + Given a mapping of extras to dependencies, strip off + environment markers and filter out any dependencies + not matching the markers. + """ + for extra in list(filter(None, dm)): + new_extra = extra + reqs = dm.pop(extra) + new_extra, _, marker = extra.partition(':') + fails_marker = marker and ( + invalid_marker(marker) + or not evaluate_marker(marker) + ) + if fails_marker: + reqs = [] + new_extra = safe_extra(new_extra) or None + + dm.setdefault(new_extra, []).extend(reqs) + return dm + + def _build_dep_map(self): + dm = {} + for name in 'requires.txt', 'depends.txt': + for extra, reqs in split_sections(self._get_metadata(name)): + dm.setdefault(extra, []).extend(parse_requirements(reqs)) + return dm + + def requires(self, extras=()): + """List of Requirements needed for this distro if `extras` are used""" + dm = self._dep_map + deps = [] + deps.extend(dm.get(None, ())) + for ext in extras: + try: + deps.extend(dm[safe_extra(ext)]) + except KeyError: + raise UnknownExtra( + "%s has no such extra feature %r" % (self, ext) + ) + return deps + + def _get_metadata(self, name): + if self.has_metadata(name): + for line in self.get_metadata_lines(name): + yield line + + def activate(self, path=None, replace=False): + """Ensure distribution is importable on `path` (default=sys.path)""" + if path is None: + path = sys.path + self.insert_on(path, replace=replace) + if path is sys.path: + fixup_namespace_packages(self.location) + for pkg in self._get_metadata('namespace_packages.txt'): + if pkg in sys.modules: + declare_namespace(pkg) + + def egg_name(self): + """Return what this distribution's standard .egg filename should be""" + filename = "%s-%s-py%s" % ( + to_filename(self.project_name), to_filename(self.version), + self.py_version or PY_MAJOR + ) + + if self.platform: + filename += '-' + self.platform + return filename + + def __repr__(self): + if self.location: + return "%s (%s)" % (self, self.location) + else: + return str(self) + + def __str__(self): + try: + version = getattr(self, 'version', None) + except ValueError: + version = None + version = version or "[unknown version]" + return "%s %s" % (self.project_name, version) + + def __getattr__(self, attr): + """Delegate all unrecognized public attributes to .metadata provider""" + if attr.startswith('_'): + raise AttributeError(attr) + return getattr(self._provider, attr) + + def __dir__(self): + return list( + set(super(Distribution, self).__dir__()) + | set( + attr for attr in self._provider.__dir__() + if not attr.startswith('_') + ) + ) + + if not hasattr(object, '__dir__'): + # python 2.7 not supported + del __dir__ + + @classmethod + def from_filename(cls, filename, metadata=None, **kw): + return cls.from_location( + _normalize_cached(filename), os.path.basename(filename), metadata, + **kw + ) + + def as_requirement(self): + """Return a ``Requirement`` that matches this distribution exactly""" + if isinstance(self.parsed_version, packaging.version.Version): + spec = "%s==%s" % (self.project_name, self.parsed_version) + else: + spec = "%s===%s" % (self.project_name, self.parsed_version) + + return Requirement.parse(spec) + + def load_entry_point(self, group, name): + """Return the `name` entry point of `group` or raise ImportError""" + ep = self.get_entry_info(group, name) + if ep is None: + raise ImportError("Entry point %r not found" % ((group, name),)) + return ep.load() + + def get_entry_map(self, group=None): + """Return the entry point map for `group`, or the full entry map""" + try: + ep_map = self._ep_map + except AttributeError: + ep_map = self._ep_map = EntryPoint.parse_map( + self._get_metadata('entry_points.txt'), self + ) + if group is not None: + return ep_map.get(group, {}) + return ep_map + + def get_entry_info(self, group, name): + """Return the EntryPoint object for `group`+`name`, or ``None``""" + return self.get_entry_map(group).get(name) + + def insert_on(self, path, loc=None, replace=False): + """Ensure self.location is on path + + If replace=False (default): + - If location is already in path anywhere, do nothing. + - Else: + - If it's an egg and its parent directory is on path, + insert just ahead of the parent. + - Else: add to the end of path. + If replace=True: + - If location is already on path anywhere (not eggs) + or higher priority than its parent (eggs) + do nothing. + - Else: + - If it's an egg and its parent directory is on path, + insert just ahead of the parent, + removing any lower-priority entries. + - Else: add it to the front of path. + """ + + loc = loc or self.location + if not loc: + return + + nloc = _normalize_cached(loc) + bdir = os.path.dirname(nloc) + npath = [(p and _normalize_cached(p) or p) for p in path] + + for p, item in enumerate(npath): + if item == nloc: + if replace: + break + else: + # don't modify path (even removing duplicates) if + # found and not replace + return + elif item == bdir and self.precedence == EGG_DIST: + # if it's an .egg, give it precedence over its directory + # UNLESS it's already been added to sys.path and replace=False + if (not replace) and nloc in npath[p:]: + return + if path is sys.path: + self.check_version_conflict() + path.insert(p, loc) + npath.insert(p, nloc) + break + else: + if path is sys.path: + self.check_version_conflict() + if replace: + path.insert(0, loc) + else: + path.append(loc) + return + + # p is the spot where we found or inserted loc; now remove duplicates + while True: + try: + np = npath.index(nloc, p + 1) + except ValueError: + break + else: + del npath[np], path[np] + # ha! + p = np + + return + + def check_version_conflict(self): + if self.key == 'setuptools': + # ignore the inevitable setuptools self-conflicts :( + return + + nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt')) + loc = normalize_path(self.location) + for modname in self._get_metadata('top_level.txt'): + if (modname not in sys.modules or modname in nsp + or modname in _namespace_packages): + continue + if modname in ('pkg_resources', 'setuptools', 'site'): + continue + fn = getattr(sys.modules[modname], '__file__', None) + if fn and (normalize_path(fn).startswith(loc) or + fn.startswith(self.location)): + continue + issue_warning( + "Module %s was already imported from %s, but %s is being added" + " to sys.path" % (modname, fn, self.location), + ) + + def has_version(self): + try: + self.version + except ValueError: + issue_warning("Unbuilt egg for " + repr(self)) + return False + return True + + def clone(self, **kw): + """Copy this distribution, substituting in any changed keyword args""" + names = 'project_name version py_version platform location precedence' + for attr in names.split(): + kw.setdefault(attr, getattr(self, attr, None)) + kw.setdefault('metadata', self._provider) + return self.__class__(**kw) + + @property + def extras(self): + return [dep for dep in self._dep_map if dep] + + +class EggInfoDistribution(Distribution): + def _reload_version(self): + """ + Packages installed by distutils (e.g. numpy or scipy), + which uses an old safe_version, and so + their version numbers can get mangled when + converted to filenames (e.g., 1.11.0.dev0+2329eae to + 1.11.0.dev0_2329eae). These distributions will not be + parsed properly + downstream by Distribution and safe_version, so + take an extra step and try to get the version number from + the metadata file itself instead of the filename. + """ + md_version = _version_from_file(self._get_metadata(self.PKG_INFO)) + if md_version: + self._version = md_version + return self + + +class DistInfoDistribution(Distribution): + """ + Wrap an actual or potential sys.path entry + w/metadata, .dist-info style. + """ + PKG_INFO = 'METADATA' + EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])") + + @property + def _parsed_pkg_info(self): + """Parse and cache metadata""" + try: + return self._pkg_info + except AttributeError: + metadata = self.get_metadata(self.PKG_INFO) + self._pkg_info = email.parser.Parser().parsestr(metadata) + return self._pkg_info + + @property + def _dep_map(self): + try: + return self.__dep_map + except AttributeError: + self.__dep_map = self._compute_dependencies() + return self.__dep_map + + def _compute_dependencies(self): + """Recompute this distribution's dependencies.""" + dm = self.__dep_map = {None: []} + + reqs = [] + # Including any condition expressions + for req in self._parsed_pkg_info.get_all('Requires-Dist') or []: + reqs.extend(parse_requirements(req)) + + def reqs_for_extra(extra): + for req in reqs: + if not req.marker or req.marker.evaluate({'extra': extra}): + yield req + + common = frozenset(reqs_for_extra(None)) + dm[None].extend(common) + + for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []: + s_extra = safe_extra(extra.strip()) + dm[s_extra] = list(frozenset(reqs_for_extra(extra)) - common) + + return dm + + +_distributionImpl = { + '.egg': Distribution, + '.egg-info': EggInfoDistribution, + '.dist-info': DistInfoDistribution, +} + + +def issue_warning(*args, **kw): + level = 1 + g = globals() + try: + # find the first stack frame that is *not* code in + # the pkg_resources module, to use for the warning + while sys._getframe(level).f_globals is g: + level += 1 + except ValueError: + pass + warnings.warn(stacklevel=level + 1, *args, **kw) + + +class RequirementParseError(ValueError): + def __str__(self): + return ' '.join(self.args) + + +def parse_requirements(strs): + """Yield ``Requirement`` objects for each specification in `strs` + + `strs` must be a string, or a (possibly-nested) iterable thereof. + """ + # create a steppable iterator, so we can handle \-continuations + lines = iter(yield_lines(strs)) + + for line in lines: + # Drop comments -- a hash without a space may be in a URL. + if ' #' in line: + line = line[:line.find(' #')] + # If there is a line continuation, drop it, and append the next line. + if line.endswith('\\'): + line = line[:-2].strip() + try: + line += next(lines) + except StopIteration: + return + yield Requirement(line) + + +class Requirement(packaging.requirements.Requirement): + def __init__(self, requirement_string): + """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!""" + try: + super(Requirement, self).__init__(requirement_string) + except packaging.requirements.InvalidRequirement as e: + raise RequirementParseError(str(e)) + self.unsafe_name = self.name + project_name = safe_name(self.name) + self.project_name, self.key = project_name, project_name.lower() + self.specs = [ + (spec.operator, spec.version) for spec in self.specifier] + self.extras = tuple(map(safe_extra, self.extras)) + self.hashCmp = ( + self.key, + self.specifier, + frozenset(self.extras), + str(self.marker) if self.marker else None, + ) + self.__hash = hash(self.hashCmp) + + def __eq__(self, other): + return ( + isinstance(other, Requirement) and + self.hashCmp == other.hashCmp + ) + + def __ne__(self, other): + return not self == other + + def __contains__(self, item): + if isinstance(item, Distribution): + if item.key != self.key: + return False + + item = item.version + + # Allow prereleases always in order to match the previous behavior of + # this method. In the future this should be smarter and follow PEP 440 + # more accurately. + return self.specifier.contains(item, prereleases=True) + + def __hash__(self): + return self.__hash + + def __repr__(self): + return "Requirement.parse(%r)" % str(self) + + @staticmethod + def parse(s): + req, = parse_requirements(s) + return req + + +def _always_object(classes): + """ + Ensure object appears in the mro even + for old-style classes. + """ + if object not in classes: + return classes + (object,) + return classes + + +def _find_adapter(registry, ob): + """Return an adapter factory for `ob` from `registry`""" + types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob)))) + for t in types: + if t in registry: + return registry[t] + + +def ensure_directory(path): + """Ensure that the parent directory of `path` exists""" + dirname = os.path.dirname(path) + py31compat.makedirs(dirname, exist_ok=True) + + +def _bypass_ensure_directory(path): + """Sandbox-bypassing version of ensure_directory()""" + if not WRITE_SUPPORT: + raise IOError('"os.mkdir" not supported on this platform.') + dirname, filename = split(path) + if dirname and filename and not isdir(dirname): + _bypass_ensure_directory(dirname) + try: + mkdir(dirname, 0o755) + except FileExistsError: + pass + + +def split_sections(s): + """Split a string or iterable thereof into (section, content) pairs + + Each ``section`` is a stripped version of the section header ("[section]") + and each ``content`` is a list of stripped lines excluding blank lines and + comment-only lines. If there are any such lines before the first section + header, they're returned in a first ``section`` of ``None``. + """ + section = None + content = [] + for line in yield_lines(s): + if line.startswith("["): + if line.endswith("]"): + if section or content: + yield section, content + section = line[1:-1].strip() + content = [] + else: + raise ValueError("Invalid section heading", line) + else: + content.append(line) + + # wrap up last segment + yield section, content + + +def _mkstemp(*args, **kw): + old_open = os.open + try: + # temporarily bypass sandboxing + os.open = os_open + return tempfile.mkstemp(*args, **kw) + finally: + # and then put it back + os.open = old_open + + +# Silence the PEP440Warning by default, so that end users don't get hit by it +# randomly just because they use pkg_resources. We want to append the rule +# because we want earlier uses of filterwarnings to take precedence over this +# one. +warnings.filterwarnings("ignore", category=PEP440Warning, append=True) + + +# from jaraco.functools 1.3 +def _call_aside(f, *args, **kwargs): + f(*args, **kwargs) + return f + + +@_call_aside +def _initialize(g=globals()): + "Set up global resource manager (deliberately not state-saved)" + manager = ResourceManager() + g['_manager'] = manager + g.update( + (name, getattr(manager, name)) + for name in dir(manager) + if not name.startswith('_') + ) + + +@_call_aside +def _initialize_master_working_set(): + """ + Prepare the master working set and make the ``require()`` + API available. + + This function has explicit effects on the global state + of pkg_resources. It is intended to be invoked once at + the initialization of this module. + + Invocation by other packages is unsupported and done + at their own risk. + """ + working_set = WorkingSet._build_master() + _declare_state('object', working_set=working_set) + + require = working_set.require + iter_entry_points = working_set.iter_entry_points + add_activation_listener = working_set.subscribe + run_script = working_set.run_script + # backward compatibility + run_main = run_script + # Activate all distributions already on sys.path with replace=False and + # ensure that all distributions added to the working set in the future + # (e.g. by calling ``require()``) will get activated as well, + # with higher priority (replace=True). + tuple( + dist.activate(replace=False) + for dist in working_set + ) + add_activation_listener( + lambda dist: dist.activate(replace=True), + existing=False, + ) + working_set.entries = [] + # match order + list(map(working_set.add_entry, sys.path)) + globals().update(locals()) + +class PkgResourcesDeprecationWarning(Warning): + """ + Base class for warning about deprecations in ``pkg_resources`` + + This class is not derived from ``DeprecationWarning``, and as such is + visible by default. + """ diff --git a/lib/pkg_resources/_vendor/__init__.py b/lib/pkg_resources/_vendor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/pkg_resources/_vendor/appdirs.py b/lib/pkg_resources/_vendor/appdirs.py new file mode 100644 index 0000000..ae67001 --- /dev/null +++ b/lib/pkg_resources/_vendor/appdirs.py @@ -0,0 +1,608 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2005-2010 ActiveState Software Inc. +# Copyright (c) 2013 Eddy Petrișor + +"""Utilities for determining application-specific dirs. + +See for details and usage. +""" +# Dev Notes: +# - MSDN on where to store app data files: +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html +# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + +__version_info__ = (1, 4, 3) +__version__ = '.'.join(map(str, __version_info__)) + + +import sys +import os + +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode = str + +if sys.platform.startswith('java'): + import platform + os_name = platform.java_ver()[3][0] + if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. + system = 'win32' + elif os_name.startswith('Mac'): # "Mac OS X", etc. + system = 'darwin' + else: # "Linux", "SunOS", "FreeBSD", etc. + # Setting this to "linux2" is not ideal, but only Windows or Mac + # are actually checked for and the rest of the module expects + # *sys.platform* style strings. + system = 'linux2' +else: + system = sys.platform + + + +def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user data directories are: + Mac OS X: ~/Library/Application Support/ + Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\\Application Data\\ + Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ + Win 7 (not roaming): C:\Users\\AppData\Local\\ + Win 7 (roaming): C:\Users\\AppData\Roaming\\ + + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. + That means, by default "~/.local/share/". + """ + if system == "win32": + if appauthor is None: + appauthor = appname + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(_get_win_folder(const)) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of data dirs should be + returned. By default, the first item from XDG_DATA_DIRS is + returned, or '/usr/local/share/', + if XDG_DATA_DIRS is not set + + Typical site data directories are: + Mac OS X: /Library/Application Support/ + Unix: /usr/local/share/ or /usr/share/ + Win XP: C:\Documents and Settings\All Users\Application Data\\ + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. + + For Unix, this is using the $XDG_DATA_DIRS[0] default. + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('/Library/Application Support') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_DATA_DIRS + # only first, if multipath is False + path = os.getenv('XDG_DATA_DIRS', + os.pathsep.join(['/usr/local/share', '/usr/share'])) + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + if appname and version: + path = os.path.join(path, version) + return path + + +def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific config dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user config directories are: + Mac OS X: same as user_data_dir + Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. + That means, by default "~/.config/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of config dirs should be + returned. By default, the first item from XDG_CONFIG_DIRS is + returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set + + Typical site config directories are: + Mac OS X: same as site_data_dir + Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in + $XDG_CONFIG_DIRS + Win *: same as site_data_dir + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + + For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system in ["win32", "darwin"]: + path = site_data_dir(appname, appauthor) + if appname and version: + path = os.path.join(path, version) + else: + # XDG default for $XDG_CONFIG_DIRS + # only first, if multipath is False + path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + +def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific cache dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Cache" to the base app data dir for Windows. See + discussion below. + + Typical user cache directories are: + Mac OS X: ~/Library/Caches/ + Unix: ~/.cache/ (XDG default) + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache + Vista: C:\Users\\AppData\Local\\\Cache + + On Windows the only suggestion in the MSDN docs is that local settings go in + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming + app data dir (the default returned by `user_data_dir` above). Apps typically + put cache data somewhere *under* the given dir here. Some examples: + ...\Mozilla\Firefox\Profiles\\Cache + ...\Acme\SuperApp\Cache\1.0 + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. + This can be disabled with the `opinion=False` option. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + if opinion: + path = os.path.join(path, "Cache") + elif system == 'darwin': + path = os.path.expanduser('~/Library/Caches') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific state dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user state directories are: + Mac OS X: same as user_data_dir + Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow this Debian proposal + to extend the XDG spec and support $XDG_STATE_HOME. + + That means, by default "~/.local/state/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific log dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Logs" to the base app data dir for Windows, and "log" to the + base cache dir for Unix. See discussion below. + + Typical user log directories are: + Mac OS X: ~/Library/Logs/ + Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs + Vista: C:\Users\\AppData\Local\\\Logs + + On Windows the only suggestion in the MSDN docs is that local settings + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in + examples of what some windows apps use for a logs dir.) + + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` + value for Windows and appends "log" to the user cache dir for Unix. + This can be disabled with the `opinion=False` option. + """ + if system == "darwin": + path = os.path.join( + os.path.expanduser('~/Library/Logs'), + appname) + elif system == "win32": + path = user_data_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "Logs") + else: + path = user_cache_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "log") + if appname and version: + path = os.path.join(path, version) + return path + + +class AppDirs(object): + """Convenience wrapper for getting application dirs.""" + def __init__(self, appname=None, appauthor=None, version=None, + roaming=False, multipath=False): + self.appname = appname + self.appauthor = appauthor + self.version = version + self.roaming = roaming + self.multipath = multipath + + @property + def user_data_dir(self): + return user_data_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_data_dir(self): + return site_data_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_config_dir(self): + return user_config_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_config_dir(self): + return site_config_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_cache_dir(self): + return user_cache_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_state_dir(self): + return user_state_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_log_dir(self): + return user_log_dir(self.appname, self.appauthor, + version=self.version) + + +#---- internal support stuff + +def _get_win_folder_from_registry(csidl_name): + """This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + if PY3: + import winreg as _winreg + else: + import _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + dir, type = _winreg.QueryValueEx(key, shell_folder_name) + return dir + + +def _get_win_folder_with_pywin32(csidl_name): + from win32com.shell import shellcon, shell + dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) + # Try to make this a unicode path because SHGetFolderPath does + # not return unicode strings when there is unicode data in the + # path. + try: + dir = unicode(dir) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + try: + import win32api + dir = win32api.GetShortPathName(dir) + except ImportError: + pass + except UnicodeError: + pass + return dir + + +def _get_win_folder_with_ctypes(csidl_name): + import ctypes + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + +def _get_win_folder_with_jna(csidl_name): + import array + from com.sun import jna + from com.sun.jna.platform import win32 + + buf_size = win32.WinDef.MAX_PATH * 2 + buf = array.zeros('c', buf_size) + shell = win32.Shell32.INSTANCE + shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf = array.zeros('c', buf_size) + kernel = win32.Kernel32.INSTANCE + if kernel.GetShortPathName(dir, buf, buf_size): + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + return dir + +if system == "win32": + try: + import win32com.shell + _get_win_folder = _get_win_folder_with_pywin32 + except ImportError: + try: + from ctypes import windll + _get_win_folder = _get_win_folder_with_ctypes + except ImportError: + try: + import com.sun.jna + _get_win_folder = _get_win_folder_with_jna + except ImportError: + _get_win_folder = _get_win_folder_from_registry + + +#---- self test code + +if __name__ == "__main__": + appname = "MyApp" + appauthor = "MyCompany" + + props = ("user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir") + + print("-- app dirs %s --" % __version__) + + print("-- app dirs (with optional 'version')") + dirs = AppDirs(appname, appauthor, version="1.0") + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'version')") + dirs = AppDirs(appname, appauthor) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'appauthor')") + dirs = AppDirs(appname) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = AppDirs(appname, appauthor=False) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/lib/pkg_resources/_vendor/packaging/__about__.py b/lib/pkg_resources/_vendor/packaging/__about__.py new file mode 100644 index 0000000..95d330e --- /dev/null +++ b/lib/pkg_resources/_vendor/packaging/__about__.py @@ -0,0 +1,21 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +__all__ = [ + "__title__", "__summary__", "__uri__", "__version__", "__author__", + "__email__", "__license__", "__copyright__", +] + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "16.8" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD or Apache License, Version 2.0" +__copyright__ = "Copyright 2014-2016 %s" % __author__ diff --git a/lib/pkg_resources/_vendor/packaging/__init__.py b/lib/pkg_resources/_vendor/packaging/__init__.py new file mode 100644 index 0000000..5ee6220 --- /dev/null +++ b/lib/pkg_resources/_vendor/packaging/__init__.py @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +from .__about__ import ( + __author__, __copyright__, __email__, __license__, __summary__, __title__, + __uri__, __version__ +) + +__all__ = [ + "__title__", "__summary__", "__uri__", "__version__", "__author__", + "__email__", "__license__", "__copyright__", +] diff --git a/lib/pkg_resources/_vendor/packaging/_compat.py b/lib/pkg_resources/_vendor/packaging/_compat.py new file mode 100644 index 0000000..210bb80 --- /dev/null +++ b/lib/pkg_resources/_vendor/packaging/_compat.py @@ -0,0 +1,30 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import sys + + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +# flake8: noqa + +if PY3: + string_types = str, +else: + string_types = basestring, + + +def with_metaclass(meta, *bases): + """ + Create a base class with a metaclass. + """ + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) diff --git a/lib/pkg_resources/_vendor/packaging/_structures.py b/lib/pkg_resources/_vendor/packaging/_structures.py new file mode 100644 index 0000000..ccc2786 --- /dev/null +++ b/lib/pkg_resources/_vendor/packaging/_structures.py @@ -0,0 +1,68 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + + +class Infinity(object): + + def __repr__(self): + return "Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return False + + def __le__(self, other): + return False + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return True + + def __ge__(self, other): + return True + + def __neg__(self): + return NegativeInfinity + +Infinity = Infinity() + + +class NegativeInfinity(object): + + def __repr__(self): + return "-Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return True + + def __le__(self, other): + return True + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return False + + def __ge__(self, other): + return False + + def __neg__(self): + return Infinity + +NegativeInfinity = NegativeInfinity() diff --git a/lib/pkg_resources/_vendor/packaging/markers.py b/lib/pkg_resources/_vendor/packaging/markers.py new file mode 100644 index 0000000..892e578 --- /dev/null +++ b/lib/pkg_resources/_vendor/packaging/markers.py @@ -0,0 +1,301 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import operator +import os +import platform +import sys + +from pkg_resources.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd +from pkg_resources.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString +from pkg_resources.extern.pyparsing import Literal as L # noqa + +from ._compat import string_types +from .specifiers import Specifier, InvalidSpecifier + + +__all__ = [ + "InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName", + "Marker", "default_environment", +] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Node(object): + + def __init__(self, value): + self.value = value + + def __str__(self): + return str(self.value) + + def __repr__(self): + return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + + def serialize(self): + raise NotImplementedError + + +class Variable(Node): + + def serialize(self): + return str(self) + + +class Value(Node): + + def serialize(self): + return '"{0}"'.format(self) + + +class Op(Node): + + def serialize(self): + return str(self) + + +VARIABLE = ( + L("implementation_version") | + L("platform_python_implementation") | + L("implementation_name") | + L("python_full_version") | + L("platform_release") | + L("platform_version") | + L("platform_machine") | + L("platform_system") | + L("python_version") | + L("sys_platform") | + L("os_name") | + L("os.name") | # PEP-345 + L("sys.platform") | # PEP-345 + L("platform.version") | # PEP-345 + L("platform.machine") | # PEP-345 + L("platform.python_implementation") | # PEP-345 + L("python_implementation") | # undocumented setuptools legacy + L("extra") +) +ALIASES = { + 'os.name': 'os_name', + 'sys.platform': 'sys_platform', + 'platform.version': 'platform_version', + 'platform.machine': 'platform_machine', + 'platform.python_implementation': 'platform_python_implementation', + 'python_implementation': 'platform_python_implementation' +} +VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) + +VERSION_CMP = ( + L("===") | + L("==") | + L(">=") | + L("<=") | + L("!=") | + L("~=") | + L(">") | + L("<") +) + +MARKER_OP = VERSION_CMP | L("not in") | L("in") +MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) + +MARKER_VALUE = QuotedString("'") | QuotedString('"') +MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) + +BOOLOP = L("and") | L("or") + +MARKER_VAR = VARIABLE | MARKER_VALUE + +MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) +MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) + +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() + +MARKER_EXPR = Forward() +MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) +MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) + +MARKER = stringStart + MARKER_EXPR + stringEnd + + +def _coerce_parse_result(results): + if isinstance(results, ParseResults): + return [_coerce_parse_result(i) for i in results] + else: + return results + + +def _format_marker(marker, first=True): + assert isinstance(marker, (list, tuple, string_types)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if (isinstance(marker, list) and len(marker) == 1 and + isinstance(marker[0], (list, tuple))): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} + + +def _eval_op(lhs, op, rhs): + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs) + + oper = _operators.get(op.serialize()) + if oper is None: + raise UndefinedComparison( + "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) + ) + + return oper(lhs, rhs) + + +_undefined = object() + + +def _get_env(environment, name): + value = environment.get(name, _undefined) + + if value is _undefined: + raise UndefinedEnvironmentName( + "{0!r} does not exist in evaluation environment.".format(name) + ) + + return value + + +def _evaluate_markers(markers, environment): + groups = [[]] + + for marker in markers: + assert isinstance(marker, (list, tuple, string_types)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + lhs_value = _get_env(environment, lhs.value) + rhs_value = rhs.value + else: + lhs_value = lhs.value + rhs_value = _get_env(environment, rhs.value) + + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info): + version = '{0.major}.{0.minor}.{0.micro}'.format(info) + kind = info.releaselevel + if kind != 'final': + version += kind[0] + str(info.serial) + return version + + +def default_environment(): + if hasattr(sys, 'implementation'): + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + else: + iver = '0' + implementation_name = '' + + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": platform.python_version()[:3], + "sys_platform": sys.platform, + } + + +class Marker(object): + + def __init__(self, marker): + try: + self._markers = _coerce_parse_result(MARKER.parseString(marker)) + except ParseException as e: + err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( + marker, marker[e.loc:e.loc + 8]) + raise InvalidMarker(err_str) + + def __str__(self): + return _format_marker(self._markers) + + def __repr__(self): + return "".format(str(self)) + + def evaluate(self, environment=None): + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = default_environment() + if environment is not None: + current_environment.update(environment) + + return _evaluate_markers(self._markers, current_environment) diff --git a/lib/pkg_resources/_vendor/packaging/requirements.py b/lib/pkg_resources/_vendor/packaging/requirements.py new file mode 100644 index 0000000..0c8c4a3 --- /dev/null +++ b/lib/pkg_resources/_vendor/packaging/requirements.py @@ -0,0 +1,127 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import string +import re + +from pkg_resources.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException +from pkg_resources.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine +from pkg_resources.extern.pyparsing import Literal as L # noqa +from pkg_resources.extern.six.moves.urllib import parse as urlparse + +from .markers import MARKER_EXPR, Marker +from .specifiers import LegacySpecifier, Specifier, SpecifierSet + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +ALPHANUM = Word(string.ascii_letters + string.digits) + +LBRACKET = L("[").suppress() +RBRACKET = L("]").suppress() +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() +COMMA = L(",").suppress() +SEMICOLON = L(";").suppress() +AT = L("@").suppress() + +PUNCTUATION = Word("-_.") +IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) +IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) + +NAME = IDENTIFIER("name") +EXTRA = IDENTIFIER + +URI = Regex(r'[^ ]+')("url") +URL = (AT + URI) + +EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) +EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") + +VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) +VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) + +VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY +VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), + joinString=",", adjacent=False)("_raw_spec") +_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '') + +VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") +VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) + +MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") +MARKER_EXPR.setParseAction( + lambda s, l, t: Marker(s[t._original_start:t._original_end]) +) +MARKER_SEPERATOR = SEMICOLON +MARKER = MARKER_SEPERATOR + MARKER_EXPR + +VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) +URL_AND_MARKER = URL + Optional(MARKER) + +NAMED_REQUIREMENT = \ + NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) + +REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd + + +class Requirement(object): + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string): + try: + req = REQUIREMENT.parseString(requirement_string) + except ParseException as e: + raise InvalidRequirement( + "Invalid requirement, parse error at \"{0!r}\"".format( + requirement_string[e.loc:e.loc + 8])) + + self.name = req.name + if req.url: + parsed_url = urlparse.urlparse(req.url) + if not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc): + raise InvalidRequirement("Invalid URL given") + self.url = req.url + else: + self.url = None + self.extras = set(req.extras.asList() if req.extras else []) + self.specifier = SpecifierSet(req.specifier) + self.marker = req.marker if req.marker else None + + def __str__(self): + parts = [self.name] + + if self.extras: + parts.append("[{0}]".format(",".join(sorted(self.extras)))) + + if self.specifier: + parts.append(str(self.specifier)) + + if self.url: + parts.append("@ {0}".format(self.url)) + + if self.marker: + parts.append("; {0}".format(self.marker)) + + return "".join(parts) + + def __repr__(self): + return "".format(str(self)) diff --git a/lib/pkg_resources/_vendor/packaging/specifiers.py b/lib/pkg_resources/_vendor/packaging/specifiers.py new file mode 100644 index 0000000..7f5a76c --- /dev/null +++ b/lib/pkg_resources/_vendor/packaging/specifiers.py @@ -0,0 +1,774 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import abc +import functools +import itertools +import re + +from ._compat import string_types, with_metaclass +from .version import Version, LegacyVersion, parse + + +class InvalidSpecifier(ValueError): + """ + An invalid specifier was found, users should refer to PEP 440. + """ + + +class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): + + @abc.abstractmethod + def __str__(self): + """ + Returns the str representation of this Specifier like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self): + """ + Returns a hash value for this Specifier like object. + """ + + @abc.abstractmethod + def __eq__(self, other): + """ + Returns a boolean representing whether or not the two Specifier like + objects are equal. + """ + + @abc.abstractmethod + def __ne__(self, other): + """ + Returns a boolean representing whether or not the two Specifier like + objects are not equal. + """ + + @abc.abstractproperty + def prereleases(self): + """ + Returns whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @prereleases.setter + def prereleases(self, value): + """ + Sets whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @abc.abstractmethod + def contains(self, item, prereleases=None): + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter(self, iterable, prereleases=None): + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class _IndividualSpecifier(BaseSpecifier): + + _operators = {} + + def __init__(self, spec="", prereleases=None): + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + + self._spec = ( + match.group("operator").strip(), + match.group("version").strip(), + ) + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + def __repr__(self): + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "<{0}({1!r}{2})>".format( + self.__class__.__name__, + str(self), + pre, + ) + + def __str__(self): + return "{0}{1}".format(*self._spec) + + def __hash__(self): + return hash(self._spec) + + def __eq__(self, other): + if isinstance(other, string_types): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec == other._spec + + def __ne__(self, other): + if isinstance(other, string_types): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec != other._spec + + def _get_operator(self, op): + return getattr(self, "_compare_{0}".format(self._operators[op])) + + def _coerce_version(self, version): + if not isinstance(version, (LegacyVersion, Version)): + version = parse(version) + return version + + @property + def operator(self): + return self._spec[0] + + @property + def version(self): + return self._spec[1] + + @property + def prereleases(self): + return self._prereleases + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + def __contains__(self, item): + return self.contains(item) + + def contains(self, item, prereleases=None): + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version or LegacyVersion, this allows us to have + # a shortcut for ``"2.0" in Specifier(">=2") + item = self._coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + return self._get_operator(self.operator)(item, self.version) + + def filter(self, iterable, prereleases=None): + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = self._coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later incase nothing + # else matches this specifier. + if (parsed_version.is_prerelease and not + (prereleases or self.prereleases)): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the begining. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +class LegacySpecifier(_IndividualSpecifier): + + _regex_str = ( + r""" + (?P(==|!=|<=|>=|<|>)) + \s* + (?P + [^,;\s)]* # Since this is a "legacy" specifier, and the version + # string can be just about anything, we match everything + # except for whitespace, a semi-colon for marker support, + # a closing paren since versions can be enclosed in + # them, and a comma since it's a version separator. + ) + """ + ) + + _regex = re.compile( + r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + } + + def _coerce_version(self, version): + if not isinstance(version, LegacyVersion): + version = LegacyVersion(str(version)) + return version + + def _compare_equal(self, prospective, spec): + return prospective == self._coerce_version(spec) + + def _compare_not_equal(self, prospective, spec): + return prospective != self._coerce_version(spec) + + def _compare_less_than_equal(self, prospective, spec): + return prospective <= self._coerce_version(spec) + + def _compare_greater_than_equal(self, prospective, spec): + return prospective >= self._coerce_version(spec) + + def _compare_less_than(self, prospective, spec): + return prospective < self._coerce_version(spec) + + def _compare_greater_than(self, prospective, spec): + return prospective > self._coerce_version(spec) + + +def _require_version_compare(fn): + @functools.wraps(fn) + def wrapped(self, prospective, spec): + if not isinstance(prospective, Version): + return False + return fn(self, prospective, spec) + return wrapped + + +class Specifier(_IndividualSpecifier): + + _regex_str = ( + r""" + (?P(~=|==|!=|<=|>=|<|>|===)) + (?P + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s]* # We just match everything, except for whitespace + # since we are only testing for strict identity. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + + # You cannot use a wild card and a dev or local version + # together so group them with a | and make them optional. + (?: + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + | + \.\* # Wild card syntax of .* + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + @_require_version_compare + def _compare_compatible(self, prospective, spec): + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore post and dev releases and we want to treat the pre-release as + # it's own separate segment. + prefix = ".".join( + list( + itertools.takewhile( + lambda x: (not x.startswith("post") and not + x.startswith("dev")), + _version_split(spec), + ) + )[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return (self._get_operator(">=")(prospective, spec) and + self._get_operator("==")(prospective, prefix)) + + @_require_version_compare + def _compare_equal(self, prospective, spec): + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + prospective = Version(prospective.public) + # Split the spec out by dots, and pretend that there is an implicit + # dot in between a release segment and a pre-release segment. + spec = _version_split(spec[:-2]) # Remove the trailing .* + + # Split the prospective version out by dots, and pretend that there + # is an implicit dot in between a release segment and a pre-release + # segment. + prospective = _version_split(str(prospective)) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + prospective = prospective[:len(spec)] + + # Pad out our two sides with zeros so that they both equal the same + # length. + spec, prospective = _pad_version(spec, prospective) + else: + # Convert our spec string into a Version + spec = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec.local: + prospective = Version(prospective.public) + + return prospective == spec + + @_require_version_compare + def _compare_not_equal(self, prospective, spec): + return not self._compare_equal(prospective, spec) + + @_require_version_compare + def _compare_less_than_equal(self, prospective, spec): + return prospective <= Version(spec) + + @_require_version_compare + def _compare_greater_than_equal(self, prospective, spec): + return prospective >= Version(spec) + + @_require_version_compare + def _compare_less_than(self, prospective, spec): + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + @_require_version_compare + def _compare_greater_than(self, prospective, spec): + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is techincally greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective, spec): + return str(prospective).lower() == str(spec).lower() + + @property + def prereleases(self): + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "==="]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if parse(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version): + result = [] + for item in version.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _pad_version(left, right): + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]):]) + right_split.append(right[len(right_split[0]):]) + + # Insert our padding + left_split.insert( + 1, + ["0"] * max(0, len(right_split[0]) - len(left_split[0])), + ) + right_split.insert( + 1, + ["0"] * max(0, len(left_split[0]) - len(right_split[0])), + ) + + return ( + list(itertools.chain(*left_split)), + list(itertools.chain(*right_split)), + ) + + +class SpecifierSet(BaseSpecifier): + + def __init__(self, specifiers="", prereleases=None): + # Split on , to break each indidivual specifier into it's own item, and + # strip each item to remove leading/trailing whitespace. + specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Parsed each individual specifier, attempting first to make it a + # Specifier and falling back to a LegacySpecifier. + parsed = set() + for specifier in specifiers: + try: + parsed.add(Specifier(specifier)) + except InvalidSpecifier: + parsed.add(LegacySpecifier(specifier)) + + # Turn our parsed specifiers into a frozen set and save them for later. + self._specs = frozenset(parsed) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + def __repr__(self): + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "".format(str(self), pre) + + def __str__(self): + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self): + return hash(self._specs) + + def __and__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif isinstance(other, _IndividualSpecifier): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __ne__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif isinstance(other, _IndividualSpecifier): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs != other._specs + + def __len__(self): + return len(self._specs) + + def __iter__(self): + return iter(self._specs) + + @property + def prereleases(self): + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + def __contains__(self, item): + return self.contains(item) + + def contains(self, item, prereleases=None): + # Ensure that our item is a Version or LegacyVersion instance. + if not isinstance(item, (LegacyVersion, Version)): + item = parse(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all( + s.contains(item, prereleases=prereleases) + for s in self._specs + ) + + def filter(self, iterable, prereleases=None): + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iterable + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases, and which will filter out LegacyVersion in general. + else: + filtered = [] + found_prereleases = [] + + for item in iterable: + # Ensure that we some kind of Version class for this item. + if not isinstance(item, (LegacyVersion, Version)): + parsed_version = parse(item) + else: + parsed_version = item + + # Filter out any item which is parsed as a LegacyVersion + if isinstance(parsed_version, LegacyVersion): + continue + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return found_prereleases + + return filtered diff --git a/lib/pkg_resources/_vendor/packaging/utils.py b/lib/pkg_resources/_vendor/packaging/utils.py new file mode 100644 index 0000000..942387c --- /dev/null +++ b/lib/pkg_resources/_vendor/packaging/utils.py @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import re + + +_canonicalize_regex = re.compile(r"[-_.]+") + + +def canonicalize_name(name): + # This is taken from PEP 503. + return _canonicalize_regex.sub("-", name).lower() diff --git a/lib/pkg_resources/_vendor/packaging/version.py b/lib/pkg_resources/_vendor/packaging/version.py new file mode 100644 index 0000000..83b5ee8 --- /dev/null +++ b/lib/pkg_resources/_vendor/packaging/version.py @@ -0,0 +1,393 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import collections +import itertools +import re + +from ._structures import Infinity + + +__all__ = [ + "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN" +] + + +_Version = collections.namedtuple( + "_Version", + ["epoch", "release", "dev", "pre", "post", "local"], +) + + +def parse(version): + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return LegacyVersion(version) + + +class InvalidVersion(ValueError): + """ + An invalid version was found, users should refer to PEP 440. + """ + + +class _BaseVersion(object): + + def __hash__(self): + return hash(self._key) + + def __lt__(self, other): + return self._compare(other, lambda s, o: s < o) + + def __le__(self, other): + return self._compare(other, lambda s, o: s <= o) + + def __eq__(self, other): + return self._compare(other, lambda s, o: s == o) + + def __ge__(self, other): + return self._compare(other, lambda s, o: s >= o) + + def __gt__(self, other): + return self._compare(other, lambda s, o: s > o) + + def __ne__(self, other): + return self._compare(other, lambda s, o: s != o) + + def _compare(self, other, method): + if not isinstance(other, _BaseVersion): + return NotImplemented + + return method(self._key, other._key) + + +class LegacyVersion(_BaseVersion): + + def __init__(self, version): + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + def __str__(self): + return self._version + + def __repr__(self): + return "".format(repr(str(self))) + + @property + def public(self): + return self._version + + @property + def base_version(self): + return self._version + + @property + def local(self): + return None + + @property + def is_prerelease(self): + return False + + @property + def is_postrelease(self): + return False + + +_legacy_version_component_re = re.compile( + r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, +) + +_legacy_version_replacement_map = { + "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@", +} + + +def _parse_version_parts(s): + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version): + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts = [] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + parts = tuple(parts) + + return epoch, parts + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P

                                          # pre-release
+            [-_\.]?
+            (?P(a|b|c|rc|alpha|beta|pre|preview))
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+"""
+
+
+class Version(_BaseVersion):
+
+    _regex = re.compile(
+        r"^\s*" + VERSION_PATTERN + r"\s*$",
+        re.VERBOSE | re.IGNORECASE,
+    )
+
+    def __init__(self, version):
+        # Validate the version and parse it into pieces
+        match = self._regex.search(version)
+        if not match:
+            raise InvalidVersion("Invalid version: '{0}'".format(version))
+
+        # Store the parsed out pieces of the version
+        self._version = _Version(
+            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+            release=tuple(int(i) for i in match.group("release").split(".")),
+            pre=_parse_letter_version(
+                match.group("pre_l"),
+                match.group("pre_n"),
+            ),
+            post=_parse_letter_version(
+                match.group("post_l"),
+                match.group("post_n1") or match.group("post_n2"),
+            ),
+            dev=_parse_letter_version(
+                match.group("dev_l"),
+                match.group("dev_n"),
+            ),
+            local=_parse_local_version(match.group("local")),
+        )
+
+        # Generate a key which will be used for sorting
+        self._key = _cmpkey(
+            self._version.epoch,
+            self._version.release,
+            self._version.pre,
+            self._version.post,
+            self._version.dev,
+            self._version.local,
+        )
+
+    def __repr__(self):
+        return "".format(repr(str(self)))
+
+    def __str__(self):
+        parts = []
+
+        # Epoch
+        if self._version.epoch != 0:
+            parts.append("{0}!".format(self._version.epoch))
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self._version.release))
+
+        # Pre-release
+        if self._version.pre is not None:
+            parts.append("".join(str(x) for x in self._version.pre))
+
+        # Post-release
+        if self._version.post is not None:
+            parts.append(".post{0}".format(self._version.post[1]))
+
+        # Development release
+        if self._version.dev is not None:
+            parts.append(".dev{0}".format(self._version.dev[1]))
+
+        # Local version segment
+        if self._version.local is not None:
+            parts.append(
+                "+{0}".format(".".join(str(x) for x in self._version.local))
+            )
+
+        return "".join(parts)
+
+    @property
+    def public(self):
+        return str(self).split("+", 1)[0]
+
+    @property
+    def base_version(self):
+        parts = []
+
+        # Epoch
+        if self._version.epoch != 0:
+            parts.append("{0}!".format(self._version.epoch))
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self._version.release))
+
+        return "".join(parts)
+
+    @property
+    def local(self):
+        version_string = str(self)
+        if "+" in version_string:
+            return version_string.split("+", 1)[1]
+
+    @property
+    def is_prerelease(self):
+        return bool(self._version.dev or self._version.pre)
+
+    @property
+    def is_postrelease(self):
+        return bool(self._version.post)
+
+
+def _parse_letter_version(letter, number):
+    if letter:
+        # We consider there to be an implicit 0 in a pre-release if there is
+        # not a numeral associated with it.
+        if number is None:
+            number = 0
+
+        # We normalize any letters to their lower case form
+        letter = letter.lower()
+
+        # We consider some words to be alternate spellings of other words and
+        # in those cases we want to normalize the spellings to our preferred
+        # spelling.
+        if letter == "alpha":
+            letter = "a"
+        elif letter == "beta":
+            letter = "b"
+        elif letter in ["c", "pre", "preview"]:
+            letter = "rc"
+        elif letter in ["rev", "r"]:
+            letter = "post"
+
+        return letter, int(number)
+    if not letter and number:
+        # We assume if we are given a number, but we are not given a letter
+        # then this is using the implicit post release syntax (e.g. 1.0-1)
+        letter = "post"
+
+        return letter, int(number)
+
+
+_local_version_seperators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local):
+    """
+    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+    """
+    if local is not None:
+        return tuple(
+            part.lower() if not part.isdigit() else int(part)
+            for part in _local_version_seperators.split(local)
+        )
+
+
+def _cmpkey(epoch, release, pre, post, dev, local):
+    # When we compare a release version, we want to compare it with all of the
+    # trailing zeros removed. So we'll use a reverse the list, drop all the now
+    # leading zeros until we come to something non zero, then take the rest
+    # re-reverse it back into the correct order and make it a tuple and use
+    # that for our sorting key.
+    release = tuple(
+        reversed(list(
+            itertools.dropwhile(
+                lambda x: x == 0,
+                reversed(release),
+            )
+        ))
+    )
+
+    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+    # We'll do this by abusing the pre segment, but we _only_ want to do this
+    # if there is not a pre or a post segment. If we have one of those then
+    # the normal sorting rules will handle this case correctly.
+    if pre is None and post is None and dev is not None:
+        pre = -Infinity
+    # Versions without a pre-release (except as noted above) should sort after
+    # those with one.
+    elif pre is None:
+        pre = Infinity
+
+    # Versions without a post segment should sort before those with one.
+    if post is None:
+        post = -Infinity
+
+    # Versions without a development segment should sort after those with one.
+    if dev is None:
+        dev = Infinity
+
+    if local is None:
+        # Versions without a local segment should sort before those with one.
+        local = -Infinity
+    else:
+        # Versions with a local segment need that segment parsed to implement
+        # the sorting rules in PEP440.
+        # - Alpha numeric segments sort before numeric segments
+        # - Alpha numeric segments sort lexicographically
+        # - Numeric segments sort numerically
+        # - Shorter versions sort before longer versions when the prefixes
+        #   match exactly
+        local = tuple(
+            (i, "") if isinstance(i, int) else (-Infinity, i)
+            for i in local
+        )
+
+    return epoch, release, pre, post, dev, local
diff --git a/lib/pkg_resources/_vendor/pyparsing.py b/lib/pkg_resources/_vendor/pyparsing.py
new file mode 100644
index 0000000..4aa30ee
--- /dev/null
+++ b/lib/pkg_resources/_vendor/pyparsing.py
@@ -0,0 +1,5742 @@
+# module pyparsing.py
+#
+# Copyright (c) 2003-2018  Paul T. McGuire
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__doc__ = \
+"""
+pyparsing module - Classes and methods to define and execute parsing grammars
+=============================================================================
+
+The pyparsing module is an alternative approach to creating and executing simple grammars,
+vs. the traditional lex/yacc approach, or the use of regular expressions.  With pyparsing, you
+don't need to learn a new syntax for defining grammars or matching expressions - the parsing module
+provides a library of classes that you use to construct the grammar directly in Python.
+
+Here is a program to parse "Hello, World!" (or any greeting of the form 
+C{", !"}), built up using L{Word}, L{Literal}, and L{And} elements 
+(L{'+'} operator gives L{And} expressions, strings are auto-converted to
+L{Literal} expressions)::
+
+    from pyparsing import Word, alphas
+
+    # define grammar of a greeting
+    greet = Word(alphas) + "," + Word(alphas) + "!"
+
+    hello = "Hello, World!"
+    print (hello, "->", greet.parseString(hello))
+
+The program outputs the following::
+
+    Hello, World! -> ['Hello', ',', 'World', '!']
+
+The Python representation of the grammar is quite readable, owing to the self-explanatory
+class names, and the use of '+', '|' and '^' operators.
+
+The L{ParseResults} object returned from L{ParserElement.parseString} can be accessed as a nested list, a dictionary, or an
+object with named attributes.
+
+The pyparsing module handles some of the problems that are typically vexing when writing text parsers:
+ - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello  ,  World  !", etc.)
+ - quoted strings
+ - embedded comments
+
+
+Getting Started -
+-----------------
+Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing
+classes inherit from. Use the docstrings for examples of how to:
+ - construct literal match expressions from L{Literal} and L{CaselessLiteral} classes
+ - construct character word-group expressions using the L{Word} class
+ - see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes
+ - use L{'+'}, L{'|'}, L{'^'}, and L{'&'} operators to combine simple expressions into more complex ones
+ - associate names with your parsed results using L{ParserElement.setResultsName}
+ - find some helpful expression short-cuts like L{delimitedList} and L{oneOf}
+ - find more useful common expressions in the L{pyparsing_common} namespace class
+"""
+
+__version__ = "2.2.1"
+__versionTime__ = "18 Sep 2018 00:49 UTC"
+__author__ = "Paul McGuire "
+
+import string
+from weakref import ref as wkref
+import copy
+import sys
+import warnings
+import re
+import sre_constants
+import collections
+import pprint
+import traceback
+import types
+from datetime import datetime
+
+try:
+    from _thread import RLock
+except ImportError:
+    from threading import RLock
+
+try:
+    # Python 3
+    from collections.abc import Iterable
+    from collections.abc import MutableMapping
+except ImportError:
+    # Python 2.7
+    from collections import Iterable
+    from collections import MutableMapping
+
+try:
+    from collections import OrderedDict as _OrderedDict
+except ImportError:
+    try:
+        from ordereddict import OrderedDict as _OrderedDict
+    except ImportError:
+        _OrderedDict = None
+
+#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) )
+
+__all__ = [
+'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty',
+'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal',
+'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or',
+'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException',
+'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException',
+'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', 
+'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore',
+'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col',
+'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString',
+'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums',
+'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno',
+'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral',
+'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables',
+'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', 
+'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd',
+'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute',
+'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass',
+'CloseMatch', 'tokenMap', 'pyparsing_common',
+]
+
+system_version = tuple(sys.version_info)[:3]
+PY_3 = system_version[0] == 3
+if PY_3:
+    _MAX_INT = sys.maxsize
+    basestring = str
+    unichr = chr
+    _ustr = str
+
+    # build list of single arg builtins, that can be used as parse actions
+    singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max]
+
+else:
+    _MAX_INT = sys.maxint
+    range = xrange
+
+    def _ustr(obj):
+        """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries
+           str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It
+           then < returns the unicode object | encodes it with the default encoding | ... >.
+        """
+        if isinstance(obj,unicode):
+            return obj
+
+        try:
+            # If this works, then _ustr(obj) has the same behaviour as str(obj), so
+            # it won't break any existing code.
+            return str(obj)
+
+        except UnicodeEncodeError:
+            # Else encode it
+            ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace')
+            xmlcharref = Regex(r'&#\d+;')
+            xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:])
+            return xmlcharref.transformString(ret)
+
+    # build list of single arg builtins, tolerant of Python version, that can be used as parse actions
+    singleArgBuiltins = []
+    import __builtin__
+    for fname in "sum len sorted reversed list tuple set any all min max".split():
+        try:
+            singleArgBuiltins.append(getattr(__builtin__,fname))
+        except AttributeError:
+            continue
+            
+_generatorType = type((y for y in range(1)))
+ 
+def _xml_escape(data):
+    """Escape &, <, >, ", ', etc. in a string of data."""
+
+    # ampersand must be replaced first
+    from_symbols = '&><"\''
+    to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split())
+    for from_,to_ in zip(from_symbols, to_symbols):
+        data = data.replace(from_, to_)
+    return data
+
+class _Constants(object):
+    pass
+
+alphas     = string.ascii_uppercase + string.ascii_lowercase
+nums       = "0123456789"
+hexnums    = nums + "ABCDEFabcdef"
+alphanums  = alphas + nums
+_bslash    = chr(92)
+printables = "".join(c for c in string.printable if c not in string.whitespace)
+
+class ParseBaseException(Exception):
+    """base exception class for all parsing runtime exceptions"""
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, pstr, loc=0, msg=None, elem=None ):
+        self.loc = loc
+        if msg is None:
+            self.msg = pstr
+            self.pstr = ""
+        else:
+            self.msg = msg
+            self.pstr = pstr
+        self.parserElement = elem
+        self.args = (pstr, loc, msg)
+
+    @classmethod
+    def _from_exception(cls, pe):
+        """
+        internal factory method to simplify creating one type of ParseException 
+        from another - avoids having __init__ signature conflicts among subclasses
+        """
+        return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
+
+    def __getattr__( self, aname ):
+        """supported attributes by name are:
+            - lineno - returns the line number of the exception text
+            - col - returns the column number of the exception text
+            - line - returns the line containing the exception text
+        """
+        if( aname == "lineno" ):
+            return lineno( self.loc, self.pstr )
+        elif( aname in ("col", "column") ):
+            return col( self.loc, self.pstr )
+        elif( aname == "line" ):
+            return line( self.loc, self.pstr )
+        else:
+            raise AttributeError(aname)
+
+    def __str__( self ):
+        return "%s (at char %d), (line:%d, col:%d)" % \
+                ( self.msg, self.loc, self.lineno, self.column )
+    def __repr__( self ):
+        return _ustr(self)
+    def markInputline( self, markerString = ">!<" ):
+        """Extracts the exception line from the input string, and marks
+           the location of the exception with a special symbol.
+        """
+        line_str = self.line
+        line_column = self.column - 1
+        if markerString:
+            line_str = "".join((line_str[:line_column],
+                                markerString, line_str[line_column:]))
+        return line_str.strip()
+    def __dir__(self):
+        return "lineno col line".split() + dir(type(self))
+
+class ParseException(ParseBaseException):
+    """
+    Exception thrown when parse expressions don't match class;
+    supported attributes by name are:
+     - lineno - returns the line number of the exception text
+     - col - returns the column number of the exception text
+     - line - returns the line containing the exception text
+        
+    Example::
+        try:
+            Word(nums).setName("integer").parseString("ABC")
+        except ParseException as pe:
+            print(pe)
+            print("column: {}".format(pe.col))
+            
+    prints::
+       Expected integer (at char 0), (line:1, col:1)
+        column: 1
+    """
+    pass
+
+class ParseFatalException(ParseBaseException):
+    """user-throwable exception thrown when inconsistent parse content
+       is found; stops all parsing immediately"""
+    pass
+
+class ParseSyntaxException(ParseFatalException):
+    """just like L{ParseFatalException}, but thrown internally when an
+       L{ErrorStop} ('-' operator) indicates that parsing is to stop 
+       immediately because an unbacktrackable syntax error has been found"""
+    pass
+
+#~ class ReparseException(ParseBaseException):
+    #~ """Experimental class - parse actions can raise this exception to cause
+       #~ pyparsing to reparse the input string:
+        #~ - with a modified input string, and/or
+        #~ - with a modified start location
+       #~ Set the values of the ReparseException in the constructor, and raise the
+       #~ exception in a parse action to cause pyparsing to use the new string/location.
+       #~ Setting the values as None causes no change to be made.
+       #~ """
+    #~ def __init_( self, newstring, restartLoc ):
+        #~ self.newParseText = newstring
+        #~ self.reparseLoc = restartLoc
+
+class RecursiveGrammarException(Exception):
+    """exception thrown by L{ParserElement.validate} if the grammar could be improperly recursive"""
+    def __init__( self, parseElementList ):
+        self.parseElementTrace = parseElementList
+
+    def __str__( self ):
+        return "RecursiveGrammarException: %s" % self.parseElementTrace
+
+class _ParseResultsWithOffset(object):
+    def __init__(self,p1,p2):
+        self.tup = (p1,p2)
+    def __getitem__(self,i):
+        return self.tup[i]
+    def __repr__(self):
+        return repr(self.tup[0])
+    def setOffset(self,i):
+        self.tup = (self.tup[0],i)
+
+class ParseResults(object):
+    """
+    Structured parse results, to provide multiple means of access to the parsed data:
+       - as a list (C{len(results)})
+       - by list index (C{results[0], results[1]}, etc.)
+       - by attribute (C{results.} - see L{ParserElement.setResultsName})
+
+    Example::
+        integer = Word(nums)
+        date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+        # equivalent form:
+        # date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+
+        # parseString returns a ParseResults object
+        result = date_str.parseString("1999/12/31")
+
+        def test(s, fn=repr):
+            print("%s -> %s" % (s, fn(eval(s))))
+        test("list(result)")
+        test("result[0]")
+        test("result['month']")
+        test("result.day")
+        test("'month' in result")
+        test("'minutes' in result")
+        test("result.dump()", str)
+    prints::
+        list(result) -> ['1999', '/', '12', '/', '31']
+        result[0] -> '1999'
+        result['month'] -> '12'
+        result.day -> '31'
+        'month' in result -> True
+        'minutes' in result -> False
+        result.dump() -> ['1999', '/', '12', '/', '31']
+        - day: 31
+        - month: 12
+        - year: 1999
+    """
+    def __new__(cls, toklist=None, name=None, asList=True, modal=True ):
+        if isinstance(toklist, cls):
+            return toklist
+        retobj = object.__new__(cls)
+        retobj.__doinit = True
+        return retobj
+
+    # Performance tuning: we construct a *lot* of these, so keep this
+    # constructor as small and fast as possible
+    def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ):
+        if self.__doinit:
+            self.__doinit = False
+            self.__name = None
+            self.__parent = None
+            self.__accumNames = {}
+            self.__asList = asList
+            self.__modal = modal
+            if toklist is None:
+                toklist = []
+            if isinstance(toklist, list):
+                self.__toklist = toklist[:]
+            elif isinstance(toklist, _generatorType):
+                self.__toklist = list(toklist)
+            else:
+                self.__toklist = [toklist]
+            self.__tokdict = dict()
+
+        if name is not None and name:
+            if not modal:
+                self.__accumNames[name] = 0
+            if isinstance(name,int):
+                name = _ustr(name) # will always return a str, but use _ustr for consistency
+            self.__name = name
+            if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])):
+                if isinstance(toklist,basestring):
+                    toklist = [ toklist ]
+                if asList:
+                    if isinstance(toklist,ParseResults):
+                        self[name] = _ParseResultsWithOffset(toklist.copy(),0)
+                    else:
+                        self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0)
+                    self[name].__name = name
+                else:
+                    try:
+                        self[name] = toklist[0]
+                    except (KeyError,TypeError,IndexError):
+                        self[name] = toklist
+
+    def __getitem__( self, i ):
+        if isinstance( i, (int,slice) ):
+            return self.__toklist[i]
+        else:
+            if i not in self.__accumNames:
+                return self.__tokdict[i][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[i] ])
+
+    def __setitem__( self, k, v, isinstance=isinstance ):
+        if isinstance(v,_ParseResultsWithOffset):
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [v]
+            sub = v[0]
+        elif isinstance(k,(int,slice)):
+            self.__toklist[k] = v
+            sub = v
+        else:
+            self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)]
+            sub = v
+        if isinstance(sub,ParseResults):
+            sub.__parent = wkref(self)
+
+    def __delitem__( self, i ):
+        if isinstance(i,(int,slice)):
+            mylen = len( self.__toklist )
+            del self.__toklist[i]
+
+            # convert int to slice
+            if isinstance(i, int):
+                if i < 0:
+                    i += mylen
+                i = slice(i, i+1)
+            # get removed indices
+            removed = list(range(*i.indices(mylen)))
+            removed.reverse()
+            # fixup indices in token dictionary
+            for name,occurrences in self.__tokdict.items():
+                for j in removed:
+                    for k, (value, position) in enumerate(occurrences):
+                        occurrences[k] = _ParseResultsWithOffset(value, position - (position > j))
+        else:
+            del self.__tokdict[i]
+
+    def __contains__( self, k ):
+        return k in self.__tokdict
+
+    def __len__( self ): return len( self.__toklist )
+    def __bool__(self): return ( not not self.__toklist )
+    __nonzero__ = __bool__
+    def __iter__( self ): return iter( self.__toklist )
+    def __reversed__( self ): return iter( self.__toklist[::-1] )
+    def _iterkeys( self ):
+        if hasattr(self.__tokdict, "iterkeys"):
+            return self.__tokdict.iterkeys()
+        else:
+            return iter(self.__tokdict)
+
+    def _itervalues( self ):
+        return (self[k] for k in self._iterkeys())
+            
+    def _iteritems( self ):
+        return ((k, self[k]) for k in self._iterkeys())
+
+    if PY_3:
+        keys = _iterkeys       
+        """Returns an iterator of all named result keys (Python 3.x only)."""
+
+        values = _itervalues
+        """Returns an iterator of all named result values (Python 3.x only)."""
+
+        items = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 3.x only)."""
+
+    else:
+        iterkeys = _iterkeys
+        """Returns an iterator of all named result keys (Python 2.x only)."""
+
+        itervalues = _itervalues
+        """Returns an iterator of all named result values (Python 2.x only)."""
+
+        iteritems = _iteritems
+        """Returns an iterator of all named result key-value tuples (Python 2.x only)."""
+
+        def keys( self ):
+            """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iterkeys())
+
+        def values( self ):
+            """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.itervalues())
+                
+        def items( self ):
+            """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x)."""
+            return list(self.iteritems())
+
+    def haskeys( self ):
+        """Since keys() returns an iterator, this method is helpful in bypassing
+           code that looks for the existence of any defined results names."""
+        return bool(self.__tokdict)
+        
+    def pop( self, *args, **kwargs):
+        """
+        Removes and returns item at specified index (default=C{last}).
+        Supports both C{list} and C{dict} semantics for C{pop()}. If passed no
+        argument or an integer argument, it will use C{list} semantics
+        and pop tokens from the list of parsed tokens. If passed a 
+        non-integer argument (most likely a string), it will use C{dict}
+        semantics and pop the corresponding value from any defined 
+        results names. A second default return value argument is 
+        supported, just as in C{dict.pop()}.
+
+        Example::
+            def remove_first(tokens):
+                tokens.pop(0)
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321']
+
+            label = Word(alphas)
+            patt = label("LABEL") + OneOrMore(Word(nums))
+            print(patt.parseString("AAB 123 321").dump())
+
+            # Use pop() in a parse action to remove named result (note that corresponding value is not
+            # removed from list form of results)
+            def remove_LABEL(tokens):
+                tokens.pop("LABEL")
+                return tokens
+            patt.addParseAction(remove_LABEL)
+            print(patt.parseString("AAB 123 321").dump())
+        prints::
+            ['AAB', '123', '321']
+            - LABEL: AAB
+
+            ['AAB', '123', '321']
+        """
+        if not args:
+            args = [-1]
+        for k,v in kwargs.items():
+            if k == 'default':
+                args = (args[0], v)
+            else:
+                raise TypeError("pop() got an unexpected keyword argument '%s'" % k)
+        if (isinstance(args[0], int) or 
+                        len(args) == 1 or 
+                        args[0] in self):
+            index = args[0]
+            ret = self[index]
+            del self[index]
+            return ret
+        else:
+            defaultvalue = args[1]
+            return defaultvalue
+
+    def get(self, key, defaultValue=None):
+        """
+        Returns named result matching the given key, or if there is no
+        such name, then returns the given C{defaultValue} or C{None} if no
+        C{defaultValue} is specified.
+
+        Similar to C{dict.get()}.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            result = date_str.parseString("1999/12/31")
+            print(result.get("year")) # -> '1999'
+            print(result.get("hour", "not specified")) # -> 'not specified'
+            print(result.get("hour")) # -> None
+        """
+        if key in self:
+            return self[key]
+        else:
+            return defaultValue
+
+    def insert( self, index, insStr ):
+        """
+        Inserts new element at location index in the list of parsed tokens.
+        
+        Similar to C{list.insert()}.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+
+            # use a parse action to insert the parse location in the front of the parsed results
+            def insert_locn(locn, tokens):
+                tokens.insert(0, locn)
+            print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321']
+        """
+        self.__toklist.insert(index, insStr)
+        # fixup indices in token dictionary
+        for name,occurrences in self.__tokdict.items():
+            for k, (value, position) in enumerate(occurrences):
+                occurrences[k] = _ParseResultsWithOffset(value, position + (position > index))
+
+    def append( self, item ):
+        """
+        Add single element to end of ParseResults list of elements.
+
+        Example::
+            print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321']
+            
+            # use a parse action to compute the sum of the parsed integers, and add it to the end
+            def append_sum(tokens):
+                tokens.append(sum(map(int, tokens)))
+            print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444]
+        """
+        self.__toklist.append(item)
+
+    def extend( self, itemseq ):
+        """
+        Add sequence of elements to end of ParseResults list of elements.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            
+            # use a parse action to append the reverse of the matched strings, to make a palindrome
+            def make_palindrome(tokens):
+                tokens.extend(reversed([t[::-1] for t in tokens]))
+                return ''.join(tokens)
+            print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl'
+        """
+        if isinstance(itemseq, ParseResults):
+            self += itemseq
+        else:
+            self.__toklist.extend(itemseq)
+
+    def clear( self ):
+        """
+        Clear all elements and results names.
+        """
+        del self.__toklist[:]
+        self.__tokdict.clear()
+
+    def __getattr__( self, name ):
+        try:
+            return self[name]
+        except KeyError:
+            return ""
+            
+        if name in self.__tokdict:
+            if name not in self.__accumNames:
+                return self.__tokdict[name][-1][0]
+            else:
+                return ParseResults([ v[0] for v in self.__tokdict[name] ])
+        else:
+            return ""
+
+    def __add__( self, other ):
+        ret = self.copy()
+        ret += other
+        return ret
+
+    def __iadd__( self, other ):
+        if other.__tokdict:
+            offset = len(self.__toklist)
+            addoffset = lambda a: offset if a<0 else a+offset
+            otheritems = other.__tokdict.items()
+            otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) )
+                                for (k,vlist) in otheritems for v in vlist]
+            for k,v in otherdictitems:
+                self[k] = v
+                if isinstance(v[0],ParseResults):
+                    v[0].__parent = wkref(self)
+            
+        self.__toklist += other.__toklist
+        self.__accumNames.update( other.__accumNames )
+        return self
+
+    def __radd__(self, other):
+        if isinstance(other,int) and other == 0:
+            # useful for merging many ParseResults using sum() builtin
+            return self.copy()
+        else:
+            # this may raise a TypeError - so be it
+            return other + self
+        
+    def __repr__( self ):
+        return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) )
+
+    def __str__( self ):
+        return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']'
+
+    def _asStringList( self, sep='' ):
+        out = []
+        for item in self.__toklist:
+            if out and sep:
+                out.append(sep)
+            if isinstance( item, ParseResults ):
+                out += item._asStringList()
+            else:
+                out.append( _ustr(item) )
+        return out
+
+    def asList( self ):
+        """
+        Returns the parse results as a nested list of matching tokens, all converted to strings.
+
+        Example::
+            patt = OneOrMore(Word(alphas))
+            result = patt.parseString("sldkj lsdkj sldkj")
+            # even though the result prints in string-like form, it is actually a pyparsing ParseResults
+            print(type(result), result) # ->  ['sldkj', 'lsdkj', 'sldkj']
+            
+            # Use asList() to create an actual list
+            result_list = result.asList()
+            print(type(result_list), result_list) # ->  ['sldkj', 'lsdkj', 'sldkj']
+        """
+        return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist]
+
+    def asDict( self ):
+        """
+        Returns the named parse results as a nested dictionary.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(type(result), repr(result)) # ->  (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]})
+            
+            result_dict = result.asDict()
+            print(type(result_dict), repr(result_dict)) # ->  {'day': '1999', 'year': '12', 'month': '31'}
+
+            # even though a ParseResults supports dict-like access, sometime you just need to have a dict
+            import json
+            print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable
+            print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"}
+        """
+        if PY_3:
+            item_fn = self.items
+        else:
+            item_fn = self.iteritems
+            
+        def toItem(obj):
+            if isinstance(obj, ParseResults):
+                if obj.haskeys():
+                    return obj.asDict()
+                else:
+                    return [toItem(v) for v in obj]
+            else:
+                return obj
+                
+        return dict((k,toItem(v)) for k,v in item_fn())
+
+    def copy( self ):
+        """
+        Returns a new copy of a C{ParseResults} object.
+        """
+        ret = ParseResults( self.__toklist )
+        ret.__tokdict = self.__tokdict.copy()
+        ret.__parent = self.__parent
+        ret.__accumNames.update( self.__accumNames )
+        ret.__name = self.__name
+        return ret
+
+    def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ):
+        """
+        (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names.
+        """
+        nl = "\n"
+        out = []
+        namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items()
+                                                            for v in vlist)
+        nextLevelIndent = indent + "  "
+
+        # collapse out indents if formatting is not desired
+        if not formatted:
+            indent = ""
+            nextLevelIndent = ""
+            nl = ""
+
+        selfTag = None
+        if doctag is not None:
+            selfTag = doctag
+        else:
+            if self.__name:
+                selfTag = self.__name
+
+        if not selfTag:
+            if namedItemsOnly:
+                return ""
+            else:
+                selfTag = "ITEM"
+
+        out += [ nl, indent, "<", selfTag, ">" ]
+
+        for i,res in enumerate(self.__toklist):
+            if isinstance(res,ParseResults):
+                if i in namedItems:
+                    out += [ res.asXML(namedItems[i],
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+                else:
+                    out += [ res.asXML(None,
+                                        namedItemsOnly and doctag is None,
+                                        nextLevelIndent,
+                                        formatted)]
+            else:
+                # individual token, see if there is a name for it
+                resTag = None
+                if i in namedItems:
+                    resTag = namedItems[i]
+                if not resTag:
+                    if namedItemsOnly:
+                        continue
+                    else:
+                        resTag = "ITEM"
+                xmlBodyText = _xml_escape(_ustr(res))
+                out += [ nl, nextLevelIndent, "<", resTag, ">",
+                                                xmlBodyText,
+                                                "" ]
+
+        out += [ nl, indent, "" ]
+        return "".join(out)
+
+    def __lookup(self,sub):
+        for k,vlist in self.__tokdict.items():
+            for v,loc in vlist:
+                if sub is v:
+                    return k
+        return None
+
+    def getName(self):
+        r"""
+        Returns the results name for this token expression. Useful when several 
+        different expressions might match at a particular location.
+
+        Example::
+            integer = Word(nums)
+            ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d")
+            house_number_expr = Suppress('#') + Word(nums, alphanums)
+            user_data = (Group(house_number_expr)("house_number") 
+                        | Group(ssn_expr)("ssn")
+                        | Group(integer)("age"))
+            user_info = OneOrMore(user_data)
+            
+            result = user_info.parseString("22 111-22-3333 #221B")
+            for item in result:
+                print(item.getName(), ':', item[0])
+        prints::
+            age : 22
+            ssn : 111-22-3333
+            house_number : 221B
+        """
+        if self.__name:
+            return self.__name
+        elif self.__parent:
+            par = self.__parent()
+            if par:
+                return par.__lookup(self)
+            else:
+                return None
+        elif (len(self) == 1 and
+               len(self.__tokdict) == 1 and
+               next(iter(self.__tokdict.values()))[0][1] in (0,-1)):
+            return next(iter(self.__tokdict.keys()))
+        else:
+            return None
+
+    def dump(self, indent='', depth=0, full=True):
+        """
+        Diagnostic method for listing out the contents of a C{ParseResults}.
+        Accepts an optional C{indent} argument so that this string can be embedded
+        in a nested display of other data.
+
+        Example::
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+            
+            result = date_str.parseString('12/31/1999')
+            print(result.dump())
+        prints::
+            ['12', '/', '31', '/', '1999']
+            - day: 1999
+            - month: 31
+            - year: 12
+        """
+        out = []
+        NL = '\n'
+        out.append( indent+_ustr(self.asList()) )
+        if full:
+            if self.haskeys():
+                items = sorted((str(k), v) for k,v in self.items())
+                for k,v in items:
+                    if out:
+                        out.append(NL)
+                    out.append( "%s%s- %s: " % (indent,('  '*depth), k) )
+                    if isinstance(v,ParseResults):
+                        if v:
+                            out.append( v.dump(indent,depth+1) )
+                        else:
+                            out.append(_ustr(v))
+                    else:
+                        out.append(repr(v))
+            elif any(isinstance(vv,ParseResults) for vv in self):
+                v = self
+                for i,vv in enumerate(v):
+                    if isinstance(vv,ParseResults):
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),vv.dump(indent,depth+1) ))
+                    else:
+                        out.append("\n%s%s[%d]:\n%s%s%s" % (indent,('  '*(depth)),i,indent,('  '*(depth+1)),_ustr(vv)))
+            
+        return "".join(out)
+
+    def pprint(self, *args, **kwargs):
+        """
+        Pretty-printer for parsed results as a list, using the C{pprint} module.
+        Accepts additional positional or keyword args as defined for the 
+        C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint})
+
+        Example::
+            ident = Word(alphas, alphanums)
+            num = Word(nums)
+            func = Forward()
+            term = ident | num | Group('(' + func + ')')
+            func <<= ident + Group(Optional(delimitedList(term)))
+            result = func.parseString("fna a,b,(fnb c,d,200),100")
+            result.pprint(width=40)
+        prints::
+            ['fna',
+             ['a',
+              'b',
+              ['(', 'fnb', ['c', 'd', '200'], ')'],
+              '100']]
+        """
+        pprint.pprint(self.asList(), *args, **kwargs)
+
+    # add support for pickle protocol
+    def __getstate__(self):
+        return ( self.__toklist,
+                 ( self.__tokdict.copy(),
+                   self.__parent is not None and self.__parent() or None,
+                   self.__accumNames,
+                   self.__name ) )
+
+    def __setstate__(self,state):
+        self.__toklist = state[0]
+        (self.__tokdict,
+         par,
+         inAccumNames,
+         self.__name) = state[1]
+        self.__accumNames = {}
+        self.__accumNames.update(inAccumNames)
+        if par is not None:
+            self.__parent = wkref(par)
+        else:
+            self.__parent = None
+
+    def __getnewargs__(self):
+        return self.__toklist, self.__name, self.__asList, self.__modal
+
+    def __dir__(self):
+        return (dir(type(self)) + list(self.keys()))
+
+MutableMapping.register(ParseResults)
+
+def col (loc,strg):
+    """Returns current column within a string, counting newlines as line separators.
+   The first column is number 1.
+
+   Note: the default parsing behavior is to expand tabs in the input string
+   before starting the parsing process.  See L{I{ParserElement.parseString}} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    s = strg
+    return 1 if 0} for more information
+   on parsing strings containing C{}s, and suggested methods to maintain a
+   consistent view of the parsed string, the parse location, and line and column
+   positions within the parsed string.
+   """
+    return strg.count("\n",0,loc) + 1
+
+def line( loc, strg ):
+    """Returns the line of text containing loc within a string, counting newlines as line separators.
+       """
+    lastCR = strg.rfind("\n", 0, loc)
+    nextCR = strg.find("\n", loc)
+    if nextCR >= 0:
+        return strg[lastCR+1:nextCR]
+    else:
+        return strg[lastCR+1:]
+
+def _defaultStartDebugAction( instring, loc, expr ):
+    print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )))
+
+def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ):
+    print ("Matched " + _ustr(expr) + " -> " + str(toks.asList()))
+
+def _defaultExceptionDebugAction( instring, loc, expr, exc ):
+    print ("Exception raised:" + _ustr(exc))
+
+def nullDebugAction(*args):
+    """'Do-nothing' debug action, to suppress debugging output during parsing."""
+    pass
+
+# Only works on Python 3.x - nonlocal is toxic to Python 2 installs
+#~ 'decorator to trim function calls to match the arity of the target'
+#~ def _trim_arity(func, maxargs=3):
+    #~ if func in singleArgBuiltins:
+        #~ return lambda s,l,t: func(t)
+    #~ limit = 0
+    #~ foundArity = False
+    #~ def wrapper(*args):
+        #~ nonlocal limit,foundArity
+        #~ while 1:
+            #~ try:
+                #~ ret = func(*args[limit:])
+                #~ foundArity = True
+                #~ return ret
+            #~ except TypeError:
+                #~ if limit == maxargs or foundArity:
+                    #~ raise
+                #~ limit += 1
+                #~ continue
+    #~ return wrapper
+
+# this version is Python 2.x-3.x cross-compatible
+'decorator to trim function calls to match the arity of the target'
+def _trim_arity(func, maxargs=2):
+    if func in singleArgBuiltins:
+        return lambda s,l,t: func(t)
+    limit = [0]
+    foundArity = [False]
+    
+    # traceback return data structure changed in Py3.5 - normalize back to plain tuples
+    if system_version[:2] >= (3,5):
+        def extract_stack(limit=0):
+            # special handling for Python 3.5.0 - extra deep call stack by 1
+            offset = -3 if system_version == (3,5,0) else -2
+            frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset]
+            return [frame_summary[:2]]
+        def extract_tb(tb, limit=0):
+            frames = traceback.extract_tb(tb, limit=limit)
+            frame_summary = frames[-1]
+            return [frame_summary[:2]]
+    else:
+        extract_stack = traceback.extract_stack
+        extract_tb = traceback.extract_tb
+    
+    # synthesize what would be returned by traceback.extract_stack at the call to 
+    # user's parse action 'func', so that we don't incur call penalty at parse time
+    
+    LINE_DIFF = 6
+    # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND 
+    # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!!
+    this_line = extract_stack(limit=2)[-1]
+    pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF)
+
+    def wrapper(*args):
+        while 1:
+            try:
+                ret = func(*args[limit[0]:])
+                foundArity[0] = True
+                return ret
+            except TypeError:
+                # re-raise TypeErrors if they did not come from our arity testing
+                if foundArity[0]:
+                    raise
+                else:
+                    try:
+                        tb = sys.exc_info()[-1]
+                        if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth:
+                            raise
+                    finally:
+                        del tb
+
+                if limit[0] <= maxargs:
+                    limit[0] += 1
+                    continue
+                raise
+
+    # copy func name to wrapper for sensible debug output
+    func_name = ""
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    wrapper.__name__ = func_name
+
+    return wrapper
+
+class ParserElement(object):
+    """Abstract base level parser element class."""
+    DEFAULT_WHITE_CHARS = " \n\t\r"
+    verbose_stacktrace = False
+
+    @staticmethod
+    def setDefaultWhitespaceChars( chars ):
+        r"""
+        Overrides the default whitespace chars
+
+        Example::
+            # default whitespace chars are space,  and newline
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def', 'ghi', 'jkl']
+            
+            # change to just treat newline as significant
+            ParserElement.setDefaultWhitespaceChars(" \t")
+            OneOrMore(Word(alphas)).parseString("abc def\nghi jkl")  # -> ['abc', 'def']
+        """
+        ParserElement.DEFAULT_WHITE_CHARS = chars
+
+    @staticmethod
+    def inlineLiteralsUsing(cls):
+        """
+        Set class to be used for inclusion of string literals into a parser.
+        
+        Example::
+            # default literal class used is Literal
+            integer = Word(nums)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+
+            # change to Suppress
+            ParserElement.inlineLiteralsUsing(Suppress)
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")           
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '12', '31']
+        """
+        ParserElement._literalStringClass = cls
+
+    def __init__( self, savelist=False ):
+        self.parseAction = list()
+        self.failAction = None
+        #~ self.name = ""  # don't define self.name, let subclasses try/except upcall
+        self.strRepr = None
+        self.resultsName = None
+        self.saveAsList = savelist
+        self.skipWhitespace = True
+        self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        self.copyDefaultWhiteChars = True
+        self.mayReturnEmpty = False # used when checking for left-recursion
+        self.keepTabs = False
+        self.ignoreExprs = list()
+        self.debug = False
+        self.streamlined = False
+        self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index
+        self.errmsg = ""
+        self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all)
+        self.debugActions = ( None, None, None ) #custom debug actions
+        self.re = None
+        self.callPreparse = True # used to avoid redundant calls to preParse
+        self.callDuringTry = False
+
+    def copy( self ):
+        """
+        Make a copy of this C{ParserElement}.  Useful for defining different parse actions
+        for the same parsing pattern, using copies of the original parse element.
+        
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K")
+            integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+            
+            print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M"))
+        prints::
+            [5120, 100, 655360, 268435456]
+        Equivalent form of C{expr.copy()} is just C{expr()}::
+            integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M")
+        """
+        cpy = copy.copy( self )
+        cpy.parseAction = self.parseAction[:]
+        cpy.ignoreExprs = self.ignoreExprs[:]
+        if self.copyDefaultWhiteChars:
+            cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS
+        return cpy
+
+    def setName( self, name ):
+        """
+        Define name for this expression, makes debugging and exception messages clearer.
+        
+        Example::
+            Word(nums).parseString("ABC")  # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1)
+            Word(nums).setName("integer").parseString("ABC")  # -> Exception: Expected integer (at char 0), (line:1, col:1)
+        """
+        self.name = name
+        self.errmsg = "Expected " + self.name
+        if hasattr(self,"exception"):
+            self.exception.msg = self.errmsg
+        return self
+
+    def setResultsName( self, name, listAllMatches=False ):
+        """
+        Define name for referencing matching tokens as a nested attribute
+        of the returned parse results.
+        NOTE: this returns a *copy* of the original C{ParserElement} object;
+        this is so that the client can define a basic element, such as an
+        integer, and reference it in multiple places with different names.
+
+        You can also set results names using the abbreviated syntax,
+        C{expr("name")} in place of C{expr.setResultsName("name")} - 
+        see L{I{__call__}<__call__>}.
+
+        Example::
+            date_str = (integer.setResultsName("year") + '/' 
+                        + integer.setResultsName("month") + '/' 
+                        + integer.setResultsName("day"))
+
+            # equivalent form:
+            date_str = integer("year") + '/' + integer("month") + '/' + integer("day")
+        """
+        newself = self.copy()
+        if name.endswith("*"):
+            name = name[:-1]
+            listAllMatches=True
+        newself.resultsName = name
+        newself.modalResults = not listAllMatches
+        return newself
+
+    def setBreak(self,breakFlag = True):
+        """Method to invoke the Python pdb debugger when this element is
+           about to be parsed. Set C{breakFlag} to True to enable, False to
+           disable.
+        """
+        if breakFlag:
+            _parseMethod = self._parse
+            def breaker(instring, loc, doActions=True, callPreParse=True):
+                import pdb
+                pdb.set_trace()
+                return _parseMethod( instring, loc, doActions, callPreParse )
+            breaker._originalParseMethod = _parseMethod
+            self._parse = breaker
+        else:
+            if hasattr(self._parse,"_originalParseMethod"):
+                self._parse = self._parse._originalParseMethod
+        return self
+
+    def setParseAction( self, *fns, **kwargs ):
+        """
+        Define one or more actions to perform when successfully matching parse element definition.
+        Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)},
+        C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where:
+         - s   = the original string being parsed (see note below)
+         - loc = the location of the matching substring
+         - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object
+        If the functions in fns modify the tokens, they can return them as the return
+        value from fn, and the modified list of tokens will replace the original.
+        Otherwise, fn does not need to return any value.
+
+        Optional keyword arguments:
+         - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing
+
+        Note: the default parsing behavior is to expand tabs in the input string
+        before starting the parsing process.  See L{I{parseString}} for more information
+        on parsing strings containing C{}s, and suggested methods to maintain a
+        consistent view of the parsed string, the parse location, and line and column
+        positions within the parsed string.
+        
+        Example::
+            integer = Word(nums)
+            date_str = integer + '/' + integer + '/' + integer
+
+            date_str.parseString("1999/12/31")  # -> ['1999', '/', '12', '/', '31']
+
+            # use parse action to convert to ints at parse time
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            date_str = integer + '/' + integer + '/' + integer
+
+            # note that integer fields are now ints, not strings
+            date_str.parseString("1999/12/31")  # -> [1999, '/', 12, '/', 31]
+        """
+        self.parseAction = list(map(_trim_arity, list(fns)))
+        self.callDuringTry = kwargs.get("callDuringTry", False)
+        return self
+
+    def addParseAction( self, *fns, **kwargs ):
+        """
+        Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}}.
+        
+        See examples in L{I{copy}}.
+        """
+        self.parseAction += list(map(_trim_arity, list(fns)))
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def addCondition(self, *fns, **kwargs):
+        """Add a boolean predicate function to expression's list of parse actions. See 
+        L{I{setParseAction}} for function call signatures. Unlike C{setParseAction}, 
+        functions passed to C{addCondition} need to return boolean success/fail of the condition.
+
+        Optional keyword arguments:
+         - message = define a custom message to be used in the raised exception
+         - fatal   = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException
+         
+        Example::
+            integer = Word(nums).setParseAction(lambda toks: int(toks[0]))
+            year_int = integer.copy()
+            year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later")
+            date_str = year_int + '/' + integer + '/' + integer
+
+            result = date_str.parseString("1999/12/31")  # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1)
+        """
+        msg = kwargs.get("message", "failed user-defined condition")
+        exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException
+        for fn in fns:
+            def pa(s,l,t):
+                if not bool(_trim_arity(fn)(s,l,t)):
+                    raise exc_type(s,l,msg)
+            self.parseAction.append(pa)
+        self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False)
+        return self
+
+    def setFailAction( self, fn ):
+        """Define action to perform if parsing fails at this expression.
+           Fail acton fn is a callable function that takes the arguments
+           C{fn(s,loc,expr,err)} where:
+            - s = string being parsed
+            - loc = location where expression match was attempted and failed
+            - expr = the parse expression that failed
+            - err = the exception thrown
+           The function returns no value.  It may throw C{L{ParseFatalException}}
+           if it is desired to stop parsing immediately."""
+        self.failAction = fn
+        return self
+
+    def _skipIgnorables( self, instring, loc ):
+        exprsFound = True
+        while exprsFound:
+            exprsFound = False
+            for e in self.ignoreExprs:
+                try:
+                    while 1:
+                        loc,dummy = e._parse( instring, loc )
+                        exprsFound = True
+                except ParseException:
+                    pass
+        return loc
+
+    def preParse( self, instring, loc ):
+        if self.ignoreExprs:
+            loc = self._skipIgnorables( instring, loc )
+
+        if self.skipWhitespace:
+            wt = self.whiteChars
+            instrlen = len(instring)
+            while loc < instrlen and instring[loc] in wt:
+                loc += 1
+
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        return loc, []
+
+    def postParse( self, instring, loc, tokenlist ):
+        return tokenlist
+
+    #~ @profile
+    def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ):
+        debugging = ( self.debug ) #and doActions )
+
+        if debugging or self.failAction:
+            #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))
+            if (self.debugActions[0] ):
+                self.debugActions[0]( instring, loc, self )
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            try:
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            except ParseBaseException as err:
+                #~ print ("Exception raised:", err)
+                if self.debugActions[2]:
+                    self.debugActions[2]( instring, tokensStart, self, err )
+                if self.failAction:
+                    self.failAction( instring, tokensStart, self, err )
+                raise
+        else:
+            if callPreParse and self.callPreparse:
+                preloc = self.preParse( instring, loc )
+            else:
+                preloc = loc
+            tokensStart = preloc
+            if self.mayIndexError or preloc >= len(instring):
+                try:
+                    loc,tokens = self.parseImpl( instring, preloc, doActions )
+                except IndexError:
+                    raise ParseException( instring, len(instring), self.errmsg, self )
+            else:
+                loc,tokens = self.parseImpl( instring, preloc, doActions )
+
+        tokens = self.postParse( instring, loc, tokens )
+
+        retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults )
+        if self.parseAction and (doActions or self.callDuringTry):
+            if debugging:
+                try:
+                    for fn in self.parseAction:
+                        tokens = fn( instring, tokensStart, retTokens )
+                        if tokens is not None:
+                            retTokens = ParseResults( tokens,
+                                                      self.resultsName,
+                                                      asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                      modal=self.modalResults )
+                except ParseBaseException as err:
+                    #~ print "Exception raised in user parse action:", err
+                    if (self.debugActions[2] ):
+                        self.debugActions[2]( instring, tokensStart, self, err )
+                    raise
+            else:
+                for fn in self.parseAction:
+                    tokens = fn( instring, tokensStart, retTokens )
+                    if tokens is not None:
+                        retTokens = ParseResults( tokens,
+                                                  self.resultsName,
+                                                  asList=self.saveAsList and isinstance(tokens,(ParseResults,list)),
+                                                  modal=self.modalResults )
+        if debugging:
+            #~ print ("Matched",self,"->",retTokens.asList())
+            if (self.debugActions[1] ):
+                self.debugActions[1]( instring, tokensStart, loc, self, retTokens )
+
+        return loc, retTokens
+
+    def tryParse( self, instring, loc ):
+        try:
+            return self._parse( instring, loc, doActions=False )[0]
+        except ParseFatalException:
+            raise ParseException( instring, loc, self.errmsg, self)
+    
+    def canParseNext(self, instring, loc):
+        try:
+            self.tryParse(instring, loc)
+        except (ParseException, IndexError):
+            return False
+        else:
+            return True
+
+    class _UnboundedCache(object):
+        def __init__(self):
+            cache = {}
+            self.not_in_cache = not_in_cache = object()
+
+            def get(self, key):
+                return cache.get(key, not_in_cache)
+
+            def set(self, key, value):
+                cache[key] = value
+
+            def clear(self):
+                cache.clear()
+                
+            def cache_len(self):
+                return len(cache)
+
+            self.get = types.MethodType(get, self)
+            self.set = types.MethodType(set, self)
+            self.clear = types.MethodType(clear, self)
+            self.__len__ = types.MethodType(cache_len, self)
+
+    if _OrderedDict is not None:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = _OrderedDict()
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(cache) > size:
+                        try:
+                            cache.popitem(False)
+                        except KeyError:
+                            pass
+
+                def clear(self):
+                    cache.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    else:
+        class _FifoCache(object):
+            def __init__(self, size):
+                self.not_in_cache = not_in_cache = object()
+
+                cache = {}
+                key_fifo = collections.deque([], size)
+
+                def get(self, key):
+                    return cache.get(key, not_in_cache)
+
+                def set(self, key, value):
+                    cache[key] = value
+                    while len(key_fifo) > size:
+                        cache.pop(key_fifo.popleft(), None)
+                    key_fifo.append(key)
+
+                def clear(self):
+                    cache.clear()
+                    key_fifo.clear()
+
+                def cache_len(self):
+                    return len(cache)
+
+                self.get = types.MethodType(get, self)
+                self.set = types.MethodType(set, self)
+                self.clear = types.MethodType(clear, self)
+                self.__len__ = types.MethodType(cache_len, self)
+
+    # argument cache for optimizing repeated calls when backtracking through recursive expressions
+    packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail
+    packrat_cache_lock = RLock()
+    packrat_cache_stats = [0, 0]
+
+    # this method gets repeatedly called during backtracking with the same arguments -
+    # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression
+    def _parseCache( self, instring, loc, doActions=True, callPreParse=True ):
+        HIT, MISS = 0, 1
+        lookup = (self, instring, loc, callPreParse, doActions)
+        with ParserElement.packrat_cache_lock:
+            cache = ParserElement.packrat_cache
+            value = cache.get(lookup)
+            if value is cache.not_in_cache:
+                ParserElement.packrat_cache_stats[MISS] += 1
+                try:
+                    value = self._parseNoCache(instring, loc, doActions, callPreParse)
+                except ParseBaseException as pe:
+                    # cache a copy of the exception, without the traceback
+                    cache.set(lookup, pe.__class__(*pe.args))
+                    raise
+                else:
+                    cache.set(lookup, (value[0], value[1].copy()))
+                    return value
+            else:
+                ParserElement.packrat_cache_stats[HIT] += 1
+                if isinstance(value, Exception):
+                    raise value
+                return (value[0], value[1].copy())
+
+    _parse = _parseNoCache
+
+    @staticmethod
+    def resetCache():
+        ParserElement.packrat_cache.clear()
+        ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats)
+
+    _packratEnabled = False
+    @staticmethod
+    def enablePackrat(cache_size_limit=128):
+        """Enables "packrat" parsing, which adds memoizing to the parsing logic.
+           Repeated parse attempts at the same string location (which happens
+           often in many complex grammars) can immediately return a cached value,
+           instead of re-executing parsing/validating code.  Memoizing is done of
+           both valid results and parsing exceptions.
+           
+           Parameters:
+            - cache_size_limit - (default=C{128}) - if an integer value is provided
+              will limit the size of the packrat cache; if None is passed, then
+              the cache size will be unbounded; if 0 is passed, the cache will
+              be effectively disabled.
+            
+           This speedup may break existing programs that use parse actions that
+           have side-effects.  For this reason, packrat parsing is disabled when
+           you first import pyparsing.  To activate the packrat feature, your
+           program must call the class method C{ParserElement.enablePackrat()}.  If
+           your program uses C{psyco} to "compile as you go", you must call
+           C{enablePackrat} before calling C{psyco.full()}.  If you do not do this,
+           Python will crash.  For best results, call C{enablePackrat()} immediately
+           after importing pyparsing.
+           
+           Example::
+               import pyparsing
+               pyparsing.ParserElement.enablePackrat()
+        """
+        if not ParserElement._packratEnabled:
+            ParserElement._packratEnabled = True
+            if cache_size_limit is None:
+                ParserElement.packrat_cache = ParserElement._UnboundedCache()
+            else:
+                ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit)
+            ParserElement._parse = ParserElement._parseCache
+
+    def parseString( self, instring, parseAll=False ):
+        """
+        Execute the parse expression with the given string.
+        This is the main interface to the client code, once the complete
+        expression has been built.
+
+        If you want the grammar to require that the entire input string be
+        successfully parsed, then set C{parseAll} to True (equivalent to ending
+        the grammar with C{L{StringEnd()}}).
+
+        Note: C{parseString} implicitly calls C{expandtabs()} on the input string,
+        in order to report proper column numbers in parse actions.
+        If the input string contains tabs and
+        the grammar uses parse actions that use the C{loc} argument to index into the
+        string being parsed, you can ensure you have a consistent view of the input
+        string by:
+         - calling C{parseWithTabs} on your grammar before calling C{parseString}
+           (see L{I{parseWithTabs}})
+         - define your parse action using the full C{(s,loc,toks)} signature, and
+           reference the input string using the parse action's C{s} argument
+         - explictly expand the tabs in your input string before calling
+           C{parseString}
+        
+        Example::
+            Word('a').parseString('aaaaabaaa')  # -> ['aaaaa']
+            Word('a').parseString('aaaaabaaa', parseAll=True)  # -> Exception: Expected end of text
+        """
+        ParserElement.resetCache()
+        if not self.streamlined:
+            self.streamline()
+            #~ self.saveAsList = True
+        for e in self.ignoreExprs:
+            e.streamline()
+        if not self.keepTabs:
+            instring = instring.expandtabs()
+        try:
+            loc, tokens = self._parse( instring, 0 )
+            if parseAll:
+                loc = self.preParse( instring, loc )
+                se = Empty() + StringEnd()
+                se._parse( instring, loc )
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+        else:
+            return tokens
+
+    def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ):
+        """
+        Scan the input string for expression matches.  Each match will return the
+        matching tokens, start location, and end location.  May be called with optional
+        C{maxMatches} argument, to clip scanning after 'n' matches are found.  If
+        C{overlap} is specified, then overlapping matches will be reported.
+
+        Note that the start and end locations are reported relative to the string
+        being parsed.  See L{I{parseString}} for more information on parsing
+        strings with embedded tabs.
+
+        Example::
+            source = "sldjf123lsdjjkf345sldkjf879lkjsfd987"
+            print(source)
+            for tokens,start,end in Word(alphas).scanString(source):
+                print(' '*start + '^'*(end-start))
+                print(' '*start + tokens[0])
+        
+        prints::
+        
+            sldjf123lsdjjkf345sldkjf879lkjsfd987
+            ^^^^^
+            sldjf
+                    ^^^^^^^
+                    lsdjjkf
+                              ^^^^^^
+                              sldkjf
+                                       ^^^^^^
+                                       lkjsfd
+        """
+        if not self.streamlined:
+            self.streamline()
+        for e in self.ignoreExprs:
+            e.streamline()
+
+        if not self.keepTabs:
+            instring = _ustr(instring).expandtabs()
+        instrlen = len(instring)
+        loc = 0
+        preparseFn = self.preParse
+        parseFn = self._parse
+        ParserElement.resetCache()
+        matches = 0
+        try:
+            while loc <= instrlen and matches < maxMatches:
+                try:
+                    preloc = preparseFn( instring, loc )
+                    nextLoc,tokens = parseFn( instring, preloc, callPreParse=False )
+                except ParseException:
+                    loc = preloc+1
+                else:
+                    if nextLoc > loc:
+                        matches += 1
+                        yield tokens, preloc, nextLoc
+                        if overlap:
+                            nextloc = preparseFn( instring, loc )
+                            if nextloc > loc:
+                                loc = nextLoc
+                            else:
+                                loc += 1
+                        else:
+                            loc = nextLoc
+                    else:
+                        loc = preloc+1
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def transformString( self, instring ):
+        """
+        Extension to C{L{scanString}}, to modify matching text with modified tokens that may
+        be returned from a parse action.  To use C{transformString}, define a grammar and
+        attach a parse action to it that modifies the returned token list.
+        Invoking C{transformString()} on a target string will then scan for matches,
+        and replace the matched text patterns according to the logic in the parse
+        action.  C{transformString()} returns the resulting transformed string.
+        
+        Example::
+            wd = Word(alphas)
+            wd.setParseAction(lambda toks: toks[0].title())
+            
+            print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york."))
+        Prints::
+            Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York.
+        """
+        out = []
+        lastE = 0
+        # force preservation of s, to minimize unwanted transformation of string, and to
+        # keep string locs straight between transformString and scanString
+        self.keepTabs = True
+        try:
+            for t,s,e in self.scanString( instring ):
+                out.append( instring[lastE:s] )
+                if t:
+                    if isinstance(t,ParseResults):
+                        out += t.asList()
+                    elif isinstance(t,list):
+                        out += t
+                    else:
+                        out.append(t)
+                lastE = e
+            out.append(instring[lastE:])
+            out = [o for o in out if o]
+            return "".join(map(_ustr,_flatten(out)))
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def searchString( self, instring, maxMatches=_MAX_INT ):
+        """
+        Another extension to C{L{scanString}}, simplifying the access to the tokens found
+        to match the given parse expression.  May be called with optional
+        C{maxMatches} argument, to clip searching after 'n' matches are found.
+        
+        Example::
+            # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters
+            cap_word = Word(alphas.upper(), alphas.lower())
+            
+            print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))
+
+            # the sum() builtin can be used to merge results into a single ParseResults object
+            print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")))
+        prints::
+            [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']]
+            ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity']
+        """
+        try:
+            return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ])
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False):
+        """
+        Generator method to split a string using the given expression as a separator.
+        May be called with optional C{maxsplit} argument, to limit the number of splits;
+        and the optional C{includeSeparators} argument (default=C{False}), if the separating
+        matching text should be included in the split results.
+        
+        Example::        
+            punc = oneOf(list(".,;:/-!?"))
+            print(list(punc.split("This, this?, this sentence, is badly punctuated!")))
+        prints::
+            ['This', ' this', '', ' this sentence', ' is badly punctuated', '']
+        """
+        splits = 0
+        last = 0
+        for t,s,e in self.scanString(instring, maxMatches=maxsplit):
+            yield instring[last:s]
+            if includeSeparators:
+                yield t[0]
+            last = e
+        yield instring[last:]
+
+    def __add__(self, other ):
+        """
+        Implementation of + operator - returns C{L{And}}. Adding strings to a ParserElement
+        converts them to L{Literal}s by default.
+        
+        Example::
+            greet = Word(alphas) + "," + Word(alphas) + "!"
+            hello = "Hello, World!"
+            print (hello, "->", greet.parseString(hello))
+        Prints::
+            Hello, World! -> ['Hello', ',', 'World', '!']
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return And( [ self, other ] )
+
+    def __radd__(self, other ):
+        """
+        Implementation of + operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other + self
+
+    def __sub__(self, other):
+        """
+        Implementation of - operator, returns C{L{And}} with error stop
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return self + And._ErrorStop() + other
+
+    def __rsub__(self, other ):
+        """
+        Implementation of - operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other - self
+
+    def __mul__(self,other):
+        """
+        Implementation of * operator, allows use of C{expr * 3} in place of
+        C{expr + expr + expr}.  Expressions may also me multiplied by a 2-integer
+        tuple, similar to C{{min,max}} multipliers in regular expressions.  Tuples
+        may also include C{None} as in:
+         - C{expr*(n,None)} or C{expr*(n,)} is equivalent
+              to C{expr*n + L{ZeroOrMore}(expr)}
+              (read as "at least n instances of C{expr}")
+         - C{expr*(None,n)} is equivalent to C{expr*(0,n)}
+              (read as "0 to n instances of C{expr}")
+         - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)}
+         - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)}
+
+        Note that C{expr*(None,n)} does not raise an exception if
+        more than n exprs exist in the input stream; that is,
+        C{expr*(None,n)} does not enforce a maximum number of expr
+        occurrences.  If this behavior is desired, then write
+        C{expr*(None,n) + ~expr}
+        """
+        if isinstance(other,int):
+            minElements, optElements = other,0
+        elif isinstance(other,tuple):
+            other = (other + (None, None))[:2]
+            if other[0] is None:
+                other = (0, other[1])
+            if isinstance(other[0],int) and other[1] is None:
+                if other[0] == 0:
+                    return ZeroOrMore(self)
+                if other[0] == 1:
+                    return OneOrMore(self)
+                else:
+                    return self*other[0] + ZeroOrMore(self)
+            elif isinstance(other[0],int) and isinstance(other[1],int):
+                minElements, optElements = other
+                optElements -= minElements
+            else:
+                raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1]))
+        else:
+            raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other))
+
+        if minElements < 0:
+            raise ValueError("cannot multiply ParserElement by negative value")
+        if optElements < 0:
+            raise ValueError("second tuple value must be greater or equal to first tuple value")
+        if minElements == optElements == 0:
+            raise ValueError("cannot multiply ParserElement by 0 or (0,0)")
+
+        if (optElements):
+            def makeOptionalList(n):
+                if n>1:
+                    return Optional(self + makeOptionalList(n-1))
+                else:
+                    return Optional(self)
+            if minElements:
+                if minElements == 1:
+                    ret = self + makeOptionalList(optElements)
+                else:
+                    ret = And([self]*minElements) + makeOptionalList(optElements)
+            else:
+                ret = makeOptionalList(optElements)
+        else:
+            if minElements == 1:
+                ret = self
+            else:
+                ret = And([self]*minElements)
+        return ret
+
+    def __rmul__(self, other):
+        return self.__mul__(other)
+
+    def __or__(self, other ):
+        """
+        Implementation of | operator - returns C{L{MatchFirst}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return MatchFirst( [ self, other ] )
+
+    def __ror__(self, other ):
+        """
+        Implementation of | operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other | self
+
+    def __xor__(self, other ):
+        """
+        Implementation of ^ operator - returns C{L{Or}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Or( [ self, other ] )
+
+    def __rxor__(self, other ):
+        """
+        Implementation of ^ operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other ^ self
+
+    def __and__(self, other ):
+        """
+        Implementation of & operator - returns C{L{Each}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return Each( [ self, other ] )
+
+    def __rand__(self, other ):
+        """
+        Implementation of & operator when left operand is not a C{L{ParserElement}}
+        """
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        if not isinstance( other, ParserElement ):
+            warnings.warn("Cannot combine element of type %s with ParserElement" % type(other),
+                    SyntaxWarning, stacklevel=2)
+            return None
+        return other & self
+
+    def __invert__( self ):
+        """
+        Implementation of ~ operator - returns C{L{NotAny}}
+        """
+        return NotAny( self )
+
+    def __call__(self, name=None):
+        """
+        Shortcut for C{L{setResultsName}}, with C{listAllMatches=False}.
+        
+        If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be
+        passed as C{True}.
+           
+        If C{name} is omitted, same as calling C{L{copy}}.
+
+        Example::
+            # these are equivalent
+            userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno")
+            userdata = Word(alphas)("name") + Word(nums+"-")("socsecno")             
+        """
+        if name is not None:
+            return self.setResultsName(name)
+        else:
+            return self.copy()
+
+    def suppress( self ):
+        """
+        Suppresses the output of this C{ParserElement}; useful to keep punctuation from
+        cluttering up returned output.
+        """
+        return Suppress( self )
+
+    def leaveWhitespace( self ):
+        """
+        Disables the skipping of whitespace before matching the characters in the
+        C{ParserElement}'s defined pattern.  This is normally only used internally by
+        the pyparsing module, but may be needed in some whitespace-sensitive grammars.
+        """
+        self.skipWhitespace = False
+        return self
+
+    def setWhitespaceChars( self, chars ):
+        """
+        Overrides the default whitespace chars
+        """
+        self.skipWhitespace = True
+        self.whiteChars = chars
+        self.copyDefaultWhiteChars = False
+        return self
+
+    def parseWithTabs( self ):
+        """
+        Overrides default behavior to expand C{}s to spaces before parsing the input string.
+        Must be called before C{parseString} when the input grammar contains elements that
+        match C{} characters.
+        """
+        self.keepTabs = True
+        return self
+
+    def ignore( self, other ):
+        """
+        Define expression to be ignored (e.g., comments) while doing pattern
+        matching; may be called repeatedly, to define multiple comment or other
+        ignorable patterns.
+        
+        Example::
+            patt = OneOrMore(Word(alphas))
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj']
+            
+            patt.ignore(cStyleComment)
+            patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd']
+        """
+        if isinstance(other, basestring):
+            other = Suppress(other)
+
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                self.ignoreExprs.append(other)
+        else:
+            self.ignoreExprs.append( Suppress( other.copy() ) )
+        return self
+
+    def setDebugActions( self, startAction, successAction, exceptionAction ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        """
+        self.debugActions = (startAction or _defaultStartDebugAction,
+                             successAction or _defaultSuccessDebugAction,
+                             exceptionAction or _defaultExceptionDebugAction)
+        self.debug = True
+        return self
+
+    def setDebug( self, flag=True ):
+        """
+        Enable display of debugging messages while doing pattern matching.
+        Set C{flag} to True to enable, False to disable.
+
+        Example::
+            wd = Word(alphas).setName("alphaword")
+            integer = Word(nums).setName("numword")
+            term = wd | integer
+            
+            # turn on debugging for wd
+            wd.setDebug()
+
+            OneOrMore(term).parseString("abc 123 xyz 890")
+        
+        prints::
+            Match alphaword at loc 0(1,1)
+            Matched alphaword -> ['abc']
+            Match alphaword at loc 3(1,4)
+            Exception raised:Expected alphaword (at char 4), (line:1, col:5)
+            Match alphaword at loc 7(1,8)
+            Matched alphaword -> ['xyz']
+            Match alphaword at loc 11(1,12)
+            Exception raised:Expected alphaword (at char 12), (line:1, col:13)
+            Match alphaword at loc 15(1,16)
+            Exception raised:Expected alphaword (at char 15), (line:1, col:16)
+
+        The output shown is that produced by the default debug actions - custom debug actions can be
+        specified using L{setDebugActions}. Prior to attempting
+        to match the C{wd} expression, the debugging message C{"Match  at loc (,)"}
+        is shown. Then if the parse succeeds, a C{"Matched"} message is shown, or an C{"Exception raised"}
+        message is shown. Also note the use of L{setName} to assign a human-readable name to the expression,
+        which makes debugging and exception messages easier to understand - for instance, the default
+        name created for the C{Word} expression without calling C{setName} is C{"W:(ABCD...)"}.
+        """
+        if flag:
+            self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction )
+        else:
+            self.debug = False
+        return self
+
+    def __str__( self ):
+        return self.name
+
+    def __repr__( self ):
+        return _ustr(self)
+
+    def streamline( self ):
+        self.streamlined = True
+        self.strRepr = None
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        pass
+
+    def validate( self, validateTrace=[] ):
+        """
+        Check defined expressions for valid structure, check for infinite recursive definitions.
+        """
+        self.checkRecursion( [] )
+
+    def parseFile( self, file_or_filename, parseAll=False ):
+        """
+        Execute the parse expression on the given file or filename.
+        If a filename is specified (instead of a file object),
+        the entire file is opened, read, and closed before parsing.
+        """
+        try:
+            file_contents = file_or_filename.read()
+        except AttributeError:
+            with open(file_or_filename, "r") as f:
+                file_contents = f.read()
+        try:
+            return self.parseString(file_contents, parseAll)
+        except ParseBaseException as exc:
+            if ParserElement.verbose_stacktrace:
+                raise
+            else:
+                # catch and re-raise exception from here, clears out pyparsing internal stack trace
+                raise exc
+
+    def __eq__(self,other):
+        if isinstance(other, ParserElement):
+            return self is other or vars(self) == vars(other)
+        elif isinstance(other, basestring):
+            return self.matches(other)
+        else:
+            return super(ParserElement,self)==other
+
+    def __ne__(self,other):
+        return not (self == other)
+
+    def __hash__(self):
+        return hash(id(self))
+
+    def __req__(self,other):
+        return self == other
+
+    def __rne__(self,other):
+        return not (self == other)
+
+    def matches(self, testString, parseAll=True):
+        """
+        Method for quick testing of a parser against a test string. Good for simple 
+        inline microtests of sub expressions while building up larger parser.
+           
+        Parameters:
+         - testString - to test against this expression for a match
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests
+            
+        Example::
+            expr = Word(nums)
+            assert expr.matches("100")
+        """
+        try:
+            self.parseString(_ustr(testString), parseAll=parseAll)
+            return True
+        except ParseBaseException:
+            return False
+                
+    def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False):
+        """
+        Execute the parse expression on a series of test strings, showing each
+        test, the parsed results or where the parse failed. Quick and easy way to
+        run a parse expression against a list of sample strings.
+           
+        Parameters:
+         - tests - a list of separate test strings, or a multiline string of test strings
+         - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests           
+         - comment - (default=C{'#'}) - expression for indicating embedded comments in the test 
+              string; pass None to disable comment filtering
+         - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline;
+              if False, only dump nested list
+         - printResults - (default=C{True}) prints test output to stdout
+         - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing
+
+        Returns: a (success, results) tuple, where success indicates that all tests succeeded
+        (or failed if C{failureTests} is True), and the results contain a list of lines of each 
+        test's output
+        
+        Example::
+            number_expr = pyparsing_common.number.copy()
+
+            result = number_expr.runTests('''
+                # unsigned integer
+                100
+                # negative integer
+                -100
+                # float with scientific notation
+                6.02e23
+                # integer with scientific notation
+                1e-12
+                ''')
+            print("Success" if result[0] else "Failed!")
+
+            result = number_expr.runTests('''
+                # stray character
+                100Z
+                # missing leading digit before '.'
+                -.100
+                # too many '.'
+                3.14.159
+                ''', failureTests=True)
+            print("Success" if result[0] else "Failed!")
+        prints::
+            # unsigned integer
+            100
+            [100]
+
+            # negative integer
+            -100
+            [-100]
+
+            # float with scientific notation
+            6.02e23
+            [6.02e+23]
+
+            # integer with scientific notation
+            1e-12
+            [1e-12]
+
+            Success
+            
+            # stray character
+            100Z
+               ^
+            FAIL: Expected end of text (at char 3), (line:1, col:4)
+
+            # missing leading digit before '.'
+            -.100
+            ^
+            FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1)
+
+            # too many '.'
+            3.14.159
+                ^
+            FAIL: Expected end of text (at char 4), (line:1, col:5)
+
+            Success
+
+        Each test string must be on a single line. If you want to test a string that spans multiple
+        lines, create a test like this::
+
+            expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines")
+        
+        (Note that this is a raw string literal, you must include the leading 'r'.)
+        """
+        if isinstance(tests, basestring):
+            tests = list(map(str.strip, tests.rstrip().splitlines()))
+        if isinstance(comment, basestring):
+            comment = Literal(comment)
+        allResults = []
+        comments = []
+        success = True
+        for t in tests:
+            if comment is not None and comment.matches(t, False) or comments and not t:
+                comments.append(t)
+                continue
+            if not t:
+                continue
+            out = ['\n'.join(comments), t]
+            comments = []
+            try:
+                t = t.replace(r'\n','\n')
+                result = self.parseString(t, parseAll=parseAll)
+                out.append(result.dump(full=fullDump))
+                success = success and not failureTests
+            except ParseBaseException as pe:
+                fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else ""
+                if '\n' in t:
+                    out.append(line(pe.loc, t))
+                    out.append(' '*(col(pe.loc,t)-1) + '^' + fatal)
+                else:
+                    out.append(' '*pe.loc + '^' + fatal)
+                out.append("FAIL: " + str(pe))
+                success = success and failureTests
+                result = pe
+            except Exception as exc:
+                out.append("FAIL-EXCEPTION: " + str(exc))
+                success = success and failureTests
+                result = exc
+
+            if printResults:
+                if fullDump:
+                    out.append('')
+                print('\n'.join(out))
+
+            allResults.append((t, result))
+        
+        return success, allResults
+
+        
+class Token(ParserElement):
+    """
+    Abstract C{ParserElement} subclass, for defining atomic matching patterns.
+    """
+    def __init__( self ):
+        super(Token,self).__init__( savelist=False )
+
+
+class Empty(Token):
+    """
+    An empty token, will always match.
+    """
+    def __init__( self ):
+        super(Empty,self).__init__()
+        self.name = "Empty"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+
+class NoMatch(Token):
+    """
+    A token that will never match.
+    """
+    def __init__( self ):
+        super(NoMatch,self).__init__()
+        self.name = "NoMatch"
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.errmsg = "Unmatchable token"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Literal(Token):
+    """
+    Token to exactly match a specified string.
+    
+    Example::
+        Literal('blah').parseString('blah')  # -> ['blah']
+        Literal('blah').parseString('blahfooblah')  # -> ['blah']
+        Literal('blah').parseString('bla')  # -> Exception: Expected "blah"
+    
+    For case-insensitive matching, use L{CaselessLiteral}.
+    
+    For keyword matching (force word break before and after the matched string),
+    use L{Keyword} or L{CaselessKeyword}.
+    """
+    def __init__( self, matchString ):
+        super(Literal,self).__init__()
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Literal; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+            self.__class__ = Empty
+        self.name = '"%s"' % _ustr(self.match)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+
+    # Performance tuning: this routine gets called a *lot*
+    # if this is a single character match string  and the first character matches,
+    # short-circuit as quickly as possible, and avoid calling startswith
+    #~ @profile
+    def parseImpl( self, instring, loc, doActions=True ):
+        if (instring[loc] == self.firstMatchChar and
+            (self.matchLen==1 or instring.startswith(self.match,loc)) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+_L = Literal
+ParserElement._literalStringClass = Literal
+
+class Keyword(Token):
+    """
+    Token to exactly match a specified string as a keyword, that is, it must be
+    immediately followed by a non-keyword character.  Compare with C{L{Literal}}:
+     - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}.
+     - C{Keyword("if")} will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'}
+    Accepts two optional constructor arguments in addition to the keyword string:
+     - C{identChars} is a string of characters that would be valid identifier characters,
+          defaulting to all alphanumerics + "_" and "$"
+     - C{caseless} allows case-insensitive matching, default is C{False}.
+       
+    Example::
+        Keyword("start").parseString("start")  # -> ['start']
+        Keyword("start").parseString("starting")  # -> Exception
+
+    For case-insensitive matching, use L{CaselessKeyword}.
+    """
+    DEFAULT_KEYWORD_CHARS = alphanums+"_$"
+
+    def __init__( self, matchString, identChars=None, caseless=False ):
+        super(Keyword,self).__init__()
+        if identChars is None:
+            identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        self.match = matchString
+        self.matchLen = len(matchString)
+        try:
+            self.firstMatchChar = matchString[0]
+        except IndexError:
+            warnings.warn("null string passed to Keyword; use Empty() instead",
+                            SyntaxWarning, stacklevel=2)
+        self.name = '"%s"' % self.match
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = False
+        self.mayIndexError = False
+        self.caseless = caseless
+        if caseless:
+            self.caselessmatch = matchString.upper()
+            identChars = identChars.upper()
+        self.identChars = set(identChars)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.caseless:
+            if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+                 (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and
+                 (loc == 0 or instring[loc-1].upper() not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        else:
+            if (instring[loc] == self.firstMatchChar and
+                (self.matchLen==1 or instring.startswith(self.match,loc)) and
+                (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and
+                (loc == 0 or instring[loc-1] not in self.identChars) ):
+                return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+    def copy(self):
+        c = super(Keyword,self).copy()
+        c.identChars = Keyword.DEFAULT_KEYWORD_CHARS
+        return c
+
+    @staticmethod
+    def setDefaultKeywordChars( chars ):
+        """Overrides the default Keyword chars
+        """
+        Keyword.DEFAULT_KEYWORD_CHARS = chars
+
+class CaselessLiteral(Literal):
+    """
+    Token to match a specified string, ignoring case of letters.
+    Note: the matched results will always be in the case of the given
+    match string, NOT the case of the input text.
+
+    Example::
+        OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessKeyword}.)
+    """
+    def __init__( self, matchString ):
+        super(CaselessLiteral,self).__init__( matchString.upper() )
+        # Preserve the defining literal.
+        self.returnString = matchString
+        self.name = "'%s'" % self.returnString
+        self.errmsg = "Expected " + self.name
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[ loc:loc+self.matchLen ].upper() == self.match:
+            return loc+self.matchLen, self.returnString
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CaselessKeyword(Keyword):
+    """
+    Caseless version of L{Keyword}.
+
+    Example::
+        OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD']
+        
+    (Contrast with example for L{CaselessLiteral}.)
+    """
+    def __init__( self, matchString, identChars=None ):
+        super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True )
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and
+             (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ):
+            return loc+self.matchLen, self.match
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class CloseMatch(Token):
+    """
+    A variation on L{Literal} which matches "close" matches, that is, 
+    strings with at most 'n' mismatching characters. C{CloseMatch} takes parameters:
+     - C{match_string} - string to be matched
+     - C{maxMismatches} - (C{default=1}) maximum number of mismatches allowed to count as a match
+    
+    The results from a successful parse will contain the matched text from the input string and the following named results:
+     - C{mismatches} - a list of the positions within the match_string where mismatches were found
+     - C{original} - the original match_string used to compare against the input string
+    
+    If C{mismatches} is an empty list, then the match was an exact match.
+    
+    Example::
+        patt = CloseMatch("ATCATCGAATGGA")
+        patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']})
+        patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1)
+
+        # exact match
+        patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']})
+
+        # close match allowing up to 2 mismatches
+        patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2)
+        patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']})
+    """
+    def __init__(self, match_string, maxMismatches=1):
+        super(CloseMatch,self).__init__()
+        self.name = match_string
+        self.match_string = match_string
+        self.maxMismatches = maxMismatches
+        self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches)
+        self.mayIndexError = False
+        self.mayReturnEmpty = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        start = loc
+        instrlen = len(instring)
+        maxloc = start + len(self.match_string)
+
+        if maxloc <= instrlen:
+            match_string = self.match_string
+            match_stringloc = 0
+            mismatches = []
+            maxMismatches = self.maxMismatches
+
+            for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)):
+                src,mat = s_m
+                if src != mat:
+                    mismatches.append(match_stringloc)
+                    if len(mismatches) > maxMismatches:
+                        break
+            else:
+                loc = match_stringloc + 1
+                results = ParseResults([instring[start:loc]])
+                results['original'] = self.match_string
+                results['mismatches'] = mismatches
+                return loc, results
+
+        raise ParseException(instring, loc, self.errmsg, self)
+
+
+class Word(Token):
+    """
+    Token for matching words composed of allowed character sets.
+    Defined with string containing all allowed initial characters,
+    an optional string containing allowed body characters (if omitted,
+    defaults to the initial character set), and an optional minimum,
+    maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction. An optional
+    C{excludeChars} parameter can list characters that might be found in 
+    the input C{bodyChars} string; useful to define a word of all printables
+    except for one or two characters, for instance.
+    
+    L{srange} is useful for defining custom character set strings for defining 
+    C{Word} expressions, using range notation from regular expression character sets.
+    
+    A common mistake is to use C{Word} to match a specific literal string, as in 
+    C{Word("Address")}. Remember that C{Word} uses the string argument to define
+    I{sets} of matchable characters. This expression would match "Add", "AAA",
+    "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'.
+    To match an exact literal string, use L{Literal} or L{Keyword}.
+
+    pyparsing includes helper strings for building Words:
+     - L{alphas}
+     - L{nums}
+     - L{alphanums}
+     - L{hexnums}
+     - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.)
+     - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.)
+     - L{printables} (any non-whitespace character)
+
+    Example::
+        # a word composed of digits
+        integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9"))
+        
+        # a word with a leading capital, and zero or more lowercase
+        capital_word = Word(alphas.upper(), alphas.lower())
+
+        # hostnames are alphanumeric, with leading alpha, and '-'
+        hostname = Word(alphas, alphanums+'-')
+        
+        # roman numeral (not a strict parser, accepts invalid mix of characters)
+        roman = Word("IVXLCDM")
+        
+        # any string of non-whitespace characters, except for ','
+        csv_value = Word(printables, excludeChars=",")
+    """
+    def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ):
+        super(Word,self).__init__()
+        if excludeChars:
+            initChars = ''.join(c for c in initChars if c not in excludeChars)
+            if bodyChars:
+                bodyChars = ''.join(c for c in bodyChars if c not in excludeChars)
+        self.initCharsOrig = initChars
+        self.initChars = set(initChars)
+        if bodyChars :
+            self.bodyCharsOrig = bodyChars
+            self.bodyChars = set(bodyChars)
+        else:
+            self.bodyCharsOrig = initChars
+            self.bodyChars = set(initChars)
+
+        self.maxSpecified = max > 0
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.asKeyword = asKeyword
+
+        if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0):
+            if self.bodyCharsOrig == self.initCharsOrig:
+                self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig)
+            elif len(self.initCharsOrig) == 1:
+                self.reString = "%s[%s]*" % \
+                                      (re.escape(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            else:
+                self.reString = "[%s][%s]*" % \
+                                      (_escapeRegexRangeChars(self.initCharsOrig),
+                                      _escapeRegexRangeChars(self.bodyCharsOrig),)
+            if self.asKeyword:
+                self.reString = r"\b"+self.reString+r"\b"
+            try:
+                self.re = re.compile( self.reString )
+            except Exception:
+                self.re = None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.re:
+            result = self.re.match(instring,loc)
+            if not result:
+                raise ParseException(instring, loc, self.errmsg, self)
+
+            loc = result.end()
+            return loc, result.group()
+
+        if not(instring[ loc ] in self.initChars):
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        instrlen = len(instring)
+        bodychars = self.bodyChars
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, instrlen )
+        while loc < maxloc and instring[loc] in bodychars:
+            loc += 1
+
+        throwException = False
+        if loc - start < self.minLen:
+            throwException = True
+        if self.maxSpecified and loc < instrlen and instring[loc] in bodychars:
+            throwException = True
+        if self.asKeyword:
+            if (start>0 and instring[start-1] in bodychars) or (loc4:
+                    return s[:4]+"..."
+                else:
+                    return s
+
+            if ( self.initCharsOrig != self.bodyCharsOrig ):
+                self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) )
+            else:
+                self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig)
+
+        return self.strRepr
+
+
+class Regex(Token):
+    r"""
+    Token for matching strings that match a given regular expression.
+    Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module.
+    If the given regex contains named groups (defined using C{(?P...)}), these will be preserved as 
+    named parse results.
+
+    Example::
+        realnum = Regex(r"[+-]?\d+\.\d*")
+        date = Regex(r'(?P\d{4})-(?P\d\d?)-(?P\d\d?)')
+        # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression
+        roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})")
+    """
+    compiledREtype = type(re.compile("[A-Z]"))
+    def __init__( self, pattern, flags=0):
+        """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags."""
+        super(Regex,self).__init__()
+
+        if isinstance(pattern, basestring):
+            if not pattern:
+                warnings.warn("null string passed to Regex; use Empty() instead",
+                        SyntaxWarning, stacklevel=2)
+
+            self.pattern = pattern
+            self.flags = flags
+
+            try:
+                self.re = re.compile(self.pattern, self.flags)
+                self.reString = self.pattern
+            except sre_constants.error:
+                warnings.warn("invalid pattern (%s) passed to Regex" % pattern,
+                    SyntaxWarning, stacklevel=2)
+                raise
+
+        elif isinstance(pattern, Regex.compiledREtype):
+            self.re = pattern
+            self.pattern = \
+            self.reString = str(pattern)
+            self.flags = flags
+            
+        else:
+            raise ValueError("Regex may only be constructed with a string or a compiled RE object")
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = self.re.match(instring,loc)
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        d = result.groupdict()
+        ret = ParseResults(result.group())
+        if d:
+            for k in d:
+                ret[k] = d[k]
+        return loc,ret
+
+    def __str__( self ):
+        try:
+            return super(Regex,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "Re:(%s)" % repr(self.pattern)
+
+        return self.strRepr
+
+
+class QuotedString(Token):
+    r"""
+    Token for matching strings that are delimited by quoting characters.
+    
+    Defined with the following parameters:
+        - quoteChar - string of one or more characters defining the quote delimiting string
+        - escChar - character to escape quotes, typically backslash (default=C{None})
+        - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{None})
+        - multiline - boolean indicating whether quotes can span multiple lines (default=C{False})
+        - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True})
+        - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar)
+        - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True})
+
+    Example::
+        qs = QuotedString('"')
+        print(qs.searchString('lsjdf "This is the quote" sldjf'))
+        complex_qs = QuotedString('{{', endQuoteChar='}}')
+        print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf'))
+        sql_qs = QuotedString('"', escQuote='""')
+        print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf'))
+    prints::
+        [['This is the quote']]
+        [['This is the "quote"']]
+        [['This is the quote with "embedded" quotes']]
+    """
+    def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True):
+        super(QuotedString,self).__init__()
+
+        # remove white space from quote chars - wont work anyway
+        quoteChar = quoteChar.strip()
+        if not quoteChar:
+            warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+            raise SyntaxError()
+
+        if endQuoteChar is None:
+            endQuoteChar = quoteChar
+        else:
+            endQuoteChar = endQuoteChar.strip()
+            if not endQuoteChar:
+                warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2)
+                raise SyntaxError()
+
+        self.quoteChar = quoteChar
+        self.quoteCharLen = len(quoteChar)
+        self.firstQuoteChar = quoteChar[0]
+        self.endQuoteChar = endQuoteChar
+        self.endQuoteCharLen = len(endQuoteChar)
+        self.escChar = escChar
+        self.escQuote = escQuote
+        self.unquoteResults = unquoteResults
+        self.convertWhitespaceEscapes = convertWhitespaceEscapes
+
+        if multiline:
+            self.flags = re.MULTILINE | re.DOTALL
+            self.pattern = r'%s(?:[^%s%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        else:
+            self.flags = 0
+            self.pattern = r'%s(?:[^%s\n\r%s]' % \
+                ( re.escape(self.quoteChar),
+                  _escapeRegexRangeChars(self.endQuoteChar[0]),
+                  (escChar is not None and _escapeRegexRangeChars(escChar) or '') )
+        if len(self.endQuoteChar) > 1:
+            self.pattern += (
+                '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]),
+                                               _escapeRegexRangeChars(self.endQuoteChar[i]))
+                                    for i in range(len(self.endQuoteChar)-1,0,-1)) + ')'
+                )
+        if escQuote:
+            self.pattern += (r'|(?:%s)' % re.escape(escQuote))
+        if escChar:
+            self.pattern += (r'|(?:%s.)' % re.escape(escChar))
+            self.escCharReplacePattern = re.escape(self.escChar)+"(.)"
+        self.pattern += (r')*%s' % re.escape(self.endQuoteChar))
+
+        try:
+            self.re = re.compile(self.pattern, self.flags)
+            self.reString = self.pattern
+        except sre_constants.error:
+            warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern,
+                SyntaxWarning, stacklevel=2)
+            raise
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayIndexError = False
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None
+        if not result:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        loc = result.end()
+        ret = result.group()
+
+        if self.unquoteResults:
+
+            # strip off quotes
+            ret = ret[self.quoteCharLen:-self.endQuoteCharLen]
+
+            if isinstance(ret,basestring):
+                # replace escaped whitespace
+                if '\\' in ret and self.convertWhitespaceEscapes:
+                    ws_map = {
+                        r'\t' : '\t',
+                        r'\n' : '\n',
+                        r'\f' : '\f',
+                        r'\r' : '\r',
+                    }
+                    for wslit,wschar in ws_map.items():
+                        ret = ret.replace(wslit, wschar)
+
+                # replace escaped characters
+                if self.escChar:
+                    ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret)
+
+                # replace escaped quotes
+                if self.escQuote:
+                    ret = ret.replace(self.escQuote, self.endQuoteChar)
+
+        return loc, ret
+
+    def __str__( self ):
+        try:
+            return super(QuotedString,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar)
+
+        return self.strRepr
+
+
+class CharsNotIn(Token):
+    """
+    Token for matching words composed of characters I{not} in a given set (will
+    include whitespace in matched characters if not listed in the provided exclusion set - see example).
+    Defined with string containing all disallowed characters, and an optional
+    minimum, maximum, and/or exact length.  The default value for C{min} is 1 (a
+    minimum value < 1 is not valid); the default values for C{max} and C{exact}
+    are 0, meaning no maximum or exact length restriction.
+
+    Example::
+        # define a comma-separated-value as anything that is not a ','
+        csv_value = CharsNotIn(',')
+        print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213"))
+    prints::
+        ['dkls', 'lsdkjf', 's12 34', '@!#', '213']
+    """
+    def __init__( self, notChars, min=1, max=0, exact=0 ):
+        super(CharsNotIn,self).__init__()
+        self.skipWhitespace = False
+        self.notChars = notChars
+
+        if min < 1:
+            raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted")
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+        self.name = _ustr(self)
+        self.errmsg = "Expected " + self.name
+        self.mayReturnEmpty = ( self.minLen == 0 )
+        self.mayIndexError = False
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if instring[loc] in self.notChars:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        start = loc
+        loc += 1
+        notchars = self.notChars
+        maxlen = min( start+self.maxLen, len(instring) )
+        while loc < maxlen and \
+              (instring[loc] not in notchars):
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+    def __str__( self ):
+        try:
+            return super(CharsNotIn, self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None:
+            if len(self.notChars) > 4:
+                self.strRepr = "!W:(%s...)" % self.notChars[:4]
+            else:
+                self.strRepr = "!W:(%s)" % self.notChars
+
+        return self.strRepr
+
+class White(Token):
+    """
+    Special matching class for matching whitespace.  Normally, whitespace is ignored
+    by pyparsing grammars.  This class is included when some whitespace structures
+    are significant.  Define with a string containing the whitespace characters to be
+    matched; default is C{" \\t\\r\\n"}.  Also takes optional C{min}, C{max}, and C{exact} arguments,
+    as defined for the C{L{Word}} class.
+    """
+    whiteStrs = {
+        " " : "",
+        "\t": "",
+        "\n": "",
+        "\r": "",
+        "\f": "",
+        }
+    def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0):
+        super(White,self).__init__()
+        self.matchWhite = ws
+        self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) )
+        #~ self.leaveWhitespace()
+        self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite))
+        self.mayReturnEmpty = True
+        self.errmsg = "Expected " + self.name
+
+        self.minLen = min
+
+        if max > 0:
+            self.maxLen = max
+        else:
+            self.maxLen = _MAX_INT
+
+        if exact > 0:
+            self.maxLen = exact
+            self.minLen = exact
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if not(instring[ loc ] in self.matchWhite):
+            raise ParseException(instring, loc, self.errmsg, self)
+        start = loc
+        loc += 1
+        maxloc = start + self.maxLen
+        maxloc = min( maxloc, len(instring) )
+        while loc < maxloc and instring[loc] in self.matchWhite:
+            loc += 1
+
+        if loc - start < self.minLen:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        return loc, instring[start:loc]
+
+
+class _PositionToken(Token):
+    def __init__( self ):
+        super(_PositionToken,self).__init__()
+        self.name=self.__class__.__name__
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+
+class GoToColumn(_PositionToken):
+    """
+    Token to advance to a specific column of input text; useful for tabular report scraping.
+    """
+    def __init__( self, colno ):
+        super(GoToColumn,self).__init__()
+        self.col = colno
+
+    def preParse( self, instring, loc ):
+        if col(loc,instring) != self.col:
+            instrlen = len(instring)
+            if self.ignoreExprs:
+                loc = self._skipIgnorables( instring, loc )
+            while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col :
+                loc += 1
+        return loc
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        thiscol = col( loc, instring )
+        if thiscol > self.col:
+            raise ParseException( instring, loc, "Text not in expected column", self )
+        newloc = loc + self.col - thiscol
+        ret = instring[ loc: newloc ]
+        return newloc, ret
+
+
+class LineStart(_PositionToken):
+    """
+    Matches if current position is at the beginning of a line within the parse string
+    
+    Example::
+    
+        test = '''\
+        AAA this line
+        AAA and this line
+          AAA but not this one
+        B AAA and definitely not this one
+        '''
+
+        for t in (LineStart() + 'AAA' + restOfLine).searchString(test):
+            print(t)
+    
+    Prints::
+        ['AAA', ' this line']
+        ['AAA', ' and this line']    
+
+    """
+    def __init__( self ):
+        super(LineStart,self).__init__()
+        self.errmsg = "Expected start of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if col(loc, instring) == 1:
+            return loc, []
+        raise ParseException(instring, loc, self.errmsg, self)
+
+class LineEnd(_PositionToken):
+    """
+    Matches if current position is at the end of a line within the parse string
+    """
+    def __init__( self ):
+        super(LineEnd,self).__init__()
+        self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") )
+        self.errmsg = "Expected end of line"
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if loc len(instring):
+            return loc, []
+        else:
+            raise ParseException(instring, loc, self.errmsg, self)
+
+class WordStart(_PositionToken):
+    """
+    Matches if the current position is at the beginning of a Word, and
+    is not preceded by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of
+    the string being parsed, or at the beginning of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordStart,self).__init__()
+        self.wordChars = set(wordChars)
+        self.errmsg = "Not at the start of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        if loc != 0:
+            if (instring[loc-1] in self.wordChars or
+                instring[loc] not in self.wordChars):
+                raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+class WordEnd(_PositionToken):
+    """
+    Matches if the current position is at the end of a Word, and
+    is not followed by any character in a given set of C{wordChars}
+    (default=C{printables}). To emulate the C{\b} behavior of regular expressions,
+    use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of
+    the string being parsed, or at the end of a line.
+    """
+    def __init__(self, wordChars = printables):
+        super(WordEnd,self).__init__()
+        self.wordChars = set(wordChars)
+        self.skipWhitespace = False
+        self.errmsg = "Not at the end of a word"
+
+    def parseImpl(self, instring, loc, doActions=True ):
+        instrlen = len(instring)
+        if instrlen>0 and loc maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+            else:
+                # save match among all matches, to retry longest to shortest
+                matches.append((loc2, e))
+
+        if matches:
+            matches.sort(key=lambda x: -x[0])
+            for _,e in matches:
+                try:
+                    return e._parse( instring, loc, doActions )
+                except ParseException as err:
+                    err.__traceback__ = None
+                    if err.loc > maxExcLoc:
+                        maxException = err
+                        maxExcLoc = err.loc
+
+        if maxException is not None:
+            maxException.msg = self.errmsg
+            raise maxException
+        else:
+            raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+
+    def __ixor__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #Or( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class MatchFirst(ParseExpression):
+    """
+    Requires that at least one C{ParseExpression} is found.
+    If two expressions match, the first one listed is the one that will match.
+    May be constructed using the C{'|'} operator.
+
+    Example::
+        # construct MatchFirst using '|' operator
+        
+        # watch the order of expressions to match
+        number = Word(nums) | Combine(Word(nums) + '.' + Word(nums))
+        print(number.searchString("123 3.1416 789")) #  Fail! -> [['123'], ['3'], ['1416'], ['789']]
+
+        # put more selective expression first
+        number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums)
+        print(number.searchString("123 3.1416 789")) #  Better -> [['123'], ['3.1416'], ['789']]
+    """
+    def __init__( self, exprs, savelist = False ):
+        super(MatchFirst,self).__init__(exprs, savelist)
+        if self.exprs:
+            self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs)
+        else:
+            self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        maxExcLoc = -1
+        maxException = None
+        for e in self.exprs:
+            try:
+                ret = e._parse( instring, loc, doActions )
+                return ret
+            except ParseException as err:
+                if err.loc > maxExcLoc:
+                    maxException = err
+                    maxExcLoc = err.loc
+            except IndexError:
+                if len(instring) > maxExcLoc:
+                    maxException = ParseException(instring,len(instring),e.errmsg,self)
+                    maxExcLoc = len(instring)
+
+        # only got here if no expression matched, raise exception for match that made it the furthest
+        else:
+            if maxException is not None:
+                maxException.msg = self.errmsg
+                raise maxException
+            else:
+                raise ParseException(instring, loc, "no defined alternatives to match", self)
+
+    def __ior__(self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass( other )
+        return self.append( other ) #MatchFirst( [ self, other ] )
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class Each(ParseExpression):
+    """
+    Requires all given C{ParseExpression}s to be found, but in any order.
+    Expressions may be separated by whitespace.
+    May be constructed using the C{'&'} operator.
+
+    Example::
+        color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN")
+        shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON")
+        integer = Word(nums)
+        shape_attr = "shape:" + shape_type("shape")
+        posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn")
+        color_attr = "color:" + color("color")
+        size_attr = "size:" + integer("size")
+
+        # use Each (using operator '&') to accept attributes in any order 
+        # (shape and posn are required, color and size are optional)
+        shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr)
+
+        shape_spec.runTests('''
+            shape: SQUARE color: BLACK posn: 100, 120
+            shape: CIRCLE size: 50 color: BLUE posn: 50,80
+            color:GREEN size:20 shape:TRIANGLE posn:20,40
+            '''
+            )
+    prints::
+        shape: SQUARE color: BLACK posn: 100, 120
+        ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']]
+        - color: BLACK
+        - posn: ['100', ',', '120']
+          - x: 100
+          - y: 120
+        - shape: SQUARE
+
+
+        shape: CIRCLE size: 50 color: BLUE posn: 50,80
+        ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']]
+        - color: BLUE
+        - posn: ['50', ',', '80']
+          - x: 50
+          - y: 80
+        - shape: CIRCLE
+        - size: 50
+
+
+        color: GREEN size: 20 shape: TRIANGLE posn: 20,40
+        ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']]
+        - color: GREEN
+        - posn: ['20', ',', '40']
+          - x: 20
+          - y: 40
+        - shape: TRIANGLE
+        - size: 20
+    """
+    def __init__( self, exprs, savelist = True ):
+        super(Each,self).__init__(exprs, savelist)
+        self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs)
+        self.skipWhitespace = True
+        self.initExprGroups = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.initExprGroups:
+            self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional))
+            opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ]
+            opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)]
+            self.optionals = opt1 + opt2
+            self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ]
+            self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ]
+            self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ]
+            self.required += self.multirequired
+            self.initExprGroups = False
+        tmpLoc = loc
+        tmpReqd = self.required[:]
+        tmpOpt  = self.optionals[:]
+        matchOrder = []
+
+        keepMatching = True
+        while keepMatching:
+            tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired
+            failed = []
+            for e in tmpExprs:
+                try:
+                    tmpLoc = e.tryParse( instring, tmpLoc )
+                except ParseException:
+                    failed.append(e)
+                else:
+                    matchOrder.append(self.opt1map.get(id(e),e))
+                    if e in tmpReqd:
+                        tmpReqd.remove(e)
+                    elif e in tmpOpt:
+                        tmpOpt.remove(e)
+            if len(failed) == len(tmpExprs):
+                keepMatching = False
+
+        if tmpReqd:
+            missing = ", ".join(_ustr(e) for e in tmpReqd)
+            raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing )
+
+        # add any unmatched Optionals, in case they have default values defined
+        matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt]
+
+        resultlist = []
+        for e in matchOrder:
+            loc,results = e._parse(instring,loc,doActions)
+            resultlist.append(results)
+
+        finalResults = sum(resultlist, ParseResults([]))
+        return loc, finalResults
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}"
+
+        return self.strRepr
+
+    def checkRecursion( self, parseElementList ):
+        subRecCheckList = parseElementList[:] + [ self ]
+        for e in self.exprs:
+            e.checkRecursion( subRecCheckList )
+
+
+class ParseElementEnhance(ParserElement):
+    """
+    Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(ParseElementEnhance,self).__init__(savelist)
+        if isinstance( expr, basestring ):
+            if issubclass(ParserElement._literalStringClass, Token):
+                expr = ParserElement._literalStringClass(expr)
+            else:
+                expr = ParserElement._literalStringClass(Literal(expr))
+        self.expr = expr
+        self.strRepr = None
+        if expr is not None:
+            self.mayIndexError = expr.mayIndexError
+            self.mayReturnEmpty = expr.mayReturnEmpty
+            self.setWhitespaceChars( expr.whiteChars )
+            self.skipWhitespace = expr.skipWhitespace
+            self.saveAsList = expr.saveAsList
+            self.callPreparse = expr.callPreparse
+            self.ignoreExprs.extend(expr.ignoreExprs)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr is not None:
+            return self.expr._parse( instring, loc, doActions, callPreParse=False )
+        else:
+            raise ParseException("",loc,self.errmsg,self)
+
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        self.expr = self.expr.copy()
+        if self.expr is not None:
+            self.expr.leaveWhitespace()
+        return self
+
+    def ignore( self, other ):
+        if isinstance( other, Suppress ):
+            if other not in self.ignoreExprs:
+                super( ParseElementEnhance, self).ignore( other )
+                if self.expr is not None:
+                    self.expr.ignore( self.ignoreExprs[-1] )
+        else:
+            super( ParseElementEnhance, self).ignore( other )
+            if self.expr is not None:
+                self.expr.ignore( self.ignoreExprs[-1] )
+        return self
+
+    def streamline( self ):
+        super(ParseElementEnhance,self).streamline()
+        if self.expr is not None:
+            self.expr.streamline()
+        return self
+
+    def checkRecursion( self, parseElementList ):
+        if self in parseElementList:
+            raise RecursiveGrammarException( parseElementList+[self] )
+        subRecCheckList = parseElementList[:] + [ self ]
+        if self.expr is not None:
+            self.expr.checkRecursion( subRecCheckList )
+
+    def validate( self, validateTrace=[] ):
+        tmp = validateTrace[:]+[self]
+        if self.expr is not None:
+            self.expr.validate(tmp)
+        self.checkRecursion( [] )
+
+    def __str__( self ):
+        try:
+            return super(ParseElementEnhance,self).__str__()
+        except Exception:
+            pass
+
+        if self.strRepr is None and self.expr is not None:
+            self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) )
+        return self.strRepr
+
+
+class FollowedBy(ParseElementEnhance):
+    """
+    Lookahead matching of the given parse expression.  C{FollowedBy}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression matches at the current
+    position.  C{FollowedBy} always returns a null token list.
+
+    Example::
+        # use FollowedBy to match a label only if it is followed by a ':'
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint()
+    prints::
+        [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']]
+    """
+    def __init__( self, expr ):
+        super(FollowedBy,self).__init__(expr)
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self.expr.tryParse( instring, loc )
+        return loc, []
+
+
+class NotAny(ParseElementEnhance):
+    """
+    Lookahead to disallow matching with the given parse expression.  C{NotAny}
+    does I{not} advance the parsing position within the input string, it only
+    verifies that the specified parse expression does I{not} match at the current
+    position.  Also, C{NotAny} does I{not} skip over leading whitespace. C{NotAny}
+    always returns a null token list.  May be constructed using the '~' operator.
+
+    Example::
+        
+    """
+    def __init__( self, expr ):
+        super(NotAny,self).__init__(expr)
+        #~ self.leaveWhitespace()
+        self.skipWhitespace = False  # do NOT use self.leaveWhitespace(), don't want to propagate to exprs
+        self.mayReturnEmpty = True
+        self.errmsg = "Found unwanted token, "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        if self.expr.canParseNext(instring, loc):
+            raise ParseException(instring, loc, self.errmsg, self)
+        return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "~{" + _ustr(self.expr) + "}"
+
+        return self.strRepr
+
+class _MultipleMatch(ParseElementEnhance):
+    def __init__( self, expr, stopOn=None):
+        super(_MultipleMatch, self).__init__(expr)
+        self.saveAsList = True
+        ender = stopOn
+        if isinstance(ender, basestring):
+            ender = ParserElement._literalStringClass(ender)
+        self.not_ender = ~ender if ender is not None else None
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        self_expr_parse = self.expr._parse
+        self_skip_ignorables = self._skipIgnorables
+        check_ender = self.not_ender is not None
+        if check_ender:
+            try_not_ender = self.not_ender.tryParse
+        
+        # must be at least one (but first see if we are the stopOn sentinel;
+        # if so, fail)
+        if check_ender:
+            try_not_ender(instring, loc)
+        loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False )
+        try:
+            hasIgnoreExprs = (not not self.ignoreExprs)
+            while 1:
+                if check_ender:
+                    try_not_ender(instring, loc)
+                if hasIgnoreExprs:
+                    preloc = self_skip_ignorables( instring, loc )
+                else:
+                    preloc = loc
+                loc, tmptokens = self_expr_parse( instring, preloc, doActions )
+                if tmptokens or tmptokens.haskeys():
+                    tokens += tmptokens
+        except (ParseException,IndexError):
+            pass
+
+        return loc, tokens
+        
+class OneOrMore(_MultipleMatch):
+    """
+    Repetition of one or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match one or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: BLACK"
+        OneOrMore(attr_expr).parseString(text).pprint()  # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']]
+
+        # use stopOn attribute for OneOrMore to avoid reading label string as part of the data
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']]
+        
+        # could also be written as
+        (attr_expr * (1,)).parseString(text).pprint()
+    """
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "{" + _ustr(self.expr) + "}..."
+
+        return self.strRepr
+
+class ZeroOrMore(_MultipleMatch):
+    """
+    Optional repetition of zero or more of the given expression.
+    
+    Parameters:
+     - expr - expression that must match zero or more times
+     - stopOn - (default=C{None}) - expression for a terminating sentinel
+          (only required if the sentinel would ordinarily match the repetition 
+          expression)          
+
+    Example: similar to L{OneOrMore}
+    """
+    def __init__( self, expr, stopOn=None):
+        super(ZeroOrMore,self).__init__(expr, stopOn=stopOn)
+        self.mayReturnEmpty = True
+        
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            return super(ZeroOrMore, self).parseImpl(instring, loc, doActions)
+        except (ParseException,IndexError):
+            return loc, []
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]..."
+
+        return self.strRepr
+
+class _NullToken(object):
+    def __bool__(self):
+        return False
+    __nonzero__ = __bool__
+    def __str__(self):
+        return ""
+
+_optionalNotMatched = _NullToken()
+class Optional(ParseElementEnhance):
+    """
+    Optional matching of the given expression.
+
+    Parameters:
+     - expr - expression that must match zero or more times
+     - default (optional) - value to be returned if the optional expression is not found.
+
+    Example::
+        # US postal code can be a 5-digit zip, plus optional 4-digit qualifier
+        zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4)))
+        zip.runTests('''
+            # traditional ZIP code
+            12345
+            
+            # ZIP+4 form
+            12101-0001
+            
+            # invalid ZIP
+            98765-
+            ''')
+    prints::
+        # traditional ZIP code
+        12345
+        ['12345']
+
+        # ZIP+4 form
+        12101-0001
+        ['12101-0001']
+
+        # invalid ZIP
+        98765-
+             ^
+        FAIL: Expected end of text (at char 5), (line:1, col:6)
+    """
+    def __init__( self, expr, default=_optionalNotMatched ):
+        super(Optional,self).__init__( expr, savelist=False )
+        self.saveAsList = self.expr.saveAsList
+        self.defaultValue = default
+        self.mayReturnEmpty = True
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        try:
+            loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False )
+        except (ParseException,IndexError):
+            if self.defaultValue is not _optionalNotMatched:
+                if self.expr.resultsName:
+                    tokens = ParseResults([ self.defaultValue ])
+                    tokens[self.expr.resultsName] = self.defaultValue
+                else:
+                    tokens = [ self.defaultValue ]
+            else:
+                tokens = []
+        return loc, tokens
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+
+        if self.strRepr is None:
+            self.strRepr = "[" + _ustr(self.expr) + "]"
+
+        return self.strRepr
+
+class SkipTo(ParseElementEnhance):
+    """
+    Token for skipping over all undefined text until the matched expression is found.
+
+    Parameters:
+     - expr - target expression marking the end of the data to be skipped
+     - include - (default=C{False}) if True, the target expression is also parsed 
+          (the skipped text and target expression are returned as a 2-element list).
+     - ignore - (default=C{None}) used to define grammars (typically quoted strings and 
+          comments) that might contain false matches to the target expression
+     - failOn - (default=C{None}) define expressions that are not allowed to be 
+          included in the skipped test; if found before the target expression is found, 
+          the SkipTo is not a match
+
+    Example::
+        report = '''
+            Outstanding Issues Report - 1 Jan 2000
+
+               # | Severity | Description                               |  Days Open
+            -----+----------+-------------------------------------------+-----------
+             101 | Critical | Intermittent system crash                 |          6
+              94 | Cosmetic | Spelling error on Login ('log|n')         |         14
+              79 | Minor    | System slow when running too many reports |         47
+            '''
+        integer = Word(nums)
+        SEP = Suppress('|')
+        # use SkipTo to simply match everything up until the next SEP
+        # - ignore quoted strings, so that a '|' character inside a quoted string does not match
+        # - parse action will call token.strip() for each matched token, i.e., the description body
+        string_data = SkipTo(SEP, ignore=quotedString)
+        string_data.setParseAction(tokenMap(str.strip))
+        ticket_expr = (integer("issue_num") + SEP 
+                      + string_data("sev") + SEP 
+                      + string_data("desc") + SEP 
+                      + integer("days_open"))
+        
+        for tkt in ticket_expr.searchString(report):
+            print tkt.dump()
+    prints::
+        ['101', 'Critical', 'Intermittent system crash', '6']
+        - days_open: 6
+        - desc: Intermittent system crash
+        - issue_num: 101
+        - sev: Critical
+        ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14']
+        - days_open: 14
+        - desc: Spelling error on Login ('log|n')
+        - issue_num: 94
+        - sev: Cosmetic
+        ['79', 'Minor', 'System slow when running too many reports', '47']
+        - days_open: 47
+        - desc: System slow when running too many reports
+        - issue_num: 79
+        - sev: Minor
+    """
+    def __init__( self, other, include=False, ignore=None, failOn=None ):
+        super( SkipTo, self ).__init__( other )
+        self.ignoreExpr = ignore
+        self.mayReturnEmpty = True
+        self.mayIndexError = False
+        self.includeMatch = include
+        self.asList = False
+        if isinstance(failOn, basestring):
+            self.failOn = ParserElement._literalStringClass(failOn)
+        else:
+            self.failOn = failOn
+        self.errmsg = "No match found for "+_ustr(self.expr)
+
+    def parseImpl( self, instring, loc, doActions=True ):
+        startloc = loc
+        instrlen = len(instring)
+        expr = self.expr
+        expr_parse = self.expr._parse
+        self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None
+        self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None
+        
+        tmploc = loc
+        while tmploc <= instrlen:
+            if self_failOn_canParseNext is not None:
+                # break if failOn expression matches
+                if self_failOn_canParseNext(instring, tmploc):
+                    break
+                    
+            if self_ignoreExpr_tryParse is not None:
+                # advance past ignore expressions
+                while 1:
+                    try:
+                        tmploc = self_ignoreExpr_tryParse(instring, tmploc)
+                    except ParseBaseException:
+                        break
+            
+            try:
+                expr_parse(instring, tmploc, doActions=False, callPreParse=False)
+            except (ParseException, IndexError):
+                # no match, advance loc in string
+                tmploc += 1
+            else:
+                # matched skipto expr, done
+                break
+
+        else:
+            # ran off the end of the input string without matching skipto expr, fail
+            raise ParseException(instring, loc, self.errmsg, self)
+
+        # build up return values
+        loc = tmploc
+        skiptext = instring[startloc:loc]
+        skipresult = ParseResults(skiptext)
+        
+        if self.includeMatch:
+            loc, mat = expr_parse(instring,loc,doActions,callPreParse=False)
+            skipresult += mat
+
+        return loc, skipresult
+
+class Forward(ParseElementEnhance):
+    """
+    Forward declaration of an expression to be defined later -
+    used for recursive grammars, such as algebraic infix notation.
+    When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator.
+
+    Note: take care when assigning to C{Forward} not to overlook precedence of operators.
+    Specifically, '|' has a lower precedence than '<<', so that::
+        fwdExpr << a | b | c
+    will actually be evaluated as::
+        (fwdExpr << a) | b | c
+    thereby leaving b and c out as parseable alternatives.  It is recommended that you
+    explicitly group the values inserted into the C{Forward}::
+        fwdExpr << (a | b | c)
+    Converting to use the '<<=' operator instead will avoid this problem.
+
+    See L{ParseResults.pprint} for an example of a recursive parser created using
+    C{Forward}.
+    """
+    def __init__( self, other=None ):
+        super(Forward,self).__init__( other, savelist=False )
+
+    def __lshift__( self, other ):
+        if isinstance( other, basestring ):
+            other = ParserElement._literalStringClass(other)
+        self.expr = other
+        self.strRepr = None
+        self.mayIndexError = self.expr.mayIndexError
+        self.mayReturnEmpty = self.expr.mayReturnEmpty
+        self.setWhitespaceChars( self.expr.whiteChars )
+        self.skipWhitespace = self.expr.skipWhitespace
+        self.saveAsList = self.expr.saveAsList
+        self.ignoreExprs.extend(self.expr.ignoreExprs)
+        return self
+        
+    def __ilshift__(self, other):
+        return self << other
+    
+    def leaveWhitespace( self ):
+        self.skipWhitespace = False
+        return self
+
+    def streamline( self ):
+        if not self.streamlined:
+            self.streamlined = True
+            if self.expr is not None:
+                self.expr.streamline()
+        return self
+
+    def validate( self, validateTrace=[] ):
+        if self not in validateTrace:
+            tmp = validateTrace[:]+[self]
+            if self.expr is not None:
+                self.expr.validate(tmp)
+        self.checkRecursion([])
+
+    def __str__( self ):
+        if hasattr(self,"name"):
+            return self.name
+        return self.__class__.__name__ + ": ..."
+
+        # stubbed out for now - creates awful memory and perf issues
+        self._revertClass = self.__class__
+        self.__class__ = _ForwardNoRecurse
+        try:
+            if self.expr is not None:
+                retString = _ustr(self.expr)
+            else:
+                retString = "None"
+        finally:
+            self.__class__ = self._revertClass
+        return self.__class__.__name__ + ": " + retString
+
+    def copy(self):
+        if self.expr is not None:
+            return super(Forward,self).copy()
+        else:
+            ret = Forward()
+            ret <<= self
+            return ret
+
+class _ForwardNoRecurse(Forward):
+    def __str__( self ):
+        return "..."
+
+class TokenConverter(ParseElementEnhance):
+    """
+    Abstract subclass of C{ParseExpression}, for converting parsed results.
+    """
+    def __init__( self, expr, savelist=False ):
+        super(TokenConverter,self).__init__( expr )#, savelist )
+        self.saveAsList = False
+
+class Combine(TokenConverter):
+    """
+    Converter to concatenate all matching tokens to a single string.
+    By default, the matching patterns must also be contiguous in the input string;
+    this can be disabled by specifying C{'adjacent=False'} in the constructor.
+
+    Example::
+        real = Word(nums) + '.' + Word(nums)
+        print(real.parseString('3.1416')) # -> ['3', '.', '1416']
+        # will also erroneously match the following
+        print(real.parseString('3. 1416')) # -> ['3', '.', '1416']
+
+        real = Combine(Word(nums) + '.' + Word(nums))
+        print(real.parseString('3.1416')) # -> ['3.1416']
+        # no match when there are internal spaces
+        print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...)
+    """
+    def __init__( self, expr, joinString="", adjacent=True ):
+        super(Combine,self).__init__( expr )
+        # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself
+        if adjacent:
+            self.leaveWhitespace()
+        self.adjacent = adjacent
+        self.skipWhitespace = True
+        self.joinString = joinString
+        self.callPreparse = True
+
+    def ignore( self, other ):
+        if self.adjacent:
+            ParserElement.ignore(self, other)
+        else:
+            super( Combine, self).ignore( other )
+        return self
+
+    def postParse( self, instring, loc, tokenlist ):
+        retToks = tokenlist.copy()
+        del retToks[:]
+        retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults)
+
+        if self.resultsName and retToks.haskeys():
+            return [ retToks ]
+        else:
+            return retToks
+
+class Group(TokenConverter):
+    """
+    Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions.
+
+    Example::
+        ident = Word(alphas)
+        num = Word(nums)
+        term = ident | num
+        func = ident + Optional(delimitedList(term))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', 'a', 'b', '100']
+
+        func = ident + Group(Optional(delimitedList(term)))
+        print(func.parseString("fn a,b,100"))  # -> ['fn', ['a', 'b', '100']]
+    """
+    def __init__( self, expr ):
+        super(Group,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        return [ tokenlist ]
+
+class Dict(TokenConverter):
+    """
+    Converter to return a repetitive expression as a list, but also as a dictionary.
+    Each element can also be referenced using the first token in the expression as its key.
+    Useful for tabular report scraping when the first column can be used as a item key.
+
+    Example::
+        data_word = Word(alphas)
+        label = data_word + FollowedBy(':')
+        attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join))
+
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        
+        # print attributes as plain groups
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names
+        result = Dict(OneOrMore(Group(attr_expr))).parseString(text)
+        print(result.dump())
+        
+        # access named fields as dict entries, or output as dict
+        print(result['shape'])        
+        print(result.asDict())
+    prints::
+        ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap']
+
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'}
+    See more examples at L{ParseResults} of accessing fields by results name.
+    """
+    def __init__( self, expr ):
+        super(Dict,self).__init__( expr )
+        self.saveAsList = True
+
+    def postParse( self, instring, loc, tokenlist ):
+        for i,tok in enumerate(tokenlist):
+            if len(tok) == 0:
+                continue
+            ikey = tok[0]
+            if isinstance(ikey,int):
+                ikey = _ustr(tok[0]).strip()
+            if len(tok)==1:
+                tokenlist[ikey] = _ParseResultsWithOffset("",i)
+            elif len(tok)==2 and not isinstance(tok[1],ParseResults):
+                tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i)
+            else:
+                dictvalue = tok.copy() #ParseResults(i)
+                del dictvalue[0]
+                if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()):
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i)
+                else:
+                    tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i)
+
+        if self.resultsName:
+            return [ tokenlist ]
+        else:
+            return tokenlist
+
+
+class Suppress(TokenConverter):
+    """
+    Converter for ignoring the results of a parsed expression.
+
+    Example::
+        source = "a, b, c,d"
+        wd = Word(alphas)
+        wd_list1 = wd + ZeroOrMore(',' + wd)
+        print(wd_list1.parseString(source))
+
+        # often, delimiters that are useful during parsing are just in the
+        # way afterward - use Suppress to keep them out of the parsed output
+        wd_list2 = wd + ZeroOrMore(Suppress(',') + wd)
+        print(wd_list2.parseString(source))
+    prints::
+        ['a', ',', 'b', ',', 'c', ',', 'd']
+        ['a', 'b', 'c', 'd']
+    (See also L{delimitedList}.)
+    """
+    def postParse( self, instring, loc, tokenlist ):
+        return []
+
+    def suppress( self ):
+        return self
+
+
+class OnlyOnce(object):
+    """
+    Wrapper for parse actions, to ensure they are only called once.
+    """
+    def __init__(self, methodCall):
+        self.callable = _trim_arity(methodCall)
+        self.called = False
+    def __call__(self,s,l,t):
+        if not self.called:
+            results = self.callable(s,l,t)
+            self.called = True
+            return results
+        raise ParseException(s,l,"")
+    def reset(self):
+        self.called = False
+
+def traceParseAction(f):
+    """
+    Decorator for debugging parse actions. 
+    
+    When the parse action is called, this decorator will print C{">> entering I{method-name}(line:I{current_source_line}, I{parse_location}, I{matched_tokens})".}
+    When the parse action completes, the decorator will print C{"<<"} followed by the returned value, or any exception that the parse action raised.
+
+    Example::
+        wd = Word(alphas)
+
+        @traceParseAction
+        def remove_duplicate_chars(tokens):
+            return ''.join(sorted(set(''.join(tokens))))
+
+        wds = OneOrMore(wd).setParseAction(remove_duplicate_chars)
+        print(wds.parseString("slkdjs sld sldd sdlf sdljf"))
+    prints::
+        >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {}))
+        <3:
+            thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc
+        sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) )
+        try:
+            ret = f(*paArgs)
+        except Exception as exc:
+            sys.stderr.write( "< ['aa', 'bb', 'cc']
+        delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE']
+    """
+    dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..."
+    if combine:
+        return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName)
+    else:
+        return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName)
+
+def countedArray( expr, intExpr=None ):
+    """
+    Helper to define a counted list of expressions.
+    This helper defines a pattern of the form::
+        integer expr expr expr...
+    where the leading integer tells how many expr expressions follow.
+    The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed.
+    
+    If C{intExpr} is specified, it should be a pyparsing expression that produces an integer value.
+
+    Example::
+        countedArray(Word(alphas)).parseString('2 ab cd ef')  # -> ['ab', 'cd']
+
+        # in this parser, the leading integer value is given in binary,
+        # '10' indicating that 2 values are in the array
+        binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2))
+        countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef')  # -> ['ab', 'cd']
+    """
+    arrayExpr = Forward()
+    def countFieldParseAction(s,l,t):
+        n = t[0]
+        arrayExpr << (n and Group(And([expr]*n)) or Group(empty))
+        return []
+    if intExpr is None:
+        intExpr = Word(nums).setParseAction(lambda t:int(t[0]))
+    else:
+        intExpr = intExpr.copy()
+    intExpr.setName("arrayLen")
+    intExpr.addParseAction(countFieldParseAction, callDuringTry=True)
+    return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...')
+
+def _flatten(L):
+    ret = []
+    for i in L:
+        if isinstance(i,list):
+            ret.extend(_flatten(i))
+        else:
+            ret.append(i)
+    return ret
+
+def matchPreviousLiteral(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousLiteral(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches a
+    previous literal, will also match the leading C{"1:1"} in C{"1:10"}.
+    If this is not desired, use C{matchPreviousExpr}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    def copyTokenToRepeater(s,l,t):
+        if t:
+            if len(t) == 1:
+                rep << t[0]
+            else:
+                # flatten t tokens
+                tflat = _flatten(t.asList())
+                rep << And(Literal(tt) for tt in tflat)
+        else:
+            rep << Empty()
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def matchPreviousExpr(expr):
+    """
+    Helper to define an expression that is indirectly defined from
+    the tokens matched in a previous expression, that is, it looks
+    for a 'repeat' of a previous expression.  For example::
+        first = Word(nums)
+        second = matchPreviousExpr(first)
+        matchExpr = first + ":" + second
+    will match C{"1:1"}, but not C{"1:2"}.  Because this matches by
+    expressions, will I{not} match the leading C{"1:1"} in C{"1:10"};
+    the expressions are evaluated first, and then compared, so
+    C{"1"} is compared with C{"10"}.
+    Do I{not} use with packrat parsing enabled.
+    """
+    rep = Forward()
+    e2 = expr.copy()
+    rep <<= e2
+    def copyTokenToRepeater(s,l,t):
+        matchTokens = _flatten(t.asList())
+        def mustMatchTheseTokens(s,l,t):
+            theseTokens = _flatten(t.asList())
+            if  theseTokens != matchTokens:
+                raise ParseException("",0,"")
+        rep.setParseAction( mustMatchTheseTokens, callDuringTry=True )
+    expr.addParseAction(copyTokenToRepeater, callDuringTry=True)
+    rep.setName('(prev) ' + _ustr(expr))
+    return rep
+
+def _escapeRegexRangeChars(s):
+    #~  escape these chars: ^-]
+    for c in r"\^-]":
+        s = s.replace(c,_bslash+c)
+    s = s.replace("\n",r"\n")
+    s = s.replace("\t",r"\t")
+    return _ustr(s)
+
+def oneOf( strs, caseless=False, useRegex=True ):
+    """
+    Helper to quickly define a set of alternative Literals, and makes sure to do
+    longest-first testing when there is a conflict, regardless of the input order,
+    but returns a C{L{MatchFirst}} for best performance.
+
+    Parameters:
+     - strs - a string of space-delimited literals, or a collection of string literals
+     - caseless - (default=C{False}) - treat all literals as caseless
+     - useRegex - (default=C{True}) - as an optimization, will generate a Regex
+          object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or
+          if creating a C{Regex} raises an exception)
+
+    Example::
+        comp_oper = oneOf("< = > <= >= !=")
+        var = Word(alphas)
+        number = Word(nums)
+        term = var | number
+        comparison_expr = term + comp_oper + term
+        print(comparison_expr.searchString("B = 12  AA=23 B<=AA AA>12"))
+    prints::
+        [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']]
+    """
+    if caseless:
+        isequal = ( lambda a,b: a.upper() == b.upper() )
+        masks = ( lambda a,b: b.upper().startswith(a.upper()) )
+        parseElementClass = CaselessLiteral
+    else:
+        isequal = ( lambda a,b: a == b )
+        masks = ( lambda a,b: b.startswith(a) )
+        parseElementClass = Literal
+
+    symbols = []
+    if isinstance(strs,basestring):
+        symbols = strs.split()
+    elif isinstance(strs, Iterable):
+        symbols = list(strs)
+    else:
+        warnings.warn("Invalid argument to oneOf, expected string or iterable",
+                SyntaxWarning, stacklevel=2)
+    if not symbols:
+        return NoMatch()
+
+    i = 0
+    while i < len(symbols)-1:
+        cur = symbols[i]
+        for j,other in enumerate(symbols[i+1:]):
+            if ( isequal(other, cur) ):
+                del symbols[i+j+1]
+                break
+            elif ( masks(cur, other) ):
+                del symbols[i+j+1]
+                symbols.insert(i,other)
+                cur = other
+                break
+        else:
+            i += 1
+
+    if not caseless and useRegex:
+        #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] ))
+        try:
+            if len(symbols)==len("".join(symbols)):
+                return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols))
+            else:
+                return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols))
+        except Exception:
+            warnings.warn("Exception creating Regex for oneOf, building MatchFirst",
+                    SyntaxWarning, stacklevel=2)
+
+
+    # last resort, just use MatchFirst
+    return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols))
+
+def dictOf( key, value ):
+    """
+    Helper to easily and clearly define a dictionary by specifying the respective patterns
+    for the key and value.  Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens
+    in the proper order.  The key pattern can include delimiting markers or punctuation,
+    as long as they are suppressed, thereby leaving the significant key text.  The value
+    pattern can include named results, so that the C{Dict} results can include named token
+    fields.
+
+    Example::
+        text = "shape: SQUARE posn: upper left color: light blue texture: burlap"
+        attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join))
+        print(OneOrMore(attr_expr).parseString(text).dump())
+        
+        attr_label = label
+        attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)
+
+        # similar to Dict, but simpler call format
+        result = dictOf(attr_label, attr_value).parseString(text)
+        print(result.dump())
+        print(result['shape'])
+        print(result.shape)  # object attribute access works too
+        print(result.asDict())
+    prints::
+        [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']]
+        - color: light blue
+        - posn: upper left
+        - shape: SQUARE
+        - texture: burlap
+        SQUARE
+        SQUARE
+        {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'}
+    """
+    return Dict( ZeroOrMore( Group ( key + value ) ) )
+
+def originalTextFor(expr, asString=True):
+    """
+    Helper to return the original, untokenized text for a given expression.  Useful to
+    restore the parsed fields of an HTML start tag into the raw tag text itself, or to
+    revert separate tokens with intervening whitespace back to the original matching
+    input text. By default, returns astring containing the original parsed text.  
+       
+    If the optional C{asString} argument is passed as C{False}, then the return value is a 
+    C{L{ParseResults}} containing any results names that were originally matched, and a 
+    single token containing the original matched text from the input string.  So if 
+    the expression passed to C{L{originalTextFor}} contains expressions with defined
+    results names, you must set C{asString} to C{False} if you want to preserve those
+    results name values.
+
+    Example::
+        src = "this is test  bold text  normal text "
+        for tag in ("b","i"):
+            opener,closer = makeHTMLTags(tag)
+            patt = originalTextFor(opener + SkipTo(closer) + closer)
+            print(patt.searchString(src)[0])
+    prints::
+        [' bold text ']
+        ['text']
+    """
+    locMarker = Empty().setParseAction(lambda s,loc,t: loc)
+    endlocMarker = locMarker.copy()
+    endlocMarker.callPreparse = False
+    matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end")
+    if asString:
+        extractText = lambda s,l,t: s[t._original_start:t._original_end]
+    else:
+        def extractText(s,l,t):
+            t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]]
+    matchExpr.setParseAction(extractText)
+    matchExpr.ignoreExprs = expr.ignoreExprs
+    return matchExpr
+
+def ungroup(expr): 
+    """
+    Helper to undo pyparsing's default grouping of And expressions, even
+    if all but one are non-empty.
+    """
+    return TokenConverter(expr).setParseAction(lambda t:t[0])
+
+def locatedExpr(expr):
+    """
+    Helper to decorate a returned token with its starting and ending locations in the input string.
+    This helper adds the following results names:
+     - locn_start = location where matched expression begins
+     - locn_end = location where matched expression ends
+     - value = the actual parsed results
+
+    Be careful if the input text contains C{} characters, you may want to call
+    C{L{ParserElement.parseWithTabs}}
+
+    Example::
+        wd = Word(alphas)
+        for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"):
+            print(match)
+    prints::
+        [[0, 'ljsdf', 5]]
+        [[8, 'lksdjjf', 15]]
+        [[18, 'lkkjj', 23]]
+    """
+    locator = Empty().setParseAction(lambda s,l,t: l)
+    return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end"))
+
+
+# convenience constants for positional expressions
+empty       = Empty().setName("empty")
+lineStart   = LineStart().setName("lineStart")
+lineEnd     = LineEnd().setName("lineEnd")
+stringStart = StringStart().setName("stringStart")
+stringEnd   = StringEnd().setName("stringEnd")
+
+_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
+_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16)))
+_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8)))
+_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1)
+_charRange = Group(_singleChar + Suppress("-") + _singleChar)
+_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]"
+
+def srange(s):
+    r"""
+    Helper to easily define string ranges for use in Word construction.  Borrows
+    syntax from regexp '[]' string range definitions::
+        srange("[0-9]")   -> "0123456789"
+        srange("[a-z]")   -> "abcdefghijklmnopqrstuvwxyz"
+        srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_"
+    The input string must be enclosed in []'s, and the returned string is the expanded
+    character set joined into a single string.
+    The values enclosed in the []'s may be:
+     - a single character
+     - an escaped character with a leading backslash (such as C{\-} or C{\]})
+     - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character) 
+         (C{\0x##} is also supported for backwards compatibility) 
+     - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character)
+     - a range of any of the above, separated by a dash (C{'a-z'}, etc.)
+     - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.)
+    """
+    _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1))
+    try:
+        return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body)
+    except Exception:
+        return ""
+
+def matchOnlyAtCol(n):
+    """
+    Helper method for defining parse actions that require matching at a specific
+    column in the input text.
+    """
+    def verifyCol(strg,locn,toks):
+        if col(locn,strg) != n:
+            raise ParseException(strg,locn,"matched token not at column %d" % n)
+    return verifyCol
+
+def replaceWith(replStr):
+    """
+    Helper method for common parse actions that simply return a literal value.  Especially
+    useful when used with C{L{transformString}()}.
+
+    Example::
+        num = Word(nums).setParseAction(lambda toks: int(toks[0]))
+        na = oneOf("N/A NA").setParseAction(replaceWith(math.nan))
+        term = na | num
+        
+        OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234]
+    """
+    return lambda s,l,t: [replStr]
+
+def removeQuotes(s,l,t):
+    """
+    Helper parse action for removing quotation marks from parsed quoted strings.
+
+    Example::
+        # by default, quotation marks are included in parsed results
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"]
+
+        # use removeQuotes to strip quotation marks from parsed results
+        quotedString.setParseAction(removeQuotes)
+        quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"]
+    """
+    return t[0][1:-1]
+
+def tokenMap(func, *args):
+    """
+    Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional 
+    args are passed, they are forwarded to the given function as additional arguments after
+    the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the
+    parsed data to an integer using base 16.
+
+    Example (compare the last to example in L{ParserElement.transformString}::
+        hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16))
+        hex_ints.runTests('''
+            00 11 22 aa FF 0a 0d 1a
+            ''')
+        
+        upperword = Word(alphas).setParseAction(tokenMap(str.upper))
+        OneOrMore(upperword).runTests('''
+            my kingdom for a horse
+            ''')
+
+        wd = Word(alphas).setParseAction(tokenMap(str.title))
+        OneOrMore(wd).setParseAction(' '.join).runTests('''
+            now is the winter of our discontent made glorious summer by this sun of york
+            ''')
+    prints::
+        00 11 22 aa FF 0a 0d 1a
+        [0, 17, 34, 170, 255, 10, 13, 26]
+
+        my kingdom for a horse
+        ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE']
+
+        now is the winter of our discontent made glorious summer by this sun of york
+        ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York']
+    """
+    def pa(s,l,t):
+        return [func(tokn, *args) for tokn in t]
+
+    try:
+        func_name = getattr(func, '__name__', 
+                            getattr(func, '__class__').__name__)
+    except Exception:
+        func_name = str(func)
+    pa.__name__ = func_name
+
+    return pa
+
+upcaseTokens = tokenMap(lambda t: _ustr(t).upper())
+"""(Deprecated) Helper parse action to convert tokens to upper case. Deprecated in favor of L{pyparsing_common.upcaseTokens}"""
+
+downcaseTokens = tokenMap(lambda t: _ustr(t).lower())
+"""(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of L{pyparsing_common.downcaseTokens}"""
+    
+def _makeTags(tagStr, xml):
+    """Internal helper to construct opening and closing tag expressions, given a tag name"""
+    if isinstance(tagStr,basestring):
+        resname = tagStr
+        tagStr = Keyword(tagStr, caseless=not xml)
+    else:
+        resname = tagStr.name
+
+    tagAttrName = Word(alphas,alphanums+"_-:")
+    if (xml):
+        tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes )
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    else:
+        printablesLessRAbrack = "".join(c for c in printables if c not in ">")
+        tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack)
+        openTag = Suppress("<") + tagStr("tag") + \
+                Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \
+                Optional( Suppress("=") + tagAttrValue ) ))) + \
+                Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">")
+    closeTag = Combine(_L("")
+
+    openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname)
+    closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("" % resname)
+    openTag.tag = resname
+    closeTag.tag = resname
+    return openTag, closeTag
+
+def makeHTMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for HTML, given a tag name. Matches
+    tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values.
+
+    Example::
+        text = 'More info at the pyparsing wiki page'
+        # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple
+        a,a_end = makeHTMLTags("A")
+        link_expr = a + SkipTo(a_end)("link_text") + a_end
+        
+        for link in link_expr.searchString(text):
+            # attributes in the  tag (like "href" shown here) are also accessible as named results
+            print(link.link_text, '->', link.href)
+    prints::
+        pyparsing -> http://pyparsing.wikispaces.com
+    """
+    return _makeTags( tagStr, False )
+
+def makeXMLTags(tagStr):
+    """
+    Helper to construct opening and closing tag expressions for XML, given a tag name. Matches
+    tags only in the given upper/lower case.
+
+    Example: similar to L{makeHTMLTags}
+    """
+    return _makeTags( tagStr, True )
+
+def withAttribute(*args,**attrDict):
+    """
+    Helper to create a validating parse action to be used with start tags created
+    with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag
+    with a required attribute value, to avoid false matches on common tags such as
+    C{} or C{
}. + + Call C{withAttribute} with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in C{(align="right")}, or + - as an explicit dict with C{**} operator, when an attribute name is also a Python + reserved word, as in C{**{"class":"Customer", "align":"right"}} + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + If just testing for C{class} (with or without a namespace), use C{L{withClass}}. + + To verify that the attribute exists, but without specifying a value, pass + C{withAttribute.ANY_VALUE} as the value. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this has no type
+
+ + ''' + div,div_end = makeHTMLTags("div") + + # only match div tag having a type attribute with value "grid" + div_grid = div().setParseAction(withAttribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + # construct a match with any div tag having a type attribute, regardless of the value + div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +def withClass(classname, namespace=''): + """ + Simplified version of C{L{withAttribute}} when matching on a div class - made + difficult because C{class} is a reserved word in Python. + + Example:: + html = ''' +
+ Some text +
1 4 0 1 0
+
1,3 2,3 1,1
+
this <div> has no class
+
+ + ''' + div,div_end = makeHTMLTags("div") + div_grid = div().setParseAction(withClass("grid")) + + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + classattr = "%s:class" % namespace if namespace else "class" + return withAttribute(**{classattr : classname}) + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): + """ + Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. The generated parser will also recognize the use + of parentheses to override operator precedences (see example below). + + Note: if you define a deep operator list, you may see performance issues + when using infixNotation. See L{ParserElement.enablePackrat} for a + mechanism to potentially improve your parser performance. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted); if the parse action + is passed a tuple or list of functions, this is equivalent to + calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) + - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) + - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) + + Example:: + # simple example of four-function arithmetic with ints and variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infixNotation(integer | varname, + [ + ('-', 1, opAssoc.RIGHT), + (oneOf('* /'), 2, opAssoc.LEFT), + (oneOf('+ -'), 2, opAssoc.LEFT), + ]) + + arith_expr.runTests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', fullDump=False) + prints:: + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + ret = Forward() + lastExpr = baseExpr | ( lpar + ret + rpar ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward().setName(termName) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.setParseAction(*pa) + else: + matchExpr.setParseAction(pa) + thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) + lastExpr = thisExpr + ret <<= lastExpr + return ret + +operatorPrecedence = infixNotation +"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release.""" + +dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") +sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") +quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| + Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): + """ + Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default=C{"("}); can also be a pyparsing expression + - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression + - content - expression for items within the nested lists (default=C{None}) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{quotedString}) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the C{ignoreExpr} argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. + The default is L{quotedString}, but if no expressions are to be ignored, + then pass C{None} for this argument. + + Example:: + data_type = oneOf("void int short long char float double") + decl_data_type = Combine(data_type + Optional(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR,RPAR = map(Suppress, "()") + + code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Optional(delimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(cStyleComment) + + source_code = ''' + int is_odd(int x) { + return (x%2); + } + + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.searchString(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + prints:: + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + ret.setName('nested %s%s expression' % (opener,closer)) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """ + Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=C{True}) + + A valid block must contain at least one C{blockStatement}. + + Example:: + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group( funcDecl + func_body ) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << ( funcDef | assignment | identifier ) + + module_body = OneOrMore(stmt) + + parseTree = module_body.parseString(data) + parseTree.pprint() + prints:: + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') + PEER = Empty().setParseAction(checkPeerIndent).setName('') + UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') + if indent: + smExpr = Group( Optional(NL) + + #~ FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.setName('indented block') + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) +_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) +commonHTMLEntity = Regex('&(?P' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") +def replaceHTMLEntity(t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") +"Comment of the form C{/* ... */}" + +htmlComment = Regex(r"").setName("HTML comment") +"Comment of the form C{}" + +restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") +dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") +"Comment of the form C{// ... (to end of line)}" + +cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") +"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}" + +javaStyleComment = cppStyleComment +"Same as C{L{cppStyleComment}}" + +pythonStyleComment = Regex(r"#.*").setName("Python style comment") +"Comment of the form C{# ... (to end of line)}" + +_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + + Optional( Word(" \t") + + ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") +commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") +"""(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. + This expression is deprecated in favor of L{pyparsing_common.comma_separated_list}.""" + +# some other useful expressions - using lower-case class name since we are really using this as a namespace +class pyparsing_common: + """ + Here are some common low-level expressions that may be useful in jump-starting parser development: + - numeric forms (L{integers}, L{reals}, L{scientific notation}) + - common L{programming identifiers} + - network addresses (L{MAC}, L{IPv4}, L{IPv6}) + - ISO8601 L{dates} and L{datetime} + - L{UUID} + - L{comma-separated list} + Parse actions: + - C{L{convertToInteger}} + - C{L{convertToFloat}} + - C{L{convertToDate}} + - C{L{convertToDatetime}} + - C{L{stripHTMLTags}} + - C{L{upcaseTokens}} + - C{L{downcaseTokens}} + + Example:: + pyparsing_common.number.runTests(''' + # any int or real number, returned as the appropriate type + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.fnumber.runTests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.hex_integer.runTests(''' + # hex numbers + 100 + FF + ''') + + pyparsing_common.fraction.runTests(''' + # fractions + 1/2 + -3/4 + ''') + + pyparsing_common.mixed_integer.runTests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(''' + # uuid + 12345678-1234-5678-1234-567812345678 + ''') + prints:: + # any int or real number, returned as the appropriate type + 100 + [100] + + -100 + [-100] + + +100 + [100] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # any int or real number, returned as float + 100 + [100.0] + + -100 + [-100.0] + + +100 + [100.0] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # hex numbers + 100 + [256] + + FF + [255] + + # fractions + 1/2 + [0.5] + + -3/4 + [-0.75] + + # mixed fractions + 1 + [1] + + 1/2 + [0.5] + + -3/4 + [-0.75] + + 1-3/4 + [1.75] + + # uuid + 12345678-1234-5678-1234-567812345678 + [UUID('12345678-1234-5678-1234-567812345678')] + """ + + convertToInteger = tokenMap(int) + """ + Parse action for converting parsed integers to Python int + """ + + convertToFloat = tokenMap(float) + """ + Parse action for converting parsed numbers to Python float + """ + + integer = Word(nums).setName("integer").setParseAction(convertToInteger) + """expression that parses an unsigned integer, returns an int""" + + hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) + """expression that parses a hexadecimal integer, returns an int""" + + signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) + """expression that parses an integer with optional leading sign, returns an int""" + + fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") + """fractional expression of an integer divided by an integer, returns a float""" + fraction.addParseAction(lambda t: t[0]/t[-1]) + + mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") + """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" + mixed_integer.addParseAction(sum) + + real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) + """expression that parses a floating point number and returns a float""" + + sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) + """expression that parses a floating point number with optional scientific notation and returns a float""" + + # streamlining this expression makes the docs nicer-looking + number = (sci_real | real | signed_integer).streamline() + """any numeric expression, returns the corresponding Python type""" + + fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) + """any int or real number, returned as float""" + + identifier = Word(alphas+'_', alphanums+'_').setName("identifier") + """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" + + ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") + "IPv4 address (C{0.0.0.0 - 255.255.255.255})" + + _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") + _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") + _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") + _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) + _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") + ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") + "IPv6 address (long, short, or mixed form)" + + mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") + "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" + + @staticmethod + def convertToDate(fmt="%Y-%m-%d"): + """ + Helper to create a parse action for converting parsed date string to Python datetime.date + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"}) + + Example:: + date_expr = pyparsing_common.iso8601_date.copy() + date_expr.setParseAction(pyparsing_common.convertToDate()) + print(date_expr.parseString("1999-12-31")) + prints:: + [datetime.date(1999, 12, 31)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt).date() + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + @staticmethod + def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): + """ + Helper to create a parse action for converting parsed datetime string to Python datetime.datetime + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"}) + + Example:: + dt_expr = pyparsing_common.iso8601_datetime.copy() + dt_expr.setParseAction(pyparsing_common.convertToDatetime()) + print(dt_expr.parseString("1999-12-31T23:59:59.999")) + prints:: + [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt) + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + iso8601_date = Regex(r'(?P\d{4})(?:-(?P\d\d)(?:-(?P\d\d))?)?').setName("ISO8601 date") + "ISO8601 date (C{yyyy-mm-dd})" + + iso8601_datetime = Regex(r'(?P\d{4})-(?P\d\d)-(?P\d\d)[T ](?P\d\d):(?P\d\d)(:(?P\d\d(\.\d*)?)?)?(?PZ|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") + "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}" + + uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") + "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})" + + _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() + @staticmethod + def stripHTMLTags(s, l, tokens): + """ + Parse action to remove HTML tags from web page HTML source + + Example:: + # strip HTML links from normal text + text = 'More info at the
pyparsing wiki page' + td,td_end = makeHTMLTags("TD") + table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end + + print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page' + """ + return pyparsing_common._html_stripper.transformString(tokens[0]) + + _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') + + Optional( White(" \t") ) ) ).streamline().setName("commaItem") + comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") + """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" + + upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) + """Parse action to convert tokens to upper case.""" + + downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) + """Parse action to convert tokens to lower case.""" + + +if __name__ == "__main__": + + selectToken = CaselessLiteral("select") + fromToken = CaselessLiteral("from") + + ident = Word(alphas, alphanums + "_$") + + columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + columnNameList = Group(delimitedList(columnName)).setName("columns") + columnSpec = ('*' | columnNameList) + + tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + tableNameList = Group(delimitedList(tableName)).setName("tables") + + simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") + + # demo runTests method, including embedded comments in test string + simpleSQL.runTests(""" + # '*' as column list and dotted table name + select * from SYS.XYZZY + + # caseless match on "SELECT", and casts back to "select" + SELECT * from XYZZY, ABC + + # list of column names, and mixed case SELECT keyword + Select AA,BB,CC from Sys.dual + + # multiple tables + Select A, B, C from Sys.dual, Table2 + + # invalid SELECT keyword - should fail + Xelect A, B, C from Sys.dual + + # incomplete command - should fail + Select + + # invalid column name - should fail + Select ^^^ frox Sys.dual + + """) + + pyparsing_common.number.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + # any int or real number, returned as float + pyparsing_common.fnumber.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + pyparsing_common.hex_integer.runTests(""" + 100 + FF + """) + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(""" + 12345678-1234-5678-1234-567812345678 + """) diff --git a/lib/pkg_resources/_vendor/six.py b/lib/pkg_resources/_vendor/six.py new file mode 100644 index 0000000..190c023 --- /dev/null +++ b/lib/pkg_resources/_vendor/six.py @@ -0,0 +1,868 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +# Copyright (c) 2010-2015 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.10.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + if from_value is None: + raise value + raise value from from_value +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + raise value from from_value +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/lib/pkg_resources/extern/__init__.py b/lib/pkg_resources/extern/__init__.py new file mode 100644 index 0000000..c1eb9e9 --- /dev/null +++ b/lib/pkg_resources/extern/__init__.py @@ -0,0 +1,73 @@ +import sys + + +class VendorImporter: + """ + A PEP 302 meta path importer for finding optionally-vendored + or otherwise naturally-installed packages from root_name. + """ + + def __init__(self, root_name, vendored_names=(), vendor_pkg=None): + self.root_name = root_name + self.vendored_names = set(vendored_names) + self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') + + @property + def search_path(self): + """ + Search first the vendor package then as a natural package. + """ + yield self.vendor_pkg + '.' + yield '' + + def find_module(self, fullname, path=None): + """ + Return self when fullname starts with root_name and the + target module is one vendored through this importer. + """ + root, base, target = fullname.partition(self.root_name + '.') + if root: + return + if not any(map(target.startswith, self.vendored_names)): + return + return self + + def load_module(self, fullname): + """ + Iterate over the search path to locate and load fullname. + """ + root, base, target = fullname.partition(self.root_name + '.') + for prefix in self.search_path: + try: + extant = prefix + target + __import__(extant) + mod = sys.modules[extant] + sys.modules[fullname] = mod + # mysterious hack: + # Remove the reference to the extant package/module + # on later Python versions to cause relative imports + # in the vendor package to resolve the same modules + # as those going through this importer. + if prefix and sys.version_info > (3, 3): + del sys.modules[extant] + return mod + except ImportError: + pass + else: + raise ImportError( + "The '{target}' package is required; " + "normally this is bundled with this package so if you get " + "this warning, consult the packager of your " + "distribution.".format(**locals()) + ) + + def install(self): + """ + Install this importer into sys.meta_path if not already present. + """ + if self not in sys.meta_path: + sys.meta_path.append(self) + + +names = 'packaging', 'pyparsing', 'six', 'appdirs' +VendorImporter(__name__, names).install() diff --git a/lib/pkg_resources/py31compat.py b/lib/pkg_resources/py31compat.py new file mode 100644 index 0000000..a381c42 --- /dev/null +++ b/lib/pkg_resources/py31compat.py @@ -0,0 +1,23 @@ +import os +import errno +import sys + +from .extern import six + + +def _makedirs_31(path, exist_ok=False): + try: + os.makedirs(path) + except OSError as exc: + if not exist_ok or exc.errno != errno.EEXIST: + raise + + +# rely on compatibility behavior until mode considerations +# and exists_ok considerations are disentangled. +# See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663 +needs_makedirs = ( + six.PY2 or + (3, 4) <= sys.version_info < (3, 4, 1) +) +makedirs = _makedirs_31 if needs_makedirs else os.makedirs diff --git a/lib/pytz/__init__.py b/lib/pytz/__init__.py new file mode 100644 index 0000000..6e92317 --- /dev/null +++ b/lib/pytz/__init__.py @@ -0,0 +1,1527 @@ +''' +datetime.tzinfo timezone definitions generated from the +Olson timezone database: + + ftp://elsie.nci.nih.gov/pub/tz*.tar.gz + +See the datetime section of the Python Library Reference for information +on how to use these modules. +''' + +import sys +import datetime +import os.path + +from pytz.exceptions import AmbiguousTimeError +from pytz.exceptions import InvalidTimeError +from pytz.exceptions import NonExistentTimeError +from pytz.exceptions import UnknownTimeZoneError +from pytz.lazy import LazyDict, LazyList, LazySet +from pytz.tzinfo import unpickler, BaseTzInfo +from pytz.tzfile import build_tzinfo + + +# The IANA (nee Olson) database is updated several times a year. +OLSON_VERSION = '2018g' +VERSION = '2018.7' # pip compatible version number. +__version__ = VERSION + +OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling + +__all__ = [ + 'timezone', 'utc', 'country_timezones', 'country_names', + 'AmbiguousTimeError', 'InvalidTimeError', + 'NonExistentTimeError', 'UnknownTimeZoneError', + 'all_timezones', 'all_timezones_set', + 'common_timezones', 'common_timezones_set', + 'BaseTzInfo', +] + + +if sys.version_info[0] > 2: # Python 3.x + + # Python 3.x doesn't have unicode(), making writing code + # for Python 2.3 and Python 3.x a pain. + unicode = str + + def ascii(s): + r""" + >>> ascii('Hello') + 'Hello' + >>> ascii('\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + UnicodeEncodeError: ... + """ + if type(s) == bytes: + s = s.decode('ASCII') + else: + s.encode('ASCII') # Raise an exception if not ASCII + return s # But the string - not a byte string. + +else: # Python 2.x + + def ascii(s): + r""" + >>> ascii('Hello') + 'Hello' + >>> ascii(u'Hello') + 'Hello' + >>> ascii(u'\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + UnicodeEncodeError: ... + """ + return s.encode('ASCII') + + +def open_resource(name): + """Open a resource from the zoneinfo subdir for reading. + + Uses the pkg_resources module if available and no standard file + found at the calculated location. + + It is possible to specify different location for zoneinfo + subdir by using the PYTZ_TZDATADIR environment variable. + """ + name_parts = name.lstrip('/').split('/') + for part in name_parts: + if part == os.path.pardir or os.path.sep in part: + raise ValueError('Bad path segment: %r' % part) + zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None) + if zoneinfo_dir is not None: + filename = os.path.join(zoneinfo_dir, *name_parts) + else: + filename = os.path.join(os.path.dirname(__file__), + 'zoneinfo', *name_parts) + if not os.path.exists(filename): + # http://bugs.launchpad.net/bugs/383171 - we avoid using this + # unless absolutely necessary to help when a broken version of + # pkg_resources is installed. + try: + from pkg_resources import resource_stream + except ImportError: + resource_stream = None + + if resource_stream is not None: + return resource_stream(__name__, 'zoneinfo/' + name) + return open(filename, 'rb') + + +def resource_exists(name): + """Return true if the given resource exists""" + try: + open_resource(name).close() + return True + except IOError: + return False + + +_tzinfo_cache = {} + + +def timezone(zone): + r''' Return a datetime.tzinfo implementation for the given timezone + + >>> from datetime import datetime, timedelta + >>> utc = timezone('UTC') + >>> eastern = timezone('US/Eastern') + >>> eastern.zone + 'US/Eastern' + >>> timezone(unicode('US/Eastern')) is eastern + True + >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) + >>> loc_dt = utc_dt.astimezone(eastern) + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> loc_dt.strftime(fmt) + '2002-10-27 01:00:00 EST (-0500)' + >>> (loc_dt - timedelta(minutes=10)).strftime(fmt) + '2002-10-27 00:50:00 EST (-0500)' + >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt) + '2002-10-27 01:50:00 EDT (-0400)' + >>> (loc_dt + timedelta(minutes=10)).strftime(fmt) + '2002-10-27 01:10:00 EST (-0500)' + + Raises UnknownTimeZoneError if passed an unknown zone. + + >>> try: + ... timezone('Asia/Shangri-La') + ... except UnknownTimeZoneError: + ... print('Unknown') + Unknown + + >>> try: + ... timezone(unicode('\N{TRADE MARK SIGN}')) + ... except UnknownTimeZoneError: + ... print('Unknown') + Unknown + + ''' + if zone.upper() == 'UTC': + return utc + + try: + zone = ascii(zone) + except UnicodeEncodeError: + # All valid timezones are ASCII + raise UnknownTimeZoneError(zone) + + zone = _unmunge_zone(zone) + if zone not in _tzinfo_cache: + if zone in all_timezones_set: + fp = open_resource(zone) + try: + _tzinfo_cache[zone] = build_tzinfo(zone, fp) + finally: + fp.close() + else: + raise UnknownTimeZoneError(zone) + + return _tzinfo_cache[zone] + + +def _unmunge_zone(zone): + """Undo the time zone name munging done by older versions of pytz.""" + return zone.replace('_plus_', '+').replace('_minus_', '-') + + +ZERO = datetime.timedelta(0) +HOUR = datetime.timedelta(hours=1) + + +class UTC(BaseTzInfo): + """UTC + + Optimized UTC implementation. It unpickles using the single module global + instance defined beneath this class declaration. + """ + zone = "UTC" + + _utcoffset = ZERO + _dst = ZERO + _tzname = zone + + def fromutc(self, dt): + if dt.tzinfo is None: + return self.localize(dt) + return super(utc.__class__, self).fromutc(dt) + + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return ZERO + + def __reduce__(self): + return _UTC, () + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + return dt.replace(tzinfo=self) + + def normalize(self, dt, is_dst=False): + '''Correct the timezone information on the given datetime''' + if dt.tzinfo is self: + return dt + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + return dt.astimezone(self) + + def __repr__(self): + return "" + + def __str__(self): + return "UTC" + + +UTC = utc = UTC() # UTC is a singleton + + +def _UTC(): + """Factory function for utc unpickling. + + Makes sure that unpickling a utc instance always returns the same + module global. + + These examples belong in the UTC class above, but it is obscured; or in + the README.txt, but we are not depending on Python 2.4 so integrating + the README.txt examples with the unit tests is not trivial. + + >>> import datetime, pickle + >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc) + >>> naive = dt.replace(tzinfo=None) + >>> p = pickle.dumps(dt, 1) + >>> naive_p = pickle.dumps(naive, 1) + >>> len(p) - len(naive_p) + 17 + >>> new = pickle.loads(p) + >>> new == dt + True + >>> new is dt + False + >>> new.tzinfo is dt.tzinfo + True + >>> utc is UTC is timezone('UTC') + True + >>> utc is timezone('GMT') + False + """ + return utc +_UTC.__safe_for_unpickling__ = True + + +def _p(*args): + """Factory function for unpickling pytz tzinfo instances. + + Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle + by shortening the path. + """ + return unpickler(*args) +_p.__safe_for_unpickling__ = True + + +class _CountryTimezoneDict(LazyDict): + """Map ISO 3166 country code to a list of timezone names commonly used + in that country. + + iso3166_code is the two letter code used to identify the country. + + >>> def print_list(list_of_strings): + ... 'We use a helper so doctests work under Python 2.3 -> 3.x' + ... for s in list_of_strings: + ... print(s) + + >>> print_list(country_timezones['nz']) + Pacific/Auckland + Pacific/Chatham + >>> print_list(country_timezones['ch']) + Europe/Zurich + >>> print_list(country_timezones['CH']) + Europe/Zurich + >>> print_list(country_timezones[unicode('ch')]) + Europe/Zurich + >>> print_list(country_timezones['XXX']) + Traceback (most recent call last): + ... + KeyError: 'XXX' + + Previously, this information was exposed as a function rather than a + dictionary. This is still supported:: + + >>> print_list(country_timezones('nz')) + Pacific/Auckland + Pacific/Chatham + """ + def __call__(self, iso3166_code): + """Backwards compatibility.""" + return self[iso3166_code] + + def _fill(self): + data = {} + zone_tab = open_resource('zone.tab') + try: + for line in zone_tab: + line = line.decode('UTF-8') + if line.startswith('#'): + continue + code, coordinates, zone = line.split(None, 4)[:3] + if zone not in all_timezones_set: + continue + try: + data[code].append(zone) + except KeyError: + data[code] = [zone] + self.data = data + finally: + zone_tab.close() + +country_timezones = _CountryTimezoneDict() + + +class _CountryNameDict(LazyDict): + '''Dictionary proving ISO3166 code -> English name. + + >>> print(country_names['au']) + Australia + ''' + def _fill(self): + data = {} + zone_tab = open_resource('iso3166.tab') + try: + for line in zone_tab.readlines(): + line = line.decode('UTF-8') + if line.startswith('#'): + continue + code, name = line.split(None, 1) + data[code] = name.strip() + self.data = data + finally: + zone_tab.close() + +country_names = _CountryNameDict() + + +# Time-zone info based solely on fixed offsets + +class _FixedOffset(datetime.tzinfo): + + zone = None # to match the standard pytz API + + def __init__(self, minutes): + if abs(minutes) >= 1440: + raise ValueError("absolute offset is too large", minutes) + self._minutes = minutes + self._offset = datetime.timedelta(minutes=minutes) + + def utcoffset(self, dt): + return self._offset + + def __reduce__(self): + return FixedOffset, (self._minutes, ) + + def dst(self, dt): + return ZERO + + def tzname(self, dt): + return None + + def __repr__(self): + return 'pytz.FixedOffset(%d)' % self._minutes + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + return dt.replace(tzinfo=self) + + def normalize(self, dt, is_dst=False): + '''Correct the timezone information on the given datetime''' + if dt.tzinfo is self: + return dt + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + return dt.astimezone(self) + + +def FixedOffset(offset, _tzinfos={}): + """return a fixed-offset timezone based off a number of minutes. + + >>> one = FixedOffset(-330) + >>> one + pytz.FixedOffset(-330) + >>> str(one.utcoffset(datetime.datetime.now())) + '-1 day, 18:30:00' + >>> str(one.dst(datetime.datetime.now())) + '0:00:00' + + >>> two = FixedOffset(1380) + >>> two + pytz.FixedOffset(1380) + >>> str(two.utcoffset(datetime.datetime.now())) + '23:00:00' + >>> str(two.dst(datetime.datetime.now())) + '0:00:00' + + The datetime.timedelta must be between the range of -1 and 1 day, + non-inclusive. + + >>> FixedOffset(1440) + Traceback (most recent call last): + ... + ValueError: ('absolute offset is too large', 1440) + + >>> FixedOffset(-1440) + Traceback (most recent call last): + ... + ValueError: ('absolute offset is too large', -1440) + + An offset of 0 is special-cased to return UTC. + + >>> FixedOffset(0) is UTC + True + + There should always be only one instance of a FixedOffset per timedelta. + This should be true for multiple creation calls. + + >>> FixedOffset(-330) is one + True + >>> FixedOffset(1380) is two + True + + It should also be true for pickling. + + >>> import pickle + >>> pickle.loads(pickle.dumps(one)) is one + True + >>> pickle.loads(pickle.dumps(two)) is two + True + """ + if offset == 0: + return UTC + + info = _tzinfos.get(offset) + if info is None: + # We haven't seen this one before. we need to save it. + + # Use setdefault to avoid a race condition and make sure we have + # only one + info = _tzinfos.setdefault(offset, _FixedOffset(offset)) + + return info + +FixedOffset.__safe_for_unpickling__ = True + + +def _test(): + import doctest + sys.path.insert(0, os.pardir) + import pytz + return doctest.testmod(pytz) + +if __name__ == '__main__': + _test() +all_timezones = \ +['Africa/Abidjan', + 'Africa/Accra', + 'Africa/Addis_Ababa', + 'Africa/Algiers', + 'Africa/Asmara', + 'Africa/Asmera', + 'Africa/Bamako', + 'Africa/Bangui', + 'Africa/Banjul', + 'Africa/Bissau', + 'Africa/Blantyre', + 'Africa/Brazzaville', + 'Africa/Bujumbura', + 'Africa/Cairo', + 'Africa/Casablanca', + 'Africa/Ceuta', + 'Africa/Conakry', + 'Africa/Dakar', + 'Africa/Dar_es_Salaam', + 'Africa/Djibouti', + 'Africa/Douala', + 'Africa/El_Aaiun', + 'Africa/Freetown', + 'Africa/Gaborone', + 'Africa/Harare', + 'Africa/Johannesburg', + 'Africa/Juba', + 'Africa/Kampala', + 'Africa/Khartoum', + 'Africa/Kigali', + 'Africa/Kinshasa', + 'Africa/Lagos', + 'Africa/Libreville', + 'Africa/Lome', + 'Africa/Luanda', + 'Africa/Lubumbashi', + 'Africa/Lusaka', + 'Africa/Malabo', + 'Africa/Maputo', + 'Africa/Maseru', + 'Africa/Mbabane', + 'Africa/Mogadishu', + 'Africa/Monrovia', + 'Africa/Nairobi', + 'Africa/Ndjamena', + 'Africa/Niamey', + 'Africa/Nouakchott', + 'Africa/Ouagadougou', + 'Africa/Porto-Novo', + 'Africa/Sao_Tome', + 'Africa/Timbuktu', + 'Africa/Tripoli', + 'Africa/Tunis', + 'Africa/Windhoek', + 'America/Adak', + 'America/Anchorage', + 'America/Anguilla', + 'America/Antigua', + 'America/Araguaina', + 'America/Argentina/Buenos_Aires', + 'America/Argentina/Catamarca', + 'America/Argentina/ComodRivadavia', + 'America/Argentina/Cordoba', + 'America/Argentina/Jujuy', + 'America/Argentina/La_Rioja', + 'America/Argentina/Mendoza', + 'America/Argentina/Rio_Gallegos', + 'America/Argentina/Salta', + 'America/Argentina/San_Juan', + 'America/Argentina/San_Luis', + 'America/Argentina/Tucuman', + 'America/Argentina/Ushuaia', + 'America/Aruba', + 'America/Asuncion', + 'America/Atikokan', + 'America/Atka', + 'America/Bahia', + 'America/Bahia_Banderas', + 'America/Barbados', + 'America/Belem', + 'America/Belize', + 'America/Blanc-Sablon', + 'America/Boa_Vista', + 'America/Bogota', + 'America/Boise', + 'America/Buenos_Aires', + 'America/Cambridge_Bay', + 'America/Campo_Grande', + 'America/Cancun', + 'America/Caracas', + 'America/Catamarca', + 'America/Cayenne', + 'America/Cayman', + 'America/Chicago', + 'America/Chihuahua', + 'America/Coral_Harbour', + 'America/Cordoba', + 'America/Costa_Rica', + 'America/Creston', + 'America/Cuiaba', + 'America/Curacao', + 'America/Danmarkshavn', + 'America/Dawson', + 'America/Dawson_Creek', + 'America/Denver', + 'America/Detroit', + 'America/Dominica', + 'America/Edmonton', + 'America/Eirunepe', + 'America/El_Salvador', + 'America/Ensenada', + 'America/Fort_Nelson', + 'America/Fort_Wayne', + 'America/Fortaleza', + 'America/Glace_Bay', + 'America/Godthab', + 'America/Goose_Bay', + 'America/Grand_Turk', + 'America/Grenada', + 'America/Guadeloupe', + 'America/Guatemala', + 'America/Guayaquil', + 'America/Guyana', + 'America/Halifax', + 'America/Havana', + 'America/Hermosillo', + 'America/Indiana/Indianapolis', + 'America/Indiana/Knox', + 'America/Indiana/Marengo', + 'America/Indiana/Petersburg', + 'America/Indiana/Tell_City', + 'America/Indiana/Vevay', + 'America/Indiana/Vincennes', + 'America/Indiana/Winamac', + 'America/Indianapolis', + 'America/Inuvik', + 'America/Iqaluit', + 'America/Jamaica', + 'America/Jujuy', + 'America/Juneau', + 'America/Kentucky/Louisville', + 'America/Kentucky/Monticello', + 'America/Knox_IN', + 'America/Kralendijk', + 'America/La_Paz', + 'America/Lima', + 'America/Los_Angeles', + 'America/Louisville', + 'America/Lower_Princes', + 'America/Maceio', + 'America/Managua', + 'America/Manaus', + 'America/Marigot', + 'America/Martinique', + 'America/Matamoros', + 'America/Mazatlan', + 'America/Mendoza', + 'America/Menominee', + 'America/Merida', + 'America/Metlakatla', + 'America/Mexico_City', + 'America/Miquelon', + 'America/Moncton', + 'America/Monterrey', + 'America/Montevideo', + 'America/Montreal', + 'America/Montserrat', + 'America/Nassau', + 'America/New_York', + 'America/Nipigon', + 'America/Nome', + 'America/Noronha', + 'America/North_Dakota/Beulah', + 'America/North_Dakota/Center', + 'America/North_Dakota/New_Salem', + 'America/Ojinaga', + 'America/Panama', + 'America/Pangnirtung', + 'America/Paramaribo', + 'America/Phoenix', + 'America/Port-au-Prince', + 'America/Port_of_Spain', + 'America/Porto_Acre', + 'America/Porto_Velho', + 'America/Puerto_Rico', + 'America/Punta_Arenas', + 'America/Rainy_River', + 'America/Rankin_Inlet', + 'America/Recife', + 'America/Regina', + 'America/Resolute', + 'America/Rio_Branco', + 'America/Rosario', + 'America/Santa_Isabel', + 'America/Santarem', + 'America/Santiago', + 'America/Santo_Domingo', + 'America/Sao_Paulo', + 'America/Scoresbysund', + 'America/Shiprock', + 'America/Sitka', + 'America/St_Barthelemy', + 'America/St_Johns', + 'America/St_Kitts', + 'America/St_Lucia', + 'America/St_Thomas', + 'America/St_Vincent', + 'America/Swift_Current', + 'America/Tegucigalpa', + 'America/Thule', + 'America/Thunder_Bay', + 'America/Tijuana', + 'America/Toronto', + 'America/Tortola', + 'America/Vancouver', + 'America/Virgin', + 'America/Whitehorse', + 'America/Winnipeg', + 'America/Yakutat', + 'America/Yellowknife', + 'Antarctica/Casey', + 'Antarctica/Davis', + 'Antarctica/DumontDUrville', + 'Antarctica/Macquarie', + 'Antarctica/Mawson', + 'Antarctica/McMurdo', + 'Antarctica/Palmer', + 'Antarctica/Rothera', + 'Antarctica/South_Pole', + 'Antarctica/Syowa', + 'Antarctica/Troll', + 'Antarctica/Vostok', + 'Arctic/Longyearbyen', + 'Asia/Aden', + 'Asia/Almaty', + 'Asia/Amman', + 'Asia/Anadyr', + 'Asia/Aqtau', + 'Asia/Aqtobe', + 'Asia/Ashgabat', + 'Asia/Ashkhabad', + 'Asia/Atyrau', + 'Asia/Baghdad', + 'Asia/Bahrain', + 'Asia/Baku', + 'Asia/Bangkok', + 'Asia/Barnaul', + 'Asia/Beirut', + 'Asia/Bishkek', + 'Asia/Brunei', + 'Asia/Calcutta', + 'Asia/Chita', + 'Asia/Choibalsan', + 'Asia/Chongqing', + 'Asia/Chungking', + 'Asia/Colombo', + 'Asia/Dacca', + 'Asia/Damascus', + 'Asia/Dhaka', + 'Asia/Dili', + 'Asia/Dubai', + 'Asia/Dushanbe', + 'Asia/Famagusta', + 'Asia/Gaza', + 'Asia/Harbin', + 'Asia/Hebron', + 'Asia/Ho_Chi_Minh', + 'Asia/Hong_Kong', + 'Asia/Hovd', + 'Asia/Irkutsk', + 'Asia/Istanbul', + 'Asia/Jakarta', + 'Asia/Jayapura', + 'Asia/Jerusalem', + 'Asia/Kabul', + 'Asia/Kamchatka', + 'Asia/Karachi', + 'Asia/Kashgar', + 'Asia/Kathmandu', + 'Asia/Katmandu', + 'Asia/Khandyga', + 'Asia/Kolkata', + 'Asia/Krasnoyarsk', + 'Asia/Kuala_Lumpur', + 'Asia/Kuching', + 'Asia/Kuwait', + 'Asia/Macao', + 'Asia/Macau', + 'Asia/Magadan', + 'Asia/Makassar', + 'Asia/Manila', + 'Asia/Muscat', + 'Asia/Nicosia', + 'Asia/Novokuznetsk', + 'Asia/Novosibirsk', + 'Asia/Omsk', + 'Asia/Oral', + 'Asia/Phnom_Penh', + 'Asia/Pontianak', + 'Asia/Pyongyang', + 'Asia/Qatar', + 'Asia/Qyzylorda', + 'Asia/Rangoon', + 'Asia/Riyadh', + 'Asia/Saigon', + 'Asia/Sakhalin', + 'Asia/Samarkand', + 'Asia/Seoul', + 'Asia/Shanghai', + 'Asia/Singapore', + 'Asia/Srednekolymsk', + 'Asia/Taipei', + 'Asia/Tashkent', + 'Asia/Tbilisi', + 'Asia/Tehran', + 'Asia/Tel_Aviv', + 'Asia/Thimbu', + 'Asia/Thimphu', + 'Asia/Tokyo', + 'Asia/Tomsk', + 'Asia/Ujung_Pandang', + 'Asia/Ulaanbaatar', + 'Asia/Ulan_Bator', + 'Asia/Urumqi', + 'Asia/Ust-Nera', + 'Asia/Vientiane', + 'Asia/Vladivostok', + 'Asia/Yakutsk', + 'Asia/Yangon', + 'Asia/Yekaterinburg', + 'Asia/Yerevan', + 'Atlantic/Azores', + 'Atlantic/Bermuda', + 'Atlantic/Canary', + 'Atlantic/Cape_Verde', + 'Atlantic/Faeroe', + 'Atlantic/Faroe', + 'Atlantic/Jan_Mayen', + 'Atlantic/Madeira', + 'Atlantic/Reykjavik', + 'Atlantic/South_Georgia', + 'Atlantic/St_Helena', + 'Atlantic/Stanley', + 'Australia/ACT', + 'Australia/Adelaide', + 'Australia/Brisbane', + 'Australia/Broken_Hill', + 'Australia/Canberra', + 'Australia/Currie', + 'Australia/Darwin', + 'Australia/Eucla', + 'Australia/Hobart', + 'Australia/LHI', + 'Australia/Lindeman', + 'Australia/Lord_Howe', + 'Australia/Melbourne', + 'Australia/NSW', + 'Australia/North', + 'Australia/Perth', + 'Australia/Queensland', + 'Australia/South', + 'Australia/Sydney', + 'Australia/Tasmania', + 'Australia/Victoria', + 'Australia/West', + 'Australia/Yancowinna', + 'Brazil/Acre', + 'Brazil/DeNoronha', + 'Brazil/East', + 'Brazil/West', + 'CET', + 'CST6CDT', + 'Canada/Atlantic', + 'Canada/Central', + 'Canada/Eastern', + 'Canada/Mountain', + 'Canada/Newfoundland', + 'Canada/Pacific', + 'Canada/Saskatchewan', + 'Canada/Yukon', + 'Chile/Continental', + 'Chile/EasterIsland', + 'Cuba', + 'EET', + 'EST', + 'EST5EDT', + 'Egypt', + 'Eire', + 'Etc/GMT', + 'Etc/GMT+0', + 'Etc/GMT+1', + 'Etc/GMT+10', + 'Etc/GMT+11', + 'Etc/GMT+12', + 'Etc/GMT+2', + 'Etc/GMT+3', + 'Etc/GMT+4', + 'Etc/GMT+5', + 'Etc/GMT+6', + 'Etc/GMT+7', + 'Etc/GMT+8', + 'Etc/GMT+9', + 'Etc/GMT-0', + 'Etc/GMT-1', + 'Etc/GMT-10', + 'Etc/GMT-11', + 'Etc/GMT-12', + 'Etc/GMT-13', + 'Etc/GMT-14', + 'Etc/GMT-2', + 'Etc/GMT-3', + 'Etc/GMT-4', + 'Etc/GMT-5', + 'Etc/GMT-6', + 'Etc/GMT-7', + 'Etc/GMT-8', + 'Etc/GMT-9', + 'Etc/GMT0', + 'Etc/Greenwich', + 'Etc/UCT', + 'Etc/UTC', + 'Etc/Universal', + 'Etc/Zulu', + 'Europe/Amsterdam', + 'Europe/Andorra', + 'Europe/Astrakhan', + 'Europe/Athens', + 'Europe/Belfast', + 'Europe/Belgrade', + 'Europe/Berlin', + 'Europe/Bratislava', + 'Europe/Brussels', + 'Europe/Bucharest', + 'Europe/Budapest', + 'Europe/Busingen', + 'Europe/Chisinau', + 'Europe/Copenhagen', + 'Europe/Dublin', + 'Europe/Gibraltar', + 'Europe/Guernsey', + 'Europe/Helsinki', + 'Europe/Isle_of_Man', + 'Europe/Istanbul', + 'Europe/Jersey', + 'Europe/Kaliningrad', + 'Europe/Kiev', + 'Europe/Kirov', + 'Europe/Lisbon', + 'Europe/Ljubljana', + 'Europe/London', + 'Europe/Luxembourg', + 'Europe/Madrid', + 'Europe/Malta', + 'Europe/Mariehamn', + 'Europe/Minsk', + 'Europe/Monaco', + 'Europe/Moscow', + 'Europe/Nicosia', + 'Europe/Oslo', + 'Europe/Paris', + 'Europe/Podgorica', + 'Europe/Prague', + 'Europe/Riga', + 'Europe/Rome', + 'Europe/Samara', + 'Europe/San_Marino', + 'Europe/Sarajevo', + 'Europe/Saratov', + 'Europe/Simferopol', + 'Europe/Skopje', + 'Europe/Sofia', + 'Europe/Stockholm', + 'Europe/Tallinn', + 'Europe/Tirane', + 'Europe/Tiraspol', + 'Europe/Ulyanovsk', + 'Europe/Uzhgorod', + 'Europe/Vaduz', + 'Europe/Vatican', + 'Europe/Vienna', + 'Europe/Vilnius', + 'Europe/Volgograd', + 'Europe/Warsaw', + 'Europe/Zagreb', + 'Europe/Zaporozhye', + 'Europe/Zurich', + 'GB', + 'GB-Eire', + 'GMT', + 'GMT+0', + 'GMT-0', + 'GMT0', + 'Greenwich', + 'HST', + 'Hongkong', + 'Iceland', + 'Indian/Antananarivo', + 'Indian/Chagos', + 'Indian/Christmas', + 'Indian/Cocos', + 'Indian/Comoro', + 'Indian/Kerguelen', + 'Indian/Mahe', + 'Indian/Maldives', + 'Indian/Mauritius', + 'Indian/Mayotte', + 'Indian/Reunion', + 'Iran', + 'Israel', + 'Jamaica', + 'Japan', + 'Kwajalein', + 'Libya', + 'MET', + 'MST', + 'MST7MDT', + 'Mexico/BajaNorte', + 'Mexico/BajaSur', + 'Mexico/General', + 'NZ', + 'NZ-CHAT', + 'Navajo', + 'PRC', + 'PST8PDT', + 'Pacific/Apia', + 'Pacific/Auckland', + 'Pacific/Bougainville', + 'Pacific/Chatham', + 'Pacific/Chuuk', + 'Pacific/Easter', + 'Pacific/Efate', + 'Pacific/Enderbury', + 'Pacific/Fakaofo', + 'Pacific/Fiji', + 'Pacific/Funafuti', + 'Pacific/Galapagos', + 'Pacific/Gambier', + 'Pacific/Guadalcanal', + 'Pacific/Guam', + 'Pacific/Honolulu', + 'Pacific/Johnston', + 'Pacific/Kiritimati', + 'Pacific/Kosrae', + 'Pacific/Kwajalein', + 'Pacific/Majuro', + 'Pacific/Marquesas', + 'Pacific/Midway', + 'Pacific/Nauru', + 'Pacific/Niue', + 'Pacific/Norfolk', + 'Pacific/Noumea', + 'Pacific/Pago_Pago', + 'Pacific/Palau', + 'Pacific/Pitcairn', + 'Pacific/Pohnpei', + 'Pacific/Ponape', + 'Pacific/Port_Moresby', + 'Pacific/Rarotonga', + 'Pacific/Saipan', + 'Pacific/Samoa', + 'Pacific/Tahiti', + 'Pacific/Tarawa', + 'Pacific/Tongatapu', + 'Pacific/Truk', + 'Pacific/Wake', + 'Pacific/Wallis', + 'Pacific/Yap', + 'Poland', + 'Portugal', + 'ROC', + 'ROK', + 'Singapore', + 'Turkey', + 'UCT', + 'US/Alaska', + 'US/Aleutian', + 'US/Arizona', + 'US/Central', + 'US/East-Indiana', + 'US/Eastern', + 'US/Hawaii', + 'US/Indiana-Starke', + 'US/Michigan', + 'US/Mountain', + 'US/Pacific', + 'US/Samoa', + 'UTC', + 'Universal', + 'W-SU', + 'WET', + 'Zulu'] +all_timezones = LazyList( + tz for tz in all_timezones if resource_exists(tz)) + +all_timezones_set = LazySet(all_timezones) +common_timezones = \ +['Africa/Abidjan', + 'Africa/Accra', + 'Africa/Addis_Ababa', + 'Africa/Algiers', + 'Africa/Asmara', + 'Africa/Bamako', + 'Africa/Bangui', + 'Africa/Banjul', + 'Africa/Bissau', + 'Africa/Blantyre', + 'Africa/Brazzaville', + 'Africa/Bujumbura', + 'Africa/Cairo', + 'Africa/Casablanca', + 'Africa/Ceuta', + 'Africa/Conakry', + 'Africa/Dakar', + 'Africa/Dar_es_Salaam', + 'Africa/Djibouti', + 'Africa/Douala', + 'Africa/El_Aaiun', + 'Africa/Freetown', + 'Africa/Gaborone', + 'Africa/Harare', + 'Africa/Johannesburg', + 'Africa/Juba', + 'Africa/Kampala', + 'Africa/Khartoum', + 'Africa/Kigali', + 'Africa/Kinshasa', + 'Africa/Lagos', + 'Africa/Libreville', + 'Africa/Lome', + 'Africa/Luanda', + 'Africa/Lubumbashi', + 'Africa/Lusaka', + 'Africa/Malabo', + 'Africa/Maputo', + 'Africa/Maseru', + 'Africa/Mbabane', + 'Africa/Mogadishu', + 'Africa/Monrovia', + 'Africa/Nairobi', + 'Africa/Ndjamena', + 'Africa/Niamey', + 'Africa/Nouakchott', + 'Africa/Ouagadougou', + 'Africa/Porto-Novo', + 'Africa/Sao_Tome', + 'Africa/Tripoli', + 'Africa/Tunis', + 'Africa/Windhoek', + 'America/Adak', + 'America/Anchorage', + 'America/Anguilla', + 'America/Antigua', + 'America/Araguaina', + 'America/Argentina/Buenos_Aires', + 'America/Argentina/Catamarca', + 'America/Argentina/Cordoba', + 'America/Argentina/Jujuy', + 'America/Argentina/La_Rioja', + 'America/Argentina/Mendoza', + 'America/Argentina/Rio_Gallegos', + 'America/Argentina/Salta', + 'America/Argentina/San_Juan', + 'America/Argentina/San_Luis', + 'America/Argentina/Tucuman', + 'America/Argentina/Ushuaia', + 'America/Aruba', + 'America/Asuncion', + 'America/Atikokan', + 'America/Bahia', + 'America/Bahia_Banderas', + 'America/Barbados', + 'America/Belem', + 'America/Belize', + 'America/Blanc-Sablon', + 'America/Boa_Vista', + 'America/Bogota', + 'America/Boise', + 'America/Cambridge_Bay', + 'America/Campo_Grande', + 'America/Cancun', + 'America/Caracas', + 'America/Cayenne', + 'America/Cayman', + 'America/Chicago', + 'America/Chihuahua', + 'America/Costa_Rica', + 'America/Creston', + 'America/Cuiaba', + 'America/Curacao', + 'America/Danmarkshavn', + 'America/Dawson', + 'America/Dawson_Creek', + 'America/Denver', + 'America/Detroit', + 'America/Dominica', + 'America/Edmonton', + 'America/Eirunepe', + 'America/El_Salvador', + 'America/Fort_Nelson', + 'America/Fortaleza', + 'America/Glace_Bay', + 'America/Godthab', + 'America/Goose_Bay', + 'America/Grand_Turk', + 'America/Grenada', + 'America/Guadeloupe', + 'America/Guatemala', + 'America/Guayaquil', + 'America/Guyana', + 'America/Halifax', + 'America/Havana', + 'America/Hermosillo', + 'America/Indiana/Indianapolis', + 'America/Indiana/Knox', + 'America/Indiana/Marengo', + 'America/Indiana/Petersburg', + 'America/Indiana/Tell_City', + 'America/Indiana/Vevay', + 'America/Indiana/Vincennes', + 'America/Indiana/Winamac', + 'America/Inuvik', + 'America/Iqaluit', + 'America/Jamaica', + 'America/Juneau', + 'America/Kentucky/Louisville', + 'America/Kentucky/Monticello', + 'America/Kralendijk', + 'America/La_Paz', + 'America/Lima', + 'America/Los_Angeles', + 'America/Lower_Princes', + 'America/Maceio', + 'America/Managua', + 'America/Manaus', + 'America/Marigot', + 'America/Martinique', + 'America/Matamoros', + 'America/Mazatlan', + 'America/Menominee', + 'America/Merida', + 'America/Metlakatla', + 'America/Mexico_City', + 'America/Miquelon', + 'America/Moncton', + 'America/Monterrey', + 'America/Montevideo', + 'America/Montserrat', + 'America/Nassau', + 'America/New_York', + 'America/Nipigon', + 'America/Nome', + 'America/Noronha', + 'America/North_Dakota/Beulah', + 'America/North_Dakota/Center', + 'America/North_Dakota/New_Salem', + 'America/Ojinaga', + 'America/Panama', + 'America/Pangnirtung', + 'America/Paramaribo', + 'America/Phoenix', + 'America/Port-au-Prince', + 'America/Port_of_Spain', + 'America/Porto_Velho', + 'America/Puerto_Rico', + 'America/Punta_Arenas', + 'America/Rainy_River', + 'America/Rankin_Inlet', + 'America/Recife', + 'America/Regina', + 'America/Resolute', + 'America/Rio_Branco', + 'America/Santarem', + 'America/Santiago', + 'America/Santo_Domingo', + 'America/Sao_Paulo', + 'America/Scoresbysund', + 'America/Sitka', + 'America/St_Barthelemy', + 'America/St_Johns', + 'America/St_Kitts', + 'America/St_Lucia', + 'America/St_Thomas', + 'America/St_Vincent', + 'America/Swift_Current', + 'America/Tegucigalpa', + 'America/Thule', + 'America/Thunder_Bay', + 'America/Tijuana', + 'America/Toronto', + 'America/Tortola', + 'America/Vancouver', + 'America/Whitehorse', + 'America/Winnipeg', + 'America/Yakutat', + 'America/Yellowknife', + 'Antarctica/Casey', + 'Antarctica/Davis', + 'Antarctica/DumontDUrville', + 'Antarctica/Macquarie', + 'Antarctica/Mawson', + 'Antarctica/McMurdo', + 'Antarctica/Palmer', + 'Antarctica/Rothera', + 'Antarctica/Syowa', + 'Antarctica/Troll', + 'Antarctica/Vostok', + 'Arctic/Longyearbyen', + 'Asia/Aden', + 'Asia/Almaty', + 'Asia/Amman', + 'Asia/Anadyr', + 'Asia/Aqtau', + 'Asia/Aqtobe', + 'Asia/Ashgabat', + 'Asia/Atyrau', + 'Asia/Baghdad', + 'Asia/Bahrain', + 'Asia/Baku', + 'Asia/Bangkok', + 'Asia/Barnaul', + 'Asia/Beirut', + 'Asia/Bishkek', + 'Asia/Brunei', + 'Asia/Chita', + 'Asia/Choibalsan', + 'Asia/Colombo', + 'Asia/Damascus', + 'Asia/Dhaka', + 'Asia/Dili', + 'Asia/Dubai', + 'Asia/Dushanbe', + 'Asia/Famagusta', + 'Asia/Gaza', + 'Asia/Hebron', + 'Asia/Ho_Chi_Minh', + 'Asia/Hong_Kong', + 'Asia/Hovd', + 'Asia/Irkutsk', + 'Asia/Jakarta', + 'Asia/Jayapura', + 'Asia/Jerusalem', + 'Asia/Kabul', + 'Asia/Kamchatka', + 'Asia/Karachi', + 'Asia/Kathmandu', + 'Asia/Khandyga', + 'Asia/Kolkata', + 'Asia/Krasnoyarsk', + 'Asia/Kuala_Lumpur', + 'Asia/Kuching', + 'Asia/Kuwait', + 'Asia/Macau', + 'Asia/Magadan', + 'Asia/Makassar', + 'Asia/Manila', + 'Asia/Muscat', + 'Asia/Nicosia', + 'Asia/Novokuznetsk', + 'Asia/Novosibirsk', + 'Asia/Omsk', + 'Asia/Oral', + 'Asia/Phnom_Penh', + 'Asia/Pontianak', + 'Asia/Pyongyang', + 'Asia/Qatar', + 'Asia/Qyzylorda', + 'Asia/Riyadh', + 'Asia/Sakhalin', + 'Asia/Samarkand', + 'Asia/Seoul', + 'Asia/Shanghai', + 'Asia/Singapore', + 'Asia/Srednekolymsk', + 'Asia/Taipei', + 'Asia/Tashkent', + 'Asia/Tbilisi', + 'Asia/Tehran', + 'Asia/Thimphu', + 'Asia/Tokyo', + 'Asia/Tomsk', + 'Asia/Ulaanbaatar', + 'Asia/Urumqi', + 'Asia/Ust-Nera', + 'Asia/Vientiane', + 'Asia/Vladivostok', + 'Asia/Yakutsk', + 'Asia/Yangon', + 'Asia/Yekaterinburg', + 'Asia/Yerevan', + 'Atlantic/Azores', + 'Atlantic/Bermuda', + 'Atlantic/Canary', + 'Atlantic/Cape_Verde', + 'Atlantic/Faroe', + 'Atlantic/Madeira', + 'Atlantic/Reykjavik', + 'Atlantic/South_Georgia', + 'Atlantic/St_Helena', + 'Atlantic/Stanley', + 'Australia/Adelaide', + 'Australia/Brisbane', + 'Australia/Broken_Hill', + 'Australia/Currie', + 'Australia/Darwin', + 'Australia/Eucla', + 'Australia/Hobart', + 'Australia/Lindeman', + 'Australia/Lord_Howe', + 'Australia/Melbourne', + 'Australia/Perth', + 'Australia/Sydney', + 'Canada/Atlantic', + 'Canada/Central', + 'Canada/Eastern', + 'Canada/Mountain', + 'Canada/Newfoundland', + 'Canada/Pacific', + 'Europe/Amsterdam', + 'Europe/Andorra', + 'Europe/Astrakhan', + 'Europe/Athens', + 'Europe/Belgrade', + 'Europe/Berlin', + 'Europe/Bratislava', + 'Europe/Brussels', + 'Europe/Bucharest', + 'Europe/Budapest', + 'Europe/Busingen', + 'Europe/Chisinau', + 'Europe/Copenhagen', + 'Europe/Dublin', + 'Europe/Gibraltar', + 'Europe/Guernsey', + 'Europe/Helsinki', + 'Europe/Isle_of_Man', + 'Europe/Istanbul', + 'Europe/Jersey', + 'Europe/Kaliningrad', + 'Europe/Kiev', + 'Europe/Kirov', + 'Europe/Lisbon', + 'Europe/Ljubljana', + 'Europe/London', + 'Europe/Luxembourg', + 'Europe/Madrid', + 'Europe/Malta', + 'Europe/Mariehamn', + 'Europe/Minsk', + 'Europe/Monaco', + 'Europe/Moscow', + 'Europe/Oslo', + 'Europe/Paris', + 'Europe/Podgorica', + 'Europe/Prague', + 'Europe/Riga', + 'Europe/Rome', + 'Europe/Samara', + 'Europe/San_Marino', + 'Europe/Sarajevo', + 'Europe/Saratov', + 'Europe/Simferopol', + 'Europe/Skopje', + 'Europe/Sofia', + 'Europe/Stockholm', + 'Europe/Tallinn', + 'Europe/Tirane', + 'Europe/Ulyanovsk', + 'Europe/Uzhgorod', + 'Europe/Vaduz', + 'Europe/Vatican', + 'Europe/Vienna', + 'Europe/Vilnius', + 'Europe/Volgograd', + 'Europe/Warsaw', + 'Europe/Zagreb', + 'Europe/Zaporozhye', + 'Europe/Zurich', + 'GMT', + 'Indian/Antananarivo', + 'Indian/Chagos', + 'Indian/Christmas', + 'Indian/Cocos', + 'Indian/Comoro', + 'Indian/Kerguelen', + 'Indian/Mahe', + 'Indian/Maldives', + 'Indian/Mauritius', + 'Indian/Mayotte', + 'Indian/Reunion', + 'Pacific/Apia', + 'Pacific/Auckland', + 'Pacific/Bougainville', + 'Pacific/Chatham', + 'Pacific/Chuuk', + 'Pacific/Easter', + 'Pacific/Efate', + 'Pacific/Enderbury', + 'Pacific/Fakaofo', + 'Pacific/Fiji', + 'Pacific/Funafuti', + 'Pacific/Galapagos', + 'Pacific/Gambier', + 'Pacific/Guadalcanal', + 'Pacific/Guam', + 'Pacific/Honolulu', + 'Pacific/Kiritimati', + 'Pacific/Kosrae', + 'Pacific/Kwajalein', + 'Pacific/Majuro', + 'Pacific/Marquesas', + 'Pacific/Midway', + 'Pacific/Nauru', + 'Pacific/Niue', + 'Pacific/Norfolk', + 'Pacific/Noumea', + 'Pacific/Pago_Pago', + 'Pacific/Palau', + 'Pacific/Pitcairn', + 'Pacific/Pohnpei', + 'Pacific/Port_Moresby', + 'Pacific/Rarotonga', + 'Pacific/Saipan', + 'Pacific/Tahiti', + 'Pacific/Tarawa', + 'Pacific/Tongatapu', + 'Pacific/Wake', + 'Pacific/Wallis', + 'US/Alaska', + 'US/Arizona', + 'US/Central', + 'US/Eastern', + 'US/Hawaii', + 'US/Mountain', + 'US/Pacific', + 'UTC'] +common_timezones = LazyList( + tz for tz in common_timezones if tz in all_timezones) + +common_timezones_set = LazySet(common_timezones) diff --git a/lib/pytz/exceptions.py b/lib/pytz/exceptions.py new file mode 100644 index 0000000..18df33e --- /dev/null +++ b/lib/pytz/exceptions.py @@ -0,0 +1,48 @@ +''' +Custom exceptions raised by pytz. +''' + +__all__ = [ + 'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError', + 'NonExistentTimeError', +] + + +class UnknownTimeZoneError(KeyError): + '''Exception raised when pytz is passed an unknown timezone. + + >>> isinstance(UnknownTimeZoneError(), LookupError) + True + + This class is actually a subclass of KeyError to provide backwards + compatibility with code relying on the undocumented behavior of earlier + pytz releases. + + >>> isinstance(UnknownTimeZoneError(), KeyError) + True + ''' + pass + + +class InvalidTimeError(Exception): + '''Base class for invalid time exceptions.''' + + +class AmbiguousTimeError(InvalidTimeError): + '''Exception raised when attempting to create an ambiguous wallclock time. + + At the end of a DST transition period, a particular wallclock time will + occur twice (once before the clocks are set back, once after). Both + possibilities may be correct, unless further information is supplied. + + See DstTzInfo.normalize() for more info + ''' + + +class NonExistentTimeError(InvalidTimeError): + '''Exception raised when attempting to create a wallclock time that + cannot exist. + + At the start of a DST transition period, the wallclock time jumps forward. + The instants jumped over never occur. + ''' diff --git a/lib/pytz/lazy.py b/lib/pytz/lazy.py new file mode 100644 index 0000000..39344fc --- /dev/null +++ b/lib/pytz/lazy.py @@ -0,0 +1,172 @@ +from threading import RLock +try: + from collections.abc import Mapping as DictMixin +except ImportError: # Python < 3.3 + try: + from UserDict import DictMixin # Python 2 + except ImportError: # Python 3.0-3.3 + from collections import Mapping as DictMixin + + +# With lazy loading, we might end up with multiple threads triggering +# it at the same time. We need a lock. +_fill_lock = RLock() + + +class LazyDict(DictMixin): + """Dictionary populated on first use.""" + data = None + + def __getitem__(self, key): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return self.data[key.upper()] + + def __contains__(self, key): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return key in self.data + + def __iter__(self): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return iter(self.data) + + def __len__(self): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return len(self.data) + + def keys(self): + if self.data is None: + _fill_lock.acquire() + try: + if self.data is None: + self._fill() + finally: + _fill_lock.release() + return self.data.keys() + + +class LazyList(list): + """List populated on first use.""" + + _props = [ + '__str__', '__repr__', '__unicode__', + '__hash__', '__sizeof__', '__cmp__', + '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', + 'append', 'count', 'index', 'extend', 'insert', 'pop', 'remove', + 'reverse', 'sort', '__add__', '__radd__', '__iadd__', '__mul__', + '__rmul__', '__imul__', '__contains__', '__len__', '__nonzero__', + '__getitem__', '__setitem__', '__delitem__', '__iter__', + '__reversed__', '__getslice__', '__setslice__', '__delslice__'] + + def __new__(cls, fill_iter=None): + + if fill_iter is None: + return list() + + # We need a new class as we will be dynamically messing with its + # methods. + class LazyList(list): + pass + + fill_iter = [fill_iter] + + def lazy(name): + def _lazy(self, *args, **kw): + _fill_lock.acquire() + try: + if len(fill_iter) > 0: + list.extend(self, fill_iter.pop()) + for method_name in cls._props: + delattr(LazyList, method_name) + finally: + _fill_lock.release() + return getattr(list, name)(self, *args, **kw) + return _lazy + + for name in cls._props: + setattr(LazyList, name, lazy(name)) + + new_list = LazyList() + return new_list + +# Not all versions of Python declare the same magic methods. +# Filter out properties that don't exist in this version of Python +# from the list. +LazyList._props = [prop for prop in LazyList._props if hasattr(list, prop)] + + +class LazySet(set): + """Set populated on first use.""" + + _props = ( + '__str__', '__repr__', '__unicode__', + '__hash__', '__sizeof__', '__cmp__', + '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', + '__contains__', '__len__', '__nonzero__', + '__getitem__', '__setitem__', '__delitem__', '__iter__', + '__sub__', '__and__', '__xor__', '__or__', + '__rsub__', '__rand__', '__rxor__', '__ror__', + '__isub__', '__iand__', '__ixor__', '__ior__', + 'add', 'clear', 'copy', 'difference', 'difference_update', + 'discard', 'intersection', 'intersection_update', 'isdisjoint', + 'issubset', 'issuperset', 'pop', 'remove', + 'symmetric_difference', 'symmetric_difference_update', + 'union', 'update') + + def __new__(cls, fill_iter=None): + + if fill_iter is None: + return set() + + class LazySet(set): + pass + + fill_iter = [fill_iter] + + def lazy(name): + def _lazy(self, *args, **kw): + _fill_lock.acquire() + try: + if len(fill_iter) > 0: + for i in fill_iter.pop(): + set.add(self, i) + for method_name in cls._props: + delattr(LazySet, method_name) + finally: + _fill_lock.release() + return getattr(set, name)(self, *args, **kw) + return _lazy + + for name in cls._props: + setattr(LazySet, name, lazy(name)) + + new_set = LazySet() + return new_set + +# Not all versions of Python declare the same magic methods. +# Filter out properties that don't exist in this version of Python +# from the list. +LazySet._props = [prop for prop in LazySet._props if hasattr(set, prop)] diff --git a/lib/pytz/reference.py b/lib/pytz/reference.py new file mode 100644 index 0000000..f765ca0 --- /dev/null +++ b/lib/pytz/reference.py @@ -0,0 +1,140 @@ +''' +Reference tzinfo implementations from the Python docs. +Used for testing against as they are only correct for the years +1987 to 2006. Do not use these for real code. +''' + +from datetime import tzinfo, timedelta, datetime +from pytz import HOUR, ZERO, UTC + +__all__ = [ + 'FixedOffset', + 'LocalTimezone', + 'USTimeZone', + 'Eastern', + 'Central', + 'Mountain', + 'Pacific', + 'UTC' +] + + +# A class building tzinfo objects for fixed-offset time zones. +# Note that FixedOffset(0, "UTC") is a different way to build a +# UTC tzinfo object. +class FixedOffset(tzinfo): + """Fixed offset in minutes east from UTC.""" + + def __init__(self, offset, name): + self.__offset = timedelta(minutes=offset) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return ZERO + + +import time as _time + +STDOFFSET = timedelta(seconds=-_time.timezone) +if _time.daylight: + DSTOFFSET = timedelta(seconds=-_time.altzone) +else: + DSTOFFSET = STDOFFSET + +DSTDIFF = DSTOFFSET - STDOFFSET + + +# A class capturing the platform's idea of local time. +class LocalTimezone(tzinfo): + + def utcoffset(self, dt): + if self._isdst(dt): + return DSTOFFSET + else: + return STDOFFSET + + def dst(self, dt): + if self._isdst(dt): + return DSTDIFF + else: + return ZERO + + def tzname(self, dt): + return _time.tzname[self._isdst(dt)] + + def _isdst(self, dt): + tt = (dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.weekday(), 0, -1) + stamp = _time.mktime(tt) + tt = _time.localtime(stamp) + return tt.tm_isdst > 0 + +Local = LocalTimezone() + + +def first_sunday_on_or_after(dt): + days_to_go = 6 - dt.weekday() + if days_to_go: + dt += timedelta(days_to_go) + return dt + + +# In the US, DST starts at 2am (standard time) on the first Sunday in April. +DSTSTART = datetime(1, 4, 1, 2) +# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct. +# which is the first Sunday on or after Oct 25. +DSTEND = datetime(1, 10, 25, 1) + + +# A complete implementation of current DST rules for major US time zones. +class USTimeZone(tzinfo): + + def __init__(self, hours, reprname, stdname, dstname): + self.stdoffset = timedelta(hours=hours) + self.reprname = reprname + self.stdname = stdname + self.dstname = dstname + + def __repr__(self): + return self.reprname + + def tzname(self, dt): + if self.dst(dt): + return self.dstname + else: + return self.stdname + + def utcoffset(self, dt): + return self.stdoffset + self.dst(dt) + + def dst(self, dt): + if dt is None or dt.tzinfo is None: + # An exception may be sensible here, in one or both cases. + # It depends on how you want to treat them. The default + # fromutc() implementation (called by the default astimezone() + # implementation) passes a datetime with dt.tzinfo is self. + return ZERO + assert dt.tzinfo is self + + # Find first Sunday in April & the last in October. + start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) + end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) + + # Can't compare naive to aware objects, so strip the timezone from + # dt first. + if start <= dt.replace(tzinfo=None) < end: + return HOUR + else: + return ZERO + +Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") +Central = USTimeZone(-6, "Central", "CST", "CDT") +Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") +Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") diff --git a/lib/pytz/tzfile.py b/lib/pytz/tzfile.py new file mode 100644 index 0000000..25117f3 --- /dev/null +++ b/lib/pytz/tzfile.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python +''' +$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $ +''' + +from datetime import datetime +from struct import unpack, calcsize + +from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo +from pytz.tzinfo import memorized_datetime, memorized_timedelta + + +def _byte_string(s): + """Cast a string or byte string to an ASCII byte string.""" + return s.encode('ASCII') + +_NULL = _byte_string('\0') + + +def _std_string(s): + """Cast a string or byte string to an ASCII string.""" + return str(s.decode('ASCII')) + + +def build_tzinfo(zone, fp): + head_fmt = '>4s c 15x 6l' + head_size = calcsize(head_fmt) + (magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, + typecnt, charcnt) = unpack(head_fmt, fp.read(head_size)) + + # Make sure it is a tzfile(5) file + assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic) + + # Read out the transition times, localtime indices and ttinfo structures. + data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict( + timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt) + data_size = calcsize(data_fmt) + data = unpack(data_fmt, fp.read(data_size)) + + # make sure we unpacked the right number of values + assert len(data) == 2 * timecnt + 3 * typecnt + 1 + transitions = [memorized_datetime(trans) + for trans in data[:timecnt]] + lindexes = list(data[timecnt:2 * timecnt]) + ttinfo_raw = data[2 * timecnt:-1] + tznames_raw = data[-1] + del data + + # Process ttinfo into separate structs + ttinfo = [] + tznames = {} + i = 0 + while i < len(ttinfo_raw): + # have we looked up this timezone name yet? + tzname_offset = ttinfo_raw[i + 2] + if tzname_offset not in tznames: + nul = tznames_raw.find(_NULL, tzname_offset) + if nul < 0: + nul = len(tznames_raw) + tznames[tzname_offset] = _std_string( + tznames_raw[tzname_offset:nul]) + ttinfo.append((ttinfo_raw[i], + bool(ttinfo_raw[i + 1]), + tznames[tzname_offset])) + i += 3 + + # Now build the timezone object + if len(ttinfo) == 1 or len(transitions) == 0: + ttinfo[0][0], ttinfo[0][2] + cls = type(zone, (StaticTzInfo,), dict( + zone=zone, + _utcoffset=memorized_timedelta(ttinfo[0][0]), + _tzname=ttinfo[0][2])) + else: + # Early dates use the first standard time ttinfo + i = 0 + while ttinfo[i][1]: + i += 1 + if ttinfo[i] == ttinfo[lindexes[0]]: + transitions[0] = datetime.min + else: + transitions.insert(0, datetime.min) + lindexes.insert(0, i) + + # calculate transition info + transition_info = [] + for i in range(len(transitions)): + inf = ttinfo[lindexes[i]] + utcoffset = inf[0] + if not inf[1]: + dst = 0 + else: + for j in range(i - 1, -1, -1): + prev_inf = ttinfo[lindexes[j]] + if not prev_inf[1]: + break + dst = inf[0] - prev_inf[0] # dst offset + + # Bad dst? Look further. DST > 24 hours happens when + # a timzone has moved across the international dateline. + if dst <= 0 or dst > 3600 * 3: + for j in range(i + 1, len(transitions)): + stdinf = ttinfo[lindexes[j]] + if not stdinf[1]: + dst = inf[0] - stdinf[0] + if dst > 0: + break # Found a useful std time. + + tzname = inf[2] + + # Round utcoffset and dst to the nearest minute or the + # datetime library will complain. Conversions to these timezones + # might be up to plus or minus 30 seconds out, but it is + # the best we can do. + utcoffset = int((utcoffset + 30) // 60) * 60 + dst = int((dst + 30) // 60) * 60 + transition_info.append(memorized_ttinfo(utcoffset, dst, tzname)) + + cls = type(zone, (DstTzInfo,), dict( + zone=zone, + _utc_transition_times=transitions, + _transition_info=transition_info)) + + return cls() + +if __name__ == '__main__': + import os.path + from pprint import pprint + base = os.path.join(os.path.dirname(__file__), 'zoneinfo') + tz = build_tzinfo('Australia/Melbourne', + open(os.path.join(base, 'Australia', 'Melbourne'), 'rb')) + tz = build_tzinfo('US/Eastern', + open(os.path.join(base, 'US', 'Eastern'), 'rb')) + pprint(tz._utc_transition_times) diff --git a/lib/pytz/tzinfo.py b/lib/pytz/tzinfo.py new file mode 100644 index 0000000..725978d --- /dev/null +++ b/lib/pytz/tzinfo.py @@ -0,0 +1,577 @@ +'''Base classes and helpers for building zone specific tzinfo classes''' + +from datetime import datetime, timedelta, tzinfo +from bisect import bisect_right +try: + set +except NameError: + from sets import Set as set + +import pytz +from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError + +__all__ = [] + +_timedelta_cache = {} + + +def memorized_timedelta(seconds): + '''Create only one instance of each distinct timedelta''' + try: + return _timedelta_cache[seconds] + except KeyError: + delta = timedelta(seconds=seconds) + _timedelta_cache[seconds] = delta + return delta + +_epoch = datetime.utcfromtimestamp(0) +_datetime_cache = {0: _epoch} + + +def memorized_datetime(seconds): + '''Create only one instance of each distinct datetime''' + try: + return _datetime_cache[seconds] + except KeyError: + # NB. We can't just do datetime.utcfromtimestamp(seconds) as this + # fails with negative values under Windows (Bug #90096) + dt = _epoch + timedelta(seconds=seconds) + _datetime_cache[seconds] = dt + return dt + +_ttinfo_cache = {} + + +def memorized_ttinfo(*args): + '''Create only one instance of each distinct tuple''' + try: + return _ttinfo_cache[args] + except KeyError: + ttinfo = ( + memorized_timedelta(args[0]), + memorized_timedelta(args[1]), + args[2] + ) + _ttinfo_cache[args] = ttinfo + return ttinfo + +_notime = memorized_timedelta(0) + + +def _to_seconds(td): + '''Convert a timedelta to seconds''' + return td.seconds + td.days * 24 * 60 * 60 + + +class BaseTzInfo(tzinfo): + # Overridden in subclass + _utcoffset = None + _tzname = None + zone = None + + def __str__(self): + return self.zone + + +class StaticTzInfo(BaseTzInfo): + '''A timezone that has a constant offset from UTC + + These timezones are rare, as most locations have changed their + offset at some point in their history + ''' + def fromutc(self, dt): + '''See datetime.tzinfo.fromutc''' + if dt.tzinfo is not None and dt.tzinfo is not self: + raise ValueError('fromutc: dt.tzinfo is not self') + return (dt + self._utcoffset).replace(tzinfo=self) + + def utcoffset(self, dt, is_dst=None): + '''See datetime.tzinfo.utcoffset + + is_dst is ignored for StaticTzInfo, and exists only to + retain compatibility with DstTzInfo. + ''' + return self._utcoffset + + def dst(self, dt, is_dst=None): + '''See datetime.tzinfo.dst + + is_dst is ignored for StaticTzInfo, and exists only to + retain compatibility with DstTzInfo. + ''' + return _notime + + def tzname(self, dt, is_dst=None): + '''See datetime.tzinfo.tzname + + is_dst is ignored for StaticTzInfo, and exists only to + retain compatibility with DstTzInfo. + ''' + return self._tzname + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + return dt.replace(tzinfo=self) + + def normalize(self, dt, is_dst=False): + '''Correct the timezone information on the given datetime. + + This is normally a no-op, as StaticTzInfo timezones never have + ambiguous cases to correct: + + >>> from pytz import timezone + >>> gmt = timezone('GMT') + >>> isinstance(gmt, StaticTzInfo) + True + >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt) + >>> gmt.normalize(dt) is dt + True + + The supported method of converting between timezones is to use + datetime.astimezone(). Currently normalize() also works: + + >>> la = timezone('America/Los_Angeles') + >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3)) + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> gmt.normalize(dt).strftime(fmt) + '2011-05-07 08:02:03 GMT (+0000)' + ''' + if dt.tzinfo is self: + return dt + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + return dt.astimezone(self) + + def __repr__(self): + return '' % (self.zone,) + + def __reduce__(self): + # Special pickle to zone remains a singleton and to cope with + # database changes. + return pytz._p, (self.zone,) + + +class DstTzInfo(BaseTzInfo): + '''A timezone that has a variable offset from UTC + + The offset might change if daylight saving time comes into effect, + or at a point in history when the region decides to change their + timezone definition. + ''' + # Overridden in subclass + + # Sorted list of DST transition times, UTC + _utc_transition_times = None + + # [(utcoffset, dstoffset, tzname)] corresponding to + # _utc_transition_times entries + _transition_info = None + + zone = None + + # Set in __init__ + + _tzinfos = None + _dst = None # DST offset + + def __init__(self, _inf=None, _tzinfos=None): + if _inf: + self._tzinfos = _tzinfos + self._utcoffset, self._dst, self._tzname = _inf + else: + _tzinfos = {} + self._tzinfos = _tzinfos + self._utcoffset, self._dst, self._tzname = ( + self._transition_info[0]) + _tzinfos[self._transition_info[0]] = self + for inf in self._transition_info[1:]: + if inf not in _tzinfos: + _tzinfos[inf] = self.__class__(inf, _tzinfos) + + def fromutc(self, dt): + '''See datetime.tzinfo.fromutc''' + if (dt.tzinfo is not None and + getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos): + raise ValueError('fromutc: dt.tzinfo is not self') + dt = dt.replace(tzinfo=None) + idx = max(0, bisect_right(self._utc_transition_times, dt) - 1) + inf = self._transition_info[idx] + return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf]) + + def normalize(self, dt): + '''Correct the timezone information on the given datetime + + If date arithmetic crosses DST boundaries, the tzinfo + is not magically adjusted. This method normalizes the + tzinfo to the correct one. + + To test, first we need to do some setup + + >>> from pytz import timezone + >>> utc = timezone('UTC') + >>> eastern = timezone('US/Eastern') + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + + We next create a datetime right on an end-of-DST transition point, + the instant when the wallclocks are wound back one hour. + + >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) + >>> loc_dt = utc_dt.astimezone(eastern) + >>> loc_dt.strftime(fmt) + '2002-10-27 01:00:00 EST (-0500)' + + Now, if we subtract a few minutes from it, note that the timezone + information has not changed. + + >>> before = loc_dt - timedelta(minutes=10) + >>> before.strftime(fmt) + '2002-10-27 00:50:00 EST (-0500)' + + But we can fix that by calling the normalize method + + >>> before = eastern.normalize(before) + >>> before.strftime(fmt) + '2002-10-27 01:50:00 EDT (-0400)' + + The supported method of converting between timezones is to use + datetime.astimezone(). Currently, normalize() also works: + + >>> th = timezone('Asia/Bangkok') + >>> am = timezone('Europe/Amsterdam') + >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3)) + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> am.normalize(dt).strftime(fmt) + '2011-05-06 20:02:03 CEST (+0200)' + ''' + if dt.tzinfo is None: + raise ValueError('Naive time - no tzinfo set') + + # Convert dt in localtime to UTC + offset = dt.tzinfo._utcoffset + dt = dt.replace(tzinfo=None) + dt = dt - offset + # convert it back, and return it + return self.fromutc(dt) + + def localize(self, dt, is_dst=False): + '''Convert naive time to local time. + + This method should be used to construct localtimes, rather + than passing a tzinfo argument to a datetime constructor. + + is_dst is used to determine the correct timezone in the ambigous + period at the end of daylight saving time. + + >>> from pytz import timezone + >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' + >>> amdam = timezone('Europe/Amsterdam') + >>> dt = datetime(2004, 10, 31, 2, 0, 0) + >>> loc_dt1 = amdam.localize(dt, is_dst=True) + >>> loc_dt2 = amdam.localize(dt, is_dst=False) + >>> loc_dt1.strftime(fmt) + '2004-10-31 02:00:00 CEST (+0200)' + >>> loc_dt2.strftime(fmt) + '2004-10-31 02:00:00 CET (+0100)' + >>> str(loc_dt2 - loc_dt1) + '1:00:00' + + Use is_dst=None to raise an AmbiguousTimeError for ambiguous + times at the end of daylight saving time + + >>> try: + ... loc_dt1 = amdam.localize(dt, is_dst=None) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + + is_dst defaults to False + + >>> amdam.localize(dt) == amdam.localize(dt, False) + True + + is_dst is also used to determine the correct timezone in the + wallclock times jumped over at the start of daylight saving time. + + >>> pacific = timezone('US/Pacific') + >>> dt = datetime(2008, 3, 9, 2, 0, 0) + >>> ploc_dt1 = pacific.localize(dt, is_dst=True) + >>> ploc_dt2 = pacific.localize(dt, is_dst=False) + >>> ploc_dt1.strftime(fmt) + '2008-03-09 02:00:00 PDT (-0700)' + >>> ploc_dt2.strftime(fmt) + '2008-03-09 02:00:00 PST (-0800)' + >>> str(ploc_dt2 - ploc_dt1) + '1:00:00' + + Use is_dst=None to raise a NonExistentTimeError for these skipped + times. + + >>> try: + ... loc_dt1 = pacific.localize(dt, is_dst=None) + ... except NonExistentTimeError: + ... print('Non-existent') + Non-existent + ''' + if dt.tzinfo is not None: + raise ValueError('Not naive datetime (tzinfo is already set)') + + # Find the two best possibilities. + possible_loc_dt = set() + for delta in [timedelta(days=-1), timedelta(days=1)]: + loc_dt = dt + delta + idx = max(0, bisect_right( + self._utc_transition_times, loc_dt) - 1) + inf = self._transition_info[idx] + tzinfo = self._tzinfos[inf] + loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo)) + if loc_dt.replace(tzinfo=None) == dt: + possible_loc_dt.add(loc_dt) + + if len(possible_loc_dt) == 1: + return possible_loc_dt.pop() + + # If there are no possibly correct timezones, we are attempting + # to convert a time that never happened - the time period jumped + # during the start-of-DST transition period. + if len(possible_loc_dt) == 0: + # If we refuse to guess, raise an exception. + if is_dst is None: + raise NonExistentTimeError(dt) + + # If we are forcing the pre-DST side of the DST transition, we + # obtain the correct timezone by winding the clock forward a few + # hours. + elif is_dst: + return self.localize( + dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6) + + # If we are forcing the post-DST side of the DST transition, we + # obtain the correct timezone by winding the clock back. + else: + return self.localize( + dt - timedelta(hours=6), + is_dst=False) + timedelta(hours=6) + + # If we get this far, we have multiple possible timezones - this + # is an ambiguous case occuring during the end-of-DST transition. + + # If told to be strict, raise an exception since we have an + # ambiguous case + if is_dst is None: + raise AmbiguousTimeError(dt) + + # Filter out the possiblilities that don't match the requested + # is_dst + filtered_possible_loc_dt = [ + p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst + ] + + # Hopefully we only have one possibility left. Return it. + if len(filtered_possible_loc_dt) == 1: + return filtered_possible_loc_dt[0] + + if len(filtered_possible_loc_dt) == 0: + filtered_possible_loc_dt = list(possible_loc_dt) + + # If we get this far, we have in a wierd timezone transition + # where the clocks have been wound back but is_dst is the same + # in both (eg. Europe/Warsaw 1915 when they switched to CET). + # At this point, we just have to guess unless we allow more + # hints to be passed in (such as the UTC offset or abbreviation), + # but that is just getting silly. + # + # Choose the earliest (by UTC) applicable timezone if is_dst=True + # Choose the latest (by UTC) applicable timezone if is_dst=False + # i.e., behave like end-of-DST transition + dates = {} # utc -> local + for local_dt in filtered_possible_loc_dt: + utc_time = ( + local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset) + assert utc_time not in dates + dates[utc_time] = local_dt + return dates[[min, max][not is_dst](dates)] + + def utcoffset(self, dt, is_dst=None): + '''See datetime.tzinfo.utcoffset + + The is_dst parameter may be used to remove ambiguity during DST + transitions. + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + >>> ambiguous = datetime(2009, 10, 31, 23, 30) + + >>> str(tz.utcoffset(ambiguous, is_dst=False)) + '-1 day, 20:30:00' + + >>> str(tz.utcoffset(ambiguous, is_dst=True)) + '-1 day, 21:30:00' + + >>> try: + ... tz.utcoffset(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + + ''' + if dt is None: + return None + elif dt.tzinfo is not self: + dt = self.localize(dt, is_dst) + return dt.tzinfo._utcoffset + else: + return self._utcoffset + + def dst(self, dt, is_dst=None): + '''See datetime.tzinfo.dst + + The is_dst parameter may be used to remove ambiguity during DST + transitions. + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + + >>> normal = datetime(2009, 9, 1) + + >>> str(tz.dst(normal)) + '1:00:00' + >>> str(tz.dst(normal, is_dst=False)) + '1:00:00' + >>> str(tz.dst(normal, is_dst=True)) + '1:00:00' + + >>> ambiguous = datetime(2009, 10, 31, 23, 30) + + >>> str(tz.dst(ambiguous, is_dst=False)) + '0:00:00' + >>> str(tz.dst(ambiguous, is_dst=True)) + '1:00:00' + >>> try: + ... tz.dst(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + + ''' + if dt is None: + return None + elif dt.tzinfo is not self: + dt = self.localize(dt, is_dst) + return dt.tzinfo._dst + else: + return self._dst + + def tzname(self, dt, is_dst=None): + '''See datetime.tzinfo.tzname + + The is_dst parameter may be used to remove ambiguity during DST + transitions. + + >>> from pytz import timezone + >>> tz = timezone('America/St_Johns') + + >>> normal = datetime(2009, 9, 1) + + >>> tz.tzname(normal) + 'NDT' + >>> tz.tzname(normal, is_dst=False) + 'NDT' + >>> tz.tzname(normal, is_dst=True) + 'NDT' + + >>> ambiguous = datetime(2009, 10, 31, 23, 30) + + >>> tz.tzname(ambiguous, is_dst=False) + 'NST' + >>> tz.tzname(ambiguous, is_dst=True) + 'NDT' + >>> try: + ... tz.tzname(ambiguous) + ... except AmbiguousTimeError: + ... print('Ambiguous') + Ambiguous + ''' + if dt is None: + return self.zone + elif dt.tzinfo is not self: + dt = self.localize(dt, is_dst) + return dt.tzinfo._tzname + else: + return self._tzname + + def __repr__(self): + if self._dst: + dst = 'DST' + else: + dst = 'STD' + if self._utcoffset > _notime: + return '' % ( + self.zone, self._tzname, self._utcoffset, dst + ) + else: + return '' % ( + self.zone, self._tzname, self._utcoffset, dst + ) + + def __reduce__(self): + # Special pickle to zone remains a singleton and to cope with + # database changes. + return pytz._p, ( + self.zone, + _to_seconds(self._utcoffset), + _to_seconds(self._dst), + self._tzname + ) + + +def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None): + """Factory function for unpickling pytz tzinfo instances. + + This is shared for both StaticTzInfo and DstTzInfo instances, because + database changes could cause a zones implementation to switch between + these two base classes and we can't break pickles on a pytz version + upgrade. + """ + # Raises a KeyError if zone no longer exists, which should never happen + # and would be a bug. + tz = pytz.timezone(zone) + + # A StaticTzInfo - just return it + if utcoffset is None: + return tz + + # This pickle was created from a DstTzInfo. We need to + # determine which of the list of tzinfo instances for this zone + # to use in order to restore the state of any datetime instances using + # it correctly. + utcoffset = memorized_timedelta(utcoffset) + dstoffset = memorized_timedelta(dstoffset) + try: + return tz._tzinfos[(utcoffset, dstoffset, tzname)] + except KeyError: + # The particular state requested in this timezone no longer exists. + # This indicates a corrupt pickle, or the timezone database has been + # corrected violently enough to make this particular + # (utcoffset,dstoffset) no longer exist in the zone, or the + # abbreviation has been changed. + pass + + # See if we can find an entry differing only by tzname. Abbreviations + # get changed from the initial guess by the database maintainers to + # match reality when this information is discovered. + for localized_tz in tz._tzinfos.values(): + if (localized_tz._utcoffset == utcoffset and + localized_tz._dst == dstoffset): + return localized_tz + + # This (utcoffset, dstoffset) information has been removed from the + # zone. Add it back. This might occur when the database maintainers have + # corrected incorrect information. datetime instances using this + # incorrect information will continue to do so, exactly as they were + # before being pickled. This is purely an overly paranoid safety net - I + # doubt this will ever been needed in real life. + inf = (utcoffset, dstoffset, tzname) + tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos) + return tz._tzinfos[inf] diff --git a/lib/pytz/zoneinfo/Africa/Abidjan b/lib/pytz/zoneinfo/Africa/Abidjan new file mode 100644 index 0000000000000000000000000000000000000000..65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@uDeu^JFFpbENZj?%D{cub+o_;fLw)Ui%&zZNC z0sSuhOue7EB_D9j$Ss7@OeiHihAA|s)Z*hp|BI-XhhnEelUu~Ury BmV^KR literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Addis_Ababa b/lib/pytz/zoneinfo/Africa/Addis_Ababa new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J%-iV(Jecdi06jW zDp%_Aw$HQP&c%q@-T3hK@|`L_cck_+HR_-zq7GMMs?fi#3a6HLRkpnA>9frBb5;I! ztIxjM6uDn+wjU-syr;2{_jdCr@3SW=kiJwwr>MfWC8<3gRCVd3*b6guWX16s<_9v3 zsWrPPo)W_h1b;lHRiPi#(%TS2$h1TPFZyRC5EWCT);7NG5@sj5x3(Ge?4|kZZEW0? zgg#S4lQdTx21gGfhT(tp-P}L;D(hM*j=n;?LEQP&{vZw^9w9CvJ|RvaULkHFej$#1 zYR?eY5Z^wvbBK3{dx(EX29O*eSwQmO%47n`1(FR#K9Gzka)M+9$qSMh{O{bVb(r1J F>Q4~B_9FlQ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Asmara b/lib/pytz/zoneinfo/Africa/Asmara new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J0|N_42?K|ZZwP~~ ffgyuCkY->6p%4;G{tpBo(?LcNZvz+5G6OCE+{Pmd literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Blantyre b/lib/pytz/zoneinfo/Africa/Blantyre new file mode 100644 index 0000000000000000000000000000000000000000..31cfad771a5c7c609e495da650e3ffbcf07c974d GIT binary patch literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Brazzaville b/lib/pytz/zoneinfo/Africa/Brazzaville new file mode 100644 index 0000000000000000000000000000000000000000..cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9 GIT binary patch literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Bujumbura b/lib/pytz/zoneinfo/Africa/Bujumbura new file mode 100644 index 0000000000000000000000000000000000000000..31cfad771a5c7c609e495da650e3ffbcf07c974d GIT binary patch literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Cairo b/lib/pytz/zoneinfo/Africa/Cairo new file mode 100644 index 0000000000000000000000000000000000000000..0272fa1ba0a09ae8380be83cc8838d97d0aa6d25 GIT binary patch literal 1963 zcmdVaZ)nw39LMo=RO)m6)8UR|~2n$6tOt>w*SdS&XQU)(!N8TDIDBFTE;ahYRr=`5p*i;2$3a*5Q(;$cNpNmNOL(p$)W4n2*z;dJCw^~lvfUec#D&jx zOS`I2{jvUx?OD7?Tx=L8duOaueYIQUr3o>0x%`;DJeU;yw`9xy&Nh+hue4VV4XCT9 z4%&hD)~G-C_sGFl_6V`L&_l@{$+Bz}%`M4ZY(J5|vIIuV8 zjZtH_Hruh8YLO?m*}PMYYFux^-g;1|@f}jew@ecg_H45g)iRa8;e@=cev`O;b)CFp zPQEC3a*`}8OsP8)U&~3^uZp7hC0lg%OI0iyZE;(bn%w!RynB0tC^__towDwIbYIrnwaQ$jCtFA#mw7kyDPSol-kIXd5Q3Ju;(tI=5JQGyMvP|{f zn4V`(oB40F1Pe|^!kYR6x@PTTrsmyDkXmt{N$psxQz!R_>4G&uR^&hW8ItwSKiB?W zA>y^}^@-xC5%(0w=ZoRjzSk^Fi)1pzN1DG!_(=bYH$CX?r2`AMBX8U5-Z%2bk#~-~ zb>zJxZytH~$lFKWKhglw0n&ok^?)?tbzLBBAblW>Ae|tsAiW^XAl)GCApIZ>Asrzt zd0kIPQ(o5<(iYMe(iqYi`qubDZ=7om=niQQ>5rp9q(hDtksgsIy{=1+HodM-q*0_( zq*bI>j%JZ=k#>=OIT}Vf=4ct|8EM+-t6-M>vjs+DrB#a%|dp| c>$c15_6ylCuiG)N+cNyW?OD`~TS-~;FR%xpXaE2J literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Casablanca b/lib/pytz/zoneinfo/Africa/Casablanca new file mode 100644 index 0000000000000000000000000000000000000000..04d16090dc9af7d28246fd6a855eb902c5d46559 GIT binary patch literal 969 zcmcK2KS-2u9LMoT?}EcFb0`Xj_*IBQ1)fwkgw;}Fr``EHr+23fEk#uHP+JH-MbJ`> z1#!eTiW(AE1s7>h!Yzpk9U3ZJtcNg69G<@KZ`(OF^gAA2_h(Rk?@v&@e6iEpbkh0_ z59gRZyw5%=uGeOh9noBQJe>Qq61{uY8804r;Vph|k2l(GoZNWxBg|a68)as%#aoxN zUiQM7c-z|(QS-x};r4}M)YASuY@K}R?b&@j?m6`>>Y3gb_U>=>dLO(G&o0eG`SV}H zzQ?Q<%XB)`9KQOzw4fA1{AdoP{u3%mmzIej|#*-r2e_pSd@!kr`c zj@-Se_m4C{I+)r5>0xRU{xDrkZG-gT&-v_g6z&5&*!+9CZ+ZHROa39*@&q-LAGM*UXab0 kx*JosgY3uD4Iw)+bxSy=m%nK}|6xx}$8}-dhjLAS0KM)Bq5uE@ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Ceuta b/lib/pytz/zoneinfo/Africa/Ceuta new file mode 100644 index 0000000000000000000000000000000000000000..dd75e3e6e4116dcf9c72bf46c0d97b3a81d9aec3 GIT binary patch literal 2050 zcmdtiZ%9>l9LMoPNmYCc`N&4W|{r7yW3rfw(8z7 zsWn8t16jmq6@`T?*TU8V7g1Csg#^PzMMN9@B}%qMg^2y$r=SOW)RP|UcR2TTxNzY< zIPZ^d(VEIM>py3J`G%VlGB?jd!sg@0KQ;yS?)NWic0CR>-}PHZ+7ETL97?l(-Zv+3 zbjQ2aFFQ9|#{!=4v0LATyAy-cy3afgM69a|N1R{Z_0GL0E2=!dE4n==9MkZ3VBqgX zVM#g=c>hF`HKJZ6e#=)fvMMMaELkC=3Vf1~l`Es&nUWZnBx9o9lO&f{lb%e}v9}X- zT;~w|@Z10$-}+452k+~I#%r2fcTrO|{-&OaBbr)zRnmg3lAhfr6X$*}8N>HTW?GG8 z_Jm~eutuGHqgA3`WJcf9GNa>y%>3(s%xY-t@GXEbpBJ1mJH65k}HFB;ghkl=+}RBac86~*%vFNtq)~s z=pQL-?2u(;ol?H_fiBNEAuCERX+`{QS()9Ul@Z5v)o|5S_jc*(o}jL|P^)Wi_;lUj z#aeZKgRK81Tm4PN^7-m`4Q$Vl4MkZJtQ;y|q>hk{xzA-&tR>YcgSEQrPpOG{sx_C- zO6{Yoy7_36eA&^aTXvt(tu1?WTkSy&HH6geWB2L%^5XH;)z8)c?OeUR-Tlnl$1%o; z9r60vingq{k#;QKThRrDK5IcPcd^g%ng=fr=Gc~PJ3q2*-y6L2z2Q`KN0{E zfujili2(@$i2?}&i3175(L{oT;%H*wFA|KSi3SOWLp(@8NJL0TNK8mj9HK(PLgGRK zLn1>$Lt;aML!v{%b2RZG0U{A1AtEs%K_XEiVIpxNfg+J2p(3#&!6MNj;X0alk$@dd z#7M|U%t+8k)JWJ!+(_U^`3rP^ho$f{Kx<}nh`*Tz|o8WG6={hAj5!+12PcE zNFYOjj0G|n$Y>zLfs6+-AdY54kRfq2V}c9{GAhWhAmf4z3^Fpv&>&-j3=T3n$nYTJ zgA5QdLdXy~nlVBK2^l41n2>Qo1_~J|WT=p_LIw*NEo8Wm@j?a+88KwY9L<;^gXU;P s4H-6M+>n7oMh+P|WbE*NH+Yd|2q&bngzm!h(^b literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Conakry b/lib/pytz/zoneinfo/Africa/Conakry new file mode 100644 index 0000000000000000000000000000000000000000..65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J?RDAhhosltO!9lI#q zAENYUm@>vDW!6u0w$0SpJ54$LPPwc>xsM^8Kd-0!XHn1elxe0?wALKQscWeJ?Zf4A zoHtU+ab9Z5rBsWr&ci=jDKGAoCl)uAKM+)Xasu+!_mlaD5&1hg7!sevm?95~3!_2| z3o$Olzz`!t3@!555QB?6I>hi0<3j|1hyW1+B8JF8M2-RxM&vjUfkci35lZA(A_s$r z1`$r=cp?Xch$wPMh?pV=g@`J0ScteH2NpRpL}-y?Lj)H&I{dfrjl=wB2>XMLUnl#* AVE_OC literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Freetown b/lib/pytz/zoneinfo/Africa/Freetown new file mode 100644 index 0000000000000000000000000000000000000000..65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@uGwXU9&d$p(IMKUsi;r&zL$G6T2uKbLLP)UkKM>?rJ34@9kkud>WIc!mIRQk2 QoB^WAa0(a5VY)_K034z`A^-pY literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Juba b/lib/pytz/zoneinfo/Africa/Juba new file mode 100644 index 0000000000000000000000000000000000000000..83eca03ab87f54441e6fbff4cfc08408c4e77c16 GIT binary patch literal 669 zcmcK1t4jny9Ki9}dwI9+spsoFy~9Ne;vt9{EaHm;@c{$3NrRKTGl;<^c$icWi+_N{ z2Z+gHwTNIFs~E$)s-RfW5w(jHmk>O&Y7#@hevUg zHO}UUjBI_F=u$<;RL#W4UUgzZnYwOYCjBXs5@qd*UgJLP%6gM9-i;^I*Dt2wbX+%{ z$5qqLhRp0etLDm?ZmHg>*4d(No4HW!#buf8J5U|oq0S}ORqpLpcE0bME?k)B@&#PnpDS0GAuf3M&V89wa_Q}DCR0VF}P zAZd_1NFpQ?k_yR%Btx==+H^=hBq5R!Nr~h{k|J4=v`AhgF_IZcjpRm>BiTc3dL%zG S0%Q!xDDa=g;o8{@47*>))q+(3 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Kampala b/lib/pytz/zoneinfo/Africa/Kampala new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4JCiJWd%I>j z&&ztaQt;>B9;6fcvh!9y{#JP*)*L`*_5R>CYegaXj=X-)bH2JVo z?ZW4_4bTT^gmgk$A-#}hNH?S%(hq4^)pkT$B0Z6&NLQpS(idrrbVgbuy^-cfcceYi eAK5`wy9ZV@GwXU9&d$p(IMKUsi;r&zL$G6T2uKbLLP)UkKM>?rJ34@9kkud>WIc!mIRQk2 QoB^WAa0(a5VY)_K034z`A^-pY literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Mbabane b/lib/pytz/zoneinfo/Africa/Mbabane new file mode 100644 index 0000000000000000000000000000000000000000..b8b9270a142bf3b1b4e3a773a0d7ffc52c489bb0 GIT binary patch literal 262 zcmWHE%1kq2zyK^j5fBCeHXsJEIU9gPliT@>GwXU9&d$p(IMKUsi;r&zL$G6T2uKbLLP)UkKM>?rJ34@9kkud>WIc!mIRQk2 QoB^WAa0(a5VY)_K034z`A^-pY literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Mogadishu b/lib/pytz/zoneinfo/Africa/Mogadishu new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J7i><+{*5JG}A|A8Q?YS|nR4YCeo1{qdz0qr;70svU3GM@kd literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Nairobi b/lib/pytz/zoneinfo/Africa/Nairobi new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4JmwR;JSOpFW+d@>+;J^=<61_l`gMh+j} m5QcC^5DpFj$pAqJ3FiL?0+1abeIR>4G%0p*0qxc`FBF7@J@O%%G$>4YQ-1qL5yZf9spI>psucK1)YUs;;FC-JQEs@pMEdQei)t9FaX^C%&6~k%Q@Bl^R;rGrK!t*1Im` z^2I_p6l{_2eqC`ici4rfTQKh_Vou1sZ-XUjI2ZL()1H{f%yIBU#;l+5{_yc1W&ofd zP#`E6K@A86C8&X+;P6aJjEDgXcg literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Africa/Tunis b/lib/pytz/zoneinfo/Africa/Tunis new file mode 100644 index 0000000000000000000000000000000000000000..0cd8ffbae1f125920be88989f19c140ede9ae0b8 GIT binary patch literal 701 zcmci9Jxjwt9DwmlUqlP7CqhK&Ag#3yI#hHiovdvU-wI|>rw$@?3(^nJ(Sk$t132iU zDEJjru&AwXt3!i=-Q)&W(R%_q=;Y#syI&Go2+!YIEw7J@U#mmEVY6a-bKg_d$9Ac> zc#+<8>&v^P`eV$!da~T>yZdlW$;yBo+fS=_W<(8*_NzoRq=qx+b~2c>rz&BU%~Zv7qV43e?=l~}bPDm7 zEWEzR8ULv?0zSVl5d6OM(~nDtt}Xqs!j}>OA)M4JK^GAl(~2_Yhq-Sbv~D_8v<_npUEyu~}m85$o8$c=gJ8x9NF z!}CLJZ*$@AufA=@_5NyXqQJYJD2eY5myYMEve^^yxBpS)FH>}&D4{CC$GS4($^;Ac zbnxuZRCzaa)sG!hee_O0`M73kzQlCx;)1E$ic9_5G1Kt=jWiC;tEO15gla}rb5B58 za@tgDS(db3K2qVda|s`$sqX#mmzqyoi^IjQM+~Y*&*2zbbYMq#bZoSBp@5Baf)v>D*mc{?KkJo0^@vqt7j*K)_f*Qz7dmyMORerXqQyXsN^AUFrngI#QPeLpD-u*z z;!c^J5v-nYdu2{syvVthEpz9B#FOV@C(cetPtrc&0yEuYLc7kS()1Z~s}9 zUv^19TmOkFSpAJIEa+2(zuu5VDIbfXi{r95{E#Rf8IdJ3&EomNZ}s}`4ye+ulX}Bf zuc)#u1KK%SqRQ9o)*CyLRYhEt_Es)b-nm>|nRQcDUagdymWGQ>XLID{J2yo2N3rt7 z>2a}T|D1ejY(&)5Ps^=C?}*yc+q&-HNwqEIvfkb}pz6cNbVJc@)i85hHzro8#tZv& zlRH;64cF`DYm3#ZM|?e%fCW* zj|Cb zzK@T~_1P7d%kOV+T)}>Sdu_lx`(0pviLm!bzOER*zqd7DiM=mcU+Q&js4#Dpc^$7S z-`w*Hyso@;=CaOQ%n9Jb`Rn5S?~#R>Kk#ynn3sFJ-<-8){usyZgVi<2=#b%A&G?W3 zq8%X@hR88v1O|zW5*a2kPGq3SNRgph%~+AaTFq#Y;UeQj28@gt88R|vWYEZ{kzpg_ zMh1?I92q(?c4Y9#=#k-D&G@Y*07wLo5Fjx?f`CK;2?G)bBoIg>kWe78K!Slp!)n5T z#KUR=fb2@Mh(BsfTPknkY!v6=uO5kf+Q#0Uuz5+x)| zNSu&BA(28tg~SR877{J12^SJCs|gqqF{=p~5;G)dNYs$9A#pIBoav^lt?U*U?R~(!imJwY66Nx)M`SC#MEkn zibNF&D-u^Eut;Q)&?2!#f{R2K2`>^~s|hd?VXFx-5@V|gG7@DZ%t)M(KqHYxLXCH0 z9UK@EdauXrnRg$bziVB+?f+@^KheH>3o}7a6Q=0Nr5UN|sUo>FEiE-IRfPQs4Q6_I literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Anchorage b/lib/pytz/zoneinfo/America/Anchorage new file mode 100644 index 0000000000000000000000000000000000000000..9bbb2fd3b361ea8aa4c126d14df5fa370343a63f GIT binary patch literal 2371 zcmciCZA{fw0LSqQ0v8C15>k=qB=Y>=0R^EbF9-r60dl(ukxF8BSP2AU_(W1Vaw{?0 z9L`3^IkwuQo#|G(7TvVgCgupY9>$_{YbHzAdYVOYJKvM57d7AI|G)G99PW7g`??!i zp3HIl>j^WzaCr8a!#!oE`Hb$#^NlC`+&11+EPo#_Q!^(vxcqOdkdA>;SHO!YGO#<@ zHLJZu2Q@AC1=l9&kfKDNGdol}UtZ@6i<;75!xOIXAI|FAz8UpJe0f<$`i6bCpB$BU zym`hIb#PeTx#y_st}Xp?cFSH@bbY&wsc3WET~H_Iq^@?&UC^rMg)MQ#2G;7>^ysMA zAB*+;i-{_3e4)PQlvBkY3(@x;zN|!7fxNGGR4wq#mkFD`6AN>%%fyvuL{iMxGCA$2 zNS>M2so{G?>f~2CZK_SAkG!ul&cCEG2M_D4;#%***zEp@Jt`Ej#F{-qRIF#U_T|Ko7^z{KaGP$%gJ-#sZF+83&q9Xcdjty8*a z*E_1X`mA2wd{C7vdP|pAR2}u z#M%kO?^ky6Pf4q2Jddw9I5rjGOyZrWxw_&S19i% zow~)Du3CmYdefyy_0)k5`Se(tc&6(SxmibuR?kw|)_+yB=gpJPwvLI8m}%KreN1%v z=jg8dbE<3dH{Cr~tL~8rz2(||wRP}4z3q!mwY}$cz2k&O^{nmH&kf|OfWTP+LBThB zLqeUm@O3yoyykHD{T=HaL4JR4TR^D&M%Z7X>^+9BBi8Tl-x&~Z?+L4_+>W9;a~?IP z#+-8gC@*n4>bX>!OHrk{nJ0h`&tDh!e{U_^`~!#Q6?3?!_|3EI)b&qsM_*AnvOQ#f zRB1(*dLfNDq)EAYDM(fb;=r1kwql6-Y0TW+2@_ z+F>>QKpJ8-9YI=x^aN=N(iNmFNMDe~Ae}*4gY*Vz4$>W@JxG6$23bvqkQO05LYjnh z32773C!|qGr;t`5y+WFWbPH*h)$|K#nALO)X_?jZ3~3tDHKc7w-;l;3okLoO^bTnr z(mkYoR?|PEfmYK&q=i<~L!^mF7m+q1eMB0GbP{PL(o3Y7NH>voBK<@fYBe22T52^t zMVe|gT}9f8^c86=(pjXnNNkwlp-TPl=ntxBFds{-rg@Q22p=h9-?{$-zN~um~qXaZ%9a z8N(r-@oXxn)X75>LG(Oz%)vtKIFqO6{j4tDPky}Qrn}Gk8x4)VN(MKcJMI%+o|Jp} zziMG+TrxG!@|o+Wdi8fozU0Q$Ui!1_{VK}-gCn!QI;ak=?n`btrw$L6Bp-$9e9@YV z@|WuJ>#+Q-uG59SCMm4_P{qWSDSj&GtJ4bkH}g){Zp=#E#ICN_PmBsT^}`RN^62im zZWy>{8ji=^7iYTnb10 zK^l2oC!`h9i{D8zq#M!>>4!8#IwCE-t|!ve>$)Osk-kV{q%+dm>v|*2y{zzV5R8j9t&E`xgfM2OH<~xM3<~QDr>gL*%X_l+%YkkOHs_xL=N(uA5cu4)Y zGvF@|CzQPw_g9S{)ZzBig{}Ds-QJcjbR<9Pj*sUGom0O0bt*W-& zJRD6np6Q)4G2J(`TKB!qM)xm`=>x@M(X6#Lu%hKJ-%#+Ex5eE<*3QK}&)QPO3tC$? zUf9|fl|ZYtZ)$-y?p|wuI^(6}Y*%qPJ#v2EB=Zj|$p_oB!?*j9wUEV-)sW?!vL3Ra zQ&vQlMAk$WMOH@=jSFDFCU!DJ38^IHd@r3Zx9A4yP1? zRDzV^lvr97lQ gq(G!Xq(r1frxb})iIj<1`LzFEXJn5w+uIZQ2~&paD*ylh literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Argentina/Catamarca b/lib/pytz/zoneinfo/America/Argentina/Catamarca new file mode 100644 index 0000000000000000000000000000000000000000..b798105e0f660c7b85a663d945d9eb7f04505e52 GIT binary patch literal 1100 zcmc)IJ!n%=9ES0mHqjUh1{4$#sn}8)6%K7<#gB3%LqW_^14<_c5d=YTu!3lGaS{Sj z1fiwTu0oGPwKUZp8*8*f38X~?hk)XysKgMzdi+1fNnCXDUe5hp?sVaOGJ~g1b_IW2 ztKBH zns>I+nKVnO6Sj6Bs%m3(`#Ex2)w|-l{&vhX)~9TvR8e1QL;7oFxA|5~+V3+*)Q`IZ zdSxW3-1Q#4di;=yww=juAD%RA@!@=X?<(Jw8r$m%C=^mbD^f@zq}*iUw0EWN1Egrac(XX1kM%9LD;#H z4WiC1mqRViy{?Ahyaz3Z)0rS8XP4@c(=F%sO)~$mlKikcH!`skSqoVVSq)jvE9)T( zdSyjqNn}lAQDjwQS!7*gVPs`wX=H6=aj&e7Ebo=|kphqkyix*EgI9_`szAy>>hMY- zNF_)qUa18s#w*nzr8uNIuat+> hhZKlZh?I!b=#?UoD!o!BX64uZf1R=Y(rjOM>?dvN>pB1c literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Argentina/ComodRivadavia b/lib/pytz/zoneinfo/America/Argentina/ComodRivadavia new file mode 100644 index 0000000000000000000000000000000000000000..b798105e0f660c7b85a663d945d9eb7f04505e52 GIT binary patch literal 1100 zcmc)IJ!n%=9ES0mHqjUh1{4$#sn}8)6%K7<#gB3%LqW_^14<_c5d=YTu!3lGaS{Sj z1fiwTu0oGPwKUZp8*8*f38X~?hk)XysKgMzdi+1fNnCXDUe5hp?sVaOGJ~g1b_IW2 ztKBH zns>I+nKVnO6Sj6Bs%m3(`#Ex2)w|-l{&vhX)~9TvR8e1QL;7oFxA|5~+V3+*)Q`IZ zdSxW3-1Q#4di;=yww=juAD%RA@!@=X?<(Jw8r$m%C=^mbD^f@zq}*iUw0EWN1Egrac(XX1kM%9LD;#H z4WiC1mqRViy{?Ahyaz3Z)0rS8XP4@c(=F%sO)~$mlKikcH!`skSqoVVSq)jvE9)T( zdSyjqNn}lAQDjwQS!7*gVPs`wX=H6=aj&e7Ebo=|kphqkyix*EgI9_`szAy>>hMY- zNF_)qUa18s#w*nzr8uNIuat+> hhZKlZh?I!b=#?UoD!o!BX64uZf1R=Y(rjOM>?dvN>pB1c literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Argentina/Cordoba b/lib/pytz/zoneinfo/America/Argentina/Cordoba new file mode 100644 index 0000000000000000000000000000000000000000..5df3cf6e6377be897b4d09fe438ec75eb8c15ad1 GIT binary patch literal 1100 zcmc)IKWLLd9Eb5YZK5$03@9if60xNuA{=dF#6RVg3{^2p4Je%yL=XhU!3v_$#YqTA z5QLV-c2zuwYH6cAjWyb#1X2;fA)vS^A~D3OJ%7(RiHlC=<=)Sc>4oo;9XffU$NS^A zLjK|K>zBiQ?PYn5U(c)i7Y6+Y8(!$CO?iK6@r;^YchO8wPUxA|J->A0m3sL4oq2RV zqGxB;`(`MwACHduPj;16`BK$9-PqJ~M}z*{aza)1rc9-NS3SQ{@aHqP^!&%GW+7wM zg8pD?@uXf%A2*FXVbvIGnlGVKs@W6uoA1YTYfaj;DmC@BG3+nZw(D=@r1@Stq<-8U z^p{7H%3ka9S56$z;m*^=Erm(l87&mMQlE9#r*p;b&8t;+^++-9SwF6K78CWF+IsVF zEY*Cbcg!aA!0;+P@Fo}Aw=}NzmyX49*4jW@`(Hkx;IF3*+uc26ZMo`s?j5wYV!W`m zFROtLYv0xbQSM&H!A#am%h{&-+sDF~?uDG6OoT%;(8-=iv|ETk@^Fr+f4l!nyil;V);oKhZAA5tJv dAyOhzqf?4Rszl1foP65^?DhZv literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Argentina/Jujuy b/lib/pytz/zoneinfo/America/Argentina/Jujuy new file mode 100644 index 0000000000000000000000000000000000000000..7d2ba91c679aca41eb44aec6115745ab107531e8 GIT binary patch literal 1072 zcmc)IKWLLd9Eb5YHqjV51QZkzDcDjQ5stPoqE${h6vQkwQ0U|!f*>dkRuGLY4k833 z2trGvT}98K8ro{l#+urp1X2;vA*i@nL}G~ldj6i<#6>6Hm&@lRyu%CMCo_EFcz5u} zwO0P&aP`UIdE+JdxqUsS9-JT454Suw#cerVTt2O)HeIk&lM`mTd0)@mc&Q$}dT$?} zi<#N!&Dsv<%#*P(J-4rDGpsKN{Bas|i&;n6l;CJ+*K-uNTv|%;L%wyOefn z$$YfcuB2J+KW6LuqpCjMuwNo4Riitu8z06^b3?yvmaFP(eMEn&?l#{`N&92wi28YF zP_K?AmAlrf*G(KU(e_h??fFU59?us#QlCx7r?Z95#E|WL``YYSxvIO&-a?{QRXcAE zwWS)*%%0hV85mh_2Hs@bvd+2CnwG!3BVlfN8C&E;oSRz+0_RGVAnaV(22tl;R6?!J zy{m=dJO{0Z)0v=O&Mws}r$^53OJx3G8~I#!X7p|m*$CMQ*$UaqE1My^d1X6fKV(B> zM`TN6Ph?YMS7cjcUu0usXRmCH?Cq7!k=>E)y|O>jfLA&|T0nY0n(#^&NE=8WUTFmB z#4D{Jy&%mX-5~8C{h)7%2kA(X4`>PL326%H3TewLeIbo`r8A^8q&K8Fq&uWNq(7uV UuXKo6`IP_NVysUJJT1rW#RmjD0& literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Argentina/La_Rioja b/lib/pytz/zoneinfo/America/Argentina/La_Rioja new file mode 100644 index 0000000000000000000000000000000000000000..7654aebf0b084f081db62e2b03dfc287df974e35 GIT binary patch literal 1114 zcmd7RJ!n%=9ES0mHqjUh1{4$#sn`z_6%K7<#m^%d3SyQT5IQ+XK@b!ND~MJXCm|p~ z5Lz1TDtH{CrLFeVSfd3?AuS>}1Qa*LN)7d^$NzJj#6>sn<=o#T(}nj*w&!GTTkyxV zLVn?J9hAfSy36voeLbh{U+C5kHa<5an{#?(?u;5;f6ht1g1Jw1NorF!`4t$lPp zW+uiqXxo!BlY@i$@!pauU7EE|Hr343@vxp+PO9>uPFt?tRnM;E^>pTznf`Fq&SadL zG4E|9kur1X6LxWbR4oqG?B~cSRcnju+PfjMv^H&*$`$oxu}^=k>@wdou5qe$8&|&Eo)Tki(+A0ON&kf3o2pu6q417 z+J5s`V`uHD**%dodTc@I`PH_oZnoD0>}|K%MC|GMiu^%0CXH<=9r=Ss6d z*txO|qR!3Fh8moEQw_y=4;qeSvO!wTPSqi&UC!^rWdC6*`C@l=!0bRKLuNy!L+10! zgvg9unG%^3nG~57nHHHBnHZTFnHrfJnH-tjE7K$MdnExR10)5naf=#%0_vXXy5Kw3y%NMcB4NNQfm4N1-`*&*p6 n`FSNlBts-cBu6AkBulTPiR9^(M6q8!@ju8kwol?c&>s5abp}>@eSp3HxLAfcklB zK(CA@l)Dnwt0(rENc&KJ%gCf@kB;O!l3z^6=hOMl4RPK1qG+}*AG2L%uZb7DZM?mPOV@7DiS^mPXb_7Wc~P$nsuUA1MH-z$+ynHF%{6qza@A zqzeT(;+0yEV!TofQVvoNQV>!RQWE-_xRIhH{(!2GvXHuv!jQ_mQW{d5SBgWb lL&`(yLkdJHL`pY=QzW@=X?P~x4 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Argentina/Rio_Gallegos b/lib/pytz/zoneinfo/America/Argentina/Rio_Gallegos new file mode 100644 index 0000000000000000000000000000000000000000..3c849fce2f09e040563440b93d1eae975eee4eea GIT binary patch literal 1100 zcmc)IPe{{Y9LMqBbgQ+51_eb(7%Ac+cu?1(Kk~#5LgdAS!czwcg23pIL6mjr6bxbn zf#S+*&{JTEv-Z@ol?NG2iJ-%v=vHLdSpR(bzMoUkrB3~RJbV52JN@AE9vD1%qBHp8 zS|e{bTs?Al-gHTRZe7c$`{(=hgRL*j^tOziUOcU4HeawaQEIk+FUbp$HRJVHK9reQ?^vSqn=;R>iNE#X8yw!yU^#< zf_ZN%9Z9p89C zwxnv$%$`ER==`$MOY3d#$U4*edbDNKITvbZ{L4EM{^f3rG|0b*bA{$0aIRPm!p@a! z5Or>;9BOjzO*ItfIcPfEHxQ)d>{8uwy5#)6$-qCXBp>X~jNKbT)j6j>En7Ficr7+D!v8d)1z+$*ai%X?*gqyVG>uatn);FThfDv&aeI=oT{ zQVCLuS873u@k%vFIY>Q7K}bbNN$6|hL5h<21FAyGLh3>aLn`x1X-I8eDGsU5E9D{e hAq658A|)ad62A literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Argentina/Salta b/lib/pytz/zoneinfo/America/Argentina/Salta new file mode 100644 index 0000000000000000000000000000000000000000..a4b71c1ff07647569d5284a3bf4832dccc9fae9c GIT binary patch literal 1072 zcmc)IKWLLd9Eb5YZKE+13@9ifQn95ZA{=dFM5~-+D2Q2VLFwcmf*@ENtRNa)oP>Y` zL1<~TYteJ4mbTinu|_+TKq4YI1Qa(#B!>8}=kGZuanZ^5u^!$yN>fx)m_R;yM zS(w|b?QqUK9v|0>`--Y~scfHYZJ4FwZF*@np-Kl+wp6>To?Xf7<;*R!{Nbuy$vCxQ z-rGt%X{zZHw!S~2>Jtt7Iec0*dSbfqZo)J-q;0cQQD5q#`fFvk`BqHY@AF60kEubu zI+j%KTAyA!dB{XM&lGm#r%h)pU+7AGGF>0f6}q>tSKZ}fg?Qj}-0UqRY8AEf=FyH+ z&(FGY)95P7iww!%QxKi>uteSIbr7(r-Hz_VmWAYu4IFVb1%xFcIV#I zLNV?^`{B$`ke0Jc^~vd#^ZOD*|FDgGt~)z+r;2QZ?1XHE?B$isklnnp9kL&?A+jT~ zC9)^7DY7fFEwV4NF|xB)wnp~$%I3)K$o5{@A8EiV9Uv_rJs?eZr3<7Dqz|t&f^_1Q zR*+thW{_@>c94G1H^fCclH>zgLV7})Lb^iQ@=9MwV_xYDY0WFWAJNJRvPfa0d8#1LOSp6@t`i*DY_xxY)M3;!>vv6CnIy+5v1 z_7@J>UCW5O=SSteO;6SAmW-TTJ}rtHF6d%mTFtd*Wa;_~asTBT{oq_! zmFG4}J(f`qr>5kiy%kZpSkv>HTWaB0KrXDrM0Gf>tBpJ2$>pqEOx{$B@2}{kq!CN% zovufR)NX0D|_2ggOLKO$RibE>^Aq1)BE_|lw^U+cTnx5|+IUOFOv+#Z!H zlS9H>9h9r552;Z1sr`ps9GQZO%fMkHA;Iug)NjPm5NE%2UNFq*~ z36ctui_<29WaG5yAo(B(AsHblAvvK-iWi%e{ReoYh2(`KhGd4M=CrvX$vJIyNP0+q lPMaW-A(A4JBa$SNC6XqRr_&~iY5R%)L8jq-Hr@jR;h$gt?34fi literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Argentina/San_Luis b/lib/pytz/zoneinfo/America/Argentina/San_Luis new file mode 100644 index 0000000000000000000000000000000000000000..acfbbe4349fc5fe53196304a7bbcdaefda890918 GIT binary patch literal 1130 zcmd7RPe{{Y9LMqB+v3w% ze6p48DYMXZ%vSftRdu*#zlA4Ntv#V@ABRnSO_!~gE9!f-U;n7=G(SrzyEJ)NE#K+Y zD}yQJt|oQkNVkbMpDJw4kD2B~zR;5XYFd_W7g{$Yb?d8=*_PR4)3s-2*HoL)#bMJK zRXWqZ%4DuD6*84xbKuKaeehn=biY2L2b^<}hUj0u!Wb|4a2O36!Wdr*L+7Sn27z;> z`5@+8*#>dv-pof5%!5 z36UASG9@ynS0+VfMW#jOMJ7gOMy5vQMkYsQN2W*S_eugt23|=4$pJ~iD_J0EcqI=c z5hN2N6|dxiB!gt*m2{ANypj--5t0&;6Ot5?75cQek-X%e5Re#>8Il^38T zy?SXVuI!aAy?kuHX=*u{-IN(OEs;#NwfnPa{dgwZwsECuD;>_pJgZ}7d$zq&R-3OM zigwqYm~B(-M&~{${rrXQ8CYR@76zkx7e~#${L$#3wKmYu_?LGm`0H-K#!!R%7P5AF z!Sk#wl)Rv|Mc-?(_GKy1Z0(y$Ai}fPd?1Wg* wbmx@zkp7&~AkrbyBGMz$B+{i*+C=(vN~1`pNUJy~ulhgq8r~@t@9qr$1mS)4g8%>k literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Argentina/Ushuaia b/lib/pytz/zoneinfo/America/Argentina/Ushuaia new file mode 100644 index 0000000000000000000000000000000000000000..1fc3256773606e80a3d300ddcbfffafe72b56e45 GIT binary patch literal 1100 zcmc)IJ!n%=9ES0mw$T_11{4$#sn}8)5e{u*#E)_$LqUu~EhwEFL=Xy!gB3)hi#P}Y z34+klXjj40p<3H&j>a18Py%TY!J(kIDJn6ICO{e^n)@{N6X zHfjpf8?+tBnnxoe`tk0fDqbktC!1e{7iL{=~Z_Q1Z)>tmzmi%PeKAg$7Z&{<-%SZAZ!AI3$cID$s6}9cg z;g)3Wso7bG8-4$v(u=EY@6amK`zq6tan6Mr8vpVRH~n%qh8yHx*ttSI2%IaHgC^%n zHi$U4SPnHi_j)N5<2h(PknRssa<;2(IbCvo-=zN^R+0~Pr-#R{AZsCuA*&(Fd1XCh zL9eWcEQzd%EQ+j(EQ_p*ER3v-ERC#$OzK2iWufmcdEYVb-CNEJvKNF81& z1gQim#VfTS#dxI}q#UFkq#&dsq$Kn;@gPM>`~g)VWg>g&~!Br8J~AuM~$==aur1 i`j7&V3Xu|#8og2^Ql(eQ#Eg8}|F1K;N1E;Fiv9#%wCh>` literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Aruba b/lib/pytz/zoneinfo/America/Aruba new file mode 100644 index 0000000000000000000000000000000000000000..d3b318d2d67190354d7d47fc691218577042457f GIT binary patch literal 198 zcmWHE%1kq2zyQoZ5fBCeCLji}`6kQhDSw;s#)FaR|Ns553=IGOAK1ab^8f$w0}Na~ lz99^{1}4S^435DeAYDKZLW0@i*>4b`7X5zD7>7@KLGdbhFOi3%YDPwXpWw6<#-q|c^KX%xuU;iZ2 zF6Z0nXQyfU$y)Qs(K4CQeA3R0AJj)T^_p3$N9kjW*O|woF?pgeW@b;W(~OawCga{m z@?`%JGe?Uwv-=A>_tF^2I(^z?$CEVl&P|(B)~`}G!Gv?GW!~D5jf_99(Y&CI{@E?L zQ|{W_zMnPk-gTSb^^MN|{-S-VtzBcCy=FoCMR~fu#TFd>P!?95F=p3(DXi}|&qO}e zMJs#E;)(C-lJI%6i0bki@7w3PVp9CEXG_}BrSx#0Eo=5< zMSRGtEUlGQD{ha(D(zx0-U{)4;v z!ijD6#m4Kpp{dAj-0-Vxs?4-6E$o%e^ONkB85d-0YQNp;9hdD1W6kzkO;X+Wi>c{c zFFU%r&CXBuYHiy&^YYu3TK8tF*;O6W_>Oj4UzD!9m%nHCWO&+;xyQbeJY5G9Yz8 z3V~DtDFspsq!>swka8gPKnj9X1SyHHs|ivRq$)^Rkh&m+K`Mik2B{5F9Hcr(d64=b z1wtx>l*rfB2q}`Us}fQsq)teokV+w?LTZH+3#k@TE~H*a!H|j}B|~b46wTLF4JjK^ zH>7Y#<&e@LwL^-BR1YbiuWP{nzkZ1eToVhT?u-; z#K_FT`v3nb83u;`|95U+WcmMp^#TSCFq;QV3V=uk5g*?W24@!_4hG_IAPxv&a0RkK vfDuZDkl?KUKv49qB?Ux-oCl&oP6W{)XM$*uQ$aMyxnP?5PUZso!ITRCV)1m0 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Atka b/lib/pytz/zoneinfo/America/Atka new file mode 100644 index 0000000000000000000000000000000000000000..43236498f681cc06f64ca2afa613880331fe6fbb GIT binary patch literal 2356 zcmciCZ%ma{0LSsm-|Iy&Dj-Bm!Hl7RhrdBt9tc86oi^IjQM+~Y*&*2zbbYMq#bZoSBp@5Baf)v>D*mc{?KkJo0^@vqt7j*K)_f*Qz7dmyMORerXqQyXsN^AUFrngI#QPeLpD-u*z z;!c^J5v-nYdu2{syvVthEpz9B#FOV@C(cetPtrc&0yEuYLc7kS()1Z~s}9 zUv^19TmOkFSpAJIEa+2(zuu5VDIbfXi{r95{E#Rf8IdJ3&EomNZ}s}`4ye+ulX}Bf zuc)#u1KK%SqRQ9o)*CyLRYhEt_Es)b-nm>|nRQcDUagdymWGQ>XLID{J2yo2N3rt7 z>2a}T|D1ejY(&)5Ps^=C?}*yc+q&-HNwqEIvfkb}pz6cNbVJc@)i85hHzro8#tZv& zlRH;64cF`DYm3#ZM|?e%fCW* zj|Cb zzK@T~_1P7d%kOV+T)}>Sdu_lx`(0pviLm!bzOER*zqd7DiM=mcU+Q&js4#Dpc^$7S z-`w*Hyso@;=CaOQ%n9Jb`Rn5S?~#R>Kk#ynn3sFJ-<-8){usyZgVi<2=#b%A&G?W3 zq8%X@hR88v1O|zW5*a2kPGq3SNRgph%~+AaTFq#Y;UeQj28@gt88R|vWYEZ{kzpg_ zMh1?I92q(?c4Y9#=#k-D&G@Y*07wLo5Fjx?f`CK;2?G)bBoIg>kWe78K!Slp!)n5T z#KUR=fb2@Mh(BsfTPknkY!v6=uO5kf+Q#0Uuz5+x)| zNSu&BA(28tg~SR877{J12^SJCs|gqqF{=p~5;G)dNYs$9A#pIBoav^lt?U*U?R~(!imJwY66Nx)M`SC#MEkn zibNF&D-u^Eut;Q)&?2!#f{R2K2`>^~s|hd?VXFx-5@V|gG7@DZ%t)M(KqHYxLXCH0 z9UK@EdauXrnRg$bziVB+?f+@^KheH>3o}7a6Q=0Nr5UN|sUo>FEiE-IRfPQs4Q6_I literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Bahia b/lib/pytz/zoneinfo/America/Bahia new file mode 100644 index 0000000000000000000000000000000000000000..143eafc2c0ab9d7f54bb0a21649d32ed1b4489d1 GIT binary patch literal 1036 zcmbu-KSD=b`c5eKH%r9@!(%q6Q%#Ufg z5X<*bXMe0YB})^h^=EyPsUA&hb?TYaQm<_7e$_00sncI0mrdh~>)Lejt!Xy9t;92? z?`8npdM#dJiA+HreCwtd=Qw-04Zdg76G4ou1p zGpaie-V?oYNq05&nanYb}Z(c@g5BC+KPR;lm^M3s) z{KpFZ4>^`V)(HAVkX4XnkadE7A!MbXUkX_ZSqxbXSq@nbSrAzfSrS<@=odv+4fOcxXDnUv?YC(!YszJ&@ z>Ol%ZDhm3NkeY(ND5NT+ETk@^Fr+f1G^949IHWqHJRJ7#ZUyZnlK)g+s>hd@k5hjD D$US9O literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Bahia_Banderas b/lib/pytz/zoneinfo/America/Bahia_Banderas new file mode 100644 index 0000000000000000000000000000000000000000..cd531078d0e8a4752bc242e83836dfdf0ae307ca GIT binary patch literal 1574 zcmdUuOGp(_0EUldSSC2wgD3*2APl>y>CLPxyOx>WG`p2q>b30JP18{;tb80u1R-(^ zf=GlQdQq4`nLQS5auXF5kqUtep{SsfAW0$9`NmZQLGAhvXTD)Jv-v(>VK9&=ep|Ty z!evGJ@;>)+?+5eNXw`6UV5k3{=as)N((Uh?XjM-?*7yfH?kFKlnNZ$hB-YK5D>4H{ zQvSG1Ub5M6Cl1M#;Y*E_`QJp!`$%Kek3o?-7HzHWeJR%TL|AJdc8Rq1&(^xmX0g8d zxs~41DL3TySe~**nX&w=wK1baZi+f$WiC#and2QsR@fq$HBe`48JQ}x`*s*P&t@v` z`B<^_=9tQ@pCR&2zg62xm&p9e`)Ygobm`0KR6Alm$(?bHYL|E>3ua$13x;pW-D3@A z;o}au=S{J>_jbEbrZ z{ozav{kLP=T|Oc7(-L8}edD5-X4~yjMDiYD*!KONkXe)8bG$y`-R=_}ee?A7X6Sc) zf1U3yWU}=T3L+ImEQnkX!5nJIAfmxZ+4#39c_JM|JcxV{0U;7XM1;r)5z?WS5+Wu; zPKclkNg<*#WQ7RJkQO2?L|%x%5Q!loLu7^s?NCb%5!<1b8zMMFa){^<*%`t^q-Tf^ zkslHOBmzhXkQg9AK%#(z;ZVl`2?P=eBos(2j9?(qFv5Yv!w3ix5hEl>OpKr)Q9;6T zsN;eJ28j$38YDJIaFFOA;X&eq1PFB3W=2wEF@Y; gxR7|^e;06s?%S=qj`JqD5?t|d-h_BpvMWB~Cwj`Z5C8xG literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Barbados b/lib/pytz/zoneinfo/America/Barbados new file mode 100644 index 0000000000000000000000000000000000000000..7bb7ac4d6ab698cc62fde7d1a357be3c06e34e8c GIT binary patch literal 330 zcmWHE%1kq2zyK^j5fBCeE+7W61y)w7Jl)Zn{FIO3(*wRKJs0?=GdvI|$-5v}_UM67 zNWcZ*`2Pta26h3A%uG=D|NjYT1_m(6^8f$U3yd89{~tfVz~kc^!r%nNjxInP90IZh z3_?h7&3_;Oxd=pqTm>=!ZBAzC~b{6>LU12Qsw+VS%sn(j$bI0_I<*&!e-w2b-LseF2|S4`@q3z6qSY+lhXHs zDZftZ^Jb)KvuC>Yoa*|>V_Ywd>>Fi>xYHh%9}S09xP{W<8H*2<)A zU0$fvtH$X`%d}s5_1EsU8NJ@u{?@%2Gc&OYZp`>jK~MBwnWD8e=XQ8<)>ii%$J(dF z?Y8#r*6CYX4xK5TmmBc^<%IuODL-ggimc6)#mH)8IkG-e3Lq6Sr36v~DS}i%${=-+ yLP#Z~6jCcwiXqi9r5sWZDacMLA|;WUNKvFJQWlHy-41Qt+@I=tb5eRf@O}Wi)&79)F>$gce{&1EtBcZv}Yj&$&pcnFV<^PQ_FFU{ledA#F%f8otL-L>9p3_q-N~u63Cx{0b^N@5#zceGu~x z{lsJOm@(PF^Ng8!)voHh;;$Y~=Xp^#WeFeDlh4vB{ZL?R*~k(fwOr;Un)b=tT{ zV5g0YghpZ`!I9`lcqBeD0AvKn5Rfq-gFr@s48v*1fegfHM}iE+X~%*L1{n=99ArF% UWI#?kB4kL;|2d|lT~}RlA9t(iegFUf literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Blanc-Sablon b/lib/pytz/zoneinfo/America/Blanc-Sablon new file mode 100644 index 0000000000000000000000000000000000000000..f9f13a1679fa9ca7cee6a41bf094a861d4c6824a GIT binary patch literal 298 zcmWHE%1kq2zyPd35fBCeHXsJEMH+y_ydA9x^LO11INk9m;ga%~f=dZ+FEBAOF|#oJ z|9`54f#LuEs}~qq{{KIIfPn+d<^hueKE5FgjxInP48-9;91sFDje&s?OoWi&ivK_m jXKM*G_y2#8Yd|!}MIajFDi95F8JMPu>$rf9HsJyQxN&4H literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Boa_Vista b/lib/pytz/zoneinfo/America/Boa_Vista new file mode 100644 index 0000000000000000000000000000000000000000..69e17a00e161ef17bb763868db7793b609a39556 GIT binary patch literal 644 zcmb8sJ4*vW6hPrkd=TQRv9XB8*eVPrU?L);wkikiuNE`{>zG4R2R}4o;fp z+N^Ho?^P>Y^;(y`rd=D>Uzv=F+@9&t{bw_FzT`#YB{P1or(=CjW~TArC9_8=RjKRL zOT?tDwZTA#CunUx>NwUu-UYhl>^Y(371x`ved* literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Bogota b/lib/pytz/zoneinfo/America/Bogota new file mode 100644 index 0000000000000000000000000000000000000000..b7ded8e64e79bd69589598da5a5b1b39d92344a1 GIT binary patch literal 262 zcmWHE%1kq2zyK^j5fBCeRv-qk1sZ_FjEK+zE#~3??R~Kh_5c6>XJlq#X8!+w*8~Oz zFv;@&|M3Hi9RL6C+`z!&;~T=@1jM=qCO~Wo)D8h5Bv=Zx&-loJtGg>|+#tLH8etT8ceMf^x@jSvf@1u| zSvs;zy7Z*2nToF2*bvunZDn(~HLZm`*DM=7Y_fRxb>8RS{_4NBzIVUh>$m&==k41N z*pwswaW3-@5BHLJ_)b>l)%*79&}#$nI`l$%sPC0)`tYjql#jcj^~j<>TSgiZ#9!l2 z%88$9#pJ~rIrUM2m_C&+ox{mO>`#(mozo)xsVO;QYody1n5!czC)Lct3GK=nRj&R8 zI!X?ys3XUvd+c>}-B*1&`qF6;Gt{nQKj;&2?}Vg$xm{e}^_;w6SChCg&?slGFA?#j ztK^*gG;vefZ8BkDv6>s@mWko1D)INlI_aAzl{_*_&pY$8nt%L?UeI?=r3B~6o1Z=8h5LrB`@mYqK&Hfk6~HjX%!_G z4$0D(dQp0!ORl+ED9T=aOP7CEr@XD5`u_J%LRI z$v9n|dsbC{pCN1Ke=BN-W99nkVX@)OG5O%=AyL;cA|D#;67`KI<;LDE;$iQ3-O$;o z9`W?+NBsfS7_(0|m6WNbt3mBg^(g6dmhKU;HFrvGYd$Hq$6uB^Dtg3@=?NXo>sP_iFZIrZeQM|6X}xPasGjca)6ZP2 zRjqC9diSXU)mGo6_v}wr;bG<%IU^!+=6~wvID6xSaGZlWEW&ZRm6+u??}oyn?OXD{ zm~Fok%Dp~OS!ABIKH;q~Po;VIHve&9_6@#&F*(OveMI6AGCgE|$OMrYB2z@>h)mLI zW{FG_nI|$)WTt4R$_I1h%w&Pta!ePQFEU|d#>kYBIa|%7ky%^Kw2^ru6GvvwF?D3_ z9Fs?8&oO;u{u~KFGQg1nBnKQxK(c_O0m*~aBm&67P9+EyJe@Ft6 z3?eB+a)=}m$)eSy5y_*~BofJ_)ua;1B}X!mY;vR%$tOobk&JSr6v-))R3xiNT9Le3 zO=6MET1{$^+*(a?k?bPrMe@s$U?jsFDMoV4kz^#x9BD@KY&D5SGHo@fMsjU6$wsn` oq#MaMl5iyBNXqelmUFkM{Bl$I4DZs+oXo5YZ+3QOc4n6QZ`ww0CjbBd literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Buenos_Aires b/lib/pytz/zoneinfo/America/Buenos_Aires new file mode 100644 index 0000000000000000000000000000000000000000..dfebfb99abb86536da12b96677dc0c76c088cab8 GIT binary patch literal 1100 zcmc)IKWLLd9Eb5YHqjUg78Dc_iP%ya5so%7;-B(LhN?764Je%yL=XhU!3v_$#YqTA z5QLV-c5x6qhiYk~J&iTCLkXlJfzzV5R8j9t&E`xgfM2OH<~xM3<~QDr>gL*%X_l+%YkkOHs_xL=N(uA5cu4)Y zGvF@|CzQPw_g9S{)ZzBig{}Ds-QJcjbR<9Pj*sUGom0O0bt*W-& zJRD6np6Q)4G2J(`TKB!qM)xm`=>x@M(X6#Lu%hKJ-%#+Ex5eE<*3QK}&)QPO3tC$? zUf9|fl|ZYtZ)$-y?p|wuI^(6}Y*%qPJ#v2EB=Zj|$p_oB!?*j9wUEV-)sW?!vL3Ra zQ&vQlMAk$WMOH@=jSFDFCU!DJ38^IHd@r3Zx9A4yP1? zRDzV^lvr97lQ gq(G!Xq(r1frxb})iIj<1`LzFEXJn5w+uIZQ2~&paD*ylh literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Cambridge_Bay b/lib/pytz/zoneinfo/America/Cambridge_Bay new file mode 100644 index 0000000000000000000000000000000000000000..f8db4b6ebf5b66d3d566fc3975d6151783f6e645 GIT binary patch literal 2084 zcmd^s#^KxUfIg>*r9A|hfIcss2ibvH}IZwtqG z2K_Js8s8bWb$#dB3;CH)qPjf`+!yh5JUPMw``>>w}U} z`J$Runk&Eat|{_4+VLB<8;%`@U1yKZtl+a-7Syve&4mPG@fnEb14+Sj|!_)pjB zf{us@99W_YBb};f$27e-xJ50g9o5C&m((*w-|3}kd8#Dqp_JSmlhQ|DkCdhUSW|ZK z7xnD-XJuKx$2@oTh%Dd#vw8mTURlw4*{tk5tY4_^H>;ZW=<{#cb~C z(y#kYnKvR)-8|`_X$e;8misXi@%nURsNO`=rs(MLGPCvWuXu`S*AOhTFl-<**YaDIVCmavGM(-J)WL6&X11c3;CZDNhFSaoRsRG>50S< zBNG#eulkdedz0brGq_6*cbW6|dp_w41SGUBETI~E*4R^C&Lb?VIQB(_Jt9f&huq_z zYxqC-kg&rEh!+qy9IYP^Mk!8#L@Ny(iBJA6-ZkceStKF(HTf<7`=fs zhtVBKdm#ORGziimNQ)e8k04EQv|WO<$4$?bF^B~=W|L693Y%7=AOy-9c`-}X6{7_+`ztA7> F`~~dE4qX5M literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Campo_Grande b/lib/pytz/zoneinfo/America/Campo_Grande new file mode 100644 index 0000000000000000000000000000000000000000..495ef4568347ab6d3887a2cdd762c960f567a0ca GIT binary patch literal 2002 zcmc)KZ%mbC7{~EPD&|mYt~O(Zq0Eeg<8?SEJ)zO1Owsg!8-HMU|)M97Z!_wHpc>YWdJ_?$5|#&&&w ziHe%7dBMM)Tij20d79nJ=cz~DZ;E!@vF?wK>&2#XWT~DWXqCR$r`k6>SudskZZCCK zOaDZc4!k}rgZ=O6P-2Pv`Pwvl<+klIYU}js?Kx(wG+)QgekbE;Lw5XRs+kzROaI!x z-(+;1(3y=toBP@eZDyq3%xZa2XHU6cvdU-Ltjl|3PAsr<&iBgPSs6O_%!tVjlA3+^ zm^}E~J$BwUH97VR&E5K~gxcTL`O7~MX>QYS#(I;tE@UIAjS`JjYILke7G&n?f}RGm zaP+*5bsUn12S+sjK)zYjHO(%rz1KYQT6lF( zmc=*QWkVCDX!>C-I`xjM7&mt1(O2ZLzJqpE^V??i>3SRAw9gbDFS8{p+hxtZ7qm39 z$UI*4gsu&;O`>q2CVpKmWm)N3*7bmtr%Y)1$uxQLa=%u*IxOo4cGwNid@UP4TVtPE zVdUvI^X#T@x7plQqLtG>G*#7Mts4JIwiMl|TfV6?)%Rb~>W>qqCiQ!*Ilfb#{iRd4 zHLaEHJ-zn1iic#!N2hFU{tS8kz%jcs{hHL(?y_~)ew6x>jkf;c8EJ?$X~X9iP2=4g zwDE&cd1zs$WD0ORv>$UYzDF$$aWz6fouq}BgmE@dxC5VvMXM;Grr(iXe5D~P)9k%F9fI2;OxWWMCY^Fk3mq}>2?XkIn| literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Cancun b/lib/pytz/zoneinfo/America/Cancun new file mode 100644 index 0000000000000000000000000000000000000000..de6930cd8ace99cd0a9658951c64b98b71a5f389 GIT binary patch literal 802 zcmchUODIH90EW*n@+`_@!%_(g8HVwG%rFh(eOFdjF_Thrv$8y8vhvs}8!4p-E0K+) zlvs*{jgsZrh>+uacVl588~^Ft?{@2MzRwz+jQPZmrI{~WR$(sh1?zh^cG29WT^)}r z-}IYZ6OO3b;al5ZR;m0a!mjh3JN4-$B2fIK0*{}f;r&cCK6_=;-mz-l`H(G#i>h_$ zUbd|`sy%TeJCX}d=fIi_jwhT@RZ@0^hMex)m<*TJIN{e>5%Cr}k?RT3^YG&I?uSM6 zB470_my73QU+27QV3GXr76Xz<BH zns>I+nKVnO6Sj6Bs%m3(`#Ex2)w|-l{&vhX)~9TvR8e1QL;7oFxA|5~+V3+*)Q`IZ zdSxW3-1Q#4di;=yww=juAD%RA@!@=X?<(Jw8r$m%C=^mbD^f@zq}*iUw0EWN1Egrac(XX1kM%9LD;#H z4WiC1mqRViy{?Ahyaz3Z)0rS8XP4@c(=F%sO)~$mlKikcH!`skSqoVVSq)jvE9)T( zdSyjqNn}lAQDjwQS!7*gVPs`wX=H6=aj&e7Ebo=|kphqkyix*EgI9_`szAy>>hMY- zNF_)qUa18s#w*nzr8uNIuat+> hhZKlZh?I!b=#?UoD!o!BX64uZf1R=Y(rjOM>?dvN>pB1c literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Cayenne b/lib/pytz/zoneinfo/America/Cayenne new file mode 100644 index 0000000000000000000000000000000000000000..ff59657820c61230c738875e72036cd133326f41 GIT binary patch literal 210 zcmWHE%1kq2zyQoZ5fBCe7@KF}7wrkZ51SsS|Ns9#BNNmA|K~n1F#P|2`~U;X|NmDn uFmU+zhA`+Fm;kXcL@PrG3C06W{SUGNM1!mW8Bq^p6KNHf4bWOUV=e$vi8s6e literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Cayman b/lib/pytz/zoneinfo/America/Cayman new file mode 100644 index 0000000000000000000000000000000000000000..55b083463a8b5b19d62b6f2707665c08eca5e65b GIT binary patch literal 194 zcmWHE%1kq2zyQoZ5fBCeCLji}c^ZI3_m{*Mj74l=1wj+F14U?9S&?zpP%=nM8G=GAw+LbysUe+MCSs)FY9gtova(V; zpca}P2#i$7LQ*ouYDzJJtQ}CHS>!6rNNMlZ^KY7Irkk2>x@b9@A2N93#ru4&8F@F3 zlD|BE`x8FA@9c-~gSGuqwlPAled8CkZuua+{;31%x%Ud>`FnmgWfH9bPJLEhaz9~S?p`LZ)Gam@O*!&vS(d4+o+wqtmzvGb%+{~vW~%C? zm+RM)$EhtdN9e6#!_>9}KV8$$qiTm9)U};$YP+AWY~Q_8z4=wAyjAHobq$TOV@18G zpV2IDS0$*OB@fE3^b*rB_cnPa`bM)m?E(Gn;44jIr`kSUj-Likex8~z%A4~JuADB<#wn>Xrn%1B-(%SZ@`P8#TAE;kwK69_qpTGEs za@Q5PTgqHfwjOA6D$tKQ7y#y7SBR(b=Wyr}X9e*!Vp4bM$=O zbK$+_m%*v}ctEZ>-jgdQ4yBmhmK6E5G2D1+!o|BO(8%gQ@hLrG`Yb*oeHRQ=zBwmp zzbW6VeiOR1f6Pb9|DiD5f5>a9f5r1Mz&x%_YFnuXwpN+I`bBzB?PF%}i;u~WH3jD6 z`wQfhq6~9tUWS~O6>ox4;^p*9Ld+Q>LnQdzvFgl#UJ2=QrV9BnSPyMKp@!`}uFrb= za}~PzGd+C$4s~|nU^(aR_3GSdKgfui-ZJOKHOcv@Yt02gTO{nFyG@v9uO2yIjv48$ z))yU4GU0Vk=!m8pRAkv=9aTL^MHgr3n3Wf(*xW)HwJ<=9PR^8zuRW~d!p6y%QSYm< z{=+1G=phr|>5)rL>@nkZx5=dkUNH%ky*hFG!{)LTZaw~KWhUg;>&r_XQdguurzg(M zSCgVkbkd}2R8sdgNsheLBsZ;*l)!Y8QoTe{yJF2%&#cl{H&0e+ON;d6tuZQnX11R4 zEZ1{#v(?O6lRl~)m= zZ|eL~-TY*V-14E<+*%kew^g>A{ER?RD|VR$aYy9#{0(Md&|WD>FEs_8E?pR3t_s~B z>N|p$t2^p8>!P0d>dvy2dPz&FT3WnF->#if2vN%T^CkeSH4LpT2+k9bdmc{pIic zuJCL{OUB9Oq^stQ(cl|KNF|h&lH#4 zHv4@3!1WJy(QDtVzMgf+J|Y{5>?E?4$X+6wiR>n_oydM78;b0xquo+uPaW;1BD;!g zE3&W1#v(h5Y%Q|4$mSxui)=5lzsLq7JB(~Gvd4~glaXC^wA+mAGqTahP9s~5>@~94 z$ZjLsjqEqF;mD37TaN6xquq35*B$M)Bm0hQyrbQDWb2W=M>ZeXePsKQ{YM($Xgh$k z0OW@JxG6$1|c0nT7>k-(KZR`64EB5Pv|s?Z|D@ywu(oukY@4d7Sb-HUr57{ zjyc+vAw6@nP2$t zJ|c}oI*GIr=_S%k9^FLR$)lf0LwR%*X(^AMI@+cpU3Ii=Mf!>~7U?X~TBNr~bCK>M z?d8#5q`^EojI@|XkC7(x=(3}2Gmkzajpos5q}52Tk!B;^M%s<^8)-Pwairx)&mC>k zd34>;ww*`c9c|-zbRKCv(tD)&NcWNUBmGBi0OSrpZUN*TaI`l8au+z-+knS?;An3I z9(MwAEAY4%keh+W-GJNEy(SH n+%L!tga6+#|L%?%V9%T}_S}g`8yz(&DkdT=Ha03YDrUfMOLBGg literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Chihuahua b/lib/pytz/zoneinfo/America/Chihuahua new file mode 100644 index 0000000000000000000000000000000000000000..b2687241cd05b6904a7d95bae697642becbe5b1a GIT binary patch literal 1508 zcmd6mZD`F=9LIl`d1$R1A|Vn=wC*142{UHbX6$Y@cQbd`-OOgpHpaRhcZyat97@y- zCGwEAl$odUN+^UBlUkB3DI$4Ff9Lb}MtP;Y@I9SAr~jM(o6jc@tgeigKQ_>Q;j*B; zyoaCa?5l78P}|d4SrodQ@hH?4l@#jw(fmHHs!`$_2G>u^Dwc$TH}#2==c}anZk;@6 zs!AF2SyFnV)b#JqWX7jqW@hI@N$rRg=ZtYQd`kdSO?g%DFc|{6~^i?u7`+3y)HZT3$^`PS55?+GwN_<2{8FA>u2!yyk%}okwJL3>R1OQ7s<9uX>cd>K`o|Ngel*Ffd2w0R zUL0s@ZavoP4)mGY)0cGJ`n#sS`LJFe>{S~oH|vHOchttr61{2kdDR#bmd0;;Rd`5H z!rgUh^Se~pay4IVeK1b8om^+Ow~Or9nP-~zy%bMBPJeqMJpcAM&e0YT$7wSX;W+0{ z$Y960(&35X?NPtJ=lBESUmTDOuFt;h{e6MIFvhXN8i+X%dmsiuEOKd0;y>8LFbZN7 z!z_qh5W^srK}>ULZG#xcunuA#!#;?C3=1J9GHiqx39%AlCd5vNp%6 z#K_FT`v3nb83u;`|95U+WcmMp^#TSCFq;QV3V=uk5g*?W24@!_4hG_IAPxv&a0RkK vfDuZDkl?KUKv49qB?Ux-oCl&oP6W{)XM$*uQ$aMyxnP?5PUZso!ITRCV)1m0 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Cordoba b/lib/pytz/zoneinfo/America/Cordoba new file mode 100644 index 0000000000000000000000000000000000000000..5df3cf6e6377be897b4d09fe438ec75eb8c15ad1 GIT binary patch literal 1100 zcmc)IKWLLd9Eb5YZK5$03@9if60xNuA{=dF#6RVg3{^2p4Je%yL=XhU!3v_$#YqTA z5QLV-c2zuwYH6cAjWyb#1X2;fA)vS^A~D3OJ%7(RiHlC=<=)Sc>4oo;9XffU$NS^A zLjK|K>zBiQ?PYn5U(c)i7Y6+Y8(!$CO?iK6@r;^YchO8wPUxA|J->A0m3sL4oq2RV zqGxB;`(`MwACHduPj;16`BK$9-PqJ~M}z*{aza)1rc9-NS3SQ{@aHqP^!&%GW+7wM zg8pD?@uXf%A2*FXVbvIGnlGVKs@W6uoA1YTYfaj;DmC@BG3+nZw(D=@r1@Stq<-8U z^p{7H%3ka9S56$z;m*^=Erm(l87&mMQlE9#r*p;b&8t;+^++-9SwF6K78CWF+IsVF zEY*Cbcg!aA!0;+P@Fo}Aw=}NzmyX49*4jW@`(Hkx;IF3*+uc26ZMo`s?j5wYV!W`m zFROtLYv0xbQSM&H!A#am%h{&-+sDF~?uDG6OoT%;(8-=iv|ETk@^Fr+f4l!nyil;V);oKhZAA5tJv dAyOhzqf?4Rszl1foP65^?DhZv literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Costa_Rica b/lib/pytz/zoneinfo/America/Costa_Rica new file mode 100644 index 0000000000000000000000000000000000000000..525a67ea7913b5a9e2507067c164de30259d0fbb GIT binary patch literal 332 zcmWHE%1kq2zyK^j5fBCeE+7W61sj0G;um7Rf@Yoxg4^=~gvGQIgr^4ts84^8ppm!j zf@b-l1kFP?FEBDQLE-=Z8;uzlz$DB6|2sD@a{m9ndI1Bkk8cP=uosYUb^+qx5TL;j z5JG};{sTc~yOuAA200640?27#8t6O_4RRuw209Z&gPaPYLCyuyASZ)q%ACyw^n)1} E04OF+k^lez literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Creston b/lib/pytz/zoneinfo/America/Creston new file mode 100644 index 0000000000000000000000000000000000000000..0fba741732f73cf241e702d41f4ccb9be60124d3 GIT binary patch literal 224 zcmWHE%1kq2zyK^j5fBCeW*`Q!c^ZJk>}%cy^L|=0FfuXz|3B#n1H=FSb0;vc{QuwI sz`y}v`}l@1_y&hC1OPD%gpgp(e;|mnE!YF1LDqq2GOXkRy1|?a0DglyQ2+n{ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Cuiaba b/lib/pytz/zoneinfo/America/Cuiaba new file mode 100644 index 0000000000000000000000000000000000000000..8a4ee7d08fcae58764f1b1c4e8ce19f548c4734d GIT binary patch literal 1974 zcmc)KZ%mbC7{~EPD&{C_E;nO^VayDL<8?TsM>2LPQ#3swc|t-hg(;CFN{k2!LDy*W zg-IYjjh-c5!BQDy?fb#b?_${jT_X6y9o-C1V5Bu~dr{~!}7!*=3$vY8y2q1X5A zGig0XbXMaf^I&IzO^*zi*=;ZBLwEjYGRkJzjEgNYCmPr}=lW#s>@=NwYSd%~3C-*{ zB=dg1-_GBzCd+=M+1tLCQ0M!)V8v%5t?e34+hB6mhioLdQF5adnmgVr3)8c8VQ+(3 zGUmVc#*GFW-;10X-xo>6D7i;Y^D~&w+ZjRj? z{>E%+FV>3bADhamuvSi-k*$UI>elb-Ox43zwCdBisZRb$s}Jvz=P!5b_NH}G)7xiX zD1TITd~(9p=FOCy`w!V&sn?{gw%OKQ`$g)DH`)60r=%g;qzzx5H;wmg)W(m-8Au&SA^e|If|P>Pf)s;PgOr2R<8=ig6?t7rNKIZ>6jGJfm4(!W z6oyoWl!nxX6o*uYl!w%Z6o^#lbtNJ-dR>u7l}MRLok*cbrAVnrtw^y*wMe;0ywzo?vLar$B*>b0-J&3?;&sb{tc%wz46-uF(jaStEDo|d z$nqfTgDeoTLSDB-$QpUwA|b2fb<2dTlh-X2vQo%WA!~&!7P4B%av|%5t@Ho6|F*Wk m-pVcGZ~vb?OIhN!EkDw{wYAPo(8`Us^A=q^*3WMtIuIsLqRoy+m zpc8pdC&wpsPkK!E##6d46w|4|grwd_rT?*41}YI5yljx6(+^1>JWFQ#PKGzGWMr)@ z?&6WSPcyFLuk-!-1dMqtDP>IMM)|+b>Vwm045$4Ur9%0Fr!sEN?yQ=!ccmPM51if~ zsu;T{!=esS2&sgWLTc5t#gJ-9Iiwy^5UGfiL~0^Mk*Y{pq%Kkzsf?6HY9qyK+UiJo eq&_kMWD3Y6kZB+jL8gLCrsls)Cuqmz2EPDo&71K6 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Dawson b/lib/pytz/zoneinfo/America/Dawson new file mode 100644 index 0000000000000000000000000000000000000000..db9ceadd963f3ad7cd3664d821e2c5cd9676469f GIT binary patch literal 2084 zcmd_qUrg0y9LMnk{y|B$XG;|6V2@%U2!}rc9>xAC?uc=O;&}3ynHqlv{(*#!T7tN- zS~=xgPHn4BkIb9anmDw%S9I8`y(*$-EMBB$=9cS>xpgvYYKG2CE|bhVrRJgQGsQnN z!(@H&r_4S%W*$CvRdjrTp0n?BnHx>h*)89zd1bfsBdb1BkEUPNIe7_{Gd^l^Q@d5} z$TueM=gn%t;7OA|+$9BxLnbh=Q3|_WF-3=#NpaIVx}<%cEDUw%(z?k~mJ`+GB|pic zl%Nj!C#m4gOuhK-O||5!d%EK4*J^2hhI#Dl$LjIkzswWw9Z*lU{$QT!`AC-444UQj z`=zqbm=%@VWW{g8I#lqM3JssuRcTvQ)xc4`YIL=F`uzb@{cVec_Z&8>KdqLU^*c@N zk$^n2G-jTSE2;BWn8=zt61kUUqGeYkdSi)RJNvd;`=wvU#)s9q0+a4PRWwyPga_i zA3fP}q(N`JbBl%>gDnW^p4i^sy%&FchnqL9o|$E51y0w$S-DB zMxX2&yl8fh^~kG<6Xvz=8f8z{+oto(O6l6rZuY*LC&@|no-i?aQu5@K|KRq#&T5~} z^Lo3JCVJk!Hs2KYKFvLq=Y1ea(|PyJa_?Ey;fOC*X-}O!&)HMUZ~LLveyFu_CAsH) zBmZItp1nF>KX?k+1F{KZ7sxh{eIOfg+MOUWknJG*K{kZ!2-y;{CuCDj zyDMZ{$i9$`Av;61hU^X59I`uPd&vHf4I(>4w&=8bL^kQPyF|8$>=W52vQuQM$X=1n zBD+Pli|iNKFtTH0%gCOQO*`$bk!>UUMmCP@9N9XucVzR(?vd>y`$rmpbO33A)Aj&q zg41>ZX@k@D0cix%38WQBFOX&+-9Xxb^aE)K(h;O3PTLctDNfrJq%BU{7o;&rXOPw) zy+N9TbO&h<(jTNjNQaOXAw5Ex@xVSh_ I94Jct8Dumjbn)ly@Uf1E)E1<+6Y}RjA#>RkYqK{Wudrez*P!CtxCusH+fnF zZM1`J3L=RJ1SxD2L2a6-oG#OJvti0ovoy2$dY_MWt=#nu=ll+{{XfaWU8xP`pKpc! z!{KYv!+TAyUPGg|{iU&ld(gkur`OGOr#oxp$^IGp)E4B~=EruzJd{l0md)g@%k$zR z|6<(9%j`}6>gqL@y>r38?my>pL&yD_u5S1C}pOU4CBlh?G7AcN4+wjVIF&FD?aJEunCn{`7 zPo0Z**80+5+QoucNnEdG|4PGfpv9Ol9CSfk-@T?RdRIobaGR=Ktx40iL(e`v2lTWh zO{>27g1>ii8LNc)wQoDN1z87K2w4ePDpJ=%7DHA;mP6J<7L3#tktLBekwuYJk!6u} zk%f_!k)@Hfk;ReKk>!!~kpd#M0;GgUtpO{*jWAtB*Xe_ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Denver b/lib/pytz/zoneinfo/America/Denver new file mode 100644 index 0000000000000000000000000000000000000000..5fbe26b1d93d1acb2561c390c1e097d07f1a262e GIT binary patch literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8vb6-0*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC}}?Nyq3Ob5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU

gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vMqPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>56p4ZvA`Q4n) zJKPlARO$WWNw&XmczW&Odv?!9d29Ap@Ab|;XXIl3?{ZO1=&$?(=8|_nC)Zq-l=4$5 z<@xDyO~xVR^A3v7JgZW5kEDJ5s!l%#1)%V>0${(wV(2=DN=N^!3qznYOw} zX9Ww*4fE6VjfJTuJFieppE711GMw(5e(kEPJps0&A4lcJyI>Dfa&rFb~3O8TQx zdUUQT>sl=3d$LtUBw{MJ{Hf*yg659p-zk5=Y%{lVNX<)0H&rvg(N&|rn)wqS>IG*m zm^;7i*VTi+$Xy>irSIPTx!m*8MqSf>L>6}MQ1>?MmD=VFs;(?1^>wwXetf_LO4jSZ z@GcWfU#Npe+svY|e7*SPURm;GjS6jVm8I|HsfM*7S=N`N?yoMB<&TZ36*-v_Zv0e* zC&p!^|4p^>$Ejvj?is!6^cAyuazHm78a8W2cIma<$IOF6ZF*hvKC`}msaBzPWy8)^ zwXvj69*Trib9#rg1jZgW?qm8qc21`@XqBSD6Oj0G8t(~bri4l*8I#ek3zIqi^; zF(HF;+EF3HLdJy*3>g_RG-PbZ;E>TF!$Zc03=kP1GDKvI$RLqXI_)r#aUugnMv4p- z87neaWVFa|k?|q}Mn;Sb85y(F4jLJ?(+(RMH!^T!=Mvn|189x#LBmzhX zkQg9AK%(HZVL;-51OkZ!5(*?1NHCCSAmKpbfdmAJ2oe$`CQcg^Bq~lD79=iC8yF-q zNNAAQAi+VRgM}yA)!KIg#-(U77{KbUP!=@ zh#?_EVul0_iJH@f4T+o61`dgw(}oU-9TGewdPw+?_#pvAB8Y?#i6Ih1B#KTOMkJ0- y8%QLQP8&)jmPjy>Xd>Z6;)w(l|CbT<*~0p5S&Kt+N-Imti$fI^r4^;+zP|v~6Kewi literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Dominica b/lib/pytz/zoneinfo/America/Dominica new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Edmonton b/lib/pytz/zoneinfo/America/Edmonton new file mode 100644 index 0000000000000000000000000000000000000000..3fa0579891a9762b7c131ec5ece5d6d02495bfc0 GIT binary patch literal 2388 zcmdtiZ%oxy9LMnkp}>tlrxHU!BP|I6Mi>RMixs4Hr-gV79MKa` zWy~@ORD#wDQ`cy0pgG+7qjT5gvJx#Ton@mxTP*C}&ig!K>rs!|`riHS>v!St=j~eM zUXw2VansCSc(~Wi!~2XE#!j5?8XVAX4h5_3oiFKb?>4pP#Y=i`+jOz7;S=4Pc~res zc2V|4^{W1ik7d8_Bk^fRnD);9y~$e>Ej};5AWz4AE&iN%MowO;6u!Z1>F@KdQO! zU)DGE99MCkIr8SM18QEmU(Rp%Ox%+Bjl6Z)dtyP4dFNL{V#$7ozH4Z=Si1cuefO>{BDe8`zNc-My0>`0zOQz(%3Jud z&d*z|@_!qZ18x9bN#SgMMv+`6PQPCc}w zNSAs7RatDZc9nmpTvsD?MdmS8@qLo4oO?l3jz-9pzEQDi-?)5utWQ+6dF3O+9iqDS zkX+rhRy^uFscYKX)nmyA^yBqzRU5uT*A*10x+@-CAD^u1k5_7UaHMj-o1+_k_(iSl zTp^!086lqZWXq=p#zkXAjBMKO6;EgWCD%0`66>SR$qmJwVuNo|d$JBF&)8YLF?xsE zI6R^^O?cF^T|N4_FDg}YORL^In4?;%>-3hLu_`cN%IBJ(DLpT6eGjwWa=FtboO$LcGtUb1l(@`ngb1)-u79yKzd6>1EDl*6vOKFsS#2nq)JGc zkUAlSvYJXErQ)a+QY?;YA?4zz7g8{eiXkOKYK9aIsTxu?tEn4OIIF1~QaY=t9a21w z>LKMr>W35%sUT89q=rZlkt!l(w3<31g|wPVBBivNS|Y{ds3uZQj(Q>m<)|o9QjVG; zMMbKLlohEfQdq00EK*vlsV!1mtEnzhUZlQAfjKISl$fK&NRc_JjFg$9&PbuHrqW2M zt)|vUv8|@sNV$=EBLzn)j+7j!IsSi(?l7TWY=WQU%t%R3NlkL5rKO~$q&ofvy_=8y literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Eirunepe b/lib/pytz/zoneinfo/America/Eirunepe new file mode 100644 index 0000000000000000000000000000000000000000..99b7d06ea467e79107b8abf448cbff3193cb6c80 GIT binary patch literal 676 zcmbW!y-Ncz7=ZEgY(c8%B7%rtoh|rP5E04PK{1t^#KEcHA0UX!)j<$koJ4VPwUd*y z4w9|tbgreX72GU>_*Gg<%yZ}@6f{Tpg&U5`^SY}W=_&DRMeG|kD`q$6?!)X}CfA!5 zxz?J_KPz=vIgrKK8&$0JtI|NrFXcC7xjCsSCk=U5zED+nS-u_(`frhK`K~kSqi0Un zQ%O}X+{;Gv$#0zX>1HjaKKGCH&~;V~Z@uV|)096t{iw%wc2%tFL67TO6;D@Xa@|ua zURZm^7$?;B>2Mrlt};RxQ_!KXG5K@R$yt1#7nfWSpO~>bVXyVG+&`4CKVhhW6bZCd zkTOUeq)?!(gp>-jwUA;+HKZI;4=IRLL`otxk)naNDpEGk)(fsy6^|J4f^IDC9V7@S>zI5-5T69hs?u;@P!fGh*)16c^7L6(AOki{UHB+I#g I?l9v508!&K$p8QV literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Ensenada b/lib/pytz/zoneinfo/America/Ensenada new file mode 100644 index 0000000000000000000000000000000000000000..ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3 GIT binary patch literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;Nhr-n$dtfe5^u0@fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Fort_Nelson b/lib/pytz/zoneinfo/America/Fort_Nelson new file mode 100644 index 0000000000000000000000000000000000000000..5a0b7f1ca032be1adf8ba91f863e727c5ec6a026 GIT binary patch literal 2240 zcmdtiUrg0y9LMnk^7jb&%{)qk^`s_(DTW{@nyEO#KtK)_ty#9B;c99Y*4Bs?t>_;Q zI{UMVzS&KzCNgHTkqmEKt*i;lM2yHv7)mNy0ura^{abfkb=P{%p6B)3x&OR__f}S< z`~GpF+&^4Sy}NuT)VQbd;30j#EnvT@OVrNUm$!9po-5y#T{OqdpnRX%Wls3MmhQj- z)7`gEPEH)to(?OgdRz5}rcZ2d`yTzV?sePOxKn?s+-6T#m+Q~@8|*Kea`e}f40|T; z9@9UyL7wna^S{<3US4O8)=vYjxjEy{Rt`1bl_=$(jpI_W569@B5=%ZBe zy_I6ZUW$|OrzV?8+vnMc&B+>B;zp&pE^`CNOmb?Y zBu@-!ioZrudcW1w!3Sl2dyC%MRc#kE?$(8^57@NoCw0;8)%LbWcA4}YbL`^0Crn0Z zl+8@uXqKc8*sSPmlbsYP+5L%T>D7K&c4XY-^n5AH_b2FzwvXlZ`Y~Pk&TDeV)>FEw zw#lw8YS%Rny<&6IRM+M{X4hWoGIG0OWO=!6s1jS6l%v72VH+Huso`PalOo*n-}ps_La&bce4)^LHY_3( zs;}|Ic;9i}E4;pG1%*Lhajv_i?%wTganM)jzByrkzrlYopO8D7R#d%+%m|qhGACqG zo^Dpiw2*lr6GLW(OwH5H4VfG=J7jvu{E!JEGeo9{%n_L+GD~Ed$UKpWA~Qv%>gnc+ zOxDxQ7MU(GUu43_jFBlLb4DhO%o>?CGH+z!$jp(cBXdV4kIdfFO&^&*k^m$F=%s)U za=>v(0J4Cj0m%cB2qY6oDv(?t$w0D!q{GwY14#&y5hNu@PLQM^SwYf*-qmpuiv#e G%l{V~?gk?O literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Fort_Wayne b/lib/pytz/zoneinfo/America/Fort_Wayne new file mode 100644 index 0000000000000000000000000000000000000000..09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4 GIT binary patch literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3Rcfep0Nn>CzSfnwSBt{aJGm1g( z*qSV~CL%=QQNiBjO)E$tRn`L$Mludk@~lMNYMzET}(z!mnX>Re68uKKL%o@{oz9~VU^ zQgA~T84>wBcB82^5xakqvBIbr%xX8j(5RAHYrn6ek6-JXXUo$1DetS+`tr=G8yk15 zJ{dOG=8es9{?Wz!wWbZy$I~=IIw7r)UY@2I(#_MfL;4{Nk&Z}9q$ko8>58;P`g)qi zNM}#e8tIKR=RN6;v`6|Q8z4I%TOfOQnoW>hJk2)9KFCJMPM&5fWG`ehOqyq@Xf>a| NqTK>92NRj5P=m+f{;YDv|wwq zt@m%>3RQdyXvm>JA0njZ|CORoX73;G9l>KjLYlrMUI($NLKZywc2WBZGAyeTAc`m0Wt zQHd`e(JOKeOTxSXy)tRH_YT}oMXpi?gztq z)BZPQUfV^R{_IYfU;T|;(7fN=96VxgS@oQ`HRV;Cv8cghOns=C{!)`UwnrEJoM~eQ@L_eggC1`P}>l*Qf4HRol&BWc9LiUDI}2 zs?uULTzyo+mnL*=aG$K5h_N+u2TjdqXKiF^uUUWKxZN<;Wj5|OXlsY+OkI7iy}!TM zXvJIlzzenVV0Mo_)L10-iOt$jnl25K<=U7LD~(?Uv?+c zW9>8T5=`ts;9x zHjC^Q*)FnQWW&ggku4*8MmCM?8rim|+c&atPq%Yq>&V`b%_F-jcsYq!&mtkZvIDK>C3+1nCIU5~L?cQ;@D8ZSiz{K^o)fI)k*v z)Aa^v4$>W@JxG6$1|c0nT7>inX%f;Uq)nc#Pe`LYU8j&%dAeR9%|g0`vkw>1`{M-4z&U$vX@B8DMxwydN_~QvO|KY<^ zYCe2#w`XIaqm#E{VrS2H4T*ZIT{=C|(7;<7`th80Z9cBu?jF$Ymv(5_nX?jpv`!-S z?w1~wDv=eNq-Rly^qRdudT0A2DkVeuIFlthJVyGq>nbrp=^FESpvFFr)_zT0wEvYh zI-vfmI%{5QT=fHu-*Q6}R-aK<{xMC=y)W*Pdhw(-$iT@vB`IQ`B)iKbxy3Jo!>V=g z<9RaVN2v}yn=Zp1=4eW7o~AYo)wHc6b@-le9Z?XaBex8ZQJF!So*yQoqhD%9dW&QR zUDvFrCzAF4g^UTjAY&S@$=K#YGOq53WZyim-l|F&fApYEC@z+Xm78_a^zAyiXrWFS zRHVM_ES(xUUZ*+x>9j8{%?ZhroO>O0#^+d>dFqqSY6_Ow2RcY@{X3cC|0sFYjWRc{ zN#?D8qw|NKmIb*tH9vBBBE7v8DeY*7UEG@daQkHE? z)#BPIvb-=-S8Pp^m6KAWq##sQCH9cj8Q)}02Zxj)I2w zvaYc~D|TPh^>zDnLwSw*tNiM>EGwXOtH6K$*UGYPZ*({;tLcuT_3wA{(}1>?#XH;U zbHuqk=HoV}7ZC94<@<|kH9ySaVtKe)9q&TEHq&%cPq(G!Xq(r1fq)1y+B~qrXsS_y_sT3&{ zsTC;}sTL_0sTV02sTe64sTnES)>Ms@ZENaA3P&nON=Ir(ibtwP%17!)764fRWC@Tp zKo$X61!Ng)%{m|pfvg0w6v$d2i-D{LvK+{IAPa)52(l!|njnjUtO~L$wq{+Bg|Rg& zgDefQHpt>2tAi{LvOdTH+5T566r5s~Da~Wv?lh;@6Q30CN{Dkiy@{@0UlW6W0s=0- ADF6Tf literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Goose_Bay b/lib/pytz/zoneinfo/America/Goose_Bay new file mode 100644 index 0000000000000000000000000000000000000000..a3f299079aebb8524bf77e7f92e0a7e6d0a7b6fb GIT binary patch literal 3210 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 Hmp}Fofim_s literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Hermosillo b/lib/pytz/zoneinfo/America/Hermosillo new file mode 100644 index 0000000000000000000000000000000000000000..26c269d9674847059472850a5287339df9213bff GIT binary patch literal 440 zcmWHE%1kq2zyNGO5fBCeejo<1MV4-RQLz3~-h*vvVF}xvFD2~YF-zF-x9!8}SA_{T zCT{t_z);X&nBF2_R4mqD?4BlI67-|NRNX_s%;;7FBNG@hGqQl;|Nql}Ffjc8KX(EH z%m4qY7cg-A|KH!hzyo443P8ktd_x#~gF_gcfj9t&eO*F8&Vhpv5}f`Y2tdvU(I78? zXplERG{`F;8sr@?4fGO-26+oaL%asEwx9t-g1iW#LEZ$>Ag_XGkas~e$jcy_M&9NE IhKV^B0F_3Lv;Y7A literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Indiana/Indianapolis b/lib/pytz/zoneinfo/America/Indiana/Indianapolis new file mode 100644 index 0000000000000000000000000000000000000000..09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4 GIT binary patch literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3R-DV@{%eY?zBUH7p6`TTcudiDF_T~hY^ zO!>=&*l&2aJ@(-}THBBMeTjPSC%>tNnz6cZ&jt1M>pp#U+K@V15^B!potKU&r_Fb% zN2ODmO;=Q%boIPtzV{v07f!4<7rS@qOZ(qc-K|ynhpp>WPko{8E%U0r>I{9^GfVwg z9H*}oq?`WCbops^thpK=D_3t$G}r9^eyx4j{M_FszjU;jfiK$R`te>h*xaMd-c#zv zwv&2jX|1|7Tq?J(ddx_tM}Ge@!T4Gd#Q%PTk=+pzP&;Twy*wy^Yr|Dg$rv4+dtKf2 z#DES-{ziqo5wAldKT@Fw-jy)(wi?s3Lx*=AG!Z8%^w?wD&A9#BC9hD=-asd+HX%B)Qtlyz&~GwY+;r97wBl=}vBWm=P}>^`G6MAxVdt%r2g@Jh9@ zeuv)FnWZ*YSLjz-5><6^fqr%OST!oZ{saa&c)jya@ZWrY=fCZ~4gQBe`&a*(-~ZuP zB7Xm|g8@N){|5~++P#On&qzLH!k^#I%l68XbL_LwJ_Yv4^~zlP&IPzn@cxJO`Rx@4 z`WlcGB1=Tph%6FWC9+JXT_>_oWTnVbk+mX=b=uV;%SG0UEEriavSeh<$fA)|Bg;nC zjVv5lIkI$Q?a1PtcJ;{eop$|50gwtHB|vI`6alFMQU;_BNFk6)Af-TRfvy<5Pz}zO zgQFfuK{zUclmw{>QWT^rPFohFE>2q*j>;gVL282(2dNHH9*+7T1>&d>QX-BTAw}Y- z5>h6PIw6JPsFc%|3aJ%RETmdUxsZAx1>>j~QZkO3Aw}b;8d5fnx;bs(kjf#YLu%)= z#p9@+)0U5;eok9JjtU|rL~4i>5vd|lMx>5NA(2WVr9^7!w8ccK>9pnKsHf8wl%t|Z zNjYkY6qTc@NLe}RiWC;9EK*vewn%Z2>N;(Ck@`AqfsqP3ZHbW@BSq$@GE!!aIwOVV zs5DY)j#?wd=BT#QmK&+J(-s`5xYL##sX0<~r0Pi7k-8&=$NyL5!|X4CS@xGfV)kQ6 RGn0}Nvr|%%Qj(Ix{s3RXO|k$0 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Indiana/Marengo b/lib/pytz/zoneinfo/America/Indiana/Marengo new file mode 100644 index 0000000000000000000000000000000000000000..1abf75e7e864625b975feebdd0b232d0de624b27 GIT binary patch literal 1722 zcmdVaUucbC0LSt7*lf0p5t>?4b|E$U$0SXWtu-8mgBj)-o6~G~Ff;$=T==^ZN}Hsa z$lo;oLt03qB-tYQvpvQ}7|+ZF$$35Br*PrQrT6W9KX2#aT>ZX}FRyq>s`J+sZhqn6 z@|%b6*noM}9!m%uy7o=hZR-;_eBhb9w<8#6ivJ>;3L^CLmYTqelY3-a<+#AB?9uXd z{*XZX@EF;VmF~RhKT5wH7U#VEJV?JY|Mu?TSN*=D&G~TdsqSpN?R?yOU4N=qf#8)` z?H+fPQxnvl?Jrf2wMvJ`pa>N|WX~KW!p67C@Z?(}eAi$Z5q(}poY|)%^)``_R4y|! zCW_4N6FO_eLY38ArL&_ZsO$@+dQxY+ntX7lobq_Q@NO)TQ!ft{)8>0+PIai5o}MIU ztmzOlWBWVZy<5&sJ0)hf_tm*^jVkwcm!2Cuq4JJ4>v=6zYW|i>dO<^}$}gzmbzSwSs#Us5a6lwP%>My(!rOP5Vsr^?O(rKw?4{o zT=(i(PpIpju5)_X@80$u&D$B^x_54PVy1X~&cqD!%rws&^W^xPO!J*-e&h1kH~9Wx zg08vpLxOe46p=Y1lSF2TOcR->)l3wbDKb@LuE=DO*&@?L=8H@inX%PO8JV-yOd6Rr zGHqnu$i$JEqn$b*%$>_j9+*8cePsSf0!Ri(3P=uClLV55)ue&sfh2-tVl}BCxmZmy zNH$iJ4w4U&5Rws+5|R^=6p|H^7Lpf|7?PRQq=w{XHOV2_SxtIKen^5yhDeG?j!2S7 zmPndNo=Bodrbwztu2z#QlC9OGi{xuH2_qRJDI+-}Nh4V!X(M?fi6faKsUx{tP4Y__Ap*4#5^wY1K!@B6uk#4G8p=kPnP-!^vl zyg#Ytwufh%t4^N&hKJLo5AVf+e)Ydz7VP}+4;k1rHF)*q@8q-RGQp`C7v)rIzWXtK zdw6zckqkA|nxC$}#SNF1nBfaIxpVs8=T1(zpND=hzr6je8##W=oPY5nH@Ytj+|EyA zY|GK$!p7HRymNPOaaphY+PEqB?T$A2y>eA>X>!_Knn;_=!wL82>4f>~#4MLNILkzP z;?C@dn^^CtoAkt}$y!q=*{xH8oTl@VJ9i|=tNK#%UMLOnr@bTjdv}=vw@s#mTZ6)H z_Ph9zwZYUwFS)wPZmF+ZAob%Pn1=Gzu3>PqOT_OC6YqAoyGLe(_q_7F%=>z-O|Ea3 z`S0Is8aF?g(2}YuOvV zc4fYo?pyn8_nD8Sr>MvF9Ns1CCYtS&{m;r%r)%x{4QYA$V2Rz(l8}u%jGYwIPgb^v z*MEOcD&jT(h$-S(h~Zf zc%dndcEzDBq%RJQA)O(uA-y5Zd9^#FJ+Jo1p+TfWq(!7hq)DVpq)o5($)QoNc8av> z)n1Wik#3Q8k$#bek&cm;k)DyJk*<-pz1la@xK}$zTK8)2Nb^YdNc%|t$Oa%gfNTM> z2goKMyMSy1vJc2ccy%X`t?=qzAe-UU-9WYj*$-qxkR3s`1lbd0Q;=OjwguT2WMjO# zGsxC>b#IW(@#^j%+k@;6vO&lWAzOs(5&oYxsX(WyMyIMQIj3TFMO9g{y1JseqN?C; Daf7s4 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Indiana/Tell_City b/lib/pytz/zoneinfo/America/Indiana/Tell_City new file mode 100644 index 0000000000000000000000000000000000000000..4ce95c152a3d8974fdc67072f944eae97b5997ec GIT binary patch literal 1726 zcmdtiOGs5g7{Kwly_JlJ3Kyb_782b|Ev1D*kRn=Fd)(C2?4_xdIhIO3vRQ<-a}n5s zxEYucQ5Jz&3k$PaL_{SL1#;E0(9B5<4C0>VKM;hi-1c40`Hcsc-+#P0C5K|gKNDoW z@Gynu;T)#S%h%>sS04V9Ee%W5)k`1bi?adsX4RB@vp-0`t(+9Sn?|+Ym#YR!ymGKU zQ4OWW%a28g{!gnz<#0-Z_`Ga`{t_83zD%stBMXHXc|EO1hX=*iyYKbbvmWv7(SRIp z_bXlQ)8CuA)x_y*a)zKQbCrpfgN6*B!q&AJDGG0e{lEDw&+LT_#W77b$B? zb;{d|A~hsKr{1d-X=5>Z%jNTGYhSwb)K#nWj%b;Ym#ellFO%C764j2%DY-K&LV0)f zN$)S0%8cujnV-T%c6hJOek#Q7K!?t09Td6!7j)j49d&sf8*cJNs|7`Vm#pWdQ>@-i7dA4~)vNn{~WxtSVYH6{@g$dvXID9 zB8!PEC$gZ(l3LB8BFl;_EV8u7;v&n7EHJXf$RZ=lj4U*=)K;_D$Z}iFf+I_gEIP96 z$igE_k1Rg2{73*u1V{)-3}}PkKopD#1Be3&1c}6ILScx-YJy>i#%jW0hzAJ>i3kY^ zi3te`i3$k|i3}ytzx`wALV<_HLms;5`X_`4149?FT>s>1wwaElEvDsEwykBhhsyHO zZH)*sijpD{%CHVn5fwxPN)ZG$1$h%j>`+Aen(tgY1znoQywBsEzrRqpYk#rx$603n za5?qn^6iV8*XYf>_|?ZhMdHab20;rYdKLbyen8Rdv5dZ(GP!+pk=Z)$hwh zaG+b(JX|koTY|DK;T83z1#-u+dC}m@lA*>|BJ^`fHkI5KO$ z8?$=%+^}jnGpYAX_o&v65#2UgqeSq5Y#-_td-D@=UnD3xGGnr{vOsh$gk@wymWaG5 zm0jseqU&ysJTU)GbSF0Jp0|tYVAQ7%-J4atjY%ypPO8HtN+0PRQAgK3(|xu5sxSFT zMmHrybndB)WyeHp`nv4@9u_A?&&rb@O2xonM4p<;5reH&^7JJ~qP<;Y|3{DO zPLw&0>pq(BcwP6-n6un|ue9$qyq&eK`|^n=yE+(h$}7xmFn6c9bs;BUz60hT$7A0R z`im%Bb6QiAV@MoGAV?%gC`c?wFjf-{5)RsU_#hyTi3kV@i3te`i3$k|i3v*sK{UBFU

CW46GxWPD@y@$16ZL!ZukRmz*B|;`IUg@P*ZrNholn<$ zcAo9eWqhYvd3!*wikw#Y$Gdbv_kLBlyG^g|C|5=6oAsK|LM8l{ zWN~AeSUasrt_%9b`cbvABs)oz^ykaqlo%1bpDs&>ghgpIr769+vRQ4Ja7$O`ZBx~em$GJ7lc?#wDQjbEMQz71x$S$t*dE#= zcRWiMJL`gSS9`puE6S1e2OTlUXRfGd3$MRlgNK;Q{AlI5TWhJ2!l>x&DANFVoyh&Ar0hivmuTdCxMxaeVd(fxj4@XHHR6qy-ru zGD2jC$QY49BBQjLVIt#128xUn87eYXWU$C+k>MiaMFxzF*lLE1jM-`ijf@%@HZpEx z;K<04p(A5Q29Jy$89p+8v;pu!1RN6r5Cakf5{1=-!66Q-2?UA6YC=I`L4rY|LBc`e zK>|V|LPA1fLV`k~vYN1vxU42HBr>ZB4T%j24v7v44~Y*65Qz{85s48A5{VKC6N%Gm z0!1RVnoyBgttMC`S|nT~UL;^7VkBfFW+Z4NY9wqVZmS6#iQH;JM`E{{;F0K&@R9hD n4*>EJKt2TcUw;fnm~TXy`9>u8rzfT+CMWn)QW8@VlSljl+LDCq literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Indiana/Winamac b/lib/pytz/zoneinfo/America/Indiana/Winamac new file mode 100644 index 0000000000000000000000000000000000000000..630935c1e1a8c1a87e728fc1dcc4c930e81b30e9 GIT binary patch literal 1778 zcmdtiNo-9~7{KxKsHrhEK`fd_SlCpnq=-BT(WWV$@zqe&F*eoW&>^j%?!vsmrbVPG z#28AbBGMp&L`0~X)iIT*a;g?Y@~-ngTQ)46n|ppYZ}k@6|BGbhE*&au>T$ND5X|B z7S-lS?>*v)-esOfYrJPy3e5Ay3h%|SovN{})O)#YwbGSyyjQsq^}1}d_aK4KlBMl*zx_s+SkXn-$gddSzzRs2w$WRf;yNS61kP-q%dwh7N0qL=UAyb|a;F&q)&Qtdn4zBBDRB_h)7 zcbff;6L2>~{$ebBd$QX{tB~jVFT6%uPV ZON!kr3E^RhLlcK2gp-pKlM{!;{sIlvy2bzi literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Indianapolis b/lib/pytz/zoneinfo/America/Indianapolis new file mode 100644 index 0000000000000000000000000000000000000000..09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4 GIT binary patch literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3RxtNr@M&Gc~^E)*%@{&bQG;3LJ%3`l9*YZ@K ztJpnPm&Lca<;{~?8Gg{+UGaskm|5bg3a(1kPdB>iYoANanP1&KU!Ibc$3nL1)FD|t z_=~MMxL?+GePZ|a9n||8kJ*qIJKIxp<^U;^&XJ`n-0jKQZ9eU0N#* zuf1gtecGmpo_<^Z{%UP(OIy=$kv_b#!J0EUx}h*)$&KG@@{ifpQh7#OekgUV3%`@r zu~}}@)R=63^Rjz%;)t~Mjl0K22c*5@q}wvGLmp3@vnTrdb!+6fJ()^tN8XTi#v8Qr zQpQr@h^EfAT6*SeO}|rP+kd>GJBA-|PoJA5&t$6IvqvvWS4)B0IXEuQRsQLAb)A&m z`4`-t#$nkrb((cg@?fJ=!zA$pw_I}o?J-r8P-v>3?+umvWUoF&} zX*t(T{|`PqGLdZd?boMG&t?Zzab0V>rG}YE7rD;P_5`6vdzc2YO9&pwl zj~IE#$YbVs(8!}k9yZ70MjklFBS#)O^4O6FALt)F^6-(zj|6~3fP{d=fCPa=frP;k z2NDQJBuFTMJ{BaHKpzbf4o5slKuAPLNJvabP)JlrSV&w*U`S+0Xh>{GaDhHLBs`Az zkN`O%L_*|<5ebqbN+e7qP9#tyQY6$sA1e}UppO;_H_*q61k4dJ5;77q5;PJu5;hVy z5;zh$5_+JI9SJ_rM~{Rb=;KEQfMW!ZA>bGTWDqz;0T~94aXCZ3euR)A pLdFOgBxID3VM4|U|F;7b@|(57Z&qnyS+pV=D@~M_N6VwJ&|mGSy~+Rp literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Iqaluit b/lib/pytz/zoneinfo/America/Iqaluit new file mode 100644 index 0000000000000000000000000000000000000000..c8138bdbb3cf141ad42ace037e144bee98d81481 GIT binary patch literal 2032 zcmd_qZ)jC@9LMo<*HXDUV$^EtT)9>2&h6g2+0|*wv~66jcHPoD(`EYAW|!+$`=xf- z%BF!K`9#E+K{1m4AVthS8AUMi4;ErM9Y$2FUqO2iEq=BjX>d=!_b>EGPuAnkIOlaZ zJ8VzR`;%(RJXP!c>j>H>oE$y&h&RaBb-Yh`*H|DwF2*N!8MqFOSwo)T-x3WpzoRO0|9=sfls5 zCib4J`S}*Jw)C7{d-keXmph`{-X1oOkL=Uy_nb6O40h@b?T5_9{*_viAF54zJ7jZp zpL#N#l=gydwIv>rEn{seT~R3MFY8ri%D7}cC{-z zu0_88{+r%RejAu`{W)(|N26X-OUi3nZO_I=F7|vZO0-Ux2qy(e}{tHDQRUl;`bs&Wxl^~@cwK#1tNHs_~NIghFNJU6VNKHslNL5H# zNL@%_NM%l28d96n7Kc=al!w%Z6o^!al!(-b6p2)cl!?@d6pB=el#0~qw8e5zEmAI0 zFH$g4F;X&8Gg35CHBvTGH&QrKxzm=8)b6y!Bh@=?`AGf90w61ZECI3x$RZ%CfGh*D z4#+|vE8(x3*6vQo%WA#3Hdi-oL~(=HdXUQWAU$ciCL nhO8N~XvnG|%ZC52bqm@pjM*8iO3n}0hND%(@dH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Jamaica b/lib/pytz/zoneinfo/America/Jamaica new file mode 100644 index 0000000000000000000000000000000000000000..162306f88a80d69cfa5053103c285e2e8b221c3c GIT binary patch literal 498 zcmb`Dy-vbV7(g$T1ZXQ1e^3~8DkF&lJ4B5z(M6{&V!Cx@F(&>H1}9lu4U2;b0|RVq zK7-qR0h9X#UcUpQE+&4>cTdydrsqT#Nxz|fOjf?IOhuOW;6{$8((EhuSWOGTBrd#- zjcXoaPv58h$BW)vUZuswoi4rJn&7#w%cD!PH8|1R$+6ivuj}2@&{Uef-U~gme-Osi z{HLioUYv0@etE2&J4&t2thI}&%3J%s%=n#dq|Rj9J=sdkRuGLY4k833 z2trGvT}98K8ro{l#+urp1X2;vA*i@nL}G~ldj6i<#6>6Hm&@lRyu%CMCo_EFcz5u} zwO0P&aP`UIdE+JdxqUsS9-JT454Suw#cerVTt2O)HeIk&lM`mTd0)@mc&Q$}dT$?} zi<#N!&Dsv<%#*P(J-4rDGpsKN{Bas|i&;n6l;CJ+*K-uNTv|%;L%wyOefn z$$YfcuB2J+KW6LuqpCjMuwNo4Riitu8z06^b3?yvmaFP(eMEn&?l#{`N&92wi28YF zP_K?AmAlrf*G(KU(e_h??fFU59?us#QlCx7r?Z95#E|WL``YYSxvIO&-a?{QRXcAE zwWS)*%%0hV85mh_2Hs@bvd+2CnwG!3BVlfN8C&E;oSRz+0_RGVAnaV(22tl;R6?!J zy{m=dJO{0Z)0v=O&Mws}r$^53OJx3G8~I#!X7p|m*$CMQ*$UaqE1My^d1X6fKV(B> zM`TN6Ph?YMS7cjcUu0usXRmCH?Cq7!k=>E)y|O>jfLA&|T0nY0n(#^&NE=8WUTFmB z#4D{Jy&%mX-5~8C{h)7%2kA(X4`>PL326%H3TewLeIbo`r8A^8q&K8Fq&uWNq(7uV UuXKo6`IP_NVysUJJT1rW#RmjD0& literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Juneau b/lib/pytz/zoneinfo/America/Juneau new file mode 100644 index 0000000000000000000000000000000000000000..451f3490096338f40e601628ac70f04112ace51d GIT binary patch literal 2353 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 RGn0}Nvr|%%Qj(Ix{s3RXO|k$0 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Kralendijk b/lib/pytz/zoneinfo/America/Kralendijk new file mode 100644 index 0000000000000000000000000000000000000000..d3b318d2d67190354d7d47fc691218577042457f GIT binary patch literal 198 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 Pzk_I6`JW3IFlJl;pAK+| literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Manaus b/lib/pytz/zoneinfo/America/Manaus new file mode 100644 index 0000000000000000000000000000000000000000..b10241e68dd415354ef12cc18fb1e61df3558104 GIT binary patch literal 616 zcmb8ry-Pw-7=ZDseIWL=v@}#!tHL22A|g&7V+Fy5pg+I}np$d;THPb4p|PCKsNry+ zLE56ILDXE@gbMl)El%%q*FfsM9G=4+?&baYo7?GW@7Hw68x9kb!@d6~ms!paZM@{a z*G%DcQD4>$Re7eU%Z-Sxj6B;)VM|rpQ@VE2P>U#;5l#`ML82=yp$}b%|Q}zxjyHob37*HI7iUq|ZsF+Y( zC^i%yiV?+$Vny+ym{HtW~TrDkL_V#0Fx~L{~tfV z!1@3G)eDTgKE5FgZf-!rF&KzlLO=$AK?n&J{s)50c9A0>8e}oZ0FdP%nrsJf0bOLm F1pu=NJQV-{ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Matamoros b/lib/pytz/zoneinfo/America/Matamoros new file mode 100644 index 0000000000000000000000000000000000000000..5c59984def290712e183eea0655eaf8dc7a133b2 GIT binary patch literal 1402 zcmc)JPe{{Y9LMozB`A^wfe<7L6?kYZH`C0tELTHwnmYfaW>)Id{_H7rI5T@7(NlQ{ zOeiEoyM$PmVMG#Emnb9%kq$yh5`pvtMMC1Q=l#2O>JWX$kJlLN^m~5-HLcAa@yD$< zzwmHX=HdH>@#y=8Z57|d_O_?m9SjRkdz?)7Rf|1kUt~sVw#f9nmV0B9MAp{NI%{c7 z?ECdfXa9&%`=c{DXL42LKDnt63@)gHccjh>JyVDJZpgzWqbk2KB)z-))Ddr~Ji4h_ z`F5mB-^z7S5R)hi=9@&};!k<(?rl{xS1kQK5mg*blO=Vv>iE!RS(=lp$~wQx@}v|M zsF;(1Rfnp~n39#Bl0|j$J6-)!h!bm3T{HGk)GiF`y1p0ULZ@e!~jnwG||BO04 z5Rqqgjj6N2TG^P;ubOo@Z6t3baU^pjbtHFNlRT2Wtw|rr-_}e3nE^5djyWKc;Ftw64UTyr z6XBQ%G8K-w*qX^8v#~YPLFQv?CWOognG!N5WKzhikZIw6o>#nCTc%l?)1U70xYC?{ Lx7+1*rN#dSVcugw literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Mazatlan b/lib/pytz/zoneinfo/America/Mazatlan new file mode 100644 index 0000000000000000000000000000000000000000..43ee12d84a7c7e47aaa92406d01a539ccf93079d GIT binary patch literal 1550 zcmdUuNk~>v97q3_4T#_^8mK6uf+)l(hX%78(kws6PcyCjEc?u|)HKlsy-)~+a7pLC(?eR=!Pu7NIZj;A}m*VE%4>FF767<~Al!qeY;eNd!ahZY}FVU<(#q9m^h z&-|t%=C4+fVJ~#lxP@x*jIXlzoxfW0^SLbjGSMvSdMeQ!erEa2R*7l)XjZh;%gVCH zCiYN^j!Ww>@kIx8Lhy03Dxp9p22`1(d9ga_TeC{`ovV}kE7h7eWAxgdY?bn8j<`-m zsnn~!l2$WKr8mBnjKT<$S$a>hVy7B+$`#3;{oUjQHp)7AX>uoD(zye-&H67#bl#n_ zCcm##Z@7F*ZR||dn+~5*1t&tZr$np5I+tut-mJE43YMY;32JN11o2MvnBtkArFbaL zY#Z*AlHPe{`>Sr*ac!(Az57h>Y<_QcUF_6l6%R~#!%1C{_fGBh*6PZo_f=J5zTPvv zO;rciNcE4SswN;$YF?D7+E3B4_eO@=_hgprKflu)XcwtFm}csay%wKQ&Kd3F`wxy~ zosJfgC7JuG-)X4V~ms?y}Zi%;Vx_w;TA(Apg zg~$pK79uS~T!_37fgut*v_*!Wabp}>@eSp3HxLAfcklB zK(CA@l)Dnwt0(rENc&KJ%gCf@kB;O!l3z^6=hOMl4RPK1qG+}*AG2L%uZb7DZM?mPOV@7DiS^mPXb_7Wc~P$nsuUA1MH-z$+ynHF%{6qza@A zqzeT(;+0yEV!TofQVvoNQV>!RQWE-_xRIhH{(!2GvXHuv!jQ_mQW{d5SBgWb lL&`(yLkdJHL`pY=QzW@=X?P~x4 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Menominee b/lib/pytz/zoneinfo/America/Menominee new file mode 100644 index 0000000000000000000000000000000000000000..314613866de53e1457f6cbf2fb617be7e4955edf GIT binary patch literal 2274 zcmd_qT};(=9LMn=X7P|bWKxI(r6ofSN5IgdNm0TWs{+K2BHZC~97JbK>;S7m=kg6?_cw0ylItWQk4A}8u%&2Y=;PRqEcQm!u$bo|>Ki zjm(KoQ@$y`7~jRPntS~#Gw;i@>b}$OnE40Cbm50DnEN|N^aHPoDQY^VA8g+#4^_Ob z7c?|U@yvEzQoKY;F2AozbJm;E;k~LXzQUCCZ&Br=dFJ8QUe*gwEfW9MR=wy*u2j_4 z=*7Dy%98nodTGlQS(cWe1ItfIU@S^k77R$`&*`cv{iLb-LaFNT5wo)Qys8=5V`|$! zSC0&CHme#As7JeN&13#gb=~$>S)J3X>w`7Y5Z|HKloraG(X~35nIXY%%XMRPlr;8v z^ty`|dE5~vp$rqo;rBWG*!kc>HN$*UGSTFrs++yA$dqOS8O-U;ZYs(^-5^u zxZae~A)5vd>()Ql%I5Aaz2*CI+1l2kpB?Z>+o~1%xjhLoF3SFH81J}|@Bj7}iS(UO zDiS$*C~ABp^7eieF@s+`U7e~v3-`=XN7$h2ULmu=Azsi0{>!05qotT z%j%IiA(KL8g-i>X7cw!Yof$GUWNyghkl7*ASt8R!=7~%c znJLFqk-2hA7MU%_bdmXTOc?BnZh6k|L+g5l51oHcK37a@ss`BnrtCk}4!uNV1S@ zA?ZT$g(M8g7?Lul%^8w3r_CBi+MG6T9En3R$B{ZDcO1z>vd57=B!5T(kqja!L~@8E z(P^`Yq|s^fh$PZ!Gl`@U$t6cJk!*6L6Uiq>LXnJeq!h_1M^c?Ot4La%Hm^uxoi?*b pYLVO`$wjh@q!-CA{tpryXA3>smfGW=<<0lzdi;5L-aKz^++RkMdw>7{ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Merida b/lib/pytz/zoneinfo/America/Merida new file mode 100644 index 0000000000000000000000000000000000000000..b46298e1f202ee4ec22ba3cf9f8079c83ebd6c7d GIT binary patch literal 1442 zcmd6mOGs2<97q3}ky&6AO7sAUKm;4#W>%(+I+!#0n$oO%l#WeWerk!8j}H_yp{!$fBdN_sEpKwbt9%IW4?Af z`^~n94|;oan<O;6 z&Fsl>%igjGQx%mg)oFpov$#;#tbAi?J}2sZ-^Y#jak$?9;)4=$hY+N_9mKQBQ(-^dLK<{q%{*BhtHE%qX}WLd|a z>E>tup8GGf@L59*K1C0RA`nd&sz7vMC78~t~i?t=$VncocWl?nK literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Metlakatla b/lib/pytz/zoneinfo/America/Metlakatla new file mode 100644 index 0000000000000000000000000000000000000000..26356078f8d158e6cea100856baf7620a3296b84 GIT binary patch literal 1409 zcmb``Pe>F|0LSrJU0qk#no9G}K^_H~X3J>!?-WvCVvC0BrdF1vE%wj9h*p|{VIIQK zL)amE5Jnx$p_3i5C_05=9-;`Og6Lw>p+gZlz3-H#LJ)exo6oR&{r-ZrO{a3Tzm7-! z!sV2!%XeCzdc7D*l?&Uywk*`A>nqbG!oHiOYh#(hb=9Xwbj69t)*Yt1K0`(wj5VW+ zz0zYWnVxHVrB|Oby_1hc>=(<7dp9M0Z)WxQCnIvh!zq2^z57DHF`{o8GR5ZZc701z zn@Fgs(i2OAVrx->o|Ks{lDAcwDc)p}8j)v)@;=DW$Nffink}lIXBjms??vr|&p5pJ zOdJ_lHjd6rh`NS(IZMj6T9ZDli@3}A>OhYOTx18^QaZhD3#&a4y(zt zQ#MV8tka+3<(cd4#@YESajvt*8D18x`6)(Q^So$J{9$yIKM);ulGRx_ zCp#CvTV2VP?3#INb$=U`7sel17hiYCOTD+Op6QV6t?Rcgj}=N+M5N2T<`WgYjz`m8 zrE8jP5BF(q+rDYKVw|_ndF!^_-=Zb(uPfz1AgEPTs(VP?3U&7dwc;}MLvfk{Ga7c7Wcu0ImfJlT$h)9e`kVuqBm`I#RppGh1BveNg zD-tXcEfOvgFA^{kF%mKoGZHisH4-)wHxjs`iW~{uQN@k~@2H|j!bjpq27rtJ83Hl} zWDv+GkYOO>KnCKdMuH5*QH=!|jH4P2G8|+)$bgU$AwxpOgntI*j)+?GiT14HSzXk? go&U6})nPftGPxoqmmS#c&-EMGft(zFj^Bv=1#$;!g8%>k literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Mexico_City b/lib/pytz/zoneinfo/America/Mexico_City new file mode 100644 index 0000000000000000000000000000000000000000..1434ab08804dac08e4f595967d8c325691f08aef GIT binary patch literal 1604 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 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Monterrey b/lib/pytz/zoneinfo/America/Monterrey new file mode 100644 index 0000000000000000000000000000000000000000..7dc50577749baded06400fbe5d2e8dbf2579253e GIT binary patch literal 1402 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 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Montserrat b/lib/pytz/zoneinfo/America/Montserrat new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Nassau b/lib/pytz/zoneinfo/America/Nassau new file mode 100644 index 0000000000000000000000000000000000000000..5091eb5d8d3aedf24ddf9ee26a1aefd837e3b4bb GIT binary patch literal 2270 zcmdVae@xVM9LMnkN$G%)iXuf)tOx`SuoFZ|R2ZFlVtP10(~kz;PAJ}~oktng!kGNy z8U?l*TWMu&jm#`vjfpw)pEAd4_G9?5E!JAp$5=&>cYU7UfBUQd>V5m{@!kFZdIlOp z+e=*kxM}tu9`}NMd?)vmF7N)WHX^?--KhTXG|I%?d-R{F6*8IFpeHBl&D0M|^k0|C z&9(D$bo6AViM$;-j_h>UsqAEdMlk3)MHNEn(%qTje66X%d^)rtsx9hOD z&9f>=@0aASUezh5gL1>EPM!MZGiK(C+jZK3W^-fv{raX*jk&oYPpA8G%`JV9RvCf z?^$!_%IEZ5>93odyj?mc`myA?>vZnOYm)b4sa`PHCHcbvRnQ-j!sCmSr(=~A^<=2x zV9=E8oKy>IeCD2Y-zsm;0<)-OP?gR|F=eyA)@7r=n8i~c>GE?I&602Wb;ZEXa_@&v z>H7|TE=yn9rYqatm1Uj#)biTrWku6gRpkjwb=3-0J$}^G6l~Eo!`;T0vP}Dqx0;n> z^YyBi56J2-E0uq5i>!G+Th(sz%i7)q^+1J3);%_=)@PzuAXd(|{VkLt!F!)D`1x8Brw!aO+Gsy8?FnJxWmwDP|vTf17+wt`-HDCk#BDG_O| z@u=ppMhWJmsoL}O9L_Y43=HeGx~P$ZC-kFUZFaWx z>0Rk(%}jL z;^Y2Uyf0i#2Phl~#yATmN^h{za`K_a6>hUsX>i3}7ODKb=K ztjJ)I(IUe|#)}LX88I?sWX#B*kx?VVM#k-E2ab#!89Fj{Wbnx7k>MlbM*@IE00{vS z10)DY6dY|BkT^KnKp>GoLV?5r2?i1kBpgUQkboc&K|+GW1PKZf6(lT3TpVp+kjNmR zL1Kdh2Z;_69wa_UfRG3wAwpt=1PO_fqYV=hCr2A7BvOtxR7k9lU?I^$!iB^O2^bPF zBxFd;kf0$^bF^VY;^t@rheXcNh7O4x5=bf6;u literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/New_York b/lib/pytz/zoneinfo/America/New_York new file mode 100644 index 0000000000000000000000000000000000000000..2f75480e069b60b6c58a9137c7eebd4796f74226 GIT binary patch literal 3536 zcmeI!Sx}W_9LMpa;)WucQm$lLAu8a83sO>PgnGmjT+r~zKnAt=mx@@5l_ud#S(Afp zgQ+NJDQ=jk;TkzM<%0WykET>6`Y0}>c}~ywz3nD0y6a`$^Et!dc=!AM;}TLQ^>F>; zscV13%X8Jfd~fl#{m5MvC`-5fp}tz+l4YO&q?RXNloj)S*LjoI$;$A2wQA%6lOK?+ z3VMEH3Opnbtdl%(plWh2bG+#$MfQ!leVGemFr@17u536ZLKXyRx;OQN?XeNpZyywcY2o*Ygn>_($mdA&IiTdbB#=7bOQy_ESH;Z{$eFTXIC* z*JU#8--7(j?+@S9SL)p`SMD6ue^iv2 ztH-zK%F-fpZD*OfUU)>z(js+Z(Pp_hcZsS>%aL0XW~tk;8FFX9ICVEHL8?2=)PMR% z%Do0-^}Xsb=KgQ}^yv}bEu!YeeWlHXO4au8RcW{TpbFgZvpl+N zgKD4dGLOCUiRuu4(R7?#s2>mCXPy}Rv3@dOl?m!RO$T}QO0aLd4lZ9Qov-xKT}rZ~ zYgwEM$xW5eO}$lE<`C)jNlVo|CB^i3rcvex`4m)4FfP zb<^+u4joZ?*z`Y>t0N1q$y3|k)=w`wBm=&fsH4(0$}{uls%K*t%X3LDtASzZGHBp) zYEV^yi4K{dqstbW7{6z9%%-VkaAik521wxg=IP|-eY7@k$yc~n>W&y=xG6a%=FkE*j6qh*H5C|M!1 zsuR?kx$ntaCnMGD%oLfkHBem`HU8;7i8vfMrso_7U>3{Iw{k_+_E!XApdVkne z%g5_2Uhit)d~fW0HXZ7Ya}643-;wqmZQtQ>cFSC@TFysY4K~ngpTs)mBV-GaJw!GU z*+pa4 zZ;{PKb{E-RN4vks20PjvMz$E)V`P(&T}HMU*=J;8OBau!btwef>G!yA2 z(oRR)Po$xawxdW(k)9$=MY@W#73nL|SfsN^Ymwd}%|*J4v=`|w(qKp1VWh=KkC7%L zT}IlB^ciV1(rKjCNUxD*Bi%;Y?P&XrG~Cg49BH|u?K#qPr0YoAk-j61M>>zR9_c;O ze5CtG`yFlnksH9#-T}xh;Armwa!WYcdjh#B9PM3!+!n}vf!r9#oq^mM$i0Ew9LU{)+#bmNf!rXD_6|XA5l4HE zAUBDly-SeW1i4R;8wI&jkXr@0SMdLv<=@{dzV?&}wR literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Nipigon b/lib/pytz/zoneinfo/America/Nipigon new file mode 100644 index 0000000000000000000000000000000000000000..f6a856e693420d6d989c45acff2c48b60db69186 GIT binary patch literal 2122 zcmdtie@xVM9LMo5L`4TAloTnELB$T?_+h7LRG6^q)DzOfluSPwcta@Oq1~Vib)lsH zxJJWT?OJKg+8mkLv>KCg=GMxT)%rF1)E0kK*vIG(7aP~-`T4iM`m3$)?elqjcmLc! z_j*Q~+csBu|9Gyn-*9;P?csgqPJ1Oz49F|*|EkVj4mW=KtdR>vf64_lrPJq($TiuA zl+QaTKC?$<=-ra}&1*XAR7|ct*{5f``HabaakI|Z+iI@sxnE!37BVx}mgwBD-`ub; zQ{U*%FnNW3byNC;nU$KMX8qxl**{LG{PVxb%_HYj!O_pxOP|ws5s+lYtsI)*QMl_1$ypqzm$$fRav4<0>|d7^4?`q zF_5P!V=+_J^_Q9#3Y$Auey@VXbIts!VYT4uEK@!ETU|Z=yIDB(p{ zu@19zqDU`$d9N)0s#Zm|x66w63RT0ph%^qSsRwGxW#tp&YE^!YM4Qg4=+vaF4!)&U z|2)I2DLA9ooW5k%P9N6IhepjqWBc^FzT@WM;SRmN<$&3cSfN$qUD?>*t~Qko$|JFe zYRT%B)=;@>ooJR=agK_8RwHd!O{%sd1+r!QOVvKO$aI{$q#upvo5zMmb!XGGk^RT@ z<3Vk@IuGcrxgVKr4Ly3>^bZoR9#rwM5$VqBR^7t~rRQ?9dNQ$Fp8C2*ZSRfA(-0^pPWibOD0dg?WMc-Om}B5k3_xtI(t^x^PoMA zQTvznycBmu|HTxN_UXE~s}`9AG7DrH$UKmVIPFaMA*O=N1(}S~&IXwdG9P3@$c&IF zA#*||h0F?>7BVknV#v&psUdSiCWp+EUypf3`Ge@S5%pI9LGJB_;J~DqK0Z0ar z6d*Z3l7M6ZNduAxBoRm^kW?VKaN1-b*>Ku)Ao+0GgdiC~Qi9|JNeYq`BrQl@ki;OF zK~jU{#%Ys-WXEaKgXG6)6NF?4NfDAGBuPk?kTfBALK1~!3P}}`Dl?TA^CIK1R@!9+7u!=M3RVP5lJJG gMKgp!e)-z};S{j)bs0vh+Mk*@@eShD0|-JD?=?ku03 zjJfW3&I6BP%iH zE0L7)rj;}`sglEAvXaNYR4G^5tkjYBRNC1WWcuJim2tF1F6@0yiC1iy*(60)N0!Vk z*(MfMMa!I|29cZfi(Kq{Smeb|xAHFCEAl6Qw(k2dT-<;DbE`llst1NoSP$+FRZI5o zw_L4PRN?lwWl_b)>Y;{xvUuTJYH9H%x%9^iq9lF5Dmi;fltyl}N{DtZDo@Tu>G|dnu{yzTt^OcI zR8L*7YK9`jW81WiFO*>>mq*9F~O|tRo z9@UgoDw{s(RL#y**?ice)?JE}Pw(vz&-hZT^cb0gz)n|3Y zOp4BmBUYz>rs~Q&BfG|bQ{Ayjc8{D_8?Nn<8wcN2o6dF0&AqRwp5q?byQWWV8OW0X zfztwlf^YVOggS5G<8T~naX9?`M%xkO_jl9St1wK$kB@gR*5VVStqhkWTnVbk+mB2Vv*Gv^>UH*A`3=V zj4T;hGqPx8)yT4ubt4N$R*ozkSv#_LWcA4Mk@XvO0gSo=NC}V{AVol`fRq8L15ya2 z5=beKS|G(hs)3ZlsOy0g#HcHRlmw{>QWT^rNLi4&Aca9HgOmoT4N@GWI!Jkt`XB`| z>IxwxLTZE*38@lNCZtYCp^!=;r9x_j6bq>qQZA#e7g8{zt{74>qplfJG^A=s*^s&+ zg+nTbln$vKQaq%3NcoJqen!N6c`+Gv&R`0dJCVwlie`!KZg6Ca^JXxlk|Mzo>rcnk(D9R M$}=-FGBZT@pJb_W#sB~S literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Noronha b/lib/pytz/zoneinfo/America/Noronha new file mode 100644 index 0000000000000000000000000000000000000000..95ff8a2573f4dbb03892a576c198573895a01985 GIT binary patch literal 728 zcmb8sJugF10EhA0BBX?~8q}dF76~=9jm8&1Lehw^2~A8!Hp~oXVqiF<_yXEaBoewc z!P4l9mxUTMp_(Gcd2V8|)V(?R-Q4BgJpcH@Y9i$Pxti=74%d)9Ja_CJuPi6K#DCT!%DIOGW`xX7(G9#*|bMm88sM@Sw z)-Fy&-FL3*N6+fBT$5irYpN%+CH)&2)vE`!96M8e+l#WlJ*@)sULCk!62b7J4qm53 zxSG_F2%|v6{_yh;B!+lOg7Khv zmWxp)L7~-}33GEeHqy-2R_4Oyv^Gjx|19dR++u;;&ig#=QLSfv?|%37yZ8C$?Ok8L zF-83C+~yx1uFE{UM=JBxb0|ZC>K)~ z%H*|=GQ{*yy7a#lDMWXKbahONfTyp?8C$|tVD$n$vv^9)%9_;fq%q}wAyo&-6DsKN zp!E1&SJ!>rr-LuPCqho_(Q}UXiR<6)mGb31;)c%WH`X%RF1m5%jn6|rL{b=(!NSUU2Cjz8z9gr4Vh;?WYdthG)j z?OUOe>)K?>w)tv#-bT5iWJ0CJ+%H$Ae4*}e$H}yaKSbL1!7_dFqR9C07kTHG!(!Ed zM`sQn5O?kUS>L^Xx5#SvRNvFKU)@_YptEasshnjm>fD?SD);x(GB2S;)U3 zTxYC_soZC<8RJHAQP#Y8)GX9`rk+vU_Bj#rz%`^I^^D_=Zu*6(|=X{mVX$R%Gx zX^3$4p7L!;{Z(vjIOy9J`kB~X)ZyDcJ+7M4`kkh+vua0JyR&0tP&H3%a(4FYQ_ox| za9Ua$)vlpk9Py4`?ylz6aZH z>GvNWaLwaggsm?0iozW8tT9iSd5XOv+x*Tpzd4uv2Jb&uo8MejVDJF4I%Ijs`j7=8 zD@2xPHETo`iL4S?CbCYn3+08Ca%QQ(S~(VrtQJ`=vR-7t$cn9I$;g_mX3@y1k!2(6 z=2$qga*m}VYv))zvU-l?BkSiV08#;t5+F4|ihxwXYRZ7r0VxDh38WNAEs$a$)j-OD z)PtiSNJThGg4D!nih@+dYRZDtg`+S?WsuS!wLyx5R0k;!QXiy1NQICRAvHpZgjC6D z%7oO3qfkhtI7)@oilbOawK&R!)C(yXQZb}tNX@LKXh_wprff*vtfp{C~W3& literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/North_Dakota/Center b/lib/pytz/zoneinfo/America/North_Dakota/Center new file mode 100644 index 0000000000000000000000000000000000000000..1fa0703778034551487b7b55d80d41ad220f2102 GIT binary patch literal 2380 zcmd_qUrd#C9LMqBLG%EDJ((235lRa}0Rh8+q%0r?c?9*yA0Y~Z5X<=a2NX5>l?>*E z`D`x2{t!5FwK8OGhI1p#YOQoGY|hq3YRego^{l*bf#>P>{@r%vMOS^#p7VOnx&OQa z>ziK46#qJ}{)dO_)(`JKrN4TQwN3}*9O&s zb*{B=(^>IA))ni)l21iq^cgE@ku8#fKikO>TSfBtX?xK`Kr9}4Q>9$kAX9sHsI=n+ za!GrYN8&({e+u^(Cmlx{ER} z?NQ}f!?OI>cu^VugH!pH5LLkw&ia9?qI$f?spblzG zhSUM|d}Fn&kLpwnMR~Gea-(WYN|24`N>r2ABb$z?kuP=^i;!|ks;M~bEDkVdrg?hDTwhZy;&x}w5ueb4;HS<6FxNd)#5U%@ipC{CH-?K%Sd5<*T zmg}B8=9$a8h*{|m2!DZo*6OE1KV<=tuYc$3-<-#ML*O>n=IW~rjXZ{|4p|T>M|g8Knj6W0x1Pj3#1rGHIQ;3 z_24K7QW1`lAT=@Sq99c<>arkp;V2AJ8Kg8wZII$1)j`U`Q6Hp092G)J#8D%pNE}r% z>N0WE2`LmurI1n~wL*%8R0}B=QZJ-nNX3wnAvH7VqH$EssLRGtH=`~bN9B;xA+LMalH0m;P)X}I5$x%t9lpM80ipfz;q?{b}L<)*j6e%fE zQ>3U!RgJo=NL`J(ut;T%y0l1bk>YYx7b!1CeUSolR2V5SM~#spb5z-=%Z$|7s0)o$ v+Nev7)EX%^Qf;K%NWGDQ>3JbO85tC(BY#_DMO_Wi(IYdh`DcF8G5y!9*q(Fx_NMvrj^|J41?wES zaPz0)&h#ttuEKZ3qVTgaF2NFUQ(sx}zHK6Y^o*5oEg%*Ty>2C5P&%pWMJxGaiC)rD zXQdokrc>)$W!m<+dTIVfxvXSdr^h`km#2N9@9`$cjHo|E#`mEzbKnQtd}xeMzsnxLW!5H0#x;QdCi8g)Z)xt=6o_)+LSC)!OJ-9awi!1tvYZ zG<`soej6joV?MXbKNMp9)G2#I?^RJT+G$s|ejpwl+GSVO921XqRoajH-`Cac&FYDy zUj1ZUg{lc_)3y28s&;IXu8WIRb>|9oz1O4ak0L?ITpLgsnO^d}dC$88H zrC~xHJZo=F|5a>jc*EWv{+ZZO)Nb#X8q-Y~y{c*SyxtknrgjeX>*n!IYFF1Gz57z3 z+SAggpBqS2EmdWDZ|5u((7ko|G~#`y2pfYoVU9@!H#pp5;NWRQ1@Fp z&gnkS9NtC5Dt|!ubIr5XJZ0u74u~A{JIDOyJnlCH{=wQDb5+5ieaPyNw)mMHY;#*wrin z!BG&TA{-?_YT{~&f>g!Tlm)2^M`4i4Af-WSgA@m;4pJVZK1hL(3LzyzYJ?OCsgkQH z6H+IRLLrsnC>2sGj$$Fz;wTqVFQi~d#gLLAHFGsZ0 zqMM{d) z6e%iFRaaA1q^_=}ut;THO=*$ZBE{vXE>d2O`XUA9s4!Aujv6CH=BTo(DKk=MS5s)D v(ype|NUf1#Bh^OAjno?{IR3vX?lWOuZUUd^Uz(hjoRa8IO-)WsPVxN((J^&k literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Ojinaga b/lib/pytz/zoneinfo/America/Ojinaga new file mode 100644 index 0000000000000000000000000000000000000000..37d78301bd100b7c34b183a7e355021f55cb366e GIT binary patch literal 1508 zcmd7RO-NKx9ES0uS&Eomq99NrC=fy?XUxycGOfvUvUHjzGo4BuCQGN3!OD*VNz%$w zhyjI?hgq^V^lEJ=@nZ_KL*nh_ZaDp z7cwJqlaU$!U1YwGF|rn(i|lW!)Yj3bB4;RCZF|rsa=SjM?Y-?{N5d19*U>9?mJF%< z>Q-5>`KsDgP$hTAH7V!DJn5XjViZPfkcHEY#@^ZGvgrDNDt=ZWT`iqz-)OEZsR*e3 zJ?rIxoFY{k3YTRG$;y3jR=R)0DNouX>3N%Ec-OrV-icV_@X~}RzcptZnYk@0I;M=H zv3lY<2S8 z7g^VH$T&3_D^7>9jQYVj(cpcX^FwzOSTtz+4;t+Gb7UrUf-;XKRq6A|`rAJgnl5W+P3l!!3R zJ2ym>Y2F_Si{U%W`1O0S%Pm}GZjsMhpuhF|`?>$37*ikBAmt$SAO#^6S?ZGbA8O(# z3aN^tETk@^Fr+f1G)r9@QXEHhNO>IfAq8?&h?K}tBT^(%B~m6*CsHUXGs}>PHs9u>xcX9BV)p z!LbTt8OS=2g&-?ImSU;bf-J^TuLfC;rCtxRAY?_zk~r3cEQ(`Q$g()rg)EF?WysPj z_1cidS?bjx%d^z$Ll%gv5LqI!Mr4u5D)IkcX0)DdhMsGZE7j((r6jrRcAMRn68#fq Cu8G0` literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Panama b/lib/pytz/zoneinfo/America/Panama new file mode 100644 index 0000000000000000000000000000000000000000..55b083463a8b5b19d62b6f2707665c08eca5e65b GIT binary patch literal 194 zcmWHE%1kq2zyQoZ5fBCeCLji}c^ZI3_m{*Mj7H0TYXIGHjqLt8q_ zfBd5cZZ(#}Kb);u8LrmGpfzi?)>WdlYH+H>A8oWlwB<72?R-zQ+S=OM`m_6X?&t3Q z^R~D5`zD&(AFgozb}cY(I9&V8;kjgJqVeE|&wMQ|7XBzNiW%>xP`LV1am(nU_SNe8 zEqPvcELYu7neuKdzM^spa^1xlQ))@zio0a?7m@qbl$&>9QY;-CbMs#t6*rw2a&O-I zgIe~~VYi^^6IHnDgm+8ioLau_Y46tTRT(q&*g~nWKvh`{#mSw#`JBCUy4Z4N_~69h`3{ZmafeGTvbk|oYj{< z&}+_L(06_@sH)CtgovT^>e3m zv~0VIjvv*rtQr+N+o{*h6sq+vJ}dA0v|7aXcgPLr3Pi)!xODr|#r;(w*|>LFY|L9I z6HV`n#N|1;DRNqD`u1AAIsd%c{O+vYk~*cDPmb%B$)jrPp<(^tNT=G?I-s`?Zcrlr zhTL(aL+mW;mk%Z5qBW~WwnamtZKhc!ix!IHM^&4_dMu*!?ydp#c=kJbPeZTTlbVv1T$|v8<7k%4e^1yLNqy?@@OHaG{A3yWv|NhCi=8vcUK>luysXMyj3qXO62H`%-iEcX4kUUioG{>v{p%Xkhf|Lgm*aM-DNAmP-w=4>>lu8v#6 zspps%wH)*8I05^Wzuo?PPW+2m_~v37$UKmVSj|k3sUUOVyO<0z8)Q1je2@ttGeV|> z%*kpdh0F?>7BVknV#v&psUdSiCWp)pnI1AfWP->Ht!9eI9Ia-O$Sjd*BJ)Hhip&(5 zDl%7OvdCwdyR+h$B1uKH# N(s+4!usj&f{1s}GV)+07 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Paramaribo b/lib/pytz/zoneinfo/America/Paramaribo new file mode 100644 index 0000000000000000000000000000000000000000..b95c784234126f6039477b5a53aac0e4a7439d9d GIT binary patch literal 282 zcmWHE%1kq2zyPd35fBCe7+Yu}Yu}E2THZ%4CFyLD-epSl7VV*Z@RAj0KY+Bv=o$?LWu~AR6Qh5Djt) X$RLn&Ky*D&j6x@I*#MnrXUqiveL_zj literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Phoenix b/lib/pytz/zoneinfo/America/Phoenix new file mode 100644 index 0000000000000000000000000000000000000000..4d51271a14ab85c1aac83f7145b9f7f8f19c512a GIT binary patch literal 344 zcmWHE%1kq2zyK^j5fBCeZXgD+1sZ_Fyk%As=I>^2SkNXjVd1Qo4W~PKCY%?)FLS>C z>6#0TQZm1OlnVTQ5y8O32!zZ)$jJ2n|Fm}u4FCVHUckum|Nq;uKkB@H%gRct^ z2Lo|<2+(i{2qD2q|A8Qmg=YhZ200BxgPaGVK~4nGAZLPTkW)c4$hlw|=wuKLayEzt PIUPh(=zK1qf6Tc6=)Qk) literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Port-au-Prince b/lib/pytz/zoneinfo/America/Port-au-Prince new file mode 100644 index 0000000000000000000000000000000000000000..d9590103bfbf4954025847504b9ac459ee9c1eb7 GIT binary patch literal 1446 zcmd7ROGs2v0Eh8AWkP}r7lNs2??KafXjYWLP-9XwdD9um(d<;(;3GXgvayF16hfPj zN?n8ywTK|B%qR%S2x=247PJUEZCnJ^6*o~a)A{bA0$1+3mwSGf8JNv{|6sV`Xn`^B zO4Oh5a8>Gu_mp$`xZcwCYgxw5iFC&|U;6AT4CwYqUwtton>*6!|8>rw}mGq*!IW1}J`DWr0y2So1YO=|sEx5%3ciu|E! z;TqW}+&$Zb=SrIJMygan`wvlAx>ao`{~)%PMa>;`B|g79Y?k<)zOtHtH2V|8zGAm5 zKRzQWGE-zQ^hyMO&dJK6J7WKfS*t2`TZL~;S=G~*RLzA^>%dsEs;wKa4i4>ABJfZi z>TME-^ZVtINI=vjM`eAfThxCE%ZM{YL|zxmhQvA1a5qaf&b$*%{adW&H?!(kSEki6 zJf&JgF-u+^Rc%GeYHuA-9jVW)&a!UR8T%-^HusCJ>2VoNi;C#jP1*fDEKUwxl&9Vo zi_<+3dFFAJ=&9W$&kh>Gv3Sv+J7LNE`|r04d5g!NdJIFKej_0sfBDR@G#-C)&q)6F zeNP~0n5I5T{Q9W~>Oa^p91a^JxPUa``fBLSHjza51u{WWvDLXC$spMv=^*(a2_YFF zDIqx_Ng-JwX(4$biP`GRkko8-Zb))Sc1U_ien^5yhDeG?j!2S7mPndNo=Bodrbwzt zu1K;-wn(~2zDUBhI%6bdBxfXPBx@vXByS{fBy%KnBzGivBzq)%B!6TAwt5E06m0bz zkV)9;Ss>Fu=7CHEnF%r#WG={Lkl7&9LFR)@$X3q?nUbxZ6EZ1VJu75d$h?q=Au~g! fhRhBB8}1ujos!0UB+U7n=h#F2)t literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Port_of_Spain b/lib/pytz/zoneinfo/America/Port_of_Spain new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Porto_Acre b/lib/pytz/zoneinfo/America/Porto_Acre new file mode 100644 index 0000000000000000000000000000000000000000..16b7f923bdbb7d360a851cd7a02ea480e0d0e1bf GIT binary patch literal 648 zcmbW!JxIeq7=YnxY(b*vB7%roy9@pmL_{)nP`pYead0ZQxd`GC9dvQ&B#Mh`J2^?? zbX(CWb7`xBn?(@+N@_Xp4LS)0y>ProhH&2#cWpB_Eq<+pdBbKU&F0*DTs+K|`g5Yx zURVCJlvnk9Q=X)s_%imsKpTwSOVHIg9V}(y}WiCTGn~n_+&I`-l0>6*LAEhp5Ja;z2Q?xI{HJ z6rZTZh~h-CqIglvC~g!xiXX+0;uzIfQaqy?Q;I9amRa$o7*m`n))a4wImMk~Pd)Rk R4*D_cpZ@lO`5%jE`wO!17YhIY literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Porto_Velho b/lib/pytz/zoneinfo/America/Porto_Velho new file mode 100644 index 0000000000000000000000000000000000000000..10cb02b8b9bba9ef4171080ba53fc1220e7a47c5 GIT binary patch literal 588 zcmb8rJ4*vW7)IgU^+H&0jfED*TdOdb6e1#{wkikUt@m4kbYS=jIG|gJS{>tUd(DkuS?me2}lf`f(6`9ffT|L%yZ!*?e&u#H|)?N)b zjm5HWJGNmqij1 zNs{<&F56`JB~6%|^WZcqOKFH?A@|GQ@BJ^Hc;ta6&guVuo%U??eSZp;l&ng&{`f?g zzwq$cVjg~XuQacHB6r5o;-~%-}wI-*dk-90;0?D2HAB@ zt%%(f(s6kQMYmOTI(|f%=sx3!?h#k5dJfyBd%oGMdWA35y@HEX?`KnVLQR_L^PpJv zJ&>UKUCfgGOTVcB`x0ehg+~nB;Fg2Zzly>0+U1bmZDMGerH6jLA%?yEq=&bj6eB*~ z(j#v+s!=UxbkeC4YII$d9SbzpNTpd_W!k+6k^Uk{PdfKfOuiGNGxju#%(ERjYwZOwr7Eap{#`Y7%_W`fJFlkY z9n#a|_o|%nwKC^jsmhHzD04g9BJWL^oOykNm=!FNv+FH2r)H9z+vFAV{5$mgV;7V^ zyHzhJ->()XeUSOZn^Zy6Ls^*mOcY*xrHiKRQWdssyWB31E6n}t>v5ZJr|^IJ^?Gf) zai?XO_cbfbwi}ccVcWO070uga-l2di_SauR0V{US+yX1#JY&QJ^Q4%^XHL<4T&~|1 z{KX-g|JyFv-R~Q6(8y6Ehm9OJa^T33BZrP0J96;I(IbbC9KWLp0Eqwz0g1uU1c5~1 zXu?3^KqnA>5D6a>3J?nt3=$0z4iXO%5E7B22?>b_2?~h{2@8n}2@Hu02@Q!22@Z)4 z2@i=62@r|U(S(S^=xBmOqC~<(;zR;PB1J+)Vnu>QqD8_*;za^RB6c((BQYaEBT*w^ zBXJ{vBatJaBe5gFBhe$_Bk>~xfQ$e#1jra5gMf^JqZtNd9FT!PMgkcMWGs-uKt=-@ z4rDx#0YOFt84_en9L=C0qvB|W1sNA)V33hPh6WiMWN?ttL52qzA7p@#5kiIt86#wn Y9L*>p!-N&)M73kLj7&m*mXG2{wMlfKEtyLlR7n zCfKLsnsXgG@tYGi@pQ9JI(5iSe&vu!I@)BfeWJ-sX^GhD8X{(Db)iizD>T}~Jb zn+k@^&E?1Joa8r6=G=WYb8Jwv5;ohc;a;74vBb{ne_De>nmievKn%gl) z^6H}|e`mZbD6KNLuD&P*nM=&V{4ZqD_%u^E{byTvVazPP@|j)o$vJb|x3Afy$49m3 zodfpvgTs2qvtsXTJgs-NJt=oryr9eK8l`xCo0b%>l9E5(GNswuZRyZaQw6{f9oXnMq+8SMXc#5oATBNI+FUgv;3=OY6E8)?AMhf~Q@>9C0 zO8?4MeQZqi*h#yt`v+4qe8kqaePr(K-)+~|y=3m|thF0LALz#R7TJ{Dt()s>q%Lv4 zZYeF2EhAgCJ~Kn=zbw~=@d0UgJ*e9*{3s6`E;A1f#O(IyJoC`;^R_XPXyo9B_ThqG z&5p(w?at&u(^S!Jo5n^oTG%bo;Q`&1vR`)f_h`%It+KnbL-%}LE_+*>_0hhdw63qz z$Bs;rxIkRss&U-^=@W~+TxCowcIrf6TrBqL^CsTEPxN=v@=(|;D|Tm%JC*LN47lH#n20)7(+9NY7E^V$}zO_bm~F$gD42m5TYVPM~IRPEg@<$^n@tN&=jI7Lsw6y zEJRyRr!GWah{6z!Au2<3hA0iu8lpBsZ;0Xy%^|8YbcZO<(B9Lj&(I%|07eENDS+ev zk_1Q=AZdW)0g?zvCLpPRgOLqSmkvfgJY7N<8G)n(k`qW$AX$N=1(Fv?Vj!7; zqy~~3PnR4>c065r82RyZ31Vajk|IWqAW33m36ds8o*;>WWD1fhNUk8sf@I6nr3;cT zPnR%A#ynlhAUT61jgd7-+8B9*B#x0eNa`55gCviUJx`ZDNd7!s0wEdnbSZ@75Rybl m79nYb>Hf~)63SJ7Z-K~8>7ZZMRWmy?&1oA5XN!8Dlw literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Rankin_Inlet b/lib/pytz/zoneinfo/America/Rankin_Inlet new file mode 100644 index 0000000000000000000000000000000000000000..61ff6fcb7dc92b4d41dd0854a2c9e0be939be6b9 GIT binary patch literal 1916 zcmdUveN5DK9LGOLPK}M64;Ugsnu+Lf+@a|CK$;w5pokAAf{>YFh^2gdI>ZP3x-tF7 z;t!V(tC2eDpK|J$Pjli!rCX~xhI4Ke(^}TtSl`l3myGNC`uR^=e`J69zWwfXyWQ@u z&+8RkR`*V>{A>N)H$1GtJ$z37b>DhTWj^?ibpL9UmHh*GkQ9Z7_lMh~_j^n&AF%8mf&+ z?xuk{si;CGFS@UJnbT!T?zcL1K$?U{{$|7X`efRZZ|wAIcVx!*U)Y%^dQJX?5A5qL z-DcKDYG>D9Hg7b)uXDnjpQ_&!pK4`d~jBZve((7uESECRA!6Yw@XP+$j<%v zpqY1TzLxH2H1p2{wXC|zEI2Sk7tYK#i?%$}#c3HPy5yEddwr%n@0^zZk}ehLH*Lk& zB9(n-?6S6>q^kRnt#1BG-t5?Bm)CqIZ?#t26{T0r+j|>zWpPsNqfxdqI_N5 zv&PhBW@znqC8lnGPwS5R&DwiE>$?Yv<-HqmyDm0S)}Od*>&ufwTfVd#@_v_%^`F{J z$(^#fY_Hwi*JEPgHjQ=PFk4gh=+=%?rt$F_-PXF_Y`0Fs*DoIb{D{05kDov3OX6JyKm8^<7?grYBq{}q-Q!s3 z_|HZE#|_8bxMRpIL++XXRryiZF3|~DuXBpmlbnY_zg*XiH z7~(R-XNc1fuOV(j{DwFV@!X?x9pbx3=RCuEkIsFD|BwbSIsj<_qX&>CFuDL~1Edd- zMnF0NX$7Pg9$hma-SFtz0qKWF*APfYAT43^1kw~nS0HU+^aauwMrR(rz+e($VlY@l3?_+@gylvt$c?SZ zLTe&IBu&3C2@CPjwB+1#tEH~F{F*L(-{&8jU0Ue#{+ufN3zySvFW(z>j~3(c;CLba zGpZ7={qiKYD$;|;GW}eVnU;H;gr$Qk9nRkj|E?~F}GJ<-Nay>@> zhIwOqV*lu3|Ju+7>EpDGkWNS|q?gk+L%KO_JER}d5b21tM0z4kk*-Kvq_5L9Mmjrf zYos^QocE+V(jMuLY=G>5Y=P|Iw3{HiIPEsbKFCJMPENZOvKO)$ZrEq3Xf>a|qTK>f JyWvPU@C%u!B(VSh literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Regina b/lib/pytz/zoneinfo/America/Regina new file mode 100644 index 0000000000000000000000000000000000000000..20c9c84df491e4072ec4c5d2c931a7433d9fd394 GIT binary patch literal 980 zcmc)I%S%*Y9Eb7Wq@_$lyqJMVi$W|41r?0;Dt1v+oCsXRm>A5;gMUCATqHt^7hF5K z5s4sImBW-o-ctz1OIfDJyk9wlE5VNMb9AR0SH8o0K8Ime^L)c~(HBK>;#@M{a5=^1 z@}BkTp#6HRuUB^_((Lz*Rqls^2hPW`Lbp%db>g{K-MAZa5?2bW#P?n2({6_KIet0v zwK?4#sNZr1Yc}1X`|Xk8!U=ceX0J1vy6h7SJ!;nJl3)J^^zSb%GB@9?|GbIW^Zl)Qq0P3PSX3`Y zpWA<5KGsVQOYP-n`FiEfEqk^6ky^_rk@eeoYW-iXY^}O#duCF0hLh?-;M7k_>ZxBJ z{rIBibu5c`-rKG~s(IIv?!Slpr{XD@6_sJBEH$^*+^6PNho!{4a{|ZD@EQJm&m00E z5s(l_3?v8=1qp-1@il>vNWLZ%5(^22L_@+M@sNN>L?k2<6A9{Tq9S3DxJY1M6B!AO z#72T6(UI^-d}IK=W(3F(kTD>GKt_QK0~rT05M(6CP>``8gF!}v3O6*LRG7d!-P)%6Mh literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Resolute b/lib/pytz/zoneinfo/America/Resolute new file mode 100644 index 0000000000000000000000000000000000000000..4365a5c816c5c487f7ecc155e6c06c57e5b64139 GIT binary patch literal 1916 zcmdUveMr=I9LGQBVl~0Wwrpljx7u>ndEVKqtL2_N5nW~vmucFw&1^M4TXkkN_Z6i6 zs{V)`#)j3f{%NB^)&r5XwF;sLvBjX09w;J^zI%Wo#oPDw+do48)Sr7FKVQdj-d~^B zD>}FCvs`&ZW88*aWR%~lwIF~_~nMCAHHrf_MXx=V=v2F&3kp!>_27n@{{)Mye@gC zWVaoY{Ig_^ZMK>HohB=BiOuS6H)9_Z+HoD*jlXNLX16w&Kzl@Unkr3j+ZYYi#w2&u z5FKAsA@9w6pm~{-WkT*noj5p6!Xxk7@V$PS)O*2BK6g{zzxJb@aeXplx}nibI}+5g>MApR$E!MHO1_!7<{zDvmSLi^uWGcPkyuf-W~mXN3pE99JecDTslyDGU; zR+nwEtNVLQEZnBC?#pIv%0^w=ao99IUZ(3>x0&^Sl<0=0HRkJMer=jR$9%JEs3!Ol zUU=5mpaI_;`2Jb{f57*+FOhis{Au5yc>KT~c_|)0e#n=^UwP%fPqKqSDTqX(QZUOs zj)jf`7k!Q!j=OQkkXwe_Grz}8GwvF4+l>2$+&JUTA-B%BcaQGoA$JeCeaQVo9DsNL zae?6j#0iEM5H}cpKpbIs;?cPR@x`Na2I39G9f&^=haet7T!Q!naf;y;#4Uzj5XTsv zL0n__=FvIF@DAc0!#{|F5Dy_PLVScc3Gou*Cd5yOqYzI$I#(IKdUVb*y!GhZW%vtm z7~(O+Wr)uZry*WL+=loKaU9~gN9Q`kcaP3_hW8$w`wagf4PbNt(gH>gAWdL&0n!FY zA0Ul@bOO=}NH09PWProhH&2#cWpB_Eq<+pdBbKU&F0*DTs+K|`g5Yx zURVCJlvnk9Q=X)s_%imsKpTwSOVHIg9V}(y}WiCTGn~n_+&I`-l0>6*LAEhp5Ja;z2Q?xI{HJ z6rZTZh~h-CqIglvC~g!xiXX+0;uzIfQaqy?Q;I9amRa$o7*m`n))a4wImMk~Pd)Rk R4*D_cpZ@lO`5%jE`wO!17YhIY literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Rosario b/lib/pytz/zoneinfo/America/Rosario new file mode 100644 index 0000000000000000000000000000000000000000..5df3cf6e6377be897b4d09fe438ec75eb8c15ad1 GIT binary patch literal 1100 zcmc)IKWLLd9Eb5YZK5$03@9if60xNuA{=dF#6RVg3{^2p4Je%yL=XhU!3v_$#YqTA z5QLV-c2zuwYH6cAjWyb#1X2;fA)vS^A~D3OJ%7(RiHlC=<=)Sc>4oo;9XffU$NS^A zLjK|K>zBiQ?PYn5U(c)i7Y6+Y8(!$CO?iK6@r;^YchO8wPUxA|J->A0m3sL4oq2RV zqGxB;`(`MwACHduPj;16`BK$9-PqJ~M}z*{aza)1rc9-NS3SQ{@aHqP^!&%GW+7wM zg8pD?@uXf%A2*FXVbvIGnlGVKs@W6uoA1YTYfaj;DmC@BG3+nZw(D=@r1@Stq<-8U z^p{7H%3ka9S56$z;m*^=Erm(l87&mMQlE9#r*p;b&8t;+^++-9SwF6K78CWF+IsVF zEY*Cbcg!aA!0;+P@Fo}Aw=}NzmyX49*4jW@`(Hkx;IF3*+uc26ZMo`s?j5wYV!W`m zFROtLYv0xbQSM&H!A#am%h{&-+sDF~?uDG6OoT%;(8-=iv|ETk@^Fr+f4l!nyil;V);oKhZAA5tJv dAyOhzqf?4Rszl1foP65^?DhZv literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Santa_Isabel b/lib/pytz/zoneinfo/America/Santa_Isabel new file mode 100644 index 0000000000000000000000000000000000000000..ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3 GIT binary patch literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;Nhr-n$dtfe5^u0@fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Santarem b/lib/pytz/zoneinfo/America/Santarem new file mode 100644 index 0000000000000000000000000000000000000000..8080efabff78b5b49d544f7ec784bf4a57f5099f GIT binary patch literal 618 zcmb8sJ4nMo9Dwn(jZ)%kadFW4SQQTAAR=<=(m@3wAUHWGf{QMSOVPEv2s(6BryEmo z$W{agL3Aih?cx?t!AEJ8^S-P?p%;$dks-(bi?_bLIcfb{wt2&5Wz6Q>eR6e}_50S| z{NEK-d|!~))niefzLDi9Eh_`BL8Z7Q!seK)o<-t0ypT0-UVNPP1fLx{;w#vb-@W6i zzB(i8r3cY)YC+?&Q#GT0`IFD9!P|2=bnv1wg~edl4b{lez8vj(QrV4NnbTS)6D^)p zO6xn{vb27@PuN``6Q16DwjdDa7DQQf;@WRtZuwbh!8o-9po=lfs0>Q(RB`rrM}=kC6|+wXfy z%c?3et$#eB<`-U`m(0ue*xlx67fTlJ_ndbhZ2orOkhMdr@}_~Vraalb(DI>dUtn zbh+Qoc~!pStn~K8kLaFr``x`)J-Tl&!96s+S`Kah&ilpLcKzkF(`q<#l^$;FkXL=V z@><7b?r8S>4T3cVI1uRG;aVWYpw z@WU5HME{74Z1aliU+j~UwsecACx>P{$(D7Z_YTD->IelNensGc|-cUOuR1_q6Fbt&gkJ=eO##jhmF%y+@`O z6sn93RWdVgM7ZQKIX8Tfm>1bAvx0|Jwlzs+e-bKkzE9EfkNzO;JRPZXpBfT*hsSjO z)?;Epn@`J?AFG91PUwQnH`QHBpVNzCo>7JA-LmlKTD2s)Q!W_`5KG4!WzoqRakp=+ zT-Ix;8QA`MrqHHO0~YeS(ooEQumjZ>kU;Y>H(M38;c^Qwny>lM*b5+z&w ze!nBY;dBHBOnjUH&LHy!hx|uA!G3@LyOw32fqs9VvO@j-L2X5FI?Orjbwo{^{Jy-n z)LLoYIbyDPUFMxwqQhaPW*^k3}L{6+q%Ju?Q7og!OB_KIv4*)6hNWWUIUksTvj zM)r(s+ScqE**3CoWaGAG=g8KPy(62qHM>W)kM{ogLIZeA2Y?nJJwTd(bOC7t(g&mw zwx$zEE0A6w%|N<=v;*k}(h#I0NK25OAWcEKg0uzc3(^={(;1{Swx%~obCB*J?Lqp3 zGzjSs(jufsNRyB*A#FnXgfz<5bP8z|(krA{NVkx7A^k!chI9;R8PYSPX-LD%!kv<}gL^_GI($@46X{N2|Celu% zpGZTIjv_5ZdWtj^=_=Azq_0S0k&h)kv>xO|y}1Bke}|jWitTIMQ;Y=Sb6$t|M(n`i?Xn={(YUr1!R_`AGMX z_9OjA?f~Q-K<)zMK0xjS GE$lDZUi%6F literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Santo_Domingo b/lib/pytz/zoneinfo/America/Santo_Domingo new file mode 100644 index 0000000000000000000000000000000000000000..4e5eba52b8a86c379efae694a296d51e1008141c GIT binary patch literal 482 zcmWHE%1kq2zyNGO5fBCeK_CXPr5k|6uKP|2zc{=v{91n4;s5fF7Z{wYPcXJ$zQFja z-h;W|Ljdz#77x~dO98C=J3ZJ9wgj***mHtYU|N9X?>!H!G#@4~GBYu=z#%I$>;M1z zDi|1mWC8=r|NqAiFmnF?zjFfv@BjY?b}$NpL>R<g{00Ib|A7F|k01)EKr|>UKs2qxg9{iyCR_l1#*DlG literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Sao_Paulo b/lib/pytz/zoneinfo/America/Sao_Paulo new file mode 100644 index 0000000000000000000000000000000000000000..c417ba1da757e94b88919b05df8a21b35a5bf66d GIT binary patch literal 2002 zcmc)Ke`wTo9LMp~a^x({Aj(30+p@7d&-dLeJ5G-re&R0 z77QHrYp@_b_(xbmeuBa&C`+t4B9>ymhMvnozf4Loy-w@;JU{)}AN8N#@!jJXgE4qN zUy0i0#z^2_&o%BRygW(w^7;BV-)sG_XI%ME&!6+mzH^1TKh-9KvG;ZGOh!*k_|Bf# zw^UAEzFAXmrR3DfZXHTQ|>WB&Uex%bI4@<~&>5PT%h$c5`cw*c{9u^zTStW(BgchbhmYFhHXAZ11 zvo3yWV?8g)>{G)!r!#2oJ(9I^TgICE4)4&S%HPcWJO0vnp<`zLwzMv|Q`vb)SymA@(vzR;l6 zGTS^z831_6WWvy-f<8x`NsYYOSg z>)Jy4^18;5&XCrS-jL>y?vVD7{*VTd4v`j-9=)zfq)V@B6X_Fa6zLRc73mde7U>pg z7wH#i80i>k8R^;Unnt?zy0($Ny{>VjbEI{occgiwd!&7&e`EuY9YD4K*#l$~kX`V) zZ9w+H>ox+}39s7ZflUe z@w&}Hb_dxWWPgwiLUst*B4m${O+t3b>$VBmC$HNmWT(7ttB}3&y3Im%3)wDYzmN?> zb`04vWX~`;?VldDB+uKcyKemT|FdwpbKTk%McwyEQ7|43hr%J9p}}}06y-zi-z1n& AE&u=k literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Scoresbysund b/lib/pytz/zoneinfo/America/Scoresbysund new file mode 100644 index 0000000000000000000000000000000000000000..e20e9e1c4272adc40c07562bb3d6767cb056b550 GIT binary patch literal 1916 zcmdVaT}ah;9LMp$Q#gNnQ{olndcD0+qjvQZqJe&*R z`~LW9wl(Ki|9H(Zf8oum!@PNxLmzo!BfV`a=jA1T5ta0$KRWW3#spu{n1>fNcJh?Y zzt*n{zB-_B=PyY7;E*I7J|YXdRT6i8EYG#J%kx_rWRb@wN$wI!b`(fTe5x#th?UgP zVojaS)3hH`G<_mkmwYozGlu_A$FW~Dv-hTE?YXSkJI<@K@ua$HZ%9u2u;dnwNZy7| zBtM}~3UWH7V8$;?<9c=J?Rr`EXNNAoP%JNgU#;$eIxQSou0?xS>54<~y0SS*SM6CQ zt4l+*xG_%Fr2MQU#WPYG`kj^~{UBviKS_D;B`F^tm6s-u%G#k5;u-r~y*=GhG5DFT zYipDB-Mh8&<^8&$wMjR=(5gO9nQn@y&?-l|Ry}rVbyTTTU!SY5Os7fB*+;s0B3!og z&ym{U-{n>RL#gW>m)Ghhq<+`0`ud7<(oj35jfn@Psc=Y}!_Mf|1l6tgKGl|)cHK7G zrElE!>6^zlYwNe$<*j{%+BUFJ-fl_MclPGX_DZ+3H^<1ku7$Fr(iM@A0cY5C{Z46Z~vQ=zshZ5(xa( zVp)N}aAKe!(h_V=?D#^D7;{Po-8^;wzD9P@Tr8BQmkm=~X!2g~;_F4+9D0j`*@ za>>XwBNvTaHFDXu=DLv!N3I;XbmZERi$|^=xqRgMkphqkkP?s@kRp&OkTQ@uY)v6Z zB}geqEl4p)HAp!~JxD=FMMz0VO-NBlRY+M#UACq$q%vDm8d4il98w)p9#S7tAW|Vx zB2pt#BvK_(CQ>I-C{n4dDHW;J))b3WiGvJA*NAPa%41hN#!TG*P!Kvn};4rD!$1wmE> zSrTMTkVQdO1z8qkU66%ARt8xbWNmEC;vlPIYnBIDA7p`$6+)H>StDeTkX5q%-!D_R d+bmX*%WXER$l=Y+%Fl9UI~`t^(|&S=KLvok!I1y} literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Shiprock b/lib/pytz/zoneinfo/America/Shiprock new file mode 100644 index 0000000000000000000000000000000000000000..5fbe26b1d93d1acb2561c390c1e097d07f1a262e GIT binary patch literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8vb6-0*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC}}?Nyq3Ob5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU

gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vMqPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5Bz0( zYMX;ubev<=mU^aJ=~`I2)|SZ#w_fI=bZeGV*Lqux8))@)K9yl(%-|NuSuMerXFHSr0@9k1I96RLP_{Kru z4D51l+8GizZ)kU>wX7Ej^(&mjO24?Jq{x|`UMP}M>q0YPlSFbvK`1#h!AbsMk)C;e zo=O>;t7o0?sM&|3^{xB9Q=+p(-qv$Ur3PloIcvTZa|^D@c}qVMX^CG+U&folH#wox zy)TON@h^48#Ws;Sd|YRpd0u4??$bF()~W^F&uaNnt;!85nb)4D@+-E<+v^fkfv;H> z=KZ3IJon1tlxd>)!hBgW@w2$&L$AE^>}R4>r|G-iIVSE7#ps25_lkR3FY2=GZ>vQ$ zAM3@<1FC$%LA|7WlUnlQIa!h2FDlNQl$G%tMdgtpx%6_QsCxN*z3kI2RlRw?zW=QT zRnxdb*X}7*%S(g$fzH{gE~QfY11{zNJyQn?&a1#T_sNyB!(!zJ8M1!zoM;$|lMjub z6ph=j$cKlA#H!{|`N&|ec(nSGZtC5y9?Krlj|X?C=6J1FR|M7S%e!^ZSE+)hJ9LXD zQ?(oj=rtGO)suVL;_1#6a;=UNt$`xh)^|m$E1V(Mw~mVT#0l9^b69kQr|Zt* z5!E^Vo9;@|s%!YH-tg;gwQ=xWz3KA~wYmFMz2#^?b+78t&-527cf=HTuXZ9DXI_jckf9-Cvzo!79UT{j$799^ z3=kP1GDKvI$RLqXBEz(raUuh?nvo(yMaGH@78xxvTx7h+fRPa+Lq^7o3>q0VGHhhr z$iR`2BSW{Ev0KgHk*^Ruf(%zE%@pB*I9Dkr*REMxu;_8UKoNWJJ`p-sl+5 nb$tJ>bC2(TZ}dNr{`2Cc-6d2!t2#d?FGpro=jP_*=1A`!V0=z$ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/St_Barthelemy b/lib/pytz/zoneinfo/America/St_Barthelemy new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/St_Johns b/lib/pytz/zoneinfo/America/St_Johns new file mode 100644 index 0000000000000000000000000000000000000000..65a5b0c720dad151ffdcba3dbe91c8bd638845c6 GIT binary patch literal 3655 zcmeI!c~F#f9LMp+gDcHj5j!juuoOA8R?9L2`Du%oBL1}16dpAygY5AtBXOLLDRsuQ zV=|z!f|Lj;T{Skl>`^gCP5U`+bZv-ep}BY;${S9wkjrz@V&n5bgwR^MMy}GWhrN~q8T=CXJi%T{=?R(7`biKZ&r~8d% zEv|Lu1^1gqt?R96J$!GcYw)1IBJHpcDhebBS(ht+X4j?JF^eFFLWr`K5ro=#C;jcF|o-WQ_| z_5VqHEy9(G_(B|xZBU1gm5C#r!sLPU z?y9;w&ssg=&ZwyCyNaIrza={4jEFwfBzt|Y#8vygmREngRa{fKMPB>bTG4yn-oSN* z*~aw~D+7J*&;P3Lkmm#a#!UCebek85y%4)X z7oPPG+ffp@<;WcWtrgYg@NF6X+g28vx4)9;ACXsR-mz?~F)|~^ywgZ9QU;}(sVSX} z)YA(BX#?Z^X$K|;Mz`PL+0POn?e1WHhl02|7a`%zjkKBKx0k*mWNDGi2*y<)A zT|nA^^Z{uE(g~y$NH1)4GmviB>UJRgKpKK{1ZfG<6Qn6fSCFad z9Hcw8x;;pLkOm}Lpq1F4(T1zJfwR_`;h+G>INbmL|TaS5NRUPMWl^LACX2PokUuR z^b%<%(oLkDNI#K=+UkxXEk$~YG!^M8(pIFeNMn)CBCSPwi!>MMF4A6G-Cv}^wz|Ve zi;*59O-8zmv>E9$(rBd9NUM=vBh5y-jkFu-H_~uh-EpMlNY9a`BV9+@j`SUAJkoij z^+@lL<|Exl+Hb4-k8A*2y#tUfV5|24vI&q~fNTR~A0Qh6*$K#2K=uN%8Iaw8YzJGt zACL`Ut9JylC2aMcKsE)kE0Ar0>K%e?5nH`S zkWFH%cL}mhkbQz|6lA9$TLsxG$Yw!y3$k61{eo;5TfJkDEn}B;d)@d*Rc6BFYT;}ar(2eU89 AC;$Ke literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/St_Kitts b/lib/pytz/zoneinfo/America/St_Kitts new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/St_Lucia b/lib/pytz/zoneinfo/America/St_Lucia new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/St_Thomas b/lib/pytz/zoneinfo/America/St_Thomas new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/St_Vincent b/lib/pytz/zoneinfo/America/St_Vincent new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Swift_Current b/lib/pytz/zoneinfo/America/Swift_Current new file mode 100644 index 0000000000000000000000000000000000000000..8e9ef255eeb11515b84126d9ee5c0c6b3c72f2a0 GIT binary patch literal 560 zcmchTtxH2;6o=29*AK?PHz>Pnf?;Jz(0@Rq17TML+sa@`EoPIj_O2KNba#TXX#lUSMSY}+(~!w(sW;H($D71TY6sJU&m(9Y0B^+ zGNWokKI$VoKDZXYn6U{jG3D#}{idBe?Ta{fRr7r3&aBMEcPie7Eeo6ZQ1Tl(1)Uw8 ztx(qWCf?5u|54Lvs0yhIsSK$NsUB17Lli(XKvY0UA>oEWT literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Tegucigalpa b/lib/pytz/zoneinfo/America/Tegucigalpa new file mode 100644 index 0000000000000000000000000000000000000000..477e93950c2f09b8c338033c07a53d4ef5c01912 GIT binary patch literal 264 zcmWHE%1kq2zyQoZ5fBCeb|40^d6xKiyC}@M^FT3Xc7RgVz6Z)SwE-@%Jr7*2ow&fn z2!{Xvud`ub`2TbwKsmI@lw7BhhkMq9R8xhj!TVJX4OHmp<#V0!@rKHr&@$_Bi#jmyOuB}B zD!XY+=JYm;g9X>*q1qymn{-Cz<)(?eZ$sLhSg71{0i7R}qVgxJ^^uhrb#$a#dOkRX zH&7*yJ=`n`DqT_x{t$(kvGVw(1yQsuT>6S13Eyf^mZXh{l3m!1q{%*8vSe_M)w-WT(w@d0=+a`T}Vn#I;f6*5P##M9Ld)?C1uP(+s(5(fHs&(z1 zY)kJGZHtq#V@reRm>!g!KRu#rv|V;D*hQeHOkSRf7CqG&^2+V6VqMrjeL|rpy*67Y zG_S(eheESYZ5upZpDicX+#(+lnB)74R6^#E3;&S}k`0m$k`Iy)k`a;;k`s~?k`8PROK?Ss~Lx=7mfQnHe%QWNyghkl9(9 z=^^v8G!sN-XlbU1%n_L+GD~Ed$UKpW;(s#JNVC{fv)lx4irr~XPVhP$c85JV@;7N6 BlW70| literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Thunder_Bay b/lib/pytz/zoneinfo/America/Thunder_Bay new file mode 100644 index 0000000000000000000000000000000000000000..e504c9acf198fbd447221f120354774e46dbbcc0 GIT binary patch literal 2202 zcmdtiZ%oxy9LMn=h(;GkC@E4R1B+dR%O6A0s5D@=t6m|!n3Czyz&nKE8yZ0xnqj0p zV9f@#8mqKs|3+rET#ZRNYini7YW*7>+u}hBJ4O#$tb04}v!3;+XRUL0&g<@O_u=Oq zX=vVB=KbT$u)lCQPuR=%tUdPWo3~Sc^0<+6dB4dyHKo0Qm`Uh8uM;2CW}3uQlKNAo z*J+hJcSJ5tKdgM-A@P~VREpjwsb9aS(@sX^vJ*Xe<|~hy^k=r}jQvgK@~+$U70p32 zYkh&v4EfEKi&OPg{uGmy>sMDNPng+>DQfnwKACfVLS>)*S*{s5qjHXZCfB}jRL$-1 z%yo|(RJrv(n7nO6dS3Y{bN%us^$nRXoBV?9IzRTI_Wg{ruFg_Io3Ql-(TDQo{jEeM9pjjO*?<@xi?!m?qt=#n&3G3OgyG5(8LJoUa_ za{8>f>DxhFIdnm8es7PyrSEgO_1P_YY1><}tY^2nt@bIYYTT@CmxOvwR{?uXt|1to(AR3h!!>Rqy1g+6`f;>rYa5R2IwX2gcQ!>MP=o4bcw^~S~lvuSXZR^hj0 zb8m~SIRwj_do&wP|k~(AzUVFgt3y^p4o~(oxZ`I>tt%Gpkc|4j+=P-y76JgZpIX zSCwj4cT^sJH%E1E49V`NJ(Z9+Eh%yOf8rC5zaH_tc>J~Jy`*^j#G77nJpR$igjDyY zyLZ;gaKx)x6Y*-eciLNLZ?*lKJqdrmk$*9jxIOI`_7)9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;Nhr-n$dtfe5^u0@fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Toronto b/lib/pytz/zoneinfo/America/Toronto new file mode 100644 index 0000000000000000000000000000000000000000..6752c5b05285678b86aea170f0921fc5f5e57738 GIT binary patch literal 3494 zcmeI!Sx}W_9LMp4A`+sAikYIhq=?EQh6_?+E`)l-1x#^!H1rH&^5lY8h%GMZ)G&<( zl@6x3Vul+gX$Wd+)08OgDL$H#3+RJ;qUZE{-`j5Tu8UsgJ)bkox&D3saS2IN!)*U} z>X`rV^4u^l-@waFg%5>%F-Ky$v zfxf-JOx(#oA@%A4QJuL<-d&I_?xkeO`xEDh2eE1LVV|+$QAmP(+;Oh@%O_Gk@f@R` zJRYrUuJ=|?&qnBHM_VfA9)IoH=u)<9r*>O%S=E}WbZzMr?&6uOGfWAOs7tbL=mFu` zx7UxyZiaWYj%{~=z z__*%p@lR)ZkT1<&e`+!k(Tihwg4GV#nF#uq<~mJTgR%m{TD}`uobb z_@g4O=AIlCo+n0K^U!-;n(IH|=Rf2Q`_zK6bkuu5So=Do-N=~adC6cou^z>uZ>YY@7 zJtMzNrNle6%q&pvhATZYC0ot%JD_LB&Qr6Umt< zeE)2uNY8M{`FmQ4j0rJv!Iw5s%k6poYP&zrum4lOb-4;w*laG>kzzM@m#c7_&C~ks zZGAQzVvn;8=x^SU=6%b&!{W?%*=%msN8EFap36KlZ>LovI;YY?F2>=oSBm_tdkRTvYK*E5;!O{c* zi3Ab~Bo;_8kZ2&`K;nS}1c?X|5+o)_P>`q~VL{@81jf=t1_=!k8zeYrMTakhhsVSR z2oMq>Bt%GzkRTyZLc)Z^2?-PuDN7S7BvweUkZ2*{LgIx442c*LG9+e5(2%GhVMF4E z1P+ND5;{v0J0y5W^pNl&@k0WLL=Xuf5}O(dL1 zJduDR5k*3Z#1siC5>+IuNL-P?B9TQxYiVMO1Q&@e5?&;}NPv+DBOyj&j072pG7@Ga z&PbpwO{9@fTbfuS!L~HfM#7E68wofPaU|qO%#olYQAfg##2pE|rHMQedP@^~B>0vl z`bhYZ_#+1Zas(iU0CEf<2LW;vAcp~R93Te*awH&!f~7eYkb}X}91Y0fU}=sAM-##_e!|ATe{f01&0NPcCmNu8r(HF)a!2+Ad$WR literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Tortola b/lib/pytz/zoneinfo/America/Tortola new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Vancouver b/lib/pytz/zoneinfo/America/Vancouver new file mode 100644 index 0000000000000000000000000000000000000000..0f9f832821b6cff451b5ecccd0b6eac8e53a9190 GIT binary patch literal 2892 zcmd_rZ%oxy9LMns{y|XkWTHf9Cp8gN1QbQlOw(O45tS>68U9INn1+g7v=nvG%KmZ4 z{L?D>Mn1?@qGL9j$`IKq6rElTPK-&|e4bf{|Z_SPpeH>n@yU)QH}i~2FS zL7#SgqZ%U)>c*yh>Wu${oUJwLoUdAb+WWEb)$EX;x4mwfDvITog4O1HNw)l&HqZQ) zlPVWt$C!)m1^QB-xvDv4f^Kdbty)5&bxVDOx_r^EuN=6gT8}sB-^&}-)v8Xpw&t+9 zUgndw%}33R!dkhx_yhAtMy32Y`2}-pRH?KNt5mmp=SfG8Qq|G^yuQ<%r#esP>c766 zq5Oy3I`Cnfa_x@QK`-@E!RveKE^CIFu1jO2+uShIeM+c=BwR5)^koTE-S2d_Azh9qCr z56=8t4UIUVW8x}QjK5W4!?vhc-**z%vP=!HIUpk%O3cWL?Gj(T#EdF=MiRD9HHrCe z=%k_{X0&^q9+TPKB*$dwu}RHlTu6#eiSDLSE=B3_cP^<3$2)cE*{{^Z{gE>1@JH&Q zvJRR2_G{|l!gDgEbg!A3Q6rBmf5l82B{F^5Dl`2?gLaR6S-Bey>a_5cDy@2#p4mEE zJ^D_y%sREgq;K3Ivp=0>G8PrfoSpGz?!;`F=T#;%I#oRL+l;4kfMg|~G+7rW=mi6> zs|8;~>ui66TDZrrANL(pi%OgH6E(Y3&hle=am5C;B;6-VU)*7qjjWX?^NY>$@Jh*b zXPeyCQpt}=HTiXUQV=r06nrv6R$L62r*`J*mET9JRbID#y2`H#vtsq?vL>}=Y)`$m z@R%x!Xw~a7_NaA%Q1PbJ8n5rNtdFcT>uc&{Lwl)twxUX&JDq1XmXyn;Lo-ZCPLXWh z9cO}rg1dCJ&wukT5P0=Xmn#r>*93J91j@F!dN|*`oL9|C_qgUvvp3V;$LyWsvA=Sc zE68~~|Dp~7dvYduuOO8`N`ce@DTbr122u{B9!NouiXbIHYJwES(N+a13sM)PFi2&P z(jc`#ii1=KDGyQ~q(Df8kP;y^LW<;QtAv!v(bfqm6jCXqR7kCmVj8mhZGR0AW}l4hDZ^SDk5b>>WCE5 z(N+>EB~nYIm`F8|aw7Fa3W`(|DJfD@q^L+$k+LFnMGA{l*3p(0sV!1mq`F9Xk@_M9 zMk+Cn3hcC@8NYK;^dsWwt>q~1uuk%}WFM{14~9jQ7}cBJk| z;gQNa+R`JnM~aVBA1Oale`EoW6+o5%Sp#GdkX1mI0a*t}yAa4qINGH^*22*)2C^E+ zav*8n^23Z+LyEMq!INHTQRtH%gWPOkYLRJV_B4mw_ zMM72yStewikcC24%F!+rvR00Ev5?hrw9AF87qVc;iXlsetQoRs$f_aBhO8U1aLCFz z+NDF*&e1L&vU-ko`H=NP77$rMWC@WqL>3YM->VpA$1=r^=7{vs@k#LsBhnKS;}hc( G!u|%QT)5Hz literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Virgin b/lib/pytz/zoneinfo/America/Virgin new file mode 100644 index 0000000000000000000000000000000000000000..bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954 GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Whitehorse b/lib/pytz/zoneinfo/America/Whitehorse new file mode 100644 index 0000000000000000000000000000000000000000..fb3cd71a69e3038f0d77e8ddd3290a08fb960d9c GIT binary patch literal 2084 zcmd_qUue~39LMpq`DeNuJfPFL&ap>l&du%YkJ~&t`)8BmbjMA1JbBFg*Z#1jZMk*S z(m7!i8nJJR8mPy|;f+K%8H!*H6AD5k+ae-kNKi&GA`4n6`}Mq^F1zZgi++da_j>lb z+Rf)3-PF=l>ifqt#eU)N9JGh~+;00yUcK3W_F9fHx2N@=>l^C6d3a&}P|k1dL)**r z??nk2TiB-_1h%T_ExYxM_y(0(9n~|JE>W}cDsi)HAX*00t4@}KqNt3OZ+GC$Y3`AL;KK5FvPyH(!E zXD0v09ct0wB~vimC56djCOEK7in?Ak#m81iN%K)%+A&`ihdXsy{bVW6jp>TgA7n{d zNQeBBROnu|Ui#pkTK369U3uqIwY)#eJaO$k^l_@Jz#3SY?a9V<7VxZ8mZm9$JCt& z%DUxovp%7u-d|~=8}3W=VZg-7zmV8>%k;)Mzo?BL`*nPLSZz8#uAd*dtTy-D)h`U5 zR9hNu=&i|pYFlK)Y=85(>?pi$UToeY4H=Y z_<7!;2A|LK zdb?95+Izciin~v9Z{>MsBxMG7-)wge)I_4bc$Gc%_B>}#9e>*ob@oG@l_$l$|2FzB zcHr6Pz#B(SBYQwLf$Rd=2C@%iBTl;$WGl#Ckj?P#up4AM$bOIwAv;30gzO2~l+*4C z*%q=dWMjzAkgXwmLpF!(4%r^EKV*Z*4v{T7?H-X$I_)lzZ6fJd@M)G)*{9^X)W!GDwa@=49| zWfZBSmTQI1rKpWe&AF9xh?1lcqLNd|Cv^*zINjG>Yi_xA>3$c#U(QLnTN|`ZYU?+;=gr5RKauTTsN14H?AqYeRTk=x z>*hQ4rCEA0&+9Z4CF-T*A|TusldHFSy4TFFxmMNeUTJJ)_Gfrl4q zmjgSb>#nz@+qPBm$hw8ny>OO1nwKFxrX)#Fa)R_68Yz!OhDdOqG5WZFi-fd^(2!ff z+N+_XhF0CvC%(F_VS6rU_>Pm>yP&H)`Bu61ne>bFo%5|c6<;k+r+g;Q1br?M{WeHM zYn4U@%$LZzz1r`ZSNd1Jt5MaC4k%r$(R(N8z@jM{vuUu#<`ziYf}T1kDN_bdXx8|M zVKOA{dwtd~R1$jqDhZ9PGW33}3_D&g!_Oa*5#=qKc<6n3Zhf6TU$Rn0<{#DD5}d%q+NnCm80ZX+v3rT_zEZEN|BzU#6q;iiIHqUmJmTuBz=Te;IMT>MrMU+!CZL+T1+Qn((v9Gr6 z{=E6)?Zd9^JN&n=fBeFS{fGyj`Fv$JM0~yjyFKlEzE8G`zrF8luiYMeJ~q`Wqeh!E z(VWTVq)_L`4uCL6ij15=2c9JwX&@X*2~< zm8H=YL|G7RLDU7&7erwYjX_if(V3-D8boUlwL$a-t>U;rbIhoYp*x217}|rV528Pa z0wEfNs1TwSXAXp-_fK87gJy6rxmyRxOQM zA$o-<7NS{*Y9YFXD3_sKhl`p>K%785)PEoS}1w(ivK}G-_w)ouPP!<{7Go=$@f`i1r!khv*-Y07wQbO$s16 zfF!}vWPy#*4X literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Yakutat b/lib/pytz/zoneinfo/America/Yakutat new file mode 100644 index 0000000000000000000000000000000000000000..da209f9f0a07625ec83d4ec84917216347f5687f GIT binary patch literal 2305 zcmciCZA{fw0LSqQ0v8C165>IklMf(I*TYrdQk0<(ArcVRn-Ezjc88T zNJX?1>d5*O6;+n3o$?Rme6e1~Bz&P_4xf>+Ka8mxz8=+apHGSScZc{!-^$>=3zQCv@J~7gYYxLG3!aSuN>(PRp0;RDoYfcc)t|t$bE4Ye-auS*^0j z{i`Z=-X}fjbA{)^Vp%fti@5WHSb5jfr=nD6>bu`QF7DYAt(PA-Ant9ysLOV~rB>8_ zq*u1?SLI9I&=uv|RmD%|WM%%Ks62a0R>f}QA~=&5lF*fj65} zZSyW&x35&ym-_XCfeh7;5E&sdL}ZLs zGe~5VRx?awoX9|tks?Dy#)=FU87(qgWW30Lkr5+9M#hW`8W}Y*Y-HS4GjOXJIWlx) z?8xAe(Idl0#*YL5i2xD;BnC(jkSHKwu$nj^fv}oLAfZ5Ffdm7I1`-Y=9!Nlth#(; zY9fn-)@ovl1lMY!i-Z@6FA`uR!bpgb7~?-djtGyu=Ie~Qj_=hX_n4mkFI~PGW@~I& Zb%VErZs*l3b-7(Kucn~DRp64be*u!YOXvUq literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/America/Yellowknife b/lib/pytz/zoneinfo/America/Yellowknife new file mode 100644 index 0000000000000000000000000000000000000000..e6afa390e879f97cef351e382ae62daf183e8d77 GIT binary patch literal 1966 zcmdtiZ%h<)9LMo5V5v9M-)0yJnrV7M+;N~JkeL;9Azc`rh(wqM-cBn>-OUW~*TOL# z@m)FAX0n_%AGDIxwVE5vHGEPwcW$je3s-4vmJO>-HkIr5{yptckJ|dWyO(2QJo&vp z@s{Maa{0&I>3+h8+v`63f9^HJb3={;m0Z5Y~#Qvde5`s7!wHrCyzPkpr7Hnb*n-QYs|^s0JoOl8~kg&~b^xNPIMW@%H! zIooul#56DXNt#DznoX0V(sFFvJTrD&T6@#x*^z!}Yd>u^5ABfWVi$BvU!UC?I;qbm zlD2)uLG6gv+m34~O$0+WalToTQ)k)a`_;Pr=j(RI;70Spg_-hVs>-}{Vq7|#3QX5P zT3)WWZFY8^mR)(5%@!Ui*nfKnwn&Q8wWip3VaxI(tTXO-+Q{=pp19XNbL6Qb&z(QQlSiIC$J0liKSu&c z21p7>4qlf8k_D0mk_VCqk_nOuk_(ayk_|^XNIo11AsKmHN=Qy#mlTo}M_Nc;NMcB4 zNNPxKNODMaNP0+qNPKOI|lk$UJ%7L?JVUOcgR$$Yde2g-jRzPv$G&hHa(Ww368J RaCtaV5-TeUmxUvNzX6=<+*SYp literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Antarctica/Casey b/lib/pytz/zoneinfo/Antarctica/Casey new file mode 100644 index 0000000000000000000000000000000000000000..f100f47461ac8e48a4df03bea0429e464b30a22e GIT binary patch literal 297 zcmWHE%1kq2zyK^j5fBCe4j=}xdH%_rY4Ezmrr_Ow>Vx0PIST&HXD38(SvN#2;TNd? z|NlQD6C)Ed6C)!?69^PEfb`AU05So@)-^C-&^EAO&^9y#NrTiehLB)C(2D;cH-I#Q m+ySCNZUNCC_kd`Sn?N+kT_76dHV{p&`?zd?F1FLP-~s@fiZV$6 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Antarctica/Davis b/lib/pytz/zoneinfo/Antarctica/Davis new file mode 100644 index 0000000000000000000000000000000000000000..916f2c25926bf444b7c366110a29a3fafb17fbc0 GIT binary patch literal 297 zcmWHE%1kq2zyK^j5fBCe4j=}xd7jU4VEE>KU*MnnY6h=cA_m^me>wQATxH|3Pj5(I9t# mXpmb#0OTGJ4RRBR2DuAFgWLw9>w#*?aUYir(8YGT=3D^3yE`2K literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Antarctica/DumontDUrville b/lib/pytz/zoneinfo/Antarctica/DumontDUrville new file mode 100644 index 0000000000000000000000000000000000000000..bd6563ec8fa005d0dc172a8f7850313ae7607807 GIT binary patch literal 202 zcmWHE%1kq2zyM4@5fBCe79a+(Ij-y}Yq)2Uknr4wsiFS=|No2(jEo=!AkedbfkoHA rfI-{P0L%{|!CatG|3PMhXps3J8e|8^pn4!1pFLbQKpX9J4Gp*e$FV4> literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Antarctica/Macquarie b/lib/pytz/zoneinfo/Antarctica/Macquarie new file mode 100644 index 0000000000000000000000000000000000000000..83c308addc4ca5a3f7eac89d19cd0881f7ddce9a GIT binary patch literal 1534 zcmdUuNk~<37)QUWK1FjV6EmmO2It{fnWjF|e2$BPL{iLx%5)L5sE{cAAnR>%Q$mt~ zW{9iM#D;}nX5h+bQQ4vaYY`C*i?D^m>wj*zYSp4ezsJ4j&*g3|@0_~Eww7G^V*%y~ z7i%&X=WsQ*z8CF!b0XpUfM@*3TyU!_KJJ#Ku?|_DX^@q(qv{1r3b$c}Xv`cbMwyE<_qvqCMmhHtwx+AYucE-hPUSy5rEsO42$dLTk@3mm~ zlkT2)qJ@)bS~N5-#r?tRx-=~%9ba|N@gXT~ex_w^kL-1gX?fOdb;tK;g`-v~1MW)Y z&q1mBculJ3x};|2jMP48kxsAIYx!8dmY?Gv{QUjRUuo~kfcS~r7_|adpZq?rK9|tU zoE#~2AE-08@;XU(I(gButkw7H{=y*hQQq^8NP(CHu?b=n#43nc5W66TLEAD8n8u84 z0OJ_eG0cP5$1u>Qu@GXSO=Ba(NSnq=h?x*O8HO?}Wthsam0>KyT86m{dl?4XG!`>V zX4uRy+NQCZVYW?UH^gv=$wnL0(SPwBDVn3q*Mg@!#7&S18uxYAblmV%OQ3#|G zMk$b57{x%UVUz=@2T~BDB1lP$njl5lG*v;$V$=mG3{n}SG)QfX;vm&A%7fI$C=gO1 pqeMuJkRl;fGRm}R>SPqksFYDEqgK{wl>9%64JtAfyE2{5pkF;ZQx5DFlEhUHg|Ns9pGBH7985lwm7+4q>+yWRlbPWs` lv<=LF*c2)mLW1!?Q~!gk0BHqT1ENW=ipvIQt(~qZ7Xb4-9Ekt` literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Antarctica/McMurdo b/lib/pytz/zoneinfo/Antarctica/McMurdo new file mode 100644 index 0000000000000000000000000000000000000000..60bcef686badda46f36652694d06d041c70a9d87 GIT binary patch literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj zup&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-LAkvq@E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-3xhrKTQc>goQF_|a7(e|BUF=TwG_ zx>lil$<3D+ihdOzxm(8%?o$fREu6T$i?Rp)smh9xwK_kEjyDWlUgIh@@9`* zkuxP$R=tzS39m&;(9$WB_r(sVmYQx1Q zy>ah8C61nuX+@y{pUjRNRzYi?3_kOTocDg6d-H?X^dwg2 zoqR6xZ;a`JrfyN#KB#5rsoH$tjxGvxsp9erx+LzjDowjBOTVx<$ z^f!x&!FpNQXQ{2dxpLd%C{b12rnh%=tLmarz2nGPRpTF*p@xI1Hs+PA%MOdWo{ze| z__%6y9LHnNUfu|=Jty*?FRz#X%Ca11KwDnN8GdQ|9OvyDdoE|ooM)cQzH9kXg|JdZ zhPeag{vCmB&wPxr_Ak;fzsMmESCa^miK|Hk$puLU$p%RW$p_tpIGBw1GNgp$gd~Mz zZmR R$V3ppZz`7!(0n^%E&!2a70dtt literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Antarctica/South_Pole b/lib/pytz/zoneinfo/Antarctica/South_Pole new file mode 100644 index 0000000000000000000000000000000000000000..60bcef686badda46f36652694d06d041c70a9d87 GIT binary patch literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj zup&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-LAkvq@E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-9ioUZkkLh=Lj(yL5fW5L2U)-OucN2v&@+B}J$nv2JfC;8b1)Wk zR$Y1K35Tn}9PZcDtqnVMp?t0HT`vt=7PYZ{MMC*+G+g>o!b=O%l>0)OGBdJk{;up! zKal3x=Ng$9mzIZjbWb87d&jS6>w)XKFW#qZJK`E`4(tBn7H#)$)AnUoJBmWm@ot?S z{JB*+pZw6SIiDQ5T`1j;zslj%cj>v2kt02Ga&+vA9^3g;j(5*$Z^@+e)uc7%o!0)1 zs{NmD>cGmd4$dU?#D}P!yx*nq*F$pZT8$>A+T`>=iJrMyB}1*%G8`+Gvw=-=uJMsFd zvMaJJvM;hRvNN(ZvNy6hvOBUpvOm%Q(t*{qfb?KBO(0z$Z6JLhjUb&MtsuQ1%^=+% z?I8Ui4Iv#NEg?NwO;bo$NLxr>NMlH6NNY%MNOMScNP9?sNP|d+NQ+31R?{TXrPZ{F w^ocZzbc(c!^olf#bZh+&?fR-s$+fQe4%U_h{gKM@s&cm?;Ex1cdspfE4Vhy68UO$Q literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Antarctica/Vostok b/lib/pytz/zoneinfo/Antarctica/Vostok new file mode 100644 index 0000000000000000000000000000000000000000..5696abf51d60ca0489d57f1545f47762378bf2e8 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@Ol|L}x?&|Ns9P86gr33?T^&EV>2;4B7@}V4)BaOamJ9 UA7mm(BYso4Y=Gw5>6&o?06nM}IRF3v literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Arctic/Longyearbyen b/lib/pytz/zoneinfo/Arctic/Longyearbyen new file mode 100644 index 0000000000000000000000000000000000000000..c6842af88c290ac7676c84846505884bbdcf652f GIT binary patch literal 2242 zcmdtie@xVM9LMqRfk3fxZ-2m9fKZ61b@Chh#YJ=iGw(FzGExatM68SQG6w#LelX`6 zWA7Tv9JwqVv!>V|lz*VLe%NT?WhQf4t}Rwp8eJmMkFokZzs>oF{?kAG(dWDSKEC^I z_s4DbdInZ(sLQpkIdSF%@alWXGS!l5+1xZfu~z3h>p9hvfTQ>sMjMSiJt$ffd2GCX@wF1t?2i1V2I zDiIycij~pGNu3zp8JXl?Ad~a{(1i30nmFkzbw(do=kU8aW$=*R^2Hv#^}`o5>Bvz@ zKF}>Gue>T#+f-7wJ|k(tkleOvt=#SlNP1DJOmi1XMzTw$-!w&BFVPVembP2Kx`&{-X4HM8|o&DwNCvuh7(PSqL74fRN#r&scqy(9%GyQMI@uG~`=qu$yiS&(sF zOTA-K7W0Xgr++QwL*L25==Wt|xKHjK+$)Q^-xOc}d+Kj*lf?&K(LsqP<{?D7M?|uG&5zmGAA@NEr`s=)=UVQ5i%uYPROK?Ss~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!c1B#~L#nrZUOnI|$)WTwbek+~w1wKcOv zri;uMnJ_YAWXi~#kx3)7My8F-8<{vVb7bns+>yy6v$r+VN9KsX}svBn!zFk}f1)wkBan#%xW>kene& zL$ZdX4apmlI3#mO>X6(a$wRV-qz}m-l0YPbwkCy04v{1xSwzx^b> literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Aden b/lib/pytz/zoneinfo/Asia/Aden new file mode 100644 index 0000000000000000000000000000000000000000..b2f9a2559a1ca4306a43c2abf074e91d51a64edc GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@Om&wAq&W|Ns9pGBPk|p8-i}88EQ;_=YfO8yJJQ3?U?# X1~lkD$V8An{HAi*0L{14HRb{Ue7_k- literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Almaty b/lib/pytz/zoneinfo/Asia/Almaty new file mode 100644 index 0000000000000000000000000000000000000000..d93201cfc49134258724c1ce280e12b5db4f67ab GIT binary patch literal 1017 zcmd6lPe@a79Ke6GRk8|3U~|rynzn3NbGfzVzps}_FdH!<#2}&)p&&vesPItUI*6=G zco-}?M3*El;^8ZJ^q?SKOz2>T4kg4cVr%|BPlS;tZ~h*?_xZiwdpvmW^GWv)J&Kw= z!((@tj8;2Ydq4SKe4EZ~L@GkBvZpGiyuR`;wZ6?Cd%83 zoV=qoZ+rCg%cRym4r|>&wbu8Z&@*lSv?08qje+la_Slk!+yx2cf6KY`U#X_&?@~?6 zi*kN`PQqWOu=OZCgDl?H~QpF_V|hu`TI(zasK*O}bzI(a~bD=sHxkbGQ$e97m3p zT$J8m?Kb9m13%?j%GmY&r>=5i()Y~q-Sh5_%kKRDOz(}f2}fdfUa>RIYmCbo&eA!h z_u<2SLJ=nrFT4OZ5I+z{4y`AMD~KyUTq!UOh4s9=xW*pjXAnicHPL literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Amman b/lib/pytz/zoneinfo/Asia/Amman new file mode 100644 index 0000000000000000000000000000000000000000..281b304e2f8bd4e9bfa58ccc1c799d3ab12f1da8 GIT binary patch literal 1863 zcmdVaYfQ~?9LMqhkz7v4-2*e1A|%JDj*?3*p%SID94#SVN-dXgRLgyYN9IA9+02Z1 zKy2pvz&-PT9{A67bN#xFS=ekCoAG=9&1SKghtAnKuhZG-+4ubk7L=9vt=}Hu<{Mr- zD$R?}9ZuEXvD<`JYFkf3pj~vbw0rZ&c84!i_w}xUh?nowbFb3waJ9d5ti5J;Iv6gU zLx=5NM{Y~+J?-s2wO^(0Mh!%U9!u2x#z4Q^OVWSpBRjhItPB`((H@w*R0c)tvtuGw zXw1_ofx*wuOKek#J><*+^)^hlhwjQyU-d~lZd0+u=O567;&kz+Hp;L`F_IXyUy`ET zGTgOAl2_G9^6Py%;^`V4dGmsdYAn>z$M;G~eXWeCtC6ua6*6vTj*KtLlhpE$Iw32h z6H|(Gk~gSnsd1X_8Lk;oT{NS)hi1BclG*e{r@ZbgQyU&=*46itz5TOHJ9tZSD(*|} z@xs9L#61$sJ)twYZ;(9y4$b?zTl2eCYyQLaI`e&j7Mz=>vu>p5?A;S}&apmPxORZf z-8EWdPFtO~y0^?vkCvi>Z&Ez4jVu`TL`uS6Nomi^Qu^YWl!ZOkvMYyW;j>G+=)g8v ze7RniY&)do^_z8R`Bq(4Q>x3Sm20RdQ&;!`T9H*El^y+B<(({5pK_$y6D!qs6J%wx zM^>HgCaas?%9$f*+E7#wiVJ_#{I*jL!&n@0twfOtbzxw4{%ZfT` z4$6v+ceS>xUXfP&pYME=l^wLw(x&qk@TsCsu$b}iU7^E7c9HbtkAfzIsB%~&!C`VHjQWjDdQW#PhQW{blQXEnpQXWztQXodmWE~vMLLe)FECsR_$YLO?fh-5I9tMjA zK~@A=5@bz~ML|}@(JTwHE{Ldq8tP}3yxGHiNxsB5 Qf1)=#A>MpUic4^R2T5_%d;kCd literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Anadyr b/lib/pytz/zoneinfo/Asia/Anadyr new file mode 100644 index 0000000000000000000000000000000000000000..6a966013d9cd9007a75318d3c8c757dfe8a72373 GIT binary patch literal 1208 zcmdVYPe_wt9KiACbz6-U6q)Hnir%D|ZiO%9pN(D?VR~dl#~q%K5UmZ>C1p zKPnafgi|(L`6e4jK1tQ_w^DszPHGa8%e2b?8Q?ZoC-Q+fG;O@bDTP8H!2tlvkQMs-$_}583W7k(S5@ zY5BP*t(8l<_5DlP@%^3NIg^&Qg%`U0VODoMzpG=HCUxidc^yA|S$AEyBfEN!>Fz`4 zq^B;ad%F+HZdar3tL>4#FA3>))Jy-XknH*Bmc-*#lAMp`2Cn8$j^^`u%TeNRTIO^u zD>|3Ei(VAJ@#o$==2B{Mn13}rtDH(@wdGdo*=x0ut7Wc*`@i>=d1gGLRu}Fk2U02+ ziJH}HR+Cu~zDg;Jqp;4D3CCaIFLY)NRUvFSZqx&NHj<|wk94VAS5CrBqSyzC?qN*EF>-@FeEZ0G$b}8 zI9n4P5}vJz4+#*75D5{95eX8B5(yKD6A2WF6bTiH6$uuJ775qZ#ES%MYa&KMMq);S WMxw@1GyDG{Y)`w%KNgBaJ-+}-2^o?A literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Aqtau b/lib/pytz/zoneinfo/Asia/Aqtau new file mode 100644 index 0000000000000000000000000000000000000000..78cbcf0ef6617bf5efd26f866fd195dfe08e3891 GIT binary patch literal 1003 zcmd7QPe_w-9LMqB)a95Gp4xP2tE{E-&zf$`nz}CGVZx7iK#+vrp@hnl6m<|hID()< zs3-9tDjh^Zp-vTa@EG+wbd&0L=+dQwbrBuw`+kNLck@I0@_{Soy2@y72=rq!_{ zEU$2J^vT6@(?;{6d$m?BoQi%(MMB&0$o|ij==Z&?=G{cR<^990*qf3)z4^elE>GIF zr(^ca%!rLo4cN0cJMFn*%%0CT+V*7GUWom$7aP9XM6f|8ejVyd-wsU2qczj<>5J~% z`KXgG-|4RPZQZ@Ns(VTcy7&IPN!^+=mnUAh^z;+gH~7r;-?-;8t9(CEW=W?;T zCigk(1`dkm%G;>By4z#&&ogH5b(0x-95KU7d!}%E-&`x5bhDL8B~VjaS0iWe#9wE9 zsQN>w{`eUTD%Bg8e}l>uRGm`SwF)Wa8P&)=AortJs?Py=_s=Jbqn=8p`sK>VmF3qc z72v2oa`*lrh5Q8-av(|gK3R}7zLEz?gk(ZeA-RxbNH!!Lk`GCUWJFRTIgzADRwONw z*H;oFnUU1Kk{d~mWJl5?`H=}AGeD*wDCU4n0+|Ie4P+k3M0{l?$W(k~F34n%*&x$F e=7UTKnGrH2WKPJWa8_FUhqDUjWpqQ`necBWYVE`IvD(rK@bW%m=t)DL_tB2;ic$e zhlmc6(jh!d$WsL#Itu*`-lV#CiJ*ga2^;HuzFh{Ly7hj%@AK|^KR)>OJo$SMXJh8C z5w>@jjJTcbFTQDh`e1LXoW0~eE?sWgcN&AcPUElB&8BZBrRF2oX*pOdMcx{4WdM(|c*P~^<5&5B=;jiij!s7n?EnUZFh3=)TLigbp z>G}9cqHp)*=Ju}iK6@p7#SQ6yRFr|41sR;))Y$y84vnp8d~#Y7tuva8jcc-;(^O%+`BPF^Q*TU8OwgEo!PmfQ>{@##Cq8>xeX{xtrxSPbCOQzeGht_vml@;p zMs@G4`G4?YKc#{jh#y{nBZw!6E05L}#2LgJ#2v&R#395Z#3jTh#3{rp#4W@x#4*G( z#5KgXN9!Eo9pc`j^$%$P(gCCeNDq)EAYDM(fb;=r1kwql6-Y0TW<1(%Ankax{XiOm jbOdP$(i5a9NLP@yAbmj^gR9ob|J+$9Z9B~LCPIGz&tTih literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Ashgabat b/lib/pytz/zoneinfo/Asia/Ashgabat new file mode 100644 index 0000000000000000000000000000000000000000..8d9e03c13ae19703ebe48105d418622e53a14b55 GIT binary patch literal 637 zcmci9Jxjwt9DwnQX|=W;T(mFoCB8;2LJ?aOigXZhsE{Ebh(o4=;3{Mj>{7+Atb+eyZiNJ^=l039Tp?67w1D)x$}*i zR;N1boCYJ=J3EuOEoMI6F0;?CLGHn^^Y@LwYM;pH^`VSi)Mfl^M<$N9#6DP)$-PCH zs#!8!Ny|*3BeT|v%%vX1iKkrW{mY$y`t%o$T7EMOL&NOr&RG1%>>n^qrOItyX|<|i zN_oDKP-@dv>E5xoW9XgkJzTAAd#X@e(W|Uii3cb{kLV;icz;mPFBl?0(cl4yP*f-~ zQMC?5h@wQ1qG(aXC~6criXKIfqDYaXXi`KesuWp@Zd5HyQKm@Krv8Ti*G^XSUszu% GCBFgayMIXl literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Ashkhabad b/lib/pytz/zoneinfo/Asia/Ashkhabad new file mode 100644 index 0000000000000000000000000000000000000000..8d9e03c13ae19703ebe48105d418622e53a14b55 GIT binary patch literal 637 zcmci9Jxjwt9DwnQX|=W;T(mFoCB8;2LJ?aOigXZhsE{Ebh(o4=;3{Mj>{7+Atb+eyZiNJ^=l039Tp?67w1D)x$}*i zR;N1boCYJ=J3EuOEoMI6F0;?CLGHn^^Y@LwYM;pH^`VSi)Mfl^M<$N9#6DP)$-PCH zs#!8!Ny|*3BeT|v%%vX1iKkrW{mY$y`t%o$T7EMOL&NOr&RG1%>>n^qrOItyX|<|i zN_oDKP-@dv>E5xoW9XgkJzTAAd#X@e(W|Uii3cb{kLV;icz;mPFBl?0(cl4yP*f-~ zQMC?5h@wQ1qG(aXC~6criXKIfqDYaXXi`KesuWp@Zd5HyQKm@Krv8Ti*G^XSUszu% GCBFgayMIXl literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Atyrau b/lib/pytz/zoneinfo/Asia/Atyrau new file mode 100644 index 0000000000000000000000000000000000000000..317466d14b4749538893135618839a4c747838b5 GIT binary patch literal 1011 zcmd6lKS-NF96&E>YDh#JN)wyf##$Rq)P5!=#5VrvA{|mVBv2?7Zm>d&lPC%ngc6+G zbSe&Fr9Y6G$z1J?yTHmD)JgD{k zonEg}PLcm3=BOH_hP3i2l{c!6-DCGZHWUE_!-pnr5e7z2}|XRj>NypIlCOhr>Yy9V`-yh8iLyzYv6j zAkU4{1&OjNe~L-{5{tqG&%Ws`Q7t!yd1~-JlTPQYlWIR^9oLjQxBT)yCq7sFTJ*ykU0&t)XDd~UU%kl3W&hNx`{wlY4O2bQ zZ_eEAH8n$NQ=5sJvu>rSYc4VMReR=K$+l?-g-058AISNi3mRSjD~;bev}wW7<`4HI z_HtA%Ouy5M6VKJT^F!jp5pD5@CE@v!j6c=Zn#X#nY)MiD^OD;6t?4aSF0Vb(D@%9v z>ij2h-@lQz7hBpswWb{p!{Ut(>b0R+>CDVXSKF#~$G&J!bx5us-<4iT>trsM3ltRo zyMm!ZTtSiOTl-cd`daLLqa(*e(q5qS;C-eqE1mW$m?hH}+(1 z6QY4%Kt-Nb2Sf=(3q%b>4@40}6GRn67epCE8$=yMA4DNUBSa-cXP#CmLn}lrLoY-z zL^DG*LpMY@L_0%0L_Z?|Mh1`+AUQCS$kS#4NrRCGBoRm^j8qu8K$3xE!$=2`4|%2+`)9WNQ>^{5S<_ZJhc;{KQiuuL&R>B^{)o z@DO?K5Eez9QelTKfeAr}Oo9iZf}}b{V8wdgzYwAh(Xr3?`JO+=U_P&951tqZ$sco@ zJ6tSjE}naC`^H+|y)4a?1#aY5mCpvfuBQp_qU~P!d&&Feqrr-qOL_m)HNAS`g037t ztJjPk(`$!%^}4~7UVkX2H>7KHRlGuPtS`~k{%?9y$p;;9`uYQ(-Tj;26z6K5FXn0= zFXV!67xLQ<&v`;;9(qDga`|x69f@>bmT2WwX$YN^hVQ2(<{we9S3T1B^|;zG9hIH) z9V&jSOEpc_tLBlg+I4fKN(_{#-52X*Px6auIl4kxtLId*<&(5ICsnHInWR3xkaqh6 zX`g*09k0h^@7-}p&)ip?SI*16iR)_rseZ|fo>N_YL#lhQPxWj+qO$2mb-Wa||F>Dr(;MA|Yomq;ujrAt4T?%2$o-(QpN z%t~D_Y_6!eBD{^*tXWU3-?IO3$mTN@IB3dY^DZ1X<>=f`6^mN$R_%GnQTyd~oP0VQYu1poj5 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Bangkok b/lib/pytz/zoneinfo/Asia/Bangkok new file mode 100644 index 0000000000000000000000000000000000000000..7249640294cd596c9445ce175455efa06b5dc6c3 GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Barnaul b/lib/pytz/zoneinfo/Asia/Barnaul new file mode 100644 index 0000000000000000000000000000000000000000..82cc49c487a34f51dba12ca0e4cf2145bfc7965a GIT binary patch literal 1241 zcmdVZUr19?9Ki9roNmkr3CcffWtlCT=5f_rP3@E}(M^LGLS+!zAA_P1LVqYUw0emc zQ4kS?IC_dWVD%6}hYEU;9>VNFq@-Tzp`s)Rjdi|P20;+?*ty*MIlEjgY`^akdyX6m zT7N8$xx>k7GAH|jp;zbT9t=;HF7Y2vEiFo0h3?^k!XK~ui#|-GiYN1}lCixh?`V%& zc6Yb(-D*|KFGtjhvz4m!s86lzT%=aTGO8@{MXe4@t8(v4wI=_m@;g5Z|IAyl_Wc`K zaq5z+7=I$xJsS~$`)N^`zAmb+T@>pFPm1c^9#Q=~-Y*UumNomX>fniEdP9A$-neV0 z-sJ1hpB!__S$C&c)<2q&4HstR)|(Sj9-NZf2EOX; z(Z{l}dt5h_56NibL%kz+KsJ}%(9NH2>z14|y5+@r-TJOm$L@9O_}FRLb|tD4v2UW| z`$yTA$z&Wk3+4}J?r%q)EBnFi%KPi#bUB@t)jr6NoY-%feX`%-wydtCwJ>|l-f@^a z^Uri8+4D@iEnx*j$edww*76$5VV32vxArCR5A!u&DZ`ABIr9Q$jm(?H%x%rwk=Y~j zXVC!C0n!4}1JVT21=0r62hs@A3DOGE3(^eIjjd@1=?7`Z)^volg!F_og>;3qh4f`> z8bdlmT0?q6nnSun+C%!YH4P#i+L{)T9+4)IE|E5oK9NR|PLWoTUXf;zZjpA8evyWe oj%`iLNYA#WX{2kUZKQ9cainvkb?h@u{13f*>dZf(p{myN3kAm+rT_o{ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Beirut b/lib/pytz/zoneinfo/Asia/Beirut new file mode 100644 index 0000000000000000000000000000000000000000..efb24c27f83894eb39286eeb571eb064ca27fb5f GIT binary patch literal 2166 zcmdVae@xVM9LMpmB8n(#wPqnC280239vD9|k&-SnIenWal8Jm2PlALJg%mQJX}M*t z?403juGEXlF(ks8xms&YUBi!C^pC@RR;_M|K$F&*x%xc6{n=mrSD)K`AK&lYU$@=s z8C+evG1vLWiLsyX=DcR#+$Zd`mv=+e=2+`4IlgqKI#JxCBk39Dy^J>9asLg|ajjdT znR`rh)FW3XeX6eZD}8Nn&f z(h5}E^(#6)w$8*~yj>>!JZvVPIwT3_Q`M9M7xdKkW7V|AlR9zKYi3+JqLcd9tK@Di z_w*h#Gs7`D<_an!*`t zq;MeLJa}_~%sYFRdFXPA%#Y-$qBCP47=)bH=s=wEdrk_>* zlCSh*u}4*DYDAX~oiU4J_UgrbZ=1(|sMAY2+f3OvuSj73I zHr;Nv54|HTU9G0I?`U%@5FH}(Yo#UlQ+r@Z&ePmpCG>p&KQtOQvKvKC}9u68xZa$N0tkOjHg6(LJP z)`TnySrxJ@WL^9Y7KW@0SsJo7WO1%`b;$Bu?fQ@fA}d6eh^!G=B(h3mnaDbkg(53O zmWr$uSuCf&k( zgH#474N@DVI7oGn@*wp=3WQV$DG^d5q)14WTy2?8Wq-;ptTy5cy$|0pgYKIgLsUA{3q<%;NkqROuL~4i>5vd|lMx>6e zBd6VGD#BDNU9Lxd?y9z1obJSa-g zAv{Kh1&}8=0dOUs-~9{ zs`;s_S{_|eNALA2YcisajW(&)ky>@U?}s`O*-|G%pVg^DA5}P5N`-eeQ>Wi9sxw~` zcH8^sR%Fez+n>!_9ZN;Kb2e*r%@u5W?6P%s{GA<5Pvp7>9(p}jujgaUcf5EsnU9y! zUNUseNfx@ibH#+y`{uCMw{AN9OP%h(tD-YFXSnB|6r9xU@9u?}-Tct-CwHj0U`?0H zWf?f|>kJ)nctr$-xHTp9=gHg@HGB8HC(?f+Yev!;5wTL&l34z4meS#oSkTAYF4iX57goHw3A;FMnNH`=O5)g@qghXN@ zL6N9PSR}5m4va)bLL;$};7D{NJQ5!n05Sq(2*?gp3Fo5;7)aP`-LpIIY|K7sIOV*K-@}h*$pvT$S04 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Brunei b/lib/pytz/zoneinfo/Asia/Brunei new file mode 100644 index 0000000000000000000000000000000000000000..8624c7ae182ca0bd5be20dcb0d9556f73bbe7d56 GIT binary patch literal 215 zcmWHE%1kq2zyQoZ5fBCe7@Kcx7n94bboY+>|Ns9pGBGhQWbXmVXI3zU}aNehq^5QLCm0npt4AWJ|r$Rd#bdLWx<%eZWS7Tf7sZ~*`+LnY$? literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Calcutta b/lib/pytz/zoneinfo/Asia/Calcutta new file mode 100644 index 0000000000000000000000000000000000000000..e1cfcb8d09dc8e16f828082396f5095a367881e7 GIT binary patch literal 303 zcmWHE%1kq2zyK^j5fBCeHXsJEg&KfF``kUdPTlU&IKx^fab~K~ic2LzGZ>kefslbA z=mt;$gSQ(EcI*|wL XL;;W!J#adcfq@I?c3o2|V*@S#`D;p@ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Chita b/lib/pytz/zoneinfo/Asia/Chita new file mode 100644 index 0000000000000000000000000000000000000000..3baf7528268ac85f44ea7672ee0827901a302740 GIT binary patch literal 1243 zcmdVZO-Pe*9KiAa+I&GJewWf(=1SMHsp%@&(rL9!bd#b+Q4$EfP!=(ws30=v^)TXh zD1zu<^adeadO4W46e>ihJXr_94s|h!2qa?tzfTQDojUe^c%IL*{~iY0@4Mu#gZo3~ zuT^Y!I9V}!axb^u&GtXJJ!0kswx#BmU79Q{yf{-j`>dzz(_m`B#PsCC(bklAD4~l6 zqFR2lP8X*uwBod=@32dkbbryM@hPor8rQ1ei2A)xby@L!4S3#4;Onp~|M1*bee#g6 zdi0F(B*qJ(Bh3Ug(ClYZBY@KsWkN zNLwteoAP!_d*u;rpFXY~?k(CewnICoLK=S<(ZuLx>AK?8Wc;Ps{%y?Hm&s&Y?m52> zPhQTEUy$=6_l-aA3k%$CV|sh+&uUU~A@RLY=dJ)N5KwmbrOdgjFQD3sB?VZOoEGUzT@~D2Z3$g3Z`R583(EH9!0;cU8z&ms6f+D(u>^!arWwk0%0HhpZ+G)*z2EOUoUWn2znpG;Ifba) z;iIf5;Sxd_;;;~Frtk_;Z#qVJcIao`FBMB7nz*R%d3|3J(PV-j{&q_L@a^^A?eqQl zISr5wNDHI~(gf*(v_bkHjgU@AE2J0F4C#inL;4{N2kMSUOQa{#6zPhzMfxI*k5jBV`Xd`ac7SXF*#oi(WEaRbkbNK NG@Q{p%f;r0e*s7H(fa@Z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Chongqing b/lib/pytz/zoneinfo/Asia/Chongqing new file mode 100644 index 0000000000000000000000000000000000000000..ce9e00a5db7c447fde613c59b214e8070f30ba2f GIT binary patch literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_%_aH+Jx!f+XbhEXAdf{Xnt zjr#xp|1&Z%voNu;F)=YPc-;Z2XYhK$z{0@b8v!yRB!Q8Sfgw7BQ3ymb0LhR92A~K? z*2g!5!54_N4NQ#w#)$?kO%Cu($1WO|6U#xB$>~PwoH! literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Dacca b/lib/pytz/zoneinfo/Asia/Dacca new file mode 100644 index 0000000000000000000000000000000000000000..776f27dac064a5925a2ca5c3cd4c3c60b0e4af37 GIT binary patch literal 361 zcmWHE%1kq2zyNGO5fBCe4j=}xWg39QsoQNE&af6roSAC0;_iw|GrX*i2zcAvm{9-! z|9?g%W+oO^78VAEkRw2)3_%wdfb8fDkO{sK417TG1O^cXhNJ>U2_N4O1`i}q$?i=q$0+{1+IVv2o@4y7yAVl$Aij`CKx>?zg1nZXG!*i-XjL`e8&M(CmvGxYZfJ1lso z2s`gFrdT4^o$>3MnA#d#|J>};8EG(jbH zSLvj#0?V_vT_(4;sg#EIbZV7QUYVws=3G>1sgKL_s83Z!WS3qR_^P#RcgkyL(PLq?mi?R_V=sex;?Ta z?RDiZ?$xEUwyQPXU3$%EjVg=WsLQ_Vw91Elx}v|@s{GQU*B;2T)*YRx*LTEPRb5F^ ztqZZL+h)i|vVXQ}Dz3;!7f)4>Wqu_$1Yc6MbC1i~p|h$k=nGwU>K(Q5hhw_FXPE9U)sn_JnK-*%h)aWM9a}kewl0L-vMj4%r>DJ!F5#23?&UB3pEI z_K0i}*(I_~WS{76loxi&Ia}q}E3#Q+x5##p{URIY*fFwYS7*=2rd^#~BirWKH?nbL z=g8JM_Ks|xWB17RIrfh^gZq#H;(kbWQy zK{|r81nCLV6r?LiTadmWjX^qtw8qux4bmK>J4kzw{vZuPI)tp}Lpq1F4(Xk%(>$bmu1@=q z{vi!SI*7Co=^@fYq>D%!kv<}gL^_GI66qzU2_N4O1`iAKwrLZ37D+wggE4K?n&J0nPmnvJ6CnEClHXSqh>-7K1=NP>yKJ Nxom(AvD3BW0swcSF2Mi* literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Dubai b/lib/pytz/zoneinfo/Asia/Dubai new file mode 100644 index 0000000000000000000000000000000000000000..7880d5d7c92a4a5fd994630588e9d74fbd636edb GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3^rY5J30zG$ATnqsf`5@$-a32tpK8h%tyzTB0QOm#4^maGn0UT9m)plfuQe44kgY;L)-S?axVZ$Cu&tf{avp zWOV*h#{8y?=O1OF>s~zjBk~ xfe3;~f{231f(V01gNTF3g9wC3gouR5ga}QjQXyhtU1$F{xtRr3zc^LSd;ylKdI5|YK$W8Ab<;+SwjLrfF`|J?@? zbB?*M#@1ZAE`fk>4WB&$86P_ur_n98msd@+9Mx0pL*`@echd% zdmsJYpXkbUu>#|<>v{JMZ?2v0oA1eAew-d0K5)$O_wTop{;qLGe0Rwl*0; z-5#){d8wVecF20M(lN(GCOBim7tC>)=dJN$y3Le?BUZ}24)diupI8%qs5f8!zRn7K z({868-Q}bmjM?d(wNA$NH|&WG<<6wl+4d_HSx#p02zzoc$(a%uw5KKwIMWi7%xU-i z*7TnT%&hBot?Y}}%o!*9tYG&EGpFmgmD~QQnYU%X6{=}A^Q(HDg2+BQT%yj*oXvJ& zT9Y%YpjBoK#br*QOXu{jk)nTEbndsM^6JfZwYaBROU};Kc^wOM{^zOsS}ao+v=_<3 zMTuHk6Oh-Z{HkT8L$WCGik4^IlJdb{WO2fIS=@J7-n?@}-s=5YDz2W>$lgv_a`dn+ zZEO^?bC)h#u}9x-sMX5Z4H~T|*LPBu=<;BeE`Jcx6?WWy@BVaIdHPRXb;B>Kzetv< zlXvC)_&ura>JzK_hOF7~yM8eLjI6D?sx=u0q_(72V=tW2x-`|g-#*j&p(b5-xlPyi zNA<(6R%yefO|oHki8l6B%EtN({ivf*HZ3cbrdWz>&QF&uWq(O?vLP*bS~=NS$a5|Nryho$Qb?^5LC|NYp4RtK=hU%m{_~mCtAR9ua+tqyDdZaoqU}jy!Vg zk)w|se zBrhZ}Br_y6BsWhtIV3wIJtRLQK_o*YMI=WgNhC`oO(ahwQ6y6&RU}tWH(4ZGPd8m8 zUnF59VncO-cvdrvogB!5qL0+1O%rU01(WD<~BK&Ani z2V^3UnLwrjnG0kxklFBbrvsS}WI~V`L8b(m6J%15SwW@+nHOYYkeNZI2ALaVa*)|U zrpMErA7p}%8A7HAnImM9kXb^e37IEkqL7(FrV5!WWU`RiLZ-{poiAjXrVN=g oWYUmXL#7RxH++UB&hIW?P5~PjjD&Lwb3=LIU?e}}eVgh353H8ZHvj+t literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Gaza b/lib/pytz/zoneinfo/Asia/Gaza new file mode 100644 index 0000000000000000000000000000000000000000..cf54deb8f1d44eeb4281a3500415fdb1e4c7ff46 GIT binary patch literal 2286 zcmdtiZA?{l0LSrjeISKVBeOY22#F}ly{8ws0<;F@?hYU{@>%vmYZ)oXXk$YyLb1^ z-Ti(2gKz`g7WHtR8&EIb6?`HuT>VnNc;cS+<2R#bu(?v2owepq`-SX# z_j}_~MOWbRHfi>IHjDmIr#=vNP7GWT=8eHgYp{EVHWX~M{yK6?8}2$|Shb4+H(Rb* zw+aiik=^r+(Upe+V;kCx@u-NvZBL(ddn!kpm=|J9{CQrRoLFJqS)8rUsy`@a-R&}G zj}M5@j$||Pv%lq22X2~C7pBAlaa50f`?QGhb(>F@d@L767wfTwZm~$q)Z@a2WZci! z%*8i?a>*Cd_4vz!Vrl0BbJ?dAGU0&3bRJtJU8SA+^168GUj4P{zW#_zEYB55S-m3J zyF{eKw2Rc(P~mYj3D3b1`OK4LA}z$Lr%g4;6~n@O_K#AzGMH_qf1V|uI~8kYv@MjG z%^~LVjWcCd(OWXR#XHC$_m=%X!G@fkyu?0d&3Z9ODKNtIDnzf-=N;WIWD>9Ra(nNg9R zBrEQP8{$g1CmjV*V^<<@V*tkWQ^Qb zN8EpBZJuAt z$yv{fU-Nm@qt5o_xR3uKRy3uqhMqND$eNHvA*(``g{%u%7_u^CX~^1;#UZP+Rm(%x zXR8*7tPoiuvPNW)$SRR#BI`sJimVh_Dza8&vB+v|)pC*b+NuR3D@K;gFUOi~)uNGA zBg;nCjVv5lIkI$Q?a1Ph)g#MC){hhbser920a62`2uKx>G9Yz83V~DtDFspsq!_lU z8b~>idLRWsDuR>*sfn#B3Q`rMEVimHNMVr5Af-WSgA@m;4pJVZK1hL(3LzyzYJ?OC zsgkWK6H+InP)MbaQX#cMiiK1QDHl>Nq+m$Jkdh%aLyCq}4Jn(gsvA-`TU9xvbV%)x z;vv;T%7@esDIiioq=ZNfks=~hM9OHZ>WCE5R#g%yB~nYIm`F8|aw7Fa3W`(|DJfD@ zq^L+$k+LFnMG9-HDvOlXR@D|ME>c~jyhweK0wWd1|8I#yRid3q#5y4_*_Gn*EKf|x LOLV(ZIfecMp5^oY literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Harbin b/lib/pytz/zoneinfo/Asia/Harbin new file mode 100644 index 0000000000000000000000000000000000000000..ce9e00a5db7c447fde613c59b214e8070f30ba2f GIT binary patch literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_Bet)(T-xo`_79Ch_Pq72 zb+N3Y;nEgq_qjKUzL6-iKj4h$zbx$Q0~5|b=Qd-|SMU6B_@*(`aoTdK7dG5DaLu`y zpJxp3oM(-!*xxX^uEiP)p4D*6-Q(Pv%rM61`dQ<@pED-Lmpiu?rJFNq_sJP|I_#Na z{ldR3-VXZYPx<)X8+P#dNikm>F+<)sB|@`0?I()fmkUA)&9M9!vCv30!vh9o_>b4@ zMb~|D@n=)bh)V-vN&9?z>De+Fxz}Y!9bGA-i`&g*H4!pq)t7cm?*lTnG*iT-b&Gh< zVv!KqDiXu|h1=C6-1~;*laH2&BtMUtG+8H?4+;CJ--_i5U%H+AX_|ccWSE`OvOuOb z``ORbPnT&0ugmnJUioa=$BrkeO=LWL$IN{2XPMcz!&*5oSFSqiXJ&mjAy*$y7SDZf zMXo97va{bEKbDi&ZRQjN$ehM2=JS=m2y?>$`GU7gyy!V@u1#nW-mIH-t+M1eS#~$jst9^SR{T0-ZMr=sH-8=AynJ=G*wQxWZ2e+XY-{p4+gpDUJIYTv zR^u7*O6Ec5)zYJ4XZS9s(o-jPJ@k&XJL-tsJydB`1=h$aU#?X>SSVjRnPk;mNR_qC z^Q^iLL*D>4K{(tz?FhY9OKipUn3qHL^VFQD=R*?&19xdzw^NUDxWzkX<3$LiUAh4A~j7HDqtd=8)YX z+e7xJRU1Ths8w4;_K0i}*(I_~WS_`Jk)0x2MfQqp7TGPbU1YynwP9q(TD4_l&&a0v z<=C}WZ5!D)vTC0*0_gLt=lt?DMyPNbhmLy?Xm zEk$~YG!^M8(pIFeNMn)CBCSPwi!@iOx{I_|tNM#H80j$5Vx-4NlaVgt|GCZnD%w$T XE3T9Eb7Q*JhWJHgVxVyk!z8jf<%SZAD7A)q_o%R45M8rRWK#O+_RUA`%G+ ziG$OxhzJMc;;1h|yw+N@-ny%$>ZM&>T5F!agM*Ven9b~Gvd8^C@utM~rRt9pa=&mn zYux2sawyT1>mF*fJ?Z6gE4IJ=e%-dV2Rp8rhbL;~QT1ihd-}ROURh;Ri|0!!Ut!bJ z!jgV6%RU``E6?nteSW(~`p(TX{TDat7Y8QH%ah&uRYStO-g`m6SrRh?&7G2&US_hZ zIwU*3&JNB#B7><#cBrsR-q~XNzP~|+PmS0QU9Ea#-#zkMBF}a#i{dGaojL+Dr^Pw#!Kek$b8>lwl`%pZH&_S8#^~krk08ku{M; zkyVjpk#&)Uk(H69J>A;K;+}4GWO-zLqyVG>qy(e}qzI%6qzt4Eq!6SMq!dqA3sQ`y zs|G0tsRt;AI72svI|v5D z87#yi!DJ&ABL-q*lhplpHCcSk&97f-Gs`!49fh|fN~#rWWipcDqKsFo+XDWns|}LL(i(D<5re>Z&X>{i7a;= ztBRBxQSo_gRK6UGs)r+^`g%ur&$f-4!$skXEf}@Ykf>W7HtJ{UM8o8qY77m@rof|vD=9JajAJKiyy;gr|j~;MG zJmIVpJ({{KN56LU*z1VgjK|}S8 z7;+)W7_uSh81f+r88RX%8FC^?k*r8sBrlQ}$&92%awEx+>_~bfKXL-d86c;CoC9(a a$XOt#kihx3y}KI# literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Irkutsk b/lib/pytz/zoneinfo/Asia/Irkutsk new file mode 100644 index 0000000000000000000000000000000000000000..e8c53c0d63d4ab9cc7b7c95597e4cb490e782cce GIT binary patch literal 1267 zcmdVZUr19?9Ki9j+-h0i5KU`pr_9z`t2N7}&Ofzmtht9`M2rlqEQB6LL>NepOnQmv zA$yP>eq_EyPZ}XY2tE`@kVb_B{e9^r3?hiKb-s5DMm_b|y`1y8_iS9)J>Ml$y{Cif zuVb}YVQ@su;JPehraPr2k0wUvUFDwc%$myK#R})Js`&mSUHR#8X6?ey#dWiZjC(32 zRrlgjeLE~Q*Zi`+zee=g64`J}$;S8>scn8Qn|$+9=bn+xt0u%_f2TcP=k%73&jQ}Q zivjQRalLhBO#8-f>1}tfYX8kXz5PnB-f=#q1E=D8XV(P@o;xJYg@ll-aJ-iL`Pmb?Ju9yy|pDx$<*<#b#FW-(frQ>&n%c zNUE3_zQ7(c8qEmtSfy;aBX^UVlmD>Y=2vEqJhfXk}8rbk}Q%fk}i@jk}#4nk}{Grk~EUFrAZsf h+tMVCWR9ecLBrQNyn9`-!0NpX%Sf56``B z&&K{Z?(Pq;bl>gIhYsa~u-6P;bzd1lw;v-Xiq8=a#&Up9Iglja*W zr|jOMu=8fP&F+gt%)W1K*>C-_+d2N>Vq>7~hB=V#x6iMfFh3be8iRu;%nMihjnALH z?p)j!G%g*fG>2=w#^rd9^ToWK_Ls#soh!i&_K5eOIdZMW{_5s2bM#`fF*aZ}N4=x9T-JW9wH}34nH}zdRjJu!eOx@E^q1{{eVk)<^3Z#557P8Wi(}e--mTx-N=Ojfn+4pNfTh+U25-x5Wd`c8ijh{bKQk z_hsob2W8pPL$ZAJ3-ZC~Nm)_)gsiyrq>Sc2FQVhiW##00vE+lf^5M}cQPo>3mcBPb zRPQPhHC>umwmBdk=_rto#;%Fljlap|MS~(%>&RGVsk6d=-l{A7TCcnDonG(j*XxG{ z^p)Qp)mNS9)8iM;Sq=Nft;XYrt;bqhbz^V4_4vA1tkv5$S!<$a^+deRTASBsHB}$7 zntm>^)_u0fXiKNl9-sHWTp9j9FRw2%@J}z_l;CZb->+%;5%rDK^0#Oinl``0Gey%1 zW@$N^7G37Kiziy{-=F{WZ}@GzA)(c)I~H5ROF}CyDOYzH|5Y82I)A)#f6x;DVk_z+ zN;kbba0S^6vKv>m9b`YqhL9a0TSE4PYzo7Lh$7n?!brY!lh1tJ)~CQ)H{iUXjfryG6E(>=)e)^FgZ}16xM+jBFa&HL`7F z-^j*Y)y_F=-Bs-!**vm)SG9d)|40Ln4j?T+dVn;6Ll=-XAbmg@fph|Cg{$fX(hQ^< zNIQ^zAPqq}g0uwb3DOj#D@a?AzPPH!Ae}*4gY*Vz4$>W@JxG6$1|c0nT7>inX%f;U zq)kYlTvelxPPwX9A-zJHg>(yP7t$}JVMxc2mLWYunuc@@X&cfvSJgPAb4cr4Rqv4I zxvK6V?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^P()k~zA*rvYg|Hp1-RjH;{FD%RY E9a%VMFFT3M6xh2q$_|-$gE)CV_>M8z#ziFP|(02!N8DIz#!w}8^YiO z#M%btMg}0#7(`kEu?0i8rxVB!BoIP^TmJ)reVS(ihz7YCM1$N7qCsv4(IEGOXpkR3 SG{_$ynjU`P0(wu^oC^Rmt6tjx literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Jayapura b/lib/pytz/zoneinfo/Asia/Jayapura new file mode 100644 index 0000000000000000000000000000000000000000..a4c0829721cf845e1bf08332503e1fe742dca5fa GIT binary patch literal 237 zcmWHE%1kq2zyK^j5fBCeW*`Q!g?5P@oN*yy=Z7y_7iBOq0fn1ifaL2YfTUYzFmQm` ud_KM*4B7^kAZ%>F5bhZQQUnGeBv|$z2tXEsXpp5Knrw@?fUeNBR literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Jerusalem b/lib/pytz/zoneinfo/Asia/Jerusalem new file mode 100644 index 0000000000000000000000000000000000000000..2d14c9998e9dc54bd1ad4710332abafc8e811242 GIT binary patch literal 2256 zcmdtie@s&GJH>!w1ZFBdCNWLOC;MTB z#pblu{JCXp^uwWITXT`M>1-o7Da%yMryn3oaK|MmZc5Mlv{hSw^;cWpyK`S>_fN*> z9V}U1l`8&m;pP_}uF*WaM=SHS+n4>uiNyzXd(W4FZ$7>yI*wnpI~%LC-Mr5J_Ek%t zeDiC0s1Z6C4XtoH)(8xL5M^A6khle*V~?zRH%is-e_6g%`mp2u={`?{5Hay$tPmM&oQrJyh<^39Vs-#o==UjBZ; zf3ckrbD>Yax`Av6*%7iOWKYPZkX<3$LiUAh4A~j7HDqtDW^>5yknJJ+b2S@8c8F{d z*(0(^WS7V`k$oZ?MRtm871=AYS!B1!c3sVW`TMb9SF>Ye%gCOQO(VNTwvFr?**LOu zWb4S@k3_K!3G=>XCKqz6b7kS-u?K>C0*0_g zg>(vO71ArDSxC2#b|L*j8isVt)wB%hnX73U(lw-QNZ*jgadZx89Y^nw=5cfnX&*=b zkOp#e5NRROLs!#8q>D%!kv<}gL^_GI66qzS~(G(N$N| wR*t?RjpgVp(psdqNOST3+TBP~3uw9sorcEF2hE7#Qq57&v@< xLm0FTOhDM!03-zjAtYD;H1~gI+pGm38e|bje?5>*v}Ig2V5{wPO{|O!xB#TGCZ+%Y literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Kamchatka b/lib/pytz/zoneinfo/Asia/Kamchatka new file mode 100644 index 0000000000000000000000000000000000000000..b9ed49caca4bd81731958cc54adaa45bf84ee2f0 GIT binary patch literal 1184 zcmd7QOK1~O6oBDLn`*3rqSU^u_F)@iOifZP)wb!>ww9VyP_j}32VdZ0A>u+PQbh$p z1%)CSMHjw;h-6^}B|;ZLToma-5URofi{M7G&_z{<_n%ORxN_$mZobLPFp!)-zU$Dz zu=UqkV6L!Py=HU1%MSypx9Auca~iT$(x`$!h;iX|BB_EelVH*PT-Dw;Q@<=6cR| z($4wbj_BI#QT0Df=(>B`H89qrt>=Qe{zQWY_b=5@A|m1A)za2oFB^9Jl#O*I(jNLK z?Z0NEqh?k+KD?4mKi=!+OiDVZUuxI=taiV+qmlCuv?qB+?E@FI_v~%iGH_V?_MMjg z=9muj?Uk)%tvcA$FN0sBGE~|kLvI4I?UP5MPZmpTGMpQ}oIf_2&*xpGC56LXR&>mp zU-YH;#-HcrF}G!nW%yTY#)n){r);xbnX&u}+39JlMW%;jAs K@<<>QF8>WumKRU} literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Karachi b/lib/pytz/zoneinfo/Asia/Karachi new file mode 100644 index 0000000000000000000000000000000000000000..337e1d58620bad1d24a1e273c34d75e4071c1bdc GIT binary patch literal 403 zcmWHE%1kq2zyNGO5fBCeZXgD+WjpKsEIBjPXvL)xp&55qJXpcxzuLfNz3T?s`5Fs6 z92XdPa=S0^d~(;o>rJ%+BNHPtD+?GhFxcGznd2M5z{0=~ox#Y(z~C0ZAi%&7lE5ee zW=s0`hA?Owm>L@ZNi#5M$`Ifk90DXlKziXIgar5g2Lh0rK{UwSAR6R$5Djua$WV|U iKs3l7AR6Qs5DoGVhz9uyOauJ|qG{H(3?U?# X1~lkD$V8An{HAi*0L{14HRA#R1VtLz literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Kathmandu b/lib/pytz/zoneinfo/Asia/Kathmandu new file mode 100644 index 0000000000000000000000000000000000000000..2f810b1202a0edf2f9d0963cdc5004246369eeb8 GIT binary patch literal 224 zcmWHE%1kq2zyQoZ5fBCe7+YZBr`i@d34;~&|NsAIWMX1q@c#pn_l;m+VPFWj!@%X^ v8^WM%U}|gtB27#|l0Xnbf)zlM|AVXn=>u5>qDi!l%LZ(@ovx{si76KV$jT&Z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Katmandu b/lib/pytz/zoneinfo/Asia/Katmandu new file mode 100644 index 0000000000000000000000000000000000000000..2f810b1202a0edf2f9d0963cdc5004246369eeb8 GIT binary patch literal 224 zcmWHE%1kq2zyQoZ5fBCe7+YZBr`i@d34;~&|NsAIWMX1q@c#pn_l;m+VPFWj!@%X^ v8^WM%U}|gtB27#|l0Xnbf)zlM|AVXn=>u5>qDi!l%LZ(@ovx{si76KV$jT&Z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Khandyga b/lib/pytz/zoneinfo/Asia/Khandyga new file mode 100644 index 0000000000000000000000000000000000000000..2b2f5bfae4bfdbe898e256cb2d148b8bd17bdcc2 GIT binary patch literal 1297 zcmdVZPe_wt9KiAC)v0r4JcmqcnJc%JO>J8xTRN9clermDJ0*e8Un^onhk|e~^v4bo z&mjoJLs8a6!b5eK-qJ%MLFLIh2<}h>CekG$*7JQu7Z13kyofteD z6n{;nUg5=b=@-YF9(~Gvd--@)RC$l5H`fd=R@?8aRR5eBsQH>rZ&_Gg+&ULcJExOs z+eAdwjy0?8BXz3ohE%RghT74;s&*!pRDJ8ba{FeL$N5U_s(7ZnwvW>LV@B@Iy>>NR zyXb0|e=7IPJdnN@cV**~>$2(o1=)P7U+%q{l>XBZ88~@f1+T=^zDTdyf6Su})HbS? zV3}(9y-|gnZW;Q#qQbvQ<-zF>s&(OuY#aYBBiXmI{q}nq9i5QT)m-|}nQ_r^Gi!Es z4~y8TF|*4vD7s@q=HZfr=&2ttdzR0c@#0Q1{w{9zF1gLbbDx=<>kxhSD$OI8i;kue zZ$u`a&l|->Yn!d4U@I*vI4OLi#OAP<7j6~hHk%NcD*oiODfIEkL&Gk_kP;i#_v48;w_4#f|}5XBM262%k6lvVADVvFL-sy0S( zMzKcmMlnZmN3looM=?loNU=!qNHIxqX;s^#__V5xQk+t(QoK^kQruGPQv6!ghAEC2 zEK@u)n5MX9uubvJVBD&9PO)xPd#9MExTn}>@K1RLD9-_t(I)=)XTi~?pO^Ngu;UMw Cz6`_w literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Kolkata b/lib/pytz/zoneinfo/Asia/Kolkata new file mode 100644 index 0000000000000000000000000000000000000000..e1cfcb8d09dc8e16f828082396f5095a367881e7 GIT binary patch literal 303 zcmWHE%1kq2zyK^j5fBCeHXsJEg&KfF``kUdPTlU&IKx^fab~K~ic2LzGZ>kefslbA z=mt;$gSQ(EcI*|wL XL;;W!J#adcfq@I?c3o2|V*@S#`D;p@ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Krasnoyarsk b/lib/pytz/zoneinfo/Asia/Krasnoyarsk new file mode 100644 index 0000000000000000000000000000000000000000..59efd24ca55cea04557fd8792f5e6ecd10b3d596 GIT binary patch literal 1229 zcmdVZPe_wt9Ki8sIcvBjUP^1NmS#(*^=s9%rZzP*opy*t*dL1ih>?)=hdKlbSwTcB zs!MbUM|6nzMRf^J712QoBI+Pfk_8>Ih%OSbp6@#bqfQ-rUf%cf?A^;?`+b+(ey}%U z{#ssphm+M}Pxd7@re}YDFftLG6FQulTb4GZo{^H$={G}VpI@iujeE`fu^lP@XrC^) z-J^l)ox1QsOc$N1(O`c-7w;_A@@!%eR=KKvw2XTHe#YtJNp;Dt1wp467MyVAPvscxve zEN!i~bYtOhX|Fi1?NgVuqo7|qo*&iD51Tb{XRCIN9g^;gb(&0k3=U>88CSuK?BOoV zJ7&(xdy)UfpL>tnZOpb}e%j<7WA_6?E{`#L2aGRw&E0X?JGp0!eI|F`_`152CM;2V z*4wj=4>PXpa`xFv@*k#azgUI|BU9!Rm^3nN787^0Q%5F`OrJ#oNCijmR)~~{)QA*`REd;{)QJ>|REm^})QS{~REw00)Qc4CXe&lacCzo=F)-9kV31&7 zC}?1i@$n5|2nJ$p19K2IG60drAkq?uEkH)0fDjTK4s_T5It8=#l%bS=05RcBzr literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Kuching b/lib/pytz/zoneinfo/Asia/Kuching new file mode 100644 index 0000000000000000000000000000000000000000..4878622dde6ae385b1faf198c0931fa5a6e2248e GIT binary patch literal 507 zcmWHE%1kq2zyNGO5fBCeVIT&vCDwMaP1u$0-m$-0y41Z=YZ?w-T@Y~C?g7K$ zZI>?`RS<4CI)gpnIHNnm@k*-;Cmx?+I2pbF!l@(w8&2DN4mdqAg5i=!?uYvS|Nk>G zGoe5hW@ZM4^a7B5nH3By3=E|Pj9d&11q}=W3=DM>7(^g!AKwrLZ3A;-10ZQ(1SWwj zOOQ4=2qD2sK$rXnc?(2?yau8{-UHJ>FM?>0H^DT}s~{TWT`&#wGKdCw8%zVe4x&Nc x2h+eH0MVde0MVeJ0MVe}0MVcz0n@->0nwnK0ePYx$fjZNaM=KZ&Q8~Y3jpCF|9Ki86?!R0$N(#IcMKQ62TM&(h&{S9=&A^4ADD4s%RzvjvuoMvliFDFK z6mKuDA_;lvM)@R+b_3=yBx?b!)X?Gvb+Aqs*s-Dd^?B2sw z>Q!m2N)%VBM0VOv7WCN3M4$aSbKmZh?e?4ai0Z$ZR0H?!s=?C_)!UX<^{!@>dVlnU z`cRywhU!}E)clM}m$o_S>?S$9 z^LpE?{6|gZ!iyzV;lW17@Z{)iczu7^hX1d(5c>}C&FxddD@4;3W1bMP3nHJpvEbKp z&Q;Y>QBfNep*3L<3YUnA$|x@i!*J2J=s%2hPA|rbjM!C=85uP)Ze--h*pbmA<3|!e zGC)#5a&Xm2AX&KTG>|-yM3797RFGVdWRPr-bdY?IgpiDol#rZUby7%Ht~xCwFC;M} zGbA-6HzYYEJ0v|MKO{jULnK8cMF|9Ki86?!R0$N(#IcMKQ62TM&(h&{S9=&A^4ADD4s%RzvjvuoMvliFDFK z6mKuDA_;lvM)@R+b_3=yBx?b!)X?Gvb+Aqs*s-Dd^?B2sw z>Q!m2N)%VBM0VOv7WCN3M4$aSbKmZh?e?4ai0Z$ZR0H?!s=?C_)!UX<^{!@>dVlnU z`cRywhU!}E)clM}m$o_S>?S$9 z^LpE?{6|gZ!iyzV;lW17@Z{)iczu7^hX1d(5c>}C&FxddD@4;3W1bMP3nHJpvEbKp z&Q;Y>QBfNep*3L<3YUnA$|x@i!*J2J=s%2hPA|rbjM!C=85uP)Ze--h*pbmA<3|!e zGC)#5a&Xm2AX&KTG>|-yM3797RFGVdWRPr-bdY?IgpiDol#rZUby7%Ht~xCwFC;M} zGbA-6HzYYEJ0v|MKO{jULnK8cMClhrFFjD>H5ZOp}}?GeZ!CYy^YKF(oKu=h4pjZw0kxn z8}cpU8DA?KNA0rd%8E2+zevmR53)J&M!b=ivc>;cTHQI>T60r;jv4J+nbg}pPpY=z zBdTrThTi`Avi47B^^S)pbo-r@?zk4yotHXw;7qd)o=i&UqEB}AbV%3W8rkKklW_2> zgnxaPZnsl+e_W6~KbQ61*%^r}zSYr3^SbBNQysfLt@q__>)x|>_5Q1QInXz(Ki0a?e$5`Ci_!nVs@8$~;rvSGCDh zM)`vQGec$u`7mWG4QpMxW&Yua%{MD@%*avm2^=?a559FiT9 z9+ID>Nf61<(xiywh$M+*iKL0-i6n|-ilmCO>izJL>Y-v(Pa<(){BUvM9 dBY7iV(rBvJIEQ-W4!1F$3lDKY$bgNg%tR zfq{d8p>6^L511|B;~T=@3&h$67C>yt5bhb`2vQ9NAtYG-9|%AW0MQ^vfM}3IKr~ex L!v%7jt_2qW<)b^z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Manila b/lib/pytz/zoneinfo/Asia/Manila new file mode 100644 index 0000000000000000000000000000000000000000..2c9220c99b03e6aec1803b6c3f5683af1fd1c8ed GIT binary patch literal 350 zcmWHE%1kq2zyPd35fBCeP9O%c1sZ_F!8u1uiV2_Qbmfgl>>NDvKjD2N6*7DR&_45opO1{nr& iIEV&0o{^b}nFWZMm|?CjEC9)o>Ia|`fgaPf-~s>wP+^b& literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Muscat b/lib/pytz/zoneinfo/Asia/Muscat new file mode 100644 index 0000000000000000000000000000000000000000..7880d5d7c92a4a5fd994630588e9d74fbd636edb GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3^rqF(rhpu|nY}s_Jnc9QbPV-i-GEb#fYtAi8r(5m5 z$Yg}XzY-#9P-GGju7RsTPxL@E2zOwMF+w`6v5iOxDx!vL=X;=6dll@>&gI_E<)ZKY z-(P6e#&DkJUr&tl3vZr?^XB{bW1l8}H+J}I+dH(^ihWjRkGpWq7~flHPS|zFdZp86 zO6yW9Zo{ZKvC1|k1t;6D=3h4AQ!kmXP3kogqK}#h54()l@9s1w|JZ1}aiziZo$Is` zPwudj4u!4c?s_|A+d^wfQ@K5LO{O)iBEwEC8fU%fkF}@!MywgJ!**IstdaKEYo`A; zY-Id&-^{%FgE4bp(De6yV`TN5GP67P897_`nt{4jBe$mC&I|6b@{84;m9@nxNNTZX z=e5i1(TL3P_2`_TbyE0Oo6bF7B5&WS)}p>zEj~L}-|3pK^A0BJyWv!w-&rW{mBnaD zolh1_|3gblMx`v~do54BE#)J>%cAH@vS{$SEWUeGmh_*HiW?U-xVu{_Pae^w&COzT z@6cr{cj^00^;-2-lZGnFb$LRiuJC8*iYEcBjxUqypC{@EkJDw<=|{TyrdQS+j+2^! z`?5CjP-=Sy#jL$4>$cz1_4CfihMF5%mvTVri~BYF^0(TMq}uT3er+6W(T&$Tbkk5s zKRmu#o33q^kG?F{=DsTVxG_aP=_-)T%Zj8WoFH3rlVxk^Q)!L!NLx<4wmtY&+9y2G zcI&EijQpaXo$8a%2hZxZ1DADs|5y4&N3TY9NA#tr7kfpI`A=USPs&1WF*6V~#^Xtx z;u-t=lV2)=Ax~*(6(1q~Dk{qT2))2<|Lr{7H~-F!BX^G6I&$yG%_Db@+&*&uNCQX* zNDD|0NE1jGNE@zBA4nreCrB$uFGw>;H%L23KS)DJM@UOZPe@ZpS4dk(U#?DLNM}fE zNN-4UNOwqkNPkF!NQX#^NRLR9NS8>PNT04wqe!Q&POC_-NV7<{NV`bCNW)0SNXtmi zNYhByNZUx?NaIN7u1@Pn@2*bsNcTwlNdL$NAUl9;0kQ|kCLp_jYy+|n$VMPL;p%J! zvKOw-W+1zPYzML*$c7+0f@}%0C&;ECyMk;BvM4YD_`&gLMygKQ77Kgb3l zJA`ZzvPZ}!A-jZZ6S7apMj<Z91c0qO^C*L2;4Y=QCdH(?jq|NOB literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Novokuznetsk b/lib/pytz/zoneinfo/Asia/Novokuznetsk new file mode 100644 index 0000000000000000000000000000000000000000..2576a3b01642c32acde9fd4ea02df316b71ec709 GIT binary patch literal 1183 zcmd7QPe_wt9Ki8sIm?`cmvXgM%VkTa^=s8!O>N7T`7bEe!TwP6M+}6dKU5GXWCani zs4mgX5fKr;urA?YR73|Uh^T`^NfLC(I&_JM^?Y9u7M;5FjQ9OKd*641?e|UZJlq{I zf34Z}3I{7`5BBAEyn)=qq4B`n(2>kMZ`M?l43$^>cstj{!N#L$F<7$N|$+_XvqClLf=2g@`?9}>QfgI z)vq5*&C_8CKgddL_PW$vJ1;8+Ps+;UJt7CYWYwNQjU3&ps~fv@&Gw|O^>5K=B&g9} zH5&6JB=)XM*Zr)N^&^uSA9G8?Esr!l`Xo)~zQ~3f&n0o_r6kWz=*HB2X+H2wH&tDg zRP$}!Tzo=Wf)}-A@`|<=^=j*jW7_s{tG3_Up&g^A6P=e+x&C}U?<$%x^>P;%yk^cS zcvE=e&%J%jZOpcu{WDE^pRxPC0auAJ`}<7U^m+P<%U+p!hPxe|X%m*HJ!1B#=L?LB z+CcGI(V8NB~F#NC-#_NDxRA zNEk>QNFa_j5+oEP7DpQl5)Bd#5)Tp(5)l#-5)%>>5)~2_5*HE}5}Bh74T;Us28Tq4 zgonh31c*e4gown51c^k6go(t71d2q8go?!KXoE$fb+q9k@ge~u5hEdEzn%Sm8*^!c Kjl8L@zVtVQ`2r{a literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Novosibirsk b/lib/pytz/zoneinfo/Asia/Novosibirsk new file mode 100644 index 0000000000000000000000000000000000000000..95e3c73bcc7aef0eabeca2d943eedd6dc1747fb0 GIT binary patch literal 1241 zcmdVZUr19?9Ki9joNmkr2}mddms>jhoh&>D`sh3_V%9pTO=X*s6dWar7mwP{Fm&=9i_g(V9$zvh$ z$MTpvoUC?pvM(FUdutwz&r~f7oJlXQ$cS?Hcv<8q)3jXX7^_g+mFghwXTp5uY2hK_B$Z^?t=!OoRJ*_vj4Cu{!cIz$v zy*eCf)Zrfy9r5+Z$aJONI%mmk+3z}?8-+8*m&#ly0r`j+nacvp88UDTZ~FYB&%y*mD&Uw7v&$i(%SPR74l`)=m+ zV4+ZO6fK%RoW;K#B`!X=T_t}#oGzzRh(wkjIeA!^eQLLw5eAW!2n7)$6@`R(7ZG6) z5i~KniXba5MCc8=3W);gMuZ@{=pu#kCd}6NoG}=6)n(^!&hOi|!(e-#C64u-30i+0 z`DTZUquE@n-J{QB;>FlvpwM?UxxRSZUF3RNUi9Pht>Q29NzbguT{7L5^iBEm84 zyMI(S+=%MND|K3a)~}noOLcQ>tya|k(k=c)t@M7>t@-cO=Ufrr(t>Q8{~E8lcqd*p z^G>$EekK0r1f&L}2&4+645SXE z5Tp{M6r>iU7^E6oQw~xOQjo2w2q_7v2`LJx3MmVz3n>h#3@Ht%4Ji((4k^#p)Q1#k zYbr!aL~2BeM5;u}MCwEeMJh!~MQTNgMXE*0Me0Qgwlx(aCEJ>sk)n~Rk+PAxk-~A% RH1Pjap4VXh0ZrARyx(YH6yN{= literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Oral b/lib/pytz/zoneinfo/Asia/Oral new file mode 100644 index 0000000000000000000000000000000000000000..e36aec475db6fbd8c4fc79bc03332a8fca64bacf GIT binary patch literal 1025 zcmd6lF=$g!6hL3vehq1n4%#Hf+NjmEiLEhhLTb}mm*S8@kPm`bxPum2or3dj{{_0aXcICcgFWi*lldoKE_O=@sx#tGYTypu&Sy#wSxWbn2 ziisI1t`^+TW?4>rh`HgFekncA%gDQS8NC&ilaJP9?Ba%;s_t@g^?E(j)ZEhCq}$u^ zd+!Xhy$1erdY&=aZv7S}oi#1Sj8{z97+*qdoJP+-Z^kPjeOnwe?T!2Lgl{t00iC=~ zL5EMPE5vF%a{B+^ME^z|FAz8U9zPIA5KjTs6~q_B8N?gJ9mF5RA;crZCB!GhDa0$p zEyORxF~l>(bwKqEaSrhwP~AiPLmGf|0BHfz1EdK^7YaigkUk)dKstf60_i27HUsG< optb|)2htFvBS=e-o*+#@x`MO?=?l)OlmE4`NJ%Rk?aN1g0lS;ty#N3J literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Phnom_Penh b/lib/pytz/zoneinfo/Asia/Phnom_Penh new file mode 100644 index 0000000000000000000000000000000000000000..7249640294cd596c9445ce175455efa06b5dc6c3 GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Pontianak b/lib/pytz/zoneinfo/Asia/Pontianak new file mode 100644 index 0000000000000000000000000000000000000000..9377d03831b60a35283cacbdee4f5a2ac6251642 GIT binary patch literal 381 zcmWHE%1kq2zyRz(5fBCe4j=}x<-7m)G3*k%&v3eO3CE?dcLmpNj&a=jug36Y>%I=f zTO|#QOw3FyOss4S3~3EO^&pakfg!U3WJ28p20jLcf(8a5Fe%2skW|1R?c*E55CFv5 z2Ij^FK++P3Ef~T*LmYvG6UYoC5JG}e{{sQYxgZ+kWDpH3P;0J|Ps^8f$< literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Pyongyang b/lib/pytz/zoneinfo/Asia/Pyongyang new file mode 100644 index 0000000000000000000000000000000000000000..dd54989fabec15b8a7f9b3fce29fc63c02b457ed GIT binary patch literal 253 zcmWHE%1kq2zyK^j5fBCeRv-qkdAhHEoHOZ}*pEy4i3OqOm_$z4qdfI`boXeX>t)jD_`PvPo}6%Gw=1)Z2~w>INFc{d+1s2PgU7#W&LXLoXWSayt|z9ow4zpi44Z<9JVvgV~p`J&bO?& z3;3|FP{9er3%|z=#1F)gN9zgV3gQdm4B`#q4&o2u5aJQy65*b>N81ae8IQIbNIQ^zAPqq} dg0uwb3DOj#D@a>#**dw5eT9;?ymWs&bOuOB+M@sf literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Rangoon b/lib/pytz/zoneinfo/Asia/Rangoon new file mode 100644 index 0000000000000000000000000000000000000000..a00282de340141690412a40fb70cb6d7631ff401 GIT binary patch literal 288 zcmWHE%1kq2zyPd35fBCe7+a_T$XWQQIPmnRKN1&brGKdZ|NlQD6EhPN14EQPNF|VD zVPJ^PVBlb2sGGpR2Vwj8hA;#Hv9^Jku>p{@1Sy4r5E85h+V($eTjxa(4RQvE1~~;p agPa30vL48$&`Df2V29f2npqheZ~*{I;66hD literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Riyadh b/lib/pytz/zoneinfo/Asia/Riyadh new file mode 100644 index 0000000000000000000000000000000000000000..b2f9a2559a1ca4306a43c2abf074e91d51a64edc GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@Om&wAq&W|Ns9pGBPk|p8-i}88EQ;_=YfO8yJJQ3?U?# X1~lkD$V8An{HAi*0L{14HRb{Ue7_k- literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Saigon b/lib/pytz/zoneinfo/Asia/Saigon new file mode 100644 index 0000000000000000000000000000000000000000..eab94fe8985949b5d98cd003d26d9c2e70e2d0d6 GIT binary patch literal 375 zcmWHE%1kq2zyNGO5fBCeE+7W6MLT+&8zwH+<~nQotl?thxq?g9b|0=9?G|{l?McI1 z{TU2gw#OOj|NsBb$i&RT#0-Q?3=AnC6Bv@eF|aT&Bo#1lGB6Z0Fz_-k)JbA->Us9Ke&C!U_p%(~x!*+Ry4pE>Fltm=b zq0Fv-Zo`XaNv7YZ=gd#e0>-Xos?|%<$u>C&CL;a^B z^2aQ-C)`Zb-aN0qVqQ$ngeJ_Iz|KTzSt{o(KJ&^uf4igX^Jrr2>`ZRmv_Ij?hSd58 zRjPbsm8uwas|^?DRAuUusyh5eZHzrr{+3Bq9lWnx50JTZZ}akKg3xYVBC zFSW0)nJrTnOz?5aY`uHL)ZL1k`pfO6;ar0Wov1Y7qkSrJHlVh()vN6Xyn07@g>DSb z>&73Vn|!6x^!B}O{{BsNWv6t@?5wnoy_2>~R@#T2%I@^2betTM&Py5H)qPc>$4B&@ zntthy4(PoFG3oIi(>-5K>t1)4?tR&-_kF0=v4=t3H{B`m8!L1&mN5svPRL+BpLe;J zEL@&~U#`NU-`*4!{q6D;c|0OR&-s4`Zi;=rmva@1q#w!h#n;6XmpvK2BP$oq`{GFn zhMFZ9j@UbF?+{-iF2;G{CHW6)wZA6MYANgG^Q@S%X3DCa+I3S_PFXu;^_2Bf2v8VM zC{Q?1NKjZ%Xi#`ih)|eNs8F~#wPYx480a{)d?+v~#NlAmNx4_E*I literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Samarkand b/lib/pytz/zoneinfo/Asia/Samarkand new file mode 100644 index 0000000000000000000000000000000000000000..a5d1e97004ff763fab160e6c8a2bcbfad7cf67fc GIT binary patch literal 605 zcmcK0y-UMD7=ZCNX{)8};G+GAAMrD45sFwppp;JHP$5G=5Qk0$!Bt#@E;{L-;2^k( zLmb>hTm^@BcW`lW&_P`UOT4EJB2EtGxO>7u!rd=e-)zpxuczDWFnJ1Q^4@cr-(R>m zY1ewagRn1m?PinbZub54IQRGx=I=bWaJv&amwVcOwygt4O&vU3)1loJb+?vucw<^e z>W+?9GCEdl>$vl*6X|>P5^3Li`|u|pp8ToCRjCz4k!8o?G24tp*N3j`<%6p2oa21SIT zLXn~9P=qK-6e)@pMU0|Gk)!BQ1SyIXNs4BtMwFsTk)X2h6@O60tK-))UcWb~c(Z&*#@q8^74O<-WqdF#tWa2-FhMadeS%W6 z(*$Kd&k2l7%#18ZkeL+-85qhrKsJ|mFt9K%)JDI0V9u(ZwP~T za0r7J5PQ3XfRw{Q2nnA04+J2OfoPEDKs3mMAR6RJ5DoGuhz5BUM1wpGqCuVp(IAh5 zX`tspG$;VTG%yfAG$F&G$>#|jt2z}hz11^hz11`h^ARUaREb6 H*OChW=SsNu literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Shanghai b/lib/pytz/zoneinfo/Asia/Shanghai new file mode 100644 index 0000000000000000000000000000000000000000..ce9e00a5db7c447fde613c59b214e8070f30ba2f GIT binary patch literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_rh`G9q1pkq1OF0Ljb>1|bH9x(N&t3=9Pg z3^G2xAq>GltZiTp!bS!l(ilWq0c_0pO&roTwju|?Sydndl|YuO%nD$SmZ?5Mmhe%FlluTAQmE0d~a z?1*YvxTbf#y{H4zX}$aLaou_+q1&!Rb^C>O9X!>jtrKwxo%c&ePn&d(Y>++PYU#4R zO4qON((NhN-Jcd@-_KROe|AQ~OYe2$$-M4)^IS)-PU{1iTe|o3U48I!R{93UbpOel za;SM!5A=&1E{o_v--rx;J1RrYP8s?Tmf_De5_{p5_*_UQZsa8u%Px=S^LdAJUEy?< zm7MF#OJ0<|@#os@aw&DYfuDBbp)%{KoWreD=B27C-itd9vr~LVnP-aosw$pHselzU zSIArzAEq3I!(JC|sehQR`C@q{j7*tNVA9C6k%`-ysUwp|rjHbWRDhI#)PNL$RDqO% z)PWR&RDzU()PfX)RAXz(LF(b4AX`%rQW8=VQWR1ZQWjDdQW#PhQW{blQXEnpQl71; z4=K>rREU&_)QA*`REd;{)QJ>|REm^})QS{~REw00)Qc2sYbr)cwly^)MI%)sWg~SX Vh2yws;Qy(-B4Yjl(N@c<_zfbFA+`Vj literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Taipei b/lib/pytz/zoneinfo/Asia/Taipei new file mode 100644 index 0000000000000000000000000000000000000000..f9cbe672ab1aa9b6c47f7fed0a0ccd73845a10ec GIT binary patch literal 781 zcmci9Jt)L+9LMqRc|6B}$)YeQgUDnM_Z6^k;GMK_?7 z+~DyJ=lS70|6*XcI=?qI3ya_N`@gP#x7_FLv~;wW$&Zt4-*7oa_VPVb+s8^%o!)Z% zdV92A?^Ms5-P!`#^99U)MLawFEi$Y5SU{BT7q>$l#c`dAc&b z-uN0E@isbAZ?Ct;;fLSH`NLpwdwPN<2N@0-4;c^{5g8I06B!g46&dzZJ1#OXGBPqW zGBz?eGCDFmGCmRjiGYMaVjw|~C`g!3Z5$*J5(x=~#6p50(U5RRJfBqkCR N|0Sx&wk|IBd;k)YUNHaw literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Tashkent b/lib/pytz/zoneinfo/Asia/Tashkent new file mode 100644 index 0000000000000000000000000000000000000000..e75bb365a78e6dd0a7ce4d75c8d36ccdb8f7f7c4 GIT binary patch literal 621 zcmci9Jxjwt9DwmVjasWh7j4s4t*=pn5QHkING3t3&>@3}Lvc_F4xOBo4z3P<0Ed8^ zxG1=Z+y`)Rc5$eKgLV4#I)VWh$MxD5T#Xk26n=(7v}*+1qNEyWTVT^BpsIvTlZsR?YDKf+_5}W@IyO zMoWEWZ1K~KyG=8ZdoYtdcgC?keCPGWpK3gprzAHH5T79l`U;2lU%G$p9S8~qlaer?gQ_{a^0L_Y0RN{9{rC)B~ zW{q|mnc(zRpFXp`9h}{a>GI>W8C(0L$Lm|>-0Ev><{HK{UilNtJ>MtKe_2=x!_aAq z?P`wp-G7XZcw2(OtDX0)dx>}~rc^$ZKZI5G16n1Ny5Ko`m2!>R*IHXAj+`{_xQ0D< zrY==fWm1+wJu0&(v%nXXa^z|5cGk*2Xp&zPR-dmq5M2;udqf0LSrvt5n2NZHb7a4MAzg{5w;fr_0Q!XrC!sH7#0O(FI-ThFW_-i1t7n z+SrpyL>&n68qqjZ@}wjPLP~-Q2SS2Gkc{^oCvkFc@SnW-%*>%lli&9ROE*@!tUsP; z^9zTk!W=$N>Z;kT9}dRq(KpV?F-;DCOo!i&d7t4QTU@1M=Lb}r=bessj8jR8EjnrN zq?$2sLeK1*tde`~%aqe4!qG8A&)RoLIqN;rF25;K3pZ(3&PU~Teb8xBo7L?2DKdT7 z1(E)xM0?&mSK`TGIp3|xOC^3S*Fg`LrA(ZOqSacib1sD7&p%j(pU{90X<{YfoNIV*z+ z(?#*9N4mr+RV5#W%Vn=R#PSF2az%ffD7|t^mv!f=mB&8IRR>zd>gMTsP1Pl}c3F=u z54hAi@lvi&t`r+4#_5WYovPw{o~(R-PgK3QE35ApiH&_Bz3K9PwfT%&)*S5>wQZHU zuJN6!U)LqK6eo(U^Alu)r&}~mE7DC9o~q{P1G4$sNYV1PS8p5isqF(^z2j!TYVB!| zJ5PNTyV|?;?tRH>Pu(-Qw|tG*8w!OYBBO>xMGpO^uSmmjkuf8KMn;Vc8yPn;@UUj&$k36o zBZEgqj|?9fKN0{E0TKcd0}=!h1ri1l2NDPp2@(nt3la z6%tlh6BiN~5*ZR25*rd65*-pA5+4#E5+M>I5+f2M5+xF5SQ94_C=w|WDiSLaED|je zE)p*iFcL8mG7>WqG!iuuHWGJO6F3q%5;_t)56jzBYpzV*O<5f literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Tel_Aviv b/lib/pytz/zoneinfo/Asia/Tel_Aviv new file mode 100644 index 0000000000000000000000000000000000000000..2d14c9998e9dc54bd1ad4710332abafc8e811242 GIT binary patch literal 2256 zcmdtie@s&GJH>!w1ZFBdCNWLOC;MTB z#pblu{JCXp^uwWITXT`M>1-o7Da%yMryn3oaK|MmZc5Mlv{hSw^;cWpyK`S>_fN*> z9V}U1l`8&m;pP_}uF*WaM=SHS+n4>uiNyzXd(W4FZ$7>yI*wnpI~%LC-Mr5J_Ek%t zeDiC0s1Z6C4XtoH)(8xL5M^A6khle*V~?zRH%is-e_6g%`mp2u={`?{5Hay$tPmM&oQrJyh<^39Vs-#o==UjBZ; zf3ckrbD>Yax`Av6*%7iOWKYPZkX<3$LiUAh4A~j7HDqtDW^>5yknJJ+b2S@8c8F{d z*(0(^WS7V`k$oZ?MRtm871=AYS!B1!c3sVW`TMb9SF>Ye%gCOQO(VNTwvFr?**LOu zWb4S@k3_K!3G=>XCKqz6b7kS-u?K>C0*0_g zg>(vO71ArDSxC2#b|L*j8isVt)wB%hnX73U(lw-QNZ*jgadZx89Y^nw=5cfnX&*=b zkOp#e5NRROLs!#8q>D%!kv<}gL^_GI66qzS~(G(N$N| wR*t?RjpgVp(psdqNOST3+TBP~u)1J-1zaU;O1HD54YJFKHOd_`{B;B zM<4F?{Qtnr$OM5549(0y^$a}=7=fDWCNOY7NFU!21}_&N4h{iHGlFmk36A&=1gVFX r6o6=uW56`fK_D9BC=d;D7>EWr4om|b2%-oMSj5>Afd3oQ@vv)6p?e|^k@Ts1V z@z-+eJDjX$eX=hZ`n><$$nZ@0iolu7%Cf9c>KZO7{rR@PY<4oUYRY4*9y^lpj`pfG zk2;j^UaMMrBdXS2u2JQueQJH@a#fMcDSz~<+7O&kmEJdMqvw?hm|sL-{)5=`>AkGF za9vhSycE^1pNQbotf5fC+ zS$w7|&7No4+EYeQg!LKGXB{77IP@|c_SU|n{$alQE9ICmGG{)3StIjiF>_lzcVza+ z{8==Bbbz#g^nf&hbb+*i^no;jbb_>k^nx^lbYrXALHa=&veg|SEg?N2O(9(&Z6SRj zjUk;Ots%W3%^}?(?b+)7kOpmahe(S^k4Te9m$tf1q)((#q*J6-q*tU_q+6t2q+g_A pq+?s%GSaiHZW`$tX&dPqX&mVsX&w7?6aPc+?wI~3G}P9)e*^u27jOUo literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Ujung_Pandang b/lib/pytz/zoneinfo/Asia/Ujung_Pandang new file mode 100644 index 0000000000000000000000000000000000000000..ed55442e2917b4bbccce34f3e1c531d1f3284ac4 GIT binary patch literal 274 zcmWHE%1kq2zyPd35fBCe79a+(MHhaGov=&n>V(rBvJIEQ-W4!1F$3lDKY$bgNg%tR zfq{d8p>6^L511|B;~T=@3&h$67C>yt5bhb`2vQ9NAtYG-9|%AW0MQ^vfM}3IKr~ex L!v%7jt_2qW<)b^z literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Ulaanbaatar b/lib/pytz/zoneinfo/Asia/Ulaanbaatar new file mode 100644 index 0000000000000000000000000000000000000000..82fd47609e125ee799e3d3be4489d6cfd4782abb GIT binary patch literal 907 zcmciAJ1j$C7=YowR3b`T5(%pA_bS!ay{-F12T~-|KpKe{{6nV_{-H4;XD}d!GuYS& z2BX1Xa~Z_QV3Ctxlhk>;nk>HNq9=DHeBP?M;zmM zlZ6NRRK~oV`dZVcUx(yIGMTibr~UDjE=1JFJ{BbFSl29>LP#aD$3kJ@ba$FhxA}DO zmDGg)uv+sUC0Q>&WW~sukyRt>Mpllj9a%lHek1{s0ZD=6K$0L?kTgghBvDE;6GJK_ z7eg{68$&uIA45VUBST6gCz2G&iljyIB8idANNOZEk{rp7q(|~2CxDy*atg>fASZ#G Y1#%iG&GSG`1pnzwvi#=J1YKQOKO>mJNdN!< literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Ulan_Bator b/lib/pytz/zoneinfo/Asia/Ulan_Bator new file mode 100644 index 0000000000000000000000000000000000000000..82fd47609e125ee799e3d3be4489d6cfd4782abb GIT binary patch literal 907 zcmciAJ1j$C7=YowR3b`T5(%pA_bS!ay{-F12T~-|KpKe{{6nV_{-H4;XD}d!GuYS& z2BX1Xa~Z_QV3Ctxlhk>;nk>HNq9=DHeBP?M;zmM zlZ6NRRK~oV`dZVcUx(yIGMTibr~UDjE=1JFJ{BbFSl29>LP#aD$3kJ@ba$FhxA}DO zmDGg)uv+sUC0Q>&WW~sukyRt>Mpllj9a%lHek1{s0ZD=6K$0L?kTgghBvDE;6GJK_ z7eg{68$&uIA45VUBST6gCz2G&iljyIB8idANNOZEk{rp7q(|~2CxDy*atg>fASZ#G Y1#%iG&GSG`1pnzwvi#=J1YKQOKO>mJNdN!< literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Urumqi b/lib/pytz/zoneinfo/Asia/Urumqi new file mode 100644 index 0000000000000000000000000000000000000000..0342b433180b416a02c5ff8764e7a0a57921091b GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3mzg;Qy|NsAIWMp6nk^xDDBrve}_=YfO8<>H(3?U?# X1~lkD$V8An{HAi*0L{14HRA#R1VtLz literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Ust-Nera b/lib/pytz/zoneinfo/Asia/Ust-Nera new file mode 100644 index 0000000000000000000000000000000000000000..c0c3767e38042e4d9d1d7c2bb102e1ffdaa2602d GIT binary patch literal 1276 zcmdVZPe_wt9Ki8sZOdsLBuZDd(v~e->b6W~)3sVEwynf=$?DV}s1f{#4pNXoe;~3y zhv+b(KU7#?(ZM8!haHLzNrXrTt5bE5ARc0uB3RG&-DTLRW6#U8@B2RQ80>vMZ+h_P zk&yapEH*1lM%+x!-S<9x%00jTN-gv4ks5a2$XfqnX{^i2xRCw#K0t3}!dDrK{$TH1r( zr2Y2~3D;HV@W&bH_%)}uOioB-_Kog*IIX*KPjvLcxZaw*relY1>TT!7WPA6Jjvu@# zI~oUdcU+{$*{OTI1Je6_uk_j5r0;D+c7Cpr#8ZzXr$Rb)r6{|O7h>r|P7fE0McV?0 z-8!5L%Z>_H*+uz{`RA2RmrJROJv?7Is~k$5xvtDg_8E`*QQlTP|J-KQNA9Yn+*Rgo z={u|>Q)%T7gw53`UA^G}OxJ7wz`Y(Iqng@IK=fx>}8 zg2IAAgTjMCgu;YEg~EkGhQfwIhr)+Kh{A|M$*SQ*A;o}|RYQxyi$aXTj6#jVjY5vX zjzW*Zk3x{bkV28d(W)UyVQJOSr0}E=r7)#XrEsN?rLd*YrSPQ?rZA>Zrf{Z^rm(hZ mXj6DwHN+{*Dby+4DdZ{aDfH>EvG5=Buj(`}N3=N@sQLp)Ml4nU literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Vientiane b/lib/pytz/zoneinfo/Asia/Vientiane new file mode 100644 index 0000000000000000000000000000000000000000..7249640294cd596c9445ce175455efa06b5dc6c3 GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Vladivostok b/lib/pytz/zoneinfo/Asia/Vladivostok new file mode 100644 index 0000000000000000000000000000000000000000..15731abc81dfb016221260ed6ae40af24aefb89e GIT binary patch literal 1230 zcmdVZPe_w-9LMqRbkM}Z5dFc{D9WsdZdp~0^>eR8%*xrwiW1zp+QbWV%I^?g_ zm^(aK#5}onKRyvl&rQ#-Zt_Jlb@i8)Jg(7Y&)U;t^g*J_R)9oi$bVuU7Hut{K7XM3a^*+&^HTTu$d@a83^RjDY&fPYecDKE| zC%YGJiT}lA+4E>b+V7r{z1NcxxEPk;nSfa7A?>)(sr&juy8pOGI~y9c%UaW}U!vXK zI_duKNqc_&kOR2|?ak*UH1kpVvN;LgcqIoXAIPDzGtz%8tC8qU88|(qhg*jwIxwb3 zsuB`w&S>oGd5u>_H2yZOM?be{;+bCu7yBi7dxNGD+12qvq2Q>jC_bE3Wsi01%YG>T z#h=$Mr&Hw0H2>PMJ7TUUpF3P46Az`f^jf;(Fn3Dt5%ZqXeW@Kxror<^?H^eF-;3MdjN8Ym(tDkw52Iw(RY zN+?n&S}0;DYU~<03_TPSHgIEp%oJiA68MW9`y zkRp+xks^|zk|C3!lOdF$lp&R(l_8d*mLZp-mm%1$QB09+*J!4Qrl_XKX6U8}r{l)J Q|D(J*WWE96b}Lx@8|YsB0{{R3 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Yakutsk b/lib/pytz/zoneinfo/Asia/Yakutsk new file mode 100644 index 0000000000000000000000000000000000000000..1f86e77f5806a6c7a19143071de4fd72145bf037 GIT binary patch literal 1229 zcmdVZO-Pe*9KiAaa_Uft-=(ycxze?4YIBur>9kxXx=B%kC@F$o=#3arln@#8dKmFL z6hU<8JqYR2%fY;*P$5C($vOyjs17F4B_h`U`;39qsbl|#=lT5i>|wC|zDsS}zc+0D zTBY_5C#&6_?B(YBFFT&x9x-zQo6~bEE=-hr&QF*Bc($|R^I&@3_|(Mw(YCaAD5(nu zVp@5js7f>)kAN!dHj>K-2E!C!B^6H?v1ov8;~`79?9A>FSNbmio|z4&~^Uf z(h<+-`ogW!S$$YLr;ce?!A9+Ry+ykx!AB?9RN|%S&t|i(f?2u4T{zC z(N>6*h}4J_iByS{iPVV{id2e}iqwh}i&Tr0i`0u0>}V@SN_Mn0BSj-sBV{9XBZag2 SZ3F*Lp{@1Sy4r5E85h+V($eTjxa(4RQvE1~~;p agPa30vL48$&`Df2V29f2npqheZ~*{I;66hD literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Yekaterinburg b/lib/pytz/zoneinfo/Asia/Yekaterinburg new file mode 100644 index 0000000000000000000000000000000000000000..fff9f3b14bfebc4344701c7b66410602de37e6ba GIT binary patch literal 1267 zcmdVZPe_wt9Ki8s>S~)FBr2_Ct;}9^S!-=$)|{*5%$o2}gCLX!nNV~ol7a#uMg>I? zb*c`Q;i*3p>XeazN2}kVOGpLfB?1rCC2Xwc`;LLssbkN}^M2m<*?3{^^IhiP(ZQhl zYt)z(CL?Jk*Gq37a$9$oj2EAd781*>%lYN?lfF9Fi$LAa?<4hJzU5cUSiY6hr}N&4 zoUD2|gB_8Fg!jajYaO=)nvpR9)tKN}Qva{=zP9FXw zyP6*BuH=-YoY!@CWD8Hw!=b^V;onDiwXl`whKRRpvUOTuNoK4)dy0TGf`%(nGrL?JXR@3X_RhC=gdEBxA~Ptwj9}Z23yaQ*nT7dBm-NM0+Iug1d;`k z29gJo2$Bhs3X%(w43Z6!4w4U&5Rws+lC8-JNeaoz)})2xg(QY#hNOn%h9rk%hopz( zha`w(h@^<*Xls&0va~g6B6%W-BAFtoBDo^TBH1G8BKaZ-BN-zpBRL~UBU#&;w2{1R fP2x!ANa{%LNb*SbIBXjDPt*6r%pW7(7WVuGNaP;S literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Asia/Yerevan b/lib/pytz/zoneinfo/Asia/Yerevan new file mode 100644 index 0000000000000000000000000000000000000000..409c3b17125b803d499e66e78c730030a56d3443 GIT binary patch literal 1199 zcmd7QJ!n%=7=Yn(O`Ak<0*7oqJSPDbH_B7&lb;9ynad9M~qo!q>abDzUWxR88rVgJ!1 zG44pdEN0&~`*;CodHl@oT|)VWgl$rV{W zc2??d4oky@Az5=~pR7HRm30SFvOX7;#&n%*Xswo}@DJHo{ZS&mkdA!y>rHQ$JJFYO zPV=)5PRsafr}gf%({}Z#(?0Uh**tv5u@7H%V$YpYN9QGp?>#Pw`je83ts;2mD_dtUP5L0WJhmV_Z<2xJDVnTZ_f+q z^WE0j#&OAhnvj0ab?KkECA()&%fN%8 zO(NGGtdXXk^&Pz>%RNgGYvs1b{?gn`6? z1cF3@gyL#qL4rY|LBc`eK>|V|LPA1fLV`k~Lc&7gLIOh~b2XtMvALSykm!)`kob@Q zkqD6xkrD7CR`+5Bw!?BBxF|;GZHiw&FugCsQqb^ JdskcB{|l@V5AFZ} literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Atlantic/Azores b/lib/pytz/zoneinfo/Atlantic/Azores new file mode 100644 index 0000000000000000000000000000000000000000..56593dbfff9bc50bb32e64ff282d0502e75526c0 GIT binary patch literal 3484 zcmeI!X>d(<7{~FGS}KsVys<5xVlt+OlfKbXh&7m99xyq-(qTbX{b! zu7B{9uJ;$|h97ro&hCSWak8`J z8rhYwTX!9~qj>?HHScP)et9xg@;@6b`IlU)AT8M{IF>7W-s>TIwhd1!Ov$whcO2Ed za-p7X7Ji#O?4y@TYd^dxJ4r~OjY;s^Wd&=dBdYJ2U& zfv$b{_A2|x`33f|4ddTpX8X$I753E^ z9Q&u|^4)8_a@_0fmb*8i=D9zI%yMs5PIGUS80+4;Hq@<<5h@S7{fIm`HCAO@Q>~a- zPbs8i)yfm5|Oe`U1;+0G-|GM(U@d|<IldZm zmirz}pXaOj$}FF6WSXzmz_GsCU55HXqI>xs3v2JIQ@x3=Zuz>tdZl_e^=`Cv>Yt8s zLW>@A9^Vnm@7>q zCrZ=1snR@nwl+WhthBf@L0fKWCr=(t(C8&YG-hK)p9k}d_g8aFdtx-Ofq-A0X) z?&+^P!F6uj_!KvNF)A zD1+8slEJChWXSA7d1gqFJp1Ye9ol-GJU6I76Kj4X!(y^Esmy0OyoT!Vb028(-4uO( z_auGcc)X7IWPpy`IaXer9;2g{^perZHFeCiCNj21w4@|ek(VMKm2t7RWqd_TCNvJx z30F?a#0PHb#C_Xk(&ZyMdF2v$xp1St^3hg(HG8hUHffQj&P>%*zuzBF`o0n+Oa0Bq z{pNdreEzM!7kGb}zkHM}SN^|xl=u73Ua>5{|8#w;q~Cw_NLbgK ztUpo!qyk6@JWUOdA|O>j%7D}XDFjjpq!dUkkYXU!K+1vC11Shn5l>SRq$WsFkg6bM zLF$4O2B{2E8l*NzaggdDh3kOh}!OLLrqxN`=%4DHc*K zq+Fh+UP!@^iXkOKYK9aIsTxu?q;5#zkjf#YLu!W<52+qfKBRu0rhrHVkrE;`M2d)1 z5h){5N2HKQC6Q7hwM2@ER1+yDQcq7)P^6-srld$sk)k41MaqiQ6)7xIS){Z`ZIR+4 z)kVsS)E6l*QejV1Vx-2NrpQQ@kuoE7MhcBo8YwkWYoypnwUKfo^+pPgRNT{)9I3gd zDLPVhr0huDk-{UDM@o;>9w|OjeWd(I{gDd*xdM<&0J#P{%|(D*1;}N9TnEU7fLsa4 zrGQ)u$i;wM4antyTo1?vfm{*DC4pQMp5~%Jt_n|cSs>R1a$z7>26Aa2*9LNNAXkT{ y`Q`rq^7#E0;osxlh4JrQ9%ZA=mC`CA+T19u!s4PDHE9&yI6N#aBHViyQT8_<^E?9p literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Atlantic/Bermuda b/lib/pytz/zoneinfo/Atlantic/Bermuda new file mode 100644 index 0000000000000000000000000000000000000000..3a5c6dbf7a9ad95ce3a2d7c1bf26e203b6fac30d GIT binary patch literal 1990 zcmdVaUrg0y9LMoPG)E^fEJ+hEu|J5x5e}Y0(9pupFiw1xgK<>oTY&^tBnd(i&1EH5 zwi#OH*jz5xMOnj~<;GAlZ%Q9Co2?BW=is8{X1cXDlhgBlZoBHL^*j6Rb#}IM_kDlj zYg;#j1OIqZ-7ma6+uY0dsfR~%Cer2(>1`RiB^Vgc;MH!q-EPsTKfhit1ffL zC!IFqgL*T)|7m+?Pno%^b+Mh1$Tpc(S@!PINi%c)O*?B|mfVwe(auh}DYJk7OS3Lr zlv%N~U>o-&U>uDJ-$x27T(t45?XP zp)&A?tXh(*t2-}9ZFZ{0>%Nxwjd5KQ*(YnpQ*8a5qo)3w%Qi8&*Q`Bo(yklpHS4z@ zv<<_Jrm?BtJ~Oz?XzXKs_MHZKu5gDw-%=q>>22B^&6VaW)!LGmDlOlKv~|k3w0@eQ zZ4;+t`{ z8`ADsZPGnFpgn(9%WH#MbnAtX^!6q6^`jZm*I2G^?71c>xBSPSWOB4D5J)CZjRbB@ zCeNFcwCY$qu)Nm2m2uA1J@J2W)JgYn*!+*(%;zI&NB1A$&Vnl*OqC~<(;zR;PB1J;=x>%85k!X={k$91Sk%*Cyk(iO7 zk*JZdk+_k-y)JSjbgzpY3Eu0XN5V(qM+N{H0b~e}F+c_Z83kk*ka0i;!s|u?849l( z3uG|7ZZweLK*j?Z5M)GL64gWX8 a7IZte$n9J~tRxf;6&1vai$lesqTt^>F$c;3 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Atlantic/Canary b/lib/pytz/zoneinfo/Atlantic/Canary new file mode 100644 index 0000000000000000000000000000000000000000..f3192156ff043a529461aa9004a8de9dda326f7d GIT binary patch literal 1897 zcmdVaUrd#C9LMqJ2qGUvpA-5QdEdkL@gpEV~`qiI@cUy zpEjm*<+A0Nb4p%NT_Cmo&X%&aWKPYs<;woe(acdgM!)ydO`BKVwE3{Z>ltU`>ihmg z*KTdh_wIVeyT9<^X>}jo6MJH7hcA?l%$yP_@}?HtR#L`qnl|M-CC8js^39Jl{n~qa z;M=2m@Uu6Ra%R9%Pxe~cTW{NpPFeb{JvOtc#b(uRwAocr%P20lhk`|xnVMyDCQi4k zxH4tkny2g^GnF$mO%H!DL67wPrQoq&G`IV*a`%0$yd7s0YB;5E-6hL!>9c~8ew(-Q zpcSSav7-DoD;n*v`6=C+e|5brxYMeI17-Hul^PZI)T^X_p%(2g)#5i(wWKjarTZ4x z;}vl#Ye=ytGOw$=Y}6{^zEWkz_f~o1CtDixi7g#GYfoN#*PiMV%qfLrdRqC1KWm*-?(W;S+q-A-Frl@wI5SQSBC=QVq)X|_n)z`KjeAt_ples znR)S^H^~AM|NCAQiF$KGVQ+PL)P1U>d>04={v~=3r#t2z&KEgh{sU*s!zm-@jGQ!b z*1qnvk@H4Q96593)RA*XP98aXgfR3U~c7%9jt%njv* LgOPBEw}gKHM@+Br literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Atlantic/Cape_Verde b/lib/pytz/zoneinfo/Atlantic/Cape_Verde new file mode 100644 index 0000000000000000000000000000000000000000..e2a49d248de086306d2dd16a2b2d497a008c8f07 GIT binary patch literal 270 zcmWHE%1kq2zyPd35fBCe7@KF(vsDYuOr4`}sia1LTl~92{r~^}8JU<_SpNTi`GtYu z|NqAi7=Y}L9~e0hYz7V=-w*~}10x_dWME(fnFu06NU#`a&wr5RAR6QV5Djt!$SjaU TKy*D&jBLkn*#I49XUGKr1p`uu literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Atlantic/Faeroe b/lib/pytz/zoneinfo/Atlantic/Faeroe new file mode 100644 index 0000000000000000000000000000000000000000..4dab7ef0859c244b916d61b7489d7371881e0ca2 GIT binary patch literal 1815 zcmdVaT};h!9LMpFG}f>$R-qD-ila_ZNC~Ni%0ov*lJr0%6s?eE%#7B)w#E#TY0WH$ zi*S*Lc^s2wvt}NeP4jHcM#HS-_x`(d<;LdU{(Jp*F1q@>zs?oKMUifQJpIitygcRR z<$LhKjg47efgja-_zU%Mf2clRuIY%b^E&czgO0j&NPVwd6~AVe_#Zzhqia`L6cH_d2=$ znTG9spy6AusH5PVM&vw|$g&oh64xqImmZcV{}U1&St-%IH8S0|UZ;2F$&8;B8gn&4 zW7vq7SzNnEmt-E$r6q-$KCMKZDapFbCrvZ# zp_=i{p;=x@lJ#VmF7FAE6_>thc88~|Y#1szEuUmn%@@h7Zfz|8hBCH`m3&ecSP6qmTB?5DqY{{)D35{wdC#=*|Zc-8Mr4^rFN#&4lTKVX-%wrJ(>AlNvTtPL$j*_iTbjKin@4t!Y#-S_ z(g4x{(gM;0(ge~4(gxB8(g@NC(u$?&1!>08bc3{m^n)~nbcD2o^n^5pbcM8q^o2Br zbcVEs^oBHNX}Uw&vo!r74I&*PEh0T4O(I<)Z6bXljUt^Qts=c5&03mnk#;RjzevMK z$4JXa&q&iq*GSt)-$>&~=Sb^F?@04V_elGerhnuHAa?+{1;{->ZUS-_klTRV2joT| zcLKQ;$h|;r268u$+hJ+$2XaF!%^g8*335-6n}XaG$R-qD-ila_ZNC~Ni%0ov*lJr0%6s?eE%#7B)w#E#TY0WH$ zi*S*Lc^s2wvt}NeP4jHcM#HS-_x`(d<;LdU{(Jp*F1q@>zs?oKMUifQJpIitygcRR z<$LhKjg47efgja-_zU%Mf2clRuIY%b^E&czgO0j&NPVwd6~AVe_#Zzhqia`L6cH_d2=$ znTG9spy6AusH5PVM&vw|$g&oh64xqImmZcV{}U1&St-%IH8S0|UZ;2F$&8;B8gn&4 zW7vq7SzNnEmt-E$r6q-$KCMKZDapFbCrvZ# zp_=i{p;=x@lJ#VmF7FAE6_>thc88~|Y#1szEuUmn%@@h7Zfz|8hBCH`m3&ecSP6qmTB?5DqY{{)D35{wdC#=*|Zc-8Mr4^rFN#&4lTKVX-%wrJ(>AlNvTtPL$j*_iTbjKin@4t!Y#-S_ z(g4x{(gM;0(ge~4(gxB8(g@NC(u$?&1!>08bc3{m^n)~nbcD2o^n^5pbcM8q^o2Br zbcVEs^oBHNX}Uw&vo!r74I&*PEh0T4O(I<)Z6bXljUt^Qts=c5&03mnk#;RjzevMK z$4JXa&q&iq*GSt)-$>&~=Sb^F?@04V_elGerhnuHAa?+{1;{->ZUS-_klTRV2joT| zcLKQ;$h|;r268u$+hJ+$2XaF!%^g8*335-6n}XaGV|lz*VLe%NT?WhQf4t}Rwp8eJmMkFokZzs>oF{?kAG(dWDSKEC^I z_s4DbdInZ(sLQpkIdSF%@alWXGS!l5+1xZfu~z3h>p9hvfTQ>sMjMSiJt$ffd2GCX@wF1t?2i1V2I zDiIycij~pGNu3zp8JXl?Ad~a{(1i30nmFkzbw(do=kU8aW$=*R^2Hv#^}`o5>Bvz@ zKF}>Gue>T#+f-7wJ|k(tkleOvt=#SlNP1DJOmi1XMzTw$-!w&BFVPVembP2Kx`&{-X4HM8|o&DwNCvuh7(PSqL74fRN#r&scqy(9%GyQMI@uG~`=qu$yiS&(sF zOTA-K7W0Xgr++QwL*L25==Wt|xKHjK+$)Q^-xOc}d+Kj*lf?&K(LsqP<{?D7M?|uG&5zmGAA@NEr`s=)=UVQ5i%uYPROK?Ss~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!c1B#~L#nrZUOnI|$)WTwbek+~w1wKcOv zri;uMnJ_YAWXi~#kx3)7My8F-8<{vVb7bns+>yy6v$r+VN9KsX}svBn!zFk}f1)wkBan#%xW>kene& zL$ZdX4apmlI3#mO>X6(a$wRV-qz}m-l0YPbwkCy04v{1xSwzx^b> literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Atlantic/Madeira b/lib/pytz/zoneinfo/Atlantic/Madeira new file mode 100644 index 0000000000000000000000000000000000000000..5213761f891303f95e1962570568ae62ed387950 GIT binary patch literal 3475 zcmeI!dvwor9LMp`uUVK&A4bfKvSe!U-Gq@P7CUmO<`<)8xnEjhjpLH`sdQtPYu?Q?W^w*@m6tlqESY`HEbVsE zEE_+=ERPIVD;`~~@`JCN{L72f2Sp#Ng7;$8hj}T+eZ7HN{pK{aW`birN}p%eK33nX zj~#3_-1d#xP_@&1a$$qnv}=bd+^}AKw)k1I`JMUd^95CE%arNri!o_xYjTR()-}{@ zkL#*-bY7u$CVy&n9z17?eDS8}VjJ`2(TQsJx}j=!#p*z@85Af!yjtyfD?#nqI%q;k zx*I6jzQ=qusipdA)ucdaQg)zp#fN6^6V=t;tbu`ju^EAVug*352klp1_u8h)IvsJo zX_xQol~`nsdcb!XRBx6SNm zbVih^HzLf`_cxZ%gi6z(-EYz`@~qq*dRQ6-?U6>8K9k1B3iHDD=Sh>Tb0vIJp>DdW zKu6>**3Gg+H-F(JeMeflzVoT!x$2PBp6%7;-nG{xdiiO&FXxc7nYmlqW^58?#0L_Sa!|*nuhs41H|qP7=IQnki*$$B zDZ0b;OdS`VBXQ+Jb*IY7(z!4}Kk!|$bXl1y@f$nKgRgayt_z#WL!%<4+ssb7d(U7= z7!j@?Zh2CABwW`$gNr0F@`z46e?s>P+M;`v?9z`MTc&##P$`EAsfjS(21CQu=pH6MvsXd7@E284%rC23&O{xnWP8{B3<1bR|j;{`j&ySr($7 z%B!bS)>h~tnU{2GPKkaxwM-A4aYlx9U9X3y6w8R_^YzI10vT0fos5nU8GY()8FM{d z#_pObjhZ*SYSsP^UsY9sD5bp5YIa+cuSQkX#ek}Pq%=rv zkm4ZKLCS;F2PqIzA*4i}wnqGiqDVYciH9;FbwUb-R0=5-QY)lbNVSl1dD?m*1w$%^ zlnkjEQZ%G$NZF9OA%#OKhm;Pf9a21`dPw<@`gz&{A{9hRh|~}%B2q=9j7S}kLL!w! zN{Q4GDJD`)q?|}SJ#9geihA0TA~i*dic}RTD^gdaut;T*(jv7*ii=biDKAoAq`*jp zJ#C4R8hhFzBUMJqjMNz^G*W4#)JUz7Vk6Z?%8k?;DL7JbPg`=N=AO3bNY# literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Atlantic/Reykjavik b/lib/pytz/zoneinfo/Atlantic/Reykjavik new file mode 100644 index 0000000000000000000000000000000000000000..ac6bd69731d25fc20724798abd4f683f1f2ccbd6 GIT binary patch literal 1174 zcmd7QPe_w-9LMozO-ES7AW$-}ZK$jMYysKwL@$Wb#X)mML|W;t;7K0hbxLRLV(p4wm-~Bt`XJUIiH&J}SaVgoOWO6(&NJFm zmXO|x1NwO0O-UApH92`!Qgil8>d6s#I*_M*EnDST*GlPcJgm>J<;sidE&8%9Bd@lX z>Fa$dc@vzk1EILQUHC%>OOu+Ol`liNw{&QDN`@z5I?~=R?|P5w`^&fGLvvI=o@$iQ zb3q;3b3#5HtCaDu>gURG`Ld!~C)O;IuXA^3WN{*VTd x4v`j-9+4)IE|E5oK9NR|PLWoTUXf;zZjpAareCCCq+_II{9k&`F@XniegRAxR~rBT literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Atlantic/South_Georgia b/lib/pytz/zoneinfo/Atlantic/South_Georgia new file mode 100644 index 0000000000000000000000000000000000000000..b3311b6331471833893fcb776ac88e2a90a607a8 GIT binary patch literal 172 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34$+|NnOnFfjc8|M&p|i;r&zgRTL@j1Uq` X0~+){v+Xa?973jY*#OPAGvWdOF=!;J literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Atlantic/St_Helena b/lib/pytz/zoneinfo/Atlantic/St_Helena new file mode 100644 index 0000000000000000000000000000000000000000..65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb GIT binary patch literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u*62on{3kL6CLt zP$5x4|GIQ&$RnrAm9&bWEMg=b3WBW~cmlVx}=*&#wO<(j`MOtzGKTw@x|tZc$}kmCMIdGwR8G zE$98GguD2sbiYWd(yu8`+08>@!?Q_G`T3BjxUpYV9`Q@hc$caQm&lDHm1>iBNp9|U zsp?g4Wld9ssQEZ0Yl|&W`+ULnet9qI?j`Ll*_5cibj5B^lVavM|w*y#DhIgBf+I(@le&>NN8bCyuWBZ+dq9YxAW7TZ1~z> zZdYa^J8-5cm(W_f@>VRn3-Zmq{NV523ktNJOIeol%-8y5*0oaWv~8`?dNJo(%ZX)9 zI3L&@wf3255I!?4W`18|^dDAhzCe$jD^4OSM%Ik18d*28a%Am}X7$MWkpz$okQ9&{ zkR(h=7Fjr&G>|-yM3797RFGVdWRPr-bdY?IgpiDol#rYpO;Si!jwUT6FC;M}GbA-6 zHzYYEJ0v|MKO{jULnK8cM@N$+lBJ_b6Uh@v6v-4x70DGz7ReS#7s(e%7|9q(8Ohnv lB#mV4XwpXVb~K42nIowqxg*JA!tC-t%)TgKeu-{h(J%cobp8MU literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/ACT b/lib/pytz/zoneinfo/Australia/ACT new file mode 100644 index 0000000000000000000000000000000000000000..4ed4467ff0f7a47e2951d3674fcc542fa3f2129e GIT binary patch literal 2214 zcmds%TTGU99LK+J!U$5)avYUVGr^F9ib0`y0E84nc{!FwfGBqG1yZpRf2obyJfvO7 zj2Y65E-H-HG{+*A8!H4>7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiEEG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsceX2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v8YI0g~YEr=xl(IPNPxDR! Aa{vGU literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/Adelaide b/lib/pytz/zoneinfo/Australia/Adelaide new file mode 100644 index 0000000000000000000000000000000000000000..190b0e33fabb8dd6c01f6d939df0f09e288c562a GIT binary patch literal 2233 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 Aa{vGU literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/Currie b/lib/pytz/zoneinfo/Australia/Currie new file mode 100644 index 0000000000000000000000000000000000000000..865801e5e0befe4e6d1b17a10ed15f721c5ac335 GIT binary patch literal 2214 zcmds%YfP1O9LK*$U<9dTIpv~+R&ogs2QSDeB{e`$NsPlaMMC6anFm27%hPXWo?fJO zag$|Bq%nHqRJv#zi;MZoS8kJXZ5OAy=#4*J^#i2 z?dF4hN~sRn3>&eTOAqOmmhW`y%$>Tecv!Q>ty60HF{S?Uy3&3awe$};r4$-5a*7_B{DEwOIDr8k3|rVUszry#Tiz3=byw&|TfCu1OKRVR`yn^YAU?{Z+eE zU(}}cxeaPaE7ykjLN!imwI)x!HT_&>&7=9&^8S3=I5^85I5E{8JaV0Fdit_GWZ&B6 z;O7?H7_HXo-?XK8Ol|(p)Sf=1tqJ|w7IReFF2AVlWBb+dsp;W&TD9YidOgy&*B^odDC5Wv@H3Bx zQ-Oc6lWPv`-pCZpUND=%?8ddT9n5|(8^Y`evnBqQJz+Kl?8>#XEzG`LI~&97%(b&M z%-%4Y19k^&57-~DL12f#7J)qin*?_0+Sw+sPhg|KPF*`&1@`LN*(_$anC)Wri`g({ z$H0~`d&X=U*fp?iVBf&Tft|Z{whrtavw2|mnC%1m$20)w0Hy^%4=_!@bOC4srVl_P zFr9Giv;xx$pc$BMVA_G{2c{uFM=&h`dV*;R&=pKufWBZFgXs*WH9&7%JIw*Q1GER| z56~c>LqLmw9sx}Px`b&HrcangVLF9r6{c6Non~RWg=rV2UzmnrItH{1=o!#7pld+e zfW85Z13Cw^4(J`wJWTgoJMF{t57R(Q2Qe+g^bpfTOcyb2#PktpB+yBql|V0nW&+)G y?X(lS+edaYOR|05Y_Bh~WFE?z8~Z2nWBGsp literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/Darwin b/lib/pytz/zoneinfo/Australia/Darwin new file mode 100644 index 0000000000000000000000000000000000000000..cf42d1d878b364a3d720997b1511ae71da458b06 GIT binary patch literal 318 zcmWHE%1kq2zyQoZ5fBCeP9O%c`5J)49KW?o=Il}baXMrd$LZs76=$}`cAQ=AP;qWS z703B@r3xlSC}d!$S^-qgFnI+d3j;&z3@G{~7C8st<)W+o_v mxwUQr$R?nRfnEl>m<8fupku+#1~~=BG^AwcTRWH&% z@OR#D!}HF~^9*y2V;?u$i~Ui*3>Unr;h|#h`=^U+q&v+!+R>nMtzq7YciMEduSJtX zrMlMCYg66JrJ}>;X^oK2svh}z)68#rxcu|N%>Qz#1td;dz$c*=IP;tckXpkM=35w8iGxsLfw{Ot;kipj+o3&}}&*THv!y z(Xl;>{_PFL{4{2zdEA{->mexIPcl5t&%Ln3YMc0^C9-n6EkDb-3mY;3)jxMb! z|G?H}w(8FGKFdgW*)k)KSeE|=tqVA%b-#Bi`@83q^XVb2AKI%8r>nKGyI8qLvvpT% znl|l=Q=YfbHdkcX-FeF_e|3x%B!yUERFD}@&Plh``yq@Zi zpY%enC-Co+Hyd*cv?ep3Bcp)NJRVLJ{EIzJIW#8dTg3uqY7F`#8Y&w!=@T?5(% z^bONEOy@AI!}Jc*JWTgoJMF{t57R(Q2Qe+g^blww&_$q)Kp%ld0-Xd}3G@Qb{dN5D5j;Do?@Dc=_;nJn7(2fi|H)TTA;T;bAj#x?FIVl+G#M*VW7o8kAWrw nUBG-el4q+|cRqdrbP- zWBSMTWoAg@b(PV&TZYO}6}+%iek!X~?twz_Dhk!G>Y!xi1l918t}>!eS2famFC#y_ zSE1w|Oz7DomDTi0MqR(GMn7pa*{51{&b3P>cjqNN=ExzFxA>4AyW^sa%NISqvOy*U z8&v-I8kyKBTTdELE|WeS)RW)rGX;OG(^Fbjn4hoA)P+q`P0`^jU0mmxsWr)ZnktrG z=7&_tG*?RVl2q93F4I#!sTp1Vl^L<;YUaDgQugAeDu2){v;I7(W;Z`Fb58%ED;h7G zxx0_+dD3X+N4Dz)WgE@H$%}MlXpUJlc%ojMm}{!KRjMU{*|OxHd=+WSk?6xTwe(hs zEIS{smY)d7iv4fZ%G%emO5Il~8Z)cQ+jVt8n^}`}Pp?hCZPxWVqu0luGV4G7rfb^k zjQqV?Z@3#Z8?Tn?O&4~_=95ZoIS`Sp+e_58>M61<7K;VqI(&V0jQd8@)VV325#aFBSAfRKoMZAeH=zBVW%DkLl3&0sB^YI dOSwZmcT}cp|M2WbFQdqFgI>_hC>jo(zW~-0_U8Zq literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/Lindeman b/lib/pytz/zoneinfo/Australia/Lindeman new file mode 100644 index 0000000000000000000000000000000000000000..8ee1a6f548b0518bf38c99632a6cdbac5d23dc32 GIT binary patch literal 513 zcmWHE%1kq2zyPd35fBCeF(3x9`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIi0T~apUKTTG&p6ef zbE&^Uw_;5L6C)Hdvp^sdh+<&qUIVm{Vb%slAiHM)11B=u$2Ww*(bWY+1c!i>F*1Tk zh7c0`^B)L`-Y&WVqCx%w(?Gw0XpsNFG|-PA8stw94e~392Kg66gZvD#7vygc4e~n( zfcy`pfnfjwpl|@wz_0)TPdaC|tlaFl;~o6h0st6hG-el4q+|cRqdrbP- zWBSMTWoAg@b(PV&TZYO}6}+%iek!X~?twz_Dhk!G>Y!xi1l918t}>!eS2famFC#y_ zSE1w|Oz7DomDTi0MqR(GMn7pa*{51{&b3P>cjqNN=ExzFxA>4AyW^sa%NISqvOy*U z8&v-I8kyKBTTdELE|WeS)RW)rGX;OG(^Fbjn4hoA)P+q`P0`^jU0mmxsWr)ZnktrG z=7&_tG*?RVl2q93F4I#!sTp1Vl^L<;YUaDgQugAeDu2){v;I7(W;Z`Fb58%ED;h7G zxx0_+dD3X+N4Dz)WgE@H$%}MlXpUJlc%ojMm}{!KRjMU{*|OxHd=+WSk?6xTwe(hs zEIS{smY)d7iv4fZ%G%emO5Il~8Z)cQ+jVt8n^}`}Pp?hCZPxWVqu0luGV4G7rfb^k zjQqV?Z@3#Z8?Tn?O&4~_=95ZoIS`Sp+e_58>M61<7K;VqI(&V0jQd8@)VV325#aFBSAfRKoMZAeH=zBVW%DkLl3&0sB^YI dOSwZmcT}cp|M2WbFQdqFgI>_hC>jo(zW~-0_U8Zq literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/Melbourne b/lib/pytz/zoneinfo/Australia/Melbourne new file mode 100644 index 0000000000000000000000000000000000000000..3f2d3d7f176b9e461f9730117bbf4771528da823 GIT binary patch literal 2214 zcmds%TTGU99LK+}!U#&qa>zjmwG<5DMa7_$JODz9p}ZVRB|sEA_yQ_eNk31ci^{Y% znK4D0(M5&Pn&w!Y@W_j&6)&vy+u4;=LO|TJgH7{P7%aARmASUQ@E$8#mS_Qg zj1?zDSV`nHE4lHbm0rDMWtT5l`EZ|A^qsbc&R*2RuMcTM+xy!1Y_~Qwcc^mfVLei| zPgQx1s?MlUO=7V&N9U?`O1*9IRoRxGi)`y?w$*)_Zrl3j*rRVxv&T-}WRJf%Zco_v zwmtZ@1-FH%zWg^eN_}`z`6a=(9C!3=vz|VX> zP6htOPHs3FqYg%6_Tt%X2D2N_ZabL$U^ax=5oSyLFMGml3fPrrw=K-RJiCoycIMe_ z4YN1Q=78M++XMCoY!KKXuti{xz$SrRdUo3c_UYMe6th#$ZmXERdUl(|>=v_K%ziN& z#_Sl_GG@=frh#1p+XnUxY#i9RXSa1=@1EV}f!zb!$Lt@|089rkEdY9eX#%DTm^NVg zfN2D#6P{fwFum~Xnt|yCrX84m01W{;0<;9^3D6XvE10$beZe#a(-}-_fZlj^%>lXt zv)~gK#PDL0ZjtBglQ9|PnbqwI)!N!rdOU_voPJlvW2tnm;8i#h+ZT2&FEJ_!Glc B{)PYm literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/NSW b/lib/pytz/zoneinfo/Australia/NSW new file mode 100644 index 0000000000000000000000000000000000000000..4ed4467ff0f7a47e2951d3674fcc542fa3f2129e GIT binary patch literal 2214 zcmds%TTGU99LK+J!U$5)avYUVGr^F9ib0`y0E84nc{!FwfGBqG1yZpRf2obyJfvO7 zj2Y65E-H-HG{+*A8!H4>7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiEEG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsceX2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v8YI0g~YEr=xl(IPNPxDR! Aa{vGU literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/North b/lib/pytz/zoneinfo/Australia/North new file mode 100644 index 0000000000000000000000000000000000000000..cf42d1d878b364a3d720997b1511ae71da458b06 GIT binary patch literal 318 zcmWHE%1kq2zyQoZ5fBCeP9O%c`5J)49KW?o=Il}baXMrd$LZs76=$}`cAQ=AP;qWS z703B@r3xlSC}d!$S^-qgFnI+d3j;&z3@G{~7C8st<)W+o_v mxwUQr$R?nRfnEl>m<8fupku+#1~~=w131_w!EjYV8bOI;; z?**LOBo;_9CM}R!UcW$7HD-b4`cn$7h5HrUSko2Um1`EbziVFL$sD)9^IYlzCPpx1 zWO#lD@ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/Queensland b/lib/pytz/zoneinfo/Australia/Queensland new file mode 100644 index 0000000000000000000000000000000000000000..26ffd9acb76d06cf1d6569e7bcf545da8c61a6ba GIT binary patch literal 443 zcmWHE%1kq2zyPd35fBCeK_CXP`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIUj8MqT0)Y$+{S`oS z8D?!@WMN?FS-`-F%=YmOVQ_SH0TIC=AZ3h!ihfgxT+Qj#l#HvF(&$TX>($Sv$TgpQEi-mGCFadsY2v8rY)3$6547r>iCn$Dw?dOf z;xu{NVZCAhaY?D|(bVqqnzrN!zB%{c~TXs*E`n+w7RN9 zSNn4{=u6SNQsQ*YOsB4y`cdz`bU|yrIIp$ir?u|QVZGew!Zz73uUk z8A~7NzN`<1zSK}_j6763B^~A8%jTj9*^+ry9=`6Bbb8;A&R_dw>*PKOf7&bC-U&<`%>vO{Ex$R3eRBD+MkiR{zXY!un4 zt=THFS6j1LWVg0vyU2c#4I?{7wv6l<*)+0iWZTHTk&PoeN4Ada-PUX#*}bjVKC*wL z0Z0ds79c%9nt*fxX#>&+q!CCbkX9hQur3h5NmDx_CPvyg7tnsy=m zLK=p23~3qCGo)!q*O0a$eM1_DbPj19(mSMiNcWKTA^o#84MaMKv=Heb(nO?-NE?wp zB8@~kiL?^wCDKf!n@Bs6e%hLbA{|9qiu4p|D$-S?tw>*y#v+|{xSZ3c+j9k+e}{JG z)0PRX$*l>k$?d7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiEEG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsceX2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v8YI0g~YEr=xl(IPNPxDR! Aa{vGU literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/Tasmania b/lib/pytz/zoneinfo/Australia/Tasmania new file mode 100644 index 0000000000000000000000000000000000000000..92d1215d60f929545276892330917df09eb8d1e4 GIT binary patch literal 2326 zcmds%YfM*l9EU%DE+fcGmQ(Jbm0UtDUJz385+JA~#+?)ik&9*i5k#`2@64jLSY<9* zwnQ4EHx8tWwy}t8V~xPtx~Dm+tBG^AwcTRWH&% z@OR#D!}HF~^9*y2V;?u$i~Ui*3>Unr;h|#h`=^U+q&v+!+R>nMtzq7YciMEduSJtX zrMlMCYg66JrJ}>;X^oK2svh}z)68#rxcu|N%>Qz#1td;dz$c*=IP;tckXpkM=35w8iGxsLfw{Ot;kipj+o3&}}&*THv!y z(Xl;>{_PFL{4{2zdEA{->mexIPcl5t&%Ln3YMc0^C9-n6EkDb-3mY;3)jxMb! z|G?H}w(8FGKFdgW*)k)KSeE|=tqVA%b-#Bi`@83q^XVb2AKI%8r>nKGyI8qLvvpT% znl|l=Q=YfbHdkcX-FeF_e|3x%B!yUERFD}@&Plh``yq@Zi zpY%enC-Co+Hyd*cv?ep3Bcp)NJRVLJ{EIzJIW#8dTg3uqY7F`#8Y&w!=@T?5(% z^bONEOy@AI!}Jc*JWTgoJMF{t57R(Q2Qe+g^blww&_$q)Kp%ld0-Xd}3G@Qb{dN5D5j;Do?@Dc=_;nJn7(2fi|H)TTA;T;bAj#x?FIVl+G#M*VW7o8kAWrw nUBzjmwG<5DMa7_$JODz9p}ZVRB|sEA_yQ_eNk31ci^{Y% znK4D0(M5&Pn&w!Y@W_j&6)&vy+u4;=LO|TJgH7{P7%aARmASUQ@E$8#mS_Qg zj1?zDSV`nHE4lHbm0rDMWtT5l`EZ|A^qsbc&R*2RuMcTM+xy!1Y_~Qwcc^mfVLei| zPgQx1s?MlUO=7V&N9U?`O1*9IRoRxGi)`y?w$*)_Zrl3j*rRVxv&T-}WRJf%Zco_v zwmtZ@1-FH%zWg^eN_}`z`6a=(9C!3=vz|VX> zP6htOPHs3FqYg%6_Tt%X2D2N_ZabL$U^ax=5oSyLFMGml3fPrrw=K-RJiCoycIMe_ z4YN1Q=78M++XMCoY!KKXuti{xz$SrRdUo3c_UYMe6th#$ZmXERdUl(|>=v_K%ziN& z#_Sl_GG@=frh#1p+XnUxY#i9RXSa1=@1EV}f!zb!$Lt@|089rkEdY9eX#%DTm^NVg zfN2D#6P{fwFum~Xnt|yCrX84m01W{;0<;9^3D6XvE10$beZe#a(-}-_fZlj^%>lXt zv)~gK#PDL0ZjtBglQ9|PnbqwI)!N!rdOU_voPJlvW2tnm;8i#h+ZT2&FEJ_!Glc B{)PYm literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/West b/lib/pytz/zoneinfo/Australia/West new file mode 100644 index 0000000000000000000000000000000000000000..d38b67e2f953dcdfe942ab3a5123f63d24313515 GIT binary patch literal 470 zcmWHE%1kq2zyPd35fBCeVIT&v`5J)49KS<*=IpT*I303c;q>w131_w!EjYV8bOI;; z?**LOBo;_9CM}R!UcW$7HD-b4`cn$7h5HrUSko2Um1`EbziVFL$sD)9^IYlzCPpx1 zWO#lD@ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Australia/Yancowinna b/lib/pytz/zoneinfo/Australia/Yancowinna new file mode 100644 index 0000000000000000000000000000000000000000..874c86505c896406516a16dd21b9ddd8d0ba2d95 GIT binary patch literal 2269 zcmbW%eN0t#9LMo5ch2jKbI%wXpSQQ7 zc4LO~za!ZEgp=cWb8;WnYAzis-*~m-Y~mjS-ZL`LU+w#-w@N?m%JY44q(uhX<9vaQ zc^Wh=UV=;FH2Akg8sZMpkk4W@H2en*?FrN=Bk${tFUQE#E8oksT>%nys$asZugY}) zaf!%1FEf05WoC4@%qnY;S^qpEv$Kljrm6chGPYhL`&H+-JUXYVPUjA#=`HO=8r2t} z(c6yct%puaOnIBewqDk_B}XN`_7}M=ZjU4sUXp~7QAtedk;K7IB`MS|NhjWsJBC~2 zPX7f>zIaemd~a#$NuQ>by`=5~6`EePRx_G%G;?9T&MS@9yP^_weuks-{|?ctxlxj} z`NWm1uZA`IuQ6G0c0lj`a!7JIf0Bh~&Pi_DhqCB!r{ryTT^8@|(E?2 zOG|QfRbHBU-7$K9OoXnU9-ynoe$@v?u4>uWm$iKGyjHy1s}G*~PAa=ENtORSsn#BO z$k!!nHu+_3+0*jy@)lW_RWIvruapgu9@!Y-(;8Q;){Iu@rlA#Ddm%^b`ckz1&1l{H zO1L)ccl43gQGK-WTWzchlgG-(q^a;{*^)gZk0)J}CvH9`&7p5g^Piovb@-t8zG#8`uie}*IAsul*=kFUvs&*I^K!L5@H^4a_l!J_cO=Lv1PWd$L65UZ*cpJz(!<5 zRx>1IOja`}WK_tokZ~ac}uVBnU_pkT4)|Kmvh80tp2Y3nUmwG>~v0@vxeJAQ7>ekXTJjkf0z@ zLBfK>1qloi86-4FY>?m}(Lut4#0Low5+Ni+Rudy6NJx~BFd=b50)<2h2^A76Bv?qa zkZ>XKLIQ?F3<;Ul#0&`<5;Y`jNZgRXA(2Buhr|vE9uhqyd`SF|03s1YLWsl=38K|R z5eXv_MF(cNJNp4A~8jRYBfB(z9uk>DcH zMZ)U}_^*G#H%#&qIN28`dhc3#pFQH3^zPg@p5CzQGCY27GhAQ_Y%^?w%wKh_#-?LD aNXcH1>E>awC*7Tzp6X8a%!9PdDgOd<ProhH&2#cWpB_Eq<+pdBbKU&F0*DTs+K|`g5Yx zURVCJlvnk9Q=X)s_%imsKpTwSOVHIg9V}(y}WiCTGn~n_+&I`-l0>6*LAEhp5Ja;z2Q?xI{HJ z6rZTZh~h-CqIglvC~g!xiXX+0;uzIfQaqy?Q;I9amRa$o7*m`n))a4wImMk~Pd)Rk R4*D_cpZ@lO`5%jE`wO!17YhIY literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Brazil/DeNoronha b/lib/pytz/zoneinfo/Brazil/DeNoronha new file mode 100644 index 0000000000000000000000000000000000000000..95ff8a2573f4dbb03892a576c198573895a01985 GIT binary patch literal 728 zcmb8sJugF10EhA0BBX?~8q}dF76~=9jm8&1Lehw^2~A8!Hp~oXVqiF<_yXEaBoewc z!P4l9mxUTMp_(Gcd2V8|)V(?R-Q4BgJpcH@Y9i$Pxti=74%d)9Ja_CJuPi6K#DCT!%DIOGW`xX7(G9#*|bMm88sM@Sw z)-Fy&-FL3*N6+fBT$5irYpN%+CH)&2)vE`!96M8e+l#WlJ*@)sULCk!62b7J4qm53 zxSG_F5G-re&R0 z77QHrYp@_b_(xbmeuBa&C`+t4B9>ymhMvnozf4Loy-w@;JU{)}AN8N#@!jJXgE4qN zUy0i0#z^2_&o%BRygW(w^7;BV-)sG_XI%ME&!6+mzH^1TKh-9KvG;ZGOh!*k_|Bf# zw^UAEzFAXmrR3DfZXHTQ|>WB&Uex%bI4@<~&>5PT%h$c5`cw*c{9u^zTStW(BgchbhmYFhHXAZ11 zvo3yWV?8g)>{G)!r!#2oJ(9I^TgICE4)4&S%HPcWJO0vnp<`zLwzMv|Q`vb)SymA@(vzR;l6 zGTS^z831_6WWvy-f<8x`NsYYOSg z>)Jy4^18;5&XCrS-jL>y?vVD7{*VTd4v`j-9=)zfq)V@B6X_Fa6zLRc73mde7U>pg z7wH#i80i>k8R^;Unnt?zy0($Ny{>VjbEI{occgiwd!&7&e`EuY9YD4K*#l$~kX`V) zZ9w+H>ox+}39s7ZflUe z@w&}Hb_dxWWPgwiLUst*B4m${O+t3b>$VBmC$HNmWT(7ttB}3&y3Im%3)wDYzmN?> zb`04vWX~`;?VldDB+uKcyKemT|FdwpbKTk%McwyEQ7|43hr%J9p}}}06y-zi-z1n& AE&u=k literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Brazil/West b/lib/pytz/zoneinfo/Brazil/West new file mode 100644 index 0000000000000000000000000000000000000000..b10241e68dd415354ef12cc18fb1e61df3558104 GIT binary patch literal 616 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 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Canada/Eastern b/lib/pytz/zoneinfo/Canada/Eastern new file mode 100644 index 0000000000000000000000000000000000000000..6752c5b05285678b86aea170f0921fc5f5e57738 GIT binary patch literal 3494 zcmeI!Sx}W_9LMp4A`+sAikYIhq=?EQh6_?+E`)l-1x#^!H1rH&^5lY8h%GMZ)G&<( zl@6x3Vul+gX$Wd+)08OgDL$H#3+RJ;qUZE{-`j5Tu8UsgJ)bkox&D3saS2IN!)*U} z>X`rV^4u^l-@waFg%5>%F-Ky$v zfxf-JOx(#oA@%A4QJuL<-d&I_?xkeO`xEDh2eE1LVV|+$QAmP(+;Oh@%O_Gk@f@R` zJRYrUuJ=|?&qnBHM_VfA9)IoH=u)<9r*>O%S=E}WbZzMr?&6uOGfWAOs7tbL=mFu` zx7UxyZiaWYj%{~=z z__*%p@lR)ZkT1<&e`+!k(Tihwg4GV#nF#uq<~mJTgR%m{TD}`uobb z_@g4O=AIlCo+n0K^U!-;n(IH|=Rf2Q`_zK6bkuu5So=Do-N=~adC6cou^z>uZ>YY@7 zJtMzNrNle6%q&pvhATZYC0ot%JD_LB&Qr6Umt< zeE)2uNY8M{`FmQ4j0rJv!Iw5s%k6poYP&zrum4lOb-4;w*laG>kzzM@m#c7_&C~ks zZGAQzVvn;8=x^SU=6%b&!{W?%*=%msN8EFap36KlZ>LovI;YY?F2>=oSBm_tdkRTvYK*E5;!O{c* zi3Ab~Bo;_8kZ2&`K;nS}1c?X|5+o)_P>`q~VL{@81jf=t1_=!k8zeYrMTakhhsVSR z2oMq>Bt%GzkRTyZLc)Z^2?-PuDN7S7BvweUkZ2*{LgIx442c*LG9+e5(2%GhVMF4E z1P+ND5;{v0J0y5W^pNl&@k0WLL=Xuf5}O(dL1 zJduDR5k*3Z#1siC5>+IuNL-P?B9TQxYiVMO1Q&@e5?&;}NPv+DBOyj&j072pG7@Ga z&PbpwO{9@fTbfuS!L~HfM#7E68wofPaU|qO%#olYQAfg##2pE|rHMQedP@^~B>0vl z`bhYZ_#+1Zas(iU0CEf<2LW;vAcp~R93Te*awH&!f~7eYkb}X}91Y0fU}=sAM-##_e!|ATe{f01&0NPcCmNu8r(HF)a!2+Ad$WR literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Canada/Mountain b/lib/pytz/zoneinfo/Canada/Mountain new file mode 100644 index 0000000000000000000000000000000000000000..3fa0579891a9762b7c131ec5ece5d6d02495bfc0 GIT binary patch literal 2388 zcmdtiZ%oxy9LMnkp}>tlrxHU!BP|I6Mi>RMixs4Hr-gV79MKa` zWy~@ORD#wDQ`cy0pgG+7qjT5gvJx#Ton@mxTP*C}&ig!K>rs!|`riHS>v!St=j~eM zUXw2VansCSc(~Wi!~2XE#!j5?8XVAX4h5_3oiFKb?>4pP#Y=i`+jOz7;S=4Pc~res zc2V|4^{W1ik7d8_Bk^fRnD);9y~$e>Ej};5AWz4AE&iN%MowO;6u!Z1>F@KdQO! zU)DGE99MCkIr8SM18QEmU(Rp%Ox%+Bjl6Z)dtyP4dFNL{V#$7ozH4Z=Si1cuefO>{BDe8`zNc-My0>`0zOQz(%3Jud z&d*z|@_!qZ18x9bN#SgMMv+`6PQPCc}w zNSAs7RatDZc9nmpTvsD?MdmS8@qLo4oO?l3jz-9pzEQDi-?)5utWQ+6dF3O+9iqDS zkX+rhRy^uFscYKX)nmyA^yBqzRU5uT*A*10x+@-CAD^u1k5_7UaHMj-o1+_k_(iSl zTp^!086lqZWXq=p#zkXAjBMKO6;EgWCD%0`66>SR$qmJwVuNo|d$JBF&)8YLF?xsE zI6R^^O?cF^T|N4_FDg}YORL^In4?;%>-3hLu_`cN%IBJ(DLpT6eGjwWa=FtboO$LcGtUb1l(@`ngb1)-u79yKzd6>1EDl*6vOKFsS#2nq)JGc zkUAlSvYJXErQ)a+QY?;YA?4zz7g8{eiXkOKYK9aIsTxu?tEn4OIIF1~QaY=t9a21w z>LKMr>W35%sUT89q=rZlkt!l(w3<31g|wPVBBivNS|Y{ds3uZQj(Q>m<)|o9QjVG; zMMbKLlohEfQdq00EK*vlsV!1mtEnzhUZlQAfjKISl$fK&NRc_JjFg$9&PbuHrqW2M zt)|vUv8|@sNV$=EBLzn)j+7j!IsSi(?l7TWY=WQU%t%R3NlkL5rKO~$q&ofvy_=8y literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Canada/Newfoundland b/lib/pytz/zoneinfo/Canada/Newfoundland new file mode 100644 index 0000000000000000000000000000000000000000..65a5b0c720dad151ffdcba3dbe91c8bd638845c6 GIT binary patch literal 3655 zcmeI!c~F#f9LMp+gDcHj5j!juuoOA8R?9L2`Du%oBL1}16dpAygY5AtBXOLLDRsuQ zV=|z!f|Lj;T{Skl>`^gCP5U`+bZv-ep}BY;${S9wkjrz@V&n5bgwR^MMy}GWhrN~q8T=CXJi%T{=?R(7`biKZ&r~8d% zEv|Lu1^1gqt?R96J$!GcYw)1IBJHpcDhebBS(ht+X4j?JF^eFFLWr`K5ro=#C;jcF|o-WQ_| z_5VqHEy9(G_(B|xZBU1gm5C#r!sLPU z?y9;w&ssg=&ZwyCyNaIrza={4jEFwfBzt|Y#8vygmREngRa{fKMPB>bTG4yn-oSN* z*~aw~D+7J*&;P3Lkmm#a#!UCebek85y%4)X z7oPPG+ffp@<;WcWtrgYg@NF6X+g28vx4)9;ACXsR-mz?~F)|~^ywgZ9QU;}(sVSX} z)YA(BX#?Z^X$K|;Mz`PL+0POn?e1WHhl02|7a`%zjkKBKx0k*mWNDGi2*y<)A zT|nA^^Z{uE(g~y$NH1)4GmviB>UJRgKpKK{1ZfG<6Qn6fSCFad z9Hcw8x;;pLkOm}Lpq1F4(T1zJfwR_`;h+G>INbmL|TaS5NRUPMWl^LACX2PokUuR z^b%<%(oLkDNI#K=+UkxXEk$~YG!^M8(pIFeNMn)CBCSPwi!>MMF4A6G-Cv}^wz|Ve zi;*59O-8zmv>E9$(rBd9NUM=vBh5y-jkFu-H_~uh-EpMlNY9a`BV9+@j`SUAJkoij z^+@lL<|Exl+Hb4-k8A*2y#tUfV5|24vI&q~fNTR~A0Qh6*$K#2K=uN%8Iaw8YzJGt zACL`Ut9JylC2aMcKsE)kE0Ar0>K%e?5nH`S zkWFH%cL}mhkbQz|6lA9$TLsxG$Yw!y3$k61{eo;5TfJkDEn}B;d)@d*Rc6BFYT;}ar(2eU89 AC;$Ke literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Canada/Pacific b/lib/pytz/zoneinfo/Canada/Pacific new file mode 100644 index 0000000000000000000000000000000000000000..0f9f832821b6cff451b5ecccd0b6eac8e53a9190 GIT binary patch literal 2892 zcmd_rZ%oxy9LMns{y|XkWTHf9Cp8gN1QbQlOw(O45tS>68U9INn1+g7v=nvG%KmZ4 z{L?D>Mn1?@qGL9j$`IKq6rElTPK-&|e4bf{|Z_SPpeH>n@yU)QH}i~2FS zL7#SgqZ%U)>c*yh>Wu${oUJwLoUdAb+WWEb)$EX;x4mwfDvITog4O1HNw)l&HqZQ) zlPVWt$C!)m1^QB-xvDv4f^Kdbty)5&bxVDOx_r^EuN=6gT8}sB-^&}-)v8Xpw&t+9 zUgndw%}33R!dkhx_yhAtMy32Y`2}-pRH?KNt5mmp=SfG8Qq|G^yuQ<%r#esP>c766 zq5Oy3I`Cnfa_x@QK`-@E!RveKE^CIFu1jO2+uShIeM+c=BwR5)^koTE-S2d_Azh9qCr z56=8t4UIUVW8x}QjK5W4!?vhc-**z%vP=!HIUpk%O3cWL?Gj(T#EdF=MiRD9HHrCe z=%k_{X0&^q9+TPKB*$dwu}RHlTu6#eiSDLSE=B3_cP^<3$2)cE*{{^Z{gE>1@JH&Q zvJRR2_G{|l!gDgEbg!A3Q6rBmf5l82B{F^5Dl`2?gLaR6S-Bey>a_5cDy@2#p4mEE zJ^D_y%sREgq;K3Ivp=0>G8PrfoSpGz?!;`F=T#;%I#oRL+l;4kfMg|~G+7rW=mi6> zs|8;~>ui66TDZrrANL(pi%OgH6E(Y3&hle=am5C;B;6-VU)*7qjjWX?^NY>$@Jh*b zXPeyCQpt}=HTiXUQV=r06nrv6R$L62r*`J*mET9JRbID#y2`H#vtsq?vL>}=Y)`$m z@R%x!Xw~a7_NaA%Q1PbJ8n5rNtdFcT>uc&{Lwl)twxUX&JDq1XmXyn;Lo-ZCPLXWh z9cO}rg1dCJ&wukT5P0=Xmn#r>*93J91j@F!dN|*`oL9|C_qgUvvp3V;$LyWsvA=Sc zE68~~|Dp~7dvYduuOO8`N`ce@DTbr122u{B9!NouiXbIHYJwES(N+a13sM)PFi2&P z(jc`#ii1=KDGyQ~q(Df8kP;y^LW<;QtAv!v(bfqm6jCXqR7kCmVj8mhZGR0AW}l4hDZ^SDk5b>>WCE5 z(N+>EB~nYIm`F8|aw7Fa3W`(|DJfD@q^L+$k+LFnMGA{l*3p(0sV!1mq`F9Xk@_M9 zMk+Cn3hcC@8NYK;^dsWwt>q~1uuk%}WFM{14~9jQ7}cBJk| z;gQNa+R`JnM~aVBA1Oale`EoW6+o5%Sp#GdkX1mI0a*t}yAa4qINGH^*22*)2C^E+ zav*8n^23Z+LyEMq!INHTQRtH%gWPOkYLRJV_B4mw_ zMM72yStewikcC24%F!+rvR00Ev5?hrw9AF87qVc;iXlsetQoRs$f_aBhO8U1aLCFz z+NDF*&e1L&vU-ko`H=NP77$rMWC@WqL>3YM->VpA$1=r^=7{vs@k#LsBhnKS;}hc( G!u|%QT)5Hz literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Canada/Saskatchewan b/lib/pytz/zoneinfo/Canada/Saskatchewan new file mode 100644 index 0000000000000000000000000000000000000000..20c9c84df491e4072ec4c5d2c931a7433d9fd394 GIT binary patch literal 980 zcmc)I%S%*Y9Eb7Wq@_$lyqJMVi$W|41r?0;Dt1v+oCsXRm>A5;gMUCATqHt^7hF5K z5s4sImBW-o-ctz1OIfDJyk9wlE5VNMb9AR0SH8o0K8Ime^L)c~(HBK>;#@M{a5=^1 z@}BkTp#6HRuUB^_((Lz*Rqls^2hPW`Lbp%db>g{K-MAZa5?2bW#P?n2({6_KIet0v zwK?4#sNZr1Yc}1X`|Xk8!U=ceX0J1vy6h7SJ!;nJl3)J^^zSb%GB@9?|GbIW^Zl)Qq0P3PSX3`Y zpWA<5KGsVQOYP-n`FiEfEqk^6ky^_rk@eeoYW-iXY^}O#duCF0hLh?-;M7k_>ZxBJ z{rIBibu5c`-rKG~s(IIv?!Slpr{XD@6_sJBEH$^*+^6PNho!{4a{|ZD@EQJm&m00E z5s(l_3?v8=1qp-1@il>vNWLZ%5(^22L_@+M@sNN>L?k2<6A9{Tq9S3DxJY1M6B!AO z#72T6(UI^-d}IK=W(3F(kTD>GKt_QK0~rT05M(6CP>``8gF!}v3O6*LRG7d!-P)%6Mh literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Canada/Yukon b/lib/pytz/zoneinfo/Canada/Yukon new file mode 100644 index 0000000000000000000000000000000000000000..fb3cd71a69e3038f0d77e8ddd3290a08fb960d9c GIT binary patch literal 2084 zcmd_qUue~39LMpq`DeNuJfPFL&ap>l&du%YkJ~&t`)8BmbjMA1JbBFg*Z#1jZMk*S z(m7!i8nJJR8mPy|;f+K%8H!*H6AD5k+ae-kNKi&GA`4n6`}Mq^F1zZgi++da_j>lb z+Rf)3-PF=l>ifqt#eU)N9JGh~+;00yUcK3W_F9fHx2N@=>l^C6d3a&}P|k1dL)**r z??nk2TiB-_1h%T_ExYxM_y(0(9n~|JE>W}cDsi)HAX*00t4@}KqNt3OZ+GC$Y3`AL;KK5FvPyH(!E zXD0v09ct0wB~vimC56djCOEK7in?Ak#m81iN%K)%+A&`ihdXsy{bVW6jp>TgA7n{d zNQeBBROnu|Ui#pkTK369U3uqIwY)#eJaO$k^l_@Jz#3SY?a9V<7VxZ8mZm9$JCt& z%DUxovp%7u-d|~=8}3W=VZg-7zmV8>%k;)Mzo?BL`*nPLSZz8#uAd*dtTy-D)h`U5 zR9hNu=&i|pYFlK)Y=85(>?pi$UToeY4H=Y z_<7!;2A|LK zdb?95+Izciin~v9Z{>MsBxMG7-)wge)I_4bc$Gc%_B>}#9e>*ob@oG@l_$l$|2FzB zcHr6Pz#B(SBYQwLf$Rd=2C@%iBTl;$WGl#Ckj?P#up4AM$bOIwAv;30gzO2~l+*4C z*%q=dWMjzAkgXwmLpF!(4%r^EKV*Z*4v{T7?H-X$I_)lzZ6f``6Q16DwjdDa7DQQf;@WRtZuwbh!8o-9po=lfs0>Q(RB`rrM}=kC6|+wXfy z%c?3et$#eB<`-U`m(0ue*xlx67fTlJ_ndbhZ2orOkhMdr@}_~Vraalb(DI>dUtn zbh+Qoc~!pStn~K8kLaFr``x`)J-Tl&!96s+S`Kah&ilpLcKzkF(`q<#l^$;FkXL=V z@><7b?r8S>4T3cVI1uRG;aVWYpw z@WU5HME{74Z1aliU+j~UwsecACx>P{$(D7Z_YTD->IelNensGc|-cUOuR1_q6Fbt&gkJ=eO##jhmF%y+@`O z6sn93RWdVgM7ZQKIX8Tfm>1bAvx0|Jwlzs+e-bKkzE9EfkNzO;JRPZXpBfT*hsSjO z)?;Epn@`J?AFG91PUwQnH`QHBpVNzCo>7JA-LmlKTD2s)Q!W_`5KG4!WzoqRakp=+ zT-Ix;8QA`MrqHHO0~YeS(ooEQumjZ>kU;Y>H(M38;c^Qwny>lM*b5+z&w ze!nBY;dBHBOnjUH&LHy!hx|uA!G3@LyOw32fqs9VvO@j-L2X5FI?Orjbwo{^{Jy-n z)LLoYIbyDPUFMxwqQhaPW*^k3}L{6+q%Ju?Q7og!OB_KIv4*)6hNWWUIUksTvj zM)r(s+ScqE**3CoWaGAG=g8KPy(62qHM>W)kM{ogLIZeA2Y?nJJwTd(bOC7t(g&mw zwx$zEE0A6w%|N<=v;*k}(h#I0NK25OAWcEKg0uzc3(^={(;1{Swx%~obCB*J?Lqp3 zGzjSs(jufsNRyB*A#FnXgfz<5bP8z|(krA{NVkx7A^k!chI9;R8PYSPX-LD%!kv<}gL^_GI($@46X{N2|Celu% zpGZTIjv_5ZdWtj^=_=Azq_0S0k&h)kv>xO|y}1Bke}|jWitTIMQ;Y=Sb6$t|M(n`i?Xn={(YUr1!R_`AGMX z_9OjA?f~Q-K<)zMK0xjS GE$lDZUi%6F literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Chile/EasterIsland b/lib/pytz/zoneinfo/Chile/EasterIsland new file mode 100644 index 0000000000000000000000000000000000000000..cae3744096402e8a452336544edf96ca9ae5ad8d GIT binary patch literal 2233 zcmdtiT};(=9LMqh;W^+W2$|3mS{MbwkLQQ*_=_YVJxF=dBs5cpfF(pgrWD3jVzpF8 zYi_!%R{7BM3tcFi%-?#l2P@BoBU`MSbgMNPJy}}*`@R3wRqLXgF8ZJI`@jA>7w6*a zeBPmkmZn1IZ&$4Sgv0f$Jv^swwzrYvy8pLurM@(9LEIA`8>iz7@paXk2wf|YcNdtb zjBJSxEYdNKUt$xE>ew$QB<@m*zU)|7;>Ul~3470}#L+SB??0(7-#wzIG!Lt!r%svV znn5+S>99%3>Q(n4;#JsL%Fs2O;c6)hTK;3yqTBs zoK)uz>+0{@Wq$IYo<9+xY9_mN?a?-MNBADS;D{p&hbnaNy;xOO-)9!>Iw4h&J

rD5BGI`|PpxN+wx;*-7 zp4s?zsoL~pvgvsxO+B_gS3ll&QT5g(>0Z}$eNhpS|M-fI`7d8FuDf%C<9PQd*FCVu z7w5XWw>yb{-4E<>>?b4QOIjEVIo0;eRwee7+EZ-*_dXx*KM4Jc&Dfv8ZP`*~zuSJh z-43!J^ftr;JL0li0``P#3fUF1Eo5KF#*m$P+N~jbLpF!(4%r^EKV*Z*4v{S)dqg&g z>=M}~vQK2A$WA@&R*}7W+RY-nMYfCV7uhhfV`R(7o{>!>yGFK+>>JrQvU5+nb!6|z z=8@ea+eh|~Gyv%U(gLIhNE47QAZ<YUfHVT>1kwtm7f3UZZg|>uApJlZf^-CF3DOg! zDM(k4wjg~$8iRBOX${gFPum=%JD#>ZNPmz9Ass?mg!Bk$64E84O-P@RMj@R-T7~oq zX_lw$7Sb+H+b^VHNXL+tAw5HyhI9>S8`3wVaY*No)_L0AA<gr&-9y@k^bctu(m|w! zNDq-FB3(q<i1ZO@B+^Nwl}Im<W_sFgBJD)_i8K`HDAH1-r$|$gt|Dzk`s!)Z@qcY> ee5LJgpv2yb13AI+-2B{<yn=$9V9}pX@xKE%a7T*( literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Cuba b/lib/pytz/zoneinfo/Cuba new file mode 100644 index 0000000000000000000000000000000000000000..8186060a4b2a81934eff93a2733b581bf60f299a GIT binary patch literal 2428 zcmdtjZA_JA9LMpS#~5l+K1UKJiIp5Y&~X-u_vQl_Ab;X$3SwqF2t=5XkO;Y!lC6zi zlw>kqRL0yYjBH>vyO;PdNAsWLw9%M>`J&CO<&2EvbbYU_t*uwBUUl8O?$5pR{`Wn> zqRR3#=Wi$4{KDn5o6C3HF7tYS^Ow6m8hBm0>q^|y#pQZ>pujzok*#Mwukrem%A~(N z-}}06f}G2^?hU+iRlbS8;EisI($P?pdt=FRy)jbg{Wh4PW1V~4-%lLUn=M=1@m@zm zog<#pHmqSSC%o|bK8@Hq>_%?-UZMh1ylD5h+%hfOjY&KxF{6!MtkWW~KUKLCE>+6J zZ})hUyd1gp=oas`?zbhb>41BC!H;@Jd5<^Q->*|v?)Rn^zo^sZHhR-DN_9qbi8nKT zrOv#v)Vp(Rp2nZu;NCSDtFyW?-Gm*5a(8Q@n^+W(*|p*BJ<AGo&g#o<(wua?*LTvL zJM|S!o<8g)k9W$v(Q|s=&|bO!!V!JoShdXW*{3NTdE#qp(A4HsSx{f3{)!w;du*|$ zXQk+a?s^H#Ixh>;4$H!uJ+dgiUl&~&(1*r8)Q3-gq8Wp)>Ef<)vgEUEn%R0pmL3SI zTVAXymIq|TwO2JOX}V;6cSu%6+>liti#{?kC^_vllG{J3dAs-O>MvGne*GntH-3?V z#gpaH=PpWN{B;Sg`BZ{q7i4XqUDjT{rt1=VbzR?iT|fSo7QNe}#X~!F!%O?M<k&{t zSlXzMceuJK?@f84r9?KT?2sobmP+ZQ4N{ghTgt9xN=0&nRD6{vmC-*)<p<$Xb>);) z?@Q39&W>o!c1NG?I-#{|hIMOer#=(t(`~hT_1UTKX<dG`){P&R?TcQP?L!BpVMdiS z9BYy1f6bL09hK5}(I-2bbEK&^PMSBS$O~;hOISqszkMRZ|MEmd{&!C()P34<%-eG! zL!nb%SWGB%^sqDW&o{s1<^`Q>bC)eQw=ihd<2Yeq7AN=*b{8_IvSnT`vOi>l$PSS$ zTFoAjO<K(^k!@PdK9P+gJ4LpN>=oIp)$A78uGQ=p*)XzWWXs5&(QcX#cFi%{2KJ3? z9N9Utb!6|z=8@ea+qatiBMq>c4j?T+dVn+m=>pOQqz_0VkWL`2Kze~R1L+3R4x}GQ zL#(DFNK25OAWcEKg0uzc3(^>*Ge~QY-XP6Ex`VU_>5tVk2<Z^gBBV!1laMYUZ9@8l zGz#ey(ki4^NVAY`A?-r?Wi<^$I%YL3LwbfZ4e1)vHl%Mz<B-lFtwVZeHO)i1XEp6Z z`e!u_L^_DH5a}V(M5K#I8<9RDjYK+$v=Zs1)ie|7rq#3)>8I5+6zM3^QlzIyQ<1JB zZAJQuG#2SB(psdqNOO_yT1|VA{#s3gkq%o;i;*59O-8zmv>E9$(rBd9NUM=vBh5y- zZ8hyi`fW80M>=jbEk}BeG#%+W(srcpNaOMU-uYM){($)dn4g#KOY<#AT`)h-@Avu5 Hmp}Fofim_s literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/EET b/lib/pytz/zoneinfo/EET new file mode 100644 index 0000000000000000000000000000000000000000..d2f54c9bae1f689433b7da8bc38655c7f2aad22d GIT binary patch literal 1876 zcmd7ST}+h)9LMp4#4Kj?M?*+nlL*iw99~0%h~U6fFoBbjN~F99MHEB~2x2Y9S~2!( zV>)Lpos2nW)CJiEP)n=nH0I`*^KEUpGRrYHb83yP=lk4t)m1%bXP;;1;#~c|zrePZ zrcBR2o<+_te0h4EFYgaMJXWu;4_DYDgML|OuEHCVwKNc=Wfdj%Xx5*KNc>&Pmt9e0 z@DGZ-{gtAweWu6GAJmGIA1Qiz#$t|5SnS82SlqBIzVBU2Xzj4achuX;@_;4g`|SyD zt|i4J+mlP8EIFiD$#=4q@^g|>=ay^LcZ;=p@}9h7H?(H-lF|mxD7|x9zJ^bgQG3xc zJ0>lwV9K(q_E=8r0n5$ow%mmQ%ZnaW-qoG9_OC9jn<=)ZFV`r4yiNsE>-5Y}kqY<6 z=-H-36%DSn=So9V+z@RgN!R5sUa-=T(<)2+(aPq3vGu{<*!tO1w&B`g+c<I5%FlkS zijiU4bnK9xZ)>y7!|$nb>j$c8ZB+GBtqPQv>4nHm+Tu;smb*UHM3h?1g{6A&PKs?i z{)e{Dh1!mT;Z{3&(_R|5ZFQrw_Hx~v?d<<ey9&Rw`r5N<h~H<81rus|@C)sZm3IHS zSIr9@YB|-TSFQ&1>XGeg{l48^d%r+!<JI<hbG+Ud%CYv!eCue6v^O*2tkZwr-U|0v zS9*lHZvAB4VRzMi?py1bzo_2N$L;OeDfR6;q5g>jdZ%Yh10w?p^88mHk0&O<xirrz zU(f@7f57ASR|h;n&J*|-XUZRNs>rz_CySgda=OU*A}5TTF>=btIU^_S>YO!l+Q@k$ zCytysa_Y#rBPWlXJ#zZU`6CG+86YVjIUq?OS-3iBAbB8(AekVkAh{sPAlV@4Ao(B( zAsHblAvqyQAz8UPX(4&JI*B2fA*ms`A;}@xA?YFcAqgTGA}Jy{B1s}yB55Ldx;lv> znYucuBDo^TBH1G8BKaZ-BN-zpBRL~UBUvM9BYC?zi6fc2I;kVMBgrG#Bk3dgBNKqk z05S#093Yc`%mOkE$UGnufy{)fGZn~OAd`X21~MJUd>|8o%m^|i$ebXPg3JmsEy%ne z6NAi*t1~so+_*ZEgUk*xJ;?kZ6NJnVGDY}5oug1^oYrKrPTq>Fw45|wdX~2$!{@$b GhyD%6ikYzh literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/EST b/lib/pytz/zoneinfo/EST new file mode 100644 index 0000000000000000000000000000000000000000..074a4fc76ad816447121db6cd004aa83ea41d437 GIT binary patch literal 118 rcmWHE%1kq2zyORu5fFv}5S!)y|D78c7+ixxfSeFA^>G2Un{ojFs8bC{ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/EST5EDT b/lib/pytz/zoneinfo/EST5EDT new file mode 100644 index 0000000000000000000000000000000000000000..087b641d2cfe4a7db66b627cf3543f5a7baa3537 GIT binary patch literal 2294 zcmdtiZ%oxy9LMnslIR5zDhd^;pkg5Z?J6P_CXB9jh4f-bre6)bLnuyaHz>oJDCyQ* z)1ZH&EHi6!WM<Q9OwE~FGgDTxKf_~NtX0%8df+7Q?Yz&^9`&qs?#_ArcAtOV!G`A5 zC7yqrIQtKm^Mbv6C-?2iP_KRTi@d(YqjtS~N<M$qsIysrs<SetFJG!RV?WK&f1N8c zS58jTkwfVw`gT-%dLt&L`+|;rT$^z!B5`s>T~(`Pe8qVcUvy03pPQ^EChwMs`*s?y z*&+#gvr7Et4V`o(q^>^Pr6;|!$t1tDTBmGnGS_t6qpxkQHrFl9)v5j*bN%c@eM3%y zNz2TU$rDD*l-LBB@`qPVy)Yu_XMR=Fj-Hl`{a>gXckP#(!X9(;Gdm=+?gx{#reELU zJ8o{B|Gd5}b&tu;U8l1npQ;>hoz5A0OXdDNOHUu@R(XR#$?t1c1qWtIVdnx>v?EQ5 zLm^YL_Oi^V_M6)meJ{T3>1JlhfXs?dGNn_$)uqF~o7rQZ=&}=M%$)D~bb0@8>W+_} z)^~3IQr-2+DqYd`zM9*$QSPqUspd7Vl*+=es;ZnPRik@Nb^Z!nJ-E&IljdswfmSnr zBug)Nb*sAf>k0{MXi*D4%#@mC0ae=*C-;>Xszr|t%i{DD6|6fc!Ld=b#P_Z&`Ein| z&p4s$k6$!PBYSnj?m_dw&^EoS>!5jXpj9t#>@_R;7HSE6pjLLb$g2Dv^-w4vjY;jQ zsk%^_MjBKoJ4HgL%2o6DQEA?nq1Ft4B`rN=ruFni{ct$lJkmd?+v*}lZ9k|V^=Y%V ztyiy0J!aO|bm;Yw^D120BjKT=sy(e;+6UfL9hVy9iN4M1$#dnhp);hOI+P)u%l&HO ziyn#b#CT%I+2_CXi$)K>=kY|NpB;&bbMFNACRQyDda5ezz2Dy2AmfPP2LHu~qV_N( zWK77QkWnGSLdNB^14BlJ3=J6@GB|Xj<AdRG?D&8IA|phGh>Q^#Br-~5m`*!RWS~wv zQe>#eSdqaZqeX^`j29U&GGb)N$e58qBcn!!jf@)^I5Ki%=uSI!Wbnx7k>MlbM*@IE z00{vS10)DY6p%0=aX<p$w2?qU;k2<pf`LQ>2?r7nBp^sckdPoTL4txr1qllh7bGx9 zWRTD}ZETR>Akjg>gTx035E3CIL`aN~AR$pg!i2;L36#@D3JI0d#tI3R(?$yk7ZNWd zU`WJ}kRdTcf`&v52^$hOBydg}IV5yW8#^R;P8&TWd`SF|03s1YLWsl=2_h0jB#cNL zkw7AmL_+Dbu|$IDw9!Pu>9p}g0*XWw2`Lg&B&bMKk+33hMFNXN774A>#uf>#(?%Bw uuhYgC2{002B*aLJksu>c#{W&2y|&msTkO2RjDnJaqP#$HaY1oGk@s&Yp|6hs literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Egypt b/lib/pytz/zoneinfo/Egypt new file mode 100644 index 0000000000000000000000000000000000000000..0272fa1ba0a09ae8380be83cc8838d97d0aa6d25 GIT binary patch literal 1963 zcmdVaZ)nw39LMo<r>=RO)m6)8UR|~2n$6tOt>w*SdS&XQU)(!N8TDI<Qc)aPg1TY{ ziNZ3%_CQHc$%;z*qn5f*A%mK;+EH18LY;-^GMlHlDZ9GXc^@AJK@WP+ckI3{?txFv z`;%V$^wKi%w;SsIg<r18{qlXJav!bDpPSZ9qdt7~f@%A<OCLG%N%-B*cj@nUbebQ& z>DBFTE;ahYRr=`5p*i;2$3a*5Q(;$cNpNmNOL(p$)W4n2*z;dJCw^~lvfUec#D&jx zOS`I2{jvUx?OD7?Tx=L8duOaueYIQUr3o>0x%`;DJeU;yw`9xy&Nh+hue4VV4XCT9 z4%&hD)~G-C_sGFl_6V`L&_<U1s<N6E$szONVrczOa@gc|ME2~My)L&y<rE}kPWNyT z9k$X&+Y@TU*#Voo^D8y-aH+ihtv+$XM@#IDFBGZR>l@{$+Bz}%`M4ZY(J5|vIIuV8 zjZtH_Hruh8YLO?m*}PMYYFux^-g;1|@f}jew@ecg_H45g)iRa8;e@=cev`O;b)CFp zPQEC3a*`}8OsP8)U&~3^uZp7hC0lg%OI0iyZE;(bn%w!RynB0tC^__towDwIb<ft( zX6m!Ky0od!l+Ewf_dapLOe@xUdR2=lkM7bHML&iW7u$o%oPA;C@twi_U2lgob~gu! z))&JEnpXrfKX^Q>YIrnwaQ$jCtFA#mw7kyDPSol-kIXd5Q3Ju;(tI=5JQGyMvP|{f zn4V`(oB40F1Pe|^!kYR6x@PTTrsmyDkXmt{N$psxQz!R_>4G&uR^&hW8ItwSKiB?W zA>y^}^@-xC5%(0w=ZoRjzSk^Fi)1pzN1DG!_(=bYH$CX?r2`AMBX8U5-Z%2bk#~-~ zb>zJxZytH~$lFKWKhglw0n&ok^?)?tbzLBBAblW>Ae|tsAiW^XAl)GCApIZ>Asrzt zd0kIPQ(o5<(iYMe(iqYi`qubDZ=7om=niQQ>5rp9q(hDtksgsIy{=1+HodM-q*0_( zq*bI>j%JZ=k#>=OIT}Vf=4ct|8EM+<y5?xx>-t6-M><DZM|$UI9_b!wAL*ZC1CSly z*aBn^kWD~#fnyuIZXb}1@VcEqwgTA;j?F-J1KAE_KadSUb_CfHWKWPyL3Rb%7O&eE zWMjN;XOOKy_6FG;WOtD5LG}mPAY_M-EkgDP*(79_kZtn1eL^<M>vjs+DrB#a%|dp| c>$c15_6ylCuiG)N+cNyW?OD`~TS-~;FR%xpXaE2J literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Eire b/lib/pytz/zoneinfo/Eire new file mode 100644 index 0000000000000000000000000000000000000000..5c5a7a3b8d67f5a5c9e44a56e70a727967d972e1 GIT binary patch literal 3522 zcmeI!Sy0tw7{~FyAqlv>np>js8g3Zk0hiPyat}pvIVov|mZByZk};wgIhCd3IFAiw zhUDu`F1X=}<{Ii`E-5a!Yc9C2i3{5M{WsH1SIv0QMd!>ppTilvafaXb@%9;-5aIme z5n#XJ#p9fP@ww8c_AR5{iYXZfOIMh_$73?*Y&Abj&oncpRyXF0b$VvXBQtBzbUk~_ z4l^fqjhP$uP|r<|a^}_Tujkj#(^(Beb=Kt~v%uMJ7UWmf3kz@PMcWhg;+?<g?D?^J ziAgm}zx3#3U+*=`lVZ$@<mD!(TbNlH-AAwTD6={u#jGiR%dD*!XVzXnVAd5nI`{BR zz5Zx#y<yFM{nN6?X5)7&^`?wKy?NABy=8Q<-Wr#xw{@Rmes0lM=e63bx5phc+Y9{7 zf#_2@zgBH?Fm{nX6xu)^4x6kG-~UDzluObDm#^rf=c<}xzwFk>x7{}<axR&Z*;VwZ z^j+q3@@wYIu#x&~kA~)Vub6WYWz6}=#ri^Eh`w0KYc4)4tqY4s=t~7x_2uI|^_6vd z^wkv)%(d^A>FeVLn;SF6>YD?i&8@U}eY<mlz7yX@->qHN{1Fwb?>W~^QIM}LI<?Q- ze|$kd*tEhtyy#;djag`lx92ALB<1OnA#vKbTb6#-zm+cKnW#$@*3kYcQTy+BtOIVu z>e9=rn=*Sny6lukrrgqsy8MU}MokMd6}oRS6;qXYE_{}$6nD#14!$f^TI5MppI@a~ zwJfQ2c8NS+G*PN=og#s!=c^ivvQ^E^6I889qJm})Q#vtO)gISXy%6J7!2=qrI-$)~ z-OgR4UYTmDe#1sm|87$W2`Dci`BkK0;Z1olr$|C~?w3aC1rqk-N@+ZDy?7=}mGFK? zR77%)Y7&{Nn)disHLIDann#RM&5P4ii@<bgaeRPk`7lLVZD^-nJ{l*j=fz88ZYz0Z zd>e_%s3ET=1WTLGTdKAleWl&NK-IqP1?kYPSatN>DV>5(s!rF=t7xCiDth-0)%omf z)g^m@irKYMx=x;?Vi(Pn*M|>R-6nk|-Fr`z9*Kjb=Szv=jp-zBRE?Ehp&`=io=4&; zcT{nQD$1L88>l|3?nvMK0QHusp!(%pQE#W+R`Kb(RsZ;WHDK%|c_(VMdbi&$85lH8 z4T{W`1izIsxTeV9i&JGtak32Ekt**U_sX!WzLJ<XLcRZ0qzs?eQ++TbNRq}kQzN>! zR>=uf)raA=)W{C^)khT^mD0Gfq}({8MwPoKqxNo7sn?Fk=%w@2nBBSZ@w6>6Hak;3 zNu48UlhdTcGbMfgem?74@+m+4OZoi=o==`UsN*>Hy}VP>ar}Zx_&H8FRicdDBgawh zXZy`xpB<-!`;FuNj^h{8)$6pkujrm$r>%W;vY+km*oS>{|B?Hn<NX&y_{2VX?+ZAF z45F(YMPwL}aYP0Z8A)U)k+DPu6B$ipIFa#mwF8QbC^DqTn7Z0QMMl-t4l6RQ$iN~a ziwrF?w#eWjql*kLGQP+FBO{CqF*3%;AS0vfYKIvaXJnv}kw%6Z8Ea&)k<mtm8yRn8 zz>yJ0h8!7lWYAshs3XIUj5{*$$jBo@?`p>$8GKhe`pEDj<BtRYi2xD;BnC(jkSHKw zK;nP|0*M3?3M3Y;HW)}WkZ>UJKmvk91PKWe6C@}|RFJSBaX|uuL<R{B5*s8qt~NSI zc#!xY0YV~#gor;IVuS<<i4qbfBu=h2P)MX)ZK#k~A;ChTg@g-<7ZNZeVo1o4m?1$! zqK1SGi5n6)Byz4cbV%%w;33gN!iU5U2_O<dB!ox|ksu;bM8b%~5eXy`Nmm<6B$lo= zm`F5{a3b+U0*XWw2`Lg&B&bMKk+33hMFNXN*42g<iLI**E)rcNyhwbJ03#7bLX5;1 z2{IC8B+N*hkw7DnMna9m+SLXdiMFc^Hxh3o;7G)gkRvfif{sKT2|E&ZB=AV&k<cTt zceTMsqVH<MkHjB20FWa9IRubn067SdqX0P!kmCS35RfAQITVm%0XZ0uqruf44#@F< z91zG6fgBRZF@YQu$WehD7RYgd92m%vfgBphv4I>M$kE|y4-e${aJ2^ra)cm<2y%=d r2MKbNAcqP5f1L2Ypq|cg5@4^FM&b5!@q~5__k=YIvo?Xo;Q@aFGI8~j literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT b/lib/pytz/zoneinfo/Etc/GMT new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+0 b/lib/pytz/zoneinfo/Etc/GMT+0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+1 b/lib/pytz/zoneinfo/Etc/GMT+1 new file mode 100644 index 0000000000000000000000000000000000000000..087d1f92576a312c68393936662125d9e21e232a GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|BoLS7<3H`ft(OB^>Nt%_1hV80RYaP4U_-? literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+10 b/lib/pytz/zoneinfo/Etc/GMT+10 new file mode 100644 index 0000000000000000000000000000000000000000..6437c684f8d14b58dbdcc75322149fc60b08bd82 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5S!)y|KbD&23<n~ASZ-OeOxv`{dR^1TmX893#b49 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+11 b/lib/pytz/zoneinfo/Etc/GMT+11 new file mode 100644 index 0000000000000000000000000000000000000000..72a912e050e1af97d12a541f69ee01584c52f509 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5S!)y|I`2m23<o#ASZ-OeOxv`{dR_iTmWzU3vd7c literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+12 b/lib/pytz/zoneinfo/Etc/GMT+12 new file mode 100644 index 0000000000000000000000000000000000000000..6938a1aff2b9a786015ebc0d0ea4d95bcf146f64 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5S!)y|8NHe23<pt`VcbpaoGU%+Zh^h0RUxj3pfA( literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+2 b/lib/pytz/zoneinfo/Etc/GMT+2 new file mode 100644 index 0000000000000000000000000000000000000000..a3155777077f680d885a3e820fe0a84d5b238d58 GIT binary patch literal 120 scmWHE%1kq2zyORu5fFv}5S!)y|Hls)7<3Il>O;uX$7KW5Z)d~>0K`lUTmS$7 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+3 b/lib/pytz/zoneinfo/Etc/GMT+3 new file mode 100644 index 0000000000000000000000000000000000000000..ee776199ab76fcb46bcbb3d82e99d0b88cb36e57 GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|Em`m7<3Jcft(OB^>Nt%_1hV90RXxB4I}^n literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+4 b/lib/pytz/zoneinfo/Etc/GMT+4 new file mode 100644 index 0000000000000000000000000000000000000000..1ea7da29dc7d975896c4cac40ee5724b5b037147 GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|KkT37<3IxfSeFA^>Nt%_1l?n0RXS44D0{^ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+5 b/lib/pytz/zoneinfo/Etc/GMT+5 new file mode 100644 index 0000000000000000000000000000000000000000..dda1a9e11ef7dc78cbec231e4e0463bc41f999ab GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|D78c7<3Ixft(OB^>Nt%_1l?p0RW{|4730M literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+6 b/lib/pytz/zoneinfo/Etc/GMT+6 new file mode 100644 index 0000000000000000000000000000000000000000..f4a0385567fe2a6998abccd61bfffb4291b9d814 GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|J4f^7<3KHfSeFA^>Nt%_1l?o0RWo>4153p literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+7 b/lib/pytz/zoneinfo/Etc/GMT+7 new file mode 100644 index 0000000000000000000000000000000000000000..2d2ccd005b9256ece18b7c48860feb77f08a2e54 GIT binary patch literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|G5(w7<3KHft(OB^>Nt%_1l?q0RWJ)3`76` literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+8 b/lib/pytz/zoneinfo/Etc/GMT+8 new file mode 100644 index 0000000000000000000000000000000000000000..826c77001b6645680d430fd46e04699e003e34bf GIT binary patch literal 120 scmWHE%1kq2zyORu5fFv}5S!)y|NaIB23-RSASZ-OeOxv`{dN{y0ES=;3;+NC literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT+9 b/lib/pytz/zoneinfo/Etc/GMT+9 new file mode 100644 index 0000000000000000000000000000000000000000..b125ad2bcf93ad431545d6390ac76ea2e8aafe07 GIT binary patch literal 120 scmWHE%1kq2zyORu5fFv}5S!)y|Hc9a23-S7ASZ-OeOxv`{dSgI0DKM$)Bpeg literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-0 b/lib/pytz/zoneinfo/Etc/GMT-0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-1 b/lib/pytz/zoneinfo/Etc/GMT-1 new file mode 100644 index 0000000000000000000000000000000000000000..dde682d83c0910e3aaa4de1fc4a56846483f5b93 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8flq*eLEFF($O$1+AD0bKzn!ij7XWsh1~~u# literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-10 b/lib/pytz/zoneinfo/Etc/GMT-10 new file mode 100644 index 0000000000000000000000000000000000000000..352ec08a14a107fbcff0844659e60e8bf3952a92 GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8p=SXDgSMdokP||tJ}w)eemh-511<pJPY8to literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-11 b/lib/pytz/zoneinfo/Etc/GMT-11 new file mode 100644 index 0000000000000000000000000000000000000000..dfa27fec76820a74da31e40ff8698fcfea3fbc8b GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8Vb%r)25mz_ASZ-OeOxv`{dT&BhFk#b%Lv5) literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-12 b/lib/pytz/zoneinfo/Etc/GMT-12 new file mode 100644 index 0000000000000000000000000000000000000000..eef949df27f2d63a9d508ee691637b8326829e04 GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8VZ{Lk25m!-`VcbpaoGU%+vyq_aRC7MM+pT0 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-13 b/lib/pytz/zoneinfo/Etc/GMT-13 new file mode 100644 index 0000000000000000000000000000000000000000..f9363b24f0a4c737afbead84994debb5fa2c9bda GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8VcP`;25m!QASZ-OeOxv`{dT&B##{gc(g{TX literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-14 b/lib/pytz/zoneinfo/Etc/GMT-14 new file mode 100644 index 0000000000000000000000000000000000000000..35add05a605a4ed9d7f353c78ee3db8b014e8141 GIT binary patch literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8;m89925mzVASZ-OeOxv`{dT&BCR_jyP6>qo literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-2 b/lib/pytz/zoneinfo/Etc/GMT-2 new file mode 100644 index 0000000000000000000000000000000000000000..315cae4f9e535ee35b868a0e3709e2a7fab6a606 GIT binary patch literal 121 scmWHE%1kq2zyORu5fFv}5SxX8K}LarLE8YNK7>qtTsA=ccDhDf0D;;Dc>n+a literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-3 b/lib/pytz/zoneinfo/Etc/GMT-3 new file mode 100644 index 0000000000000000000000000000000000000000..7489a153dbb61b904090d6bb484c6f29770f44a1 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8LCb)FLEFF>$O$1+AD0bKzn!iz7XXbH2DtzL literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-4 b/lib/pytz/zoneinfo/Etc/GMT-4 new file mode 100644 index 0000000000000000000000000000000000000000..560243e841ff12e0554eb328e40dbcbdd65b1327 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8!NP%oLEFFt$O$1+AD0bKzn!iL7XX-42KfL0 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-5 b/lib/pytz/zoneinfo/Etc/GMT-5 new file mode 100644 index 0000000000000000000000000000000000000000..b2bbe977df886770874563aaf86d7a358814ac00 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8!7YG+LEFF-$O$1+AD0bKzn!ir7XYJ?2RQ%$ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-6 b/lib/pytz/zoneinfo/Etc/GMT-6 new file mode 100644 index 0000000000000000000000000000000000000000..b979dbbc5c86789f34638aebc9644e7af0fb83bc GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8AtZr;LEFF#$O$1+AD0bKzn!ib7XYr#2YCPh literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-7 b/lib/pytz/zoneinfo/Etc/GMT-7 new file mode 100644 index 0000000000000000000000000000000000000000..365ab1f64683d2b1867072378c1adcdf289e432a GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8A*q0YLEFF_$O$1+AD0bKzn!i*7XZ2o2e|+M literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-8 b/lib/pytz/zoneinfo/Etc/GMT-8 new file mode 100644 index 0000000000000000000000000000000000000000..742082fcd4fcf8bc9c1a8b8f35af7533d74b0a3a GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8p`d|*LEFFr$O$1+AD0bKzn!iH7XZab2l)U1 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT-9 b/lib/pytz/zoneinfo/Etc/GMT-9 new file mode 100644 index 0000000000000000000000000000000000000000..abc0b2758a3b5fffe3acff6d42973ce9632acc33 GIT binary patch literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8p>6^LgSLSskP||tJ}w)eemh-DE&$Ml2sr=% literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/GMT0 b/lib/pytz/zoneinfo/Etc/GMT0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/Greenwich b/lib/pytz/zoneinfo/Etc/Greenwich new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/UCT b/lib/pytz/zoneinfo/Etc/UCT new file mode 100644 index 0000000000000000000000000000000000000000..a88c4b665b3ec94711c735fc7593f460668958cd GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U33UzuGD67I#|6}Gzy$z!Xa;ov literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/UTC b/lib/pytz/zoneinfo/Etc/UTC new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/Universal b/lib/pytz/zoneinfo/Etc/Universal new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Etc/Zulu b/lib/pytz/zoneinfo/Etc/Zulu new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Amsterdam b/lib/pytz/zoneinfo/Europe/Amsterdam new file mode 100644 index 0000000000000000000000000000000000000000..ed064ed4ac9d86707173d480921c6ec27bb69916 GIT binary patch literal 2940 zcmeIzdrVe!9LMnog)}<iS4<$qTSRz4d;ldiNeR(RAk-%!1uu!HMTSTWOT$X7)o<>b zW0_O)hM*1+4Q~Uy-<nERqttY5y0VLrn>n?{>i0gaE!RJ_{^+01;rw3DbNCnDAK!#o zd2yb<TtW5?57$on@VVzgd#rR~uC8qQEPK_tJ<jU#{m%Q9k<OasA<kM&GV6+OIqM4| z%!bVKPI*Rv*_cr8R76fRl~D)Hrb(C0<~AG5=H>~`mMe=*Rn#S?>i7(^^@lvCx^lGH z*0{pdywTTe-`>saD0s~IVA(jQHlvTTbM9iZ%j-D1eS4if?VFvw!%NKGAI~^-VRKB~ z*{i1h_C}{+Povp){;b*mUX3}>kYf&(EHj5HPnts)a<UI+6%`yQJyLLVP-elgDL1bj z?^$^5#E2akr>=~V)0;!JaecCUv}}N!c`Z^tE)0=R(uZriJ}m|9%BN}2+nuHTq7;>) z1nrO;rNO>1?U*(~?v4LhJ4Jl2AssJhXzSA&dVQaUUEZemeY#dVAAU!>)Hg_YRkd_o zwMM!bmG1Ljl^*$p(ldLS+@I=`h@mO+Ky-pchDXVRPMAamBx}@-0ov=UNbTJeqJ56G z(Z02}G`jp-?N@eQV-}s%{zdib&E2Z8Ip-v<uvX#|>tw*VcV%GL)shfbA_*;}GN?<L z4!Ss12LDp5LmHCh;V-guXvJhrtQ(?_EJ@N~E5mhoUW6ts8Z3{F4AA7<E;1tWil!vD z$jE>XbyUQcGOGD&8QuDzjBY$3k6+#(W2(1E>Z$#jw$w;^)jEA*_H4;8uj|;S-qLaT z({=nq`RYp@rB8;YYi4wB&AjQ=tWF~(>+@iJ`bICAu<IwC*c2$)Ydc6z?NymndR-=$ zHOiF9O)_=Cclyk*-7+obl;(C{A=4A9HLvYXozYcw#y87#W=o;YIx$b5z39{DHciz0 zV{_#BHxqSs#dvvPW_K-EGEnA>9V&%+p)xnNn-rzomKQsCq_}@4ExvY1N`h``$(bWE zulbz5R8t`@H`eL=6^C>|^=f@(Ub&VoE!9?jty=s4&AUy&UG@%ad$)VHyT|#jZjZ;m z%>FNWg3JB<-Op|B{On)jaqOD|e!+j7LE;JXc*1-5h2MGp{+=^6&F4uU@AD*C`+B`` zUVFycGdaa)ANvb?`Ps`J{QT*%-+cd|BEQ<JRq)t+q$aMmC`eV1vLJOq3WHPzDGgE^ zq&P@*kn$k)K?;Oa2q}@Ptr1crq)M)~Oh}!OLLrqxN`=%4DHc*Kq+Ce7kb)r<LrR9! z%+(eRshX=T8&Wr<a7g8l(jm1&iicFs)s_#bA5uW1f=CIG8X`qRs)&>ksUuQIq>@M} zky`SXKrvlyHIZ^6^+XDaR1_&GQd6X;NL7)tB6W4Og+(fhloqKiQe33ENO_U^A_Yb& zjFcFuF;Zlt%1D`!IwOU4wUtInjno<`Hd1Y*+(^BVf+H12N{-YVDLPVhr0huDk;1#$ z$|I$BwY5izk5nHiKT>~W0gx3ymH=4;WD$^6K$Zbn2V^0Tl|Yul)vg7y7_N3TkmW$u z16dGcMUW*y)&yA;WL1!5LDmIX7-VITrE#@ugDj4#T^(e3ko7?p2w5RyiI6oy771A; zWSNk4LKX^HDP*aTwL%ul)vgw@T*!JM3x=#1vSi4bA&Z8r8nSH2x*-dPtQ@j*$l4)` z=W16ESw2_0e#inMD~K#1vWCbaBCCl1FQX^OT};k@wTyvwKl{ZppwVgZF#}_~(P^<> I>>nTa8?hLa1^@s6 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Andorra b/lib/pytz/zoneinfo/Europe/Andorra new file mode 100644 index 0000000000000000000000000000000000000000..5962550392fa78514061582e9371c32b9f1d929b GIT binary patch literal 1742 zcmdVaU1*H~9LMqJm=i7OKjo!thM5`1UTtPJ%yG7`jqN;aUS?kA*f_?HS(~>*){@rx zUo2WIL`&kN3>PC2B3dpi&5|OoNs5KEc)pKIS8kMNJ?C>;=koXcn=7k)DaId<)A|oD zkKcOv9<ks0c%0K`M4k^x)bSHCu305|&jA_l56Fbo8)Ra>DKS|dndC~B*vL4UJZy}_ zIo%r9KSQUyiq-g@Q9AX`5S`ZfO<iZ+>Gam8n$Y+_6Kk(&QpE*LF6)()K&PZ;cgc+6 zBa#-?F6k-tl0FcWjIphn@ob&U`cbEu*WEI^uT---$~C(yQ|C10=-gwGI?orQIgPVq zey&s96=P*V>>KsC2PD^dU-M#~OWw!VvM}tXEbP7~i(a0V#i4VO|L}$uv@}cM`BS>2 zx>}YtAJn3f!&>a$sNR`=HS_ay+2}%D?uystUz4<SWUiDx8KEosr^w1HUvyPZxU4=I zA!VH(WKHn1l(%-v+VUP*xA(oSpL<m{ls(jn=r-Ay9a7)W%epB_b<^9US~(EVs(TH( z`I)I(&aP7b-5S|?C|j#Lyt1t_TDLc)Nlj6f1bm}qNAftS^?a9|5r)(yj?}tOFQk6> zSFL|^TN*z0>aL3&vb(!W_q5&8y`gs9*KkIIEkSh*3LE^d{tUyxPIv|z#&9u)8b;)J z$FSeu^9xL)#A6z6`}Laq%;B&<%)c1mPwUy2eyJ51A`fFk28oOk874ALWT41Mk)a}E zMFxwE78$OsHC|-E$cT|4BV$Gejf@%@HZpEx;K<04p(A5Q29Jy$8NRJGek1@}D*_}0 zBnBi1Bnl)9Bn~7HBoZVPBo-tXBpM_fBpxInTPq?YBwH&cBq$^*BrGH@Brqg0Bs3&8 zBse5GBs?TOBtTm$LL@|6D@G(pBuXSqBu*qyBvK?)BvvF?Bw8d~Bwi$7Bw{3FTPtQH zXe4SRY$R?Za3pdhbR>2pcqDoxd?bG403b&IIRv)WF+dK2t#uTT!+;zI<Uk-t0yz}O hvB3Z4V1!#oWO}mO<279csR?NbNv?wABuq>V{{?7%e9iy> literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Astrakhan b/lib/pytz/zoneinfo/Europe/Astrakhan new file mode 100644 index 0000000000000000000000000000000000000000..5e069ea5b3a9c8eaaeb9c48abe3d9a4b89ee3b1a GIT binary patch literal 1183 zcmd7QJ7`l;7{Kupn>00Xa8MiDq&{jI`iRCfDN)m!nx>(eLXps+Dij|DLGVK|C<qm- zom5Z+5k&-pgYQ8ns|0Zo+fHs(IN;<1Ig1Dujpu)(lY_W<FX#L&hnqn1{flFVC*tal zYpFe9arN89^YYt)T<LMyKe94%rLrn8A8Txw%QSv-Jq&zMmDLN$Skvs8O7qM`9lU#5 zhpv@$%h^ME&B;-{_DEi@+ndts3+=i!-J~~k`E@wxGj0Aw6Y)eO@+BbcuRo1<y!a@c zkKRaEc}}92p2^0k2ePSjLt+On$mV@BCVuR)>F&L561z{BWayOXiSIEz-}jqT^KnVN z9WYxK$7Ji%HnVLZE$LfX>3z~7ebZstepN|k!Yln#L9-+GUa|*%n1S#s$z`<}^xTtt z>vNO;{L&27mCexn9kcV@wApq4oGHxSl;QJ7O|cLfAN{&QCacw|t8Pi{c6)x^_1=1~ zo9*wn+v|2K6<x-^+7VOsd2qzlpj0ZO>{)I9ed4kwwRbo#3>Q^rtlO@nT?xKGxwxDs zw&Fhw(*BMr!$bzk=NT$8SY)`4cEHGxkwGKFMh1=y9T_|_d?WxQ0we?^1|$e13M339 z4kQpq8wnB$5(^Rx5)Bd#5)Tp(5)l#-5)%>>5)~2_5|^V542jIqhK9t31cyY2gonh3 z1c*e4gown51c^k6go(t71d2rJXhTI}b+o}E(IVj@@ge~u5hEevq@DeL8?!HMBkzkQ Gd_MuxBoHP5 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Athens b/lib/pytz/zoneinfo/Europe/Athens new file mode 100644 index 0000000000000000000000000000000000000000..9f3a0678d766881389e129c93def7fffd74f14f1 GIT binary patch literal 2262 zcmd_qUrd#C9LMqJ2qYerzDWE@Kr|xMj&S(vpCF>+ftd$GI2x%42BH{|5-~`OV=>np zbKe%LoS9aPIcCQ$DCdw`R!$4Zt+8h1V7W5OIhHzW#p?HdYHO{lZo27r_B^lWJmZ3k z^LhK%)z@WO|2R(bhKsY;Ts+SXnA?HCyugX}%i||bA3JyU$C~kv<w)t!rIj+==bIRL zJ#PHd7wWf<MZH=*cHCJy?aIdL>2cSqrw<3M8H0zcnLTR)5nYvmTRRsAWZSP2S-W#$ zR$1RfRM9ilw`H9wjZXeSW1_y&*pOk3z4orgT|B0@e|A`BfAGA<pB$8gqd`f0<&ezj zQb}sxEpr>1WZs4vxx?p|<UFt3>B^OqgjBg}Mx3OEc{KG#w$A@1MHh_5=)#Yt>fQZ6 zsjKI*F6#b5(>g!V^yZW5u6<K8D$h%1Q@>>84@h>|i;|OgP;xU{C3mtzmc)1Kk_($; z=`St1Y|tb3d|9D+y;Yh&uuSjWQ>X<867;^hWG(DmD)$$Ksi!txmZw}&uV+$<!p>@O z@|YA~{Z>|lye}(8&d387-;kBTKJg8`rzQKkWYy8v^ufkPDeZbzS3k5@%NjOo`Qirk z`-*i<>?&RBTA*uhy0s#@NGd*`r4QejFYDg9uIopgvf*%~RQ6wyjUCsds(VDLt43wh zuJ84cg5y$CIi$5o`(<-}Q0v0q)-8#uTfTcqw@x-`{h2o1cEPWY9$Bvqr+3I>&*f`l zZ@E0aHA%Pc$&nqa^Q5URR-VY1BhB8Q<;h4(TGFGnW#VgTjku|;=RTCStLOEpw|Zsg z$bh!*Kc%~Z2leT;9_`rIp^lI#AydLa!$OU>Otn9}2??L}Uw<qsCeQq6xrbWTLboH_ zvJ&Q6Gc7AZtVo_6(SLvc*WdU`{8pj4yxwwde#>KD=F1<)Va}hAmc!xy7d!gRoaTa; zmm_;ZHf3veg=`Di7qT&AXUNu&y&;>kHM`@#VtdH`Y|REa?2yA2kv$@tM0Sa66WJ%S zQDmpcR*}6To3%B&MYfCV7uhhfV`R(7o{>!>yGFK+>>JrQvU6nX$lj67BfGaX+eh|~ zGyv%U(gLIhNE47QAZ<YUfHVT>1kwtm7f3UZZrGZ3ApNj44M94Bv;^r1(iEgCNL!G; zAdNvfgR}<e4bmK>J4kzw{@9uZAsw<cEkb&PGzsYv(k7%&NTZNWA+17sg)|H47Sb-H zU$&-UNXKkV%aEQSO+&hdv<>MS(m14ZNb8W^A<aX&hqMprAJRaigSMuHNDq-FB3(q< zi1ZO@B+^Nwl}Im<W+L50+KKcNX(-ZBThmgcr?#f4NLP`zB7H>~i*y!gE&gA7>oi4P Ylu4UiC0S`XY3}qaS4oE3e#&<K4n&?MSpWb4 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Belfast b/lib/pytz/zoneinfo/Europe/Belfast new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Belgrade b/lib/pytz/zoneinfo/Europe/Belgrade new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Berlin b/lib/pytz/zoneinfo/Europe/Berlin new file mode 100644 index 0000000000000000000000000000000000000000..7ddd510ec65b70cb833aba2f9500282b2c0fad98 GIT binary patch literal 2326 zcmd_qZ%kEn9LMo<`IExPeZ}CKfKWt;UHK1*L1tHA23J#VMk*nJphXy0V^A7a%(=$g z*T!HD$v-h>&8P=xYpBisjTYVnyV=!TTdv4zj?J7_WA%HVwrcB9PkPeta?b1A^N`)o z+q-5{eYW+F6Jg$ParT&t=ja{g)*Izq-y1kTxi2`Vef>xEm3LJ4cl78;M6-@gl*#GW zoa#U1mQP>Kl`}7-$e1ry#*TGc<CQ0Oj2~Pf;}>!#&(_B2XJyG6C>To$G)!pV@D}Uy zv>`d?nj>FCbW1Sglm;)iO0>Uca+YVQ+>-N)#w7osv9rEZXXt5lUOlO^FTJU^o;{#* z-ru8f$4^N7kwHn=w_oOVsw8fEUXmJpa@)FExxL&g$%Vyohbv!F;#1|$sM(Sl?$*?6 zxjOHg6io}n>iiF<>0LuN)YX4k7j&K1^o~z7W9xCvtb0SVs)Ca38<L!&VaZ+9EqMuj zlAqlw`BUw(Fs@4%UaXOVUt4t13AfyHp;8M6s<mi%k>0y=i7xJq*Zb;|bxB8o++Px| z?z%Wxnlhoq?kOn=|5!_tzmn1`-^#MkQ?krIA`e`8Rmuimm-5kf)w8=(mLEB&E1H_* z!Oj<T<-;%Os>Tgkad)G7%S-hk=W<=`O4HTXGqp0NL@Lik>m%3Z$(mz7>DoY~tUEAM zs)l})N87JTb(de(R|lkK`}g|T;<u%?YE<hI_sWK%L9L&DR2vdh8@}t&jZ;3|G}5Mz zU-aq|hu3Q3N6qr&t|Dz3sF2MY6ZNT`dD6VHP<-`H*^)I^wif>^PtUZZB_l>#Ccl=} z8P~P-%m>nTC8*E5H6YLWhjrWD_jLPUpFY>tukE|rHN+7T8usTW+!4mtn;iD5kZIHZ z`;TQs?J~b>D?Z5)W?3_YpZedQ=Vv-@wyZzyf4wjFcrABv1vf79%^Zir{5g0s$Kmk) zi)sC4&gMfkH;{?hnwcR}L*|A|4w)S?J!F2!1d$mcQ$*&7Ow!iO5}Bs0nWwFpC^A!I zs>obz&17xOY?0|A^F=0%%-GgUng0lLMkbBS8ksgSZ)D=OX6DG$k+~z2M`n*qADKUr z03-uQ3XmKiNkFoIqyfnTk_aRdwk8!wE|6p(*+9~P<O4|vk`W{&NKTNXAX!1ug5(8B z43ZgJlNuy9wkA19c98TS`9TtdWC%$Sk|QKZNS2T^A$dX)g=7jz6_P7klPn}#wkBOj zzL10=8ADQr<P1p~k~Ji4NZyddA(=x`hvd%IBoE1+tw|q}KO})j29XpZIYg3(WD!Xt zl1C(wNG6d~BDqA8iDc8(q!Y;}l29b0NJ^2MB1uKEili0EE0S0wvq)-@+#<<EvTJM7 wi{#hVBpAssl42ysNRp8(BWcF}S)L<Jx))>-ahE42Juf}e<;lv#jGV~d0abN>yZ`_I literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Bratislava b/lib/pytz/zoneinfo/Europe/Bratislava new file mode 100644 index 0000000000000000000000000000000000000000..85036de352d20683bdc6cd5e43ded7f1d5ec307a GIT binary patch literal 2329 zcmc)Me@xVM9LMqRfyj?3d!xaT{464*PJTmZkl6{01XH<;R08!xEh4y#L1|bq=NfZw ziRm1YW{g!cY71)(t@U%X5Px2V<=S#(S~)j!T8-7``HdJY|M&al?(yNvb{iY7=kP3B zUz=t9?+P(bcyVnvFU}F0&0E(LXHA#?^rhV+ecIh~Kwo}ebx+$)9Sm*Mp>qr5@as+; z-shGh9XWFJ`D8ifi;`pe-l;jhDp*czj@6T;$K~Wp{fYhnU!uP(U%pE1ms748@^$DA z8F4ho$oXcGU%d?x-V~kYiPq`m^W~=OKQuDwXN{WvtvUk_tMl>)8h!RHz4^pmo$<+b zjX8KoV)yq+-0nRR->#Cd@i|GX^T{nMR?Dqr9!V-FlG|K)k{p{Nw@-<dlpwdJT*=Xy zKO}3aKT7ZTELiXCzoxF9^E#{Zw5GLvsp%UIYKHes&8!-cEMLE57Y<0yk{yy8*DZNj z&5}3TD)}*;ntx`c%>J`U=Nxj&-QQGdL2tDd4$RSew#?JHU9oy^ZIaGwn=SVh2dUc| zBlDBbX_0$Wii5t;lBDmX<l>J~8u*cv4iC!xXJ3^CeQ!wF(1%*Stz8!Ge?=dtua`yb zFX-ZjUeqOZYqa97I`x#5=!4FMy401bORr{VWn{5bo|>i)UzsV(-u+FN`@>|#-UzAc z|3w~Yy)4z8!%|c2mzA3?=&HHz$?B>h^(O3+HHCdz8*)I`#;LCTX{W9m_38S-7Jc-L zM<07_xz>H&D35O~)cW2Ed176HHf+h2#>EBVt98ngnenor=y!Q4!jh)+NNu|Gy)=hk z)#jt0O3TF&efsTQd1iP(H}3jaH}!Svvn@T^x~)|M907ro#&3r?28}%km>hf~Zp)gw z)%;ysv5AgJmK82m=zq_a<(NA0Nm;qaau-$b=CMl5H|BCU__8mD!*l&bnUCe8?W<$# z9Ql{I;!8WOVcn4nwk(YASsAi4WNpaekkui}L)M2Z5LqFzL}ZP~B5lnok!2$5L>6jm zR*Edu)~pp-EV5c;xyX8L&4T$YSuwI?WX;H;kyRthM%Ili+}5ldSvs<IWbw%Ak>w-n zM+$&c04V`d1EdH@6_7F@bwCP%RKnJj0;vU345S)JIgolF1wkr;lmw{>QWT^rNLi4& zAca9HV{1x-)W+5n2dNHH9;7}<fshIzB|>V16bY#kQYNHMNTHBQA*DiUWowFsRLj<s z3#k`UFr;Eg$&i{MMMJ8FlntpHQaGe?Na>K;*_z@Z)w4C_L+XbV5UC(iLZpUB5s@k) zWkl+T6cVW<Qc9$jNHLLW+M04A^+XDaR1_&GQd6X;NL7)tB6URyi&PdVEmB*gxJY$v zO?i>}+L{6*6-G*o)EFr;Qe~vfNSz&lCdNVIcYrxg9(xcN9C9P>fAef2ZSrg)ZT=Gp v7wexSkDpC~BPRZoNH4lhs3(@%oWo4RXJt}zS9x|?Zd!(`JTn8+v%~%dH&JpN literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Brussels b/lib/pytz/zoneinfo/Europe/Brussels new file mode 100644 index 0000000000000000000000000000000000000000..d0d0a08a29c0743517ec537c18ef3111ece34ca5 GIT binary patch literal 2961 zcmc)Ldu&d39LMqJ(dTg~>RVQIsd=`pD^|T~hcTBbDXr@>I(3~Yrn*OGQs$a9#)y0^ zGNjBw)n$}fUF$M;GCG^v*hE6cY>S=Egl!S~y^n~5E%Bdodd}-S&1w2i`~LU`j>%1Q zJ#o}D-{IoeWiFoU&oH;eUD9V1*NO@hpS@@=y-=phO1A2!J5`q#Ez;$ccjK*LtL+uj zDgrBG>)NX(XV@jlGwszwA_JvS$@ZG)VtZ{1x4o{~RD0dcR)O``huWW=?`>~5+R5H{ ze2rbUCeq$y^$KkM&})A_XRN&?kYRmMu*E9x9cyh(AL!c_FV^<NiPnxfe^@*2U$u7r zcE;LuZmzH5yWf4gk6rQY*>T#pcljJ^-{P&lFN+)d_7^Vl9T+{^cQ9*`?@)(0>u~FA z-;uWaeMhs-Sw}C{u#R<a=sR(}hn!p&u9YiO<?D}I$*Jsp!%pQ7(QgXs%eOr{>gfd? z<xG!At=-~Ypmxa!4Vl|W>J+7^Oi0ntoM^4<Z=m%uyU24%e`{FeFIvCe4;mhHQp0cU z)dp8LX~WZ&HafUK8}HsD5gW>+$<pQW{5+MWQ)Y@UuRvZHG(uj?@JnR7G<hi|MWQ02 z<>gupB-)#*(YIS^vmc|h`Pur~;&3&6rTnhOl-$sk3omMH(N`KbVYkNTY}ACo7bLNu zT$0*XNUOe|NOF^9l9E^`DfgyJ>&6SU^`+s`=AVh$c2BCj_I;MND;=usE86PovpQ*q z;t1`S8>yX&+DPZFUQNwuEM20mYg+0(>FPbM-6Ahbx0}}_J!rqAS00n@S64}ovh|X2 zVxMNtpC>&xtk7Qh`O<sd2ij-AN7^@Ur1pC?PyHF)^o{VI+CQeb_P-OaSz%oz>wH~( z^L8^Cxb08PJ{v59tWX(T{;Ld`eoKZftdwCx&&u$rKkHi^w#$gYCp4$&5*gXPOmnMm z)lp4UNBy)&N8c;ZF~=t9+n4-0c5Sxi9T_j<X1CY;(th&J=%yN&l`P}?w3C9|aCtZ3 zd6|&*m%JD1l8JF)I?=u&g&}ve@YErhbn}8v-drm0S61khB?okB*)p9rsYIvGpRVpI z-YP+!AW!wG)t>s~{U9$lPn8<M&+2r!JdMmhDTl}93K3Uzmn*{OuJ!n{JM`f@58izA zPDZBRH6YFJN_DP&d@wh2xZUQ<9L^6qpWXidZio48E-sD=bMf5hzB&4DyL=7D-N-9N zT7vY%(KH3=3epy&FGyn?O=pnSAiY7FgLDUJ57Hl`K}d&?79l-yG)+RfgtQ6i6VfQ8 zQ%I|jULnmwx`nh0=@-&4q+>|Ske)f3rXgKJ+J^KEX&llyq;*K|kme!XL)wS*4{0FM zL8OI94;@VtkuD-_MEZy{66qw;N=MU6q?t%Jk#-{e<d1=dI+~6mEk$~YG!^M8(pIFe zNMn)CBCSPw>u8#bbQfta(qE*(NQaRYBRxi%jC2`kGty_I(MYF}RwKPen(b)1jkFu- zH_~vV<4DVqo+C|1x{kCR={wSRr1MDYk=`TCcQoBc+V5!kk8A+41IQL2dw^^LvJ1#I zAp3x91hNyzRv>$UYzDF$$aXlI{XjOv(d-DaCCHv2n}X~LvMtEIARB}146-%I-XNQU z><+R$j%I(54RSO)glrMAN6022yM$~LvQNlHAv=X^6|z^zW+A(UY!|X$$c8zZ9YeMZ z*)wF*kX=Kz4cRwj<B*+0whq}lWb=^SL$(juKV$<P%?=`4=xFv3*+gU)k!?iw5!py& zC-K2n22~C6cs<@4)v7=B$@`wF+`K`-HJ{b_a4gManz1y88Oso1uFM?D<InD}hwnUi p^U*s@X6OH#%_nw~6%97)+A<;4ENe_=QfzW;d`xCSJjNvj{|lpI#FPL4 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Bucharest b/lib/pytz/zoneinfo/Europe/Bucharest new file mode 100644 index 0000000000000000000000000000000000000000..4eb7ed0dfaed722604aa3909fffeec9abde87dd4 GIT binary patch literal 2212 zcmdtie@xVM9LMp`1y}6)u@4e~0ivRTcEACmXy7jbvN%OKja1}oBBGs?sO(7eZt0ve za_@{)mTtHlvu4B}lo=YSWVE@Hez?{g^Sf--nzJ@^t~pkp=ePc8u0PuPtM~2haXWr& zY`mU<<?9>r)omANKH<sjHBa8hN6gFdo-gB0%pS2${BqFx=<-We--R94$-%enQy;!z ze|+>syZ`kz`;$Ez?bD%E_L;^-_Su@b_P}F#_NPS??Q>ZP_GihlcKo|vSYxW~ss#Vt zL-!X9S&13Lkp~i@k))XZNYY48B>BqW$k?y;M;<)8Gm>)hJ(YT_Q>7hzS&eIdL8Z5C zR^x;9YQoB;YGP%%$|#<pCV8{e<kYe1p?f?k(_I+J4ENfZH#}C>_qVJmUthJR4h&ll z_eL%6k$x+CQm38W(PQQ8dZj9N%YMsOzqKl_w%g8c-ZoTF(x;~_c~uvt9n#bDLwfpX zSQn*qNYU_WJ@ZzJ%sN%7AGuT`#a(q$(l<+H@17@f-b|Ib4H+_T*GxUX+%2W`Df-dL zKT28Ys4jPZE)^M<b;Y$G^n#evdO>thKYryc{Y3XWy7GL#`1iHzg~txdqNXNY)xJ{} zKe<<y1lLOSjGzQ6D`aWXLRsdWBFk?2q$aUk*L*WZp1P5xmw#|wR$TPxm7Vdrw)d)D z6&}%b9Z_x7UDT_$|13|>Ij+~#o|pRc1A1*qw=~4PFYD4o*8TLFG>$gQ`oUJ&FdUF) zj;@g4xlMZGo)T&5s@Bgorpt4?3-zYO#k#p6NpH>@r?-^-rnkl`-IALqEx&%JL-*a3 z(9nQxz4om<-_xbHMf;@fz!}-zeMok+9+B|Au(;fJ#Qgmc>yEwcGrr@m56xG3+<Di3 z{ZJ}7Wd3P+ja6!@&t?9PQ^%`&|LJ|DKcMEDXIWV_F9GwTF4vz=evQi&_!o2e-CXd3 z9f!;YnU15G4>BQSM#z+qIU$omW`#@(nHMrKWM;_Jkhvj~LuSWc!1R#$ArnMqh)fZg zBQi-xGfQNe$UKpWA~WTfD#u(oCd)Bfj_GpDmt(>lGv=5w$DBDP%`t07Gi^sRZ)D=g z%#o=hb4MnR%-+#VADKUr03-uQ3XmKiNkFoIqyfo;qe%pk2}hF(Bo|0BkZd67K=Oek z1jz`J5+o-`Qjn}5X+iRWBnHWhqe%^t8%L8IBs)lYko+JCLNbJ;2+0wWBqU2nnvgsp zi9#~vXi|mb%F!eX$rh3>Bwt9vkc=TILvn^B4apjkHY9II;*iWCsY7z-Xp)Cy4@n=A zKO})j29XpZIYg3(WD!Xtl1C(wNG6d~BDr)l$wad0Xwr$~6G<qNQ6!~EPLZVI|2(T6 a)7tEOs_XR^<P_%katplvJfHJ!n&%H8T0s{8 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Budapest b/lib/pytz/zoneinfo/Europe/Budapest new file mode 100644 index 0000000000000000000000000000000000000000..dfdc6d247faa6497db386a7714a32e3ac743e922 GIT binary patch literal 2396 zcmdtiUrd#C9LMp8f0@kOmkf^v2t|Yx^6vnNL1rglB$&v_NF*>3|3rjD3`(Ptx#pPr z=9mr<MI*9i)E3$WS_##3ArVFf>M~Kl{xFK1Wn=YwKR2y))lC=uo}K6Q@SJhO_`KaK z))&WF|2R|38$O%{^WpiX7tO74nxn9>e{g)yK$|wT9M-)txn+Cbh-ltdyGxsQ_l4~L zs8df)bd`0FES6JmI<)72OFrKbFTF2^N$+)!vM(B{F7$2dC_KHiRlm&Wlm4VG`6{Aa z&dh#YzMi^M20V}Jz?CW){4rY34(G|m0|&>4vVShTwB=mcWoOuh;fim^NBoWyj=KA- z(VseXMpmMAi}Skrhkma!XPi@q*C};Oe4w+2-qG2ooAuVNS2f`1F$p}>CP91l$(%Zs z;7u<{NJ+WewsN)Hp6Qm*<W#vMGEu?;!{yFtvn1TdrQuiOb?)Ubjp(1LcYQKN?{5E9 zBU`TMy!t_ns{Kr(D~@VR(c2oEKOk}C?cz-7koaXgB_XIu662~Qak56{2h{8Q^Q$E3 z_ex!G%q91p&DG@A0!`^yp!aQEqzfAZ_5R{eT~wPS52X31t0+Jo3>(o@*QBKRoYeHt zZzO$eSTekhOGeKLd1&Z0S={!PWOje3S=;Mm$)WxFaA~P5t=pp6k8aatC2KV2o)UFu zrt2e)CAvH^LYH5Q(OmyD$vtzkK6Z7ktT^(E=Jor^%4R>wZ~s9aubGg7`W`7P=$BQS zztblczAvluyR|5|LDr<SY4MGRb#0L9+R<IQZn9k0pQzRi=iU0`!8|SbbfY}=N{W`Y z=E&3Qg0*aGf^5uAmhxhUJQF)dDpG%wXZ<XxjP}>c@rzP5{hC(wek|2v1Nz*%t@3<N zhi+=<)Xi;8`a*S!)@-j)PY+KoZ*w!3x0ku>pLyW2U-OmE4gdLNS$>zy|GyO&;^A#s z)5V(l_vgRfWM;W7S85J7H^1Rw4w?t|zgXKJ=4=>yFA!OstyvzjK4gK&3T@32ku@TV zL{^C`6Imy+P-LaZQjxXVn#CfkMV5=K7g;c}Vr0q4nvq2#tF|@EM%Ili99cQCbY$(w z;*r%O%SYC4YYM<$mI`nv0a62`2uKxdO&O3nAca6Gfs_KN1yT&88b~>idLRWsDuR>* zsR>dPq$)^RY)xH|!XTAFN`uq}DGpK{q&!G{kOCnULP~_x2q_X$C8SKYrcOwqY)z$* zQX#cMiiK1QDHl>Nq+m$Jkdh%aLyCq}4JjK^H>7a3rgBK>Y)$Qu;vv;T%7@esDIiio zq=ZNfks=~hM9PTN5h<jtsU%WLTT@G<m`F8|aw7Fa3W`(|DJfD@q^L+$k+LFnMGA{l z7AdW*sV!1mq`F9Xk@_M9Mk<Vy7^yK*WTeVSnUOjpg+?lkl-kzR8Y#A|sWwt>q~1uu kk%}WFM{17$|DyYv$j^%<_mNr7sD!AP$gJ2HjCT6|3H&I{GXMYp literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Busingen b/lib/pytz/zoneinfo/Europe/Busingen new file mode 100644 index 0000000000000000000000000000000000000000..ad6cf59281a1046d9dcd045fda521585e3e33e06 GIT binary patch literal 1909 zcmciCX-L$09LMqhq+=x|UkjT`&1!PZnmp6((5_jPcE=8#Ej!E(waeVJGVQV`Btqi5 zAVpMc%Z5ah^}y<Z9c&jJCJUQH7eUcw5kZ9=Nd4abC5WxZ{r}9ohV0?@{qfIST%2Tm z^*GJH@Zni)KK$;!(R^KTEwQfLFSD+;`>f`(xmK9_nfB^=M_mEe)b;AL_I_|g`~164 z`=0w<!%v=)h(iq$x#th*SE~}WZj<ycDVG7W7sx=LU)*UKGRTuE(GfB7L$}@%<Me9G zo8db6VYJ4!_R=92I_uEJx9ZvdREO2w(zq>GHGbtuO(;C9iTO7rsk~8=)0<>?&JIb5 z+$*U`m6F;~EhEC~bj00xGV()(jymO)(YNz7t-e6hn?~uFn(;bzcZ7~BcI)^pBV|IS zQ@w@Z@>BF<&G2?ert`99x$jBVi$^js;BT4Oa!G!E@R$73a8P{BXEb|ztxP)fr%o;{ zl_|BGb?WqOnp0Awxj&Yu-<PGox+du~PpnRBPtd%uOv$^^Lub4hEHjV4)>*B=GJ9XB z<TpN-In}SEpsq#c7PQK|^=&$T><L+r->ijEyQC<+L5sT_(}j_$3!m)NMIGh3_)?WF zx$D=Z2WDx>#WGp8HC;>VbLF>1QM$Y)Marh8NqMnLRwVY5l^O43Rj4Hu@nKr=^1f7t zv}@%*=cVe!O<i-eUe>lW>AGEKb$!EL-B7h(tG8EcCx>|h0>AfbSzXLgSyn`UN1$be zh}HGW-@a_W<;}?D%g_IEIP5R~w{JGc{E-h&rTOqX^rLwOy=>cvW!HmhkQ=r&cZ}RJ za?d>6G;-I-ZQGjrMs6IrbL7^Mdq-{_xqIaHk^4s)KsrELKzcx$K)OKMK>DyXjUb&M ztsuQ1%^=+%?I8Ui4Iv#NEg?N2O(9(&Z6STxn#PdMY)xxOZ%A`UcSw6ke@KH!he(S^ zk4Te9mq?pPpGc!fr?#e5q*q(hEYdB~F48a3Fw!y7GSV~BG}1NFHqtlJIMTVTX&vd^ z)-;cFkF<~Uk8A+41IQL2dw^^LvJ1#IAp3x91hNyzRv>#}Yc>Pf4P-lz{XjMZ*%4$* zkUc>*1=$s3TabN0HU`-lWNVPUu{E26?2fJ39%O%z4MKJZ*&<|*kWE5%$q~@Wyn)W| z{eB*%p!b#;CNocFr$WT){^f7xX~O>|>c5RL-@#_Hh9$CIp6ukfl(+;>c47j?CkKB5 Dj=i{v literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Chisinau b/lib/pytz/zoneinfo/Europe/Chisinau new file mode 100644 index 0000000000000000000000000000000000000000..5bc1bfeba62d14ffa8995c1d08ba7500956c494a GIT binary patch literal 2436 zcmeIzeN0t#9LMqRjYkpbx<)*!VFD`hD2gRYUcnTuUIdX$@RWooXrW@5fWJA%Xd0SW zvtq?sQ`wZ^Vw8eLWy*?x8e28A&ekk@n4VS6mbv=94`bE(tG52@b9e3=?wE7V?~8&p z8_QDUZ*%HTcvzEu_&P@Ex0fqk34EjDWB=0&el$*BZ!yk%@r=<uSa0<7wV7w%e9=62 ze4qK&ky^92akKe$O^NwV`3m#hqD=GrgURN5>CxtexVy}Yq26X+PqXp<lXaf{uXCIq zwuKxVc-7?`JT%)mwEUnE9D8i&?$Aq_A^sztkYDcX4gEG~C~U-)8Q$MK6w%XaMV@%p zifY+oP1?WBnp|6IO{rXE-Lp2^iq4&DO`RWS-5VcfO^Xb$V#eRHVqCdqtWTPXy%D40 zu7s%R##Lkb@b|`y^Mjt5odZVvYo|Q34tE#{dz(Fp+YcE@rPZF~;&wA->#m{HjFU2Z zWuv4;9gsOGn`F-IDoKxMQ0bTRW!|lFb>G=ExqoDtntwD;Wpu}@1r4cc;Y&d(vn*IW zuy>{`%DSmsr4wZFw9izQ>$+t7bgLy(KawTaK9!|juglVbGqUW<=Om}SMRNO3DR)!7 zEI-k#R%|iEv%gy9t*%uoD_5)hxh1L~cb-}mlBo*gBUItdXjK%PE=8YstA}oc%bKpQ z)WgHS%Gy>hDenASN_Jn7b%%S!Sa(q#sk@{eUDzh;i_fXj$@}E7j8;|Ve@tzN+M_mH zeOi^@E>#=*D%7US*{b5WTUB0IFPj?^)s~}K@_6|~^+ZFgROKbe*0OQ3E!ipCv%ZsR z=MS<Y(NFELhNULx8&xyZB|EQupq}bDBD)4os@fMiRbBf5RloCjwY#ZGIqvZB^8V|~ zcbuQUpa1xP(O2MvKVH82Cw+YX!<R^ezy6Ob;XjGDNNA1d?`9-90!1RH$i%Vt(NBV$ z63i!#u(9{_OvfaCUZTg|$37YR6LQ@J;?mC|{bXh3^QJ$rAN`Fxf3bdY_zO53qmTUs zAN+X*|KKiv(<|)i`<Zf|lpCeosa<`mlzXMzEah$~w@bNS$_-QQm~zXMd#2np<*q5W zO}TH%jZ^NNa_f|Pr`$Z{?kTrVxqtp0Fo5C!#R9w91BwY07brGRe4rRXae`t6#S4lV z6gMb#Q2d}6LUDv*iCygp#T2{R6^bnsU+ijQD9%u<p?E_vhvE*!9*RE{gD4JBEMj=X zFp1%kU2PM?C%f7xhEo))7+z7#qPRt|i{cl>Fp6Ug%P5{POryBQu#MuIU2Po0IlJ0A zhIb6}817N*WB5lgkl`T3LW+kJ6DclIY^3;TR~t!j(yq3W;w8gOikl2O8GbShWjM;P zl;J7GREDb*TN%DmjHNhhS6fT*mSQf&U5dRFe;Ec-9A;Qd@t9#U!)1oe44)ZBGn}@o zt!8*_SDVdnn_@S^Z;Ig*$0?T6|2_YAo(JgP0<%*1eGu<XO-M^figza`(Ztk%-vMds BjH>_u literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Copenhagen b/lib/pytz/zoneinfo/Europe/Copenhagen new file mode 100644 index 0000000000000000000000000000000000000000..cb2ec0671a372099fbbb7fe8f049ef0fd70889ef GIT binary patch literal 2151 zcmciCeN0t#9LMo<@8yb-vM(xJizgEatt-!v8f10_W^y&<s-z;Ch*CsyGX|y6gHdbD zeRE9b(52>BIida_{R1DCTEm4`O7EDKv*pU3W^U%}VXS`d;~1{`+uzwaud~D0_=C?o zu(YN++xpLmG+($mE#~HZ^eg7kp47UsJ$$sk*SBPS-<7#?`U8)K_WI<^)?Dd-Emgh< z#>tnHM9((8A?M;dHN2uz!k500fs#`?aO}8@32nbN)<0Xu<@~9!DL-r6*ss;&I<21T zM|J$=5A~k2yY=1^?`ZttBa(2SOA>d!D@m;?$s1pl3AI7FZ~1DOSQ?O&f+CsZ&6m`K zG`WA&cu8~nH0?&NPW~=cr-bA5flnj#!S3JHyYEMx+IB(HH-D}f8xCt`Ri|cEoR{oi zx8xM|NbbUIl9#wk^0ONyf3R7m#kc9Si>qY%Uky6rh)*8=s$2^?Dz&g@hCb3VOJ}wx z=%dvsI&1TEd92v2zN&bcoq9!!e1lT#KBXlom!#zC_cF)ziOdQ0%Hx;clet~{rL^yu z`nR;oyaRi5eqEhB(fYbBSn{?mtX-pJ57la*v_uzq=ILVZ6kU8XQ_EwErF>wFK6zuZ zEIs&}E(=G=^4&2~(fy08Xud9$Z6R4%8J1O>e$uCA9+K4+eOi^gL)H{_Y4ymDb#0>R z+8?&-y1}5<^fu|!7X$jt`^&WUvwC^<twOEqD3j;bCF}Z@JgHw$Ai-*nJfD>$8;bsr z7h)`F$cWX3Yu`#^^i6H-KPgRD&+CgHb;wJh9^JU(xNhp&r7t(_)8;MB>U20Au87+% zr+dVmzbxzSKJyQ1B}{NcSXQ)HcMUyneHs2<>JM1HqB0%<e#2o7nwP-;&tatbAvfnu zbMqeaw>dQ}XS%U|)UUb7JZ#NGkeMJ;LFR%?2AK^q9b`Vpgpe5_Q$prsYbJ%v%GOMa zKaP1J6GLW(ObwYEGC5l_J7jvu{E!JEGeo9{%n_L+GD~Ed$UKpWA~Qv%ip&+6EHYbL zGhJl9$b^v@BU47^j7%DtH8O2v-pIs}nIlt2=8jArnZ2!<J~Dq>lK><GND7b~AW1;7 zfTRJ*1Cj_N6G$qMTp-CnvVo)n$%m~;2$B(7lM*B+NK%ljAZbDJf+PmX43ZioH%M}j z>>%ku@?&cfgk;FpqzK6ok|ZQcNScs5A&EjVg`^6}6_P9@TS&T)d?5)#GG=R1hU5%M z8j>|6ZAjjb#37kOQitRYNgk3tBz;K!kOU$bv^6P2a%gLkh-48-Ba%lXkw_+yR3f=_ zxSTF$g!{J3H6rrPUqih#)ik{{bu>|n7Hjm-^VXN)?{+o+RnFmbnztyE)2Ug6)$7km P&r8qr`m-`IBPZ$~7FsAY literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Dublin b/lib/pytz/zoneinfo/Europe/Dublin new file mode 100644 index 0000000000000000000000000000000000000000..5c5a7a3b8d67f5a5c9e44a56e70a727967d972e1 GIT binary patch literal 3522 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 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Gibraltar b/lib/pytz/zoneinfo/Europe/Gibraltar new file mode 100644 index 0000000000000000000000000000000000000000..117aadb8364cd7901388098503f4538c7b445aeb GIT binary patch literal 3052 zcmeIzSx}W_9LMo<Ledz=9-ROa#K#N~Lj+uMBTGc0fLu<ByQZQhk&+R(WKCvG<2e5s ziX0#xlh851C0udI4L3v)A;nD7+#R#bQB$<%`!-!w7v6N$`_6kl&zza}?ws$>Gi-87 zl<RL-L;DR6SGj#Sw|K{X<hCs~xwYOp?_h+<FW6ze$jdj2a#|Sk{zknx<F5H~LY`hS zbB`%VT5rDUeMc7tkI_p*%Js7LVS3r+TV}bd+AOc})n8w{ri*r`>f*hJb;;5sy~1Rf zl^;atRi7L(tEVQIZ_-zr(*EIQP5dyuHbR+oQ5k0aqraLB&63TApO2W07hSsS=r4NH z@gaKi`f9yp)jhNI^ELY0+yK2TGe>WqQLlF-XX%{-3e2u<!*zL&ZF+Zdt=V1oh}q}Y zR`%r#mHkygQt?(#Ik3tlm1C0CK{+jl(nd*Dx}U1L6QvGMzNf3lg_<Mrr*utFYg5yw zUTRLBQng7%y7r!zIU2q|AHDslu4^(?*ImA%kDv87-|w%~CwAU8Ka^fFCrkYFsq6~# zWBT*v^pxrP%)s{MCy$u3QH{;H=wf|7AXHy?%wzn$4v7EtEz<nV3VFQRNQ)gGOTebh zDzK<Twai<jg64~AHS1laQ`1%J5#!Vo2_Ds^SFCz6tg8wR3{h<xH&-FvF%oj4v$Q)K zETJ`i(tcM%d8+i5gcV$r4%u}QK6k%#oW4mS#urQE$YUxhy;OCIE>oQcex|y#%vW8b zGF8|5IjUPgwsbo&R&~FVAw4$7sGi4@rB~r-i7x9Q&&+#Hdglhpvng%lxw+j{pMKsF zGd@7|4L&ciG4-mS_g;x>b5g}!J*VQmwyF5aJ?e!sOVo=c%T+?fR!LkiUnLbSmY34f zRR8(!$$%jr$-vYJGU(}4@g&5_%l=6+I4o2K-;9uC-+n6jyJj-<MmsfZ?Qb%?#z&1X zP1VTKD{9o7>uPj%r5ZE3Mva|)NnYu_PK_H`A>&&uR1>00B&E?Bnb=Zf;)NWURG%)B z_hiYdCp<D`*>FiMpQc{>Fj~?I2dUR5wUVjxx~ORbda3jjfAvOWkeVKQTfN!Tr7}AD zO2)O*DznK=$vm`EWnDccGgcO=nU!TSYvFd8U6L<vXBEht1#{#f_k-SE?!Ru{^!lgw z8+bqb-`@A{|9;p0!Cg(1y8*fyxm<zT-I#A&O`WsvpXY=`kE_ohk1G!Q+Cxk%k3G2D zZnu4$*WGT<1GMG7y@pizG(Z~TXgh<n2I&pb9Hcu)dyxJh4MIAEv<T@D(j=ryNShpO zpO8j5+D;*@LVATX3+WcpE~H;b!;p?4Ekk;SG!5w*(l(@TNaGxB=aAMRy+fLZbPs7C z(m$kuNC%M?B0WT!h;$KYBhp7l+eoC7j<%IZFOg;<-9*}n^b=_)(osj-QlzIyQ<1JB zZFRJLb+nB|I_qd#i}cpfHW%rxqirwJU!=iEhmjT|Jw}?0bQx(g(r2X6NT-ojBfUnN z%}<<eJKA<5{YDy&bR20p(sQKgNY|0JBYj62k8~bsJ<@xm`AGMX_B-1CBO8G10I~(h z9w3{5>;ke4$UYz&f$Rjb706y7n}O^GvK@|gKadS^v^#=q39={1rXah5Yzwk4$i^T$ zgKQ15H^}B7yMt^GvOmZMIocgUw#d=$5wc0hE+N~5>=Uw4$W9?!h3plwS;%f7+lA~G zvSE&P$B->^w0nkZ8nSE1wjuk5Y#g$4$kriyhio3Qd&u@7`-f~GvV+JLI@&!%HWArH zWE+uvL^cxHNn|ULy+k$>*-d0Sk^MwA6xmT^OC9Z=BAe=HcNN)IWM7euMRpe1T4ZnW g|IPjP&GoTc+#!-N4omD5-X%ODEHN?yJ9hH<16u-G00000 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Guernsey b/lib/pytz/zoneinfo/Europe/Guernsey new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Helsinki b/lib/pytz/zoneinfo/Europe/Helsinki new file mode 100644 index 0000000000000000000000000000000000000000..b4f8f9cbb57450549933f83ac90dd56a2ca75344 GIT binary patch literal 1900 zcmdVaYiP}J9LMqh%+NyL(Hv|u%xpNev#Z<YHfM9$FguvbY}n>D!>~5Db3~GszG{&W zvX;c`!B9r-BI~5Igq9-LB!!R`zxUr0<&h`KIi3IOv`%~UeSbXjSCl4Nf4n-GzwqHz zX+C@p@tH^6`ZZzq{JBLfS6>u`Mz#5R_4NB3fmeKvkBz?G&(CU~2gkJUjeQz+>9T~M zZjgw>N2OnlO5~R9(!Z=i1}t1E1G7C6mFAW~&QysGkCDM$drM4EhQ@qO*4P)(I;6Fi z4!zY`hc$gwXWbheUi(<%cHYzY4VTnad`1%r9!X+FlO&}#OY*G!k`i%5QWL8rwcRTt z!)kS8+hQ5@y;4VC&X6%r@-?l#P}7@7>)2frbljnE9bX!y6LyZ0iJ3u~Q5+_dqF<>y zqg^tC?rK)lQ^|V&Ql<o6lPUf?GWGchnbvShvRkfb&fXfCe)_o1C@+_pH9ItS?jD_0 zR-$<$%G8scrL!H=b&hk0&iUff{LoCvf7nCkeU6p+=RfI!)?it9EJO;L-pL~GM=7lJ zOHpB~EZ+K7myEk0OAA`GIP##Bq&H}3mvg!-LUq~e1G>DuLRZ|W)|G7@U3GGSmfc<_ zt9Pesd3~O&SstltccsX>+%%~ub;$aJezL*+O*V#DQW+nrl^>o-RrfDib^oSRzkj5g z8tY}Vzgf2&ysldtj_9`PI`!`LYCvEI``t0<U%oBNQDP2?XGhB#>I&#$S>gSyZohxe z&hc22&ByJ|<Kf}=RzSe7r{^zD_lJ4qT^xJ}Ibr0CkyGYBa?Z#}BWG=EP8&II<iwFP zM@}6%cjV-evqw%JIe#PpBm*P`BnKo3Bnu=BTayQp2$Bhs3X%(w43Z6!4w4U&5Rws+ z5|R^=6p|H^maWMPNzB${hNOn%h9rk%hopz(ha`w(h@^<*h$M+*iKL0-i6m-kGDT9g zHMt_mBH1G8BKaZ-BN-zpBRL~UBUvM9BY7i<+nUUg)NM`fNb*SbNcu?r$OIrWfJ^~0 z2goEKvw%zkG7rc^ATxnXg{_$jWHON1K&Atk4`f1+89}B5nG<AEkXb>d1(_FQVvw0Z zrpDIH4Kg{lW_FP2LFNaUAY_J+DMIE5|KmvtHXAiOk+pK>B*mq~x#E+YISDTNTXOJE D35>6g literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Isle_of_Man b/lib/pytz/zoneinfo/Europe/Isle_of_Man new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Istanbul b/lib/pytz/zoneinfo/Europe/Istanbul new file mode 100644 index 0000000000000000000000000000000000000000..833d4eba3985c0221daf6789c9ac30266bccacd8 GIT binary patch literal 2157 zcmd_qe`u6-0LSs??shwGZuRtI&13UN&UvR(o7-A9o0~PyF}<xPJtx?Nhn%J;<69O~ z20PjiOu<Jo94ut&TC&6-CD2N56fv8vC!OWmMo)x+2>LBrQNyn9`-!0NpX%Sf56``B z&&K{Z?(<Hp-@3U}yX{O>Pq;bl>gIhYsa~u-6P;bzd1lw;v-Xiq8=a#&Up9Iglja*W zr|jOMu=8fP&F+gt%)W1K*>C-_+d2N>Vq>7~hB=V#x6iMfFh3be8iRu;%nMihjnALH z?p)j!G%g*fG>2=w#^rd9^ToWK_Ls#soh!i&_K5eOIdZMW{_5s2bM#`fF*a<Q<0tpo z-~V{p{GsKHJ+URz{Ap&?*1FF)o^2;>Z}N4=x9T-JW9w<tU;d7f+4`yoR31wPCZE%S zq2BJ`=n^MO2JCEo$jly@W90Nyns@Z>H}34nH}zdRjJu!eOx@E^q1{{eVk)<AJb7QZ zIhE%-mz)-;PK9<KFhXMoQUxDhvZr_D8Z-9pkTYLuk+Zh1mG?KVkh9|t$-=6LoU<S# ziwd&j+^Lf?><^3Z#557P8Wi(}e--mTx-N=Ojfn+4pNfTh+U25-x5Wd`c8ijh{bKQk z_hsob2W8pPL$ZAJ3-ZC~Nm)_)gsiyrq>Sc2FQVhiW##00vE+lf^5M}cQPo>3mcBPb zRPQPhHC>umwmBdk=_rto#;%Fljlap|MS~(%>&RGVsk6d=-l{A7TCcnDonG(j*XxG{ z^p)Qp)mNS9)8iM;Sq=Nft;XYrt;bqhbz^V4_4vA1tkv5$S!<$a^+deRTASBsHB}$7 zntm>^)_u0fXiKNl9-sHWTp9j9FRw2%@J}z_l;CZb->+%;5%rDK^0#Oinl``0Gey%1 zW@$N^7G37Kiziy{-=F{WZ}@GzA)(c)I~H5ROF}CyDOYzH|5Y82I)A)#f6x;DVk_z+ zN;kbba0S^6vKv>m9b`YqhL9a0TSE4PYzo<xtJ)T_FJxoL&Ro^jki8+BLw1L357{5G zL1c%>7Lh$7n?!brY!lh1tJ)~CQ)H{iUXjfryG6E(>=)e)^FgZ}16xM+jBFa&HL`7F z-^j*Y)y_F=-Bs-!**vm)SG9d)|40Ln4j?T+dVn;6Ll=-XAbmg@fph|Cg{$fX(hQ^< zNIQ^zAPqq}g0uwb3DOj#D@a?AzPPH!Ae}*4gY*Vz4$>W@JxG6$1|c0nT7>inX%f;U zq)kYlTvelxPPwX9A-zJHg>(yP7t$}JVMxc2mLWYunuc@@X&cfvSJgPAb4cr4Rqv4I zxvK6V?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^P()k~zA*rvYg|Hp1-RjH;{FD%RY E9a<Tx5dZ)H literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Jersey b/lib/pytz/zoneinfo/Europe/Jersey new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Kaliningrad b/lib/pytz/zoneinfo/Europe/Kaliningrad new file mode 100644 index 0000000000000000000000000000000000000000..982d82a3ac959624e4cd5be0b33f40ed03a46e46 GIT binary patch literal 1509 zcmd^;OGs2<97q4xS2~S45jH+XGtEan%Em`iX<6fFzQ}Ug^Z*wk6+v1pBR)hVB5Ha_ z3oS#$2vUe@Q6{u#5j5E-2-y?_5rK=kn~034?_3j#Hnr||x%XT?24-N+@$Ws<;g&y^ zs86`rPJQ#9rSzlU?riIyd1(&Jjs>m`-wRy39|#QgTnRjh>k0(lH#(l(bOt7eypHD= z%N(KZ97ky0=$z{7X`4PX+A<x^eUp@TBeWv<RkbrV+~S;1e3U$w5K0Mqt5buQLTL}L zo32|I%=Ce7Gvn-WbLGi)GxNwEbJhM@GplKXnO&K0t}aS3b6hEsYqv>Wbh*m=l&kXJ zIn|n(SXJ=&iz>W3r;3Kds`zqHt?ijm>rRZRlJ-Hh{=idl9~+U<s!=K1eOk)X&r5~- zuvC2O5>HyM^1N=9jb9I{O%vs^IozNsM|P>I@glXQw^VKIPf*nzNore9p=|ekR^Ik_ zsmYmDKJQ1VjUHEZ*;7*Y;f2&k-jVvr2h#AiPa4Mtq$zk?HJ>{r{-(mt2qPjgYVnGR zj)`5eaK+hvUQ6wNf5qEHmV5NSA+Ag#N+iJ{algM8lyL6K<lIwj#ETS^$g;)f;)#)D zeJ0g9&scbGYW9oQ*UG~$&8;>1ZWJE}K7CMFQp(pF#=_Ik<L#IE|3RqldPj%$?SP17 zQOgAp3?dmsG>B{v;ULmM#DmBO5fCDwMJ*ykMu?CWwUqpP#DvHR5!9lV6e21_R*0|= zX(8f5<b?<fkr*N}L}rN45UC+zL*!-%&XC-q79AoxM0kkw5b+`MLjr(A00{vS10)DW z6pSz!aWDd5L}F2g!idG94u%m8BpgOOkboc&K|+GW1PKZf6(cN2T#UdVkugGp#AZ<k c$B53N4v!HZBS1!kkPz8_6{AfzTkN*|07r&%X8-^I literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Kiev b/lib/pytz/zoneinfo/Europe/Kiev new file mode 100644 index 0000000000000000000000000000000000000000..9337c9ea27c0a61b1082f4be37cfb0f9484cf5e2 GIT binary patch literal 2088 zcmeIye@xVM9LMqRSD@%LeCwj~0)!+L?SunF(IBz&U<RkBoJ1;d)`?m~ipHQcTshYo zxwn()T)J5?G^flqv_G)*W3H^hb${fVBUh`H%{lkos=0EkKF_bC{^+m%=)b<-eZP+{ zU&d~`@p?wrZfPm8{&C!G-f(ernTzKcyUp#S?|A%dpD*_LNMUmLYORdC<JH7lK}n39 z*qS-}c=Fz9Usrk4M<d?fCx1BYT1L`08mzi+_&_r2{4po{)P5)D$ji?3p6yQV&bX7; zy2iPGeYG>AZlRN3QQ$n__c=4OJ<hC&x13q8=Y3o7gLn6wFR#buUic#N;9w%=?|U~g zulG!>aL<8A(T?*Ph@aNtrpr>&J}9M?Lo$E$K`F~QDho<FWx-gtlxOv7`IQZ_=vId= zem^J=jW%jUf3sE&E!KzkF4ZM(WNTGRzAoLfNFJ$9(_lPHmd(7Xq2QQQr+ub1`QJ#* zjUQ!s%15$1aZw)q{%xrpI4O0*A82@AkE}TLrmk#jlSt1iy6Umlbam@StzX!x(YhL4 z<6WT*{yExkGoX!`)zbLYRDJx{0$F?R7hQMBBkPY%k*2}x@<jK~(%hSnSo0;>u<M#W zx#T^0s%co`xrb$A<$$)NpVdt{s+)c~q?^aub<4#refmmNpE<EkTR+(*&%RozZT<D~ z+~!=}y0=WWt*Vgr7O!kCo-R8=zsd7cEa@o9)DGu6>CCvPoyiN*b>p(WaHd~gObqGH z!yoIefus6TSD$w8>sEV0${nd`sVR5fKGW~|`}9n>eNLS8U!0beeZ>5KaZfe(JS*L@ z<_4@umX#rv@W#Gp{9ayV`^JAe{%q&)hC6>-7mixB<_d+PR=B>_3L1l<dVW}DUYZ9E z+jcMb>#*(UznH`y=4d$gX&jjbG7n@T$V`x_Aaijwli_2T4Kf{MKCWg$$c&IFA#*|| zh0F?>7BVknV#v&psUdSiCWp)pnI1AfS2ICmhR76=IU<upW{FG_nI|$)WTwbek+~w1 zMP`dk7n!fCnJ_YAS2JZ~&d8*ZStHX%=8a4onK?3bWbVl1k=Y~DN9K<t0Lg%>Ndb}r zSCa%J3rHG}JRpfcGJ&K5$pw-OBpXONkbEEsK{DcMQi9~f)g%SU3X&EiFGymL%pj>j za)TrX$qtepBtJ-kkPIOyLUQD4l7wUlNfVMMBvDAFkW?YLLXw4K3rQD}FC<||#*maD zIde5hL$c;-(uU*>NgR?nBy~vckmTY2*)v~)ZF@{B=atY#f4H=;tT0ei>JJwO+^6}T FKLL|r@~!{? literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Kirov b/lib/pytz/zoneinfo/Europe/Kirov new file mode 100644 index 0000000000000000000000000000000000000000..a3b5320a0bd139c07b8642c4efd7b98f57c6e8dd GIT binary patch literal 1153 zcmd7QJ7`l;9LMn!oAj2%!9^R}q&{jI`t+JKCDpV>(>AoGP$W21h2o<i2>y`{3PKCk zPAVvZiXw`^#pj@tRf4#PZ6~)X9B}f1oW;eW@qDj#au7G~&Ap#+Ng(9+Esh=;PpChR z8vBHcBWo|-ueznWr=FBTBdg<A%WJ|5Zf(tcw)UIvQTU@OuU$;Jb#rIR^|Kds<lbo= zy*{BE&K}n5PVLd_kLLA;eQCY1(5xFXb$U}<NXH^U(-c}V@jzVSU&GS;=JQy~%TLn! z_^q^+=B548bLp6VD4Qp4ihJmSY}r3+62~u_WX}!Lx%;F^MW;+xVz24?e!!&bPe}S* zpXpv2m95X3%(lghWbWjo=V^oV&ctN<H6__`M|Mm{%+CG~k~{dr^u=CFe@>f$z<tR# zzA*VOugqXo$qX*sHAC-b%&rILOkwV}44*${iiPOdWTjH^Rjv4S`UAhuYNy)qbNl=0 zcO0cUuGs%kwYbW!)WC?({;TP%TDg2*e&VxF_)KBAs9N2my;An-RLW;x_CSu}KWt}z zeue#z4f#GhB3mMRdfH8qU6E~(eUXikosq4Py^+n4-I48){gDQc4v-d*9*`zHZ5K!z zNFPWeNGC`uNH0h;NH<73NIytJNJmIZNKc-&DWoe;+ZNIn(iqYi(i+km(j3wq(jL+u w(jd|y(jw9$(j?NQr)?AI)6+JJbc(c!^olf#bc>UA^Z%{gV8)i++nx;m1PZbYN&o-= literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Lisbon b/lib/pytz/zoneinfo/Europe/Lisbon new file mode 100644 index 0000000000000000000000000000000000000000..355817b52b1b05680bbb57e4dc8de358eff27a39 GIT binary patch literal 3469 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 Y{v0ZPotiXi*2uqcfM2Hof8Le;4LU9nuK)l5 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Ljubljana b/lib/pytz/zoneinfo/Europe/Ljubljana new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/London b/lib/pytz/zoneinfo/Europe/London new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Luxembourg b/lib/pytz/zoneinfo/Europe/Luxembourg new file mode 100644 index 0000000000000000000000000000000000000000..6c194a5cdcb22da9319183df65478ec4e55555fc GIT binary patch literal 2960 zcmeIze{{`t9LMo{o5hyR+px)oTWbx?es3Xbm~mwD^TzU9X2_arX=f9b(~iiF^VV^a zM!RM<#Hfa8Lm1}7QcNf(ha;tqZpqP)v!l=Ro9cA(PyN$BeZS}XeSFXTvA=eoFXy;f zS+Tai9scGOE{+;=@f=uGwymzDOYxMFrt_Y)*D5crD_`S!bB}6i(P}NdSnqOA+2~on z=!&~6GQhK8{wyg^SnSz2DOoGR5<HdRTWp(p*gczDz9E~h$Gf&%&9S|8w#>El=m^`k zW0jt&%2?ZW_i)#am)qOkUN+nFjw|}bcXOAzsz*e+cMcin*cC1A-LZM@nl|6NYi?h5 z@A>w$`@J*E9kpk_bJR6la_p@+<=9uc%)S5hosRcQf*l9)S33^QT<SQKQQ)Xgj&dLF zHQsTg&jH8Lj5F?|=Y8A_1EL+ruMU+Hn}f8mEJZ$iHC|56*&!e02Fl07`st~a{p9q} zQ1$E4?D8w0rvA%2Nt>cnm3fKUHZxoUoE^1Y`ao$P_p^2g{aORteWpQPCp75BKJ9pU zyWW4wt(^|7)Zn_k60)^QI<GC2F2yQc7c3S>cCI`yVVXRc=9JLBsq#=nqJ)Kn%fqcZ zO1O85hTn|WZdbyzdsCqHINVYnss2$T%D>W{tIlg=(FYneuTG;gw`t78a}t|dEpbV; z5<luSN$9*z5@YivvAIxs1+UUx7p6+@U-Goi-V}N4(+urfF<Fyp`{?6K`fGAYi1y10 z)&51jWxybBP00+FfnirQHKkbwc^}ikp`Xj(>tDzauLClqu|b}=yg`OmZIQI&`!#(< zu?*X~UWe!8$cW;XbmZ7qbX4|q9sOvwI@1R0lR?9DOhk7bb1PaiIt-GGPXhF*o84sG zt{-%Kldnv0x0Q+2-^irG8!~xSqfD9HBvTh&)2EYn%e0BdHM8p)nVwXoS*>>JjLxbv zzFe&{n{#zmLxDbX!Kt%1jo0iWbLH8kNt#nJTIS5`s;(soGIwNO$;}Fq=VH3ZywsoM z`L;I6i|U|xo=cMNe@pXE)=R<lb2@)Vg}l&Ms|(f~)P+^+bWuUM7Op7Nd+g@)YH_dk z-OiS+?snei<Ll#VzT?&6p4NW<)oHVN*YiUSaoAg!pQ_k={`%}Mc0b;?1^j;h?G^J$ z^NRVFdH0X|oxhNl?zE+(j^^gHjpbpic`%3F&SmaA{CU{z&VNvn+vcDqNKq_JRgkhE zbwLV)RL0Vj2B{5F9Hcr(d64=b1wtx>lnAL2QY1@LC8SJ9osdEyl|o8|)CwsUQZ1xh zNWGAPAr(VPhSUrxnx&~4QZ}S+Na2vmA*DlVhZGN~9#TG}en<h43L+&$YKRok(o_*C zBT`4CkVqwwQX;iPiiuPcDJN1-{y!+FrKu=VQlzFxQIV=5Wku?W6c(v0Qd*?8mZrE! zb&>KS^+gJdR2V5SQe&jZNR^Q?BXvd!jZ_*bHBxJ&*p{Z+NV$=EBLzn)j+7j!IZ||_ z>PXp<x+8^0Dvy*NsXbDBOH+NM{FbKv$O0fMfGh#B2FM~HtAH#6vJS{XAS;0^1+o^% zVj!!5EQh684`e|s&59sPf~*O$D9EZH%Yv*6vM|WXAWMU+4YD}M>LANwY1Ri>AWO4C z$PyuIge(%WO2{%H>x3*6vQo%WA!~&!7P4B%av|%5ESRNPF=WY*HA5B+Sv6$Ykaa^A z4p}*5>5#QU77tlHWciTwLl)4|tRS+4mSzo+MMPE+Sw>_Xk%dH768~SyJA3)Bm(tg4 YYR?$fH6lGOG9fZLB0VM=qvCvj1?KmuQ2+n{ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Madrid b/lib/pytz/zoneinfo/Europe/Madrid new file mode 100644 index 0000000000000000000000000000000000000000..ccc9d85750eaddf9b5eafac627bace09f4c72043 GIT binary patch literal 2628 zcmeIzT};(=9LMoLA`1BQ1x2g_sF9%s$b*m=goZ#S_{2#`B#w%xg>)ctK7-S#ImW&< zs<AS0GHj%1g}H^+Qc0aama;}=v0Ry@cA7GKV)T3eyJ=+?U3Js{zjI#af7s3i+wc8x zE?ij@XZ`IOWWM3Wwb#7(?5H$vufEgj*<F`p)v3d?XLE67qt6fS#>*wvfiwF&P5a`k zgLSjrhqjNf-gtJM^`<+f@~yHR?&djxmB-Iq^_<Afw@yBs>N&Z}(cPL<?m9I#ukut+ zwdZWl7p}G!N<Hl-X1Y4+I^7@cjdGo<JmUUn%P`l+u3Go`B|o|@OkMBp3d`v3x{~Md z9n#+Id-Hj>-?f9?{uj#KgIYqm2fyy{45?3Y4c*!68MZZDr94sPTA@3jFhT>JqjY%o zbQux<iw1>%uOo+luEE~z8hqnj9d+rT-f_N8?>zCGI*uNbko`>(`pRwzt5O-gVY7@W zE|al~mdagOP6<y=m%AeqWn4&v+%tHTMEIm>M9(B0|MfVX&^c1?Z5^ohHUFfM^;a~i z=2MN{dR8ZvAJv$`0~(v(C2?iV5}(o{ljglB38A|rF>Z|{_EyMbM~zOtxLA^YU9D4& zrO5-I<!W+6fu^)f(FeCp)6|zkbZSw!PTQI!4`ujhTA@RxkGrhtX}yx+b4D}6zm&}D z-^dK__hm-MX?f(*UYXg{C|PaqX?As$%-X+4XP1=7oT@E4cfn4bSG-Jf?k`qnR;E50 zJWJ<CPSE+cVl+1>LvlX})W>?p%fh#BYF?+mEUF8T{N}6jc*PAVsOb<_L8mO<_?<3E zJt9l<+q7_Wtt?Av(jwo(x;#{M`L{3Wirz9^d3voranY$yzLuxOAC$_f9VuGUkRwm6 z7_IJY2~s*YS;~rn<>}ZkDNp}do(ZsI^~4}u?fFX9485gm&YhIC*SmDxp$1vs(V`n_ zkL$*!UAk#)y;f9LXn(JMf4RNA`VH{8%{|a}fS><=argE8R%K3%HB`L%qvdB=A!EFT z{ONNbf3<?`?{D)tE8A%;NYCMomw8I#Da|}tmKVR{WiI>o{&;Zyi__d^u1$%DE#y=q zCmT84$O%VIxve?r$Z1DTJaX!hlaHK!qyR_-kP;v@K#G7=!Pb-ksRL37TT=<76i6+Q zVj$H(%7N4aDF{*#q$Eg9kfPX{s_-AAEViaDNMVr5Af-WSgA@m;4pJVZKDMSnNQICR zAvHpZgj5MB6H+HzQz)cTNU4xoA;m(fg_H}a7g8{!Vo1r5nju9)s)m#esT)!_TT?lt zbV%)x;vv;T%7@esDIiioq=ZNfks=~hM9PTN5h<jtsU%WLTT@G<m`F8|aw7Fa3W`(| zDJfD@q^L+$k+LFnMGA{l7AdW*sV!1mTT@-6yhweK0wWbhN{rMPDKb)Jq|8X2kwPPt zMoMjKYK;`z)>IoQH&Sn;;7G-hk|Q-oijGtrDLYbkr0_`Pk<ufzM~ZK2s*jW(sXwv+ z$O<4!fUE(s2*@fR%Ydu{vJl8hAWMO)1+o~nW;KxIur=#}EC{k9$dVvyf-DNMD)|4h d{L`}do6(7iWqu;F<D(OzV<NL-V{l@;|L;A&?*9M) literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Malta b/lib/pytz/zoneinfo/Europe/Malta new file mode 100644 index 0000000000000000000000000000000000000000..bf2452da40314be196f61e6a7cdd48eaf5c426f3 GIT binary patch literal 2620 zcmd_rZA_JQ7{~F$lLDz-_yDE{wa^en9z^gV(Uj1c2TM7bsVFL<7SSykL8%<eTs7vd zmf{$g_z=sc)D(4v)-q`{kr2bwaygw@+3eoTkSW^#dpqq#uX@v)?laEkaFmzV_vf9x zEO)r;A5UlV4L>|P%@3cu`pjF!sOwfmYvZ}f`lI&d1Fr1%-nwONsdAsI%6{M8x_Wo^ zwz(s%?Vdi_4SC6S<E5!`Y-fZvZA+7H^t?9b&(q@t;nL!1u}_Rk)NiM>NNZ%9buwm? zee$wTPlc38d(u9;{q&J@H{31OjZbRrn>TB%j`A}5*2QM~_G^1BSN$H_Z{bGEzjC$} zF!5z8@Qp${Xz06kr#?wm=g@&xa74Hjd}f3d($-T$4|nz5ck)l|vh{ag*Zrro+nVFP z`^!F6S+Lg^R#>UsCv5cfNS`A;hwO3nin^@fJ$}%LfMXhQ^)u~#;Uj(EM3wgW@*VAa z@Q}E7)ktLJ7U@^2(tqU|8IV^X56)dA4|%*2H8x!a#-_+1ceFg*rME-}rD^oF5jyzd zAdP7a*CF+tb!hE%jotB!4%=`><JKP4_!S2=A!oNHW}TMd1+|itS|=lBlu2^rW=R=d zBq<#wGOF(e9d&krjJ{E*V-BUsBj3-`vDNc6wQh`#TQ@<+SGe`j+$f!}cC<{K6r^c6 zedV!1moz=CLnZ}%t&^jEl*w0qk|_aS$dslAdHlk5nOgIqcp5*~jP<25ZP!+PB0pcA zEPY+4KmE4O$Xl$LBlFbjnXFGmOw*aMF*>s?L1*=vB(uKjuFqT>EVK9jp>tY8Wo}iN zWYzvA^GdGD{0&W#J-<~Jtol_Kjz1uavKlp~f4MA9t<l`zeYzx4b;-}0bZJL{E^8>( zXU}@|xew-O-dD@z`8QKFzdBQ1SlVB$b;+`P`dBH*jgS`;`^k#*c6lkxC57?5w9q~; zMcvx8sQHK#UpcL>d|WNBHr46M^255SX0xs?-k~MyOEkdG|K2Mg(7gQoc{i`S-ucb> zmwW%yKd94x{WAAdY3|A89^e<~a&;3|$ldol-~9c(C&TMXOV8xZ%U}4J2h9iXzqsDp z=CKZ)$U&~y(Ofce&B#R~SB+dYa^1*<BUg@GI&$sE#UodbTt0IBNCA)vASFO*fD{3# z0#XJ?QwO9FNF^LiDUez?nqnZ;K+1tmJ@`ODcuYlrk{~rfih@)HDGO2;q%cTj98GDE z+91V2s)LjVsSi>hq(VrEkQyOHLaKz6iHABNg+eOjXiA0D3Mm#+Eu>sXy^w+-6+=pf z)C?&aQZ=M(NZpXaA(cZ)=V)q&6c4E$Qa+@9NCA-wA|*s>h!hd2B2q@Ajz}SqN+P9n zG_^#E>1e8nloP2ZQc$F#NJ){JB1J{2ij)<pD^ggbvPfx>+9JhuG}T4Q>uBnW6d0*6 zQevdWNRg2$BV|VFj1(HFG*W7$)=04(O|_A7JDPeU1xG55lpLu!Qgo#1NZFCPBZWsQ zkCYy%JyLw6`bha5P5qGtKvn=*0%Q%4ML<>oSq5YskcB{20$B=VEs(`PRs&fMN3$Nt wf;gHLL6!tr6J$}4RY8^oSr`2OElj8xoneX0Pi#g~Tyk7OY(`=N#wUgT1<GOd9smFU literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Mariehamn b/lib/pytz/zoneinfo/Europe/Mariehamn new file mode 100644 index 0000000000000000000000000000000000000000..b4f8f9cbb57450549933f83ac90dd56a2ca75344 GIT binary patch literal 1900 zcmdVaYiP}J9LMqh%+NyL(Hv|u%xpNev#Z<YHfM9$FguvbY}n>D!>~5Db3~GszG{&W zvX;c`!B9r-BI~5Igq9-LB!!R`zxUr0<&h`KIi3IOv`%~UeSbXjSCl4Nf4n-GzwqHz zX+C@p@tH^6`ZZzq{JBLfS6>u`Mz#5R_4NB3fmeKvkBz?G&(CU~2gkJUjeQz+>9T~M zZjgw>N2OnlO5~R9(!Z=i1}t1E1G7C6mFAW~&QysGkCDM$drM4EhQ@qO*4P)(I;6Fi z4!zY`hc$gwXWbheUi(<%cHYzY4VTnad`1%r9!X+FlO&}#OY*G!k`i%5QWL8rwcRTt z!)kS8+hQ5@y;4VC&X6%r@-?l#P}7@7>)2frbljnE9bX!y6LyZ0iJ3u~Q5+_dqF<>y zqg^tC?rK)lQ^|V&Ql<o6lPUf?GWGchnbvShvRkfb&fXfCe)_o1C@+_pH9ItS?jD_0 zR-$<$%G8scrL!H=b&hk0&iUff{LoCvf7nCkeU6p+=RfI!)?it9EJO;L-pL~GM=7lJ zOHpB~EZ+K7myEk0OAA`GIP##Bq&H}3mvg!-LUq~e1G>DuLRZ|W)|G7@U3GGSmfc<_ zt9Pesd3~O&SstltccsX>+%%~ub;$aJezL*+O*V#DQW+nrl^>o-RrfDib^oSRzkj5g z8tY}Vzgf2&ysldtj_9`PI`!`LYCvEI``t0<U%oBNQDP2?XGhB#>I&#$S>gSyZohxe z&hc22&ByJ|<Kf}=RzSe7r{^zD_lJ4qT^xJ}Ibr0CkyGYBa?Z#}BWG=EP8&II<iwFP zM@}6%cjV-evqw%JIe#PpBm*P`BnKo3Bnu=BTayQp2$Bhs3X%(w43Z6!4w4U&5Rws+ z5|R^=6p|H^maWMPNzB${hNOn%h9rk%hopz(ha`w(h@^<*h$M+*iKL0-i6m-kGDT9g zHMt_mBH1G8BKaZ-BN-zpBRL~UBUvM9BY7i<+nUUg)NM`fNb*SbNcu?r$OIrWfJ^~0 z2goEKvw%zkG7rc^ATxnXg{_$jWHON1K&Atk4`f1+89}B5nG<AEkXb>d1(_FQVvw0Z zrpDIH4Kg{lW_FP2LFNaUAY_J+DMIE5|KmvtHXAiOk+pK>B*mq~x#E+YISDTNTXOJE D35>6g literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Minsk b/lib/pytz/zoneinfo/Europe/Minsk new file mode 100644 index 0000000000000000000000000000000000000000..801aead7d245b98f0bf90ec9d9a59d1bb53a8794 GIT binary patch literal 1361 zcmds$O-NKx7>2)boSG(`W|56IXUa6w94&LyrW$Q(9K}DdoLWSMi$N+{ShkEjgh-3% zzg1EZVk9L*KZ{Ig(IRZJX%QGxtBAlw-AzPAOy{{V5wxjo@8O*1+;h2Hxc7PEyY~0h z${)$sCrr|!C;L@OFXP5|=-#N&JH59*m3-gfd3M8lX69Pd^ZZ<$C*{iNEhxX9T2lBr z;&rCGy<ZnSDx7ntjC3@zG<iN%^zf?byLs01j~q9bog6TiA5ECW2X~k&cDI-%o$Jlg zaD}<D$}r1(F0<_NKzI44!v2bPUbXtAQ&m3xq5^m4RMlu&RbNP|HA7Qs?U4yplenVR z?RhG-hsPw?cuVTGpOAY0X=$iEAPwISNvLR8h2HeYhHv}S#;JPQlx|nyv2ChxvPv}# z2i4|FZWZY(P+NuqvNiTuMHBhbT=q)Eq93KjF{xThUr6hRS!uK1k+zu!(*E|MbWDs$ zXY#h{I(1A2GntGn$38FDk?XL}|2dosehs^8;optCJP~77e_%15NUn&lSezo2HKNbm z9`SHuTbx}(5u1^{o88;|zdcpb*%g;AJ!3J=o(_p>Mq@o(H0wjX3Dg96o!D$l9qTH) z#Q(ty{RuKV?@w1i{D3$D@dV-u#21J&5N{yv@NWEpI0W&?qIL=56T~TqR}i-#enA|A zcm{C|;v2*{h<6b8ApSudw5UCVxCrqP;v~dNh?@{UA&x>kg}4gw72+(xTY|d;e+do~ zJhrG^CirYoJ5BH!;x@r=h~p5?A+AGwhd2-Mp5Q*je?kW!Js@-e(g%yW6NFw^)ZHNT agU}H|Pas_(gZf|pk9~24b)TB5gRURG6*U|H literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Monaco b/lib/pytz/zoneinfo/Europe/Monaco new file mode 100644 index 0000000000000000000000000000000000000000..686ae8831550bb0fe033409c8b4df460fcd61a04 GIT binary patch literal 2944 zcmeIzYfu$+7{~ENMG<cgnqY!r5~7COAvFo@h-3nyo|IGs6H$|FC3Da;baG71aUL7S zGBi;UZ-`cS3%uX(Mpk2{<<#h;T^##2=GbM-_PdkQi(dJvH|@@z&+fiBug>}YyrZWS z#99A3S~zdGIQBXh_o4HgkL7t;L(9^NP1%J=dsW?Xd-cJm&3lXgwAYmPH0604%-VS^ z&AO<zX8r7RQ;|5|Y#0}2SB58=jS*#LQ+79dbF;Z-^UVZ%%k^Bd^+J}ZIx)a(JGs$R zZwxcr%ZJ)K-taZ=FP>^Xu)EqH7A>-CvYOaC2S=M-(bw$Vaq0G+Hud(NdwcD@KW(u; z`aI9nesSE?)$ca@_G~iy*W}s<-dSP}zH7{(;zDzHa*jEYooSAy_}a&M$C%@NSC|vo zRrZNX&C5>(jWMULc)vNlZHhT_eTbah9Hb5FJo52d336`n&*qb&K>0MYzqY9DW3}8G zY+6-J(AIBulr{@eRc0h<Kz@X_^>)y983W~^_}{gC*pC|6?kf#yd{%>Q?bi-hx9h{7 zm21Z%%Qd)epM+FZOQ%(9r1Mgh&^hy^OJR{bGIoM=P4`Mzzf^fNGD*TiBBWc(4ie$( z(TLj#+Wp&b?QuR(dmd}1kJbFDkrm%-uhL5zwcxZy&#2Rw{B0VWb5Y`oY9v0nRuYD< zki<@_B`L00lJ1sB@8DAHeR;g}`D3Q`-RF@fzRcEsmART++gG1lI6za%LbQKDm=0Lb zN1htwtDgK|85n+DQ$2TOknc%N3;SBqZeEkYjStD-hI)DC>UtScy+zW`9MFtqOC_^v ztqz?wO|q80uERzx(cy&?b;RR^>P=76XM-|zWMmH=c_&7*+YgfL3vKne+uddKu3vP_ zd4CyO9w0e2H)LGNEy*oyki6XUGJft4`h3c6nUHfv^Fvq4#N=u%@Y|`AI;l?j?roiX zw@9bd&(aqzdv)rjF<N+hy1ck3S*KNwke4Qhs=Y8#rVr~UMFm0fa%^Xrk@}mw5@5;9 z==M6(T#@3|ceME2QJHn~qR!q?DX%uv>YSB_b#C=)eQj2SmMkk#pQeo(KiJvC_x|bE z%&)ose|1_`(;LqJ(`poJHL<ML;^W7Y)$0CzNEe@g`){24K;B!ymi5nldWP4^a;{OS zBlzHQo;*BxoF~ij@o~T4Z@BmI@jgI5?m5TQ`qWaSBS=eJot_{~LArvp1?daY7^E{u zYmnX`%|W_@v<K-A(jcTmu1<@P9wALax`ea|=@Zf@q*F+%kX|9pLb`>t3+WfqFr;Iy zPRo#<Ax%TNhO`ao8`3zWb4cru-XYCHx`(t6=^xTSq=T+b3y~hWI!#2nh_n&uBhpBu zlSnI(ULwupmq9l<XeZK7q@hShk(MGoMVg9q6=^HdSER8>XI-7vBE3bLi*y%hFVbJ6 z!AOUZ79%}Inv8TAX*1Giq|r#Hkyg7py+)dibQ@_m(r={UNXL<uBRxl&j&vPqJJNTg z@kr;9*1I~rN1E^IbRTIy(tl(FkR3p_0NDd%6Odg%wgK4(WFwHBK(+$e3uH4~o!vmT z!`0ajWJ8c0LAC_h6J%46T|u@5*%xGEkexxc2H6{Ab6lO>LAJ-$*&k$skR3v{2-zcK zlaO6Pwh7rMWTTLsLbeLoD`c~f-9om@)!8p(!;l?AwhY-bWYdscL$(dsH)P|GokO+` z**j$OkljPJ&(+yKWCLBD9YnSe*+XO#kzGW#5!pxle<L4!BmJHE>=nz9MrOoEB}T<W LX2ix|biDsxtOUee literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Moscow b/lib/pytz/zoneinfo/Europe/Moscow new file mode 100644 index 0000000000000000000000000000000000000000..ddb3f4e99a1030f33b56fad986c8d9c16e59eb32 GIT binary patch literal 1535 zcmd_pOGs2v0Eh8&eK(rb!qglm&2)U$sA*0)IyEzjsUbNPSW%Qng3+OZ6j}@+wy8i0 zTv(<w>Y|s6YLnFvfkYNAS_B#d(ZT{b1Q9Ad&UZz$TD9&D_x_Go1;Ov{Z)$BR5`SH5 z^c!xj-TLO770{2~!?v;O6<<2~a%X1yzByZObnc(+f7>=aAe@1L@*#I{^@&i>RWve~ zaC~IY6&@P4`5GPslaD0WhbPu1O}P_eCL0pxR)vy2#ZM$pdfe;AuS}$j_ABe{Zk2lN zys}+9t=6AwR%vZ}Rr<jywV`gS$|%oP8}pM@rq!adV&|1T(k|^^lVtYC#6V8_(?HIf zIhp(Xv&_3cCG&%?WWm)Za#QC$x%o`LbToI%!b78~=v0p?cJ-+(dpcA}YCx419Z;p; zkE*hic3Jk$tDN&qa@*r9wSBT&mJfNP>yb@XbY;rQULoBr(Q-$pRqgamOV6<%%A5I8 z`aJJdRpcF6o$*Xn&%97I;XzgN`j*=Dp-a`?y`<{KZ_4`1CzZc0^@tH379J565g8R7 z6CJf8Dth5#iCy-ITe<9u<=^=89B&aK!>RufJR^iCykNxW^I6W7Jw}`mWo|?Nw{jgK zVewqmU?dA+O%tiVzt43T>5K2n+)F>t@7C4(MLl<;zP&sez51>dd5#j{^ZE6yUz(S} z(^$BcUYI8#{QuC`Pkrrs7#c%5Ls~<6Gu6!@-68EE{h8_pkq%9Di%5^Ax=Ex<q)q-* z`a~K<IyKd;BE2HbBHbeGBK;x_BON0xBRwNcBV8kHBYh){Bb_6yo9f<?=8^7Ab^A#F z$Oe!dAX`B8fNTQU1+oofAIL_KogiC5_F}3xgY3psZwJ{AvLR$g$d-^jA)7*Wg=`Di s7qT&AXUNu&y&;=Jc4w-$hwRT(ZxGobvPEQ%$R_cB-=#%wxuDqc3lb5KyZ`_I literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Nicosia b/lib/pytz/zoneinfo/Europe/Nicosia new file mode 100644 index 0000000000000000000000000000000000000000..f7f10ab7665e94ca44fd8cd98a362cd4b304eff1 GIT binary patch literal 2002 zcmdVaZAeuI9LMqNOjy@ye{0UQ>qF(rhpu|nY}s_Jnc9QbPV-i-GEb#fYtAi8r(5m5 z$Yg}XzY-#9P-GGju7RsTPxL@E2zOwMF+w`6v5iOxDx!vL=X;=6dll@>&gI_E<)ZKY z-(P6e#&DkJUr&tl3vZr?^XB{bW1l8}H+J}I+dH(^ihWjRkGpWq7~flHPS|zFdZp86 zO6yW9Zo{ZKvC1|k1t;6D=3h4AQ!kmXP3kogqK}#h54()l@9s1w|JZ1}aiziZo$Is` zPwudj4u!4c?s_|A+d^wfQ@K5LO{O)iBEwEC8fU%fkF}@!MywgJ!**IstdaKEYo`A; zY-Id&-^{%FgE4bp(De6yV`TN5GP67P897_`nt{4jBe$mC&I|6b@{84;m9@nxNNTZX z=e5i1(TL3P_2`_TbyE0Oo6bF7B5&WS)}p>zEj~L}-|3pK^A0BJyWv!w-&rW{mBnaD zolh1_|3gblMx`v~do54BE#)J>%cAH@vS{$SEWUeGmh_*HiW?U-xVu{_Pae^w&COzT z@6cr{cj^00^;-2-lZGnFb$LRiuJC8*iYEcBjxUqypC{@EkJDw<=|{TyrdQS+j+2^! z`?5CjP-=Sy#jL$4>$cz1_4CfihMF5%mvTVri~BYF^0(TMq}uT3er+6W(T&$Tbkk5s zKRmu#o33q^kG?F{=DsTVxG_aP=_-)T%Zj8WoFH3rlVxk^Q)!L!NLx<4wmtY&+9y2G zcI&EijQpaXo$8a%2hZxZ1DADs|5y4&N3TY9NA#tr7kfpI`A=USPs&1WF*6V~#^Xtx z;u-t=lV2)=Ax~*(6(1q~Dk{qT2))2<|Lr{7H~-F!BX^G6I&$yG%_Db@+&*&uNCQX* zNDD|0NE1jGNE@zBA4nreCrB$uFGw>;H%L23KS)DJM@UOZPe@ZpS4dk(U#?DLNM}fE zNN-4UNOwqkNPkF!NQX#^NRLR9NS8>PNT04wqe!Q&POC_-NV7<{NV`bCNW)0SNXtmi zNYhByNZUx?NaIN7u1@Pn@2*bsNcTwlNdL$NAUl9;0kQ|kCLp_jYy+|n$VMPL;p%J! zvKOw-W+1zPYzML*$c7+0f@}%0C&;ECyMk;BvM<QSAUlI>4YD_`&gLMygKQ77Kgb3l zJA`ZzvPZ}!A-jZZ6S7apMj<<eY!$LsuFhs5yXER^7qVZ-h9NtKY#Fj=$fn`{b=SPk a%w^><c>Z91c0qO^C*L2;4Y=QCdH(?jq|NOB literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Oslo b/lib/pytz/zoneinfo/Europe/Oslo new file mode 100644 index 0000000000000000000000000000000000000000..c6842af88c290ac7676c84846505884bbdcf652f GIT binary patch literal 2242 zcmdtie@xVM9LMqRfk3fxZ-2m9fKZ61b@Chh#YJ=iGw(FzGExatM68SQG6w#LelX`6 zWA7Tv9JwqVv!>V|lz*VLe%NT?WhQf4t}Rwp8eJmMkFokZzs>oF{?kAG(dWDSKEC^I z_s4DbdInZ(sLQpkIdSF<A5OdZ@O<;r=GN&Nv^r01sp&iHujxO(NRGeZ)bQ(G`Rv7f zIq__Ud>%@alWXGS!l5+1xZfu~z3h>p9hvfTQ>sMjMSiJt$ffd2GCX@wF1t?2i1V2I zDiIycij~pGNu3zp8JXl?Ad~a{(1i30nmFkzbw(do=kU8aW$=*R^2Hv#^}`o5>Bvz@ zKF}>Gue>T#+f-7wJ|k(tkleOvt=#SlNP1DJOmi1XMzTw$-!w&BF<y0z<m-%YGj!%a zqTX>VPVembP2Kx`&{-X4HM8|o&DwNCvuh7(PSqL74fRN#r&scqy(9%GyQMI<NeahW zWKL3t&N;VQ=Kk5J^NxCD{+E?n)K#sX-g$c0_7W}bOxC;W(zT>@uG~`=qu$yiS&(sF zOTA-K7W0Xgr++QwL*L25==Wt|xKHjK+$)Q^-xOc}d+Kj*lf?&K(<KcJa$nnXy7YnP zby;woR?H4+z*nyKI~VJ6_e@<rnyr-yWm0*1qCPk>Lsq<VSyv9k%c?ySq^jqlJk&BQ z)g57}sUDEk+kVtF#fN2WRlnAz?viz$ZmqlFZC#(Dy8io}T0a)j4Smh}@VS6KvVWxp zKi(*h?(k?sSA{%QpQ?{<FOZE(izHO%lqYhg%BIra<;e+_G-f4eW8@oY8b7K{Cq9zq zp)<PqtuEOT?$xckKG1F5yY;E&ecICAqEU`0NA$SsTv0Kx|NUiI@srIT*-B1xjI*rq zV%>P<{?D7M?|uG&<t?q?7T_BWbI?2l{>5zmGAA@NEr`s=)=UVQ5i%uYPROK?Ss~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!c1B#~L#nrZUOnI|$)WTwbek+~w1wKcOv zri;uMnJ_YAWXi~#kx3)7My8F-8<{vVb7bns+>yy6v$r+VN9K<t0LcK70wf1W5|At) zX+ZLTBm&6<k_sdjNHUOY*qU@8`LHz!K{A4*1jz}K6eKH1T9CXTi9s@hqz1_ik{l#E zNP3X`*qQ_(8L~AgLUM#83CR+YCL~WtqL54>sX}svBn!zFk}f1)wkBan#%xW>kene& zL$ZdX4apmlI3#mO>X6(a$wRV-qz}m-l0YPbwkCy04v{1xSwzx^<Pk|El1U_$NG_3N zBH2XJiR2SWD3VcIlTsw7wkD}aR*|$Kc|{V7WEM#+{!eooZwfpshZej2d6@;7*=~PM JHfH6;{|(X%Sn>b> literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Paris b/lib/pytz/zoneinfo/Europe/Paris new file mode 100644 index 0000000000000000000000000000000000000000..ca854351687d88b3919ff33138f0a71994356b29 GIT binary patch literal 2962 zcmeIzYfw~m7{~ENK@briG{gkOBt#9lLuwM*ilhRfZb~YGiKt0-By-R-baKqwIFF5E z8JehwH$*E`47}fPQnDH|Eho!KyN&%DbL=u^=X-ilO|P6@^ro|O&gblzo!K|L-=Am9 z)S?8}KaSS+6Hbmg`{X`+zI|C)kh82jqtujNh_==?thCl0e%!pV<Tq=5MNd;vu-R;w zA7D1dv^AUNWSYw41!nX32&*bG*=&g_H(PVNS=)T)nQb?ctnJtH&5jE>rux(Xv-9*8 zQ?n()?5Y@M?S9$Eyt!nWdCTf*y<M`{s?G7X_6&|Sdt<Lz`w}v(x;Bkg-QE4x{_nS2 z?|f8X>OVed8XEVR19e-?!S(spq1Tq0!>=22q_o%^oswsc<z}1XX+GA8-f`w+-__<+ zZnbslQp<|Q;;1WUt`C*7+d{Nyqg&p4B}vW=`~1qeB|n(=OWMl^+5NRueIHlAj!@IO za-s$<>nLp&rmM_M(V)U8ZR_cv?Xm{SeTlzlaKyLTzTKx9()_H3+&riquI|$NKdjJ> z$5v`+!vP7au8~e_*GuQ+D&cb%NSERgd0^Z`>6+=0h<@qvV04N^hDAxYfDRJn<JPEK zN!tC(NbPaHz4koes}I%wq|udMYp+$8G-lzu8auN=;|h0beBMP#D5;gi)Otx8v09Ql zt&^05Qc1Z}CcQ&fY46Juq|dLjwC@48Jn~7d_N&U*)cU^q=%N9dRvxDPiz0Nu!anlY zARl!XhRVRm>zeMqBZGWSYevLpl5yjj3~qiz1~)azkgJ<yXw7!XJab61RxFq7>J2(< z`gF-z{*n$Ky;MgOPtuVO7po^TL!SuA)=|+tbkyxQ%?%zTxfj~%lefCdn7u#h*z^7} zt|CbCYQK~5Wj7^%Rg)CtpO*>qzR{=B_Q}M&Gg=tFMkb}!Xp!F@o!m)v@>j3wlshFl zwQ;sSec7YawvN@}lQZO*#i=^IYNR|nC0wmV$ueVjKPf2+k>}z&%gpqj<@q3&%!&=x zS>_8V4ZN+T=Z?$l8y9uX?kahqsb1%<IjZw&*6EA0E46GznR@v&YkqGpZ@(74Eqq&g zxAgb(Zx!%gy<Dzl@%CSntGCM)C|-WN<3IcNdsmRl71qTo_%GLveCE&R+-GKaTsihR zI(;M;kIU_x&Kvu&%jM<u$LZWS-^9hs%X1GYxoaQO8iy=Hl7eK#(M}7J7bG!AW{}h% zxj~YHWCuwPk{={NNQRIUAvr>l<Y;FJNfVMMBvDAFkW?YLLXw4K3rQD}FC<||#*maD zIYW}>XlD&c8<IC9aY*Kn)FHV;l80muNgt9wB!Nf<krX01M3U%eXAwywl1C(wNG6d~ zBDr+5lZj*#NhiMu^2tF$k&GfKMRJNH70D`+RwS=TVv)=usYP<@XeSrRE|Oj(zes|S z3?nH<a*QMy$ug2=B+p2qkxV10MskfL+tJQ8l5Qm5NWzhfBPmC6jwBt)I+AuI??~d2 z%p<8sa*rh6(at`Sen&h1$OIrWfJ^~02goEKvw%zkG7rc^ATxnX1u_@NWFWJFOoyXA zAIO9_+B1So2{I?hq#(0`ObaqE$iyHsgG>!FH^}55vx7{Jqdh;!1UcF>giH}KN5~{0 zvxH0&GEc}vAv1+c6*5=IWFfPKOcyd=$b>oCGlonVGH1x7A+v@|8!~Um#33_>OdT?J z$mAijhfE(bf5-$n+B1kup`$&A$Rr}Oh)g3gkH|zKGl~CCB@-I>Z>G}U-qc?4ZhK*) Svl3&HW8$K-;^Qzj(f@Z_2E5k* literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Podgorica b/lib/pytz/zoneinfo/Europe/Podgorica new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Prague b/lib/pytz/zoneinfo/Europe/Prague new file mode 100644 index 0000000000000000000000000000000000000000..85036de352d20683bdc6cd5e43ded7f1d5ec307a GIT binary patch literal 2329 zcmc)Me@xVM9LMqRfyj?3d!xaT{464*PJTmZkl6{01XH<;R08!xEh4y#L1|bq=NfZw ziRm1YW{g!cY71)(t@U%X5Px2V<=S#(S~)j!T8-7``HdJY|M&al?(yNvb{iY7=kP3B zUz=t9?+P(bcyVnvFU}F0&0E(LXHA#?^rhV+ecIh~Kwo}ebx+$)9Sm*Mp>qr5@as+; z-shGh9XWFJ`D8ifi;`pe-l;jhDp*czj@6T;$K~Wp{fYhnU!uP(U%pE1ms748@^$DA z8F4ho$oXcGU%d?x-V~kYiPq`m^W~=OKQuDwXN{WvtvUk_tMl>)8h!RHz4^pmo$<+b zjX8KoV)yq+-0nRR->#Cd@i|GX^T{nMR?Dqr9!V-FlG|K)k{p{Nw@-<dlpwdJT*=Xy zKO}3aKT7ZTELiXCzoxF9^E#{Zw5GLvsp%UIYKHes&8!-cEMLE57Y<0yk{yy8*DZNj z&5}3TD)}*;ntx`c%>J`U=Nxj&-QQGdL2tDd4$RSew#?JHU9oy^ZIaGwn=SVh2dUc| zBlDBbX_0$Wii5t;lBDmX<l>J~8u*cv4iC!xXJ3^CeQ!wF(1%*Stz8!Ge?=dtua`yb zFX-ZjUeqOZYqa97I`x#5=!4FMy401bORr{VWn{5bo|>i)UzsV(-u+FN`@>|#-UzAc z|3w~Yy)4z8!%|c2mzA3?=&HHz$?B>h^(O3+HHCdz8*)I`#;LCTX{W9m_38S-7Jc-L zM<07_xz>H&D35O~)cW2Ed176HHf+h2#>EBVt98ngnenor=y!Q4!jh)+NNu|Gy)=hk z)#jt0O3TF&efsTQd1iP(H}3jaH}!Svvn@T^x~)|M907ro#&3r?28}%km>hf~Zp)gw z)%;ysv5AgJmK82m=zq_a<(NA0Nm;qaau-$b=CMl5H|BCU__8mD!*l&bnUCe8?W<$# z9Ql{I;!8WOVcn4nwk(YASsAi4WNpaekkui}L)M2Z5LqFzL}ZP~B5lnok!2$5L>6jm zR*Edu)~pp-EV5c;xyX8L&4T$YSuwI?WX;H;kyRthM%Ili+}5ldSvs<IWbw%Ak>w-n zM+$&c04V`d1EdH@6_7F@bwCP%RKnJj0;vU345S)JIgolF1wkr;lmw{>QWT^rNLi4& zAca9HV{1x-)W+5n2dNHH9;7}<fshIzB|>V16bY#kQYNHMNTHBQA*DiUWowFsRLj<s z3#k`UFr;Eg$&i{MMMJ8FlntpHQaGe?Na>K;*_z@Z)w4C_L+XbV5UC(iLZpUB5s@k) zWkl+T6cVW<Qc9$jNHLLW+M04A^+XDaR1_&GQd6X;NL7)tB6URyi&PdVEmB*gxJY$v zO?i>}+L{6*6-G*o)EFr;Qe~vfNSz&lCdNVIcYrxg9(xcN9C9P>fAef2ZSrg)ZT=Gp v7wexSkDpC~BPRZoNH4lhs3(@%oWo4RXJt}zS9x|?Zd!(`JTn8+v%~%dH&JpN literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Riga b/lib/pytz/zoneinfo/Europe/Riga new file mode 100644 index 0000000000000000000000000000000000000000..8495c506e8cecf39abac3f8322a8723184ae6f1c GIT binary patch literal 2226 zcmeIydrXye9LMqB2`D1MuPr<fAR2PfkUL5Si5-C%978w~sfZb%7Lm6cl**Yhv&PuB zVl`JLnWM{$S~FXr>oV6Uk<LG?HM+Q5(O9$7$~oJhe(wiZYyHt$|Mq)!p4W38#>O9f z-o7<knsUtF&J6p8565pmJg4yR!n==69O>wq=s8%rx99A>iPuMe{(9hzh7F%?Y^)p{ zEthkzx^?KNM?QNhPXharC7_RQjZS?zFe~QEVt4pRrTe!TAH<AB1!6}$#WxK;9f&*C z6O2FpWH90ReZkqCJA#S38-jD1R|n^=D-GUUu{fAim=>Iu9ve)Kk2NWg5hgV(-=BIZ z#h><_+dqFe+`r(=75^<K#{B8WM*IuA2mKixr~R2b`}|oANBr4!XHAaxl*zd;c0RYL zU-GJ+k^F?ivM8rj7LB({L0q>My!X@jC8s@d+eobz_SS1r{}R3Zz%pI>T)Y-HCF!z` zTjh?@F!eOV$@1h;E%A&?Y1k)Pmh_F3UHo2FgnTF~h6d!$3ol7|-)mAa_`Z6(I%VbY z7xk`|7OCvqudD8ULaUk^wR&;0`YOtFwR@%3q|euy%UN0*T`IMo-=OzgN|QAwf7P|a z5wfmlrqrGIQP#KrBK6%vvY~!h?%gw{8<)N#_tgz*L*gN6Eb7yy@V9kSg6gIpp4H9c z+jYx8o8JGGPak+?tu}wWO&&a0q%FPG^3di)-FhHjwyi3Z?M-gkkv&^>mRymCXByd+ z8Lhj5-%4xLWo-?dk+zFp=p%3T%A-U5y8F;ax~K23?rl4!?OpBay2@T5q1S|6ZLiSq z$?MvPsq4DPzg{8NM{!Lt`Q^XejhS`S{tI#sHD=yhu5e@G=a@)i7GxPQCQ8hWym7@$ z-wTRe3DZBFes<0M^S7p-E4)5aWj__wK2uWSGv4ZQ<FR^5><e%8a{JOgILGBO#^s#J z?^Ab|%l9vq^t(MZC(o@%7KN+|S(c++7qT#9WysQywIPc`R);JPS)Zd_Ab%t)M3#uG z(a|mvStYVeWSz)Dk(DA#Mb?Te7FjK_Tx7k-f{_&?OXjd<4vTiQt45ZMtQ%Q4vT|hU z$l8&`BdbT2kF1|V0UT`wkP;v@K#G7=!O@lhse_{}1X2m46i6+QVj$H(%7N4aDF{*# zq$Eg9kfI<}LCWH2>w*-<(N+d24N@DVI7oGn@*wp=3WQV$DG^d5q)14WkTN;iIw6H} zw3R|ih13cu7E&#wTu8l;f*}<{N`}-7DH>8Wq-;ptkit3I$|0pgYKIgLsUA{3q<%;N zkqROuL~4i>5vd|lMx>5NAsuZcky1L^S|Y_ns)>{nsV7oUq@wtLN&l&&5jMdKb4YHw UH#Z|cBP%mE-J6}|Jmp3F0Y>aD<p2Nx literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Rome b/lib/pytz/zoneinfo/Europe/Rome new file mode 100644 index 0000000000000000000000000000000000000000..78a131b9fff014c3ea895a9cc7bf4197462b6602 GIT binary patch literal 2683 zcmciDdrVe!9LMqJ!Xt=EUo^!8#3EA@5kwSBG(}`4Sjv-{SG=JX(UFj}RJ`TNG50M^ z$JE42u8UAB%r(?jsk4b_7?zcjWoAWlXEUb+XutPij9LBdIXvg}a2S97-XH(8#W{mq z|G5Iq8$MhI%!lutR-4E6q+8bZ+N!!8$4}Wi54p16e*Lz!t2CmnH2WQU_o}k&Ju`+{ zdoy}upUh3PtFDfh)9;08_1c2E>OHA)=FP!!=JJWUvw5@hoBVE4lTl-z8xgPHj;oQ{ z$eY&re%tKx^{e&!_FJVP;h^1c;aFW`M2*$>S%uxyRADujY_)G+Icwip`-$Z`{;}<z z`<CU|G0pOhe#r`Wwb1tU-)y(b+iV4PSZW8Q4YPylr`W-<Q>@@q$#%z(WUJ!`;dZBA z>g<q>&Gvn#n=L8mVTG1mvN~rqS)Hr5*<CWn$o&a_YuD&{?bhWd4fCGXu<J*)`{fVy zfpewW<Eu9`{O}QpI8ZK;J9bIWVwI>BYou3hzC1W%o;;M{m+0YX@^H)$=^fEW9_iR! z`uI|{&yB&__eyW=SKCb=Jszn2D{g7bzTb7gmLD{B{V5$-a9HE!mudXW3o<CbLK0Fc zW$>h}k{G#Dh74LJLz;^uDSV42U7RDyjfFbwNUA*c<5V5Kf3~Jn4$~1EM(N1y5&C#e zw2oSzEThNzG<ANsjOl$<(^8vdtnX`0kG>@7*M608-Y;Za^+|c+@?IHV{=Q^XeXg0C zie<uq-TGu+o;+2&PA5*;sFQLRXx7kN^=G8((_s^Ia!fy+d^1j`b{#8Izw4~e+~_OQ zKK)Cl*9Oas(omUM@u$oxx-PS~R7>{kTA8!*51l*mkj$G|rSqdoWI;-~<^&zog^{WY zf7_;un)7w>$>sX&MZZ4x?sUyPu|%GKEk*P8XUPkTqSV@uC`%>|m;9VCc`?4H6r?rC z(omNa4(zIh_Rq2`<fbk=drX#JyPz+Bv|nDSuGAGJU+T*8ow{oIJ}uf*q#k$M?G14A z<Msr2ox9bR-|l*PeC_W2?Q(UfFvrUk(aRm+a)pSiee1L3P22m7Ous8NEsKYr|8ScN z%@_awALbzQM?PF-=EL{UJLXC`+OrC+!)q+$a66g<jvR92pd*JJIq=A#M-D!6_>ll0 z5kNwK!~h9`qlp3%21gSIBoIg>kWe78K!Slp0||$ti3bu8BqEL`BuGpgO;C`iAYnl# zE_@*{TqZI=Xpq<-!9k*fga?Ta5+Ec(jwVD%j2umnkSIBtFd=b50)<2h2^A76Bv_6n zT1dE%cp(8pB8G&_(Zmc18WJ@mY)IUYz#)-CLWjf-2_6zXBz#ExkN_ePL_&zf(9r}D zi6Rn4B#uZRkw_w;L}G~q6Nx4gP9&a4K#_<dAw^>9Xo8AF)zO3%i7OIVB(g|ok=P=^ zMWTy@7l|(tU?jpwh>;j0K}MqNXu^!d+0g_Vi8K;wB-Ti<k!T~~M&gYG9Ems*awO(R z(2=M+ny@2rcQk=VB9DX~i9Hg0B>G7Bk@zD6fQ$e#1jra5gMf?zG7QK#IGTY#MgkcM zWGs-uKt=-@4rDx#0YOFt84_enkU>F41sN7(TpZ27AS2^wh6WiMWN?ttL52qzA7p@# z5psJyZFhjr!;i=73vljMTYkIi>1`Ky@9)+XGFxO;$ZU?8iV$&iYJIl6X?xEWP5Rd! fwGEQ_7HmdpKs<95lbH~k7#kOp86SrO6N3K%dd(YT literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Samara b/lib/pytz/zoneinfo/Europe/Samara new file mode 100644 index 0000000000000000000000000000000000000000..97d5dd9e6ed7fc924c9bcb514f991cc8b52061b3 GIT binary patch literal 1215 zcmdVYPe_wt9Ki8sx_@jDUTVuf{WDvey4KvrtZ6gTVQnBWIz+((WzZjJh=&fr1T6?6 zLLw-Nkfc+H2RoUxL(s)`kZwcxL3D|T5p^hu^?cvir6B0o^X}pE?B(T!?f1=}x^O<K z{#agfhs_!=n{(5w>YaQ(=N;V=xL?}pFGqatH)-E@+k*dtDs8L8Bh4$<OD!*Er1ja9 zv^|`V?YG8c$F-BP^KwRZoleT`Y*5-$&9bM<D;=$#>R#`9HQ)#o0$=@weeZpfLG@Y% z-+t7gS8KX+v8=o1Uh3|<3pzYKtM^aL=*YP#ec;TzM8|JRPv0Ghowy|NwsA>BbCURx zmt@ODom@*u?|N1rT=vVMN?50!#&zFPlkUIa(}y2?*6FctdSH6992u(U!LwC4+Oe#M z23KX+@mOct7bWv)Nk$s)$w>K;9D8?Fj?Wh*yYi%vyM3ivtkr6^hQ|73cWhivm(%5T zHT?SeH=QoKU8(RF^Jl71M459kt=vitkJ>i<ezuwW^=Cp6oAo4jcs`rUtIkM|*)g-@ zO4-b(zBq2I{67rV{H_|qMFz|(7&0<wWZ0Hw;K<OC!6U;*0ze`_LO^0bf<U4`!a(9c z0zo1{LP26dg0VEwAmJeKSek&4h>(zwn2?~5sF1LbxD1NGkjRkGkl2vmkmxK;cu0Jf zCO{-YBt#@eBuFGmBupeuBv2$$Bvd3;Bv>R`BwQq3OA{~>v84$ai5UqRi5dwTi)Qx! OP28T8iNC))=J^Tns}05g literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/San_Marino b/lib/pytz/zoneinfo/Europe/San_Marino new file mode 100644 index 0000000000000000000000000000000000000000..78a131b9fff014c3ea895a9cc7bf4197462b6602 GIT binary patch literal 2683 zcmciDdrVe!9LMqJ!Xt=EUo^!8#3EA@5kwSBG(}`4Sjv-{SG=JX(UFj}RJ`TNG50M^ z$JE42u8UAB%r(?jsk4b_7?zcjWoAWlXEUb+XutPij9LBdIXvg}a2S97-XH(8#W{mq z|G5Iq8$MhI%!lutR-4E6q+8bZ+N!!8$4}Wi54p16e*Lz!t2CmnH2WQU_o}k&Ju`+{ zdoy}upUh3PtFDfh)9;08_1c2E>OHA)=FP!!=JJWUvw5@hoBVE4lTl-z8xgPHj;oQ{ z$eY&re%tKx^{e&!_FJVP;h^1c;aFW`M2*$>S%uxyRADujY_)G+Icwip`-$Z`{;}<z z`<CU|G0pOhe#r`Wwb1tU-)y(b+iV4PSZW8Q4YPylr`W-<Q>@@q$#%z(WUJ!`;dZBA z>g<q>&Gvn#n=L8mVTG1mvN~rqS)Hr5*<CWn$o&a_YuD&{?bhWd4fCGXu<J*)`{fVy zfpewW<Eu9`{O}QpI8ZK;J9bIWVwI>BYou3hzC1W%o;;M{m+0YX@^H)$=^fEW9_iR! z`uI|{&yB&__eyW=SKCb=Jszn2D{g7bzTb7gmLD{B{V5$-a9HE!mudXW3o<CbLK0Fc zW$>h}k{G#Dh74LJLz;^uDSV42U7RDyjfFbwNUA*c<5V5Kf3~Jn4$~1EM(N1y5&C#e zw2oSzEThNzG<ANsjOl$<(^8vdtnX`0kG>@7*M608-Y;Za^+|c+@?IHV{=Q^XeXg0C zie<uq-TGu+o;+2&PA5*;sFQLRXx7kN^=G8((_s^Ia!fy+d^1j`b{#8Izw4~e+~_OQ zKK)Cl*9Oas(omUM@u$oxx-PS~R7>{kTA8!*51l*mkj$G|rSqdoWI;-~<^&zog^{WY zf7_;un)7w>$>sX&MZZ4x?sUyPu|%GKEk*P8XUPkTqSV@uC`%>|m;9VCc`?4H6r?rC z(omNa4(zIh_Rq2`<fbk=drX#JyPz+Bv|nDSuGAGJU+T*8ow{oIJ}uf*q#k$M?G14A z<Msr2ox9bR-|l*PeC_W2?Q(UfFvrUk(aRm+a)pSiee1L3P22m7Ous8NEsKYr|8ScN z%@_awALbzQM?PF-=EL{UJLXC`+OrC+!)q+$a66g<jvR92pd*JJIq=A#M-D!6_>ll0 z5kNwK!~h9`qlp3%21gSIBoIg>kWe78K!Slp0||$ti3bu8BqEL`BuGpgO;C`iAYnl# zE_@*{TqZI=Xpq<-!9k*fga?Ta5+Ec(jwVD%j2umnkSIBtFd=b50)<2h2^A76Bv_6n zT1dE%cp(8pB8G&_(Zmc18WJ@mY)IUYz#)-CLWjf-2_6zXBz#ExkN_ePL_&zf(9r}D zi6Rn4B#uZRkw_w;L}G~q6Nx4gP9&a4K#_<dAw^>9Xo8AF)zO3%i7OIVB(g|ok=P=^ zMWTy@7l|(tU?jpwh>;j0K}MqNXu^!d+0g_Vi8K;wB-Ti<k!T~~M&gYG9Ems*awO(R z(2=M+ny@2rcQk=VB9DX~i9Hg0B>G7Bk@zD6fQ$e#1jra5gMf?zG7QK#IGTY#MgkcM zWGs-uKt=-@4rDx#0YOFt84_enkU>F41sN7(TpZ27AS2^wh6WiMWN?ttL52qzA7p@# z5psJyZFhjr!;i=73vljMTYkIi>1`Ky@9)+XGFxO;$ZU?8iV$&iYJIl6X?xEWP5Rd! fwGEQ_7HmdpKs<95lbH~k7#kOp86SrO6N3K%dd(YT literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Sarajevo b/lib/pytz/zoneinfo/Europe/Sarajevo new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Saratov b/lib/pytz/zoneinfo/Europe/Saratov new file mode 100644 index 0000000000000000000000000000000000000000..8fd5f6d4b881457d13fdcdd35abb6fc5429d7084 GIT binary patch literal 1183 zcmd7QO-R#W9Ki8sxiy;|x|B0Fd$GB6T5E1HYuaqSV9k&i5mq3*2tm+~@K6vaWS%N0 zf`}rDMwea>b;@K!mq<Nzv)~V%dLe$7E=jHD`xj3gqFc{@&;Rr1*%)lUZ(-=fNW%QF zR@f6ZtIKYlSKT%3<Ijs#gR7%AN^631@#@OiZ1oS%)8J=Qs+mv4*Unrh)lOY?LJ!Y7 z;aj6l-Nob1x^w%T^(XtB4TsXs#(bkwpV_RNnrk!?3TQ*sf<}E&iGB}C<GZiJO|QR5 z?Ad#1F3w8JwQ1Qh@kF+c-jVpRE3)nIlqODJ*Vc~Pn%s9*Q{i!KOB~d;pGP!Zdq&b9 zy0v{_NVdOh&>iy`$=uIL$BR1YoQ%lMn?|xDe(9PB>8_qnk~{iKyCZL<C+BFd?~(M? zztX;MZ?wOnsQq&fboa+e-Sha8=4bB7z~xg~$cKjy<o3!~xm@;CEL*(1KKEMg=khM{ zx4YNx^%@g%|L>-_vCqAOo=RiVS+jEKzI5WTCySrq-TXko#Nw@Xr|eD|<FPLm5AG`b z!yxVNC^JlCpnL&CMFxuu*VPUf88R|xWZ1~Sk)b1lM~06CfJA_VfW&|Vfkc6Xfy99X z;%Xy7LP26df<dA|!a?Fe0zx7}LPBCff<mG~!b0M5wSgg#x!TZ>*pT3m=#cP`_>cgR z2$2wx7?B{6D3LIcIFUe+NL_8HNUW|lSR`5`TqIs3U?gHBWE``z|8HXsWNhS}Ey=)d D1dR{C literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Simferopol b/lib/pytz/zoneinfo/Europe/Simferopol new file mode 100644 index 0000000000000000000000000000000000000000..e82dbbc78647086170f0d291ee449122ed18e875 GIT binary patch literal 1481 zcmd_pTS!xJ0LStF+N`-u{|}Wew=~Tzre@Q0n$~PiF}GT7U5G9V%m~DXjOZas%6bTm ze<i(ivl1hbD5zd6x*(Bcx`*B@5`u(?IFC_C{l7=_rHAObb2#U7JYypG{Y7^jYVwFb zjuicdizB2jp0nEYt<Tw$`KZSkdt>X24^Py~w|7(3$SuDdy;v+qZ+OQSEWZ<9nD*Y6 zY99-y{z!SA_9Z#)9P|4Y4PT0<Kj}7H{TB@P)l<gej#eY%c%zZoyv<m$yT-^0mKxa= zD~zRiP9w+VFmkT9hL?2~7%Sg>jpYuGhF3itiRJY?2<La*kLAC76)$K%<6C{CJzUs$ z(6?q!cieNhS9pd_#EU8iWO2h~xz>G6mUxcIlF5_Oo8G0oANI?#pGQ^sQ@>m{wo_H~ z?o*Wm<!b%eDz)K?OZl3zRaJYL+*p&K{Eg|dI%h%!{FAaKVMx_xf0VV~KFd1m3t2bv zT5kGuTh`y}lfmI<Ds;X>ZtlOSwzRa!aK~x2b;mi?(0o8eO82%!gYKARmUXr*!Iog1 zGj-Y%XHM%Z2PYpS+O2bw{&vo@3z6|%{|`jQxUdP~$`txdZlOpLB3TN_8_WFZyVqxN zPJcT6Y;p63`_y;6KEBIu2!^5}qOU+8DngNZ;n(^D5q_xFFZBb5#bRC>{V;RzHu?`4 z{nE$w$AfkxCnPB(D<mx>FC;M}GgF-!e*n25$syU9>hzHOkOYwokra^}ktC5Uku;G! zkwlS9kyMdfkz`GEwn(~2zDUAI#z@LY&ZatPBx@vXByS{fBy&@pI+8n*Jd!<<zNyY1 znSiOD0Wt+-4#*^sSs>Fu=7CHEnF%r#WG={Lkl7&9LFR)@$W+e=nUblV6EZ1eR>-uF cc_9-+W`;}+nHw@WWOn!;P0ykCnqTDj4a}fXaR2}S literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Skopje b/lib/pytz/zoneinfo/Europe/Skopje new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Sofia b/lib/pytz/zoneinfo/Europe/Sofia new file mode 100644 index 0000000000000000000000000000000000000000..dcfdd0822defde60d3949775489f4edfaf5cb2f7 GIT binary patch literal 2121 zcmb`{Z%kEn9LMqB6;Qk~_C-TfKrkTCuJYf1L1YAs1iQ+GNF}a`h(&~`3{vAxopX)3 zZ^UY@+_W64=7=`32h{qbYqrtlwsNhNi$%-U)Y{CsYOH?mLw9ak&-|U8`#PSp@yO5H zw{b^vp1JL~?Hg`RkG*-G8?Zm4m)6SXZzgE)wNe@DE0EBE3<>?|YX71mJvDCYuin|U zCF66dXNB%6Tr?i<xe}T?=cD$7n6Xg8wV}YgiIai(U-ktSoIMyweE)5ebozuze)R>j zu=jvT*|*E2wl<kXn`_P8RplnFXoX4lWSNYl`R1M&x5<nu3}p5Uk7r(Y`?G$$;$8gJ zW&e_MW8Qm*gMQDccf3pchy2S9z39!}-RI9~+T+b_7#z>**c-|(9+83#FG*qYaao?% zCCev!WMyK%uDrNKR{hnft3N1}`@XH$qJc&&9$BsTA6}y+uO#UM&1t&k&?<SbJW5NO z66K+cOIlVsDdkb0X+_%iQgQVssf_qoDud_c;fdE}?ciHdHTt1eAMKTOr%&o5ZEfQ1 zJ*ev+eO@=TZqu3-t?H|)(AtD`TIX4;bvJUfKE7P)zn-m+UC)w@XMWdB7u>S>M4U7X zUzW#vu1RBmQ2dP-WXt|v^of%9WNX8yHl-YsZN-Dy9R03tPgdRj^UK;Y*`Yhmck7cE zefrcJo3!=Qo$~aNVr?6!k!M;`wEb|Q>|9?Y9nA@{D|eynF8f3F#2M+#j@QoLevq!% z8`>2*C*4=S(PxJSWN&ap_Z|C0_YWS|=ekd6&(R)rO^dKsq$_Ibnm*%}EBwJRdgh(J zjEVow{_itMsV35xSTWH&yJEu6OLAOs;jdHAH{VoM`%GDx&y+fM4gat|#<*PE{7xO0 z%lF@7m}S3na{~6}dBNZI)SU^YW5?NvdB`}BfgmG6hJuX6(GCU~js07O;UMEd284_V z84@xkWKfQFRLHQ9aUlakM#f=i9LC0Ba2!U*VR#(I$6<gRM#y1^9LC6DkQ_$oXou-& z$B7IS87VSUWUR<wk<lW<MaGK^7#T4#WMs_9ppj7{!*;aeMh5O^M~(~~89Op~Wc0}J zk?|t|Kq7#I0Eqz-1SASb7?3z1fpD~uKtkbYV}S$%i3So5BpygWkcc25L1Kag1&In0 z79=i6U>t2^kkB~V*dW0{qJxA7i4PJWBtl4tkQgCBLZXC(35gRDC?rxys2pvqkYFLv zLc)c_3ket!F(hP2%#ffVQA5Ip#0?1?5;-Jvjy85k@EmRQknkb#Ljs6I5D6g?L;N?0 zkr9y*QPZYRT{EI@xxybDXS(n76)s|83q`bDv_*^+V~c3JDB)*Y!T9jkspp$-=wjvn iwDGn$+81sc(WQCB(^H+ltZ-RQcD|=NH^+G@aQ_2r6zRYK literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Stockholm b/lib/pytz/zoneinfo/Europe/Stockholm new file mode 100644 index 0000000000000000000000000000000000000000..f3e0c7f0f25f0a7290e56281c91190e3611498a7 GIT binary patch literal 1909 zcmciCYfQ~?9LMqhQ3q?ZZ%F8dB$uPBax1r^8ai$r<dR&HTS7<?G0n`HeQk|d3}elV zcn}`sGS@LQo1K~4Y|P!vhPkcrd;c3A@Yrep|EryP_<eu8(-##aT7P|<<{KV9Ys|y% zZ8w@%O+?k~8sGi*?LDKUL((@5j(VdV+dtG0zgrse;hc7QdR#l-*{@wL?a<IOXC>@t zorLe%ClOUDk>#7DYhkf;n>kOqXL%(mHC=kRQY1PoMtZjCBr#66#(e6py`DvDZ(m34 zbETE`t^cB~L$9=7^?i-4yrFTc&S-r8F-^$5CyB-Nl9bjU{U_~|<nX<cl2|G!O%*aQ zv|0x~nj?e0m+0WLZW;0*M^kI_G_7H<4&5?Bht-7X@Pa5EQ8`FPW;oTIA1b4wUue3! zNiv+*H8bk5WWIYYqx~+(=*DX@=IKEhTX#gVZk|`q_9_{7^ni{pDv}9Rn|0#UZ91uN zzGe?7RBu+MP7WETQ(V1u%IA2^3C@t5yX|z^r(QDs)JL7+3y_)ngCw{9t<0+UAbHh| zGCR*FbJoAsxx-G&yxg0bAGurRr`2ge>yx@5Ty??AUAnNTSQlL@)5VXxy5#T-Exfuy zmTpbcqS|a(wlGqcZ%LLF6H}$QAVgLsM98Z2ud+JGl9IS!EqVV$N&`P@>Fvu>_U@jp zJy9#`8XL5H_eEV_w^uim9ny;J73yf=@bmxwKb9qL%~e@}V)<KESXW2uUvIw2@^~$G zI#0Hj|8h9&m-pW{+tU1zhfk?__&w-{`FMT%s<C|X%DKo5+nPJ(pSfk^o{^hI?i#sm zTXWyYjU#uC+&Xga$ju{nkK8_T|40K!2S^J@4@eV87f2gOAGW3uq!XkSq!*+aq#L9i zq#vXqq$8vyq$i{)q${K?q%T|37}A-oX$|QOX%6WQX%FcSX%OiUX%XoWX%guYX%p!a zX%y+y*0hTBYHONBx<%SW`b8Q>I!0PXdPbT?x<=YY`bHW@I=3~gBfZ<2=8^7^_L2UP z4M27P*#cw_kWD~#0oev*ACQeeb^_T7WG`&ZW+1zPYzML*$c7+0f@}%0C&;ECyMk;B zvM<QSAUlI>4YD`3W^<6;u{GO+><_X*$POV}gzOQrN!Ywgel7f+|NrOrFhwv-fnqfe sQyY7p%$skRr)+zk{!CQ!Mwxej8LoZ_ESJlZ6q_6y@A4$XV_Z_ePe)m?eE<Le literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Tallinn b/lib/pytz/zoneinfo/Europe/Tallinn new file mode 100644 index 0000000000000000000000000000000000000000..3a744cc6f28f3a886952ab19c8ec93f0e66cefcb GIT binary patch literal 2178 zcmeIye@xVM9LMp`fk)RR=Z7{ri8~=lLFC8rQ)`ge3CuVQ<s^R*{Xo<rR3Hb1@m(?J z8nbuB#$4r(V)SFi{6lTce$P3|i|!xR8ns-lY^=HOR?gYR>ht{8^^bqFw*KsM`+gr^ z?(UEKgV!^<WmiY3y6$N64i{&@Ts$wf%<ahL!q|~_MUD=Q%5k-P&sfjy$mGe>7e6~c z+7!Fgpg(`pFH^6E^%pOc>v->li|M(q$8TBqb&cOM)8zkc!AA>ceesN$aLui;7vq`l z9kT-`p0|T9KW#4>?y<85+U@MljrMKZ>g>gl6?RTlo_%{khP@<^p_b0~s$6%4m3w8W zmG^_+S~l&m^3PqfmY<rn3dUxv!jYI&H1xhz+&f`~+K*Z#trt{j_qZynep{7qIH)Ru z&#B6k51J}V`*h{ppkA3dB30vUQhoMLx$D4cS@lvtYC3Xc_0S#q?mD-G+cWi=C6}Z& zJg4j2pGtksx4QoFk9uwL$9nD5q`v3-*L1_gTRIZ^P#O;n>vboN%e`G)x@q_sS%2TN zvY~UkG_UBCXrx{?`qxQI!7^#N8j?-vb$Zj6H_PTLd3wv4UuEmGS8qF(rdv<_tnVNE zMYoMi=?B`T^@ICo<)KyY=!aWl(w=o#Z?B$^4$o=X5fs_+)2Qs6>y}-U{qo2+QF-)@ zt<rgZuYT-cwRDX)>)kuEWY2*Ly?1?;?(XpGo{~knxAvNTJWc6+#p$xo{!aJ#u1a70 zobJE;l{|5BTt7K=Rt64#BKs#^k*E5{Wbn|SxaK7#yWA-^Bqt>&rFi~)-RS-6b<_Oo zUJFuvZeQwu^HR!RZvNf4&r>Rp?eZw)(<(J_4`iz}rSd~6J@M(v8dpZ*oy4;%$ftZW z@qOOO54nPo#;A&#D_q;mEvjnG)!5u%t~KV_ys4DS#V;^$oX5X!=I`jgSkCX}w48~> zkOeuK6(LJP)`TnySrxJ@WL?O@kd+}zL)OOcV{yppkmWg=^&tyHR){PSStGJYWR=J= zk#!;qMOKO|6<I5?SY)*vmdjzij%LBgijgHFYep80tQuK1vTkJI$jXtWb67iv#dBCa zhvjovzoRLDqp1K=0;C2=5s)e%WkBkH6auLPQVOINNHLIVAmwm0^*{>ZXexq~1gQy9 z6r?IhS&+IQg+VHVlm@8{QXHf@NO>GheUJh<nhGH$LTZE*38@lNCZtYCp^!=;r9x_j z6bq>qQZA%kNWmOU#gLLAHA9MqR1GN`Qa7Y<Nac{yA+<w_hg1(KA5uT0fR3huNC_QH v4Ur-uRYb~&)DbBpQc3*3l>bypuW4&xDN!wGEGw!g3Kf?XG?s*%r*iKf0hJHV literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Tirane b/lib/pytz/zoneinfo/Europe/Tirane new file mode 100644 index 0000000000000000000000000000000000000000..0b86017d243f1b7bbb41d6b4feefcb2b7edfc7d8 GIT binary patch literal 2084 zcmdVaZ%kEn9LMoPdKbGQeZlZ%fLMs2U4hF#MM1M0pb46CRs4e-6A=^XK*Yd5v6fkD z%zYo+S+1PA98;&{8vSwTtfmepGS|kMI?L6{XpQbx&YZIRz0V^z*Mpw4b<WOxo!#Bt zN59WIv}#jbj`h&xG2ifTy=5NW$L=|rSKqhgZKwa{Lb-Irr<cAM(&&uBNc8V>Y_F#+ z;=SB-W6aQEC#Gk<J@%W;k=XDw`>_-E9BGNM<McZxzH-<e=X~irKKrDdF#n`8e%vRv zFEAMK-5a<u!3sMQuGQF2_J15nJat-<j&)1&hx=t>r%KB9H)K*nvpltCy*ynWlGLIS znd~o+w4`*I67z(ldxDyND^D|iO4F%><8|7(NA;PWztn$dNT-LdYUa+1n$>bjvulrQ zp!$mBH1|kuaj)bp-6Q$Q`=lVJO$tUjWM*PmXI@_?g?C$Z*6E<kzE-70T{T+VJ4@&6 znx}I=NYZEPQgz<WLV2##qruulc|Pr?mIOzn)N?`0Qoon7;h$x}sIO$fK%czu<43Zv z`>2%npV5llowDfIL0#O~C@*%tsY_P8t4kX;XyuFs4V9PaGT$Oy?w_j5Z)a;&La9`J z8?P&GWyq?}{?yfjURiS>PO5u;leHaxNKJS^?3zJYx8qlRY3}E;zPev)Q})V+;%=>r z{!}+6t8V<|J*^*U)=how`ttRVZa%zP8_qY$mUoJ^v8z&EsZUX7SH3hYDU#+opS&8F zC@m#-<h3|UTC)<gHS&YB#opGo%V(v1_=;{l(IwjkdUgBWuXRWFK7GCYkaq0u(5OfL z=^i!uKf5g}{(VkrtXQKhD``?x^n>r^6(K8F!c!UIS5Z;!N9bRi{J+h`=|>iTtN>Yp zt62ko&mvsSDv)Kknsp!xK~{n+1z8KS7-Tiba**{P3qn?eED2c?vM5)xDr8x%W?jg_ zkd+}zL)L~Y4p|+tJY;>y0+AIWOGMU)ED~9zt63(pPGq6TN|B`^Yeg1|tQJ`=vR-7t z$cm9ABWp$$jjY<$EE`$3t64a*a%Abq+L6U0t4EfPtRE==QURm{NDYu8AXPxhfYia& z6auM)t0@Ii3#1rGHIQ;3^*{=OR0JsrQWK;oNL7%sAa!vyg+VIgYD$CD1}P3w9i%)+ zeUJhn6+%jc)CegOQYEBJNS%;EA(e79r9x_j6bq>qQZA%kNWqYbAtgg<h7=8{8d5f- zZb;#f%DI}-A+>Wg#Y3uxln<#NQb44FND1-(T|=)a<n#cE^jG9&=4WR6D+1Y=mFv9^ D;y&Xi literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Tiraspol b/lib/pytz/zoneinfo/Europe/Tiraspol new file mode 100644 index 0000000000000000000000000000000000000000..5bc1bfeba62d14ffa8995c1d08ba7500956c494a GIT binary patch literal 2436 zcmeIzeN0t#9LMqRjYkpbx<)*!VFD`hD2gRYUcnTuUIdX$@RWooXrW@5fWJA%Xd0SW zvtq?sQ`wZ^Vw8eLWy*?x8e28A&ekk@n4VS6mbv=94`bE(tG52@b9e3=?wE7V?~8&p z8_QDUZ*%HTcvzEu_&P@Ex0fqk34EjDWB=0&el$*BZ!yk%@r=<uSa0<7wV7w%e9=62 ze4qK&ky^92akKe$O^NwV`3m#hqD=GrgURN5>CxtexVy}Yq26X+PqXp<lXaf{uXCIq zwuKxVc-7?`JT%)mwEUnE9D8i&?$Aq_A^sztkYDcX4gEG~C~U-)8Q$MK6w%XaMV@%p zifY+oP1?WBnp|6IO{rXE-Lp2^iq4&DO`RWS-5VcfO^Xb$V#eRHVqCdqtWTPXy%D40 zu7s%R##Lkb@b|`y^Mjt5odZVvYo|Q34tE#{dz(Fp+YcE@rPZF~;&wA->#m{HjFU2Z zWuv4;9gsOGn`F-IDoKxMQ0bTRW!|lFb>G=ExqoDtntwD;Wpu}@1r4cc;Y&d(vn*IW zuy>{`%DSmsr4wZFw9izQ>$+t7bgLy(KawTaK9!|juglVbGqUW<=Om}SMRNO3DR)!7 zEI-k#R%|iEv%gy9t*%uoD_5)hxh1L~cb-}mlBo*gBUItdXjK%PE=8YstA}oc%bKpQ z)WgHS%Gy>hDenASN_Jn7b%%S!Sa(q#sk@{eUDzh;i_fXj$@}E7j8;|Ve@tzN+M_mH zeOi^@E>#=*D%7US*{b5WTUB0IFPj?^)s~}K@_6|~^+ZFgROKbe*0OQ3E!ipCv%ZsR z=MS<Y(NFELhNULx8&xyZB|EQupq}bDBD)4os@fMiRbBf5RloCjwY#ZGIqvZB^8V|~ zcbuQUpa1xP(O2MvKVH82Cw+YX!<R^ezy6Ob;XjGDNNA1d?`9-90!1RH$i%Vt(NBV$ z63i!#u(9{_OvfaCUZTg|$37YR6LQ@J;?mC|{bXh3^QJ$rAN`Fxf3bdY_zO53qmTUs zAN+X*|KKiv(<|)i`<Zf|lpCeosa<`mlzXMzEah$~w@bNS$_-QQm~zXMd#2np<*q5W zO}TH%jZ^NNa_f|Pr`$Z{?kTrVxqtp0Fo5C!#R9w91BwY07brGRe4rRXae`t6#S4lV z6gMb#Q2d}6LUDv*iCygp#T2{R6^bnsU+ijQD9%u<p?E_vhvE*!9*RE{gD4JBEMj=X zFp1%kU2PM?C%f7xhEo))7+z7#qPRt|i{cl>Fp6Ug%P5{POryBQu#MuIU2Po0IlJ0A zhIb6}817N*WB5lgkl`T3LW+kJ6DclIY^3;TR~t!j(yq3W;w8gOikl2O8GbShWjM;P zl;J7GREDb*TN%DmjHNhhS6fT*mSQf&U5dRFe;Ec-9A;Qd@t9#U!)1oe44)ZBGn}@o zt!8*_SDVdnn_@S^Z;Ig*$0?T6|2_YAo(JgP0<%*1eGu<XO-M^figza`(Ztk%-vMds BjH>_u literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Ulyanovsk b/lib/pytz/zoneinfo/Europe/Ulyanovsk new file mode 100644 index 0000000000000000000000000000000000000000..7b61bdc522b5b7f4397fdb9246185f4d972f4b6c GIT binary patch literal 1267 zcmdVZPe{{I0KoD0%gwE=LkDY1H?wT6O;^9>CbOokrVeX@#PDDR(jOrRdV~jqz(MAz zf+C10qG)vK@1agvj_4Apr*0PfAUaeK?-GGh>wSOIQ<pmSetW;qkH=%M-}|mGd}1^% z{upcY3X_r5ljpiSqO<s{<Q-fWzFuDMpErH(xr}eoc;f#e${QAvX8r8>a>Mi$EAZf) z6}&lSHC{MwZ9F?<HJ!{^n+~O{&H0GcoUXUFw0W&iz@u8cODgOP%kX!<-1^B^jEv6| zBkvYv^z~QS`t-eQE6vH+)t7SH<YT#g?6x$IU6wlzPpkOpYpT8LmP#Brqmsd*>WCjy z9Y2q#RKvJTedtl0OT%*Kix#zOAuZGQva;)WqwJmv$=x@E%#6C^p2>jP+xuB&kN#3U zp|`R(YpFixLz!!SrE=fisQ#Lg>Yu-__I;dE`yX9Y`PsX2;L<5o$OlJ;e$>f{N~L1d ztg2oP=kitSs&%<n>)YR44wu6rL~KOARuMIYe(oDI+(M)>yz1(GWyR1d)jd(u&^rT7 zVl8`EXJ>w(AX?3KJ(GGS^wbAx=+E-td1Vy-;kfm$tZ?MWvGW}qJ#zd=0=7B>Bn2b~ zBnc!7Bn>1FBoQPNBo!nVBpD<dBpoClBq1atTb&Y;6Oxpz&I(Bj$qPvg$qY#i$qh*k z$qq>m$qz{o$q-4=R_BN$X{)nD(nRt^5=AmaQblq_l0~vb(naz`5=JscQbuw{lD5@Z kBWc^}yphC_%#qZQ+>zvw>~TW3@SmpdN$WpHcP!!g4SK2@ng9R* literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Uzhgorod b/lib/pytz/zoneinfo/Europe/Uzhgorod new file mode 100644 index 0000000000000000000000000000000000000000..677f0887b0c6c965fba6fb3c8a4d47c9f6f51695 GIT binary patch literal 2094 zcmeIyZ%kEn9LMo<QWS4RUs`k_E`JKiKV9MSPthRJ>xC&?yUNu_C9Z||C!zpjU>dHR zYmMBuWI0zlEJxXl(MI(ETYu&nZFIeDx#q~#YNd0Iovoa!#_IPzW?Sn~4|>))JLh%w zdvc%L&pWzddrPtPkLzZ0!o#)8JbY*GHLtN*?K*y;Rz5%B)%a^cIrCDfB=(+&C5|2a z_KP2;oU}P_ByLaty2_h85%K<d%SY*#QW6;x!KypP4<<5C4LH7&FFIMTKI3Hfbvik_ zo1NU&b<Ules-3&)7CCtpbDi1#3@6{`aq^FLM(%#0z?pMp`M|s{FURJeJ`=fTBp&l0 ze<!k_e>hgK?_i{G=P3;|zpX`$=cKq}L`o`0rF8AfQkHc@78ZBQ!l_;<&+OOo^P6Sy zZ(X|NgP`0y(V!KBO<Fm+MDN?bOqagm)2fy{UAAwrEU!+|U~{Id$iJYW;FMG+eWo>e z-$>2mkFs*w$Fed$Cab=GU22EklDhE^HGH5?R-Zhk_qVl4r0-c>^T6}EwsnivFKX3j zU5&2uuGaPbdAj~;KpWDkrQxev^}#E1Wy5<v>&CMl*))(UjU$)kq28aQsXs2Urn9nn z&n10$>HG3X<G42G9F{GWL)w!3u5Qgz-FoqmZky`R?PEQ<<9t*feRHF>e%daNJy)r1 zgZ1+Gwj6z8f0?wesgRBquXGk=%g)d*@?@$dU4?1d<$NdIDOa^SaawvN&*`q=L3t`Z zs=E(=qI-sp=+iyNwf8`;+BZxy_jG$k(zSbL@?ZB1dzQz&Z@lThxGn3pi{?*erHD1% zvV6IA^54J9t8DX6oFA}KufMOI_;Rf@PV9{9Z%*pMQ7ahYsgGLW`W5D>wL<0~#DQhm z_Mb2JgWu({?dZSQ#P8<XF*%%$Yy;T`vJqq_$X1ZOxSGu%yWyAF4zeF)L#}2=$d-^j zA)7*Wg=`Di7qT&AXUNu&y&;=Jc86>a*`KS~AhJVbi^v|4O(MHQwu$T$*(kD8WUI(t zk<B8zMYfCV7um3@*)g(ZSF>ki)5xxoZ6o_eHjeBZ**da!Wb?@Gk?kY<M;d^10BM1% z=>gINSJMTg4M-o5Mj)L)T7mQeX$H~_q#Z~<kcJ=~L0aN!dV(~?)pP}E3(^;)F-T{S z)*!t>nuBx)X%EsLq(MlBkQO05LYm}ix`ea|=@Zf@q*F+%kX|9pLb`>t3+WfqFr;Hh z%aEQSO>;F}L)zwQ`i3+P=^WBJq<2X3knZ9C?Xzu<>EnW80_hKz6qFSN3QPRqqJaBV G>iGj{t@?!k literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Vaduz b/lib/pytz/zoneinfo/Europe/Vaduz new file mode 100644 index 0000000000000000000000000000000000000000..ad6cf59281a1046d9dcd045fda521585e3e33e06 GIT binary patch literal 1909 zcmciCX-L$09LMqhq+=x|UkjT`&1!PZnmp6((5_jPcE=8#Ej!E(waeVJGVQV`Btqi5 zAVpMc%Z5ah^}y<Z9c&jJCJUQH7eUcw5kZ9=Nd4abC5WxZ{r}9ohV0?@{qfIST%2Tm z^*GJH@Zni)KK$;!(R^KTEwQfLFSD+;`>f`(xmK9_nfB^=M_mEe)b;AL_I_|g`~164 z`=0w<!%v=)h(iq$x#th*SE~}WZj<ycDVG7W7sx=LU)*UKGRTuE(GfB7L$}@%<Me9G zo8db6VYJ4!_R=92I_uEJx9ZvdREO2w(zq>GHGbtuO(;C9iTO7rsk~8=)0<>?&JIb5 z+$*U`m6F;~EhEC~bj00xGV()(jymO)(YNz7t-e6hn?~uFn(;bzcZ7~BcI)^pBV|IS zQ@w@Z@>BF<&G2?ert`99x$jBVi$^js;BT4Oa!G!E@R$73a8P{BXEb|ztxP)fr%o;{ zl_|BGb?WqOnp0Awxj&Yu-<PGox+du~PpnRBPtd%uOv$^^Lub4hEHjV4)>*B=GJ9XB z<TpN-In}SEpsq#c7PQK|^=&$T><L+r->ijEyQC<+L5sT_(}j_$3!m)NMIGh3_)?WF zx$D=Z2WDx>#WGp8HC;>VbLF>1QM$Y)Marh8NqMnLRwVY5l^O43Rj4Hu@nKr=^1f7t zv}@%*=cVe!O<i-eUe>lW>AGEKb$!EL-B7h(tG8EcCx>|h0>AfbSzXLgSyn`UN1$be zh}HGW-@a_W<;}?D%g_IEIP5R~w{JGc{E-h&rTOqX^rLwOy=>cvW!HmhkQ=r&cZ}RJ za?d>6G;-I-ZQGjrMs6IrbL7^Mdq-{_xqIaHk^4s)KsrELKzcx$K)OKMK>DyXjUb&M ztsuQ1%^=+%?I8Ui4Iv#NEg?N2O(9(&Z6STxn#PdMY)xxOZ%A`UcSw6ke@KH!he(S^ zk4Te9mq?pPpGc!fr?#e5q*q(hEYdB~F48a3Fw!y7GSV~BG}1NFHqtlJIMTVTX&vd^ z)-;cFkF<~Uk8A+41IQL2dw^^LvJ1#IAp3x91hNyzRv>#}Yc>Pf4P-lz{XjMZ*%4$* zkUc>*1=$s3TabN0HU`-lWNVPUu{E26?2fJ39%O%z4MKJZ*&<|*kWE5%$q~@Wyn)W| z{eB*%p!b#;CNocFr$WT){^f7xX~O>|>c5RL-@#_Hh9$CIp6ukfl(+;>c47j?CkKB5 Dj=i{v literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Vatican b/lib/pytz/zoneinfo/Europe/Vatican new file mode 100644 index 0000000000000000000000000000000000000000..78a131b9fff014c3ea895a9cc7bf4197462b6602 GIT binary patch literal 2683 zcmciDdrVe!9LMqJ!Xt=EUo^!8#3EA@5kwSBG(}`4Sjv-{SG=JX(UFj}RJ`TNG50M^ z$JE42u8UAB%r(?jsk4b_7?zcjWoAWlXEUb+XutPij9LBdIXvg}a2S97-XH(8#W{mq z|G5Iq8$MhI%!lutR-4E6q+8bZ+N!!8$4}Wi54p16e*Lz!t2CmnH2WQU_o}k&Ju`+{ zdoy}upUh3PtFDfh)9;08_1c2E>OHA)=FP!!=JJWUvw5@hoBVE4lTl-z8xgPHj;oQ{ z$eY&re%tKx^{e&!_FJVP;h^1c;aFW`M2*$>S%uxyRADujY_)G+Icwip`-$Z`{;}<z z`<CU|G0pOhe#r`Wwb1tU-)y(b+iV4PSZW8Q4YPylr`W-<Q>@@q$#%z(WUJ!`;dZBA z>g<q>&Gvn#n=L8mVTG1mvN~rqS)Hr5*<CWn$o&a_YuD&{?bhWd4fCGXu<J*)`{fVy zfpewW<Eu9`{O}QpI8ZK;J9bIWVwI>BYou3hzC1W%o;;M{m+0YX@^H)$=^fEW9_iR! z`uI|{&yB&__eyW=SKCb=Jszn2D{g7bzTb7gmLD{B{V5$-a9HE!mudXW3o<CbLK0Fc zW$>h}k{G#Dh74LJLz;^uDSV42U7RDyjfFbwNUA*c<5V5Kf3~Jn4$~1EM(N1y5&C#e zw2oSzEThNzG<ANsjOl$<(^8vdtnX`0kG>@7*M608-Y;Za^+|c+@?IHV{=Q^XeXg0C zie<uq-TGu+o;+2&PA5*;sFQLRXx7kN^=G8((_s^Ia!fy+d^1j`b{#8Izw4~e+~_OQ zKK)Cl*9Oas(omUM@u$oxx-PS~R7>{kTA8!*51l*mkj$G|rSqdoWI;-~<^&zog^{WY zf7_;un)7w>$>sX&MZZ4x?sUyPu|%GKEk*P8XUPkTqSV@uC`%>|m;9VCc`?4H6r?rC z(omNa4(zIh_Rq2`<fbk=drX#JyPz+Bv|nDSuGAGJU+T*8ow{oIJ}uf*q#k$M?G14A z<Msr2ox9bR-|l*PeC_W2?Q(UfFvrUk(aRm+a)pSiee1L3P22m7Ous8NEsKYr|8ScN z%@_awALbzQM?PF-=EL{UJLXC`+OrC+!)q+$a66g<jvR92pd*JJIq=A#M-D!6_>ll0 z5kNwK!~h9`qlp3%21gSIBoIg>kWe78K!Slp0||$ti3bu8BqEL`BuGpgO;C`iAYnl# zE_@*{TqZI=Xpq<-!9k*fga?Ta5+Ec(jwVD%j2umnkSIBtFd=b50)<2h2^A76Bv_6n zT1dE%cp(8pB8G&_(Zmc18WJ@mY)IUYz#)-CLWjf-2_6zXBz#ExkN_ePL_&zf(9r}D zi6Rn4B#uZRkw_w;L}G~q6Nx4gP9&a4K#_<dAw^>9Xo8AF)zO3%i7OIVB(g|ok=P=^ zMWTy@7l|(tU?jpwh>;j0K}MqNXu^!d+0g_Vi8K;wB-Ti<k!T~~M&gYG9Ems*awO(R z(2=M+ny@2rcQk=VB9DX~i9Hg0B>G7Bk@zD6fQ$e#1jra5gMf?zG7QK#IGTY#MgkcM zWGs-uKt=-@4rDx#0YOFt84_enkU>F41sN7(TpZ27AS2^wh6WiMWN?ttL52qzA7p@# z5psJyZFhjr!;i=73vljMTYkIi>1`Ky@9)+XGFxO;$ZU?8iV$&iYJIl6X?xEWP5Rd! fwGEQ_7HmdpKs<95lbH~k7#kOp86SrO6N3K%dd(YT literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Vienna b/lib/pytz/zoneinfo/Europe/Vienna new file mode 100644 index 0000000000000000000000000000000000000000..9e2d0c94860438443e8d8307f2d5be74f6eea2af GIT binary patch literal 2228 zcmdtie@xVM9LMo5zcQHF+YC<mAr=y1C%-{4$m~2A%2~*zBoY{inuKr}gVL@a%(X`D ztz$Zeq#|S0?CKA!HT*HN8Z9K$E<<x|x%^ShG0j<PEI-fhAN|w%qksCN&+WdC&u+Uv zZoAjhyK;Sfmi4zY!F<A-^QL)ozw2f5=$h!L=?b13+cO%_?%qTC*2k)Q+jr^y>&KdU znquX^qDmc%Y}TR6OXTD`4jt}s$yYmaWaQOkIpvFyQ+~IcKA$sorZ!H$E;%j1{1fs` zYQLOy#>%&mJ7tO|W$fPUk(%hFpEYL6cj^c`sg4^*b?U|Ub=sMI8hc{9#vMK)@dpEv zuxGDK?@&qH{EEzI@X39vYUTb?uOt<?Wu`MvlH*h4fr(QkCBmgCH*<8>kI9-EjM3Sj zP0$DXe^Y1gRh`p$PSe`I)bve<HKXo5&8!-gEMLE57Y<0yvYnEf&@Fjct&%tHmw9oW zI`6`2$^WB8=O1y&L+2~Bps!jB2j=U;TNmnru6TW<K1mn0=gXr-5$dXolSRpw)$JOW zqKGfFIO%&SzII7U!j4PH@SrTdctDl}-j&j!W9r$~AxjVL*T)(g<?)W!v~0y2x~yT1 zmd|ZaZ)veU;aIB6ovFI~R)$tY7fHq0d-Tbhvt;FmziMSLN>=TgELHu#$W#6sQr$T$ zHPt~`z2zr;dcj9hTQ#J0iMwS@VL<EeJfv$ARM%eFrR&Chx_+=tpSj@G4R2R!!{^QN z?CXWv*jFwa*ClGx)?8^WD-d73L!Qf=E}PuH%kz^hX-SXPma!kCb<!<u9r;w+u8ryo zAN0wK!vnf`_b0j~(5)}E^{RiHUqeGe!@|SD?FW~?t|6fjcl_5c%euSF{D-ah86n}8 zHA$?<f4Y}?yq3#d&cn+$Ld-#P@&1d&{Atd{p6YaDIksj!$byg+AxlEmge(eK6|yX3 zUC6?al_5(*)`lz&S)Hv}9<n}UfyfGxB_eA?7KyBqU(Pa-bs`HzR*Edu)~pp-EV5c; zxyX8v1tTj)mW-?!Sv0b0WZB5Nk%c2GN0yGP-PSA~Sv|6RWc^41kP09rKx%*#0jUB~ z2BZ#1A&^QSr9f(7Yl?wX!`74osRvRJq#{U3keVPxL8^k31*r>C7^E^tX^`3=#X+iL zYs!Pv$JP`GsSr{kq((@QkSZZ%Lh6JR3aJ!QDx_9Ov5;!nnsOoavNZ)mDu$E{sToo< zq-sdnkh&p-Ln?=q4yhedJfwO^`H=eAngSvfL`sO%5Gf*3MWl>K9g#vJl|)L3)DkHs zQca|sNIh*$L6M5unvx<lMT&}46)7uHSER7`|Ez433GbXt672M3r{$()I6aven4TSV E8yebJwEzGB literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Vilnius b/lib/pytz/zoneinfo/Europe/Vilnius new file mode 100644 index 0000000000000000000000000000000000000000..46ce484fb415aed15a6484e34a757c1b30a60eec GIT binary patch literal 2190 zcmeIyZ%kEn9LMqB4NxM&FBo135QzjNgv&p*O%&tb6i}2a{w1jpv4|ASfoYtTIoFu` zR&3?c<-bumVm>f?kS%MjQQh!9VXaxK)yl@2JFT3vjn(gcfLis)t%v>24zIIwf$@OP zJGgOsTao$KNw9CYIeYERbCzSj99@;(aN@ZYublij;GSyhZW$iDyyKG%x#7^&TKViv zzmA-&kjpQYNH|dTO=9-z;aN#vmiwcxH28m;aWQEuE}VR&qWtdAOW~9Y$KBMy7u>X? z&$zSucDd>MTHQGvYu${^)$ToY3*F4cIqtoA$?n|LWRn#eW3r=4o$TvbPR{p!XWnSE zlY8l=bKki!C-3wXXMTUk$v^O(Q?Pr;3ADcE6gGcqin<0&aoJm@WX&N{a^_-7Y1%Pa zP}D68#(QN^O1~~inAx)A;4)qMYO0pEW$Lm6_sav-QCiWOB9(KmYE{L!R7ZWRHJM*a z&9xt7dBg{@d}LTwe0NG}ht5b{=zXm}+$SpsPw1-7PHE_SR#&fkUe|PN)y9P#8mz0) zwf>dbls8YCZUl6FVzsRQJV7^H&ykJif7MN+F|zr1yfmNtNgnL|MYi;hNXwQ{d1(Kb zKD_iDd89d{t?5T(YuS*tMZc}v(p0zo__DT-cj@-w9)0wypg#7-ChhoWr#ybBOgje} z<%#xm-Epu~cCKD5U2T5ZRXAIASKX8+<BjYoNYp*<x6&PVL%YM5r03cfy7%mWJT>yJ z?mP0K?jJg)PxqYG-ow4>yDcIzA}V^?^l8(hZlAbf?wGt{@BHT#88;(NBID)1xQy{% zu>WLc=2;VI%&c}3ZA_e)SYuM>7%?U{VD9?sjW3D!d|g!TOPl(1>e-hu^-~ky^Y`E5 z%c!dlnsxTBwRc5T&{WyGzOmLmH}X(vU)YC<<MSEgn>>@R{`|z}3*N$pez&LTe5exH z60#>`Q^>B6Z6W(YHiqmB*&4DpWOMvk><-x;vOiC|L1c%>7Lh$7n?!brY!lfhvQcEG z$X1cPBAZ2ai)@#}emQK|)9x7AGO}l6)5xxoZ6o_eHjeBZ**da!4x8t&dk)+8wEIUI zfONpqwgBk?(gaW21*8o~ACN{Moj_WF^a5!H(hZ~?p0*!ILp*Iqkd`1lL7IYe1!)V? z7o;&rXOPw)y+N9TbO&jVr|l2YAWz#Nq(w-NkR~BrLfVA%327A4DWp|MuaIUT-9p-h z^b2X2r|lThGNfln(~zzqZA1EoG!E$;(mJGfNb`{HA?-u@hcwXBb`WWyr|luqM5K#I p8<9RDjYK+$|F`niTNz_BJHLpu=G7PHm*xiwiu39V1Kv|f%pWP&2jTz# literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Volgograd b/lib/pytz/zoneinfo/Europe/Volgograd new file mode 100644 index 0000000000000000000000000000000000000000..8f170dd97adfbb9a93d3ce6b727cec5d40e64e13 GIT binary patch literal 1183 zcmd7QJ!lhQ9LMqJ?8Vg7!9|<eq`uTNrZ0M?Nr{@))U*lJ6p93gR-yP(5Ck8jgMv`O z+DQdP5K%-ixcEBgWR)N;V%w=(D;zla%75u%(fE9?gP?=BdB@%72uUFMeT##~M&jy^ zV}*Hzi=*3IJg>U5keh#2su@@vy<T1uoN;Qar!%$RtS7;bs$4ghbk<ItFV|0AaYGNz zx#63mZo`G6?z%Jk-Sx-w?uG*?cVi*uHl{brrq()%gaXo3vnWwtR7V$rIyU=xxcT)b z-STu^x0a@L+trtP%lKoxb@aA&j$GE;4o*t^<TYvUyd{Z!rzIIaD;@D6>G*y~QuU{F z>Rpd)UmVmsUNp(hxwKB-%j(YO4Z3S0qIca;Iy2(e-QywI-TOgj5C4#!$Xnf;btUI} zsPm1lB>(k|^i`FlZ|1)2c|Re0A6=Bf)Lq?w>4X#u;o+g#FEUoCRIFuHOQ-Gg&gFhi zdx^i@ZNF_R6)W+tHap5Zr!uNqsoa3I^7m)Uy#DJwh5n*yaoWw5FjtZ<P!@;x#I5)b zLz>@FVN7IDKF_Ggu*kTcW?*DwWN2h;WN>73WO!tJBmg7=Bm^V|BnTu5Bn%`DBoI#% z2@(nt3la<x4H6C#4-ya(5fTy-6A}~>6%rN_m!}B~iOkc4hQx*hheU^jhs1{jh(w5l zh{T8liA0HniNuKnibU#ZLPcWrG{GX#BH<$OA^{^2BO&9M+5W$cIgmD?ceNz~KLI3! B7OnsQ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Warsaw b/lib/pytz/zoneinfo/Europe/Warsaw new file mode 100644 index 0000000000000000000000000000000000000000..d6bb1561db672f94bfd5bccc95fe1a8d18a158d6 GIT binary patch literal 2696 zcmeIzdrXye9LMn=97!a#FWyiDFA=G9R1if)$c&E81oLvd;RQ5AEi$}BXcEtoxn|CN zbBOa+P{hzFxplN9I%l`hGQtZQHf!Zdab%d9wZ`oCeq3wK*47{W*YDYRUe7r@|32@J zKXX~`Fmu<r*Z#tXQ*A#yM>_Vly*jR8XUB-_osH*PcQw`M?#hGu+Iy<6mu%DW9fwTC z;-jXjXkB()!B=wP(j@t8PlVRLktUyS87>XZp6rH_!{+4HE%~Q5)@GkxbUXjdq!?{n zuwTv&3dlKcq<qn#Oqzm^Yg2QfT=t(bm#+n!=5O9|uAD4$TDp~)mc#FuANE$7t2?%u zAJ1f(*0s-@Yk?H=Q|26Vy|j<HkvzuSEJ}8Mj*K>);@a{%RnBlaztYj%S2EI()dQXI zoL){Bf0)xXBgu42Y;n5BTyT1Ht#=|k$DD}k2b`W4E1X`Zw>Xg>tao}JdD}$oD>u=* zUNwC-y=3~XTV?v?<(U5SW|;ox&$iy5?w6PppFH4AlGvyL@?giFG9V;P2izR41HX&a zL5)2$?xXhlP~aE!RyOP4((^i`<Wn8G`iREo?AL_(O)_j{KoV1HW%#r*84<l(l7<yZ zQd_Z%>Rqa%E-aMMzZGcm(KH$J<!nu<%F@)@WPNzUI32q)N*~FM(QzfC<<apWnwHaB z9*e!CzO*(OAM%M#i1}J3T>V}qdXCG)`Z{_1;+rz5X0N25IHnn!H_7CE75c>T<uYZ{ zdYw9JqfX0PtkXy4sXu*!&WM<-Grfa!=B;?0-F>{wKG#L(+#D#Ghi>TH#xR*z9xn3( zEwZ5ax@48sOLkVHEG)XBi^jeyPtHG~IeoXw;?x?=4Lzt!qE(k%-lj|2R_e04HTu*A zzdl_(SMxqzA<w*=s>`dU%d<=SYW{{1vSMnAtjvv&RSA7$weMGXF5F1L(C%8$`mGdp zzNLi?AIh4mO}h3#mAp`2tLwJEuSGSx^~E)nTD-YfgFL~Wb|LLT?`iJ|4zUlxcfRv@ z*To<I=JIq1`|mGfx*o8v68Cn-MD+^_HKwzePJexliw_Ft7t`a<`yc;I&+waB_LJtD z&dqOpJoxMbC&(UqbD!^g_y3Ex{I)$a4>e3d-ge}TceQUl^5!FNKT-gs0!Rsv8X!eL zs(_RMsRL37q!LIeTx~6oVj$H(%7N4aDF{*#q$Eg9kfONSsvu=?wRJ%XgH#47jjOE< zQXHf@NO_R@AO%7ygp>%W5mF?iN*v1MYU_j)ibJK4QX#cMiiK2*L%EQ8Aq7J!=4wlZ z)C?&aQZ=M(9O}lQa2zVfp>!N-$Dw$xwt7hUkoqA7L@J1s5UHW7Eh17yq>M-%kwPMs zL`sR&5-BE9O{APiJ&}SU6-7#l)YR1$6{)JLEh|!2q_9Y3k<ucyMT(157b!1NU!=fD zg^>~?HAaezRN2**8L6|YEi_VTq|`{Qkzym&M#_!U8!0$aairu(&5@!bRd=;zN9yis z3y)MDDLqnqr1(hnk@6$;M-~8C0b~h~H9!^tSp{Snkacji3xTWzvJ}W#Ad7*l2C^K; zdLRpetO&9s$eJLFf~*R%EXcaJ+J!+@#?>wjvNp)#AghBc53)YU0^$EF^iL}kW|wMk W0-NQ{NE|X^NW3>AAs&Y&hW!qIlp4DL literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Zagreb b/lib/pytz/zoneinfo/Europe/Zagreb new file mode 100644 index 0000000000000000000000000000000000000000..32a572233d4850a4bb4b1278cc2d25480ffc6a80 GIT binary patch literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Zaporozhye b/lib/pytz/zoneinfo/Europe/Zaporozhye new file mode 100644 index 0000000000000000000000000000000000000000..e42edfc8506b9b99362b36d90c8b8c4db67d50d8 GIT binary patch literal 2106 zcmeIye@xVM9LMp`2^5`$Z{6@9Ku9!D5spI?4H7#KW`Lb?5~;*lh+0I5#=taMIp-R4 z?<T9ca<gPq%$RLxf1vA^Yu4zxKeFb?wN;De%zd|VR*u!@`E|6m{_2nZ>-*jJ`}p!@ z?6w=PXJq4!)`0oPaff}w#j(d;JkNX9-iFeT`%ev|M?W2!h>uOw$Y*c)H1>K>VrReX zNX>gAK0EE}N?-DL*!TO4_tP$?#M8%vm3NLEj%S=X=476D(aC!CIcHAaE+>0$i<8r~ z!MSU5l{2??nUh<d@60Pmcjjk$ox7*saPpG!I`Xcib>x5lQ+UA_SE38geI8yk5{niL zyc1sBe==IQ|8Tfy_ZjuKysgDe7bVa+A|(~0vSj^BQkr#CmIk_I>13~zW%O&=r7g1j zMwhNQ8<cy-8?}6}St~|Y=)DJ4>B?6!wX!u=SM6Ue_f;inuq8uQ&!5mxa8jz0KGEvj zZ>0L_53(lV16dP0FZX|UTxy1gq;~8*tvl2wYfrqU545*SxbJyg_uvb<zHO`4FKg3C zZMAOjt<{Et1=?`kuZ^iy()iUZedt=gY&`voZo1%=&Bta+)5sNhxc6si?vF{d`GRcO zcUd1<`K~<LG^Q=tM`UZou(l?j(rsC)+kSjmw@-HJj`KbG*rkX*{^lla`*^23aj-($ z2kYg@?b+IKpj39QE0@kzpX@50BfCSt$x}0pbQPs)m-DT3r(D<W_&MphdQtbB9F(VH zqq_IVhq`b0s6Nv(puLBB)iW*Omc*pQgj;W($+!LO^iI2ZPQU%XIE~5q)&7&2oVZCe zCNsx)jale7DaNFTnZ+B=?5TTMr6*(Rw^PraY~FC^Z)@u!W|2P-@S9L5V(RK^Owbw( z)$_w@`_evecs%X}e;poA<X<e~4|_D6{wNt)2(l7nDacxo#UQJ3waejSSr4)xWJRua zNywUzMIoy~mW8YfSs1c1WNFCSki{XZLzaiE4_P3xLS%`qc8$m)kyRqgMAnHc6j>>< zRAjBlVv*G%%SG0UEEriavSe4gW@OQ>cGbwTk#!>rM^=t39a%fFcx3g+@{#o;1wbl* zlmMv#QUq691*8nFwhl-kkV+t>Kx%;$1E~g54x}DPL6C|dB|&O}6vfq61u2WGtqW2Z zq%ufpklG-{L8^n42dNKIAf!S_iI5s0MMA2Cl*!fB2`Lm(DWp_Lt&n0N)k4aJ)C(yX zQZb}tNX?L<Ayq@l=4$JP6wcLF4k;Z{JEV9>^^o!*^~3)Q$hSZdy*8VR17xzGuB5QE Q&|g$iP*?1CpO$$41Tp&eZ~y=R literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Europe/Zurich b/lib/pytz/zoneinfo/Europe/Zurich new file mode 100644 index 0000000000000000000000000000000000000000..ad6cf59281a1046d9dcd045fda521585e3e33e06 GIT binary patch literal 1909 zcmciCX-L$09LMqhq+=x|UkjT`&1!PZnmp6((5_jPcE=8#Ej!E(waeVJGVQV`Btqi5 zAVpMc%Z5ah^}y<Z9c&jJCJUQH7eUcw5kZ9=Nd4abC5WxZ{r}9ohV0?@{qfIST%2Tm z^*GJH@Zni)KK$;!(R^KTEwQfLFSD+;`>f`(xmK9_nfB^=M_mEe)b;AL_I_|g`~164 z`=0w<!%v=)h(iq$x#th*SE~}WZj<ycDVG7W7sx=LU)*UKGRTuE(GfB7L$}@%<Me9G zo8db6VYJ4!_R=92I_uEJx9ZvdREO2w(zq>GHGbtuO(;C9iTO7rsk~8=)0<>?&JIb5 z+$*U`m6F;~EhEC~bj00xGV()(jymO)(YNz7t-e6hn?~uFn(;bzcZ7~BcI)^pBV|IS zQ@w@Z@>BF<&G2?ert`99x$jBVi$^js;BT4Oa!G!E@R$73a8P{BXEb|ztxP)fr%o;{ zl_|BGb?WqOnp0Awxj&Yu-<PGox+du~PpnRBPtd%uOv$^^Lub4hEHjV4)>*B=GJ9XB z<TpN-In}SEpsq#c7PQK|^=&$T><L+r->ijEyQC<+L5sT_(}j_$3!m)NMIGh3_)?WF zx$D=Z2WDx>#WGp8HC;>VbLF>1QM$Y)Marh8NqMnLRwVY5l^O43Rj4Hu@nKr=^1f7t zv}@%*=cVe!O<i-eUe>lW>AGEKb$!EL-B7h(tG8EcCx>|h0>AfbSzXLgSyn`UN1$be zh}HGW-@a_W<;}?D%g_IEIP5R~w{JGc{E-h&rTOqX^rLwOy=>cvW!HmhkQ=r&cZ}RJ za?d>6G;-I-ZQGjrMs6IrbL7^Mdq-{_xqIaHk^4s)KsrELKzcx$K)OKMK>DyXjUb&M ztsuQ1%^=+%?I8Ui4Iv#NEg?N2O(9(&Z6STxn#PdMY)xxOZ%A`UcSw6ke@KH!he(S^ zk4Te9mq?pPpGc!fr?#e5q*q(hEYdB~F48a3Fw!y7GSV~BG}1NFHqtlJIMTVTX&vd^ z)-;cFkF<~Uk8A+41IQL2dw^^LvJ1#IAp3x91hNyzRv>#}Yc>Pf4P-lz{XjMZ*%4$* zkUc>*1=$s3TabN0HU`-lWNVPUu{E26?2fJ39%O%z4MKJZ*&<|*kWE5%$q~@Wyn)W| z{eB*%p!b#;CNocFr$WT){^f7xX~O>|>c5RL-@#_Hh9$CIp6ukfl(+;>c47j?CkKB5 Dj=i{v literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Factory b/lib/pytz/zoneinfo/Factory new file mode 100644 index 0000000000000000000000000000000000000000..95bc3a3b1a0c21d0314dd63c80d4e2e8ac0513fb GIT binary patch literal 120 ocmWHE%1kq2zyORu5fFv}5Ss<U(KRptGD67I$7KW5Z)d;-0A>3H(f|Me literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/GB b/lib/pytz/zoneinfo/GB new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 zcmeI!c~I149LMn`L<JQeEs?<EF+9Q$7g95d%sUjwW6MoLv=TGPkc=cVYC3fs=WAn; zsrh)M<bfBMNtUFcrs08@V&0V^=51mK_Pl>H%{0?YGdBI%?yRq!U3dJ;@B8B!IwmFB z^|wb|`w1@|m+g!9jb68Z%x=5F%--*(=j?Kuxr;WNPv*=u^Rh#Xd3%GNpK-%17&S*P zoV3j>N?dO~?N+51w;7?Av@6m%?IUzfMWtElDltn-o9Jcb7xeP2sd~lsy*hVEqF!k- z&8m0ZdiDFe&6>1$vo?K=$r})9^5cf;b#7%okIpdb8(uPBH1?V=&hIlD%3ZqPz(xJ# zp&@$X`V#%s>KgO)NBMfw>`+}eF<XE0O10kX&D2{4E;8SC9j1$73iVd+ezUdA-+UL; zR=&%ALU!zFAv<4wK)zq?lHw6wwM$OO?s3CqPkNBrQx&cDj;YZlPlua*amRFNi&mzz zN41n5J*M_2F4y~Oe9eK#>H5I+4|G{znl7t2s}KF$)cm-kSRdYU-5kj~V~*wq>0>i@ zn&at@m=j|s=#zumo1Z*levYnheu-V7Plbl-(|35x>8g6Vyn4JoQ}(N_I6PRN-LOml zy7qT-?&H<^{2Qaqg?W?p#gTF5QdY9Q+%H96N$#$%wrXI0>lUW3xz3x)mVUbO*dBAe z=9K<@(^^w?+SlBew9Hg*ElBfCE7G;Xz1nX;j;`$=qwBb*>N@4kw7*K#{@ZGFz=cFz zZ%v4)zuT=F%(!6!S2flR#~(3jR-|b(aFc1Asm$$B3(OtfOQuO!g#>jilBT}jOK@<G z1fN_fcUDf7kj*nB^!O6hY<aF~UN}Xym?o;_{IN=>rmI$OB&fUMJt}NuN7Xu_i)z!a zzqG9%tlG8jAnmSplJJ0r5?&f4?aMF9J$aQ9vAsk(%q)}0&+?_?gfGQCC0pF5TDzi> z4yx$%Jaunuf$B8qL)E$YT-7CdqUuterMiaBl&*(Is{5-lBxYkz^}r#oJh(VnVhdvA zp?A7Vx7p3);gm4x{?`4fM;|}w`An$l)#jA+?pdw+_-&WIVMkTpbHAuK-$E5vyiN5x zxlr}bU8>@Dek}>pr>Vr{^X1WT<J5p@Z_2<SQ)N)<C>eZTs(9l2%41Cv<?)DcdHkAN zyiNKj?+=aTiK}<3q3f>5u+jkaq-ms*^3JN^S(jDv%wjbnxm1mud`6z?_PKgGX{S8X za+Vqun=2`G^JR2%k<q8K<=N_V8M7@@o;&Q3u{pyewP?J0{=HZkw|KC6;n|jw_D*Lt ze&B;DJ*BC7F{*`{(EGZ2sgX-%bZjCS7fz^&f!Ac>?ky_w+(CI|)nYZNxIkW=^^Hu< zohz?pE|RS2S#q1ttv-Ey{(SOF-&^>7t=fP8%YL>0=~?HWzWle}-Mo1--(E0Rpz^7O z9JOZJr#_#!>?eQyoNwb3Jgy#tJ+8jk%f4~>`1shD^IP=X!tJ#G_|1?c{~gE;b+mUB zxuwWGMQ$o`SCQL_+*jnrB6k+KwaC3iZZ2|nk=yHN?=NzL9qk=PZZUF?k(-R%W#l#^ z_Zhj-$el)RHFB?!n~mIU<aQ(X8@b_*_KqXB9J%MnO-JrJa@&#nj@)?U&Lg)Tx%bG; zNA5my`;q(aXd8fZz|pn<=>gINqzgzJkUk)dKsw=QTY>ZfX$H~_q#cg7AC9&mNJkuP zOOT#8+NK~~akOng`hqkD=?u~uq&G-&knSMuLHdI<2<Z^gBBV!1lN@cAkTxNGLK=m1 z3TYM6E2LRSw~%%r{X!asbPQ=3(lexKNY@-~+mOB?jYB$zv<~SV|2b(M(mkYoNdJ%q zI@%5*Ep)U!M4E_n5osgRN2HNRCy`bny+oRcbQ5VO(odwJNJo*DI@+EhO+~tjv=!+q z(paRkNNbVaBF#m*i?kQ%FVbM7!$^xAZI6*AJK8QIZASWxG#cqN(rTpFNV6Snw|UTR zq~A!xk&YuRM|$pPn~rqd(Y77wJJNWh^GNHF-XqONx{tIU=|8dokR5<*0b~y#n*iAb zj&>U$`@qp|1Y{>5TLIY%$Ywxx1F{{E{eWx;WJe%d0@)MDra*RuqumzBzHqb~1KAnK z)<E_KvN@35fou<Ce;^wK*&)alLG}o;NswKFY!hUkINFVZ>=b0HAbSPbEXZy_whOXf zkPU<E7-Y*Jdj{Dw$gV-Q4YF?>?Z!cNj-%Z=$lgIV53+lZ?St$eWCP*<JLtdJK>>Cl Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/GB-Eire b/lib/pytz/zoneinfo/GB-Eire new file mode 100644 index 0000000000000000000000000000000000000000..a340326e837ac8dd173701dc722fe2f9a272aeb6 GIT binary patch literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/GMT b/lib/pytz/zoneinfo/GMT new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/GMT+0 b/lib/pytz/zoneinfo/GMT+0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/GMT-0 b/lib/pytz/zoneinfo/GMT-0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/GMT0 b/lib/pytz/zoneinfo/GMT0 new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Greenwich b/lib/pytz/zoneinfo/Greenwich new file mode 100644 index 0000000000000000000000000000000000000000..2ee14295f108ab15ee013cd912e7688407fa3cde GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/HST b/lib/pytz/zoneinfo/HST new file mode 100644 index 0000000000000000000000000000000000000000..616c31bc5ea69cbc5ba90dba190a0f500fe8cc35 GIT binary patch literal 119 rcmWHE%1kq2zyORu5fFv}5S!)y|KbD&29MwnASZ-OeOy58h6Y>!g3%07 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Hongkong b/lib/pytz/zoneinfo/Hongkong new file mode 100644 index 0000000000000000000000000000000000000000..8e5c5813666a4d023bc9f7f7bd0e53ca1a61dd85 GIT binary patch literal 1175 zcmc)IO>E3T9Eb7Q*JhWJHgVxVyk!z8jf<%SZAD7A)q_o%R45M8rRWK#O+_RUA`%G+ ziG$OxhzJMc;;1h|yw+N@-ny%$>ZM&>T5F!agM*Ven9b~Gvd8^C@utM~rRt9pa=&mn zYux2sawyT1>mF*fJ?Z6gE4IJ=e%-dV2Rp8rhbL;~QT1ihd-}ROURh;Ri|0!!Ut!bJ z!jgV6%RU``E6?nteSW(~`p(TX{TDat7Y8QH%ah&uRYStO-g`m6SrRh?&7G2&US_hZ zIwU*3&JNB#B7><#cBrsR-q~XNzP~|+PmS0QU9Ea#-#z<L*6UA=SMBG+<@!tIT{GHJ zs>kMBF}a#i{dGaojL+Dr^Pw#!Kek$b8>lwl`<i9q&SpD#qe^~Us<ef(^F$q+YkkR( z_;&|wU{h9t^%)zg3F`2&fGJv-(M1PWm`J!wM{=DenmMnh^mdr3H;?L)GZm(EPpkNS z{(!sx_EBnQlz%#T+!s`;tzAWUtKwhpy85_U8{5EFT-7>%pZH&_S8#^~krk08ku{M; zkyVjpk#&)Uk(H69J>A;K;+}4GWO-zLqyVG>qy(e}qzI%6qzt4Eq!6SMq!dqA3sQ`y zs|G0tsRt<tsR$_vsR=0xsR}6zsS7C#sSGI%sSPO(sm{}tht!7@h*XG_h+d66D3ar< r1j<C}L<&VJMM_0#MT+%w)gtA3x_Xg<k&2O$k(%-U7aetxmzn5Kw|Ne+ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Iceland b/lib/pytz/zoneinfo/Iceland new file mode 100644 index 0000000000000000000000000000000000000000..ac6bd69731d25fc20724798abd4f683f1f2ccbd6 GIT binary patch literal 1174 zcmd7QPe_w-9LMozO-ES7AW$-}Z<HEHu>K$jMYysKwL@$Wb#X)mML|W;t;7<dl=M)i zRo}!@^bZ2T*dYiKHLYdZT4sOEsr}I`n`J%y-aii=I@PJ)!}fZ3o}Ko2N4D+WwcPpR z_{<YNoOR~Iz5jIdxW*a^ob!p3^%o9quDaOc^=r7sxzt=*-?XZ@s;T9W|86MXX<c0` zt*N{lZAELf?M{(&_zJb-N>K0hbxLRLV(p4wm-~Bt`XJUIiH&J}SaVgoOWO6(&NJFm zmXO|x1NwO0O-UApH92`!Qgil8>d6s#I*_M*EnDST*GlPcJgm>J<;sidE&8%9Bd@lX z>Fa$dc@vzk1EILQUHC%>OOu+Ol`liNw{&QDN`@z5I?~=R?|P5w`^&fGLvvI=o@$iQ zb3q;3b3#5HtCaDu>gURG`Ld!~C)O;IuXA^3W<j=O#@FlQ&q4Xty+psKy*d@IkQtue zmpAL5u58yGiJJe`98Z?(j*U7qr@yD4*cY=mg(6N#AmA(wEOR!Pdw%Tk*mq9kFZOfI zVMAm`WJ_dEWK(2UWLsoktJxUY8QI!u_C_|hn%$A@t!96u0i*+@1*8Y038V|84WtjG z5u_8O6{HuW8LR0AX~$~%K^j6jLRvz4LYhLlLfS(5LK;IlLs~<6Lz+XnL)x>N{*VTd x4v`j-9+4)IE|E5oK9NR|PLWoTUXf;zZjpAareCCCq+_II{9k&`F@XniegRAxR~rBT literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Antananarivo b/lib/pytz/zoneinfo/Indian/Antananarivo new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Chagos b/lib/pytz/zoneinfo/Indian/Chagos new file mode 100644 index 0000000000000000000000000000000000000000..f609611c8fc76edae40cfcfede2767156b11429d GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@McF?)w~rXLmQ$|NsA=k%@_c!TAYD-YtNEg@GX?fq}!v pH-tgkz!ZqhKoUR@LW1!?Q~!gk0O<o+1ENW=ipvIQt(~qJ7XZtCBcA{O literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Christmas b/lib/pytz/zoneinfo/Indian/Christmas new file mode 100644 index 0000000000000000000000000000000000000000..6babdeead509d64c1317373a537b4bd7391af8e3 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14FV5NGhp-fyKu+ghAWD9K>Y^A;C1D XLH~=zRz3mg!*42=4bXf$U2`q~*SHut literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Cocos b/lib/pytz/zoneinfo/Indian/Cocos new file mode 100644 index 0000000000000000000000000000000000000000..58f80514e13d1929df97047c7a3de221640d93f1 GIT binary patch literal 182 zcmWHE%1kq2zyM4@5fBCe7@MmB$f^JT|34!m14GmukW_RA1B;Ju2!pnPnXv&#fFXng c^MFSEuL)IK0@6pwTrL~19d^2AR>lTg02q%N)Bpeg literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Comoro b/lib/pytz/zoneinfo/Indian/Comoro new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Kerguelen b/lib/pytz/zoneinfo/Indian/Kerguelen new file mode 100644 index 0000000000000000000000000000000000000000..2cb6f3e357b6ea6832fa9a5d56ad915a110b23f0 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@Ol(Vp2o>|Ns9P86gr33~m7oEV>2;4B7^!V4)BaOamJ9 UA7mm(BYso4Y=Gw5>6&r@01o#Tp8x;= literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Mahe b/lib/pytz/zoneinfo/Indian/Mahe new file mode 100644 index 0000000000000000000000000000000000000000..49e23e5a0a8d5e90a9da0838a08c17c20eefaac0 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@MOb<ylMp|Ns9P85tOi|A3?{92i)9d_x$t4NO2>h7b}= X0~+)nWFkl(ep9(@facrjns5OC04Eyn literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Maldives b/lib/pytz/zoneinfo/Indian/Maldives new file mode 100644 index 0000000000000000000000000000000000000000..ffa33658444a473605f9a04a78f3ec345e0fbe7d GIT binary patch literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$a$-Oct-vI|Nj}8m>3vbUV!9*BntzBTL1%xk8cQr rFA!@Rn1UpLAcO?tfu{Zs+csYUM1!mW>8}T}iL{E#257CFt|=D)o|z^D literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Mauritius b/lib/pytz/zoneinfo/Indian/Mauritius new file mode 100644 index 0000000000000000000000000000000000000000..b23e2cee1f1bca4abedd105b04824431f40e8392 GIT binary patch literal 253 zcmWHE%1kq2zyQoZ5fBCeHXsJEc{=M^XGpNVb&$ASWZ=m>?SbdH{tNa0|Nm!V1VSbT z2GbWH<!%9tEDQ`54h$SVz99_S2Btu40+IlN5E5(y8vGw*BS;^}Rxk~;8AO9@2h%_o RfN0WO!DRz<k)5sy7XT^`DjNU* literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Mayotte b/lib/pytz/zoneinfo/Indian/Mayotte new file mode 100644 index 0000000000000000000000000000000000000000..6e19601f7d3a420c1d4832178352c1eafbd08146 GIT binary patch literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Indian/Reunion b/lib/pytz/zoneinfo/Indian/Reunion new file mode 100644 index 0000000000000000000000000000000000000000..11c6002e2ec443e44f07313dd8d169195b5c74fc GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3_8Ow(H|Ns9pGBPljfTb)P7+8FKLm0FTAp8&#OamJ9 UA7moPApE9s*#OPA(>37&07mc_c>n+a literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Iran b/lib/pytz/zoneinfo/Iran new file mode 100644 index 0000000000000000000000000000000000000000..ad9058b4937b8786f58e2cbd61adff5ffb6f0657 GIT binary patch literal 1704 zcmdVaNo>qf0LSrvt5n2NZHb7a4MAzg{5w;fr_0Q!XrC!sH7#0O(FI-ThFW_-i1t7n z+SrpyL>&n68qqjZ@}wjPLP~-Q2SS2Gkc{^oCvkFc@SnW-%*>%lli&9ROE*@!tUsP; z^9zTk!W=$N>Z;kT9}dRq(KpV?F-;DCOo!i&d7t4QTU@1M=Lb}r=bessj8jR8EjnrN zq?$2sLeK1*tde`~%aqe4!qG8A&)RoLIqN;rF25;K3pZ(3&PU~Teb8xBo7L?2DKdT7 z1(E)xM0?&mSK`TGIp<D{$mks-GcO(&SzS3g`-oQFoh{N=`$PCwUeI$3oGQmROV3Na zs`6gnk@9hYSa5r>3|xOC^3S*Fg`LrA(ZOqSacib1sD7&p%j(pU{90X<{YfoNIV*z+ z(?#*9N4mr+RV5#W%Vn=R#PSF2az%ffD7|t^mv!f=mB&8IRR>zd>gMTsP1Pl}c3F=u z54hAi@lvi&t`r+4#_5WYovPw{o~(R-PgK3QE35ApiH&_Bz3K9PwfT%&)*S5>wQZHU zuJN6!U)LqK6eo(U^Alu)r&}~mE7DC9o~q{P1G4$sNYV1PS8p5isqF(^z2j!TYVB!| zJ5PNTyV|?;?tRH>Pu(-Qw|tG*8w!OYBBO>xMGpO^uSm<X1xqmRQI?e|tl^fGTNp9g zvV`A?wJe{E82{_{g^Pk#(41u3?Y7PPY;)2$=G*U2@GpjE{?8EOLk7rSVuZ*Lkuiof zgG5G&3=<hAGEiisVa-sHu_A*-MvDv=880$mWW>mjkuf8KMn;Vc8yPn;@UUj&$k36o zBZEgqj|?9fKN0{E0TKcd0}=!h1ri1l2NDPp2@(nt3la<x4H6C#4-ya(5fTy-6A}~> z6%tlh6BiN~5*ZR25*rd65*-pA5+4#E5+M>I5+f2M5+xF5SQ94_C=w|WDiSLaED|je zE)p*iFcL8mG7>WqG!iuuHWGJO6F3q%5;_t)5<C(;5<U_?aseQh0CEw8HEsO2m%)gv e@P*-ZxHH_g`E0HWZ%RPePCF&wN>6jzBYpzV*O<5f literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Israel b/lib/pytz/zoneinfo/Israel new file mode 100644 index 0000000000000000000000000000000000000000..2d14c9998e9dc54bd1ad4710332abafc8e811242 GIT binary patch literal 2256 zcmdtie@s<n9LMqJCS!=^WQnzgfMptrmkV5wO43je2;>&GJH>!w1ZFBdCNWLOC;MTB z#pblu{JCXp^uwWITXT`M>1-o7Da%yMryn3oaK|MmZc5Mlv{hSw^;cWpyK`S>_fN*> z9V}U1l`8&m;pP_}uF*WaM=SHS+n4>uiNyzXd(W4FZ$7>yI*wnpI~%LC-Mr5J_Ek%t zeDiC0s<K6Ot<d)Ak_p!73voxkPdY5m#OCO;<NKVmmk#N3t$xw{S-d{4-x5ExcBl*c zzEHZMR{R*M7eBS-TR*4!?O%3Y6Fn0r+PyWW^u?%?s&Cqe=<6%8BkCG-#LX6a^w6(X zWK*gg({^1?+I+>1Z6C4XtoH)(8xL5M^A6khl<n5{#+L@Bl=!Us#GJs?F#~$)cfIzs zOP%`uqoV=|UH#Vd<~V!CkxHGo*<;V#u|Rvvn*&Mf6SOb)n4O&aj!v0f9Y{?+WclyD zWv4}Lx6-aO1!jdss9BvJnK4wZ9_Sv{584Crq5Vs&+3)X_nR^df55KZe&Z*m@=dMYY zj|BEvSp|(^UT%?`pSW6N#|}9;V~a&je}%K4KTYIb9F%$OcZ&SBoz9~l4U2*;qn*O- z(Q4tQ9kQtAnhKO<IgbU;sK;04$zuNz6)cz`pO~;qg%WR<p{pCzqUZ!?QP)zncyNqU z(mGEq=^AjJ++~T<)=sCaK1-BuJK$KQQ^eBx4*67OxTq*?kx$3;iOS5avMM}GEt^y$ zm-Y0jXKse%@?$5|v*$D9iVr?iD~|`ARj+MP)lKQnbCuOfzJ8apdSQcjzVe2%#=A<? zWOq9+j4Kkg-eFmLIa#b7cTTSDxI?VF{JDJblR>e*V~?zRH%is-e_6g%`<vSEc7^ne zy6w+T*k7;z)teBL-GA@+>mp2u={`?{5Hay$tPmM<J>&oQrJyh<^39Vs-#o==UjBZ; zf3ckrbD>Yax`Av6*%7iOWKYPZkX<3$LiUAh4A~j7HDqtDW^>5yknJJ+b2S@8c8F{d z*(0(^WS7V`k$oZ?MRtm871=AYS!B1!c3sVW`TMb9SF>Ye%gCOQO(VNTwvFr?**LOu zWb4S@k<BB!N4D>3_K!3G=>XCKqz6b7kS-u?K>C0*0_g<O3ZxfEGmvf|?Qk{yKpNs| zI)bzW=?T&lq$@~UkiH;|K{|u92I&pb9FFcF?cwMT(jZsUA*4kdJwlqq(Iuo!9DPC> zg>(vO71ArDSxC2#b|L*j8isVt)wB%hnX73U(lw-QNZ*jgadZx89Y^nw=5cfnX&*=b zkOp#e5NRROLs!#8q>D%!kv<}gL^_GI66qz<Or)DgJ30D^G?b&GNJ}|->S~(G(N$N| wR*t?RjpgVp(psdqNOST3+TBP~<C!TY%ZY`lUcc9$l#-rUnC$bWd3}+;1NQXO$p8QV literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Jamaica b/lib/pytz/zoneinfo/Jamaica new file mode 100644 index 0000000000000000000000000000000000000000..162306f88a80d69cfa5053103c285e2e8b221c3c GIT binary patch literal 498 zcmb`Dy-vbV7(g$T1ZXQ1e^3~8DkF&lJ4B5z(M6{&V!Cx@F(&>H1}9lu4U2;b0|RVq zK7-qR0h9X#UcUpQE+&4>cTdydrsqT#Nxz|fOjf?IOhuOW;6{$8((EhuSWOGTBrd#- zjcXoaPv58h$BW)vUZuswoi4rJn&7#w%cD!PH8|1R$+6ivuj}2@&{Uef-U~gme-Osi z{HLioUYv0@etE2&J4&t2thI}&%3J%s%=n#dq|Rj9J=s<y|FoXy4<=S786I9kjJN?S vh}nu_2Qh?LLQEmH5Mzin#2jJ|DFCSeDFLYgDFUeiDFdkk|F4iM$&TD_ya{zn literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Japan b/lib/pytz/zoneinfo/Japan new file mode 100644 index 0000000000000000000000000000000000000000..26f4d34d67b46513491f26c2e661c6e653cc130d GIT binary patch literal 309 zcmWHE%1kq2zyK^j5fBCeP9O%cc^ZJkbvvel>u)1J-1zaU;O1HD54YJFKHOd_`{B;B zM<4F?{Qtnr$OM5549(0y^$a}=7=fDWCNOY7NFU!21}_&N4h{iHGlFmk36A&=1gVFX r6o6=uW56`fK_D9BC=d;D7>EWr4om|b2%<rb1kq$Wlndx;T}v(iZ^UHp literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Kwajalein b/lib/pytz/zoneinfo/Kwajalein new file mode 100644 index 0000000000000000000000000000000000000000..54bd71ff2dc7d7fc6f3ddb6e3e5ceed4c92b72f5 GIT binary patch literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$obzU9iUUP=Rp1c|Nj}8n3)+E<~#rjGtAn+!1Dip zxB~+R1H*~~3_L!*Aq?7vh77ufMnD>2LkJ1>0j>C7XC|QlqCxh8>;c&gqU(V|<k-(; L19XiY&{bRj+D$OG literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Libya b/lib/pytz/zoneinfo/Libya new file mode 100644 index 0000000000000000000000000000000000000000..bd885315f84f8615da866553c9c0086ad430ad01 GIT binary patch literal 641 zcmcK1y)Oe{9Ki9Xt$}*US)7;RXlo!MImJk57onkU5{bt^QqwbvK^lJolR?C25Ro)7 zh{0lNBC&~(r-?>FBF7@J@O%%G$>4YQ-1qL5yZf9spI>psuc<P3Sd3#9=Z*WX=ZV|X zW9u${D9dYCR{3FBR|ZB^<)EaWvKM~S*0{8*-<18{r<)&p{g#_W*;+dC+s^J~thnyC z@7cOzyH<5>K1)YUs;;FC-JQEs@pMEdQei)t9FaX^C%&6~k%Q@Bl^R;rGrK!t*1Im` z^2I_p6l{_2eqC`ici4rfTQKh_Vou1sZ-XUjI2ZL()1H{f%yIBU#;l+5{_yc1W&ofd zP#`E6K@A86C8&X+;P6a<C`dsK6a|X{MnR*%QSc~$6hsOn1(O0wL8ZV_a4EnPWC}C| Un*vTjXMm^wf*&=1qTh{v0>JjEDgXcg literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/MET b/lib/pytz/zoneinfo/MET new file mode 100644 index 0000000000000000000000000000000000000000..388dd7442a540317163ddc7d83fae62ad9bc9155 GIT binary patch literal 2102 zcmdVae@xVM9LMn^VkI+rqrs7Yn1pDZ{D#yZwew&mM=6()N@yTz5yeFeN@EpsPMLev zSk577#h5j_`U7hXt@U%XkgUcUmTSwE{hD(#XFta3^ZfQ_fAwE|Z{K^|zIT7zAD`DV zvTj>*k?$YZ<?abL*DiPSd0PK;|Hl4_Q|Av%#xyW^RL{LB9qies(P?cOJG;tGyyVyT z3qkwjnG!qsWR86rO|{Qzmg%a&MkVc;WmorvWbI{2Zpzh+NSbDbSJ*YhzbhsCC#BB( zO8)p0@?UsMX=h&5wVxl-toNQ)`my7daU^D$2M*foURl=mCu~kj)UMmmXmhJ0mR%mQ z>jPz$laXulrl(nMLQuIEOEmx694(kg)eRq9p&N&PRbcQ3E$lm`yq=Gh-+oL5O|Pi1 ze$tAf!&Y1|VkK*ywbIN1D=X@>vZ-!cl-{RBr#IQ+KRUGJc+hVCvQFhg4XPMfqFZ(@ z*V6tBEo;u!@}9+ZYh{9hP3g8G=d41(DXUEQNLATiTh+PmY-RjAwsL&bZaed$t%|*D z)njifytmhCjy$j1TU+gp-lw$suH9PGvRSn^wJ1_ur91sKS{qoPwU-K1mr`kUU(C?m z7w6l$*MHIaiA39QDB0?Vf3|zNFIYq0xNU5huuVID)V)jJu*Ui^HD&F$%@r{<UwKqp zGNmow@6*<)sJ4xE>Auqu-G6w!T0U&E2X<Acb*R=J+?u6_c9vS(>T-)V`|aVv+14KV z%^peiSx0_~I?jJ%ok^F}dGdYhIyb3DUmLQ=#z(Y$|GU}|8_?rjgX-SftvKIh@&ARF z&zEEz+>AMK?%e+U&XaH`;_ljr`zkJuA4LAe;s4WJ48YTk02u-@24oP(D3D<w<3I+2 zj0719G8SYo$Y}U~3<ntxG9Y9`$dHgRA%j9jg$xTB7cwwUH!@^s$k>p<A)`Zvhl~#y zATmN^h{za`K_a6>hKY<587MMRPd8L#tjJ)I(IUe|#)}LX88I?sWX#B*kx?VVM#hZ{ z92vQ%8#*#}Pd9jE^vLj$@go61B7lSdi2)J>Bnn6vkT@WLKq7&J0*QsE3kDJmPZtg( z9!Nlth#(<BVuAz(i3$=HBrZr`kjNmRL1N?Sf`dfI(}f3#4-z0GLP&^^7$HGIqJ)GA zi4zhiBvMGIkXRwXLZapA!iB^O2^bPFBxFd;kf0$^L&Ao{4GA0)IV5yQ?2zCg(erfS vL*nP@0*FKq2_X_gB#1~9kuc)_GLDHZmkSH&WguLfSDIH42p1M$esSVoBHs-X literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/MST b/lib/pytz/zoneinfo/MST new file mode 100644 index 0000000000000000000000000000000000000000..da3e926d23e76bc4ded05861c01c1f494b9c8d27 GIT binary patch literal 118 rcmWHE%1kq2zyORu5fFv}5S!)y|G5(w7<_|6fSeFA^>G2Un{xpGmY)pj literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/MST7MDT b/lib/pytz/zoneinfo/MST7MDT new file mode 100644 index 0000000000000000000000000000000000000000..ddca8d1967d93336bb0432f4df79da3efa7ac689 GIT binary patch literal 2294 zcmdtidrZ}39LMo5NFgUKPZtaU&9o#45Kt0MW>nA#=>*{s&<LYIcC-R7owN|2f@1v9 zd{)LRlc1ZmW?t89Y^ZCvwz9e7*0dIOzgRZ9Y_YhUp7--_fAwGM%bwrsjQ>CHz^dSy z0{O?q*#Gdj=k4RS>U+a$ULK6q{ZFTxffs+&ANItX@Vr0T!i_2N*VrR!{D(T3I8&=8 zKk&-bkzy5jGhJk7nu_Y4lITY#)wK1gI;L@^xu|MFUtBV7V)I6H>@$TXP94*6dk-kj z=qvh?F9uBf*`tzhtkWdEHy}xG^(*y4r(D|eq`GWNvs@l*Qdg|>Npkt^YDRIUT$y>D zN|{@xXU2I{YIKH9{cXNU`zlVSho_ra$A8kZ51cnw4V=_-LbKG>PwdlkS47mjwI9hf zg<q;`m%StNliybvSw=FZ&Y4Wl2FV;bZnDk?<ocmkP4?+FozwS}$vsr97j(3kyyq6_ z{FW|NuyKZ7=v$)}RgdYyj62i~1)u80v017p?N2HCK3)}%pB3+kU(}7C4a$=J9&^*+ zZdtnhXLIweZBo+uvALydm%g=fzqzext1eyetSKv9t;>ENR=%8e@txkI$`cx-{7{ct zcA-QnUV7c!`ANO@w|ASn-d(CI>w~7McaFY$NrkBnMeF71IVP~;8y%QPGBt%Kbj`Qf zs&4kzQa7BaR!$Aes@F!<JtKRhzALQm9qN&Wro(D=-#WR^f66p=ckBCe4w?sAg1RYT zhiUdz=;jL{(~^;+TTa%QU~G~O?)93r-~FoB_13BfPbJDjp<?y$zENqdNmFgx!?M0` zQf+8GEE|*0sZEu=vT17Ego+O8(8%XzbIK0AdFZIwG8WR0^$nQE&(!Jmj!v`nh*x(s zG@ETZ({+?YN%S=P`7ixNBD;-9B=UBDRE+yhci(vb@__hD?W?t~D!@3Rc!7U0qKG|) zgp3Ip6f!DgSjf1Xc3{ZJkf9-CLk5R#bo^j=oE;x9K#mb2Lqx`i3=$b7GEAo(Co)i{ z9Vs$YWUR<wIYx^Nmt(xhfH_8t44Grh$e=k!jSQP(+{nO@kt0KQ+OZ>pM@Ekf9~nOq z03-rP2#^>cLEwl25(bVqAc1h&NFbqb+E^gL;D`ni4kR8(K#+(aAwgn-1O<r-5*8#b zNMMl2Afa*E*dW2-hz=4Sj`$z};)oCuB90g#K|-R0gb9fg5-6vQ6cQ?@jTI6sr;Qd8 zE{=F10Yf5&gbaxp5;P=gNZ63LA%Sz+$RVL~+Snn%bK2-3;p2!O5<rd!A|d36AreH6 zC?a7*;)nzii6jzAr;Q~NOs9<|5>BU$ClXL3qDV+NVu}QnBdSPPIpT^0mLsxAXq`5; zNN}Atx=47PHoi!Jkq9FpMq-Qv8HqCfZ^G=c#a?WSo$X(kTacTV?a$B8&CkvA{0**M BL=gZ0 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Mexico/BajaNorte b/lib/pytz/zoneinfo/Mexico/BajaNorte new file mode 100644 index 0000000000000000000000000000000000000000..ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3 GIT binary patch literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV<wIXJb{bS^oTWhSsR+x=3Q(zcHtQD2x^t^vvcGX?$clJE5-_G6d;`8?J zsIE+N{_)JU|8RIZ?BPA~wccM_x*7}Xx~H3_dMnH8-i=ny>9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_<F$=t4Cy7@@9=PN^Sy zep8cY2i1@5=hgg?ZnNP0fC}$#Hw)kER*Smc)arP<y6#!giyQ0JlIp#BY3Vi<k>}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*<iwcnXLTDxRpVV}9P{5i>8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;<SEq10?`P*NO|WBl8u#eX%{lw^J- zC70Lh?JIs(+dqlXrL*VMj+3+czTtP&&ejoqf8X<}to)3AptDi!@(r5@pXrd@$^GV` zs{K+Pe!^6EOQmA6)l|jjNYy~4sSb^m>Nhr-n$dtfe5^u0@<oi=)8N&QcF(HXk_27X zHliNOny>fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;<l1wuJw<*7<2XTo-~N9wu7G_Q7&0<sXvo-*!6BnVhKG#L)eaCDAu>c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX<orL;?u~5(^|4NHmaexY~Fi0dchvK|+GW z1PKZf6(lT3T#&#ZkwHR(#0Cit5*;KwNPLh0x!MRJAwpt=1PO@}5+)>0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Mexico/BajaSur b/lib/pytz/zoneinfo/Mexico/BajaSur new file mode 100644 index 0000000000000000000000000000000000000000..43ee12d84a7c7e47aaa92406d01a539ccf93079d GIT binary patch literal 1550 zcmdUuNk~>v97q3_4T#_^8mK6uf+)l(hX%78(kws6PcyCjEc?u|)HKlsy-)~+<u!-` zvw|op2_<r%0a`@^6&ZpRfkae9kQmW~yxw_2n^rAa^t;@59&htD=eYAqykYXk#@b)F zY@>a7pLC(?eR=!Pu7NIZj;A}m*VE%4>FF767<~Al!qeY;eNd!ahZY}FVU<(#q9m^h z&-|t%=C4+fVJ~#lxP@x*jIXlzoxfW0^SLbjGSMvSdMeQ!erEa2R*7l)XjZh;%gVCH zCiYN^j!Ww>@kIx8Lhy03Dxp9p22`1(d9ga_TeC{`ovV}kE7h7eWAxgdY?bn8j<`-m zsnn~!l2$WKr8mBnjKT<$S$a>hVy7B+$`#3;{oUjQHp)7AX>uoD(zye-&H67#bl#n_ zCcm##Z@7F*ZR||dn+~5*1t&tZr$np5I+tut-mJE43YMY;32JN11o2MvnBtkArFbaL zY#Z*AlHPe{`>Sr*ac!(Az57h>Y<_QcUF_6l6%R~#!%1C{_fGBh*6PZo_f=J5zTPvv zO;rciNcE4SswN;$YF?D7+E3B4_eO@=_hgprKflu)XcwtFm}csay%wKQ&Kd3F`wxy~ zosJf<tX3nwmeqDn##>gC7JuG-)X4V~ms?y}Zi%;Vx_w;<Zlw4<_g@HP*+U|TND!GI zLP4Z*Xp04r3yuiJZ_71LM1#l%5e_09L_COm5CI_)I<!TE$jA^9A|*pih@1>TA(Apg zg~$pK79uS~T!_37fgut*v_*!<?9dh(A~i&8h};apA(Ashhse$l9wI$Me2Dyv03Z=C zLV(2J&<+9;1tbhe9FRaDkw8L$!~zKh5)C69NIZ;yAQ3S_g2d#|4hj+#BP>W<jKCm~ zK|+JX1_=%l9V9$Re2@Sk5kf+Q#OTlt5)!3DJ4{HNj6flgGD3yK$_N(z7t#9JMMv2s V2fD(8LW4pAU7;aC5kVn-zW_OOkC6ZX literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Mexico/General b/lib/pytz/zoneinfo/Mexico/General new file mode 100644 index 0000000000000000000000000000000000000000..1434ab08804dac08e4f595967d8c325691f08aef GIT binary patch literal 1604 zcmd6mU1*JA0LLG5WLQm@3m+vZt>*0OI2dMg#+vQehhv*zJDWLk7<=a9Gz?EoN-^7o ze5{o$U(q%fX3T|@Q)Eq(FwrTJxbRSme7&CEn>$y^h5ys@{@&iJ_u}{Sl?4MS;*X6o zU%0HwT;3<0>v=1?K5dKi1d9FFJ%j$<7`MOo$02?9Ww$?k!c}l@^~cD)cP|PCqQa4Z z>%|2_r{W^dLro8pYeFJrN3=}ME)}k#cXICRG~rHpAm@#qCgx9ltLMM@DHeRYuhWJd zYGKbEy{PNETHM#H(~rGWOWLH)sJpE)4<40E^Uo?zb)C#gJgAms70TtY0hK)~Rc3!_ z5;=}Ine%j`Sn=w!%<at<dHoZ$_hh_Sd1YAVH;fYnN1o`y%Gs)@_J&@SIY#;N+Vtuf z?^SWaK3yXERcYL5SsLk5Yla(T+3i-f_Hnsfcd1jXzm_4(54VVl<1?i{xLs84PuEpB zKCz*EhOUlxi;d|~IxwL~)l7e_Yd*QurXOJ)9Gt8+zqqKkT>YYI!*}J@)*-d+`~_K8 ze@n?jhh=@)GqF9eMea!J6FZlC<*q61B9s`^p|1x-Lu{^Y7^)PzKg`j4ZhFMtfmq$x zQK9yAe$@M$GSz|RM|wmQXQVj}`^nqCJ(krGBZOtOw+M%2T|OhCE$c@2h#31hKF{kD z-c>%~;bxgz;=_~Q^ZkWUmKjz-%!1ejF$`jvO=B9wHi&T$>uehHAokfb20|=^n8+_; zBg06Dl?*c>b}|fwSjsTfrm>Y_EW}!fxe$9H216`{m<+KQVl>2Rh}jUkA%;UNhnQ~D z*v>HCrm>!3KE!@T0gwt9B|vIm6alFMQU;_BNFk6)Af-TRffQrYR0AmoQV*mcNJWg2 zAT=?Hf>gyQ3sM)OFi2&L(jc`#inD2|gOmrU4^kkcLP&{_8X-kOs)UpYsgqGCq*6wy zkXjkV+BDTN%C%|gWfTmlm{BsMW=PSHs^R}%_E;0V+XSEBbvcurNeSNMB<Eab(%4`7 C-Qw5) literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/NZ b/lib/pytz/zoneinfo/NZ new file mode 100644 index 0000000000000000000000000000000000000000..60bcef686badda46f36652694d06d041c70a9d87 GIT binary patch literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj z<t{CbH~x0rWPQTJwaa=qkKbs$+B(`j2bOLrXs<qR9$a`{Itu#Dcf<STP-3guIjU8< zLh8+~pZfIhwKDU_$IbfQP@ehzIgcKF=ZfdpiI22<+mNTHX`dcncf`}Xd7GZd-R1e9 zs6zXkwVspN4bmSdo`I-x8Ms<wp8EQ=RG*MP)o0%x^}V!5{2KpI|Dod=P<uuLyP7np zut#rxwNA&T?ACGBB|1LrIh|0Dr4vSqH8?R+gD-5Bkg1sx(!W|l9T5`Ryhv{O-d`qd znI*UOT$Hd9Kbic-piIdamZ=-t<+fNK4KMvvrv?3}w>up&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-<KEG6FN9>h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-L<DAb!D#1FYJ&7$+5C9<rPVr zG)~gPswMsUuaYt1mPJ?VH1kZdWSuV2#mB;ANoSU3HyV<&Gg5PF&PrZYfZkI)qDv)0 z?#)nLmg+CA>Akvq@<qw_eoOOj49a~!Jg)`cwabc=rn<XdmizbD$;y^Cec;6sDSTnO zK3JY5Vpi%yd6BXzGhd5h0_5SDiMl%Qk`#|!F2&dUwB+(UF;5R`>E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ<OE72Q%HZSv@fZ}hR$?Xo`Us8&v?l*cE&t{aSe+3?%5TBV6n{Z)}Z z(Gx099!}S%S`+l?-K(T#YlzlvN|R^I-_^_EHR_*k@6lua)7vnbhO9Xl`v)AO4dcx& z!^bdMdN>~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-<jQlOc9wQGD&2X z$TX38A`?YsicHnknkzC{WVXn3k@+GMMrMpm8JROOX=K*Ow2^ru6Gvu_OdXlKtu=XM z_WU+X-`1Kxk^m$FND7b~AW1;7fTRJ*1Cj_N6G$p-tz00<u(h&*q{G(A2a*t5D<eos zkenb%L9&9R1<4DN7$h@DYLMI@$w9J%q{r6E50W4xLr98{93e?UvV^1w$rF+&BvVML zkX#|jLb8RV%ht*lk}xD=NXn3$AxT5BhNKP28<IFAb4cot+#$(BvWKM4*2*7}KqP}m z3XvQlNkp=Uq!Gy@l1L<zNGg$BBFRLuiKG+BCz4QGE2BtCk(?q)MY4*d70D}-SR}JZ zYLVO`$wjh@q!-CAl3*mmwpNOf93x3avW%n|$up8@B-2Q$kz6CmMzW2h8_744a3teM z%5ANjBS}ZHj-(yQJCb-L^GNEE+~fZ!`M&%iM90PFy3<@yIZ4jB&e*7&InFp|Y|L!m FzW~Tp3}^rV literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/NZ-CHAT b/lib/pytz/zoneinfo/NZ-CHAT new file mode 100644 index 0000000000000000000000000000000000000000..abe09cb9138504ee3f29cff16411e0a3b0b957ea GIT binary patch literal 2078 zcmdVaYfRO39LMqhA$8+$Q*#W-P}96{xE*eS(FqL@lp|g6q(t;dlLH!QNkSLVR4V6O zO`G<Jn&r%4i?of}D$uQ34=S@YyDa-=TRv!LuGUS1e(!$|d(@-WIXl1C|NOV};B5cT zyRoUgwb1*=YrMO|hu3!Z;W~YrJ5GPO|E<J~KP~a-Aq!qSq@=zpmi+k%OX)dlsc*b! zX)VJx@wr}`6gg;<yW@6s)=rzUvd*TAwpw~w$kNa6P{zz!WxT&unSpF&9$unrzD?G& zC+F+h;S0)&Cu#Z%r!=ExL^HP?({%-jHmm(3o1ON9U7r`woP@<TH|0CcJvZCtjl8S* zCns3;hldsFpQW5vb}4uFc;)R`ul#lARnQStc<Bc!jI~=)cE5^Cmsv@0k4n>Xt@NiO zw&0gEEBkb}m49~079Q`g8-`L<v9G~y?E6uXO<}vK=d>!DmupeW>snk<pqnH6RW)t0 zs<XOPefekAj5cY>uf0}#wnBAh;<ogiEG-+Xv-&<y(E}k{zU7=6x>D?x)=|4v*}AR9 z?DooJHO@I{E2dviY{E+x8#|>tzJJ12esfH#-fOd_p=WjHtG#M|dBE;^I<A(d_S)SW z>ZG<#yQd+f)wMBOlb@n{=at#o)Qf7Jv_h@FC0YE^0=1nPvGz}<YTe++wtnEU?mO_B zbv*O6?(f=X8#WGUqXzWAs;}(9%DviD{<d|_=+r|~pSR6kOq+jy+`6nx-CwM+hlexu z$eY#n=#f%;?BHr`*_B~E+pDy-?GM{JHZ~R*mvGg8`cCk?{wwZb^o~|}<2>&~b0Fw> z$Lqa`f1mZyMlTdD441gGsF<_Rot_s6@Egx-{1?x>`@3U{2CH@=&pyBB`S*1jKz4v^ z0oenx31k<@HjsTF8$ou0Yz5hiuiFf=8(+5_WIw)cL&%PN-IkC&A)7*Wg=`Di7qT&A zXUNu&y&;=Jc86>a*`Kf5AhJVbi^v|4O(MHQwu$T$*(kD8WUI(tk<B8zMYfCV*Vk<r z*)g(ZWY5T^kzFI(M)r+t9N9Utb!6|z=8@ea+eh~A>l%P`0BHfz1EdK^7mzj}eLxz4 zbOLDw(hH;+NH>snApJlZf^@{!wFK!2(iEgCNL!G;AdNvfgR}<e4bmK>J4kzw{vZuP zI)t>y*YyZ#64E84O-P@RMj@R-T7~oqX%^Bgq+LkAkcJ^0Lt2LP%-1yy=^D~Dq;E*$ zkj^2kLwbia59yw-Yry}#eZdO9hLPNG;lkn)_r(P=k`pb@E6FR!=T=U%I4|t3i`;ea EFB%CJV*mgE literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Navajo b/lib/pytz/zoneinfo/Navajo new file mode 100644 index 0000000000000000000000000000000000000000..5fbe26b1d93d1acb2561c390c1e097d07f1a262e GIT binary patch literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8v<nU)CgtR#>b6-0<P2(NH8!YHnHS1a(Ln-=0RD8?U+ zvtrCL36!+fOng|gv7xTv+REl|Yg!BKxhxw!Y?8peo%dPwPk;4STVM9OuOIjS&-=Po z`_|@&f812_4G-6C9^R)b{@GWcUmFNlJ<liU-dDa?dprTXw{@E6E54}vI`*j#+N1RF zyx$s!>*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC<xjIswP>}}?Nyq3Ob<M?I9d-V=h(6JxW8Uo* zv2XTB`ErZ6w*6Uo-Bypd-d8WDuPPC7rT5Ai`6=Rtlm#+=Zn2sf>5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU<P zcbE6;d+N8TqRba{anTx8{Ogb`NpBJ*XZOp}=vq;Fq+Kq%Tqw$3eO)jAxJEgf+VuVJ zELG(-K3&l@M?J8lOjr6t)rzEa?OOSja!thQs@zkm>gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vM<TJe`zEf=(Jg&En`PI|iz51DRZq?M>qPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(<dr(EuI31^XcR+y*SJQXgpA|XQThwERgFKDhdEUF(_ zA+khdjmRRARU*qo)@d~hMOKO|)oRv?EEZWUvRq`nR<mGa#mJJ8HKScLFRYp~%LdlX zv2bMN$kLIuBa25?Z#BzD)^9ZhKq`Qg0I2~-5s)fylmV#&M<I|(aFhb61xGQEYH*YT zsRvRJq#{;R5~L<bQIM)2WkKqK6b7jbQW~T-9K}JZ!%-fjK2}p8q(W9xBBVwfMMA2C zlnJR5QYfTSNU4xoA;m(fg_H}a7g8{!VpdZ!q-GpNL#oD6Hl%JGg+nUGQ97h{Nb!*B zA>~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5<wa#mE15^&RHNV6pj8 VNOLaC$jQh`b7p5}WM^bK{s0D?lEVN1 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/PRC b/lib/pytz/zoneinfo/PRC new file mode 100644 index 0000000000000000000000000000000000000000..ce9e00a5db7c447fde613c59b214e8070f30ba2f GIT binary patch literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-<beU|pf zjcbQR>1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_<xx7r6*b|6_9zz#6+EmOik3e$Yf+TG98(ZBtSACDUckAnuPZx3z7!OgCs&SA*qmD WNHQc_qNYRgCH_BQMr*FDXTAZK`l!MH literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/PST8PDT b/lib/pytz/zoneinfo/PST8PDT new file mode 100644 index 0000000000000000000000000000000000000000..d773e28f1e7743936188263954598b2b521c15b4 GIT binary patch literal 2294 zcmdtieN5F=9LMnsqI7wv(*sHbc2bk>6|R7SDE6S@E|>_$mBh@%co?LiLI`DKi&fSf zUvj8D(b2h<Yle>5Y!;DQt=8xPTC;47wfV4$Rn`_M-P?JefBUQdTIcTkUccSHe*b*l zp>_4OIi7!<82b;G(`_&Bs^|40^V+E-F;Dx=!I%D!Pj{!7p_#vL9jcnGE{{K@uUxEE z6K87lUmq8#@X4T#yx~`#13n$~Y=Vkzjn|X5-6k<r>1OikIGM7<Yhts0lGr`Vj8}ao z-lOm9xL=M*{AjO9_-0Tg4s@BT-|1D?9C=Ht7rWH8yG38u)}*el->z?{T%(eTLwf4M z1!|fvPbbePmm9swIwd++QZCImH+?@%QirCRv=9E2>Bq;-%?HnlZkeNRdGbA(QIV); zHhimQ<^HU1UGc7(o%E$n_xGsu@R;$%?NYvx&yD|wO=?d6ag#CJE}1=hO`vb1%x!<f zWbIul*>x}IoTgbaFW9W-mrs%0^a`Dqb5Rz==Ii{_$twRsie7l-f?D*^gf2MyiCTOt z+1!5WO?5}-Wpn4td(>Tx-<c&HM`USfzgbrOycEtgW_jTjS^nFw4rV^5g2N|tapGoG z+_zt^7+a<8ex=W>{JKF(c6OUpZ?BZn^*c=2zJRP=TxsrUQBs~-U_xsyNoXR?ROEgo z73UV|wbOr9Yd=iYmEmEv?r>P&H*!L)?-<qh_wQF5s!!_&dfL>+k`Yt&Ot);x954^o z?U3rkL#8HJDK%pqrY^le>IQ2~eQcW4A1yKs=Ogmaz8byxi&V9xC8!_n4XefqpWfOz zs<!3D>+OxFRa4TKZZ18nnj>D*l0P6VBR`tf<U`WhKWN&<JLJ)xUh~+QTG`p&Wgb6S zB<&lT%o7Lv66J~VL{GAh|I#lK>D=Y<L?W+BRE&FG<(>&;<sna5p}qIoTNbjLu%B&j z=wFN|Vh=+?#)J$C85J@tWL!==Fl1!N(2%hqgF`nuUKk$7jt>|hGD2jC$QY49BBMlx z>9pfS2I{mUMTUxu6&WltT4cD$c##1kBSwadj2Rg;GHPVl$heV#BO^zK?zCe^29Jy$ z89p+8BmhVRkPsj-K!Sio0SN;V2P6<q8wn&7P8$m(7)Ugba3JwO0)j*Y2?-JtBq&H! zkgy<eK>~wB1__PR#s&!v5*;KwNPLh0ArV4Cgv1C55)vgOOh}xNKsjxskWe{otdL+i zZM2YZA@M>2hC~br84@!jXh_tMupx0n0_U`mLqg}Yu|tCAw9!Mthr|yFAQC|&gh&jL zAR<vj!idBX2_zCpB$Q4YOC*?18%-pfP8&}oph!fKkRmZff{H{H2`ds;B(O+ik<dDA zY?0tPZFG_FI&FNB03#7bLX5;12{IC8{NIE*&K5i07CWP4ULYrsl~IzN9mo!3#r+Mb C_f!G^ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Apia b/lib/pytz/zoneinfo/Pacific/Apia new file mode 100644 index 0000000000000000000000000000000000000000..fd03ff765eace5b92fbe13e3f151a1bc49c88d03 GIT binary patch literal 1125 zcmd7QPi%{E9LMort*fML2?w!VI3c=qd$j11S$04sS+}7p8@8{`y3U_%tXBVKNC<Jj zNOqiv11Ap$vV%-SBu?fei1^0A4RQ1T=lAYO;>5|*JkRS%lm5x`d8a!2vn$LW$87fw zCr7<IxvxCoE|*Hbo0pkjqIq(upPYzj%jjEa`EmYgYoScrvOjgBESF8|RW~=zmDG}l z+P>(6q(j#=ZD(Z5yED4=%^m4@oYT(fE3)m@Y1w|m*Bux7r0eXsb`Ne4$>nutYeX_l zY3->glU>Ua+FSlnvh&&{`+1J`eOfKK=kK-uS%vJr_f+@z-(>H^ZQXbAwG0$4>)_C| z42duMJ6`F5#&H?0yRZ4hc{x}ysUs#WBVSHxK@)Q5MUNhSP$@^I*6GoktMu5ljEtUJ zsK-yOm9gAc9kbR3W(7-vC85%Pc!iAd-Q%*h+dVUDyP8bU+F=QVt?g|y<;L8dG4qXi zq=5y^$&{(~yjZQfa(X7>#oQTppZxtu{l!x5&)b1DPuwqC%VNlCd>_jp>mdskbt@uE zB5NXxBC8_HBI_axBP$nmOCxI|izBNe%OmR}1t1k5B_K5*MIcomWgvARg&>t6r4)6w zAjKfnAmt$SAO#^6AtfO-Aw?loA!Q+TA%!88A*CU;6?MfS)gk2}^&tf!6(S`fH6leK mRTgzy{$FK=>s_%8)n3dQZ>UPvMOQ{^YhqQ&c+_(@@$fIA@G;;3 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Auckland b/lib/pytz/zoneinfo/Pacific/Auckland new file mode 100644 index 0000000000000000000000000000000000000000..60bcef686badda46f36652694d06d041c70a9d87 GIT binary patch literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj z<t{CbH~x0rWPQTJwaa=qkKbs$+B(`j2bOLrXs<qR9$a`{Itu#Dcf<STP-3guIjU8< zLh8+~pZfIhwKDU_$IbfQP@ehzIgcKF=ZfdpiI22<+mNTHX`dcncf`}Xd7GZd-R1e9 zs6zXkwVspN4bmSdo`I-x8Ms<wp8EQ=RG*MP)o0%x^}V!5{2KpI|Dod=P<uuLyP7np zut#rxwNA&T?ACGBB|1LrIh|0Dr4vSqH8?R+gD-5Bkg1sx(!W|l9T5`Ryhv{O-d`qd znI*UOT$Hd9Kbic-piIdamZ=-t<+fNK4KMvvrv?3}w>up&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-<KEG6FN9>h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-L<DAb!D#1FYJ&7$+5C9<rPVr zG)~gPswMsUuaYt1mPJ?VH1kZdWSuV2#mB;ANoSU3HyV<&Gg5PF&PrZYfZkI)qDv)0 z?#)nLmg+CA>Akvq@<qw_eoOOj49a~!Jg)`cwabc=rn<XdmizbD$;y^Cec;6sDSTnO zK3JY5Vpi%yd6BXzGhd5h0_5SDiMl%Qk`#|!F2&dUwB+(UF;5R`>E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ<OE72Q%HZSv@fZ}hR$?Xo`Us8&v?l*cE&t{aSe+3?%5TBV6n{Z)}Z z(Gx099!}S%S`+l?-K(T#YlzlvN|R^I-_^_EHR_*k@6lua)7vnbhO9Xl`v)AO4dcx& z!^bdMdN>~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-<jQlOc9wQGD&2X z$TX38A`?YsicHnknkzC{WVXn3k@+GMMrMpm8JROOX=K*Ow2^ru6Gvu_OdXlKtu=XM z_WU+X-`1Kxk^m$FND7b~AW1;7fTRJ*1Cj_N6G$p-tz00<u(h&*q{G(A2a*t5D<eos zkenb%L9&9R1<4DN7$h@DYLMI@$w9J%q{r6E50W4xLr98{93e?UvV^1w$rF+&BvVML zkX#|jLb8RV%ht*lk}xD=NXn3$AxT5BhNKP28<IFAb4cot+#$(BvWKM4*2*7}KqP}m z3XvQlNkp=Uq!Gy@l1L<zNGg$BBFRLuiKG+BCz4QGE2BtCk(?q)MY4*d70D}-SR}JZ zYLVO`$wjh@q!-CAl3*mmwpNOf93x3avW%n|$up8@B-2Q$kz6CmMzW2h8_744a3teM z%5ANjBS}ZHj-(yQJCb-L^GNEE+~fZ!`M&%iM90PFy3<@yIZ4jB&e*7&InFp|Y|L!m FzW~Tp3}^rV literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Bougainville b/lib/pytz/zoneinfo/Pacific/Bougainville new file mode 100644 index 0000000000000000000000000000000000000000..6a6c2da28faa75b344004f55ad2a90caf9046091 GIT binary patch literal 286 zcmWHE%1kq2zyK^j5fBCeRv-qk1sZ_F8E3PEOWHXfLgrm>sQ>@}KO++(GcyCj#2Y|4 zhMol=g>@4cI2agaZD8Q>@eN_nHZ)++Hn0TJh9D(i5Q5!OkVPO20&GAGVv7Rp_#d_{ zNCQL{y<IW^M1vdwqCpM;DF!(PMArkw7@3%vSb(m8xTy03$VEVRaf02&3wD<v$Xx-x SI2^~oz-0q;tevi*Ar}CQuRnzV literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Chatham b/lib/pytz/zoneinfo/Pacific/Chatham new file mode 100644 index 0000000000000000000000000000000000000000..abe09cb9138504ee3f29cff16411e0a3b0b957ea GIT binary patch literal 2078 zcmdVaYfRO39LMqhA$8+$Q*#W-P}96{xE*eS(FqL@lp|g6q(t;dlLH!QNkSLVR4V6O zO`G<Jn&r%4i?of}D$uQ34=S@YyDa-=TRv!LuGUS1e(!$|d(@-WIXl1C|NOV};B5cT zyRoUgwb1*=YrMO|hu3!Z;W~YrJ5GPO|E<J~KP~a-Aq!qSq@=zpmi+k%OX)dlsc*b! zX)VJx@wr}`6gg;<yW@6s)=rzUvd*TAwpw~w$kNa6P{zz!WxT&unSpF&9$unrzD?G& zC+F+h;S0)&Cu#Z%r!=ExL^HP?({%-jHmm(3o1ON9U7r`woP@<TH|0CcJvZCtjl8S* zCns3;hldsFpQW5vb}4uFc;)R`ul#lARnQStc<Bc!jI~=)cE5^Cmsv@0k4n>Xt@NiO zw&0gEEBkb}m49~079Q`g8-`L<v9G~y?E6uXO<}vK=d>!DmupeW>snk<pqnH6RW)t0 zs<XOPefekAj5cY>uf0}#wnBAh;<ogiEG-+Xv-&<y(E}k{zU7=6x>D?x)=|4v*}AR9 z?DooJHO@I{E2dviY{E+x8#|>tzJJ12esfH#-fOd_p=WjHtG#M|dBE;^I<A(d_S)SW z>ZG<#yQd+f)wMBOlb@n{=at#o)Qf7Jv_h@FC0YE^0=1nPvGz}<YTe++wtnEU?mO_B zbv*O6?(f=X8#WGUqXzWAs;}(9%DviD{<d|_=+r|~pSR6kOq+jy+`6nx-CwM+hlexu z$eY#n=#f%;?BHr`*_B~E+pDy-?GM{JHZ~R*mvGg8`cCk?{wwZb^o~|}<2>&~b0Fw> z$Lqa`f1mZyMlTdD441gGsF<_Rot_s6@Egx-{1?x>`@3U{2CH@=&pyBB`S*1jKz4v^ z0oenx31k<@HjsTF8$ou0Yz5hiuiFf=8(+5_WIw)cL&%PN-IkC&A)7*Wg=`Di7qT&A zXUNu&y&;=Jc86>a*`Kf5AhJVbi^v|4O(MHQwu$T$*(kD8WUI(tk<B8zMYfCV*Vk<r z*)g(ZWY5T^kzFI(M)r+t9N9Utb!6|z=8@ea+eh~A>l%P`0BHfz1EdK^7mzj}eLxz4 zbOLDw(hH;+NH>snApJlZf^@{!wFK!2(iEgCNL!G;AdNvfgR}<e4bmK>J4kzw{vZuP zI)t>y*YyZ#64E84O-P@RMj@R-T7~oqX%^Bgq+LkAkcJ^0Lt2LP%-1yy=^D~Dq;E*$ zkj^2kLwbia59yw-Yry}#eZdO9hLPNG;lkn)_r(P=k`pb@E6FR!=T=U%I4|t3i`;ea EFB%CJV*mgE literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Chuuk b/lib/pytz/zoneinfo/Pacific/Chuuk new file mode 100644 index 0000000000000000000000000000000000000000..e79bca2dafa7e914a9baa568edec1b428a2ac63d GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f<scix2!*42=4bXf$T|)yd06J?KGXMYp literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Easter b/lib/pytz/zoneinfo/Pacific/Easter new file mode 100644 index 0000000000000000000000000000000000000000..cae3744096402e8a452336544edf96ca9ae5ad8d GIT binary patch literal 2233 zcmdtiT};(=9LMqh;W^+W2$|3mS{MbwkLQQ*_=_YVJxF=dBs5cpfF(pgrWD3jVzpF8 zYi_!%R{7BM3tcFi%-?#l2P@BoBU`MSbgMNPJy}}*`@R3wRqLXgF8ZJI`@jA>7w6*a zeBPmkmZn1IZ&$4Sgv0f$Jv^swwzrYvy8pLurM@(9LEIA`8>iz7@paXk2wf|YcNdtb zjBJSxEYdNKUt$xE>ew$QB<@m*zU)|7;>Ul~3470}#L+SB??0(7-#wzIG!Lt!r%svV znn5+S>99%3>Q<?@?=)8=56HAxo6NMyPMIFF+)NKIk+idOP5MxoT=i+AzIsQxTyrR( zuWkQTuG^NOGkPP{jJ60pv;3mEzV0i1L)y5?EOSieFUQoZ?|wEno_<MXoqxyN^wy}{ zJocK&e)&boIoxk%_dOxGFSMGxRjWm9-lFrXs-<9Mi!Piqri%0eU7RpamH3b7(wI|H z=1kFLAH}Kiud_|X{%_PRANWn>(<juNy%Q$TdQi>n4;#JsL%Fs2O;c6)hTK;3yqTBs zoK)uz>+0{@Wq$IYo<9+xY9_mN?a?-MNBADS;D{p&hbnaNy;xOO-)9!>Iw<v3r_G%` z+vTq8pY-C!4hbcErk9qURZ9<jYnEO4zFM~J6Vq^hzq+?gOyj<_vb?j$tk_yB_k~uN zl`YwFe~~t;YW=c0b*5R9H6d$$h%!x66IIjr483;poN6A8)GgtYs&&^Hy>4h&J<xMp zKe%I1t#90?+ct`{S3aX3Y8a4?%-7As6`j%<z14K3FOjY@>rD5BGI`|PpxN+wx;*-7 zp4s?zsoL~pvgvsxO+B_gS3ll&QT5g(>0Z}$eNhpS|M-fI`7d8FuDf%C<9PQd*FCVu z7w5XWw>yb{-4E<>>?b4QOIjEVIo0;eRwee7+EZ-*_dXx*KM4Jc&Dfv8ZP`*~zuSJh z-43!J^ftr;JL0li0``P#3fUF1Eo5KF#*m$P+N~jbLpF!(4%r^EKV*Z*4v{S)dqg&g z>=M}~vQK2A$WA@&R*}7W+RY-nMYfCV7uhhfV`R(7o{>!>yGFK+>>JrQvU5+nb!6|z z=8@ea+eh|~Gyv%U(gLIhNE47QAZ<YUfHVT>1kwtm7f3UZZg|>uApJlZf^-CF3DOg! zDM(k4wjg~$8iRBOX${gFPum=%JD#>ZNPmz9Ass?mg!Bk$64E84O-P@RMj@R-T7~oq zX_lw$7Sb+H+b^VHNXL+tAw5HyhI9>S8`3wVaY*No)_L0AA<gr&-9y@k^bctu(m|w! zNDq-FB3(q<i1ZO@B+^Nwl}Im<W_sFgBJD)_i8K`HDAH1-r$|$gt|Dzk`s!)Z@qcY> ee5LJgpv2yb13AI+-2B{<yn=$9V9}pX@xKE%a7T*( literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Efate b/lib/pytz/zoneinfo/Pacific/Efate new file mode 100644 index 0000000000000000000000000000000000000000..d650a056d9e7c734f886bb4a82d2256898140458 GIT binary patch literal 478 zcmWHE%1kq2zyQoZ5fBCeF(3x9c_w{5v_<mL_X|>oZXJ-mm3Bd9(Vhdcv%dw%HO)I9 zUwtD$A$zxjV)U*6CGWWo%GPrNRJ7|IRHf?z)VLEJ)P5%fsK0i0(0EdFL9;Q?LF=W* z1?@y*hx-5j|1&XSflLexbI*XRUvYqug@Iw#1_llv-w+0ELn9zI1W5ov2nntSTJ#^} z1CTzDFF-WNCm<T+8xRfh5r_u)3Pgi^2BJZ}1JNKKf@qL0K{UvxAR6Rb5DoG%hz9u@ tM1y<|rh&c((V!3j(V$QO(V&n3(V)-((V!3k(UgS>mklt4>~swcxd1L<X6XO` literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Enderbury b/lib/pytz/zoneinfo/Pacific/Enderbury new file mode 100644 index 0000000000000000000000000000000000000000..80873503faeb389853e1bc3f04b71f5156b48a14 GIT binary patch literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$Po%-IiSyKxuO35|No3k%*_A)$IoG4`2RoLfq~`! z|I`2m4hDv87Z`Yad_x#?4UK@<kU`tf7-S9@gpgn#(2D<c=AkJdjUanLG{|lcO|Jc1 NHbB?d=^7ey0RWqlFUJ4? literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Fakaofo b/lib/pytz/zoneinfo/Pacific/Fakaofo new file mode 100644 index 0000000000000000000000000000000000000000..4fa169f3cc6cfbe9414982b9eef32eb7f6718251 GIT binary patch literal 212 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$npC-b3^_A|Nj}8nEwBduV7&K|35W=frWu#+XV&= vAKwrLT|+|#Z9`*_Mj!|w!FZsl|Le>{J3#tC)_`adtm3i(T5G3kXv_rwq0K43 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Fiji b/lib/pytz/zoneinfo/Pacific/Fiji new file mode 100644 index 0000000000000000000000000000000000000000..61a669535f14d380929c8fed6708b5ca9f8d5bae GIT binary patch literal 1090 zcmciAO-R#m9LMp$D<Uk=>L7&Dq3EJ5w~1Mn)TPTF68j@Md-#f|%&Ex|)Acau&>^8q z#6svYqKC^MN`)X8BZwkMND!jnOCUj=k|2<3ec!)prwICuAFr|Thq2GwPo{<<-XCX! zeZu7&wafdhb2}cDTHEWib!A=J_OwXb;(Lj1Ytm@3Mq=g963-mdMB%lZ*!f&LuNI|q z{hoHEUuoC7?2_+)694IlcDKBg?zx!uG^}b*HmJQnUuZH}t9>6H>ZyZkdivR{p4nX^ zXYY)u1iniDwd>OV@vRIDjmkiIUIvdRWU!Ez;l0f=Jo{Kvo3=|TeM?7HzezfJK}X-z z>R4m1ju+ST+?IBoxUsAgUm7$szMz@qLo%7Tt&>mcW$M7ZOx^q?({*{7KCd$KeMYh; z%W`q0PcB97%H_o)a;3hcx%@84{U~UzQmF*0{^=F)y!$2lAH9X8s*RrapyX|i#eJ_a z6tNrT3p@T_>|sBt!X`Y&E>7D9*~e)cAv-y3D`YQZGh{bpJ7hnnZHVmXv@MZ6owg~m ztJAhc_I29E$j->t$ll22$nMDY$o@zJNC!@90qMbMO(0!3tqr6Pr!|6f;<Q$fUXW&x zZjg46evpQcj-1vK(v#DgLb`HVTS#9{YYgekX{{l>A<ZG(A?+dkAq^rOI;}<Lf9kRN eu(jE-FBCQ-9Zm62C>RPho564}ygw9<RR0E(xg)Ls literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Funafuti b/lib/pytz/zoneinfo/Pacific/Funafuti new file mode 100644 index 0000000000000000000000000000000000000000..e6a154474bd8d6619556bbab19b0921f81a13d2b GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H%dykkpC;3@kprAq?7v5HmtZFb!zX X|2i|CKOl4Po62PaG~Z6w(1;5FNf#M~ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Galapagos b/lib/pytz/zoneinfo/Pacific/Galapagos new file mode 100644 index 0000000000000000000000000000000000000000..859b76d94d52a9979e757fa3d5869436458aec8a GIT binary patch literal 254 zcmWHE%1kq2zyK^j5fBCeRv-qkdA2R_X^@jR5}+;4^+3DuOF;eq|Nj}8nV6aX|6c=? z|NnpI1_l-o$p|D@FJR#C@eN_nH82HYGoU;Kgpgn%(ER@(OF=q87K3Pz<zO1<01!=< NBe-mUPO>xO0stI?I=lb? literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Gambier b/lib/pytz/zoneinfo/Pacific/Gambier new file mode 100644 index 0000000000000000000000000000000000000000..4e9e36c5a7edb3ebbe08ccc56308ac262df9f58f GIT binary patch literal 172 zcmWHE%1kq2zyM4@5fBCe7@K2CfCo$c|Ns9P8UO!ptYKgPk_8MbKE5Fgx(1eDr6DAk W1~lkD$V8Av{HAi*0L{0v<N^SLeIXVA literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Guadalcanal b/lib/pytz/zoneinfo/Pacific/Guadalcanal new file mode 100644 index 0000000000000000000000000000000000000000..908ccc144ef9e69da0c108dde21f8dade3203ff2 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@K2?zj06f|Ns9P85tO+egR3%+Q7i#;~T=DZD<JMGK7#| Y8qlEsAQM6Q@SDnI12o@G*U*p)07Whw2LJ#7 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Guam b/lib/pytz/zoneinfo/Pacific/Guam new file mode 100644 index 0000000000000000000000000000000000000000..ffdf8c24b86220d1bc67908fa080a37f250544db GIT binary patch literal 216 zcmWHE%1kq2zyQoZ5fBCeCLji}c^iO)m2+GIBh&x?W+p%mL(c*R7BI=-;~T=@9vs5p zoB<>tAOyS7Kn);GU;r`}#OD1E1R@WQo&nKyX1YEgS%_84FuU5ffCTZ{$iTn_vR~KG GfC~UJHYHvF literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Honolulu b/lib/pytz/zoneinfo/Pacific/Honolulu new file mode 100644 index 0000000000000000000000000000000000000000..c7cd060159bd22fc5e6f10ac5a2089afb2c19c6a GIT binary patch literal 329 zcmWHE%1kq2zyNGO5fBCeb|40^MH+y_ZdPZH-HL?~r#o#=TvGm0a4FH#;%aZP2O|?B zGYcc@|Nl8m3=BXrf`R4#|Edf|4lv0BCI$ZgFHT@!@$n5|@CXKC7a$G?;(!pK!3+$H uP%?xBC;bP4k_QF*Ks3l{U>fK=5Dju7hz2<mOaq+?qN(g$E}&lw4Y&a7X=UL6 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Johnston b/lib/pytz/zoneinfo/Pacific/Johnston new file mode 100644 index 0000000000000000000000000000000000000000..c7cd060159bd22fc5e6f10ac5a2089afb2c19c6a GIT binary patch literal 329 zcmWHE%1kq2zyNGO5fBCeb|40^MH+y_ZdPZH-HL?~r#o#=TvGm0a4FH#;%aZP2O|?B zGYcc@|Nl8m3=BXrf`R4#|Edf|4lv0BCI$ZgFHT@!@$n5|@CXKC7a$G?;(!pK!3+$H uP%?xBC;bP4k_QF*Ks3l{U>fK=5Dju7hz2<mOaq+?qN(g$E}&lw4Y&a7X=UL6 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Kiritimati b/lib/pytz/zoneinfo/Pacific/Kiritimati new file mode 100644 index 0000000000000000000000000000000000000000..cf5b3bd348fcc7d6a39dd6b64b1658046444f71f GIT binary patch literal 254 zcmWHE%1kq2zyK^j5fBCe7+a_T$Po(t#Gucry`cX8|No3k%*_A)=KzKO|IY%d`~SZ< zfq{#G;m899J|Eu@23<n~69W**plxUZ)C&P2B-jYF<bR#HM*~PF$Yu}#*$$$~cLA3T L&^>m#h9+D9eoZjO literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Kosrae b/lib/pytz/zoneinfo/Pacific/Kosrae new file mode 100644 index 0000000000000000000000000000000000000000..b6bd4b089416a12b02a37eba8a1082dfa28b7797 GIT binary patch literal 242 zcmWHE%1kq2zyK^j5fBCe7@Ma7$obzU9bnd-?oj{#|9?g%Mn(pP8D~I>W^DkeTXBGa z1H$(44PnqWGz4OV=^-T81vLJDotcaYhz8jSvIAr<h^_|;k!m-W4bU}qx`u{a0Gwtk AUjP6A literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Kwajalein b/lib/pytz/zoneinfo/Pacific/Kwajalein new file mode 100644 index 0000000000000000000000000000000000000000..54bd71ff2dc7d7fc6f3ddb6e3e5ceed4c92b72f5 GIT binary patch literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$obzU9iUUP=Rp1c|Nj}8n3)+E<~#rjGtAn+!1Dip zxB~+R1H*~~3_L!*Aq?7vh77ufMnD>2LkJ1>0j>C7XC|QlqCxh8>;c&gqU(V|<k-(; L19XiY&{bRj+D$OG literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Majuro b/lib/pytz/zoneinfo/Pacific/Majuro new file mode 100644 index 0000000000000000000000000000000000000000..53f32886d08156820aaf860bcaedd3009c35f601 GIT binary patch literal 212 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$obzU9Z>)O|9?g%CI*HDAQ6UH8yHv^7*-r$;PCMc rVbC@-1Y($xAtV?NH1&U-nMebO23Z3#yB^3S(kd<+ptW|ohDKZfj`Jm{ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Marquesas b/lib/pytz/zoneinfo/Pacific/Marquesas new file mode 100644 index 0000000000000000000000000000000000000000..5fad0e1b201fb0cf49f3d2c27b117e9a029501a4 GIT binary patch literal 181 zcmWHE%1kq2zyM4@5fBCe7@KQKfR9K0|Ns9P8UO#UwP0ZQ|Gz4OfyKu+ghAK9(%1m3 cID`cAfJXfXnF-QJ$XqTPupM@mR>lTg0Jwo7!T<mO literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Midway b/lib/pytz/zoneinfo/Pacific/Midway new file mode 100644 index 0000000000000000000000000000000000000000..72707b5e15cccac9888e5ba22bb1b4e92170df5b GIT binary patch literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Nauru b/lib/pytz/zoneinfo/Pacific/Nauru new file mode 100644 index 0000000000000000000000000000000000000000..7e7d920e11396d3b210f28c1d69389bc68d33548 GIT binary patch literal 268 zcmWHE%1kq2zyK^j5fBCeRv-qkg%&^8W;wlOzsCiqfC9m8mJ{{=|Nm!XVq|7!V3<<_ zQn_#kNMYRs1}+AM6$cpje0)O~v<(f74H&cyEP*V9MIj_u3$*1w$Z8M`vK~Z(oB*;B X<O~p94-_NQDO@%{huP^G8gT&t{~b3+ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Niue b/lib/pytz/zoneinfo/Pacific/Niue new file mode 100644 index 0000000000000000000000000000000000000000..1d58fe36f47433fcde7743c626a8073f65dd331f GIT binary patch literal 257 zcmWHE%1kq2zyK^j5fBCe7+a(P$hqTenjlbe%A)@N|No3k%*_A)Cv0J00FnzBSpNTy z^I+im|35W=LBPj1ghAKP(8vHp8iPrYSzr)Cf}KEX{@0m@_<(4T-5>)%_JinppcuJs N;IaX_%g)e{3jo^+IynFU literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Norfolk b/lib/pytz/zoneinfo/Pacific/Norfolk new file mode 100644 index 0000000000000000000000000000000000000000..f630a65d5778d651b9d6842159d593c3cc18a996 GIT binary patch literal 314 zcmWHE%1kq2zyPd35fBCeHXsJEr5b?59mgLHocyOUIJe%62;+a2QUCw{e?}%|CKeV3 zhPf3$1q^c=7=Y}BI~cea7}lR)6kuSOwShs*$2WvQ+tAR^2t*ni07)Y-X$aB+1tBE3 t1!(R6Iy3PO5Djt@$Ow?Tz%<ZpAR6R85M2*cLoGLQ*#O;dr)y})1pov^I^+NV literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Noumea b/lib/pytz/zoneinfo/Pacific/Noumea new file mode 100644 index 0000000000000000000000000000000000000000..99f6bca20d23a1ea93182d86b066cabb764abc0b GIT binary patch literal 314 zcmWHE%1kq2zyPd35fBCe4j=}xc_w{5Qo{FaR{($5?gIkH<^%|uzjrWN;Qhf^!lj`8 z|Ns9?j6lfD!ot8XhX<r_#Q{bjd)5Y!2?(~2ZwQ07p%D-pf)p__f=GrC65Ii_>OaUW pAblYBfM}4LKs3l*AR6R05Dju4hz7Y4L{sWcE*qe`?Q{(dxd2h3I*kAT literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Pago_Pago b/lib/pytz/zoneinfo/Pacific/Pago_Pago new file mode 100644 index 0000000000000000000000000000000000000000..72707b5e15cccac9888e5ba22bb1b4e92170df5b GIT binary patch literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Palau b/lib/pytz/zoneinfo/Pacific/Palau new file mode 100644 index 0000000000000000000000000000000000000000..968f1956c8eefeb78d96651051befba0b1a3ab82 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Eq%NUClE1B;Ju2!pnPC5X!qLV{^P XgZ|f<nY{t&!*42=4bXf$T}v(i_(&L^ literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Pitcairn b/lib/pytz/zoneinfo/Pacific/Pitcairn new file mode 100644 index 0000000000000000000000000000000000000000..9092e481678c18c41823cd24d370ad9623398b5c GIT binary patch literal 214 zcmWHE%1kq2zyQoZ5fBCe7@MyF$T4+s;;8@s|34!W)Bpc%JPZu~|94a{u>Aku-@w4- s;~T=DYhYn)03tzJfgpqg3xMYSuQS*C0@4Sv2t<=)8J7*vVmk{i0FU=74*&oF literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Pohnpei b/lib/pytz/zoneinfo/Pacific/Pohnpei new file mode 100644 index 0000000000000000000000000000000000000000..d3393a20d85209df94887e0de0c88e08442e4009 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+UMkkqUV3@kprAq?7vh9E9O2nnVE Y4f<barmzO255K8gHbC?3bPWx;08pJ7fdBvi literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Ponape b/lib/pytz/zoneinfo/Pacific/Ponape new file mode 100644 index 0000000000000000000000000000000000000000..d3393a20d85209df94887e0de0c88e08442e4009 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+UMkkqUV3@kprAq?7vh9E9O2nnVE Y4f<barmzO255K8gHbC?3bPWx;08pJ7fdBvi literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Port_Moresby b/lib/pytz/zoneinfo/Pacific/Port_Moresby new file mode 100644 index 0000000000000000000000000000000000000000..f6fd51cb93f23872e8809dd4b2e99ca444fac10e GIT binary patch literal 196 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14HKzkW|kC1{NRR5C&~S0}z)X1iNX> zKq(Lg0T`PXXwd(#ZBZN`y6Eka2_VUOAe)g1Xd%S-&JPSMU`sebmIU}B*~GxWWdpR; JPS?<Y3jkR5BfJ0r literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Rarotonga b/lib/pytz/zoneinfo/Pacific/Rarotonga new file mode 100644 index 0000000000000000000000000000000000000000..9708b8707230d12731657b936499626858049654 GIT binary patch literal 593 zcmcK0u}Z^09LMo%EC?B-YAAo&)SlH)5ekV8S`-~bgrZvp>!b)$i<^VAgWAm(5N|v; zhpxUrr=p9GAWn7j3H<y29XN@DgBR{TM<9g%H$6B#SyDe%R^DJ^g|cxEuI0$}iwl#R zk2KZk>FM$v1<hj}aEJ2sZJi(88mb&w8eW)!w`B_tO;db}Y<RO~N`rAb(<+(r{kzpW zY-W4UwvvQ2*B<Hl+$TkiCmnsfQgyMft1kn(V&=@4_uJz!w^x;7KHFDW=JP}4Gk05= ziQT<a)slEQ#;Iy?U2fki{ll#Cd%j$04Dl^yhL{^-c8K{Q8aSl`L<^_%fN0{BE)Z>; u(g&guL??(=5WOIpL3D#?2hk6rAw)-rmQLvj(bOqj;eWLC*QNJH68m5DID0_= literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Saipan b/lib/pytz/zoneinfo/Pacific/Saipan new file mode 100644 index 0000000000000000000000000000000000000000..ffdf8c24b86220d1bc67908fa080a37f250544db GIT binary patch literal 216 zcmWHE%1kq2zyQoZ5fBCeCLji}c^iO)m2+GIBh&x?W+p%mL(c*R7BI=-;~T=@9vs5p zoB<>tAOyS7Kn);GU;r`}#OD1E1R@WQo&nKyX1YEgS%_84FuU5ffCTZ{$iTn_vR~KG GfC~UJHYHvF literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Samoa b/lib/pytz/zoneinfo/Pacific/Samoa new file mode 100644 index 0000000000000000000000000000000000000000..72707b5e15cccac9888e5ba22bb1b4e92170df5b GIT binary patch literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Tahiti b/lib/pytz/zoneinfo/Pacific/Tahiti new file mode 100644 index 0000000000000000000000000000000000000000..37e4e883368265e85b7db406348aa4b25350f100 GIT binary patch literal 173 zcmWHE%1kq2zyM4@5fBCe7@K2CK<JM8|Ns9pGXDQxe1d@iNG33_`1pn}=o%V;m4=XD X8qlEsAQM3v@tewJ12o^x(0~g7G|wXF literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Tarawa b/lib/pytz/zoneinfo/Pacific/Tarawa new file mode 100644 index 0000000000000000000000000000000000000000..e23c0cd2cb4a1542809e253e0980646caae226d5 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H&Q{kkpC;3@kprAq?7v5HmtZFb!zX X|2i|FGaz&Do62PaG~Z6w(1;5FS@#*Q literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Tongatapu b/lib/pytz/zoneinfo/Pacific/Tongatapu new file mode 100644 index 0000000000000000000000000000000000000000..35c9e2c64642a3f2e17341e2d7f704b2bf5f156b GIT binary patch literal 384 zcmWHE%1kq2zyNGO5fBCeZXgD+g&Kgw$zrDo=D*!9SX{2XV8y}_U{$ok!TRy50Gps$ z4iU1~FGN%+T&Vy5|34!WGYcyd2r@A+tn~ouWLR6kz{0?=?E(WA1H+LAjC{y!AKwrL zZ9^j?10ZP(#3mpOjEo=>2tr73B+%~vb!O~4Ks3m)ApIZ*gJ_VWK{UwWU>fLn5DoGG ghz5BAM1wp6rh%RT(e*&HsOlju8=yDsbPbKU0LCg#(f|Me literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Truk b/lib/pytz/zoneinfo/Pacific/Truk new file mode 100644 index 0000000000000000000000000000000000000000..e79bca2dafa7e914a9baa568edec1b428a2ac63d GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f<scix2!*42=4bXf$T|)yd06J?KGXMYp literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Wake b/lib/pytz/zoneinfo/Pacific/Wake new file mode 100644 index 0000000000000000000000000000000000000000..837ce1f5c7f7ad25955108003ce9cd44ec9f2f1e GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H&8>kkpC;3@kprAq?7v5HmtZFb!zX X|2i{?Gaz&Do62PaG~Z6w(1;5FSBDv? literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Wallis b/lib/pytz/zoneinfo/Pacific/Wallis new file mode 100644 index 0000000000000000000000000000000000000000..8be9ac4d3bbe8f54267e85eebc8b11e1d612c9a4 GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+mKkkpC;3@kprAq?7v5HmtZFb!zX X|2i{{6(Do)o62PaG~Z6w(1;5FY=ary literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Pacific/Yap b/lib/pytz/zoneinfo/Pacific/Yap new file mode 100644 index 0000000000000000000000000000000000000000..e79bca2dafa7e914a9baa568edec1b428a2ac63d GIT binary patch literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f<scix2!*42=4bXf$T|)yd06J?KGXMYp literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Poland b/lib/pytz/zoneinfo/Poland new file mode 100644 index 0000000000000000000000000000000000000000..d6bb1561db672f94bfd5bccc95fe1a8d18a158d6 GIT binary patch literal 2696 zcmeIzdrXye9LMn=97!a#FWyiDFA=G9R1if)$c&E81oLvd;RQ5AEi$}BXcEtoxn|CN zbBOa+P{hzFxplN9I%l`hGQtZQHf!Zdab%d9wZ`oCeq3wK*47{W*YDYRUe7r@|32@J zKXX~`Fmu<r*Z#tXQ*A#yM>_Vly*jR8XUB-_osH*PcQw`M?#hGu+Iy<6mu%DW9fwTC z;-jXjXkB()!B=wP(j@t8PlVRLktUyS87>XZp6rH_!{+4HE%~Q5)@GkxbUXjdq!?{n zuwTv&3dlKcq<qn#Oqzm^Yg2QfT=t(bm#+n!=5O9|uAD4$TDp~)mc#FuANE$7t2?%u zAJ1f(*0s-@Yk?H=Q|26Vy|j<HkvzuSEJ}8Mj*K>);@a{%RnBlaztYj%S2EI()dQXI zoL){Bf0)xXBgu42Y;n5BTyT1Ht#=|k$DD}k2b`W4E1X`Zw>Xg>tao}JdD}$oD>u=* zUNwC-y=3~XTV?v?<(U5SW|;ox&$iy5?w6PppFH4AlGvyL@?giFG9V;P2izR41HX&a zL5)2$?xXhlP~aE!RyOP4((^i`<Wn8G`iREo?AL_(O)_j{KoV1HW%#r*84<l(l7<yZ zQd_Z%>Rqa%E-aMMzZGcm(KH$J<!nu<%F@)@WPNzUI32q)N*~FM(QzfC<<apWnwHaB z9*e!CzO*(OAM%M#i1}J3T>V}qdXCG)`Z{_1;+rz5X0N25IHnn!H_7CE75c>T<uYZ{ zdYw9JqfX0PtkXy4sXu*!&WM<-Grfa!=B;?0-F>{wKG#L(+#D#Ghi>TH#xR*z9xn3( zEwZ5ax@48sOLkVHEG)XBi^jeyPtHG~IeoXw;?x?=4Lzt!qE(k%-lj|2R_e04HTu*A zzdl_(SMxqzA<w*=s>`dU%d<=SYW{{1vSMnAtjvv&RSA7$weMGXF5F1L(C%8$`mGdp zzNLi?AIh4mO}h3#mAp`2tLwJEuSGSx^~E)nTD-YfgFL~Wb|LLT?`iJ|4zUlxcfRv@ z*To<I=JIq1`|mGfx*o8v68Cn-MD+^_HKwzePJexliw_Ft7t`a<`yc;I&+waB_LJtD z&dqOpJoxMbC&(UqbD!^g_y3Ex{I)$a4>e3d-ge}TceQUl^5!FNKT-gs0!Rsv8X!eL zs(_RMsRL37q!LIeTx~6oVj$H(%7N4aDF{*#q$Eg9kfONSsvu=?wRJ%XgH#47jjOE< zQXHf@NO_R@AO%7ygp>%W5mF?iN*v1MYU_j)ibJK4QX#cMiiK2*L%EQ8Aq7J!=4wlZ z)C?&aQZ=M(9O}lQa2zVfp>!N-$Dw$xwt7hUkoqA7L@J1s5UHW7Eh17yq>M-%kwPMs zL`sR&5-BE9O{APiJ&}SU6-7#l)YR1$6{)JLEh|!2q_9Y3k<ucyMT(157b!1NU!=fD zg^>~?HAaezRN2**8L6|YEi_VTq|`{Qkzym&M#_!U8!0$aairu(&5@!bRd=;zN9yis z3y)MDDLqnqr1(hnk@6$;M-~8C0b~h~H9!^tSp{Snkacji3xTWzvJ}W#Ad7*l2C^K; zdLRpetO&9s$eJLFf~*R%EXcaJ+J!+@#?>wjvNp)#AghBc53)YU0^$EF^iL}kW|wMk W0-NQ{NE|X^NW3>AAs&Y&hW!qIlp4DL literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Portugal b/lib/pytz/zoneinfo/Portugal new file mode 100644 index 0000000000000000000000000000000000000000..355817b52b1b05680bbb57e4dc8de358eff27a39 GIT binary patch literal 3469 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 Y{v0ZPotiXi*2uqcfM2Hof8Le;4LU9nuK)l5 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/ROC b/lib/pytz/zoneinfo/ROC new file mode 100644 index 0000000000000000000000000000000000000000..f9cbe672ab1aa9b6c47f7fed0a0ccd73845a10ec GIT binary patch literal 781 zcmci9Jt)L+9LMqRc|6B}$)YeQgUDnM<rwHp{?sX(l<nuCB+_p&U@>_Z6^k;GMK_?7 z+~DyJ=lS70|6*XcI=?qI3ya_N`@gP#x7_FLv~;wW$&Zt4-*7oa_VPVb+s8^%o!)Z% zdV92A?^Ms5-P!`#^99U)ML<F+?JAu6qQf^+<{(}uhwGc_=+v(xb7GFWy5(e{O`Q%5 z$=UdXId5Ik7rh(mvhqQ$O7qlpey7}I&#Gv`jE=sB%<V-;?shZO{aU(ySgbOSZ!vkA zTs6<(5^eh4RcfqR+>awFEi$Y<!DW+mIIXjth;k*k5`Xm(>5SU{BT7q>$l#c`dAc&b z-uN0E@isbAZ?Ct;;fLSH`NLpwdwPN<2N@0-4;c^{5g8I06B!g46&dzZJ1#OXGBPqW zGBz?eGCDFmGCmRjiGYMaVjw|~C`g!3Z5$*J5(x=~#6p50(U5RRJf<Wdhlof>BqkCR N|0Sx&wk|IBd;k)YUNHaw literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/ROK b/lib/pytz/zoneinfo/ROK new file mode 100644 index 0000000000000000000000000000000000000000..fa1cbd3952c362552f524061301b11f03770f335 GIT binary patch literal 517 zcmWHE%1kq2zyNGO5fBCeQ6L7f1-h?)sF?Ij?8hZ%^$(ByH9tJb-u~mMutdhQB<70e zf<7-^=oeJHc>X2h6@O60tK-))UcWb~c(Z&*#@q8^74O<-WqdF#tWa2-FhMadeS%W6 z(*$Kd&k2l7%#18ZkeL+-85qhrKsJ|mFt9K%)J<Rj@_;0e-8zGjhk>DI0V9u(ZwP~T za0r7J5PQ3XfRw{Q2nnA04+J2OfoPEDKs3mMAR6RJ5DoGuhz5BUM1wpGqCuVp(IAh5 zX`tspG$;VTG%yfAG$<fIG$=4YG$=qoG$>F&G$>#|jt2z}hz11^hz11`h^ARUaREb6 H*OChW=SsNu literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Singapore b/lib/pytz/zoneinfo/Singapore new file mode 100644 index 0000000000000000000000000000000000000000..ebc4b0d9d2ade7381506bc35d054f361f8a942d9 GIT binary patch literal 415 zcmWHE%1kq2zyKUT5fBCeP9O%c6&ip<TXXB;UFm)k4sbn5IJNm{!s&_e9G5(DKZs5I z%2EIS|9?g%W)@a9R(5s<hQwH)dJxINz>rh`G9q1pkq1OF0Ljb>1|bH9x(N&t3=9Pg z3^G2xAq>GltZiTp!bS!l(ilWq0<i_iC=?Jvg2RFC`d?=jyck4-JOH9Wo&eDxkAP^9 hXFxQ_Lm(RDDUgLAkAdiVpcvge$7KWbvYoC47XV%lV9Nji literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Turkey b/lib/pytz/zoneinfo/Turkey new file mode 100644 index 0000000000000000000000000000000000000000..833d4eba3985c0221daf6789c9ac30266bccacd8 GIT binary patch literal 2157 zcmd_qe`u6-0LSs??shwGZuRtI&13UN&UvR(o7-A9o0~PyF}<xPJtx?Nhn%J;<69O~ z20PjiOu<Jo94ut&TC&6-CD2N56fv8vC!OWmMo)x+2>LBrQNyn9`-!0NpX%Sf56``B z&&K{Z?(<Hp-@3U}yX{O>Pq;bl>gIhYsa~u-6P;bzd1lw;v-Xiq8=a#&Up9Iglja*W zr|jOMu=8fP&F+gt%)W1K*>C-_+d2N>Vq>7~hB=V#x6iMfFh3be8iRu;%nMihjnALH z?p)j!G%g*fG>2=w#^rd9^ToWK_Ls#soh!i&_K5eOIdZMW{_5s2bM#`fF*a<Q<0tpo z-~V{p{GsKHJ+URz{Ap&?*1FF)o^2;>Z}N4=x9T-JW9w<tU;d7f+4`yoR31wPCZE%S zq2BJ`=n^MO2JCEo$jly@W90Nyns@Z>H}34nH}zdRjJu!eOx@E^q1{{eVk)<AJb7QZ zIhE%-mz)-;PK9<KFhXMoQUxDhvZr_D8Z-9pkTYLuk+Zh1mG?KVkh9|t$-=6LoU<S# ziwd&j+^Lf?><^3Z#557P8Wi(}e--mTx-N=Ojfn+4pNfTh+U25-x5Wd`c8ijh{bKQk z_hsob2W8pPL$ZAJ3-ZC~Nm)_)gsiyrq>Sc2FQVhiW##00vE+lf^5M}cQPo>3mcBPb zRPQPhHC>umwmBdk=_rto#;%Fljlap|MS~(%>&RGVsk6d=-l{A7TCcnDonG(j*XxG{ z^p)Qp)mNS9)8iM;Sq=Nft;XYrt;bqhbz^V4_4vA1tkv5$S!<$a^+deRTASBsHB}$7 zntm>^)_u0fXiKNl9-sHWTp9j9FRw2%@J}z_l;CZb->+%;5%rDK^0#Oinl``0Gey%1 zW@$N^7G37Kiziy{-=F{WZ}@GzA)(c)I~H5ROF}CyDOYzH|5Y82I)A)#f6x;DVk_z+ zN;kbba0S^6vKv>m9b`YqhL9a0TSE4PYzo<xtJ)T_FJxoL&Ro^jki8+BLw1L357{5G zL1c%>7Lh$7n?!brY!lh1tJ)~CQ)H{iUXjfryG6E(>=)e)^FgZ}16xM+jBFa&HL`7F z-^j*Y)y_F=-Bs-!**vm)SG9d)|40Ln4j?T+dVn;6Ll=-XAbmg@fph|Cg{$fX(hQ^< zNIQ^zAPqq}g0uwb3DOj#D@a?AzPPH!Ae}*4gY*Vz4$>W@JxG6$1|c0nT7>inX%f;U zq)kYlTvelxPPwX9A-zJHg>(yP7t$}JVMxc2mLWYunuc@@X&cfvSJgPAb4cr4Rqv4I zxvK6V?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^P()k~zA*rvYg|Hp1-RjH;{FD%RY E9a<Tx5dZ)H literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/UCT b/lib/pytz/zoneinfo/UCT new file mode 100644 index 0000000000000000000000000000000000000000..a88c4b665b3ec94711c735fc7593f460668958cd GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U33UzuGD67I#|6}Gzy$z!Xa;ov literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Alaska b/lib/pytz/zoneinfo/US/Alaska new file mode 100644 index 0000000000000000000000000000000000000000..9bbb2fd3b361ea8aa4c126d14df5fa370343a63f GIT binary patch literal 2371 zcmciCZA{fw0LSqQ0v8C15>k=qB=Y>=0R^EbF9-r60dl(ukxF8BSP2AU_(W1Vaw{?0 z9L`3^IkwuQo#|G(7TvVgCgupY9>$_{YbHzAdYVOYJKvM57d7AI|G)G99PW7g`??!i zp3HIl>j^WzaCr8a!#!oE`Hb$#^NlC`+&11+EPo#_Q!^(vxcqOdkdA>;SHO!YGO#<@ zHLJZu2Q@AC1=l9&kfKDNGdol}UtZ@6i<;75!xOIXAI|FAz8UpJe0f<$`i6bCpB$BU zym`hIb#PeTx#y_st}Xp?cFSH@bbY&wsc3WET~H_Iq^@?&UC^rMg)MQ#2G;7>^ysMA zAB*+;i-{_3e4)PQlvBkY3(@x;zN|!7fxNGGR4wq#mkFD`6AN>%%fyvuL{iMxGCA$2 zNS>M2so{G?>f~2CZK_SAkG!ul&cCEG2M_D4<D1o@o)@%ywMJ!omCWhLQH#r-mrLrR zRc>;#%***zEp@Jt`Ej#F{-qRIF#U_T|Ko7^z{KaGP$%gJ-#sZF+83&q9Xcdjty8*a z*E_1X`mA2wd{C7vdP|p<Y*VE_U65s&1ETEwX;~4uRa6`wk}Iz?iptkM(5pV{R#n@N z=!f5KP}PmQb<Kf7Ra@xQtGnV=U0j8BdmPIBN4oapUR0iM%jKGQzgY88nyjC>AR2}u z<YSYkMdPlk^6`-&v9@_kt{dzV>#M%kO?^ky6Pf4q2Jddw9I5rjGOyZrWxw_&S19i% zow~)Du3CmYdefyy_0)k5`Se(tc&6(SxmibuR?kw|)_+yB=gpJPwvLI8m}%KreN1%v z=jg8dbE<3dH{Cr~tL~8rz2(||wRP}4z3q!mwY}$cz2k&O^{nmH&kf|OfWTP+LBThB zLqeUm@O3yoyykHD{T=HaL4JR4TR^D&M%Z7X>^+9BBi8Tl-x&~Z?+L4_+>W9;a~?IP z#+-8gC@*n4>bX>!OHrk{nJ0h`&tDh!e{U_^`~!#Q6?3?!_|3EI)b&qsM_*AnvOQ#f zR<l85hiJFRg+20^O#-__wu$T$*(kD8WUI(tt!A^xZmnj!$bOLxBRfX6jO-cNG_q@C z+sM9=jUzipwvOx_**vm)Wc$eet)>B1(*dLfNDq)EAYDM(fb;=r1kwql6-Y0TW+2@_ z+F>>QKpJ8-9YI=x^aN=N(iNmFNMDe~Ae}*4gY*Vz4$>W@JxG6$23bvqkQO05LYjnh z32773C!|qGr;t`5y+WFWbPH*h)$|K#nALO)X_?jZ3~3tDHKc7w-;l;3okLoO^bTnr z(mkYoR?|PEfmYK&q=i<~L!^mF7m+q1eMB0GbP{PL(o3Y7NH>voBK<@fYBe22T52^t zMVe|gT}9f8^c86=(pjXnNN<tmBHcyWi}V+1u+?-JX|dJx7-_QAbQx(g(r2X6NT-oj zBfZ8O%?=6-4!POu3=6%5@88kx{$JDmPrGm2!ijnTdC#a?oRyO$Gpe$)v$C^f_@5Pu BZASnA literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Aleutian b/lib/pytz/zoneinfo/US/Aleutian new file mode 100644 index 0000000000000000000000000000000000000000..43236498f681cc06f64ca2afa613880331fe6fbb GIT binary patch literal 2356 zcmciCZ%ma{0LSsm-|Iy&Dj-Bm!Hl7RhrdBt9tc86<UwwrWC#iolmVg)3ox8hdC|nN zoE0&9(I3~SV{FZ&u`@U43+KAv*1#M!H|MM|UA1J6yq)jK){C0&@;rN<&)MC5`}=yU zn_f<L{p)zlFT9+7^Ky@W%Y4rF75FBW|JFKD=g8X=FQ_}G+8qC<Ug<hk;RGDYmVupF zPEgxM9b8xL3n|akp?MiTcUrV|zrDlfiI~-%;p<M=%}aXzk5j${Q@3Qe9`!B!dP+WU zV$z9tcT_&uciMSq&j<41ra>oi^IjQM+~Y*&*2zbbYMq#bZoSBp@5Baf)v>D*mc{<! z=*3quRNO?mUUDW%J^E#&Ui#rJwXCB^#`jLCgvunjy!m(WSoVCmqGVD$9yKEqSDqG$ zeveKH8x%>?KkJo0^@vqt7j*K)_f*Qz7dmyMORerXqQyXsN^AUFrngI#QPeLpD-u*z z;!c^J5v-nYdu2{syvVthEpz9B#FOV@<Wt{Y6>C(cetPtrc&0yEuYLc7kS()1Z~s}9 zUv^19TmOkFSpAJIEa+2(zuu5VDIbfXi{r95{E#Rf8IdJ3&EomNZ}s}`4ye+ulX}Bf zuc)#u1KK%SqRQ9o)*CyLRYhEt_Es)b-nm>|nRQcDUagdymWGQ>XLID{J2yo2N3rt7 z>2a}T|D1ejY(&)5Ps^=C?}*yc+q&-HNwqEIvfkb}pz6cNbVJc@)i85hHzro8#tZv& zlRH;64cF`DYm3#ZM|<UKz8tZmW4nA^#fp~7LfLwFPPAnw%AGCKqCMIpca>?e%fCW* z<Xl!AKe%;g%$VvNyRP@l9#?M+o!4(p?o(Yo!@B!az3QnstoI&!P6Y%81q6rO>j|Cb zzK@T~_1P7d%kOV+T)}>Sdu_lx`(0pviLm!bzOER*zqd7DiM=mcU+Q&js4#Dpc^$7S z-`w*Hyso@;=CaOQ%n9Jb`Rn5S?~#R>Kk#ynn3sFJ-<-8){usyZgVi<2=#b%A&G?W3 zq8%X@hR88v1O|zW5*a2kPGq3SNRgph%~+AaTFq#Y;UeQj28@gt88R|vWYEZ{kzpg_ zMh1?I92q(?c4Y9#=#k-D&G@Y*07wLo5Fjx?f`CK;2?G)bBoIg>kWe78K!Slp!)n5T z#KUR=f<y!f2@(?|C`eS0upn_k0)s>b2@Mh(BsfTPknkY!v6=uO5kf+Q#0Uuz5+x)| zNSu&BA(28tg~SR877{J12^SJCs|gqqF{=p~5;G)dNYs$9A#p<jheQqu9TGbvcu4fD zCVWWztR{d+1g$27NDPr6B2h%bh{O>IBoav^lt?U*U?R~(!imJwY66Nx)M`SC#MEkn zibNF&D-u^Eut;Q)&?2!#f{R2K2`>^~s|hd?VXFx-5@V|gG7@DZ%t)M(KqHYxLXCH0 z9UK@EdauXrnRg$bziVB+?f+@^KheH>3o}7a6Q=0Nr5UN|sUo>FEiE-IRfPQs4Q6_I literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Arizona b/lib/pytz/zoneinfo/US/Arizona new file mode 100644 index 0000000000000000000000000000000000000000..4d51271a14ab85c1aac83f7145b9f7f8f19c512a GIT binary patch literal 344 zcmWHE%1kq2zyK^j5fBCeZXgD+1sZ_Fyk%As=I>^2SkNXjVd1Qo4W~PKCY%?)FLS>C z>6#0TQZm1OlnVTQ5y8O32!zZ)$jJ2n|Fm}u4FCVHUckum|Nq<x3>;uKkB@H%gRct^ z2Lo|<2+(i{2qD2q|A8Qmg=YhZ200BxgPaGVK~4nGAZLPTkW)c4$hlw|=wuKLayEzt PIUPh(=zK1qf6Tc6=)Qk) literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Central b/lib/pytz/zoneinfo/US/Central new file mode 100644 index 0000000000000000000000000000000000000000..a5b1617c7f70bfc77b7d504aaa3f23603082c3cb GIT binary patch literal 3576 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 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/East-Indiana b/lib/pytz/zoneinfo/US/East-Indiana new file mode 100644 index 0000000000000000000000000000000000000000..09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4 GIT binary patch literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?<VS1QTNoUyPx&yV6)0S+okgc4ypV-t$Iy+nJQS{pbHzbl<;4ZMf*#|+Sq!z zuGlZuhgHiB8S!Gnr(9V)Gh7sRrpS`f!=mJJl-xAbElTT?b=l+3YI9Yj-qO;g%5#ER z9&S}zla#I~Z&2GJ?&$5=HEMfsP*%;Y7gYndW%bl*QQdw<)_ltqI~w=OoxLfdwys$2 zYKsze1(|a9F-MH>+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x<i+?D1o2{`HIJ>7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3R<GEZcp$V`!`B6CG1Yc;b)ri;uMnJ_YAWXi~#kx3)7My8F-8<{vV zb7bmh=gte0=a|_8(?{lyBw#feASqZ)4oDJKlLe9nk_VCqk_nOuk_(ayk`0m$k`I!Q z)ntUEWHmV<Nm)%+NLol<NMcB4NNPxKNODMaNP0+qNP<X)NQzdIBa)=mWQn9{HF+Y5 zBAFtoBDo^TBH1G8BKaZ-BN-zpTTRYL(pHl-lD5_4jU<j_j--y{jwFv{kN<J{q2?DM Y$^0V3_-Dr@#?6ZHCnUrr#LWu*39tolUH||9 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Eastern b/lib/pytz/zoneinfo/US/Eastern new file mode 100644 index 0000000000000000000000000000000000000000..2f75480e069b60b6c58a9137c7eebd4796f74226 GIT binary patch literal 3536 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 EZ-WKA#Q*>R literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Hawaii b/lib/pytz/zoneinfo/US/Hawaii new file mode 100644 index 0000000000000000000000000000000000000000..c7cd060159bd22fc5e6f10ac5a2089afb2c19c6a GIT binary patch literal 329 zcmWHE%1kq2zyNGO5fBCeb|40^MH+y_ZdPZH-HL?~r#o#=TvGm0a4FH#;%aZP2O|?B zGYcc@|Nl8m3=BXrf`R4#|Edf|4lv0BCI$ZgFHT@!@$n5|@CXKC7a$G?;(!pK!3+$H uP%?xBC;bP4k_QF*Ks3l{U>fK=5Dju7hz2<mOaq+?qN(g$E}&lw4Y&a7X=UL6 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Indiana-Starke b/lib/pytz/zoneinfo/US/Indiana-Starke new file mode 100644 index 0000000000000000000000000000000000000000..fcd408d74df43310a9a85c475f83d545f6d75911 GIT binary patch literal 2428 zcmd_rQB2ik7{~Dkfe-{G6GJjEt(buXHk3Bl+S0K@BA5qIA&k@z%Y0QJQKQ$bL0&XV zn~G})i(IZ5T2rwtG^N&R&d?-CCBP(SA+N>-DV@{%eY?zBUH7p6`TTcudiDF_T~hY^ zO!>=&*l&2aJ@(-}THBBMeTjPSC%>tNnz6cZ&jt1M>pp#U+K@V15^B!potKU&r_Fb% zN2ODmO;=Q%boIPtzV{v07f!4<7rS@qOZ(qc-K|ynhpp>WPko{8E%U0r>I{9^GfVwg z9H*}oq?`WCbops^thpK=D_3t$G}r9^eyx4j{M_FszjU;jfiK$R`te>h*xaMd-c#zv zwv&2jX|1|7Tq?J(ddx_tM}Ge@!T4Gd#Q%PTk=+pzP&;Twy*wy^Yr|Dg$rv4+dtKf2 z#DES-{ziqo5wAldKT@Fw-jy)(wi?s3Lx*=AG!Z8%^w?wD&A9#BC9<yE+`YA2##iN= zd&=@<!s0X&<w=u?kH?sMr^iV2)Y)p%=n;t-HA%(XjMn${-d2;_Z|VC#yQE?dUDR=n z$JLa|aq_^HMm06>hD=-asd+H<oII4Z*E}3`SmGbqV&Z-6dV1J0Gw0DtHFwSeHTTz} zk~w3w$vjslo`@Xd`FN9L4WyW--r1$+b<9`Uo2&HvBgrbKs8Hwb9IqCnXXvLZhSb8z zaoU^Lp}ZpjIzP2V<zI=FMX}$SMW2f-_8l=xn);-$d$%citxcY3-DrxJ?~|qVMdsP; zle(m~N<BBDNiQocRLdi3^oq<3wPIkUE{%^<rKhuWSxA5?JCLYX^<P#m?DWWsXZ&V$ zWrDoa+-uh4M~K>X%B)Qtlyz&~GwY+;r97wBl=}vBWm=P}>^`G6MAxVdt%r2g@Jh9@ zeuv)FnWZ*YSLjz-5><6^fqr%OST!oZ{saa&c)jya@ZWrY=fCZ~4gQBe`&a*(-~ZuP zB7Xm|g8@N){|5~++P#On&qzLH!k^#I%l68XbL_LwJ_Yv4^~zlP&IPzn@cxJO`Rx@4 z`WlcGB1=Tph%6FWC9+JXT_>_oWTnVbk+mX=b=uV;%SG0UEEriavSeh<$fA)|Bg;nC zjVv5lIkI$Q?a1PtcJ;{eop$|50gwtHB|vI`6alFMQU;_BNFk6)Af-TRfvy<5Pz}zO zgQFfuK{zUclmw{>QWT^rPFohFE>2q*j>;gVL282(2dNHH9*+7T1>&d>QX-BTAw}Y- z5>h6PIw6JPsFc%|3aJ%RETmdUxsZAx1>>j~QZkO3Aw}b;8d5fnx;bs(kjf#YLu%)= z#p9@+)0U5;eok9JjtU|rL~4i>5vd|lMx>5NA(2WVr9^7!w8ccK>9pnKsHf8wl%t|Z zNjYkY6qTc@NLe}RiWC;9EK*vewn%Z2>N;(Ck@`AqfsqP3ZHbW@BSq$@GE!!aIwOVV zs5DY)j#?wd=BT#QmK&+J(-s`5xYL##sX0<~r0Pi7k-8&=$NyL5!|X4CS@xGfV)kQ6 RGn0}Nvr|%%Qj(Ix{s3RXO|k$0 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Michigan b/lib/pytz/zoneinfo/US/Michigan new file mode 100644 index 0000000000000000000000000000000000000000..5e0226057ac5e154901b0debb90547c096f9083a GIT binary patch literal 2174 zcmdtiUrg0y9LMn=L|_L<C@EB;f{cOux1(rO7_ie(kC+}z(e%;4vm+GW)Xty`Ye9xv zb4`c28q2gswMJ^TT*cI!wK+3omHin$wZ%n?dW<ewY@D9=v+la;uJt>6p4ZvA`Q4n) zJKPlARO$WWNw&XmczW&Odv?!9d29Ap@Ab|;XXIl3?{ZO1=&$?(=8|_nC)Zq-l=4$5 z<@xDyO~xVR^A3v7JgZW5kEDJ5s!l%<k!z24>#1)%V>0${(wV(2=DN=N^!3qznYOw} zX9Ww*4fE6VjfJTuJFieppE71<B&Mnvzxib5_hTyO!q0Nk$@41r@Mm)Kfy3&Sm}hQ% zdXLI${K4dJ9@Mw_Pn%hbUeLE^y>1GMw(5e(kEPJps0&A4lcJyI>Dfa&rFb~3O8TQx zdUUQT>sl=3d$LtUBw{MJ{Hf*yg659p-zk5=Y%{lVNX<)0H&rvg(N&|rn)wqS>IG*m zm^;7i*VTi+$Xy>irSIPTx!m*8MqSf>L>6}MQ1>?MmD=VFs;(?1^>wwXetf_LO4jSZ z@GcWfU#Npe+svY|e7*SPURm;GjS6jVm8I|HsfM*7S=N`N?yoMB<&TZ36*-v_Zv0e* zC&p!^|4p^>$Ejvj?is!6^cAyuazHm78a8W2cIma<$IOF6ZF*hvKC`}msaBzPWy8)^ zwXvj69*Trib9#rg1j<y)Sd&BwGF9YUwM3K0RrKv#**yA%YVBKK+Rk6m565!MBZI@b zy>ZgW?qm8<zcyRi_vx)!r_8p7PQ7jNvc#(TRBYs=bYyp^j-i9n`A3s_yuU}DxKypS zcSYpM_j6U(x}fZM(NhVDS0yE0{U7+m<40zBUOfKRD_&AOe*7J8N<99_iG(zFXSjRX zl2F*IT@m)`IS<&g%$~Y1e|j(B?>qc21`@XqBSD6Oj0G8t(~bri4l*8I#ek3zIqi^; zF(HF;+EF3HLdJy*3>g_RG-PbZ;E>TF!$Zc03=kP1GDKvI$RLqXI_)r#aUugnMv4p- z87neaWVFa|k?|q}Mn;Sb85y(F4jLJ?(+(RMH!^T!<jByGu_J>=Mvn|189x#LBmzhX zkQg9AK%(HZVL;-51OkZ!5(*?1NHCCSAmKpbfdmAJ2oe$`CQcg^Bq~lD79=iC8yF-q zNNAAQAi+VRgM<f(4-z0GLP&^^7&&c_kSIBAn2<O*ZJ>}yA)!KIg#-(U77{KbUP!=@ zh#?_EVul0_iJH@f4T+o61`dgw(}oU-9TGewdPw+?_#pvAB8Y?#i6Ih1B#KTOMkJ0- y8%QLQP8&)jmPjy>Xd>Z6;)w(l|CbT<*~0p5S&Kt+N-Imti$fI^r4^;+zP|v~6Kewi literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Mountain b/lib/pytz/zoneinfo/US/Mountain new file mode 100644 index 0000000000000000000000000000000000000000..5fbe26b1d93d1acb2561c390c1e097d07f1a262e GIT binary patch literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8v<nU)CgtR#>b6-0<P2(NH8!YHnHS1a(Ln-=0RD8?U+ zvtrCL36!+fOng|gv7xTv+REl|Yg!BKxhxw!Y?8peo%dPwPk;4STVM9OuOIjS&-=Po z`_|@&f812_4G-6C9^R)b{@GWcUmFNlJ<liU-dDa?dprTXw{@E6E54}vI`*j#+N1RF zyx$s!>*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC<xjIswP>}}?Nyq3Ob<M?I9d-V=h(6JxW8Uo* zv2XTB`ErZ6w*6Uo-Bypd-d8WDuPPC7rT5Ai`6=Rtlm#+=Zn2sf>5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU<P zcbE6;d+N8TqRba{anTx8{Ogb`NpBJ*XZOp}=vq;Fq+Kq%Tqw$3eO)jAxJEgf+VuVJ zELG(-K3&l@M?J8lOjr6t)rzEa?OOSja!thQs@zkm>gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vM<TJe`zEf=(Jg&En`PI|iz51DRZq?M>qPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(<dr(EuI31^XcR+y*SJQXgpA|XQThwERgFKDhdEUF(_ zA+khdjmRRARU*qo)@d~hMOKO|)oRv?EEZWUvRq`nR<mGa#mJJ8HKScLFRYp~%LdlX zv2bMN$kLIuBa25?Z#BzD)^9ZhKq`Qg0I2~-5s)fylmV#&M<I|(aFhb61xGQEYH*YT zsRvRJq#{;R5~L<bQIM)2WkKqK6b7jbQW~T-9K}JZ!%-fjK2}p8q(W9xBBVwfMMA2C zlnJR5QYfTSNU4xoA;m(fg_H}a7g8{!VpdZ!q-GpNL#oD6Hl%JGg+nUGQ97h{Nb!*B zA>~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5<wa#mE15^&RHNV6pj8 VNOLaC$jQh`b7p5}WM^bK{s0D?lEVN1 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Pacific b/lib/pytz/zoneinfo/US/Pacific new file mode 100644 index 0000000000000000000000000000000000000000..9dad4f4c75b373635ccbe634798f8d9e587e36c1 GIT binary patch literal 2836 zcmd_rX;76_9LMpCq6mtfOq2-iq$YxZfTFmRxecHqDoA36O9F#ws1Rxy(nOgx#-Gfk zjgDqbP8phGV_AeYIW>)C&^T@pSt6t2f|j^+Z|8g7_Ntdn&ok%woVoAs_m?@lATPo5 zkEetEg~RiiJ=}Yg*-zDbDdz3{A!447GFxB2F5j&SGj;v0Ev=hBKppiK&pB4MQ%-ol zl9RQfPBpwMKkxWZ8fw<cFY8{G#;OAOwP2~7E}bmDrOuGwb7JI7<WOl!o}|uppRSrC zqE&P25Opq~t2$Q~qRuy6Ru^_(S1pI?)Wyo<>QePZxx8$@x>9jOTGt$qtA!uSwYl%e zAL*~kpJSer>w`<AZQwR_quVUG*{NLJY<pJUYR*%)kLBvWzDZHueaYJQew6ZTiPU~C zbW!bAcGm5e4HW<R5vIfRAn7<Z&;-O?kbw2$O`!T-0(X9?gD&rq&W+Wk%kjf1xVF-C z{j^$j+wqZBuT`o$)`{-Esz}{guw3`Zo~c4oGj-1q!&R@yVLG&LhTIhxs>9kPN?7Yq zbNA_95?<HS^geJy`s{8q_iQ~Wx@3^P_n9xGZ&tAGx9EiGpLj{%H|cXVAmm3K5mluk zye%d&s7ysR{9vNaEl`7McAMz>Qi-YBU}E>olfk7=n79q&BtHKYolw+Yh9np3p&1<| zF(OM3OK6ti0ZBS3yn{+Q8>UCxI;%z=x~)f@{8o+L6>9F^|ABg-;-(q%#(MQ&;VCn= ze20unuQB5nz9bU{8#8gj5}A0lUMI)AsFLgV>eS%HDs|6hJ*j1?n*8P-Gv(+aNn5?q zO#Nhvq|aGlrfrIq>7%pFj1nao;iF9E%vQ;~-P>d({v=svM(SC8uBcgGhwE%_y_&t< zs~>LItLBt9>PKoetDJ=g_1vmeYF=7{nZI_UEQqN!kLItCg~8iQZgRHdwv?Ovh*6S% zIL{OW^p=91DP~cVPafNps}~;$S4&Eg_2boERhSj2msT{YWy3n_<%I`TQAmp}PT#JI zeSxMVsa8rF&YP8?+hk?UVY8~OT%N3|HcuVPlhvh_=IMPYQkqj_)@+HAc7FD4@9*IH z-+6t$$^jma&-a%2`TKkoWu8v%-o<^@l(bCGv<dcP*z=G*(=zQp+T-zapUi(z0-t?y z{KIOIA|O>j%7D}XDFjjpr!56i3#1rGHIQ;3^*{=OR0JsrQWK;oNL7%sAay|sgH#47 z4N@DYEe=v0r!5asAEZD?g^&^<HA0GnR0$~)QYWNPNTrZcA+<t^g;WbEm($h@DHu{Q zq-5x7#)YEs*s1|#L+XYU4yhbcI;3_;@tn4LNco($en<h43L+&$YKRmOsUlKFq>e}- zkxC+^L~4l?6R9RrPNbelL7lduNJ){JB1J{2ij)<pD^ggbvPfx>+9Jh8s*9A@Y3qv= z*l8<_lo+WoQe>pcNSTp3BZWpPjg%UxHBxM(+DN&PdLspQ+KMA3M{14~9jQ7}cBJk| z;gQNCrAKOy6d$QRQhukcKe7N$y8_4(IPDrBi-4>GvJA*NAPa%41hN#!S|E#otOl|i zPP-n+f;jDpAWP!3Yl18avMR{3AnSrG46-uF(jaStEDo|d$nqfTgDjBKt`M?BPP<0P zB023UA<KlU6S7dqN+C;ytQE3Y$Z8?Wg{&8{U{1SY$dWnjnjwqkw5x_J8?tW5!XYb% jEFH3T`2StJAUlLfb`Yb}hQubs#zm*a$H&IU#s&Qiukn{d literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/US/Samoa b/lib/pytz/zoneinfo/US/Samoa new file mode 100644 index 0000000000000000000000000000000000000000..72707b5e15cccac9888e5ba22bb1b4e92170df5b GIT binary patch literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/UTC b/lib/pytz/zoneinfo/UTC new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Universal b/lib/pytz/zoneinfo/Universal new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/W-SU b/lib/pytz/zoneinfo/W-SU new file mode 100644 index 0000000000000000000000000000000000000000..ddb3f4e99a1030f33b56fad986c8d9c16e59eb32 GIT binary patch literal 1535 zcmd_pOGs2v0Eh8&eK(rb!qglm&2)U$sA*0)IyEzjsUbNPSW%Qng3+OZ6j}@+wy8i0 zTv(<w>Y|s6YLnFvfkYNAS_B#d(ZT{b1Q9Ad&UZz$TD9&D_x_Go1;Ov{Z)$BR5`SH5 z^c!xj-TLO770{2~!?v;O6<<2~a%X1yzByZObnc(+f7>=aAe@1L@*#I{^@&i>RWve~ zaC~IY6&@P4`5GPslaD0WhbPu1O}P_eCL0pxR)vy2#ZM$pdfe;AuS}$j_ABe{Zk2lN zys}+9t=6AwR%vZ}Rr<jywV`gS$|%oP8}pM@rq!adV&|1T(k|^^lVtYC#6V8_(?HIf zIhp(Xv&_3cCG&%?WWm)Za#QC$x%o`LbToI%!b78~=v0p?cJ-+(dpcA}YCx419Z;p; zkE*hic3Jk$tDN&qa@*r9wSBT&mJfNP>yb@XbY;rQULoBr(Q-$pRqgamOV6<%%A5I8 z`aJJdRpcF6o$*Xn&%97I;XzgN`j*=Dp-a`?y`<{KZ_4`1CzZc0^@tH379J565g8R7 z6CJf8Dth5#iCy-ITe<9u<=^=89B&aK!>RufJR^iCykNxW^I6W7Jw}`mWo|?Nw{jgK zVewqmU?dA+O%tiVzt43T>5K2n+)F>t@7C4(MLl<;zP&sez51>dd5#j{^ZE6yUz(S} z(^$BcUYI8#{QuC`Pkrrs7#c%5Ls~<6Gu6!@-68EE{h8_pkq%9Di%5^Ax=Ex<q)q-* z`a~K<IyKd;BE2HbBHbeGBK;x_BON0xBRwNcBV8kHBYh){Bb_6yo9f<?=8^7Ab^A#F z$Oe!dAX`B8fNTQU1+oofAIL_KogiC5_F}3xgY3psZwJ{AvLR$g$d-^jA)7*Wg=`Di s7qT&AXUNu&y&;=Jc4w-$hwRT(ZxGobvPEQ%$R_cB-=#%wxuDqc3lb5KyZ`_I literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/WET b/lib/pytz/zoneinfo/WET new file mode 100644 index 0000000000000000000000000000000000000000..9b03a17f41153b8673c144b42dd3cb0adc3f4ba8 GIT binary patch literal 1873 zcmd7ST}+h)9LMoP#448b2jeAqONl@|!T|(@ATvBL3ozs;gd!=3Scrniz_ha#bFG;B zxiOndCoRTWv!fSS7hulU;Uwnfn6q?kx!%;dHgi^v&FA~vcGXorXJ?;h=i*%bzrR5J z=9XN~zn;gOU-<IubiTYlHDjnrQ6JB;XZrlIt^!3jXDB9+pqZ7+?Ag436q|NSaWk(e zKKwhy-~CnzH;(GL%Y&MAW}gzrPFT{BkR=~HY$^S+)Lrk}^KG3rdu@}=sR&qFvEN?s z7Fc>xhQ0W7f@MUMD&yWlW&WJ5xl?hPckT(zANfn(q2IM&;Cp5DT~v10n0(D&DW~D8 z<#vu(UdgB}T)E%!lMh)zZjTjA2W@fUfEHh0Z-o!KRdk}%UizU<#lwv%87<PwdzNa+ zr%8IHB~45F3hmXh2$eP`+OqUt<u9GKvWN>RPy5lz@BCuR!%x}r$#Hw_#^?5W=u4}Z zIH}6L{Z@74Gp%TEw>SDfP<8D`TG_TyHH+F5s3_N}_$saT&eiJsKGnsRS>2Twy?HOw z>c9R|Yo;P??O?PujNG<$!MoNtFllc!PTBh1H??8OH`dfJq2|;Bwy`9nmM4#EQ?j(_ zw@=hM-Kovvy?Xn4K<|99Ms4RiY|DowY9Fq#t*xorwkO{@s*A0&CEm8@q*#~#Z`%>= zvF_|xb^q~`^+erQ&!w~0d*`Zl9vilICr7pGz-jFc9nyQfLkjK<D$MhLeK<FsC<}YM z)*tZH`fCE7Fy{&Uhco35I92zLoO4A^7CBqwbdmE#P8d03<dl(fMo!w*Icwy!k@H4Q z96593)RA*XP98aX<n)pAM-o6XKvF<*K$1YRaCOo^@<0+nGC@*7azT<ovO&^8@<9?p zGD1>9azc_qvT}9OLh^ET5<@aWQbTe>l0&jX(nIn?5=1gYQbck@l0>pZ(nRufbrMA~ zb#+ojaz&CwvPIHG@<kFxGDcEHaz>IyvPRNI@^*C+M>2PHQb%$}l1H*f(ns<~CIFcM zWD1ZuKqdj11!NkKc|axtnF&{CDv-HACIgubWIB-fKqds45oAh`IYA}`nH6MOka<BS z2ALUGXKIkSadjpKnH^+$koiF-2$>;ditrJgqey3*e2kN~GA}DX%a@(wt<3T97Woif Cu$Y+u literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/Zulu b/lib/pytz/zoneinfo/Zulu new file mode 100644 index 0000000000000000000000000000000000000000..5583f5b0c6e6949372648a7d75502e4d01b44931 GIT binary patch literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/iso3166.tab b/lib/pytz/zoneinfo/iso3166.tab new file mode 100644 index 0000000..c2e0f8e --- /dev/null +++ b/lib/pytz/zoneinfo/iso3166.tab @@ -0,0 +1,274 @@ +# ISO 3166 alpha-2 country codes +# +# This file is in the public domain, so clarified as of +# 2009-05-17 by Arthur David Olson. +# +# From Paul Eggert (2015-05-02): +# This file contains a table of two-letter country codes. Columns are +# separated by a single tab. Lines beginning with '#' are comments. +# All text uses UTF-8 encoding. The columns of the table are as follows: +# +# 1. ISO 3166-1 alpha-2 country code, current as of +# ISO 3166-1 N905 (2016-11-15). See: Updates on ISO 3166-1 +# http://isotc.iso.org/livelink/livelink/Open/16944257 +# 2. The usual English name for the coded region, +# chosen so that alphabetic sorting of subsets produces helpful lists. +# This is not the same as the English name in the ISO 3166 tables. +# +# The table is sorted by country code. +# +# This table is intended as an aid for users, to help them select time +# zone data appropriate for their practical needs. It is not intended +# to take or endorse any position on legal or territorial claims. +# +#country- +#code name of country, territory, area, or subdivision +AD Andorra +AE United Arab Emirates +AF Afghanistan +AG Antigua & Barbuda +AI Anguilla +AL Albania +AM Armenia +AO Angola +AQ Antarctica +AR Argentina +AS Samoa (American) +AT Austria +AU Australia +AW Aruba +AX Åland Islands +AZ Azerbaijan +BA Bosnia & Herzegovina +BB Barbados +BD Bangladesh +BE Belgium +BF Burkina Faso +BG Bulgaria +BH Bahrain +BI Burundi +BJ Benin +BL St Barthelemy +BM Bermuda +BN Brunei +BO Bolivia +BQ Caribbean NL +BR Brazil +BS Bahamas +BT Bhutan +BV Bouvet Island +BW Botswana +BY Belarus +BZ Belize +CA Canada +CC Cocos (Keeling) Islands +CD Congo (Dem. Rep.) +CF Central African Rep. +CG Congo (Rep.) +CH Switzerland +CI Côte d'Ivoire +CK Cook Islands +CL Chile +CM Cameroon +CN China +CO Colombia +CR Costa Rica +CU Cuba +CV Cape Verde +CW Curaçao +CX Christmas Island +CY Cyprus +CZ Czech Republic +DE Germany +DJ Djibouti +DK Denmark +DM Dominica +DO Dominican Republic +DZ Algeria +EC Ecuador +EE Estonia +EG Egypt +EH Western Sahara +ER Eritrea +ES Spain +ET Ethiopia +FI Finland +FJ Fiji +FK Falkland Islands +FM Micronesia +FO Faroe Islands +FR France +GA Gabon +GB Britain (UK) +GD Grenada +GE Georgia +GF French Guiana +GG Guernsey +GH Ghana +GI Gibraltar +GL Greenland +GM Gambia +GN Guinea +GP Guadeloupe +GQ Equatorial Guinea +GR Greece +GS South Georgia & the South Sandwich Islands +GT Guatemala +GU Guam +GW Guinea-Bissau +GY Guyana +HK Hong Kong +HM Heard Island & McDonald Islands +HN Honduras +HR Croatia +HT Haiti +HU Hungary +ID Indonesia +IE Ireland +IL Israel +IM Isle of Man +IN India +IO British Indian Ocean Territory +IQ Iraq +IR Iran +IS Iceland +IT Italy +JE Jersey +JM Jamaica +JO Jordan +JP Japan +KE Kenya +KG Kyrgyzstan +KH Cambodia +KI Kiribati +KM Comoros +KN St Kitts & Nevis +KP Korea (North) +KR Korea (South) +KW Kuwait +KY Cayman Islands +KZ Kazakhstan +LA Laos +LB Lebanon +LC St Lucia +LI Liechtenstein +LK Sri Lanka +LR Liberia +LS Lesotho +LT Lithuania +LU Luxembourg +LV Latvia +LY Libya +MA Morocco +MC Monaco +MD Moldova +ME Montenegro +MF St Martin (French) +MG Madagascar +MH Marshall Islands +MK Macedonia +ML Mali +MM Myanmar (Burma) +MN Mongolia +MO Macau +MP Northern Mariana Islands +MQ Martinique +MR Mauritania +MS Montserrat +MT Malta +MU Mauritius +MV Maldives +MW Malawi +MX Mexico +MY Malaysia +MZ Mozambique +NA Namibia +NC New Caledonia +NE Niger +NF Norfolk Island +NG Nigeria +NI Nicaragua +NL Netherlands +NO Norway +NP Nepal +NR Nauru +NU Niue +NZ New Zealand +OM Oman +PA Panama +PE Peru +PF French Polynesia +PG Papua New Guinea +PH Philippines +PK Pakistan +PL Poland +PM St Pierre & Miquelon +PN Pitcairn +PR Puerto Rico +PS Palestine +PT Portugal +PW Palau +PY Paraguay +QA Qatar +RE Réunion +RO Romania +RS Serbia +RU Russia +RW Rwanda +SA Saudi Arabia +SB Solomon Islands +SC Seychelles +SD Sudan +SE Sweden +SG Singapore +SH St Helena +SI Slovenia +SJ Svalbard & Jan Mayen +SK Slovakia +SL Sierra Leone +SM San Marino +SN Senegal +SO Somalia +SR Suriname +SS South Sudan +ST Sao Tome & Principe +SV El Salvador +SX St Maarten (Dutch) +SY Syria +SZ Swaziland +TC Turks & Caicos Is +TD Chad +TF French Southern & Antarctic Lands +TG Togo +TH Thailand +TJ Tajikistan +TK Tokelau +TL East Timor +TM Turkmenistan +TN Tunisia +TO Tonga +TR Turkey +TT Trinidad & Tobago +TV Tuvalu +TW Taiwan +TZ Tanzania +UA Ukraine +UG Uganda +UM US minor outlying islands +US United States +UY Uruguay +UZ Uzbekistan +VA Vatican City +VC St Vincent +VE Venezuela +VG Virgin Islands (UK) +VI Virgin Islands (US) +VN Vietnam +VU Vanuatu +WF Wallis & Futuna +WS Samoa (western) +YE Yemen +YT Mayotte +ZA South Africa +ZM Zambia +ZW Zimbabwe diff --git a/lib/pytz/zoneinfo/leapseconds b/lib/pytz/zoneinfo/leapseconds new file mode 100644 index 0000000..148aa8e --- /dev/null +++ b/lib/pytz/zoneinfo/leapseconds @@ -0,0 +1,66 @@ +# Allowance for leap seconds added to each time zone file. + +# This file is in the public domain. + +# This file is generated automatically from the data in the public-domain +# leap-seconds.list file, which can be copied from +# <ftp://ftp.nist.gov/pub/time/leap-seconds.list> +# or <ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list> +# or <ftp://tycho.usno.navy.mil/pub/ntp/leap-seconds.list>. +# For more about leap-seconds.list, please see +# The NTP Timescale and Leap Seconds +# <https://www.eecis.udel.edu/~mills/leap.html>. + +# The International Earth Rotation and Reference Systems Service +# periodically uses leap seconds to keep UTC to within 0.9 s of UT1 +# (which measures the true angular orientation of the earth in space) +# and publishes leap second data in a copyrighted file +# <https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat>. +# See: Levine J. Coordinated Universal Time and the leap second. +# URSI Radio Sci Bull. 2016;89(4):30-6. doi:10.23919/URSIRSB.2016.7909995 +# <https://ieeexplore.ieee.org/document/7909995>. +# There were no leap seconds before 1972, because the official mechanism +# accounting for the discrepancy between atomic time and the earth's rotation +# did not exist. + +# The correction (+ or -) is made at the given time, so lines +# will typically look like: +# Leap YEAR MON DAY 23:59:60 + R/S +# or +# Leap YEAR MON DAY 23:59:59 - R/S + +# If the leap second is Rolling (R) the given time is local time (unused here). +Leap 1972 Jun 30 23:59:60 + S +Leap 1972 Dec 31 23:59:60 + S +Leap 1973 Dec 31 23:59:60 + S +Leap 1974 Dec 31 23:59:60 + S +Leap 1975 Dec 31 23:59:60 + S +Leap 1976 Dec 31 23:59:60 + S +Leap 1977 Dec 31 23:59:60 + S +Leap 1978 Dec 31 23:59:60 + S +Leap 1979 Dec 31 23:59:60 + S +Leap 1981 Jun 30 23:59:60 + S +Leap 1982 Jun 30 23:59:60 + S +Leap 1983 Jun 30 23:59:60 + S +Leap 1985 Jun 30 23:59:60 + S +Leap 1987 Dec 31 23:59:60 + S +Leap 1989 Dec 31 23:59:60 + S +Leap 1990 Dec 31 23:59:60 + S +Leap 1992 Jun 30 23:59:60 + S +Leap 1993 Jun 30 23:59:60 + S +Leap 1994 Jun 30 23:59:60 + S +Leap 1995 Dec 31 23:59:60 + S +Leap 1997 Jun 30 23:59:60 + S +Leap 1998 Dec 31 23:59:60 + S +Leap 2005 Dec 31 23:59:60 + S +Leap 2008 Dec 31 23:59:60 + S +Leap 2012 Jun 30 23:59:60 + S +Leap 2015 Jun 30 23:59:60 + S +Leap 2016 Dec 31 23:59:60 + S + +# POSIX timestamps for the data in this file: +#updated 1467936000 +#expires 1561680000 + +# Updated through IERS Bulletin C56 +# File expires on: 28 June 2019 diff --git a/lib/pytz/zoneinfo/posixrules b/lib/pytz/zoneinfo/posixrules new file mode 100644 index 0000000000000000000000000000000000000000..2f75480e069b60b6c58a9137c7eebd4796f74226 GIT binary patch literal 3536 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 EZ-WKA#Q*>R literal 0 HcmV?d00001 diff --git a/lib/pytz/zoneinfo/tzdata.zi b/lib/pytz/zoneinfo/tzdata.zi new file mode 100644 index 0000000..7eb8193 --- /dev/null +++ b/lib/pytz/zoneinfo/tzdata.zi @@ -0,0 +1,4177 @@ +# version unknown +# This zic input file is in the public domain. +R d 1916 o - Jun 14 23s 1 S +R d 1916 1919 - O Sun>=1 23s 0 - +R d 1917 o - Mar 24 23s 1 S +R d 1918 o - Mar 9 23s 1 S +R d 1919 o - Mar 1 23s 1 S +R d 1920 o - F 14 23s 1 S +R d 1920 o - O 23 23s 0 - +R d 1921 o - Mar 14 23s 1 S +R d 1921 o - Jun 21 23s 0 - +R d 1939 o - S 11 23s 1 S +R d 1939 o - N 19 1 0 - +R d 1944 1945 - Ap M>=1 2 1 S +R d 1944 o - O 8 2 0 - +R d 1945 o - S 16 1 0 - +R d 1971 o - Ap 25 23s 1 S +R d 1971 o - S 26 23s 0 - +R d 1977 o - May 6 0 1 S +R d 1977 o - O 21 0 0 - +R d 1978 o - Mar 24 1 1 S +R d 1978 o - S 22 3 0 - +R d 1980 o - Ap 25 0 1 S +R d 1980 o - O 31 2 0 - +Z Africa/Algiers 0:12:12 - LMT 1891 Mar 15 0:1 +0:9:21 - PMT 1911 Mar 11 +0 d WE%sT 1940 F 25 2 +1 d CE%sT 1946 O 7 +0 - WET 1956 Ja 29 +1 - CET 1963 Ap 14 +0 d WE%sT 1977 O 21 +1 d CE%sT 1979 O 26 +0 d WE%sT 1981 May +1 - CET +Z Atlantic/Cape_Verde -1:34:4 - LMT 1912 Ja 1 2u +-2 - -02 1942 S +-2 1 -01 1945 O 15 +-2 - -02 1975 N 25 2 +-1 - -01 +Z Africa/Ndjamena 1:0:12 - LMT 1912 +1 - WAT 1979 O 14 +1 1 WAST 1980 Mar 8 +1 - WAT +Z Africa/Abidjan -0:16:8 - LMT 1912 +0 - GMT +Li Africa/Abidjan Africa/Bamako +Li Africa/Abidjan Africa/Banjul +Li Africa/Abidjan Africa/Conakry +Li Africa/Abidjan Africa/Dakar +Li Africa/Abidjan Africa/Freetown +Li Africa/Abidjan Africa/Lome +Li Africa/Abidjan Africa/Nouakchott +Li Africa/Abidjan Africa/Ouagadougou +Li Africa/Abidjan Atlantic/St_Helena +R K 1940 o - Jul 15 0 1 S +R K 1940 o - O 1 0 0 - +R K 1941 o - Ap 15 0 1 S +R K 1941 o - S 16 0 0 - +R K 1942 1944 - Ap 1 0 1 S +R K 1942 o - O 27 0 0 - +R K 1943 1945 - N 1 0 0 - +R K 1945 o - Ap 16 0 1 S +R K 1957 o - May 10 0 1 S +R K 1957 1958 - O 1 0 0 - +R K 1958 o - May 1 0 1 S +R K 1959 1981 - May 1 1 1 S +R K 1959 1965 - S 30 3 0 - +R K 1966 1994 - O 1 3 0 - +R K 1982 o - Jul 25 1 1 S +R K 1983 o - Jul 12 1 1 S +R K 1984 1988 - May 1 1 1 S +R K 1989 o - May 6 1 1 S +R K 1990 1994 - May 1 1 1 S +R K 1995 2010 - Ap lastF 0s 1 S +R K 1995 2005 - S lastTh 24 0 - +R K 2006 o - S 21 24 0 - +R K 2007 o - S Th>=1 24 0 - +R K 2008 o - Au lastTh 24 0 - +R K 2009 o - Au 20 24 0 - +R K 2010 o - Au 10 24 0 - +R K 2010 o - S 9 24 1 S +R K 2010 o - S lastTh 24 0 - +R K 2014 o - May 15 24 1 S +R K 2014 o - Jun 26 24 0 - +R K 2014 o - Jul 31 24 1 S +R K 2014 o - S lastTh 24 0 - +Z Africa/Cairo 2:5:9 - LMT 1900 O +2 K EE%sT +R GH 1920 1942 - S 1 0 0:20 - +R GH 1920 1942 - D 31 0 0 - +Z Africa/Accra -0:0:52 - LMT 1918 +0 GH GMT/+0020 +Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u +-1 - -01 1975 +0 - GMT +Z Africa/Nairobi 2:27:16 - LMT 1928 Jul +3 - EAT 1930 +2:30 - +0230 1940 +2:45 - +0245 1960 +3 - EAT +Li Africa/Nairobi Africa/Addis_Ababa +Li Africa/Nairobi Africa/Asmara +Li Africa/Nairobi Africa/Dar_es_Salaam +Li Africa/Nairobi Africa/Djibouti +Li Africa/Nairobi Africa/Kampala +Li Africa/Nairobi Africa/Mogadishu +Li Africa/Nairobi Indian/Antananarivo +Li Africa/Nairobi Indian/Comoro +Li Africa/Nairobi Indian/Mayotte +Z Africa/Monrovia -0:43:8 - LMT 1882 +-0:43:8 - MMT 1919 Mar +-0:44:30 - MMT 1972 Ja 7 +0 - GMT +R L 1951 o - O 14 2 1 S +R L 1952 o - Ja 1 0 0 - +R L 1953 o - O 9 2 1 S +R L 1954 o - Ja 1 0 0 - +R L 1955 o - S 30 0 1 S +R L 1956 o - Ja 1 0 0 - +R L 1982 1984 - Ap 1 0 1 S +R L 1982 1985 - O 1 0 0 - +R L 1985 o - Ap 6 0 1 S +R L 1986 o - Ap 4 0 1 S +R L 1986 o - O 3 0 0 - +R L 1987 1989 - Ap 1 0 1 S +R L 1987 1989 - O 1 0 0 - +R L 1997 o - Ap 4 0 1 S +R L 1997 o - O 4 0 0 - +R L 2013 o - Mar lastF 1 1 S +R L 2013 o - O lastF 2 0 - +Z Africa/Tripoli 0:52:44 - LMT 1920 +1 L CE%sT 1959 +2 - EET 1982 +1 L CE%sT 1990 May 4 +2 - EET 1996 S 30 +1 L CE%sT 1997 O 4 +2 - EET 2012 N 10 2 +1 L CE%sT 2013 O 25 2 +2 - EET +R MU 1982 o - O 10 0 1 - +R MU 1983 o - Mar 21 0 0 - +R MU 2008 o - O lastSun 2 1 - +R MU 2009 o - Mar lastSun 2 0 - +Z Indian/Mauritius 3:50 - LMT 1907 +4 MU +04/+05 +R M 1939 o - S 12 0 1 - +R M 1939 o - N 19 0 0 - +R M 1940 o - F 25 0 1 - +R M 1945 o - N 18 0 0 - +R M 1950 o - Jun 11 0 1 - +R M 1950 o - O 29 0 0 - +R M 1967 o - Jun 3 12 1 - +R M 1967 o - O 1 0 0 - +R M 1974 o - Jun 24 0 1 - +R M 1974 o - S 1 0 0 - +R M 1976 1977 - May 1 0 1 - +R M 1976 o - Au 1 0 0 - +R M 1977 o - S 28 0 0 - +R M 1978 o - Jun 1 0 1 - +R M 1978 o - Au 4 0 0 - +R M 2008 o - Jun 1 0 1 - +R M 2008 o - S 1 0 0 - +R M 2009 o - Jun 1 0 1 - +R M 2009 o - Au 21 0 0 - +R M 2010 o - May 2 0 1 - +R M 2010 o - Au 8 0 0 - +R M 2011 o - Ap 3 0 1 - +R M 2011 o - Jul 31 0 0 - +R M 2012 2013 - Ap lastSun 2 1 - +R M 2012 o - Jul 20 3 0 - +R M 2012 o - Au 20 2 1 - +R M 2012 o - S 30 3 0 - +R M 2013 o - Jul 7 3 0 - +R M 2013 o - Au 10 2 1 - +R M 2013 2018 - O lastSun 3 0 - +R M 2014 2018 - Mar lastSun 2 1 - +R M 2014 o - Jun 28 3 0 - +R M 2014 o - Au 2 2 1 - +R M 2015 o - Jun 14 3 0 - +R M 2015 o - Jul 19 2 1 - +R M 2016 o - Jun 5 3 0 - +R M 2016 o - Jul 10 2 1 - +R M 2017 o - May 21 3 0 - +R M 2017 o - Jul 2 2 1 - +R M 2018 o - May 13 3 0 - +R M 2018 o - Jun 17 2 1 - +Z Africa/Casablanca -0:30:20 - LMT 1913 O 26 +0 M +00/+01 1984 Mar 16 +1 - +01 1986 +0 M +00/+01 2018 O 27 +1 - +01 +Z Africa/El_Aaiun -0:52:48 - LMT 1934 +-1 - -01 1976 Ap 14 +0 M +00/+01 2018 O 27 +1 - +01 +Z Africa/Maputo 2:10:20 - LMT 1903 Mar +2 - CAT +Li Africa/Maputo Africa/Blantyre +Li Africa/Maputo Africa/Bujumbura +Li Africa/Maputo Africa/Gaborone +Li Africa/Maputo Africa/Harare +Li Africa/Maputo Africa/Kigali +Li Africa/Maputo Africa/Lubumbashi +Li Africa/Maputo Africa/Lusaka +R NA 1994 o - Mar 21 0 -1 WAT +R NA 1994 2017 - S Sun>=1 2 0 CAT +R NA 1995 2017 - Ap Sun>=1 2 -1 WAT +Z Africa/Windhoek 1:8:24 - LMT 1892 F 8 +1:30 - +0130 1903 Mar +2 - SAST 1942 S 20 2 +2 1 SAST 1943 Mar 21 2 +2 - SAST 1990 Mar 21 +2 NA %s +Z Africa/Lagos 0:13:36 - LMT 1919 S +1 - WAT +Li Africa/Lagos Africa/Bangui +Li Africa/Lagos Africa/Brazzaville +Li Africa/Lagos Africa/Douala +Li Africa/Lagos Africa/Kinshasa +Li Africa/Lagos Africa/Libreville +Li Africa/Lagos Africa/Luanda +Li Africa/Lagos Africa/Malabo +Li Africa/Lagos Africa/Niamey +Li Africa/Lagos Africa/Porto-Novo +Z Indian/Reunion 3:41:52 - LMT 1911 Jun +4 - +04 +Z Africa/Sao_Tome 0:26:56 - LMT 1884 +-0:36:45 - LMT 1912 Ja 1 0u +0 - GMT 2018 Ja 1 1 +1 - WAT +Z Indian/Mahe 3:41:48 - LMT 1906 Jun +4 - +04 +R SA 1942 1943 - S Sun>=15 2 1 - +R SA 1943 1944 - Mar Sun>=15 2 0 - +Z Africa/Johannesburg 1:52 - LMT 1892 F 8 +1:30 - SAST 1903 Mar +2 SA SAST +Li Africa/Johannesburg Africa/Maseru +Li Africa/Johannesburg Africa/Mbabane +R SD 1970 o - May 1 0 1 S +R SD 1970 1985 - O 15 0 0 - +R SD 1971 o - Ap 30 0 1 S +R SD 1972 1985 - Ap lastSun 0 1 S +Z Africa/Khartoum 2:10:8 - LMT 1931 +2 SD CA%sT 2000 Ja 15 12 +3 - EAT 2017 N +2 - CAT +Z Africa/Juba 2:6:28 - LMT 1931 +2 SD CA%sT 2000 Ja 15 12 +3 - EAT +R n 1939 o - Ap 15 23s 1 S +R n 1939 o - N 18 23s 0 - +R n 1940 o - F 25 23s 1 S +R n 1941 o - O 6 0 0 - +R n 1942 o - Mar 9 0 1 S +R n 1942 o - N 2 3 0 - +R n 1943 o - Mar 29 2 1 S +R n 1943 o - Ap 17 2 0 - +R n 1943 o - Ap 25 2 1 S +R n 1943 o - O 4 2 0 - +R n 1944 1945 - Ap M>=1 2 1 S +R n 1944 o - O 8 0 0 - +R n 1945 o - S 16 0 0 - +R n 1977 o - Ap 30 0s 1 S +R n 1977 o - S 24 0s 0 - +R n 1978 o - May 1 0s 1 S +R n 1978 o - O 1 0s 0 - +R n 1988 o - Jun 1 0s 1 S +R n 1988 1990 - S lastSun 0s 0 - +R n 1989 o - Mar 26 0s 1 S +R n 1990 o - May 1 0s 1 S +R n 2005 o - May 1 0s 1 S +R n 2005 o - S 30 1s 0 - +R n 2006 2008 - Mar lastSun 2s 1 S +R n 2006 2008 - O lastSun 2s 0 - +Z Africa/Tunis 0:40:44 - LMT 1881 May 12 +0:9:21 - PMT 1911 Mar 11 +1 n CE%sT +Z Antarctica/Casey 0 - -00 1969 +8 - +08 2009 O 18 2 +11 - +11 2010 Mar 5 2 +8 - +08 2011 O 28 2 +11 - +11 2012 F 21 17u +8 - +08 2016 O 22 +11 - +11 2018 Mar 11 4 +8 - +08 +Z Antarctica/Davis 0 - -00 1957 Ja 13 +7 - +07 1964 N +0 - -00 1969 F +7 - +07 2009 O 18 2 +5 - +05 2010 Mar 10 20u +7 - +07 2011 O 28 2 +5 - +05 2012 F 21 20u +7 - +07 +Z Antarctica/Mawson 0 - -00 1954 F 13 +6 - +06 2009 O 18 2 +5 - +05 +Z Indian/Kerguelen 0 - -00 1950 +5 - +05 +Z Antarctica/DumontDUrville 0 - -00 1947 +10 - +10 1952 Ja 14 +0 - -00 1956 N +10 - +10 +Z Antarctica/Syowa 0 - -00 1957 Ja 29 +3 - +03 +R Tr 2005 ma - Mar lastSun 1u 2 +02 +R Tr 2004 ma - O lastSun 1u 0 +00 +Z Antarctica/Troll 0 - -00 2005 F 12 +0 Tr %s +Z Antarctica/Vostok 0 - -00 1957 D 16 +6 - +06 +Z Antarctica/Rothera 0 - -00 1976 D +-3 - -03 +Z Asia/Kabul 4:36:48 - LMT 1890 +4 - +04 1945 +4:30 - +0430 +R AM 2011 o - Mar lastSun 2s 1 - +R AM 2011 o - O lastSun 2s 0 - +Z Asia/Yerevan 2:58 - LMT 1924 May 2 +3 - +03 1957 Mar +4 R +04/+05 1991 Mar 31 2s +3 R +03/+04 1995 S 24 2s +4 - +04 1997 +4 R +04/+05 2011 +4 AM +04/+05 +R AZ 1997 2015 - Mar lastSun 4 1 - +R AZ 1997 2015 - O lastSun 5 0 - +Z Asia/Baku 3:19:24 - LMT 1924 May 2 +3 - +03 1957 Mar +4 R +04/+05 1991 Mar 31 2s +3 R +03/+04 1992 S lastSun 2s +4 - +04 1996 +4 E +04/+05 1997 +4 AZ +04/+05 +R BD 2009 o - Jun 19 23 1 - +R BD 2009 o - D 31 24 0 - +Z Asia/Dhaka 6:1:40 - LMT 1890 +5:53:20 - HMT 1941 O +6:30 - +0630 1942 May 15 +5:30 - +0530 1942 S +6:30 - +0630 1951 S 30 +6 - +06 2009 +6 BD +06/+07 +Z Asia/Thimphu 5:58:36 - LMT 1947 Au 15 +5:30 - +0530 1987 O +6 - +06 +Z Indian/Chagos 4:49:40 - LMT 1907 +5 - +05 1996 +6 - +06 +Z Asia/Brunei 7:39:40 - LMT 1926 Mar +7:30 - +0730 1933 +8 - +08 +Z Asia/Yangon 6:24:47 - LMT 1880 +6:24:47 - RMT 1920 +6:30 - +0630 1942 May +9 - +09 1945 May 3 +6:30 - +0630 +R Sh 1940 o - Jun 1 0 1 D +R Sh 1940 o - O 12 24 0 S +R Sh 1941 o - Mar 15 0 1 D +R Sh 1941 o - N 1 24 0 S +R Sh 1942 o - Ja 31 0 1 D +R Sh 1945 o - S 1 24 0 S +R Sh 1946 o - May 15 0 1 D +R Sh 1946 o - S 30 24 0 S +R Sh 1947 o - Ap 15 0 1 D +R Sh 1947 o - O 31 24 0 S +R Sh 1948 1949 - May 1 0 1 D +R Sh 1948 1949 - S 30 24 0 S +R CN 1986 o - May 4 2 1 D +R CN 1986 1991 - S Sun>=11 2 0 S +R CN 1987 1991 - Ap Sun>=11 2 1 D +Z Asia/Shanghai 8:5:43 - LMT 1901 +8 Sh C%sT 1949 May 28 +8 CN C%sT +Z Asia/Urumqi 5:50:20 - LMT 1928 +6 - +06 +R HK 1941 o - Ap 1 3:30 1 S +R HK 1941 o - S 30 3:30 0 - +R HK 1946 o - Ap 20 3:30 1 S +R HK 1946 o - D 1 3:30 0 - +R HK 1947 o - Ap 13 3:30 1 S +R HK 1947 o - D 30 3:30 0 - +R HK 1948 o - May 2 3:30 1 S +R HK 1948 1951 - O lastSun 3:30 0 - +R HK 1952 o - O 25 3:30 0 - +R HK 1949 1953 - Ap Sun>=1 3:30 1 S +R HK 1953 o - N 1 3:30 0 - +R HK 1954 1964 - Mar Sun>=18 3:30 1 S +R HK 1954 o - O 31 3:30 0 - +R HK 1955 1964 - N Sun>=1 3:30 0 - +R HK 1965 1976 - Ap Sun>=16 3:30 1 S +R HK 1965 1976 - O Sun>=16 3:30 0 - +R HK 1973 o - D 30 3:30 1 S +R HK 1979 o - May Sun>=8 3:30 1 S +R HK 1979 o - O Sun>=16 3:30 0 - +Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 30 +8 HK HK%sT 1941 D 25 +9 - JST 1945 S 15 +8 HK HK%sT +R f 1946 o - May 15 0 1 D +R f 1946 o - O 1 0 0 S +R f 1947 o - Ap 15 0 1 D +R f 1947 o - N 1 0 0 S +R f 1948 1951 - May 1 0 1 D +R f 1948 1951 - O 1 0 0 S +R f 1952 o - Mar 1 0 1 D +R f 1952 1954 - N 1 0 0 S +R f 1953 1959 - Ap 1 0 1 D +R f 1955 1961 - O 1 0 0 S +R f 1960 1961 - Jun 1 0 1 D +R f 1974 1975 - Ap 1 0 1 D +R f 1974 1975 - O 1 0 0 S +R f 1979 o - Jul 1 0 1 D +R f 1979 o - O 1 0 0 S +Z Asia/Taipei 8:6 - LMT 1896 +8 - CST 1937 O +9 - JST 1945 S 21 1 +8 f C%sT +R _ 1942 1943 - Ap 30 23 1 - +R _ 1942 o - N 17 23 0 - +R _ 1943 o - S 30 23 0 S +R _ 1946 o - Ap 30 23s 1 D +R _ 1946 o - S 30 23s 0 S +R _ 1947 o - Ap 19 23s 1 D +R _ 1947 o - N 30 23s 0 S +R _ 1948 o - May 2 23s 1 D +R _ 1948 o - O 31 23s 0 S +R _ 1949 1950 - Ap Sat>=1 23s 1 D +R _ 1949 1950 - O lastSat 23s 0 S +R _ 1951 o - Mar 31 23s 1 D +R _ 1951 o - O 28 23s 0 S +R _ 1952 1953 - Ap Sat>=1 23s 1 D +R _ 1952 o - N 1 23s 0 S +R _ 1953 1954 - O lastSat 23s 0 S +R _ 1954 1956 - Mar Sat>=17 23s 1 D +R _ 1955 o - N 5 23s 0 S +R _ 1956 1964 - N Sun>=1 3:30 0 S +R _ 1957 1964 - Mar Sun>=18 3:30 1 D +R _ 1965 1973 - Ap Sun>=16 3:30 1 D +R _ 1965 1966 - O Sun>=16 2:30 0 S +R _ 1967 1976 - O Sun>=16 3:30 0 S +R _ 1973 o - D 30 3:30 1 D +R _ 1975 1976 - Ap Sun>=16 3:30 1 D +R _ 1979 o - May 13 3:30 1 D +R _ 1979 o - O Sun>=16 3:30 0 S +Z Asia/Macau 7:34:10 - LMT 1904 O 30 +8 - CST 1941 D 21 23 +9 _ +09/+10 1945 S 30 24 +8 _ C%sT +R CY 1975 o - Ap 13 0 1 S +R CY 1975 o - O 12 0 0 - +R CY 1976 o - May 15 0 1 S +R CY 1976 o - O 11 0 0 - +R CY 1977 1980 - Ap Sun>=1 0 1 S +R CY 1977 o - S 25 0 0 - +R CY 1978 o - O 2 0 0 - +R CY 1979 1997 - S lastSun 0 0 - +R CY 1981 1998 - Mar lastSun 0 1 S +Z Asia/Nicosia 2:13:28 - LMT 1921 N 14 +2 CY EE%sT 1998 S +2 E EE%sT +Z Asia/Famagusta 2:15:48 - LMT 1921 N 14 +2 CY EE%sT 1998 S +2 E EE%sT 2016 S 8 +3 - +03 2017 O 29 1u +2 E EE%sT +Li Asia/Nicosia Europe/Nicosia +Z Asia/Tbilisi 2:59:11 - LMT 1880 +2:59:11 - TBMT 1924 May 2 +3 - +03 1957 Mar +4 R +04/+05 1991 Mar 31 2s +3 R +03/+04 1992 +3 e +03/+04 1994 S lastSun +4 e +04/+05 1996 O lastSun +4 1 +05 1997 Mar lastSun +4 e +04/+05 2004 Jun 27 +3 R +03/+04 2005 Mar lastSun 2 +4 - +04 +Z Asia/Dili 8:22:20 - LMT 1912 +8 - +08 1942 F 21 23 +9 - +09 1976 May 3 +8 - +08 2000 S 17 +9 - +09 +Z Asia/Kolkata 5:53:28 - LMT 1854 Jun 28 +5:53:20 - HMT 1870 +5:21:10 - MMT 1906 +5:30 - IST 1941 O +5:30 1 +0630 1942 May 15 +5:30 - IST 1942 S +5:30 1 +0630 1945 O 15 +5:30 - IST +Z Asia/Jakarta 7:7:12 - LMT 1867 Au 10 +7:7:12 - BMT 1923 D 31 23:47:12 +7:20 - +0720 1932 N +7:30 - +0730 1942 Mar 23 +9 - +09 1945 S 23 +7:30 - +0730 1948 May +8 - +08 1950 May +7:30 - +0730 1964 +7 - WIB +Z Asia/Pontianak 7:17:20 - LMT 1908 May +7:17:20 - PMT 1932 N +7:30 - +0730 1942 Ja 29 +9 - +09 1945 S 23 +7:30 - +0730 1948 May +8 - +08 1950 May +7:30 - +0730 1964 +8 - WITA 1988 +7 - WIB +Z Asia/Makassar 7:57:36 - LMT 1920 +7:57:36 - MMT 1932 N +8 - +08 1942 F 9 +9 - +09 1945 S 23 +8 - WITA +Z Asia/Jayapura 9:22:48 - LMT 1932 N +9 - +09 1944 S +9:30 - +0930 1964 +9 - WIT +R i 1978 1980 - Mar 21 0 1 - +R i 1978 o - O 21 0 0 - +R i 1979 o - S 19 0 0 - +R i 1980 o - S 23 0 0 - +R i 1991 o - May 3 0 1 - +R i 1992 1995 - Mar 22 0 1 - +R i 1991 1995 - S 22 0 0 - +R i 1996 o - Mar 21 0 1 - +R i 1996 o - S 21 0 0 - +R i 1997 1999 - Mar 22 0 1 - +R i 1997 1999 - S 22 0 0 - +R i 2000 o - Mar 21 0 1 - +R i 2000 o - S 21 0 0 - +R i 2001 2003 - Mar 22 0 1 - +R i 2001 2003 - S 22 0 0 - +R i 2004 o - Mar 21 0 1 - +R i 2004 o - S 21 0 0 - +R i 2005 o - Mar 22 0 1 - +R i 2005 o - S 22 0 0 - +R i 2008 o - Mar 21 0 1 - +R i 2008 o - S 21 0 0 - +R i 2009 2011 - Mar 22 0 1 - +R i 2009 2011 - S 22 0 0 - +R i 2012 o - Mar 21 0 1 - +R i 2012 o - S 21 0 0 - +R i 2013 2015 - Mar 22 0 1 - +R i 2013 2015 - S 22 0 0 - +R i 2016 o - Mar 21 0 1 - +R i 2016 o - S 21 0 0 - +R i 2017 2019 - Mar 22 0 1 - +R i 2017 2019 - S 22 0 0 - +R i 2020 o - Mar 21 0 1 - +R i 2020 o - S 21 0 0 - +R i 2021 2023 - Mar 22 0 1 - +R i 2021 2023 - S 22 0 0 - +R i 2024 o - Mar 21 0 1 - +R i 2024 o - S 21 0 0 - +R i 2025 2027 - Mar 22 0 1 - +R i 2025 2027 - S 22 0 0 - +R i 2028 2029 - Mar 21 0 1 - +R i 2028 2029 - S 21 0 0 - +R i 2030 2031 - Mar 22 0 1 - +R i 2030 2031 - S 22 0 0 - +R i 2032 2033 - Mar 21 0 1 - +R i 2032 2033 - S 21 0 0 - +R i 2034 2035 - Mar 22 0 1 - +R i 2034 2035 - S 22 0 0 - +R i 2036 ma - Mar 21 0 1 - +R i 2036 ma - S 21 0 0 - +Z Asia/Tehran 3:25:44 - LMT 1916 +3:25:44 - TMT 1946 +3:30 - +0330 1977 N +4 i +04/+05 1979 +3:30 i +0330/+0430 +R IQ 1982 o - May 1 0 1 - +R IQ 1982 1984 - O 1 0 0 - +R IQ 1983 o - Mar 31 0 1 - +R IQ 1984 1985 - Ap 1 0 1 - +R IQ 1985 1990 - S lastSun 1s 0 - +R IQ 1986 1990 - Mar lastSun 1s 1 - +R IQ 1991 2007 - Ap 1 3s 1 - +R IQ 1991 2007 - O 1 3s 0 - +Z Asia/Baghdad 2:57:40 - LMT 1890 +2:57:36 - BMT 1918 +3 - +03 1982 May +3 IQ +03/+04 +R Z 1940 o - Jun 1 0 1 D +R Z 1942 1944 - N 1 0 0 S +R Z 1943 o - Ap 1 2 1 D +R Z 1944 o - Ap 1 0 1 D +R Z 1945 o - Ap 16 0 1 D +R Z 1945 o - N 1 2 0 S +R Z 1946 o - Ap 16 2 1 D +R Z 1946 o - N 1 0 0 S +R Z 1948 o - May 23 0 2 DD +R Z 1948 o - S 1 0 1 D +R Z 1948 1949 - N 1 2 0 S +R Z 1949 o - May 1 0 1 D +R Z 1950 o - Ap 16 0 1 D +R Z 1950 o - S 15 3 0 S +R Z 1951 o - Ap 1 0 1 D +R Z 1951 o - N 11 3 0 S +R Z 1952 o - Ap 20 2 1 D +R Z 1952 o - O 19 3 0 S +R Z 1953 o - Ap 12 2 1 D +R Z 1953 o - S 13 3 0 S +R Z 1954 o - Jun 13 0 1 D +R Z 1954 o - S 12 0 0 S +R Z 1955 o - Jun 11 2 1 D +R Z 1955 o - S 11 0 0 S +R Z 1956 o - Jun 3 0 1 D +R Z 1956 o - S 30 3 0 S +R Z 1957 o - Ap 29 2 1 D +R Z 1957 o - S 22 0 0 S +R Z 1974 o - Jul 7 0 1 D +R Z 1974 o - O 13 0 0 S +R Z 1975 o - Ap 20 0 1 D +R Z 1975 o - Au 31 0 0 S +R Z 1985 o - Ap 14 0 1 D +R Z 1985 o - S 15 0 0 S +R Z 1986 o - May 18 0 1 D +R Z 1986 o - S 7 0 0 S +R Z 1987 o - Ap 15 0 1 D +R Z 1987 o - S 13 0 0 S +R Z 1988 o - Ap 10 0 1 D +R Z 1988 o - S 4 0 0 S +R Z 1989 o - Ap 30 0 1 D +R Z 1989 o - S 3 0 0 S +R Z 1990 o - Mar 25 0 1 D +R Z 1990 o - Au 26 0 0 S +R Z 1991 o - Mar 24 0 1 D +R Z 1991 o - S 1 0 0 S +R Z 1992 o - Mar 29 0 1 D +R Z 1992 o - S 6 0 0 S +R Z 1993 o - Ap 2 0 1 D +R Z 1993 o - S 5 0 0 S +R Z 1994 o - Ap 1 0 1 D +R Z 1994 o - Au 28 0 0 S +R Z 1995 o - Mar 31 0 1 D +R Z 1995 o - S 3 0 0 S +R Z 1996 o - Mar 15 0 1 D +R Z 1996 o - S 16 0 0 S +R Z 1997 o - Mar 21 0 1 D +R Z 1997 o - S 14 0 0 S +R Z 1998 o - Mar 20 0 1 D +R Z 1998 o - S 6 0 0 S +R Z 1999 o - Ap 2 2 1 D +R Z 1999 o - S 3 2 0 S +R Z 2000 o - Ap 14 2 1 D +R Z 2000 o - O 6 1 0 S +R Z 2001 o - Ap 9 1 1 D +R Z 2001 o - S 24 1 0 S +R Z 2002 o - Mar 29 1 1 D +R Z 2002 o - O 7 1 0 S +R Z 2003 o - Mar 28 1 1 D +R Z 2003 o - O 3 1 0 S +R Z 2004 o - Ap 7 1 1 D +R Z 2004 o - S 22 1 0 S +R Z 2005 o - Ap 1 2 1 D +R Z 2005 o - O 9 2 0 S +R Z 2006 2010 - Mar F>=26 2 1 D +R Z 2006 o - O 1 2 0 S +R Z 2007 o - S 16 2 0 S +R Z 2008 o - O 5 2 0 S +R Z 2009 o - S 27 2 0 S +R Z 2010 o - S 12 2 0 S +R Z 2011 o - Ap 1 2 1 D +R Z 2011 o - O 2 2 0 S +R Z 2012 o - Mar F>=26 2 1 D +R Z 2012 o - S 23 2 0 S +R Z 2013 ma - Mar F>=23 2 1 D +R Z 2013 ma - O lastSun 2 0 S +Z Asia/Jerusalem 2:20:54 - LMT 1880 +2:20:40 - JMT 1918 +2 Z I%sT +R JP 1948 o - May Sat>=1 24 1 D +R JP 1948 1951 - S Sat>=8 25 0 S +R JP 1949 o - Ap Sat>=1 24 1 D +R JP 1950 1951 - May Sat>=1 24 1 D +Z Asia/Tokyo 9:18:59 - LMT 1887 D 31 15u +9 JP J%sT +R J 1973 o - Jun 6 0 1 S +R J 1973 1975 - O 1 0 0 - +R J 1974 1977 - May 1 0 1 S +R J 1976 o - N 1 0 0 - +R J 1977 o - O 1 0 0 - +R J 1978 o - Ap 30 0 1 S +R J 1978 o - S 30 0 0 - +R J 1985 o - Ap 1 0 1 S +R J 1985 o - O 1 0 0 - +R J 1986 1988 - Ap F>=1 0 1 S +R J 1986 1990 - O F>=1 0 0 - +R J 1989 o - May 8 0 1 S +R J 1990 o - Ap 27 0 1 S +R J 1991 o - Ap 17 0 1 S +R J 1991 o - S 27 0 0 - +R J 1992 o - Ap 10 0 1 S +R J 1992 1993 - O F>=1 0 0 - +R J 1993 1998 - Ap F>=1 0 1 S +R J 1994 o - S F>=15 0 0 - +R J 1995 1998 - S F>=15 0s 0 - +R J 1999 o - Jul 1 0s 1 S +R J 1999 2002 - S lastF 0s 0 - +R J 2000 2001 - Mar lastTh 0s 1 S +R J 2002 2012 - Mar lastTh 24 1 S +R J 2003 o - O 24 0s 0 - +R J 2004 o - O 15 0s 0 - +R J 2005 o - S lastF 0s 0 - +R J 2006 2011 - O lastF 0s 0 - +R J 2013 o - D 20 0 0 - +R J 2014 ma - Mar lastTh 24 1 S +R J 2014 ma - O lastF 0s 0 - +Z Asia/Amman 2:23:44 - LMT 1931 +2 J EE%sT +Z Asia/Almaty 5:7:48 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 R +05/+06 1992 Ja 19 2s +6 R +06/+07 2004 O 31 2s +6 - +06 +Z Asia/Qyzylorda 4:21:52 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1991 S 29 2s +5 R +05/+06 1992 Ja 19 2s +6 R +06/+07 1992 Mar 29 2s +5 R +05/+06 2004 O 31 2s +6 - +06 +Z Asia/Aqtobe 3:48:40 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 2004 O 31 2s +5 - +05 +Z Asia/Aqtau 3:21:4 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 1994 S 25 2s +4 R +04/+05 2004 O 31 2s +5 - +05 +Z Asia/Atyrau 3:27:44 - LMT 1924 May 2 +3 - +03 1930 Jun 21 +5 - +05 1981 O +6 - +06 1982 Ap +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 1999 Mar 28 2s +4 R +04/+05 2004 O 31 2s +5 - +05 +Z Asia/Oral 3:25:24 - LMT 1924 May 2 +3 - +03 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1989 Mar 26 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 1992 Mar 29 2s +4 R +04/+05 2004 O 31 2s +5 - +05 +R KG 1992 1996 - Ap Sun>=7 0s 1 - +R KG 1992 1996 - S lastSun 0 0 - +R KG 1997 2005 - Mar lastSun 2:30 1 - +R KG 1997 2004 - O lastSun 2:30 0 - +Z Asia/Bishkek 4:58:24 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 R +05/+06 1991 Au 31 2 +5 KG +05/+06 2005 Au 12 +6 - +06 +R KR 1948 o - Jun 1 0 1 D +R KR 1948 o - S 13 0 0 S +R KR 1949 o - Ap 3 0 1 D +R KR 1949 1951 - S Sun>=8 0 0 S +R KR 1950 o - Ap 1 0 1 D +R KR 1951 o - May 6 0 1 D +R KR 1955 o - May 5 0 1 D +R KR 1955 o - S 9 0 0 S +R KR 1956 o - May 20 0 1 D +R KR 1956 o - S 30 0 0 S +R KR 1957 1960 - May Sun>=1 0 1 D +R KR 1957 1960 - S Sun>=18 0 0 S +R KR 1987 1988 - May Sun>=8 2 1 D +R KR 1987 1988 - O Sun>=8 3 0 S +Z Asia/Seoul 8:27:52 - LMT 1908 Ap +8:30 - KST 1912 +9 - JST 1945 S 8 +9 - KST 1954 Mar 21 +8:30 KR K%sT 1961 Au 10 +9 KR K%sT +Z Asia/Pyongyang 8:23 - LMT 1908 Ap +8:30 - KST 1912 +9 - JST 1945 Au 24 +9 - KST 2015 Au 15 +8:30 - KST 2018 May 4 23:30 +9 - KST +R l 1920 o - Mar 28 0 1 S +R l 1920 o - O 25 0 0 - +R l 1921 o - Ap 3 0 1 S +R l 1921 o - O 3 0 0 - +R l 1922 o - Mar 26 0 1 S +R l 1922 o - O 8 0 0 - +R l 1923 o - Ap 22 0 1 S +R l 1923 o - S 16 0 0 - +R l 1957 1961 - May 1 0 1 S +R l 1957 1961 - O 1 0 0 - +R l 1972 o - Jun 22 0 1 S +R l 1972 1977 - O 1 0 0 - +R l 1973 1977 - May 1 0 1 S +R l 1978 o - Ap 30 0 1 S +R l 1978 o - S 30 0 0 - +R l 1984 1987 - May 1 0 1 S +R l 1984 1991 - O 16 0 0 - +R l 1988 o - Jun 1 0 1 S +R l 1989 o - May 10 0 1 S +R l 1990 1992 - May 1 0 1 S +R l 1992 o - O 4 0 0 - +R l 1993 ma - Mar lastSun 0 1 S +R l 1993 1998 - S lastSun 0 0 - +R l 1999 ma - O lastSun 0 0 - +Z Asia/Beirut 2:22 - LMT 1880 +2 l EE%sT +R NB 1935 1941 - S 14 0 0:20 - +R NB 1935 1941 - D 14 0 0 - +Z Asia/Kuala_Lumpur 6:46:46 - LMT 1901 +6:55:25 - SMT 1905 Jun +7 - +07 1933 +7 0:20 +0720 1936 +7:20 - +0720 1941 S +7:30 - +0730 1942 F 16 +9 - +09 1945 S 12 +7:30 - +0730 1982 +8 - +08 +Z Asia/Kuching 7:21:20 - LMT 1926 Mar +7:30 - +0730 1933 +8 NB +08/+0820 1942 F 16 +9 - +09 1945 S 12 +8 - +08 +Z Indian/Maldives 4:54 - LMT 1880 +4:54 - MMT 1960 +5 - +05 +R X 1983 1984 - Ap 1 0 1 - +R X 1983 o - O 1 0 0 - +R X 1985 1998 - Mar lastSun 0 1 - +R X 1984 1998 - S lastSun 0 0 - +R X 2001 o - Ap lastSat 2 1 - +R X 2001 2006 - S lastSat 2 0 - +R X 2002 2006 - Mar lastSat 2 1 - +R X 2015 2016 - Mar lastSat 2 1 - +R X 2015 2016 - S lastSat 0 0 - +Z Asia/Hovd 6:6:36 - LMT 1905 Au +6 - +06 1978 +7 X +07/+08 +Z Asia/Ulaanbaatar 7:7:32 - LMT 1905 Au +7 - +07 1978 +8 X +08/+09 +Z Asia/Choibalsan 7:38 - LMT 1905 Au +7 - +07 1978 +8 - +08 1983 Ap +9 X +09/+10 2008 Mar 31 +8 X +08/+09 +Z Asia/Kathmandu 5:41:16 - LMT 1920 +5:30 - +0530 1986 +5:45 - +0545 +R PK 2002 o - Ap Sun>=2 0 1 S +R PK 2002 o - O Sun>=2 0 0 - +R PK 2008 o - Jun 1 0 1 S +R PK 2008 2009 - N 1 0 0 - +R PK 2009 o - Ap 15 0 1 S +Z Asia/Karachi 4:28:12 - LMT 1907 +5:30 - +0530 1942 S +5:30 1 +0630 1945 O 15 +5:30 - +0530 1951 S 30 +5 - +05 1971 Mar 26 +5 PK PK%sT +R P 1999 2005 - Ap F>=15 0 1 S +R P 1999 2003 - O F>=15 0 0 - +R P 2004 o - O 1 1 0 - +R P 2005 o - O 4 2 0 - +R P 2006 2007 - Ap 1 0 1 S +R P 2006 o - S 22 0 0 - +R P 2007 o - S Th>=8 2 0 - +R P 2008 2009 - Mar lastF 0 1 S +R P 2008 o - S 1 0 0 - +R P 2009 o - S F>=1 1 0 - +R P 2010 o - Mar 26 0 1 S +R P 2010 o - Au 11 0 0 - +R P 2011 o - Ap 1 0:1 1 S +R P 2011 o - Au 1 0 0 - +R P 2011 o - Au 30 0 1 S +R P 2011 o - S 30 0 0 - +R P 2012 2014 - Mar lastTh 24 1 S +R P 2012 o - S 21 1 0 - +R P 2013 o - S F>=21 0 0 - +R P 2014 2015 - O F>=21 0 0 - +R P 2015 o - Mar lastF 24 1 S +R P 2016 ma - Mar Sat>=22 1 1 S +R P 2016 ma - O lastSat 1 0 - +Z Asia/Gaza 2:17:52 - LMT 1900 O +2 Z EET/EEST 1948 May 15 +2 K EE%sT 1967 Jun 5 +2 Z I%sT 1996 +2 J EE%sT 1999 +2 P EE%sT 2008 Au 29 +2 - EET 2008 S +2 P EE%sT 2010 +2 - EET 2010 Mar 27 0:1 +2 P EE%sT 2011 Au +2 - EET 2012 +2 P EE%sT +Z Asia/Hebron 2:20:23 - LMT 1900 O +2 Z EET/EEST 1948 May 15 +2 K EE%sT 1967 Jun 5 +2 Z I%sT 1996 +2 J EE%sT 1999 +2 P EE%sT +R PH 1936 o - N 1 0 1 D +R PH 1937 o - F 1 0 0 S +R PH 1954 o - Ap 12 0 1 D +R PH 1954 o - Jul 1 0 0 S +R PH 1978 o - Mar 22 0 1 D +R PH 1978 o - S 21 0 0 S +Z Asia/Manila -15:56 - LMT 1844 D 31 +8:4 - LMT 1899 May 11 +8 PH P%sT 1942 May +9 - JST 1944 N +8 PH P%sT +Z Asia/Qatar 3:26:8 - LMT 1920 +4 - +04 1972 Jun +3 - +03 +Li Asia/Qatar Asia/Bahrain +Z Asia/Riyadh 3:6:52 - LMT 1947 Mar 14 +3 - +03 +Li Asia/Riyadh Asia/Aden +Li Asia/Riyadh Asia/Kuwait +Z Asia/Singapore 6:55:25 - LMT 1901 +6:55:25 - SMT 1905 Jun +7 - +07 1933 +7 0:20 +0720 1936 +7:20 - +0720 1941 S +7:30 - +0730 1942 F 16 +9 - +09 1945 S 12 +7:30 - +0730 1982 +8 - +08 +Z Asia/Colombo 5:19:24 - LMT 1880 +5:19:32 - MMT 1906 +5:30 - +0530 1942 Ja 5 +5:30 0:30 +06 1942 S +5:30 1 +0630 1945 O 16 2 +5:30 - +0530 1996 May 25 +6:30 - +0630 1996 O 26 0:30 +6 - +06 2006 Ap 15 0:30 +5:30 - +0530 +R S 1920 1923 - Ap Sun>=15 2 1 S +R S 1920 1923 - O Sun>=1 2 0 - +R S 1962 o - Ap 29 2 1 S +R S 1962 o - O 1 2 0 - +R S 1963 1965 - May 1 2 1 S +R S 1963 o - S 30 2 0 - +R S 1964 o - O 1 2 0 - +R S 1965 o - S 30 2 0 - +R S 1966 o - Ap 24 2 1 S +R S 1966 1976 - O 1 2 0 - +R S 1967 1978 - May 1 2 1 S +R S 1977 1978 - S 1 2 0 - +R S 1983 1984 - Ap 9 2 1 S +R S 1983 1984 - O 1 2 0 - +R S 1986 o - F 16 2 1 S +R S 1986 o - O 9 2 0 - +R S 1987 o - Mar 1 2 1 S +R S 1987 1988 - O 31 2 0 - +R S 1988 o - Mar 15 2 1 S +R S 1989 o - Mar 31 2 1 S +R S 1989 o - O 1 2 0 - +R S 1990 o - Ap 1 2 1 S +R S 1990 o - S 30 2 0 - +R S 1991 o - Ap 1 0 1 S +R S 1991 1992 - O 1 0 0 - +R S 1992 o - Ap 8 0 1 S +R S 1993 o - Mar 26 0 1 S +R S 1993 o - S 25 0 0 - +R S 1994 1996 - Ap 1 0 1 S +R S 1994 2005 - O 1 0 0 - +R S 1997 1998 - Mar lastM 0 1 S +R S 1999 2006 - Ap 1 0 1 S +R S 2006 o - S 22 0 0 - +R S 2007 o - Mar lastF 0 1 S +R S 2007 o - N F>=1 0 0 - +R S 2008 o - Ap F>=1 0 1 S +R S 2008 o - N 1 0 0 - +R S 2009 o - Mar lastF 0 1 S +R S 2010 2011 - Ap F>=1 0 1 S +R S 2012 ma - Mar lastF 0 1 S +R S 2009 ma - O lastF 0 0 - +Z Asia/Damascus 2:25:12 - LMT 1920 +2 S EE%sT +Z Asia/Dushanbe 4:35:12 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 1 +05/+06 1991 S 9 2s +5 - +05 +Z Asia/Bangkok 6:42:4 - LMT 1880 +6:42:4 - BMT 1920 Ap +7 - +07 +Li Asia/Bangkok Asia/Phnom_Penh +Li Asia/Bangkok Asia/Vientiane +Z Asia/Ashgabat 3:53:32 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 R +05/+06 1991 Mar 31 2 +4 R +04/+05 1992 Ja 19 2 +5 - +05 +Z Asia/Dubai 3:41:12 - LMT 1920 +4 - +04 +Li Asia/Dubai Asia/Muscat +Z Asia/Samarkand 4:27:53 - LMT 1924 May 2 +4 - +04 1930 Jun 21 +5 - +05 1981 Ap +5 1 +06 1981 O +6 - +06 1982 Ap +5 R +05/+06 1992 +5 - +05 +Z Asia/Tashkent 4:37:11 - LMT 1924 May 2 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2 +5 R +05/+06 1992 +5 - +05 +Z Asia/Ho_Chi_Minh 7:6:40 - LMT 1906 Jul +7:6:30 - PLMT 1911 May +7 - +07 1942 D 31 23 +8 - +08 1945 Mar 14 23 +9 - +09 1945 S 2 +7 - +07 1947 Ap +8 - +08 1955 Jul +7 - +07 1959 D 31 23 +8 - +08 1975 Jun 13 +7 - +07 +R AU 1917 o - Ja 1 0:1 1 D +R AU 1917 o - Mar 25 2 0 S +R AU 1942 o - Ja 1 2 1 D +R AU 1942 o - Mar 29 2 0 S +R AU 1942 o - S 27 2 1 D +R AU 1943 1944 - Mar lastSun 2 0 S +R AU 1943 o - O 3 2 1 D +Z Australia/Darwin 8:43:20 - LMT 1895 F +9 - ACST 1899 May +9:30 AU AC%sT +R AW 1974 o - O lastSun 2s 1 D +R AW 1975 o - Mar Sun>=1 2s 0 S +R AW 1983 o - O lastSun 2s 1 D +R AW 1984 o - Mar Sun>=1 2s 0 S +R AW 1991 o - N 17 2s 1 D +R AW 1992 o - Mar Sun>=1 2s 0 S +R AW 2006 o - D 3 2s 1 D +R AW 2007 2009 - Mar lastSun 2s 0 S +R AW 2007 2008 - O lastSun 2s 1 D +Z Australia/Perth 7:43:24 - LMT 1895 D +8 AU AW%sT 1943 Jul +8 AW AW%sT +Z Australia/Eucla 8:35:28 - LMT 1895 D +8:45 AU +0845/+0945 1943 Jul +8:45 AW +0845/+0945 +R AQ 1971 o - O lastSun 2s 1 D +R AQ 1972 o - F lastSun 2s 0 S +R AQ 1989 1991 - O lastSun 2s 1 D +R AQ 1990 1992 - Mar Sun>=1 2s 0 S +R Ho 1992 1993 - O lastSun 2s 1 D +R Ho 1993 1994 - Mar Sun>=1 2s 0 S +Z Australia/Brisbane 10:12:8 - LMT 1895 +10 AU AE%sT 1971 +10 AQ AE%sT +Z Australia/Lindeman 9:55:56 - LMT 1895 +10 AU AE%sT 1971 +10 AQ AE%sT 1992 Jul +10 Ho AE%sT +R AS 1971 1985 - O lastSun 2s 1 D +R AS 1986 o - O 19 2s 1 D +R AS 1987 2007 - O lastSun 2s 1 D +R AS 1972 o - F 27 2s 0 S +R AS 1973 1985 - Mar Sun>=1 2s 0 S +R AS 1986 1990 - Mar Sun>=15 2s 0 S +R AS 1991 o - Mar 3 2s 0 S +R AS 1992 o - Mar 22 2s 0 S +R AS 1993 o - Mar 7 2s 0 S +R AS 1994 o - Mar 20 2s 0 S +R AS 1995 2005 - Mar lastSun 2s 0 S +R AS 2006 o - Ap 2 2s 0 S +R AS 2007 o - Mar lastSun 2s 0 S +R AS 2008 ma - Ap Sun>=1 2s 0 S +R AS 2008 ma - O Sun>=1 2s 1 D +Z Australia/Adelaide 9:14:20 - LMT 1895 F +9 - ACST 1899 May +9:30 AU AC%sT 1971 +9:30 AS AC%sT +R AT 1967 o - O Sun>=1 2s 1 D +R AT 1968 o - Mar lastSun 2s 0 S +R AT 1968 1985 - O lastSun 2s 1 D +R AT 1969 1971 - Mar Sun>=8 2s 0 S +R AT 1972 o - F lastSun 2s 0 S +R AT 1973 1981 - Mar Sun>=1 2s 0 S +R AT 1982 1983 - Mar lastSun 2s 0 S +R AT 1984 1986 - Mar Sun>=1 2s 0 S +R AT 1986 o - O Sun>=15 2s 1 D +R AT 1987 1990 - Mar Sun>=15 2s 0 S +R AT 1987 o - O Sun>=22 2s 1 D +R AT 1988 1990 - O lastSun 2s 1 D +R AT 1991 1999 - O Sun>=1 2s 1 D +R AT 1991 2005 - Mar lastSun 2s 0 S +R AT 2000 o - Au lastSun 2s 1 D +R AT 2001 ma - O Sun>=1 2s 1 D +R AT 2006 o - Ap Sun>=1 2s 0 S +R AT 2007 o - Mar lastSun 2s 0 S +R AT 2008 ma - Ap Sun>=1 2s 0 S +Z Australia/Hobart 9:49:16 - LMT 1895 S +10 - AEST 1916 O 1 2 +10 1 AEDT 1917 F +10 AU AE%sT 1967 +10 AT AE%sT +Z Australia/Currie 9:35:28 - LMT 1895 S +10 - AEST 1916 O 1 2 +10 1 AEDT 1917 F +10 AU AE%sT 1971 Jul +10 AT AE%sT +R AV 1971 1985 - O lastSun 2s 1 D +R AV 1972 o - F lastSun 2s 0 S +R AV 1973 1985 - Mar Sun>=1 2s 0 S +R AV 1986 1990 - Mar Sun>=15 2s 0 S +R AV 1986 1987 - O Sun>=15 2s 1 D +R AV 1988 1999 - O lastSun 2s 1 D +R AV 1991 1994 - Mar Sun>=1 2s 0 S +R AV 1995 2005 - Mar lastSun 2s 0 S +R AV 2000 o - Au lastSun 2s 1 D +R AV 2001 2007 - O lastSun 2s 1 D +R AV 2006 o - Ap Sun>=1 2s 0 S +R AV 2007 o - Mar lastSun 2s 0 S +R AV 2008 ma - Ap Sun>=1 2s 0 S +R AV 2008 ma - O Sun>=1 2s 1 D +Z Australia/Melbourne 9:39:52 - LMT 1895 F +10 AU AE%sT 1971 +10 AV AE%sT +R AN 1971 1985 - O lastSun 2s 1 D +R AN 1972 o - F 27 2s 0 S +R AN 1973 1981 - Mar Sun>=1 2s 0 S +R AN 1982 o - Ap Sun>=1 2s 0 S +R AN 1983 1985 - Mar Sun>=1 2s 0 S +R AN 1986 1989 - Mar Sun>=15 2s 0 S +R AN 1986 o - O 19 2s 1 D +R AN 1987 1999 - O lastSun 2s 1 D +R AN 1990 1995 - Mar Sun>=1 2s 0 S +R AN 1996 2005 - Mar lastSun 2s 0 S +R AN 2000 o - Au lastSun 2s 1 D +R AN 2001 2007 - O lastSun 2s 1 D +R AN 2006 o - Ap Sun>=1 2s 0 S +R AN 2007 o - Mar lastSun 2s 0 S +R AN 2008 ma - Ap Sun>=1 2s 0 S +R AN 2008 ma - O Sun>=1 2s 1 D +Z Australia/Sydney 10:4:52 - LMT 1895 F +10 AU AE%sT 1971 +10 AN AE%sT +Z Australia/Broken_Hill 9:25:48 - LMT 1895 F +10 - AEST 1896 Au 23 +9 - ACST 1899 May +9:30 AU AC%sT 1971 +9:30 AN AC%sT 2000 +9:30 AS AC%sT +R LH 1981 1984 - O lastSun 2 1 - +R LH 1982 1985 - Mar Sun>=1 2 0 - +R LH 1985 o - O lastSun 2 0:30 - +R LH 1986 1989 - Mar Sun>=15 2 0 - +R LH 1986 o - O 19 2 0:30 - +R LH 1987 1999 - O lastSun 2 0:30 - +R LH 1990 1995 - Mar Sun>=1 2 0 - +R LH 1996 2005 - Mar lastSun 2 0 - +R LH 2000 o - Au lastSun 2 0:30 - +R LH 2001 2007 - O lastSun 2 0:30 - +R LH 2006 o - Ap Sun>=1 2 0 - +R LH 2007 o - Mar lastSun 2 0 - +R LH 2008 ma - Ap Sun>=1 2 0 - +R LH 2008 ma - O Sun>=1 2 0:30 - +Z Australia/Lord_Howe 10:36:20 - LMT 1895 F +10 - AEST 1981 Mar +10:30 LH +1030/+1130 1985 Jul +10:30 LH +1030/+11 +Z Antarctica/Macquarie 0 - -00 1899 N +10 - AEST 1916 O 1 2 +10 1 AEDT 1917 F +10 AU AE%sT 1919 Ap 1 0s +0 - -00 1948 Mar 25 +10 AU AE%sT 1967 +10 AT AE%sT 2010 Ap 4 3 +11 - +11 +Z Indian/Christmas 7:2:52 - LMT 1895 F +7 - +07 +Z Indian/Cocos 6:27:40 - LMT 1900 +6:30 - +0630 +R FJ 1998 1999 - N Sun>=1 2 1 - +R FJ 1999 2000 - F lastSun 3 0 - +R FJ 2009 o - N 29 2 1 - +R FJ 2010 o - Mar lastSun 3 0 - +R FJ 2010 2013 - O Sun>=21 2 1 - +R FJ 2011 o - Mar Sun>=1 3 0 - +R FJ 2012 2013 - Ja Sun>=18 3 0 - +R FJ 2014 o - Ja Sun>=18 2 0 - +R FJ 2014 ma - N Sun>=1 2 1 - +R FJ 2015 ma - Ja Sun>=13 3 0 - +Z Pacific/Fiji 11:55:44 - LMT 1915 O 26 +12 FJ +12/+13 +Z Pacific/Gambier -8:59:48 - LMT 1912 O +-9 - -09 +Z Pacific/Marquesas -9:18 - LMT 1912 O +-9:30 - -0930 +Z Pacific/Tahiti -9:58:16 - LMT 1912 O +-10 - -10 +Z Pacific/Guam -14:21 - LMT 1844 D 31 +9:39 - LMT 1901 +10 - GST 2000 D 23 +10 - ChST +Li Pacific/Guam Pacific/Saipan +Z Pacific/Tarawa 11:32:4 - LMT 1901 +12 - +12 +Z Pacific/Enderbury -11:24:20 - LMT 1901 +-12 - -12 1979 O +-11 - -11 1994 D 31 +13 - +13 +Z Pacific/Kiritimati -10:29:20 - LMT 1901 +-10:40 - -1040 1979 O +-10 - -10 1994 D 31 +14 - +14 +Z Pacific/Majuro 11:24:48 - LMT 1901 +11 - +11 1969 O +12 - +12 +Z Pacific/Kwajalein 11:9:20 - LMT 1901 +11 - +11 1969 O +-12 - -12 1993 Au 20 +12 - +12 +Z Pacific/Chuuk 10:7:8 - LMT 1901 +10 - +10 +Z Pacific/Pohnpei 10:32:52 - LMT 1901 +11 - +11 +Z Pacific/Kosrae 10:51:56 - LMT 1901 +11 - +11 1969 O +12 - +12 1999 +11 - +11 +Z Pacific/Nauru 11:7:40 - LMT 1921 Ja 15 +11:30 - +1130 1942 Mar 15 +9 - +09 1944 Au 15 +11:30 - +1130 1979 May +12 - +12 +R NC 1977 1978 - D Sun>=1 0 1 - +R NC 1978 1979 - F 27 0 0 - +R NC 1996 o - D 1 2s 1 - +R NC 1997 o - Mar 2 2s 0 - +Z Pacific/Noumea 11:5:48 - LMT 1912 Ja 13 +11 NC +11/+12 +R NZ 1927 o - N 6 2 1 S +R NZ 1928 o - Mar 4 2 0 M +R NZ 1928 1933 - O Sun>=8 2 0:30 S +R NZ 1929 1933 - Mar Sun>=15 2 0 M +R NZ 1934 1940 - Ap lastSun 2 0 M +R NZ 1934 1940 - S lastSun 2 0:30 S +R NZ 1946 o - Ja 1 0 0 S +R NZ 1974 o - N Sun>=1 2s 1 D +R k 1974 o - N Sun>=1 2:45s 1 - +R NZ 1975 o - F lastSun 2s 0 S +R k 1975 o - F lastSun 2:45s 0 - +R NZ 1975 1988 - O lastSun 2s 1 D +R k 1975 1988 - O lastSun 2:45s 1 - +R NZ 1976 1989 - Mar Sun>=1 2s 0 S +R k 1976 1989 - Mar Sun>=1 2:45s 0 - +R NZ 1989 o - O Sun>=8 2s 1 D +R k 1989 o - O Sun>=8 2:45s 1 - +R NZ 1990 2006 - O Sun>=1 2s 1 D +R k 1990 2006 - O Sun>=1 2:45s 1 - +R NZ 1990 2007 - Mar Sun>=15 2s 0 S +R k 1990 2007 - Mar Sun>=15 2:45s 0 - +R NZ 2007 ma - S lastSun 2s 1 D +R k 2007 ma - S lastSun 2:45s 1 - +R NZ 2008 ma - Ap Sun>=1 2s 0 S +R k 2008 ma - Ap Sun>=1 2:45s 0 - +Z Pacific/Auckland 11:39:4 - LMT 1868 N 2 +11:30 NZ NZ%sT 1946 +12 NZ NZ%sT +Z Pacific/Chatham 12:13:48 - LMT 1868 N 2 +12:15 - +1215 1946 +12:45 k +1245/+1345 +Li Pacific/Auckland Antarctica/McMurdo +R CK 1978 o - N 12 0 0:30 - +R CK 1979 1991 - Mar Sun>=1 0 0 - +R CK 1979 1990 - O lastSun 0 0:30 - +Z Pacific/Rarotonga -10:39:4 - LMT 1901 +-10:30 - -1030 1978 N 12 +-10 CK -10/-0930 +Z Pacific/Niue -11:19:40 - LMT 1901 +-11:20 - -1120 1951 +-11:30 - -1130 1978 O +-11 - -11 +Z Pacific/Norfolk 11:11:52 - LMT 1901 +11:12 - +1112 1951 +11:30 - +1130 1974 O 27 2 +11:30 1 +1230 1975 Mar 2 2 +11:30 - +1130 2015 O 4 2 +11 - +11 +Z Pacific/Palau 8:57:56 - LMT 1901 +9 - +09 +Z Pacific/Port_Moresby 9:48:40 - LMT 1880 +9:48:32 - PMMT 1895 +10 - +10 +Z Pacific/Bougainville 10:22:16 - LMT 1880 +9:48:32 - PMMT 1895 +10 - +10 1942 Jul +9 - +09 1945 Au 21 +10 - +10 2014 D 28 2 +11 - +11 +Z Pacific/Pitcairn -8:40:20 - LMT 1901 +-8:30 - -0830 1998 Ap 27 +-8 - -08 +Z Pacific/Pago_Pago 12:37:12 - LMT 1892 Jul 5 +-11:22:48 - LMT 1911 +-11 - SST +Li Pacific/Pago_Pago Pacific/Midway +R WS 2010 o - S lastSun 0 1 - +R WS 2011 o - Ap Sat>=1 4 0 - +R WS 2011 o - S lastSat 3 1 - +R WS 2012 ma - Ap Sun>=1 4 0 - +R WS 2012 ma - S lastSun 3 1 - +Z Pacific/Apia 12:33:4 - LMT 1892 Jul 5 +-11:26:56 - LMT 1911 +-11:30 - -1130 1950 +-11 WS -11/-10 2011 D 29 24 +13 WS +13/+14 +Z Pacific/Guadalcanal 10:39:48 - LMT 1912 O +11 - +11 +Z Pacific/Fakaofo -11:24:56 - LMT 1901 +-11 - -11 2011 D 30 +13 - +13 +R TO 1999 o - O 7 2s 1 - +R TO 2000 o - Mar 19 2s 0 - +R TO 2000 2001 - N Sun>=1 2 1 - +R TO 2001 2002 - Ja lastSun 2 0 - +R TO 2016 o - N Sun>=1 2 1 - +R TO 2017 o - Ja Sun>=15 3 0 - +Z Pacific/Tongatapu 12:19:20 - LMT 1901 +12:20 - +1220 1941 +13 - +13 1999 +13 TO +13/+14 +Z Pacific/Funafuti 11:56:52 - LMT 1901 +12 - +12 +Z Pacific/Wake 11:6:28 - LMT 1901 +12 - +12 +R VU 1983 o - S 25 0 1 - +R VU 1984 1991 - Mar Sun>=23 0 0 - +R VU 1984 o - O 23 0 1 - +R VU 1985 1991 - S Sun>=23 0 1 - +R VU 1992 1993 - Ja Sun>=23 0 0 - +R VU 1992 o - O Sun>=23 0 1 - +Z Pacific/Efate 11:13:16 - LMT 1912 Ja 13 +11 VU +11/+12 +Z Pacific/Wallis 12:15:20 - LMT 1901 +12 - +12 +R G 1916 o - May 21 2s 1 BST +R G 1916 o - O 1 2s 0 GMT +R G 1917 o - Ap 8 2s 1 BST +R G 1917 o - S 17 2s 0 GMT +R G 1918 o - Mar 24 2s 1 BST +R G 1918 o - S 30 2s 0 GMT +R G 1919 o - Mar 30 2s 1 BST +R G 1919 o - S 29 2s 0 GMT +R G 1920 o - Mar 28 2s 1 BST +R G 1920 o - O 25 2s 0 GMT +R G 1921 o - Ap 3 2s 1 BST +R G 1921 o - O 3 2s 0 GMT +R G 1922 o - Mar 26 2s 1 BST +R G 1922 o - O 8 2s 0 GMT +R G 1923 o - Ap Sun>=16 2s 1 BST +R G 1923 1924 - S Sun>=16 2s 0 GMT +R G 1924 o - Ap Sun>=9 2s 1 BST +R G 1925 1926 - Ap Sun>=16 2s 1 BST +R G 1925 1938 - O Sun>=2 2s 0 GMT +R G 1927 o - Ap Sun>=9 2s 1 BST +R G 1928 1929 - Ap Sun>=16 2s 1 BST +R G 1930 o - Ap Sun>=9 2s 1 BST +R G 1931 1932 - Ap Sun>=16 2s 1 BST +R G 1933 o - Ap Sun>=9 2s 1 BST +R G 1934 o - Ap Sun>=16 2s 1 BST +R G 1935 o - Ap Sun>=9 2s 1 BST +R G 1936 1937 - Ap Sun>=16 2s 1 BST +R G 1938 o - Ap Sun>=9 2s 1 BST +R G 1939 o - Ap Sun>=16 2s 1 BST +R G 1939 o - N Sun>=16 2s 0 GMT +R G 1940 o - F Sun>=23 2s 1 BST +R G 1941 o - May Sun>=2 1s 2 BDST +R G 1941 1943 - Au Sun>=9 1s 1 BST +R G 1942 1944 - Ap Sun>=2 1s 2 BDST +R G 1944 o - S Sun>=16 1s 1 BST +R G 1945 o - Ap M>=2 1s 2 BDST +R G 1945 o - Jul Sun>=9 1s 1 BST +R G 1945 1946 - O Sun>=2 2s 0 GMT +R G 1946 o - Ap Sun>=9 2s 1 BST +R G 1947 o - Mar 16 2s 1 BST +R G 1947 o - Ap 13 1s 2 BDST +R G 1947 o - Au 10 1s 1 BST +R G 1947 o - N 2 2s 0 GMT +R G 1948 o - Mar 14 2s 1 BST +R G 1948 o - O 31 2s 0 GMT +R G 1949 o - Ap 3 2s 1 BST +R G 1949 o - O 30 2s 0 GMT +R G 1950 1952 - Ap Sun>=14 2s 1 BST +R G 1950 1952 - O Sun>=21 2s 0 GMT +R G 1953 o - Ap Sun>=16 2s 1 BST +R G 1953 1960 - O Sun>=2 2s 0 GMT +R G 1954 o - Ap Sun>=9 2s 1 BST +R G 1955 1956 - Ap Sun>=16 2s 1 BST +R G 1957 o - Ap Sun>=9 2s 1 BST +R G 1958 1959 - Ap Sun>=16 2s 1 BST +R G 1960 o - Ap Sun>=9 2s 1 BST +R G 1961 1963 - Mar lastSun 2s 1 BST +R G 1961 1968 - O Sun>=23 2s 0 GMT +R G 1964 1967 - Mar Sun>=19 2s 1 BST +R G 1968 o - F 18 2s 1 BST +R G 1972 1980 - Mar Sun>=16 2s 1 BST +R G 1972 1980 - O Sun>=23 2s 0 GMT +R G 1981 1995 - Mar lastSun 1u 1 BST +R G 1981 1989 - O Sun>=23 1u 0 GMT +R G 1990 1995 - O Sun>=22 1u 0 GMT +Z Europe/London -0:1:15 - LMT 1847 D 1 0s +0 G %s 1968 O 27 +1 - BST 1971 O 31 2u +0 G %s 1996 +0 E GMT/BST +Li Europe/London Europe/Jersey +Li Europe/London Europe/Guernsey +Li Europe/London Europe/Isle_of_Man +R IE 1971 o - O 31 2u -1 - +R IE 1972 1980 - Mar Sun>=16 2u 0 - +R IE 1972 1980 - O Sun>=23 2u -1 - +R IE 1981 ma - Mar lastSun 1u 0 - +R IE 1981 1989 - O Sun>=23 1u -1 - +R IE 1990 1995 - O Sun>=22 1u -1 - +R IE 1996 ma - O lastSun 1u -1 - +Z Europe/Dublin -0:25 - LMT 1880 Au 2 +-0:25:21 - DMT 1916 May 21 2s +-0:25:21 1 IST 1916 O 1 2s +0 G %s 1921 D 6 +0 G GMT/IST 1940 F 25 2s +0 1 IST 1946 O 6 2s +0 - GMT 1947 Mar 16 2s +0 1 IST 1947 N 2 2s +0 - GMT 1948 Ap 18 2s +0 G GMT/IST 1968 O 27 +1 IE IST/GMT +R E 1977 1980 - Ap Sun>=1 1u 1 S +R E 1977 o - S lastSun 1u 0 - +R E 1978 o - O 1 1u 0 - +R E 1979 1995 - S lastSun 1u 0 - +R E 1981 ma - Mar lastSun 1u 1 S +R E 1996 ma - O lastSun 1u 0 - +R W- 1977 1980 - Ap Sun>=1 1s 1 S +R W- 1977 o - S lastSun 1s 0 - +R W- 1978 o - O 1 1s 0 - +R W- 1979 1995 - S lastSun 1s 0 - +R W- 1981 ma - Mar lastSun 1s 1 S +R W- 1996 ma - O lastSun 1s 0 - +R c 1916 o - Ap 30 23 1 S +R c 1916 o - O 1 1 0 - +R c 1917 1918 - Ap M>=15 2s 1 S +R c 1917 1918 - S M>=15 2s 0 - +R c 1940 o - Ap 1 2s 1 S +R c 1942 o - N 2 2s 0 - +R c 1943 o - Mar 29 2s 1 S +R c 1943 o - O 4 2s 0 - +R c 1944 1945 - Ap M>=1 2s 1 S +R c 1944 o - O 2 2s 0 - +R c 1945 o - S 16 2s 0 - +R c 1977 1980 - Ap Sun>=1 2s 1 S +R c 1977 o - S lastSun 2s 0 - +R c 1978 o - O 1 2s 0 - +R c 1979 1995 - S lastSun 2s 0 - +R c 1981 ma - Mar lastSun 2s 1 S +R c 1996 ma - O lastSun 2s 0 - +R e 1977 1980 - Ap Sun>=1 0 1 S +R e 1977 o - S lastSun 0 0 - +R e 1978 o - O 1 0 0 - +R e 1979 1995 - S lastSun 0 0 - +R e 1981 ma - Mar lastSun 0 1 S +R e 1996 ma - O lastSun 0 0 - +R R 1917 o - Jul 1 23 1 MST +R R 1917 o - D 28 0 0 MMT +R R 1918 o - May 31 22 2 MDST +R R 1918 o - S 16 1 1 MST +R R 1919 o - May 31 23 2 MDST +R R 1919 o - Jul 1 0u 1 MSD +R R 1919 o - Au 16 0 0 MSK +R R 1921 o - F 14 23 1 MSD +R R 1921 o - Mar 20 23 2 +05 +R R 1921 o - S 1 0 1 MSD +R R 1921 o - O 1 0 0 - +R R 1981 1984 - Ap 1 0 1 S +R R 1981 1983 - O 1 0 0 - +R R 1984 1995 - S lastSun 2s 0 - +R R 1985 2010 - Mar lastSun 2s 1 S +R R 1996 2010 - O lastSun 2s 0 - +Z WET 0 E WE%sT +Z CET 1 c CE%sT +Z MET 1 c ME%sT +Z EET 2 E EE%sT +R q 1940 o - Jun 16 0 1 S +R q 1942 o - N 2 3 0 - +R q 1943 o - Mar 29 2 1 S +R q 1943 o - Ap 10 3 0 - +R q 1974 o - May 4 0 1 S +R q 1974 o - O 2 0 0 - +R q 1975 o - May 1 0 1 S +R q 1975 o - O 2 0 0 - +R q 1976 o - May 2 0 1 S +R q 1976 o - O 3 0 0 - +R q 1977 o - May 8 0 1 S +R q 1977 o - O 2 0 0 - +R q 1978 o - May 6 0 1 S +R q 1978 o - O 1 0 0 - +R q 1979 o - May 5 0 1 S +R q 1979 o - S 30 0 0 - +R q 1980 o - May 3 0 1 S +R q 1980 o - O 4 0 0 - +R q 1981 o - Ap 26 0 1 S +R q 1981 o - S 27 0 0 - +R q 1982 o - May 2 0 1 S +R q 1982 o - O 3 0 0 - +R q 1983 o - Ap 18 0 1 S +R q 1983 o - O 1 0 0 - +R q 1984 o - Ap 1 0 1 S +Z Europe/Tirane 1:19:20 - LMT 1914 +1 - CET 1940 Jun 16 +1 q CE%sT 1984 Jul +1 E CE%sT +Z Europe/Andorra 0:6:4 - LMT 1901 +0 - WET 1946 S 30 +1 - CET 1985 Mar 31 2 +1 E CE%sT +R a 1920 o - Ap 5 2s 1 S +R a 1920 o - S 13 2s 0 - +R a 1946 o - Ap 14 2s 1 S +R a 1946 1948 - O Sun>=1 2s 0 - +R a 1947 o - Ap 6 2s 1 S +R a 1948 o - Ap 18 2s 1 S +R a 1980 o - Ap 6 0 1 S +R a 1980 o - S 28 0 0 - +Z Europe/Vienna 1:5:21 - LMT 1893 Ap +1 c CE%sT 1920 +1 a CE%sT 1940 Ap 1 2s +1 c CE%sT 1945 Ap 2 2s +1 1 CEST 1945 Ap 12 2s +1 - CET 1946 +1 a CE%sT 1981 +1 E CE%sT +Z Europe/Minsk 1:50:16 - LMT 1880 +1:50 - MMT 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 Jun 28 +1 c CE%sT 1944 Jul 3 +3 R MSK/MSD 1990 +3 - MSK 1991 Mar 31 2s +2 R EE%sT 2011 Mar 27 2s +3 - +03 +R b 1918 o - Mar 9 0s 1 S +R b 1918 1919 - O Sat>=1 23s 0 - +R b 1919 o - Mar 1 23s 1 S +R b 1920 o - F 14 23s 1 S +R b 1920 o - O 23 23s 0 - +R b 1921 o - Mar 14 23s 1 S +R b 1921 o - O 25 23s 0 - +R b 1922 o - Mar 25 23s 1 S +R b 1922 1927 - O Sat>=1 23s 0 - +R b 1923 o - Ap 21 23s 1 S +R b 1924 o - Mar 29 23s 1 S +R b 1925 o - Ap 4 23s 1 S +R b 1926 o - Ap 17 23s 1 S +R b 1927 o - Ap 9 23s 1 S +R b 1928 o - Ap 14 23s 1 S +R b 1928 1938 - O Sun>=2 2s 0 - +R b 1929 o - Ap 21 2s 1 S +R b 1930 o - Ap 13 2s 1 S +R b 1931 o - Ap 19 2s 1 S +R b 1932 o - Ap 3 2s 1 S +R b 1933 o - Mar 26 2s 1 S +R b 1934 o - Ap 8 2s 1 S +R b 1935 o - Mar 31 2s 1 S +R b 1936 o - Ap 19 2s 1 S +R b 1937 o - Ap 4 2s 1 S +R b 1938 o - Mar 27 2s 1 S +R b 1939 o - Ap 16 2s 1 S +R b 1939 o - N 19 2s 0 - +R b 1940 o - F 25 2s 1 S +R b 1944 o - S 17 2s 0 - +R b 1945 o - Ap 2 2s 1 S +R b 1945 o - S 16 2s 0 - +R b 1946 o - May 19 2s 1 S +R b 1946 o - O 7 2s 0 - +Z Europe/Brussels 0:17:30 - LMT 1880 +0:17:30 - BMT 1892 May 1 12 +0 - WET 1914 N 8 +1 - CET 1916 May +1 c CE%sT 1918 N 11 11u +0 b WE%sT 1940 May 20 2s +1 c CE%sT 1944 S 3 +1 b CE%sT 1977 +1 E CE%sT +R BG 1979 o - Mar 31 23 1 S +R BG 1979 o - O 1 1 0 - +R BG 1980 1982 - Ap Sat>=1 23 1 S +R BG 1980 o - S 29 1 0 - +R BG 1981 o - S 27 2 0 - +Z Europe/Sofia 1:33:16 - LMT 1880 +1:56:56 - IMT 1894 N 30 +2 - EET 1942 N 2 3 +1 c CE%sT 1945 +1 - CET 1945 Ap 2 3 +2 - EET 1979 Mar 31 23 +2 BG EE%sT 1982 S 26 3 +2 c EE%sT 1991 +2 e EE%sT 1997 +2 E EE%sT +R CZ 1945 o - Ap M>=1 2s 1 S +R CZ 1945 o - O 1 2s 0 - +R CZ 1946 o - May 6 2s 1 S +R CZ 1946 1949 - O Sun>=1 2s 0 - +R CZ 1947 1948 - Ap Sun>=15 2s 1 S +R CZ 1949 o - Ap 9 2s 1 S +Z Europe/Prague 0:57:44 - LMT 1850 +0:57:44 - PMT 1891 O +1 c CE%sT 1945 May 9 +1 CZ CE%sT 1946 D 1 3 +1 -1 GMT 1947 F 23 2 +1 CZ CE%sT 1979 +1 E CE%sT +R D 1916 o - May 14 23 1 S +R D 1916 o - S 30 23 0 - +R D 1940 o - May 15 0 1 S +R D 1945 o - Ap 2 2s 1 S +R D 1945 o - Au 15 2s 0 - +R D 1946 o - May 1 2s 1 S +R D 1946 o - S 1 2s 0 - +R D 1947 o - May 4 2s 1 S +R D 1947 o - Au 10 2s 0 - +R D 1948 o - May 9 2s 1 S +R D 1948 o - Au 8 2s 0 - +Z Europe/Copenhagen 0:50:20 - LMT 1890 +0:50:20 - CMT 1894 +1 D CE%sT 1942 N 2 2s +1 c CE%sT 1945 Ap 2 2 +1 D CE%sT 1980 +1 E CE%sT +Z Atlantic/Faroe -0:27:4 - LMT 1908 Ja 11 +0 - WET 1981 +0 E WE%sT +R Th 1991 1992 - Mar lastSun 2 1 D +R Th 1991 1992 - S lastSun 2 0 S +R Th 1993 2006 - Ap Sun>=1 2 1 D +R Th 1993 2006 - O lastSun 2 0 S +R Th 2007 ma - Mar Sun>=8 2 1 D +R Th 2007 ma - N Sun>=1 2 0 S +Z America/Danmarkshavn -1:14:40 - LMT 1916 Jul 28 +-3 - -03 1980 Ap 6 2 +-3 E -03/-02 1996 +0 - GMT +Z America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 +-2 - -02 1980 Ap 6 2 +-2 c -02/-01 1981 Mar 29 +-1 E -01/+00 +Z America/Godthab -3:26:56 - LMT 1916 Jul 28 +-3 - -03 1980 Ap 6 2 +-3 E -03/-02 +Z America/Thule -4:35:8 - LMT 1916 Jul 28 +-4 Th A%sT +Z Europe/Tallinn 1:39 - LMT 1880 +1:39 - TMT 1918 F +1 c CE%sT 1919 Jul +1:39 - TMT 1921 May +2 - EET 1940 Au 6 +3 - MSK 1941 S 15 +1 c CE%sT 1944 S 22 +3 R MSK/MSD 1989 Mar 26 2s +2 1 EEST 1989 S 24 2s +2 c EE%sT 1998 S 22 +2 E EE%sT 1999 O 31 4 +2 - EET 2002 F 21 +2 E EE%sT +R FI 1942 o - Ap 2 24 1 S +R FI 1942 o - O 4 1 0 - +R FI 1981 1982 - Mar lastSun 2 1 S +R FI 1981 1982 - S lastSun 3 0 - +Z Europe/Helsinki 1:39:49 - LMT 1878 May 31 +1:39:49 - HMT 1921 May +2 FI EE%sT 1983 +2 E EE%sT +Li Europe/Helsinki Europe/Mariehamn +R F 1916 o - Jun 14 23s 1 S +R F 1916 1919 - O Sun>=1 23s 0 - +R F 1917 o - Mar 24 23s 1 S +R F 1918 o - Mar 9 23s 1 S +R F 1919 o - Mar 1 23s 1 S +R F 1920 o - F 14 23s 1 S +R F 1920 o - O 23 23s 0 - +R F 1921 o - Mar 14 23s 1 S +R F 1921 o - O 25 23s 0 - +R F 1922 o - Mar 25 23s 1 S +R F 1922 1938 - O Sat>=1 23s 0 - +R F 1923 o - May 26 23s 1 S +R F 1924 o - Mar 29 23s 1 S +R F 1925 o - Ap 4 23s 1 S +R F 1926 o - Ap 17 23s 1 S +R F 1927 o - Ap 9 23s 1 S +R F 1928 o - Ap 14 23s 1 S +R F 1929 o - Ap 20 23s 1 S +R F 1930 o - Ap 12 23s 1 S +R F 1931 o - Ap 18 23s 1 S +R F 1932 o - Ap 2 23s 1 S +R F 1933 o - Mar 25 23s 1 S +R F 1934 o - Ap 7 23s 1 S +R F 1935 o - Mar 30 23s 1 S +R F 1936 o - Ap 18 23s 1 S +R F 1937 o - Ap 3 23s 1 S +R F 1938 o - Mar 26 23s 1 S +R F 1939 o - Ap 15 23s 1 S +R F 1939 o - N 18 23s 0 - +R F 1940 o - F 25 2 1 S +R F 1941 o - May 5 0 2 M +R F 1941 o - O 6 0 1 S +R F 1942 o - Mar 9 0 2 M +R F 1942 o - N 2 3 1 S +R F 1943 o - Mar 29 2 2 M +R F 1943 o - O 4 3 1 S +R F 1944 o - Ap 3 2 2 M +R F 1944 o - O 8 1 1 S +R F 1945 o - Ap 2 2 2 M +R F 1945 o - S 16 3 0 - +R F 1976 o - Mar 28 1 1 S +R F 1976 o - S 26 1 0 - +Z Europe/Paris 0:9:21 - LMT 1891 Mar 15 0:1 +0:9:21 - PMT 1911 Mar 11 0:1 +0 F WE%sT 1940 Jun 14 23 +1 c CE%sT 1944 Au 25 +0 F WE%sT 1945 S 16 3 +1 F CE%sT 1977 +1 E CE%sT +R DE 1946 o - Ap 14 2s 1 S +R DE 1946 o - O 7 2s 0 - +R DE 1947 1949 - O Sun>=1 2s 0 - +R DE 1947 o - Ap 6 3s 1 S +R DE 1947 o - May 11 2s 2 M +R DE 1947 o - Jun 29 3 1 S +R DE 1948 o - Ap 18 2s 1 S +R DE 1949 o - Ap 10 2s 1 S +R So 1945 o - May 24 2 2 M +R So 1945 o - S 24 3 1 S +R So 1945 o - N 18 2s 0 - +Z Europe/Berlin 0:53:28 - LMT 1893 Ap +1 c CE%sT 1945 May 24 2 +1 So CE%sT 1946 +1 DE CE%sT 1980 +1 E CE%sT +Li Europe/Zurich Europe/Busingen +Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 0s +0 G %s 1957 Ap 14 2 +1 - CET 1982 +1 E CE%sT +R g 1932 o - Jul 7 0 1 S +R g 1932 o - S 1 0 0 - +R g 1941 o - Ap 7 0 1 S +R g 1942 o - N 2 3 0 - +R g 1943 o - Mar 30 0 1 S +R g 1943 o - O 4 0 0 - +R g 1952 o - Jul 1 0 1 S +R g 1952 o - N 2 0 0 - +R g 1975 o - Ap 12 0s 1 S +R g 1975 o - N 26 0s 0 - +R g 1976 o - Ap 11 2s 1 S +R g 1976 o - O 10 2s 0 - +R g 1977 1978 - Ap Sun>=1 2s 1 S +R g 1977 o - S 26 2s 0 - +R g 1978 o - S 24 4 0 - +R g 1979 o - Ap 1 9 1 S +R g 1979 o - S 29 2 0 - +R g 1980 o - Ap 1 0 1 S +R g 1980 o - S 28 0 0 - +Z Europe/Athens 1:34:52 - LMT 1895 S 14 +1:34:52 - AMT 1916 Jul 28 0:1 +2 g EE%sT 1941 Ap 30 +1 g CE%sT 1944 Ap 4 +2 g EE%sT 1981 +2 E EE%sT +R h 1918 o - Ap 1 3 1 S +R h 1918 o - S 16 3 0 - +R h 1919 o - Ap 15 3 1 S +R h 1919 o - N 24 3 0 - +R h 1945 o - May 1 23 1 S +R h 1945 o - N 1 0 0 - +R h 1946 o - Mar 31 2s 1 S +R h 1946 1949 - O Sun>=1 2s 0 - +R h 1947 1949 - Ap Sun>=4 2s 1 S +R h 1950 o - Ap 17 2s 1 S +R h 1950 o - O 23 2s 0 - +R h 1954 1955 - May 23 0 1 S +R h 1954 1955 - O 3 0 0 - +R h 1956 o - Jun Sun>=1 0 1 S +R h 1956 o - S lastSun 0 0 - +R h 1957 o - Jun Sun>=1 1 1 S +R h 1957 o - S lastSun 3 0 - +R h 1980 o - Ap 6 1 1 S +Z Europe/Budapest 1:16:20 - LMT 1890 O +1 c CE%sT 1918 +1 h CE%sT 1941 Ap 8 +1 c CE%sT 1945 +1 h CE%sT 1980 S 28 2s +1 E CE%sT +R w 1917 1919 - F 19 23 1 - +R w 1917 o - O 21 1 0 - +R w 1918 1919 - N 16 1 0 - +R w 1921 o - Mar 19 23 1 - +R w 1921 o - Jun 23 1 0 - +R w 1939 o - Ap 29 23 1 - +R w 1939 o - O 29 2 0 - +R w 1940 o - F 25 2 1 - +R w 1940 1941 - N Sun>=2 1s 0 - +R w 1941 1942 - Mar Sun>=2 1s 1 - +R w 1943 1946 - Mar Sun>=1 1s 1 - +R w 1942 1948 - O Sun>=22 1s 0 - +R w 1947 1967 - Ap Sun>=1 1s 1 - +R w 1949 o - O 30 1s 0 - +R w 1950 1966 - O Sun>=22 1s 0 - +R w 1967 o - O 29 1s 0 - +Z Atlantic/Reykjavik -1:28 - LMT 1908 +-1 w -01/+00 1968 Ap 7 1s +0 - GMT +R I 1916 o - Jun 3 24 1 S +R I 1916 1917 - S 30 24 0 - +R I 1917 o - Mar 31 24 1 S +R I 1918 o - Mar 9 24 1 S +R I 1918 o - O 6 24 0 - +R I 1919 o - Mar 1 24 1 S +R I 1919 o - O 4 24 0 - +R I 1920 o - Mar 20 24 1 S +R I 1920 o - S 18 24 0 - +R I 1940 o - Jun 14 24 1 S +R I 1942 o - N 2 2s 0 - +R I 1943 o - Mar 29 2s 1 S +R I 1943 o - O 4 2s 0 - +R I 1944 o - Ap 2 2s 1 S +R I 1944 o - S 17 2s 0 - +R I 1945 o - Ap 2 2 1 S +R I 1945 o - S 15 1 0 - +R I 1946 o - Mar 17 2s 1 S +R I 1946 o - O 6 2s 0 - +R I 1947 o - Mar 16 0s 1 S +R I 1947 o - O 5 0s 0 - +R I 1948 o - F 29 2s 1 S +R I 1948 o - O 3 2s 0 - +R I 1966 1968 - May Sun>=22 0s 1 S +R I 1966 o - S 24 24 0 - +R I 1967 1969 - S Sun>=22 0s 0 - +R I 1969 o - Jun 1 0s 1 S +R I 1970 o - May 31 0s 1 S +R I 1970 o - S lastSun 0s 0 - +R I 1971 1972 - May Sun>=22 0s 1 S +R I 1971 o - S lastSun 0s 0 - +R I 1972 o - O 1 0s 0 - +R I 1973 o - Jun 3 0s 1 S +R I 1973 1974 - S lastSun 0s 0 - +R I 1974 o - May 26 0s 1 S +R I 1975 o - Jun 1 0s 1 S +R I 1975 1977 - S lastSun 0s 0 - +R I 1976 o - May 30 0s 1 S +R I 1977 1979 - May Sun>=22 0s 1 S +R I 1978 o - O 1 0s 0 - +R I 1979 o - S 30 0s 0 - +Z Europe/Rome 0:49:56 - LMT 1866 S 22 +0:49:56 - RMT 1893 O 31 23:49:56 +1 I CE%sT 1943 S 10 +1 c CE%sT 1944 Jun 4 +1 I CE%sT 1980 +1 E CE%sT +Li Europe/Rome Europe/Vatican +Li Europe/Rome Europe/San_Marino +R LV 1989 1996 - Mar lastSun 2s 1 S +R LV 1989 1996 - S lastSun 2s 0 - +Z Europe/Riga 1:36:34 - LMT 1880 +1:36:34 - RMT 1918 Ap 15 2 +1:36:34 1 LST 1918 S 16 3 +1:36:34 - RMT 1919 Ap 1 2 +1:36:34 1 LST 1919 May 22 3 +1:36:34 - RMT 1926 May 11 +2 - EET 1940 Au 5 +3 - MSK 1941 Jul +1 c CE%sT 1944 O 13 +3 R MSK/MSD 1989 Mar lastSun 2s +2 1 EEST 1989 S lastSun 2s +2 LV EE%sT 1997 Ja 21 +2 E EE%sT 2000 F 29 +2 - EET 2001 Ja 2 +2 E EE%sT +Li Europe/Zurich Europe/Vaduz +Z Europe/Vilnius 1:41:16 - LMT 1880 +1:24 - WMT 1917 +1:35:36 - KMT 1919 O 10 +1 - CET 1920 Jul 12 +2 - EET 1920 O 9 +1 - CET 1940 Au 3 +3 - MSK 1941 Jun 24 +1 c CE%sT 1944 Au +3 R MSK/MSD 1989 Mar 26 2s +2 R EE%sT 1991 S 29 2s +2 c EE%sT 1998 +2 - EET 1998 Mar 29 1u +1 E CE%sT 1999 O 31 1u +2 - EET 2003 +2 E EE%sT +R LX 1916 o - May 14 23 1 S +R LX 1916 o - O 1 1 0 - +R LX 1917 o - Ap 28 23 1 S +R LX 1917 o - S 17 1 0 - +R LX 1918 o - Ap M>=15 2s 1 S +R LX 1918 o - S M>=15 2s 0 - +R LX 1919 o - Mar 1 23 1 S +R LX 1919 o - O 5 3 0 - +R LX 1920 o - F 14 23 1 S +R LX 1920 o - O 24 2 0 - +R LX 1921 o - Mar 14 23 1 S +R LX 1921 o - O 26 2 0 - +R LX 1922 o - Mar 25 23 1 S +R LX 1922 o - O Sun>=2 1 0 - +R LX 1923 o - Ap 21 23 1 S +R LX 1923 o - O Sun>=2 2 0 - +R LX 1924 o - Mar 29 23 1 S +R LX 1924 1928 - O Sun>=2 1 0 - +R LX 1925 o - Ap 5 23 1 S +R LX 1926 o - Ap 17 23 1 S +R LX 1927 o - Ap 9 23 1 S +R LX 1928 o - Ap 14 23 1 S +R LX 1929 o - Ap 20 23 1 S +Z Europe/Luxembourg 0:24:36 - LMT 1904 Jun +1 LX CE%sT 1918 N 25 +0 LX WE%sT 1929 O 6 2s +0 b WE%sT 1940 May 14 3 +1 c WE%sT 1944 S 18 3 +1 b CE%sT 1977 +1 E CE%sT +R MT 1973 o - Mar 31 0s 1 S +R MT 1973 o - S 29 0s 0 - +R MT 1974 o - Ap 21 0s 1 S +R MT 1974 o - S 16 0s 0 - +R MT 1975 1979 - Ap Sun>=15 2 1 S +R MT 1975 1980 - S Sun>=15 2 0 - +R MT 1980 o - Mar 31 2 1 S +Z Europe/Malta 0:58:4 - LMT 1893 N 2 0s +1 I CE%sT 1973 Mar 31 +1 MT CE%sT 1981 +1 E CE%sT +R MD 1997 ma - Mar lastSun 2 1 S +R MD 1997 ma - O lastSun 3 0 - +Z Europe/Chisinau 1:55:20 - LMT 1880 +1:55 - CMT 1918 F 15 +1:44:24 - BMT 1931 Jul 24 +2 z EE%sT 1940 Au 15 +2 1 EEST 1941 Jul 17 +1 c CE%sT 1944 Au 24 +3 R MSK/MSD 1990 May 6 2 +2 R EE%sT 1992 +2 e EE%sT 1997 +2 MD EE%sT +Z Europe/Monaco 0:29:32 - LMT 1891 Mar 15 +0:9:21 - PMT 1911 Mar 11 +0 F WE%sT 1945 S 16 3 +1 F CE%sT 1977 +1 E CE%sT +R N 1916 o - May 1 0 1 NST +R N 1916 o - O 1 0 0 AMT +R N 1917 o - Ap 16 2s 1 NST +R N 1917 o - S 17 2s 0 AMT +R N 1918 1921 - Ap M>=1 2s 1 NST +R N 1918 1921 - S lastM 2s 0 AMT +R N 1922 o - Mar lastSun 2s 1 NST +R N 1922 1936 - O Sun>=2 2s 0 AMT +R N 1923 o - Jun F>=1 2s 1 NST +R N 1924 o - Mar lastSun 2s 1 NST +R N 1925 o - Jun F>=1 2s 1 NST +R N 1926 1931 - May 15 2s 1 NST +R N 1932 o - May 22 2s 1 NST +R N 1933 1936 - May 15 2s 1 NST +R N 1937 o - May 22 2s 1 NST +R N 1937 o - Jul 1 0 1 S +R N 1937 1939 - O Sun>=2 2s 0 - +R N 1938 1939 - May 15 2s 1 S +R N 1945 o - Ap 2 2s 1 S +R N 1945 o - S 16 2s 0 - +Z Europe/Amsterdam 0:19:32 - LMT 1835 +0:19:32 N %s 1937 Jul +0:20 N +0020/+0120 1940 May 16 +1 c CE%sT 1945 Ap 2 2 +1 N CE%sT 1977 +1 E CE%sT +R NO 1916 o - May 22 1 1 S +R NO 1916 o - S 30 0 0 - +R NO 1945 o - Ap 2 2s 1 S +R NO 1945 o - O 1 2s 0 - +R NO 1959 1964 - Mar Sun>=15 2s 1 S +R NO 1959 1965 - S Sun>=15 2s 0 - +R NO 1965 o - Ap 25 2s 1 S +Z Europe/Oslo 0:43 - LMT 1895 +1 NO CE%sT 1940 Au 10 23 +1 c CE%sT 1945 Ap 2 2 +1 NO CE%sT 1980 +1 E CE%sT +Li Europe/Oslo Arctic/Longyearbyen +R O 1918 1919 - S 16 2s 0 - +R O 1919 o - Ap 15 2s 1 S +R O 1944 o - Ap 3 2s 1 S +R O 1944 o - O 4 2 0 - +R O 1945 o - Ap 29 0 1 S +R O 1945 o - N 1 0 0 - +R O 1946 o - Ap 14 0s 1 S +R O 1946 o - O 7 2s 0 - +R O 1947 o - May 4 2s 1 S +R O 1947 1949 - O Sun>=1 2s 0 - +R O 1948 o - Ap 18 2s 1 S +R O 1949 o - Ap 10 2s 1 S +R O 1957 o - Jun 2 1s 1 S +R O 1957 1958 - S lastSun 1s 0 - +R O 1958 o - Mar 30 1s 1 S +R O 1959 o - May 31 1s 1 S +R O 1959 1961 - O Sun>=1 1s 0 - +R O 1960 o - Ap 3 1s 1 S +R O 1961 1964 - May lastSun 1s 1 S +R O 1962 1964 - S lastSun 1s 0 - +Z Europe/Warsaw 1:24 - LMT 1880 +1:24 - WMT 1915 Au 5 +1 c CE%sT 1918 S 16 3 +2 O EE%sT 1922 Jun +1 O CE%sT 1940 Jun 23 2 +1 c CE%sT 1944 O +1 O CE%sT 1977 +1 W- CE%sT 1988 +1 E CE%sT +R p 1916 o - Jun 17 23 1 S +R p 1916 o - N 1 1 0 - +R p 1917 o - F 28 23s 1 S +R p 1917 1921 - O 14 23s 0 - +R p 1918 o - Mar 1 23s 1 S +R p 1919 o - F 28 23s 1 S +R p 1920 o - F 29 23s 1 S +R p 1921 o - F 28 23s 1 S +R p 1924 o - Ap 16 23s 1 S +R p 1924 o - O 14 23s 0 - +R p 1926 o - Ap 17 23s 1 S +R p 1926 1929 - O Sat>=1 23s 0 - +R p 1927 o - Ap 9 23s 1 S +R p 1928 o - Ap 14 23s 1 S +R p 1929 o - Ap 20 23s 1 S +R p 1931 o - Ap 18 23s 1 S +R p 1931 1932 - O Sat>=1 23s 0 - +R p 1932 o - Ap 2 23s 1 S +R p 1934 o - Ap 7 23s 1 S +R p 1934 1938 - O Sat>=1 23s 0 - +R p 1935 o - Mar 30 23s 1 S +R p 1936 o - Ap 18 23s 1 S +R p 1937 o - Ap 3 23s 1 S +R p 1938 o - Mar 26 23s 1 S +R p 1939 o - Ap 15 23s 1 S +R p 1939 o - N 18 23s 0 - +R p 1940 o - F 24 23s 1 S +R p 1940 1941 - O 5 23s 0 - +R p 1941 o - Ap 5 23s 1 S +R p 1942 1945 - Mar Sat>=8 23s 1 S +R p 1942 o - Ap 25 22s 2 M +R p 1942 o - Au 15 22s 1 S +R p 1942 1945 - O Sat>=24 23s 0 - +R p 1943 o - Ap 17 22s 2 M +R p 1943 1945 - Au Sat>=25 22s 1 S +R p 1944 1945 - Ap Sat>=21 22s 2 M +R p 1946 o - Ap Sat>=1 23s 1 S +R p 1946 o - O Sat>=1 23s 0 - +R p 1947 1949 - Ap Sun>=1 2s 1 S +R p 1947 1949 - O Sun>=1 2s 0 - +R p 1951 1965 - Ap Sun>=1 2s 1 S +R p 1951 1965 - O Sun>=1 2s 0 - +R p 1977 o - Mar 27 0s 1 S +R p 1977 o - S 25 0s 0 - +R p 1978 1979 - Ap Sun>=1 0s 1 S +R p 1978 o - O 1 0s 0 - +R p 1979 1982 - S lastSun 1s 0 - +R p 1980 o - Mar lastSun 0s 1 S +R p 1981 1982 - Mar lastSun 1s 1 S +R p 1983 o - Mar lastSun 2s 1 S +Z Europe/Lisbon -0:36:45 - LMT 1884 +-0:36:45 - LMT 1912 Ja 1 0u +0 p WE%sT 1966 Ap 3 2 +1 - CET 1976 S 26 1 +0 p WE%sT 1983 S 25 1s +0 W- WE%sT 1992 S 27 1s +1 E CE%sT 1996 Mar 31 1u +0 E WE%sT +Z Atlantic/Azores -1:42:40 - LMT 1884 +-1:54:32 - HMT 1912 Ja 1 2u +-2 p -02/-01 1942 Ap 25 22s +-2 p +00 1942 Au 15 22s +-2 p -02/-01 1943 Ap 17 22s +-2 p +00 1943 Au 28 22s +-2 p -02/-01 1944 Ap 22 22s +-2 p +00 1944 Au 26 22s +-2 p -02/-01 1945 Ap 21 22s +-2 p +00 1945 Au 25 22s +-2 p -02/-01 1966 Ap 3 2 +-1 p -01/+00 1983 S 25 1s +-1 W- -01/+00 1992 S 27 1s +0 E WE%sT 1993 Mar 28 1u +-1 E -01/+00 +Z Atlantic/Madeira -1:7:36 - LMT 1884 +-1:7:36 - FMT 1912 Ja 1 1u +-1 p -01/+00 1942 Ap 25 22s +-1 p +01 1942 Au 15 22s +-1 p -01/+00 1943 Ap 17 22s +-1 p +01 1943 Au 28 22s +-1 p -01/+00 1944 Ap 22 22s +-1 p +01 1944 Au 26 22s +-1 p -01/+00 1945 Ap 21 22s +-1 p +01 1945 Au 25 22s +-1 p -01/+00 1966 Ap 3 2 +0 p WE%sT 1983 S 25 1s +0 E WE%sT +R z 1932 o - May 21 0s 1 S +R z 1932 1939 - O Sun>=1 0s 0 - +R z 1933 1939 - Ap Sun>=2 0s 1 S +R z 1979 o - May 27 0 1 S +R z 1979 o - S lastSun 0 0 - +R z 1980 o - Ap 5 23 1 S +R z 1980 o - S lastSun 1 0 - +R z 1991 1993 - Mar lastSun 0s 1 S +R z 1991 1993 - S lastSun 0s 0 - +Z Europe/Bucharest 1:44:24 - LMT 1891 O +1:44:24 - BMT 1931 Jul 24 +2 z EE%sT 1981 Mar 29 2s +2 c EE%sT 1991 +2 z EE%sT 1994 +2 e EE%sT 1997 +2 E EE%sT +Z Europe/Kaliningrad 1:22 - LMT 1893 Ap +1 c CE%sT 1945 +2 O CE%sT 1946 +3 R MSK/MSD 1989 Mar 26 2s +2 R EE%sT 2011 Mar 27 2s +3 - +03 2014 O 26 2s +2 - EET +Z Europe/Moscow 2:30:17 - LMT 1880 +2:30:17 - MMT 1916 Jul 3 +2:31:19 R %s 1919 Jul 1 0u +3 R %s 1921 O +3 R MSK/MSD 1922 O +2 - EET 1930 Jun 21 +3 R MSK/MSD 1991 Mar 31 2s +2 R EE%sT 1992 Ja 19 2s +3 R MSK/MSD 2011 Mar 27 2s +4 - MSK 2014 O 26 2s +3 - MSK +Z Europe/Simferopol 2:16:24 - LMT 1880 +2:16 - SMT 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 N +1 c CE%sT 1944 Ap 13 +3 R MSK/MSD 1990 +3 - MSK 1990 Jul 1 2 +2 - EET 1992 +2 e EE%sT 1994 May +3 e MSK/MSD 1996 Mar 31 0s +3 1 MSD 1996 O 27 3s +3 R MSK/MSD 1997 +3 - MSK 1997 Mar lastSun 1u +2 E EE%sT 2014 Mar 30 2 +4 - MSK 2014 O 26 2s +3 - MSK +Z Europe/Astrakhan 3:12:12 - LMT 1924 May +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2016 Mar 27 2s +4 - +04 +Z Europe/Volgograd 2:57:40 - LMT 1920 Ja 3 +3 - +03 1930 Jun 21 +4 - +04 1961 N 11 +4 R +04/+05 1988 Mar 27 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2018 O 28 2s +4 - +04 +Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1988 Mar 27 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2016 D 4 2s +4 - +04 +Z Europe/Kirov 3:18:48 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +4 - +04 1992 Mar 29 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 +Z Europe/Samara 3:20:20 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 - +04 1935 Ja 27 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +2 R +02/+03 1991 S 29 2s +3 - +03 1991 O 20 3 +4 R +04/+05 2010 Mar 28 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 +Z Europe/Ulyanovsk 3:13:36 - LMT 1919 Jul 1 0u +3 - +03 1930 Jun 21 +4 R +04/+05 1989 Mar 26 2s +3 R +03/+04 1991 Mar 31 2s +2 R +02/+03 1992 Ja 19 2s +3 R +03/+04 2011 Mar 27 2s +4 - +04 2014 O 26 2s +3 - +03 2016 Mar 27 2s +4 - +04 +Z Asia/Yekaterinburg 4:2:33 - LMT 1916 Jul 3 +3:45:5 - PMT 1919 Jul 15 4 +4 - +04 1930 Jun 21 +5 R +05/+06 1991 Mar 31 2s +4 R +04/+05 1992 Ja 19 2s +5 R +05/+06 2011 Mar 27 2s +6 - +06 2014 O 26 2s +5 - +05 +Z Asia/Omsk 4:53:30 - LMT 1919 N 14 +5 - +05 1930 Jun 21 +6 R +06/+07 1991 Mar 31 2s +5 R +05/+06 1992 Ja 19 2s +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 +Z Asia/Barnaul 5:35 - LMT 1919 D 10 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 1995 May 28 +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 2016 Mar 27 2s +7 - +07 +Z Asia/Novosibirsk 5:31:40 - LMT 1919 D 14 6 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 1993 May 23 +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 2016 Jul 24 2s +7 - +07 +Z Asia/Tomsk 5:39:51 - LMT 1919 D 22 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 2002 May 1 3 +6 R +06/+07 2011 Mar 27 2s +7 - +07 2014 O 26 2s +6 - +06 2016 May 29 2s +7 - +07 +Z Asia/Novokuznetsk 5:48:48 - LMT 1924 May +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 2010 Mar 28 2s +6 R +06/+07 2011 Mar 27 2s +7 - +07 +Z Asia/Krasnoyarsk 6:11:26 - LMT 1920 Ja 6 +6 - +06 1930 Jun 21 +7 R +07/+08 1991 Mar 31 2s +6 R +06/+07 1992 Ja 19 2s +7 R +07/+08 2011 Mar 27 2s +8 - +08 2014 O 26 2s +7 - +07 +Z Asia/Irkutsk 6:57:5 - LMT 1880 +6:57:5 - IMT 1920 Ja 25 +7 - +07 1930 Jun 21 +8 R +08/+09 1991 Mar 31 2s +7 R +07/+08 1992 Ja 19 2s +8 R +08/+09 2011 Mar 27 2s +9 - +09 2014 O 26 2s +8 - +08 +Z Asia/Chita 7:33:52 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1991 Mar 31 2s +8 R +08/+09 1992 Ja 19 2s +9 R +09/+10 2011 Mar 27 2s +10 - +10 2014 O 26 2s +8 - +08 2016 Mar 27 2 +9 - +09 +Z Asia/Yakutsk 8:38:58 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1991 Mar 31 2s +8 R +08/+09 1992 Ja 19 2s +9 R +09/+10 2011 Mar 27 2s +10 - +10 2014 O 26 2s +9 - +09 +Z Asia/Vladivostok 8:47:31 - LMT 1922 N 15 +9 - +09 1930 Jun 21 +10 R +10/+11 1991 Mar 31 2s +9 R +09/+10 1992 Ja 19 2s +10 R +10/+11 2011 Mar 27 2s +11 - +11 2014 O 26 2s +10 - +10 +Z Asia/Khandyga 9:2:13 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1991 Mar 31 2s +8 R +08/+09 1992 Ja 19 2s +9 R +09/+10 2004 +10 R +10/+11 2011 Mar 27 2s +11 - +11 2011 S 13 0s +10 - +10 2014 O 26 2s +9 - +09 +Z Asia/Sakhalin 9:30:48 - LMT 1905 Au 23 +9 - +09 1945 Au 25 +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 1997 Mar lastSun 2s +10 R +10/+11 2011 Mar 27 2s +11 - +11 2014 O 26 2s +10 - +10 2016 Mar 27 2s +11 - +11 +Z Asia/Magadan 10:3:12 - LMT 1924 May 2 +10 - +10 1930 Jun 21 +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 2014 O 26 2s +10 - +10 2016 Ap 24 2s +11 - +11 +Z Asia/Srednekolymsk 10:14:52 - LMT 1924 May 2 +10 - +10 1930 Jun 21 +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 2014 O 26 2s +11 - +11 +Z Asia/Ust-Nera 9:32:54 - LMT 1919 D 15 +8 - +08 1930 Jun 21 +9 R +09/+10 1981 Ap +11 R +11/+12 1991 Mar 31 2s +10 R +10/+11 1992 Ja 19 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 2011 S 13 0s +11 - +11 2014 O 26 2s +10 - +10 +Z Asia/Kamchatka 10:34:36 - LMT 1922 N 10 +11 - +11 1930 Jun 21 +12 R +12/+13 1991 Mar 31 2s +11 R +11/+12 1992 Ja 19 2s +12 R +12/+13 2010 Mar 28 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 +Z Asia/Anadyr 11:49:56 - LMT 1924 May 2 +12 - +12 1930 Jun 21 +13 R +13/+14 1982 Ap 1 0s +12 R +12/+13 1991 Mar 31 2s +11 R +11/+12 1992 Ja 19 2s +12 R +12/+13 2010 Mar 28 2s +11 R +11/+12 2011 Mar 27 2s +12 - +12 +Z Europe/Belgrade 1:22 - LMT 1884 +1 - CET 1941 Ap 18 23 +1 c CE%sT 1945 +1 - CET 1945 May 8 2s +1 1 CEST 1945 S 16 2s +1 - CET 1982 N 27 +1 E CE%sT +Li Europe/Belgrade Europe/Ljubljana +Li Europe/Belgrade Europe/Podgorica +Li Europe/Belgrade Europe/Sarajevo +Li Europe/Belgrade Europe/Skopje +Li Europe/Belgrade Europe/Zagreb +Li Europe/Prague Europe/Bratislava +R s 1918 o - Ap 15 23 1 S +R s 1918 1919 - O 6 24s 0 - +R s 1919 o - Ap 6 23 1 S +R s 1924 o - Ap 16 23 1 S +R s 1924 o - O 4 24s 0 - +R s 1926 o - Ap 17 23 1 S +R s 1926 1929 - O Sat>=1 24s 0 - +R s 1927 o - Ap 9 23 1 S +R s 1928 o - Ap 15 0 1 S +R s 1929 o - Ap 20 23 1 S +R s 1937 o - Jun 16 23 1 S +R s 1937 o - O 2 24s 0 - +R s 1938 o - Ap 2 23 1 S +R s 1938 o - Ap 30 23 2 M +R s 1938 o - O 2 24 1 S +R s 1939 o - O 7 24s 0 - +R s 1942 o - May 2 23 1 S +R s 1942 o - S 1 1 0 - +R s 1943 1946 - Ap Sat>=13 23 1 S +R s 1943 1944 - O Sun>=1 1 0 - +R s 1945 1946 - S lastSun 1 0 - +R s 1949 o - Ap 30 23 1 S +R s 1949 o - O 2 1 0 - +R s 1974 1975 - Ap Sat>=12 23 1 S +R s 1974 1975 - O Sun>=1 1 0 - +R s 1976 o - Mar 27 23 1 S +R s 1976 1977 - S lastSun 1 0 - +R s 1977 o - Ap 2 23 1 S +R s 1978 o - Ap 2 2s 1 S +R s 1978 o - O 1 2s 0 - +R Sp 1967 o - Jun 3 12 1 S +R Sp 1967 o - O 1 0 0 - +R Sp 1974 o - Jun 24 0 1 S +R Sp 1974 o - S 1 0 0 - +R Sp 1976 1977 - May 1 0 1 S +R Sp 1976 o - Au 1 0 0 - +R Sp 1977 o - S 28 0 0 - +R Sp 1978 o - Jun 1 0 1 S +R Sp 1978 o - Au 4 0 0 - +Z Europe/Madrid -0:14:44 - LMT 1900 D 31 23:45:16 +0 s WE%sT 1940 Mar 16 23 +1 s CE%sT 1979 +1 E CE%sT +Z Africa/Ceuta -0:21:16 - LMT 1900 D 31 23:38:44 +0 - WET 1918 May 6 23 +0 1 WEST 1918 O 7 23 +0 - WET 1924 +0 s WE%sT 1929 +0 - WET 1967 +0 Sp WE%sT 1984 Mar 16 +1 - CET 1986 +1 E CE%sT +Z Atlantic/Canary -1:1:36 - LMT 1922 Mar +-1 - -01 1946 S 30 1 +0 - WET 1980 Ap 6 0s +0 1 WEST 1980 S 28 1u +0 E WE%sT +Z Europe/Stockholm 1:12:12 - LMT 1879 +1:0:14 - SET 1900 +1 - CET 1916 May 14 23 +1 1 CEST 1916 O 1 1 +1 - CET 1980 +1 E CE%sT +R CH 1941 1942 - May M>=1 1 1 S +R CH 1941 1942 - O M>=1 2 0 - +Z Europe/Zurich 0:34:8 - LMT 1853 Jul 16 +0:29:46 - BMT 1894 Jun +1 CH CE%sT 1981 +1 E CE%sT +R T 1916 o - May 1 0 1 S +R T 1916 o - O 1 0 0 - +R T 1920 o - Mar 28 0 1 S +R T 1920 o - O 25 0 0 - +R T 1921 o - Ap 3 0 1 S +R T 1921 o - O 3 0 0 - +R T 1922 o - Mar 26 0 1 S +R T 1922 o - O 8 0 0 - +R T 1924 o - May 13 0 1 S +R T 1924 1925 - O 1 0 0 - +R T 1925 o - May 1 0 1 S +R T 1940 o - Jun 30 0 1 S +R T 1940 o - O 5 0 0 - +R T 1940 o - D 1 0 1 S +R T 1941 o - S 21 0 0 - +R T 1942 o - Ap 1 0 1 S +R T 1942 o - N 1 0 0 - +R T 1945 o - Ap 2 0 1 S +R T 1945 o - O 8 0 0 - +R T 1946 o - Jun 1 0 1 S +R T 1946 o - O 1 0 0 - +R T 1947 1948 - Ap Sun>=16 0 1 S +R T 1947 1950 - O Sun>=2 0 0 - +R T 1949 o - Ap 10 0 1 S +R T 1950 o - Ap 19 0 1 S +R T 1951 o - Ap 22 0 1 S +R T 1951 o - O 8 0 0 - +R T 1962 o - Jul 15 0 1 S +R T 1962 o - O 8 0 0 - +R T 1964 o - May 15 0 1 S +R T 1964 o - O 1 0 0 - +R T 1970 1972 - May Sun>=2 0 1 S +R T 1970 1972 - O Sun>=2 0 0 - +R T 1973 o - Jun 3 1 1 S +R T 1973 o - N 4 3 0 - +R T 1974 o - Mar 31 2 1 S +R T 1974 o - N 3 5 0 - +R T 1975 o - Mar 30 0 1 S +R T 1975 1976 - O lastSun 0 0 - +R T 1976 o - Jun 1 0 1 S +R T 1977 1978 - Ap Sun>=1 0 1 S +R T 1977 o - O 16 0 0 - +R T 1979 1980 - Ap Sun>=1 3 1 S +R T 1979 1982 - O M>=11 0 0 - +R T 1981 1982 - Mar lastSun 3 1 S +R T 1983 o - Jul 31 0 1 S +R T 1983 o - O 2 0 0 - +R T 1985 o - Ap 20 0 1 S +R T 1985 o - S 28 0 0 - +R T 1986 1993 - Mar lastSun 1s 1 S +R T 1986 1995 - S lastSun 1s 0 - +R T 1994 o - Mar 20 1s 1 S +R T 1995 2006 - Mar lastSun 1s 1 S +R T 1996 2006 - O lastSun 1s 0 - +Z Europe/Istanbul 1:55:52 - LMT 1880 +1:56:56 - IMT 1910 O +2 T EE%sT 1978 O 15 +3 T +03/+04 1985 Ap 20 +2 T EE%sT 2007 +2 E EE%sT 2011 Mar 27 1u +2 - EET 2011 Mar 28 1u +2 E EE%sT 2014 Mar 30 1u +2 - EET 2014 Mar 31 1u +2 E EE%sT 2015 O 25 1u +2 1 EEST 2015 N 8 1u +2 E EE%sT 2016 S 7 +3 - +03 +Li Europe/Istanbul Asia/Istanbul +Z Europe/Kiev 2:2:4 - LMT 1880 +2:2:4 - KMT 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 S 20 +1 c CE%sT 1943 N 6 +3 R MSK/MSD 1990 Jul 1 2 +2 1 EEST 1991 S 29 3 +2 e EE%sT 1995 +2 E EE%sT +Z Europe/Uzhgorod 1:29:12 - LMT 1890 O +1 - CET 1940 +1 c CE%sT 1944 O +1 1 CEST 1944 O 26 +1 - CET 1945 Jun 29 +3 R MSK/MSD 1990 +3 - MSK 1990 Jul 1 2 +1 - CET 1991 Mar 31 3 +2 - EET 1992 +2 e EE%sT 1995 +2 E EE%sT +Z Europe/Zaporozhye 2:20:40 - LMT 1880 +2:20 - +0220 1924 May 2 +2 - EET 1930 Jun 21 +3 - MSK 1941 Au 25 +1 c CE%sT 1943 O 25 +3 R MSK/MSD 1991 Mar 31 2 +2 e EE%sT 1995 +2 E EE%sT +R u 1918 1919 - Mar lastSun 2 1 D +R u 1918 1919 - O lastSun 2 0 S +R u 1942 o - F 9 2 1 W +R u 1945 o - Au 14 23u 1 P +R u 1945 o - S lastSun 2 0 S +R u 1967 2006 - O lastSun 2 0 S +R u 1967 1973 - Ap lastSun 2 1 D +R u 1974 o - Ja 6 2 1 D +R u 1975 o - F 23 2 1 D +R u 1976 1986 - Ap lastSun 2 1 D +R u 1987 2006 - Ap Sun>=1 2 1 D +R u 2007 ma - Mar Sun>=8 2 1 D +R u 2007 ma - N Sun>=1 2 0 S +Z EST -5 - EST +Z MST -7 - MST +Z HST -10 - HST +Z EST5EDT -5 u E%sT +Z CST6CDT -6 u C%sT +Z MST7MDT -7 u M%sT +Z PST8PDT -8 u P%sT +R NY 1920 o - Mar lastSun 2 1 D +R NY 1920 o - O lastSun 2 0 S +R NY 1921 1966 - Ap lastSun 2 1 D +R NY 1921 1954 - S lastSun 2 0 S +R NY 1955 1966 - O lastSun 2 0 S +Z America/New_York -4:56:2 - LMT 1883 N 18 12:3:58 +-5 u E%sT 1920 +-5 NY E%sT 1942 +-5 u E%sT 1946 +-5 NY E%sT 1967 +-5 u E%sT +R Ch 1920 o - Jun 13 2 1 D +R Ch 1920 1921 - O lastSun 2 0 S +R Ch 1921 o - Mar lastSun 2 1 D +R Ch 1922 1966 - Ap lastSun 2 1 D +R Ch 1922 1954 - S lastSun 2 0 S +R Ch 1955 1966 - O lastSun 2 0 S +Z America/Chicago -5:50:36 - LMT 1883 N 18 12:9:24 +-6 u C%sT 1920 +-6 Ch C%sT 1936 Mar 1 2 +-5 - EST 1936 N 15 2 +-6 Ch C%sT 1942 +-6 u C%sT 1946 +-6 Ch C%sT 1967 +-6 u C%sT +Z America/North_Dakota/Center -6:45:12 - LMT 1883 N 18 12:14:48 +-7 u M%sT 1992 O 25 2 +-6 u C%sT +Z America/North_Dakota/New_Salem -6:45:39 - LMT 1883 N 18 12:14:21 +-7 u M%sT 2003 O 26 2 +-6 u C%sT +Z America/North_Dakota/Beulah -6:47:7 - LMT 1883 N 18 12:12:53 +-7 u M%sT 2010 N 7 2 +-6 u C%sT +R De 1920 1921 - Mar lastSun 2 1 D +R De 1920 o - O lastSun 2 0 S +R De 1921 o - May 22 2 0 S +R De 1965 1966 - Ap lastSun 2 1 D +R De 1965 1966 - O lastSun 2 0 S +Z America/Denver -6:59:56 - LMT 1883 N 18 12:0:4 +-7 u M%sT 1920 +-7 De M%sT 1942 +-7 u M%sT 1946 +-7 De M%sT 1967 +-7 u M%sT +R CA 1948 o - Mar 14 2:1 1 D +R CA 1949 o - Ja 1 2 0 S +R CA 1950 1966 - Ap lastSun 1 1 D +R CA 1950 1961 - S lastSun 2 0 S +R CA 1962 1966 - O lastSun 2 0 S +Z America/Los_Angeles -7:52:58 - LMT 1883 N 18 12:7:2 +-8 u P%sT 1946 +-8 CA P%sT 1967 +-8 u P%sT +Z America/Juneau 15:2:19 - LMT 1867 O 19 15:33:32 +-8:57:41 - LMT 1900 Au 20 12 +-8 - PST 1942 +-8 u P%sT 1946 +-8 - PST 1969 +-8 u P%sT 1980 Ap 27 2 +-9 u Y%sT 1980 O 26 2 +-8 u P%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Sitka 14:58:47 - LMT 1867 O 19 15:30 +-9:1:13 - LMT 1900 Au 20 12 +-8 - PST 1942 +-8 u P%sT 1946 +-8 - PST 1969 +-8 u P%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Metlakatla 15:13:42 - LMT 1867 O 19 15:44:55 +-8:46:18 - LMT 1900 Au 20 12 +-8 - PST 1942 +-8 u P%sT 1946 +-8 - PST 1969 +-8 u P%sT 1983 O 30 2 +-8 - PST 2015 N 1 2 +-9 u AK%sT +Z America/Yakutat 14:41:5 - LMT 1867 O 19 15:12:18 +-9:18:55 - LMT 1900 Au 20 12 +-9 - YST 1942 +-9 u Y%sT 1946 +-9 - YST 1969 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Anchorage 14:0:24 - LMT 1867 O 19 14:31:37 +-9:59:36 - LMT 1900 Au 20 12 +-10 - AST 1942 +-10 u A%sT 1967 Ap +-10 - AHST 1969 +-10 u AH%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Nome 12:58:22 - LMT 1867 O 19 13:29:35 +-11:1:38 - LMT 1900 Au 20 12 +-11 - NST 1942 +-11 u N%sT 1946 +-11 - NST 1967 Ap +-11 - BST 1969 +-11 u B%sT 1983 O 30 2 +-9 u Y%sT 1983 N 30 +-9 u AK%sT +Z America/Adak 12:13:22 - LMT 1867 O 19 12:44:35 +-11:46:38 - LMT 1900 Au 20 12 +-11 - NST 1942 +-11 u N%sT 1946 +-11 - NST 1967 Ap +-11 - BST 1969 +-11 u B%sT 1983 O 30 2 +-10 u AH%sT 1983 N 30 +-10 u H%sT +Z Pacific/Honolulu -10:31:26 - LMT 1896 Ja 13 12 +-10:30 - HST 1933 Ap 30 2 +-10:30 1 HDT 1933 May 21 12 +-10:30 u H%sT 1947 Jun 8 2 +-10 - HST +Z America/Phoenix -7:28:18 - LMT 1883 N 18 11:31:42 +-7 u M%sT 1944 Ja 1 0:1 +-7 - MST 1944 Ap 1 0:1 +-7 u M%sT 1944 O 1 0:1 +-7 - MST 1967 +-7 u M%sT 1968 Mar 21 +-7 - MST +Z America/Boise -7:44:49 - LMT 1883 N 18 12:15:11 +-8 u P%sT 1923 May 13 2 +-7 u M%sT 1974 +-7 - MST 1974 F 3 2 +-7 u M%sT +R In 1941 o - Jun 22 2 1 D +R In 1941 1954 - S lastSun 2 0 S +R In 1946 1954 - Ap lastSun 2 1 D +Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 12:15:22 +-6 u C%sT 1920 +-6 In C%sT 1942 +-6 u C%sT 1946 +-6 In C%sT 1955 Ap 24 2 +-5 - EST 1957 S 29 2 +-6 - CST 1958 Ap 27 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 +-5 u E%sT +R Ma 1951 o - Ap lastSun 2 1 D +R Ma 1951 o - S lastSun 2 0 S +R Ma 1954 1960 - Ap lastSun 2 1 D +R Ma 1954 1960 - S lastSun 2 0 S +Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 12:14:37 +-6 u C%sT 1951 +-6 Ma C%sT 1961 Ap 30 2 +-5 - EST 1969 +-5 u E%sT 1974 Ja 6 2 +-6 1 CDT 1974 O 27 2 +-5 u E%sT 1976 +-5 - EST 2006 +-5 u E%sT +R V 1946 o - Ap lastSun 2 1 D +R V 1946 o - S lastSun 2 0 S +R V 1953 1954 - Ap lastSun 2 1 D +R V 1953 1959 - S lastSun 2 0 S +R V 1955 o - May 1 0 1 D +R V 1956 1963 - Ap lastSun 2 1 D +R V 1960 o - O lastSun 2 0 S +R V 1961 o - S lastSun 2 0 S +R V 1962 1963 - O lastSun 2 0 S +Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 12:9:53 +-6 u C%sT 1946 +-6 V C%sT 1964 Ap 26 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT 2007 N 4 2 +-5 u E%sT +R Pe 1946 o - Ap lastSun 2 1 D +R Pe 1946 o - S lastSun 2 0 S +R Pe 1953 1954 - Ap lastSun 2 1 D +R Pe 1953 1959 - S lastSun 2 0 S +R Pe 1955 o - May 1 0 1 D +R Pe 1956 1963 - Ap lastSun 2 1 D +R Pe 1960 o - O lastSun 2 0 S +R Pe 1961 o - S lastSun 2 0 S +R Pe 1962 1963 - O lastSun 2 0 S +Z America/Indiana/Tell_City -5:47:3 - LMT 1883 N 18 12:12:57 +-6 u C%sT 1946 +-6 Pe C%sT 1964 Ap 26 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT +R Pi 1955 o - May 1 0 1 D +R Pi 1955 1960 - S lastSun 2 0 S +R Pi 1956 1964 - Ap lastSun 2 1 D +R Pi 1961 1964 - O lastSun 2 0 S +Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 12:10:53 +-6 u C%sT 1955 +-6 Pi C%sT 1965 Ap 25 2 +-5 - EST 1966 O 30 2 +-6 u C%sT 1977 O 30 2 +-5 - EST 2006 Ap 2 2 +-6 u C%sT 2007 N 4 2 +-5 u E%sT +R St 1947 1961 - Ap lastSun 2 1 D +R St 1947 1954 - S lastSun 2 0 S +R St 1955 1956 - O lastSun 2 0 S +R St 1957 1958 - S lastSun 2 0 S +R St 1959 1961 - O lastSun 2 0 S +Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 12:13:30 +-6 u C%sT 1947 +-6 St C%sT 1962 Ap 29 2 +-5 - EST 1963 O 27 2 +-6 u C%sT 1991 O 27 2 +-5 - EST 2006 Ap 2 2 +-6 u C%sT +R Pu 1946 1960 - Ap lastSun 2 1 D +R Pu 1946 1954 - S lastSun 2 0 S +R Pu 1955 1956 - O lastSun 2 0 S +R Pu 1957 1960 - S lastSun 2 0 S +Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 12:13:35 +-6 u C%sT 1946 +-6 Pu C%sT 1961 Ap 30 2 +-5 - EST 1969 +-5 u E%sT 1971 +-5 - EST 2006 Ap 2 2 +-6 u C%sT 2007 Mar 11 2 +-5 u E%sT +Z America/Indiana/Vevay -5:40:16 - LMT 1883 N 18 12:19:44 +-6 u C%sT 1954 Ap 25 2 +-5 - EST 1969 +-5 u E%sT 1973 +-5 - EST 2006 +-5 u E%sT +R v 1921 o - May 1 2 1 D +R v 1921 o - S 1 2 0 S +R v 1941 1961 - Ap lastSun 2 1 D +R v 1941 o - S lastSun 2 0 S +R v 1946 o - Jun 2 2 0 S +R v 1950 1955 - S lastSun 2 0 S +R v 1956 1960 - O lastSun 2 0 S +Z America/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 12:16:58 +-6 u C%sT 1921 +-6 v C%sT 1942 +-6 u C%sT 1946 +-6 v C%sT 1961 Jul 23 2 +-5 - EST 1968 +-5 u E%sT 1974 Ja 6 2 +-6 1 CDT 1974 O 27 2 +-5 u E%sT +Z America/Kentucky/Monticello -5:39:24 - LMT 1883 N 18 12:20:36 +-6 u C%sT 1946 +-6 - CST 1968 +-6 u C%sT 2000 O 29 2 +-5 u E%sT +R Dt 1948 o - Ap lastSun 2 1 D +R Dt 1948 o - S lastSun 2 0 S +Z America/Detroit -5:32:11 - LMT 1905 +-6 - CST 1915 May 15 2 +-5 - EST 1942 +-5 u E%sT 1946 +-5 Dt E%sT 1973 +-5 u E%sT 1975 +-5 - EST 1975 Ap 27 2 +-5 u E%sT +R Me 1946 o - Ap lastSun 2 1 D +R Me 1946 o - S lastSun 2 0 S +R Me 1966 o - Ap lastSun 2 1 D +R Me 1966 o - O lastSun 2 0 S +Z America/Menominee -5:50:27 - LMT 1885 S 18 12 +-6 u C%sT 1946 +-6 Me C%sT 1969 Ap 27 2 +-5 - EST 1973 Ap 29 2 +-6 u C%sT +R C 1918 o - Ap 14 2 1 D +R C 1918 o - O 27 2 0 S +R C 1942 o - F 9 2 1 W +R C 1945 o - Au 14 23u 1 P +R C 1945 o - S 30 2 0 S +R C 1974 1986 - Ap lastSun 2 1 D +R C 1974 2006 - O lastSun 2 0 S +R C 1987 2006 - Ap Sun>=1 2 1 D +R C 2007 ma - Mar Sun>=8 2 1 D +R C 2007 ma - N Sun>=1 2 0 S +R j 1917 o - Ap 8 2 1 D +R j 1917 o - S 17 2 0 S +R j 1919 o - May 5 23 1 D +R j 1919 o - Au 12 23 0 S +R j 1920 1935 - May Sun>=1 23 1 D +R j 1920 1935 - O lastSun 23 0 S +R j 1936 1941 - May M>=9 0 1 D +R j 1936 1941 - O M>=2 0 0 S +R j 1946 1950 - May Sun>=8 2 1 D +R j 1946 1950 - O Sun>=2 2 0 S +R j 1951 1986 - Ap lastSun 2 1 D +R j 1951 1959 - S lastSun 2 0 S +R j 1960 1986 - O lastSun 2 0 S +R j 1987 o - Ap Sun>=1 0:1 1 D +R j 1987 2006 - O lastSun 0:1 0 S +R j 1988 o - Ap Sun>=1 0:1 2 DD +R j 1989 2006 - Ap Sun>=1 0:1 1 D +R j 2007 2011 - Mar Sun>=8 0:1 1 D +R j 2007 2010 - N Sun>=1 0:1 0 S +Z America/St_Johns -3:30:52 - LMT 1884 +-3:30:52 j N%sT 1918 +-3:30:52 C N%sT 1919 +-3:30:52 j N%sT 1935 Mar 30 +-3:30 j N%sT 1942 May 11 +-3:30 C N%sT 1946 +-3:30 j N%sT 2011 N +-3:30 C N%sT +Z America/Goose_Bay -4:1:40 - LMT 1884 +-3:30:52 - NST 1918 +-3:30:52 C N%sT 1919 +-3:30:52 - NST 1935 Mar 30 +-3:30 - NST 1936 +-3:30 j N%sT 1942 May 11 +-3:30 C N%sT 1946 +-3:30 j N%sT 1966 Mar 15 2 +-4 j A%sT 2011 N +-4 C A%sT +R H 1916 o - Ap 1 0 1 D +R H 1916 o - O 1 0 0 S +R H 1920 o - May 9 0 1 D +R H 1920 o - Au 29 0 0 S +R H 1921 o - May 6 0 1 D +R H 1921 1922 - S 5 0 0 S +R H 1922 o - Ap 30 0 1 D +R H 1923 1925 - May Sun>=1 0 1 D +R H 1923 o - S 4 0 0 S +R H 1924 o - S 15 0 0 S +R H 1925 o - S 28 0 0 S +R H 1926 o - May 16 0 1 D +R H 1926 o - S 13 0 0 S +R H 1927 o - May 1 0 1 D +R H 1927 o - S 26 0 0 S +R H 1928 1931 - May Sun>=8 0 1 D +R H 1928 o - S 9 0 0 S +R H 1929 o - S 3 0 0 S +R H 1930 o - S 15 0 0 S +R H 1931 1932 - S M>=24 0 0 S +R H 1932 o - May 1 0 1 D +R H 1933 o - Ap 30 0 1 D +R H 1933 o - O 2 0 0 S +R H 1934 o - May 20 0 1 D +R H 1934 o - S 16 0 0 S +R H 1935 o - Jun 2 0 1 D +R H 1935 o - S 30 0 0 S +R H 1936 o - Jun 1 0 1 D +R H 1936 o - S 14 0 0 S +R H 1937 1938 - May Sun>=1 0 1 D +R H 1937 1941 - S M>=24 0 0 S +R H 1939 o - May 28 0 1 D +R H 1940 1941 - May Sun>=1 0 1 D +R H 1946 1949 - Ap lastSun 2 1 D +R H 1946 1949 - S lastSun 2 0 S +R H 1951 1954 - Ap lastSun 2 1 D +R H 1951 1954 - S lastSun 2 0 S +R H 1956 1959 - Ap lastSun 2 1 D +R H 1956 1959 - S lastSun 2 0 S +R H 1962 1973 - Ap lastSun 2 1 D +R H 1962 1973 - O lastSun 2 0 S +Z America/Halifax -4:14:24 - LMT 1902 Jun 15 +-4 H A%sT 1918 +-4 C A%sT 1919 +-4 H A%sT 1942 F 9 2s +-4 C A%sT 1946 +-4 H A%sT 1974 +-4 C A%sT +Z America/Glace_Bay -3:59:48 - LMT 1902 Jun 15 +-4 C A%sT 1953 +-4 H A%sT 1954 +-4 - AST 1972 +-4 H A%sT 1974 +-4 C A%sT +R o 1933 1935 - Jun Sun>=8 1 1 D +R o 1933 1935 - S Sun>=8 1 0 S +R o 1936 1938 - Jun Sun>=1 1 1 D +R o 1936 1938 - S Sun>=1 1 0 S +R o 1939 o - May 27 1 1 D +R o 1939 1941 - S Sat>=21 1 0 S +R o 1940 o - May 19 1 1 D +R o 1941 o - May 4 1 1 D +R o 1946 1972 - Ap lastSun 2 1 D +R o 1946 1956 - S lastSun 2 0 S +R o 1957 1972 - O lastSun 2 0 S +R o 1993 2006 - Ap Sun>=1 0:1 1 D +R o 1993 2006 - O lastSun 0:1 0 S +Z America/Moncton -4:19:8 - LMT 1883 D 9 +-5 - EST 1902 Jun 15 +-4 C A%sT 1933 +-4 o A%sT 1942 +-4 C A%sT 1946 +-4 o A%sT 1973 +-4 C A%sT 1993 +-4 o A%sT 2007 +-4 C A%sT +Z America/Blanc-Sablon -3:48:28 - LMT 1884 +-4 C A%sT 1970 +-4 - AST +R t 1919 o - Mar 30 23:30 1 D +R t 1919 o - O 26 0 0 S +R t 1920 o - May 2 2 1 D +R t 1920 o - S 26 0 0 S +R t 1921 o - May 15 2 1 D +R t 1921 o - S 15 2 0 S +R t 1922 1923 - May Sun>=8 2 1 D +R t 1922 1926 - S Sun>=15 2 0 S +R t 1924 1927 - May Sun>=1 2 1 D +R t 1927 1932 - S lastSun 2 0 S +R t 1928 1931 - Ap lastSun 2 1 D +R t 1932 o - May 1 2 1 D +R t 1933 1940 - Ap lastSun 2 1 D +R t 1933 o - O 1 2 0 S +R t 1934 1939 - S lastSun 2 0 S +R t 1945 1946 - S lastSun 2 0 S +R t 1946 o - Ap lastSun 2 1 D +R t 1947 1949 - Ap lastSun 0 1 D +R t 1947 1948 - S lastSun 0 0 S +R t 1949 o - N lastSun 0 0 S +R t 1950 1973 - Ap lastSun 2 1 D +R t 1950 o - N lastSun 2 0 S +R t 1951 1956 - S lastSun 2 0 S +R t 1957 1973 - O lastSun 2 0 S +Z America/Toronto -5:17:32 - LMT 1895 +-5 C E%sT 1919 +-5 t E%sT 1942 F 9 2s +-5 C E%sT 1946 +-5 t E%sT 1974 +-5 C E%sT +Z America/Thunder_Bay -5:57 - LMT 1895 +-6 - CST 1910 +-5 - EST 1942 +-5 C E%sT 1970 +-5 t E%sT 1973 +-5 - EST 1974 +-5 C E%sT +Z America/Nipigon -5:53:4 - LMT 1895 +-5 C E%sT 1940 S 29 +-5 1 EDT 1942 F 9 2s +-5 C E%sT +Z America/Rainy_River -6:18:16 - LMT 1895 +-6 C C%sT 1940 S 29 +-6 1 CDT 1942 F 9 2s +-6 C C%sT +Z America/Atikokan -6:6:28 - LMT 1895 +-6 C C%sT 1940 S 29 +-6 1 CDT 1942 F 9 2s +-6 C C%sT 1945 S 30 2 +-5 - EST +R W 1916 o - Ap 23 0 1 D +R W 1916 o - S 17 0 0 S +R W 1918 o - Ap 14 2 1 D +R W 1918 o - O 27 2 0 S +R W 1937 o - May 16 2 1 D +R W 1937 o - S 26 2 0 S +R W 1942 o - F 9 2 1 W +R W 1945 o - Au 14 23u 1 P +R W 1945 o - S lastSun 2 0 S +R W 1946 o - May 12 2 1 D +R W 1946 o - O 13 2 0 S +R W 1947 1949 - Ap lastSun 2 1 D +R W 1947 1949 - S lastSun 2 0 S +R W 1950 o - May 1 2 1 D +R W 1950 o - S 30 2 0 S +R W 1951 1960 - Ap lastSun 2 1 D +R W 1951 1958 - S lastSun 2 0 S +R W 1959 o - O lastSun 2 0 S +R W 1960 o - S lastSun 2 0 S +R W 1963 o - Ap lastSun 2 1 D +R W 1963 o - S 22 2 0 S +R W 1966 1986 - Ap lastSun 2s 1 D +R W 1966 2005 - O lastSun 2s 0 S +R W 1987 2005 - Ap Sun>=1 2s 1 D +Z America/Winnipeg -6:28:36 - LMT 1887 Jul 16 +-6 W C%sT 2006 +-6 C C%sT +R r 1918 o - Ap 14 2 1 D +R r 1918 o - O 27 2 0 S +R r 1930 1934 - May Sun>=1 0 1 D +R r 1930 1934 - O Sun>=1 0 0 S +R r 1937 1941 - Ap Sun>=8 0 1 D +R r 1937 o - O Sun>=8 0 0 S +R r 1938 o - O Sun>=1 0 0 S +R r 1939 1941 - O Sun>=8 0 0 S +R r 1942 o - F 9 2 1 W +R r 1945 o - Au 14 23u 1 P +R r 1945 o - S lastSun 2 0 S +R r 1946 o - Ap Sun>=8 2 1 D +R r 1946 o - O Sun>=8 2 0 S +R r 1947 1957 - Ap lastSun 2 1 D +R r 1947 1957 - S lastSun 2 0 S +R r 1959 o - Ap lastSun 2 1 D +R r 1959 o - O lastSun 2 0 S +R Sw 1957 o - Ap lastSun 2 1 D +R Sw 1957 o - O lastSun 2 0 S +R Sw 1959 1961 - Ap lastSun 2 1 D +R Sw 1959 o - O lastSun 2 0 S +R Sw 1960 1961 - S lastSun 2 0 S +Z America/Regina -6:58:36 - LMT 1905 S +-7 r M%sT 1960 Ap lastSun 2 +-6 - CST +Z America/Swift_Current -7:11:20 - LMT 1905 S +-7 C M%sT 1946 Ap lastSun 2 +-7 r M%sT 1950 +-7 Sw M%sT 1972 Ap lastSun 2 +-6 - CST +R Ed 1918 1919 - Ap Sun>=8 2 1 D +R Ed 1918 o - O 27 2 0 S +R Ed 1919 o - May 27 2 0 S +R Ed 1920 1923 - Ap lastSun 2 1 D +R Ed 1920 o - O lastSun 2 0 S +R Ed 1921 1923 - S lastSun 2 0 S +R Ed 1942 o - F 9 2 1 W +R Ed 1945 o - Au 14 23u 1 P +R Ed 1945 o - S lastSun 2 0 S +R Ed 1947 o - Ap lastSun 2 1 D +R Ed 1947 o - S lastSun 2 0 S +R Ed 1967 o - Ap lastSun 2 1 D +R Ed 1967 o - O lastSun 2 0 S +R Ed 1969 o - Ap lastSun 2 1 D +R Ed 1969 o - O lastSun 2 0 S +R Ed 1972 1986 - Ap lastSun 2 1 D +R Ed 1972 2006 - O lastSun 2 0 S +Z America/Edmonton -7:33:52 - LMT 1906 S +-7 Ed M%sT 1987 +-7 C M%sT +R Va 1918 o - Ap 14 2 1 D +R Va 1918 o - O 27 2 0 S +R Va 1942 o - F 9 2 1 W +R Va 1945 o - Au 14 23u 1 P +R Va 1945 o - S 30 2 0 S +R Va 1946 1986 - Ap lastSun 2 1 D +R Va 1946 o - O 13 2 0 S +R Va 1947 1961 - S lastSun 2 0 S +R Va 1962 2006 - O lastSun 2 0 S +Z America/Vancouver -8:12:28 - LMT 1884 +-8 Va P%sT 1987 +-8 C P%sT +Z America/Dawson_Creek -8:0:56 - LMT 1884 +-8 C P%sT 1947 +-8 Va P%sT 1972 Au 30 2 +-7 - MST +Z America/Fort_Nelson -8:10:47 - LMT 1884 +-8 Va P%sT 1946 +-8 - PST 1947 +-8 Va P%sT 1987 +-8 C P%sT 2015 Mar 8 2 +-7 - MST +Z America/Creston -7:46:4 - LMT 1884 +-7 - MST 1916 O +-8 - PST 1918 Jun 2 +-7 - MST +R Y 1918 o - Ap 14 2 1 D +R Y 1918 o - O 27 2 0 S +R Y 1919 o - May 25 2 1 D +R Y 1919 o - N 1 0 0 S +R Y 1942 o - F 9 2 1 W +R Y 1945 o - Au 14 23u 1 P +R Y 1945 o - S 30 2 0 S +R Y 1965 o - Ap lastSun 0 2 DD +R Y 1965 o - O lastSun 2 0 S +R Y 1980 1986 - Ap lastSun 2 1 D +R Y 1980 2006 - O lastSun 2 0 S +R Y 1987 2006 - Ap Sun>=1 2 1 D +Z America/Pangnirtung 0 - -00 1921 +-4 Y A%sT 1995 Ap Sun>=1 2 +-5 C E%sT 1999 O 31 2 +-6 C C%sT 2000 O 29 2 +-5 C E%sT +Z America/Iqaluit 0 - -00 1942 Au +-5 Y E%sT 1999 O 31 2 +-6 C C%sT 2000 O 29 2 +-5 C E%sT +Z America/Resolute 0 - -00 1947 Au 31 +-6 Y C%sT 2000 O 29 2 +-5 - EST 2001 Ap 1 3 +-6 C C%sT 2006 O 29 2 +-5 - EST 2007 Mar 11 3 +-6 C C%sT +Z America/Rankin_Inlet 0 - -00 1957 +-6 Y C%sT 2000 O 29 2 +-5 - EST 2001 Ap 1 3 +-6 C C%sT +Z America/Cambridge_Bay 0 - -00 1920 +-7 Y M%sT 1999 O 31 2 +-6 C C%sT 2000 O 29 2 +-5 - EST 2000 N 5 +-6 - CST 2001 Ap 1 3 +-7 C M%sT +Z America/Yellowknife 0 - -00 1935 +-7 Y M%sT 1980 +-7 C M%sT +Z America/Inuvik 0 - -00 1953 +-8 Y P%sT 1979 Ap lastSun 2 +-7 Y M%sT 1980 +-7 C M%sT +Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 +-9 Y Y%sT 1967 May 28 +-8 Y P%sT 1980 +-8 C P%sT +Z America/Dawson -9:17:40 - LMT 1900 Au 20 +-9 Y Y%sT 1973 O 28 +-8 Y P%sT 1980 +-8 C P%sT +R m 1939 o - F 5 0 1 D +R m 1939 o - Jun 25 0 0 S +R m 1940 o - D 9 0 1 D +R m 1941 o - Ap 1 0 0 S +R m 1943 o - D 16 0 1 W +R m 1944 o - May 1 0 0 S +R m 1950 o - F 12 0 1 D +R m 1950 o - Jul 30 0 0 S +R m 1996 2000 - Ap Sun>=1 2 1 D +R m 1996 2000 - O lastSun 2 0 S +R m 2001 o - May Sun>=1 2 1 D +R m 2001 o - S lastSun 2 0 S +R m 2002 ma - Ap Sun>=1 2 1 D +R m 2002 ma - O lastSun 2 0 S +Z America/Cancun -5:47:4 - LMT 1922 Ja 1 0:12:56 +-6 - CST 1981 D 23 +-5 m E%sT 1998 Au 2 2 +-6 m C%sT 2015 F 1 2 +-5 - EST +Z America/Merida -5:58:28 - LMT 1922 Ja 1 0:1:32 +-6 - CST 1981 D 23 +-5 - EST 1982 D 2 +-6 m C%sT +Z America/Matamoros -6:40 - LMT 1921 D 31 23:20 +-6 - CST 1988 +-6 u C%sT 1989 +-6 m C%sT 2010 +-6 u C%sT +Z America/Monterrey -6:41:16 - LMT 1921 D 31 23:18:44 +-6 - CST 1988 +-6 u C%sT 1989 +-6 m C%sT +Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 0:23:24 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 m C%sT 2001 S 30 2 +-6 - CST 2002 F 20 +-6 m C%sT +Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 0:2:20 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1996 +-6 m C%sT 1998 +-6 - CST 1998 Ap Sun>=1 3 +-7 m M%sT 2010 +-7 u M%sT +Z America/Chihuahua -7:4:20 - LMT 1921 D 31 23:55:40 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1996 +-6 m C%sT 1998 +-6 - CST 1998 Ap Sun>=1 3 +-7 m M%sT +Z America/Hermosillo -7:23:52 - LMT 1921 D 31 23:36:8 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT 1999 +-7 - MST +Z America/Mazatlan -7:5:40 - LMT 1921 D 31 23:54:20 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT +Z America/Bahia_Banderas -7:1 - LMT 1921 D 31 23:59 +-7 - MST 1927 Jun 10 23 +-6 - CST 1930 N 15 +-7 - MST 1931 May 1 23 +-6 - CST 1931 O +-7 - MST 1932 Ap +-6 - CST 1942 Ap 24 +-7 - MST 1949 Ja 14 +-8 - PST 1970 +-7 m M%sT 2010 Ap 4 2 +-6 m C%sT +Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 0:11:56 +-7 - MST 1924 +-8 - PST 1927 Jun 10 23 +-7 - MST 1930 N 15 +-8 - PST 1931 Ap +-8 1 PDT 1931 S 30 +-8 - PST 1942 Ap 24 +-8 1 PWT 1945 Au 14 23u +-8 1 PPT 1945 N 12 +-8 - PST 1948 Ap 5 +-8 1 PDT 1949 Ja 14 +-8 - PST 1954 +-8 CA P%sT 1961 +-8 - PST 1976 +-8 u P%sT 1996 +-8 m P%sT 2001 +-8 u P%sT 2002 F 20 +-8 m P%sT 2010 +-8 u P%sT +R BS 1964 1975 - O lastSun 2 0 S +R BS 1964 1975 - Ap lastSun 2 1 D +Z America/Nassau -5:9:30 - LMT 1912 Mar 2 +-5 BS E%sT 1976 +-5 u E%sT +R BB 1977 o - Jun 12 2 1 D +R BB 1977 1978 - O Sun>=1 2 0 S +R BB 1978 1980 - Ap Sun>=15 2 1 D +R BB 1979 o - S 30 2 0 S +R BB 1980 o - S 25 2 0 S +Z America/Barbados -3:58:29 - LMT 1924 +-3:58:29 - BMT 1932 +-4 BB A%sT +R BZ 1918 1942 - O Sun>=2 0 0:30 -0530 +R BZ 1919 1943 - F Sun>=9 0 0 CST +R BZ 1973 o - D 5 0 1 CDT +R BZ 1974 o - F 9 0 0 CST +R BZ 1982 o - D 18 0 1 CDT +R BZ 1983 o - F 12 0 0 CST +Z America/Belize -5:52:48 - LMT 1912 Ap +-6 BZ %s +Z Atlantic/Bermuda -4:19:18 - LMT 1930 Ja 1 2 +-4 - AST 1974 Ap 28 2 +-4 C A%sT 1976 +-4 u A%sT +R CR 1979 1980 - F lastSun 0 1 D +R CR 1979 1980 - Jun Sun>=1 0 0 S +R CR 1991 1992 - Ja Sat>=15 0 1 D +R CR 1991 o - Jul 1 0 0 S +R CR 1992 o - Mar 15 0 0 S +Z America/Costa_Rica -5:36:13 - LMT 1890 +-5:36:13 - SJMT 1921 Ja 15 +-6 CR C%sT +R Q 1928 o - Jun 10 0 1 D +R Q 1928 o - O 10 0 0 S +R Q 1940 1942 - Jun Sun>=1 0 1 D +R Q 1940 1942 - S Sun>=1 0 0 S +R Q 1945 1946 - Jun Sun>=1 0 1 D +R Q 1945 1946 - S Sun>=1 0 0 S +R Q 1965 o - Jun 1 0 1 D +R Q 1965 o - S 30 0 0 S +R Q 1966 o - May 29 0 1 D +R Q 1966 o - O 2 0 0 S +R Q 1967 o - Ap 8 0 1 D +R Q 1967 1968 - S Sun>=8 0 0 S +R Q 1968 o - Ap 14 0 1 D +R Q 1969 1977 - Ap lastSun 0 1 D +R Q 1969 1971 - O lastSun 0 0 S +R Q 1972 1974 - O 8 0 0 S +R Q 1975 1977 - O lastSun 0 0 S +R Q 1978 o - May 7 0 1 D +R Q 1978 1990 - O Sun>=8 0 0 S +R Q 1979 1980 - Mar Sun>=15 0 1 D +R Q 1981 1985 - May Sun>=5 0 1 D +R Q 1986 1989 - Mar Sun>=14 0 1 D +R Q 1990 1997 - Ap Sun>=1 0 1 D +R Q 1991 1995 - O Sun>=8 0s 0 S +R Q 1996 o - O 6 0s 0 S +R Q 1997 o - O 12 0s 0 S +R Q 1998 1999 - Mar lastSun 0s 1 D +R Q 1998 2003 - O lastSun 0s 0 S +R Q 2000 2003 - Ap Sun>=1 0s 1 D +R Q 2004 o - Mar lastSun 0s 1 D +R Q 2006 2010 - O lastSun 0s 0 S +R Q 2007 o - Mar Sun>=8 0s 1 D +R Q 2008 o - Mar Sun>=15 0s 1 D +R Q 2009 2010 - Mar Sun>=8 0s 1 D +R Q 2011 o - Mar Sun>=15 0s 1 D +R Q 2011 o - N 13 0s 0 S +R Q 2012 o - Ap 1 0s 1 D +R Q 2012 ma - N Sun>=1 0s 0 S +R Q 2013 ma - Mar Sun>=8 0s 1 D +Z America/Havana -5:29:28 - LMT 1890 +-5:29:36 - HMT 1925 Jul 19 12 +-5 Q C%sT +R DO 1966 o - O 30 0 1 EDT +R DO 1967 o - F 28 0 0 EST +R DO 1969 1973 - O lastSun 0 0:30 -0430 +R DO 1970 o - F 21 0 0 EST +R DO 1971 o - Ja 20 0 0 EST +R DO 1972 1974 - Ja 21 0 0 EST +Z America/Santo_Domingo -4:39:36 - LMT 1890 +-4:40 - SDMT 1933 Ap 1 12 +-5 DO %s 1974 O 27 +-4 - AST 2000 O 29 2 +-5 u E%sT 2000 D 3 1 +-4 - AST +R SV 1987 1988 - May Sun>=1 0 1 D +R SV 1987 1988 - S lastSun 0 0 S +Z America/El_Salvador -5:56:48 - LMT 1921 +-6 SV C%sT +R GT 1973 o - N 25 0 1 D +R GT 1974 o - F 24 0 0 S +R GT 1983 o - May 21 0 1 D +R GT 1983 o - S 22 0 0 S +R GT 1991 o - Mar 23 0 1 D +R GT 1991 o - S 7 0 0 S +R GT 2006 o - Ap 30 0 1 D +R GT 2006 o - O 1 0 0 S +Z America/Guatemala -6:2:4 - LMT 1918 O 5 +-6 GT C%sT +R HT 1983 o - May 8 0 1 D +R HT 1984 1987 - Ap lastSun 0 1 D +R HT 1983 1987 - O lastSun 0 0 S +R HT 1988 1997 - Ap Sun>=1 1s 1 D +R HT 1988 1997 - O lastSun 1s 0 S +R HT 2005 2006 - Ap Sun>=1 0 1 D +R HT 2005 2006 - O lastSun 0 0 S +R HT 2012 2015 - Mar Sun>=8 2 1 D +R HT 2012 2015 - N Sun>=1 2 0 S +R HT 2017 ma - Mar Sun>=8 2 1 D +R HT 2017 ma - N Sun>=1 2 0 S +Z America/Port-au-Prince -4:49:20 - LMT 1890 +-4:49 - PPMT 1917 Ja 24 12 +-5 HT E%sT +R HN 1987 1988 - May Sun>=1 0 1 D +R HN 1987 1988 - S lastSun 0 0 S +R HN 2006 o - May Sun>=1 0 1 D +R HN 2006 o - Au M>=1 0 0 S +Z America/Tegucigalpa -5:48:52 - LMT 1921 Ap +-6 HN C%sT +Z America/Jamaica -5:7:10 - LMT 1890 +-5:7:10 - KMT 1912 F +-5 - EST 1974 +-5 u E%sT 1984 +-5 - EST +Z America/Martinique -4:4:20 - LMT 1890 +-4:4:20 - FFMT 1911 May +-4 - AST 1980 Ap 6 +-4 1 ADT 1980 S 28 +-4 - AST +R NI 1979 1980 - Mar Sun>=16 0 1 D +R NI 1979 1980 - Jun M>=23 0 0 S +R NI 2005 o - Ap 10 0 1 D +R NI 2005 o - O Sun>=1 0 0 S +R NI 2006 o - Ap 30 2 1 D +R NI 2006 o - O Sun>=1 1 0 S +Z America/Managua -5:45:8 - LMT 1890 +-5:45:12 - MMT 1934 Jun 23 +-6 - CST 1973 May +-5 - EST 1975 F 16 +-6 NI C%sT 1992 Ja 1 4 +-5 - EST 1992 S 24 +-6 - CST 1993 +-5 - EST 1997 +-6 NI C%sT +Z America/Panama -5:18:8 - LMT 1890 +-5:19:36 - CMT 1908 Ap 22 +-5 - EST +Li America/Panama America/Cayman +Z America/Puerto_Rico -4:24:25 - LMT 1899 Mar 28 12 +-4 - AST 1942 May 3 +-4 u A%sT 1946 +-4 - AST +Z America/Miquelon -3:44:40 - LMT 1911 May 15 +-4 - AST 1980 May +-3 - -03 1987 +-3 C -03/-02 +Z America/Grand_Turk -4:44:32 - LMT 1890 +-5:7:10 - KMT 1912 F +-5 - EST 1979 +-5 u E%sT 2015 N Sun>=1 2 +-4 - AST 2018 Mar 11 3 +-5 u E%sT +R A 1930 o - D 1 0 1 - +R A 1931 o - Ap 1 0 0 - +R A 1931 o - O 15 0 1 - +R A 1932 1940 - Mar 1 0 0 - +R A 1932 1939 - N 1 0 1 - +R A 1940 o - Jul 1 0 1 - +R A 1941 o - Jun 15 0 0 - +R A 1941 o - O 15 0 1 - +R A 1943 o - Au 1 0 0 - +R A 1943 o - O 15 0 1 - +R A 1946 o - Mar 1 0 0 - +R A 1946 o - O 1 0 1 - +R A 1963 o - O 1 0 0 - +R A 1963 o - D 15 0 1 - +R A 1964 1966 - Mar 1 0 0 - +R A 1964 1966 - O 15 0 1 - +R A 1967 o - Ap 2 0 0 - +R A 1967 1968 - O Sun>=1 0 1 - +R A 1968 1969 - Ap Sun>=1 0 0 - +R A 1974 o - Ja 23 0 1 - +R A 1974 o - May 1 0 0 - +R A 1988 o - D 1 0 1 - +R A 1989 1993 - Mar Sun>=1 0 0 - +R A 1989 1992 - O Sun>=15 0 1 - +R A 1999 o - O Sun>=1 0 1 - +R A 2000 o - Mar 3 0 0 - +R A 2007 o - D 30 0 1 - +R A 2008 2009 - Mar Sun>=15 0 0 - +R A 2008 o - O Sun>=15 0 1 - +Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 +Z America/Argentina/Cordoba -4:16:48 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 +Z America/Argentina/Salta -4:21:40 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Tucuman -4:20:52 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 13 +-3 A -03/-02 +Z America/Argentina/La_Rioja -4:27:24 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar +-4 - -04 1991 May 7 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/San_Juan -4:34:4 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar +-4 - -04 1991 May 7 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 May 31 +-4 - -04 2004 Jul 25 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Jujuy -4:21:12 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1990 Mar 4 +-4 - -04 1990 O 28 +-4 1 -03 1991 Mar 17 +-4 - -04 1991 O 6 +-3 1 -02 1992 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Catamarca -4:23:8 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1991 Mar 3 +-4 - -04 1991 O 20 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Mendoza -4:35:16 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1990 Mar 4 +-4 - -04 1990 O 15 +-4 1 -03 1991 Mar +-4 - -04 1991 O 15 +-4 1 -03 1992 Mar +-4 - -04 1992 O 18 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 May 23 +-4 - -04 2004 S 26 +-3 A -03/-02 2008 O 18 +-3 - -03 +R Sa 2008 2009 - Mar Sun>=8 0 0 - +R Sa 2007 2008 - O Sun>=8 0 1 - +Z America/Argentina/San_Luis -4:25:24 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1990 +-3 1 -02 1990 Mar 14 +-4 - -04 1990 O 15 +-4 1 -03 1991 Mar +-4 - -04 1991 Jun +-3 - -03 1999 O 3 +-4 1 -03 2000 Mar 3 +-3 - -03 2004 May 31 +-4 - -04 2004 Jul 25 +-3 A -03/-02 2008 Ja 21 +-4 Sa -04/-03 2009 O 11 +-3 - -03 +Z America/Argentina/Rio_Gallegos -4:36:52 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 Jun +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Z America/Argentina/Ushuaia -4:33:12 - LMT 1894 O 31 +-4:16:48 - CMT 1920 May +-4 - -04 1930 D +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1999 O 3 +-4 A -04/-03 2000 Mar 3 +-3 - -03 2004 May 30 +-4 - -04 2004 Jun 20 +-3 A -03/-02 2008 O 18 +-3 - -03 +Li America/Curacao America/Aruba +Z America/La_Paz -4:32:36 - LMT 1890 +-4:32:36 - CMT 1931 O 15 +-4:32:36 1 BST 1932 Mar 21 +-4 - -04 +R B 1931 o - O 3 11 1 - +R B 1932 1933 - Ap 1 0 0 - +R B 1932 o - O 3 0 1 - +R B 1949 1952 - D 1 0 1 - +R B 1950 o - Ap 16 1 0 - +R B 1951 1952 - Ap 1 0 0 - +R B 1953 o - Mar 1 0 0 - +R B 1963 o - D 9 0 1 - +R B 1964 o - Mar 1 0 0 - +R B 1965 o - Ja 31 0 1 - +R B 1965 o - Mar 31 0 0 - +R B 1965 o - D 1 0 1 - +R B 1966 1968 - Mar 1 0 0 - +R B 1966 1967 - N 1 0 1 - +R B 1985 o - N 2 0 1 - +R B 1986 o - Mar 15 0 0 - +R B 1986 o - O 25 0 1 - +R B 1987 o - F 14 0 0 - +R B 1987 o - O 25 0 1 - +R B 1988 o - F 7 0 0 - +R B 1988 o - O 16 0 1 - +R B 1989 o - Ja 29 0 0 - +R B 1989 o - O 15 0 1 - +R B 1990 o - F 11 0 0 - +R B 1990 o - O 21 0 1 - +R B 1991 o - F 17 0 0 - +R B 1991 o - O 20 0 1 - +R B 1992 o - F 9 0 0 - +R B 1992 o - O 25 0 1 - +R B 1993 o - Ja 31 0 0 - +R B 1993 1995 - O Sun>=11 0 1 - +R B 1994 1995 - F Sun>=15 0 0 - +R B 1996 o - F 11 0 0 - +R B 1996 o - O 6 0 1 - +R B 1997 o - F 16 0 0 - +R B 1997 o - O 6 0 1 - +R B 1998 o - Mar 1 0 0 - +R B 1998 o - O 11 0 1 - +R B 1999 o - F 21 0 0 - +R B 1999 o - O 3 0 1 - +R B 2000 o - F 27 0 0 - +R B 2000 2001 - O Sun>=8 0 1 - +R B 2001 2006 - F Sun>=15 0 0 - +R B 2002 o - N 3 0 1 - +R B 2003 o - O 19 0 1 - +R B 2004 o - N 2 0 1 - +R B 2005 o - O 16 0 1 - +R B 2006 o - N 5 0 1 - +R B 2007 o - F 25 0 0 - +R B 2007 o - O Sun>=8 0 1 - +R B 2008 2017 - O Sun>=15 0 1 - +R B 2008 2011 - F Sun>=15 0 0 - +R B 2012 o - F Sun>=22 0 0 - +R B 2013 2014 - F Sun>=15 0 0 - +R B 2015 o - F Sun>=22 0 0 - +R B 2016 2022 - F Sun>=15 0 0 - +R B 2018 ma - N Sun>=1 0 1 - +R B 2023 o - F Sun>=22 0 0 - +R B 2024 2025 - F Sun>=15 0 0 - +R B 2026 o - F Sun>=22 0 0 - +R B 2027 2033 - F Sun>=15 0 0 - +R B 2034 o - F Sun>=22 0 0 - +R B 2035 2036 - F Sun>=15 0 0 - +R B 2037 o - F Sun>=22 0 0 - +R B 2038 ma - F Sun>=15 0 0 - +Z America/Noronha -2:9:40 - LMT 1914 +-2 B -02/-01 1990 S 17 +-2 - -02 1999 S 30 +-2 B -02/-01 2000 O 15 +-2 - -02 2001 S 13 +-2 B -02/-01 2002 O +-2 - -02 +Z America/Belem -3:13:56 - LMT 1914 +-3 B -03/-02 1988 S 12 +-3 - -03 +Z America/Santarem -3:38:48 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 2008 Jun 24 +-3 - -03 +Z America/Fortaleza -2:34 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1999 S 30 +-3 B -03/-02 2000 O 22 +-3 - -03 2001 S 13 +-3 B -03/-02 2002 O +-3 - -03 +Z America/Recife -2:19:36 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1999 S 30 +-3 B -03/-02 2000 O 15 +-3 - -03 2001 S 13 +-3 B -03/-02 2002 O +-3 - -03 +Z America/Araguaina -3:12:48 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1995 S 14 +-3 B -03/-02 2003 S 24 +-3 - -03 2012 O 21 +-3 B -03/-02 2013 S +-3 - -03 +Z America/Maceio -2:22:52 - LMT 1914 +-3 B -03/-02 1990 S 17 +-3 - -03 1995 O 13 +-3 B -03/-02 1996 S 4 +-3 - -03 1999 S 30 +-3 B -03/-02 2000 O 22 +-3 - -03 2001 S 13 +-3 B -03/-02 2002 O +-3 - -03 +Z America/Bahia -2:34:4 - LMT 1914 +-3 B -03/-02 2003 S 24 +-3 - -03 2011 O 16 +-3 B -03/-02 2012 O 21 +-3 - -03 +Z America/Sao_Paulo -3:6:28 - LMT 1914 +-3 B -03/-02 1963 O 23 +-3 1 -02 1964 +-3 B -03/-02 +Z America/Campo_Grande -3:38:28 - LMT 1914 +-4 B -04/-03 +Z America/Cuiaba -3:44:20 - LMT 1914 +-4 B -04/-03 2003 S 24 +-4 - -04 2004 O +-4 B -04/-03 +Z America/Porto_Velho -4:15:36 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 +Z America/Boa_Vista -4:2:40 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 1999 S 30 +-4 B -04/-03 2000 O 15 +-4 - -04 +Z America/Manaus -4:0:4 - LMT 1914 +-4 B -04/-03 1988 S 12 +-4 - -04 1993 S 28 +-4 B -04/-03 1994 S 22 +-4 - -04 +Z America/Eirunepe -4:39:28 - LMT 1914 +-5 B -05/-04 1988 S 12 +-5 - -05 1993 S 28 +-5 B -05/-04 1994 S 22 +-5 - -05 2008 Jun 24 +-4 - -04 2013 N 10 +-5 - -05 +Z America/Rio_Branco -4:31:12 - LMT 1914 +-5 B -05/-04 1988 S 12 +-5 - -05 2008 Jun 24 +-4 - -04 2013 N 10 +-5 - -05 +R x 1927 1931 - S 1 0 1 - +R x 1928 1932 - Ap 1 0 0 - +R x 1968 o - N 3 4u 1 - +R x 1969 o - Mar 30 3u 0 - +R x 1969 o - N 23 4u 1 - +R x 1970 o - Mar 29 3u 0 - +R x 1971 o - Mar 14 3u 0 - +R x 1970 1972 - O Sun>=9 4u 1 - +R x 1972 1986 - Mar Sun>=9 3u 0 - +R x 1973 o - S 30 4u 1 - +R x 1974 1987 - O Sun>=9 4u 1 - +R x 1987 o - Ap 12 3u 0 - +R x 1988 1990 - Mar Sun>=9 3u 0 - +R x 1988 1989 - O Sun>=9 4u 1 - +R x 1990 o - S 16 4u 1 - +R x 1991 1996 - Mar Sun>=9 3u 0 - +R x 1991 1997 - O Sun>=9 4u 1 - +R x 1997 o - Mar 30 3u 0 - +R x 1998 o - Mar Sun>=9 3u 0 - +R x 1998 o - S 27 4u 1 - +R x 1999 o - Ap 4 3u 0 - +R x 1999 2010 - O Sun>=9 4u 1 - +R x 2000 2007 - Mar Sun>=9 3u 0 - +R x 2008 o - Mar 30 3u 0 - +R x 2009 o - Mar Sun>=9 3u 0 - +R x 2010 o - Ap Sun>=1 3u 0 - +R x 2011 o - May Sun>=2 3u 0 - +R x 2011 o - Au Sun>=16 4u 1 - +R x 2012 2014 - Ap Sun>=23 3u 0 - +R x 2012 2014 - S Sun>=2 4u 1 - +R x 2016 2018 - May Sun>=9 3u 0 - +R x 2016 2018 - Au Sun>=9 4u 1 - +R x 2019 ma - Ap Sun>=2 3u 0 - +R x 2019 ma - S Sun>=2 4u 1 - +Z America/Santiago -4:42:46 - LMT 1890 +-4:42:46 - SMT 1910 Ja 10 +-5 - -05 1916 Jul +-4:42:46 - SMT 1918 S 10 +-4 - -04 1919 Jul +-4:42:46 - SMT 1927 S +-5 x -05/-04 1932 S +-4 - -04 1942 Jun +-5 - -05 1942 Au +-4 - -04 1946 Jul 15 +-4 1 -03 1946 S +-4 - -04 1947 Ap +-5 - -05 1947 May 21 23 +-4 x -04/-03 +Z America/Punta_Arenas -4:43:40 - LMT 1890 +-4:42:46 - SMT 1910 Ja 10 +-5 - -05 1916 Jul +-4:42:46 - SMT 1918 S 10 +-4 - -04 1919 Jul +-4:42:46 - SMT 1927 S +-5 x -05/-04 1932 S +-4 - -04 1942 Jun +-5 - -05 1942 Au +-4 - -04 1947 Ap +-5 - -05 1947 May 21 23 +-4 x -04/-03 2016 D 4 +-3 - -03 +Z Pacific/Easter -7:17:28 - LMT 1890 +-7:17:28 - EMT 1932 S +-7 x -07/-06 1982 Mar 14 3u +-6 x -06/-05 +Z Antarctica/Palmer 0 - -00 1965 +-4 A -04/-03 1969 O 5 +-3 A -03/-02 1982 May +-4 x -04/-03 2016 D 4 +-3 - -03 +R CO 1992 o - May 3 0 1 - +R CO 1993 o - Ap 4 0 0 - +Z America/Bogota -4:56:16 - LMT 1884 Mar 13 +-4:56:16 - BMT 1914 N 23 +-5 CO -05/-04 +Z America/Curacao -4:35:47 - LMT 1912 F 12 +-4:30 - -0430 1965 +-4 - AST +Li America/Curacao America/Lower_Princes +Li America/Curacao America/Kralendijk +R EC 1992 o - N 28 0 1 - +R EC 1993 o - F 5 0 0 - +Z America/Guayaquil -5:19:20 - LMT 1890 +-5:14 - QMT 1931 +-5 EC -05/-04 +Z Pacific/Galapagos -5:58:24 - LMT 1931 +-5 - -05 1986 +-6 EC -06/-05 +R FK 1937 1938 - S lastSun 0 1 - +R FK 1938 1942 - Mar Sun>=19 0 0 - +R FK 1939 o - O 1 0 1 - +R FK 1940 1942 - S lastSun 0 1 - +R FK 1943 o - Ja 1 0 0 - +R FK 1983 o - S lastSun 0 1 - +R FK 1984 1985 - Ap lastSun 0 0 - +R FK 1984 o - S 16 0 1 - +R FK 1985 2000 - S Sun>=9 0 1 - +R FK 1986 2000 - Ap Sun>=16 0 0 - +R FK 2001 2010 - Ap Sun>=15 2 0 - +R FK 2001 2010 - S Sun>=1 2 1 - +Z Atlantic/Stanley -3:51:24 - LMT 1890 +-3:51:24 - SMT 1912 Mar 12 +-4 FK -04/-03 1983 May +-3 FK -03/-02 1985 S 15 +-4 FK -04/-03 2010 S 5 2 +-3 - -03 +Z America/Cayenne -3:29:20 - LMT 1911 Jul +-4 - -04 1967 O +-3 - -03 +Z America/Guyana -3:52:40 - LMT 1915 Mar +-3:45 - -0345 1975 Jul 31 +-3 - -03 1991 +-4 - -04 +R y 1975 1988 - O 1 0 1 - +R y 1975 1978 - Mar 1 0 0 - +R y 1979 1991 - Ap 1 0 0 - +R y 1989 o - O 22 0 1 - +R y 1990 o - O 1 0 1 - +R y 1991 o - O 6 0 1 - +R y 1992 o - Mar 1 0 0 - +R y 1992 o - O 5 0 1 - +R y 1993 o - Mar 31 0 0 - +R y 1993 1995 - O 1 0 1 - +R y 1994 1995 - F lastSun 0 0 - +R y 1996 o - Mar 1 0 0 - +R y 1996 2001 - O Sun>=1 0 1 - +R y 1997 o - F lastSun 0 0 - +R y 1998 2001 - Mar Sun>=1 0 0 - +R y 2002 2004 - Ap Sun>=1 0 0 - +R y 2002 2003 - S Sun>=1 0 1 - +R y 2004 2009 - O Sun>=15 0 1 - +R y 2005 2009 - Mar Sun>=8 0 0 - +R y 2010 ma - O Sun>=1 0 1 - +R y 2010 2012 - Ap Sun>=8 0 0 - +R y 2013 ma - Mar Sun>=22 0 0 - +Z America/Asuncion -3:50:40 - LMT 1890 +-3:50:40 - AMT 1931 O 10 +-4 - -04 1972 O +-3 - -03 1974 Ap +-4 y -04/-03 +R PE 1938 o - Ja 1 0 1 - +R PE 1938 o - Ap 1 0 0 - +R PE 1938 1939 - S lastSun 0 1 - +R PE 1939 1940 - Mar Sun>=24 0 0 - +R PE 1986 1987 - Ja 1 0 1 - +R PE 1986 1987 - Ap 1 0 0 - +R PE 1990 o - Ja 1 0 1 - +R PE 1990 o - Ap 1 0 0 - +R PE 1994 o - Ja 1 0 1 - +R PE 1994 o - Ap 1 0 0 - +Z America/Lima -5:8:12 - LMT 1890 +-5:8:36 - LMT 1908 Jul 28 +-5 PE -05/-04 +Z Atlantic/South_Georgia -2:26:8 - LMT 1890 +-2 - -02 +Z America/Paramaribo -3:40:40 - LMT 1911 +-3:40:52 - PMT 1935 +-3:40:36 - PMT 1945 O +-3:30 - -0330 1984 O +-3 - -03 +Z America/Port_of_Spain -4:6:4 - LMT 1912 Mar 2 +-4 - AST +Li America/Port_of_Spain America/Anguilla +Li America/Port_of_Spain America/Antigua +Li America/Port_of_Spain America/Dominica +Li America/Port_of_Spain America/Grenada +Li America/Port_of_Spain America/Guadeloupe +Li America/Port_of_Spain America/Marigot +Li America/Port_of_Spain America/Montserrat +Li America/Port_of_Spain America/St_Barthelemy +Li America/Port_of_Spain America/St_Kitts +Li America/Port_of_Spain America/St_Lucia +Li America/Port_of_Spain America/St_Thomas +Li America/Port_of_Spain America/St_Vincent +Li America/Port_of_Spain America/Tortola +R U 1923 1925 - O 1 0 0:30 - +R U 1924 1926 - Ap 1 0 0 - +R U 1933 1938 - O lastSun 0 0:30 - +R U 1934 1941 - Mar lastSat 24 0 - +R U 1939 o - O 1 0 0:30 - +R U 1940 o - O 27 0 0:30 - +R U 1941 o - Au 1 0 0:30 - +R U 1942 o - D 14 0 0:30 - +R U 1943 o - Mar 14 0 0 - +R U 1959 o - May 24 0 0:30 - +R U 1959 o - N 15 0 0 - +R U 1960 o - Ja 17 0 1 - +R U 1960 o - Mar 6 0 0 - +R U 1965 o - Ap 4 0 1 - +R U 1965 o - S 26 0 0 - +R U 1968 o - May 27 0 0:30 - +R U 1968 o - D 1 0 0 - +R U 1970 o - Ap 25 0 1 - +R U 1970 o - Jun 14 0 0 - +R U 1972 o - Ap 23 0 1 - +R U 1972 o - Jul 16 0 0 - +R U 1974 o - Ja 13 0 1:30 - +R U 1974 o - Mar 10 0 0:30 - +R U 1974 o - S 1 0 0 - +R U 1974 o - D 22 0 1 - +R U 1975 o - Mar 30 0 0 - +R U 1976 o - D 19 0 1 - +R U 1977 o - Mar 6 0 0 - +R U 1977 o - D 4 0 1 - +R U 1978 1979 - Mar Sun>=1 0 0 - +R U 1978 o - D 17 0 1 - +R U 1979 o - Ap 29 0 1 - +R U 1980 o - Mar 16 0 0 - +R U 1987 o - D 14 0 1 - +R U 1988 o - F 28 0 0 - +R U 1988 o - D 11 0 1 - +R U 1989 o - Mar 5 0 0 - +R U 1989 o - O 29 0 1 - +R U 1990 o - F 25 0 0 - +R U 1990 1991 - O Sun>=21 0 1 - +R U 1991 1992 - Mar Sun>=1 0 0 - +R U 1992 o - O 18 0 1 - +R U 1993 o - F 28 0 0 - +R U 2004 o - S 19 0 1 - +R U 2005 o - Mar 27 2 0 - +R U 2005 o - O 9 2 1 - +R U 2006 2015 - Mar Sun>=8 2 0 - +R U 2006 2014 - O Sun>=1 2 1 - +Z America/Montevideo -3:44:51 - LMT 1908 Jun 10 +-3:44:51 - MMT 1920 May +-4 - -04 1923 O +-3:30 U -0330/-03 1942 D 14 +-3 U -03/-0230 1960 +-3 U -03/-02 1968 +-3 U -03/-0230 1970 +-3 U -03/-02 1974 +-3 U -03/-0130 1974 Mar 10 +-3 U -03/-0230 1974 D 22 +-3 U -03/-02 +Z America/Caracas -4:27:44 - LMT 1890 +-4:27:40 - CMT 1912 F 12 +-4:30 - -0430 1965 +-4 - -04 2007 D 9 3 +-4:30 - -0430 2016 May 1 2:30 +-4 - -04 +Z Etc/GMT 0 - GMT +Z Etc/UTC 0 - UTC +Z Etc/UCT 0 - UCT +Li Etc/GMT GMT +Li Etc/UTC Etc/Universal +Li Etc/UTC Etc/Zulu +Li Etc/GMT Etc/Greenwich +Li Etc/GMT Etc/GMT-0 +Li Etc/GMT Etc/GMT+0 +Li Etc/GMT Etc/GMT0 +Z Etc/GMT-14 14 - +14 +Z Etc/GMT-13 13 - +13 +Z Etc/GMT-12 12 - +12 +Z Etc/GMT-11 11 - +11 +Z Etc/GMT-10 10 - +10 +Z Etc/GMT-9 9 - +09 +Z Etc/GMT-8 8 - +08 +Z Etc/GMT-7 7 - +07 +Z Etc/GMT-6 6 - +06 +Z Etc/GMT-5 5 - +05 +Z Etc/GMT-4 4 - +04 +Z Etc/GMT-3 3 - +03 +Z Etc/GMT-2 2 - +02 +Z Etc/GMT-1 1 - +01 +Z Etc/GMT+1 -1 - -01 +Z Etc/GMT+2 -2 - -02 +Z Etc/GMT+3 -3 - -03 +Z Etc/GMT+4 -4 - -04 +Z Etc/GMT+5 -5 - -05 +Z Etc/GMT+6 -6 - -06 +Z Etc/GMT+7 -7 - -07 +Z Etc/GMT+8 -8 - -08 +Z Etc/GMT+9 -9 - -09 +Z Etc/GMT+10 -10 - -10 +Z Etc/GMT+11 -11 - -11 +Z Etc/GMT+12 -12 - -12 +Z Factory 0 - -00 +Li Africa/Nairobi Africa/Asmera +Li Africa/Abidjan Africa/Timbuktu +Li America/Argentina/Catamarca America/Argentina/ComodRivadavia +Li America/Adak America/Atka +Li America/Argentina/Buenos_Aires America/Buenos_Aires +Li America/Argentina/Catamarca America/Catamarca +Li America/Atikokan America/Coral_Harbour +Li America/Argentina/Cordoba America/Cordoba +Li America/Tijuana America/Ensenada +Li America/Indiana/Indianapolis America/Fort_Wayne +Li America/Indiana/Indianapolis America/Indianapolis +Li America/Argentina/Jujuy America/Jujuy +Li America/Indiana/Knox America/Knox_IN +Li America/Kentucky/Louisville America/Louisville +Li America/Argentina/Mendoza America/Mendoza +Li America/Toronto America/Montreal +Li America/Rio_Branco America/Porto_Acre +Li America/Argentina/Cordoba America/Rosario +Li America/Tijuana America/Santa_Isabel +Li America/Denver America/Shiprock +Li America/Port_of_Spain America/Virgin +Li Pacific/Auckland Antarctica/South_Pole +Li Asia/Ashgabat Asia/Ashkhabad +Li Asia/Kolkata Asia/Calcutta +Li Asia/Shanghai Asia/Chongqing +Li Asia/Shanghai Asia/Chungking +Li Asia/Dhaka Asia/Dacca +Li Asia/Shanghai Asia/Harbin +Li Asia/Urumqi Asia/Kashgar +Li Asia/Kathmandu Asia/Katmandu +Li Asia/Macau Asia/Macao +Li Asia/Yangon Asia/Rangoon +Li Asia/Ho_Chi_Minh Asia/Saigon +Li Asia/Jerusalem Asia/Tel_Aviv +Li Asia/Thimphu Asia/Thimbu +Li Asia/Makassar Asia/Ujung_Pandang +Li Asia/Ulaanbaatar Asia/Ulan_Bator +Li Atlantic/Faroe Atlantic/Faeroe +Li Europe/Oslo Atlantic/Jan_Mayen +Li Australia/Sydney Australia/ACT +Li Australia/Sydney Australia/Canberra +Li Australia/Lord_Howe Australia/LHI +Li Australia/Sydney Australia/NSW +Li Australia/Darwin Australia/North +Li Australia/Brisbane Australia/Queensland +Li Australia/Adelaide Australia/South +Li Australia/Hobart Australia/Tasmania +Li Australia/Melbourne Australia/Victoria +Li Australia/Perth Australia/West +Li Australia/Broken_Hill Australia/Yancowinna +Li America/Rio_Branco Brazil/Acre +Li America/Noronha Brazil/DeNoronha +Li America/Sao_Paulo Brazil/East +Li America/Manaus Brazil/West +Li America/Halifax Canada/Atlantic +Li America/Winnipeg Canada/Central +Li America/Toronto Canada/Eastern +Li America/Edmonton Canada/Mountain +Li America/St_Johns Canada/Newfoundland +Li America/Vancouver Canada/Pacific +Li America/Regina Canada/Saskatchewan +Li America/Whitehorse Canada/Yukon +Li America/Santiago Chile/Continental +Li Pacific/Easter Chile/EasterIsland +Li America/Havana Cuba +Li Africa/Cairo Egypt +Li Europe/Dublin Eire +Li Europe/London Europe/Belfast +Li Europe/Chisinau Europe/Tiraspol +Li Europe/London GB +Li Europe/London GB-Eire +Li Etc/GMT GMT+0 +Li Etc/GMT GMT-0 +Li Etc/GMT GMT0 +Li Etc/GMT Greenwich +Li Asia/Hong_Kong Hongkong +Li Atlantic/Reykjavik Iceland +Li Asia/Tehran Iran +Li Asia/Jerusalem Israel +Li America/Jamaica Jamaica +Li Asia/Tokyo Japan +Li Pacific/Kwajalein Kwajalein +Li Africa/Tripoli Libya +Li America/Tijuana Mexico/BajaNorte +Li America/Mazatlan Mexico/BajaSur +Li America/Mexico_City Mexico/General +Li Pacific/Auckland NZ +Li Pacific/Chatham NZ-CHAT +Li America/Denver Navajo +Li Asia/Shanghai PRC +Li Pacific/Honolulu Pacific/Johnston +Li Pacific/Pohnpei Pacific/Ponape +Li Pacific/Pago_Pago Pacific/Samoa +Li Pacific/Chuuk Pacific/Truk +Li Pacific/Chuuk Pacific/Yap +Li Europe/Warsaw Poland +Li Europe/Lisbon Portugal +Li Asia/Taipei ROC +Li Asia/Seoul ROK +Li Asia/Singapore Singapore +Li Europe/Istanbul Turkey +Li Etc/UCT UCT +Li America/Anchorage US/Alaska +Li America/Adak US/Aleutian +Li America/Phoenix US/Arizona +Li America/Chicago US/Central +Li America/Indiana/Indianapolis US/East-Indiana +Li America/New_York US/Eastern +Li Pacific/Honolulu US/Hawaii +Li America/Indiana/Knox US/Indiana-Starke +Li America/Detroit US/Michigan +Li America/Denver US/Mountain +Li America/Los_Angeles US/Pacific +Li Pacific/Pago_Pago US/Samoa +Li Etc/UTC UTC +Li Etc/UTC Universal +Li Europe/Moscow W-SU +Li Etc/UTC Zulu diff --git a/lib/pytz/zoneinfo/zone.tab b/lib/pytz/zoneinfo/zone.tab new file mode 100644 index 0000000..dcb6e1d --- /dev/null +++ b/lib/pytz/zoneinfo/zone.tab @@ -0,0 +1,448 @@ +# tzdb timezone descriptions (deprecated version) +# +# This file is in the public domain, so clarified as of +# 2009-05-17 by Arthur David Olson. +# +# From Paul Eggert (2018-06-27): +# This file is intended as a backward-compatibility aid for older programs. +# New programs should use zone1970.tab. This file is like zone1970.tab (see +# zone1970.tab's comments), but with the following additional restrictions: +# +# 1. This file contains only ASCII characters. +# 2. The first data column contains exactly one country code. +# +# Because of (2), each row stands for an area that is the intersection +# of a region identified by a country code and of a timezone where civil +# clocks have agreed since 1970; this is a narrower definition than +# that of zone1970.tab. +# +# This table is intended as an aid for users, to help them select timezones +# appropriate for their practical needs. It is not intended to take or +# endorse any position on legal or territorial claims. +# +#country- +#code coordinates TZ comments +AD +4230+00131 Europe/Andorra +AE +2518+05518 Asia/Dubai +AF +3431+06912 Asia/Kabul +AG +1703-06148 America/Antigua +AI +1812-06304 America/Anguilla +AL +4120+01950 Europe/Tirane +AM +4011+04430 Asia/Yerevan +AO -0848+01314 Africa/Luanda +AQ -7750+16636 Antarctica/McMurdo New Zealand time - McMurdo, South Pole +AQ -6617+11031 Antarctica/Casey Casey +AQ -6835+07758 Antarctica/Davis Davis +AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville +AQ -6736+06253 Antarctica/Mawson Mawson +AQ -6448-06406 Antarctica/Palmer Palmer +AQ -6734-06808 Antarctica/Rothera Rothera +AQ -690022+0393524 Antarctica/Syowa Syowa +AQ -720041+0023206 Antarctica/Troll Troll +AQ -7824+10654 Antarctica/Vostok Vostok +AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) +AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) +AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) +AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) +AR -2649-06513 America/Argentina/Tucuman Tucuman (TM) +AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH) +AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) +AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) +AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) +AR -3319-06621 America/Argentina/San_Luis San Luis (SL) +AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) +AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) +AS -1416-17042 Pacific/Pago_Pago +AT +4813+01620 Europe/Vienna +AU -3133+15905 Australia/Lord_Howe Lord Howe Island +AU -5430+15857 Antarctica/Macquarie Macquarie Island +AU -4253+14719 Australia/Hobart Tasmania (most areas) +AU -3956+14352 Australia/Currie Tasmania (King Island) +AU -3749+14458 Australia/Melbourne Victoria +AU -3352+15113 Australia/Sydney New South Wales (most areas) +AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) +AU -2728+15302 Australia/Brisbane Queensland (most areas) +AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) +AU -3455+13835 Australia/Adelaide South Australia +AU -1228+13050 Australia/Darwin Northern Territory +AU -3157+11551 Australia/Perth Western Australia (most areas) +AU -3143+12852 Australia/Eucla Western Australia (Eucla) +AW +1230-06958 America/Aruba +AX +6006+01957 Europe/Mariehamn +AZ +4023+04951 Asia/Baku +BA +4352+01825 Europe/Sarajevo +BB +1306-05937 America/Barbados +BD +2343+09025 Asia/Dhaka +BE +5050+00420 Europe/Brussels +BF +1222-00131 Africa/Ouagadougou +BG +4241+02319 Europe/Sofia +BH +2623+05035 Asia/Bahrain +BI -0323+02922 Africa/Bujumbura +BJ +0629+00237 Africa/Porto-Novo +BL +1753-06251 America/St_Barthelemy +BM +3217-06446 Atlantic/Bermuda +BN +0456+11455 Asia/Brunei +BO -1630-06809 America/La_Paz +BQ +120903-0681636 America/Kralendijk +BR -0351-03225 America/Noronha Atlantic islands +BR -0127-04829 America/Belem Para (east); Amapa +BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) +BR -0803-03454 America/Recife Pernambuco +BR -0712-04812 America/Araguaina Tocantins +BR -0940-03543 America/Maceio Alagoas, Sergipe +BR -1259-03831 America/Bahia Bahia +BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) +BR -2027-05437 America/Campo_Grande Mato Grosso do Sul +BR -1535-05605 America/Cuiaba Mato Grosso +BR -0226-05452 America/Santarem Para (west) +BR -0846-06354 America/Porto_Velho Rondonia +BR +0249-06040 America/Boa_Vista Roraima +BR -0308-06001 America/Manaus Amazonas (east) +BR -0640-06952 America/Eirunepe Amazonas (west) +BR -0958-06748 America/Rio_Branco Acre +BS +2505-07721 America/Nassau +BT +2728+08939 Asia/Thimphu +BW -2439+02555 Africa/Gaborone +BY +5354+02734 Europe/Minsk +BZ +1730-08812 America/Belize +CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast) +CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE +CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) +CA +4606-06447 America/Moncton Atlantic - New Brunswick +CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) +CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) +CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) +CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) +CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) +CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) +CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) +CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) +CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba +CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) +CA +744144-0944945 America/Resolute Central - NU (Resolute) +CA +624900-0920459 America/Rankin_Inlet Central - NU (central) +CA +5024-10439 America/Regina CST - SK (most areas) +CA +5017-10750 America/Swift_Current CST - SK (midwest) +CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) +CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) +CA +6227-11421 America/Yellowknife Mountain - NT (central) +CA +682059-1334300 America/Inuvik Mountain - NT (west) +CA +4906-11631 America/Creston MST - BC (Creston) +CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) +CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) +CA +4916-12307 America/Vancouver Pacific - BC (most areas) +CA +6043-13503 America/Whitehorse Pacific - Yukon (south) +CA +6404-13925 America/Dawson Pacific - Yukon (north) +CC -1210+09655 Indian/Cocos +CD -0418+01518 Africa/Kinshasa Dem. Rep. of Congo (west) +CD -1140+02728 Africa/Lubumbashi Dem. Rep. of Congo (east) +CF +0422+01835 Africa/Bangui +CG -0416+01517 Africa/Brazzaville +CH +4723+00832 Europe/Zurich +CI +0519-00402 Africa/Abidjan +CK -2114-15946 Pacific/Rarotonga +CL -3327-07040 America/Santiago Chile (most areas) +CL -5309-07055 America/Punta_Arenas Region of Magallanes +CL -2709-10926 Pacific/Easter Easter Island +CM +0403+00942 Africa/Douala +CN +3114+12128 Asia/Shanghai Beijing Time +CN +4348+08735 Asia/Urumqi Xinjiang Time +CO +0436-07405 America/Bogota +CR +0956-08405 America/Costa_Rica +CU +2308-08222 America/Havana +CV +1455-02331 Atlantic/Cape_Verde +CW +1211-06900 America/Curacao +CX -1025+10543 Indian/Christmas +CY +3510+03322 Asia/Nicosia Cyprus (most areas) +CY +3507+03357 Asia/Famagusta Northern Cyprus +CZ +5005+01426 Europe/Prague +DE +5230+01322 Europe/Berlin Germany (most areas) +DE +4742+00841 Europe/Busingen Busingen +DJ +1136+04309 Africa/Djibouti +DK +5540+01235 Europe/Copenhagen +DM +1518-06124 America/Dominica +DO +1828-06954 America/Santo_Domingo +DZ +3647+00303 Africa/Algiers +EC -0210-07950 America/Guayaquil Ecuador (mainland) +EC -0054-08936 Pacific/Galapagos Galapagos Islands +EE +5925+02445 Europe/Tallinn +EG +3003+03115 Africa/Cairo +EH +2709-01312 Africa/El_Aaiun +ER +1520+03853 Africa/Asmara +ES +4024-00341 Europe/Madrid Spain (mainland) +ES +3553-00519 Africa/Ceuta Ceuta, Melilla +ES +2806-01524 Atlantic/Canary Canary Islands +ET +0902+03842 Africa/Addis_Ababa +FI +6010+02458 Europe/Helsinki +FJ -1808+17825 Pacific/Fiji +FK -5142-05751 Atlantic/Stanley +FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap +FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape +FM +0519+16259 Pacific/Kosrae Kosrae +FO +6201-00646 Atlantic/Faroe +FR +4852+00220 Europe/Paris +GA +0023+00927 Africa/Libreville +GB +513030-0000731 Europe/London +GD +1203-06145 America/Grenada +GE +4143+04449 Asia/Tbilisi +GF +0456-05220 America/Cayenne +GG +492717-0023210 Europe/Guernsey +GH +0533-00013 Africa/Accra +GI +3608-00521 Europe/Gibraltar +GL +6411-05144 America/Godthab Greenland (most areas) +GL +7646-01840 America/Danmarkshavn National Park (east coast) +GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit +GL +7634-06847 America/Thule Thule/Pituffik +GM +1328-01639 Africa/Banjul +GN +0931-01343 Africa/Conakry +GP +1614-06132 America/Guadeloupe +GQ +0345+00847 Africa/Malabo +GR +3758+02343 Europe/Athens +GS -5416-03632 Atlantic/South_Georgia +GT +1438-09031 America/Guatemala +GU +1328+14445 Pacific/Guam +GW +1151-01535 Africa/Bissau +GY +0648-05810 America/Guyana +HK +2217+11409 Asia/Hong_Kong +HN +1406-08713 America/Tegucigalpa +HR +4548+01558 Europe/Zagreb +HT +1832-07220 America/Port-au-Prince +HU +4730+01905 Europe/Budapest +ID -0610+10648 Asia/Jakarta Java, Sumatra +ID -0002+10920 Asia/Pontianak Borneo (west, central) +ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west) +ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas +IE +5320-00615 Europe/Dublin +IL +314650+0351326 Asia/Jerusalem +IM +5409-00428 Europe/Isle_of_Man +IN +2232+08822 Asia/Kolkata +IO -0720+07225 Indian/Chagos +IQ +3321+04425 Asia/Baghdad +IR +3540+05126 Asia/Tehran +IS +6409-02151 Atlantic/Reykjavik +IT +4154+01229 Europe/Rome +JE +491101-0020624 Europe/Jersey +JM +175805-0764736 America/Jamaica +JO +3157+03556 Asia/Amman +JP +353916+1394441 Asia/Tokyo +KE -0117+03649 Africa/Nairobi +KG +4254+07436 Asia/Bishkek +KH +1133+10455 Asia/Phnom_Penh +KI +0125+17300 Pacific/Tarawa Gilbert Islands +KI -0308-17105 Pacific/Enderbury Phoenix Islands +KI +0152-15720 Pacific/Kiritimati Line Islands +KM -1141+04316 Indian/Comoro +KN +1718-06243 America/St_Kitts +KP +3901+12545 Asia/Pyongyang +KR +3733+12658 Asia/Seoul +KW +2920+04759 Asia/Kuwait +KY +1918-08123 America/Cayman +KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) +KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda +KZ +5017+05710 Asia/Aqtobe Aqtobe/Aktobe +KZ +4431+05016 Asia/Aqtau Mangghystau/Mankistau +KZ +4707+05156 Asia/Atyrau Atyrau/Atirau/Gur'yev +KZ +5113+05121 Asia/Oral West Kazakhstan +LA +1758+10236 Asia/Vientiane +LB +3353+03530 Asia/Beirut +LC +1401-06100 America/St_Lucia +LI +4709+00931 Europe/Vaduz +LK +0656+07951 Asia/Colombo +LR +0618-01047 Africa/Monrovia +LS -2928+02730 Africa/Maseru +LT +5441+02519 Europe/Vilnius +LU +4936+00609 Europe/Luxembourg +LV +5657+02406 Europe/Riga +LY +3254+01311 Africa/Tripoli +MA +3339-00735 Africa/Casablanca +MC +4342+00723 Europe/Monaco +MD +4700+02850 Europe/Chisinau +ME +4226+01916 Europe/Podgorica +MF +1804-06305 America/Marigot +MG -1855+04731 Indian/Antananarivo +MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) +MH +0905+16720 Pacific/Kwajalein Kwajalein +MK +4159+02126 Europe/Skopje +ML +1239-00800 Africa/Bamako +MM +1647+09610 Asia/Yangon +MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) +MN +4801+09139 Asia/Hovd Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan +MN +4804+11430 Asia/Choibalsan Dornod, Sukhbaatar +MO +221150+1133230 Asia/Macau +MP +1512+14545 Pacific/Saipan +MQ +1436-06105 America/Martinique +MR +1806-01557 Africa/Nouakchott +MS +1643-06213 America/Montserrat +MT +3554+01431 Europe/Malta +MU -2010+05730 Indian/Mauritius +MV +0410+07330 Indian/Maldives +MW -1547+03500 Africa/Blantyre +MX +1924-09909 America/Mexico_City Central Time +MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo +MX +2058-08937 America/Merida Central Time - Campeche, Yucatan +MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas) +MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo Leon, Tamaulipas (US border) +MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa +MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) +MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) +MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora +MX +3232-11701 America/Tijuana Pacific Time US - Baja California +MX +2048-10515 America/Bahia_Banderas Central Time - Bahia de Banderas +MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) +MY +0133+11020 Asia/Kuching Sabah, Sarawak +MZ -2558+03235 Africa/Maputo +NA -2234+01706 Africa/Windhoek +NC -2216+16627 Pacific/Noumea +NE +1331+00207 Africa/Niamey +NF -2903+16758 Pacific/Norfolk +NG +0627+00324 Africa/Lagos +NI +1209-08617 America/Managua +NL +5222+00454 Europe/Amsterdam +NO +5955+01045 Europe/Oslo +NP +2743+08519 Asia/Kathmandu +NR -0031+16655 Pacific/Nauru +NU -1901-16955 Pacific/Niue +NZ -3652+17446 Pacific/Auckland New Zealand (most areas) +NZ -4357-17633 Pacific/Chatham Chatham Islands +OM +2336+05835 Asia/Muscat +PA +0858-07932 America/Panama +PE -1203-07703 America/Lima +PF -1732-14934 Pacific/Tahiti Society Islands +PF -0900-13930 Pacific/Marquesas Marquesas Islands +PF -2308-13457 Pacific/Gambier Gambier Islands +PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) +PG -0613+15534 Pacific/Bougainville Bougainville +PH +1435+12100 Asia/Manila +PK +2452+06703 Asia/Karachi +PL +5215+02100 Europe/Warsaw +PM +4703-05620 America/Miquelon +PN -2504-13005 Pacific/Pitcairn +PR +182806-0660622 America/Puerto_Rico +PS +3130+03428 Asia/Gaza Gaza Strip +PS +313200+0350542 Asia/Hebron West Bank +PT +3843-00908 Europe/Lisbon Portugal (mainland) +PT +3238-01654 Atlantic/Madeira Madeira Islands +PT +3744-02540 Atlantic/Azores Azores +PW +0720+13429 Pacific/Palau +PY -2516-05740 America/Asuncion +QA +2517+05132 Asia/Qatar +RE -2052+05528 Indian/Reunion +RO +4426+02606 Europe/Bucharest +RS +4450+02030 Europe/Belgrade +RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad +RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area +RU +4457+03406 Europe/Simferopol MSK+00 - Crimea +RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd +RU +5836+04939 Europe/Kirov MSK+00 - Kirov +RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan +RU +5134+04602 Europe/Saratov MSK+01 - Saratov +RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk +RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia +RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals +RU +5500+07324 Asia/Omsk MSK+03 - Omsk +RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk +RU +5322+08345 Asia/Barnaul MSK+04 - Altai +RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk +RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo +RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area +RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia +RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky +RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River +RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky +RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River +RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky +RU +5934+15048 Asia/Magadan MSK+08 - Magadan +RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is +RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka +RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea +RW -0157+03004 Africa/Kigali +SA +2438+04643 Asia/Riyadh +SB -0932+16012 Pacific/Guadalcanal +SC -0440+05528 Indian/Mahe +SD +1536+03232 Africa/Khartoum +SE +5920+01803 Europe/Stockholm +SG +0117+10351 Asia/Singapore +SH -1555-00542 Atlantic/St_Helena +SI +4603+01431 Europe/Ljubljana +SJ +7800+01600 Arctic/Longyearbyen +SK +4809+01707 Europe/Bratislava +SL +0830-01315 Africa/Freetown +SM +4355+01228 Europe/San_Marino +SN +1440-01726 Africa/Dakar +SO +0204+04522 Africa/Mogadishu +SR +0550-05510 America/Paramaribo +SS +0451+03137 Africa/Juba +ST +0020+00644 Africa/Sao_Tome +SV +1342-08912 America/El_Salvador +SX +180305-0630250 America/Lower_Princes +SY +3330+03618 Asia/Damascus +SZ -2618+03106 Africa/Mbabane +TC +2128-07108 America/Grand_Turk +TD +1207+01503 Africa/Ndjamena +TF -492110+0701303 Indian/Kerguelen +TG +0608+00113 Africa/Lome +TH +1345+10031 Asia/Bangkok +TJ +3835+06848 Asia/Dushanbe +TK -0922-17114 Pacific/Fakaofo +TL -0833+12535 Asia/Dili +TM +3757+05823 Asia/Ashgabat +TN +3648+01011 Africa/Tunis +TO -2110-17510 Pacific/Tongatapu +TR +4101+02858 Europe/Istanbul +TT +1039-06131 America/Port_of_Spain +TV -0831+17913 Pacific/Funafuti +TW +2503+12130 Asia/Taipei +TZ -0648+03917 Africa/Dar_es_Salaam +UA +5026+03031 Europe/Kiev Ukraine (most areas) +UA +4837+02218 Europe/Uzhgorod Ruthenia +UA +4750+03510 Europe/Zaporozhye Zaporozh'ye/Zaporizhia; Lugansk/Luhansk (east) +UG +0019+03225 Africa/Kampala +UM +2813-17722 Pacific/Midway Midway Islands +UM +1917+16637 Pacific/Wake Wake Island +US +404251-0740023 America/New_York Eastern (most areas) +US +421953-0830245 America/Detroit Eastern - MI (most areas) +US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) +US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) +US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) +US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) +US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) +US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) +US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) +US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) +US +415100-0873900 America/Chicago Central (most areas) +US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) +US +411745-0863730 America/Indiana/Knox Central - IN (Starke) +US +450628-0873651 America/Menominee Central - MI (Wisconsin border) +US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) +US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) +US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) +US +394421-1045903 America/Denver Mountain (most areas) +US +433649-1161209 America/Boise Mountain - ID (south); OR (east) +US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) +US +340308-1181434 America/Los_Angeles Pacific +US +611305-1495401 America/Anchorage Alaska (most areas) +US +581807-1342511 America/Juneau Alaska - Juneau area +US +571035-1351807 America/Sitka Alaska - Sitka area +US +550737-1313435 America/Metlakatla Alaska - Annette Island +US +593249-1394338 America/Yakutat Alaska - Yakutat +US +643004-1652423 America/Nome Alaska (west) +US +515248-1763929 America/Adak Aleutian Islands +US +211825-1575130 Pacific/Honolulu Hawaii +UY -345433-0561245 America/Montevideo +UZ +3940+06648 Asia/Samarkand Uzbekistan (west) +UZ +4120+06918 Asia/Tashkent Uzbekistan (east) +VA +415408+0122711 Europe/Vatican +VC +1309-06114 America/St_Vincent +VE +1030-06656 America/Caracas +VG +1827-06437 America/Tortola +VI +1821-06456 America/St_Thomas +VN +1045+10640 Asia/Ho_Chi_Minh +VU -1740+16825 Pacific/Efate +WF -1318-17610 Pacific/Wallis +WS -1350-17144 Pacific/Apia +YE +1245+04512 Asia/Aden +YT -1247+04514 Indian/Mayotte +ZA -2615+02800 Africa/Johannesburg +ZM -1525+02817 Africa/Lusaka +ZW -1750+03103 Africa/Harare diff --git a/lib/pytz/zoneinfo/zone1970.tab b/lib/pytz/zoneinfo/zone1970.tab new file mode 100644 index 0000000..7c86fb6 --- /dev/null +++ b/lib/pytz/zoneinfo/zone1970.tab @@ -0,0 +1,382 @@ +# tzdb timezone descriptions +# +# This file is in the public domain. +# +# From Paul Eggert (2018-06-27): +# This file contains a table where each row stands for a timezone where +# civil timestamps have agreed since 1970. Columns are separated by +# a single tab. Lines beginning with '#' are comments. All text uses +# UTF-8 encoding. The columns of the table are as follows: +# +# 1. The countries that overlap the timezone, as a comma-separated list +# of ISO 3166 2-character country codes. See the file 'iso3166.tab'. +# 2. Latitude and longitude of the timezone's principal location +# in ISO 6709 sign-degrees-minutes-seconds format, +# either ±DDMM±DDDMM or ±DDMMSS±DDDMMSS, +# first latitude (+ is north), then longitude (+ is east). +# 3. Timezone name used in value of TZ environment variable. +# Please see the theory.html file for how these names are chosen. +# If multiple timezones overlap a country, each has a row in the +# table, with each column 1 containing the country code. +# 4. Comments; present if and only if a country has multiple timezones. +# +# If a timezone covers multiple countries, the most-populous city is used, +# and that country is listed first in column 1; any other countries +# are listed alphabetically by country code. The table is sorted +# first by country code, then (if possible) by an order within the +# country that (1) makes some geographical sense, and (2) puts the +# most populous timezones first, where that does not contradict (1). +# +# This table is intended as an aid for users, to help them select timezones +# appropriate for their practical needs. It is not intended to take or +# endorse any position on legal or territorial claims. +# +#country- +#codes coordinates TZ comments +AD +4230+00131 Europe/Andorra +AE,OM +2518+05518 Asia/Dubai +AF +3431+06912 Asia/Kabul +AL +4120+01950 Europe/Tirane +AM +4011+04430 Asia/Yerevan +AQ -6617+11031 Antarctica/Casey Casey +AQ -6835+07758 Antarctica/Davis Davis +AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville +AQ -6736+06253 Antarctica/Mawson Mawson +AQ -6448-06406 Antarctica/Palmer Palmer +AQ -6734-06808 Antarctica/Rothera Rothera +AQ -690022+0393524 Antarctica/Syowa Syowa +AQ -720041+0023206 Antarctica/Troll Troll +AQ -7824+10654 Antarctica/Vostok Vostok +AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) +AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) +AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) +AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) +AR -2649-06513 America/Argentina/Tucuman Tucumán (TM) +AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH) +AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) +AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) +AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) +AR -3319-06621 America/Argentina/San_Luis San Luis (SL) +AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) +AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) +AS,UM -1416-17042 Pacific/Pago_Pago Samoa, Midway +AT +4813+01620 Europe/Vienna +AU -3133+15905 Australia/Lord_Howe Lord Howe Island +AU -5430+15857 Antarctica/Macquarie Macquarie Island +AU -4253+14719 Australia/Hobart Tasmania (most areas) +AU -3956+14352 Australia/Currie Tasmania (King Island) +AU -3749+14458 Australia/Melbourne Victoria +AU -3352+15113 Australia/Sydney New South Wales (most areas) +AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) +AU -2728+15302 Australia/Brisbane Queensland (most areas) +AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) +AU -3455+13835 Australia/Adelaide South Australia +AU -1228+13050 Australia/Darwin Northern Territory +AU -3157+11551 Australia/Perth Western Australia (most areas) +AU -3143+12852 Australia/Eucla Western Australia (Eucla) +AZ +4023+04951 Asia/Baku +BB +1306-05937 America/Barbados +BD +2343+09025 Asia/Dhaka +BE +5050+00420 Europe/Brussels +BG +4241+02319 Europe/Sofia +BM +3217-06446 Atlantic/Bermuda +BN +0456+11455 Asia/Brunei +BO -1630-06809 America/La_Paz +BR -0351-03225 America/Noronha Atlantic islands +BR -0127-04829 America/Belem Pará (east); Amapá +BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) +BR -0803-03454 America/Recife Pernambuco +BR -0712-04812 America/Araguaina Tocantins +BR -0940-03543 America/Maceio Alagoas, Sergipe +BR -1259-03831 America/Bahia Bahia +BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) +BR -2027-05437 America/Campo_Grande Mato Grosso do Sul +BR -1535-05605 America/Cuiaba Mato Grosso +BR -0226-05452 America/Santarem Pará (west) +BR -0846-06354 America/Porto_Velho Rondônia +BR +0249-06040 America/Boa_Vista Roraima +BR -0308-06001 America/Manaus Amazonas (east) +BR -0640-06952 America/Eirunepe Amazonas (west) +BR -0958-06748 America/Rio_Branco Acre +BS +2505-07721 America/Nassau +BT +2728+08939 Asia/Thimphu +BY +5354+02734 Europe/Minsk +BZ +1730-08812 America/Belize +CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast) +CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE +CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) +CA +4606-06447 America/Moncton Atlantic - New Brunswick +CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) +CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) +CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) +CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) +CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) +CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) +CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) +CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) +CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba +CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) +CA +744144-0944945 America/Resolute Central - NU (Resolute) +CA +624900-0920459 America/Rankin_Inlet Central - NU (central) +CA +5024-10439 America/Regina CST - SK (most areas) +CA +5017-10750 America/Swift_Current CST - SK (midwest) +CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) +CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) +CA +6227-11421 America/Yellowknife Mountain - NT (central) +CA +682059-1334300 America/Inuvik Mountain - NT (west) +CA +4906-11631 America/Creston MST - BC (Creston) +CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) +CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) +CA +4916-12307 America/Vancouver Pacific - BC (most areas) +CA +6043-13503 America/Whitehorse Pacific - Yukon (south) +CA +6404-13925 America/Dawson Pacific - Yukon (north) +CC -1210+09655 Indian/Cocos +CH,DE,LI +4723+00832 Europe/Zurich Swiss time +CI,BF,GM,GN,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan +CK -2114-15946 Pacific/Rarotonga +CL -3327-07040 America/Santiago Chile (most areas) +CL -5309-07055 America/Punta_Arenas Region of Magallanes +CL -2709-10926 Pacific/Easter Easter Island +CN +3114+12128 Asia/Shanghai Beijing Time +CN +4348+08735 Asia/Urumqi Xinjiang Time +CO +0436-07405 America/Bogota +CR +0956-08405 America/Costa_Rica +CU +2308-08222 America/Havana +CV +1455-02331 Atlantic/Cape_Verde +CW,AW,BQ,SX +1211-06900 America/Curacao +CX -1025+10543 Indian/Christmas +CY +3510+03322 Asia/Nicosia Cyprus (most areas) +CY +3507+03357 Asia/Famagusta Northern Cyprus +CZ,SK +5005+01426 Europe/Prague +DE +5230+01322 Europe/Berlin Germany (most areas) +DK +5540+01235 Europe/Copenhagen +DO +1828-06954 America/Santo_Domingo +DZ +3647+00303 Africa/Algiers +EC -0210-07950 America/Guayaquil Ecuador (mainland) +EC -0054-08936 Pacific/Galapagos Galápagos Islands +EE +5925+02445 Europe/Tallinn +EG +3003+03115 Africa/Cairo +EH +2709-01312 Africa/El_Aaiun +ES +4024-00341 Europe/Madrid Spain (mainland) +ES +3553-00519 Africa/Ceuta Ceuta, Melilla +ES +2806-01524 Atlantic/Canary Canary Islands +FI,AX +6010+02458 Europe/Helsinki +FJ -1808+17825 Pacific/Fiji +FK -5142-05751 Atlantic/Stanley +FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap +FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape +FM +0519+16259 Pacific/Kosrae Kosrae +FO +6201-00646 Atlantic/Faroe +FR +4852+00220 Europe/Paris +GB,GG,IM,JE +513030-0000731 Europe/London +GE +4143+04449 Asia/Tbilisi +GF +0456-05220 America/Cayenne +GH +0533-00013 Africa/Accra +GI +3608-00521 Europe/Gibraltar +GL +6411-05144 America/Godthab Greenland (most areas) +GL +7646-01840 America/Danmarkshavn National Park (east coast) +GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit +GL +7634-06847 America/Thule Thule/Pituffik +GR +3758+02343 Europe/Athens +GS -5416-03632 Atlantic/South_Georgia +GT +1438-09031 America/Guatemala +GU,MP +1328+14445 Pacific/Guam +GW +1151-01535 Africa/Bissau +GY +0648-05810 America/Guyana +HK +2217+11409 Asia/Hong_Kong +HN +1406-08713 America/Tegucigalpa +HT +1832-07220 America/Port-au-Prince +HU +4730+01905 Europe/Budapest +ID -0610+10648 Asia/Jakarta Java, Sumatra +ID -0002+10920 Asia/Pontianak Borneo (west, central) +ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west) +ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas +IE +5320-00615 Europe/Dublin +IL +314650+0351326 Asia/Jerusalem +IN +2232+08822 Asia/Kolkata +IO -0720+07225 Indian/Chagos +IQ +3321+04425 Asia/Baghdad +IR +3540+05126 Asia/Tehran +IS +6409-02151 Atlantic/Reykjavik +IT,SM,VA +4154+01229 Europe/Rome +JM +175805-0764736 America/Jamaica +JO +3157+03556 Asia/Amman +JP +353916+1394441 Asia/Tokyo +KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi +KG +4254+07436 Asia/Bishkek +KI +0125+17300 Pacific/Tarawa Gilbert Islands +KI -0308-17105 Pacific/Enderbury Phoenix Islands +KI +0152-15720 Pacific/Kiritimati Line Islands +KP +3901+12545 Asia/Pyongyang +KR +3733+12658 Asia/Seoul +KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) +KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda +KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe +KZ +4431+05016 Asia/Aqtau Mangghystaū/Mankistau +KZ +4707+05156 Asia/Atyrau Atyraū/Atirau/Gur'yev +KZ +5113+05121 Asia/Oral West Kazakhstan +LB +3353+03530 Asia/Beirut +LK +0656+07951 Asia/Colombo +LR +0618-01047 Africa/Monrovia +LT +5441+02519 Europe/Vilnius +LU +4936+00609 Europe/Luxembourg +LV +5657+02406 Europe/Riga +LY +3254+01311 Africa/Tripoli +MA +3339-00735 Africa/Casablanca +MC +4342+00723 Europe/Monaco +MD +4700+02850 Europe/Chisinau +MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) +MH +0905+16720 Pacific/Kwajalein Kwajalein +MM +1647+09610 Asia/Yangon +MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) +MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan +MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar +MO +221150+1133230 Asia/Macau +MQ +1436-06105 America/Martinique +MT +3554+01431 Europe/Malta +MU -2010+05730 Indian/Mauritius +MV +0410+07330 Indian/Maldives +MX +1924-09909 America/Mexico_City Central Time +MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo +MX +2058-08937 America/Merida Central Time - Campeche, Yucatán +MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo León, Tamaulipas (most areas) +MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo León, Tamaulipas (US border) +MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa +MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) +MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) +MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora +MX +3232-11701 America/Tijuana Pacific Time US - Baja California +MX +2048-10515 America/Bahia_Banderas Central Time - Bahía de Banderas +MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) +MY +0133+11020 Asia/Kuching Sabah, Sarawak +MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time +NA -2234+01706 Africa/Windhoek +NC -2216+16627 Pacific/Noumea +NF -2903+16758 Pacific/Norfolk +NG,AO,BJ,CD,CF,CG,CM,GA,GQ,NE +0627+00324 Africa/Lagos West Africa Time +NI +1209-08617 America/Managua +NL +5222+00454 Europe/Amsterdam +NO,SJ +5955+01045 Europe/Oslo +NP +2743+08519 Asia/Kathmandu +NR -0031+16655 Pacific/Nauru +NU -1901-16955 Pacific/Niue +NZ,AQ -3652+17446 Pacific/Auckland New Zealand time +NZ -4357-17633 Pacific/Chatham Chatham Islands +PA,KY +0858-07932 America/Panama +PE -1203-07703 America/Lima +PF -1732-14934 Pacific/Tahiti Society Islands +PF -0900-13930 Pacific/Marquesas Marquesas Islands +PF -2308-13457 Pacific/Gambier Gambier Islands +PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) +PG -0613+15534 Pacific/Bougainville Bougainville +PH +1435+12100 Asia/Manila +PK +2452+06703 Asia/Karachi +PL +5215+02100 Europe/Warsaw +PM +4703-05620 America/Miquelon +PN -2504-13005 Pacific/Pitcairn +PR +182806-0660622 America/Puerto_Rico +PS +3130+03428 Asia/Gaza Gaza Strip +PS +313200+0350542 Asia/Hebron West Bank +PT +3843-00908 Europe/Lisbon Portugal (mainland) +PT +3238-01654 Atlantic/Madeira Madeira Islands +PT +3744-02540 Atlantic/Azores Azores +PW +0720+13429 Pacific/Palau +PY -2516-05740 America/Asuncion +QA,BH +2517+05132 Asia/Qatar +RE,TF -2052+05528 Indian/Reunion Réunion, Crozet, Scattered Islands +RO +4426+02606 Europe/Bucharest +RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade +RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad +RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area +RU +4457+03406 Europe/Simferopol MSK+00 - Crimea +RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd +RU +5836+04939 Europe/Kirov MSK+00 - Kirov +RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan +RU +5134+04602 Europe/Saratov MSK+01 - Saratov +RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk +RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia +RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals +RU +5500+07324 Asia/Omsk MSK+03 - Omsk +RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk +RU +5322+08345 Asia/Barnaul MSK+04 - Altai +RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk +RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo +RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area +RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia +RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky +RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River +RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky +RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River +RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky +RU +5934+15048 Asia/Magadan MSK+08 - Magadan +RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is +RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka +RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea +SA,KW,YE +2438+04643 Asia/Riyadh +SB -0932+16012 Pacific/Guadalcanal +SC -0440+05528 Indian/Mahe +SD +1536+03232 Africa/Khartoum +SE +5920+01803 Europe/Stockholm +SG +0117+10351 Asia/Singapore +SR +0550-05510 America/Paramaribo +SS +0451+03137 Africa/Juba +ST +0020+00644 Africa/Sao_Tome +SV +1342-08912 America/El_Salvador +SY +3330+03618 Asia/Damascus +TC +2128-07108 America/Grand_Turk +TD +1207+01503 Africa/Ndjamena +TF -492110+0701303 Indian/Kerguelen Kerguelen, St Paul Island, Amsterdam Island +TH,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) +TJ +3835+06848 Asia/Dushanbe +TK -0922-17114 Pacific/Fakaofo +TL -0833+12535 Asia/Dili +TM +3757+05823 Asia/Ashgabat +TN +3648+01011 Africa/Tunis +TO -2110-17510 Pacific/Tongatapu +TR +4101+02858 Europe/Istanbul +TT,AG,AI,BL,DM,GD,GP,KN,LC,MF,MS,VC,VG,VI +1039-06131 America/Port_of_Spain +TV -0831+17913 Pacific/Funafuti +TW +2503+12130 Asia/Taipei +UA +5026+03031 Europe/Kiev Ukraine (most areas) +UA +4837+02218 Europe/Uzhgorod Ruthenia +UA +4750+03510 Europe/Zaporozhye Zaporozh'ye/Zaporizhia; Lugansk/Luhansk (east) +UM +1917+16637 Pacific/Wake Wake Island +US +404251-0740023 America/New_York Eastern (most areas) +US +421953-0830245 America/Detroit Eastern - MI (most areas) +US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) +US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) +US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) +US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) +US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) +US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) +US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) +US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) +US +415100-0873900 America/Chicago Central (most areas) +US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) +US +411745-0863730 America/Indiana/Knox Central - IN (Starke) +US +450628-0873651 America/Menominee Central - MI (Wisconsin border) +US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) +US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) +US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) +US +394421-1045903 America/Denver Mountain (most areas) +US +433649-1161209 America/Boise Mountain - ID (south); OR (east) +US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) +US +340308-1181434 America/Los_Angeles Pacific +US +611305-1495401 America/Anchorage Alaska (most areas) +US +581807-1342511 America/Juneau Alaska - Juneau area +US +571035-1351807 America/Sitka Alaska - Sitka area +US +550737-1313435 America/Metlakatla Alaska - Annette Island +US +593249-1394338 America/Yakutat Alaska - Yakutat +US +643004-1652423 America/Nome Alaska (west) +US +515248-1763929 America/Adak Aleutian Islands +US,UM +211825-1575130 Pacific/Honolulu Hawaii +UY -345433-0561245 America/Montevideo +UZ +3940+06648 Asia/Samarkand Uzbekistan (west) +UZ +4120+06918 Asia/Tashkent Uzbekistan (east) +VE +1030-06656 America/Caracas +VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south) +VU -1740+16825 Pacific/Efate +WF -1318-17610 Pacific/Wallis +WS -1350-17144 Pacific/Apia +ZA,LS,SZ -2615+02800 Africa/Johannesburg diff --git a/lib/requests/__init__.py b/lib/requests/__init__.py new file mode 100644 index 0000000..bc168ee --- /dev/null +++ b/lib/requests/__init__.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- + +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. Basic GET +usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> 'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key2": "value2", + "key1": "value1" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at <http://python-requests.org>. + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +import urllib3 +import chardet +import warnings +from .exceptions import RequestsDependencyWarning + + +def check_compatibility(urllib3_version, chardet_version): + urllib3_version = urllib3_version.split('.') + assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. + + # Sometimes, urllib3 only reports its version as 16.1. + if len(urllib3_version) == 2: + urllib3_version.append('0') + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1, <= 1.24 + assert major == 1 + assert minor >= 21 + assert minor <= 24 + + # Check chardet for compatibility. + major, minor, patch = chardet_version.split('.')[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet >= 3.0.2, < 3.1.0 + assert major == 3 + assert minor < 1 + assert patch >= 2 + + +def _check_cryptography(cryptography_version): + # cryptography < 1.3.4 + try: + cryptography_version = list(map(int, cryptography_version.split('.'))) + except ValueError: + return + + if cryptography_version < [1, 3, 4]: + warning = 'Old version of cryptography ({}) may cause slowdown.'.format(cryptography_version) + warnings.warn(warning, RequestsDependencyWarning) + +# Check imported dependencies for compatibility. +try: + check_compatibility(urllib3.__version__, chardet.__version__) +except (AssertionError, ValueError): + warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported " + "version!".format(urllib3.__version__, chardet.__version__), + RequestsDependencyWarning) + +# Attempt to enable urllib3's SNI support, if possible +try: + from urllib3.contrib import pyopenssl + pyopenssl.inject_into_urllib3() + + # Check cryptography version + from cryptography import __version__ as cryptography_version + _check_cryptography(cryptography_version) +except ImportError: + pass + +# urllib3's DependencyWarnings should be silenced. +from urllib3.exceptions import DependencyWarning +warnings.simplefilter('ignore', DependencyWarning) + +from .__version__ import __title__, __description__, __url__, __version__ +from .__version__ import __build__, __author__, __author_email__, __license__ +from .__version__ import __copyright__, __cake__ + +from . import utils +from . import packages +from .models import Request, Response, PreparedRequest +from .api import request, get, head, post, patch, put, delete, options +from .sessions import session, Session +from .status_codes import codes +from .exceptions import ( + RequestException, Timeout, URLRequired, + TooManyRedirects, HTTPError, ConnectionError, + FileModeWarning, ConnectTimeout, ReadTimeout +) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter('default', FileModeWarning, append=True) diff --git a/lib/requests/__version__.py b/lib/requests/__version__.py new file mode 100644 index 0000000..803773a --- /dev/null +++ b/lib/requests/__version__.py @@ -0,0 +1,14 @@ +# .-. .-. .-. . . .-. .-. .-. .-. +# |( |- |.| | | |- `-. | `-. +# ' ' `-' `-`.`-' `-' `-' ' `-' + +__title__ = 'requests' +__description__ = 'Python HTTP for Humans.' +__url__ = 'http://python-requests.org' +__version__ = '2.20.1' +__build__ = 0x022001 +__author__ = 'Kenneth Reitz' +__author_email__ = 'me@kennethreitz.org' +__license__ = 'Apache 2.0' +__copyright__ = 'Copyright 2018 Kenneth Reitz' +__cake__ = u'\u2728 \U0001f370 \u2728' diff --git a/lib/requests/_internal_utils.py b/lib/requests/_internal_utils.py new file mode 100644 index 0000000..759d9a5 --- /dev/null +++ b/lib/requests/_internal_utils.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +""" +requests._internal_utils +~~~~~~~~~~~~~~ + +Provides utility functions that are consumed internally by Requests +which depend on extremely few external helpers (such as compat) +""" + +from .compat import is_py2, builtin_str, str + + +def to_native_string(string, encoding='ascii'): + """Given a string object, regardless of type, returns a representation of + that string in the native string type, encoding and decoding where + necessary. This assumes ASCII unless told otherwise. + """ + if isinstance(string, builtin_str): + out = string + else: + if is_py2: + out = string.encode(encoding) + else: + out = string.decode(encoding) + + return out + + +def unicode_is_ascii(u_string): + """Determine if unicode string only contains ASCII characters. + + :param str u_string: unicode string to check. Must be unicode + and not Python 2 `str`. + :rtype: bool + """ + assert isinstance(u_string, str) + try: + u_string.encode('ascii') + return True + except UnicodeEncodeError: + return False diff --git a/lib/requests/adapters.py b/lib/requests/adapters.py new file mode 100644 index 0000000..fa4d9b3 --- /dev/null +++ b/lib/requests/adapters.py @@ -0,0 +1,533 @@ +# -*- coding: utf-8 -*- + +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +import os.path +import socket + +from urllib3.poolmanager import PoolManager, proxy_from_url +from urllib3.response import HTTPResponse +from urllib3.util import parse_url +from urllib3.util import Timeout as TimeoutSauce +from urllib3.util.retry import Retry +from urllib3.exceptions import ClosedPoolError +from urllib3.exceptions import ConnectTimeoutError +from urllib3.exceptions import HTTPError as _HTTPError +from urllib3.exceptions import MaxRetryError +from urllib3.exceptions import NewConnectionError +from urllib3.exceptions import ProxyError as _ProxyError +from urllib3.exceptions import ProtocolError +from urllib3.exceptions import ReadTimeoutError +from urllib3.exceptions import SSLError as _SSLError +from urllib3.exceptions import ResponseError +from urllib3.exceptions import LocationValueError + +from .models import Response +from .compat import urlparse, basestring +from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths, + get_encoding_from_headers, prepend_scheme_if_needed, + get_auth_from_url, urldefragauth, select_proxy) +from .structures import CaseInsensitiveDict +from .cookies import extract_cookies_to_jar +from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, + ProxyError, RetryError, InvalidSchema, InvalidProxyURL, + InvalidURL) +from .auth import _basic_auth_str + +try: + from urllib3.contrib.socks import SOCKSProxyManager +except ImportError: + def SOCKSProxyManager(*args, **kwargs): + raise InvalidSchema("Missing dependencies for SOCKS support.") + +DEFAULT_POOLBLOCK = False +DEFAULT_POOLSIZE = 10 +DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None + + +class BaseAdapter(object): + """The Base Transport Adapter""" + + def __init__(self): + super(BaseAdapter, self).__init__() + + def send(self, request, stream=False, timeout=None, verify=True, + cert=None, proxies=None): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self): + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session <Session>` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> a = requests.adapters.HTTPAdapter(max_retries=3) + >>> s.mount('http://', a) + """ + __attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize', + '_pool_block'] + + def __init__(self, pool_connections=DEFAULT_POOLSIZE, + pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES, + pool_block=DEFAULT_POOLBLOCK): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super(HTTPAdapter, self).__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager(self._pool_connections, self._pool_maxsize, + block=self._pool_block) + + def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, + block=block, strict=True, **pool_kwargs) + + def proxy_manager_for(self, proxy, **proxy_kwargs): + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith('socks'): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs) + + return manager + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith('https') and verify: + + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) + + if not cert_loc or not os.path.exists(cert_loc): + raise IOError("Could not find a suitable TLS CA certificate bundle, " + "invalid path: {}".format(cert_loc)) + + conn.cert_reqs = 'CERT_REQUIRED' + + if not os.path.isdir(cert_loc): + conn.ca_certs = cert_loc + else: + conn.ca_cert_dir = cert_loc + else: + conn.cert_reqs = 'CERT_NONE' + conn.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + conn.cert_file = cert + conn.key_file = None + if conn.cert_file and not os.path.exists(conn.cert_file): + raise IOError("Could not find the TLS certificate file, " + "invalid path: {}".format(conn.cert_file)) + if conn.key_file and not os.path.exists(conn.key_file): + raise IOError("Could not find the TLS key file, " + "invalid path: {}".format(conn.key_file)) + + def build_response(self, req, resp): + """Builds a :class:`Response <requests.Response>` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>` + + :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, 'status', None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode('utf-8') + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def get_connection(self, url, proxies=None): + """Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.ConnectionPool + """ + proxy = select_proxy(url, proxies) + + if proxy: + proxy = prepend_scheme_if_needed(proxy, 'http') + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL("Please check proxy URL. It is malformed" + " and could be missing the host.") + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + is_proxied_http_request = (proxy and scheme != 'https') + using_socks_proxy = False + if proxy: + proxy_scheme = urlparse(proxy).scheme.lower() + using_socks_proxy = proxy_scheme.startswith('socks') + + url = request.path_url + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username: + headers['Proxy-Authorization'] = _basic_auth_str(username, + password) + + return headers + + def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + try: + conn = self.get_connection(request.url, proxies) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + self.cert_verify(conn, request.url, verify, cert) + url = self.request_url(request, proxies) + self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies) + + chunked = not (request.body is None or 'Content-Length' in request.headers) + + if isinstance(timeout, tuple): + try: + connect, read = timeout + timeout = TimeoutSauce(connect=connect, read=read) + except ValueError as e: + # this may raise a string formatting error. + err = ("Invalid timeout {}. Pass a (connect, read) " + "timeout tuple, or a single float to set " + "both timeouts to the same value".format(timeout)) + raise ValueError(err) + elif isinstance(timeout, TimeoutSauce): + pass + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + if not chunked: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout + ) + + # Send the request. + else: + if hasattr(conn, 'proxy_pool'): + conn = conn.proxy_pool + + low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) + + try: + low_conn.putrequest(request.method, + url, + skip_accept_encoding=True) + + for header, value in request.headers.items(): + low_conn.putheader(header, value) + + low_conn.endheaders() + + for i in request.body: + low_conn.send(hex(len(i))[2:].encode('utf-8')) + low_conn.send(b'\r\n') + low_conn.send(i) + low_conn.send(b'\r\n') + low_conn.send(b'0\r\n\r\n') + + # Receive the response from the server + try: + # For Python 2.7, use buffering of HTTP responses + r = low_conn.getresponse(buffering=True) + except TypeError: + # For compatibility with Python 3.3+ + r = low_conn.getresponse() + + resp = HTTPResponse.from_httplib( + r, + pool=conn, + connection=low_conn, + preload_content=False, + decode_content=False + ) + except: + # If we hit any problems here, clean up the connection. + # Then, reraise so that we can handle the actual exception. + low_conn.close() + raise + + except (ProtocolError, socket.error) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/lib/requests/api.py b/lib/requests/api.py new file mode 100644 index 0000000..abada96 --- /dev/null +++ b/lib/requests/api.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- + +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request <Request>`. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the body of the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response <Response>` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + <Response [200]> + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return request('get', url, params=params, **kwargs) + + +def options(url, **kwargs): + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return request('options', url, **kwargs) + + +def head(url, **kwargs): + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', False) + return request('head', url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('post', url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('put', url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json data to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('patch', url, data=data, **kwargs) + + +def delete(url, **kwargs): + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response <Response>` object + :rtype: requests.Response + """ + + return request('delete', url, **kwargs) diff --git a/lib/requests/auth.py b/lib/requests/auth.py new file mode 100644 index 0000000..bdde51c --- /dev/null +++ b/lib/requests/auth.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- + +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import os +import re +import time +import hashlib +import threading +import warnings + +from base64 import b64encode + +from .compat import urlparse, str, basestring +from .cookies import extract_cookies_to_jar +from ._internal_utils import to_native_string +from .utils import parse_dict_header + +CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' +CONTENT_TYPE_MULTI_PART = 'multipart/form-data' + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(username), + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + "3.0.0. Please convert the object you've passed in ({!r}) to " + "a string or bytes object in the near future to avoid " + "problems.".format(password), + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode('latin1') + + if isinstance(password, str): + password = password.encode('latin1') + + authstr = 'Basic ' + to_native_string( + b64encode(b':'.join((username, password))).strip() + ) + + return authstr + + +class AuthBase(object): + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError('Auth hooks must be callable.') + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + + def __eq__(self, other): + return all([ + self.username == getattr(other, 'username', None), + self.password == getattr(other, 'password', None) + ]) + + def __ne__(self, other): + return not self == other + + def __call__(self, r): + r.headers['Authorization'] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authentication to a given Request object.""" + + def __call__(self, r): + r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, 'init'): + self._thread_local.init = True + self._thread_local.last_nonce = '' + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + """ + :rtype: str + """ + + realm = self._thread_local.chal['realm'] + nonce = self._thread_local.chal['nonce'] + qop = self._thread_local.chal.get('qop') + algorithm = self._thread_local.chal.get('algorithm') + opaque = self._thread_local.chal.get('opaque') + hash_utf8 = None + + if algorithm is None: + _algorithm = 'MD5' + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': + def md5_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.md5(x).hexdigest() + hash_utf8 = md5_utf8 + elif _algorithm == 'SHA': + def sha_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha1(x).hexdigest() + hash_utf8 = sha_utf8 + elif _algorithm == 'SHA-256': + def sha256_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha256(x).hexdigest() + hash_utf8 = sha256_utf8 + elif _algorithm == 'SHA-512': + def sha512_utf8(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha512(x).hexdigest() + hash_utf8 = sha512_utf8 + + KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: path is request-uri defined in RFC 2616 which should not be empty + path = p_parsed.path or "/" + if p_parsed.query: + path += '?' + p_parsed.query + + A1 = '%s:%s:%s' % (self.username, realm, self.password) + A2 = '%s:%s' % (method, path) + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 + else: + self._thread_local.nonce_count = 1 + ncvalue = '%08x' % self._thread_local.nonce_count + s = str(self._thread_local.nonce_count).encode('utf-8') + s += nonce.encode('utf-8') + s += time.ctime().encode('utf-8') + s += os.urandom(8) + + cnonce = (hashlib.sha1(s).hexdigest()[:16]) + if _algorithm == 'MD5-SESS': + HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) + + if not qop: + respdig = KD(HA1, "%s:%s" % (nonce, HA2)) + elif qop == 'auth' or 'auth' in qop.split(','): + noncebit = "%s:%s:%s:%s:%s" % ( + nonce, ncvalue, cnonce, 'auth', HA2 + ) + respdig = KD(HA1, noncebit) + else: + # XXX handle auth-int. + return None + + self._thread_local.last_nonce = nonce + + # XXX should the partial digests be encoded too? + base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ + 'response="%s"' % (self.username, realm, nonce, path, respdig) + if opaque: + base += ', opaque="%s"' % opaque + if algorithm: + base += ', algorithm="%s"' % algorithm + if entdig: + base += ', digest="%s"' % entdig + if qop: + base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) + + return 'Digest %s' % (base) + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/requests/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + r.request.body.seek(self._thread_local.pos) + s_auth = r.headers.get('www-authenticate', '') + + if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: + + self._thread_local.num_401_calls += 1 + pat = re.compile(r'digest ', flags=re.IGNORECASE) + self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + extract_cookies_to_jar(prep._cookies, r.request, r.raw) + prep.prepare_cookies(prep._cookies) + + prep.headers['Authorization'] = self.build_digest_header( + prep.method, prep.url) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + self.init_per_thread_state() + # If we have a saved nonce, skip the 401 + if self._thread_local.last_nonce: + r.headers['Authorization'] = self.build_digest_header(r.method, r.url) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self._thread_local.pos = None + r.register_hook('response', self.handle_401) + r.register_hook('response', self.handle_redirect) + self._thread_local.num_401_calls = 1 + + return r + + def __eq__(self, other): + return all([ + self.username == getattr(other, 'username', None), + self.password == getattr(other, 'password', None) + ]) + + def __ne__(self, other): + return not self == other diff --git a/lib/requests/certs.py b/lib/requests/certs.py new file mode 100644 index 0000000..d1a378d --- /dev/null +++ b/lib/requests/certs.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +If you are packaging Requests, e.g., for a Linux distribution or a managed +environment, you can change the definition of where() to return a separately +packaged CA bundle. +""" +from certifi import where + +if __name__ == '__main__': + print(where()) diff --git a/lib/requests/compat.py b/lib/requests/compat.py new file mode 100644 index 0000000..c44b35e --- /dev/null +++ b/lib/requests/compat.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module handles import compatibility issues between Python 2 and +Python 3. +""" + +import chardet + +import sys + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = (_ver[0] == 2) + +#: Python 3.x? +is_py3 = (_ver[0] == 3) + +try: + import simplejson as json +except ImportError: + import json + +# --------- +# Specifics +# --------- + +if is_py2: + from urllib import ( + quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, + proxy_bypass, proxy_bypass_environment, getproxies_environment) + from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag + from urllib2 import parse_http_list + import cookielib + from Cookie import Morsel + from StringIO import StringIO + from collections import Callable, Mapping, MutableMapping, OrderedDict + + + builtin_str = str + bytes = str + str = unicode + basestring = basestring + numeric_types = (int, long, float) + integer_types = (int, long) + +elif is_py3: + from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag + from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment + from http import cookiejar as cookielib + from http.cookies import Morsel + from io import StringIO + from collections import OrderedDict + from collections.abc import Callable, Mapping, MutableMapping + + builtin_str = str + str = str + bytes = bytes + basestring = (str, bytes) + numeric_types = (int, float) + integer_types = (int,) diff --git a/lib/requests/cookies.py b/lib/requests/cookies.py new file mode 100644 index 0000000..56fccd9 --- /dev/null +++ b/lib/requests/cookies.py @@ -0,0 +1,549 @@ +# -*- coding: utf-8 -*- + +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `cookielib.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import copy +import time +import calendar + +from ._internal_utils import to_native_string +from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping + +try: + import threading +except ImportError: + import dummy_threading as threading + + +class MockRequest(object): + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `cookielib.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + def get_full_url(self): + # Only return the response's URL if the user hadn't set the Host + # header + if not self._r.headers.get('Host'): + return self._r.url + # If they did set it, retrieve it and reconstruct the expected domain + host = to_native_string(self._r.headers['Host'], encoding='utf-8') + parsed = urlparse(self._r.url) + # Reconstruct the URL as we expect it + return urlunparse([ + parsed.scheme, host, parsed.path, parsed.params, parsed.query, + parsed.fragment + ]) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + def add_header(self, key, val): + """cookielib has no legitimate use for this method; add it back if you find one.""" + raise NotImplementedError("Cookie headers should be added with add_unredirected_header()") + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse(object): + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `cookielib` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookielib` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, '_original_response') and + response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get('Cookie') + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(cookielib.CookieJar, MutableMapping): + """Compatibility class; is a cookielib.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # support client code that unsets cookies by assignment of a None value: + if value is None: + remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path')) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary = {} + for cookie in iter(self): + if ( + (domain is None or cookie.domain == domain) and + (path is None or cookie.path == path) + ): + dictionary[cookie.name] = cookie.value + return dictionary + + def __contains__(self, name): + try: + return super(RequestsCookieJar, self).__contains__(name) + except CookieConflictError: + return True + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s + ``remove_cookie_by_name()``. + """ + remove_cookie_by_name(self, name) + + def set_cookie(self, cookie, *args, **kwargs): + if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'): + cookie.value = cookie.value.replace('\\"', '') + return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super(RequestsCookieJar, self).update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if toReturn is not None: # if there are multiple cookies that meet passed in criteria + raise CookieConflictError('There are multiple cookies with name, %r' % (name)) + toReturn = cookie.value # we will eventually return this as long as no cookie conflict + + if toReturn: + return toReturn + raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop('_cookies_lock') + return state + + def __setstate__(self, state): + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if '_cookies_lock' not in self.__dict__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self): + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, 'copy'): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + and sent on every request (this is sometimes called a "supercookie"). + """ + result = { + 'version': 0, + 'name': name, + 'value': value, + 'port': None, + 'domain': '', + 'path': '/', + 'secure': False, + 'expires': None, + 'discard': True, + 'comment': None, + 'comment_url': None, + 'rest': {'HttpOnly': None}, + 'rfc2109': False, + } + + badargs = set(kwargs) - set(result) + if badargs: + err = 'create_cookie() got unexpected keyword arguments: %s' + raise TypeError(err % list(badargs)) + + result.update(kwargs) + result['port_specified'] = bool(result['port']) + result['domain_specified'] = bool(result['domain']) + result['domain_initial_dot'] = result['domain'].startswith('.') + result['path_specified'] = bool(result['path']) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel['max-age']: + try: + expires = int(time.time() + int(morsel['max-age'])) + except ValueError: + raise TypeError('max-age: %s must be integer' % morsel['max-age']) + elif morsel['expires']: + time_template = '%a, %d-%b-%Y %H:%M:%S GMT' + expires = calendar.timegm( + time.strptime(morsel['expires'], time_template) + ) + return create_cookie( + comment=morsel['comment'], + comment_url=bool(morsel['comment']), + discard=False, + domain=morsel['domain'], + expires=expires, + name=morsel.key, + path=morsel['path'], + port=None, + rest={'HttpOnly': morsel['httponly']}, + rfc2109=False, + secure=bool(morsel['secure']), + value=morsel.value, + version=morsel['version'] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError('You can only merge into CookieJar') + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict( + cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/lib/requests/exceptions.py b/lib/requests/exceptions.py new file mode 100644 index 0000000..a80cad8 --- /dev/null +++ b/lib/requests/exceptions.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" +from urllib3.exceptions import HTTPError as BaseHTTPError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + def __init__(self, *args, **kwargs): + """Initialize RequestException with `request` and `response` objects.""" + response = kwargs.pop('response', None) + self.response = response + self.request = kwargs.pop('request', None) + if (response is not None and not self.request and + hasattr(response, 'request')): + self.request = self.response.request + super(RequestException, self).__init__(*args, **kwargs) + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL schema (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """See defaults.py for valid schemas.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body""" + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + pass + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + pass + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" + pass diff --git a/lib/requests/help.py b/lib/requests/help.py new file mode 100644 index 0000000..e53d35e --- /dev/null +++ b/lib/requests/help.py @@ -0,0 +1,119 @@ +"""Module containing bug report helper(s).""" +from __future__ import print_function + +import json +import platform +import sys +import ssl + +import idna +import urllib3 +import chardet + +from . import __version__ as requests_version + +try: + from urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import OpenSSL + import cryptography + + +def _implementation(): + """Return a dict with the Python implementation and version. + + Provide both the name and the version of the Python implementation + currently running. For example, on CPython 2.7.5 it will return + {'name': 'CPython', 'version': '2.7.5'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + if implementation == 'CPython': + implementation_version = platform.python_version() + elif implementation == 'PyPy': + implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major, + sys.pypy_version_info.minor, + sys.pypy_version_info.micro) + if sys.pypy_version_info.releaselevel != 'final': + implementation_version = ''.join([ + implementation_version, sys.pypy_version_info.releaselevel + ]) + elif implementation == 'Jython': + implementation_version = platform.python_version() # Complete Guess + elif implementation == 'IronPython': + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = 'Unknown' + + return {'name': implementation, 'version': implementation_version} + + +def info(): + """Generate information for a bug report.""" + try: + platform_info = { + 'system': platform.system(), + 'release': platform.release(), + } + except IOError: + platform_info = { + 'system': 'Unknown', + 'release': 'Unknown', + } + + implementation_info = _implementation() + urllib3_info = {'version': urllib3.__version__} + chardet_info = {'version': chardet.__version__} + + pyopenssl_info = { + 'version': None, + 'openssl_version': '', + } + if OpenSSL: + pyopenssl_info = { + 'version': OpenSSL.__version__, + 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, + } + cryptography_info = { + 'version': getattr(cryptography, '__version__', ''), + } + idna_info = { + 'version': getattr(idna, '__version__', ''), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = { + 'version': '%x' % system_ssl if system_ssl is not None else '' + } + + return { + 'platform': platform_info, + 'implementation': implementation_info, + 'system_ssl': system_ssl_info, + 'using_pyopenssl': pyopenssl is not None, + 'pyOpenSSL': pyopenssl_info, + 'urllib3': urllib3_info, + 'chardet': chardet_info, + 'cryptography': cryptography_info, + 'idna': idna_info, + 'requests': { + 'version': requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/lib/requests/hooks.py b/lib/requests/hooks.py new file mode 100644 index 0000000..7a51f21 --- /dev/null +++ b/lib/requests/hooks.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" +HOOKS = ['response'] + + +def default_hooks(): + return {event: [] for event in HOOKS} + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or {} + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, '__call__'): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/lib/requests/models.py b/lib/requests/models.py new file mode 100644 index 0000000..3dded57 --- /dev/null +++ b/lib/requests/models.py @@ -0,0 +1,953 @@ +# -*- coding: utf-8 -*- + +""" +requests.models +~~~~~~~~~~~~~~~ + +This module contains the primary objects that power Requests. +""" + +import datetime +import sys + +# Import encoding now, to avoid implicit import later. +# Implicit import within threads may cause LookupError when standard library is in a ZIP, +# such as in Embedded Python. See https://github.com/requests/requests/issues/3578. +import encodings.idna + +from urllib3.fields import RequestField +from urllib3.filepost import encode_multipart_formdata +from urllib3.util import parse_url +from urllib3.exceptions import ( + DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) + +from io import UnsupportedOperation +from .hooks import default_hooks +from .structures import CaseInsensitiveDict + +from .auth import HTTPBasicAuth +from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar +from .exceptions import ( + HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, + ContentDecodingError, ConnectionError, StreamConsumedError) +from ._internal_utils import to_native_string, unicode_is_ascii +from .utils import ( + guess_filename, get_auth_from_url, requote_uri, + stream_decode_response_unicode, to_key_val_list, parse_header_links, + iter_slices, guess_json_utf, super_len, check_header_validity) +from .compat import ( + Callable, Mapping, + cookielib, urlunparse, urlsplit, urlencode, str, bytes, + is_py2, chardet, builtin_str, basestring) +from .compat import json as complexjson +from .status_codes import codes + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin(object): + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = '/' + + url.append(path) + + query = p.query + if query: + url.append('?') + url.append(query) + + return ''.join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, 'read'): + return data + elif hasattr(data, '__iter__'): + result = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): + vs = [vs] + for v in vs: + if v is not None: + result.append( + (k.encode('utf-8') if isinstance(k, str) else k, + v.encode('utf-8') if isinstance(v, str) else v)) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if (not files): + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, '__iter__'): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + v = str(v) + + new_fields.append( + (field.decode('utf-8') if isinstance(field, bytes) else field, + v.encode('utf-8') if isinstance(v, str) else v)) + + for (k, v) in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif hasattr(fp, 'read'): + fdata = fp.read() + elif fp is None: + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin(object): + def register_hook(self, event, hook): + """Properly register a hook.""" + + if event not in self.hooks: + raise ValueError('Unsupported event specified, with event name "%s"' % (event)) + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, '__iter__'): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request <Request>` object. + + Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + <PreparedRequest [GET]> + """ + + def __init__(self, + method=None, url=None, headers=None, files=None, data=None, + params=None, auth=None, cookies=None, hooks=None, json=None): + + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for (k, v) in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return '<Request [%s]>' % (self.method) + + def prepare(self): + """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest <PreparedRequest>` object, + containing the exact bytes that will be sent to the server. + + Generated from either a :class:`Request <Request>` object or manually. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + <PreparedRequest [GET]> + + >>> s = requests.Session() + >>> s.send(r) + <Response [200]> + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: integer denoting starting position of a readable file-like body. + self._body_position = None + + def prepare(self, + method=None, url=None, headers=None, files=None, data=None, + params=None, auth=None, cookies=None, hooks=None, json=None): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return '<PreparedRequest [%s]>' % (self.method) + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host): + import idna + + try: + host = idna.encode(host, uts46=True).decode('utf-8') + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/requests/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode('utf8') + else: + url = unicode(url) if is_py2 else str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. + if ':' in url and not url.lower().startswith('http'): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise InvalidURL(*e.args) + + if not scheme: + error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?") + error = error.format(to_native_string(url, 'utf8')) + + raise MissingSchema(error) + + if not host: + raise InvalidURL("Invalid URL %r: No host supplied" % url) + + # In general, we want to try IDNA encoding the hostname if the string contains + # non-ASCII characters. This allows users to automatically get the correct IDNA + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + try: + host = self._get_idna_encoded_host(host) + except UnicodeError: + raise InvalidURL('URL has an invalid label.') + elif host.startswith(u'*'): + raise InvalidURL('URL has an invalid label.') + + # Carefully reconstruct the network location + netloc = auth or '' + if netloc: + netloc += '@' + netloc += host + if port: + netloc += ':' + str(port) + + # Bare domains aren't valid URLs. + if not path: + path = '/' + + if is_py2: + if isinstance(scheme, str): + scheme = scheme.encode('utf-8') + if isinstance(netloc, str): + netloc = netloc.encode('utf-8') + if isinstance(path, str): + path = path.encode('utf-8') + if isinstance(query, str): + query = query.encode('utf-8') + if isinstance(fragment, str): + fragment = fragment.encode('utf-8') + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = '%s&%s' % (query, enc_params) + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + if not data and json is not None: + # urllib3 requires a bytes-like body. Python 2's json.dumps + # provides this natively, but Python 3 gives a Unicode string. + content_type = 'application/json' + body = complexjson.dumps(json) + if not isinstance(body, bytes): + body = body.encode('utf-8') + + is_stream = all([ + hasattr(data, '__iter__'), + not isinstance(data, (basestring, list, tuple, Mapping)) + ]) + + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + if is_stream: + body = data + + if getattr(body, 'tell', None) is not None: + # Record the current file position before reading. + # This will allow us to rewind a file in the event + # of a redirect. + try: + self._body_position = body.tell() + except (IOError, OSError): + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body + self._body_position = object() + + if files: + raise NotImplementedError('Streamed bodies and files are mutually exclusive.') + + if length: + self.headers['Content-Length'] = builtin_str(length) + else: + self.headers['Transfer-Encoding'] = 'chunked' + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + content_type = 'application/x-www-form-urlencoded' + + self.prepare_content_length(body) + + # Add content-type if it wasn't explicitly provided. + if content_type and ('content-type' not in self.headers): + self.headers['Content-Type'] = content_type + + self.body = body + + def prepare_content_length(self, body): + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + if length: + # If length exists, set it. Otherwise, we fallback + # to Transfer-Encoding: chunked. + self.headers['Content-Length'] = builtin_str(length) + elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: + # Set Content-Length to 0 for methods that can have a body + # but don't provide one. (i.e. not GET or HEAD) + self.headers['Content-Length'] = '0' + + def prepare_auth(self, auth, url=''): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers['Cookie'] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response(object): + """The :class:`Response <Response>` object, which contains a + server's response to an HTTP request. + """ + + __attrs__ = [ + '_content', 'status_code', 'headers', 'url', 'history', + 'encoding', 'reason', 'cookies', 'elapsed', 'request' + ] + + def __init__(self): + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + # This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response <Response>` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest <PreparedRequest>` object to which this + #: is a response. + self.request = None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, '_content_consumed', True) + setattr(self, 'raw', None) + + def __repr__(self): + return '<Response [%s]>' % (self.status_code) + + def __bool__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return ('location' in self.headers and self.status_code in REDIRECT_STATI) + + @property + def is_permanent_redirect(self): + """True if this Response one of the permanent versions of redirect.""" + return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect)) + + @property + def next(self): + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the chardet library.""" + return chardet.detect(self.content)['encoding'] + + def iter_content(self, chunk_size=1, decode_unicode=False): + """Iterates over the response data. When stream=True is set on the + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, 'stream'): + try: + for chunk in self.raw.stream(chunk_size, decode_content=True): + yield chunk + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + if self._content_consumed and isinstance(self._content, bool): + raise StreamConsumedError() + elif chunk_size is not None and not isinstance(chunk_size, int): + raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size)) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None): + """Iterates over the response data, one line at a time. When + stream=True is set on the request, this avoids reading the + content at once into memory for large responses. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): + + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + for line in lines: + yield line + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_consumed: + raise RuntimeError( + 'The content for this response was already consumed') + + if self.status_code == 0 or self.raw is None: + self._content = None + else: + self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b'' + + self._content_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return str('') + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + content = str(self.content, encoding, errors='replace') + except (LookupError, TypeError): + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors='replace') + + return content + + def json(self, **kwargs): + r"""Returns the json-encoded content of a response, if any. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises ValueError: If the response body does not contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using chardet to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + return complexjson.loads( + self.content.decode(encoding), **kwargs + ) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + return complexjson.loads(self.text, **kwargs) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get('link') + + # l = MultiDict() + l = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get('rel') or link.get('url') + l[key] = link + + return l + + def raise_for_status(self): + """Raises stored :class:`HTTPError`, if one occurred.""" + + http_error_msg = '' + if isinstance(self.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. (See PR #3538) + try: + reason = self.reason.decode('utf-8') + except UnicodeDecodeError: + reason = self.reason.decode('iso-8859-1') + else: + reason = self.reason + + if 400 <= self.status_code < 500: + http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url) + + elif 500 <= self.status_code < 600: + http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, 'release_conn', None) + if release_conn is not None: + release_conn() diff --git a/lib/requests/packages.py b/lib/requests/packages.py new file mode 100644 index 0000000..7232fe0 --- /dev/null +++ b/lib/requests/packages.py @@ -0,0 +1,14 @@ +import sys + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ('urllib3', 'idna', 'chardet'): + locals()[package] = __import__(package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == package or mod.startswith(package + '.'): + sys.modules['requests.packages.' + mod] = sys.modules[mod] + +# Kinda cool, though, right? diff --git a/lib/requests/sessions.py b/lib/requests/sessions.py new file mode 100644 index 0000000..d73d700 --- /dev/null +++ b/lib/requests/sessions.py @@ -0,0 +1,770 @@ +# -*- coding: utf-8 -*- + +""" +requests.session +~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" +import os +import sys +import time +from datetime import timedelta + +from .auth import _basic_auth_str +from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping +from .cookies import ( + cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) +from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT +from .hooks import default_hooks, dispatch_hook +from ._internal_utils import to_native_string +from .utils import to_key_val_list, default_headers, DEFAULT_PORTS +from .exceptions import ( + TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) + +from .structures import CaseInsensitiveDict +from .adapters import HTTPAdapter + +from .utils import ( + requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies, + get_auth_from_url, rewind_body +) + +from .status_codes import codes + +# formerly defined here, reexposed here for backward compatibility +from .models import REDIRECT_STATI + +# Preferred clock, based on which one is more accurate on a given system. +if sys.platform == 'win32': + try: # Python 3.4+ + preferred_clock = time.perf_counter + except AttributeError: # Earlier than Python 3. + preferred_clock = time.clock +else: + preferred_clock = time.time + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and + isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get('response') == []: + return request_hooks + + if request_hooks is None or request_hooks.get('response') == []: + return session_hooks + + return merge_setting(request_hooks, session_hooks, dict_class) + + +class SessionRedirectMixin(object): + + def get_redirect_target(self, resp): + """Receives a Response. Returns a redirect URI or ``None``""" + # Due to the nature of how requests processes redirects this method will + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + location = resp.headers['location'] + # Currently the underlying http module on py3 decode headers + # in latin1, but empirical evidence suggests that latin1 is very + # rarely used with non-ASCII characters in HTTP headers. + # It is more likely to get UTF8 header rather than latin1. + # This causes incorrect handling of UTF8 encoded location headers. + # To solve this, we re-encode the location in latin1. + if is_py3: + location = location.encode('latin1') + return to_native_string(location, 'utf8') + return None + + def should_strip_auth(self, old_url, new_url): + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # ports. This isn't specified by RFC 7235, but is kept to avoid + # breaking backwards compatibility with older versions of requests + # that allowed any redirects on the same host. + if (old_parsed.scheme == 'http' and old_parsed.port in (80, None) + and new_parsed.scheme == 'https' and new_parsed.port in (443, None)): + return False + + # Handle default port usage corresponding to scheme. + changed_port = old_parsed.port != new_parsed.port + changed_scheme = old_parsed.scheme != new_parsed.scheme + default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) + if (not changed_scheme and old_parsed.port in default_port + and new_parsed.port in default_port): + return False + + # Standard case: root URI must match + return changed_port or changed_scheme + + def resolve_redirects(self, resp, req, stream=False, timeout=None, + verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs): + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + # resp.history must ignore the original request in this loop + hist.append(resp) + resp.history = hist[1:] + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith('//'): + parsed_rurl = urlparse(resp.url) + url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url) + + # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) + parsed = urlparse(url) + if parsed.fragment == '' and previous_fragment: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/requests/requests/issues/1084 + if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): + # https://github.com/requests/requests/issues/3490 + purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding') + for header in purged_headers: + prepared_request.headers.pop(header, None) + prepared_request.body = None + + headers = prepared_request.headers + try: + del headers['Cookie'] + except KeyError: + pass + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + merge_cookies(prepared_request._cookies, self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # A failed tell() sets `_body_position` to `object()`. This non-None + # value ensures `rewindable` will be True, allowing us to raise an + # UnrewindableBodyError, instead of hanging the connection. + rewindable = ( + prepared_request._body_position is not None and + ('Content-Length' in headers or 'Transfer-Encoding' in headers) + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req + else: + + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth(self, prepared_request, response): + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.url + + if 'Authorization' in headers and self.should_strip_auth(response.request.url, url): + # If we get redirected to a new host, we should strip out any + # authentication headers. + del headers['Authorization'] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + return + + def rebuild_proxies(self, prepared_request, proxies): + """This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :rtype: dict + """ + proxies = proxies if proxies is not None else {} + headers = prepared_request.headers + url = prepared_request.url + scheme = urlparse(url).scheme + new_proxies = proxies.copy() + no_proxy = proxies.get('no_proxy') + + bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy) + if self.trust_env and not bypass_proxy: + environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) + + proxy = environ_proxies.get(scheme, environ_proxies.get('all')) + + if proxy: + new_proxies.setdefault(scheme, proxy) + + if 'Proxy-Authorization' in headers: + del headers['Proxy-Authorization'] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + if username and password: + headers['Proxy-Authorization'] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method(self, prepared_request, response): + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = prepared_request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.see_other and method != 'HEAD': + method = 'GET' + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + if response.status_code == codes.found and method != 'HEAD': + method = 'GET' + + # Second, if a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in Issue 1704. + if response.status_code == codes.moved and method == 'POST': + method = 'GET' + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + <Response [200]> + + Or as a context manager:: + + >>> with requests.Session() as s: + >>> s.get('https://httpbin.org/get') + <Response [200]> + """ + + __attrs__ = [ + 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', + 'cert', 'prefetch', 'adapters', 'stream', 'trust_env', + 'max_redirects', + ] + + def __init__(self): + + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request <Request>` sent from this + #: :class:`Session <Session>`. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request <Request>`. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request <Request>`. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request <Request>`. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount('https://', HTTPAdapter()) + self.mount('http://', HTTPAdapter()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest <PreparedRequest>` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request <Request>` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), + params=merge_setting(request.params, self.params), + auth=merge_setting(auth, self.auth), + cookies=merged_cookies, + hooks=merge_hooks(request.hooks, self.hooks), + ) + return p + + def request(self, method, url, + params=None, data=None, headers=None, cookies=None, files=None, + auth=None, timeout=None, allow_redirects=True, proxies=None, + hooks=None, stream=None, verify=None, cert=None, json=None): + """Constructs a :class:`Request <Request>`, prepares it and sends it. + Returns :class:`Response <Response>` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) <timeouts>` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + 'timeout': timeout, + 'allow_redirects': allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('GET', url, **kwargs) + + def options(self, url, **kwargs): + r"""Sends a OPTIONS request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', True) + return self.request('OPTIONS', url, **kwargs) + + def head(self, url, **kwargs): + r"""Sends a HEAD request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault('allow_redirects', False) + return self.request('HEAD', url, **kwargs) + + def post(self, url, data=None, json=None, **kwargs): + r"""Sends a POST request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('POST', url, data=data, json=json, **kwargs) + + def put(self, url, data=None, **kwargs): + r"""Sends a PUT request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PUT', url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('PATCH', url, data=data, **kwargs) + + def delete(self, url, **kwargs): + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request('DELETE', url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault('stream', self.stream) + kwargs.setdefault('verify', self.verify) + kwargs.setdefault('cert', self.cert) + kwargs.setdefault('proxies', self.proxies) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if isinstance(request, Request): + raise ValueError('You can only send PreparedRequests.') + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop('allow_redirects', True) + stream = kwargs.get('stream') + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook('response', hooks, r, **kwargs) + + # Persist cookies + if r.history: + + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + + # Resolve redirects if allowed. + history = [resp for resp in gen] if allow_redirects else [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # If redirects aren't being followed, store the response on the Request for Response.next(). + if not allow_redirects: + try: + r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs)) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + no_proxy = proxies.get('no_proxy') if proxies is not None else None + env_proxies = get_environ_proxies(url, no_proxy=no_proxy) + for (k, v) in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for requests environment configuration and be compatible + # with cURL. + if verify is True or verify is None: + verify = (os.environ.get('REQUESTS_CA_BUNDLE') or + os.environ.get('CURL_CA_BUNDLE')) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {'verify': verify, 'proxies': proxies, 'stream': stream, + 'cert': cert} + + def get_adapter(self, url): + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for (prefix, adapter) in self.adapters.items(): + + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema("No connection adapters were found for '%s'" % url) + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state): + for attr, value in state.items(): + setattr(self, attr, value) + + +def session(): + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/lib/requests/status_codes.py b/lib/requests/status_codes.py new file mode 100644 index 0000000..813e8c4 --- /dev/null +++ b/lib/requests/status_codes.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +r""" +The ``codes`` object defines a mapping from common names for HTTP statuses +to their numerical codes, accessible either as attributes or as dictionary +items. + +>>> requests.codes['temporary_redirect'] +307 +>>> requests.codes.teapot +418 +>>> requests.codes['\o/'] +200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +from .structures import LookupDict + +_codes = { + + # Informational. + 100: ('continue',), + 101: ('switching_protocols',), + 102: ('processing',), + 103: ('checkpoint',), + 122: ('uri_too_long', 'request_uri_too_long'), + 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'), + 201: ('created',), + 202: ('accepted',), + 203: ('non_authoritative_info', 'non_authoritative_information'), + 204: ('no_content',), + 205: ('reset_content', 'reset'), + 206: ('partial_content', 'partial'), + 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), + 208: ('already_reported',), + 226: ('im_used',), + + # Redirection. + 300: ('multiple_choices',), + 301: ('moved_permanently', 'moved', '\\o-'), + 302: ('found',), + 303: ('see_other', 'other'), + 304: ('not_modified',), + 305: ('use_proxy',), + 306: ('switch_proxy',), + 307: ('temporary_redirect', 'temporary_moved', 'temporary'), + 308: ('permanent_redirect', + 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 + + # Client Error. + 400: ('bad_request', 'bad'), + 401: ('unauthorized',), + 402: ('payment_required', 'payment'), + 403: ('forbidden',), + 404: ('not_found', '-o-'), + 405: ('method_not_allowed', 'not_allowed'), + 406: ('not_acceptable',), + 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), + 408: ('request_timeout', 'timeout'), + 409: ('conflict',), + 410: ('gone',), + 411: ('length_required',), + 412: ('precondition_failed', 'precondition'), + 413: ('request_entity_too_large',), + 414: ('request_uri_too_large',), + 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), + 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), + 417: ('expectation_failed',), + 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), + 421: ('misdirected_request',), + 422: ('unprocessable_entity', 'unprocessable'), + 423: ('locked',), + 424: ('failed_dependency', 'dependency'), + 425: ('unordered_collection', 'unordered'), + 426: ('upgrade_required', 'upgrade'), + 428: ('precondition_required', 'precondition'), + 429: ('too_many_requests', 'too_many'), + 431: ('header_fields_too_large', 'fields_too_large'), + 444: ('no_response', 'none'), + 449: ('retry_with', 'retry'), + 450: ('blocked_by_windows_parental_controls', 'parental_controls'), + 451: ('unavailable_for_legal_reasons', 'legal_reasons'), + 499: ('client_closed_request',), + + # Server Error. + 500: ('internal_server_error', 'server_error', '/o\\', '✗'), + 501: ('not_implemented',), + 502: ('bad_gateway',), + 503: ('service_unavailable', 'unavailable'), + 504: ('gateway_timeout',), + 505: ('http_version_not_supported', 'http_version'), + 506: ('variant_also_negotiates',), + 507: ('insufficient_storage',), + 509: ('bandwidth_limit_exceeded', 'bandwidth'), + 510: ('not_extended',), + 511: ('network_authentication_required', 'network_auth', 'network_authentication'), +} + +codes = LookupDict(name='status_codes') + +def _init(): + for code, titles in _codes.items(): + for title in titles: + setattr(codes, title, code) + if not title.startswith(('\\', '/')): + setattr(codes, title.upper(), code) + + def doc(code): + names = ', '.join('``%s``' % n for n in _codes[code]) + return '* %d: %s' % (code, names) + + global __doc__ + __doc__ = (__doc__ + '\n' + + '\n'.join(doc(code) for code in sorted(_codes)) + if __doc__ is not None else None) + +_init() diff --git a/lib/requests/structures.py b/lib/requests/structures.py new file mode 100644 index 0000000..da930e2 --- /dev/null +++ b/lib/requests/structures.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from .compat import OrderedDict, Mapping, MutableMapping + + +class CaseInsensitiveDict(MutableMapping): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + def __init__(self, data=None, **kwargs): + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ( + (lowerkey, keyval[1]) + for (lowerkey, keyval) + in self._store.items() + ) + + def __eq__(self, other): + if isinstance(other, Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super(LookupDict, self).__init__() + + def __repr__(self): + return '<lookup \'%s\'>' % (self.name) + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/lib/requests/utils.py b/lib/requests/utils.py new file mode 100644 index 0000000..8170a8d --- /dev/null +++ b/lib/requests/utils.py @@ -0,0 +1,977 @@ +# -*- coding: utf-8 -*- + +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile + +from .__version__ import __version__ +from . import certs +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import to_native_string +from .compat import parse_http_list as _parse_list_header +from .compat import ( + quote, urlparse, bytes, str, OrderedDict, unquote, getproxies, + proxy_bypass, urlunparse, basestring, integer_types, is_py3, + proxy_bypass_environment, getproxies_environment, Mapping) +from .cookies import cookiejar_from_dict +from .structures import CaseInsensitiveDict +from .exceptions import ( + InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError) + +NETRC_FILES = ('.netrc', '_netrc') + +DEFAULT_CA_BUNDLE_PATH = certs.where() + +DEFAULT_PORTS = {'http': 80, 'https': 443} + + +if sys.platform == 'win32': + # provide a proxy_bypass version on Windows without DNS lookups + + def proxy_bypass_registry(host): + try: + if is_py3: + import winreg + else: + import _winreg as winreg + except ImportError: + return False + + try: + internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER, + r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') + # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it + proxyEnable = int(winreg.QueryValueEx(internetSettings, + 'ProxyEnable')[0]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, + 'ProxyOverride')[0] + except OSError: + return False + if not proxyEnable or not proxyOverride: + return False + + # make a check value list from the registry entry: replace the + # '<local>' string by the localhost entry and the corresponding + # canonical entry. + proxyOverride = proxyOverride.split(';') + # now check if we match one of the registry values. + for test in proxyOverride: + if test == '<local>': + if '.' not in host: + return True + test = test.replace(".", r"\.") # mask dots + test = test.replace("*", r".*") # change glob sequence + test = test.replace("?", r".") # change glob char + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host): # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, 'items'): + d = d.items() + + return d + + +def super_len(o): + total_length = None + current_position = 0 + + if hasattr(o, '__len__'): + total_length = len(o) + + elif hasattr(o, 'len'): + total_length = o.len + + elif hasattr(o, 'fileno'): + try: + fileno = o.fileno() + except io.UnsupportedOperation: + pass + else: + total_length = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if 'b' not in o.mode: + warnings.warn(( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode."), + FileModeWarning + ) + + if hasattr(o, 'tell'): + try: + current_position = o.tell() + except (OSError, IOError): + # This can happen in some weird situations, such as when the file + # is actually a special file descriptor like stdin. In this + # instance, we don't know what the length is, so set it to zero and + # let requests chunk it instead. + if total_length is not None: + current_position = total_length + else: + if hasattr(o, 'seek') and total_length is None: + # StringIO and BytesIO have seek but no useable fileno + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except (OSError, IOError): + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + try: + from netrc import netrc, NetrcParseError + + netrc_path = None + + for f in NETRC_FILES: + try: + loc = os.path.expanduser('~/{}'.format(f)) + except KeyError: + # os.path.expanduser can fail when $HOME is undefined and + # getpwuid fails. See https://bugs.python.org/issue20164 & + # https://github.com/requests/requests/issues/1846 + return + + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + + # Strip port numbers from netloc. This weird `if...encode`` dance is + # used for Python 3.2, which doesn't support unicode literals. + splitstr = b':' + if isinstance(url, str): + splitstr = splitstr.decode('ascii') + host = ri.netloc.split(splitstr)[0] + + try: + _netrc = netrc(netrc_path).authenticators(host) + if _netrc: + # Return with login / password + login_i = (0 if _netrc[0] else 1) + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, IOError): + # If there was a parsing error or a permissions issue reading the file, + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: + raise + + # AppEngine hackiness. + except (ImportError, AttributeError): + pass + + +def guess_filename(obj): + """Tries to guess the filename of the given object.""" + name = getattr(obj, 'name', None) + if (name and isinstance(name, basestring) and name[0] != '<' and + name[-1] != '>'): + return os.path.basename(name) + + +def extract_zipped_paths(path): + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + archive, member = os.path.split(path) + while archive and not os.path.exists(archive): + archive, prefix = os.path.split(archive) + member = '/'.join([prefix, member]) + + if not zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return path + + # we have a valid zip archive and a valid member of that archive + tmp = tempfile.gettempdir() + extracted_path = os.path.join(tmp, *member.split('/')) + if not os.path.exists(extracted_path): + extracted_path = zip_file.extract(member, path=tmp) + + return extracted_path + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + ValueError: cannot encode objects that are not 2-tuples. + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError('cannot encode objects that are not 2-tuples') + + if isinstance(value, Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result = {} + for item in _parse_list_header(value): + if '=' not in item: + result[item] = None + continue + name, value = item.split('=', 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != '\\\\': + return value.replace('\\\\', '\\').replace('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {} + + for cookie in cj: + cookie_dict[cookie.name] = cookie.value + + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :param content: bytestring to extract encodings from. + """ + warnings.warn(( + 'In requests 3.0, get_encodings_from_content will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) + + charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I) + pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I) + xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') + + return (charset_re.findall(content) + + pragma_re.findall(content) + + xml_re.findall(content)) + + +def _parse_content_type_header(header): + """Returns content type and parameters from given header + + :param header: string + :return: tuple containing content type and dictionary of + parameters + """ + + tokens = header.split(';') + content_type, params = tokens[0].strip(), tokens[1:] + params_dict = {} + items_to_strip = "\"' " + + for param in params: + param = param.strip() + if param: + key, value = param, True + index_of_equals = param.find("=") + if index_of_equals != -1: + key = param[:index_of_equals].strip(items_to_strip) + value = param[index_of_equals + 1:].strip(items_to_strip) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + content_type = headers.get('content-type') + + if not content_type: + return None + + content_type, params = _parse_content_type_header(content_type) + + if 'charset' in params: + return params['charset'].strip("'\"") + + if 'text' in content_type: + return 'ISO-8859-1' + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes a iterator.""" + + if r.encoding is None: + for item in iterator: + yield item + return + + decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace') + for chunk in iterator: + rv = decoder.decode(chunk) + if rv: + yield rv + rv = decoder.decode(b'', final=True) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos:pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :rtype: str + """ + warnings.warn(( + 'In requests 3.0, get_unicode_from_response will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors='replace') + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~") + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + parts = uri.split('%') + for i in range(1, len(parts)): + h = parts[i][0:2] + if len(h) == 2 and h.isalnum(): + try: + c = chr(int(h, 16)) + except ValueError: + raise InvalidURL("Invalid percent-escape sequence: '%s'" % h) + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = '%' + parts[i] + else: + parts[i] = '%' + parts[i] + return ''.join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :rtype: bool + """ + ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0] + netaddr, bits = net.split('/') + netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0] + network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xffffffff ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack('>I', bits)) + + +def is_ipv4_address(string_ip): + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except socket.error: + return False + return True + + +def is_valid_cidr(string_network): + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count('/') == 1: + try: + mask = int(string_network.split('/')[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split('/')[0]) + except socket.error: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + # Prioritize lowercase environment variables over uppercase + # to keep a consistent behaviour with other http projects (curl, wget). + get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper()) + + # First check whether no_proxy is defined. If it is, check that the URL + # we're getting isn't in the no_proxy list. + no_proxy_arg = no_proxy + if no_proxy is None: + no_proxy = get_proxy('no_proxy') + parsed = urlparse(url) + + if parsed.hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + if no_proxy: + # We need to check whether we match here. We need to see if we match + # the end of the hostname, both with and without the port. + no_proxy = ( + host for host in no_proxy.replace(' ', '').split(',') if host + ) + + if is_ipv4_address(parsed.hostname): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(parsed.hostname, proxy_ip): + return True + elif parsed.hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = parsed.hostname + if parsed.port: + host_with_port += ':{}'.format(parsed.port) + + for host in no_proxy: + if parsed.hostname.endswith(host) or host_with_port.endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + with set_environ('no_proxy', no_proxy_arg): + # parsed.hostname can be `None` in cases such as a file URI. + try: + bypass = proxy_bypass(parsed.hostname) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url, no_proxy=None): + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get('all')) + + proxy_keys = [ + urlparts.scheme + '://' + urlparts.hostname, + urlparts.scheme, + 'all://' + urlparts.hostname, + 'all', + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def default_user_agent(name="python-requests"): + """ + Return a string representing the default user agent. + + :rtype: str + """ + return '%s/%s' % (name, __version__) + + +def default_headers(): + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict({ + 'User-Agent': default_user_agent(), + 'Accept-Encoding': ', '.join(('gzip', 'deflate')), + 'Accept': '*/*', + 'Connection': 'keep-alive', + }) + + +def parse_header_links(value): + """Return a list of parsed link headers proxies. + + i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg" + + :rtype: list + """ + + links = [] + + replace_chars = ' \'"' + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(', *<', value): + try: + url, params = val.split(';', 1) + except ValueError: + url, params = val, '' + + link = {'url': url.strip('<> \'"')} + + for param in params.split(';'): + try: + key, value = param.split('=') + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # determine the encoding. Also detect a BOM, if present. + sample = data[:4] + if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): + return 'utf-32' # BOM included + if sample[:3] == codecs.BOM_UTF8: + return 'utf-8-sig' # BOM included, MS style (discouraged) + if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + return 'utf-16' # BOM included + nullcount = sample.count(_null) + if nullcount == 0: + return 'utf-8' + if nullcount == 2: + if sample[::2] == _null2: # 1st and 3rd are null + return 'utf-16-be' + if sample[1::2] == _null2: # 2nd and 4th are null + return 'utf-16-le' + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return 'utf-32-be' + if sample[1:] == _null3: + return 'utf-32-le' + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme) + + # urlparse is a finicky beast, and sometimes decides that there isn't a + # netloc present. Assume that it's being over-cautious, and switch netloc + # and path if urlparse decided there was no netloc. + if not netloc: + netloc, path = path, netloc + + return urlunparse((scheme, netloc, path, params, query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + auth = ('', '') + + return auth + + +# Moved outside of function to avoid recompile every call +_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$') +_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$') + + +def check_header_validity(header): + """Verifies that header value is a string which doesn't contain + leading whitespace or return characters. This prevents unintended + header injection. + + :param header: tuple, in the format (name, value). + """ + name, value = header + + if isinstance(value, bytes): + pat = _CLEAN_HEADER_REGEX_BYTE + else: + pat = _CLEAN_HEADER_REGEX_STR + try: + if not pat.match(value): + raise InvalidHeader("Invalid return character or leading space in header: %s" % name) + except TypeError: + raise InvalidHeader("Value for header {%s: %s} must be of type str or " + "bytes, not %s" % (name, value, type(value))) + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit('@', 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, '')) + + +def rewind_body(prepared_request): + """Move file pointer back to its recorded starting position + so it can be read again on redirect. + """ + body_seek = getattr(prepared_request.body, 'seek', None) + if body_seek is not None and isinstance(prepared_request._body_position, integer_types): + try: + body_seek(prepared_request._body_position) + except (IOError, OSError): + raise UnrewindableBodyError("An error occurred when rewinding request " + "body for redirect.") + else: + raise UnrewindableBodyError("Unable to rewind request body for redirect.") diff --git a/lib/schedule/__init__.py b/lib/schedule/__init__.py new file mode 100644 index 0000000..3f6daa8 --- /dev/null +++ b/lib/schedule/__init__.py @@ -0,0 +1,528 @@ +""" +Python job scheduling for humans. + +github.com/dbader/schedule + +An in-process scheduler for periodic jobs that uses the builder pattern +for configuration. Schedule lets you run Python functions (or any other +callable) periodically at pre-determined intervals using a simple, +human-friendly syntax. + +Inspired by Addam Wiggins' article "Rethinking Cron" [1] and the +"clockwork" Ruby module [2][3]. + +Features: + - A simple to use API for scheduling jobs. + - Very lightweight and no external dependencies. + - Excellent test coverage. + - Works with Python 2.7 and 3.3 + +Usage: + >>> import schedule + >>> import time + + >>> def job(message='stuff'): + >>> print("I'm working on:", message) + + >>> schedule.every(10).minutes.do(job) + >>> schedule.every(5).to(10).days.do(job) + >>> schedule.every().hour.do(job, message='things') + >>> schedule.every().day.at("10:30").do(job) + + >>> while True: + >>> schedule.run_pending() + >>> time.sleep(1) + +[1] https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ +[2] https://github.com/Rykian/clockwork +[3] https://adam.herokuapp.com/past/2010/6/30/replace_cron_with_clockwork/ +""" +import collections +import datetime +import functools +import logging +import random +import time + +logger = logging.getLogger('schedule') + + +class CancelJob(object): + """ + Can be returned from a job to unschedule itself. + """ + pass + + +class Scheduler(object): + """ + Objects instantiated by the :class:`Scheduler <Scheduler>` are + factories to create jobs, keep record of scheduled jobs and + handle their execution. + """ + def __init__(self): + self.jobs = [] + + def run_pending(self): + """ + Run all jobs that are scheduled to run. + + Please note that it is *intended behavior that run_pending() + does not run missed jobs*. For example, if you've registered a job + that should run every minute and you only call run_pending() + in one hour increments then your job won't be run 60 times in + between but only once. + """ + runnable_jobs = (job for job in self.jobs if job.should_run) + for job in sorted(runnable_jobs): + self._run_job(job) + + def run_all(self, delay_seconds=0): + """ + Run all jobs regardless if they are scheduled to run or not. + + A delay of `delay` seconds is added between each job. This helps + distribute system load generated by the jobs more evenly + over time. + + :param delay_seconds: A delay added between every executed job + """ + logger.info('Running *all* %i jobs with %is delay inbetween', + len(self.jobs), delay_seconds) + for job in self.jobs[:]: + self._run_job(job) + time.sleep(delay_seconds) + + def clear(self, tag=None): + """ + Deletes scheduled jobs marked with the given tag, or all jobs + if tag is omitted. + + :param tag: An identifier used to identify a subset of + jobs to delete + """ + if tag is None: + del self.jobs[:] + else: + self.jobs[:] = (job for job in self.jobs if tag not in job.tags) + + def cancel_job(self, job): + """ + Delete a scheduled job. + + :param job: The job to be unscheduled + """ + try: + self.jobs.remove(job) + except ValueError: + pass + + def every(self, interval=1): + """ + Schedule a new periodic job. + + :param interval: A quantity of a certain time unit + :return: An unconfigured :class:`Job <Job>` + """ + job = Job(interval, self) + return job + + def _run_job(self, job): + ret = job.run() + if isinstance(ret, CancelJob) or ret is CancelJob: + self.cancel_job(job) + + @property + def next_run(self): + """ + Datetime when the next job should run. + + :return: A :class:`~datetime.datetime` object + """ + if not self.jobs: + return None + return min(self.jobs).next_run + + @property + def idle_seconds(self): + """ + :return: Number of seconds until + :meth:`next_run <Scheduler.next_run>`. + """ + return (self.next_run - datetime.datetime.now()).total_seconds() + + +class Job(object): + """ + A periodic job as used by :class:`Scheduler`. + + :param interval: A quantity of a certain time unit + :param scheduler: The :class:`Scheduler <Scheduler>` instance that + this job will register itself with once it has + been fully configured in :meth:`Job.do()`. + + Every job runs at a given fixed time interval that is defined by: + + * a :meth:`time unit <Job.second>` + * a quantity of `time units` defined by `interval` + + A job is usually created and returned by :meth:`Scheduler.every` + method, which also defines its `interval`. + """ + def __init__(self, interval, scheduler=None): + self.interval = interval # pause interval * unit between runs + self.latest = None # upper limit to the interval + self.job_func = None # the job job_func to run + self.unit = None # time units, e.g. 'minutes', 'hours', ... + self.at_time = None # optional time at which this job runs + self.last_run = None # datetime of the last run + self.next_run = None # datetime of the next run + self.period = None # timedelta between runs, only valid for + self.start_day = None # Specific day of the week to start on + self.tags = set() # unique set of tags for the job + self.scheduler = scheduler # scheduler to register with + + def __lt__(self, other): + """ + PeriodicJobs are sortable based on the scheduled time they + run next. + """ + return self.next_run < other.next_run + + def __repr__(self): + def format_time(t): + return t.strftime('%Y-%m-%d %H:%M:%S') if t else '[never]' + + timestats = '(last run: %s, next run: %s)' % ( + format_time(self.last_run), format_time(self.next_run)) + + if hasattr(self.job_func, '__name__'): + job_func_name = self.job_func.__name__ + else: + job_func_name = repr(self.job_func) + args = [repr(x) for x in self.job_func.args] + kwargs = ['%s=%s' % (k, repr(v)) + for k, v in self.job_func.keywords.items()] + call_repr = job_func_name + '(' + ', '.join(args + kwargs) + ')' + + if self.at_time is not None: + return 'Every %s %s at %s do %s %s' % ( + self.interval, + self.unit[:-1] if self.interval == 1 else self.unit, + self.at_time, call_repr, timestats) + else: + fmt = ( + 'Every %(interval)s ' + + ('to %(latest)s ' if self.latest is not None else '') + + '%(unit)s do %(call_repr)s %(timestats)s' + ) + + return fmt % dict( + interval=self.interval, + latest=self.latest, + unit=(self.unit[:-1] if self.interval == 1 else self.unit), + call_repr=call_repr, + timestats=timestats + ) + + @property + def second(self): + assert self.interval == 1, 'Use seconds instead of second' + return self.seconds + + @property + def seconds(self): + self.unit = 'seconds' + return self + + @property + def minute(self): + assert self.interval == 1, 'Use minutes instead of minute' + return self.minutes + + @property + def minutes(self): + self.unit = 'minutes' + return self + + @property + def hour(self): + assert self.interval == 1, 'Use hours instead of hour' + return self.hours + + @property + def hours(self): + self.unit = 'hours' + return self + + @property + def day(self): + assert self.interval == 1, 'Use days instead of day' + return self.days + + @property + def days(self): + self.unit = 'days' + return self + + @property + def week(self): + assert self.interval == 1, 'Use weeks instead of week' + return self.weeks + + @property + def weeks(self): + self.unit = 'weeks' + return self + + @property + def monday(self): + assert self.interval == 1, 'Use mondays instead of monday' + self.start_day = 'monday' + return self.weeks + + @property + def tuesday(self): + assert self.interval == 1, 'Use tuesdays instead of tuesday' + self.start_day = 'tuesday' + return self.weeks + + @property + def wednesday(self): + assert self.interval == 1, 'Use wedesdays instead of wednesday' + self.start_day = 'wednesday' + return self.weeks + + @property + def thursday(self): + assert self.interval == 1, 'Use thursday instead of thursday' + self.start_day = 'thursday' + return self.weeks + + @property + def friday(self): + assert self.interval == 1, 'Use fridays instead of friday' + self.start_day = 'friday' + return self.weeks + + @property + def saturday(self): + assert self.interval == 1, 'Use saturdays instead of saturday' + self.start_day = 'saturday' + return self.weeks + + @property + def sunday(self): + assert self.interval == 1, 'Use sundays instead of sunday' + self.start_day = 'sunday' + return self.weeks + + def tag(self, *tags): + """ + Tags the job with one or more unique indentifiers. + + Tags must be hashable. Duplicate tags are discarded. + + :param tags: A unique list of ``Hashable`` tags. + :return: The invoked job instance + """ + if any([not isinstance(tag, collections.Hashable) for tag in tags]): + raise TypeError('Every tag should be hashable') + + if not all(isinstance(tag, collections.Hashable) for tag in tags): + raise TypeError('Tags must be hashable') + self.tags.update(tags) + return self + + def at(self, time_str): + """ + Schedule the job every day at a specific time. + + Calling this is only valid for jobs scheduled to run + every N day(s). + + :param time_str: A string in `XX:YY` format. + :return: The invoked job instance + """ + assert self.unit in ('days', 'hours') or self.start_day + hour, minute = time_str.split(':') + minute = int(minute) + if self.unit == 'days' or self.start_day: + hour = int(hour) + assert 0 <= hour <= 23 + elif self.unit == 'hours': + hour = 0 + assert 0 <= minute <= 59 + self.at_time = datetime.time(hour, minute) + return self + + def to(self, latest): + """ + Schedule the job to run at an irregular (randomized) interval. + + The job's interval will randomly vary from the value given + to `every` to `latest`. The range defined is inclusive on + both ends. For example, `every(A).to(B).seconds` executes + the job function every N seconds such that A <= N <= B. + + :param latest: Maximum interval between randomized job runs + :return: The invoked job instance + """ + self.latest = latest + return self + + def do(self, job_func, *args, **kwargs): + """ + Specifies the job_func that should be called every time the + job runs. + + Any additional arguments are passed on to job_func when + the job runs. + + :param job_func: The function to be scheduled + :return: The invoked job instance + """ + self.job_func = functools.partial(job_func, *args, **kwargs) + try: + functools.update_wrapper(self.job_func, job_func) + except AttributeError: + # job_funcs already wrapped by functools.partial won't have + # __name__, __module__ or __doc__ and the update_wrapper() + # call will fail. + pass + self._schedule_next_run() + self.scheduler.jobs.append(self) + return self + + @property + def should_run(self): + """ + :return: ``True`` if the job should be run now. + """ + return datetime.datetime.now() >= self.next_run + + def run(self): + """ + Run the job and immediately reschedule it. + + :return: The return value returned by the `job_func` + """ + logger.info('Running job %s', self) + ret = self.job_func() + self.last_run = datetime.datetime.now() + self._schedule_next_run() + return ret + + def _schedule_next_run(self): + """ + Compute the instant when this job should run next. + """ + assert self.unit in ('seconds', 'minutes', 'hours', 'days', 'weeks') + + if self.latest is not None: + assert self.latest >= self.interval + interval = random.randint(self.interval, self.latest) + else: + interval = self.interval + + self.period = datetime.timedelta(**{self.unit: interval}) + self.next_run = datetime.datetime.now() + self.period + if self.start_day is not None: + assert self.unit == 'weeks' + weekdays = ( + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'sunday' + ) + assert self.start_day in weekdays + weekday = weekdays.index(self.start_day) + days_ahead = weekday - self.next_run.weekday() + if days_ahead <= 0: # Target day already happened this week + days_ahead += 7 + self.next_run += datetime.timedelta(days_ahead) - self.period + if self.at_time is not None: + assert self.unit in ('days', 'hours') or self.start_day is not None + kwargs = { + 'minute': self.at_time.minute, + 'second': self.at_time.second, + 'microsecond': 0 + } + if self.unit == 'days' or self.start_day is not None: + kwargs['hour'] = self.at_time.hour + self.next_run = self.next_run.replace(**kwargs) + # If we are running for the first time, make sure we run + # at the specified time *today* (or *this hour*) as well + if not self.last_run: + now = datetime.datetime.now() + if (self.unit == 'days' and self.at_time > now.time() and + self.interval == 1): + self.next_run = self.next_run - datetime.timedelta(days=1) + elif self.unit == 'hours' and self.at_time.minute > now.minute: + self.next_run = self.next_run - datetime.timedelta(hours=1) + if self.start_day is not None and self.at_time is not None: + # Let's see if we will still make that time we specified today + if (self.next_run - datetime.datetime.now()).days >= 7: + self.next_run -= self.period + + +# The following methods are shortcuts for not having to +# create a Scheduler instance: + +#: Default :class:`Scheduler <Scheduler>` object +default_scheduler = Scheduler() + +#: Default :class:`Jobs <Job>` list +jobs = default_scheduler.jobs # todo: should this be a copy, e.g. jobs()? + + +def every(interval=1): + """Calls :meth:`every <Scheduler.every>` on the + :data:`default scheduler instance <default_scheduler>`. + """ + return default_scheduler.every(interval) + + +def run_pending(): + """Calls :meth:`run_pending <Scheduler.run_pending>` on the + :data:`default scheduler instance <default_scheduler>`. + """ + default_scheduler.run_pending() + + +def run_all(delay_seconds=0): + """Calls :meth:`run_all <Scheduler.run_all>` on the + :data:`default scheduler instance <default_scheduler>`. + """ + default_scheduler.run_all(delay_seconds=delay_seconds) + + +def clear(tag=None): + """Calls :meth:`clear <Scheduler.clear>` on the + :data:`default scheduler instance <default_scheduler>`. + """ + default_scheduler.clear(tag) + + +def cancel_job(job): + """Calls :meth:`cancel_job <Scheduler.cancel_job>` on the + :data:`default scheduler instance <default_scheduler>`. + """ + default_scheduler.cancel_job(job) + + +def next_run(): + """Calls :meth:`next_run <Scheduler.next_run>` on the + :data:`default scheduler instance <default_scheduler>`. + """ + return default_scheduler.next_run + + +def idle_seconds(): + """Calls :meth:`idle_seconds <Scheduler.idle_seconds>` on the + :data:`default scheduler instance <default_scheduler>`. + """ + return default_scheduler.idle_seconds diff --git a/lib/setuptools/__init__.py b/lib/setuptools/__init__.py new file mode 100644 index 0000000..e438036 --- /dev/null +++ b/lib/setuptools/__init__.py @@ -0,0 +1,195 @@ +"""Extensions to the 'distutils' for large or complex distributions""" + +import os +import sys +import functools +import distutils.core +import distutils.filelist +from distutils.util import convert_path +from fnmatch import fnmatchcase + +from ._deprecation_warning import SetuptoolsDeprecationWarning + +from setuptools.extern.six import PY3 +from setuptools.extern.six.moves import filter, map + +import setuptools.version +from setuptools.extension import Extension +from setuptools.dist import Distribution, Feature +from setuptools.depends import Require +from . import monkey + +__metaclass__ = type + + +__all__ = [ + 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', + 'SetuptoolsDeprecationWarning', + 'find_packages' +] + +if PY3: + __all__.append('find_namespace_packages') + +__version__ = setuptools.version.__version__ + +bootstrap_install_from = None + +# If we run 2to3 on .py files, should we also convert docstrings? +# Default: yes; assume that we can detect doctests reliably +run_2to3_on_doctests = True +# Standard package names for fixer packages +lib2to3_fixer_packages = ['lib2to3.fixes'] + + +class PackageFinder: + """ + Generate a list of all Python packages found within a directory + """ + + @classmethod + def find(cls, where='.', exclude=(), include=('*',)): + """Return a list all Python packages found within directory 'where' + + 'where' is the root directory which will be searched for packages. It + should be supplied as a "cross-platform" (i.e. URL-style) path; it will + be converted to the appropriate local path syntax. + + 'exclude' is a sequence of package names to exclude; '*' can be used + as a wildcard in the names, such that 'foo.*' will exclude all + subpackages of 'foo' (but not 'foo' itself). + + 'include' is a sequence of package names to include. If it's + specified, only the named packages will be included. If it's not + specified, all found packages will be included. 'include' can contain + shell style wildcard patterns just like 'exclude'. + """ + + return list(cls._find_packages_iter( + convert_path(where), + cls._build_filter('ez_setup', '*__pycache__', *exclude), + cls._build_filter(*include))) + + @classmethod + def _find_packages_iter(cls, where, exclude, include): + """ + All the packages found in 'where' that pass the 'include' filter, but + not the 'exclude' filter. + """ + for root, dirs, files in os.walk(where, followlinks=True): + # Copy dirs to iterate over it, then empty dirs. + all_dirs = dirs[:] + dirs[:] = [] + + for dir in all_dirs: + full_path = os.path.join(root, dir) + rel_path = os.path.relpath(full_path, where) + package = rel_path.replace(os.path.sep, '.') + + # Skip directory trees that are not valid packages + if ('.' in dir or not cls._looks_like_package(full_path)): + continue + + # Should this package be included? + if include(package) and not exclude(package): + yield package + + # Keep searching subdirectories, as there may be more packages + # down there, even if the parent was excluded. + dirs.append(dir) + + @staticmethod + def _looks_like_package(path): + """Does a directory look like a package?""" + return os.path.isfile(os.path.join(path, '__init__.py')) + + @staticmethod + def _build_filter(*patterns): + """ + Given a list of patterns, return a callable that will be true only if + the input matches at least one of the patterns. + """ + return lambda name: any(fnmatchcase(name, pat=pat) for pat in patterns) + + +class PEP420PackageFinder(PackageFinder): + @staticmethod + def _looks_like_package(path): + return True + + +find_packages = PackageFinder.find + +if PY3: + find_namespace_packages = PEP420PackageFinder.find + + +def _install_setup_requires(attrs): + # Note: do not use `setuptools.Distribution` directly, as + # our PEP 517 backend patch `distutils.core.Distribution`. + dist = distutils.core.Distribution(dict( + (k, v) for k, v in attrs.items() + if k in ('dependency_links', 'setup_requires') + )) + # Honor setup.cfg's options. + dist.parse_config_files(ignore_option_errors=True) + if dist.setup_requires: + dist.fetch_build_eggs(dist.setup_requires) + + +def setup(**attrs): + # Make sure we have any requirements needed to interpret 'attrs'. + _install_setup_requires(attrs) + return distutils.core.setup(**attrs) + +setup.__doc__ = distutils.core.setup.__doc__ + + +_Command = monkey.get_unpatched(distutils.core.Command) + + +class Command(_Command): + __doc__ = _Command.__doc__ + + command_consumes_arguments = False + + def __init__(self, dist, **kw): + """ + Construct the command for dist, updating + vars(self) with any keyword parameters. + """ + _Command.__init__(self, dist) + vars(self).update(kw) + + def reinitialize_command(self, command, reinit_subcommands=0, **kw): + cmd = _Command.reinitialize_command(self, command, reinit_subcommands) + vars(cmd).update(kw) + return cmd + + +def _find_all_simple(path): + """ + Find all files under 'path' + """ + results = ( + os.path.join(base, file) + for base, dirs, files in os.walk(path, followlinks=True) + for file in files + ) + return filter(os.path.isfile, results) + + +def findall(dir=os.curdir): + """ + Find all files under 'dir' and return the list of full filenames. + Unless dir is '.', return full filenames with dir prepended. + """ + files = _find_all_simple(dir) + if dir == os.curdir: + make_rel = functools.partial(os.path.relpath, start=dir) + files = map(make_rel, files) + return list(files) + + +# Apply monkey patches +monkey.patch_all() diff --git a/lib/setuptools/_deprecation_warning.py b/lib/setuptools/_deprecation_warning.py new file mode 100644 index 0000000..086b64d --- /dev/null +++ b/lib/setuptools/_deprecation_warning.py @@ -0,0 +1,7 @@ +class SetuptoolsDeprecationWarning(Warning): + """ + Base class for warning deprecations in ``setuptools`` + + This class is not derived from ``DeprecationWarning``, and as such is + visible by default. + """ diff --git a/lib/setuptools/_vendor/__init__.py b/lib/setuptools/_vendor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/setuptools/_vendor/packaging/__about__.py b/lib/setuptools/_vendor/packaging/__about__.py new file mode 100644 index 0000000..95d330e --- /dev/null +++ b/lib/setuptools/_vendor/packaging/__about__.py @@ -0,0 +1,21 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +__all__ = [ + "__title__", "__summary__", "__uri__", "__version__", "__author__", + "__email__", "__license__", "__copyright__", +] + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "16.8" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD or Apache License, Version 2.0" +__copyright__ = "Copyright 2014-2016 %s" % __author__ diff --git a/lib/setuptools/_vendor/packaging/__init__.py b/lib/setuptools/_vendor/packaging/__init__.py new file mode 100644 index 0000000..5ee6220 --- /dev/null +++ b/lib/setuptools/_vendor/packaging/__init__.py @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +from .__about__ import ( + __author__, __copyright__, __email__, __license__, __summary__, __title__, + __uri__, __version__ +) + +__all__ = [ + "__title__", "__summary__", "__uri__", "__version__", "__author__", + "__email__", "__license__", "__copyright__", +] diff --git a/lib/setuptools/_vendor/packaging/_compat.py b/lib/setuptools/_vendor/packaging/_compat.py new file mode 100644 index 0000000..210bb80 --- /dev/null +++ b/lib/setuptools/_vendor/packaging/_compat.py @@ -0,0 +1,30 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import sys + + +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +# flake8: noqa + +if PY3: + string_types = str, +else: + string_types = basestring, + + +def with_metaclass(meta, *bases): + """ + Create a base class with a metaclass. + """ + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) diff --git a/lib/setuptools/_vendor/packaging/_structures.py b/lib/setuptools/_vendor/packaging/_structures.py new file mode 100644 index 0000000..ccc2786 --- /dev/null +++ b/lib/setuptools/_vendor/packaging/_structures.py @@ -0,0 +1,68 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + + +class Infinity(object): + + def __repr__(self): + return "Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return False + + def __le__(self, other): + return False + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return True + + def __ge__(self, other): + return True + + def __neg__(self): + return NegativeInfinity + +Infinity = Infinity() + + +class NegativeInfinity(object): + + def __repr__(self): + return "-Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return True + + def __le__(self, other): + return True + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return False + + def __ge__(self, other): + return False + + def __neg__(self): + return Infinity + +NegativeInfinity = NegativeInfinity() diff --git a/lib/setuptools/_vendor/packaging/markers.py b/lib/setuptools/_vendor/packaging/markers.py new file mode 100644 index 0000000..031332a --- /dev/null +++ b/lib/setuptools/_vendor/packaging/markers.py @@ -0,0 +1,301 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import operator +import os +import platform +import sys + +from setuptools.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd +from setuptools.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString +from setuptools.extern.pyparsing import Literal as L # noqa + +from ._compat import string_types +from .specifiers import Specifier, InvalidSpecifier + + +__all__ = [ + "InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName", + "Marker", "default_environment", +] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Node(object): + + def __init__(self, value): + self.value = value + + def __str__(self): + return str(self.value) + + def __repr__(self): + return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) + + def serialize(self): + raise NotImplementedError + + +class Variable(Node): + + def serialize(self): + return str(self) + + +class Value(Node): + + def serialize(self): + return '"{0}"'.format(self) + + +class Op(Node): + + def serialize(self): + return str(self) + + +VARIABLE = ( + L("implementation_version") | + L("platform_python_implementation") | + L("implementation_name") | + L("python_full_version") | + L("platform_release") | + L("platform_version") | + L("platform_machine") | + L("platform_system") | + L("python_version") | + L("sys_platform") | + L("os_name") | + L("os.name") | # PEP-345 + L("sys.platform") | # PEP-345 + L("platform.version") | # PEP-345 + L("platform.machine") | # PEP-345 + L("platform.python_implementation") | # PEP-345 + L("python_implementation") | # undocumented setuptools legacy + L("extra") +) +ALIASES = { + 'os.name': 'os_name', + 'sys.platform': 'sys_platform', + 'platform.version': 'platform_version', + 'platform.machine': 'platform_machine', + 'platform.python_implementation': 'platform_python_implementation', + 'python_implementation': 'platform_python_implementation' +} +VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) + +VERSION_CMP = ( + L("===") | + L("==") | + L(">=") | + L("<=") | + L("!=") | + L("~=") | + L(">") | + L("<") +) + +MARKER_OP = VERSION_CMP | L("not in") | L("in") +MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) + +MARKER_VALUE = QuotedString("'") | QuotedString('"') +MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) + +BOOLOP = L("and") | L("or") + +MARKER_VAR = VARIABLE | MARKER_VALUE + +MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) +MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) + +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() + +MARKER_EXPR = Forward() +MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) +MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) + +MARKER = stringStart + MARKER_EXPR + stringEnd + + +def _coerce_parse_result(results): + if isinstance(results, ParseResults): + return [_coerce_parse_result(i) for i in results] + else: + return results + + +def _format_marker(marker, first=True): + assert isinstance(marker, (list, tuple, string_types)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if (isinstance(marker, list) and len(marker) == 1 and + isinstance(marker[0], (list, tuple))): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} + + +def _eval_op(lhs, op, rhs): + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs) + + oper = _operators.get(op.serialize()) + if oper is None: + raise UndefinedComparison( + "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) + ) + + return oper(lhs, rhs) + + +_undefined = object() + + +def _get_env(environment, name): + value = environment.get(name, _undefined) + + if value is _undefined: + raise UndefinedEnvironmentName( + "{0!r} does not exist in evaluation environment.".format(name) + ) + + return value + + +def _evaluate_markers(markers, environment): + groups = [[]] + + for marker in markers: + assert isinstance(marker, (list, tuple, string_types)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + lhs_value = _get_env(environment, lhs.value) + rhs_value = rhs.value + else: + lhs_value = lhs.value + rhs_value = _get_env(environment, rhs.value) + + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info): + version = '{0.major}.{0.minor}.{0.micro}'.format(info) + kind = info.releaselevel + if kind != 'final': + version += kind[0] + str(info.serial) + return version + + +def default_environment(): + if hasattr(sys, 'implementation'): + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + else: + iver = '0' + implementation_name = '' + + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": platform.python_version()[:3], + "sys_platform": sys.platform, + } + + +class Marker(object): + + def __init__(self, marker): + try: + self._markers = _coerce_parse_result(MARKER.parseString(marker)) + except ParseException as e: + err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( + marker, marker[e.loc:e.loc + 8]) + raise InvalidMarker(err_str) + + def __str__(self): + return _format_marker(self._markers) + + def __repr__(self): + return "<Marker({0!r})>".format(str(self)) + + def evaluate(self, environment=None): + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = default_environment() + if environment is not None: + current_environment.update(environment) + + return _evaluate_markers(self._markers, current_environment) diff --git a/lib/setuptools/_vendor/packaging/requirements.py b/lib/setuptools/_vendor/packaging/requirements.py new file mode 100644 index 0000000..5b49341 --- /dev/null +++ b/lib/setuptools/_vendor/packaging/requirements.py @@ -0,0 +1,127 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import string +import re + +from setuptools.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException +from setuptools.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine +from setuptools.extern.pyparsing import Literal as L # noqa +from setuptools.extern.six.moves.urllib import parse as urlparse + +from .markers import MARKER_EXPR, Marker +from .specifiers import LegacySpecifier, Specifier, SpecifierSet + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +ALPHANUM = Word(string.ascii_letters + string.digits) + +LBRACKET = L("[").suppress() +RBRACKET = L("]").suppress() +LPAREN = L("(").suppress() +RPAREN = L(")").suppress() +COMMA = L(",").suppress() +SEMICOLON = L(";").suppress() +AT = L("@").suppress() + +PUNCTUATION = Word("-_.") +IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) +IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) + +NAME = IDENTIFIER("name") +EXTRA = IDENTIFIER + +URI = Regex(r'[^ ]+')("url") +URL = (AT + URI) + +EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) +EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") + +VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) +VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) + +VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY +VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), + joinString=",", adjacent=False)("_raw_spec") +_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) +_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '') + +VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") +VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) + +MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") +MARKER_EXPR.setParseAction( + lambda s, l, t: Marker(s[t._original_start:t._original_end]) +) +MARKER_SEPERATOR = SEMICOLON +MARKER = MARKER_SEPERATOR + MARKER_EXPR + +VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) +URL_AND_MARKER = URL + Optional(MARKER) + +NAMED_REQUIREMENT = \ + NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) + +REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd + + +class Requirement(object): + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string): + try: + req = REQUIREMENT.parseString(requirement_string) + except ParseException as e: + raise InvalidRequirement( + "Invalid requirement, parse error at \"{0!r}\"".format( + requirement_string[e.loc:e.loc + 8])) + + self.name = req.name + if req.url: + parsed_url = urlparse.urlparse(req.url) + if not (parsed_url.scheme and parsed_url.netloc) or ( + not parsed_url.scheme and not parsed_url.netloc): + raise InvalidRequirement("Invalid URL given") + self.url = req.url + else: + self.url = None + self.extras = set(req.extras.asList() if req.extras else []) + self.specifier = SpecifierSet(req.specifier) + self.marker = req.marker if req.marker else None + + def __str__(self): + parts = [self.name] + + if self.extras: + parts.append("[{0}]".format(",".join(sorted(self.extras)))) + + if self.specifier: + parts.append(str(self.specifier)) + + if self.url: + parts.append("@ {0}".format(self.url)) + + if self.marker: + parts.append("; {0}".format(self.marker)) + + return "".join(parts) + + def __repr__(self): + return "<Requirement({0!r})>".format(str(self)) diff --git a/lib/setuptools/_vendor/packaging/specifiers.py b/lib/setuptools/_vendor/packaging/specifiers.py new file mode 100644 index 0000000..7f5a76c --- /dev/null +++ b/lib/setuptools/_vendor/packaging/specifiers.py @@ -0,0 +1,774 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import abc +import functools +import itertools +import re + +from ._compat import string_types, with_metaclass +from .version import Version, LegacyVersion, parse + + +class InvalidSpecifier(ValueError): + """ + An invalid specifier was found, users should refer to PEP 440. + """ + + +class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): + + @abc.abstractmethod + def __str__(self): + """ + Returns the str representation of this Specifier like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self): + """ + Returns a hash value for this Specifier like object. + """ + + @abc.abstractmethod + def __eq__(self, other): + """ + Returns a boolean representing whether or not the two Specifier like + objects are equal. + """ + + @abc.abstractmethod + def __ne__(self, other): + """ + Returns a boolean representing whether or not the two Specifier like + objects are not equal. + """ + + @abc.abstractproperty + def prereleases(self): + """ + Returns whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @prereleases.setter + def prereleases(self, value): + """ + Sets whether or not pre-releases as a whole are allowed by this + specifier. + """ + + @abc.abstractmethod + def contains(self, item, prereleases=None): + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter(self, iterable, prereleases=None): + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class _IndividualSpecifier(BaseSpecifier): + + _operators = {} + + def __init__(self, spec="", prereleases=None): + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) + + self._spec = ( + match.group("operator").strip(), + match.group("version").strip(), + ) + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + def __repr__(self): + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "<{0}({1!r}{2})>".format( + self.__class__.__name__, + str(self), + pre, + ) + + def __str__(self): + return "{0}{1}".format(*self._spec) + + def __hash__(self): + return hash(self._spec) + + def __eq__(self, other): + if isinstance(other, string_types): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec == other._spec + + def __ne__(self, other): + if isinstance(other, string_types): + try: + other = self.__class__(other) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._spec != other._spec + + def _get_operator(self, op): + return getattr(self, "_compare_{0}".format(self._operators[op])) + + def _coerce_version(self, version): + if not isinstance(version, (LegacyVersion, Version)): + version = parse(version) + return version + + @property + def operator(self): + return self._spec[0] + + @property + def version(self): + return self._spec[1] + + @property + def prereleases(self): + return self._prereleases + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + def __contains__(self, item): + return self.contains(item) + + def contains(self, item, prereleases=None): + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version or LegacyVersion, this allows us to have + # a shortcut for ``"2.0" in Specifier(">=2") + item = self._coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + return self._get_operator(self.operator)(item, self.version) + + def filter(self, iterable, prereleases=None): + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = self._coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later incase nothing + # else matches this specifier. + if (parsed_version.is_prerelease and not + (prereleases or self.prereleases)): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the begining. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +class LegacySpecifier(_IndividualSpecifier): + + _regex_str = ( + r""" + (?P<operator>(==|!=|<=|>=|<|>)) + \s* + (?P<version> + [^,;\s)]* # Since this is a "legacy" specifier, and the version + # string can be just about anything, we match everything + # except for whitespace, a semi-colon for marker support, + # a closing paren since versions can be enclosed in + # them, and a comma since it's a version separator. + ) + """ + ) + + _regex = re.compile( + r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + } + + def _coerce_version(self, version): + if not isinstance(version, LegacyVersion): + version = LegacyVersion(str(version)) + return version + + def _compare_equal(self, prospective, spec): + return prospective == self._coerce_version(spec) + + def _compare_not_equal(self, prospective, spec): + return prospective != self._coerce_version(spec) + + def _compare_less_than_equal(self, prospective, spec): + return prospective <= self._coerce_version(spec) + + def _compare_greater_than_equal(self, prospective, spec): + return prospective >= self._coerce_version(spec) + + def _compare_less_than(self, prospective, spec): + return prospective < self._coerce_version(spec) + + def _compare_greater_than(self, prospective, spec): + return prospective > self._coerce_version(spec) + + +def _require_version_compare(fn): + @functools.wraps(fn) + def wrapped(self, prospective, spec): + if not isinstance(prospective, Version): + return False + return fn(self, prospective, spec) + return wrapped + + +class Specifier(_IndividualSpecifier): + + _regex_str = ( + r""" + (?P<operator>(~=|==|!=|<=|>=|<|>|===)) + (?P<version> + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s]* # We just match everything, except for whitespace + # since we are only testing for strict identity. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + + # You cannot use a wild card and a dev or local version + # together so group them with a | and make them optional. + (?: + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + | + \.\* # Wild card syntax of .* + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?<!==|!=|~=) # We have special cases for these + # operators so we want to make sure they + # don't match here. + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + (?: # pre release + [-_\.]? + (a|b|c|rc|alpha|beta|pre|preview) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + ) + """ + ) + + _regex = re.compile( + r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) + + _operators = { + "~=": "compatible", + "==": "equal", + "!=": "not_equal", + "<=": "less_than_equal", + ">=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + @_require_version_compare + def _compare_compatible(self, prospective, spec): + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore post and dev releases and we want to treat the pre-release as + # it's own separate segment. + prefix = ".".join( + list( + itertools.takewhile( + lambda x: (not x.startswith("post") and not + x.startswith("dev")), + _version_split(spec), + ) + )[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return (self._get_operator(">=")(prospective, spec) and + self._get_operator("==")(prospective, prefix)) + + @_require_version_compare + def _compare_equal(self, prospective, spec): + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + prospective = Version(prospective.public) + # Split the spec out by dots, and pretend that there is an implicit + # dot in between a release segment and a pre-release segment. + spec = _version_split(spec[:-2]) # Remove the trailing .* + + # Split the prospective version out by dots, and pretend that there + # is an implicit dot in between a release segment and a pre-release + # segment. + prospective = _version_split(str(prospective)) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + prospective = prospective[:len(spec)] + + # Pad out our two sides with zeros so that they both equal the same + # length. + spec, prospective = _pad_version(spec, prospective) + else: + # Convert our spec string into a Version + spec = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec.local: + prospective = Version(prospective.public) + + return prospective == spec + + @_require_version_compare + def _compare_not_equal(self, prospective, spec): + return not self._compare_equal(prospective, spec) + + @_require_version_compare + def _compare_less_than_equal(self, prospective, spec): + return prospective <= Version(spec) + + @_require_version_compare + def _compare_greater_than_equal(self, prospective, spec): + return prospective >= Version(spec) + + @_require_version_compare + def _compare_less_than(self, prospective, spec): + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + @_require_version_compare + def _compare_greater_than(self, prospective, spec): + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is techincally greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective, spec): + return str(prospective).lower() == str(spec).lower() + + @property + def prereleases(self): + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "==="]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if parse(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version): + result = [] + for item in version.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _pad_version(left, right): + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]):]) + right_split.append(right[len(right_split[0]):]) + + # Insert our padding + left_split.insert( + 1, + ["0"] * max(0, len(right_split[0]) - len(left_split[0])), + ) + right_split.insert( + 1, + ["0"] * max(0, len(left_split[0]) - len(right_split[0])), + ) + + return ( + list(itertools.chain(*left_split)), + list(itertools.chain(*right_split)), + ) + + +class SpecifierSet(BaseSpecifier): + + def __init__(self, specifiers="", prereleases=None): + # Split on , to break each indidivual specifier into it's own item, and + # strip each item to remove leading/trailing whitespace. + specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Parsed each individual specifier, attempting first to make it a + # Specifier and falling back to a LegacySpecifier. + parsed = set() + for specifier in specifiers: + try: + parsed.add(Specifier(specifier)) + except InvalidSpecifier: + parsed.add(LegacySpecifier(specifier)) + + # Turn our parsed specifiers into a frozen set and save them for later. + self._specs = frozenset(parsed) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + def __repr__(self): + pre = ( + ", prereleases={0!r}".format(self.prereleases) + if self._prereleases is not None + else "" + ) + + return "<SpecifierSet({0!r}{1})>".format(str(self), pre) + + def __str__(self): + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self): + return hash(self._specs) + + def __and__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif isinstance(other, _IndividualSpecifier): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __ne__(self, other): + if isinstance(other, string_types): + other = SpecifierSet(other) + elif isinstance(other, _IndividualSpecifier): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs != other._specs + + def __len__(self): + return len(self._specs) + + def __iter__(self): + return iter(self._specs) + + @property + def prereleases(self): + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value): + self._prereleases = value + + def __contains__(self, item): + return self.contains(item) + + def contains(self, item, prereleases=None): + # Ensure that our item is a Version or LegacyVersion instance. + if not isinstance(item, (LegacyVersion, Version)): + item = parse(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all( + s.contains(item, prereleases=prereleases) + for s in self._specs + ) + + def filter(self, iterable, prereleases=None): + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iterable + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases, and which will filter out LegacyVersion in general. + else: + filtered = [] + found_prereleases = [] + + for item in iterable: + # Ensure that we some kind of Version class for this item. + if not isinstance(item, (LegacyVersion, Version)): + parsed_version = parse(item) + else: + parsed_version = item + + # Filter out any item which is parsed as a LegacyVersion + if isinstance(parsed_version, LegacyVersion): + continue + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return found_prereleases + + return filtered diff --git a/lib/setuptools/_vendor/packaging/utils.py b/lib/setuptools/_vendor/packaging/utils.py new file mode 100644 index 0000000..942387c --- /dev/null +++ b/lib/setuptools/_vendor/packaging/utils.py @@ -0,0 +1,14 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import re + + +_canonicalize_regex = re.compile(r"[-_.]+") + + +def canonicalize_name(name): + # This is taken from PEP 503. + return _canonicalize_regex.sub("-", name).lower() diff --git a/lib/setuptools/_vendor/packaging/version.py b/lib/setuptools/_vendor/packaging/version.py new file mode 100644 index 0000000..83b5ee8 --- /dev/null +++ b/lib/setuptools/_vendor/packaging/version.py @@ -0,0 +1,393 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import absolute_import, division, print_function + +import collections +import itertools +import re + +from ._structures import Infinity + + +__all__ = [ + "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN" +] + + +_Version = collections.namedtuple( + "_Version", + ["epoch", "release", "dev", "pre", "post", "local"], +) + + +def parse(version): + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return LegacyVersion(version) + + +class InvalidVersion(ValueError): + """ + An invalid version was found, users should refer to PEP 440. + """ + + +class _BaseVersion(object): + + def __hash__(self): + return hash(self._key) + + def __lt__(self, other): + return self._compare(other, lambda s, o: s < o) + + def __le__(self, other): + return self._compare(other, lambda s, o: s <= o) + + def __eq__(self, other): + return self._compare(other, lambda s, o: s == o) + + def __ge__(self, other): + return self._compare(other, lambda s, o: s >= o) + + def __gt__(self, other): + return self._compare(other, lambda s, o: s > o) + + def __ne__(self, other): + return self._compare(other, lambda s, o: s != o) + + def _compare(self, other, method): + if not isinstance(other, _BaseVersion): + return NotImplemented + + return method(self._key, other._key) + + +class LegacyVersion(_BaseVersion): + + def __init__(self, version): + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + def __str__(self): + return self._version + + def __repr__(self): + return "<LegacyVersion({0})>".format(repr(str(self))) + + @property + def public(self): + return self._version + + @property + def base_version(self): + return self._version + + @property + def local(self): + return None + + @property + def is_prerelease(self): + return False + + @property + def is_postrelease(self): + return False + + +_legacy_version_component_re = re.compile( + r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, +) + +_legacy_version_replacement_map = { + "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@", +} + + +def _parse_version_parts(s): + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version): + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts = [] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + parts = tuple(parts) + + return epoch, parts + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +VERSION_PATTERN = r""" + v? + (?: + (?:(?P<epoch>[0-9]+)!)? # epoch + (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment + (?P<pre> # pre-release + [-_\.]? + (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview)) + [-_\.]? + (?P<pre_n>[0-9]+)? + )? + (?P<post> # post release + (?:-(?P<post_n1>[0-9]+)) + | + (?: + [-_\.]? + (?P<post_l>post|rev|r) + [-_\.]? + (?P<post_n2>[0-9]+)? + ) + )? + (?P<dev> # dev release + [-_\.]? + (?P<dev_l>dev) + [-_\.]? + (?P<dev_n>[0-9]+)? + )? + ) + (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version +""" + + +class Version(_BaseVersion): + + _regex = re.compile( + r"^\s*" + VERSION_PATTERN + r"\s*$", + re.VERBOSE | re.IGNORECASE, + ) + + def __init__(self, version): + # Validate the version and parse it into pieces + match = self._regex.search(version) + if not match: + raise InvalidVersion("Invalid version: '{0}'".format(version)) + + # Store the parsed out pieces of the version + self._version = _Version( + epoch=int(match.group("epoch")) if match.group("epoch") else 0, + release=tuple(int(i) for i in match.group("release").split(".")), + pre=_parse_letter_version( + match.group("pre_l"), + match.group("pre_n"), + ), + post=_parse_letter_version( + match.group("post_l"), + match.group("post_n1") or match.group("post_n2"), + ), + dev=_parse_letter_version( + match.group("dev_l"), + match.group("dev_n"), + ), + local=_parse_local_version(match.group("local")), + ) + + # Generate a key which will be used for sorting + self._key = _cmpkey( + self._version.epoch, + self._version.release, + self._version.pre, + self._version.post, + self._version.dev, + self._version.local, + ) + + def __repr__(self): + return "<Version({0})>".format(repr(str(self))) + + def __str__(self): + parts = [] + + # Epoch + if self._version.epoch != 0: + parts.append("{0}!".format(self._version.epoch)) + + # Release segment + parts.append(".".join(str(x) for x in self._version.release)) + + # Pre-release + if self._version.pre is not None: + parts.append("".join(str(x) for x in self._version.pre)) + + # Post-release + if self._version.post is not None: + parts.append(".post{0}".format(self._version.post[1])) + + # Development release + if self._version.dev is not None: + parts.append(".dev{0}".format(self._version.dev[1])) + + # Local version segment + if self._version.local is not None: + parts.append( + "+{0}".format(".".join(str(x) for x in self._version.local)) + ) + + return "".join(parts) + + @property + def public(self): + return str(self).split("+", 1)[0] + + @property + def base_version(self): + parts = [] + + # Epoch + if self._version.epoch != 0: + parts.append("{0}!".format(self._version.epoch)) + + # Release segment + parts.append(".".join(str(x) for x in self._version.release)) + + return "".join(parts) + + @property + def local(self): + version_string = str(self) + if "+" in version_string: + return version_string.split("+", 1)[1] + + @property + def is_prerelease(self): + return bool(self._version.dev or self._version.pre) + + @property + def is_postrelease(self): + return bool(self._version.post) + + +def _parse_letter_version(letter, number): + if letter: + # We consider there to be an implicit 0 in a pre-release if there is + # not a numeral associated with it. + if number is None: + number = 0 + + # We normalize any letters to their lower case form + letter = letter.lower() + + # We consider some words to be alternate spellings of other words and + # in those cases we want to normalize the spellings to our preferred + # spelling. + if letter == "alpha": + letter = "a" + elif letter == "beta": + letter = "b" + elif letter in ["c", "pre", "preview"]: + letter = "rc" + elif letter in ["rev", "r"]: + letter = "post" + + return letter, int(number) + if not letter and number: + # We assume if we are given a number, but we are not given a letter + # then this is using the implicit post release syntax (e.g. 1.0-1) + letter = "post" + + return letter, int(number) + + +_local_version_seperators = re.compile(r"[\._-]") + + +def _parse_local_version(local): + """ + Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). + """ + if local is not None: + return tuple( + part.lower() if not part.isdigit() else int(part) + for part in _local_version_seperators.split(local) + ) + + +def _cmpkey(epoch, release, pre, post, dev, local): + # When we compare a release version, we want to compare it with all of the + # trailing zeros removed. So we'll use a reverse the list, drop all the now + # leading zeros until we come to something non zero, then take the rest + # re-reverse it back into the correct order and make it a tuple and use + # that for our sorting key. + release = tuple( + reversed(list( + itertools.dropwhile( + lambda x: x == 0, + reversed(release), + ) + )) + ) + + # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. + # We'll do this by abusing the pre segment, but we _only_ want to do this + # if there is not a pre or a post segment. If we have one of those then + # the normal sorting rules will handle this case correctly. + if pre is None and post is None and dev is not None: + pre = -Infinity + # Versions without a pre-release (except as noted above) should sort after + # those with one. + elif pre is None: + pre = Infinity + + # Versions without a post segment should sort before those with one. + if post is None: + post = -Infinity + + # Versions without a development segment should sort after those with one. + if dev is None: + dev = Infinity + + if local is None: + # Versions without a local segment should sort before those with one. + local = -Infinity + else: + # Versions with a local segment need that segment parsed to implement + # the sorting rules in PEP440. + # - Alpha numeric segments sort before numeric segments + # - Alpha numeric segments sort lexicographically + # - Numeric segments sort numerically + # - Shorter versions sort before longer versions when the prefixes + # match exactly + local = tuple( + (i, "") if isinstance(i, int) else (-Infinity, i) + for i in local + ) + + return epoch, release, pre, post, dev, local diff --git a/lib/setuptools/_vendor/pyparsing.py b/lib/setuptools/_vendor/pyparsing.py new file mode 100644 index 0000000..4aa30ee --- /dev/null +++ b/lib/setuptools/_vendor/pyparsing.py @@ -0,0 +1,5742 @@ +# module pyparsing.py +# +# Copyright (c) 2003-2018 Paul T. McGuire +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__doc__ = \ +""" +pyparsing module - Classes and methods to define and execute parsing grammars +============================================================================= + +The pyparsing module is an alternative approach to creating and executing simple grammars, +vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you +don't need to learn a new syntax for defining grammars or matching expressions - the parsing module +provides a library of classes that you use to construct the grammar directly in Python. + +Here is a program to parse "Hello, World!" (or any greeting of the form +C{"<salutation>, <addressee>!"}), built up using L{Word}, L{Literal}, and L{And} elements +(L{'+'<ParserElement.__add__>} operator gives L{And} expressions, strings are auto-converted to +L{Literal} expressions):: + + from pyparsing import Word, alphas + + # define grammar of a greeting + greet = Word(alphas) + "," + Word(alphas) + "!" + + hello = "Hello, World!" + print (hello, "->", greet.parseString(hello)) + +The program outputs the following:: + + Hello, World! -> ['Hello', ',', 'World', '!'] + +The Python representation of the grammar is quite readable, owing to the self-explanatory +class names, and the use of '+', '|' and '^' operators. + +The L{ParseResults} object returned from L{ParserElement.parseString<ParserElement.parseString>} can be accessed as a nested list, a dictionary, or an +object with named attributes. + +The pyparsing module handles some of the problems that are typically vexing when writing text parsers: + - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) + - quoted strings + - embedded comments + + +Getting Started - +----------------- +Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing +classes inherit from. Use the docstrings for examples of how to: + - construct literal match expressions from L{Literal} and L{CaselessLiteral} classes + - construct character word-group expressions using the L{Word} class + - see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes + - use L{'+'<And>}, L{'|'<MatchFirst>}, L{'^'<Or>}, and L{'&'<Each>} operators to combine simple expressions into more complex ones + - associate names with your parsed results using L{ParserElement.setResultsName} + - find some helpful expression short-cuts like L{delimitedList} and L{oneOf} + - find more useful common expressions in the L{pyparsing_common} namespace class +""" + +__version__ = "2.2.1" +__versionTime__ = "18 Sep 2018 00:49 UTC" +__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" + +import string +from weakref import ref as wkref +import copy +import sys +import warnings +import re +import sre_constants +import collections +import pprint +import traceback +import types +from datetime import datetime + +try: + from _thread import RLock +except ImportError: + from threading import RLock + +try: + # Python 3 + from collections.abc import Iterable + from collections.abc import MutableMapping +except ImportError: + # Python 2.7 + from collections import Iterable + from collections import MutableMapping + +try: + from collections import OrderedDict as _OrderedDict +except ImportError: + try: + from ordereddict import OrderedDict as _OrderedDict + except ImportError: + _OrderedDict = None + +#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) + +__all__ = [ +'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', +'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', +'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', +'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', +'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', +'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', +'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', +'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', +'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', +'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', +'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno', +'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', +'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', +'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', +'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', +'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', +'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass', +'CloseMatch', 'tokenMap', 'pyparsing_common', +] + +system_version = tuple(sys.version_info)[:3] +PY_3 = system_version[0] == 3 +if PY_3: + _MAX_INT = sys.maxsize + basestring = str + unichr = chr + _ustr = str + + # build list of single arg builtins, that can be used as parse actions + singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max] + +else: + _MAX_INT = sys.maxint + range = xrange + + def _ustr(obj): + """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries + str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It + then < returns the unicode object | encodes it with the default encoding | ... >. + """ + if isinstance(obj,unicode): + return obj + + try: + # If this works, then _ustr(obj) has the same behaviour as str(obj), so + # it won't break any existing code. + return str(obj) + + except UnicodeEncodeError: + # Else encode it + ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace') + xmlcharref = Regex(r'&#\d+;') + xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:]) + return xmlcharref.transformString(ret) + + # build list of single arg builtins, tolerant of Python version, that can be used as parse actions + singleArgBuiltins = [] + import __builtin__ + for fname in "sum len sorted reversed list tuple set any all min max".split(): + try: + singleArgBuiltins.append(getattr(__builtin__,fname)) + except AttributeError: + continue + +_generatorType = type((y for y in range(1))) + +def _xml_escape(data): + """Escape &, <, >, ", ', etc. in a string of data.""" + + # ampersand must be replaced first + from_symbols = '&><"\'' + to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split()) + for from_,to_ in zip(from_symbols, to_symbols): + data = data.replace(from_, to_) + return data + +class _Constants(object): + pass + +alphas = string.ascii_uppercase + string.ascii_lowercase +nums = "0123456789" +hexnums = nums + "ABCDEFabcdef" +alphanums = alphas + nums +_bslash = chr(92) +printables = "".join(c for c in string.printable if c not in string.whitespace) + +class ParseBaseException(Exception): + """base exception class for all parsing runtime exceptions""" + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, pstr, loc=0, msg=None, elem=None ): + self.loc = loc + if msg is None: + self.msg = pstr + self.pstr = "" + else: + self.msg = msg + self.pstr = pstr + self.parserElement = elem + self.args = (pstr, loc, msg) + + @classmethod + def _from_exception(cls, pe): + """ + internal factory method to simplify creating one type of ParseException + from another - avoids having __init__ signature conflicts among subclasses + """ + return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement) + + def __getattr__( self, aname ): + """supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + """ + if( aname == "lineno" ): + return lineno( self.loc, self.pstr ) + elif( aname in ("col", "column") ): + return col( self.loc, self.pstr ) + elif( aname == "line" ): + return line( self.loc, self.pstr ) + else: + raise AttributeError(aname) + + def __str__( self ): + return "%s (at char %d), (line:%d, col:%d)" % \ + ( self.msg, self.loc, self.lineno, self.column ) + def __repr__( self ): + return _ustr(self) + def markInputline( self, markerString = ">!<" ): + """Extracts the exception line from the input string, and marks + the location of the exception with a special symbol. + """ + line_str = self.line + line_column = self.column - 1 + if markerString: + line_str = "".join((line_str[:line_column], + markerString, line_str[line_column:])) + return line_str.strip() + def __dir__(self): + return "lineno col line".split() + dir(type(self)) + +class ParseException(ParseBaseException): + """ + Exception thrown when parse expressions don't match class; + supported attributes by name are: + - lineno - returns the line number of the exception text + - col - returns the column number of the exception text + - line - returns the line containing the exception text + + Example:: + try: + Word(nums).setName("integer").parseString("ABC") + except ParseException as pe: + print(pe) + print("column: {}".format(pe.col)) + + prints:: + Expected integer (at char 0), (line:1, col:1) + column: 1 + """ + pass + +class ParseFatalException(ParseBaseException): + """user-throwable exception thrown when inconsistent parse content + is found; stops all parsing immediately""" + pass + +class ParseSyntaxException(ParseFatalException): + """just like L{ParseFatalException}, but thrown internally when an + L{ErrorStop<And._ErrorStop>} ('-' operator) indicates that parsing is to stop + immediately because an unbacktrackable syntax error has been found""" + pass + +#~ class ReparseException(ParseBaseException): + #~ """Experimental class - parse actions can raise this exception to cause + #~ pyparsing to reparse the input string: + #~ - with a modified input string, and/or + #~ - with a modified start location + #~ Set the values of the ReparseException in the constructor, and raise the + #~ exception in a parse action to cause pyparsing to use the new string/location. + #~ Setting the values as None causes no change to be made. + #~ """ + #~ def __init_( self, newstring, restartLoc ): + #~ self.newParseText = newstring + #~ self.reparseLoc = restartLoc + +class RecursiveGrammarException(Exception): + """exception thrown by L{ParserElement.validate} if the grammar could be improperly recursive""" + def __init__( self, parseElementList ): + self.parseElementTrace = parseElementList + + def __str__( self ): + return "RecursiveGrammarException: %s" % self.parseElementTrace + +class _ParseResultsWithOffset(object): + def __init__(self,p1,p2): + self.tup = (p1,p2) + def __getitem__(self,i): + return self.tup[i] + def __repr__(self): + return repr(self.tup[0]) + def setOffset(self,i): + self.tup = (self.tup[0],i) + +class ParseResults(object): + """ + Structured parse results, to provide multiple means of access to the parsed data: + - as a list (C{len(results)}) + - by list index (C{results[0], results[1]}, etc.) + - by attribute (C{results.<resultsName>} - see L{ParserElement.setResultsName}) + + Example:: + integer = Word(nums) + date_str = (integer.setResultsName("year") + '/' + + integer.setResultsName("month") + '/' + + integer.setResultsName("day")) + # equivalent form: + # date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + # parseString returns a ParseResults object + result = date_str.parseString("1999/12/31") + + def test(s, fn=repr): + print("%s -> %s" % (s, fn(eval(s)))) + test("list(result)") + test("result[0]") + test("result['month']") + test("result.day") + test("'month' in result") + test("'minutes' in result") + test("result.dump()", str) + prints:: + list(result) -> ['1999', '/', '12', '/', '31'] + result[0] -> '1999' + result['month'] -> '12' + result.day -> '31' + 'month' in result -> True + 'minutes' in result -> False + result.dump() -> ['1999', '/', '12', '/', '31'] + - day: 31 + - month: 12 + - year: 1999 + """ + def __new__(cls, toklist=None, name=None, asList=True, modal=True ): + if isinstance(toklist, cls): + return toklist + retobj = object.__new__(cls) + retobj.__doinit = True + return retobj + + # Performance tuning: we construct a *lot* of these, so keep this + # constructor as small and fast as possible + def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ): + if self.__doinit: + self.__doinit = False + self.__name = None + self.__parent = None + self.__accumNames = {} + self.__asList = asList + self.__modal = modal + if toklist is None: + toklist = [] + if isinstance(toklist, list): + self.__toklist = toklist[:] + elif isinstance(toklist, _generatorType): + self.__toklist = list(toklist) + else: + self.__toklist = [toklist] + self.__tokdict = dict() + + if name is not None and name: + if not modal: + self.__accumNames[name] = 0 + if isinstance(name,int): + name = _ustr(name) # will always return a str, but use _ustr for consistency + self.__name = name + if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])): + if isinstance(toklist,basestring): + toklist = [ toklist ] + if asList: + if isinstance(toklist,ParseResults): + self[name] = _ParseResultsWithOffset(toklist.copy(),0) + else: + self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) + self[name].__name = name + else: + try: + self[name] = toklist[0] + except (KeyError,TypeError,IndexError): + self[name] = toklist + + def __getitem__( self, i ): + if isinstance( i, (int,slice) ): + return self.__toklist[i] + else: + if i not in self.__accumNames: + return self.__tokdict[i][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[i] ]) + + def __setitem__( self, k, v, isinstance=isinstance ): + if isinstance(v,_ParseResultsWithOffset): + self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] + sub = v[0] + elif isinstance(k,(int,slice)): + self.__toklist[k] = v + sub = v + else: + self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] + sub = v + if isinstance(sub,ParseResults): + sub.__parent = wkref(self) + + def __delitem__( self, i ): + if isinstance(i,(int,slice)): + mylen = len( self.__toklist ) + del self.__toklist[i] + + # convert int to slice + if isinstance(i, int): + if i < 0: + i += mylen + i = slice(i, i+1) + # get removed indices + removed = list(range(*i.indices(mylen))) + removed.reverse() + # fixup indices in token dictionary + for name,occurrences in self.__tokdict.items(): + for j in removed: + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) + else: + del self.__tokdict[i] + + def __contains__( self, k ): + return k in self.__tokdict + + def __len__( self ): return len( self.__toklist ) + def __bool__(self): return ( not not self.__toklist ) + __nonzero__ = __bool__ + def __iter__( self ): return iter( self.__toklist ) + def __reversed__( self ): return iter( self.__toklist[::-1] ) + def _iterkeys( self ): + if hasattr(self.__tokdict, "iterkeys"): + return self.__tokdict.iterkeys() + else: + return iter(self.__tokdict) + + def _itervalues( self ): + return (self[k] for k in self._iterkeys()) + + def _iteritems( self ): + return ((k, self[k]) for k in self._iterkeys()) + + if PY_3: + keys = _iterkeys + """Returns an iterator of all named result keys (Python 3.x only).""" + + values = _itervalues + """Returns an iterator of all named result values (Python 3.x only).""" + + items = _iteritems + """Returns an iterator of all named result key-value tuples (Python 3.x only).""" + + else: + iterkeys = _iterkeys + """Returns an iterator of all named result keys (Python 2.x only).""" + + itervalues = _itervalues + """Returns an iterator of all named result values (Python 2.x only).""" + + iteritems = _iteritems + """Returns an iterator of all named result key-value tuples (Python 2.x only).""" + + def keys( self ): + """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x).""" + return list(self.iterkeys()) + + def values( self ): + """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x).""" + return list(self.itervalues()) + + def items( self ): + """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x).""" + return list(self.iteritems()) + + def haskeys( self ): + """Since keys() returns an iterator, this method is helpful in bypassing + code that looks for the existence of any defined results names.""" + return bool(self.__tokdict) + + def pop( self, *args, **kwargs): + """ + Removes and returns item at specified index (default=C{last}). + Supports both C{list} and C{dict} semantics for C{pop()}. If passed no + argument or an integer argument, it will use C{list} semantics + and pop tokens from the list of parsed tokens. If passed a + non-integer argument (most likely a string), it will use C{dict} + semantics and pop the corresponding value from any defined + results names. A second default return value argument is + supported, just as in C{dict.pop()}. + + Example:: + def remove_first(tokens): + tokens.pop(0) + print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] + print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321'] + + label = Word(alphas) + patt = label("LABEL") + OneOrMore(Word(nums)) + print(patt.parseString("AAB 123 321").dump()) + + # Use pop() in a parse action to remove named result (note that corresponding value is not + # removed from list form of results) + def remove_LABEL(tokens): + tokens.pop("LABEL") + return tokens + patt.addParseAction(remove_LABEL) + print(patt.parseString("AAB 123 321").dump()) + prints:: + ['AAB', '123', '321'] + - LABEL: AAB + + ['AAB', '123', '321'] + """ + if not args: + args = [-1] + for k,v in kwargs.items(): + if k == 'default': + args = (args[0], v) + else: + raise TypeError("pop() got an unexpected keyword argument '%s'" % k) + if (isinstance(args[0], int) or + len(args) == 1 or + args[0] in self): + index = args[0] + ret = self[index] + del self[index] + return ret + else: + defaultvalue = args[1] + return defaultvalue + + def get(self, key, defaultValue=None): + """ + Returns named result matching the given key, or if there is no + such name, then returns the given C{defaultValue} or C{None} if no + C{defaultValue} is specified. + + Similar to C{dict.get()}. + + Example:: + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parseString("1999/12/31") + print(result.get("year")) # -> '1999' + print(result.get("hour", "not specified")) # -> 'not specified' + print(result.get("hour")) # -> None + """ + if key in self: + return self[key] + else: + return defaultValue + + def insert( self, index, insStr ): + """ + Inserts new element at location index in the list of parsed tokens. + + Similar to C{list.insert()}. + + Example:: + print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] + + # use a parse action to insert the parse location in the front of the parsed results + def insert_locn(locn, tokens): + tokens.insert(0, locn) + print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321'] + """ + self.__toklist.insert(index, insStr) + # fixup indices in token dictionary + for name,occurrences in self.__tokdict.items(): + for k, (value, position) in enumerate(occurrences): + occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) + + def append( self, item ): + """ + Add single element to end of ParseResults list of elements. + + Example:: + print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] + + # use a parse action to compute the sum of the parsed integers, and add it to the end + def append_sum(tokens): + tokens.append(sum(map(int, tokens))) + print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444] + """ + self.__toklist.append(item) + + def extend( self, itemseq ): + """ + Add sequence of elements to end of ParseResults list of elements. + + Example:: + patt = OneOrMore(Word(alphas)) + + # use a parse action to append the reverse of the matched strings, to make a palindrome + def make_palindrome(tokens): + tokens.extend(reversed([t[::-1] for t in tokens])) + return ''.join(tokens) + print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' + """ + if isinstance(itemseq, ParseResults): + self += itemseq + else: + self.__toklist.extend(itemseq) + + def clear( self ): + """ + Clear all elements and results names. + """ + del self.__toklist[:] + self.__tokdict.clear() + + def __getattr__( self, name ): + try: + return self[name] + except KeyError: + return "" + + if name in self.__tokdict: + if name not in self.__accumNames: + return self.__tokdict[name][-1][0] + else: + return ParseResults([ v[0] for v in self.__tokdict[name] ]) + else: + return "" + + def __add__( self, other ): + ret = self.copy() + ret += other + return ret + + def __iadd__( self, other ): + if other.__tokdict: + offset = len(self.__toklist) + addoffset = lambda a: offset if a<0 else a+offset + otheritems = other.__tokdict.items() + otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) + for (k,vlist) in otheritems for v in vlist] + for k,v in otherdictitems: + self[k] = v + if isinstance(v[0],ParseResults): + v[0].__parent = wkref(self) + + self.__toklist += other.__toklist + self.__accumNames.update( other.__accumNames ) + return self + + def __radd__(self, other): + if isinstance(other,int) and other == 0: + # useful for merging many ParseResults using sum() builtin + return self.copy() + else: + # this may raise a TypeError - so be it + return other + self + + def __repr__( self ): + return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) + + def __str__( self ): + return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']' + + def _asStringList( self, sep='' ): + out = [] + for item in self.__toklist: + if out and sep: + out.append(sep) + if isinstance( item, ParseResults ): + out += item._asStringList() + else: + out.append( _ustr(item) ) + return out + + def asList( self ): + """ + Returns the parse results as a nested list of matching tokens, all converted to strings. + + Example:: + patt = OneOrMore(Word(alphas)) + result = patt.parseString("sldkj lsdkj sldkj") + # even though the result prints in string-like form, it is actually a pyparsing ParseResults + print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj'] + + # Use asList() to create an actual list + result_list = result.asList() + print(type(result_list), result_list) # -> <class 'list'> ['sldkj', 'lsdkj', 'sldkj'] + """ + return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist] + + def asDict( self ): + """ + Returns the named parse results as a nested dictionary. + + Example:: + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parseString('12/31/1999') + print(type(result), repr(result)) # -> <class 'pyparsing.ParseResults'> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]}) + + result_dict = result.asDict() + print(type(result_dict), repr(result_dict)) # -> <class 'dict'> {'day': '1999', 'year': '12', 'month': '31'} + + # even though a ParseResults supports dict-like access, sometime you just need to have a dict + import json + print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable + print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"} + """ + if PY_3: + item_fn = self.items + else: + item_fn = self.iteritems + + def toItem(obj): + if isinstance(obj, ParseResults): + if obj.haskeys(): + return obj.asDict() + else: + return [toItem(v) for v in obj] + else: + return obj + + return dict((k,toItem(v)) for k,v in item_fn()) + + def copy( self ): + """ + Returns a new copy of a C{ParseResults} object. + """ + ret = ParseResults( self.__toklist ) + ret.__tokdict = self.__tokdict.copy() + ret.__parent = self.__parent + ret.__accumNames.update( self.__accumNames ) + ret.__name = self.__name + return ret + + def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): + """ + (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names. + """ + nl = "\n" + out = [] + namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items() + for v in vlist) + nextLevelIndent = indent + " " + + # collapse out indents if formatting is not desired + if not formatted: + indent = "" + nextLevelIndent = "" + nl = "" + + selfTag = None + if doctag is not None: + selfTag = doctag + else: + if self.__name: + selfTag = self.__name + + if not selfTag: + if namedItemsOnly: + return "" + else: + selfTag = "ITEM" + + out += [ nl, indent, "<", selfTag, ">" ] + + for i,res in enumerate(self.__toklist): + if isinstance(res,ParseResults): + if i in namedItems: + out += [ res.asXML(namedItems[i], + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + out += [ res.asXML(None, + namedItemsOnly and doctag is None, + nextLevelIndent, + formatted)] + else: + # individual token, see if there is a name for it + resTag = None + if i in namedItems: + resTag = namedItems[i] + if not resTag: + if namedItemsOnly: + continue + else: + resTag = "ITEM" + xmlBodyText = _xml_escape(_ustr(res)) + out += [ nl, nextLevelIndent, "<", resTag, ">", + xmlBodyText, + "</", resTag, ">" ] + + out += [ nl, indent, "</", selfTag, ">" ] + return "".join(out) + + def __lookup(self,sub): + for k,vlist in self.__tokdict.items(): + for v,loc in vlist: + if sub is v: + return k + return None + + def getName(self): + r""" + Returns the results name for this token expression. Useful when several + different expressions might match at a particular location. + + Example:: + integer = Word(nums) + ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") + house_number_expr = Suppress('#') + Word(nums, alphanums) + user_data = (Group(house_number_expr)("house_number") + | Group(ssn_expr)("ssn") + | Group(integer)("age")) + user_info = OneOrMore(user_data) + + result = user_info.parseString("22 111-22-3333 #221B") + for item in result: + print(item.getName(), ':', item[0]) + prints:: + age : 22 + ssn : 111-22-3333 + house_number : 221B + """ + if self.__name: + return self.__name + elif self.__parent: + par = self.__parent() + if par: + return par.__lookup(self) + else: + return None + elif (len(self) == 1 and + len(self.__tokdict) == 1 and + next(iter(self.__tokdict.values()))[0][1] in (0,-1)): + return next(iter(self.__tokdict.keys())) + else: + return None + + def dump(self, indent='', depth=0, full=True): + """ + Diagnostic method for listing out the contents of a C{ParseResults}. + Accepts an optional C{indent} argument so that this string can be embedded + in a nested display of other data. + + Example:: + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + result = date_str.parseString('12/31/1999') + print(result.dump()) + prints:: + ['12', '/', '31', '/', '1999'] + - day: 1999 + - month: 31 + - year: 12 + """ + out = [] + NL = '\n' + out.append( indent+_ustr(self.asList()) ) + if full: + if self.haskeys(): + items = sorted((str(k), v) for k,v in self.items()) + for k,v in items: + if out: + out.append(NL) + out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) + if isinstance(v,ParseResults): + if v: + out.append( v.dump(indent,depth+1) ) + else: + out.append(_ustr(v)) + else: + out.append(repr(v)) + elif any(isinstance(vv,ParseResults) for vv in self): + v = self + for i,vv in enumerate(v): + if isinstance(vv,ParseResults): + out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),vv.dump(indent,depth+1) )) + else: + out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),_ustr(vv))) + + return "".join(out) + + def pprint(self, *args, **kwargs): + """ + Pretty-printer for parsed results as a list, using the C{pprint} module. + Accepts additional positional or keyword args as defined for the + C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint}) + + Example:: + ident = Word(alphas, alphanums) + num = Word(nums) + func = Forward() + term = ident | num | Group('(' + func + ')') + func <<= ident + Group(Optional(delimitedList(term))) + result = func.parseString("fna a,b,(fnb c,d,200),100") + result.pprint(width=40) + prints:: + ['fna', + ['a', + 'b', + ['(', 'fnb', ['c', 'd', '200'], ')'], + '100']] + """ + pprint.pprint(self.asList(), *args, **kwargs) + + # add support for pickle protocol + def __getstate__(self): + return ( self.__toklist, + ( self.__tokdict.copy(), + self.__parent is not None and self.__parent() or None, + self.__accumNames, + self.__name ) ) + + def __setstate__(self,state): + self.__toklist = state[0] + (self.__tokdict, + par, + inAccumNames, + self.__name) = state[1] + self.__accumNames = {} + self.__accumNames.update(inAccumNames) + if par is not None: + self.__parent = wkref(par) + else: + self.__parent = None + + def __getnewargs__(self): + return self.__toklist, self.__name, self.__asList, self.__modal + + def __dir__(self): + return (dir(type(self)) + list(self.keys())) + +MutableMapping.register(ParseResults) + +def col (loc,strg): + """Returns current column within a string, counting newlines as line separators. + The first column is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information + on parsing strings containing C{<TAB>}s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + s = strg + return 1 if 0<loc<len(s) and s[loc-1] == '\n' else loc - s.rfind("\n", 0, loc) + +def lineno(loc,strg): + """Returns current line number within a string, counting newlines as line separators. + The first line is number 1. + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information + on parsing strings containing C{<TAB>}s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + """ + return strg.count("\n",0,loc) + 1 + +def line( loc, strg ): + """Returns the line of text containing loc within a string, counting newlines as line separators. + """ + lastCR = strg.rfind("\n", 0, loc) + nextCR = strg.find("\n", loc) + if nextCR >= 0: + return strg[lastCR+1:nextCR] + else: + return strg[lastCR+1:] + +def _defaultStartDebugAction( instring, loc, expr ): + print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))) + +def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): + print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) + +def _defaultExceptionDebugAction( instring, loc, expr, exc ): + print ("Exception raised:" + _ustr(exc)) + +def nullDebugAction(*args): + """'Do-nothing' debug action, to suppress debugging output during parsing.""" + pass + +# Only works on Python 3.x - nonlocal is toxic to Python 2 installs +#~ 'decorator to trim function calls to match the arity of the target' +#~ def _trim_arity(func, maxargs=3): + #~ if func in singleArgBuiltins: + #~ return lambda s,l,t: func(t) + #~ limit = 0 + #~ foundArity = False + #~ def wrapper(*args): + #~ nonlocal limit,foundArity + #~ while 1: + #~ try: + #~ ret = func(*args[limit:]) + #~ foundArity = True + #~ return ret + #~ except TypeError: + #~ if limit == maxargs or foundArity: + #~ raise + #~ limit += 1 + #~ continue + #~ return wrapper + +# this version is Python 2.x-3.x cross-compatible +'decorator to trim function calls to match the arity of the target' +def _trim_arity(func, maxargs=2): + if func in singleArgBuiltins: + return lambda s,l,t: func(t) + limit = [0] + foundArity = [False] + + # traceback return data structure changed in Py3.5 - normalize back to plain tuples + if system_version[:2] >= (3,5): + def extract_stack(limit=0): + # special handling for Python 3.5.0 - extra deep call stack by 1 + offset = -3 if system_version == (3,5,0) else -2 + frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset] + return [frame_summary[:2]] + def extract_tb(tb, limit=0): + frames = traceback.extract_tb(tb, limit=limit) + frame_summary = frames[-1] + return [frame_summary[:2]] + else: + extract_stack = traceback.extract_stack + extract_tb = traceback.extract_tb + + # synthesize what would be returned by traceback.extract_stack at the call to + # user's parse action 'func', so that we don't incur call penalty at parse time + + LINE_DIFF = 6 + # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND + # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!! + this_line = extract_stack(limit=2)[-1] + pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF) + + def wrapper(*args): + while 1: + try: + ret = func(*args[limit[0]:]) + foundArity[0] = True + return ret + except TypeError: + # re-raise TypeErrors if they did not come from our arity testing + if foundArity[0]: + raise + else: + try: + tb = sys.exc_info()[-1] + if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth: + raise + finally: + del tb + + if limit[0] <= maxargs: + limit[0] += 1 + continue + raise + + # copy func name to wrapper for sensible debug output + func_name = "<parse action>" + try: + func_name = getattr(func, '__name__', + getattr(func, '__class__').__name__) + except Exception: + func_name = str(func) + wrapper.__name__ = func_name + + return wrapper + +class ParserElement(object): + """Abstract base level parser element class.""" + DEFAULT_WHITE_CHARS = " \n\t\r" + verbose_stacktrace = False + + @staticmethod + def setDefaultWhitespaceChars( chars ): + r""" + Overrides the default whitespace chars + + Example:: + # default whitespace chars are space, <TAB> and newline + OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl'] + + # change to just treat newline as significant + ParserElement.setDefaultWhitespaceChars(" \t") + OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def'] + """ + ParserElement.DEFAULT_WHITE_CHARS = chars + + @staticmethod + def inlineLiteralsUsing(cls): + """ + Set class to be used for inclusion of string literals into a parser. + + Example:: + # default literal class used is Literal + integer = Word(nums) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31'] + + + # change to Suppress + ParserElement.inlineLiteralsUsing(Suppress) + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + + date_str.parseString("1999/12/31") # -> ['1999', '12', '31'] + """ + ParserElement._literalStringClass = cls + + def __init__( self, savelist=False ): + self.parseAction = list() + self.failAction = None + #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall + self.strRepr = None + self.resultsName = None + self.saveAsList = savelist + self.skipWhitespace = True + self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + self.copyDefaultWhiteChars = True + self.mayReturnEmpty = False # used when checking for left-recursion + self.keepTabs = False + self.ignoreExprs = list() + self.debug = False + self.streamlined = False + self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index + self.errmsg = "" + self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) + self.debugActions = ( None, None, None ) #custom debug actions + self.re = None + self.callPreparse = True # used to avoid redundant calls to preParse + self.callDuringTry = False + + def copy( self ): + """ + Make a copy of this C{ParserElement}. Useful for defining different parse actions + for the same parsing pattern, using copies of the original parse element. + + Example:: + integer = Word(nums).setParseAction(lambda toks: int(toks[0])) + integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K") + integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") + + print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M")) + prints:: + [5120, 100, 655360, 268435456] + Equivalent form of C{expr.copy()} is just C{expr()}:: + integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") + """ + cpy = copy.copy( self ) + cpy.parseAction = self.parseAction[:] + cpy.ignoreExprs = self.ignoreExprs[:] + if self.copyDefaultWhiteChars: + cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS + return cpy + + def setName( self, name ): + """ + Define name for this expression, makes debugging and exception messages clearer. + + Example:: + Word(nums).parseString("ABC") # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1) + Word(nums).setName("integer").parseString("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1) + """ + self.name = name + self.errmsg = "Expected " + self.name + if hasattr(self,"exception"): + self.exception.msg = self.errmsg + return self + + def setResultsName( self, name, listAllMatches=False ): + """ + Define name for referencing matching tokens as a nested attribute + of the returned parse results. + NOTE: this returns a *copy* of the original C{ParserElement} object; + this is so that the client can define a basic element, such as an + integer, and reference it in multiple places with different names. + + You can also set results names using the abbreviated syntax, + C{expr("name")} in place of C{expr.setResultsName("name")} - + see L{I{__call__}<__call__>}. + + Example:: + date_str = (integer.setResultsName("year") + '/' + + integer.setResultsName("month") + '/' + + integer.setResultsName("day")) + + # equivalent form: + date_str = integer("year") + '/' + integer("month") + '/' + integer("day") + """ + newself = self.copy() + if name.endswith("*"): + name = name[:-1] + listAllMatches=True + newself.resultsName = name + newself.modalResults = not listAllMatches + return newself + + def setBreak(self,breakFlag = True): + """Method to invoke the Python pdb debugger when this element is + about to be parsed. Set C{breakFlag} to True to enable, False to + disable. + """ + if breakFlag: + _parseMethod = self._parse + def breaker(instring, loc, doActions=True, callPreParse=True): + import pdb + pdb.set_trace() + return _parseMethod( instring, loc, doActions, callPreParse ) + breaker._originalParseMethod = _parseMethod + self._parse = breaker + else: + if hasattr(self._parse,"_originalParseMethod"): + self._parse = self._parse._originalParseMethod + return self + + def setParseAction( self, *fns, **kwargs ): + """ + Define one or more actions to perform when successfully matching parse element definition. + Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)}, + C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where: + - s = the original string being parsed (see note below) + - loc = the location of the matching substring + - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object + If the functions in fns modify the tokens, they can return them as the return + value from fn, and the modified list of tokens will replace the original. + Otherwise, fn does not need to return any value. + + Optional keyword arguments: + - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing + + Note: the default parsing behavior is to expand tabs in the input string + before starting the parsing process. See L{I{parseString}<parseString>} for more information + on parsing strings containing C{<TAB>}s, and suggested methods to maintain a + consistent view of the parsed string, the parse location, and line and column + positions within the parsed string. + + Example:: + integer = Word(nums) + date_str = integer + '/' + integer + '/' + integer + + date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31'] + + # use parse action to convert to ints at parse time + integer = Word(nums).setParseAction(lambda toks: int(toks[0])) + date_str = integer + '/' + integer + '/' + integer + + # note that integer fields are now ints, not strings + date_str.parseString("1999/12/31") # -> [1999, '/', 12, '/', 31] + """ + self.parseAction = list(map(_trim_arity, list(fns))) + self.callDuringTry = kwargs.get("callDuringTry", False) + return self + + def addParseAction( self, *fns, **kwargs ): + """ + Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}<setParseAction>}. + + See examples in L{I{copy}<copy>}. + """ + self.parseAction += list(map(_trim_arity, list(fns))) + self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) + return self + + def addCondition(self, *fns, **kwargs): + """Add a boolean predicate function to expression's list of parse actions. See + L{I{setParseAction}<setParseAction>} for function call signatures. Unlike C{setParseAction}, + functions passed to C{addCondition} need to return boolean success/fail of the condition. + + Optional keyword arguments: + - message = define a custom message to be used in the raised exception + - fatal = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException + + Example:: + integer = Word(nums).setParseAction(lambda toks: int(toks[0])) + year_int = integer.copy() + year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later") + date_str = year_int + '/' + integer + '/' + integer + + result = date_str.parseString("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1) + """ + msg = kwargs.get("message", "failed user-defined condition") + exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException + for fn in fns: + def pa(s,l,t): + if not bool(_trim_arity(fn)(s,l,t)): + raise exc_type(s,l,msg) + self.parseAction.append(pa) + self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) + return self + + def setFailAction( self, fn ): + """Define action to perform if parsing fails at this expression. + Fail acton fn is a callable function that takes the arguments + C{fn(s,loc,expr,err)} where: + - s = string being parsed + - loc = location where expression match was attempted and failed + - expr = the parse expression that failed + - err = the exception thrown + The function returns no value. It may throw C{L{ParseFatalException}} + if it is desired to stop parsing immediately.""" + self.failAction = fn + return self + + def _skipIgnorables( self, instring, loc ): + exprsFound = True + while exprsFound: + exprsFound = False + for e in self.ignoreExprs: + try: + while 1: + loc,dummy = e._parse( instring, loc ) + exprsFound = True + except ParseException: + pass + return loc + + def preParse( self, instring, loc ): + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + + if self.skipWhitespace: + wt = self.whiteChars + instrlen = len(instring) + while loc < instrlen and instring[loc] in wt: + loc += 1 + + return loc + + def parseImpl( self, instring, loc, doActions=True ): + return loc, [] + + def postParse( self, instring, loc, tokenlist ): + return tokenlist + + #~ @profile + def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): + debugging = ( self.debug ) #and doActions ) + + if debugging or self.failAction: + #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) + if (self.debugActions[0] ): + self.debugActions[0]( instring, loc, self ) + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = preloc + try: + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + except ParseBaseException as err: + #~ print ("Exception raised:", err) + if self.debugActions[2]: + self.debugActions[2]( instring, tokensStart, self, err ) + if self.failAction: + self.failAction( instring, tokensStart, self, err ) + raise + else: + if callPreParse and self.callPreparse: + preloc = self.preParse( instring, loc ) + else: + preloc = loc + tokensStart = preloc + if self.mayIndexError or preloc >= len(instring): + try: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + except IndexError: + raise ParseException( instring, len(instring), self.errmsg, self ) + else: + loc,tokens = self.parseImpl( instring, preloc, doActions ) + + tokens = self.postParse( instring, loc, tokens ) + + retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) + if self.parseAction and (doActions or self.callDuringTry): + if debugging: + try: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + except ParseBaseException as err: + #~ print "Exception raised in user parse action:", err + if (self.debugActions[2] ): + self.debugActions[2]( instring, tokensStart, self, err ) + raise + else: + for fn in self.parseAction: + tokens = fn( instring, tokensStart, retTokens ) + if tokens is not None: + retTokens = ParseResults( tokens, + self.resultsName, + asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), + modal=self.modalResults ) + if debugging: + #~ print ("Matched",self,"->",retTokens.asList()) + if (self.debugActions[1] ): + self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) + + return loc, retTokens + + def tryParse( self, instring, loc ): + try: + return self._parse( instring, loc, doActions=False )[0] + except ParseFatalException: + raise ParseException( instring, loc, self.errmsg, self) + + def canParseNext(self, instring, loc): + try: + self.tryParse(instring, loc) + except (ParseException, IndexError): + return False + else: + return True + + class _UnboundedCache(object): + def __init__(self): + cache = {} + self.not_in_cache = not_in_cache = object() + + def get(self, key): + return cache.get(key, not_in_cache) + + def set(self, key, value): + cache[key] = value + + def clear(self): + cache.clear() + + def cache_len(self): + return len(cache) + + self.get = types.MethodType(get, self) + self.set = types.MethodType(set, self) + self.clear = types.MethodType(clear, self) + self.__len__ = types.MethodType(cache_len, self) + + if _OrderedDict is not None: + class _FifoCache(object): + def __init__(self, size): + self.not_in_cache = not_in_cache = object() + + cache = _OrderedDict() + + def get(self, key): + return cache.get(key, not_in_cache) + + def set(self, key, value): + cache[key] = value + while len(cache) > size: + try: + cache.popitem(False) + except KeyError: + pass + + def clear(self): + cache.clear() + + def cache_len(self): + return len(cache) + + self.get = types.MethodType(get, self) + self.set = types.MethodType(set, self) + self.clear = types.MethodType(clear, self) + self.__len__ = types.MethodType(cache_len, self) + + else: + class _FifoCache(object): + def __init__(self, size): + self.not_in_cache = not_in_cache = object() + + cache = {} + key_fifo = collections.deque([], size) + + def get(self, key): + return cache.get(key, not_in_cache) + + def set(self, key, value): + cache[key] = value + while len(key_fifo) > size: + cache.pop(key_fifo.popleft(), None) + key_fifo.append(key) + + def clear(self): + cache.clear() + key_fifo.clear() + + def cache_len(self): + return len(cache) + + self.get = types.MethodType(get, self) + self.set = types.MethodType(set, self) + self.clear = types.MethodType(clear, self) + self.__len__ = types.MethodType(cache_len, self) + + # argument cache for optimizing repeated calls when backtracking through recursive expressions + packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail + packrat_cache_lock = RLock() + packrat_cache_stats = [0, 0] + + # this method gets repeatedly called during backtracking with the same arguments - + # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression + def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): + HIT, MISS = 0, 1 + lookup = (self, instring, loc, callPreParse, doActions) + with ParserElement.packrat_cache_lock: + cache = ParserElement.packrat_cache + value = cache.get(lookup) + if value is cache.not_in_cache: + ParserElement.packrat_cache_stats[MISS] += 1 + try: + value = self._parseNoCache(instring, loc, doActions, callPreParse) + except ParseBaseException as pe: + # cache a copy of the exception, without the traceback + cache.set(lookup, pe.__class__(*pe.args)) + raise + else: + cache.set(lookup, (value[0], value[1].copy())) + return value + else: + ParserElement.packrat_cache_stats[HIT] += 1 + if isinstance(value, Exception): + raise value + return (value[0], value[1].copy()) + + _parse = _parseNoCache + + @staticmethod + def resetCache(): + ParserElement.packrat_cache.clear() + ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats) + + _packratEnabled = False + @staticmethod + def enablePackrat(cache_size_limit=128): + """Enables "packrat" parsing, which adds memoizing to the parsing logic. + Repeated parse attempts at the same string location (which happens + often in many complex grammars) can immediately return a cached value, + instead of re-executing parsing/validating code. Memoizing is done of + both valid results and parsing exceptions. + + Parameters: + - cache_size_limit - (default=C{128}) - if an integer value is provided + will limit the size of the packrat cache; if None is passed, then + the cache size will be unbounded; if 0 is passed, the cache will + be effectively disabled. + + This speedup may break existing programs that use parse actions that + have side-effects. For this reason, packrat parsing is disabled when + you first import pyparsing. To activate the packrat feature, your + program must call the class method C{ParserElement.enablePackrat()}. If + your program uses C{psyco} to "compile as you go", you must call + C{enablePackrat} before calling C{psyco.full()}. If you do not do this, + Python will crash. For best results, call C{enablePackrat()} immediately + after importing pyparsing. + + Example:: + import pyparsing + pyparsing.ParserElement.enablePackrat() + """ + if not ParserElement._packratEnabled: + ParserElement._packratEnabled = True + if cache_size_limit is None: + ParserElement.packrat_cache = ParserElement._UnboundedCache() + else: + ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit) + ParserElement._parse = ParserElement._parseCache + + def parseString( self, instring, parseAll=False ): + """ + Execute the parse expression with the given string. + This is the main interface to the client code, once the complete + expression has been built. + + If you want the grammar to require that the entire input string be + successfully parsed, then set C{parseAll} to True (equivalent to ending + the grammar with C{L{StringEnd()}}). + + Note: C{parseString} implicitly calls C{expandtabs()} on the input string, + in order to report proper column numbers in parse actions. + If the input string contains tabs and + the grammar uses parse actions that use the C{loc} argument to index into the + string being parsed, you can ensure you have a consistent view of the input + string by: + - calling C{parseWithTabs} on your grammar before calling C{parseString} + (see L{I{parseWithTabs}<parseWithTabs>}) + - define your parse action using the full C{(s,loc,toks)} signature, and + reference the input string using the parse action's C{s} argument + - explictly expand the tabs in your input string before calling + C{parseString} + + Example:: + Word('a').parseString('aaaaabaaa') # -> ['aaaaa'] + Word('a').parseString('aaaaabaaa', parseAll=True) # -> Exception: Expected end of text + """ + ParserElement.resetCache() + if not self.streamlined: + self.streamline() + #~ self.saveAsList = True + for e in self.ignoreExprs: + e.streamline() + if not self.keepTabs: + instring = instring.expandtabs() + try: + loc, tokens = self._parse( instring, 0 ) + if parseAll: + loc = self.preParse( instring, loc ) + se = Empty() + StringEnd() + se._parse( instring, loc ) + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc + else: + return tokens + + def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ): + """ + Scan the input string for expression matches. Each match will return the + matching tokens, start location, and end location. May be called with optional + C{maxMatches} argument, to clip scanning after 'n' matches are found. If + C{overlap} is specified, then overlapping matches will be reported. + + Note that the start and end locations are reported relative to the string + being parsed. See L{I{parseString}<parseString>} for more information on parsing + strings with embedded tabs. + + Example:: + source = "sldjf123lsdjjkf345sldkjf879lkjsfd987" + print(source) + for tokens,start,end in Word(alphas).scanString(source): + print(' '*start + '^'*(end-start)) + print(' '*start + tokens[0]) + + prints:: + + sldjf123lsdjjkf345sldkjf879lkjsfd987 + ^^^^^ + sldjf + ^^^^^^^ + lsdjjkf + ^^^^^^ + sldkjf + ^^^^^^ + lkjsfd + """ + if not self.streamlined: + self.streamline() + for e in self.ignoreExprs: + e.streamline() + + if not self.keepTabs: + instring = _ustr(instring).expandtabs() + instrlen = len(instring) + loc = 0 + preparseFn = self.preParse + parseFn = self._parse + ParserElement.resetCache() + matches = 0 + try: + while loc <= instrlen and matches < maxMatches: + try: + preloc = preparseFn( instring, loc ) + nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) + except ParseException: + loc = preloc+1 + else: + if nextLoc > loc: + matches += 1 + yield tokens, preloc, nextLoc + if overlap: + nextloc = preparseFn( instring, loc ) + if nextloc > loc: + loc = nextLoc + else: + loc += 1 + else: + loc = nextLoc + else: + loc = preloc+1 + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc + + def transformString( self, instring ): + """ + Extension to C{L{scanString}}, to modify matching text with modified tokens that may + be returned from a parse action. To use C{transformString}, define a grammar and + attach a parse action to it that modifies the returned token list. + Invoking C{transformString()} on a target string will then scan for matches, + and replace the matched text patterns according to the logic in the parse + action. C{transformString()} returns the resulting transformed string. + + Example:: + wd = Word(alphas) + wd.setParseAction(lambda toks: toks[0].title()) + + print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york.")) + Prints:: + Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York. + """ + out = [] + lastE = 0 + # force preservation of <TAB>s, to minimize unwanted transformation of string, and to + # keep string locs straight between transformString and scanString + self.keepTabs = True + try: + for t,s,e in self.scanString( instring ): + out.append( instring[lastE:s] ) + if t: + if isinstance(t,ParseResults): + out += t.asList() + elif isinstance(t,list): + out += t + else: + out.append(t) + lastE = e + out.append(instring[lastE:]) + out = [o for o in out if o] + return "".join(map(_ustr,_flatten(out))) + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc + + def searchString( self, instring, maxMatches=_MAX_INT ): + """ + Another extension to C{L{scanString}}, simplifying the access to the tokens found + to match the given parse expression. May be called with optional + C{maxMatches} argument, to clip searching after 'n' matches are found. + + Example:: + # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters + cap_word = Word(alphas.upper(), alphas.lower()) + + print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")) + + # the sum() builtin can be used to merge results into a single ParseResults object + print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))) + prints:: + [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']] + ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] + """ + try: + return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc + + def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False): + """ + Generator method to split a string using the given expression as a separator. + May be called with optional C{maxsplit} argument, to limit the number of splits; + and the optional C{includeSeparators} argument (default=C{False}), if the separating + matching text should be included in the split results. + + Example:: + punc = oneOf(list(".,;:/-!?")) + print(list(punc.split("This, this?, this sentence, is badly punctuated!"))) + prints:: + ['This', ' this', '', ' this sentence', ' is badly punctuated', ''] + """ + splits = 0 + last = 0 + for t,s,e in self.scanString(instring, maxMatches=maxsplit): + yield instring[last:s] + if includeSeparators: + yield t[0] + last = e + yield instring[last:] + + def __add__(self, other ): + """ + Implementation of + operator - returns C{L{And}}. Adding strings to a ParserElement + converts them to L{Literal}s by default. + + Example:: + greet = Word(alphas) + "," + Word(alphas) + "!" + hello = "Hello, World!" + print (hello, "->", greet.parseString(hello)) + Prints:: + Hello, World! -> ['Hello', ',', 'World', '!'] + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return And( [ self, other ] ) + + def __radd__(self, other ): + """ + Implementation of + operator when left operand is not a C{L{ParserElement}} + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other + self + + def __sub__(self, other): + """ + Implementation of - operator, returns C{L{And}} with error stop + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return self + And._ErrorStop() + other + + def __rsub__(self, other ): + """ + Implementation of - operator when left operand is not a C{L{ParserElement}} + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other - self + + def __mul__(self,other): + """ + Implementation of * operator, allows use of C{expr * 3} in place of + C{expr + expr + expr}. Expressions may also me multiplied by a 2-integer + tuple, similar to C{{min,max}} multipliers in regular expressions. Tuples + may also include C{None} as in: + - C{expr*(n,None)} or C{expr*(n,)} is equivalent + to C{expr*n + L{ZeroOrMore}(expr)} + (read as "at least n instances of C{expr}") + - C{expr*(None,n)} is equivalent to C{expr*(0,n)} + (read as "0 to n instances of C{expr}") + - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)} + - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)} + + Note that C{expr*(None,n)} does not raise an exception if + more than n exprs exist in the input stream; that is, + C{expr*(None,n)} does not enforce a maximum number of expr + occurrences. If this behavior is desired, then write + C{expr*(None,n) + ~expr} + """ + if isinstance(other,int): + minElements, optElements = other,0 + elif isinstance(other,tuple): + other = (other + (None, None))[:2] + if other[0] is None: + other = (0, other[1]) + if isinstance(other[0],int) and other[1] is None: + if other[0] == 0: + return ZeroOrMore(self) + if other[0] == 1: + return OneOrMore(self) + else: + return self*other[0] + ZeroOrMore(self) + elif isinstance(other[0],int) and isinstance(other[1],int): + minElements, optElements = other + optElements -= minElements + else: + raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) + else: + raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) + + if minElements < 0: + raise ValueError("cannot multiply ParserElement by negative value") + if optElements < 0: + raise ValueError("second tuple value must be greater or equal to first tuple value") + if minElements == optElements == 0: + raise ValueError("cannot multiply ParserElement by 0 or (0,0)") + + if (optElements): + def makeOptionalList(n): + if n>1: + return Optional(self + makeOptionalList(n-1)) + else: + return Optional(self) + if minElements: + if minElements == 1: + ret = self + makeOptionalList(optElements) + else: + ret = And([self]*minElements) + makeOptionalList(optElements) + else: + ret = makeOptionalList(optElements) + else: + if minElements == 1: + ret = self + else: + ret = And([self]*minElements) + return ret + + def __rmul__(self, other): + return self.__mul__(other) + + def __or__(self, other ): + """ + Implementation of | operator - returns C{L{MatchFirst}} + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return MatchFirst( [ self, other ] ) + + def __ror__(self, other ): + """ + Implementation of | operator when left operand is not a C{L{ParserElement}} + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other | self + + def __xor__(self, other ): + """ + Implementation of ^ operator - returns C{L{Or}} + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Or( [ self, other ] ) + + def __rxor__(self, other ): + """ + Implementation of ^ operator when left operand is not a C{L{ParserElement}} + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other ^ self + + def __and__(self, other ): + """ + Implementation of & operator - returns C{L{Each}} + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return Each( [ self, other ] ) + + def __rand__(self, other ): + """ + Implementation of & operator when left operand is not a C{L{ParserElement}} + """ + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + if not isinstance( other, ParserElement ): + warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), + SyntaxWarning, stacklevel=2) + return None + return other & self + + def __invert__( self ): + """ + Implementation of ~ operator - returns C{L{NotAny}} + """ + return NotAny( self ) + + def __call__(self, name=None): + """ + Shortcut for C{L{setResultsName}}, with C{listAllMatches=False}. + + If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be + passed as C{True}. + + If C{name} is omitted, same as calling C{L{copy}}. + + Example:: + # these are equivalent + userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") + userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") + """ + if name is not None: + return self.setResultsName(name) + else: + return self.copy() + + def suppress( self ): + """ + Suppresses the output of this C{ParserElement}; useful to keep punctuation from + cluttering up returned output. + """ + return Suppress( self ) + + def leaveWhitespace( self ): + """ + Disables the skipping of whitespace before matching the characters in the + C{ParserElement}'s defined pattern. This is normally only used internally by + the pyparsing module, but may be needed in some whitespace-sensitive grammars. + """ + self.skipWhitespace = False + return self + + def setWhitespaceChars( self, chars ): + """ + Overrides the default whitespace chars + """ + self.skipWhitespace = True + self.whiteChars = chars + self.copyDefaultWhiteChars = False + return self + + def parseWithTabs( self ): + """ + Overrides default behavior to expand C{<TAB>}s to spaces before parsing the input string. + Must be called before C{parseString} when the input grammar contains elements that + match C{<TAB>} characters. + """ + self.keepTabs = True + return self + + def ignore( self, other ): + """ + Define expression to be ignored (e.g., comments) while doing pattern + matching; may be called repeatedly, to define multiple comment or other + ignorable patterns. + + Example:: + patt = OneOrMore(Word(alphas)) + patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj'] + + patt.ignore(cStyleComment) + patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd'] + """ + if isinstance(other, basestring): + other = Suppress(other) + + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + self.ignoreExprs.append(other) + else: + self.ignoreExprs.append( Suppress( other.copy() ) ) + return self + + def setDebugActions( self, startAction, successAction, exceptionAction ): + """ + Enable display of debugging messages while doing pattern matching. + """ + self.debugActions = (startAction or _defaultStartDebugAction, + successAction or _defaultSuccessDebugAction, + exceptionAction or _defaultExceptionDebugAction) + self.debug = True + return self + + def setDebug( self, flag=True ): + """ + Enable display of debugging messages while doing pattern matching. + Set C{flag} to True to enable, False to disable. + + Example:: + wd = Word(alphas).setName("alphaword") + integer = Word(nums).setName("numword") + term = wd | integer + + # turn on debugging for wd + wd.setDebug() + + OneOrMore(term).parseString("abc 123 xyz 890") + + prints:: + Match alphaword at loc 0(1,1) + Matched alphaword -> ['abc'] + Match alphaword at loc 3(1,4) + Exception raised:Expected alphaword (at char 4), (line:1, col:5) + Match alphaword at loc 7(1,8) + Matched alphaword -> ['xyz'] + Match alphaword at loc 11(1,12) + Exception raised:Expected alphaword (at char 12), (line:1, col:13) + Match alphaword at loc 15(1,16) + Exception raised:Expected alphaword (at char 15), (line:1, col:16) + + The output shown is that produced by the default debug actions - custom debug actions can be + specified using L{setDebugActions}. Prior to attempting + to match the C{wd} expression, the debugging message C{"Match <exprname> at loc <n>(<line>,<col>)"} + is shown. Then if the parse succeeds, a C{"Matched"} message is shown, or an C{"Exception raised"} + message is shown. Also note the use of L{setName} to assign a human-readable name to the expression, + which makes debugging and exception messages easier to understand - for instance, the default + name created for the C{Word} expression without calling C{setName} is C{"W:(ABCD...)"}. + """ + if flag: + self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) + else: + self.debug = False + return self + + def __str__( self ): + return self.name + + def __repr__( self ): + return _ustr(self) + + def streamline( self ): + self.streamlined = True + self.strRepr = None + return self + + def checkRecursion( self, parseElementList ): + pass + + def validate( self, validateTrace=[] ): + """ + Check defined expressions for valid structure, check for infinite recursive definitions. + """ + self.checkRecursion( [] ) + + def parseFile( self, file_or_filename, parseAll=False ): + """ + Execute the parse expression on the given file or filename. + If a filename is specified (instead of a file object), + the entire file is opened, read, and closed before parsing. + """ + try: + file_contents = file_or_filename.read() + except AttributeError: + with open(file_or_filename, "r") as f: + file_contents = f.read() + try: + return self.parseString(file_contents, parseAll) + except ParseBaseException as exc: + if ParserElement.verbose_stacktrace: + raise + else: + # catch and re-raise exception from here, clears out pyparsing internal stack trace + raise exc + + def __eq__(self,other): + if isinstance(other, ParserElement): + return self is other or vars(self) == vars(other) + elif isinstance(other, basestring): + return self.matches(other) + else: + return super(ParserElement,self)==other + + def __ne__(self,other): + return not (self == other) + + def __hash__(self): + return hash(id(self)) + + def __req__(self,other): + return self == other + + def __rne__(self,other): + return not (self == other) + + def matches(self, testString, parseAll=True): + """ + Method for quick testing of a parser against a test string. Good for simple + inline microtests of sub expressions while building up larger parser. + + Parameters: + - testString - to test against this expression for a match + - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests + + Example:: + expr = Word(nums) + assert expr.matches("100") + """ + try: + self.parseString(_ustr(testString), parseAll=parseAll) + return True + except ParseBaseException: + return False + + def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False): + """ + Execute the parse expression on a series of test strings, showing each + test, the parsed results or where the parse failed. Quick and easy way to + run a parse expression against a list of sample strings. + + Parameters: + - tests - a list of separate test strings, or a multiline string of test strings + - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests + - comment - (default=C{'#'}) - expression for indicating embedded comments in the test + string; pass None to disable comment filtering + - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline; + if False, only dump nested list + - printResults - (default=C{True}) prints test output to stdout + - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing + + Returns: a (success, results) tuple, where success indicates that all tests succeeded + (or failed if C{failureTests} is True), and the results contain a list of lines of each + test's output + + Example:: + number_expr = pyparsing_common.number.copy() + + result = number_expr.runTests(''' + # unsigned integer + 100 + # negative integer + -100 + # float with scientific notation + 6.02e23 + # integer with scientific notation + 1e-12 + ''') + print("Success" if result[0] else "Failed!") + + result = number_expr.runTests(''' + # stray character + 100Z + # missing leading digit before '.' + -.100 + # too many '.' + 3.14.159 + ''', failureTests=True) + print("Success" if result[0] else "Failed!") + prints:: + # unsigned integer + 100 + [100] + + # negative integer + -100 + [-100] + + # float with scientific notation + 6.02e23 + [6.02e+23] + + # integer with scientific notation + 1e-12 + [1e-12] + + Success + + # stray character + 100Z + ^ + FAIL: Expected end of text (at char 3), (line:1, col:4) + + # missing leading digit before '.' + -.100 + ^ + FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1) + + # too many '.' + 3.14.159 + ^ + FAIL: Expected end of text (at char 4), (line:1, col:5) + + Success + + Each test string must be on a single line. If you want to test a string that spans multiple + lines, create a test like this:: + + expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines") + + (Note that this is a raw string literal, you must include the leading 'r'.) + """ + if isinstance(tests, basestring): + tests = list(map(str.strip, tests.rstrip().splitlines())) + if isinstance(comment, basestring): + comment = Literal(comment) + allResults = [] + comments = [] + success = True + for t in tests: + if comment is not None and comment.matches(t, False) or comments and not t: + comments.append(t) + continue + if not t: + continue + out = ['\n'.join(comments), t] + comments = [] + try: + t = t.replace(r'\n','\n') + result = self.parseString(t, parseAll=parseAll) + out.append(result.dump(full=fullDump)) + success = success and not failureTests + except ParseBaseException as pe: + fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else "" + if '\n' in t: + out.append(line(pe.loc, t)) + out.append(' '*(col(pe.loc,t)-1) + '^' + fatal) + else: + out.append(' '*pe.loc + '^' + fatal) + out.append("FAIL: " + str(pe)) + success = success and failureTests + result = pe + except Exception as exc: + out.append("FAIL-EXCEPTION: " + str(exc)) + success = success and failureTests + result = exc + + if printResults: + if fullDump: + out.append('') + print('\n'.join(out)) + + allResults.append((t, result)) + + return success, allResults + + +class Token(ParserElement): + """ + Abstract C{ParserElement} subclass, for defining atomic matching patterns. + """ + def __init__( self ): + super(Token,self).__init__( savelist=False ) + + +class Empty(Token): + """ + An empty token, will always match. + """ + def __init__( self ): + super(Empty,self).__init__() + self.name = "Empty" + self.mayReturnEmpty = True + self.mayIndexError = False + + +class NoMatch(Token): + """ + A token that will never match. + """ + def __init__( self ): + super(NoMatch,self).__init__() + self.name = "NoMatch" + self.mayReturnEmpty = True + self.mayIndexError = False + self.errmsg = "Unmatchable token" + + def parseImpl( self, instring, loc, doActions=True ): + raise ParseException(instring, loc, self.errmsg, self) + + +class Literal(Token): + """ + Token to exactly match a specified string. + + Example:: + Literal('blah').parseString('blah') # -> ['blah'] + Literal('blah').parseString('blahfooblah') # -> ['blah'] + Literal('blah').parseString('bla') # -> Exception: Expected "blah" + + For case-insensitive matching, use L{CaselessLiteral}. + + For keyword matching (force word break before and after the matched string), + use L{Keyword} or L{CaselessKeyword}. + """ + def __init__( self, matchString ): + super(Literal,self).__init__() + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Literal; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.__class__ = Empty + self.name = '"%s"' % _ustr(self.match) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + self.mayIndexError = False + + # Performance tuning: this routine gets called a *lot* + # if this is a single character match string and the first character matches, + # short-circuit as quickly as possible, and avoid calling startswith + #~ @profile + def parseImpl( self, instring, loc, doActions=True ): + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) ): + return loc+self.matchLen, self.match + raise ParseException(instring, loc, self.errmsg, self) +_L = Literal +ParserElement._literalStringClass = Literal + +class Keyword(Token): + """ + Token to exactly match a specified string as a keyword, that is, it must be + immediately followed by a non-keyword character. Compare with C{L{Literal}}: + - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}. + - C{Keyword("if")} will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'} + Accepts two optional constructor arguments in addition to the keyword string: + - C{identChars} is a string of characters that would be valid identifier characters, + defaulting to all alphanumerics + "_" and "$" + - C{caseless} allows case-insensitive matching, default is C{False}. + + Example:: + Keyword("start").parseString("start") # -> ['start'] + Keyword("start").parseString("starting") # -> Exception + + For case-insensitive matching, use L{CaselessKeyword}. + """ + DEFAULT_KEYWORD_CHARS = alphanums+"_$" + + def __init__( self, matchString, identChars=None, caseless=False ): + super(Keyword,self).__init__() + if identChars is None: + identChars = Keyword.DEFAULT_KEYWORD_CHARS + self.match = matchString + self.matchLen = len(matchString) + try: + self.firstMatchChar = matchString[0] + except IndexError: + warnings.warn("null string passed to Keyword; use Empty() instead", + SyntaxWarning, stacklevel=2) + self.name = '"%s"' % self.match + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = False + self.mayIndexError = False + self.caseless = caseless + if caseless: + self.caselessmatch = matchString.upper() + identChars = identChars.upper() + self.identChars = set(identChars) + + def parseImpl( self, instring, loc, doActions=True ): + if self.caseless: + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and + (loc == 0 or instring[loc-1].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + else: + if (instring[loc] == self.firstMatchChar and + (self.matchLen==1 or instring.startswith(self.match,loc)) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and + (loc == 0 or instring[loc-1] not in self.identChars) ): + return loc+self.matchLen, self.match + raise ParseException(instring, loc, self.errmsg, self) + + def copy(self): + c = super(Keyword,self).copy() + c.identChars = Keyword.DEFAULT_KEYWORD_CHARS + return c + + @staticmethod + def setDefaultKeywordChars( chars ): + """Overrides the default Keyword chars + """ + Keyword.DEFAULT_KEYWORD_CHARS = chars + +class CaselessLiteral(Literal): + """ + Token to match a specified string, ignoring case of letters. + Note: the matched results will always be in the case of the given + match string, NOT the case of the input text. + + Example:: + OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD'] + + (Contrast with example for L{CaselessKeyword}.) + """ + def __init__( self, matchString ): + super(CaselessLiteral,self).__init__( matchString.upper() ) + # Preserve the defining literal. + self.returnString = matchString + self.name = "'%s'" % self.returnString + self.errmsg = "Expected " + self.name + + def parseImpl( self, instring, loc, doActions=True ): + if instring[ loc:loc+self.matchLen ].upper() == self.match: + return loc+self.matchLen, self.returnString + raise ParseException(instring, loc, self.errmsg, self) + +class CaselessKeyword(Keyword): + """ + Caseless version of L{Keyword}. + + Example:: + OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD'] + + (Contrast with example for L{CaselessLiteral}.) + """ + def __init__( self, matchString, identChars=None ): + super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) + + def parseImpl( self, instring, loc, doActions=True ): + if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and + (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): + return loc+self.matchLen, self.match + raise ParseException(instring, loc, self.errmsg, self) + +class CloseMatch(Token): + """ + A variation on L{Literal} which matches "close" matches, that is, + strings with at most 'n' mismatching characters. C{CloseMatch} takes parameters: + - C{match_string} - string to be matched + - C{maxMismatches} - (C{default=1}) maximum number of mismatches allowed to count as a match + + The results from a successful parse will contain the matched text from the input string and the following named results: + - C{mismatches} - a list of the positions within the match_string where mismatches were found + - C{original} - the original match_string used to compare against the input string + + If C{mismatches} is an empty list, then the match was an exact match. + + Example:: + patt = CloseMatch("ATCATCGAATGGA") + patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']}) + patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1) + + # exact match + patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']}) + + # close match allowing up to 2 mismatches + patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2) + patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']}) + """ + def __init__(self, match_string, maxMismatches=1): + super(CloseMatch,self).__init__() + self.name = match_string + self.match_string = match_string + self.maxMismatches = maxMismatches + self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches) + self.mayIndexError = False + self.mayReturnEmpty = False + + def parseImpl( self, instring, loc, doActions=True ): + start = loc + instrlen = len(instring) + maxloc = start + len(self.match_string) + + if maxloc <= instrlen: + match_string = self.match_string + match_stringloc = 0 + mismatches = [] + maxMismatches = self.maxMismatches + + for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)): + src,mat = s_m + if src != mat: + mismatches.append(match_stringloc) + if len(mismatches) > maxMismatches: + break + else: + loc = match_stringloc + 1 + results = ParseResults([instring[start:loc]]) + results['original'] = self.match_string + results['mismatches'] = mismatches + return loc, results + + raise ParseException(instring, loc, self.errmsg, self) + + +class Word(Token): + """ + Token for matching words composed of allowed character sets. + Defined with string containing all allowed initial characters, + an optional string containing allowed body characters (if omitted, + defaults to the initial character set), and an optional minimum, + maximum, and/or exact length. The default value for C{min} is 1 (a + minimum value < 1 is not valid); the default values for C{max} and C{exact} + are 0, meaning no maximum or exact length restriction. An optional + C{excludeChars} parameter can list characters that might be found in + the input C{bodyChars} string; useful to define a word of all printables + except for one or two characters, for instance. + + L{srange} is useful for defining custom character set strings for defining + C{Word} expressions, using range notation from regular expression character sets. + + A common mistake is to use C{Word} to match a specific literal string, as in + C{Word("Address")}. Remember that C{Word} uses the string argument to define + I{sets} of matchable characters. This expression would match "Add", "AAA", + "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'. + To match an exact literal string, use L{Literal} or L{Keyword}. + + pyparsing includes helper strings for building Words: + - L{alphas} + - L{nums} + - L{alphanums} + - L{hexnums} + - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.) + - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.) + - L{printables} (any non-whitespace character) + + Example:: + # a word composed of digits + integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9")) + + # a word with a leading capital, and zero or more lowercase + capital_word = Word(alphas.upper(), alphas.lower()) + + # hostnames are alphanumeric, with leading alpha, and '-' + hostname = Word(alphas, alphanums+'-') + + # roman numeral (not a strict parser, accepts invalid mix of characters) + roman = Word("IVXLCDM") + + # any string of non-whitespace characters, except for ',' + csv_value = Word(printables, excludeChars=",") + """ + def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ): + super(Word,self).__init__() + if excludeChars: + initChars = ''.join(c for c in initChars if c not in excludeChars) + if bodyChars: + bodyChars = ''.join(c for c in bodyChars if c not in excludeChars) + self.initCharsOrig = initChars + self.initChars = set(initChars) + if bodyChars : + self.bodyCharsOrig = bodyChars + self.bodyChars = set(bodyChars) + else: + self.bodyCharsOrig = initChars + self.bodyChars = set(initChars) + + self.maxSpecified = max > 0 + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.asKeyword = asKeyword + + if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): + if self.bodyCharsOrig == self.initCharsOrig: + self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) + elif len(self.initCharsOrig) == 1: + self.reString = "%s[%s]*" % \ + (re.escape(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + else: + self.reString = "[%s][%s]*" % \ + (_escapeRegexRangeChars(self.initCharsOrig), + _escapeRegexRangeChars(self.bodyCharsOrig),) + if self.asKeyword: + self.reString = r"\b"+self.reString+r"\b" + try: + self.re = re.compile( self.reString ) + except Exception: + self.re = None + + def parseImpl( self, instring, loc, doActions=True ): + if self.re: + result = self.re.match(instring,loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + return loc, result.group() + + if not(instring[ loc ] in self.initChars): + raise ParseException(instring, loc, self.errmsg, self) + + start = loc + loc += 1 + instrlen = len(instring) + bodychars = self.bodyChars + maxloc = start + self.maxLen + maxloc = min( maxloc, instrlen ) + while loc < maxloc and instring[loc] in bodychars: + loc += 1 + + throwException = False + if loc - start < self.minLen: + throwException = True + if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: + throwException = True + if self.asKeyword: + if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): + throwException = True + + if throwException: + raise ParseException(instring, loc, self.errmsg, self) + + return loc, instring[start:loc] + + def __str__( self ): + try: + return super(Word,self).__str__() + except Exception: + pass + + + if self.strRepr is None: + + def charsAsStr(s): + if len(s)>4: + return s[:4]+"..." + else: + return s + + if ( self.initCharsOrig != self.bodyCharsOrig ): + self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) + else: + self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) + + return self.strRepr + + +class Regex(Token): + r""" + Token for matching strings that match a given regular expression. + Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. + If the given regex contains named groups (defined using C{(?P<name>...)}), these will be preserved as + named parse results. + + Example:: + realnum = Regex(r"[+-]?\d+\.\d*") + date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\d\d?)') + # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression + roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})") + """ + compiledREtype = type(re.compile("[A-Z]")) + def __init__( self, pattern, flags=0): + """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags.""" + super(Regex,self).__init__() + + if isinstance(pattern, basestring): + if not pattern: + warnings.warn("null string passed to Regex; use Empty() instead", + SyntaxWarning, stacklevel=2) + + self.pattern = pattern + self.flags = flags + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % pattern, + SyntaxWarning, stacklevel=2) + raise + + elif isinstance(pattern, Regex.compiledREtype): + self.re = pattern + self.pattern = \ + self.reString = str(pattern) + self.flags = flags + + else: + raise ValueError("Regex may only be constructed with a string or a compiled RE object") + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = self.re.match(instring,loc) + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + d = result.groupdict() + ret = ParseResults(result.group()) + if d: + for k in d: + ret[k] = d[k] + return loc,ret + + def __str__( self ): + try: + return super(Regex,self).__str__() + except Exception: + pass + + if self.strRepr is None: + self.strRepr = "Re:(%s)" % repr(self.pattern) + + return self.strRepr + + +class QuotedString(Token): + r""" + Token for matching strings that are delimited by quoting characters. + + Defined with the following parameters: + - quoteChar - string of one or more characters defining the quote delimiting string + - escChar - character to escape quotes, typically backslash (default=C{None}) + - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{None}) + - multiline - boolean indicating whether quotes can span multiple lines (default=C{False}) + - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True}) + - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar) + - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True}) + + Example:: + qs = QuotedString('"') + print(qs.searchString('lsjdf "This is the quote" sldjf')) + complex_qs = QuotedString('{{', endQuoteChar='}}') + print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf')) + sql_qs = QuotedString('"', escQuote='""') + print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf')) + prints:: + [['This is the quote']] + [['This is the "quote"']] + [['This is the quote with "embedded" quotes']] + """ + def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True): + super(QuotedString,self).__init__() + + # remove white space from quote chars - wont work anyway + quoteChar = quoteChar.strip() + if not quoteChar: + warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + if endQuoteChar is None: + endQuoteChar = quoteChar + else: + endQuoteChar = endQuoteChar.strip() + if not endQuoteChar: + warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) + raise SyntaxError() + + self.quoteChar = quoteChar + self.quoteCharLen = len(quoteChar) + self.firstQuoteChar = quoteChar[0] + self.endQuoteChar = endQuoteChar + self.endQuoteCharLen = len(endQuoteChar) + self.escChar = escChar + self.escQuote = escQuote + self.unquoteResults = unquoteResults + self.convertWhitespaceEscapes = convertWhitespaceEscapes + + if multiline: + self.flags = re.MULTILINE | re.DOTALL + self.pattern = r'%s(?:[^%s%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + else: + self.flags = 0 + self.pattern = r'%s(?:[^%s\n\r%s]' % \ + ( re.escape(self.quoteChar), + _escapeRegexRangeChars(self.endQuoteChar[0]), + (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) + if len(self.endQuoteChar) > 1: + self.pattern += ( + '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]), + _escapeRegexRangeChars(self.endQuoteChar[i])) + for i in range(len(self.endQuoteChar)-1,0,-1)) + ')' + ) + if escQuote: + self.pattern += (r'|(?:%s)' % re.escape(escQuote)) + if escChar: + self.pattern += (r'|(?:%s.)' % re.escape(escChar)) + self.escCharReplacePattern = re.escape(self.escChar)+"(.)" + self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) + + try: + self.re = re.compile(self.pattern, self.flags) + self.reString = self.pattern + except sre_constants.error: + warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, + SyntaxWarning, stacklevel=2) + raise + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayIndexError = False + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None + if not result: + raise ParseException(instring, loc, self.errmsg, self) + + loc = result.end() + ret = result.group() + + if self.unquoteResults: + + # strip off quotes + ret = ret[self.quoteCharLen:-self.endQuoteCharLen] + + if isinstance(ret,basestring): + # replace escaped whitespace + if '\\' in ret and self.convertWhitespaceEscapes: + ws_map = { + r'\t' : '\t', + r'\n' : '\n', + r'\f' : '\f', + r'\r' : '\r', + } + for wslit,wschar in ws_map.items(): + ret = ret.replace(wslit, wschar) + + # replace escaped characters + if self.escChar: + ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret) + + # replace escaped quotes + if self.escQuote: + ret = ret.replace(self.escQuote, self.endQuoteChar) + + return loc, ret + + def __str__( self ): + try: + return super(QuotedString,self).__str__() + except Exception: + pass + + if self.strRepr is None: + self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) + + return self.strRepr + + +class CharsNotIn(Token): + """ + Token for matching words composed of characters I{not} in a given set (will + include whitespace in matched characters if not listed in the provided exclusion set - see example). + Defined with string containing all disallowed characters, and an optional + minimum, maximum, and/or exact length. The default value for C{min} is 1 (a + minimum value < 1 is not valid); the default values for C{max} and C{exact} + are 0, meaning no maximum or exact length restriction. + + Example:: + # define a comma-separated-value as anything that is not a ',' + csv_value = CharsNotIn(',') + print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213")) + prints:: + ['dkls', 'lsdkjf', 's12 34', '@!#', '213'] + """ + def __init__( self, notChars, min=1, max=0, exact=0 ): + super(CharsNotIn,self).__init__() + self.skipWhitespace = False + self.notChars = notChars + + if min < 1: + raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + self.name = _ustr(self) + self.errmsg = "Expected " + self.name + self.mayReturnEmpty = ( self.minLen == 0 ) + self.mayIndexError = False + + def parseImpl( self, instring, loc, doActions=True ): + if instring[loc] in self.notChars: + raise ParseException(instring, loc, self.errmsg, self) + + start = loc + loc += 1 + notchars = self.notChars + maxlen = min( start+self.maxLen, len(instring) ) + while loc < maxlen and \ + (instring[loc] not in notchars): + loc += 1 + + if loc - start < self.minLen: + raise ParseException(instring, loc, self.errmsg, self) + + return loc, instring[start:loc] + + def __str__( self ): + try: + return super(CharsNotIn, self).__str__() + except Exception: + pass + + if self.strRepr is None: + if len(self.notChars) > 4: + self.strRepr = "!W:(%s...)" % self.notChars[:4] + else: + self.strRepr = "!W:(%s)" % self.notChars + + return self.strRepr + +class White(Token): + """ + Special matching class for matching whitespace. Normally, whitespace is ignored + by pyparsing grammars. This class is included when some whitespace structures + are significant. Define with a string containing the whitespace characters to be + matched; default is C{" \\t\\r\\n"}. Also takes optional C{min}, C{max}, and C{exact} arguments, + as defined for the C{L{Word}} class. + """ + whiteStrs = { + " " : "<SPC>", + "\t": "<TAB>", + "\n": "<LF>", + "\r": "<CR>", + "\f": "<FF>", + } + def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): + super(White,self).__init__() + self.matchWhite = ws + self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) ) + #~ self.leaveWhitespace() + self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite)) + self.mayReturnEmpty = True + self.errmsg = "Expected " + self.name + + self.minLen = min + + if max > 0: + self.maxLen = max + else: + self.maxLen = _MAX_INT + + if exact > 0: + self.maxLen = exact + self.minLen = exact + + def parseImpl( self, instring, loc, doActions=True ): + if not(instring[ loc ] in self.matchWhite): + raise ParseException(instring, loc, self.errmsg, self) + start = loc + loc += 1 + maxloc = start + self.maxLen + maxloc = min( maxloc, len(instring) ) + while loc < maxloc and instring[loc] in self.matchWhite: + loc += 1 + + if loc - start < self.minLen: + raise ParseException(instring, loc, self.errmsg, self) + + return loc, instring[start:loc] + + +class _PositionToken(Token): + def __init__( self ): + super(_PositionToken,self).__init__() + self.name=self.__class__.__name__ + self.mayReturnEmpty = True + self.mayIndexError = False + +class GoToColumn(_PositionToken): + """ + Token to advance to a specific column of input text; useful for tabular report scraping. + """ + def __init__( self, colno ): + super(GoToColumn,self).__init__() + self.col = colno + + def preParse( self, instring, loc ): + if col(loc,instring) != self.col: + instrlen = len(instring) + if self.ignoreExprs: + loc = self._skipIgnorables( instring, loc ) + while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : + loc += 1 + return loc + + def parseImpl( self, instring, loc, doActions=True ): + thiscol = col( loc, instring ) + if thiscol > self.col: + raise ParseException( instring, loc, "Text not in expected column", self ) + newloc = loc + self.col - thiscol + ret = instring[ loc: newloc ] + return newloc, ret + + +class LineStart(_PositionToken): + """ + Matches if current position is at the beginning of a line within the parse string + + Example:: + + test = '''\ + AAA this line + AAA and this line + AAA but not this one + B AAA and definitely not this one + ''' + + for t in (LineStart() + 'AAA' + restOfLine).searchString(test): + print(t) + + Prints:: + ['AAA', ' this line'] + ['AAA', ' and this line'] + + """ + def __init__( self ): + super(LineStart,self).__init__() + self.errmsg = "Expected start of line" + + def parseImpl( self, instring, loc, doActions=True ): + if col(loc, instring) == 1: + return loc, [] + raise ParseException(instring, loc, self.errmsg, self) + +class LineEnd(_PositionToken): + """ + Matches if current position is at the end of a line within the parse string + """ + def __init__( self ): + super(LineEnd,self).__init__() + self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) + self.errmsg = "Expected end of line" + + def parseImpl( self, instring, loc, doActions=True ): + if loc<len(instring): + if instring[loc] == "\n": + return loc+1, "\n" + else: + raise ParseException(instring, loc, self.errmsg, self) + elif loc == len(instring): + return loc+1, [] + else: + raise ParseException(instring, loc, self.errmsg, self) + +class StringStart(_PositionToken): + """ + Matches if current position is at the beginning of the parse string + """ + def __init__( self ): + super(StringStart,self).__init__() + self.errmsg = "Expected start of text" + + def parseImpl( self, instring, loc, doActions=True ): + if loc != 0: + # see if entire string up to here is just whitespace and ignoreables + if loc != self.preParse( instring, 0 ): + raise ParseException(instring, loc, self.errmsg, self) + return loc, [] + +class StringEnd(_PositionToken): + """ + Matches if current position is at the end of the parse string + """ + def __init__( self ): + super(StringEnd,self).__init__() + self.errmsg = "Expected end of text" + + def parseImpl( self, instring, loc, doActions=True ): + if loc < len(instring): + raise ParseException(instring, loc, self.errmsg, self) + elif loc == len(instring): + return loc+1, [] + elif loc > len(instring): + return loc, [] + else: + raise ParseException(instring, loc, self.errmsg, self) + +class WordStart(_PositionToken): + """ + Matches if the current position is at the beginning of a Word, and + is not preceded by any character in a given set of C{wordChars} + (default=C{printables}). To emulate the C{\b} behavior of regular expressions, + use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of + the string being parsed, or at the beginning of a line. + """ + def __init__(self, wordChars = printables): + super(WordStart,self).__init__() + self.wordChars = set(wordChars) + self.errmsg = "Not at the start of a word" + + def parseImpl(self, instring, loc, doActions=True ): + if loc != 0: + if (instring[loc-1] in self.wordChars or + instring[loc] not in self.wordChars): + raise ParseException(instring, loc, self.errmsg, self) + return loc, [] + +class WordEnd(_PositionToken): + """ + Matches if the current position is at the end of a Word, and + is not followed by any character in a given set of C{wordChars} + (default=C{printables}). To emulate the C{\b} behavior of regular expressions, + use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of + the string being parsed, or at the end of a line. + """ + def __init__(self, wordChars = printables): + super(WordEnd,self).__init__() + self.wordChars = set(wordChars) + self.skipWhitespace = False + self.errmsg = "Not at the end of a word" + + def parseImpl(self, instring, loc, doActions=True ): + instrlen = len(instring) + if instrlen>0 and loc<instrlen: + if (instring[loc] in self.wordChars or + instring[loc-1] not in self.wordChars): + raise ParseException(instring, loc, self.errmsg, self) + return loc, [] + + +class ParseExpression(ParserElement): + """ + Abstract subclass of ParserElement, for combining and post-processing parsed tokens. + """ + def __init__( self, exprs, savelist = False ): + super(ParseExpression,self).__init__(savelist) + if isinstance( exprs, _generatorType ): + exprs = list(exprs) + + if isinstance( exprs, basestring ): + self.exprs = [ ParserElement._literalStringClass( exprs ) ] + elif isinstance( exprs, Iterable ): + exprs = list(exprs) + # if sequence of strings provided, wrap with Literal + if all(isinstance(expr, basestring) for expr in exprs): + exprs = map(ParserElement._literalStringClass, exprs) + self.exprs = list(exprs) + else: + try: + self.exprs = list( exprs ) + except TypeError: + self.exprs = [ exprs ] + self.callPreparse = False + + def __getitem__( self, i ): + return self.exprs[i] + + def append( self, other ): + self.exprs.append( other ) + self.strRepr = None + return self + + def leaveWhitespace( self ): + """Extends C{leaveWhitespace} defined in base class, and also invokes C{leaveWhitespace} on + all contained expressions.""" + self.skipWhitespace = False + self.exprs = [ e.copy() for e in self.exprs ] + for e in self.exprs: + e.leaveWhitespace() + return self + + def ignore( self, other ): + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + super( ParseExpression, self).ignore( other ) + for e in self.exprs: + e.ignore( self.ignoreExprs[-1] ) + else: + super( ParseExpression, self).ignore( other ) + for e in self.exprs: + e.ignore( self.ignoreExprs[-1] ) + return self + + def __str__( self ): + try: + return super(ParseExpression,self).__str__() + except Exception: + pass + + if self.strRepr is None: + self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) + return self.strRepr + + def streamline( self ): + super(ParseExpression,self).streamline() + + for e in self.exprs: + e.streamline() + + # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) + # but only if there are no parse actions or resultsNames on the nested And's + # (likewise for Or's and MatchFirst's) + if ( len(self.exprs) == 2 ): + other = self.exprs[0] + if ( isinstance( other, self.__class__ ) and + not(other.parseAction) and + other.resultsName is None and + not other.debug ): + self.exprs = other.exprs[:] + [ self.exprs[1] ] + self.strRepr = None + self.mayReturnEmpty |= other.mayReturnEmpty + self.mayIndexError |= other.mayIndexError + + other = self.exprs[-1] + if ( isinstance( other, self.__class__ ) and + not(other.parseAction) and + other.resultsName is None and + not other.debug ): + self.exprs = self.exprs[:-1] + other.exprs[:] + self.strRepr = None + self.mayReturnEmpty |= other.mayReturnEmpty + self.mayIndexError |= other.mayIndexError + + self.errmsg = "Expected " + _ustr(self) + + return self + + def setResultsName( self, name, listAllMatches=False ): + ret = super(ParseExpression,self).setResultsName(name,listAllMatches) + return ret + + def validate( self, validateTrace=[] ): + tmp = validateTrace[:]+[self] + for e in self.exprs: + e.validate(tmp) + self.checkRecursion( [] ) + + def copy(self): + ret = super(ParseExpression,self).copy() + ret.exprs = [e.copy() for e in self.exprs] + return ret + +class And(ParseExpression): + """ + Requires all given C{ParseExpression}s to be found in the given order. + Expressions may be separated by whitespace. + May be constructed using the C{'+'} operator. + May also be constructed using the C{'-'} operator, which will suppress backtracking. + + Example:: + integer = Word(nums) + name_expr = OneOrMore(Word(alphas)) + + expr = And([integer("id"),name_expr("name"),integer("age")]) + # more easily written as: + expr = integer("id") + name_expr("name") + integer("age") + """ + + class _ErrorStop(Empty): + def __init__(self, *args, **kwargs): + super(And._ErrorStop,self).__init__(*args, **kwargs) + self.name = '-' + self.leaveWhitespace() + + def __init__( self, exprs, savelist = True ): + super(And,self).__init__(exprs, savelist) + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + self.setWhitespaceChars( self.exprs[0].whiteChars ) + self.skipWhitespace = self.exprs[0].skipWhitespace + self.callPreparse = True + + def parseImpl( self, instring, loc, doActions=True ): + # pass False as last arg to _parse for first element, since we already + # pre-parsed the string as part of our And pre-parsing + loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) + errorStop = False + for e in self.exprs[1:]: + if isinstance(e, And._ErrorStop): + errorStop = True + continue + if errorStop: + try: + loc, exprtokens = e._parse( instring, loc, doActions ) + except ParseSyntaxException: + raise + except ParseBaseException as pe: + pe.__traceback__ = None + raise ParseSyntaxException._from_exception(pe) + except IndexError: + raise ParseSyntaxException(instring, len(instring), self.errmsg, self) + else: + loc, exprtokens = e._parse( instring, loc, doActions ) + if exprtokens or exprtokens.haskeys(): + resultlist += exprtokens + return loc, resultlist + + def __iadd__(self, other ): + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + return self.append( other ) #And( [ self, other ] ) + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + if not e.mayReturnEmpty: + break + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " ".join(_ustr(e) for e in self.exprs) + "}" + + return self.strRepr + + +class Or(ParseExpression): + """ + Requires that at least one C{ParseExpression} is found. + If two expressions match, the expression that matches the longest string will be used. + May be constructed using the C{'^'} operator. + + Example:: + # construct Or using '^' operator + + number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums)) + print(number.searchString("123 3.1416 789")) + prints:: + [['123'], ['3.1416'], ['789']] + """ + def __init__( self, exprs, savelist = False ): + super(Or,self).__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + else: + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + maxExcLoc = -1 + maxException = None + matches = [] + for e in self.exprs: + try: + loc2 = e.tryParse( instring, loc ) + except ParseException as err: + err.__traceback__ = None + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + else: + # save match among all matches, to retry longest to shortest + matches.append((loc2, e)) + + if matches: + matches.sort(key=lambda x: -x[0]) + for _,e in matches: + try: + return e._parse( instring, loc, doActions ) + except ParseException as err: + err.__traceback__ = None + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + + if maxException is not None: + maxException.msg = self.errmsg + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + + def __ixor__(self, other ): + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + return self.append( other ) #Or( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class MatchFirst(ParseExpression): + """ + Requires that at least one C{ParseExpression} is found. + If two expressions match, the first one listed is the one that will match. + May be constructed using the C{'|'} operator. + + Example:: + # construct MatchFirst using '|' operator + + # watch the order of expressions to match + number = Word(nums) | Combine(Word(nums) + '.' + Word(nums)) + print(number.searchString("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']] + + # put more selective expression first + number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) + print(number.searchString("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] + """ + def __init__( self, exprs, savelist = False ): + super(MatchFirst,self).__init__(exprs, savelist) + if self.exprs: + self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) + else: + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + maxExcLoc = -1 + maxException = None + for e in self.exprs: + try: + ret = e._parse( instring, loc, doActions ) + return ret + except ParseException as err: + if err.loc > maxExcLoc: + maxException = err + maxExcLoc = err.loc + except IndexError: + if len(instring) > maxExcLoc: + maxException = ParseException(instring,len(instring),e.errmsg,self) + maxExcLoc = len(instring) + + # only got here if no expression matched, raise exception for match that made it the furthest + else: + if maxException is not None: + maxException.msg = self.errmsg + raise maxException + else: + raise ParseException(instring, loc, "no defined alternatives to match", self) + + def __ior__(self, other ): + if isinstance( other, basestring ): + other = ParserElement._literalStringClass( other ) + return self.append( other ) #MatchFirst( [ self, other ] ) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class Each(ParseExpression): + """ + Requires all given C{ParseExpression}s to be found, but in any order. + Expressions may be separated by whitespace. + May be constructed using the C{'&'} operator. + + Example:: + color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN") + shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON") + integer = Word(nums) + shape_attr = "shape:" + shape_type("shape") + posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn") + color_attr = "color:" + color("color") + size_attr = "size:" + integer("size") + + # use Each (using operator '&') to accept attributes in any order + # (shape and posn are required, color and size are optional) + shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr) + + shape_spec.runTests(''' + shape: SQUARE color: BLACK posn: 100, 120 + shape: CIRCLE size: 50 color: BLUE posn: 50,80 + color:GREEN size:20 shape:TRIANGLE posn:20,40 + ''' + ) + prints:: + shape: SQUARE color: BLACK posn: 100, 120 + ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']] + - color: BLACK + - posn: ['100', ',', '120'] + - x: 100 + - y: 120 + - shape: SQUARE + + + shape: CIRCLE size: 50 color: BLUE posn: 50,80 + ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']] + - color: BLUE + - posn: ['50', ',', '80'] + - x: 50 + - y: 80 + - shape: CIRCLE + - size: 50 + + + color: GREEN size: 20 shape: TRIANGLE posn: 20,40 + ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']] + - color: GREEN + - posn: ['20', ',', '40'] + - x: 20 + - y: 40 + - shape: TRIANGLE + - size: 20 + """ + def __init__( self, exprs, savelist = True ): + super(Each,self).__init__(exprs, savelist) + self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) + self.skipWhitespace = True + self.initExprGroups = True + + def parseImpl( self, instring, loc, doActions=True ): + if self.initExprGroups: + self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional)) + opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] + opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)] + self.optionals = opt1 + opt2 + self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] + self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] + self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] + self.required += self.multirequired + self.initExprGroups = False + tmpLoc = loc + tmpReqd = self.required[:] + tmpOpt = self.optionals[:] + matchOrder = [] + + keepMatching = True + while keepMatching: + tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired + failed = [] + for e in tmpExprs: + try: + tmpLoc = e.tryParse( instring, tmpLoc ) + except ParseException: + failed.append(e) + else: + matchOrder.append(self.opt1map.get(id(e),e)) + if e in tmpReqd: + tmpReqd.remove(e) + elif e in tmpOpt: + tmpOpt.remove(e) + if len(failed) == len(tmpExprs): + keepMatching = False + + if tmpReqd: + missing = ", ".join(_ustr(e) for e in tmpReqd) + raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) + + # add any unmatched Optionals, in case they have default values defined + matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt] + + resultlist = [] + for e in matchOrder: + loc,results = e._parse(instring,loc,doActions) + resultlist.append(results) + + finalResults = sum(resultlist, ParseResults([])) + return loc, finalResults + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}" + + return self.strRepr + + def checkRecursion( self, parseElementList ): + subRecCheckList = parseElementList[:] + [ self ] + for e in self.exprs: + e.checkRecursion( subRecCheckList ) + + +class ParseElementEnhance(ParserElement): + """ + Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens. + """ + def __init__( self, expr, savelist=False ): + super(ParseElementEnhance,self).__init__(savelist) + if isinstance( expr, basestring ): + if issubclass(ParserElement._literalStringClass, Token): + expr = ParserElement._literalStringClass(expr) + else: + expr = ParserElement._literalStringClass(Literal(expr)) + self.expr = expr + self.strRepr = None + if expr is not None: + self.mayIndexError = expr.mayIndexError + self.mayReturnEmpty = expr.mayReturnEmpty + self.setWhitespaceChars( expr.whiteChars ) + self.skipWhitespace = expr.skipWhitespace + self.saveAsList = expr.saveAsList + self.callPreparse = expr.callPreparse + self.ignoreExprs.extend(expr.ignoreExprs) + + def parseImpl( self, instring, loc, doActions=True ): + if self.expr is not None: + return self.expr._parse( instring, loc, doActions, callPreParse=False ) + else: + raise ParseException("",loc,self.errmsg,self) + + def leaveWhitespace( self ): + self.skipWhitespace = False + self.expr = self.expr.copy() + if self.expr is not None: + self.expr.leaveWhitespace() + return self + + def ignore( self, other ): + if isinstance( other, Suppress ): + if other not in self.ignoreExprs: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + else: + super( ParseElementEnhance, self).ignore( other ) + if self.expr is not None: + self.expr.ignore( self.ignoreExprs[-1] ) + return self + + def streamline( self ): + super(ParseElementEnhance,self).streamline() + if self.expr is not None: + self.expr.streamline() + return self + + def checkRecursion( self, parseElementList ): + if self in parseElementList: + raise RecursiveGrammarException( parseElementList+[self] ) + subRecCheckList = parseElementList[:] + [ self ] + if self.expr is not None: + self.expr.checkRecursion( subRecCheckList ) + + def validate( self, validateTrace=[] ): + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion( [] ) + + def __str__( self ): + try: + return super(ParseElementEnhance,self).__str__() + except Exception: + pass + + if self.strRepr is None and self.expr is not None: + self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) + return self.strRepr + + +class FollowedBy(ParseElementEnhance): + """ + Lookahead matching of the given parse expression. C{FollowedBy} + does I{not} advance the parsing position within the input string, it only + verifies that the specified parse expression matches at the current + position. C{FollowedBy} always returns a null token list. + + Example:: + # use FollowedBy to match a label only if it is followed by a ':' + data_word = Word(alphas) + label = data_word + FollowedBy(':') + attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) + + OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint() + prints:: + [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']] + """ + def __init__( self, expr ): + super(FollowedBy,self).__init__(expr) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + self.expr.tryParse( instring, loc ) + return loc, [] + + +class NotAny(ParseElementEnhance): + """ + Lookahead to disallow matching with the given parse expression. C{NotAny} + does I{not} advance the parsing position within the input string, it only + verifies that the specified parse expression does I{not} match at the current + position. Also, C{NotAny} does I{not} skip over leading whitespace. C{NotAny} + always returns a null token list. May be constructed using the '~' operator. + + Example:: + + """ + def __init__( self, expr ): + super(NotAny,self).__init__(expr) + #~ self.leaveWhitespace() + self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs + self.mayReturnEmpty = True + self.errmsg = "Found unwanted token, "+_ustr(self.expr) + + def parseImpl( self, instring, loc, doActions=True ): + if self.expr.canParseNext(instring, loc): + raise ParseException(instring, loc, self.errmsg, self) + return loc, [] + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "~{" + _ustr(self.expr) + "}" + + return self.strRepr + +class _MultipleMatch(ParseElementEnhance): + def __init__( self, expr, stopOn=None): + super(_MultipleMatch, self).__init__(expr) + self.saveAsList = True + ender = stopOn + if isinstance(ender, basestring): + ender = ParserElement._literalStringClass(ender) + self.not_ender = ~ender if ender is not None else None + + def parseImpl( self, instring, loc, doActions=True ): + self_expr_parse = self.expr._parse + self_skip_ignorables = self._skipIgnorables + check_ender = self.not_ender is not None + if check_ender: + try_not_ender = self.not_ender.tryParse + + # must be at least one (but first see if we are the stopOn sentinel; + # if so, fail) + if check_ender: + try_not_ender(instring, loc) + loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False ) + try: + hasIgnoreExprs = (not not self.ignoreExprs) + while 1: + if check_ender: + try_not_ender(instring, loc) + if hasIgnoreExprs: + preloc = self_skip_ignorables( instring, loc ) + else: + preloc = loc + loc, tmptokens = self_expr_parse( instring, preloc, doActions ) + if tmptokens or tmptokens.haskeys(): + tokens += tmptokens + except (ParseException,IndexError): + pass + + return loc, tokens + +class OneOrMore(_MultipleMatch): + """ + Repetition of one or more of the given expression. + + Parameters: + - expr - expression that must match one or more times + - stopOn - (default=C{None}) - expression for a terminating sentinel + (only required if the sentinel would ordinarily match the repetition + expression) + + Example:: + data_word = Word(alphas) + label = data_word + FollowedBy(':') + attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join)) + + text = "shape: SQUARE posn: upper left color: BLACK" + OneOrMore(attr_expr).parseString(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']] + + # use stopOn attribute for OneOrMore to avoid reading label string as part of the data + attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) + OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']] + + # could also be written as + (attr_expr * (1,)).parseString(text).pprint() + """ + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "{" + _ustr(self.expr) + "}..." + + return self.strRepr + +class ZeroOrMore(_MultipleMatch): + """ + Optional repetition of zero or more of the given expression. + + Parameters: + - expr - expression that must match zero or more times + - stopOn - (default=C{None}) - expression for a terminating sentinel + (only required if the sentinel would ordinarily match the repetition + expression) + + Example: similar to L{OneOrMore} + """ + def __init__( self, expr, stopOn=None): + super(ZeroOrMore,self).__init__(expr, stopOn=stopOn) + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + try: + return super(ZeroOrMore, self).parseImpl(instring, loc, doActions) + except (ParseException,IndexError): + return loc, [] + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]..." + + return self.strRepr + +class _NullToken(object): + def __bool__(self): + return False + __nonzero__ = __bool__ + def __str__(self): + return "" + +_optionalNotMatched = _NullToken() +class Optional(ParseElementEnhance): + """ + Optional matching of the given expression. + + Parameters: + - expr - expression that must match zero or more times + - default (optional) - value to be returned if the optional expression is not found. + + Example:: + # US postal code can be a 5-digit zip, plus optional 4-digit qualifier + zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4))) + zip.runTests(''' + # traditional ZIP code + 12345 + + # ZIP+4 form + 12101-0001 + + # invalid ZIP + 98765- + ''') + prints:: + # traditional ZIP code + 12345 + ['12345'] + + # ZIP+4 form + 12101-0001 + ['12101-0001'] + + # invalid ZIP + 98765- + ^ + FAIL: Expected end of text (at char 5), (line:1, col:6) + """ + def __init__( self, expr, default=_optionalNotMatched ): + super(Optional,self).__init__( expr, savelist=False ) + self.saveAsList = self.expr.saveAsList + self.defaultValue = default + self.mayReturnEmpty = True + + def parseImpl( self, instring, loc, doActions=True ): + try: + loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) + except (ParseException,IndexError): + if self.defaultValue is not _optionalNotMatched: + if self.expr.resultsName: + tokens = ParseResults([ self.defaultValue ]) + tokens[self.expr.resultsName] = self.defaultValue + else: + tokens = [ self.defaultValue ] + else: + tokens = [] + return loc, tokens + + def __str__( self ): + if hasattr(self,"name"): + return self.name + + if self.strRepr is None: + self.strRepr = "[" + _ustr(self.expr) + "]" + + return self.strRepr + +class SkipTo(ParseElementEnhance): + """ + Token for skipping over all undefined text until the matched expression is found. + + Parameters: + - expr - target expression marking the end of the data to be skipped + - include - (default=C{False}) if True, the target expression is also parsed + (the skipped text and target expression are returned as a 2-element list). + - ignore - (default=C{None}) used to define grammars (typically quoted strings and + comments) that might contain false matches to the target expression + - failOn - (default=C{None}) define expressions that are not allowed to be + included in the skipped test; if found before the target expression is found, + the SkipTo is not a match + + Example:: + report = ''' + Outstanding Issues Report - 1 Jan 2000 + + # | Severity | Description | Days Open + -----+----------+-------------------------------------------+----------- + 101 | Critical | Intermittent system crash | 6 + 94 | Cosmetic | Spelling error on Login ('log|n') | 14 + 79 | Minor | System slow when running too many reports | 47 + ''' + integer = Word(nums) + SEP = Suppress('|') + # use SkipTo to simply match everything up until the next SEP + # - ignore quoted strings, so that a '|' character inside a quoted string does not match + # - parse action will call token.strip() for each matched token, i.e., the description body + string_data = SkipTo(SEP, ignore=quotedString) + string_data.setParseAction(tokenMap(str.strip)) + ticket_expr = (integer("issue_num") + SEP + + string_data("sev") + SEP + + string_data("desc") + SEP + + integer("days_open")) + + for tkt in ticket_expr.searchString(report): + print tkt.dump() + prints:: + ['101', 'Critical', 'Intermittent system crash', '6'] + - days_open: 6 + - desc: Intermittent system crash + - issue_num: 101 + - sev: Critical + ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14'] + - days_open: 14 + - desc: Spelling error on Login ('log|n') + - issue_num: 94 + - sev: Cosmetic + ['79', 'Minor', 'System slow when running too many reports', '47'] + - days_open: 47 + - desc: System slow when running too many reports + - issue_num: 79 + - sev: Minor + """ + def __init__( self, other, include=False, ignore=None, failOn=None ): + super( SkipTo, self ).__init__( other ) + self.ignoreExpr = ignore + self.mayReturnEmpty = True + self.mayIndexError = False + self.includeMatch = include + self.asList = False + if isinstance(failOn, basestring): + self.failOn = ParserElement._literalStringClass(failOn) + else: + self.failOn = failOn + self.errmsg = "No match found for "+_ustr(self.expr) + + def parseImpl( self, instring, loc, doActions=True ): + startloc = loc + instrlen = len(instring) + expr = self.expr + expr_parse = self.expr._parse + self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None + self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None + + tmploc = loc + while tmploc <= instrlen: + if self_failOn_canParseNext is not None: + # break if failOn expression matches + if self_failOn_canParseNext(instring, tmploc): + break + + if self_ignoreExpr_tryParse is not None: + # advance past ignore expressions + while 1: + try: + tmploc = self_ignoreExpr_tryParse(instring, tmploc) + except ParseBaseException: + break + + try: + expr_parse(instring, tmploc, doActions=False, callPreParse=False) + except (ParseException, IndexError): + # no match, advance loc in string + tmploc += 1 + else: + # matched skipto expr, done + break + + else: + # ran off the end of the input string without matching skipto expr, fail + raise ParseException(instring, loc, self.errmsg, self) + + # build up return values + loc = tmploc + skiptext = instring[startloc:loc] + skipresult = ParseResults(skiptext) + + if self.includeMatch: + loc, mat = expr_parse(instring,loc,doActions,callPreParse=False) + skipresult += mat + + return loc, skipresult + +class Forward(ParseElementEnhance): + """ + Forward declaration of an expression to be defined later - + used for recursive grammars, such as algebraic infix notation. + When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator. + + Note: take care when assigning to C{Forward} not to overlook precedence of operators. + Specifically, '|' has a lower precedence than '<<', so that:: + fwdExpr << a | b | c + will actually be evaluated as:: + (fwdExpr << a) | b | c + thereby leaving b and c out as parseable alternatives. It is recommended that you + explicitly group the values inserted into the C{Forward}:: + fwdExpr << (a | b | c) + Converting to use the '<<=' operator instead will avoid this problem. + + See L{ParseResults.pprint} for an example of a recursive parser created using + C{Forward}. + """ + def __init__( self, other=None ): + super(Forward,self).__init__( other, savelist=False ) + + def __lshift__( self, other ): + if isinstance( other, basestring ): + other = ParserElement._literalStringClass(other) + self.expr = other + self.strRepr = None + self.mayIndexError = self.expr.mayIndexError + self.mayReturnEmpty = self.expr.mayReturnEmpty + self.setWhitespaceChars( self.expr.whiteChars ) + self.skipWhitespace = self.expr.skipWhitespace + self.saveAsList = self.expr.saveAsList + self.ignoreExprs.extend(self.expr.ignoreExprs) + return self + + def __ilshift__(self, other): + return self << other + + def leaveWhitespace( self ): + self.skipWhitespace = False + return self + + def streamline( self ): + if not self.streamlined: + self.streamlined = True + if self.expr is not None: + self.expr.streamline() + return self + + def validate( self, validateTrace=[] ): + if self not in validateTrace: + tmp = validateTrace[:]+[self] + if self.expr is not None: + self.expr.validate(tmp) + self.checkRecursion([]) + + def __str__( self ): + if hasattr(self,"name"): + return self.name + return self.__class__.__name__ + ": ..." + + # stubbed out for now - creates awful memory and perf issues + self._revertClass = self.__class__ + self.__class__ = _ForwardNoRecurse + try: + if self.expr is not None: + retString = _ustr(self.expr) + else: + retString = "None" + finally: + self.__class__ = self._revertClass + return self.__class__.__name__ + ": " + retString + + def copy(self): + if self.expr is not None: + return super(Forward,self).copy() + else: + ret = Forward() + ret <<= self + return ret + +class _ForwardNoRecurse(Forward): + def __str__( self ): + return "..." + +class TokenConverter(ParseElementEnhance): + """ + Abstract subclass of C{ParseExpression}, for converting parsed results. + """ + def __init__( self, expr, savelist=False ): + super(TokenConverter,self).__init__( expr )#, savelist ) + self.saveAsList = False + +class Combine(TokenConverter): + """ + Converter to concatenate all matching tokens to a single string. + By default, the matching patterns must also be contiguous in the input string; + this can be disabled by specifying C{'adjacent=False'} in the constructor. + + Example:: + real = Word(nums) + '.' + Word(nums) + print(real.parseString('3.1416')) # -> ['3', '.', '1416'] + # will also erroneously match the following + print(real.parseString('3. 1416')) # -> ['3', '.', '1416'] + + real = Combine(Word(nums) + '.' + Word(nums)) + print(real.parseString('3.1416')) # -> ['3.1416'] + # no match when there are internal spaces + print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...) + """ + def __init__( self, expr, joinString="", adjacent=True ): + super(Combine,self).__init__( expr ) + # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself + if adjacent: + self.leaveWhitespace() + self.adjacent = adjacent + self.skipWhitespace = True + self.joinString = joinString + self.callPreparse = True + + def ignore( self, other ): + if self.adjacent: + ParserElement.ignore(self, other) + else: + super( Combine, self).ignore( other ) + return self + + def postParse( self, instring, loc, tokenlist ): + retToks = tokenlist.copy() + del retToks[:] + retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) + + if self.resultsName and retToks.haskeys(): + return [ retToks ] + else: + return retToks + +class Group(TokenConverter): + """ + Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions. + + Example:: + ident = Word(alphas) + num = Word(nums) + term = ident | num + func = ident + Optional(delimitedList(term)) + print(func.parseString("fn a,b,100")) # -> ['fn', 'a', 'b', '100'] + + func = ident + Group(Optional(delimitedList(term))) + print(func.parseString("fn a,b,100")) # -> ['fn', ['a', 'b', '100']] + """ + def __init__( self, expr ): + super(Group,self).__init__( expr ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + return [ tokenlist ] + +class Dict(TokenConverter): + """ + Converter to return a repetitive expression as a list, but also as a dictionary. + Each element can also be referenced using the first token in the expression as its key. + Useful for tabular report scraping when the first column can be used as a item key. + + Example:: + data_word = Word(alphas) + label = data_word + FollowedBy(':') + attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join)) + + text = "shape: SQUARE posn: upper left color: light blue texture: burlap" + attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) + + # print attributes as plain groups + print(OneOrMore(attr_expr).parseString(text).dump()) + + # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names + result = Dict(OneOrMore(Group(attr_expr))).parseString(text) + print(result.dump()) + + # access named fields as dict entries, or output as dict + print(result['shape']) + print(result.asDict()) + prints:: + ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap'] + + [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] + - color: light blue + - posn: upper left + - shape: SQUARE + - texture: burlap + SQUARE + {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'} + See more examples at L{ParseResults} of accessing fields by results name. + """ + def __init__( self, expr ): + super(Dict,self).__init__( expr ) + self.saveAsList = True + + def postParse( self, instring, loc, tokenlist ): + for i,tok in enumerate(tokenlist): + if len(tok) == 0: + continue + ikey = tok[0] + if isinstance(ikey,int): + ikey = _ustr(tok[0]).strip() + if len(tok)==1: + tokenlist[ikey] = _ParseResultsWithOffset("",i) + elif len(tok)==2 and not isinstance(tok[1],ParseResults): + tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) + else: + dictvalue = tok.copy() #ParseResults(i) + del dictvalue[0] + if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()): + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) + else: + tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) + + if self.resultsName: + return [ tokenlist ] + else: + return tokenlist + + +class Suppress(TokenConverter): + """ + Converter for ignoring the results of a parsed expression. + + Example:: + source = "a, b, c,d" + wd = Word(alphas) + wd_list1 = wd + ZeroOrMore(',' + wd) + print(wd_list1.parseString(source)) + + # often, delimiters that are useful during parsing are just in the + # way afterward - use Suppress to keep them out of the parsed output + wd_list2 = wd + ZeroOrMore(Suppress(',') + wd) + print(wd_list2.parseString(source)) + prints:: + ['a', ',', 'b', ',', 'c', ',', 'd'] + ['a', 'b', 'c', 'd'] + (See also L{delimitedList}.) + """ + def postParse( self, instring, loc, tokenlist ): + return [] + + def suppress( self ): + return self + + +class OnlyOnce(object): + """ + Wrapper for parse actions, to ensure they are only called once. + """ + def __init__(self, methodCall): + self.callable = _trim_arity(methodCall) + self.called = False + def __call__(self,s,l,t): + if not self.called: + results = self.callable(s,l,t) + self.called = True + return results + raise ParseException(s,l,"") + def reset(self): + self.called = False + +def traceParseAction(f): + """ + Decorator for debugging parse actions. + + When the parse action is called, this decorator will print C{">> entering I{method-name}(line:I{current_source_line}, I{parse_location}, I{matched_tokens})".} + When the parse action completes, the decorator will print C{"<<"} followed by the returned value, or any exception that the parse action raised. + + Example:: + wd = Word(alphas) + + @traceParseAction + def remove_duplicate_chars(tokens): + return ''.join(sorted(set(''.join(tokens)))) + + wds = OneOrMore(wd).setParseAction(remove_duplicate_chars) + print(wds.parseString("slkdjs sld sldd sdlf sdljf")) + prints:: + >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {})) + <<leaving remove_duplicate_chars (ret: 'dfjkls') + ['dfjkls'] + """ + f = _trim_arity(f) + def z(*paArgs): + thisFunc = f.__name__ + s,l,t = paArgs[-3:] + if len(paArgs)>3: + thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc + sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) ) + try: + ret = f(*paArgs) + except Exception as exc: + sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) + raise + sys.stderr.write( "<<leaving %s (ret: %r)\n" % (thisFunc,ret) ) + return ret + try: + z.__name__ = f.__name__ + except AttributeError: + pass + return z + +# +# global helpers +# +def delimitedList( expr, delim=",", combine=False ): + """ + Helper to define a delimited list of expressions - the delimiter defaults to ','. + By default, the list elements and delimiters can have intervening whitespace, and + comments, but this can be overridden by passing C{combine=True} in the constructor. + If C{combine} is set to C{True}, the matching tokens are returned as a single token + string, with the delimiters included; otherwise, the matching tokens are returned + as a list of tokens, with the delimiters suppressed. + + Example:: + delimitedList(Word(alphas)).parseString("aa,bb,cc") # -> ['aa', 'bb', 'cc'] + delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE'] + """ + dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." + if combine: + return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) + else: + return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) + +def countedArray( expr, intExpr=None ): + """ + Helper to define a counted list of expressions. + This helper defines a pattern of the form:: + integer expr expr expr... + where the leading integer tells how many expr expressions follow. + The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed. + + If C{intExpr} is specified, it should be a pyparsing expression that produces an integer value. + + Example:: + countedArray(Word(alphas)).parseString('2 ab cd ef') # -> ['ab', 'cd'] + + # in this parser, the leading integer value is given in binary, + # '10' indicating that 2 values are in the array + binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2)) + countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef') # -> ['ab', 'cd'] + """ + arrayExpr = Forward() + def countFieldParseAction(s,l,t): + n = t[0] + arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) + return [] + if intExpr is None: + intExpr = Word(nums).setParseAction(lambda t:int(t[0])) + else: + intExpr = intExpr.copy() + intExpr.setName("arrayLen") + intExpr.addParseAction(countFieldParseAction, callDuringTry=True) + return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...') + +def _flatten(L): + ret = [] + for i in L: + if isinstance(i,list): + ret.extend(_flatten(i)) + else: + ret.append(i) + return ret + +def matchPreviousLiteral(expr): + """ + Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks + for a 'repeat' of a previous expression. For example:: + first = Word(nums) + second = matchPreviousLiteral(first) + matchExpr = first + ":" + second + will match C{"1:1"}, but not C{"1:2"}. Because this matches a + previous literal, will also match the leading C{"1:1"} in C{"1:10"}. + If this is not desired, use C{matchPreviousExpr}. + Do I{not} use with packrat parsing enabled. + """ + rep = Forward() + def copyTokenToRepeater(s,l,t): + if t: + if len(t) == 1: + rep << t[0] + else: + # flatten t tokens + tflat = _flatten(t.asList()) + rep << And(Literal(tt) for tt in tflat) + else: + rep << Empty() + expr.addParseAction(copyTokenToRepeater, callDuringTry=True) + rep.setName('(prev) ' + _ustr(expr)) + return rep + +def matchPreviousExpr(expr): + """ + Helper to define an expression that is indirectly defined from + the tokens matched in a previous expression, that is, it looks + for a 'repeat' of a previous expression. For example:: + first = Word(nums) + second = matchPreviousExpr(first) + matchExpr = first + ":" + second + will match C{"1:1"}, but not C{"1:2"}. Because this matches by + expressions, will I{not} match the leading C{"1:1"} in C{"1:10"}; + the expressions are evaluated first, and then compared, so + C{"1"} is compared with C{"10"}. + Do I{not} use with packrat parsing enabled. + """ + rep = Forward() + e2 = expr.copy() + rep <<= e2 + def copyTokenToRepeater(s,l,t): + matchTokens = _flatten(t.asList()) + def mustMatchTheseTokens(s,l,t): + theseTokens = _flatten(t.asList()) + if theseTokens != matchTokens: + raise ParseException("",0,"") + rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) + expr.addParseAction(copyTokenToRepeater, callDuringTry=True) + rep.setName('(prev) ' + _ustr(expr)) + return rep + +def _escapeRegexRangeChars(s): + #~ escape these chars: ^-] + for c in r"\^-]": + s = s.replace(c,_bslash+c) + s = s.replace("\n",r"\n") + s = s.replace("\t",r"\t") + return _ustr(s) + +def oneOf( strs, caseless=False, useRegex=True ): + """ + Helper to quickly define a set of alternative Literals, and makes sure to do + longest-first testing when there is a conflict, regardless of the input order, + but returns a C{L{MatchFirst}} for best performance. + + Parameters: + - strs - a string of space-delimited literals, or a collection of string literals + - caseless - (default=C{False}) - treat all literals as caseless + - useRegex - (default=C{True}) - as an optimization, will generate a Regex + object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or + if creating a C{Regex} raises an exception) + + Example:: + comp_oper = oneOf("< = > <= >= !=") + var = Word(alphas) + number = Word(nums) + term = var | number + comparison_expr = term + comp_oper + term + print(comparison_expr.searchString("B = 12 AA=23 B<=AA AA>12")) + prints:: + [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] + """ + if caseless: + isequal = ( lambda a,b: a.upper() == b.upper() ) + masks = ( lambda a,b: b.upper().startswith(a.upper()) ) + parseElementClass = CaselessLiteral + else: + isequal = ( lambda a,b: a == b ) + masks = ( lambda a,b: b.startswith(a) ) + parseElementClass = Literal + + symbols = [] + if isinstance(strs,basestring): + symbols = strs.split() + elif isinstance(strs, Iterable): + symbols = list(strs) + else: + warnings.warn("Invalid argument to oneOf, expected string or iterable", + SyntaxWarning, stacklevel=2) + if not symbols: + return NoMatch() + + i = 0 + while i < len(symbols)-1: + cur = symbols[i] + for j,other in enumerate(symbols[i+1:]): + if ( isequal(other, cur) ): + del symbols[i+j+1] + break + elif ( masks(cur, other) ): + del symbols[i+j+1] + symbols.insert(i,other) + cur = other + break + else: + i += 1 + + if not caseless and useRegex: + #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) + try: + if len(symbols)==len("".join(symbols)): + return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols)) + else: + return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols)) + except Exception: + warnings.warn("Exception creating Regex for oneOf, building MatchFirst", + SyntaxWarning, stacklevel=2) + + + # last resort, just use MatchFirst + return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols)) + +def dictOf( key, value ): + """ + Helper to easily and clearly define a dictionary by specifying the respective patterns + for the key and value. Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens + in the proper order. The key pattern can include delimiting markers or punctuation, + as long as they are suppressed, thereby leaving the significant key text. The value + pattern can include named results, so that the C{Dict} results can include named token + fields. + + Example:: + text = "shape: SQUARE posn: upper left color: light blue texture: burlap" + attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) + print(OneOrMore(attr_expr).parseString(text).dump()) + + attr_label = label + attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join) + + # similar to Dict, but simpler call format + result = dictOf(attr_label, attr_value).parseString(text) + print(result.dump()) + print(result['shape']) + print(result.shape) # object attribute access works too + print(result.asDict()) + prints:: + [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] + - color: light blue + - posn: upper left + - shape: SQUARE + - texture: burlap + SQUARE + SQUARE + {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'} + """ + return Dict( ZeroOrMore( Group ( key + value ) ) ) + +def originalTextFor(expr, asString=True): + """ + Helper to return the original, untokenized text for a given expression. Useful to + restore the parsed fields of an HTML start tag into the raw tag text itself, or to + revert separate tokens with intervening whitespace back to the original matching + input text. By default, returns astring containing the original parsed text. + + If the optional C{asString} argument is passed as C{False}, then the return value is a + C{L{ParseResults}} containing any results names that were originally matched, and a + single token containing the original matched text from the input string. So if + the expression passed to C{L{originalTextFor}} contains expressions with defined + results names, you must set C{asString} to C{False} if you want to preserve those + results name values. + + Example:: + src = "this is test <b> bold <i>text</i> </b> normal text " + for tag in ("b","i"): + opener,closer = makeHTMLTags(tag) + patt = originalTextFor(opener + SkipTo(closer) + closer) + print(patt.searchString(src)[0]) + prints:: + ['<b> bold <i>text</i> </b>'] + ['<i>text</i>'] + """ + locMarker = Empty().setParseAction(lambda s,loc,t: loc) + endlocMarker = locMarker.copy() + endlocMarker.callPreparse = False + matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") + if asString: + extractText = lambda s,l,t: s[t._original_start:t._original_end] + else: + def extractText(s,l,t): + t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]] + matchExpr.setParseAction(extractText) + matchExpr.ignoreExprs = expr.ignoreExprs + return matchExpr + +def ungroup(expr): + """ + Helper to undo pyparsing's default grouping of And expressions, even + if all but one are non-empty. + """ + return TokenConverter(expr).setParseAction(lambda t:t[0]) + +def locatedExpr(expr): + """ + Helper to decorate a returned token with its starting and ending locations in the input string. + This helper adds the following results names: + - locn_start = location where matched expression begins + - locn_end = location where matched expression ends + - value = the actual parsed results + + Be careful if the input text contains C{<TAB>} characters, you may want to call + C{L{ParserElement.parseWithTabs}} + + Example:: + wd = Word(alphas) + for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"): + print(match) + prints:: + [[0, 'ljsdf', 5]] + [[8, 'lksdjjf', 15]] + [[18, 'lkkjj', 23]] + """ + locator = Empty().setParseAction(lambda s,l,t: l) + return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end")) + + +# convenience constants for positional expressions +empty = Empty().setName("empty") +lineStart = LineStart().setName("lineStart") +lineEnd = LineEnd().setName("lineEnd") +stringStart = StringStart().setName("stringStart") +stringEnd = StringEnd().setName("stringEnd") + +_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) +_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16))) +_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8))) +_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1) +_charRange = Group(_singleChar + Suppress("-") + _singleChar) +_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" + +def srange(s): + r""" + Helper to easily define string ranges for use in Word construction. Borrows + syntax from regexp '[]' string range definitions:: + srange("[0-9]") -> "0123456789" + srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" + srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" + The input string must be enclosed in []'s, and the returned string is the expanded + character set joined into a single string. + The values enclosed in the []'s may be: + - a single character + - an escaped character with a leading backslash (such as C{\-} or C{\]}) + - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character) + (C{\0x##} is also supported for backwards compatibility) + - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character) + - a range of any of the above, separated by a dash (C{'a-z'}, etc.) + - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.) + """ + _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1)) + try: + return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body) + except Exception: + return "" + +def matchOnlyAtCol(n): + """ + Helper method for defining parse actions that require matching at a specific + column in the input text. + """ + def verifyCol(strg,locn,toks): + if col(locn,strg) != n: + raise ParseException(strg,locn,"matched token not at column %d" % n) + return verifyCol + +def replaceWith(replStr): + """ + Helper method for common parse actions that simply return a literal value. Especially + useful when used with C{L{transformString<ParserElement.transformString>}()}. + + Example:: + num = Word(nums).setParseAction(lambda toks: int(toks[0])) + na = oneOf("N/A NA").setParseAction(replaceWith(math.nan)) + term = na | num + + OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234] + """ + return lambda s,l,t: [replStr] + +def removeQuotes(s,l,t): + """ + Helper parse action for removing quotation marks from parsed quoted strings. + + Example:: + # by default, quotation marks are included in parsed results + quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"] + + # use removeQuotes to strip quotation marks from parsed results + quotedString.setParseAction(removeQuotes) + quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"] + """ + return t[0][1:-1] + +def tokenMap(func, *args): + """ + Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional + args are passed, they are forwarded to the given function as additional arguments after + the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the + parsed data to an integer using base 16. + + Example (compare the last to example in L{ParserElement.transformString}:: + hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16)) + hex_ints.runTests(''' + 00 11 22 aa FF 0a 0d 1a + ''') + + upperword = Word(alphas).setParseAction(tokenMap(str.upper)) + OneOrMore(upperword).runTests(''' + my kingdom for a horse + ''') + + wd = Word(alphas).setParseAction(tokenMap(str.title)) + OneOrMore(wd).setParseAction(' '.join).runTests(''' + now is the winter of our discontent made glorious summer by this sun of york + ''') + prints:: + 00 11 22 aa FF 0a 0d 1a + [0, 17, 34, 170, 255, 10, 13, 26] + + my kingdom for a horse + ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE'] + + now is the winter of our discontent made glorious summer by this sun of york + ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York'] + """ + def pa(s,l,t): + return [func(tokn, *args) for tokn in t] + + try: + func_name = getattr(func, '__name__', + getattr(func, '__class__').__name__) + except Exception: + func_name = str(func) + pa.__name__ = func_name + + return pa + +upcaseTokens = tokenMap(lambda t: _ustr(t).upper()) +"""(Deprecated) Helper parse action to convert tokens to upper case. Deprecated in favor of L{pyparsing_common.upcaseTokens}""" + +downcaseTokens = tokenMap(lambda t: _ustr(t).lower()) +"""(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of L{pyparsing_common.downcaseTokens}""" + +def _makeTags(tagStr, xml): + """Internal helper to construct opening and closing tag expressions, given a tag name""" + if isinstance(tagStr,basestring): + resname = tagStr + tagStr = Keyword(tagStr, caseless=not xml) + else: + resname = tagStr.name + + tagAttrName = Word(alphas,alphanums+"_-:") + if (xml): + tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) + openTag = Suppress("<") + tagStr("tag") + \ + Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + else: + printablesLessRAbrack = "".join(c for c in printables if c not in ">") + tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) + openTag = Suppress("<") + tagStr("tag") + \ + Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ + Optional( Suppress("=") + tagAttrValue ) ))) + \ + Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") + closeTag = Combine(_L("</") + tagStr + ">") + + openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname) + closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % resname) + openTag.tag = resname + closeTag.tag = resname + return openTag, closeTag + +def makeHTMLTags(tagStr): + """ + Helper to construct opening and closing tag expressions for HTML, given a tag name. Matches + tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values. + + Example:: + text = '<td>More info at the <a href="http://pyparsing.wikispaces.com">pyparsing</a> wiki page</td>' + # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple + a,a_end = makeHTMLTags("A") + link_expr = a + SkipTo(a_end)("link_text") + a_end + + for link in link_expr.searchString(text): + # attributes in the <A> tag (like "href" shown here) are also accessible as named results + print(link.link_text, '->', link.href) + prints:: + pyparsing -> http://pyparsing.wikispaces.com + """ + return _makeTags( tagStr, False ) + +def makeXMLTags(tagStr): + """ + Helper to construct opening and closing tag expressions for XML, given a tag name. Matches + tags only in the given upper/lower case. + + Example: similar to L{makeHTMLTags} + """ + return _makeTags( tagStr, True ) + +def withAttribute(*args,**attrDict): + """ + Helper to create a validating parse action to be used with start tags created + with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag + with a required attribute value, to avoid false matches on common tags such as + C{<TD>} or C{<DIV>}. + + Call C{withAttribute} with a series of attribute names and values. Specify the list + of filter attributes names and values as: + - keyword arguments, as in C{(align="right")}, or + - as an explicit dict with C{**} operator, when an attribute name is also a Python + reserved word, as in C{**{"class":"Customer", "align":"right"}} + - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) + For attribute names with a namespace prefix, you must use the second form. Attribute + names are matched insensitive to upper/lower case. + + If just testing for C{class} (with or without a namespace), use C{L{withClass}}. + + To verify that the attribute exists, but without specifying a value, pass + C{withAttribute.ANY_VALUE} as the value. + + Example:: + html = ''' + <div> + Some text + <div type="grid">1 4 0 1 0</div> + <div type="graph">1,3 2,3 1,1</div> + <div>this has no type</div> + </div> + + ''' + div,div_end = makeHTMLTags("div") + + # only match div tag having a type attribute with value "grid" + div_grid = div().setParseAction(withAttribute(type="grid")) + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + # construct a match with any div tag having a type attribute, regardless of the value + div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + if args: + attrs = args[:] + else: + attrs = attrDict.items() + attrs = [(k,v) for k,v in attrs] + def pa(s,l,tokens): + for attrName,attrValue in attrs: + if attrName not in tokens: + raise ParseException(s,l,"no matching attribute " + attrName) + if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: + raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % + (attrName, tokens[attrName], attrValue)) + return pa +withAttribute.ANY_VALUE = object() + +def withClass(classname, namespace=''): + """ + Simplified version of C{L{withAttribute}} when matching on a div class - made + difficult because C{class} is a reserved word in Python. + + Example:: + html = ''' + <div> + Some text + <div class="grid">1 4 0 1 0</div> + <div class="graph">1,3 2,3 1,1</div> + <div>this &lt;div&gt; has no class</div> + </div> + + ''' + div,div_end = makeHTMLTags("div") + div_grid = div().setParseAction(withClass("grid")) + + grid_expr = div_grid + SkipTo(div | div_end)("body") + for grid_header in grid_expr.searchString(html): + print(grid_header.body) + + div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) + div_expr = div_any_type + SkipTo(div | div_end)("body") + for div_header in div_expr.searchString(html): + print(div_header.body) + prints:: + 1 4 0 1 0 + + 1 4 0 1 0 + 1,3 2,3 1,1 + """ + classattr = "%s:class" % namespace if namespace else "class" + return withAttribute(**{classattr : classname}) + +opAssoc = _Constants() +opAssoc.LEFT = object() +opAssoc.RIGHT = object() + +def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): + """ + Helper method for constructing grammars of expressions made up of + operators working in a precedence hierarchy. Operators may be unary or + binary, left- or right-associative. Parse actions can also be attached + to operator expressions. The generated parser will also recognize the use + of parentheses to override operator precedences (see example below). + + Note: if you define a deep operator list, you may see performance issues + when using infixNotation. See L{ParserElement.enablePackrat} for a + mechanism to potentially improve your parser performance. + + Parameters: + - baseExpr - expression representing the most basic element for the nested + - opList - list of tuples, one for each operator precedence level in the + expression grammar; each tuple is of the form + (opExpr, numTerms, rightLeftAssoc, parseAction), where: + - opExpr is the pyparsing expression for the operator; + may also be a string, which will be converted to a Literal; + if numTerms is 3, opExpr is a tuple of two expressions, for the + two operators separating the 3 terms + - numTerms is the number of terms for this operator (must + be 1, 2, or 3) + - rightLeftAssoc is the indicator whether the operator is + right or left associative, using the pyparsing-defined + constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. + - parseAction is the parse action to be associated with + expressions matching this operator expression (the + parse action tuple member may be omitted); if the parse action + is passed a tuple or list of functions, this is equivalent to + calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) + - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) + - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) + + Example:: + # simple example of four-function arithmetic with ints and variable names + integer = pyparsing_common.signed_integer + varname = pyparsing_common.identifier + + arith_expr = infixNotation(integer | varname, + [ + ('-', 1, opAssoc.RIGHT), + (oneOf('* /'), 2, opAssoc.LEFT), + (oneOf('+ -'), 2, opAssoc.LEFT), + ]) + + arith_expr.runTests(''' + 5+3*6 + (5+3)*6 + -2--11 + ''', fullDump=False) + prints:: + 5+3*6 + [[5, '+', [3, '*', 6]]] + + (5+3)*6 + [[[5, '+', 3], '*', 6]] + + -2--11 + [[['-', 2], '-', ['-', 11]]] + """ + ret = Forward() + lastExpr = baseExpr | ( lpar + ret + rpar ) + for i,operDef in enumerate(opList): + opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] + termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr + if arity == 3: + if opExpr is None or len(opExpr) != 2: + raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") + opExpr1, opExpr2 = opExpr + thisExpr = Forward().setName(termName) + if rightLeftAssoc == opAssoc.LEFT: + if arity == 1: + matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) + else: + matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ + Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + elif rightLeftAssoc == opAssoc.RIGHT: + if arity == 1: + # try to avoid LR with this extra test + if not isinstance(opExpr, Optional): + opExpr = Optional(opExpr) + matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) + elif arity == 2: + if opExpr is not None: + matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) + else: + matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) + elif arity == 3: + matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ + Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) + else: + raise ValueError("operator must be unary (1), binary (2), or ternary (3)") + else: + raise ValueError("operator must indicate right or left associativity") + if pa: + if isinstance(pa, (tuple, list)): + matchExpr.setParseAction(*pa) + else: + matchExpr.setParseAction(pa) + thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) + lastExpr = thisExpr + ret <<= lastExpr + return ret + +operatorPrecedence = infixNotation +"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release.""" + +dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") +sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") +quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| + Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") +unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") + +def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): + """ + Helper method for defining nested lists enclosed in opening and closing + delimiters ("(" and ")" are the default). + + Parameters: + - opener - opening character for a nested list (default=C{"("}); can also be a pyparsing expression + - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression + - content - expression for items within the nested lists (default=C{None}) + - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{quotedString}) + + If an expression is not provided for the content argument, the nested + expression will capture all whitespace-delimited content between delimiters + as a list of separate values. + + Use the C{ignoreExpr} argument to define expressions that may contain + opening or closing characters that should not be treated as opening + or closing characters for nesting, such as quotedString or a comment + expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. + The default is L{quotedString}, but if no expressions are to be ignored, + then pass C{None} for this argument. + + Example:: + data_type = oneOf("void int short long char float double") + decl_data_type = Combine(data_type + Optional(Word('*'))) + ident = Word(alphas+'_', alphanums+'_') + number = pyparsing_common.number + arg = Group(decl_data_type + ident) + LPAR,RPAR = map(Suppress, "()") + + code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) + + c_function = (decl_data_type("type") + + ident("name") + + LPAR + Optional(delimitedList(arg), [])("args") + RPAR + + code_body("body")) + c_function.ignore(cStyleComment) + + source_code = ''' + int is_odd(int x) { + return (x%2); + } + + int dec_to_hex(char hchar) { + if (hchar >= '0' && hchar <= '9') { + return (ord(hchar)-ord('0')); + } else { + return (10+ord(hchar)-ord('A')); + } + } + ''' + for func in c_function.searchString(source_code): + print("%(name)s (%(type)s) args: %(args)s" % func) + + prints:: + is_odd (int) args: [['int', 'x']] + dec_to_hex (int) args: [['char', 'hchar']] + """ + if opener == closer: + raise ValueError("opening and closing strings cannot be the same") + if content is None: + if isinstance(opener,basestring) and isinstance(closer,basestring): + if len(opener) == 1 and len(closer)==1: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS + ).setParseAction(lambda t:t[0].strip())) + else: + if ignoreExpr is not None: + content = (Combine(OneOrMore(~ignoreExpr + + ~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + + CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) + ).setParseAction(lambda t:t[0].strip())) + else: + raise ValueError("opening and closing arguments must be strings if no content expression is given") + ret = Forward() + if ignoreExpr is not None: + ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) + else: + ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) + ret.setName('nested %s%s expression' % (opener,closer)) + return ret + +def indentedBlock(blockStatementExpr, indentStack, indent=True): + """ + Helper method for defining space-delimited indentation blocks, such as + those used to define block statements in Python source code. + + Parameters: + - blockStatementExpr - expression defining syntax of statement that + is repeated within the indented block + - indentStack - list created by caller to manage indentation stack + (multiple statementWithIndentedBlock expressions within a single grammar + should share a common indentStack) + - indent - boolean indicating whether block must be indented beyond the + the current level; set to False for block of left-most statements + (default=C{True}) + + A valid block must contain at least one C{blockStatement}. + + Example:: + data = ''' + def A(z): + A1 + B = 100 + G = A2 + A2 + A3 + B + def BB(a,b,c): + BB1 + def BBA(): + bba1 + bba2 + bba3 + C + D + def spam(x,y): + def eggs(z): + pass + ''' + + + indentStack = [1] + stmt = Forward() + + identifier = Word(alphas, alphanums) + funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") + func_body = indentedBlock(stmt, indentStack) + funcDef = Group( funcDecl + func_body ) + + rvalue = Forward() + funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") + rvalue << (funcCall | identifier | Word(nums)) + assignment = Group(identifier + "=" + rvalue) + stmt << ( funcDef | assignment | identifier ) + + module_body = OneOrMore(stmt) + + parseTree = module_body.parseString(data) + parseTree.pprint() + prints:: + [['def', + 'A', + ['(', 'z', ')'], + ':', + [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], + 'B', + ['def', + 'BB', + ['(', 'a', 'b', 'c', ')'], + ':', + [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], + 'C', + 'D', + ['def', + 'spam', + ['(', 'x', 'y', ')'], + ':', + [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] + """ + def checkPeerIndent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if curCol != indentStack[-1]: + if curCol > indentStack[-1]: + raise ParseFatalException(s,l,"illegal nesting") + raise ParseException(s,l,"not a peer entry") + + def checkSubIndent(s,l,t): + curCol = col(l,s) + if curCol > indentStack[-1]: + indentStack.append( curCol ) + else: + raise ParseException(s,l,"not a subentry") + + def checkUnindent(s,l,t): + if l >= len(s): return + curCol = col(l,s) + if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): + raise ParseException(s,l,"not an unindent") + indentStack.pop() + + NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) + INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') + PEER = Empty().setParseAction(checkPeerIndent).setName('') + UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') + if indent: + smExpr = Group( Optional(NL) + + #~ FollowedBy(blockStatementExpr) + + INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) + else: + smExpr = Group( Optional(NL) + + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) + blockStatementExpr.ignore(_bslash + LineEnd()) + return smExpr.setName('indented block') + +alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") +punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") + +anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) +_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) +commonHTMLEntity = Regex('&(?P<entity>' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") +def replaceHTMLEntity(t): + """Helper parser action to replace common HTML entities with their special characters""" + return _htmlEntityMap.get(t.entity) + +# it's easy to get these comment structures wrong - they're very common, so may as well make them available +cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") +"Comment of the form C{/* ... */}" + +htmlComment = Regex(r"<!--[\s\S]*?-->").setName("HTML comment") +"Comment of the form C{<!-- ... -->}" + +restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") +dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") +"Comment of the form C{// ... (to end of line)}" + +cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") +"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}" + +javaStyleComment = cppStyleComment +"Same as C{L{cppStyleComment}}" + +pythonStyleComment = Regex(r"#.*").setName("Python style comment") +"Comment of the form C{# ... (to end of line)}" + +_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + + Optional( Word(" \t") + + ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") +commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") +"""(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. + This expression is deprecated in favor of L{pyparsing_common.comma_separated_list}.""" + +# some other useful expressions - using lower-case class name since we are really using this as a namespace +class pyparsing_common: + """ + Here are some common low-level expressions that may be useful in jump-starting parser development: + - numeric forms (L{integers<integer>}, L{reals<real>}, L{scientific notation<sci_real>}) + - common L{programming identifiers<identifier>} + - network addresses (L{MAC<mac_address>}, L{IPv4<ipv4_address>}, L{IPv6<ipv6_address>}) + - ISO8601 L{dates<iso8601_date>} and L{datetime<iso8601_datetime>} + - L{UUID<uuid>} + - L{comma-separated list<comma_separated_list>} + Parse actions: + - C{L{convertToInteger}} + - C{L{convertToFloat}} + - C{L{convertToDate}} + - C{L{convertToDatetime}} + - C{L{stripHTMLTags}} + - C{L{upcaseTokens}} + - C{L{downcaseTokens}} + + Example:: + pyparsing_common.number.runTests(''' + # any int or real number, returned as the appropriate type + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.fnumber.runTests(''' + # any int or real number, returned as float + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + ''') + + pyparsing_common.hex_integer.runTests(''' + # hex numbers + 100 + FF + ''') + + pyparsing_common.fraction.runTests(''' + # fractions + 1/2 + -3/4 + ''') + + pyparsing_common.mixed_integer.runTests(''' + # mixed fractions + 1 + 1/2 + -3/4 + 1-3/4 + ''') + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(''' + # uuid + 12345678-1234-5678-1234-567812345678 + ''') + prints:: + # any int or real number, returned as the appropriate type + 100 + [100] + + -100 + [-100] + + +100 + [100] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # any int or real number, returned as float + 100 + [100.0] + + -100 + [-100.0] + + +100 + [100.0] + + 3.14159 + [3.14159] + + 6.02e23 + [6.02e+23] + + 1e-12 + [1e-12] + + # hex numbers + 100 + [256] + + FF + [255] + + # fractions + 1/2 + [0.5] + + -3/4 + [-0.75] + + # mixed fractions + 1 + [1] + + 1/2 + [0.5] + + -3/4 + [-0.75] + + 1-3/4 + [1.75] + + # uuid + 12345678-1234-5678-1234-567812345678 + [UUID('12345678-1234-5678-1234-567812345678')] + """ + + convertToInteger = tokenMap(int) + """ + Parse action for converting parsed integers to Python int + """ + + convertToFloat = tokenMap(float) + """ + Parse action for converting parsed numbers to Python float + """ + + integer = Word(nums).setName("integer").setParseAction(convertToInteger) + """expression that parses an unsigned integer, returns an int""" + + hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) + """expression that parses a hexadecimal integer, returns an int""" + + signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) + """expression that parses an integer with optional leading sign, returns an int""" + + fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") + """fractional expression of an integer divided by an integer, returns a float""" + fraction.addParseAction(lambda t: t[0]/t[-1]) + + mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") + """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" + mixed_integer.addParseAction(sum) + + real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) + """expression that parses a floating point number and returns a float""" + + sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) + """expression that parses a floating point number with optional scientific notation and returns a float""" + + # streamlining this expression makes the docs nicer-looking + number = (sci_real | real | signed_integer).streamline() + """any numeric expression, returns the corresponding Python type""" + + fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) + """any int or real number, returned as float""" + + identifier = Word(alphas+'_', alphanums+'_').setName("identifier") + """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" + + ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") + "IPv4 address (C{0.0.0.0 - 255.255.255.255})" + + _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") + _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") + _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") + _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) + _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") + ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") + "IPv6 address (long, short, or mixed form)" + + mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") + "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" + + @staticmethod + def convertToDate(fmt="%Y-%m-%d"): + """ + Helper to create a parse action for converting parsed date string to Python datetime.date + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"}) + + Example:: + date_expr = pyparsing_common.iso8601_date.copy() + date_expr.setParseAction(pyparsing_common.convertToDate()) + print(date_expr.parseString("1999-12-31")) + prints:: + [datetime.date(1999, 12, 31)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt).date() + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + @staticmethod + def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): + """ + Helper to create a parse action for converting parsed datetime string to Python datetime.datetime + + Params - + - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"}) + + Example:: + dt_expr = pyparsing_common.iso8601_datetime.copy() + dt_expr.setParseAction(pyparsing_common.convertToDatetime()) + print(dt_expr.parseString("1999-12-31T23:59:59.999")) + prints:: + [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] + """ + def cvt_fn(s,l,t): + try: + return datetime.strptime(t[0], fmt) + except ValueError as ve: + raise ParseException(s, l, str(ve)) + return cvt_fn + + iso8601_date = Regex(r'(?P<year>\d{4})(?:-(?P<month>\d\d)(?:-(?P<day>\d\d))?)?').setName("ISO8601 date") + "ISO8601 date (C{yyyy-mm-dd})" + + iso8601_datetime = Regex(r'(?P<year>\d{4})-(?P<month>\d\d)-(?P<day>\d\d)[T ](?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d(\.\d*)?)?)?(?P<tz>Z|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") + "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}" + + uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") + "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})" + + _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() + @staticmethod + def stripHTMLTags(s, l, tokens): + """ + Parse action to remove HTML tags from web page HTML source + + Example:: + # strip HTML links from normal text + text = '<td>More info at the <a href="http://pyparsing.wikispaces.com">pyparsing</a> wiki page</td>' + td,td_end = makeHTMLTags("TD") + table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end + + print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page' + """ + return pyparsing_common._html_stripper.transformString(tokens[0]) + + _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') + + Optional( White(" \t") ) ) ).streamline().setName("commaItem") + comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") + """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" + + upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) + """Parse action to convert tokens to upper case.""" + + downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) + """Parse action to convert tokens to lower case.""" + + +if __name__ == "__main__": + + selectToken = CaselessLiteral("select") + fromToken = CaselessLiteral("from") + + ident = Word(alphas, alphanums + "_$") + + columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + columnNameList = Group(delimitedList(columnName)).setName("columns") + columnSpec = ('*' | columnNameList) + + tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) + tableNameList = Group(delimitedList(tableName)).setName("tables") + + simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") + + # demo runTests method, including embedded comments in test string + simpleSQL.runTests(""" + # '*' as column list and dotted table name + select * from SYS.XYZZY + + # caseless match on "SELECT", and casts back to "select" + SELECT * from XYZZY, ABC + + # list of column names, and mixed case SELECT keyword + Select AA,BB,CC from Sys.dual + + # multiple tables + Select A, B, C from Sys.dual, Table2 + + # invalid SELECT keyword - should fail + Xelect A, B, C from Sys.dual + + # incomplete command - should fail + Select + + # invalid column name - should fail + Select ^^^ frox Sys.dual + + """) + + pyparsing_common.number.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + # any int or real number, returned as float + pyparsing_common.fnumber.runTests(""" + 100 + -100 + +100 + 3.14159 + 6.02e23 + 1e-12 + """) + + pyparsing_common.hex_integer.runTests(""" + 100 + FF + """) + + import uuid + pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) + pyparsing_common.uuid.runTests(""" + 12345678-1234-5678-1234-567812345678 + """) diff --git a/lib/setuptools/_vendor/six.py b/lib/setuptools/_vendor/six.py new file mode 100644 index 0000000..190c023 --- /dev/null +++ b/lib/setuptools/_vendor/six.py @@ -0,0 +1,868 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +# Copyright (c) 2010-2015 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson <benjamin@python.org>" +__version__ = "1.10.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + if from_value is None: + raise value + raise value from from_value +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + raise value from from_value +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/lib/setuptools/archive_util.py b/lib/setuptools/archive_util.py new file mode 100644 index 0000000..8143604 --- /dev/null +++ b/lib/setuptools/archive_util.py @@ -0,0 +1,173 @@ +"""Utilities for extracting common archive formats""" + +import zipfile +import tarfile +import os +import shutil +import posixpath +import contextlib +from distutils.errors import DistutilsError + +from pkg_resources import ensure_directory + +__all__ = [ + "unpack_archive", "unpack_zipfile", "unpack_tarfile", "default_filter", + "UnrecognizedFormat", "extraction_drivers", "unpack_directory", +] + + +class UnrecognizedFormat(DistutilsError): + """Couldn't recognize the archive type""" + + +def default_filter(src, dst): + """The default progress/filter callback; returns True for all files""" + return dst + + +def unpack_archive(filename, extract_dir, progress_filter=default_filter, + drivers=None): + """Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat`` + + `progress_filter` is a function taking two arguments: a source path + internal to the archive ('/'-separated), and a filesystem path where it + will be extracted. The callback must return the desired extract path + (which may be the same as the one passed in), or else ``None`` to skip + that file or directory. The callback can thus be used to report on the + progress of the extraction, as well as to filter the items extracted or + alter their extraction paths. + + `drivers`, if supplied, must be a non-empty sequence of functions with the + same signature as this function (minus the `drivers` argument), that raise + ``UnrecognizedFormat`` if they do not support extracting the designated + archive type. The `drivers` are tried in sequence until one is found that + does not raise an error, or until all are exhausted (in which case + ``UnrecognizedFormat`` is raised). If you do not supply a sequence of + drivers, the module's ``extraction_drivers`` constant will be used, which + means that ``unpack_zipfile`` and ``unpack_tarfile`` will be tried, in that + order. + """ + for driver in drivers or extraction_drivers: + try: + driver(filename, extract_dir, progress_filter) + except UnrecognizedFormat: + continue + else: + return + else: + raise UnrecognizedFormat( + "Not a recognized archive type: %s" % filename + ) + + +def unpack_directory(filename, extract_dir, progress_filter=default_filter): + """"Unpack" a directory, using the same interface as for archives + + Raises ``UnrecognizedFormat`` if `filename` is not a directory + """ + if not os.path.isdir(filename): + raise UnrecognizedFormat("%s is not a directory" % filename) + + paths = { + filename: ('', extract_dir), + } + for base, dirs, files in os.walk(filename): + src, dst = paths[base] + for d in dirs: + paths[os.path.join(base, d)] = src + d + '/', os.path.join(dst, d) + for f in files: + target = os.path.join(dst, f) + target = progress_filter(src + f, target) + if not target: + # skip non-files + continue + ensure_directory(target) + f = os.path.join(base, f) + shutil.copyfile(f, target) + shutil.copystat(f, target) + + +def unpack_zipfile(filename, extract_dir, progress_filter=default_filter): + """Unpack zip `filename` to `extract_dir` + + Raises ``UnrecognizedFormat`` if `filename` is not a zipfile (as determined + by ``zipfile.is_zipfile()``). See ``unpack_archive()`` for an explanation + of the `progress_filter` argument. + """ + + if not zipfile.is_zipfile(filename): + raise UnrecognizedFormat("%s is not a zip file" % (filename,)) + + with zipfile.ZipFile(filename) as z: + for info in z.infolist(): + name = info.filename + + # don't extract absolute paths or ones with .. in them + if name.startswith('/') or '..' in name.split('/'): + continue + + target = os.path.join(extract_dir, *name.split('/')) + target = progress_filter(name, target) + if not target: + continue + if name.endswith('/'): + # directory + ensure_directory(target) + else: + # file + ensure_directory(target) + data = z.read(info.filename) + with open(target, 'wb') as f: + f.write(data) + unix_attributes = info.external_attr >> 16 + if unix_attributes: + os.chmod(target, unix_attributes) + + +def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): + """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir` + + Raises ``UnrecognizedFormat`` if `filename` is not a tarfile (as determined + by ``tarfile.open()``). See ``unpack_archive()`` for an explanation + of the `progress_filter` argument. + """ + try: + tarobj = tarfile.open(filename) + except tarfile.TarError: + raise UnrecognizedFormat( + "%s is not a compressed or uncompressed tar file" % (filename,) + ) + with contextlib.closing(tarobj): + # don't do any chowning! + tarobj.chown = lambda *args: None + for member in tarobj: + name = member.name + # don't extract absolute paths or ones with .. in them + if not name.startswith('/') and '..' not in name.split('/'): + prelim_dst = os.path.join(extract_dir, *name.split('/')) + + # resolve any links and to extract the link targets as normal + # files + while member is not None and (member.islnk() or member.issym()): + linkpath = member.linkname + if member.issym(): + base = posixpath.dirname(member.name) + linkpath = posixpath.join(base, linkpath) + linkpath = posixpath.normpath(linkpath) + member = tarobj._getmember(linkpath) + + if member is not None and (member.isfile() or member.isdir()): + final_dst = progress_filter(name, prelim_dst) + if final_dst: + if final_dst.endswith(os.sep): + final_dst = final_dst[:-1] + try: + # XXX Ugh + tarobj._extract_member(member, final_dst) + except tarfile.ExtractError: + # chown/chmod/mkfifo/mknode/makedev failed + pass + return True + + +extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile diff --git a/lib/setuptools/build_meta.py b/lib/setuptools/build_meta.py new file mode 100644 index 0000000..0067a7a --- /dev/null +++ b/lib/setuptools/build_meta.py @@ -0,0 +1,182 @@ +"""A PEP 517 interface to setuptools + +Previously, when a user or a command line tool (let's call it a "frontend") +needed to make a request of setuptools to take a certain action, for +example, generating a list of installation requirements, the frontend would +would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line. + +PEP 517 defines a different method of interfacing with setuptools. Rather +than calling "setup.py" directly, the frontend should: + + 1. Set the current directory to the directory with a setup.py file + 2. Import this module into a safe python interpreter (one in which + setuptools can potentially set global variables or crash hard). + 3. Call one of the functions defined in PEP 517. + +What each function does is defined in PEP 517. However, here is a "casual" +definition of the functions (this definition should not be relied on for +bug reports or API stability): + + - `build_wheel`: build a wheel in the folder and return the basename + - `get_requires_for_build_wheel`: get the `setup_requires` to build + - `prepare_metadata_for_build_wheel`: get the `install_requires` + - `build_sdist`: build an sdist in the folder and return the basename + - `get_requires_for_build_sdist`: get the `setup_requires` to build + +Again, this is not a formal definition! Just a "taste" of the module. +""" + +import os +import sys +import tokenize +import shutil +import contextlib + +import setuptools +import distutils + + +class SetupRequirementsError(BaseException): + def __init__(self, specifiers): + self.specifiers = specifiers + + +class Distribution(setuptools.dist.Distribution): + def fetch_build_eggs(self, specifiers): + raise SetupRequirementsError(specifiers) + + @classmethod + @contextlib.contextmanager + def patch(cls): + """ + Replace + distutils.dist.Distribution with this class + for the duration of this context. + """ + orig = distutils.core.Distribution + distutils.core.Distribution = cls + try: + yield + finally: + distutils.core.Distribution = orig + + +def _to_str(s): + """ + Convert a filename to a string (on Python 2, explicitly + a byte string, not Unicode) as distutils checks for the + exact type str. + """ + if sys.version_info[0] == 2 and not isinstance(s, str): + # Assume it's Unicode, as that's what the PEP says + # should be provided. + return s.encode(sys.getfilesystemencoding()) + return s + + +def _run_setup(setup_script='setup.py'): + # Note that we can reuse our build directory between calls + # Correctness comes first, then optimization later + __file__ = setup_script + __name__ = '__main__' + f = getattr(tokenize, 'open', open)(__file__) + code = f.read().replace('\\r\\n', '\\n') + f.close() + exec(compile(code, __file__, 'exec'), locals()) + + +def _fix_config(config_settings): + config_settings = config_settings or {} + config_settings.setdefault('--global-option', []) + return config_settings + + +def _get_build_requires(config_settings, requirements): + config_settings = _fix_config(config_settings) + + sys.argv = sys.argv[:1] + ['egg_info'] + \ + config_settings["--global-option"] + try: + with Distribution.patch(): + _run_setup() + except SetupRequirementsError as e: + requirements += e.specifiers + + return requirements + + +def _get_immediate_subdirectories(a_dir): + return [name for name in os.listdir(a_dir) + if os.path.isdir(os.path.join(a_dir, name))] + + +def get_requires_for_build_wheel(config_settings=None): + config_settings = _fix_config(config_settings) + return _get_build_requires(config_settings, requirements=['setuptools', 'wheel']) + + +def get_requires_for_build_sdist(config_settings=None): + config_settings = _fix_config(config_settings) + return _get_build_requires(config_settings, requirements=['setuptools']) + + +def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): + sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', _to_str(metadata_directory)] + _run_setup() + + dist_info_directory = metadata_directory + while True: + dist_infos = [f for f in os.listdir(dist_info_directory) + if f.endswith('.dist-info')] + + if len(dist_infos) == 0 and \ + len(_get_immediate_subdirectories(dist_info_directory)) == 1: + dist_info_directory = os.path.join( + dist_info_directory, os.listdir(dist_info_directory)[0]) + continue + + assert len(dist_infos) == 1 + break + + # PEP 517 requires that the .dist-info directory be placed in the + # metadata_directory. To comply, we MUST copy the directory to the root + if dist_info_directory != metadata_directory: + shutil.move( + os.path.join(dist_info_directory, dist_infos[0]), + metadata_directory) + shutil.rmtree(dist_info_directory, ignore_errors=True) + + return dist_infos[0] + + +def build_wheel(wheel_directory, config_settings=None, + metadata_directory=None): + config_settings = _fix_config(config_settings) + wheel_directory = os.path.abspath(wheel_directory) + sys.argv = sys.argv[:1] + ['bdist_wheel'] + \ + config_settings["--global-option"] + _run_setup() + if wheel_directory != 'dist': + shutil.rmtree(wheel_directory) + shutil.copytree('dist', wheel_directory) + + wheels = [f for f in os.listdir(wheel_directory) + if f.endswith('.whl')] + + assert len(wheels) == 1 + return wheels[0] + + +def build_sdist(sdist_directory, config_settings=None): + config_settings = _fix_config(config_settings) + sdist_directory = os.path.abspath(sdist_directory) + sys.argv = sys.argv[:1] + ['sdist'] + \ + config_settings["--global-option"] + \ + ["--dist-dir", sdist_directory] + _run_setup() + + sdists = [f for f in os.listdir(sdist_directory) + if f.endswith('.tar.gz')] + + assert len(sdists) == 1 + return sdists[0] diff --git a/lib/setuptools/cli-32.exe b/lib/setuptools/cli-32.exe new file mode 100644 index 0000000000000000000000000000000000000000..b1487b7819e7286577a043c7726fbe0ca1543083 GIT binary patch literal 65536 zcmeFae|%KMxj%k3yGc&ShO@v10t8qfC>m5WpovRhA=wa=z=p_%6%z1@blsvwI0vv2 zNIY4alVK~j)mwY3trY!Sy|tffZ$+^cObBMdpZutbN^PuECoa`kXb2K>zVBzw<_Fq) zU-$d^{_*|%@qt&)nVIv<%rnnC&oeX6JTqHy>n_PINs<G9rYTAL@TPx0@%--}9r!$a z((i^#&t<$Zd7o|Z8<TGd-?_=NVdM9{v+=gOJh$I=_ub!9J^yrvXQOtv=gzx5rAw<k zcYSZ|9am>%4a-Xw9jfY!Ot@}WQUBkK=MqH|Mf{(O%J6=?F0E)R-u5-_q9XB5EmFjL zRMB1HZ7a&fd)b}0hpCKjVjS>G(qfxk>Uow`_J8Y;?6yo>h9td;lqFW`r_=Cu;je?@ zJ}aCeNvRaYzy7!6vsuJK8t7Ip04X137Vm)<B}y|cNYZo>`v3N5I`@q}=|CK){8#_3 zR`1xV;$zJbJP0ppD|Paae;!F%bM?lxx2d-wfQV@O6ujTW-;jSkRCTolCLPMh2Nx=) zGP{NVA?TB&mP=FqZ|whc3RJSvJUJGyHOs!nBie<k<-z=e)r`kVud+vM0lsONB<Y9b z0<+))qcqReE=`GTutop6y*iN=`x&*3EzZknc4W?3rP&uIJaeXK<D%wvS9N4nkT;0D zPW$-+vpsE9St6ytWVaCXsHU`%GVdR^wE=Xv01fto0vp%r_OvPOWj3j{W@V_Y;fxbp zySskme5v4&(U>PA7G%%m<=|b-UJ~!-boN$bi#jT{Hcy&A=Niq?KHpr`Y-?=MzKk{I zIl-)f*v>o`q`5M7OP+gKtTfLZsOCS(qPDr~x8=!_5`6-VLD0EMY5XaI$Uqq@V-Jap zR-V}6Ja=V~*CHdz@F4Rb<?;{KZ*yd>ij_JtwPEG;g{#zT!Uq*Py$3gDv`Z2tYF|X8 zYEi!^3#I2mi!9?8K!AuX>_C;=ltI=m5eE7*@I4UZ&p}=3ho&bc^h3P|C;`K|s)PJt z@!8GLOb})@Yp*SMou>fLhC@WZw%7ar>1Sm0aW&hPm&@Wqv5z<cJW4gM&zmkfJJ+a@ zj6&r=dVrlbR^{dLe--p{MqAX8%7LY}g_XQXq&T82+UL#6!luP}xs6BE?<fb3E#r6f ze^S%+ZFw$9UEExnmrHC?k~jf28Qa}v(?%Aw6cJb9i=;f%LL7GNV)O&mRYm+WAK2)J zoc6N?AE0A$CG}^`sG(_iS>i_&0GwOEjRhPMrYB*+WA64e$@ELiFO?ay?gvgcC<n$Y z<L^1CK%h$vSZG@q;PL(x?eqG1V1nyS(*z5;SA+M!_HB5xgCaCQzioLANgKIa^30b| zP)0-wnAuW?PuhpB1D*9VD+*d7r2(|XN$tU(8-F?I^V~ojiGY&$x^&Sr^ySP^J_*UW zrARijT__0kuL5&8h*xu#MI`axM$bS5AWndQ;JM+aKJrO?BE}`X#TVcgz$PT9E&8Dq zZ6JXIg6WKy%Zx0-)XbKtWRx0n<OM3tY=>1!dbl2?B=#{!9_2$Llg!~3%n@58CG`RW z1LPlkk=p2eFSa3N`&F?g@~A1mHitQyVq0yNK4^CN8joui^5gTpuf^0f+qMtEYVL?F z$fu`~#PaZA)VQ4Amx;XbZ%EJqQT~UlXZwx7HHW!>vn=MgCVU7v0(=qWSe%!~9KS(N zgLM=3LHzO$mU+*{wx!#)wXd#auhgvU=lF&*IVnT+hZ`~0nCHPOETKA3I;S!sQ8$^{ zZcv4UbEsTEpxvZ3yazYCQD1%G)vA+(ndH~oy5$RmDNA{h9?j)8QlvdBd-|V!63d!_ zr{P-1vS(7D+|itM9Rk61MnI<ijY!Ly%7^jv=YUlg`cLmOwOJ@HClJm79G^?wO8q+) z2vf7m?6nYbY6S#*GNiuY5H+x^+G@?tJP#TL9re>+K~KhBa?C)KKh+E*p-K?e54p;H z-uNb0vkbWyR)1lbnp%G$OG`vjpo}PU*o}&pp;`PEODluTuiNcFBFmELneD_AsyG+G zkGm*r)oMJHmxrXL#=Plxfj%;6&nXBm<I#%{teK#)2aU^vKFj+G2|d8ZfX<DYT4pfZ zfo|^HD@jrnxXrnoJ(D*BEsHtwkuBFp`spvA2GpIQLK~G_Fij)vWt2{I(c2x~KW)!t zCOE{y+%GQUQ^og%kazlaaoZ=NV(uK8O?>)d`#6i)km>UtDzrb-*V{hPU&@;WB&3=+ zxL1-^s(vuM%+x$5wc!b>TMmX_2j=|8Kt*)b-4;r#_ff_ny|oEKpX@DE=!THWD9l;8 zEWjV=HO&BTAtLP*tp;IMlM0_Vn8(sUqI$?Nv_U1G^tEZC@of=jxa%BH_{Ai!MYo}y zE@)vjviC#f;TCVZ=HXtX$EDFgCrJNz+eAX#tsgc!-#{X?u;vu7>K}|6xr+Y+O$ixV zZ+D5)r){a?S581&?=jW!dQYD^njLNZDwQ49Kbq9~QJUTP@Z(p`mlCNjK7uj2dw$*y z?Fs@NOQ3Fcxb;G+-Z81QBhBuJS%CWlpf9gp&E>m+$xzI$NMcrT+APveYg4QEVhkj# zC+2qrf~MxI;{Q2Zk_`Xps%rkG7-Dkc{@y;QZ4Oz0#y`#fgd*BZP3DWK6>a+@*L<mM zcZ+wv6pXlQp*qv|N$8nGnzy|!owe_wFT`9w_5eJz=cRm7?ApYLBWTQ~Z~Xh0d`OLq zTT$CqaQsCoH<7xV;0<Sr-s;g0IvOs}L}lA&k-l0$xByYj4z~8BGDno!&c4z=oz(hi z8grx*iDYlPN`q&LaV@ehXt=Ne8MeK-x}c@DjsM$J%twl6LU~JSD&H^}!^3Q<i@!_g zv@vrzI}>D@EZXPo+Bl`5Zw>0+GLF5OFNogis^p(SM>i~SO7+N+7^b&-f@XG3hYwRL zs{rPg^&WTKXuZW1;J*Vf^E(^LEqH+VoqCH0;~Qle%pqFtZQVGjSX7wPu*PZbFwOi{ zG*lGy6QCZdX|wX?4#`^~>lfT8wQf{0k4{L2{|oR+{f=JfFn@0V9WOeR5QLU=M!U6~ zB7d(sir<zi(J(xWuRwrR^cpgzK1ceMKSTyn=7h94qQ})c3tBJ-kufbC-S8FZ{*A-+ z;wE$p2;6zcG#Z^Q=wCTDUVHvM{Uf{T%s<wYuE%Y9r%meyA9u+1R(iScdR70ky|pt% zO*{K56g<p=`;6dF!Rj_V9Z4Kex3fBWL}~ny1nH|{??HFC&$rtV!@%g$GEs~YjUt-3 zyg5y8xAoVl=3`2GjRmRwg}nzj?Kb^myE<wR3=lWy37hs;ROnh+ySnXsoC;P)_ZOlx zK7zQFs(oe^qFNu3t$Ssyg|9J2k2}y#^%uW0`}(%CH2YD#%Pcs^MniW#E!k`h>Z!)# z>Ws#2b>jJh;6zDv(pxgML&lgyPQ#zcbb!!sgpiDoqu{tG6%!Ja>nvz7KufAa>qaA# z=oV|HC9oE}Y-%~C<~B7KIy+)gcYDw!`k|a8<5gBx6?_n^Hfnl`YGk#JRXDw`Y3W5Z zF72K~Dqd=&sK!kRIocXZ$WcQ@HMx}F(UwwzM=dX^$<yW*)lApsLU0ONe1#L$wDK}< z+m`P7xi@OFy|1a`^g5Sax&QBIL?i`BM9fM)?J~l{Rc2^%VhrUz829&peWXrWCnHlz z(^x9cG-`TL;&SCcT7aJf@*!}hy(}@hIc?50YSx@pYQ~(aH5qypGnehQvcielAG{aU zX~0_@&*J%hxyYZhxenZpYC#MBj39u^sFM>J%<uNLp{5+>??vDyuV3EiM+4QdBA;io zzdv6tSFL<#t<s2TfRwNG7HQKrPlW>QrIPdbG7F+JhObn}j(kln(mY$%K{!!5k#)1E ziz+3WTCrR!=CNXVR%|-O_{kh9N!CV3M%Px+KVv3eg)|H^tUYmMQB9Bbm&lY5<g+!A z3q(W{bNLa7G-%8GR2a%BXjxsm@<>uSRpgw1Z~T#cB&t&nSAs!Ug_}|kVHMz$WCS?l zqwD<1@hy6X9b^#7A}+?pyqY#|7U^Uy<!oE$R#G6OIHC7~?928tC#m||`Rwb!vt=?X zUvCU&<zZuqgAMm)Z5TgaQb)3^o#QYflyA_|`O&KZm&VE*-qc-V@o_Xmrh)G=FTI?~ zaUiwZw;@Gy>*X6#P>C%ujL9h3=b(@6wKWGF78?2)w89yy=;G^09Q<ASzGu)Qw(X;0 z{;ohoCMo#dETWJz;bQfN@r_l;$_tKiy+f|A>y^}WR?(y1w&Cj}$@F5L2YsfEL<3pY z8Z-dF^8sAbhP4Aqi=v(obhDs>e#QftDyng66L`)T%)98HH5&8BF<Y>v2#E?5hTb_9 zH2mD~chFE=MQHmw0&)Lo6u2YqKeGV1@zG*g<1#Bwv#zb_%-_+JlMrxKd<~ir3Ze1+ zy(_eP6{~SYKhV+(S~~v~1yt)79UHaSeZ5h0^WBheRNU;+TO4|;1L|kljg`GxMRVY5 zgy-B?`L%XKbD$65%Wkaf(<V0uOoUxGf)z4#f3Kscu6N_X#60DBpQ${*$V`+W)Q3=C zVh%!IBlLCRI)r)=>P<|yYD*~1E|lWFafIgb%{TqMMK!$}&wwd`weq~AJfD%@n)sU_ zUiHfyy0+TP&cgr)(wf;G1RCO$+F-8vOp><HO7p|jNn-Q6t|xsd^WT9I=Ikc$B){h> zOt(p4nn%&aNx*RFpHZMF4f(Ufvk=7?JRPMYo=R06O@dN!hp9(J{WAdZdPL@b!%!G% zLqHJ$fo+g=B{EqW3P?d+m=J67#;*QZ08JwbS`rFm!NrD0j{xSFfN^d-(+{H;KZnVO zq>c^Kn`akV>TQ^)nUX?$=?!SjnvZ-^xEv3@Td*3+ToB$GLi`Q1f1eLu;*Pvh0=OLj zdhtFgHl&UZQ-JSB8KgFySnsCLa+gvITEM<JVb|Z0=_NNbv&@H6(`bHB@Igt@ghI@c zl*U&;NMph*gq!`YU((D;uXAEi{}>T?_A^wxGy~aKk5P9rYN}h!*-ueoBA*hw4DFOr zciPZ8^v@j#d(UsI=5c%~N>l%e$W7+;ycJQ_!+(R9k!HS|Ec90*HCfot5kX%T)t%N- zi~Jqxa4NIzB;-ca!0JvWei7b)=I>ieG+2$PYbd;x;wr_LQoMggi&;CG;F7fIhG-(% zJ!c$nrEc$qdPCdkvnu1mRQk}y|2ztlU(w@aFd)D-lsL#-NVQSwulrLY!m_|0v*K-t zB7y%f8D%CG3s<7iT|s_@7ZVu%+>P|Sc?3OwD#DH8xgHD=<f-VsApaaa9sX=8nv;#Z z`k}l%#O<|7rBhsro=L%+c2xoT1-LwYZBh#O<!BUXr-(Z|lREpYkzkpMTP0~-Q7W02 zwZh$V@M_pc5wh%Sm%o^4qt8t_^m(klPsMxqW>>+Hq9%@@@^GtBaXR79?>LQ?^WZ#C z2`ni`a{1lFpInCsiUb$05edblZ^2mnBP=hXEp>8aJojRG7BaJEcKD<{j}yzhTP#U? z=Aa#XBtim8=Gg?r4Uj`5WN-&1pw{2h8%&)Z;9p{i7uubJoO^Qd2$-{7c$u@ERF>y& zqN~6wdfjPB!z|)D^aBs!k+_=q&oG%~7!{|m@ca2}v;&KPJ2>;78Umj~@P&9JSqLha zzlFYP<2&bKzVZaVB-Mc?2YHnu!LA|`O$fbh{3s#N;_-HA4$=p_MZ|rGufc4|OmzUu z^JPvljA~1&s$+Aa<w()zNx!G<0L@dyGr)f#BOMeS6)ST`QZT9-X)BDf9E^O4EH=;B zE*o==+8m?Sfptj=P=j*yt%Pm3WkA!^$&z|GbdnQQQMu~aAXl=XRo6Mq&w=2&97(@S z($~pS2zk2aJAG=JelIfRnTs4-Gueoy6w{_W-;!`D2U;p&H9!}KX!)wyGt%13G>Z>O zBaXr}qS-H-6;8gFl+j!hB|&HG__QCH?uAZY6+qd0>UH`KS<+@;OtPgV@|*2uh0NaK zb;wtOjM^yvHpr<LUa2YUt!L-)wNxOQvg7UAl}UBoaAs>tzb)z&!{3Y1&uQu2YF0;6 z-&pJkNPw~TIeP9tMbGFy@$3@M*Ts{I=TY%&5zoVT@~P)d6APo+yaISwqj*6}fd26l zSTkcVuiyVH03~%8i#~&ZzGlPMWCA!0Gf#IJR{FI;?gP_@en$)RA<KPQ>9elZzErW? z-z!$}DeP6T*8k_BYkgYiUq~IY)=yyvyM1}}O7uIRM!^y9drD&sLd~O$*hyeu#5%<D zB|MuR{sPa&<4WTs;8UXSCjiNK>=0hc&P=2=ADrQtvtr8#<-kGZK>Z2~i+YDr(2b== zcR`DCps{r;k|OD?J&uqOeF)jSt;!F64YPom7yZ+9fQ}L6K;B(=8G8lk_6m~j6~x@z zCDMtQotu#j_2}HA-lTK8dcDqNby|73nvIwet;T0PM(}dy%>!Xa=e&Wit+N2(1_4tK zJ>Ho&@F}G;2jTj!uGD5=No4gi+tKUoGxifUO6&p|zC}*Q`Nt@!^HZd-C<VXUGE6z} zYOGW~YKVB}>-c2srIvNJB1pwv_RV7Hs}lRAC|1y*^It@P6dqcjDCIs;$|7}n{a0bN zwEnC0YEJ!ETa@VSNVnP}A=G&bfqB<!qf3&BkW{O;I*ahh!r#?-)j-(OIT_(*`<&~w z3HA5cW@%$e`m=&S$*g^tLCz@<0M`kCCyB^pUPuD`kpR{zjc?QYPNne;dVddtKfN`j zaX-DcDvf*Ty+UdHHQvTv;)Yn1ge#yte=uO|J&YiKVh)%++R_{)&I_qiSd0WOwwE}M zKLJhMY%j5@ZER5*pMVy>1mb=`bXK5zVw9e>%7YwwQE9vvGOqVjDG&Y)-L5pEZIaIC zt1d9l3jE3C<x2EN7|!Ysdg9Sts0z6xi~B92`HDn$#vVI|kHS`EJa!sEBl<X=N~|0e z#G}+#WRvWC64CQfBGXLJSBXA?#3B7;AUgP28#eff33<>jm|E(KL}PG`1?WOK18iyR zr@EEK-#D<=?b9-MKLq7qL@AMpXFN*8q(*e^0F2H-_4k1j+Inw(tI~Km%BD8|oIZZL z3U#LP!ouD_m~3*fC^b0{i;`Lh@J}(6VsVI}X;M5&;!2eyMl~<&Z4!WS0Y`~eMhmOX z*{Fz-wZUowjBH+3?(n{;&a#?E?5n&i88K>u>i%i|!DBr`8qsAZj-fVnlD&ENu7UOj zcr8tPJKsdI-m^h@@FMC~8b8KU@3}+S`I1Qgj`G7<7-#jKJJoyip1alQde8Ti=;Qd- zEqbZmLK{d(>TSv1K-&|`*$o3Y^LH_kih}8`ftlRO=24yNSd>_EospK1t)P)MNSMz5 zMFbXV!)H|iohdPqaK2TlCsdyXsw|yVJM_5R`8Fcji2AR-qupV#6XH@LR3unydzvBM z4f~1F_TbC*c}(zSLwgMXgM4Bpq**9!s9VzD=qH!e1;$?DRCY2k%qp0&7j#pf$VRk@ zJ}vAuqB{{t3Z*G@GUUh<RahMtFhwyjk)sMzr4_lDBo%wm1?Ew<pEzDWl-uxWJxW(S zme6Q9$r7u~*=q@WxCI^x)$b=M|BjXmCLRK`hJZRJi82A?y-FLA>=QH+(oZ~6)oG_G zm7oW8n-SZG)I^@nHz|$JLoI;48x87n8XKNR#<&=^F9+-;eGV0gPPh}0%>uwt*&h7^ zikjIJeH*WM^eCR-1*y{y7<3vkDAAj#<hY}|)uZNEl<988lt+1aVQ<1g!t+y1WES>P zqW!0sNgW>q8t;8)$CzynZ~LYZ=TGX#rStC(HZCa)yTB3evmPy_-~(OswN&RE!Vcqf zp@Gi}J#;B+uy|&hmNr=+9n;P-K_62nm1xV3H2SPw#e|IhbXfof`+6|7-a1piP-HwN z7^H{2zdg+^sM$1pNn(G@e>T6pEQuKCV2I4dULmNrfxpt(oApIA)u1V4mx*V)ZKf|V zchNeer}=!|H??#5LN6WbNlX_CYfykKg_THOR9^_2FTwuZg0(8r_mh$V#aE#VnGn{e zeCl;DfP%p?tggB$k@J+TKa!uwd@4m9VSVvf-3M5SiBUWMu?`fM{}^?u#Rg7oj438} zF(JrR5f9(+cj98FDW)K7zZihT$5@OwgKx%nE3=G6vK4Y@Bde<-Gp$1S)m91meo|RL zn<`b;MO(K26BC3>4jV6|nK2@IAd(jIpM#El1d*~p8E?Q^LTFiSdXY#}J?38eXq6wU zILE&{2PF4XZYiYgP2}og_GW_ZL=T`a(o6hRfQ6D1w{88ns)Va232{Fagx$LRq%S0O zl)0Az+ySZ5pA=~!CT4ui_9ihZH^Qxh#U26>6Z7Hbqn#h2z5ie)Ybiu*0bt+kjg>s@ zjA<Te+x6L%J}EKXCyl?tC*6y`SMYZff1{CJnvdz?E#UyIH1B}!gaNm%H|Bp7#ui@( z%oNtXQp6YWU}CIctPO>{aix*=UiZ)(*qFTw&sY<UCyANuK8K{sX1gzSn6XuE_vK0L zzG=hSeU~9x*zTJ}dxI>C@-?(l4s4*jzOJb5O{H-dahv}rm2DF96vkFyo8F5}t^)$F zZ(9oMi~Bo>vl1%_AO0!k4`R(0WECATr`T9CY<emo<caMP7+pC8BYll5)vw8`??*{r zQwa1doJQE+frH9%)8A24O!>DxmPlhFq~FmY!A0jT?5Z*B+?Z-mztE>vHrpWqH$Nq7 znQ$bS14=<K=P<2<wbKUBCzDz~Nwd$g_PdY~mJ)PknIrr-mL;(=XMopVX(6vP9zl!D zG8t8u=>F3%*>!CDalr@dER`@@Y?!6d@*<PA64UCJIO-D{+shmcuo$LBx>vxe+Ey;C zzAb-8pA`ZV>?nizOJLlY2g_U%w^_#AX+&7PCq<)De2EOb$F4aLln1f;?205wZvaM# zVFVXXgXYER?xJ1UNedWLbhw#43pHVVJOXQCT7oAT1xqP@drH6g1<S->K{s|^C-D8~ zII-`VG_Cp(PnuTk%;)M~Y9hy;0G87Oi^b`fGFXmJv{=-iJc*G;s){U*MNc7w4PZX$ zFG5NYGosTWBeCdAJRx94bOr)R^%*-w;fF~?jmJo-7}k16tTxu|e7FZm>vqP@h}UDJ zMb_<%9ulu7Tg2<vB$|&tC^RDTJ7N`%xTwhn&1g*%jMzDVutmMrtSTNQWXCw9mbgHc zSQk?Rq?y?(K)r~>PMX=bAQTgbqx%Agz--_|=gN^3-U*{nC`=`o*^BWB5aoD5zDc^L zbCPah$}ndW(fDOKfCnSmYs?O0|98q>)A^t1Kmi5fV)^NK<0K|?>Ztkpg{wAx87u#* zeqqFx;gPHrpt<9XQ}|ZXmRbrVBf~@9!{b|~w(2b~o%2V>(ripi+vjs*FBxfV+~`j# zwUV4ks{+SXm<c0&r6KeC5rkopzl66j6a9?+$nen{e9~GIIv0{&3jd(>d9E1#@;j=6 z)uOkr_4gLM5-{%ICcH@ey-Dse{MZBUT1zu282Bo>*21v||3a&=U&8)UQ`x`eDO#(a z$+2t;o8*GowEI!b(%StdRN6V}iP(KElBg`U#9@D{z*)%O`vf>Iabn-XiXWl4ADbAC zbxL$JvcOIfTh5KDUbfOny8snu^oxD!YWTy%94p!42i&pJ2V91~3)1fIfdSdg-sO4d z0#s^?wrun5SjhZ6>?CT{-mI^K=Fel0?4c+GlPClQ3ODjHfx<bfb!|YLTAMfm$~F|; zzUi(GI2jc0gto%WFHCQ)PbR4%le@x}%Msf$Gn>-kp8?Z8kIzIS{LZ2kPIYA1qR0t$ zn7?WzV-v+FcYYJ4Hb@syr5~l=QXFk8m(jW!<oq3}hoUN{(zpzPWU;St4WBx5kz$$J zstdZw%J~Xa)f0lN%jHF>w}53gPr_z=9*MvMv}fS8675hU*yDz=>Qxqp`&p8$PzafG z#m<%=%AZ_k$Zh6-SXSFN%1V}W(ZY$4no;C;s{g~%TEA5qZDWZ>Vk4~|HI(T3pO(1a zDly^=Z=limT__6dNkqF<O)qXlFWR+|h=Y&CAT5mkLH;f(3SopqcV`3xyoaI#cJoZI zim;&G0GtxTkTVqo4z&eA!rAH-<PNvS(l(>HhpOr_vsaOh;YYEgH_}4<XGm>}xWc;# zn?;DgBeLc+Ou7F;1!12zVqb04b$E-(L8Pvlop1dlMR<bP+lzA4QYLl#oVuz6cm(EQ z;W=YB{ik))y=}SxV~#Y-JE9cTiWGBJ8vh#n6tWyja?=(jex4Nl0ne6Hft8KlkV35y z+y&dDCbKdpJ6!*f9e$D*QZ(PwG9*?lf;3mNx%oX9!Dm#%Tj>sXK7|7O2c;w@PH!A` z$}(qT%e{);@wHLrOr+~eoF4r(b2T#R>l_%jYgt>r>5{5}aWNyvNppn~*97@Ca5!n) zRB&u!64`2fsMa0iy>Oxm@QbJ?bpB*$d`r@}3#0zCM9#0Uq@}4Awna{XqNUUrOuWc% zslzKgZj_jgN(3Qdj%SMs)!HOMgJ?$SA5m?n;P?V#d2f=I&$4o7cdM>mQ?y*xMg;gx zgc(g7CW7dRu|;*V=I(Ayq5ilg`3a_A7|!c@Ic8!~S)viH$y!IUBc2WN3Q-Bvj^$c3 z5<sx!+AtAP?XbA>`_KmLmGEEV1Gd_1d=iz5E(t<VUtR&}*5~|vF-8WPHZkV-dpSZz zp_pr!Gxc~5uY<A@^EYRi-j}!SIA#*7YuofZ0ZDU<FPT}zCJ=W74^VFOBqlYZ^z9Ct znpJI{sOCq(3^0R-^me(SFPx2e+bIFLTI}*=5Tu69@DqdIKdD`5F%49^IqMZF*38aD z71(fbhEG!8)PhF}%!TM2><dpIQPFbva~SF(6L|_oSg~2j>p!M007t}T351I#sty)U z+#Si`84w_Buz4?P3V#KB5SPf|6%DG44C5i97KEp0qBcViqnfK8ixAqFYTieA`GW(w zAaRLIV{Rh7ntx26`g<b-#gL;{Hz3<k?DQn<ll%HHt7-aNNgEa5Q|P1E;2FVHjLjkQ z`T-Xxw7Q2{9Y#SISPD$<Tbr+rbgU>ie*R0Z-#Na;r%mD}%<5Jvs_7s90pggwVaNJy z;Gz5ncB#LFXNdQ_W-sV26M91L>)3K<zv8-CZ&&nBu)9dR+1}I*&}Lh1fJ$0Sh=Bu1 zZIV!tHtTQUYHDH4Y44xZ5%^qP#jpQBOzXUV(rydFEg-4H)}rs&NhB^VDy~OgsRcp) zBQj;caunT&@|oX7tBL@ERuek?2okS5fdLs%LT$*NCE(OF3x;97gEqE-ocb9DFl2Q! zgtm63uT#EgNyte@*InzB9Z1=+&_xdqJ!aCwM~?tK*3e@^?B#m2W|4N3p`^dmSjEDp zr5EJ*DeEctDj!a93cWB2&A~*29n=53!&rXK`>HxJ|5fbYYy!?SjKig2`8l{-`R#sJ z{y|JM;N@7?!z#|5{daszTz&pedK?9JQ8F;@qU0|0D_iceAI?7tSL#Z>U6e&#kwgbP zkkbtwSlf+Cu<f@_ncfPo253+zF_re*BqkMOz=e-l@dSF=3tHNe6Mx!NOm-RZ<2n>! z2^i*I1ua#Wv>X0&z_aSn73?s&*dqlVd-T@)W9p>J$FO7ZOZr;Fjpb*IiZ0<kj-=(t z)3frtzZVEN)Zu&;5GEyyDoKyR4}t#_Nqfj|4VZ{Qpi+zi1s_y<&#G{Aa&GbPMOY+9 zMu&t)2l!LwN5#q;zBt0;6CDn2Z&SxMOE<QuqarD*i|U-p1COE7rnIv5v>VIdYQtLL z+vF=8tIkQ-iCW8@Pz=4^uQuJ=>}nca<}1w6IQAlU`d|lyHiM6o3qDTHh2A>nrl2_S zA+q^%P|?VQl|Hvwh66uk?P7j%C%U{@zVS76a{Yy?)f|yCw>|CZvLrN|l>4FS+vXAI zH~1Q@M_VFOIwyh-O%sQD3<-Z4nfz%+pMuT$dA}3f(Y)N<c#Ca<Hc{-Aj|5{d<1iXZ zo-tGXE}|+3jBfS)BafO0JZ&L^nBNGx!%&i(k|jT2v%Ep@)Id7GlWuGz+R=G5+`2DW z)a`k83dV!1XXu&z6g?+ALC@Kb)3f+dJlE~aJ}h2YFNxQLN5m`jA@Q2FOT4byiPxhK zrncaPvkrTn6K}_!eR#*Pnmk1DXa@$0c&dc34gYu3$34$Yo-f5ypTaYP)@Z5EAVe%L z79fULyzOojc5hm0T5GmFJpjT`w=@qL21F6dx9}hS>_d<iZ+bBSNLanucs{{|sq9Nu zZ%5j$dIA$Db&Ad%>KL78sm^jCQ2QJXENk|S6i>1Swe1^0VH!|z6vhVJ3d~qpZgqg? zzXJ`{qP%dJwHn(Uw4c1)+4_+yvo*He^{Zd~>O~p~F~0$D{+lmT#%8yz$>m$BosT^* z0nr20&}O%cv?bbkjJiUE8qVZG$Ol*3*xZhC4DtbUv%|~|qj@h=J~GK)1f2?6ni^AS zZU9&Mjpv%9p98c#N(mlVtgend_5~7@=MO8-+r5XkjLvWM1!50n(f5dF84tfLw0Q}( zm*9+g613dxj758q1+@iGGXVyKBgR-iD*K=c=}3jXt{(VYjZ9Vis|CbfrAYwv)gXY_ zQ4v6I3!prr+D<=J)7@%Qhu1Goo8W5RnM%bbM$r5yo02?~go2uOrV+Uka(kl)NYvB= ziJ(Qrc=R;N`2{d8IC6yuvxg}q);OGU*^kC<_2?JJZgJKx9*$a$VY4ft=wFT9f@+7O zj$`$od74}ad%Gmf_rA69AldC`VZZbwE$pF`3rQ)z)dl0=BiP1ZJ-dY$-og#)1bxSP zNgczsgfSnLVGH~D`xwSpJO32GZILW~7K4{qB>)7j@ZQ<NRquK%CdOgGwE<m;>40L* znbh<k|G`<n?<OE)VVDVMWCQ4WfcB5bU=AtqL#CZZ1^b}qlhbb~9C*-Gk;ZxAT`V0Y zybkv}y{}K37*C}jNCD~Cih>GjdU1BZa@I@C(fhvEMh*p00h0JY@9QPky)JkP4t`7= zqP*~?>!A&M*52<x2k*Th{F-zns1|+)7*@OCH45wZaE#_Jpf@pHc?`&iqX9+x9zkQ3 z#(yT{uqtVpS=@!-#!nke{xxk-Yyf0~*(t(n5msJ^!~C*MP!4Ndq{RF@00SGz1&Krf zl7x`PN^-FpYdVe!k1rrQ)O`+Ple1_!S03m=74>zWqxiQFifLao4{wB9^g%?F=gS~0 zM>_u(!b6Igk78KGX%zF_BQvo$i2dd%>Ll%S;>zYS8{}-d^88%#^8m>@n(H6JN4eBH z0j1d%dV4m1hFL&aSv{tK$Ix%EF=8gH*LA?R>-5G>76)qa5?U!q{5zOkM$(KDXRO2( zGaf}bx2|K?&R=KDobU79gq@AE{9S-_z5ubTUu>V?@OfJ|ccbj>v{^6<LJ%vN_+lT5 zs+VQoBJBbzaqyAIfg+76Ibk<ohp|+arK#>CO_g}6Xg2YP5?z6EY1!XzyS@qf0Ycyo zuOK0K^{@C^(P8ojvDHkzYo|CVWwttu893J<y#^+hB@U&rn!3T0f)?HX1<Az8=m$z; z84_P?0&WlocJb_!`cw(tn=;==vp-BaJ7}^<vkj)5GB<|@BxD3D3m20zCAX#9AzLA% zHeAJuNh-{DyURAfZT&N3>rN%fv?<X)A_D19F*sY|SK`=n3hiSh@}3UycJ4WiH(bHN zbUmqcI2E<H#I??F`i~;nm*C<{G3o5OtmefzxlK(?W9UPt^?{_R4jL<mG)z;|t{nRI z35>GnumQA32}vG6{NITX#smVXGT-f&W{?OLdm#JQzu|LRVj9_7JPjAE=2mf)a`9Ab zAy_6`@*nHK5Zl4;M_QX+{4AWn;AI>6ng`K$p?E4K0IPv1nYAu|;3Z1JysS<AUUB&Z z&@#*(cou0$s4dFTZe<VbvtnZq!)oOs{F}_@DHn%f0h22Bz;l-Xygvx=wvPbJ=czn? za4`J^1Sw++(os(-O7^h_4k30Gv1ow*3jo*yuOlp`=K1je*G1A%BvDKgg|#5YBM4&7 z6Fcw+#8`T96Shm$F-4CMRvOmRzlU3yc>^y2SSS?R4u@cwoDv##^y~sxs3TZ9P{;%d zV4{fxRJ6JmKGh2ygURWXjF~(9skC^I_ki6)F#9EEOd#ZJVmWw7$<^jN><83bny&>Y zLev|G5KaS;mcdAD^#EG;S!iW2dlFE;4^Gs>Ag}%LHh~9<rUs`{k*H`89YP}tZwN9_ z5Nb4>{Qrg)EWdHM7sD`c1JExBvYFoV>hx-(khc<7V#FIC<h0_$S~x^Q-Xqi}81h0S z`z(%QOf59lZteEL8@Cf<Egd#yUDjAzwgL0B?HFrwc{U|)Sf3nluR1}w+xceXKz4pV zDF<3R#md&RV)B~jccRiE>scXhtpKePdPzHNO}c{S>_$Md+4Z2J`3~AJd3QY$$aFIX z`~CFMe8)VB4>GIofqW${KcIdLn~0fokH)b<em8~*vP0#B*Wwcfs_7_=ve2~sD0Cwh z4X~qPqW%M5l^nSL-&NiFUsQeeSbx>K{=2Hp>_(s@oc@#bn%UH3)&+`=hYRR5kn9dZ z4t}=DW@k4MKznW507XWFA~^)<B}jO2XA!N;-9#m#*l;v`Co<_-f^MC^gCL=EAEC~D z;8WB52Ias8vj}~36ULEv*{WTgK1{L~8r$6<UY<ovHi3v~o-iID>W8V7CdN|4i6qAM z4ebxmQmUl=ftwL8iI;^*g+j63Erc38A%+wZ;C|f;g&~0xDhNPW0h~tJdNR=LCeA_F z+`OLKFu)Did$N&(XP^abKo7X0_}Qc+i1%iQ04)<N6RtU%hyow&e})9WON1!ABurbj zSe5(+yGE=FcDHWzM$lQ1Z?>CA%1Iyuqv1qukiSCW1Bc&-h@49tFbOAM`K$%MhYGq; z(=Mdb8GBlv@Exc~)FVe+e8f?}(3glDZXwD$X&-}Zr%EHufLK``s0(E{f(m10Gpv~1 zip{cOe+QoUHphy6YQ=n3>^&=1YQ<i&V&ztBzZF|mOkGKpJVOZ}R|iHdYfRoAhPD`o zCJfAjO>5Ar<~s<uzn7}5Uivr6h%|Jr#I~<T-l^66Eav$kuMl+A-Czo(;)D~h21A_* zQ`$fw6Ok*(FQ;<(B5a<J1c>h2oIp|=g`GTNh0%lGX3!tM2{;A|w$fM&6xeLy#&FBW zLg$8`qxT*s`p<kP{FI20Bq8#+h)~a(@94z@fxIM8dq{xP(RwifN@|u~OhA%2g_*aT zWO5IE*-dg3Po<1&m-?_UCn%BE66HNfnNu2R6tx5x!vsx*e~$$I3b+71-N?j8VH#)w z2u!(M#6@{R?1`9`T<@Vo{xRYha7AVO8L$Pq_Kxt1N(i1+U@-~+tM2Jnl;!>0eF79t za`&uDxqFzE1tpCq?*5dbmvA>3m(ux<kWSVVOF6@ag?XYYR>Ap^S5b0}94oOE(<En$ z!u;GijRYIYiiCzU!>x6)Op5~OTCvw2;0wtUob>WYcvweLn*2RYH5c0bU(rF-f+I~e zJ?;Jr(tMPJ0|^`4<^~5H^sJ2edjcqjt{$0)Qv~`U4^)Gz(0`5=KwY!|f-Tvtyx{Mh z>UY-HodcW0prhZm;p_foQ6+hf2l<u`8iBB-=?pz}zcz*!!uA`N$aE~WIpFqu4VnV? zo-95=e42t!iI1_GgLA`ZxTinmQW}4NG`2+6JNk^_*djq;ddC;~VR*GW0Rc<))4~;g z2LDMLdW{_CRVQa6OiuGzWHovkZVzODhQ2)jTTloaCA8|ORvPQ6bQ~a?8!NZrbl8%d z{GLVLi#U9?eL^*zV&kXaC_#%Te{Z5fKkPxRwAFGijIrd5F`k?;MzdBpU9)32kS*M< zlV`D$N30zl6+ZY?Rh9fosNJat!B{j>Ohc{B6>^iD7!8eD4O5Y*?yiCAaCS<~NYV+e zhRHr%y%HyDErVkvwwGnv>kvLO-rTR7pmo&@vJdL!n2n#~q3B!C%!r+T--lM~JvOCr zmX&ZPC4eH3zMZf!;lp@*Xt+p=5T$WG!r={2V83@`)=~Ac2U1bZXBG-lfSt0eBkU(X zBsp=58&D1u0S23U?Wx6=&4)aSdmK=~W#JVlCwwu5)X?WQ^p~LYyTw0bl>rj~{NsJV zan9z#Apbr&%YW{*w@2(R&YC`73g3c4@(;rh-7PqhhQ|>F-4+^^RuM2Fc83FigO{62 zKsg6dy~={YUOskRc7jj<O28b9t{nuDlkIVNY*KhSN~-23iv>*Ly2!btcgsodhiaaF z(Nrfzump#s%=((j!^xyq;0+K8nAcaC*^fYXVZw?9q@DMn+llsSHX>hA1Z0_%q`Njc zOeE)5^kMVbq|hXU=vWCIk%UpXI(fk9RTw<1<4v^u?B%~hoHUL1ymCKHgxQDre~Ohj z^d85?E!F&ORD%QiC617{XH)q;;lk9jDTT%DaafQPuv#zQ^bu7ATt>$hVvAy<Po&l) zQ`Ku*FQ%YzkMOr)#t!YFqg%9OjU#5@jI<-jUlJea_!hV`L^fQ}WQ@nK%X)Ym(obiW z9tIf5EK1lz(3lRSMsjd~A6sX1%pMaYPQ&yaAU|(83}~9OpspSw#gHj%|E5y|0NeO4 z0BMnlU|#@v$PWp-o#nJ_3GVAS=aUZ5qZ)f*?VA*a6EWiCUEJaA+xVr>vB7<upy=`6 zK~=->`GOD2F7$Fc8S&#d-jJr7(>HPy^SbCOY;q)zN!e7K+yM^r=h#~t3dIqrFK`n< zCWLBTQF)H?&_Q-k_@P+0N#J~Z@;EFjpJP9)yfEKg6;xihC#~Q(ZYh#;qTQRvvpOgC zSG^ZDX0R2q{XOr+jl&k`Ez`a4Y{Y_Htc?20qPHk7(ifJ`L-K^L%WiOp6rg*D1{_>^ z;NUXg%>qvs%rFQj3@McOm7u2O$gv!KdljX@JDk1*#1|Q)^fF&wE1z`!sNP{qPFaTf z#0ZxdTwg#Zrfdbr#r}<G`Ve<5>=F&}qOo#d(l#A<^XgOJ1`lz$Z!2mWEtukH0>@N` zI(+e;%#kF%0kCc1td+=iIaw0-kj`l9*ONiM1}sR^L(3Awf~$6`=uBEivRA8$iqzrk z<aa-C>a9-u``*_!e*WDSr~RP!@FuyaNORz<w6!}i45Y_!lRPR*7HIuqs^%oOKH$_z zb{PF46zPWuuqA7Z3T%rxjU{W~_pV=%l_;%~SymVo!+=B2WA+Q)ckA-Ld&J4MuhQ4z z#0D!CpC{1g1@=DyA@7N8e`Ynk*a6$Vw)ltG`_eMvWot>`6Sc*=`r{20Us4QXqV>Iz z;&Y3C+#iop{OaOZfBb%mPb_}0KmGv4hZp~d;^`>A8F6#-TI_P32pQYg!Yu)ftTa!+ z{uwgL)?fr&xw?NG0)Ol&1iAOjp@)wirFbMw2l&deh}glRfCFAZUw*gSY1d@E#p!L| zcm_?kSID*A)=jDO8Fa2`GiOs7{QWP{k8Kf8xSW{bCfJvg{t72C>gg9VcPv)3Sz9C} zl;5gO!Jmx3wfU`DDc=MRNFFc6>2FLjZiC<*AQX4gBeBNZvWlG$Ck^4`(=M~L#I3AN z=ZZQ<=V@wwITqVLe6Qc^)IUzSk%F-<@xKocdb{b77=3`+yqg}0VF#$yyXleKx(x8q zXoKPJ2;u&Px(;y0NszV3-=U>rAo$xWa9e^a16By_P?Ufn|H6y1It-12KgUIfHl8g7 z7yZFlxCZI4A1z&LR2+>jT)Pv+P|DR7H{moQ%MuKgP26LDwW#7$-B?y}iWsYUl~FnZ z&Yh<cAMow45#X>w(w`zbS;{1H%i1b)c}FNQ7L>)=Sn}GzaaLSC^e5^9@$FK?um#wU zRT`XTjfHCqTKF048dwrX9I+U57-WGxD=v+$5>fc}gsF4yLQYHNlmC*L{dfna`*0e$ zCb{(s5*8dO9s}l79%^N+q(2(!Iw+3C3*c!b_>FDg)t4Z%X0Ud1HbwY0vVlOWC{*E5 z3eo0n4Qw%kNHeLSP<Xjrsc&`JwLIo?7kg5FJXXyvo=mUd#Z%~&UM%^3YSU7AiI}?6 zy#nDMuEtV9?9IWr({HIv<>gpr!CpmYRxzSr7|bE|d>kDyr&zTu400V?93i@~t2qsu zQlCW}3*oR2#)HpV$S9^0t62TLW|dHtSP<mPkb#{nsh?XMQm>8Js`xTM1D1xmCBdoy z-*z>4Ma*#qW?WO=7MzSR%zl<E^DmkLBW{O`>C*@~NxvK`uO|k~sUb)^<dW*=e<V4W zMnQ=t!l$iy3S0)N3R;3jI{O>8sN-Zl2B*tv1_`TQb{M0;-Su;)XfE7y<nR6M6x=jd zMsw;pW;(nH<mR-d6gU$(n<pyIx4|ENB6*3R4WrC-ItvQxV1=_e&Gb8)Y-Okb)ir*A z!=Si*L3_IXq6gP!UChvafs!2U3rulz7%fv8JAno+{_v=dIT>17S>o)H#K+<TSy|~| zC=kT$JA|OiwBaas!I4Bt+5GystJDjG?Pb`c!&HqfdBA3-t-f#y#)GazRzV9~bNsz@ zU7o-9SSOq<M=lbTr>t6l1|8A9q_&_B)#U<587SO5CqrF``|^r$AT|Ktsl14$T4-ce za~hgwHO|CRs=uX)EIv93VlOk(@oBlUtTTuK7}?X?QzW7oWpH&4M<QBMyAs9Ob&q7) z`Y)q6<HT|*SY0%MtmEL)L$Cx`6ZS9!Az0NkVLiN7tm*o0I#+GXo{r9iX*eBigO7k6 zccrl9@X7B9R8__5&hcTGmC;7nA!jjaoww;G?C)bOv}pnBY5g=M=1|~Oe?83E?*ObT z1b2ullG*Kj)j=xY2n;<|0p)w>%(WrTUt>*4ewWE9BqqPRHvlmm_(No#gNRobd_evZ z+SM>R!?{Uy##0G`SS>NtvOMWMTeV@4lofmE1MY<qC1BMPZ2%DYLs?nHT^Fw+iN)6y zO;U&ZeCuExzhJ%o#%4c@+TgX3AFn#r;|o;d9u@yN^BwqvfGXDn_|p&|OiOzan_PwU zc@HMe=Kw{<2Xeve<@?Zfa<an64KvR(D2}xyR>AjOh0R^N-^_lBlDfQSmBx*rAug;L zM(!9F>Cv6v?hBwUz5vxg@PW1yw$>+*LwF9MzF;+fI$y|j@&kEp_OHE3z@WXsn_)V- z1cT&0WZgr4WI!*4bewMw`Ew>U9kx%!7N&kjj}V-y>X(;%;`=>pC^)<uSF@sRYR37a zd&m<Zu?9Cmp|#ns6Z%?jf!1SYA4a&K%d*qa`;drZW(l|!g7cp%@OKq-!8t4az*3Z) z$c&!VaOoFramws6glqKqcZ}IoLG9}PR*+c2QCZ;*Se7lD0qJJp&c6*VTy#icV=n&$ z)>E+vv_SaXhzrNC#5mlI)<GwsnRPM)D|6*Qsm-Bx_+W^(T71}sD+*G#f-=^?(m#i$ zyQ<E&V&w}T>1LbWO8cBktOV@~+J%;q{#VHtvxzI4k{34Nq7>`8CeG&fBIk9Dr`5ct zK~6Zm<0YADO5%;!e7Ysik>A=Do8LDO`g$PLn+yr{iY|f>Xin^6u{xLctmgJ!-0T90 zz=0_S+?+ba3Q)xDIRDZBo-%iA9?#>jfepC}D1a!agS&um`A-gQm~YxgqS#fm!mUIf z1#Y-|$o(QML)T$<^?Jyzf|@d`tAf1nIm+wgD$0mUuu@=y0YN4<)%$P25nPB|*Lg2) znZXxP?NbJBB0Bz-s2v;WIG+mylbh+CcOl$_c?7iv?r$W|0%qC}n6U`QDx8&7)xn4@ zR^hI!GHRT#SDD!)tH|hv%aszXr7RUPT&DILw#1A5O5yuTlnxY-xX}?3??vT-)p%30 zZu_lhR_9X0t!2}tu0z|P>_D<XS%FQ62zMjaoA7NS7q>xArfE_=?XQ3PN+99B#9u@m zbhF0mK^!`8XSQh5(aA1^o#gDuP9h}Z-No9@uSNP{)=qExvBW}zS0RP2Q3K4e&SM`O z`|Q}s%p=;l^JiHXpm4_@zPQeRVn4QVxEF9+<c*3Ku$wcM<m1D5T%K9*0YWlD&hzi% zAmaNHdzGEQU1+GM_Ml7Br`1EI#4WX0B%&_D%nb~4mM;rbR)#%y4xE{=TpkYLN=SLF zF%A7irzmD(c?9Sg1!LI;C)_WvKD;Gwmi|>Abl%@KUmcsZIkxJzE|v)=fBimO-}<`n zGQh?(Pr)ID7pdDR;zlI#?Aix~nBnFzuv8n#!uk0Q+SJ@faB2bS!%b0g!D0T(y(U)A z;T&@V_`wA$CZ7v3gHvk+44Pr2>?2Wz(<5%fWLKE?<eK;7nD<QQ*-1dm*l-(f75j{a z^@8JMP&1EV%7ae-jD5*kv1_q<Cial&>k)i6%}+2qfk<?{OE?a?RPvux;>KUvFkOzj zd*x-7CT^JH&k5#n)*O_v+Y)Y~xo*Q7K<<vy(4Mk)w(vup0x!@*e*kCD6c`Mdi7DVe zuzAFgu??Uvp8%*e&nACxxVb7n*p22@RkPx?kOjS%G(EWtH(*-^F2iqO(rH<iD!{X$ z&~DQGFh^;_u?2&huoC2T7r=Q!9LK^=UKKGZ8HF%CwUt?Zvx7eS?~*@*c6G#ATa+ri zU9-vd@=J0zz|2DdLY?=a0KVjPEH!5Gh2pguF6;^Tq~AwiyZ~vIldHIH1dD*Dh%jL! zW3q_Shm+ZLJfYF~I(i#=52(P+>UQXlQ0EIsO1kwbQM&F^EDHr0nh^tqwh)D2B7?_n zilAi&`QQE=G)hu@5lxJ9;K%_k0oJMH<2)NCd6<`o@)-0kXC=MmSfHk`cDiQkG`}$q z6y~3x0xU+5+li9FoOHubIR>^gcpbyJc)-h;taj85W;S(+Ri@{gWqvXhWtv(Cf0>$e z$lbp%!;Bqs(+)|yc1RbX^k5a#NV3>Jpjg%eryF=Q*T`t}QyBQb7ImkwPZNC^B_zF( zX9T(9EIyHg$#JkFe-8TyIOC_SA3Sie8c8r`C00{j8cFzr7LXdYIx2CGz~tKqz*{(& zWQ18k{xfpq06{0AH#WZ!<c#9H1ZDO2H;*II#%JQ$xeYyx{G<64#0HT$euNgO*ceY7 z7y1~}VN77XuWg<l=_ok9f}Fx#n{xSI0VW)4t)jVxIB1AT<b1e;yP&|nq$>(Di9HWr zfsSP->B2i6qq!$mQ&>m2y&rCJ<(~y}+y7L>SNvLN4Kb7IUjt@^Au7Aq<MG`iZu{ZH z2pnq44>)mgC1zF|GxQc*KD;q8ux7+CO`gv4T{Ko#v%dU$!4bW!U*Im9JC8WPF|nPt zQeq*D8N(MD6*w)9sp$!PsEXxY%SOT9ngx4}<vnn*#_-mC(59)aUpa2lznZt%9+`J5 zyV>ErS=JWN_Ex?Am1omf_Ueg5Y;lU?{E5k{_LcT!Xj6f}<gtm|*i9V+Umo2@ekb^d zRfaq{<banNtCHDD2Yj9E73Yjw9kimtbD0cBDWF9=8AEEV>Cr#788zpWDC|YJ$FPUh z^t4`dMCO4fZ?5%zxH*M=Xos;&<U)4uJ4kuQ`#w&Lz%TzEhxZ;?^Bxd5U-WDm!(Kb_ z`T2JytH5`$-Jwk;q^?bji{0EI(x0=irB4Fidw?cNk=Y^#T?r^kWQ$~Di3}pcCmQQZ z>_9=AzOOXaqY@0rG3PNB0<=u~L&(1bPZ>||5?Nc*401J9D1EI>2oMpc)z>K!eDq!w zWId4pJ{e<0SWvfgUui~8;tB!e0$GPZg&c_gjv992vsk0RI|H+_UL(yYoe9_aE)!P2 zv-rMyo0xoC1|XKT4GhI*zXTBuOFl_z{YbHwJAY4ehpI{}P{enUC0TYxKo(J)Q?)+o zPc%`NTIC|Oue`(pD0kK0TOw&0`Wi={NYS^#1LF=-92g$o5lI*&2ldDrAOR~9u{q%g zHfPzy@A-#gi$|QPjFr2w<?`2jkQMWBoRAlw-c*9!?9lI$-9kF{sMI1@eJI^1ruGT@ z;O?ymVf9Ak!{CA4xLLTH_PZ@^cu`O-16q>Q84g3yg;!hkRLbSDa_teq*X_0o`0%0m z(D0WWy)eqKb)m*1j<Dnr#%mW{2Y3?YVW$p7jx;yB2CAXfCVr+bkxkrxwcTN+5@M{( zg()+`mF4~RVsHSP4@)__$AvX#!ftOV!DV6>SlgW~LW&z_k`#mg{XMrDKH2a&a2oX{ z?OepcE{Zi*>!*tSUT2tkG>HrbRGDl&kD=FMKan;-2`q;f|CSQ=YW`cTolfk)%-73% zOugw0wkplou3o$h7v3;b#eKb96b(4y^&A0;q|(}Mk@gyv)|f}9l4nS4sS|gb8}sGZ zO$f-we22dF=cU4(<fWezzciPXG#~D3ZEQhTH7zN@@vE&4!D0}}&(0s89FQ3<+wWh2 zVdX6dA(kF4EIgd--TX>uv@xxpDeTp6XtZ-|X)jLLEb@LC+g8-eCK(kjtbdgsE(c=x zl>sG62d=SkaaMWIix5;#>jejNV2^%b-sZH(ybzhoS3A6`Wv#^0Zx=k9#*sAk#1`9x zg4;z3?lMvrV-u6~Rw%f^kB{!61`g42OJ$U1K-n#IupP2-FDB}){5NeCy=0G3e)uGy z={N<B)R>N?vBlS7%Ty@Y)vV@REcc>O<AQ>u{538kBpWw7NTb{=<LM2_T6Oc{bZC)L zq(#yly6M@JTVFSdw8&dS^uyR#>8?`tR>C8`xnfJdp*$J|(n#)?bC)n}^~OrC!yU@T zVjJ$LMG6d0#)4j>^tztTIUpTYdxdx@G1@zaF24f)0ZVMg&AqWz1-(pjwe~rdVDvzO z-Y1$=+YR3lC0b8S)_Uo4{|6AqyL4bc>7xPVO$-}qT0gyq4-P0x#DF5ce2dr^P(bf3 zLfLMSQ7Y+M4K~wW!@_5v!isY-=a=kWA|<&cgT6Q8DJMrZkTtDeIj1>vAOx}s<@_d1 zY3fgWLCU#Eko8R>E54!e9Ya3e>xd=Ex?~7h{Vv09l;-qeraP3u-MfVXsF0zO?5U(` z^wu%@M_m}8!JSo$^b4L~bzP?Zrg`FXy`slVWP$DUSIvU%6Q9vAoh9_%dzcqgIhc3q z@}8-EneS@D^fouVF}x=?a_>oP2b(|z{}(Xt0p>kzWdchg+-o<OvkN(|P3FwF<lB22 zyO1NBKMo%ib`td@_oFgWXoh+tY|tTgv&*ot5|>_Rs(&#i2qa5f%mtOBe}#Du+bI~2 zZQE5kwSsVd3kSKe_+S=4mY1@k{<aLq^{eck8$o<nH4>kaw)wW?FWyyJU`~A#Uh`JL zC^X_(4ZV3}Ve|;}X2m&n%LNA;mXCSQmr4GExNpatrWV`RjbtrmH#xjF$=WK&l8~Uf z%h+2a;JvYJh2Tb`=FHSpO{E6@`V_5zRh+@VKRGio1JYxG?G!_z1wDCepMo4(CV&7s z`DRCQqR@kSWcGcBajydvvhR~(P#Uo<28GnmnK#J>04fQ<sFag<)mogH+1CoLYyy|o zO|7rXl(bC2dXSngGQ4b%NqaN4HI>q&0U%j}44QEt&ADPPS*R}Q5R;-4pJ&_vMFtyk zrZLP|Jc5KCx=`z~A0xR&(sdB)b8L9*UYju&w&ii&2{g`v+?Z>L$%2-yPopGKtA-p~ z;230bvKz@5dvT^1>y%u+_W<l3^e=f2Mls@;H)pmb7U23pUA+On5dz<tAUnwqO(&O) z-@Zf#i4(X+NvB)D>QYe>n7J$$!|t#Ef3ua=4%>5a07wiT;uz~;TG0K3O2$tJV2_vX z<wi&2hY;episL$buxb~G@ZaqhD9~<#ldeEiom3dk^8G6S+k*UG9;YhmdV^wDdg$7i zYy^q7QGAe}CLn77-*<W(mN11dQ4Jo=z_kM~9U9SD@Xs>#7K-OgJc~4!Fa~$Rwt#y= zF6U1H87y3Xh*#3CI2x7k(E~Vk9snp7+t@me<EoX|EbEe$H0wtN?D6Imc_|+py=d&6 zj^djhyByE@i@0gE{-RBri9zW6G1^nOjL$=fz-T6)`i-i71%jhTI!jOwE`RW-Bj^%d z%Yt+}P64AEXd&~?XJ{}vyFCWMXKCG~>5h7(aTg*yL6&#lde}D0-LYscFo1b8z|zcF z=|;?hsF~e?nGj`O19-rRR8?-oQH20f%<NP6&K?ug5(Qv)GCBu2ah-tjzyi?Sh?XMS z9HsW*V!r5iAj8d>OtiY71;1!Qdm~Y*3>VqQ^{u$;DZ4o^t7-YUri#DQ%{Ta|6WoB5 zxLG;S8sP7q5sguAWHG8U|22CBHi~@S!^#6sqF}&AeMrZ`dk&Zq6H$0jS-0Vpm;#Z+ zcx--IKv>!jfr&Y2#0&%?sklR_61Kw_6;z39&4@0^+?Ey5au8UB3~=lbtqs83eJ;SF z)RjyE`7FmCBHR@KW1?ynBSx~f7VRYh8Bt;`WoI_N>-(ww67EL?3k{SB9EKFy?mw4x zNx?^9tJ3#VQ8s1gTZouZD&G|43Onx{_?OH{(IzV|6cij;r}u%>ttBP8Kqkf5OYO6| zISIJT6lr|gG%SPHc?BhvXqf5|g{CC&RIk7#ECEA&=RJ8tfxQ9`YMF%%j;<Do`jq=G ze2umI<@nBqH;=NgY`R66#fBTDN@3@4d?+|VEC5ypf4&UvVwMz&jsV9+X(J}dT@~Oi z53=C$Bf&{5MugCxBwmy91#iTn<%oDIT$_s6!}Qe@UDZ5te*IU&@WTayTJ2Jn&teRm zFth><`>7BU4v{$McG4;(AIJV;(HTe&fO)7~OG*a2d4a%}AZ&tG-Zo|DjUtVz&KE6# zK|;BIG0N`r;EN>~5P2nf3=J!yCRHGPut|i6{v_r9R+Gxu!{V#em&ywx=g(iKqgkVM z(X5n6*2;B8j?bryHm4+C>kOCA*C2SNkJ`8Qf8M@-qM=t%V6c6+iZsGwNc-kd`+WE! z8nlf-V&7^A$!Ylo)2yZLnPasDjj-({Nc)?jDY)r}+F)<D33;)eXo0=mYQa-bdmCRa z=ne+M%d@bkiFLt#Ss9B_x%sW)p2z@e4Ftn<G%hK)C-EygjXy~WndnZ|mfs$THO{8Y z|44vUr+qI0dOzIpTEc1V6Ih&&lvS2sTdlVQTJ-TS&>%4nEEA)w^m7O1UQ$=)%zlP} zONt<-{v=5uc!5Ob((?8FlqPBG_5A`yy(*GgTO=eDzcw)%Cfejy)<gu2nTdHx>77Ex z+r+g=xe)r^2ZO8N!1}^*V(pyA-+7+$=YkacLj-k?*razdfk?h!qSY%gODK4wmWO{X zPPn<koQ7)-a9ZSJ(``KerInZeKokeNC>0|XuNcVV1N(22`Mm(ZQJ2*NaMqCiDU9+M z!*Ep){R&PjSKN&TXB%-Z8Ou}-EWXyEe`Hf%4)7vUG#K5Py}NWKF4h=LWVJ4`xw?l+ zf$Qz*#Ax1&B9oMHh)QX0(Qh&(3~9y?#uxFkLpqg8m&eFGXqyws$+nH+za1!u+Vt<p z3G-sxK%2(#9}NHq10x@oY|K%sF>@|$jDp4t7maBT@by!vG1&J_?=DS4W3Hu<x?>6w zu^D>0gT`DfGs$gel^vGnqMFm{Sbi<)U=^ovM}T{v_J7pCAK<HK;4i5rYraFfgY*j$ zGNyO$V3#gw78UcBTEs20XoQTC*g71?|MMF#H(D_Gc^3R00hwTMkv3e;yLj+XLh4+s z%q$AYYHm69mA4F2o_BSZ4x8Y>-2wQGBXnZ^mrGc?bvo8MSvz1spgD`Uk!U$&1RXiB ziRLDk1WeoL$6{zZ(?vgjfdRksQ|J|JABy`ECh`m*He~nmN52(q!R-kxq=%5#(KIn} zL~My()Fw7f<R<|!B!jiL=kA;iaIxQchU-5gPQZSrtYPQET@3_-e9tiO_aRp&{Z^HZ zJHTlb-mWRlN|Wqch>H;>;rMA{+(1;m2|oZ);nqGU6zokoKJN)7dKi3EIEij9ciXht zv8{BCA-qf{#{6gCkKc>mtqAa$FGGaMK#t4K@nbN(oBm8cIMe$S7UyjwVs!oZt(d7| zb7u36v2AI6Mx7gFOt#8!i!#n&PTXIHyGV1R3^>@om0y9&buceznv`%ftx7WsYkJ68 z{~S5%M*=IvZ_I!|FZ|~vJF-4R!5u?^u^+US9nODKzmT%6BDOV&Lb4ea3U_`R1vJAA zm;KzPN&FU+$qq-ZTw&O#+%e=Ff|CJ>;X`W~@D#>A8Uzz08Hu~S8w&sUN9<g|BW^3$ zeDDWS+=KJ@svzxwe_1r4kyb#3RaN9WA71+znNrbv@VxF4Ql`pAF@Yqq`}ct17!psV zq!f@EJ-2-d-LBzxEh@}WWgmXVs9Qe*)^O*ymV5o~I-Ae%yLS^jyf&1^XHYoC{>CSW zMaZFqcBaJ7AbD{0QyR{S8-5R)eFl}o|Dq<3+(O(~@Q@@qUI8rpFf@<leWElzh=lDW z)_%r$l)v$YSm`{uSi+of%P9Ush&DTfJ?-4M^g7PABt~Gr2|w`?LQ+OtA{xQo2$vMn zALoi-m~Whm0>R7YtXnVW*CkLFO;bNc&1^Q&q^imS5H5D_u)|n@dtbATexLU{scQ8K z{0foM_$;z`D{_?w{|y0C%Z20&&Dpt&zQ4BJpWKci^kI?7NTNTQzcmF_o`V!e;%S6F zJS-FAa39pi-)sRKso=2>!1=<ZMWAmv04DozN>vs8dX%H8Dv@R(LV%#G#~Sxxe+^nk zsF9cd2PUF0g@!sqqHC~&(nUH^^o|=R5a~Cl2D*y$vd2Tp+J6RX39$y8jC@|dM``>3 zErhERybREN)Ngz)K(XBinxhZ?z-DtnP*59RErJ3Uc=n_hba%dh+}n%wo{lYr=q9UE zNAnjagDSo7TKZ!=T~H-1s4|QE+%D-??CRk+dI9(x8jC{;Ek6>v6A|<R6a@NsXpOjc zKQRr&fnN?f3iknkINBK=n}q6c-%%H^KL6qP?y1PmW4)*>F|MDKC@eYBn%UGK26~-S zGl-TwzX2rlBrtR0_pr!G^)Di+J$6S2j0<80!7u-pfeRop27#nBXiP?;sZB=^zi}n7 zAr7(_6R7j)KmsR<{*jkNW#yot?{0$VS<-$1guRjcj<CrZ6tWJlryd|on$(z0fQeZ{ z#GL%UL}IEaM9A-3=oFIQINm~jIRZj{bHEhoLVj}w<<~><>k{(o9F*Uje);_sb@7}A zvkP7}TkuPvgR*;^=>84a4Ul{9rG1P|boI`dV;+7?wu*naOZ0FxRS61_^r9v-4);#E zY5N&2uGCzxSQS4)W<PLwLM!Md;Sk7!y>sa|*9KaGF6Q$mfW3*gX-Hq_MK4Yyrgnj; zodHzA?*st-l3xx)@D%p)2KtC<gxqJJBc|xVR~(!A<Ufcb;;}o<40QkWhyFqLPeCF& zUUWY=@zTB@-A65jP50X#GBh0^|NI6BAud|sn^B*+S>|_(x0A0EZx^o>Z#NH$cMe}d z@9X(O5%utS;+@BD5bx>y8u6aNFBk8be3E$2;$y@+mn-63$kWAp4mbZdVdyhA`}jEo z&CR9!jChyx)8f6DpAzo?|ATnn!e1Bf75tERui`I>_Zt43c(3Kph<BJjA>QlxqvE}R zKP28N-znZ(d82r5<J<5i6rQgKm+`wP_4!5$-Y$Yo6kH*K<Oj|xM39s+Um$`HQSb&4 ze1w8CM39`j_+$}$oPwi8@CgcLir`Zeln~Sp%^0}xQgn(so27YE#mx!O1AoLmInKr6 z*Vh))T?$BfO{8pwKTANQ1o?}U@{K~a<KP~y*G%U5iB*cro4O*I617s?-qcmelucGj zjyH8pGUYZaCD)s}Hkq>2O7VD8!^xClk+M0@JA1uI3G#eO>Bk1M4dD+9c}&Na7W~x4 z^W9I2X`?aIn(tqUC}u^N3E@Iznw~oF3u^DPqlM#C$AYCAxt@OBJiKYxf-=kv?Mt<@ z@X&POMyy+@81d_RUncfmaw-S2oM7@C!T;0Vxd290UW<AsGbBR@%pgI-dk|0*#3&CF z0ydEZf)W@AB&3QG$zT#g5|h1oSON(XY?3jR+SaPa(~79Ix3<SVL~XStKodZUAXZU1 z6_itV&TupyBg7h+`>lV^B$Ei%bK85*z2}~RmA&`>e*f!VYyE3s2}W2t*mRDL+r|C9 z-BHe;*vF%45dPr)Anr&THpVEgmMG^A`}nF4xLvr{9lmX$=(*rPy-;UNcrz=pvd2^n zSL)zXy(+bgPpeXY3}em*(8-p1R3Xtv6xu5|ZyY%94b*Ei^$HB@{&Xygz<DtdNR|Bx zU*#HVe2GU;&gE_E8LA+eOC;w|J8TKbaD*ED<(~3Q?p?lTe-tiXQn=BF(db8%VEA10 zqjfj*F!LkAhBIjH)zBdUP6W@y^tR*dZX2T-g?7<1ql_su>SZ$vqKpY~r}R<HrfX(; zv@s0F!7~eNh70}%wqxT?8Hk-Aw7+e{t|KRWyQ21--OY-m>4}Ze^cBgxPX`g{_}Sgj z;{Nz*KOU0)AzWJ|{oj-ROTOmlKz&%Al>X0?;}_&#p&K`I^QR^C95bfVxkWI_+D`>} zt>jK%J**<`M(5?Cj?edJXX?3IZ!;XX-nOD`GBoXw3DKcgA;t75cZw>n{P>CB`0p+K zcAB=$-}-B*tgp>p$pu-PZ65}AingU;cc-aP{CS#uZd=cv$ANvoIBDKk^!U`zi)x%3 zO}h2-qJ1qkU#m*}V0Y?_%kHo$RFtnJ+SeK_Wq7hX)HW*&_EV*V7;VM3zT1~HZlWN` zKoT$!a07{e3vdAbjBlN4$hhwmPm`y~^EA)XJllD;^X%Z+!LyTRCr|jI_jNVdg@vQp z+HIYo=I{rl(xt$9;9f}^>G<1FMlUsve79;Ja*=r%*&;MYIBb)C4ZNt7u23h8@9Bhr zpMU&B7x}i|PcFf;Z_?6_@=99aKKaz@lS$Gi9h8L-5_p@PKNA5D&^XsN?nwPSo9_eF zdLOFR`$a_3QnpZ-p1%4Z+V`RAh5Cq)+akhI18NxRvkz>(52a_FTXLDI5iv;namw&C z@GIa&U@veGcnx?Tpsh#J)+2c)@=WBJz%zlTizmXO--_pnfa<p#Jh7_%Ejv$?=tuUA z)kfNP=x-nqm<)v5m~zts5q+V)scl3*SYa%;UVRsyY&^f(dg~9Wg%*hhYoYxJLPx|( zyLhoMjaZk#yErH2VR^I5Oc=}*dj)i^)fj9R?+BBm{H^{s0yly{HDz~!Ux|pkc2Z$% z1RP@FrXY0vJ?72C$q&4u)bxi8Qd?B9Ca7OE?$5#PV6w{Px{`#Vi9)<uL<~64Vi^(j z{uYI9q^XIkTQmRVvF<Xo_+M{3%rxjjqI;bXkmz3Q4rr0+GWcdg2<-cE5*?hX?^y|a zqfY`hD*@Qy{@sC_J!XYVj#E8^JW#)$6NdR?h5ES~Q24v-L}0jiRd;IUbd|m@`?%7u z6(;G$QxmlO`j?$B?<asFdi_+gu!vrk9Xus%V-9;<P?BsUUWAe`&^JHc(VCtp0y2TY zeAt`P6Y#=GR%|4Dd<7_0j*6g0ai8LLgtLVQ?wh@h^8|OQoLjkV2~~lc!NH-AC`?#X zU|h*U9a4eO@iBK&tYdZpu4wu|m>#>Dr^J1SBolnyV}9RqJggkQ8*<!YIsQsHJ{WRb zgJb@VNBN=_2}O@s$$QLY%KZ`Cx62<emqjU~B$z(WWBwA);B@&y$NiHMQgn5k(I+F| zI8mJ<hBak(E-pc6{WR<^Pw)*Ak2!-5dZT}BHcjN#0x8?2T%?<Xk}*kwAQMDuPZuvE zw@dl(9O5zOhCDeQbSZ!Ie&K0O3AuB8krRwMKM+9f&4QPNZX(e^a(m;@#?jE0HlaPi zW+ZISaC3N@s2&Xi)yD|)B3QYRyw`_+s75N(T97zMx>+(SQV0ZRd4+J6-wAV;j}bDG zv%Io9W*{f53OE^I*<~OQmV|J^>++U~gs?uqU)AONpuecLv!SalJPu)+X(BJ{f_@Sb zzO^&8k<xE5KP7$i;fRz0N(t@exF<=CJE`V<4f3LJpW4$C*_V3`wrBcn122ur<%VUP zIaNq$X58;#VsVx&x!8>7HQx#X)yd+Fi7lCizq9=a15F?HhL8a-u~!iV24Y#T^QU!{ zzy%a@KNyVRv@S+2W^M_82|+%>&P54kmL$+nE{9_yh&RjZ#d!=%aOw5)#$eD|pOKzl zro`tR4>7@@#^heAX)EMxiF)EM$opT5EPsMOt83~$^A}r{yuZuunYhI78Nb9#po4sS z9bXXlmrD%Xd|2k;BD{-CLiQf4p4jVY!aTfX$$?N4<?e#qS_tYheH+J5#sp=mK7R7r ztGKn`kN;%@_T%N+!p2{6Z{ZT_-a^JN9p-#lPvqq`UINcau?sDe5S*&13s<cQ{V=h> z@HW_`44C#^9PeKepR(9t^ix+E_T()7&373PfdQcx5<zy$(J;r}aA*9o#h&H)EAnsV zhC=XgnA)F!bh*%4PMgox2{FJ0W+`hvSAozyW=uAZJkndnBcE@U`kLxa(bQrQg(0>d zW6?^fPSE2)<fAw4=kNH<ShYBv(>R)C9OLM|7oMi*QJXFi0yOtBOB^24%Q{IIMghjK zzr7ECJkUUM1NN;M!~Gh^%nP*Ee0G%)<I7Hr4j}e0$*|!FWfgkly*H7k&|m6qP%q=1 z_oeUxSLDi?&yt{SW+p(3hn&+GJ8M1G+LtRQhd7PJkL8Ms*1k@cF@)g8AQj3!Yq?>c zCt3Vlio;UG%JAx0$gewJc0L!s@JzE^cQ}9hvac;EFoH{5<fmWL_;O8KLCvSba9?Nh zwYh!G`%|+Ms)kW$2NydlFE{L|2iA_|)2@vFqJ=tf5!QCxN`EmbmE&cz2;9sCKj%NK zNU*&L(?_cAXF>-zKgHecr=pD6z7x@U|5~UW$gZvHPc0`w^<R6LnFJT&OlD$KtHz+$ zU>an11p`i85cF8iVrFY$?WJRB(CCI_ao25US9JC2K$r@F#Bi9TUS4RZ?!KMRv9o(o zPU$Cx$&J{e^&=Q?X!rREbDV+EOBaQpQGbW?%0`C$h0ZJXAAtLYapTDIO5#5%+&Dq} z!I2;2bK6AzECtpB-Di+5JFiIU;IrLf&wpM~Ww_vZC6vZz<Y@vYfMdX6U>~pxcpd=9 z{X3jjBr|_dDm@aI2+R_f|Ly0MM}H{!s`HA6*9)9i9;YmFq9Me#U-5nn(D(?SG0uBl zk<ef5yrR+#r`3(sf7y8@l=f1xxCJN#N&y|%2-E@J2k4u>!+AwA^9P^d@AJSu;JCPi z`{r*suPE$5&KG&P=1Z_&gjTD2wu{9r-#M_eGc`i>i!uiI&P5v|&!lC*8wa(xpP(gC zDA#L{I2=Uuk-28IymRPqfSIt&#91c}i<OXTz6k>I#RErv3nvcIClH@!{vM)zJ_weD zu_-L8NU*G<xQC7$Bg`f~d>lC{d0L!!VW10^+~>qmNB~Y8H+F}!P8_d(PpvjzMJQmr z)F<LB!IdzF`7%cck^aLb_J<@DD#CfB0B$E^bzV@-Vr`q!&`=<s^68_Wa_GZ_v^?aY zU=VZGXAzm5x{LcyVkUd8JxnNsqtS!3fw-nje@5tui@0AmI$b-*P5O7)s<z9AVj!{a zusK!aLirXkGmKBs9|=}}+<^)RB1ao<^{^>kX;2B~<|3JfJeWv@IXo~nTtp$}Gjie> zs8UDG*kid(%i5QCBp~MA;#I186PI-nZ&k7!k8BiLJSuR>h7ArSYHD~<iO|JiNP|OD zR=9Lm@@Ua+Eq87EAwAZBPGrH*)zP)xEF>B0I<PUu3WRluor4HwG59U@*GT3C4#)*> z=T6L{zqglekt0JjG5z&|GWb4?+B5+{p^fgTufl_KesA{@I&g7rNq==^SGc5GcM%$N zDBG2)qExz*Z;jGN_-iD-y8i2BCq)p}2lKcspLg>w-;qwg(()HXrZa3jd!}spuwBVX zwmX!iwU<Qo&ds@10tJ4pnneT?LI)M|HS1v7YY$x9Bv-SsJ$Cl+xPAV;6Eqk-srxG9 z{LT5_#k!V#{GO}ibh%Xvw5jxHs@yzGY~@?`(yJD$GqsX;X$pypI5DT^o5eVu9#Z@z zw!tumU}_j8#vZXTB&Vb!;K(WYBw))aIfHo=I@urFFfxYS9PyXWVFQN5U;5Dw%tIz$ zw`nTQR_c;mZr;Y5QwPf3_^KR#GvcZKkFXD~jQGWdi~_bGh!>?#7uoQnunw|OlU~+c z^L5Ak3zWhaA4B^FhMMboO0k*O2GL)lD9_<$5b>czbCvKcSt+u*gA*=%dH>Q-Bc11h zzO7jbXN)&5mBf=w2anK6P$YcJZQoWa2#E!v{hFKxxm7Fc)Fc9iC35{|Lp7bIDjrhC zgMiGf4r2yquH{U7WdMio;XS4Y%Ry{q7#kv#gZ07i`7eo#MMh_o68E*Fd_#nrri^4b zX+slbsv>+8pmck%oLDU<yTk`c&RTk8mVQAOK~qMQ#2raos*zaqlvJZo>L()8NRJ#Z z8DReF_eq2zsjEXGs)yS{k}ykS1B!ZrY0f6O65^lslJv3g&wfpDg-&EwF8wrc=hSwm zPlV&n%%yE_@onOwK?)`GNJ6MQ0drMuBYWCH5dkD)uErh@*k}#GcFl<-;;TN+5vb|b zctkCv;*zL7f)A;QuO%(81r0)&aUz4EQu;kA!k@7i8RZ)koMaWW`5cC6n@{w!!J$5d zx}l)4VP4xL=BKi&c^{n_Qi`q@G{vimblcVR53b#<Dz&@nl0LRIeY=p^I1%{g=J)$y zJ4tny{}tcKG0i7qLLJtU;jl;LnJu8bQak(kB&;UDjom{#=dp=&3s}YXYz3C()*?Ie zpOr>*X$FUOQFm!A8JKahNSiBdY+x3bJZfD8n{--FLUM4+Mx@{vM<W!B9QJEa7>_ep zkk)U=K8R(rhU(X_faI*ZO}cn`5t*O}lx^j8|0rt-)o=Axn^DGcQTi!#7hxLTq?|HQ zB;T6(nrsCeYK0_o%)IO+CP{n#+|;w1ZmvD2c-J{i88bp63RjyKOE!B!D3U{RCs*Zh z&^%65VM(J34230U4bHS}M@SYS9TEK}c%)2<$h1|T;##zRtjRt@#1T%J=kAhOiw+Z% z7DpyWVK@6%9K^uVD9LDKj)dR^aZK6$@Lt)l;sj@`QSzBm{TlLG{JKM_^60Zr2w~nr zr>P-BaV8OjjWm?hQ3$ZCx+lyD%q`~4iNF9xWKi$t&pzBhwN9Dq-o^v9@=abLR#|<P zZAhQVQAqt{KX8b!o72`jV*h~V{I<6~6`|CSYi!tcFRq-OP_ri!l#8;keBk$FyRh37 zh-vx<nho1V<uSlQEH;(ry7_afSZop_PK$8boQKoq+i)shoyMOs4}aFK<j<xGJnq14 zb2)CC*WtE#b4An68qy4#ciQ16Pbjcq3r`~(syir#2qbbvYtKWddcXwdfk_9bi9C9n ze)1pT^3siP-~5MsCpR}_o2eh^LneJBm*p>KZqkLal4YCRR9VNhIM|rBqmzzcImvcx z66fD`zj4}M-A;gyA17cSC-oI$`q?*q&8~)Qv|C#(aSFd|hYbf}FFVB?n3Q?Svt+Td z#AW4x=9X}?aizE|`r{}3l-H&b6-{_j#STR!lD001vu;K>KT;*^ChCevBwCMFpg{JI zv``4YsjK1&142Pl%%A#u3rbGso1<_fngd1`+}!pMu@z5Me_5UFxiPYKqFL4_`WXmY zeWJrZUKzrrMuBcHupOq4Wr12sE*T-*CXh;FA=)Q+BMN(?DJ!kq?%Ww`xlG3e;lz2t zY?tl;i?gHO_79VwJ_cThq^>FqRUPlqS?IuI+CfSbNkv_1l~7eGaCwRmuOF|ic1ac2 z9ldo$TN~LhX~J01P75nyi&d8=Y@QNZ5e<=6v_R3rM}nN}5ae`^LV&sAD<=;*z=!~` zvJ0@i!orMuT*5kyXNzJnxfU!+#FTW(syy@yj7XX8#zD_9TWBSg(;KZ25VO;is;-&R zf(29n3U}agkC`j4sjX{=`D1EkCC@enOA~v{GOLYQKAdPN6+?W+QE4fLMhrW4RG<SI z@?qI-KY>bH5^K(rm4T}`=ra<6GP2}cRBE9K8^r(O+ZvKpJDL~qNguPmwQZp-8m7V@ zN^KFU8@Q*E7UJswZD=OYtct4KqA&NDKSOfc-#M>@o#)4;YLqtENdFS^3K9&dFBr|M z*loqE3X2sMmi8hv#7H5<kgna*Z>rqGc_y=ShEbHT^m7S`?4d%B+(-6dYGI-*t5E+< z^P3gqvBIHjFQNKiDKj-p;Y*MmMAXOK^8{gVhrBn?Un}%9(JqaGPiann?Ll$aX-{n1 z!AnT<v!xN*zo+dH+)yR$d)}fNUUOcJ)Xz$%vH5mur0%L;@p((;IW$raH52Q@7``Z{ z?rO>WyjwZ7y=hrziEYVZVX)-}D^!8a+Bc<5#*3h1xvWqS7I$WL>iwNNvp;P<;TX`| zOF6ZibFB4T(YJC~mj~?Ev*ln|9sgYVFTcLiEi{YE;!ZWj>X*aK9|va;HulW-D`RH9 zw=O#R&of(j+rwMS%oCi;+oFskQ}@q2q4x)O3<fKs&%WtzzFD};-G{Hxx)V?F$WHWF z7(*i07&g=2&}`P4G>k5e6yDx`kLvQs@M`+D)vGA+`X6%Dl9YOA?Qrurfg>XqT9E@^ zgWxOT&hX+yo>7=HCb!3BO$p54I3{j@qbN!+nu>Ti*O~vw`5RU!f_JXS+*x#-zFp@m zr}GGVhgT1=p-TFp#dtAVjM3QdpDoi{l*z?1s=d~(E;Fkn=*i8+oB<M)E&5W?I^M)M zknOw+hdKDcP%Q}tuai)WoEa!7&-Iumsf3KA>cJ3Ib?Vh+rZWNZ$pO`dl8LcBv_cAA zc18lYB|rc<0u%wEdTGEup|%_S`L>@ui4LTkvnNApm<q=y*er!iCv8V>#>+b4WIF<} z^J}=w7L&$J%unXCb|Wy{z3WVlMDNhz3o7S-3)6oqjx)7WX0HTEH<C-Do)>{-=9>q+ zXXtoVPHKfVJMk8bt&h;MII}u~0l79^#`5CdW6Ef!eb|E&Q{UJ$n$yP;^Jd)qhw~ej zB?c~nN*%0zm%$}MD%|<q*x?^2$-sGY)_qDIsjoQeKH{k^*%_~Mm`JG>VZuS8W+Qtf zS+Uu?;oSPL<h#s;p3UgxZ3c;@9(LZhh9?&RH`z;Ufi?^GL|RbrQ|i$u#k>L}G`jMH zn3`(J{6K%B(Gykos(!d}z)Wr!%sjC6=V@s)qG1MJN~uoVlq{jeI#XKPMI;@L^`RBZ z<X%K$e<C_&9&p~HQ%fuI$-p5?U{jDsR}QoVqzzw}E77mP5v&U`27f1F&0F8zlxE2) ze=M@fh-;2;q_!ewec2frY%fKQkh6Y#Ck=~JBu;z6vOFXzd7O1mkt`yaC)8Gn>0Fhm zEI{|uQr0z1gk4W{mj*%4Z*00DBL5ko{4X}2{Dl0wAi#aSmq_r~FBHL|;}P&0k>OU! zhx64h5vSKwffV0W4JQs2dFBrfQx(B{AK=BGc`U!}S&BFnE6QSvw?`~m^}8j(4$IzQ z_WzjR?fD!VI8Aa=N;O96$f<JeDN}@@k24)dnpa7nV{o~|y480HWd%qi09M-w5HA7H z5t)dJA9OeU2(Ddz+nofIxgaM#sfN{v)}n+p872aEFyGb(<(TUTpJ(1Bv9RRP<lWbe zn*X9W;yA^EqlAv1#u2Gg|1wrNw~{@z1W#o_GFNuVYLs|BsZ*hkg_h`Il0YDiCHm+W zmS~Y0wwCC%sMd>IWzW@IV2KtfOm4MwFVU~FM5pwL+-yY-+$4mvEEjvjP+5JUm8n(w zTE>U0(q9W!VAi2soP~_07HUw%Pt_tTYxD^79a6Fw-(PjP4xwLxv3Ycv!%RV}m`xvC zX`nx*(H@IF+EJ)392Ul)-t@Oj>L>VGb7%C~V}eWde6yYkCcYR2>L5_BFiz*D#3I_* zY)|v0XvW#xv=Y0=d;t!!=&NUW2H8t2>2H>>rUwQga=@Hd8s$Z+x+rNk0%K7J*cGvn za#2GFTwHgcx}(hY&AoeJJ>OtvvdouZfGLkWz?5@JX6KrhfDJ0`xz(qU+f2hY)2ykx zl5dMrs#`m^OO;aljpVNpXHI7j?NBazjFr-P<5NZ{lysyym6ILI!i}auR#r=s8-sHH zo|F}x&aDr!mLdRfA3dBON<#lrL!uSm7=o9syd*hDuX`F0HkX``(5Ixonj|KOyUg3^ zQc-Q1zi|oXoEJ7t`z@l)r8HbVnV=3@R147(4T%Z?MF>|u+vhb+dmd}f?PMV8SW8Om zNGeF;<~ukE61hiT7Fejt`7XmU^|R{ev+p#`i$*Qly)%e2TjDu=LV)p<*h6u5gyTBv zF2X}pxW+%<Fj!P}AZas9RZ`k$Jvv1owwn8%W?{}x!+bkqQCghlz9l!;d?w_cXMXg@ z&=}JPT7tF@L2ahnMB72@q!wG|Y3@>;eRIVAvq#45Tg=WlQSFR|)0f>5G`p(9xM7}| zFKtPEbWZkN=1qLjD*3c&W=C5QZ78nOyIt7^bEIKqkTQs5B8y0Tx?-c7F3RU`pPOs` z_?hl<U&@p~CMd0Mfz5AN1#S&Vwsi0NvWloHbK|_KEOMjJm}q8E=E&9JuvOv6IZ8ov zcoQ8$o#cQM?=kPAi}LePW480inT%^k+4bRRjjowT_3NF_?RV~cwfUrD02;pIjR9GK zQO@U%q%4cq2SOIu>A-(AYe*|k@#n%-mt4P66m+?M)nmWXqWP-^>As_PEzQPQQFQR8 z8-h3Q39C3Q91oVz2*#A-KL%2bY;8!cmJ9uHA`|<v{z~0`eQ`+GHZb5=o_|mCd#>C8 z$NX`>3!Xc-34zzMQ(s0p^HbkPL0@}t>MK)QkhQHnsYONA8Y3sjLq95yD8o_vXX;;L z>_rtUVz~Yrx{&>y!BX_$%=h%m(WLsmNbc^@hvIY`rx=`G3p{Y^ZC06YKwy@l-|)Hh zU=6u>PjJFvP!kJ(Tc+sbM_EIjrY|G=W}4NvvWB>k^nM4`K&TNt=8t0byviN1Lph6= zm_yLKL?eam;`vUGWXllNQpvgH+$3sPb_yL=Bg|EjmK*vv&mK-$JqW8%=|ASK>2#&P z_Hr|Y5Dkgu7#^X*C_?v-?p6bh!n7?WmSW!JeSwnSm}M7T5((zV1Sgd@d05#6N@`iq zIof-m%Wyrh&Os_zmvwFpf)UBIy{<8BeDtovo%NaL&_|tBV$bJ-C;E$apFPY)zG1$1 z&owMVml>CDJKAdL5zE6EYkt$pYmLfF?wDG0`I8N*#DQu4-A7E6KcN`U27=18Fz;s6 zgRIKZJ=&bE;>8osoUL9Ryh=TbC>SSDx$a_ae4Sb3Y{(ciQKVJ&x*C=an(TMl4xLH2 zXX$$5{C?<{&`X7#bw|C!?@WU>(wf=M60Egk4C)t`yyBd`(C=(qFld4VoFf6R4+pHN zK8Ll6cJ>?zJRuIOK|)?8A%{uGgm6egv3W?S%i_2=V{%GzdHk`#X)(c}lhxAXtow#+ zFHp)}cHUdTEBD@=-@HTIVx!PQ#~t7^T8*<#^hS~|xc9~6%di^At;m{`IHO;U1JyJ& z?$6LV#Y%45gWjnIu3a5-`VNydN5;meS;L)mKjUK-hMMbbbJA&Cbq9~|S=gw!q$wS} z<Z(t^y7;u%;xGk;LG3lcOw_zt$NHvB?!ZTuJIo+vtIY)W*7UDg7nZYhgoJ`|`U@?# zf&SRW>>!$M`UNJWuIMmgl*gmkLk_ZS(?`c%lMZ(&XFK8NP#)0^vSl6vFEG>}Yt=qY z>WCarV-#iQR(@uObO3d9Zj~Ae<}6f(n;Hky?Oz`=r|lj-I0#^gmZN5;ee)19uN-uf zbLW7xnioz$Qqpv@afoy00q1WU<dahvrqv*^Tb#kb-RY_O47=@EAgz1AjGqJEU%$BD z#{P{%{LcENgC^i$Gs0h&&6#v8aM9Ug50ykMQMk~#qpD^cswS=IIHD-)jLMD@Eu?Zl zXzx^j#tYp#^O##HK)x^gH2Y8oBzw6P^DLtqvNE>|&pEgH8343To6masFPXZZ+i2fw zw(TOJh6NWV1zH#tgBTU7eP2E-U^0`E%lVvRweM3##v6R|Hc)r2ZWu6UP8uu_SKF^7 z5Ei+b&tX|(bW>KeN_C)b7q?VhC2@*pFT<#gaK20zQb%f_ppm8Xf&=AdHBgp?2g=0N zzUt06{THYVS>0fh!O|&%MP5GTWr9DpB_rmtxWJV%cw()<Th-`+9pNw^epR)x<&H5y zNn}p<5E>yvDADh1(g)ek#K;gD6diD^_G>B>y~3*2ri=>?y@k#|fr6r^y=jEkKl3E7 z4M}aqf+KgXac<4$1&vT`xA250AV##H0=5ek@I!)vK3Iwme$0oDmHS)WNy*wIdYTYj zZRu7LFxIS58JMfP!&x-K4>+HK()5vW=nSz9Me#w3T`4{giqU44ixK<NS-`KgQcF~+ z$)Xx~#$%3oPu5N7C1^%ShRb#_>rd!tunBaOeaO;`@Gg0VSi}FyYeUlc*jfuoTFFEd zOR8Z4RTBHrnM_v=qLS_KTIyGvYt1|?i!+C4y??`sV=b9MS0Ju6Q)C6T`W3;Z%o85d ziENh~l0#_RtCgzGELP8JHB9M!#^AHfT3W1T^h?P+q1$V+gEe9y%{FPzuSsRs@Ay-r z&&$%MWa*cg*GZ8R;SHL@d5gHczoSYe+a|;+l&uAZooROH4pP=g`GeNXPLfFzb`#S1 z2_-JE19Kg4B`^wb`OGw9drEbu!t~n%qeIJiU}$Ld55)5#)skz}?aZlPlQ8z#UJ#-| zYO^vmzd2P;V*j5ETWQQ}A;NIjCB|%xCEmF;jXrG6JdLv!xSAK@X@Sdl!B-26nk^;Q zowGGGn&>N2cRRN_tq77S`L(hZ^0u`V19Af$;OpSM*@-NJvG_<B4C7r?o87^iy*8Wb zMrpq6c67@_sMBrzt2>@@hy5J^v<IIiJ1y|!Q!YK$isdqQoTPDML_TG>d5CVZ8v5tF zwQ7lkRx1I6-#=R@`m)Md`q#Na+?08k)vz7fn~b?P7;2Kt8t}>IiMVUrKGxYujGZWb zLanz`MzcgG7IDuLahiX|7e$b)I}hh9p%{<(HOiH54&kp~Ytv~>ArTCn#S8~^$oQ)X zh^?`%yGTMs6NUtL_ntBL;MA&#6mDP#8v#36b}%i_U$y`ln#i)B;*>S*Pvjco$ClL? z%=q~elnuXpj0WVh4c6?B5^b?x@W;C;BYJ#|yQV(-^BV8xS@qdyP_7}XGtF%KKWAjn zLectNCDB|O$s?N`pgU^fn(!runKLO{ZL*IDdN#goZ=z)9FDy|a4b+7tIf&rq{hz40 z&UP~#62@?Yv#|LPJJk&HQ3e)?F*x^tH_b5TT8Z=h%QKll3XntrekU{W1ucz%R_!vl zu6JTwtI@B2wku%k4*@aLHLf+aS<jd)!%M#cTQ)o{<ty6y;vrvlB!}@s{CO0_`ltZs z3fJ>dHs*_rgZ{Wh2W%`KXEPa`u}qU^8Nd`Gtzm`f-1-zBi0iySJ$H?3COIw5Sts}8 z<+Vm%m)h*yTBpLCW?Q^x1F!Vd+Cd-yYm=~2?%cW>C+BZ7&rJ<xIqNRtBg?sU36IuH zGk8uOY8JK)$4P80(iq7HrP*8qcI&NRs5o4XL)iMFv+i5c$~Hy3oMB$wp_-Th?yNKL zAangr28eU(Pbpw+wfW(1ey17vQuDUsxUj8DIfV^QQ0G0jGyEy5^P3)CLis=cawvai z-5gx4GVHJ%DF#_>{WkI2`jH<!Izhz8W}oAaF^s~#^M*_X2XtOm#D*kvo)l8G*-}>+ z<t5PsS#I^dD)cT0YpM^@RaIwOUV(>b9w~ZgNut<T7H`U!4Nfz|w82YY^r-kX#J6>( zRG;4bHiKMr_Jpiv$aIiF9yPwvac%awnv<K8gmQS^5Q443>2~cp8C&!2=C}j(2#tMi zjAaHm5bPpSUwa%RYp-#*{ngfz;(tXArj2S*S=&8{L(57D#>Sy>ye}&aBu|6{WXYoR zJy=+9jhe&f&&Pd^I=}K3&D!?hXM~&KKNL|-rI@I}J}9IBm%CT4Pr(h2lA`RU!W}#z zTt1O71J@X3uEEEm16dpYC#BMwiUd{3p3PQWl4fnzvSl_Q9@M}hNeE;-!hE}nWGGc1 zPd%s4GDneKLvjGcS1HB`9XaviNE~IJ5)rQKQ@w;(FbQa{p*Dyv{NvkHXAi;5a-v(C z`r^gH3Wfzd%G^(xROzgOnu~kNc%v|Y{{$u`D4$wu6mDT|WDAsPz{x$PmVRmi?cZF+ z-U3yHJ4XL3ya%Jx{3B1Os@RU`W_KkhwTO`EP<`_mS~KR8U+7dTIE{Ja&Tt#Gon$nl zE(dWJp-%nLFGR6dIAy<_TXIXDnE(n>ay2-K8OIy5nAx_qmLyOgtQ6Fj%*-=qe@HKi z0nCq$syuW4!}7)5RiQ;?m+>J6id0FQbux>KbU4=#b?)3Fg%G{}A@pSk=NYO@J@Gx( z+{gD5$inzGt&2vIBM=9%&Ys$We)D#=;$X>?T(d~*H3&8|nSsg$L4-o()4BCDnT9d8 zE_0<UD}u4Lw;fd;UFHK1Sw-$AMSfUDn)r(v5hd^Sk`)Y2*Ymsk6l$eaD9LZJB+_ZC z?#wseq9VdWMx##Wq_ehmu!z%RL@#$oFo~*F_DyBDl?uh~G*>`&P_=OS)^ylwt2<5* zvwCk}v{^^0RD(Mo4Ce-R%T811{Z?J%>mVhkZSqsZUab`AH#ms$5NI#mLjx`}s<cDr zd(bT?x#j~c4Ean`t;tA{$e7DliznxUyYchy8+U-d7c;x*N+iTJseQy>ob@d<%w|L( zocFxQ+iwIN$`Lbg(^wA>sk1CDaCHq1dn;88aoAtv)vqavty0V_rw}n1A$&%RTW^fp zY)}2T(vF=bG5SC~B*4=@Q8ksK&3H(1Umvsi=+-mqUO_!8b(bJ>RT_kck`^w4=oz2- zwmQq2dD6<s{fq(TOjQ^`MAUW8j=)Q)pKZQtBiUBnNhi3h<-*+j`^bGNgVvX9{sEGR zNO&hvNz2S>)<X=Yal0`ZAdBD?=G#SKJjZ;G*RVweNW@0_IHN=HbIvdd$%?KtCDDXl zS-puTv{HE}Vwupja?ML6W68l~ZcsT0fl8=k*}`^H<U@)jw_TZWQdA3@6ACGl0(xdK zv6O82hzlWrpNr9j5G_^2VwJ3Rizru3uw+-GLsw+ulN!^ZTID%+Zm>hOs(rtPvK;BG z{Y=ms-NO?H{RW<b%v>f<@R!l@1ap~PGv8k0k3-q__{PCC@7C5Fh^ikPxV*RPmYM_6 z0kfvSzBw?k$ERj&%~qlI8?ow$vto~Q!31rW=wT=8P}xDGS$oy?u<(xFOYiHeWgsP# zT)aFG=O0)ID^^KfcN36{h|5_lk9ol<i^Xs#!VJ1=)5TyRo4{4=Mm$HcD9|-JJ&<fh zkv<f^_enN#g)O(Tku&Sh7?;YX7>2Erhw1%VG`GJQ^J0PAl8jr?Yx*E!U4=K2it(Ud zQ6rhrtZtLI1dW*3;fTHQ-7(GY#w6b|7=sK8vsi6UF!k;QP1I`7T{{)D%r}j9f6JY_ z`axh=-H>^}`P?qy;<rl2GrJD5de^xKlln23Oy<F+EPK<&BrJD#Zc35s&LNx|Ji}&J zXm_K>er7j3=la1cXR(2P^}~G5U@)^Y9R^W~(Yf&ei6pNG>XS)n>Z@{y@SU?&+x_PP zwi4TIm{g4?h9h`GI^_u<CDQ?3teJ-(%{L@AWgch0dr;Ksu;h1GD-v@Vd?KD%8=f^m z;~-ZoK9U+x<NkT(4r1pAmLrJ72_nawwuDKdgr0<*Fp4!2$;P1$QjoiH>ccL{tvDS( zC7i=<#ERSNqK5joFl%3Dof%|KBvEU5qQ@ea%d`kN0xVuIHgfZRyPgfKsk;4%Cssd! zRZy@kcG~O{Xfb=dB)TDUpTCpV$~J|+y5e-hioLf6Tpsh<?=bFK?P5~WABz$q<20L1 zgK^Njk^zL6F8vdO>o_n_hSP(E;qsV|s#j?^8BAB(5Hf@{N#z(eFM>tMXu;~1uk&K# zE;Rzpm%)M=;(^<h1j!5clYZyCd5BydPFZnUI5nru$8oe_LALrZ21JRzsDzD_MOjK( zk00E|rj4;t{uou#?P7|O!p$-N?LHWDp|9zbIyggai<?WN4itPete-Y-G=orT;ji9@ zLZ=ymGJHhw=e8|l=poY$b}_LL$-0_PXX|5f%|!A;LiZHb1)@|=P1CS_a;kCA%$JSh zxHn`U3rtF09;IJZvp#yJae2*p+iYVjBMKEb-&RqNfxq_i50rAjaJMzrB+u3l!Dye9 ziMZoyHmr2-3XD;W@iY-=yLLglF9DNcS7U9=rn>O${@GT2SY*Q<WH6{6fu7s|*TK2< zT3P#Nn0GR%^BYE+f1!axn_2WK8jB`q6;Wudt(Y3NX71&$7WkD1)-24lgPvS-^RHD$ z_24>}7pOi8US|%YNHQuI9Dx}gPKACg9BY2xSRbtn$9iuY9oSBsmKgV3c(wEn=%-nK zD|%o2NhvE{vveJc2sn-K3I^M)_Ob0-oNJyT-AUD_7&*4H{_58PGyIvmsB7>#GLE9O zM_%Yt+6~?L-bud7E~=~mV~m!R6?=_4{MCo0O}Rex{k}23X2mR8`5ssCbIoY$sMFI9 zV=R9en4=k(1bGJ`JxbOSr0X_SY1>&AMP{IxnuM;$(R1rZhlZsNjrRzXB)?&li~var z?B}%klDLWDf^4)nO#Q>nX4L#{frSueKHj{6e&Bw?L>`d{`ZHFsWS3ZmQoc`R>p!Zt z)MWNo*@Q0+(@KUAHQ#)n2!1ZmKjktmg>5tXOlEwvo@l;@bE{CFH1qfBRZ%~VD0^FK zYxkW_5R7B$+uR~XI@m1DA|0`t2h;L9#E9HeM)1wN?ybHta2K0&yD%+>v34#tOPGE6 z`4T2CtnhJRUgKcr&fU(Poo6zxgN->hy>T#X%%RSme-YWd)|AY6<Q>vM0lNYNQ&yn% zUR-P#5K5nU)Yx-dWQHOQ5Jo1y$g%9Mk}!8IeeMr47nESfX>;2=StXRpPm!JqVOg!O zss1JtXWbeChf1w%MT>HGxYweE6iHzp10k|K23P|lvUm(HB!wrCOfHOAC+sN2t35LB zOh)u5<f*#!IgOW4DXvp=1(w6XCDf~{2e47@U+w>B9syRTR=6tT`Fqj2nANt5guo2m zFRo1DZ{oTuaTy*M?|e>p@X=?|N4fNYq|h*m3`rtjb3S)K(tr~W*Ak!p*pjtM&|QE` z1g;w|3YQ_Trwmq5RfH^6ge+BrELDUoRfH^6gsiVr1gXj)W9({XO@BJWxitVf8QE40 zLOB<V*u~}OEb%~M+|m&GzUoKm-f$<4BQ9%Yue(_y!71{a^buyY_Xq#|XDDPs%>2Ws z#?1K7`D%?yj@5<1AMJ1LLKc%*@PGU7yMNKNXMh&qIPd`w1JXJYm<B8WRsu!9-9SC? zFz__+B5(jW4s-yHF5&^nKrT=M+zs3V+z<Q!*a;j0jsd5DGl2bbjG6(Xfr&seun_n< zPy*Z!JPqsx{seRYgCIwZ1g-=!fTchQPzP)SegOOo_$_c4I0bY7age!&1CxR40S|CH zPzG!S?gbtLegW(T4g>E39l%IX`-wm@a3j$7_kLoU_KWm1ZQ4y~+M(s#*}g5UJIHUI zPSYM7*7F_qSY1$D>MeBZ<?cJYy4$<HSa+`~FZ8-sSC+4FS5%g-@>W$%;b7krZdIkX zK=(%axhGU<{MY7`8>NNrvT{ksyGmSfD<~6()x~9nZqEk2sJu*h8hXL)rCx%Nv^H*R zh4Ps~G%44(vEA{?E4*bY)KyihDvK-hDHR(epUO-M>aj|vX=}79ZIxE8Rcc=TP0<Rq zQvT7GTA603_bVh>ZDN^GT57!tV<JYH(52a8w3uj@Ju@@2pZumLX&x2Wo$Og2>(H)C zO3L#<8gjb@-_RT@i&pZ}wDlG1`8fyy(bwVN;ozTqYEO+#*R)Fkeo@gjd%u`iNB_71 z@dF1rU4t(gk}&k*OA?0-A2D*&=rQiGmyR1h;j+soUUB85$yZIeI_a8gr%szb<GSRO znW?j8U;nkV^c&`6WX_$JHUGw&7Gy76<XOBVXDJptm*;=|=37?WdfUo^+gBBOSKm=o zTykgWnzHhWyDF=6W9_>28}9zb#_CO*6`47+OuE!lUR<VoD=E`WTBf!{Tgcx9+EndY zS}cRN1**Im-riy7mR8NJ^m;X(IbJ=tpwv+B^CI5UOH0dFN#shSOfO#Jb$cr-%PZZQ zHjvI;x?oXGj^!esTF(51^CCXAj78b$^B4BGESZrsb=ttV^fGrrMMY`xssg>3AyZUP z<z7?3uq?n`*S%{hbQ!Xx<pm7gBCmUnJDhiE@$Hobl^fi})VZ?KyGk$JFeT1Y>Mf}9 zGO)|^f>p#MMnvkDSGlW<ii+||e7pr~+^Z@4n(|67Y4Ey6m0*f0Jmr`2O&u6_l{>ws z7zSx)=geOaF>~~y;wpDRRh4(m?WG&sg+^s@*&XgOl3FXppd!U(#d>i;Y4P1E`M9ML zo;e~F_7c;5yKx8K?hWNeWn@{WxaaF`g03mA(%q%ScX~-(s#EE$GD>xK`D*v7g3?mS zjFyrzUA3xwO@*4`6R%!XT6u+gwNbW8wW*rn1wDl-tI{itRXUaDzw*o|EzK?{E>m@v zdS5H`R@1wz+_<C2T~$%Aij{)k41fZrb3}thw%0X%+N-<nUaRw#EVbHOFQU-pWvjeX zzIuB|K2o+M$zu*FN%?v*C=B^un=JlDnOb!iIXxlVMc#r6tF)wZ?R8&L$92UK5mmqS z#G7%!cvX7gm&BVc@hS{P+uGtv-6$yS=^*Jzm4TFtIdOruzpcDXmhGz<II?=Hg|)j} z*Q7|io_eeGlzC89PInc0*A}nx_Jj?!k#~Is^M*}9TBc`as&>9cwU0rLp)hM0cEx%T zdqSa%f;;<$zi_*RA{7?s1r%YR)#VY>Qce0w?_GwsN(v*Rd`W15p#xdT))X_L7<AI# zGTe<aqe>cZUBTaR%G35qstwOO?!9I7T6x(TZ<$UVB&=$~^M);`yu*-yRjR=yteQ`& zS;TaiuobdCcdtZ}ge-4fHG(xQyLeS)c~$vp-JM&kYB^`pr0(`uU@dwqPg)%FVak*# z+AQ|&J1SYt$_iMKjj}t-%GZ@$PalSwFjLm(v2k&1q7rPTTO#x0<g^R2zWR;gT^RfF zdm!SyiFdUb;*JiC?svpDyWh7(yu<A4cIU1@_xpDu-eYQN?y0G*VMDgvQ*+OjnuLD+ z*patx-AaLyl4?9P^_oMQczLoXuZI1WP1)nACwuqAn)(`IX>7|yMMVxr?D~p|brlu8 z_G7&NzyG<lzW*kIA6ftU`ke1O3ry+D{?%z;{MS2tt=97|O8aX6B2(C+_56#5xcycB zh2y*bzwdwT3;pj#!{h(q5fD||{SSfXuk;J|pggxk_56#D`fC5e@y|D=|6^`{Z3akA z3H%G^C|^DAE)ntm5B&Ou|7x}E3FXpy-mSN&D47H`wOf33TkrX1eM6)F-llKex9!{a zf9Jd3d*J&IKJ@TEJo1k}_~E15AKUTx6Hor=sUQE3pFI83pZ(J_KmWxqfA#Fn=bnGz z*S~r3rQiN;SM%;Ydw<{3x^Mr1mk<8o&?|?Jyn6JtKfeCPu{Ym(`}jZq>75fN-+k}Y zzx?@qv+Z94r~mDP58FTb_m4Y1Idiu2)4zPy#pTGq`9O5x1J74F5dCM@|35qbzq$SY z+JW@K{^~&bpI!f~teI=p%&Zd9gjUFJvOAlfTV6Ks)3UR#E-bv77k-{>O-lzj6LXGJ zM`vwe`P%OHMVywzImcVUk<<#1Zrov1>6&(<QL56o5nNf)O0TFa7MetMLFK9<o^!po zR~j5t#qY*~GWAM6lD<Z|lBPylk`7QtybY3u#Fw}dN6RVDjmkniB)!UF^|rLgsH_UP z<#`LsyrGY!pwZ%-U0$YqbBxflK$o~0@if9~gp)8D{u+n;5RD~|qiOlN99<oH#C=(n zw{p?#C7cuH_Z*Ui;(_0Sf+{_oGv-=I4i!d)a<jgzWVCE(N(Fa#Zzx}%t}V;STr&0A zDH#hOKaeL`QvwP?c_<b&wAzO%Q*#=CcAz<E6&i;&qN!*xX*hm!7A;(~Z0UGy3TIyV z4%3sS+^&+reNCZqzlFRuaH?3dq`X`*;Fo1R{+IsNT$HXIhC^v1_TlT;X^TN)A3A?h zkaeNtX&N+m^$dT%0qstH;qQHY{9hc`+y7vM|Bol6X)git3&+1V!hhEEG%XE?^zWPh zdoz3cAC8DG@qV7#+dndY@lTy?`OAAO@8NRv&1cv3R=5lKfBdxz`;SUb(^3HWT`2xl z^LqRDE$3%9_V({vzB?Cwx&Kc+J#~9A;{8~k_9|b}6Yd)k?|t)|p5Hsa$aLQRdYbkj zAir>ZBmJ+sIZe9;i1gppryTXS_V$nL*F@;USBGfC;q?2K?~0NO$CrF(miG4V8~^$Z zz5OHem-q{7zuf=oExrBw_UHKT_4e<Z{!8Ega{r~<d;9k-|I1JG_U}6{zx^Z2U*q?O zCwuz5Z#fqHtamzn{fl<@_U~KI0SD5wrJs^X=r>3MojVc!>izt0p32|GQ&|!<&s*lL zgt#=vqLj_iD@!xiLc4)ag`Y0mhdDx04|5>O?0E&n`rPu$94I-ZUTbI6zNgJmypm8b zw#R?6K}3&8G^?PjuoMj96G=6@ywE81&V^XJ5Sk64-_kOLVn3%6QZdB99CllX;qZc@ z7kCTSdcWZQm!4Ftg!43Ql0B!?3odbKG&x8?(hCbA7K8uvi;85TR7l)8<!jbZq6Nie zWZy1jwbFsHBXz%C(#X*ZEk}505=Y9rbVG$#n`QYHK*g*Oq##}U9hg(8msadkf$Qu` z!_>R(7W^M7e*=<zSs3Zivh2&sic|{~X0Bfal11&wPBAgY*eTrwy<d->UzOp7hJJ^) z(nEEn>)w|f1UFHnFHL(gIt%)yVs2=UsdtN!af>R6N2;LxK6<|NfDkslh4af`eF+6m z)0!jQ!9K$7ITAO0jz`lHq%{_0X3P5tN(1MlxKNE5FdyxD`_j@X0$BW%S@IR)qI^x> zyE!eh<x3T@LwX~k^goMeuceCoIv?ET`}REAT8$y?O!NZihau7+qv_X_ImC15+au{^ zg*g?)WmY%e6eSsE_E0u+bm3l9rE9w+&o6pt3oZ~NPph-%6&HHv6cto1EzcH8@eLbv zueSUA=`dO!SN&kk8ci#(=UOyz)dKmp#fG<XgU4H`xH7N_RC$>_CDPVQi&xzl8mB*r zXq(Ugqj7T7_*7`$Qn*y<Rchq&raf$1qL(f!TL+S>{aBS?iP!3mTf-#?^-i5iIkYIy zvkydkGkwAIZ-|;(YE%_T+BX=hS9>d&X@8DhFekg9!fHo)VvMc3EtZyt8%Q%FL(vv# z)_jt-m-$7!IlWy7(<b>ZP|O!=%4zS*IFa1D*?m7zHOeWzo6==yb4tsryrBtvuQggi z>ruM)a71ku8G41G%jkWeSExKKMrK~bDzG86%1Nf!ErdI}rlO$I+g;n--Y%5-n3OSM z9OV{N77Jr0UArlB$->M9oCgX^IV_dgmcUk!bT#ddR-D2`tF7<Lq%A_7EAtph04cpH zgwBAy-GGlqoBj9i|LzvpB?|HQ$<v}xh05y+JtH0nS_#&3!JqgG{P*v_Ti~m<z`{SL z{pRPxewXpD<I>dFDt#B-`T)nMV2ubY{4f4woL&rs$D}RvZs(Z@^aBP0$f0Qcfmk3O zaD<-XCf`y7@e`h0*iX`xxbj3Rhsr~yi?|I2E((F<Jr)r6>41EvhrZ{8zFFW^oFyUm zoY0eHTBV=QQ}SjxR_Uza=>}MEkw-%21CX*xJ)}G}fRwp5^xVQz{C$A<*8x%<xd3<t z@Pp9zcAiqc#{tRjM}UNT4v;z>0>u9fK>QPF6ltGuoAKJcHblus#4r3Eeullm-+iBb z{ri6ZweT1652y2A@9DbW&#J5Yg1`S7ZE<0ygjK%_6UF~))L&|G!66XZ$uBqr-2Zjj zfSUY2J`{?Ef`>)h9gnkNt=zI<%h*uoJo%3Gvi%9`S^L8iUGkQ;sYX4YB7F0Xw|2NK z?=SqVMfO#GX`$z{Uom`oDEv;szw+3r$A)YF@|gM9%~oO&f4kG)v|Ysz-BF9*y7eu$ zcH3JeZ(SP^(t52udhAappr>84$%<L}Zx-!tPAFt}4gW&KztLga@bq3O{H@<o&c0<8 zd)47zQ6Nog|1eFf_$W=QADON_Nd6LDp3>KX=g3d?)=o1`;TQ*b%AWlwPua^IJY^Ce ze?Lv_#ZU7T9HXA+5T3X26r5%}&tW{f{+y-_=ed{X2%h)y6kMT@=V+c8Jjd`n@h@qb zo99zJ$MSsURGP91=Hj`YZ;j^$9_{a?X?OEH!BYm?ah^e*2YDWXzWY^x;iK><NmuF= zT9h<tpA!21!H?6l?*iL^dx3hO4yXav0~J6Ka0}o8vVd7YGB6ED0wx0!f$@MF7zrc- z34jZT2kb!Sztbmx2}t-8JdXi~fxW<sz%#((z@xw;z&2nbPyzI}_w>2+=@jadL7(4y z#b1Zbp`VPADB?+6d4_+|PVRo+k#0QiPsT~)ucpF^-~N%s&+_Cfjr9Hxzk4$Nw)lss zmkZ@sGN!|sN4^W6LqL8q7E^(*12QhY4?GLJ27C+*reTtRg@9a?3CEd<Up}x7cmVhn sa1{7=KrVY;4P*nQ!2j#Nzb3L0-REZu{lfJw?Z8eMa0{>$=sSM?C)~1m4*&oF literal 0 HcmV?d00001 diff --git a/lib/setuptools/cli-64.exe b/lib/setuptools/cli-64.exe new file mode 100644 index 0000000000000000000000000000000000000000..675e6bf3743f3d3011c238657e7128ee9960ef7f GIT binary patch literal 74752 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 literal 0 HcmV?d00001 diff --git a/lib/setuptools/cli.exe b/lib/setuptools/cli.exe new file mode 100644 index 0000000000000000000000000000000000000000..b1487b7819e7286577a043c7726fbe0ca1543083 GIT binary patch literal 65536 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 literal 0 HcmV?d00001 diff --git a/lib/setuptools/command/__init__.py b/lib/setuptools/command/__init__.py new file mode 100644 index 0000000..fe619e2 --- /dev/null +++ b/lib/setuptools/command/__init__.py @@ -0,0 +1,18 @@ +__all__ = [ + 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop', + 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts', + 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts', + 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib', + 'dist_info', +] + +from distutils.command.bdist import bdist +import sys + +from setuptools.command import install_scripts + +if 'egg' not in bdist.format_commands: + bdist.format_command['egg'] = ('bdist_egg', "Python .egg file") + bdist.format_commands.append('egg') + +del bdist, sys diff --git a/lib/setuptools/command/alias.py b/lib/setuptools/command/alias.py new file mode 100644 index 0000000..4532b1c --- /dev/null +++ b/lib/setuptools/command/alias.py @@ -0,0 +1,80 @@ +from distutils.errors import DistutilsOptionError + +from setuptools.extern.six.moves import map + +from setuptools.command.setopt import edit_config, option_base, config_file + + +def shquote(arg): + """Quote an argument for later parsing by shlex.split()""" + for c in '"', "'", "\\", "#": + if c in arg: + return repr(arg) + if arg.split() != [arg]: + return repr(arg) + return arg + + +class alias(option_base): + """Define a shortcut that invokes one or more commands""" + + description = "define a shortcut to invoke one or more commands" + command_consumes_arguments = True + + user_options = [ + ('remove', 'r', 'remove (unset) the alias'), + ] + option_base.user_options + + boolean_options = option_base.boolean_options + ['remove'] + + def initialize_options(self): + option_base.initialize_options(self) + self.args = None + self.remove = None + + def finalize_options(self): + option_base.finalize_options(self) + if self.remove and len(self.args) != 1: + raise DistutilsOptionError( + "Must specify exactly one argument (the alias name) when " + "using --remove" + ) + + def run(self): + aliases = self.distribution.get_option_dict('aliases') + + if not self.args: + print("Command Aliases") + print("---------------") + for alias in aliases: + print("setup.py alias", format_alias(alias, aliases)) + return + + elif len(self.args) == 1: + alias, = self.args + if self.remove: + command = None + elif alias in aliases: + print("setup.py alias", format_alias(alias, aliases)) + return + else: + print("No alias definition found for %r" % alias) + return + else: + alias = self.args[0] + command = ' '.join(map(shquote, self.args[1:])) + + edit_config(self.filename, {'aliases': {alias: command}}, self.dry_run) + + +def format_alias(name, aliases): + source, command = aliases[name] + if source == config_file('global'): + source = '--global-config ' + elif source == config_file('user'): + source = '--user-config ' + elif source == config_file('local'): + source = '' + else: + source = '--filename=%r' % source + return source + name + ' ' + command diff --git a/lib/setuptools/command/bdist_egg.py b/lib/setuptools/command/bdist_egg.py new file mode 100644 index 0000000..9f8df91 --- /dev/null +++ b/lib/setuptools/command/bdist_egg.py @@ -0,0 +1,502 @@ +"""setuptools.command.bdist_egg + +Build .egg distributions""" + +from distutils.errors import DistutilsSetupError +from distutils.dir_util import remove_tree, mkpath +from distutils import log +from types import CodeType +import sys +import os +import re +import textwrap +import marshal + +from setuptools.extern import six + +from pkg_resources import get_build_platform, Distribution, ensure_directory +from pkg_resources import EntryPoint +from setuptools.extension import Library +from setuptools import Command + +try: + # Python 2.7 or >=3.2 + from sysconfig import get_path, get_python_version + + def _get_purelib(): + return get_path("purelib") +except ImportError: + from distutils.sysconfig import get_python_lib, get_python_version + + def _get_purelib(): + return get_python_lib(False) + + +def strip_module(filename): + if '.' in filename: + filename = os.path.splitext(filename)[0] + if filename.endswith('module'): + filename = filename[:-6] + return filename + + +def sorted_walk(dir): + """Do os.walk in a reproducible way, + independent of indeterministic filesystem readdir order + """ + for base, dirs, files in os.walk(dir): + dirs.sort() + files.sort() + yield base, dirs, files + + +def write_stub(resource, pyfile): + _stub_template = textwrap.dedent(""" + def __bootstrap__(): + global __bootstrap__, __loader__, __file__ + import sys, pkg_resources, imp + __file__ = pkg_resources.resource_filename(__name__, %r) + __loader__ = None; del __bootstrap__, __loader__ + imp.load_dynamic(__name__,__file__) + __bootstrap__() + """).lstrip() + with open(pyfile, 'w') as f: + f.write(_stub_template % resource) + + +class bdist_egg(Command): + description = "create an \"egg\" distribution" + + user_options = [ + ('bdist-dir=', 'b', + "temporary directory for creating the distribution"), + ('plat-name=', 'p', "platform name to embed in generated filenames " + "(default: %s)" % get_build_platform()), + ('exclude-source-files', None, + "remove all .py files from the generated egg"), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ] + + boolean_options = [ + 'keep-temp', 'skip-build', 'exclude-source-files' + ] + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.keep_temp = 0 + self.dist_dir = None + self.skip_build = 0 + self.egg_output = None + self.exclude_source_files = None + + def finalize_options(self): + ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info") + self.egg_info = ei_cmd.egg_info + + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'egg') + + if self.plat_name is None: + self.plat_name = get_build_platform() + + self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) + + if self.egg_output is None: + + # Compute filename of the output egg + basename = Distribution( + None, None, ei_cmd.egg_name, ei_cmd.egg_version, + get_python_version(), + self.distribution.has_ext_modules() and self.plat_name + ).egg_name() + + self.egg_output = os.path.join(self.dist_dir, basename + '.egg') + + def do_install_data(self): + # Hack for packages that install data to install's --install-lib + self.get_finalized_command('install').install_lib = self.bdist_dir + + site_packages = os.path.normcase(os.path.realpath(_get_purelib())) + old, self.distribution.data_files = self.distribution.data_files, [] + + for item in old: + if isinstance(item, tuple) and len(item) == 2: + if os.path.isabs(item[0]): + realpath = os.path.realpath(item[0]) + normalized = os.path.normcase(realpath) + if normalized == site_packages or normalized.startswith( + site_packages + os.sep + ): + item = realpath[len(site_packages) + 1:], item[1] + # XXX else: raise ??? + self.distribution.data_files.append(item) + + try: + log.info("installing package data to %s", self.bdist_dir) + self.call_command('install_data', force=0, root=None) + finally: + self.distribution.data_files = old + + def get_outputs(self): + return [self.egg_output] + + def call_command(self, cmdname, **kw): + """Invoke reinitialized command `cmdname` with keyword args""" + for dirname in INSTALL_DIRECTORY_ATTRS: + kw.setdefault(dirname, self.bdist_dir) + kw.setdefault('skip_build', self.skip_build) + kw.setdefault('dry_run', self.dry_run) + cmd = self.reinitialize_command(cmdname, **kw) + self.run_command(cmdname) + return cmd + + def run(self): + # Generate metadata first + self.run_command("egg_info") + # We run install_lib before install_data, because some data hacks + # pull their data path from the install_lib command. + log.info("installing library code to %s", self.bdist_dir) + instcmd = self.get_finalized_command('install') + old_root = instcmd.root + instcmd.root = None + if self.distribution.has_c_libraries() and not self.skip_build: + self.run_command('build_clib') + cmd = self.call_command('install_lib', warn_dir=0) + instcmd.root = old_root + + all_outputs, ext_outputs = self.get_ext_outputs() + self.stubs = [] + to_compile = [] + for (p, ext_name) in enumerate(ext_outputs): + filename, ext = os.path.splitext(ext_name) + pyfile = os.path.join(self.bdist_dir, strip_module(filename) + + '.py') + self.stubs.append(pyfile) + log.info("creating stub loader for %s", ext_name) + if not self.dry_run: + write_stub(os.path.basename(ext_name), pyfile) + to_compile.append(pyfile) + ext_outputs[p] = ext_name.replace(os.sep, '/') + + if to_compile: + cmd.byte_compile(to_compile) + if self.distribution.data_files: + self.do_install_data() + + # Make the EGG-INFO directory + archive_root = self.bdist_dir + egg_info = os.path.join(archive_root, 'EGG-INFO') + self.mkpath(egg_info) + if self.distribution.scripts: + script_dir = os.path.join(egg_info, 'scripts') + log.info("installing scripts to %s", script_dir) + self.call_command('install_scripts', install_dir=script_dir, + no_ep=1) + + self.copy_metadata_to(egg_info) + native_libs = os.path.join(egg_info, "native_libs.txt") + if all_outputs: + log.info("writing %s", native_libs) + if not self.dry_run: + ensure_directory(native_libs) + libs_file = open(native_libs, 'wt') + libs_file.write('\n'.join(all_outputs)) + libs_file.write('\n') + libs_file.close() + elif os.path.isfile(native_libs): + log.info("removing %s", native_libs) + if not self.dry_run: + os.unlink(native_libs) + + write_safety_flag( + os.path.join(archive_root, 'EGG-INFO'), self.zip_safe() + ) + + if os.path.exists(os.path.join(self.egg_info, 'depends.txt')): + log.warn( + "WARNING: 'depends.txt' will not be used by setuptools 0.6!\n" + "Use the install_requires/extras_require setup() args instead." + ) + + if self.exclude_source_files: + self.zap_pyfiles() + + # Make the archive + make_zipfile(self.egg_output, archive_root, verbose=self.verbose, + dry_run=self.dry_run, mode=self.gen_header()) + if not self.keep_temp: + remove_tree(self.bdist_dir, dry_run=self.dry_run) + + # Add to 'Distribution.dist_files' so that the "upload" command works + getattr(self.distribution, 'dist_files', []).append( + ('bdist_egg', get_python_version(), self.egg_output)) + + def zap_pyfiles(self): + log.info("Removing .py files from temporary directory") + for base, dirs, files in walk_egg(self.bdist_dir): + for name in files: + path = os.path.join(base, name) + + if name.endswith('.py'): + log.debug("Deleting %s", path) + os.unlink(path) + + if base.endswith('__pycache__'): + path_old = path + + pattern = r'(?P<name>.+)\.(?P<magic>[^.]+)\.pyc' + m = re.match(pattern, name) + path_new = os.path.join( + base, os.pardir, m.group('name') + '.pyc') + log.info( + "Renaming file from [%s] to [%s]" + % (path_old, path_new)) + try: + os.remove(path_new) + except OSError: + pass + os.rename(path_old, path_new) + + def zip_safe(self): + safe = getattr(self.distribution, 'zip_safe', None) + if safe is not None: + return safe + log.warn("zip_safe flag not set; analyzing archive contents...") + return analyze_egg(self.bdist_dir, self.stubs) + + def gen_header(self): + epm = EntryPoint.parse_map(self.distribution.entry_points or '') + ep = epm.get('setuptools.installation', {}).get('eggsecutable') + if ep is None: + return 'w' # not an eggsecutable, do it the usual way. + + if not ep.attrs or ep.extras: + raise DistutilsSetupError( + "eggsecutable entry point (%r) cannot have 'extras' " + "or refer to a module" % (ep,) + ) + + pyver = sys.version[:3] + pkg = ep.module_name + full = '.'.join(ep.attrs) + base = ep.attrs[0] + basename = os.path.basename(self.egg_output) + + header = ( + "#!/bin/sh\n" + 'if [ `basename $0` = "%(basename)s" ]\n' + 'then exec python%(pyver)s -c "' + "import sys, os; sys.path.insert(0, os.path.abspath('$0')); " + "from %(pkg)s import %(base)s; sys.exit(%(full)s())" + '" "$@"\n' + 'else\n' + ' echo $0 is not the correct name for this egg file.\n' + ' echo Please rename it back to %(basename)s and try again.\n' + ' exec false\n' + 'fi\n' + ) % locals() + + if not self.dry_run: + mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run) + f = open(self.egg_output, 'w') + f.write(header) + f.close() + return 'a' + + def copy_metadata_to(self, target_dir): + "Copy metadata (egg info) to the target_dir" + # normalize the path (so that a forward-slash in egg_info will + # match using startswith below) + norm_egg_info = os.path.normpath(self.egg_info) + prefix = os.path.join(norm_egg_info, '') + for path in self.ei_cmd.filelist.files: + if path.startswith(prefix): + target = os.path.join(target_dir, path[len(prefix):]) + ensure_directory(target) + self.copy_file(path, target) + + def get_ext_outputs(self): + """Get a list of relative paths to C extensions in the output distro""" + + all_outputs = [] + ext_outputs = [] + + paths = {self.bdist_dir: ''} + for base, dirs, files in sorted_walk(self.bdist_dir): + for filename in files: + if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS: + all_outputs.append(paths[base] + filename) + for filename in dirs: + paths[os.path.join(base, filename)] = (paths[base] + + filename + '/') + + if self.distribution.has_ext_modules(): + build_cmd = self.get_finalized_command('build_ext') + for ext in build_cmd.extensions: + if isinstance(ext, Library): + continue + fullname = build_cmd.get_ext_fullname(ext.name) + filename = build_cmd.get_ext_filename(fullname) + if not os.path.basename(filename).startswith('dl-'): + if os.path.exists(os.path.join(self.bdist_dir, filename)): + ext_outputs.append(filename) + + return all_outputs, ext_outputs + + +NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split()) + + +def walk_egg(egg_dir): + """Walk an unpacked egg's contents, skipping the metadata directory""" + walker = sorted_walk(egg_dir) + base, dirs, files = next(walker) + if 'EGG-INFO' in dirs: + dirs.remove('EGG-INFO') + yield base, dirs, files + for bdf in walker: + yield bdf + + +def analyze_egg(egg_dir, stubs): + # check for existing flag in EGG-INFO + for flag, fn in safety_flags.items(): + if os.path.exists(os.path.join(egg_dir, 'EGG-INFO', fn)): + return flag + if not can_scan(): + return False + safe = True + for base, dirs, files in walk_egg(egg_dir): + for name in files: + if name.endswith('.py') or name.endswith('.pyw'): + continue + elif name.endswith('.pyc') or name.endswith('.pyo'): + # always scan, even if we already know we're not safe + safe = scan_module(egg_dir, base, name, stubs) and safe + return safe + + +def write_safety_flag(egg_dir, safe): + # Write or remove zip safety flag file(s) + for flag, fn in safety_flags.items(): + fn = os.path.join(egg_dir, fn) + if os.path.exists(fn): + if safe is None or bool(safe) != flag: + os.unlink(fn) + elif safe is not None and bool(safe) == flag: + f = open(fn, 'wt') + f.write('\n') + f.close() + + +safety_flags = { + True: 'zip-safe', + False: 'not-zip-safe', +} + + +def scan_module(egg_dir, base, name, stubs): + """Check whether module possibly uses unsafe-for-zipfile stuff""" + + filename = os.path.join(base, name) + if filename[:-1] in stubs: + return True # Extension module + pkg = base[len(egg_dir) + 1:].replace(os.sep, '.') + module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0] + if six.PY2: + skip = 8 # skip magic & date + elif sys.version_info < (3, 7): + skip = 12 # skip magic & date & file size + else: + skip = 16 # skip magic & reserved? & date & file size + f = open(filename, 'rb') + f.read(skip) + code = marshal.load(f) + f.close() + safe = True + symbols = dict.fromkeys(iter_symbols(code)) + for bad in ['__file__', '__path__']: + if bad in symbols: + log.warn("%s: module references %s", module, bad) + safe = False + if 'inspect' in symbols: + for bad in [ + 'getsource', 'getabsfile', 'getsourcefile', 'getfile' + 'getsourcelines', 'findsource', 'getcomments', 'getframeinfo', + 'getinnerframes', 'getouterframes', 'stack', 'trace' + ]: + if bad in symbols: + log.warn("%s: module MAY be using inspect.%s", module, bad) + safe = False + return safe + + +def iter_symbols(code): + """Yield names and strings used by `code` and its nested code objects""" + for name in code.co_names: + yield name + for const in code.co_consts: + if isinstance(const, six.string_types): + yield const + elif isinstance(const, CodeType): + for name in iter_symbols(const): + yield name + + +def can_scan(): + if not sys.platform.startswith('java') and sys.platform != 'cli': + # CPython, PyPy, etc. + return True + log.warn("Unable to analyze compiled code on this platform.") + log.warn("Please ask the author to include a 'zip_safe'" + " setting (either True or False) in the package's setup.py") + + +# Attribute names of options for commands that might need to be convinced to +# install to the egg build directory + +INSTALL_DIRECTORY_ATTRS = [ + 'install_lib', 'install_dir', 'install_data', 'install_base' +] + + +def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True, + mode='w'): + """Create a zip file from all the files under 'base_dir'. The output + zip file will be named 'base_dir' + ".zip". Uses either the "zipfile" + Python module (if available) or the InfoZIP "zip" utility (if installed + and found on the default search path). If neither tool is available, + raises DistutilsExecError. Returns the name of the output zip file. + """ + import zipfile + + mkpath(os.path.dirname(zip_filename), dry_run=dry_run) + log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir) + + def visit(z, dirname, names): + for name in names: + path = os.path.normpath(os.path.join(dirname, name)) + if os.path.isfile(path): + p = path[len(base_dir) + 1:] + if not dry_run: + z.write(path, p) + log.debug("adding '%s'", p) + + compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED + if not dry_run: + z = zipfile.ZipFile(zip_filename, mode, compression=compression) + for dirname, dirs, files in sorted_walk(base_dir): + visit(z, dirname, files) + z.close() + else: + for dirname, dirs, files in sorted_walk(base_dir): + visit(None, dirname, files) + return zip_filename diff --git a/lib/setuptools/command/bdist_rpm.py b/lib/setuptools/command/bdist_rpm.py new file mode 100644 index 0000000..7073092 --- /dev/null +++ b/lib/setuptools/command/bdist_rpm.py @@ -0,0 +1,43 @@ +import distutils.command.bdist_rpm as orig + + +class bdist_rpm(orig.bdist_rpm): + """ + Override the default bdist_rpm behavior to do the following: + + 1. Run egg_info to ensure the name and version are properly calculated. + 2. Always run 'install' using --single-version-externally-managed to + disable eggs in RPM distributions. + 3. Replace dash with underscore in the version numbers for better RPM + compatibility. + """ + + def run(self): + # ensure distro name is up-to-date + self.run_command('egg_info') + + orig.bdist_rpm.run(self) + + def _make_spec_file(self): + version = self.distribution.get_version() + rpmversion = version.replace('-', '_') + spec = orig.bdist_rpm._make_spec_file(self) + line23 = '%define version ' + version + line24 = '%define version ' + rpmversion + spec = [ + line.replace( + "Source0: %{name}-%{version}.tar", + "Source0: %{name}-%{unmangled_version}.tar" + ).replace( + "setup.py install ", + "setup.py install --single-version-externally-managed " + ).replace( + "%setup", + "%setup -n %{name}-%{unmangled_version}" + ).replace(line23, line24) + for line in spec + ] + insert_loc = spec.index(line24) + 1 + unmangled_version = "%define unmangled_version " + version + spec.insert(insert_loc, unmangled_version) + return spec diff --git a/lib/setuptools/command/bdist_wininst.py b/lib/setuptools/command/bdist_wininst.py new file mode 100644 index 0000000..073de97 --- /dev/null +++ b/lib/setuptools/command/bdist_wininst.py @@ -0,0 +1,21 @@ +import distutils.command.bdist_wininst as orig + + +class bdist_wininst(orig.bdist_wininst): + def reinitialize_command(self, command, reinit_subcommands=0): + """ + Supplement reinitialize_command to work around + http://bugs.python.org/issue20819 + """ + cmd = self.distribution.reinitialize_command( + command, reinit_subcommands) + if command in ('install', 'install_lib'): + cmd.install_lib = None + return cmd + + def run(self): + self._is_running = True + try: + orig.bdist_wininst.run(self) + finally: + self._is_running = False diff --git a/lib/setuptools/command/build_clib.py b/lib/setuptools/command/build_clib.py new file mode 100644 index 0000000..09caff6 --- /dev/null +++ b/lib/setuptools/command/build_clib.py @@ -0,0 +1,98 @@ +import distutils.command.build_clib as orig +from distutils.errors import DistutilsSetupError +from distutils import log +from setuptools.dep_util import newer_pairwise_group + + +class build_clib(orig.build_clib): + """ + Override the default build_clib behaviour to do the following: + + 1. Implement a rudimentary timestamp-based dependency system + so 'compile()' doesn't run every time. + 2. Add more keys to the 'build_info' dictionary: + * obj_deps - specify dependencies for each object compiled. + this should be a dictionary mapping a key + with the source filename to a list of + dependencies. Use an empty string for global + dependencies. + * cflags - specify a list of additional flags to pass to + the compiler. + """ + + def build_libraries(self, libraries): + for (lib_name, build_info) in libraries: + sources = build_info.get('sources') + if sources is None or not isinstance(sources, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'sources' must be present and must be " + "a list of source filenames" % lib_name) + sources = list(sources) + + log.info("building '%s' library", lib_name) + + # Make sure everything is the correct type. + # obj_deps should be a dictionary of keys as sources + # and a list/tuple of files that are its dependencies. + obj_deps = build_info.get('obj_deps', dict()) + if not isinstance(obj_deps, dict): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) + dependencies = [] + + # Get the global dependencies that are specified by the '' key. + # These will go into every source's dependency list. + global_deps = obj_deps.get('', list()) + if not isinstance(global_deps, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) + + # Build the list to be used by newer_pairwise_group + # each source will be auto-added to its dependencies. + for source in sources: + src_deps = [source] + src_deps.extend(global_deps) + extra_deps = obj_deps.get(source, list()) + if not isinstance(extra_deps, (list, tuple)): + raise DistutilsSetupError( + "in 'libraries' option (library '%s'), " + "'obj_deps' must be a dictionary of " + "type 'source: list'" % lib_name) + src_deps.extend(extra_deps) + dependencies.append(src_deps) + + expected_objects = self.compiler.object_filenames( + sources, + output_dir=self.build_temp + ) + + if newer_pairwise_group(dependencies, expected_objects) != ([], []): + # First, compile the source code to object files in the library + # directory. (This should probably change to putting object + # files in a temporary build directory.) + macros = build_info.get('macros') + include_dirs = build_info.get('include_dirs') + cflags = build_info.get('cflags') + objects = self.compiler.compile( + sources, + output_dir=self.build_temp, + macros=macros, + include_dirs=include_dirs, + extra_postargs=cflags, + debug=self.debug + ) + + # Now "link" the object files together into a static library. + # (On Unix at least, this isn't really linking -- it just + # builds an archive. Whatever.) + self.compiler.create_static_lib( + expected_objects, + lib_name, + output_dir=self.build_clib, + debug=self.debug + ) diff --git a/lib/setuptools/command/build_ext.py b/lib/setuptools/command/build_ext.py new file mode 100644 index 0000000..60a8a32 --- /dev/null +++ b/lib/setuptools/command/build_ext.py @@ -0,0 +1,321 @@ +import os +import sys +import itertools +import imp +from distutils.command.build_ext import build_ext as _du_build_ext +from distutils.file_util import copy_file +from distutils.ccompiler import new_compiler +from distutils.sysconfig import customize_compiler, get_config_var +from distutils.errors import DistutilsError +from distutils import log + +from setuptools.extension import Library +from setuptools.extern import six + +try: + # Attempt to use Cython for building extensions, if available + from Cython.Distutils.build_ext import build_ext as _build_ext + # Additionally, assert that the compiler module will load + # also. Ref #1229. + __import__('Cython.Compiler.Main') +except ImportError: + _build_ext = _du_build_ext + +# make sure _config_vars is initialized +get_config_var("LDSHARED") +from distutils.sysconfig import _config_vars as _CONFIG_VARS + + +def _customize_compiler_for_shlib(compiler): + if sys.platform == "darwin": + # building .dylib requires additional compiler flags on OSX; here we + # temporarily substitute the pyconfig.h variables so that distutils' + # 'customize_compiler' uses them before we build the shared libraries. + tmp = _CONFIG_VARS.copy() + try: + # XXX Help! I don't have any idea whether these are right... + _CONFIG_VARS['LDSHARED'] = ( + "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup") + _CONFIG_VARS['CCSHARED'] = " -dynamiclib" + _CONFIG_VARS['SO'] = ".dylib" + customize_compiler(compiler) + finally: + _CONFIG_VARS.clear() + _CONFIG_VARS.update(tmp) + else: + customize_compiler(compiler) + + +have_rtld = False +use_stubs = False +libtype = 'shared' + +if sys.platform == "darwin": + use_stubs = True +elif os.name != 'nt': + try: + import dl + use_stubs = have_rtld = hasattr(dl, 'RTLD_NOW') + except ImportError: + pass + +if_dl = lambda s: s if have_rtld else '' + + +def get_abi3_suffix(): + """Return the file extension for an abi3-compliant Extension()""" + for suffix, _, _ in (s for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION): + if '.abi3' in suffix: # Unix + return suffix + elif suffix == '.pyd': # Windows + return suffix + + +class build_ext(_build_ext): + def run(self): + """Build extensions in build directory, then copy if --inplace""" + old_inplace, self.inplace = self.inplace, 0 + _build_ext.run(self) + self.inplace = old_inplace + if old_inplace: + self.copy_extensions_to_source() + + def copy_extensions_to_source(self): + build_py = self.get_finalized_command('build_py') + for ext in self.extensions: + fullname = self.get_ext_fullname(ext.name) + filename = self.get_ext_filename(fullname) + modpath = fullname.split('.') + package = '.'.join(modpath[:-1]) + package_dir = build_py.get_package_dir(package) + dest_filename = os.path.join(package_dir, + os.path.basename(filename)) + src_filename = os.path.join(self.build_lib, filename) + + # Always copy, even if source is older than destination, to ensure + # that the right extensions for the current Python/platform are + # used. + copy_file( + src_filename, dest_filename, verbose=self.verbose, + dry_run=self.dry_run + ) + if ext._needs_stub: + self.write_stub(package_dir or os.curdir, ext, True) + + def get_ext_filename(self, fullname): + filename = _build_ext.get_ext_filename(self, fullname) + if fullname in self.ext_map: + ext = self.ext_map[fullname] + use_abi3 = ( + six.PY3 + and getattr(ext, 'py_limited_api') + and get_abi3_suffix() + ) + if use_abi3: + so_ext = get_config_var('EXT_SUFFIX') + filename = filename[:-len(so_ext)] + filename = filename + get_abi3_suffix() + if isinstance(ext, Library): + fn, ext = os.path.splitext(filename) + return self.shlib_compiler.library_filename(fn, libtype) + elif use_stubs and ext._links_to_dynamic: + d, fn = os.path.split(filename) + return os.path.join(d, 'dl-' + fn) + return filename + + def initialize_options(self): + _build_ext.initialize_options(self) + self.shlib_compiler = None + self.shlibs = [] + self.ext_map = {} + + def finalize_options(self): + _build_ext.finalize_options(self) + self.extensions = self.extensions or [] + self.check_extensions_list(self.extensions) + self.shlibs = [ext for ext in self.extensions + if isinstance(ext, Library)] + if self.shlibs: + self.setup_shlib_compiler() + for ext in self.extensions: + ext._full_name = self.get_ext_fullname(ext.name) + for ext in self.extensions: + fullname = ext._full_name + self.ext_map[fullname] = ext + + # distutils 3.1 will also ask for module names + # XXX what to do with conflicts? + self.ext_map[fullname.split('.')[-1]] = ext + + ltd = self.shlibs and self.links_to_dynamic(ext) or False + ns = ltd and use_stubs and not isinstance(ext, Library) + ext._links_to_dynamic = ltd + ext._needs_stub = ns + filename = ext._file_name = self.get_ext_filename(fullname) + libdir = os.path.dirname(os.path.join(self.build_lib, filename)) + if ltd and libdir not in ext.library_dirs: + ext.library_dirs.append(libdir) + if ltd and use_stubs and os.curdir not in ext.runtime_library_dirs: + ext.runtime_library_dirs.append(os.curdir) + + def setup_shlib_compiler(self): + compiler = self.shlib_compiler = new_compiler( + compiler=self.compiler, dry_run=self.dry_run, force=self.force + ) + _customize_compiler_for_shlib(compiler) + + if self.include_dirs is not None: + compiler.set_include_dirs(self.include_dirs) + if self.define is not None: + # 'define' option is a list of (name,value) tuples + for (name, value) in self.define: + compiler.define_macro(name, value) + if self.undef is not None: + for macro in self.undef: + compiler.undefine_macro(macro) + if self.libraries is not None: + compiler.set_libraries(self.libraries) + if self.library_dirs is not None: + compiler.set_library_dirs(self.library_dirs) + if self.rpath is not None: + compiler.set_runtime_library_dirs(self.rpath) + if self.link_objects is not None: + compiler.set_link_objects(self.link_objects) + + # hack so distutils' build_extension() builds a library instead + compiler.link_shared_object = link_shared_object.__get__(compiler) + + def get_export_symbols(self, ext): + if isinstance(ext, Library): + return ext.export_symbols + return _build_ext.get_export_symbols(self, ext) + + def build_extension(self, ext): + ext._convert_pyx_sources_to_lang() + _compiler = self.compiler + try: + if isinstance(ext, Library): + self.compiler = self.shlib_compiler + _build_ext.build_extension(self, ext) + if ext._needs_stub: + cmd = self.get_finalized_command('build_py').build_lib + self.write_stub(cmd, ext) + finally: + self.compiler = _compiler + + def links_to_dynamic(self, ext): + """Return true if 'ext' links to a dynamic lib in the same package""" + # XXX this should check to ensure the lib is actually being built + # XXX as dynamic, and not just using a locally-found version or a + # XXX static-compiled version + libnames = dict.fromkeys([lib._full_name for lib in self.shlibs]) + pkg = '.'.join(ext._full_name.split('.')[:-1] + ['']) + return any(pkg + libname in libnames for libname in ext.libraries) + + def get_outputs(self): + return _build_ext.get_outputs(self) + self.__get_stubs_outputs() + + def __get_stubs_outputs(self): + # assemble the base name for each extension that needs a stub + ns_ext_bases = ( + os.path.join(self.build_lib, *ext._full_name.split('.')) + for ext in self.extensions + if ext._needs_stub + ) + # pair each base with the extension + pairs = itertools.product(ns_ext_bases, self.__get_output_extensions()) + return list(base + fnext for base, fnext in pairs) + + def __get_output_extensions(self): + yield '.py' + yield '.pyc' + if self.get_finalized_command('build_py').optimize: + yield '.pyo' + + def write_stub(self, output_dir, ext, compile=False): + log.info("writing stub loader for %s to %s", ext._full_name, + output_dir) + stub_file = (os.path.join(output_dir, *ext._full_name.split('.')) + + '.py') + if compile and os.path.exists(stub_file): + raise DistutilsError(stub_file + " already exists! Please delete.") + if not self.dry_run: + f = open(stub_file, 'w') + f.write( + '\n'.join([ + "def __bootstrap__():", + " global __bootstrap__, __file__, __loader__", + " import sys, os, pkg_resources, imp" + if_dl(", dl"), + " __file__ = pkg_resources.resource_filename" + "(__name__,%r)" + % os.path.basename(ext._file_name), + " del __bootstrap__", + " if '__loader__' in globals():", + " del __loader__", + if_dl(" old_flags = sys.getdlopenflags()"), + " old_dir = os.getcwd()", + " try:", + " os.chdir(os.path.dirname(__file__))", + if_dl(" sys.setdlopenflags(dl.RTLD_NOW)"), + " imp.load_dynamic(__name__,__file__)", + " finally:", + if_dl(" sys.setdlopenflags(old_flags)"), + " os.chdir(old_dir)", + "__bootstrap__()", + "" # terminal \n + ]) + ) + f.close() + if compile: + from distutils.util import byte_compile + + byte_compile([stub_file], optimize=0, + force=True, dry_run=self.dry_run) + optimize = self.get_finalized_command('install_lib').optimize + if optimize > 0: + byte_compile([stub_file], optimize=optimize, + force=True, dry_run=self.dry_run) + if os.path.exists(stub_file) and not self.dry_run: + os.unlink(stub_file) + + +if use_stubs or os.name == 'nt': + # Build shared libraries + # + def link_shared_object( + self, objects, output_libname, output_dir=None, libraries=None, + library_dirs=None, runtime_library_dirs=None, export_symbols=None, + debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, + target_lang=None): + self.link( + self.SHARED_LIBRARY, objects, output_libname, + output_dir, libraries, library_dirs, runtime_library_dirs, + export_symbols, debug, extra_preargs, extra_postargs, + build_temp, target_lang + ) +else: + # Build static libraries everywhere else + libtype = 'static' + + def link_shared_object( + self, objects, output_libname, output_dir=None, libraries=None, + library_dirs=None, runtime_library_dirs=None, export_symbols=None, + debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, + target_lang=None): + # XXX we need to either disallow these attrs on Library instances, + # or warn/abort here if set, or something... + # libraries=None, library_dirs=None, runtime_library_dirs=None, + # export_symbols=None, extra_preargs=None, extra_postargs=None, + # build_temp=None + + assert output_dir is None # distutils build_ext doesn't pass this + output_dir, filename = os.path.split(output_libname) + basename, ext = os.path.splitext(filename) + if self.library_filename("x").startswith('lib'): + # strip 'lib' prefix; this is kludgy if some platform uses + # a different prefix + basename = basename[3:] + + self.create_static_lib( + objects, basename, output_dir, debug, target_lang + ) diff --git a/lib/setuptools/command/build_py.py b/lib/setuptools/command/build_py.py new file mode 100644 index 0000000..b0314fd --- /dev/null +++ b/lib/setuptools/command/build_py.py @@ -0,0 +1,270 @@ +from glob import glob +from distutils.util import convert_path +import distutils.command.build_py as orig +import os +import fnmatch +import textwrap +import io +import distutils.errors +import itertools + +from setuptools.extern import six +from setuptools.extern.six.moves import map, filter, filterfalse + +try: + from setuptools.lib2to3_ex import Mixin2to3 +except ImportError: + + class Mixin2to3: + def run_2to3(self, files, doctests=True): + "do nothing" + + +class build_py(orig.build_py, Mixin2to3): + """Enhanced 'build_py' command that includes data files with packages + + The data files are specified via a 'package_data' argument to 'setup()'. + See 'setuptools.dist.Distribution' for more details. + + Also, this version of the 'build_py' command allows you to specify both + 'py_modules' and 'packages' in the same setup operation. + """ + + def finalize_options(self): + orig.build_py.finalize_options(self) + self.package_data = self.distribution.package_data + self.exclude_package_data = (self.distribution.exclude_package_data or + {}) + if 'data_files' in self.__dict__: + del self.__dict__['data_files'] + self.__updated_files = [] + self.__doctests_2to3 = [] + + def run(self): + """Build modules, packages, and copy data files to build directory""" + if not self.py_modules and not self.packages: + return + + if self.py_modules: + self.build_modules() + + if self.packages: + self.build_packages() + self.build_package_data() + + self.run_2to3(self.__updated_files, False) + self.run_2to3(self.__updated_files, True) + self.run_2to3(self.__doctests_2to3, True) + + # Only compile actual .py files, using our base class' idea of what our + # output files are. + self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0)) + + def __getattr__(self, attr): + "lazily compute data files" + if attr == 'data_files': + self.data_files = self._get_data_files() + return self.data_files + return orig.build_py.__getattr__(self, attr) + + def build_module(self, module, module_file, package): + if six.PY2 and isinstance(package, six.string_types): + # avoid errors on Python 2 when unicode is passed (#190) + package = package.split('.') + outfile, copied = orig.build_py.build_module(self, module, module_file, + package) + if copied: + self.__updated_files.append(outfile) + return outfile, copied + + def _get_data_files(self): + """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" + self.analyze_manifest() + return list(map(self._get_pkg_data_files, self.packages or ())) + + def _get_pkg_data_files(self, package): + # Locate package source directory + src_dir = self.get_package_dir(package) + + # Compute package build directory + build_dir = os.path.join(*([self.build_lib] + package.split('.'))) + + # Strip directory from globbed filenames + filenames = [ + os.path.relpath(file, src_dir) + for file in self.find_data_files(package, src_dir) + ] + return package, src_dir, build_dir, filenames + + def find_data_files(self, package, src_dir): + """Return filenames for package's data files in 'src_dir'""" + patterns = self._get_platform_patterns( + self.package_data, + package, + src_dir, + ) + globs_expanded = map(glob, patterns) + # flatten the expanded globs into an iterable of matches + globs_matches = itertools.chain.from_iterable(globs_expanded) + glob_files = filter(os.path.isfile, globs_matches) + files = itertools.chain( + self.manifest_files.get(package, []), + glob_files, + ) + return self.exclude_data_files(package, src_dir, files) + + def build_package_data(self): + """Copy data files into build directory""" + for package, src_dir, build_dir, filenames in self.data_files: + for filename in filenames: + target = os.path.join(build_dir, filename) + self.mkpath(os.path.dirname(target)) + srcfile = os.path.join(src_dir, filename) + outf, copied = self.copy_file(srcfile, target) + srcfile = os.path.abspath(srcfile) + if (copied and + srcfile in self.distribution.convert_2to3_doctests): + self.__doctests_2to3.append(outf) + + def analyze_manifest(self): + self.manifest_files = mf = {} + if not self.distribution.include_package_data: + return + src_dirs = {} + for package in self.packages or (): + # Locate package source directory + src_dirs[assert_relative(self.get_package_dir(package))] = package + + self.run_command('egg_info') + ei_cmd = self.get_finalized_command('egg_info') + for path in ei_cmd.filelist.files: + d, f = os.path.split(assert_relative(path)) + prev = None + oldf = f + while d and d != prev and d not in src_dirs: + prev = d + d, df = os.path.split(d) + f = os.path.join(df, f) + if d in src_dirs: + if path.endswith('.py') and f == oldf: + continue # it's a module, not data + mf.setdefault(src_dirs[d], []).append(path) + + def get_data_files(self): + pass # Lazily compute data files in _get_data_files() function. + + def check_package(self, package, package_dir): + """Check namespace packages' __init__ for declare_namespace""" + try: + return self.packages_checked[package] + except KeyError: + pass + + init_py = orig.build_py.check_package(self, package, package_dir) + self.packages_checked[package] = init_py + + if not init_py or not self.distribution.namespace_packages: + return init_py + + for pkg in self.distribution.namespace_packages: + if pkg == package or pkg.startswith(package + '.'): + break + else: + return init_py + + with io.open(init_py, 'rb') as f: + contents = f.read() + if b'declare_namespace' not in contents: + raise distutils.errors.DistutilsError( + "Namespace package problem: %s is a namespace package, but " + "its\n__init__.py does not call declare_namespace()! Please " + 'fix it.\n(See the setuptools manual under ' + '"Namespace Packages" for details.)\n"' % (package,) + ) + return init_py + + def initialize_options(self): + self.packages_checked = {} + orig.build_py.initialize_options(self) + + def get_package_dir(self, package): + res = orig.build_py.get_package_dir(self, package) + if self.distribution.src_root is not None: + return os.path.join(self.distribution.src_root, res) + return res + + def exclude_data_files(self, package, src_dir, files): + """Filter filenames for package's data files in 'src_dir'""" + files = list(files) + patterns = self._get_platform_patterns( + self.exclude_package_data, + package, + src_dir, + ) + match_groups = ( + fnmatch.filter(files, pattern) + for pattern in patterns + ) + # flatten the groups of matches into an iterable of matches + matches = itertools.chain.from_iterable(match_groups) + bad = set(matches) + keepers = ( + fn + for fn in files + if fn not in bad + ) + # ditch dupes + return list(_unique_everseen(keepers)) + + @staticmethod + def _get_platform_patterns(spec, package, src_dir): + """ + yield platform-specific path patterns (suitable for glob + or fn_match) from a glob-based spec (such as + self.package_data or self.exclude_package_data) + matching package in src_dir. + """ + raw_patterns = itertools.chain( + spec.get('', []), + spec.get(package, []), + ) + return ( + # Each pattern has to be converted to a platform-specific path + os.path.join(src_dir, convert_path(pattern)) + for pattern in raw_patterns + ) + + +# from Python docs +def _unique_everseen(iterable, key=None): + "List unique elements, preserving order. Remember all elements ever seen." + # unique_everseen('AAAABBBCCDAABBB') --> A B C D + # unique_everseen('ABBCcAD', str.lower) --> A B C D + seen = set() + seen_add = seen.add + if key is None: + for element in filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element + + +def assert_relative(path): + if not os.path.isabs(path): + return path + from distutils.errors import DistutilsSetupError + + msg = textwrap.dedent(""" + Error: setup script specifies an absolute path: + + %s + + setup() arguments must *always* be /-separated paths relative to the + setup.py directory, *never* absolute paths. + """).lstrip() % path + raise DistutilsSetupError(msg) diff --git a/lib/setuptools/command/develop.py b/lib/setuptools/command/develop.py new file mode 100644 index 0000000..fdc9fc4 --- /dev/null +++ b/lib/setuptools/command/develop.py @@ -0,0 +1,218 @@ +from distutils.util import convert_path +from distutils import log +from distutils.errors import DistutilsError, DistutilsOptionError +import os +import glob +import io + +from setuptools.extern import six + +from pkg_resources import Distribution, PathMetadata, normalize_path +from setuptools.command.easy_install import easy_install +from setuptools import namespaces +import setuptools + +__metaclass__ = type + + +class develop(namespaces.DevelopInstaller, easy_install): + """Set up package for development""" + + description = "install package in 'development mode'" + + user_options = easy_install.user_options + [ + ("uninstall", "u", "Uninstall this source package"), + ("egg-path=", None, "Set the path to be used in the .egg-link file"), + ] + + boolean_options = easy_install.boolean_options + ['uninstall'] + + command_consumes_arguments = False # override base + + def run(self): + if self.uninstall: + self.multi_version = True + self.uninstall_link() + self.uninstall_namespaces() + else: + self.install_for_development() + self.warn_deprecated_options() + + def initialize_options(self): + self.uninstall = None + self.egg_path = None + easy_install.initialize_options(self) + self.setup_path = None + self.always_copy_from = '.' # always copy eggs installed in curdir + + def finalize_options(self): + ei = self.get_finalized_command("egg_info") + if ei.broken_egg_info: + template = "Please rename %r to %r before using 'develop'" + args = ei.egg_info, ei.broken_egg_info + raise DistutilsError(template % args) + self.args = [ei.egg_name] + + easy_install.finalize_options(self) + self.expand_basedirs() + self.expand_dirs() + # pick up setup-dir .egg files only: no .egg-info + self.package_index.scan(glob.glob('*.egg')) + + egg_link_fn = ei.egg_name + '.egg-link' + self.egg_link = os.path.join(self.install_dir, egg_link_fn) + self.egg_base = ei.egg_base + if self.egg_path is None: + self.egg_path = os.path.abspath(ei.egg_base) + + target = normalize_path(self.egg_base) + egg_path = normalize_path(os.path.join(self.install_dir, + self.egg_path)) + if egg_path != target: + raise DistutilsOptionError( + "--egg-path must be a relative path from the install" + " directory to " + target + ) + + # Make a distribution for the package's source + self.dist = Distribution( + target, + PathMetadata(target, os.path.abspath(ei.egg_info)), + project_name=ei.egg_name + ) + + self.setup_path = self._resolve_setup_path( + self.egg_base, + self.install_dir, + self.egg_path, + ) + + @staticmethod + def _resolve_setup_path(egg_base, install_dir, egg_path): + """ + Generate a path from egg_base back to '.' where the + setup script resides and ensure that path points to the + setup path from $install_dir/$egg_path. + """ + path_to_setup = egg_base.replace(os.sep, '/').rstrip('/') + if path_to_setup != os.curdir: + path_to_setup = '../' * (path_to_setup.count('/') + 1) + resolved = normalize_path( + os.path.join(install_dir, egg_path, path_to_setup) + ) + if resolved != normalize_path(os.curdir): + raise DistutilsOptionError( + "Can't get a consistent path to setup script from" + " installation directory", resolved, normalize_path(os.curdir)) + return path_to_setup + + def install_for_development(self): + if six.PY3 and getattr(self.distribution, 'use_2to3', False): + # If we run 2to3 we can not do this inplace: + + # Ensure metadata is up-to-date + self.reinitialize_command('build_py', inplace=0) + self.run_command('build_py') + bpy_cmd = self.get_finalized_command("build_py") + build_path = normalize_path(bpy_cmd.build_lib) + + # Build extensions + self.reinitialize_command('egg_info', egg_base=build_path) + self.run_command('egg_info') + + self.reinitialize_command('build_ext', inplace=0) + self.run_command('build_ext') + + # Fixup egg-link and easy-install.pth + ei_cmd = self.get_finalized_command("egg_info") + self.egg_path = build_path + self.dist.location = build_path + # XXX + self.dist._provider = PathMetadata(build_path, ei_cmd.egg_info) + else: + # Without 2to3 inplace works fine: + self.run_command('egg_info') + + # Build extensions in-place + self.reinitialize_command('build_ext', inplace=1) + self.run_command('build_ext') + + self.install_site_py() # ensure that target dir is site-safe + if setuptools.bootstrap_install_from: + self.easy_install(setuptools.bootstrap_install_from) + setuptools.bootstrap_install_from = None + + self.install_namespaces() + + # create an .egg-link in the installation dir, pointing to our egg + log.info("Creating %s (link to %s)", self.egg_link, self.egg_base) + if not self.dry_run: + with open(self.egg_link, "w") as f: + f.write(self.egg_path + "\n" + self.setup_path) + # postprocess the installed distro, fixing up .pth, installing scripts, + # and handling requirements + self.process_distribution(None, self.dist, not self.no_deps) + + def uninstall_link(self): + if os.path.exists(self.egg_link): + log.info("Removing %s (link to %s)", self.egg_link, self.egg_base) + egg_link_file = open(self.egg_link) + contents = [line.rstrip() for line in egg_link_file] + egg_link_file.close() + if contents not in ([self.egg_path], + [self.egg_path, self.setup_path]): + log.warn("Link points to %s: uninstall aborted", contents) + return + if not self.dry_run: + os.unlink(self.egg_link) + if not self.dry_run: + self.update_pth(self.dist) # remove any .pth link to us + if self.distribution.scripts: + # XXX should also check for entry point scripts! + log.warn("Note: you must uninstall or replace scripts manually!") + + def install_egg_scripts(self, dist): + if dist is not self.dist: + # Installing a dependency, so fall back to normal behavior + return easy_install.install_egg_scripts(self, dist) + + # create wrapper scripts in the script dir, pointing to dist.scripts + + # new-style... + self.install_wrapper_scripts(dist) + + # ...and old-style + for script_name in self.distribution.scripts or []: + script_path = os.path.abspath(convert_path(script_name)) + script_name = os.path.basename(script_path) + with io.open(script_path) as strm: + script_text = strm.read() + self.install_script(dist, script_name, script_text, script_path) + + def install_wrapper_scripts(self, dist): + dist = VersionlessRequirement(dist) + return easy_install.install_wrapper_scripts(self, dist) + + +class VersionlessRequirement: + """ + Adapt a pkg_resources.Distribution to simply return the project + name as the 'requirement' so that scripts will work across + multiple versions. + + >>> dist = Distribution(project_name='foo', version='1.0') + >>> str(dist.as_requirement()) + 'foo==1.0' + >>> adapted_dist = VersionlessRequirement(dist) + >>> str(adapted_dist.as_requirement()) + 'foo' + """ + + def __init__(self, dist): + self.__dist = dist + + def __getattr__(self, name): + return getattr(self.__dist, name) + + def as_requirement(self): + return self.project_name diff --git a/lib/setuptools/command/dist_info.py b/lib/setuptools/command/dist_info.py new file mode 100644 index 0000000..c45258f --- /dev/null +++ b/lib/setuptools/command/dist_info.py @@ -0,0 +1,36 @@ +""" +Create a dist_info directory +As defined in the wheel specification +""" + +import os + +from distutils.core import Command +from distutils import log + + +class dist_info(Command): + + description = 'create a .dist-info directory' + + user_options = [ + ('egg-base=', 'e', "directory containing .egg-info directories" + " (default: top of the source tree)"), + ] + + def initialize_options(self): + self.egg_base = None + + def finalize_options(self): + pass + + def run(self): + egg_info = self.get_finalized_command('egg_info') + egg_info.egg_base = self.egg_base + egg_info.finalize_options() + egg_info.run() + dist_info_dir = egg_info.egg_info[:-len('.egg-info')] + '.dist-info' + log.info("creating '{}'".format(os.path.abspath(dist_info_dir))) + + bdist_wheel = self.get_finalized_command('bdist_wheel') + bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir) diff --git a/lib/setuptools/command/easy_install.py b/lib/setuptools/command/easy_install.py new file mode 100644 index 0000000..06c9827 --- /dev/null +++ b/lib/setuptools/command/easy_install.py @@ -0,0 +1,2342 @@ +#!/usr/bin/env python +""" +Easy Install +------------ + +A tool for doing automatic download/extract/build of distutils-based Python +packages. For detailed documentation, see the accompanying EasyInstall.txt +file, or visit the `EasyInstall home page`__. + +__ https://setuptools.readthedocs.io/en/latest/easy_install.html + +""" + +from glob import glob +from distutils.util import get_platform +from distutils.util import convert_path, subst_vars +from distutils.errors import ( + DistutilsArgError, DistutilsOptionError, + DistutilsError, DistutilsPlatformError, +) +from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS +from distutils import log, dir_util +from distutils.command.build_scripts import first_line_re +from distutils.spawn import find_executable +import sys +import os +import zipimport +import shutil +import tempfile +import zipfile +import re +import stat +import random +import textwrap +import warnings +import site +import struct +import contextlib +import subprocess +import shlex +import io + + +from sysconfig import get_config_vars, get_path + +from setuptools import SetuptoolsDeprecationWarning + +from setuptools.extern import six +from setuptools.extern.six.moves import configparser, map + +from setuptools import Command +from setuptools.sandbox import run_setup +from setuptools.py27compat import rmtree_safe +from setuptools.command import setopt +from setuptools.archive_util import unpack_archive +from setuptools.package_index import ( + PackageIndex, parse_requirement_arg, URL_SCHEME, +) +from setuptools.command import bdist_egg, egg_info +from setuptools.wheel import Wheel +from pkg_resources import ( + yield_lines, normalize_path, resource_string, ensure_directory, + get_distribution, find_distributions, Environment, Requirement, + Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound, + VersionConflict, DEVELOP_DIST, +) +import pkg_resources.py31compat + +__metaclass__ = type + +# Turn on PEP440Warnings +warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) + +__all__ = [ + 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg', + 'main', 'get_exe_prefixes', +] + + +def is_64bit(): + return struct.calcsize("P") == 8 + + +def samefile(p1, p2): + """ + Determine if two paths reference the same file. + + Augments os.path.samefile to work on Windows and + suppresses errors if the path doesn't exist. + """ + both_exist = os.path.exists(p1) and os.path.exists(p2) + use_samefile = hasattr(os.path, 'samefile') and both_exist + if use_samefile: + return os.path.samefile(p1, p2) + norm_p1 = os.path.normpath(os.path.normcase(p1)) + norm_p2 = os.path.normpath(os.path.normcase(p2)) + return norm_p1 == norm_p2 + + +if six.PY2: + + def _to_bytes(s): + return s + + def isascii(s): + try: + six.text_type(s, 'ascii') + return True + except UnicodeError: + return False +else: + + def _to_bytes(s): + return s.encode('utf8') + + def isascii(s): + try: + s.encode('ascii') + return True + except UnicodeError: + return False + + +_one_liner = lambda text: textwrap.dedent(text).strip().replace('\n', '; ') + + +class easy_install(Command): + """Manage a download/build/install process""" + description = "Find/get/install Python packages" + command_consumes_arguments = True + + user_options = [ + ('prefix=', None, "installation prefix"), + ("zip-ok", "z", "install package as a zipfile"), + ("multi-version", "m", "make apps have to require() a version"), + ("upgrade", "U", "force upgrade (searches PyPI for latest versions)"), + ("install-dir=", "d", "install package to DIR"), + ("script-dir=", "s", "install scripts to DIR"), + ("exclude-scripts", "x", "Don't install scripts"), + ("always-copy", "a", "Copy all needed packages to install dir"), + ("index-url=", "i", "base URL of Python Package Index"), + ("find-links=", "f", "additional URL(s) to search for packages"), + ("build-directory=", "b", + "download/extract/build in DIR; keep the results"), + ('optimize=', 'O', + "also compile with optimization: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + ('record=', None, + "filename in which to record list of installed files"), + ('always-unzip', 'Z', "don't install as a zipfile, no matter what"), + ('site-dirs=', 'S', "list of directories where .pth files work"), + ('editable', 'e', "Install specified packages in editable form"), + ('no-deps', 'N', "don't install dependencies"), + ('allow-hosts=', 'H', "pattern(s) that hostnames must match"), + ('local-snapshots-ok', 'l', + "allow building eggs from local checkouts"), + ('version', None, "print version information and exit"), + ('no-find-links', None, + "Don't load find-links defined in packages being installed") + ] + boolean_options = [ + 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', + 'editable', + 'no-deps', 'local-snapshots-ok', 'version' + ] + + if site.ENABLE_USER_SITE: + help_msg = "install in user site-package '%s'" % site.USER_SITE + user_options.append(('user', None, help_msg)) + boolean_options.append('user') + + negative_opt = {'always-unzip': 'zip-ok'} + create_index = PackageIndex + + def initialize_options(self): + # the --user option seems to be an opt-in one, + # so the default should be False. + self.user = 0 + self.zip_ok = self.local_snapshots_ok = None + self.install_dir = self.script_dir = self.exclude_scripts = None + self.index_url = None + self.find_links = None + self.build_directory = None + self.args = None + self.optimize = self.record = None + self.upgrade = self.always_copy = self.multi_version = None + self.editable = self.no_deps = self.allow_hosts = None + self.root = self.prefix = self.no_report = None + self.version = None + self.install_purelib = None # for pure module distributions + self.install_platlib = None # non-pure (dists w/ extensions) + self.install_headers = None # for C/C++ headers + self.install_lib = None # set to either purelib or platlib + self.install_scripts = None + self.install_data = None + self.install_base = None + self.install_platbase = None + if site.ENABLE_USER_SITE: + self.install_userbase = site.USER_BASE + self.install_usersite = site.USER_SITE + else: + self.install_userbase = None + self.install_usersite = None + self.no_find_links = None + + # Options not specifiable via command line + self.package_index = None + self.pth_file = self.always_copy_from = None + self.site_dirs = None + self.installed_projects = {} + self.sitepy_installed = False + # Always read easy_install options, even if we are subclassed, or have + # an independent instance created. This ensures that defaults will + # always come from the standard configuration file(s)' "easy_install" + # section, even if this is a "develop" or "install" command, or some + # other embedding. + self._dry_run = None + self.verbose = self.distribution.verbose + self.distribution._set_command_options( + self, self.distribution.get_option_dict('easy_install') + ) + + def delete_blockers(self, blockers): + extant_blockers = ( + filename for filename in blockers + if os.path.exists(filename) or os.path.islink(filename) + ) + list(map(self._delete_path, extant_blockers)) + + def _delete_path(self, path): + log.info("Deleting %s", path) + if self.dry_run: + return + + is_tree = os.path.isdir(path) and not os.path.islink(path) + remover = rmtree if is_tree else os.unlink + remover(path) + + @staticmethod + def _render_version(): + """ + Render the Setuptools version and installation details, then exit. + """ + ver = sys.version[:3] + dist = get_distribution('setuptools') + tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})' + print(tmpl.format(**locals())) + raise SystemExit() + + def finalize_options(self): + self.version and self._render_version() + + py_version = sys.version.split()[0] + prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix') + + self.config_vars = { + 'dist_name': self.distribution.get_name(), + 'dist_version': self.distribution.get_version(), + 'dist_fullname': self.distribution.get_fullname(), + 'py_version': py_version, + 'py_version_short': py_version[0:3], + 'py_version_nodot': py_version[0] + py_version[2], + 'sys_prefix': prefix, + 'prefix': prefix, + 'sys_exec_prefix': exec_prefix, + 'exec_prefix': exec_prefix, + # Only python 3.2+ has abiflags + 'abiflags': getattr(sys, 'abiflags', ''), + } + + if site.ENABLE_USER_SITE: + self.config_vars['userbase'] = self.install_userbase + self.config_vars['usersite'] = self.install_usersite + + self._fix_install_dir_for_user_site() + + self.expand_basedirs() + self.expand_dirs() + + self._expand( + 'install_dir', 'script_dir', 'build_directory', + 'site_dirs', + ) + # If a non-default installation directory was specified, default the + # script directory to match it. + if self.script_dir is None: + self.script_dir = self.install_dir + + if self.no_find_links is None: + self.no_find_links = False + + # Let install_dir get set by install_lib command, which in turn + # gets its info from the install command, and takes into account + # --prefix and --home and all that other crud. + self.set_undefined_options( + 'install_lib', ('install_dir', 'install_dir') + ) + # Likewise, set default script_dir from 'install_scripts.install_dir' + self.set_undefined_options( + 'install_scripts', ('install_dir', 'script_dir') + ) + + if self.user and self.install_purelib: + self.install_dir = self.install_purelib + self.script_dir = self.install_scripts + # default --record from the install command + self.set_undefined_options('install', ('record', 'record')) + # Should this be moved to the if statement below? It's not used + # elsewhere + normpath = map(normalize_path, sys.path) + self.all_site_dirs = get_site_dirs() + if self.site_dirs is not None: + site_dirs = [ + os.path.expanduser(s.strip()) for s in + self.site_dirs.split(',') + ] + for d in site_dirs: + if not os.path.isdir(d): + log.warn("%s (in --site-dirs) does not exist", d) + elif normalize_path(d) not in normpath: + raise DistutilsOptionError( + d + " (in --site-dirs) is not on sys.path" + ) + else: + self.all_site_dirs.append(normalize_path(d)) + if not self.editable: + self.check_site_dir() + self.index_url = self.index_url or "https://pypi.org/simple/" + self.shadow_path = self.all_site_dirs[:] + for path_item in self.install_dir, normalize_path(self.script_dir): + if path_item not in self.shadow_path: + self.shadow_path.insert(0, path_item) + + if self.allow_hosts is not None: + hosts = [s.strip() for s in self.allow_hosts.split(',')] + else: + hosts = ['*'] + if self.package_index is None: + self.package_index = self.create_index( + self.index_url, search_path=self.shadow_path, hosts=hosts, + ) + self.local_index = Environment(self.shadow_path + sys.path) + + if self.find_links is not None: + if isinstance(self.find_links, six.string_types): + self.find_links = self.find_links.split() + else: + self.find_links = [] + if self.local_snapshots_ok: + self.package_index.scan_egg_links(self.shadow_path + sys.path) + if not self.no_find_links: + self.package_index.add_find_links(self.find_links) + self.set_undefined_options('install_lib', ('optimize', 'optimize')) + if not isinstance(self.optimize, int): + try: + self.optimize = int(self.optimize) + if not (0 <= self.optimize <= 2): + raise ValueError + except ValueError: + raise DistutilsOptionError("--optimize must be 0, 1, or 2") + + if self.editable and not self.build_directory: + raise DistutilsArgError( + "Must specify a build directory (-b) when using --editable" + ) + if not self.args: + raise DistutilsArgError( + "No urls, filenames, or requirements specified (see --help)") + + self.outputs = [] + + def _fix_install_dir_for_user_site(self): + """ + Fix the install_dir if "--user" was used. + """ + if not self.user or not site.ENABLE_USER_SITE: + return + + self.create_home_path() + if self.install_userbase is None: + msg = "User base directory is not specified" + raise DistutilsPlatformError(msg) + self.install_base = self.install_platbase = self.install_userbase + scheme_name = os.name.replace('posix', 'unix') + '_user' + self.select_scheme(scheme_name) + + def _expand_attrs(self, attrs): + for attr in attrs: + val = getattr(self, attr) + if val is not None: + if os.name == 'posix' or os.name == 'nt': + val = os.path.expanduser(val) + val = subst_vars(val, self.config_vars) + setattr(self, attr, val) + + def expand_basedirs(self): + """Calls `os.path.expanduser` on install_base, install_platbase and + root.""" + self._expand_attrs(['install_base', 'install_platbase', 'root']) + + def expand_dirs(self): + """Calls `os.path.expanduser` on install dirs.""" + dirs = [ + 'install_purelib', + 'install_platlib', + 'install_lib', + 'install_headers', + 'install_scripts', + 'install_data', + ] + self._expand_attrs(dirs) + + def run(self): + if self.verbose != self.distribution.verbose: + log.set_verbosity(self.verbose) + try: + for spec in self.args: + self.easy_install(spec, not self.no_deps) + if self.record: + outputs = self.outputs + if self.root: # strip any package prefix + root_len = len(self.root) + for counter in range(len(outputs)): + outputs[counter] = outputs[counter][root_len:] + from distutils import file_util + + self.execute( + file_util.write_file, (self.record, outputs), + "writing list of installed files to '%s'" % + self.record + ) + self.warn_deprecated_options() + finally: + log.set_verbosity(self.distribution.verbose) + + def pseudo_tempname(self): + """Return a pseudo-tempname base in the install directory. + This code is intentionally naive; if a malicious party can write to + the target directory you're already in deep doodoo. + """ + try: + pid = os.getpid() + except Exception: + pid = random.randint(0, sys.maxsize) + return os.path.join(self.install_dir, "test-easy-install-%s" % pid) + + def warn_deprecated_options(self): + pass + + def check_site_dir(self): + """Verify that self.install_dir is .pth-capable dir, if needed""" + + instdir = normalize_path(self.install_dir) + pth_file = os.path.join(instdir, 'easy-install.pth') + + # Is it a configured, PYTHONPATH, implicit, or explicit site dir? + is_site_dir = instdir in self.all_site_dirs + + if not is_site_dir and not self.multi_version: + # No? Then directly test whether it does .pth file processing + is_site_dir = self.check_pth_processing() + else: + # make sure we can write to target dir + testfile = self.pseudo_tempname() + '.write-test' + test_exists = os.path.exists(testfile) + try: + if test_exists: + os.unlink(testfile) + open(testfile, 'w').close() + os.unlink(testfile) + except (OSError, IOError): + self.cant_write_to_target() + + if not is_site_dir and not self.multi_version: + # Can't install non-multi to non-site dir + raise DistutilsError(self.no_default_version_msg()) + + if is_site_dir: + if self.pth_file is None: + self.pth_file = PthDistributions(pth_file, self.all_site_dirs) + else: + self.pth_file = None + + if instdir not in map(normalize_path, _pythonpath()): + # only PYTHONPATH dirs need a site.py, so pretend it's there + self.sitepy_installed = True + elif self.multi_version and not os.path.exists(pth_file): + self.sitepy_installed = True # don't need site.py in this case + self.pth_file = None # and don't create a .pth file + self.install_dir = instdir + + __cant_write_msg = textwrap.dedent(""" + can't create or remove files in install directory + + The following error occurred while trying to add or remove files in the + installation directory: + + %s + + The installation directory you specified (via --install-dir, --prefix, or + the distutils default setting) was: + + %s + """).lstrip() + + __not_exists_id = textwrap.dedent(""" + This directory does not currently exist. Please create it and try again, or + choose a different installation directory (using the -d or --install-dir + option). + """).lstrip() + + __access_msg = textwrap.dedent(""" + Perhaps your account does not have write access to this directory? If the + installation directory is a system-owned directory, you may need to sign in + as the administrator or "root" account. If you do not have administrative + access to this machine, you may wish to choose a different installation + directory, preferably one that is listed in your PYTHONPATH environment + variable. + + For information on other options, you may wish to consult the + documentation at: + + https://setuptools.readthedocs.io/en/latest/easy_install.html + + Please make the appropriate changes for your system and try again. + """).lstrip() + + def cant_write_to_target(self): + msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,) + + if not os.path.exists(self.install_dir): + msg += '\n' + self.__not_exists_id + else: + msg += '\n' + self.__access_msg + raise DistutilsError(msg) + + def check_pth_processing(self): + """Empirically verify whether .pth files are supported in inst. dir""" + instdir = self.install_dir + log.info("Checking .pth file support in %s", instdir) + pth_file = self.pseudo_tempname() + ".pth" + ok_file = pth_file + '.ok' + ok_exists = os.path.exists(ok_file) + tmpl = _one_liner(""" + import os + f = open({ok_file!r}, 'w') + f.write('OK') + f.close() + """) + '\n' + try: + if ok_exists: + os.unlink(ok_file) + dirname = os.path.dirname(ok_file) + pkg_resources.py31compat.makedirs(dirname, exist_ok=True) + f = open(pth_file, 'w') + except (OSError, IOError): + self.cant_write_to_target() + else: + try: + f.write(tmpl.format(**locals())) + f.close() + f = None + executable = sys.executable + if os.name == 'nt': + dirname, basename = os.path.split(executable) + alt = os.path.join(dirname, 'pythonw.exe') + use_alt = ( + basename.lower() == 'python.exe' and + os.path.exists(alt) + ) + if use_alt: + # use pythonw.exe to avoid opening a console window + executable = alt + + from distutils.spawn import spawn + + spawn([executable, '-E', '-c', 'pass'], 0) + + if os.path.exists(ok_file): + log.info( + "TEST PASSED: %s appears to support .pth files", + instdir + ) + return True + finally: + if f: + f.close() + if os.path.exists(ok_file): + os.unlink(ok_file) + if os.path.exists(pth_file): + os.unlink(pth_file) + if not self.multi_version: + log.warn("TEST FAILED: %s does NOT support .pth files", instdir) + return False + + def install_egg_scripts(self, dist): + """Write all the scripts for `dist`, unless scripts are excluded""" + if not self.exclude_scripts and dist.metadata_isdir('scripts'): + for script_name in dist.metadata_listdir('scripts'): + if dist.metadata_isdir('scripts/' + script_name): + # The "script" is a directory, likely a Python 3 + # __pycache__ directory, so skip it. + continue + self.install_script( + dist, script_name, + dist.get_metadata('scripts/' + script_name) + ) + self.install_wrapper_scripts(dist) + + def add_output(self, path): + if os.path.isdir(path): + for base, dirs, files in os.walk(path): + for filename in files: + self.outputs.append(os.path.join(base, filename)) + else: + self.outputs.append(path) + + def not_editable(self, spec): + if self.editable: + raise DistutilsArgError( + "Invalid argument %r: you can't use filenames or URLs " + "with --editable (except via the --find-links option)." + % (spec,) + ) + + def check_editable(self, spec): + if not self.editable: + return + + if os.path.exists(os.path.join(self.build_directory, spec.key)): + raise DistutilsArgError( + "%r already exists in %s; can't do a checkout there" % + (spec.key, self.build_directory) + ) + + @contextlib.contextmanager + def _tmpdir(self): + tmpdir = tempfile.mkdtemp(prefix=u"easy_install-") + try: + # cast to str as workaround for #709 and #710 and #712 + yield str(tmpdir) + finally: + os.path.exists(tmpdir) and rmtree(rmtree_safe(tmpdir)) + + def easy_install(self, spec, deps=False): + if not self.editable: + self.install_site_py() + + with self._tmpdir() as tmpdir: + if not isinstance(spec, Requirement): + if URL_SCHEME(spec): + # It's a url, download it to tmpdir and process + self.not_editable(spec) + dl = self.package_index.download(spec, tmpdir) + return self.install_item(None, dl, tmpdir, deps, True) + + elif os.path.exists(spec): + # Existing file or directory, just process it directly + self.not_editable(spec) + return self.install_item(None, spec, tmpdir, deps, True) + else: + spec = parse_requirement_arg(spec) + + self.check_editable(spec) + dist = self.package_index.fetch_distribution( + spec, tmpdir, self.upgrade, self.editable, + not self.always_copy, self.local_index + ) + if dist is None: + msg = "Could not find suitable distribution for %r" % spec + if self.always_copy: + msg += " (--always-copy skips system and development eggs)" + raise DistutilsError(msg) + elif dist.precedence == DEVELOP_DIST: + # .egg-info dists don't need installing, just process deps + self.process_distribution(spec, dist, deps, "Using") + return dist + else: + return self.install_item(spec, dist.location, tmpdir, deps) + + def install_item(self, spec, download, tmpdir, deps, install_needed=False): + + # Installation is also needed if file in tmpdir or is not an egg + install_needed = install_needed or self.always_copy + install_needed = install_needed or os.path.dirname(download) == tmpdir + install_needed = install_needed or not download.endswith('.egg') + install_needed = install_needed or ( + self.always_copy_from is not None and + os.path.dirname(normalize_path(download)) == + normalize_path(self.always_copy_from) + ) + + if spec and not install_needed: + # at this point, we know it's a local .egg, we just don't know if + # it's already installed. + for dist in self.local_index[spec.project_name]: + if dist.location == download: + break + else: + install_needed = True # it's not in the local index + + log.info("Processing %s", os.path.basename(download)) + + if install_needed: + dists = self.install_eggs(spec, download, tmpdir) + for dist in dists: + self.process_distribution(spec, dist, deps) + else: + dists = [self.egg_distribution(download)] + self.process_distribution(spec, dists[0], deps, "Using") + + if spec is not None: + for dist in dists: + if dist in spec: + return dist + + def select_scheme(self, name): + """Sets the install directories by applying the install schemes.""" + # it's the caller's problem if they supply a bad name! + scheme = INSTALL_SCHEMES[name] + for key in SCHEME_KEYS: + attrname = 'install_' + key + if getattr(self, attrname) is None: + setattr(self, attrname, scheme[key]) + + def process_distribution(self, requirement, dist, deps=True, *info): + self.update_pth(dist) + self.package_index.add(dist) + if dist in self.local_index[dist.key]: + self.local_index.remove(dist) + self.local_index.add(dist) + self.install_egg_scripts(dist) + self.installed_projects[dist.key] = dist + log.info(self.installation_report(requirement, dist, *info)) + if (dist.has_metadata('dependency_links.txt') and + not self.no_find_links): + self.package_index.add_find_links( + dist.get_metadata_lines('dependency_links.txt') + ) + if not deps and not self.always_copy: + return + elif requirement is not None and dist.key != requirement.key: + log.warn("Skipping dependencies for %s", dist) + return # XXX this is not the distribution we were looking for + elif requirement is None or dist not in requirement: + # if we wound up with a different version, resolve what we've got + distreq = dist.as_requirement() + requirement = Requirement(str(distreq)) + log.info("Processing dependencies for %s", requirement) + try: + distros = WorkingSet([]).resolve( + [requirement], self.local_index, self.easy_install + ) + except DistributionNotFound as e: + raise DistutilsError(str(e)) + except VersionConflict as e: + raise DistutilsError(e.report()) + if self.always_copy or self.always_copy_from: + # Force all the relevant distros to be copied or activated + for dist in distros: + if dist.key not in self.installed_projects: + self.easy_install(dist.as_requirement()) + log.info("Finished processing dependencies for %s", requirement) + + def should_unzip(self, dist): + if self.zip_ok is not None: + return not self.zip_ok + if dist.has_metadata('not-zip-safe'): + return True + if not dist.has_metadata('zip-safe'): + return True + return False + + def maybe_move(self, spec, dist_filename, setup_base): + dst = os.path.join(self.build_directory, spec.key) + if os.path.exists(dst): + msg = ( + "%r already exists in %s; build directory %s will not be kept" + ) + log.warn(msg, spec.key, self.build_directory, setup_base) + return setup_base + if os.path.isdir(dist_filename): + setup_base = dist_filename + else: + if os.path.dirname(dist_filename) == setup_base: + os.unlink(dist_filename) # get it out of the tmp dir + contents = os.listdir(setup_base) + if len(contents) == 1: + dist_filename = os.path.join(setup_base, contents[0]) + if os.path.isdir(dist_filename): + # if the only thing there is a directory, move it instead + setup_base = dist_filename + ensure_directory(dst) + shutil.move(setup_base, dst) + return dst + + def install_wrapper_scripts(self, dist): + if self.exclude_scripts: + return + for args in ScriptWriter.best().get_args(dist): + self.write_script(*args) + + def install_script(self, dist, script_name, script_text, dev_path=None): + """Generate a legacy script wrapper and install it""" + spec = str(dist.as_requirement()) + is_script = is_python_script(script_text, script_name) + + if is_script: + body = self._load_template(dev_path) % locals() + script_text = ScriptWriter.get_header(script_text) + body + self.write_script(script_name, _to_bytes(script_text), 'b') + + @staticmethod + def _load_template(dev_path): + """ + There are a couple of template scripts in the package. This + function loads one of them and prepares it for use. + """ + # See https://github.com/pypa/setuptools/issues/134 for info + # on script file naming and downstream issues with SVR4 + name = 'script.tmpl' + if dev_path: + name = name.replace('.tmpl', ' (dev).tmpl') + + raw_bytes = resource_string('setuptools', name) + return raw_bytes.decode('utf-8') + + def write_script(self, script_name, contents, mode="t", blockers=()): + """Write an executable file to the scripts directory""" + self.delete_blockers( # clean up old .py/.pyw w/o a script + [os.path.join(self.script_dir, x) for x in blockers] + ) + log.info("Installing %s script to %s", script_name, self.script_dir) + target = os.path.join(self.script_dir, script_name) + self.add_output(target) + + if self.dry_run: + return + + mask = current_umask() + ensure_directory(target) + if os.path.exists(target): + os.unlink(target) + with open(target, "w" + mode) as f: + f.write(contents) + chmod(target, 0o777 - mask) + + def install_eggs(self, spec, dist_filename, tmpdir): + # .egg dirs or files are already built, so just return them + if dist_filename.lower().endswith('.egg'): + return [self.install_egg(dist_filename, tmpdir)] + elif dist_filename.lower().endswith('.exe'): + return [self.install_exe(dist_filename, tmpdir)] + elif dist_filename.lower().endswith('.whl'): + return [self.install_wheel(dist_filename, tmpdir)] + + # Anything else, try to extract and build + setup_base = tmpdir + if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'): + unpack_archive(dist_filename, tmpdir, self.unpack_progress) + elif os.path.isdir(dist_filename): + setup_base = os.path.abspath(dist_filename) + + if (setup_base.startswith(tmpdir) # something we downloaded + and self.build_directory and spec is not None): + setup_base = self.maybe_move(spec, dist_filename, setup_base) + + # Find the setup.py file + setup_script = os.path.join(setup_base, 'setup.py') + + if not os.path.exists(setup_script): + setups = glob(os.path.join(setup_base, '*', 'setup.py')) + if not setups: + raise DistutilsError( + "Couldn't find a setup script in %s" % + os.path.abspath(dist_filename) + ) + if len(setups) > 1: + raise DistutilsError( + "Multiple setup scripts in %s" % + os.path.abspath(dist_filename) + ) + setup_script = setups[0] + + # Now run it, and return the result + if self.editable: + log.info(self.report_editable(spec, setup_script)) + return [] + else: + return self.build_and_install(setup_script, setup_base) + + def egg_distribution(self, egg_path): + if os.path.isdir(egg_path): + metadata = PathMetadata(egg_path, os.path.join(egg_path, + 'EGG-INFO')) + else: + metadata = EggMetadata(zipimport.zipimporter(egg_path)) + return Distribution.from_filename(egg_path, metadata=metadata) + + def install_egg(self, egg_path, tmpdir): + destination = os.path.join( + self.install_dir, + os.path.basename(egg_path), + ) + destination = os.path.abspath(destination) + if not self.dry_run: + ensure_directory(destination) + + dist = self.egg_distribution(egg_path) + if not samefile(egg_path, destination): + if os.path.isdir(destination) and not os.path.islink(destination): + dir_util.remove_tree(destination, dry_run=self.dry_run) + elif os.path.exists(destination): + self.execute( + os.unlink, + (destination,), + "Removing " + destination, + ) + try: + new_dist_is_zipped = False + if os.path.isdir(egg_path): + if egg_path.startswith(tmpdir): + f, m = shutil.move, "Moving" + else: + f, m = shutil.copytree, "Copying" + elif self.should_unzip(dist): + self.mkpath(destination) + f, m = self.unpack_and_compile, "Extracting" + else: + new_dist_is_zipped = True + if egg_path.startswith(tmpdir): + f, m = shutil.move, "Moving" + else: + f, m = shutil.copy2, "Copying" + self.execute( + f, + (egg_path, destination), + (m + " %s to %s") % ( + os.path.basename(egg_path), + os.path.dirname(destination) + ), + ) + update_dist_caches( + destination, + fix_zipimporter_caches=new_dist_is_zipped, + ) + except Exception: + update_dist_caches(destination, fix_zipimporter_caches=False) + raise + + self.add_output(destination) + return self.egg_distribution(destination) + + def install_exe(self, dist_filename, tmpdir): + # See if it's valid, get data + cfg = extract_wininst_cfg(dist_filename) + if cfg is None: + raise DistutilsError( + "%s is not a valid distutils Windows .exe" % dist_filename + ) + # Create a dummy distribution object until we build the real distro + dist = Distribution( + None, + project_name=cfg.get('metadata', 'name'), + version=cfg.get('metadata', 'version'), platform=get_platform(), + ) + + # Convert the .exe to an unpacked egg + egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg') + dist.location = egg_path + egg_tmp = egg_path + '.tmp' + _egg_info = os.path.join(egg_tmp, 'EGG-INFO') + pkg_inf = os.path.join(_egg_info, 'PKG-INFO') + ensure_directory(pkg_inf) # make sure EGG-INFO dir exists + dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX + self.exe_to_egg(dist_filename, egg_tmp) + + # Write EGG-INFO/PKG-INFO + if not os.path.exists(pkg_inf): + f = open(pkg_inf, 'w') + f.write('Metadata-Version: 1.0\n') + for k, v in cfg.items('metadata'): + if k != 'target_version': + f.write('%s: %s\n' % (k.replace('_', '-').title(), v)) + f.close() + script_dir = os.path.join(_egg_info, 'scripts') + # delete entry-point scripts to avoid duping + self.delete_blockers([ + os.path.join(script_dir, args[0]) + for args in ScriptWriter.get_args(dist) + ]) + # Build .egg file from tmpdir + bdist_egg.make_zipfile( + egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run, + ) + # install the .egg + return self.install_egg(egg_path, tmpdir) + + def exe_to_egg(self, dist_filename, egg_tmp): + """Extract a bdist_wininst to the directories an egg would use""" + # Check for .pth file and set up prefix translations + prefixes = get_exe_prefixes(dist_filename) + to_compile = [] + native_libs = [] + top_level = {} + + def process(src, dst): + s = src.lower() + for old, new in prefixes: + if s.startswith(old): + src = new + src[len(old):] + parts = src.split('/') + dst = os.path.join(egg_tmp, *parts) + dl = dst.lower() + if dl.endswith('.pyd') or dl.endswith('.dll'): + parts[-1] = bdist_egg.strip_module(parts[-1]) + top_level[os.path.splitext(parts[0])[0]] = 1 + native_libs.append(src) + elif dl.endswith('.py') and old != 'SCRIPTS/': + top_level[os.path.splitext(parts[0])[0]] = 1 + to_compile.append(dst) + return dst + if not src.endswith('.pth'): + log.warn("WARNING: can't process %s", src) + return None + + # extract, tracking .pyd/.dll->native_libs and .py -> to_compile + unpack_archive(dist_filename, egg_tmp, process) + stubs = [] + for res in native_libs: + if res.lower().endswith('.pyd'): # create stubs for .pyd's + parts = res.split('/') + resource = parts[-1] + parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py' + pyfile = os.path.join(egg_tmp, *parts) + to_compile.append(pyfile) + stubs.append(pyfile) + bdist_egg.write_stub(resource, pyfile) + self.byte_compile(to_compile) # compile .py's + bdist_egg.write_safety_flag( + os.path.join(egg_tmp, 'EGG-INFO'), + bdist_egg.analyze_egg(egg_tmp, stubs)) # write zip-safety flag + + for name in 'top_level', 'native_libs': + if locals()[name]: + txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt') + if not os.path.exists(txt): + f = open(txt, 'w') + f.write('\n'.join(locals()[name]) + '\n') + f.close() + + def install_wheel(self, wheel_path, tmpdir): + wheel = Wheel(wheel_path) + assert wheel.is_compatible() + destination = os.path.join(self.install_dir, wheel.egg_name()) + destination = os.path.abspath(destination) + if not self.dry_run: + ensure_directory(destination) + if os.path.isdir(destination) and not os.path.islink(destination): + dir_util.remove_tree(destination, dry_run=self.dry_run) + elif os.path.exists(destination): + self.execute( + os.unlink, + (destination,), + "Removing " + destination, + ) + try: + self.execute( + wheel.install_as_egg, + (destination,), + ("Installing %s to %s") % ( + os.path.basename(wheel_path), + os.path.dirname(destination) + ), + ) + finally: + update_dist_caches(destination, fix_zipimporter_caches=False) + self.add_output(destination) + return self.egg_distribution(destination) + + __mv_warning = textwrap.dedent(""" + Because this distribution was installed --multi-version, before you can + import modules from this package in an application, you will need to + 'import pkg_resources' and then use a 'require()' call similar to one of + these examples, in order to select the desired version: + + pkg_resources.require("%(name)s") # latest installed version + pkg_resources.require("%(name)s==%(version)s") # this exact version + pkg_resources.require("%(name)s>=%(version)s") # this version or higher + """).lstrip() + + __id_warning = textwrap.dedent(""" + Note also that the installation directory must be on sys.path at runtime for + this to work. (e.g. by being the application's script directory, by being on + PYTHONPATH, or by being added to sys.path by your code.) + """) + + def installation_report(self, req, dist, what="Installed"): + """Helpful installation message for display to package users""" + msg = "\n%(what)s %(eggloc)s%(extras)s" + if self.multi_version and not self.no_report: + msg += '\n' + self.__mv_warning + if self.install_dir not in map(normalize_path, sys.path): + msg += '\n' + self.__id_warning + + eggloc = dist.location + name = dist.project_name + version = dist.version + extras = '' # TODO: self.report_extras(req, dist) + return msg % locals() + + __editable_msg = textwrap.dedent(""" + Extracted editable version of %(spec)s to %(dirname)s + + If it uses setuptools in its setup script, you can activate it in + "development" mode by going to that directory and running:: + + %(python)s setup.py develop + + See the setuptools documentation for the "develop" command for more info. + """).lstrip() + + def report_editable(self, spec, setup_script): + dirname = os.path.dirname(setup_script) + python = sys.executable + return '\n' + self.__editable_msg % locals() + + def run_setup(self, setup_script, setup_base, args): + sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg) + sys.modules.setdefault('distutils.command.egg_info', egg_info) + + args = list(args) + if self.verbose > 2: + v = 'v' * (self.verbose - 1) + args.insert(0, '-' + v) + elif self.verbose < 2: + args.insert(0, '-q') + if self.dry_run: + args.insert(0, '-n') + log.info( + "Running %s %s", setup_script[len(setup_base) + 1:], ' '.join(args) + ) + try: + run_setup(setup_script, args) + except SystemExit as v: + raise DistutilsError("Setup script exited with %s" % (v.args[0],)) + + def build_and_install(self, setup_script, setup_base): + args = ['bdist_egg', '--dist-dir'] + + dist_dir = tempfile.mkdtemp( + prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script) + ) + try: + self._set_fetcher_options(os.path.dirname(setup_script)) + args.append(dist_dir) + + self.run_setup(setup_script, setup_base, args) + all_eggs = Environment([dist_dir]) + eggs = [] + for key in all_eggs: + for dist in all_eggs[key]: + eggs.append(self.install_egg(dist.location, setup_base)) + if not eggs and not self.dry_run: + log.warn("No eggs found in %s (setup script problem?)", + dist_dir) + return eggs + finally: + rmtree(dist_dir) + log.set_verbosity(self.verbose) # restore our log verbosity + + def _set_fetcher_options(self, base): + """ + When easy_install is about to run bdist_egg on a source dist, that + source dist might have 'setup_requires' directives, requiring + additional fetching. Ensure the fetcher options given to easy_install + are available to that command as well. + """ + # find the fetch options from easy_install and write them out + # to the setup.cfg file. + ei_opts = self.distribution.get_option_dict('easy_install').copy() + fetch_directives = ( + 'find_links', 'site_dirs', 'index_url', 'optimize', + 'site_dirs', 'allow_hosts', + ) + fetch_options = {} + for key, val in ei_opts.items(): + if key not in fetch_directives: + continue + fetch_options[key.replace('_', '-')] = val[1] + # create a settings dictionary suitable for `edit_config` + settings = dict(easy_install=fetch_options) + cfg_filename = os.path.join(base, 'setup.cfg') + setopt.edit_config(cfg_filename, settings) + + def update_pth(self, dist): + if self.pth_file is None: + return + + for d in self.pth_file[dist.key]: # drop old entries + if self.multi_version or d.location != dist.location: + log.info("Removing %s from easy-install.pth file", d) + self.pth_file.remove(d) + if d.location in self.shadow_path: + self.shadow_path.remove(d.location) + + if not self.multi_version: + if dist.location in self.pth_file.paths: + log.info( + "%s is already the active version in easy-install.pth", + dist, + ) + else: + log.info("Adding %s to easy-install.pth file", dist) + self.pth_file.add(dist) # add new entry + if dist.location not in self.shadow_path: + self.shadow_path.append(dist.location) + + if not self.dry_run: + + self.pth_file.save() + + if dist.key == 'setuptools': + # Ensure that setuptools itself never becomes unavailable! + # XXX should this check for latest version? + filename = os.path.join(self.install_dir, 'setuptools.pth') + if os.path.islink(filename): + os.unlink(filename) + f = open(filename, 'wt') + f.write(self.pth_file.make_relative(dist.location) + '\n') + f.close() + + def unpack_progress(self, src, dst): + # Progress filter for unpacking + log.debug("Unpacking %s to %s", src, dst) + return dst # only unpack-and-compile skips files for dry run + + def unpack_and_compile(self, egg_path, destination): + to_compile = [] + to_chmod = [] + + def pf(src, dst): + if dst.endswith('.py') and not src.startswith('EGG-INFO/'): + to_compile.append(dst) + elif dst.endswith('.dll') or dst.endswith('.so'): + to_chmod.append(dst) + self.unpack_progress(src, dst) + return not self.dry_run and dst or None + + unpack_archive(egg_path, destination, pf) + self.byte_compile(to_compile) + if not self.dry_run: + for f in to_chmod: + mode = ((os.stat(f)[stat.ST_MODE]) | 0o555) & 0o7755 + chmod(f, mode) + + def byte_compile(self, to_compile): + if sys.dont_write_bytecode: + return + + from distutils.util import byte_compile + + try: + # try to make the byte compile messages quieter + log.set_verbosity(self.verbose - 1) + + byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run) + if self.optimize: + byte_compile( + to_compile, optimize=self.optimize, force=1, + dry_run=self.dry_run, + ) + finally: + log.set_verbosity(self.verbose) # restore original verbosity + + __no_default_msg = textwrap.dedent(""" + bad install directory or PYTHONPATH + + You are attempting to install a package to a directory that is not + on PYTHONPATH and which Python does not read ".pth" files from. The + installation directory you specified (via --install-dir, --prefix, or + the distutils default setting) was: + + %s + + and your PYTHONPATH environment variable currently contains: + + %r + + Here are some of your options for correcting the problem: + + * You can choose a different installation directory, i.e., one that is + on PYTHONPATH or supports .pth files + + * You can add the installation directory to the PYTHONPATH environment + variable. (It must then also be on PYTHONPATH whenever you run + Python and want to use the package(s) you are installing.) + + * You can set up the installation directory to support ".pth" files by + using one of the approaches described here: + + https://setuptools.readthedocs.io/en/latest/easy_install.html#custom-installation-locations + + + Please make the appropriate changes for your system and try again.""").lstrip() + + def no_default_version_msg(self): + template = self.__no_default_msg + return template % (self.install_dir, os.environ.get('PYTHONPATH', '')) + + def install_site_py(self): + """Make sure there's a site.py in the target dir, if needed""" + + if self.sitepy_installed: + return # already did it, or don't need to + + sitepy = os.path.join(self.install_dir, "site.py") + source = resource_string("setuptools", "site-patch.py") + source = source.decode('utf-8') + current = "" + + if os.path.exists(sitepy): + log.debug("Checking existing site.py in %s", self.install_dir) + with io.open(sitepy) as strm: + current = strm.read() + + if not current.startswith('def __boot():'): + raise DistutilsError( + "%s is not a setuptools-generated site.py; please" + " remove it." % sitepy + ) + + if current != source: + log.info("Creating %s", sitepy) + if not self.dry_run: + ensure_directory(sitepy) + with io.open(sitepy, 'w', encoding='utf-8') as strm: + strm.write(source) + self.byte_compile([sitepy]) + + self.sitepy_installed = True + + def create_home_path(self): + """Create directories under ~.""" + if not self.user: + return + home = convert_path(os.path.expanduser("~")) + for name, path in six.iteritems(self.config_vars): + if path.startswith(home) and not os.path.isdir(path): + self.debug_print("os.makedirs('%s', 0o700)" % path) + os.makedirs(path, 0o700) + + INSTALL_SCHEMES = dict( + posix=dict( + install_dir='$base/lib/python$py_version_short/site-packages', + script_dir='$base/bin', + ), + ) + + DEFAULT_SCHEME = dict( + install_dir='$base/Lib/site-packages', + script_dir='$base/Scripts', + ) + + def _expand(self, *attrs): + config_vars = self.get_finalized_command('install').config_vars + + if self.prefix: + # Set default install_dir/scripts from --prefix + config_vars = config_vars.copy() + config_vars['base'] = self.prefix + scheme = self.INSTALL_SCHEMES.get(os.name, self.DEFAULT_SCHEME) + for attr, val in scheme.items(): + if getattr(self, attr, None) is None: + setattr(self, attr, val) + + from distutils.util import subst_vars + + for attr in attrs: + val = getattr(self, attr) + if val is not None: + val = subst_vars(val, config_vars) + if os.name == 'posix': + val = os.path.expanduser(val) + setattr(self, attr, val) + + +def _pythonpath(): + items = os.environ.get('PYTHONPATH', '').split(os.pathsep) + return filter(None, items) + + +def get_site_dirs(): + """ + Return a list of 'site' dirs + """ + + sitedirs = [] + + # start with PYTHONPATH + sitedirs.extend(_pythonpath()) + + prefixes = [sys.prefix] + if sys.exec_prefix != sys.prefix: + prefixes.append(sys.exec_prefix) + for prefix in prefixes: + if prefix: + if sys.platform in ('os2emx', 'riscos'): + sitedirs.append(os.path.join(prefix, "Lib", "site-packages")) + elif os.sep == '/': + sitedirs.extend([ + os.path.join( + prefix, + "lib", + "python" + sys.version[:3], + "site-packages", + ), + os.path.join(prefix, "lib", "site-python"), + ]) + else: + sitedirs.extend([ + prefix, + os.path.join(prefix, "lib", "site-packages"), + ]) + if sys.platform == 'darwin': + # for framework builds *only* we add the standard Apple + # locations. Currently only per-user, but /Library and + # /Network/Library could be added too + if 'Python.framework' in prefix: + home = os.environ.get('HOME') + if home: + home_sp = os.path.join( + home, + 'Library', + 'Python', + sys.version[:3], + 'site-packages', + ) + sitedirs.append(home_sp) + lib_paths = get_path('purelib'), get_path('platlib') + for site_lib in lib_paths: + if site_lib not in sitedirs: + sitedirs.append(site_lib) + + if site.ENABLE_USER_SITE: + sitedirs.append(site.USER_SITE) + + try: + sitedirs.extend(site.getsitepackages()) + except AttributeError: + pass + + sitedirs = list(map(normalize_path, sitedirs)) + + return sitedirs + + +def expand_paths(inputs): + """Yield sys.path directories that might contain "old-style" packages""" + + seen = {} + + for dirname in inputs: + dirname = normalize_path(dirname) + if dirname in seen: + continue + + seen[dirname] = 1 + if not os.path.isdir(dirname): + continue + + files = os.listdir(dirname) + yield dirname, files + + for name in files: + if not name.endswith('.pth'): + # We only care about the .pth files + continue + if name in ('easy-install.pth', 'setuptools.pth'): + # Ignore .pth files that we control + continue + + # Read the .pth file + f = open(os.path.join(dirname, name)) + lines = list(yield_lines(f)) + f.close() + + # Yield existing non-dupe, non-import directory lines from it + for line in lines: + if not line.startswith("import"): + line = normalize_path(line.rstrip()) + if line not in seen: + seen[line] = 1 + if not os.path.isdir(line): + continue + yield line, os.listdir(line) + + +def extract_wininst_cfg(dist_filename): + """Extract configuration data from a bdist_wininst .exe + + Returns a configparser.RawConfigParser, or None + """ + f = open(dist_filename, 'rb') + try: + endrec = zipfile._EndRecData(f) + if endrec is None: + return None + + prepended = (endrec[9] - endrec[5]) - endrec[6] + if prepended < 12: # no wininst data here + return None + f.seek(prepended - 12) + + tag, cfglen, bmlen = struct.unpack("<iii", f.read(12)) + if tag not in (0x1234567A, 0x1234567B): + return None # not a valid tag + + f.seek(prepended - (12 + cfglen)) + init = {'version': '', 'target_version': ''} + cfg = configparser.RawConfigParser(init) + try: + part = f.read(cfglen) + # Read up to the first null byte. + config = part.split(b'\0', 1)[0] + # Now the config is in bytes, but for RawConfigParser, it should + # be text, so decode it. + config = config.decode(sys.getfilesystemencoding()) + cfg.readfp(six.StringIO(config)) + except configparser.Error: + return None + if not cfg.has_section('metadata') or not cfg.has_section('Setup'): + return None + return cfg + + finally: + f.close() + + +def get_exe_prefixes(exe_filename): + """Get exe->egg path translations for a given .exe file""" + + prefixes = [ + ('PURELIB/', ''), + ('PLATLIB/pywin32_system32', ''), + ('PLATLIB/', ''), + ('SCRIPTS/', 'EGG-INFO/scripts/'), + ('DATA/lib/site-packages', ''), + ] + z = zipfile.ZipFile(exe_filename) + try: + for info in z.infolist(): + name = info.filename + parts = name.split('/') + if len(parts) == 3 and parts[2] == 'PKG-INFO': + if parts[1].endswith('.egg-info'): + prefixes.insert(0, ('/'.join(parts[:2]), 'EGG-INFO/')) + break + if len(parts) != 2 or not name.endswith('.pth'): + continue + if name.endswith('-nspkg.pth'): + continue + if parts[0].upper() in ('PURELIB', 'PLATLIB'): + contents = z.read(name) + if six.PY3: + contents = contents.decode() + for pth in yield_lines(contents): + pth = pth.strip().replace('\\', '/') + if not pth.startswith('import'): + prefixes.append((('%s/%s/' % (parts[0], pth)), '')) + finally: + z.close() + prefixes = [(x.lower(), y) for x, y in prefixes] + prefixes.sort() + prefixes.reverse() + return prefixes + + +class PthDistributions(Environment): + """A .pth file with Distribution paths in it""" + + dirty = False + + def __init__(self, filename, sitedirs=()): + self.filename = filename + self.sitedirs = list(map(normalize_path, sitedirs)) + self.basedir = normalize_path(os.path.dirname(self.filename)) + self._load() + Environment.__init__(self, [], None, None) + for path in yield_lines(self.paths): + list(map(self.add, find_distributions(path, True))) + + def _load(self): + self.paths = [] + saw_import = False + seen = dict.fromkeys(self.sitedirs) + if os.path.isfile(self.filename): + f = open(self.filename, 'rt') + for line in f: + if line.startswith('import'): + saw_import = True + continue + path = line.rstrip() + self.paths.append(path) + if not path.strip() or path.strip().startswith('#'): + continue + # skip non-existent paths, in case somebody deleted a package + # manually, and duplicate paths as well + path = self.paths[-1] = normalize_path( + os.path.join(self.basedir, path) + ) + if not os.path.exists(path) or path in seen: + self.paths.pop() # skip it + self.dirty = True # we cleaned up, so we're dirty now :) + continue + seen[path] = 1 + f.close() + + if self.paths and not saw_import: + self.dirty = True # ensure anything we touch has import wrappers + while self.paths and not self.paths[-1].strip(): + self.paths.pop() + + def save(self): + """Write changed .pth file back to disk""" + if not self.dirty: + return + + rel_paths = list(map(self.make_relative, self.paths)) + if rel_paths: + log.debug("Saving %s", self.filename) + lines = self._wrap_lines(rel_paths) + data = '\n'.join(lines) + '\n' + + if os.path.islink(self.filename): + os.unlink(self.filename) + with open(self.filename, 'wt') as f: + f.write(data) + + elif os.path.exists(self.filename): + log.debug("Deleting empty %s", self.filename) + os.unlink(self.filename) + + self.dirty = False + + @staticmethod + def _wrap_lines(lines): + return lines + + def add(self, dist): + """Add `dist` to the distribution map""" + new_path = ( + dist.location not in self.paths and ( + dist.location not in self.sitedirs or + # account for '.' being in PYTHONPATH + dist.location == os.getcwd() + ) + ) + if new_path: + self.paths.append(dist.location) + self.dirty = True + Environment.add(self, dist) + + def remove(self, dist): + """Remove `dist` from the distribution map""" + while dist.location in self.paths: + self.paths.remove(dist.location) + self.dirty = True + Environment.remove(self, dist) + + def make_relative(self, path): + npath, last = os.path.split(normalize_path(path)) + baselen = len(self.basedir) + parts = [last] + sep = os.altsep == '/' and '/' or os.sep + while len(npath) >= baselen: + if npath == self.basedir: + parts.append(os.curdir) + parts.reverse() + return sep.join(parts) + npath, last = os.path.split(npath) + parts.append(last) + else: + return path + + +class RewritePthDistributions(PthDistributions): + @classmethod + def _wrap_lines(cls, lines): + yield cls.prelude + for line in lines: + yield line + yield cls.postlude + + prelude = _one_liner(""" + import sys + sys.__plen = len(sys.path) + """) + postlude = _one_liner(""" + import sys + new = sys.path[sys.__plen:] + del sys.path[sys.__plen:] + p = getattr(sys, '__egginsert', 0) + sys.path[p:p] = new + sys.__egginsert = p + len(new) + """) + + +if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite': + PthDistributions = RewritePthDistributions + + +def _first_line_re(): + """ + Return a regular expression based on first_line_re suitable for matching + strings. + """ + if isinstance(first_line_re.pattern, str): + return first_line_re + + # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern. + return re.compile(first_line_re.pattern.decode()) + + +def auto_chmod(func, arg, exc): + if func in [os.unlink, os.remove] and os.name == 'nt': + chmod(arg, stat.S_IWRITE) + return func(arg) + et, ev, _ = sys.exc_info() + six.reraise(et, (ev[0], ev[1] + (" %s %s" % (func, arg)))) + + +def update_dist_caches(dist_path, fix_zipimporter_caches): + """ + Fix any globally cached `dist_path` related data + + `dist_path` should be a path of a newly installed egg distribution (zipped + or unzipped). + + sys.path_importer_cache contains finder objects that have been cached when + importing data from the original distribution. Any such finders need to be + cleared since the replacement distribution might be packaged differently, + e.g. a zipped egg distribution might get replaced with an unzipped egg + folder or vice versa. Having the old finders cached may then cause Python + to attempt loading modules from the replacement distribution using an + incorrect loader. + + zipimport.zipimporter objects are Python loaders charged with importing + data packaged inside zip archives. If stale loaders referencing the + original distribution, are left behind, they can fail to load modules from + the replacement distribution. E.g. if an old zipimport.zipimporter instance + is used to load data from a new zipped egg archive, it may cause the + operation to attempt to locate the requested data in the wrong location - + one indicated by the original distribution's zip archive directory + information. Such an operation may then fail outright, e.g. report having + read a 'bad local file header', or even worse, it may fail silently & + return invalid data. + + zipimport._zip_directory_cache contains cached zip archive directory + information for all existing zipimport.zipimporter instances and all such + instances connected to the same archive share the same cached directory + information. + + If asked, and the underlying Python implementation allows it, we can fix + all existing zipimport.zipimporter instances instead of having to track + them down and remove them one by one, by updating their shared cached zip + archive directory information. This, of course, assumes that the + replacement distribution is packaged as a zipped egg. + + If not asked to fix existing zipimport.zipimporter instances, we still do + our best to clear any remaining zipimport.zipimporter related cached data + that might somehow later get used when attempting to load data from the new + distribution and thus cause such load operations to fail. Note that when + tracking down such remaining stale data, we can not catch every conceivable + usage from here, and we clear only those that we know of and have found to + cause problems if left alive. Any remaining caches should be updated by + whomever is in charge of maintaining them, i.e. they should be ready to + handle us replacing their zip archives with new distributions at runtime. + + """ + # There are several other known sources of stale zipimport.zipimporter + # instances that we do not clear here, but might if ever given a reason to + # do so: + # * Global setuptools pkg_resources.working_set (a.k.a. 'master working + # set') may contain distributions which may in turn contain their + # zipimport.zipimporter loaders. + # * Several zipimport.zipimporter loaders held by local variables further + # up the function call stack when running the setuptools installation. + # * Already loaded modules may have their __loader__ attribute set to the + # exact loader instance used when importing them. Python 3.4 docs state + # that this information is intended mostly for introspection and so is + # not expected to cause us problems. + normalized_path = normalize_path(dist_path) + _uncache(normalized_path, sys.path_importer_cache) + if fix_zipimporter_caches: + _replace_zip_directory_cache_data(normalized_path) + else: + # Here, even though we do not want to fix existing and now stale + # zipimporter cache information, we still want to remove it. Related to + # Python's zip archive directory information cache, we clear each of + # its stale entries in two phases: + # 1. Clear the entry so attempting to access zip archive information + # via any existing stale zipimport.zipimporter instances fails. + # 2. Remove the entry from the cache so any newly constructed + # zipimport.zipimporter instances do not end up using old stale + # zip archive directory information. + # This whole stale data removal step does not seem strictly necessary, + # but has been left in because it was done before we started replacing + # the zip archive directory information cache content if possible, and + # there are no relevant unit tests that we can depend on to tell us if + # this is really needed. + _remove_and_clear_zip_directory_cache_data(normalized_path) + + +def _collect_zipimporter_cache_entries(normalized_path, cache): + """ + Return zipimporter cache entry keys related to a given normalized path. + + Alternative path spellings (e.g. those using different character case or + those using alternative path separators) related to the same path are + included. Any sub-path entries are included as well, i.e. those + corresponding to zip archives embedded in other zip archives. + + """ + result = [] + prefix_len = len(normalized_path) + for p in cache: + np = normalize_path(p) + if (np.startswith(normalized_path) and + np[prefix_len:prefix_len + 1] in (os.sep, '')): + result.append(p) + return result + + +def _update_zipimporter_cache(normalized_path, cache, updater=None): + """ + Update zipimporter cache data for a given normalized path. + + Any sub-path entries are processed as well, i.e. those corresponding to zip + archives embedded in other zip archives. + + Given updater is a callable taking a cache entry key and the original entry + (after already removing the entry from the cache), and expected to update + the entry and possibly return a new one to be inserted in its place. + Returning None indicates that the entry should not be replaced with a new + one. If no updater is given, the cache entries are simply removed without + any additional processing, the same as if the updater simply returned None. + + """ + for p in _collect_zipimporter_cache_entries(normalized_path, cache): + # N.B. pypy's custom zipimport._zip_directory_cache implementation does + # not support the complete dict interface: + # * Does not support item assignment, thus not allowing this function + # to be used only for removing existing cache entries. + # * Does not support the dict.pop() method, forcing us to use the + # get/del patterns instead. For more detailed information see the + # following links: + # https://github.com/pypa/setuptools/issues/202#issuecomment-202913420 + # http://bit.ly/2h9itJX + old_entry = cache[p] + del cache[p] + new_entry = updater and updater(p, old_entry) + if new_entry is not None: + cache[p] = new_entry + + +def _uncache(normalized_path, cache): + _update_zipimporter_cache(normalized_path, cache) + + +def _remove_and_clear_zip_directory_cache_data(normalized_path): + def clear_and_remove_cached_zip_archive_directory_data(path, old_entry): + old_entry.clear() + + _update_zipimporter_cache( + normalized_path, zipimport._zip_directory_cache, + updater=clear_and_remove_cached_zip_archive_directory_data) + + +# PyPy Python implementation does not allow directly writing to the +# zipimport._zip_directory_cache and so prevents us from attempting to correct +# its content. The best we can do there is clear the problematic cache content +# and have PyPy repopulate it as needed. The downside is that if there are any +# stale zipimport.zipimporter instances laying around, attempting to use them +# will fail due to not having its zip archive directory information available +# instead of being automatically corrected to use the new correct zip archive +# directory information. +if '__pypy__' in sys.builtin_module_names: + _replace_zip_directory_cache_data = \ + _remove_and_clear_zip_directory_cache_data +else: + + def _replace_zip_directory_cache_data(normalized_path): + def replace_cached_zip_archive_directory_data(path, old_entry): + # N.B. In theory, we could load the zip directory information just + # once for all updated path spellings, and then copy it locally and + # update its contained path strings to contain the correct + # spelling, but that seems like a way too invasive move (this cache + # structure is not officially documented anywhere and could in + # theory change with new Python releases) for no significant + # benefit. + old_entry.clear() + zipimport.zipimporter(path) + old_entry.update(zipimport._zip_directory_cache[path]) + return old_entry + + _update_zipimporter_cache( + normalized_path, zipimport._zip_directory_cache, + updater=replace_cached_zip_archive_directory_data) + + +def is_python(text, filename='<string>'): + "Is this string a valid Python script?" + try: + compile(text, filename, 'exec') + except (SyntaxError, TypeError): + return False + else: + return True + + +def is_sh(executable): + """Determine if the specified executable is a .sh (contains a #! line)""" + try: + with io.open(executable, encoding='latin-1') as fp: + magic = fp.read(2) + except (OSError, IOError): + return executable + return magic == '#!' + + +def nt_quote_arg(arg): + """Quote a command line argument according to Windows parsing rules""" + return subprocess.list2cmdline([arg]) + + +def is_python_script(script_text, filename): + """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc. + """ + if filename.endswith('.py') or filename.endswith('.pyw'): + return True # extension says it's Python + if is_python(script_text, filename): + return True # it's syntactically valid Python + if script_text.startswith('#!'): + # It begins with a '#!' line, so check if 'python' is in it somewhere + return 'python' in script_text.splitlines()[0].lower() + + return False # Not any Python I can recognize + + +try: + from os import chmod as _chmod +except ImportError: + # Jython compatibility + def _chmod(*args): + pass + + +def chmod(path, mode): + log.debug("changing mode of %s to %o", path, mode) + try: + _chmod(path, mode) + except os.error as e: + log.debug("chmod failed: %s", e) + + +class CommandSpec(list): + """ + A command spec for a #! header, specified as a list of arguments akin to + those passed to Popen. + """ + + options = [] + split_args = dict() + + @classmethod + def best(cls): + """ + Choose the best CommandSpec class based on environmental conditions. + """ + return cls + + @classmethod + def _sys_executable(cls): + _default = os.path.normpath(sys.executable) + return os.environ.get('__PYVENV_LAUNCHER__', _default) + + @classmethod + def from_param(cls, param): + """ + Construct a CommandSpec from a parameter to build_scripts, which may + be None. + """ + if isinstance(param, cls): + return param + if isinstance(param, list): + return cls(param) + if param is None: + return cls.from_environment() + # otherwise, assume it's a string. + return cls.from_string(param) + + @classmethod + def from_environment(cls): + return cls([cls._sys_executable()]) + + @classmethod + def from_string(cls, string): + """ + Construct a command spec from a simple string representing a command + line parseable by shlex.split. + """ + items = shlex.split(string, **cls.split_args) + return cls(items) + + def install_options(self, script_text): + self.options = shlex.split(self._extract_options(script_text)) + cmdline = subprocess.list2cmdline(self) + if not isascii(cmdline): + self.options[:0] = ['-x'] + + @staticmethod + def _extract_options(orig_script): + """ + Extract any options from the first line of the script. + """ + first = (orig_script + '\n').splitlines()[0] + match = _first_line_re().match(first) + options = match.group(1) or '' if match else '' + return options.strip() + + def as_header(self): + return self._render(self + list(self.options)) + + @staticmethod + def _strip_quotes(item): + _QUOTES = '"\'' + for q in _QUOTES: + if item.startswith(q) and item.endswith(q): + return item[1:-1] + return item + + @staticmethod + def _render(items): + cmdline = subprocess.list2cmdline( + CommandSpec._strip_quotes(item.strip()) for item in items) + return '#!' + cmdline + '\n' + + +# For pbr compat; will be removed in a future version. +sys_executable = CommandSpec._sys_executable() + + +class WindowsCommandSpec(CommandSpec): + split_args = dict(posix=False) + + +class ScriptWriter: + """ + Encapsulates behavior around writing entry point scripts for console and + gui apps. + """ + + template = textwrap.dedent(r""" + # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r + __requires__ = %(spec)r + import re + import sys + from pkg_resources import load_entry_point + + if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit( + load_entry_point(%(spec)r, %(group)r, %(name)r)() + ) + """).lstrip() + + command_spec_class = CommandSpec + + @classmethod + def get_script_args(cls, dist, executable=None, wininst=False): + # for backward compatibility + warnings.warn("Use get_args", EasyInstallDeprecationWarning) + writer = (WindowsScriptWriter if wininst else ScriptWriter).best() + header = cls.get_script_header("", executable, wininst) + return writer.get_args(dist, header) + + @classmethod + def get_script_header(cls, script_text, executable=None, wininst=False): + # for backward compatibility + warnings.warn("Use get_header", EasyInstallDeprecationWarning, stacklevel=2) + if wininst: + executable = "python.exe" + return cls.get_header(script_text, executable) + + @classmethod + def get_args(cls, dist, header=None): + """ + Yield write_script() argument tuples for a distribution's + console_scripts and gui_scripts entry points. + """ + if header is None: + header = cls.get_header() + spec = str(dist.as_requirement()) + for type_ in 'console', 'gui': + group = type_ + '_scripts' + for name, ep in dist.get_entry_map(group).items(): + cls._ensure_safe_name(name) + script_text = cls.template % locals() + args = cls._get_script_args(type_, name, header, script_text) + for res in args: + yield res + + @staticmethod + def _ensure_safe_name(name): + """ + Prevent paths in *_scripts entry point names. + """ + has_path_sep = re.search(r'[\\/]', name) + if has_path_sep: + raise ValueError("Path separators not allowed in script names") + + @classmethod + def get_writer(cls, force_windows): + # for backward compatibility + warnings.warn("Use best", EasyInstallDeprecationWarning) + return WindowsScriptWriter.best() if force_windows else cls.best() + + @classmethod + def best(cls): + """ + Select the best ScriptWriter for this environment. + """ + if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'): + return WindowsScriptWriter.best() + else: + return cls + + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + # Simply write the stub with no extension. + yield (name, header + script_text) + + @classmethod + def get_header(cls, script_text="", executable=None): + """Create a #! line, getting options (if any) from script_text""" + cmd = cls.command_spec_class.best().from_param(executable) + cmd.install_options(script_text) + return cmd.as_header() + + +class WindowsScriptWriter(ScriptWriter): + command_spec_class = WindowsCommandSpec + + @classmethod + def get_writer(cls): + # for backward compatibility + warnings.warn("Use best", EasyInstallDeprecationWarning) + return cls.best() + + @classmethod + def best(cls): + """ + Select the best ScriptWriter suitable for Windows + """ + writer_lookup = dict( + executable=WindowsExecutableLauncherWriter, + natural=cls, + ) + # for compatibility, use the executable launcher by default + launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable') + return writer_lookup[launcher] + + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + "For Windows, add a .py extension" + ext = dict(console='.pya', gui='.pyw')[type_] + if ext not in os.environ['PATHEXT'].lower().split(';'): + msg = ( + "{ext} not listed in PATHEXT; scripts will not be " + "recognized as executables." + ).format(**locals()) + warnings.warn(msg, UserWarning) + old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe'] + old.remove(ext) + header = cls._adjust_header(type_, header) + blockers = [name + x for x in old] + yield name + ext, header + script_text, 't', blockers + + @classmethod + def _adjust_header(cls, type_, orig_header): + """ + Make sure 'pythonw' is used for gui and and 'python' is used for + console (regardless of what sys.executable is). + """ + pattern = 'pythonw.exe' + repl = 'python.exe' + if type_ == 'gui': + pattern, repl = repl, pattern + pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE) + new_header = pattern_ob.sub(string=orig_header, repl=repl) + return new_header if cls._use_header(new_header) else orig_header + + @staticmethod + def _use_header(new_header): + """ + Should _adjust_header use the replaced header? + + On non-windows systems, always use. On + Windows systems, only use the replaced header if it resolves + to an executable on the system. + """ + clean_header = new_header[2:-1].strip('"') + return sys.platform != 'win32' or find_executable(clean_header) + + +class WindowsExecutableLauncherWriter(WindowsScriptWriter): + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + """ + For Windows, add a .py extension and an .exe launcher + """ + if type_ == 'gui': + launcher_type = 'gui' + ext = '-script.pyw' + old = ['.pyw'] + else: + launcher_type = 'cli' + ext = '-script.py' + old = ['.py', '.pyc', '.pyo'] + hdr = cls._adjust_header(type_, header) + blockers = [name + x for x in old] + yield (name + ext, hdr + script_text, 't', blockers) + yield ( + name + '.exe', get_win_launcher(launcher_type), + 'b' # write in binary mode + ) + if not is_64bit(): + # install a manifest for the launcher to prevent Windows + # from detecting it as an installer (which it will for + # launchers like easy_install.exe). Consider only + # adding a manifest for launchers detected as installers. + # See Distribute #143 for details. + m_name = name + '.exe.manifest' + yield (m_name, load_launcher_manifest(name), 't') + + +# for backward-compatibility +get_script_args = ScriptWriter.get_script_args +get_script_header = ScriptWriter.get_script_header + + +def get_win_launcher(type): + """ + Load the Windows launcher (executable) suitable for launching a script. + + `type` should be either 'cli' or 'gui' + + Returns the executable as a byte string. + """ + launcher_fn = '%s.exe' % type + if is_64bit(): + launcher_fn = launcher_fn.replace(".", "-64.") + else: + launcher_fn = launcher_fn.replace(".", "-32.") + return resource_string('setuptools', launcher_fn) + + +def load_launcher_manifest(name): + manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml') + if six.PY2: + return manifest % vars() + else: + return manifest.decode('utf-8') % vars() + + +def rmtree(path, ignore_errors=False, onerror=auto_chmod): + return shutil.rmtree(path, ignore_errors, onerror) + + +def current_umask(): + tmp = os.umask(0o022) + os.umask(tmp) + return tmp + + +def bootstrap(): + # This function is called when setuptools*.egg is run using /bin/sh + import setuptools + + argv0 = os.path.dirname(setuptools.__path__[0]) + sys.argv[0] = argv0 + sys.argv.append(argv0) + main() + + +def main(argv=None, **kw): + from setuptools import setup + from setuptools.dist import Distribution + + class DistributionWithoutHelpCommands(Distribution): + common_usage = "" + + def _show_help(self, *args, **kw): + with _patch_usage(): + Distribution._show_help(self, *args, **kw) + + if argv is None: + argv = sys.argv[1:] + + with _patch_usage(): + setup( + script_args=['-q', 'easy_install', '-v'] + argv, + script_name=sys.argv[0] or 'easy_install', + distclass=DistributionWithoutHelpCommands, + **kw + ) + + +@contextlib.contextmanager +def _patch_usage(): + import distutils.core + USAGE = textwrap.dedent(""" + usage: %(script)s [options] requirement_or_url ... + or: %(script)s --help + """).lstrip() + + def gen_usage(script_name): + return USAGE % dict( + script=os.path.basename(script_name), + ) + + saved = distutils.core.gen_usage + distutils.core.gen_usage = gen_usage + try: + yield + finally: + distutils.core.gen_usage = saved + +class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): + """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning.""" + diff --git a/lib/setuptools/command/egg_info.py b/lib/setuptools/command/egg_info.py new file mode 100644 index 0000000..d9fe3da --- /dev/null +++ b/lib/setuptools/command/egg_info.py @@ -0,0 +1,716 @@ +"""setuptools.command.egg_info + +Create a distribution's .egg-info directory and contents""" + +from distutils.filelist import FileList as _FileList +from distutils.errors import DistutilsInternalError +from distutils.util import convert_path +from distutils import log +import distutils.errors +import distutils.filelist +import os +import re +import sys +import io +import warnings +import time +import collections + +from setuptools.extern import six +from setuptools.extern.six.moves import map + +from setuptools import Command +from setuptools.command.sdist import sdist +from setuptools.command.sdist import walk_revctrl +from setuptools.command.setopt import edit_config +from setuptools.command import bdist_egg +from pkg_resources import ( + parse_requirements, safe_name, parse_version, + safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename) +import setuptools.unicode_utils as unicode_utils +from setuptools.glob import glob + +from setuptools.extern import packaging +from setuptools import SetuptoolsDeprecationWarning + +def translate_pattern(glob): + """ + Translate a file path glob like '*.txt' in to a regular expression. + This differs from fnmatch.translate which allows wildcards to match + directory separators. It also knows about '**/' which matches any number of + directories. + """ + pat = '' + + # This will split on '/' within [character classes]. This is deliberate. + chunks = glob.split(os.path.sep) + + sep = re.escape(os.sep) + valid_char = '[^%s]' % (sep,) + + for c, chunk in enumerate(chunks): + last_chunk = c == len(chunks) - 1 + + # Chunks that are a literal ** are globstars. They match anything. + if chunk == '**': + if last_chunk: + # Match anything if this is the last component + pat += '.*' + else: + # Match '(name/)*' + pat += '(?:%s+%s)*' % (valid_char, sep) + continue # Break here as the whole path component has been handled + + # Find any special characters in the remainder + i = 0 + chunk_len = len(chunk) + while i < chunk_len: + char = chunk[i] + if char == '*': + # Match any number of name characters + pat += valid_char + '*' + elif char == '?': + # Match a name character + pat += valid_char + elif char == '[': + # Character class + inner_i = i + 1 + # Skip initial !/] chars + if inner_i < chunk_len and chunk[inner_i] == '!': + inner_i = inner_i + 1 + if inner_i < chunk_len and chunk[inner_i] == ']': + inner_i = inner_i + 1 + + # Loop till the closing ] is found + while inner_i < chunk_len and chunk[inner_i] != ']': + inner_i = inner_i + 1 + + if inner_i >= chunk_len: + # Got to the end of the string without finding a closing ] + # Do not treat this as a matching group, but as a literal [ + pat += re.escape(char) + else: + # Grab the insides of the [brackets] + inner = chunk[i + 1:inner_i] + char_class = '' + + # Class negation + if inner[0] == '!': + char_class = '^' + inner = inner[1:] + + char_class += re.escape(inner) + pat += '[%s]' % (char_class,) + + # Skip to the end ] + i = inner_i + else: + pat += re.escape(char) + i += 1 + + # Join each chunk with the dir separator + if not last_chunk: + pat += sep + + pat += r'\Z' + return re.compile(pat, flags=re.MULTILINE|re.DOTALL) + + +class InfoCommon: + tag_build = None + tag_date = None + + @property + def name(self): + return safe_name(self.distribution.get_name()) + + def tagged_version(self): + version = self.distribution.get_version() + # egg_info may be called more than once for a distribution, + # in which case the version string already contains all tags. + if self.vtags and version.endswith(self.vtags): + return safe_version(version) + return safe_version(version + self.vtags) + + def tags(self): + version = '' + if self.tag_build: + version += self.tag_build + if self.tag_date: + version += time.strftime("-%Y%m%d") + return version + vtags = property(tags) + + +class egg_info(InfoCommon, Command): + description = "create a distribution's .egg-info directory" + + user_options = [ + ('egg-base=', 'e', "directory containing .egg-info directories" + " (default: top of the source tree)"), + ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"), + ('tag-build=', 'b', "Specify explicit tag to add to version number"), + ('no-date', 'D', "Don't include date stamp [default]"), + ] + + boolean_options = ['tag-date'] + negative_opt = { + 'no-date': 'tag-date', + } + + def initialize_options(self): + self.egg_base = None + self.egg_name = None + self.egg_info = None + self.egg_version = None + self.broken_egg_info = False + + #################################### + # allow the 'tag_svn_revision' to be detected and + # set, supporting sdists built on older Setuptools. + @property + def tag_svn_revision(self): + pass + + @tag_svn_revision.setter + def tag_svn_revision(self, value): + pass + #################################### + + def save_version_info(self, filename): + """ + Materialize the value of date into the + build tag. Install build keys in a deterministic order + to avoid arbitrary reordering on subsequent builds. + """ + egg_info = collections.OrderedDict() + # follow the order these keys would have been added + # when PYTHONHASHSEED=0 + egg_info['tag_build'] = self.tags() + egg_info['tag_date'] = 0 + edit_config(filename, dict(egg_info=egg_info)) + + def finalize_options(self): + # Note: we need to capture the current value returned + # by `self.tagged_version()`, so we can later update + # `self.distribution.metadata.version` without + # repercussions. + self.egg_name = self.name + self.egg_version = self.tagged_version() + parsed_version = parse_version(self.egg_version) + + try: + is_version = isinstance(parsed_version, packaging.version.Version) + spec = ( + "%s==%s" if is_version else "%s===%s" + ) + list( + parse_requirements(spec % (self.egg_name, self.egg_version)) + ) + except ValueError: + raise distutils.errors.DistutilsOptionError( + "Invalid distribution name or version syntax: %s-%s" % + (self.egg_name, self.egg_version) + ) + + if self.egg_base is None: + dirs = self.distribution.package_dir + self.egg_base = (dirs or {}).get('', os.curdir) + + self.ensure_dirname('egg_base') + self.egg_info = to_filename(self.egg_name) + '.egg-info' + if self.egg_base != os.curdir: + self.egg_info = os.path.join(self.egg_base, self.egg_info) + if '-' in self.egg_name: + self.check_broken_egg_info() + + # Set package version for the benefit of dumber commands + # (e.g. sdist, bdist_wininst, etc.) + # + self.distribution.metadata.version = self.egg_version + + # If we bootstrapped around the lack of a PKG-INFO, as might be the + # case in a fresh checkout, make sure that any special tags get added + # to the version info + # + pd = self.distribution._patched_dist + if pd is not None and pd.key == self.egg_name.lower(): + pd._version = self.egg_version + pd._parsed_version = parse_version(self.egg_version) + self.distribution._patched_dist = None + + def write_or_delete_file(self, what, filename, data, force=False): + """Write `data` to `filename` or delete if empty + + If `data` is non-empty, this routine is the same as ``write_file()``. + If `data` is empty but not ``None``, this is the same as calling + ``delete_file(filename)`. If `data` is ``None``, then this is a no-op + unless `filename` exists, in which case a warning is issued about the + orphaned file (if `force` is false), or deleted (if `force` is true). + """ + if data: + self.write_file(what, filename, data) + elif os.path.exists(filename): + if data is None and not force: + log.warn( + "%s not set in setup(), but %s exists", what, filename + ) + return + else: + self.delete_file(filename) + + def write_file(self, what, filename, data): + """Write `data` to `filename` (if not a dry run) after announcing it + + `what` is used in a log message to identify what is being written + to the file. + """ + log.info("writing %s to %s", what, filename) + if six.PY3: + data = data.encode("utf-8") + if not self.dry_run: + f = open(filename, 'wb') + f.write(data) + f.close() + + def delete_file(self, filename): + """Delete `filename` (if not a dry run) after announcing it""" + log.info("deleting %s", filename) + if not self.dry_run: + os.unlink(filename) + + def run(self): + self.mkpath(self.egg_info) + os.utime(self.egg_info, None) + installer = self.distribution.fetch_build_egg + for ep in iter_entry_points('egg_info.writers'): + ep.require(installer=installer) + writer = ep.resolve() + writer(self, ep.name, os.path.join(self.egg_info, ep.name)) + + # Get rid of native_libs.txt if it was put there by older bdist_egg + nl = os.path.join(self.egg_info, "native_libs.txt") + if os.path.exists(nl): + self.delete_file(nl) + + self.find_sources() + + def find_sources(self): + """Generate SOURCES.txt manifest file""" + manifest_filename = os.path.join(self.egg_info, "SOURCES.txt") + mm = manifest_maker(self.distribution) + mm.manifest = manifest_filename + mm.run() + self.filelist = mm.filelist + + def check_broken_egg_info(self): + bei = self.egg_name + '.egg-info' + if self.egg_base != os.curdir: + bei = os.path.join(self.egg_base, bei) + if os.path.exists(bei): + log.warn( + "-" * 78 + '\n' + "Note: Your current .egg-info directory has a '-' in its name;" + '\nthis will not work correctly with "setup.py develop".\n\n' + 'Please rename %s to %s to correct this problem.\n' + '-' * 78, + bei, self.egg_info + ) + self.broken_egg_info = self.egg_info + self.egg_info = bei # make it work for now + + +class FileList(_FileList): + # Implementations of the various MANIFEST.in commands + + def process_template_line(self, line): + # Parse the line: split it up, make sure the right number of words + # is there, and return the relevant words. 'action' is always + # defined: it's the first word of the line. Which of the other + # three are defined depends on the action; it'll be either + # patterns, (dir and patterns), or (dir_pattern). + (action, patterns, dir, dir_pattern) = self._parse_template_line(line) + + # OK, now we know that the action is valid and we have the + # right number of words on the line for that action -- so we + # can proceed with minimal error-checking. + if action == 'include': + self.debug_print("include " + ' '.join(patterns)) + for pattern in patterns: + if not self.include(pattern): + log.warn("warning: no files found matching '%s'", pattern) + + elif action == 'exclude': + self.debug_print("exclude " + ' '.join(patterns)) + for pattern in patterns: + if not self.exclude(pattern): + log.warn(("warning: no previously-included files " + "found matching '%s'"), pattern) + + elif action == 'global-include': + self.debug_print("global-include " + ' '.join(patterns)) + for pattern in patterns: + if not self.global_include(pattern): + log.warn(("warning: no files found matching '%s' " + "anywhere in distribution"), pattern) + + elif action == 'global-exclude': + self.debug_print("global-exclude " + ' '.join(patterns)) + for pattern in patterns: + if not self.global_exclude(pattern): + log.warn(("warning: no previously-included files matching " + "'%s' found anywhere in distribution"), + pattern) + + elif action == 'recursive-include': + self.debug_print("recursive-include %s %s" % + (dir, ' '.join(patterns))) + for pattern in patterns: + if not self.recursive_include(dir, pattern): + log.warn(("warning: no files found matching '%s' " + "under directory '%s'"), + pattern, dir) + + elif action == 'recursive-exclude': + self.debug_print("recursive-exclude %s %s" % + (dir, ' '.join(patterns))) + for pattern in patterns: + if not self.recursive_exclude(dir, pattern): + log.warn(("warning: no previously-included files matching " + "'%s' found under directory '%s'"), + pattern, dir) + + elif action == 'graft': + self.debug_print("graft " + dir_pattern) + if not self.graft(dir_pattern): + log.warn("warning: no directories found matching '%s'", + dir_pattern) + + elif action == 'prune': + self.debug_print("prune " + dir_pattern) + if not self.prune(dir_pattern): + log.warn(("no previously-included directories found " + "matching '%s'"), dir_pattern) + + else: + raise DistutilsInternalError( + "this cannot happen: invalid action '%s'" % action) + + def _remove_files(self, predicate): + """ + Remove all files from the file list that match the predicate. + Return True if any matching files were removed + """ + found = False + for i in range(len(self.files) - 1, -1, -1): + if predicate(self.files[i]): + self.debug_print(" removing " + self.files[i]) + del self.files[i] + found = True + return found + + def include(self, pattern): + """Include files that match 'pattern'.""" + found = [f for f in glob(pattern) if not os.path.isdir(f)] + self.extend(found) + return bool(found) + + def exclude(self, pattern): + """Exclude files that match 'pattern'.""" + match = translate_pattern(pattern) + return self._remove_files(match.match) + + def recursive_include(self, dir, pattern): + """ + Include all files anywhere in 'dir/' that match the pattern. + """ + full_pattern = os.path.join(dir, '**', pattern) + found = [f for f in glob(full_pattern, recursive=True) + if not os.path.isdir(f)] + self.extend(found) + return bool(found) + + def recursive_exclude(self, dir, pattern): + """ + Exclude any file anywhere in 'dir/' that match the pattern. + """ + match = translate_pattern(os.path.join(dir, '**', pattern)) + return self._remove_files(match.match) + + def graft(self, dir): + """Include all files from 'dir/'.""" + found = [ + item + for match_dir in glob(dir) + for item in distutils.filelist.findall(match_dir) + ] + self.extend(found) + return bool(found) + + def prune(self, dir): + """Filter out files from 'dir/'.""" + match = translate_pattern(os.path.join(dir, '**')) + return self._remove_files(match.match) + + def global_include(self, pattern): + """ + Include all files anywhere in the current directory that match the + pattern. This is very inefficient on large file trees. + """ + if self.allfiles is None: + self.findall() + match = translate_pattern(os.path.join('**', pattern)) + found = [f for f in self.allfiles if match.match(f)] + self.extend(found) + return bool(found) + + def global_exclude(self, pattern): + """ + Exclude all files anywhere that match the pattern. + """ + match = translate_pattern(os.path.join('**', pattern)) + return self._remove_files(match.match) + + def append(self, item): + if item.endswith('\r'): # Fix older sdists built on Windows + item = item[:-1] + path = convert_path(item) + + if self._safe_path(path): + self.files.append(path) + + def extend(self, paths): + self.files.extend(filter(self._safe_path, paths)) + + def _repair(self): + """ + Replace self.files with only safe paths + + Because some owners of FileList manipulate the underlying + ``files`` attribute directly, this method must be called to + repair those paths. + """ + self.files = list(filter(self._safe_path, self.files)) + + def _safe_path(self, path): + enc_warn = "'%s' not %s encodable -- skipping" + + # To avoid accidental trans-codings errors, first to unicode + u_path = unicode_utils.filesys_decode(path) + if u_path is None: + log.warn("'%s' in unexpected encoding -- skipping" % path) + return False + + # Must ensure utf-8 encodability + utf8_path = unicode_utils.try_encode(u_path, "utf-8") + if utf8_path is None: + log.warn(enc_warn, path, 'utf-8') + return False + + try: + # accept is either way checks out + if os.path.exists(u_path) or os.path.exists(utf8_path): + return True + # this will catch any encode errors decoding u_path + except UnicodeEncodeError: + log.warn(enc_warn, path, sys.getfilesystemencoding()) + + +class manifest_maker(sdist): + template = "MANIFEST.in" + + def initialize_options(self): + self.use_defaults = 1 + self.prune = 1 + self.manifest_only = 1 + self.force_manifest = 1 + + def finalize_options(self): + pass + + def run(self): + self.filelist = FileList() + if not os.path.exists(self.manifest): + self.write_manifest() # it must exist so it'll get in the list + self.add_defaults() + if os.path.exists(self.template): + self.read_template() + self.prune_file_list() + self.filelist.sort() + self.filelist.remove_duplicates() + self.write_manifest() + + def _manifest_normalize(self, path): + path = unicode_utils.filesys_decode(path) + return path.replace(os.sep, '/') + + def write_manifest(self): + """ + Write the file list in 'self.filelist' to the manifest file + named by 'self.manifest'. + """ + self.filelist._repair() + + # Now _repairs should encodability, but not unicode + files = [self._manifest_normalize(f) for f in self.filelist.files] + msg = "writing manifest file '%s'" % self.manifest + self.execute(write_file, (self.manifest, files), msg) + + def warn(self, msg): + if not self._should_suppress_warning(msg): + sdist.warn(self, msg) + + @staticmethod + def _should_suppress_warning(msg): + """ + suppress missing-file warnings from sdist + """ + return re.match(r"standard file .*not found", msg) + + def add_defaults(self): + sdist.add_defaults(self) + self.filelist.append(self.template) + self.filelist.append(self.manifest) + rcfiles = list(walk_revctrl()) + if rcfiles: + self.filelist.extend(rcfiles) + elif os.path.exists(self.manifest): + self.read_manifest() + + if os.path.exists("setup.py"): + # setup.py should be included by default, even if it's not + # the script called to create the sdist + self.filelist.append("setup.py") + + ei_cmd = self.get_finalized_command('egg_info') + self.filelist.graft(ei_cmd.egg_info) + + def prune_file_list(self): + build = self.get_finalized_command('build') + base_dir = self.distribution.get_fullname() + self.filelist.prune(build.build_base) + self.filelist.prune(base_dir) + sep = re.escape(os.sep) + self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep, + is_regex=1) + + +def write_file(filename, contents): + """Create a file with the specified name and write 'contents' (a + sequence of strings without line terminators) to it. + """ + contents = "\n".join(contents) + + # assuming the contents has been vetted for utf-8 encoding + contents = contents.encode("utf-8") + + with open(filename, "wb") as f: # always write POSIX-style manifest + f.write(contents) + + +def write_pkg_info(cmd, basename, filename): + log.info("writing %s", filename) + if not cmd.dry_run: + metadata = cmd.distribution.metadata + metadata.version, oldver = cmd.egg_version, metadata.version + metadata.name, oldname = cmd.egg_name, metadata.name + + try: + # write unescaped data to PKG-INFO, so older pkg_resources + # can still parse it + metadata.write_pkg_info(cmd.egg_info) + finally: + metadata.name, metadata.version = oldname, oldver + + safe = getattr(cmd.distribution, 'zip_safe', None) + + bdist_egg.write_safety_flag(cmd.egg_info, safe) + + +def warn_depends_obsolete(cmd, basename, filename): + if os.path.exists(filename): + log.warn( + "WARNING: 'depends.txt' is not used by setuptools 0.6!\n" + "Use the install_requires/extras_require setup() args instead." + ) + + +def _write_requirements(stream, reqs): + lines = yield_lines(reqs or ()) + append_cr = lambda line: line + '\n' + lines = map(append_cr, lines) + stream.writelines(lines) + + +def write_requirements(cmd, basename, filename): + dist = cmd.distribution + data = six.StringIO() + _write_requirements(data, dist.install_requires) + extras_require = dist.extras_require or {} + for extra in sorted(extras_require): + data.write('\n[{extra}]\n'.format(**vars())) + _write_requirements(data, extras_require[extra]) + cmd.write_or_delete_file("requirements", filename, data.getvalue()) + + +def write_setup_requirements(cmd, basename, filename): + data = io.StringIO() + _write_requirements(data, cmd.distribution.setup_requires) + cmd.write_or_delete_file("setup-requirements", filename, data.getvalue()) + + +def write_toplevel_names(cmd, basename, filename): + pkgs = dict.fromkeys( + [ + k.split('.', 1)[0] + for k in cmd.distribution.iter_distribution_names() + ] + ) + cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n') + + +def overwrite_arg(cmd, basename, filename): + write_arg(cmd, basename, filename, True) + + +def write_arg(cmd, basename, filename, force=False): + argname = os.path.splitext(basename)[0] + value = getattr(cmd.distribution, argname, None) + if value is not None: + value = '\n'.join(value) + '\n' + cmd.write_or_delete_file(argname, filename, value, force) + + +def write_entries(cmd, basename, filename): + ep = cmd.distribution.entry_points + + if isinstance(ep, six.string_types) or ep is None: + data = ep + elif ep is not None: + data = [] + for section, contents in sorted(ep.items()): + if not isinstance(contents, six.string_types): + contents = EntryPoint.parse_group(section, contents) + contents = '\n'.join(sorted(map(str, contents.values()))) + data.append('[%s]\n%s\n\n' % (section, contents)) + data = ''.join(data) + + cmd.write_or_delete_file('entry points', filename, data, True) + + +def get_pkg_info_revision(): + """ + Get a -r### off of PKG-INFO Version in case this is an sdist of + a subversion revision. + """ + warnings.warn("get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning) + if os.path.exists('PKG-INFO'): + with io.open('PKG-INFO') as f: + for line in f: + match = re.match(r"Version:.*-r(\d+)\s*$", line) + if match: + return int(match.group(1)) + return 0 + + +class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning): + """Class for warning about deprecations in eggInfo in setupTools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/lib/setuptools/command/install.py b/lib/setuptools/command/install.py new file mode 100644 index 0000000..31a5ddb --- /dev/null +++ b/lib/setuptools/command/install.py @@ -0,0 +1,125 @@ +from distutils.errors import DistutilsArgError +import inspect +import glob +import warnings +import platform +import distutils.command.install as orig + +import setuptools + +# Prior to numpy 1.9, NumPy relies on the '_install' name, so provide it for +# now. See https://github.com/pypa/setuptools/issues/199/ +_install = orig.install + + +class install(orig.install): + """Use easy_install to install the package, w/dependencies""" + + user_options = orig.install.user_options + [ + ('old-and-unmanageable', None, "Try not to use this!"), + ('single-version-externally-managed', None, + "used by system package builders to create 'flat' eggs"), + ] + boolean_options = orig.install.boolean_options + [ + 'old-and-unmanageable', 'single-version-externally-managed', + ] + new_commands = [ + ('install_egg_info', lambda self: True), + ('install_scripts', lambda self: True), + ] + _nc = dict(new_commands) + + def initialize_options(self): + orig.install.initialize_options(self) + self.old_and_unmanageable = None + self.single_version_externally_managed = None + + def finalize_options(self): + orig.install.finalize_options(self) + if self.root: + self.single_version_externally_managed = True + elif self.single_version_externally_managed: + if not self.root and not self.record: + raise DistutilsArgError( + "You must specify --record or --root when building system" + " packages" + ) + + def handle_extra_path(self): + if self.root or self.single_version_externally_managed: + # explicit backward-compatibility mode, allow extra_path to work + return orig.install.handle_extra_path(self) + + # Ignore extra_path when installing an egg (or being run by another + # command without --root or --single-version-externally-managed + self.path_file = None + self.extra_dirs = '' + + def run(self): + # Explicit request for old-style install? Just do it + if self.old_and_unmanageable or self.single_version_externally_managed: + return orig.install.run(self) + + if not self._called_from_setup(inspect.currentframe()): + # Run in backward-compatibility mode to support bdist_* commands. + orig.install.run(self) + else: + self.do_egg_install() + + @staticmethod + def _called_from_setup(run_frame): + """ + Attempt to detect whether run() was called from setup() or by another + command. If called by setup(), the parent caller will be the + 'run_command' method in 'distutils.dist', and *its* caller will be + the 'run_commands' method. If called any other way, the + immediate caller *might* be 'run_command', but it won't have been + called by 'run_commands'. Return True in that case or if a call stack + is unavailable. Return False otherwise. + """ + if run_frame is None: + msg = "Call stack not available. bdist_* commands may fail." + warnings.warn(msg) + if platform.python_implementation() == 'IronPython': + msg = "For best results, pass -X:Frames to enable call stack." + warnings.warn(msg) + return True + res = inspect.getouterframes(run_frame)[2] + caller, = res[:1] + info = inspect.getframeinfo(caller) + caller_module = caller.f_globals.get('__name__', '') + return ( + caller_module == 'distutils.dist' + and info.function == 'run_commands' + ) + + def do_egg_install(self): + + easy_install = self.distribution.get_command_class('easy_install') + + cmd = easy_install( + self.distribution, args="x", root=self.root, record=self.record, + ) + cmd.ensure_finalized() # finalize before bdist_egg munges install cmd + cmd.always_copy_from = '.' # make sure local-dir eggs get installed + + # pick up setup-dir .egg files only: no .egg-info + cmd.package_index.scan(glob.glob('*.egg')) + + self.run_command('bdist_egg') + args = [self.distribution.get_command_obj('bdist_egg').egg_output] + + if setuptools.bootstrap_install_from: + # Bootstrap self-installation of setuptools + args.insert(0, setuptools.bootstrap_install_from) + + cmd.args = args + cmd.run() + setuptools.bootstrap_install_from = None + + +# XXX Python 3.1 doesn't see _nc if this is inside the class +install.sub_commands = ( + [cmd for cmd in orig.install.sub_commands if cmd[0] not in install._nc] + + install.new_commands +) diff --git a/lib/setuptools/command/install_egg_info.py b/lib/setuptools/command/install_egg_info.py new file mode 100644 index 0000000..edc4718 --- /dev/null +++ b/lib/setuptools/command/install_egg_info.py @@ -0,0 +1,62 @@ +from distutils import log, dir_util +import os + +from setuptools import Command +from setuptools import namespaces +from setuptools.archive_util import unpack_archive +import pkg_resources + + +class install_egg_info(namespaces.Installer, Command): + """Install an .egg-info directory for the package""" + + description = "Install an .egg-info directory for the package" + + user_options = [ + ('install-dir=', 'd', "directory to install to"), + ] + + def initialize_options(self): + self.install_dir = None + + def finalize_options(self): + self.set_undefined_options('install_lib', + ('install_dir', 'install_dir')) + ei_cmd = self.get_finalized_command("egg_info") + basename = pkg_resources.Distribution( + None, None, ei_cmd.egg_name, ei_cmd.egg_version + ).egg_name() + '.egg-info' + self.source = ei_cmd.egg_info + self.target = os.path.join(self.install_dir, basename) + self.outputs = [] + + def run(self): + self.run_command('egg_info') + if os.path.isdir(self.target) and not os.path.islink(self.target): + dir_util.remove_tree(self.target, dry_run=self.dry_run) + elif os.path.exists(self.target): + self.execute(os.unlink, (self.target,), "Removing " + self.target) + if not self.dry_run: + pkg_resources.ensure_directory(self.target) + self.execute( + self.copytree, (), "Copying %s to %s" % (self.source, self.target) + ) + self.install_namespaces() + + def get_outputs(self): + return self.outputs + + def copytree(self): + # Copy the .egg-info tree to site-packages + def skimmer(src, dst): + # filter out source-control directories; note that 'src' is always + # a '/'-separated path, regardless of platform. 'dst' is a + # platform-specific path. + for skip in '.svn/', 'CVS/': + if src.startswith(skip) or '/' + skip in src: + return None + self.outputs.append(dst) + log.debug("Copying %s to %s", src, dst) + return dst + + unpack_archive(self.source, self.target, skimmer) diff --git a/lib/setuptools/command/install_lib.py b/lib/setuptools/command/install_lib.py new file mode 100644 index 0000000..2b31c3e --- /dev/null +++ b/lib/setuptools/command/install_lib.py @@ -0,0 +1,121 @@ +import os +import imp +from itertools import product, starmap +import distutils.command.install_lib as orig + + +class install_lib(orig.install_lib): + """Don't add compiled flags to filenames of non-Python files""" + + def run(self): + self.build() + outfiles = self.install() + if outfiles is not None: + # always compile, in case we have any extension stubs to deal with + self.byte_compile(outfiles) + + def get_exclusions(self): + """ + Return a collections.Sized collections.Container of paths to be + excluded for single_version_externally_managed installations. + """ + all_packages = ( + pkg + for ns_pkg in self._get_SVEM_NSPs() + for pkg in self._all_packages(ns_pkg) + ) + + excl_specs = product(all_packages, self._gen_exclusion_paths()) + return set(starmap(self._exclude_pkg_path, excl_specs)) + + def _exclude_pkg_path(self, pkg, exclusion_path): + """ + Given a package name and exclusion path within that package, + compute the full exclusion path. + """ + parts = pkg.split('.') + [exclusion_path] + return os.path.join(self.install_dir, *parts) + + @staticmethod + def _all_packages(pkg_name): + """ + >>> list(install_lib._all_packages('foo.bar.baz')) + ['foo.bar.baz', 'foo.bar', 'foo'] + """ + while pkg_name: + yield pkg_name + pkg_name, sep, child = pkg_name.rpartition('.') + + def _get_SVEM_NSPs(self): + """ + Get namespace packages (list) but only for + single_version_externally_managed installations and empty otherwise. + """ + # TODO: is it necessary to short-circuit here? i.e. what's the cost + # if get_finalized_command is called even when namespace_packages is + # False? + if not self.distribution.namespace_packages: + return [] + + install_cmd = self.get_finalized_command('install') + svem = install_cmd.single_version_externally_managed + + return self.distribution.namespace_packages if svem else [] + + @staticmethod + def _gen_exclusion_paths(): + """ + Generate file paths to be excluded for namespace packages (bytecode + cache files). + """ + # always exclude the package module itself + yield '__init__.py' + + yield '__init__.pyc' + yield '__init__.pyo' + + if not hasattr(imp, 'get_tag'): + return + + base = os.path.join('__pycache__', '__init__.' + imp.get_tag()) + yield base + '.pyc' + yield base + '.pyo' + yield base + '.opt-1.pyc' + yield base + '.opt-2.pyc' + + def copy_tree( + self, infile, outfile, + preserve_mode=1, preserve_times=1, preserve_symlinks=0, level=1 + ): + assert preserve_mode and preserve_times and not preserve_symlinks + exclude = self.get_exclusions() + + if not exclude: + return orig.install_lib.copy_tree(self, infile, outfile) + + # Exclude namespace package __init__.py* files from the output + + from setuptools.archive_util import unpack_directory + from distutils import log + + outfiles = [] + + def pf(src, dst): + if dst in exclude: + log.warn("Skipping installation of %s (namespace package)", + dst) + return False + + log.info("copying %s -> %s", src, os.path.dirname(dst)) + outfiles.append(dst) + return dst + + unpack_directory(infile, outfile, pf) + return outfiles + + def get_outputs(self): + outputs = orig.install_lib.get_outputs(self) + exclude = self.get_exclusions() + if exclude: + return [f for f in outputs if f not in exclude] + return outputs diff --git a/lib/setuptools/command/install_scripts.py b/lib/setuptools/command/install_scripts.py new file mode 100644 index 0000000..1623427 --- /dev/null +++ b/lib/setuptools/command/install_scripts.py @@ -0,0 +1,65 @@ +from distutils import log +import distutils.command.install_scripts as orig +import os +import sys + +from pkg_resources import Distribution, PathMetadata, ensure_directory + + +class install_scripts(orig.install_scripts): + """Do normal script install, plus any egg_info wrapper scripts""" + + def initialize_options(self): + orig.install_scripts.initialize_options(self) + self.no_ep = False + + def run(self): + import setuptools.command.easy_install as ei + + self.run_command("egg_info") + if self.distribution.scripts: + orig.install_scripts.run(self) # run first to set up self.outfiles + else: + self.outfiles = [] + if self.no_ep: + # don't install entry point scripts into .egg file! + return + + ei_cmd = self.get_finalized_command("egg_info") + dist = Distribution( + ei_cmd.egg_base, PathMetadata(ei_cmd.egg_base, ei_cmd.egg_info), + ei_cmd.egg_name, ei_cmd.egg_version, + ) + bs_cmd = self.get_finalized_command('build_scripts') + exec_param = getattr(bs_cmd, 'executable', None) + bw_cmd = self.get_finalized_command("bdist_wininst") + is_wininst = getattr(bw_cmd, '_is_running', False) + writer = ei.ScriptWriter + if is_wininst: + exec_param = "python.exe" + writer = ei.WindowsScriptWriter + if exec_param == sys.executable: + # In case the path to the Python executable contains a space, wrap + # it so it's not split up. + exec_param = [exec_param] + # resolve the writer to the environment + writer = writer.best() + cmd = writer.command_spec_class.best().from_param(exec_param) + for args in writer.get_args(dist, cmd.as_header()): + self.write_script(*args) + + def write_script(self, script_name, contents, mode="t", *ignored): + """Write an executable file to the scripts directory""" + from setuptools.command.easy_install import chmod, current_umask + + log.info("Installing %s script to %s", script_name, self.install_dir) + target = os.path.join(self.install_dir, script_name) + self.outfiles.append(target) + + mask = current_umask() + if not self.dry_run: + ensure_directory(target) + f = open(target, "w" + mode) + f.write(contents) + f.close() + chmod(target, 0o777 - mask) diff --git a/lib/setuptools/command/launcher manifest.xml b/lib/setuptools/command/launcher manifest.xml new file mode 100644 index 0000000..5972a96 --- /dev/null +++ b/lib/setuptools/command/launcher manifest.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> + <assemblyIdentity version="1.0.0.0" + processorArchitecture="X86" + name="%(name)s" + type="win32"/> + <!-- Identify the application security requirements. --> + <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> + <security> + <requestedPrivileges> + <requestedExecutionLevel level="asInvoker" uiAccess="false"/> + </requestedPrivileges> + </security> + </trustInfo> +</assembly> diff --git a/lib/setuptools/command/py36compat.py b/lib/setuptools/command/py36compat.py new file mode 100644 index 0000000..61063e7 --- /dev/null +++ b/lib/setuptools/command/py36compat.py @@ -0,0 +1,136 @@ +import os +from glob import glob +from distutils.util import convert_path +from distutils.command import sdist + +from setuptools.extern.six.moves import filter + + +class sdist_add_defaults: + """ + Mix-in providing forward-compatibility for functionality as found in + distutils on Python 3.7. + + Do not edit the code in this class except to update functionality + as implemented in distutils. Instead, override in the subclass. + """ + + def add_defaults(self): + """Add all the default files to self.filelist: + - README or README.txt + - setup.py + - test/test*.py + - all pure Python modules mentioned in setup script + - all files pointed by package_data (build_py) + - all files defined in data_files. + - all files defined as scripts. + - all C sources listed as part of extensions or C libraries + in the setup script (doesn't catch C headers!) + Warns if (README or README.txt) or setup.py are missing; everything + else is optional. + """ + self._add_defaults_standards() + self._add_defaults_optional() + self._add_defaults_python() + self._add_defaults_data_files() + self._add_defaults_ext() + self._add_defaults_c_libs() + self._add_defaults_scripts() + + @staticmethod + def _cs_path_exists(fspath): + """ + Case-sensitive path existence check + + >>> sdist_add_defaults._cs_path_exists(__file__) + True + >>> sdist_add_defaults._cs_path_exists(__file__.upper()) + False + """ + if not os.path.exists(fspath): + return False + # make absolute so we always have a directory + abspath = os.path.abspath(fspath) + directory, filename = os.path.split(abspath) + return filename in os.listdir(directory) + + def _add_defaults_standards(self): + standards = [self.READMES, self.distribution.script_name] + for fn in standards: + if isinstance(fn, tuple): + alts = fn + got_it = False + for fn in alts: + if self._cs_path_exists(fn): + got_it = True + self.filelist.append(fn) + break + + if not got_it: + self.warn("standard file not found: should have one of " + + ', '.join(alts)) + else: + if self._cs_path_exists(fn): + self.filelist.append(fn) + else: + self.warn("standard file '%s' not found" % fn) + + def _add_defaults_optional(self): + optional = ['test/test*.py', 'setup.cfg'] + for pattern in optional: + files = filter(os.path.isfile, glob(pattern)) + self.filelist.extend(files) + + def _add_defaults_python(self): + # build_py is used to get: + # - python modules + # - files defined in package_data + build_py = self.get_finalized_command('build_py') + + # getting python files + if self.distribution.has_pure_modules(): + self.filelist.extend(build_py.get_source_files()) + + # getting package_data files + # (computed in build_py.data_files by build_py.finalize_options) + for pkg, src_dir, build_dir, filenames in build_py.data_files: + for filename in filenames: + self.filelist.append(os.path.join(src_dir, filename)) + + def _add_defaults_data_files(self): + # getting distribution.data_files + if self.distribution.has_data_files(): + for item in self.distribution.data_files: + if isinstance(item, str): + # plain file + item = convert_path(item) + if os.path.isfile(item): + self.filelist.append(item) + else: + # a (dirname, filenames) tuple + dirname, filenames = item + for f in filenames: + f = convert_path(f) + if os.path.isfile(f): + self.filelist.append(f) + + def _add_defaults_ext(self): + if self.distribution.has_ext_modules(): + build_ext = self.get_finalized_command('build_ext') + self.filelist.extend(build_ext.get_source_files()) + + def _add_defaults_c_libs(self): + if self.distribution.has_c_libraries(): + build_clib = self.get_finalized_command('build_clib') + self.filelist.extend(build_clib.get_source_files()) + + def _add_defaults_scripts(self): + if self.distribution.has_scripts(): + build_scripts = self.get_finalized_command('build_scripts') + self.filelist.extend(build_scripts.get_source_files()) + + +if hasattr(sdist.sdist, '_add_defaults_standards'): + # disable the functionality already available upstream + class sdist_add_defaults: + pass diff --git a/lib/setuptools/command/register.py b/lib/setuptools/command/register.py new file mode 100644 index 0000000..98bc015 --- /dev/null +++ b/lib/setuptools/command/register.py @@ -0,0 +1,18 @@ +from distutils import log +import distutils.command.register as orig + + +class register(orig.register): + __doc__ = orig.register.__doc__ + + def run(self): + try: + # Make sure that we are using valid current name/version info + self.run_command('egg_info') + orig.register.run(self) + finally: + self.announce( + "WARNING: Registering is deprecated, use twine to " + "upload instead (https://pypi.org/p/twine/)", + log.WARN + ) diff --git a/lib/setuptools/command/rotate.py b/lib/setuptools/command/rotate.py new file mode 100644 index 0000000..b89353f --- /dev/null +++ b/lib/setuptools/command/rotate.py @@ -0,0 +1,66 @@ +from distutils.util import convert_path +from distutils import log +from distutils.errors import DistutilsOptionError +import os +import shutil + +from setuptools.extern import six + +from setuptools import Command + + +class rotate(Command): + """Delete older distributions""" + + description = "delete older distributions, keeping N newest files" + user_options = [ + ('match=', 'm', "patterns to match (required)"), + ('dist-dir=', 'd', "directory where the distributions are"), + ('keep=', 'k', "number of matching distributions to keep"), + ] + + boolean_options = [] + + def initialize_options(self): + self.match = None + self.dist_dir = None + self.keep = None + + def finalize_options(self): + if self.match is None: + raise DistutilsOptionError( + "Must specify one or more (comma-separated) match patterns " + "(e.g. '.zip' or '.egg')" + ) + if self.keep is None: + raise DistutilsOptionError("Must specify number of files to keep") + try: + self.keep = int(self.keep) + except ValueError: + raise DistutilsOptionError("--keep must be an integer") + if isinstance(self.match, six.string_types): + self.match = [ + convert_path(p.strip()) for p in self.match.split(',') + ] + self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) + + def run(self): + self.run_command("egg_info") + from glob import glob + + for pattern in self.match: + pattern = self.distribution.get_name() + '*' + pattern + files = glob(os.path.join(self.dist_dir, pattern)) + files = [(os.path.getmtime(f), f) for f in files] + files.sort() + files.reverse() + + log.info("%d file(s) matching %s", len(files), pattern) + files = files[self.keep:] + for (t, f) in files: + log.info("Deleting %s", f) + if not self.dry_run: + if os.path.isdir(f): + shutil.rmtree(f) + else: + os.unlink(f) diff --git a/lib/setuptools/command/saveopts.py b/lib/setuptools/command/saveopts.py new file mode 100644 index 0000000..611cec5 --- /dev/null +++ b/lib/setuptools/command/saveopts.py @@ -0,0 +1,22 @@ +from setuptools.command.setopt import edit_config, option_base + + +class saveopts(option_base): + """Save command-line options to a file""" + + description = "save supplied options to setup.cfg or other config file" + + def run(self): + dist = self.distribution + settings = {} + + for cmd in dist.command_options: + + if cmd == 'saveopts': + continue # don't save our own options! + + for opt, (src, val) in dist.get_option_dict(cmd).items(): + if src == "command line": + settings.setdefault(cmd, {})[opt] = val + + edit_config(self.filename, settings, self.dry_run) diff --git a/lib/setuptools/command/sdist.py b/lib/setuptools/command/sdist.py new file mode 100644 index 0000000..bcfae4d --- /dev/null +++ b/lib/setuptools/command/sdist.py @@ -0,0 +1,200 @@ +from distutils import log +import distutils.command.sdist as orig +import os +import sys +import io +import contextlib + +from setuptools.extern import six + +from .py36compat import sdist_add_defaults + +import pkg_resources + +_default_revctrl = list + + +def walk_revctrl(dirname=''): + """Find all files under revision control""" + for ep in pkg_resources.iter_entry_points('setuptools.file_finders'): + for item in ep.load()(dirname): + yield item + + +class sdist(sdist_add_defaults, orig.sdist): + """Smart sdist that finds anything supported by revision control""" + + user_options = [ + ('formats=', None, + "formats for source distribution (comma-separated list)"), + ('keep-temp', 'k', + "keep the distribution tree around after creating " + + "archive file(s)"), + ('dist-dir=', 'd', + "directory to put the source distribution archive(s) in " + "[default: dist]"), + ] + + negative_opt = {} + + README_EXTENSIONS = ['', '.rst', '.txt', '.md'] + READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS) + + def run(self): + self.run_command('egg_info') + ei_cmd = self.get_finalized_command('egg_info') + self.filelist = ei_cmd.filelist + self.filelist.append(os.path.join(ei_cmd.egg_info, 'SOURCES.txt')) + self.check_readme() + + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + self.make_distribution() + + dist_files = getattr(self.distribution, 'dist_files', []) + for file in self.archive_files: + data = ('sdist', '', file) + if data not in dist_files: + dist_files.append(data) + + def initialize_options(self): + orig.sdist.initialize_options(self) + + self._default_to_gztar() + + def _default_to_gztar(self): + # only needed on Python prior to 3.6. + if sys.version_info >= (3, 6, 0, 'beta', 1): + return + self.formats = ['gztar'] + + def make_distribution(self): + """ + Workaround for #516 + """ + with self._remove_os_link(): + orig.sdist.make_distribution(self) + + @staticmethod + @contextlib.contextmanager + def _remove_os_link(): + """ + In a context, remove and restore os.link if it exists + """ + + class NoValue: + pass + + orig_val = getattr(os, 'link', NoValue) + try: + del os.link + except Exception: + pass + try: + yield + finally: + if orig_val is not NoValue: + setattr(os, 'link', orig_val) + + def __read_template_hack(self): + # This grody hack closes the template file (MANIFEST.in) if an + # exception occurs during read_template. + # Doing so prevents an error when easy_install attempts to delete the + # file. + try: + orig.sdist.read_template(self) + except Exception: + _, _, tb = sys.exc_info() + tb.tb_next.tb_frame.f_locals['template'].close() + raise + + # Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle + # has been fixed, so only override the method if we're using an earlier + # Python. + has_leaky_handle = ( + sys.version_info < (2, 7, 2) + or (3, 0) <= sys.version_info < (3, 1, 4) + or (3, 2) <= sys.version_info < (3, 2, 1) + ) + if has_leaky_handle: + read_template = __read_template_hack + + def _add_defaults_python(self): + """getting python files""" + if self.distribution.has_pure_modules(): + build_py = self.get_finalized_command('build_py') + self.filelist.extend(build_py.get_source_files()) + # This functionality is incompatible with include_package_data, and + # will in fact create an infinite recursion if include_package_data + # is True. Use of include_package_data will imply that + # distutils-style automatic handling of package_data is disabled + if not self.distribution.include_package_data: + for _, src_dir, _, filenames in build_py.data_files: + self.filelist.extend([os.path.join(src_dir, filename) + for filename in filenames]) + + def _add_defaults_data_files(self): + try: + if six.PY2: + sdist_add_defaults._add_defaults_data_files(self) + else: + super()._add_defaults_data_files() + except TypeError: + log.warn("data_files contains unexpected objects") + + def check_readme(self): + for f in self.READMES: + if os.path.exists(f): + return + else: + self.warn( + "standard file not found: should have one of " + + ', '.join(self.READMES) + ) + + def make_release_tree(self, base_dir, files): + orig.sdist.make_release_tree(self, base_dir, files) + + # Save any egg_info command line options used to create this sdist + dest = os.path.join(base_dir, 'setup.cfg') + if hasattr(os, 'link') and os.path.exists(dest): + # unlink and re-copy, since it might be hard-linked, and + # we don't want to change the source version + os.unlink(dest) + self.copy_file('setup.cfg', dest) + + self.get_finalized_command('egg_info').save_version_info(dest) + + def _manifest_is_not_generated(self): + # check for special comment used in 2.7.1 and higher + if not os.path.isfile(self.manifest): + return False + + with io.open(self.manifest, 'rb') as fp: + first_line = fp.readline() + return (first_line != + '# file GENERATED by distutils, do NOT edit\n'.encode()) + + def read_manifest(self): + """Read the manifest file (named by 'self.manifest') and use it to + fill in 'self.filelist', the list of files to include in the source + distribution. + """ + log.info("reading manifest file '%s'", self.manifest) + manifest = open(self.manifest, 'rb') + for line in manifest: + # The manifest must contain UTF-8. See #303. + if six.PY3: + try: + line = line.decode('UTF-8') + except UnicodeDecodeError: + log.warn("%r not UTF-8 decodable -- skipping" % line) + continue + # ignore comments and blank lines + line = line.strip() + if line.startswith('#') or not line: + continue + self.filelist.append(line) + manifest.close() diff --git a/lib/setuptools/command/setopt.py b/lib/setuptools/command/setopt.py new file mode 100644 index 0000000..7e57cc0 --- /dev/null +++ b/lib/setuptools/command/setopt.py @@ -0,0 +1,149 @@ +from distutils.util import convert_path +from distutils import log +from distutils.errors import DistutilsOptionError +import distutils +import os + +from setuptools.extern.six.moves import configparser + +from setuptools import Command + +__all__ = ['config_file', 'edit_config', 'option_base', 'setopt'] + + +def config_file(kind="local"): + """Get the filename of the distutils, local, global, or per-user config + + `kind` must be one of "local", "global", or "user" + """ + if kind == 'local': + return 'setup.cfg' + if kind == 'global': + return os.path.join( + os.path.dirname(distutils.__file__), 'distutils.cfg' + ) + if kind == 'user': + dot = os.name == 'posix' and '.' or '' + return os.path.expanduser(convert_path("~/%spydistutils.cfg" % dot)) + raise ValueError( + "config_file() type must be 'local', 'global', or 'user'", kind + ) + + +def edit_config(filename, settings, dry_run=False): + """Edit a configuration file to include `settings` + + `settings` is a dictionary of dictionaries or ``None`` values, keyed by + command/section name. A ``None`` value means to delete the entire section, + while a dictionary lists settings to be changed or deleted in that section. + A setting of ``None`` means to delete that setting. + """ + log.debug("Reading configuration from %s", filename) + opts = configparser.RawConfigParser() + opts.read([filename]) + for section, options in settings.items(): + if options is None: + log.info("Deleting section [%s] from %s", section, filename) + opts.remove_section(section) + else: + if not opts.has_section(section): + log.debug("Adding new section [%s] to %s", section, filename) + opts.add_section(section) + for option, value in options.items(): + if value is None: + log.debug( + "Deleting %s.%s from %s", + section, option, filename + ) + opts.remove_option(section, option) + if not opts.options(section): + log.info("Deleting empty [%s] section from %s", + section, filename) + opts.remove_section(section) + else: + log.debug( + "Setting %s.%s to %r in %s", + section, option, value, filename + ) + opts.set(section, option, value) + + log.info("Writing %s", filename) + if not dry_run: + with open(filename, 'w') as f: + opts.write(f) + + +class option_base(Command): + """Abstract base class for commands that mess with config files""" + + user_options = [ + ('global-config', 'g', + "save options to the site-wide distutils.cfg file"), + ('user-config', 'u', + "save options to the current user's pydistutils.cfg file"), + ('filename=', 'f', + "configuration file to use (default=setup.cfg)"), + ] + + boolean_options = [ + 'global-config', 'user-config', + ] + + def initialize_options(self): + self.global_config = None + self.user_config = None + self.filename = None + + def finalize_options(self): + filenames = [] + if self.global_config: + filenames.append(config_file('global')) + if self.user_config: + filenames.append(config_file('user')) + if self.filename is not None: + filenames.append(self.filename) + if not filenames: + filenames.append(config_file('local')) + if len(filenames) > 1: + raise DistutilsOptionError( + "Must specify only one configuration file option", + filenames + ) + self.filename, = filenames + + +class setopt(option_base): + """Save command-line options to a file""" + + description = "set an option in setup.cfg or another config file" + + user_options = [ + ('command=', 'c', 'command to set an option for'), + ('option=', 'o', 'option to set'), + ('set-value=', 's', 'value of the option'), + ('remove', 'r', 'remove (unset) the value'), + ] + option_base.user_options + + boolean_options = option_base.boolean_options + ['remove'] + + def initialize_options(self): + option_base.initialize_options(self) + self.command = None + self.option = None + self.set_value = None + self.remove = None + + def finalize_options(self): + option_base.finalize_options(self) + if self.command is None or self.option is None: + raise DistutilsOptionError("Must specify --command *and* --option") + if self.set_value is None and not self.remove: + raise DistutilsOptionError("Must specify --set-value or --remove") + + def run(self): + edit_config( + self.filename, { + self.command: {self.option.replace('-', '_'): self.set_value} + }, + self.dry_run + ) diff --git a/lib/setuptools/command/test.py b/lib/setuptools/command/test.py new file mode 100644 index 0000000..dde0118 --- /dev/null +++ b/lib/setuptools/command/test.py @@ -0,0 +1,270 @@ +import os +import operator +import sys +import contextlib +import itertools +import unittest +from distutils.errors import DistutilsError, DistutilsOptionError +from distutils import log +from unittest import TestLoader + +from setuptools.extern import six +from setuptools.extern.six.moves import map, filter + +from pkg_resources import (resource_listdir, resource_exists, normalize_path, + working_set, _namespace_packages, evaluate_marker, + add_activation_listener, require, EntryPoint) +from setuptools import Command + +__metaclass__ = type + + +class ScanningLoader(TestLoader): + + def __init__(self): + TestLoader.__init__(self) + self._visited = set() + + def loadTestsFromModule(self, module, pattern=None): + """Return a suite of all tests cases contained in the given module + + If the module is a package, load tests from all the modules in it. + If the module has an ``additional_tests`` function, call it and add + the return value to the tests. + """ + if module in self._visited: + return None + self._visited.add(module) + + tests = [] + tests.append(TestLoader.loadTestsFromModule(self, module)) + + if hasattr(module, "additional_tests"): + tests.append(module.additional_tests()) + + if hasattr(module, '__path__'): + for file in resource_listdir(module.__name__, ''): + if file.endswith('.py') and file != '__init__.py': + submodule = module.__name__ + '.' + file[:-3] + else: + if resource_exists(module.__name__, file + '/__init__.py'): + submodule = module.__name__ + '.' + file + else: + continue + tests.append(self.loadTestsFromName(submodule)) + + if len(tests) != 1: + return self.suiteClass(tests) + else: + return tests[0] # don't create a nested suite for only one return + + +# adapted from jaraco.classes.properties:NonDataProperty +class NonDataProperty: + def __init__(self, fget): + self.fget = fget + + def __get__(self, obj, objtype=None): + if obj is None: + return self + return self.fget(obj) + + +class test(Command): + """Command to run unit tests after in-place build""" + + description = "run unit tests after in-place build" + + user_options = [ + ('test-module=', 'm', "Run 'test_suite' in specified module"), + ('test-suite=', 's', + "Run single test, case or suite (e.g. 'module.test_suite')"), + ('test-runner=', 'r', "Test runner to use"), + ] + + def initialize_options(self): + self.test_suite = None + self.test_module = None + self.test_loader = None + self.test_runner = None + + def finalize_options(self): + + if self.test_suite and self.test_module: + msg = "You may specify a module or a suite, but not both" + raise DistutilsOptionError(msg) + + if self.test_suite is None: + if self.test_module is None: + self.test_suite = self.distribution.test_suite + else: + self.test_suite = self.test_module + ".test_suite" + + if self.test_loader is None: + self.test_loader = getattr(self.distribution, 'test_loader', None) + if self.test_loader is None: + self.test_loader = "setuptools.command.test:ScanningLoader" + if self.test_runner is None: + self.test_runner = getattr(self.distribution, 'test_runner', None) + + @NonDataProperty + def test_args(self): + return list(self._test_args()) + + def _test_args(self): + if not self.test_suite and sys.version_info >= (2, 7): + yield 'discover' + if self.verbose: + yield '--verbose' + if self.test_suite: + yield self.test_suite + + def with_project_on_sys_path(self, func): + """ + Backward compatibility for project_on_sys_path context. + """ + with self.project_on_sys_path(): + func() + + @contextlib.contextmanager + def project_on_sys_path(self, include_dists=[]): + with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False) + + if with_2to3: + # If we run 2to3 we can not do this inplace: + + # Ensure metadata is up-to-date + self.reinitialize_command('build_py', inplace=0) + self.run_command('build_py') + bpy_cmd = self.get_finalized_command("build_py") + build_path = normalize_path(bpy_cmd.build_lib) + + # Build extensions + self.reinitialize_command('egg_info', egg_base=build_path) + self.run_command('egg_info') + + self.reinitialize_command('build_ext', inplace=0) + self.run_command('build_ext') + else: + # Without 2to3 inplace works fine: + self.run_command('egg_info') + + # Build extensions in-place + self.reinitialize_command('build_ext', inplace=1) + self.run_command('build_ext') + + ei_cmd = self.get_finalized_command("egg_info") + + old_path = sys.path[:] + old_modules = sys.modules.copy() + + try: + project_path = normalize_path(ei_cmd.egg_base) + sys.path.insert(0, project_path) + working_set.__init__() + add_activation_listener(lambda dist: dist.activate()) + require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version)) + with self.paths_on_pythonpath([project_path]): + yield + finally: + sys.path[:] = old_path + sys.modules.clear() + sys.modules.update(old_modules) + working_set.__init__() + + @staticmethod + @contextlib.contextmanager + def paths_on_pythonpath(paths): + """ + Add the indicated paths to the head of the PYTHONPATH environment + variable so that subprocesses will also see the packages at + these paths. + + Do this in a context that restores the value on exit. + """ + nothing = object() + orig_pythonpath = os.environ.get('PYTHONPATH', nothing) + current_pythonpath = os.environ.get('PYTHONPATH', '') + try: + prefix = os.pathsep.join(paths) + to_join = filter(None, [prefix, current_pythonpath]) + new_path = os.pathsep.join(to_join) + if new_path: + os.environ['PYTHONPATH'] = new_path + yield + finally: + if orig_pythonpath is nothing: + os.environ.pop('PYTHONPATH', None) + else: + os.environ['PYTHONPATH'] = orig_pythonpath + + @staticmethod + def install_dists(dist): + """ + Install the requirements indicated by self.distribution and + return an iterable of the dists that were built. + """ + ir_d = dist.fetch_build_eggs(dist.install_requires) + tr_d = dist.fetch_build_eggs(dist.tests_require or []) + er_d = dist.fetch_build_eggs( + v for k, v in dist.extras_require.items() + if k.startswith(':') and evaluate_marker(k[1:]) + ) + return itertools.chain(ir_d, tr_d, er_d) + + def run(self): + installed_dists = self.install_dists(self.distribution) + + cmd = ' '.join(self._argv) + if self.dry_run: + self.announce('skipping "%s" (dry run)' % cmd) + return + + self.announce('running "%s"' % cmd) + + paths = map(operator.attrgetter('location'), installed_dists) + with self.paths_on_pythonpath(paths): + with self.project_on_sys_path(): + self.run_tests() + + def run_tests(self): + # Purge modules under test from sys.modules. The test loader will + # re-import them from the build location. Required when 2to3 is used + # with namespace packages. + if six.PY3 and getattr(self.distribution, 'use_2to3', False): + module = self.test_suite.split('.')[0] + if module in _namespace_packages: + del_modules = [] + if module in sys.modules: + del_modules.append(module) + module += '.' + for name in sys.modules: + if name.startswith(module): + del_modules.append(name) + list(map(sys.modules.__delitem__, del_modules)) + + test = unittest.main( + None, None, self._argv, + testLoader=self._resolve_as_ep(self.test_loader), + testRunner=self._resolve_as_ep(self.test_runner), + exit=False, + ) + if not test.result.wasSuccessful(): + msg = 'Test failed: %s' % test.result + self.announce(msg, log.ERROR) + raise DistutilsError(msg) + + @property + def _argv(self): + return ['unittest'] + self.test_args + + @staticmethod + def _resolve_as_ep(val): + """ + Load the indicated attribute value, called, as a as if it were + specified as an entry point. + """ + if val is None: + return + parsed = EntryPoint.parse("x=" + val) + return parsed.resolve()() diff --git a/lib/setuptools/command/upload.py b/lib/setuptools/command/upload.py new file mode 100644 index 0000000..dd17f7a --- /dev/null +++ b/lib/setuptools/command/upload.py @@ -0,0 +1,196 @@ +import io +import os +import hashlib +import getpass +import platform + +from base64 import standard_b64encode + +from distutils import log +from distutils.command import upload as orig +from distutils.spawn import spawn + +from distutils.errors import DistutilsError + +from setuptools.extern.six.moves.urllib.request import urlopen, Request +from setuptools.extern.six.moves.urllib.error import HTTPError +from setuptools.extern.six.moves.urllib.parse import urlparse + +class upload(orig.upload): + """ + Override default upload behavior to obtain password + in a variety of different ways. + """ + def run(self): + try: + orig.upload.run(self) + finally: + self.announce( + "WARNING: Uploading via this command is deprecated, use twine " + "to upload instead (https://pypi.org/p/twine/)", + log.WARN + ) + + def finalize_options(self): + orig.upload.finalize_options(self) + self.username = ( + self.username or + getpass.getuser() + ) + # Attempt to obtain password. Short circuit evaluation at the first + # sign of success. + self.password = ( + self.password or + self._load_password_from_keyring() or + self._prompt_for_password() + ) + + def upload_file(self, command, pyversion, filename): + # Makes sure the repository URL is compliant + schema, netloc, url, params, query, fragments = \ + urlparse(self.repository) + if params or query or fragments: + raise AssertionError("Incompatible url %s" % self.repository) + + if schema not in ('http', 'https'): + raise AssertionError("unsupported schema " + schema) + + # Sign if requested + if self.sign: + gpg_args = ["gpg", "--detach-sign", "-a", filename] + if self.identity: + gpg_args[2:2] = ["--local-user", self.identity] + spawn(gpg_args, + dry_run=self.dry_run) + + # Fill in the data - send all the meta-data in case we need to + # register a new release + with open(filename, 'rb') as f: + content = f.read() + + meta = self.distribution.metadata + + data = { + # action + ':action': 'file_upload', + 'protocol_version': '1', + + # identify release + 'name': meta.get_name(), + 'version': meta.get_version(), + + # file content + 'content': (os.path.basename(filename),content), + 'filetype': command, + 'pyversion': pyversion, + 'md5_digest': hashlib.md5(content).hexdigest(), + + # additional meta-data + 'metadata_version': str(meta.get_metadata_version()), + 'summary': meta.get_description(), + 'home_page': meta.get_url(), + 'author': meta.get_contact(), + 'author_email': meta.get_contact_email(), + 'license': meta.get_licence(), + 'description': meta.get_long_description(), + 'keywords': meta.get_keywords(), + 'platform': meta.get_platforms(), + 'classifiers': meta.get_classifiers(), + 'download_url': meta.get_download_url(), + # PEP 314 + 'provides': meta.get_provides(), + 'requires': meta.get_requires(), + 'obsoletes': meta.get_obsoletes(), + } + + data['comment'] = '' + + if self.sign: + data['gpg_signature'] = (os.path.basename(filename) + ".asc", + open(filename+".asc", "rb").read()) + + # set up the authentication + user_pass = (self.username + ":" + self.password).encode('ascii') + # The exact encoding of the authentication string is debated. + # Anyway PyPI only accepts ascii for both username or password. + auth = "Basic " + standard_b64encode(user_pass).decode('ascii') + + # Build up the MIME payload for the POST data + boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b'\r\n--' + boundary.encode('ascii') + end_boundary = sep_boundary + b'--\r\n' + body = io.BytesIO() + for key, value in data.items(): + title = '\r\nContent-Disposition: form-data; name="%s"' % key + # handle multiple entries for the same name + if not isinstance(value, list): + value = [value] + for value in value: + if type(value) is tuple: + title += '; filename="%s"' % value[0] + value = value[1] + else: + value = str(value).encode('utf-8') + body.write(sep_boundary) + body.write(title.encode('utf-8')) + body.write(b"\r\n\r\n") + body.write(value) + body.write(end_boundary) + body = body.getvalue() + + msg = "Submitting %s to %s" % (filename, self.repository) + self.announce(msg, log.INFO) + + # build the Request + headers = { + 'Content-type': 'multipart/form-data; boundary=%s' % boundary, + 'Content-length': str(len(body)), + 'Authorization': auth, + } + + request = Request(self.repository, data=body, + headers=headers) + # send the data + try: + result = urlopen(request) + status = result.getcode() + reason = result.msg + except HTTPError as e: + status = e.code + reason = e.msg + except OSError as e: + self.announce(str(e), log.ERROR) + raise + + if status == 200: + self.announce('Server response (%s): %s' % (status, reason), + log.INFO) + if self.show_response: + text = getattr(self, '_read_pypi_response', + lambda x: None)(result) + if text is not None: + msg = '\n'.join(('-' * 75, text, '-' * 75)) + self.announce(msg, log.INFO) + else: + msg = 'Upload failed (%s): %s' % (status, reason) + self.announce(msg, log.ERROR) + raise DistutilsError(msg) + + def _load_password_from_keyring(self): + """ + Attempt to load password from keyring. Suppress Exceptions. + """ + try: + keyring = __import__('keyring') + return keyring.get_password(self.repository, self.username) + except Exception: + pass + + def _prompt_for_password(self): + """ + Prompt for a password on the tty. Suppress Exceptions. + """ + try: + return getpass.getpass() + except (Exception, KeyboardInterrupt): + pass diff --git a/lib/setuptools/command/upload_docs.py b/lib/setuptools/command/upload_docs.py new file mode 100644 index 0000000..07aa564 --- /dev/null +++ b/lib/setuptools/command/upload_docs.py @@ -0,0 +1,206 @@ +# -*- coding: utf-8 -*- +"""upload_docs + +Implements a Distutils 'upload_docs' subcommand (upload documentation to +PyPI's pythonhosted.org). +""" + +from base64 import standard_b64encode +from distutils import log +from distutils.errors import DistutilsOptionError +import os +import socket +import zipfile +import tempfile +import shutil +import itertools +import functools + +from setuptools.extern import six +from setuptools.extern.six.moves import http_client, urllib + +from pkg_resources import iter_entry_points +from .upload import upload + + +def _encode(s): + errors = 'surrogateescape' if six.PY3 else 'strict' + return s.encode('utf-8', errors) + + +class upload_docs(upload): + # override the default repository as upload_docs isn't + # supported by Warehouse (and won't be). + DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/' + + description = 'Upload documentation to PyPI' + + user_options = [ + ('repository=', 'r', + "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY), + ('show-response', None, + 'display full response text from server'), + ('upload-dir=', None, 'directory to upload'), + ] + boolean_options = upload.boolean_options + + def has_sphinx(self): + if self.upload_dir is None: + for ep in iter_entry_points('distutils.commands', 'build_sphinx'): + return True + + sub_commands = [('build_sphinx', has_sphinx)] + + def initialize_options(self): + upload.initialize_options(self) + self.upload_dir = None + self.target_dir = None + + def finalize_options(self): + upload.finalize_options(self) + if self.upload_dir is None: + if self.has_sphinx(): + build_sphinx = self.get_finalized_command('build_sphinx') + self.target_dir = build_sphinx.builder_target_dir + else: + build = self.get_finalized_command('build') + self.target_dir = os.path.join(build.build_base, 'docs') + else: + self.ensure_dirname('upload_dir') + self.target_dir = self.upload_dir + if 'pypi.python.org' in self.repository: + log.warn("Upload_docs command is deprecated. Use RTD instead.") + self.announce('Using upload directory %s' % self.target_dir) + + def create_zipfile(self, filename): + zip_file = zipfile.ZipFile(filename, "w") + try: + self.mkpath(self.target_dir) # just in case + for root, dirs, files in os.walk(self.target_dir): + if root == self.target_dir and not files: + tmpl = "no files found in upload directory '%s'" + raise DistutilsOptionError(tmpl % self.target_dir) + for name in files: + full = os.path.join(root, name) + relative = root[len(self.target_dir):].lstrip(os.path.sep) + dest = os.path.join(relative, name) + zip_file.write(full, dest) + finally: + zip_file.close() + + def run(self): + # Run sub commands + for cmd_name in self.get_sub_commands(): + self.run_command(cmd_name) + + tmp_dir = tempfile.mkdtemp() + name = self.distribution.metadata.get_name() + zip_file = os.path.join(tmp_dir, "%s.zip" % name) + try: + self.create_zipfile(zip_file) + self.upload_file(zip_file) + finally: + shutil.rmtree(tmp_dir) + + @staticmethod + def _build_part(item, sep_boundary): + key, values = item + title = '\nContent-Disposition: form-data; name="%s"' % key + # handle multiple entries for the same name + if not isinstance(values, list): + values = [values] + for value in values: + if isinstance(value, tuple): + title += '; filename="%s"' % value[0] + value = value[1] + else: + value = _encode(value) + yield sep_boundary + yield _encode(title) + yield b"\n\n" + yield value + if value and value[-1:] == b'\r': + yield b'\n' # write an extra newline (lurve Macs) + + @classmethod + def _build_multipart(cls, data): + """ + Build up the MIME payload for the POST data + """ + boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' + sep_boundary = b'\n--' + boundary + end_boundary = sep_boundary + b'--' + end_items = end_boundary, b"\n", + builder = functools.partial( + cls._build_part, + sep_boundary=sep_boundary, + ) + part_groups = map(builder, data.items()) + parts = itertools.chain.from_iterable(part_groups) + body_items = itertools.chain(parts, end_items) + content_type = 'multipart/form-data; boundary=%s' % boundary.decode('ascii') + return b''.join(body_items), content_type + + def upload_file(self, filename): + with open(filename, 'rb') as f: + content = f.read() + meta = self.distribution.metadata + data = { + ':action': 'doc_upload', + 'name': meta.get_name(), + 'content': (os.path.basename(filename), content), + } + # set up the authentication + credentials = _encode(self.username + ':' + self.password) + credentials = standard_b64encode(credentials) + if six.PY3: + credentials = credentials.decode('ascii') + auth = "Basic " + credentials + + body, ct = self._build_multipart(data) + + msg = "Submitting documentation to %s" % (self.repository) + self.announce(msg, log.INFO) + + # build the Request + # We can't use urllib2 since we need to send the Basic + # auth right with the first request + schema, netloc, url, params, query, fragments = \ + urllib.parse.urlparse(self.repository) + assert not params and not query and not fragments + if schema == 'http': + conn = http_client.HTTPConnection(netloc) + elif schema == 'https': + conn = http_client.HTTPSConnection(netloc) + else: + raise AssertionError("unsupported schema " + schema) + + data = '' + try: + conn.connect() + conn.putrequest("POST", url) + content_type = ct + conn.putheader('Content-type', content_type) + conn.putheader('Content-length', str(len(body))) + conn.putheader('Authorization', auth) + conn.endheaders() + conn.send(body) + except socket.error as e: + self.announce(str(e), log.ERROR) + return + + r = conn.getresponse() + if r.status == 200: + msg = 'Server response (%s): %s' % (r.status, r.reason) + self.announce(msg, log.INFO) + elif r.status == 301: + location = r.getheader('Location') + if location is None: + location = 'https://pythonhosted.org/%s/' % meta.get_name() + msg = 'Upload successful. Visit %s' % location + self.announce(msg, log.INFO) + else: + msg = 'Upload failed (%s): %s' % (r.status, r.reason) + self.announce(msg, log.ERROR) + if self.show_response: + print('-' * 75, r.read(), '-' * 75) diff --git a/lib/setuptools/config.py b/lib/setuptools/config.py new file mode 100644 index 0000000..d1ac673 --- /dev/null +++ b/lib/setuptools/config.py @@ -0,0 +1,635 @@ +from __future__ import absolute_import, unicode_literals +import io +import os +import sys + +import warnings +import functools +from collections import defaultdict +from functools import partial +from functools import wraps +from importlib import import_module + +from distutils.errors import DistutilsOptionError, DistutilsFileError +from setuptools.extern.packaging.version import LegacyVersion, parse +from setuptools.extern.six import string_types, PY3 + + +__metaclass__ = type + + +def read_configuration( + filepath, find_others=False, ignore_option_errors=False): + """Read given configuration file and returns options from it as a dict. + + :param str|unicode filepath: Path to configuration file + to get options from. + + :param bool find_others: Whether to search for other configuration files + which could be on in various places. + + :param bool ignore_option_errors: Whether to silently ignore + options, values of which could not be resolved (e.g. due to exceptions + in directives such as file:, attr:, etc.). + If False exceptions are propagated as expected. + + :rtype: dict + """ + from setuptools.dist import Distribution, _Distribution + + filepath = os.path.abspath(filepath) + + if not os.path.isfile(filepath): + raise DistutilsFileError( + 'Configuration file %s does not exist.' % filepath) + + current_directory = os.getcwd() + os.chdir(os.path.dirname(filepath)) + + try: + dist = Distribution() + + filenames = dist.find_config_files() if find_others else [] + if filepath not in filenames: + filenames.append(filepath) + + _Distribution.parse_config_files(dist, filenames=filenames) + + handlers = parse_configuration( + dist, dist.command_options, + ignore_option_errors=ignore_option_errors) + + finally: + os.chdir(current_directory) + + return configuration_to_dict(handlers) + + +def _get_option(target_obj, key): + """ + Given a target object and option key, get that option from + the target object, either through a get_{key} method or + from an attribute directly. + """ + getter_name = 'get_{key}'.format(**locals()) + by_attribute = functools.partial(getattr, target_obj, key) + getter = getattr(target_obj, getter_name, by_attribute) + return getter() + + +def configuration_to_dict(handlers): + """Returns configuration data gathered by given handlers as a dict. + + :param list[ConfigHandler] handlers: Handlers list, + usually from parse_configuration() + + :rtype: dict + """ + config_dict = defaultdict(dict) + + for handler in handlers: + for option in handler.set_options: + value = _get_option(handler.target_obj, option) + config_dict[handler.section_prefix][option] = value + + return config_dict + + +def parse_configuration( + distribution, command_options, ignore_option_errors=False): + """Performs additional parsing of configuration options + for a distribution. + + Returns a list of used option handlers. + + :param Distribution distribution: + :param dict command_options: + :param bool ignore_option_errors: Whether to silently ignore + options, values of which could not be resolved (e.g. due to exceptions + in directives such as file:, attr:, etc.). + If False exceptions are propagated as expected. + :rtype: list + """ + options = ConfigOptionsHandler( + distribution, command_options, ignore_option_errors) + options.parse() + + meta = ConfigMetadataHandler( + distribution.metadata, command_options, ignore_option_errors, + distribution.package_dir) + meta.parse() + + return meta, options + + +class ConfigHandler: + """Handles metadata supplied in configuration files.""" + + section_prefix = None + """Prefix for config sections handled by this handler. + Must be provided by class heirs. + + """ + + aliases = {} + """Options aliases. + For compatibility with various packages. E.g.: d2to1 and pbr. + Note: `-` in keys is replaced with `_` by config parser. + + """ + + def __init__(self, target_obj, options, ignore_option_errors=False): + sections = {} + + section_prefix = self.section_prefix + for section_name, section_options in options.items(): + if not section_name.startswith(section_prefix): + continue + + section_name = section_name.replace(section_prefix, '').strip('.') + sections[section_name] = section_options + + self.ignore_option_errors = ignore_option_errors + self.target_obj = target_obj + self.sections = sections + self.set_options = [] + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + raise NotImplementedError( + '%s must provide .parsers property' % self.__class__.__name__) + + def __setitem__(self, option_name, value): + unknown = tuple() + target_obj = self.target_obj + + # Translate alias into real name. + option_name = self.aliases.get(option_name, option_name) + + current_value = getattr(target_obj, option_name, unknown) + + if current_value is unknown: + raise KeyError(option_name) + + if current_value: + # Already inhabited. Skipping. + return + + skip_option = False + parser = self.parsers.get(option_name) + if parser: + try: + value = parser(value) + + except Exception: + skip_option = True + if not self.ignore_option_errors: + raise + + if skip_option: + return + + setter = getattr(target_obj, 'set_%s' % option_name, None) + if setter is None: + setattr(target_obj, option_name, value) + else: + setter(value) + + self.set_options.append(option_name) + + @classmethod + def _parse_list(cls, value, separator=','): + """Represents value as a list. + + Value is split either by separator (defaults to comma) or by lines. + + :param value: + :param separator: List items separator character. + :rtype: list + """ + if isinstance(value, list): # _get_parser_compound case + return value + + if '\n' in value: + value = value.splitlines() + else: + value = value.split(separator) + + return [chunk.strip() for chunk in value if chunk.strip()] + + @classmethod + def _parse_dict(cls, value): + """Represents value as a dict. + + :param value: + :rtype: dict + """ + separator = '=' + result = {} + for line in cls._parse_list(value): + key, sep, val = line.partition(separator) + if sep != separator: + raise DistutilsOptionError( + 'Unable to parse option value to dict: %s' % value) + result[key.strip()] = val.strip() + + return result + + @classmethod + def _parse_bool(cls, value): + """Represents value as boolean. + + :param value: + :rtype: bool + """ + value = value.lower() + return value in ('1', 'true', 'yes') + + @classmethod + def _parse_file(cls, value): + """Represents value as a string, allowing including text + from nearest files using `file:` directive. + + Directive is sandboxed and won't reach anything outside + directory with setup.py. + + Examples: + file: LICENSE + file: README.rst, CHANGELOG.md, src/file.txt + + :param str value: + :rtype: str + """ + include_directive = 'file:' + + if not isinstance(value, string_types): + return value + + if not value.startswith(include_directive): + return value + + spec = value[len(include_directive):] + filepaths = (os.path.abspath(path.strip()) for path in spec.split(',')) + return '\n'.join( + cls._read_file(path) + for path in filepaths + if (cls._assert_local(path) or True) + and os.path.isfile(path) + ) + + @staticmethod + def _assert_local(filepath): + if not filepath.startswith(os.getcwd()): + raise DistutilsOptionError( + '`file:` directive can not access %s' % filepath) + + @staticmethod + def _read_file(filepath): + with io.open(filepath, encoding='utf-8') as f: + return f.read() + + @classmethod + def _parse_attr(cls, value, package_dir=None): + """Represents value as a module attribute. + + Examples: + attr: package.attr + attr: package.module.attr + + :param str value: + :rtype: str + """ + attr_directive = 'attr:' + if not value.startswith(attr_directive): + return value + + attrs_path = value.replace(attr_directive, '').strip().split('.') + attr_name = attrs_path.pop() + + module_name = '.'.join(attrs_path) + module_name = module_name or '__init__' + + parent_path = os.getcwd() + if package_dir: + if attrs_path[0] in package_dir: + # A custom path was specified for the module we want to import + custom_path = package_dir[attrs_path[0]] + parts = custom_path.rsplit('/', 1) + if len(parts) > 1: + parent_path = os.path.join(os.getcwd(), parts[0]) + module_name = parts[1] + else: + module_name = custom_path + elif '' in package_dir: + # A custom parent directory was specified for all root modules + parent_path = os.path.join(os.getcwd(), package_dir['']) + sys.path.insert(0, parent_path) + try: + module = import_module(module_name) + value = getattr(module, attr_name) + + finally: + sys.path = sys.path[1:] + + return value + + @classmethod + def _get_parser_compound(cls, *parse_methods): + """Returns parser function to represents value as a list. + + Parses a value applying given methods one after another. + + :param parse_methods: + :rtype: callable + """ + def parse(value): + parsed = value + + for method in parse_methods: + parsed = method(parsed) + + return parsed + + return parse + + @classmethod + def _parse_section_to_dict(cls, section_options, values_parser=None): + """Parses section options into a dictionary. + + Optionally applies a given parser to values. + + :param dict section_options: + :param callable values_parser: + :rtype: dict + """ + value = {} + values_parser = values_parser or (lambda val: val) + for key, (_, val) in section_options.items(): + value[key] = values_parser(val) + return value + + def parse_section(self, section_options): + """Parses configuration file section. + + :param dict section_options: + """ + for (name, (_, value)) in section_options.items(): + try: + self[name] = value + + except KeyError: + pass # Keep silent for a new option may appear anytime. + + def parse(self): + """Parses configuration file items from one + or more related sections. + + """ + for section_name, section_options in self.sections.items(): + + method_postfix = '' + if section_name: # [section.option] variant + method_postfix = '_%s' % section_name + + section_parser_method = getattr( + self, + # Dots in section names are tranlsated into dunderscores. + ('parse_section%s' % method_postfix).replace('.', '__'), + None) + + if section_parser_method is None: + raise DistutilsOptionError( + 'Unsupported distribution option section: [%s.%s]' % ( + self.section_prefix, section_name)) + + section_parser_method(section_options) + + def _deprecated_config_handler(self, func, msg, warning_class): + """ this function will wrap around parameters that are deprecated + + :param msg: deprecation message + :param warning_class: class of warning exception to be raised + :param func: function to be wrapped around + """ + @wraps(func) + def config_handler(*args, **kwargs): + warnings.warn(msg, warning_class) + return func(*args, **kwargs) + + return config_handler + + +class ConfigMetadataHandler(ConfigHandler): + + section_prefix = 'metadata' + + aliases = { + 'home_page': 'url', + 'summary': 'description', + 'classifier': 'classifiers', + 'platform': 'platforms', + } + + strict_mode = False + """We need to keep it loose, to be partially compatible with + `pbr` and `d2to1` packages which also uses `metadata` section. + + """ + + def __init__(self, target_obj, options, ignore_option_errors=False, + package_dir=None): + super(ConfigMetadataHandler, self).__init__(target_obj, options, + ignore_option_errors) + self.package_dir = package_dir + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + parse_list = self._parse_list + parse_file = self._parse_file + parse_dict = self._parse_dict + + return { + 'platforms': parse_list, + 'keywords': parse_list, + 'provides': parse_list, + 'requires': self._deprecated_config_handler(parse_list, + "The requires parameter is deprecated, please use " + + "install_requires for runtime dependencies.", + DeprecationWarning), + 'obsoletes': parse_list, + 'classifiers': self._get_parser_compound(parse_file, parse_list), + 'license': parse_file, + 'description': parse_file, + 'long_description': parse_file, + 'version': self._parse_version, + 'project_urls': parse_dict, + } + + def _parse_version(self, value): + """Parses `version` option value. + + :param value: + :rtype: str + + """ + version = self._parse_file(value) + + if version != value: + version = version.strip() + # Be strict about versions loaded from file because it's easy to + # accidentally include newlines and other unintended content + if isinstance(parse(version), LegacyVersion): + tmpl = ( + 'Version loaded from {value} does not ' + 'comply with PEP 440: {version}' + ) + raise DistutilsOptionError(tmpl.format(**locals())) + + return version + + version = self._parse_attr(value, self.package_dir) + + if callable(version): + version = version() + + if not isinstance(version, string_types): + if hasattr(version, '__iter__'): + version = '.'.join(map(str, version)) + else: + version = '%s' % version + + return version + + +class ConfigOptionsHandler(ConfigHandler): + + section_prefix = 'options' + + @property + def parsers(self): + """Metadata item name to parser function mapping.""" + parse_list = self._parse_list + parse_list_semicolon = partial(self._parse_list, separator=';') + parse_bool = self._parse_bool + parse_dict = self._parse_dict + + return { + 'zip_safe': parse_bool, + 'use_2to3': parse_bool, + 'include_package_data': parse_bool, + 'package_dir': parse_dict, + 'use_2to3_fixers': parse_list, + 'use_2to3_exclude_fixers': parse_list, + 'convert_2to3_doctests': parse_list, + 'scripts': parse_list, + 'eager_resources': parse_list, + 'dependency_links': parse_list, + 'namespace_packages': parse_list, + 'install_requires': parse_list_semicolon, + 'setup_requires': parse_list_semicolon, + 'tests_require': parse_list_semicolon, + 'packages': self._parse_packages, + 'entry_points': self._parse_file, + 'py_modules': parse_list, + } + + def _parse_packages(self, value): + """Parses `packages` option value. + + :param value: + :rtype: list + """ + find_directives = ['find:', 'find_namespace:'] + trimmed_value = value.strip() + + if trimmed_value not in find_directives: + return self._parse_list(value) + + findns = trimmed_value == find_directives[1] + if findns and not PY3: + raise DistutilsOptionError( + 'find_namespace: directive is unsupported on Python < 3.3') + + # Read function arguments from a dedicated section. + find_kwargs = self.parse_section_packages__find( + self.sections.get('packages.find', {})) + + if findns: + from setuptools import find_namespace_packages as find_packages + else: + from setuptools import find_packages + + return find_packages(**find_kwargs) + + def parse_section_packages__find(self, section_options): + """Parses `packages.find` configuration file section. + + To be used in conjunction with _parse_packages(). + + :param dict section_options: + """ + section_data = self._parse_section_to_dict( + section_options, self._parse_list) + + valid_keys = ['where', 'include', 'exclude'] + + find_kwargs = dict( + [(k, v) for k, v in section_data.items() if k in valid_keys and v]) + + where = find_kwargs.get('where') + if where is not None: + find_kwargs['where'] = where[0] # cast list to single val + + return find_kwargs + + def parse_section_entry_points(self, section_options): + """Parses `entry_points` configuration file section. + + :param dict section_options: + """ + parsed = self._parse_section_to_dict(section_options, self._parse_list) + self['entry_points'] = parsed + + def _parse_package_data(self, section_options): + parsed = self._parse_section_to_dict(section_options, self._parse_list) + + root = parsed.get('*') + if root: + parsed[''] = root + del parsed['*'] + + return parsed + + def parse_section_package_data(self, section_options): + """Parses `package_data` configuration file section. + + :param dict section_options: + """ + self['package_data'] = self._parse_package_data(section_options) + + def parse_section_exclude_package_data(self, section_options): + """Parses `exclude_package_data` configuration file section. + + :param dict section_options: + """ + self['exclude_package_data'] = self._parse_package_data( + section_options) + + def parse_section_extras_require(self, section_options): + """Parses `extras_require` configuration file section. + + :param dict section_options: + """ + parse_list = partial(self._parse_list, separator=';') + self['extras_require'] = self._parse_section_to_dict( + section_options, parse_list) + + def parse_section_data_files(self, section_options): + """Parses `data_files` configuration file section. + + :param dict section_options: + """ + parsed = self._parse_section_to_dict(section_options, self._parse_list) + self['data_files'] = [(k, v) for k, v in parsed.items()] diff --git a/lib/setuptools/dep_util.py b/lib/setuptools/dep_util.py new file mode 100644 index 0000000..2931c13 --- /dev/null +++ b/lib/setuptools/dep_util.py @@ -0,0 +1,23 @@ +from distutils.dep_util import newer_group + +# yes, this is was almost entirely copy-pasted from +# 'newer_pairwise()', this is just another convenience +# function. +def newer_pairwise_group(sources_groups, targets): + """Walk both arguments in parallel, testing if each source group is newer + than its corresponding target. Returns a pair of lists (sources_groups, + targets) where sources is newer than target, according to the semantics + of 'newer_group()'. + """ + if len(sources_groups) != len(targets): + raise ValueError("'sources_group' and 'targets' must be the same length") + + # build a pair of lists (sources_groups, targets) where source is newer + n_sources = [] + n_targets = [] + for i in range(len(sources_groups)): + if newer_group(sources_groups[i], targets[i]): + n_sources.append(sources_groups[i]) + n_targets.append(targets[i]) + + return n_sources, n_targets diff --git a/lib/setuptools/depends.py b/lib/setuptools/depends.py new file mode 100644 index 0000000..45e7052 --- /dev/null +++ b/lib/setuptools/depends.py @@ -0,0 +1,186 @@ +import sys +import imp +import marshal +from distutils.version import StrictVersion +from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN + +from .py33compat import Bytecode + + +__all__ = [ + 'Require', 'find_module', 'get_module_constant', 'extract_constant' +] + + +class Require: + """A prerequisite to building or installing a distribution""" + + def __init__(self, name, requested_version, module, homepage='', + attribute=None, format=None): + + if format is None and requested_version is not None: + format = StrictVersion + + if format is not None: + requested_version = format(requested_version) + if attribute is None: + attribute = '__version__' + + self.__dict__.update(locals()) + del self.self + + def full_name(self): + """Return full package/distribution name, w/version""" + if self.requested_version is not None: + return '%s-%s' % (self.name, self.requested_version) + return self.name + + def version_ok(self, version): + """Is 'version' sufficiently up-to-date?""" + return self.attribute is None or self.format is None or \ + str(version) != "unknown" and version >= self.requested_version + + def get_version(self, paths=None, default="unknown"): + """Get version number of installed module, 'None', or 'default' + + Search 'paths' for module. If not found, return 'None'. If found, + return the extracted version attribute, or 'default' if no version + attribute was specified, or the value cannot be determined without + importing the module. The version is formatted according to the + requirement's version format (if any), unless it is 'None' or the + supplied 'default'. + """ + + if self.attribute is None: + try: + f, p, i = find_module(self.module, paths) + if f: + f.close() + return default + except ImportError: + return None + + v = get_module_constant(self.module, self.attribute, default, paths) + + if v is not None and v is not default and self.format is not None: + return self.format(v) + + return v + + def is_present(self, paths=None): + """Return true if dependency is present on 'paths'""" + return self.get_version(paths) is not None + + def is_current(self, paths=None): + """Return true if dependency is present and up-to-date on 'paths'""" + version = self.get_version(paths) + if version is None: + return False + return self.version_ok(version) + + +def find_module(module, paths=None): + """Just like 'imp.find_module()', but with package support""" + + parts = module.split('.') + + while parts: + part = parts.pop(0) + f, path, (suffix, mode, kind) = info = imp.find_module(part, paths) + + if kind == PKG_DIRECTORY: + parts = parts or ['__init__'] + paths = [path] + + elif parts: + raise ImportError("Can't find %r in %s" % (parts, module)) + + return info + + +def get_module_constant(module, symbol, default=-1, paths=None): + """Find 'module' by searching 'paths', and extract 'symbol' + + Return 'None' if 'module' does not exist on 'paths', or it does not define + 'symbol'. If the module defines 'symbol' as a constant, return the + constant. Otherwise, return 'default'.""" + + try: + f, path, (suffix, mode, kind) = find_module(module, paths) + except ImportError: + # Module doesn't exist + return None + + try: + if kind == PY_COMPILED: + f.read(8) # skip magic & date + code = marshal.load(f) + elif kind == PY_FROZEN: + code = imp.get_frozen_object(module) + elif kind == PY_SOURCE: + code = compile(f.read(), path, 'exec') + else: + # Not something we can parse; we'll have to import it. :( + if module not in sys.modules: + imp.load_module(module, f, path, (suffix, mode, kind)) + return getattr(sys.modules[module], symbol, None) + + finally: + if f: + f.close() + + return extract_constant(code, symbol, default) + + +def extract_constant(code, symbol, default=-1): + """Extract the constant value of 'symbol' from 'code' + + If the name 'symbol' is bound to a constant value by the Python code + object 'code', return that value. If 'symbol' is bound to an expression, + return 'default'. Otherwise, return 'None'. + + Return value is based on the first assignment to 'symbol'. 'symbol' must + be a global, or at least a non-"fast" local in the code block. That is, + only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol' + must be present in 'code.co_names'. + """ + if symbol not in code.co_names: + # name's not there, can't possibly be an assignment + return None + + name_idx = list(code.co_names).index(symbol) + + STORE_NAME = 90 + STORE_GLOBAL = 97 + LOAD_CONST = 100 + + const = default + + for byte_code in Bytecode(code): + op = byte_code.opcode + arg = byte_code.arg + + if op == LOAD_CONST: + const = code.co_consts[arg] + elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL): + return const + else: + const = default + + +def _update_globals(): + """ + Patch the globals to remove the objects not available on some platforms. + + XXX it'd be better to test assertions about bytecode instead. + """ + + if not sys.platform.startswith('java') and sys.platform != 'cli': + return + incompatible = 'extract_constant', 'get_module_constant' + for name in incompatible: + del globals()[name] + __all__.remove(name) + + +_update_globals() diff --git a/lib/setuptools/dist.py b/lib/setuptools/dist.py new file mode 100644 index 0000000..7062ae8 --- /dev/null +++ b/lib/setuptools/dist.py @@ -0,0 +1,1147 @@ +# -*- coding: utf-8 -*- +__all__ = ['Distribution'] + +import re +import os +import warnings +import numbers +import distutils.log +import distutils.core +import distutils.cmd +import distutils.dist +import itertools + + +from collections import defaultdict +from email import message_from_file + +from distutils.errors import ( + DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError, +) +from distutils.util import rfc822_escape +from distutils.version import StrictVersion + +from setuptools.extern import six +from setuptools.extern import packaging +from setuptools.extern.six.moves import map, filter, filterfalse + +from . import SetuptoolsDeprecationWarning + +from setuptools.depends import Require +from setuptools import windows_support +from setuptools.monkey import get_unpatched +from setuptools.config import parse_configuration +import pkg_resources +from .py36compat import Distribution_parse_config_files + +__import__('setuptools.extern.packaging.specifiers') +__import__('setuptools.extern.packaging.version') + + +def _get_unpatched(cls): + warnings.warn("Do not call this function", DistDeprecationWarning) + return get_unpatched(cls) + + +def get_metadata_version(self): + mv = getattr(self, 'metadata_version', None) + + if mv is None: + if self.long_description_content_type or self.provides_extras: + mv = StrictVersion('2.1') + elif (self.maintainer is not None or + self.maintainer_email is not None or + getattr(self, 'python_requires', None) is not None): + mv = StrictVersion('1.2') + elif (self.provides or self.requires or self.obsoletes or + self.classifiers or self.download_url): + mv = StrictVersion('1.1') + else: + mv = StrictVersion('1.0') + + self.metadata_version = mv + + return mv + + +def read_pkg_file(self, file): + """Reads the metadata values from a file object.""" + msg = message_from_file(file) + + def _read_field(name): + value = msg[name] + if value == 'UNKNOWN': + return None + return value + + def _read_list(name): + values = msg.get_all(name, None) + if values == []: + return None + return values + + self.metadata_version = StrictVersion(msg['metadata-version']) + self.name = _read_field('name') + self.version = _read_field('version') + self.description = _read_field('summary') + # we are filling author only. + self.author = _read_field('author') + self.maintainer = None + self.author_email = _read_field('author-email') + self.maintainer_email = None + self.url = _read_field('home-page') + self.license = _read_field('license') + + if 'download-url' in msg: + self.download_url = _read_field('download-url') + else: + self.download_url = None + + self.long_description = _read_field('description') + self.description = _read_field('summary') + + if 'keywords' in msg: + self.keywords = _read_field('keywords').split(',') + + self.platforms = _read_list('platform') + self.classifiers = _read_list('classifier') + + # PEP 314 - these fields only exist in 1.1 + if self.metadata_version == StrictVersion('1.1'): + self.requires = _read_list('requires') + self.provides = _read_list('provides') + self.obsoletes = _read_list('obsoletes') + else: + self.requires = None + self.provides = None + self.obsoletes = None + + +# Based on Python 3.5 version +def write_pkg_file(self, file): + """Write the PKG-INFO format data to a file object. + """ + version = self.get_metadata_version() + + if six.PY2: + def write_field(key, value): + file.write("%s: %s\n" % (key, self._encode_field(value))) + else: + def write_field(key, value): + file.write("%s: %s\n" % (key, value)) + + + write_field('Metadata-Version', str(version)) + write_field('Name', self.get_name()) + write_field('Version', self.get_version()) + write_field('Summary', self.get_description()) + write_field('Home-page', self.get_url()) + + if version < StrictVersion('1.2'): + write_field('Author', self.get_contact()) + write_field('Author-email', self.get_contact_email()) + else: + optional_fields = ( + ('Author', 'author'), + ('Author-email', 'author_email'), + ('Maintainer', 'maintainer'), + ('Maintainer-email', 'maintainer_email'), + ) + + for field, attr in optional_fields: + attr_val = getattr(self, attr) + + if attr_val is not None: + write_field(field, attr_val) + + write_field('License', self.get_license()) + if self.download_url: + write_field('Download-URL', self.download_url) + for project_url in self.project_urls.items(): + write_field('Project-URL', '%s, %s' % project_url) + + long_desc = rfc822_escape(self.get_long_description()) + write_field('Description', long_desc) + + keywords = ','.join(self.get_keywords()) + if keywords: + write_field('Keywords', keywords) + + if version >= StrictVersion('1.2'): + for platform in self.get_platforms(): + write_field('Platform', platform) + else: + self._write_list(file, 'Platform', self.get_platforms()) + + self._write_list(file, 'Classifier', self.get_classifiers()) + + # PEP 314 + self._write_list(file, 'Requires', self.get_requires()) + self._write_list(file, 'Provides', self.get_provides()) + self._write_list(file, 'Obsoletes', self.get_obsoletes()) + + # Setuptools specific for PEP 345 + if hasattr(self, 'python_requires'): + write_field('Requires-Python', self.python_requires) + + # PEP 566 + if self.long_description_content_type: + write_field( + 'Description-Content-Type', + self.long_description_content_type + ) + if self.provides_extras: + for extra in self.provides_extras: + write_field('Provides-Extra', extra) + + +sequence = tuple, list + + +def check_importable(dist, attr, value): + try: + ep = pkg_resources.EntryPoint.parse('x=' + value) + assert not ep.extras + except (TypeError, ValueError, AttributeError, AssertionError): + raise DistutilsSetupError( + "%r must be importable 'module:attrs' string (got %r)" + % (attr, value) + ) + + +def assert_string_list(dist, attr, value): + """Verify that value is a string list or None""" + try: + assert ''.join(value) != value + except (TypeError, ValueError, AttributeError, AssertionError): + raise DistutilsSetupError( + "%r must be a list of strings (got %r)" % (attr, value) + ) + + +def check_nsp(dist, attr, value): + """Verify that namespace packages are valid""" + ns_packages = value + assert_string_list(dist, attr, ns_packages) + for nsp in ns_packages: + if not dist.has_contents_for(nsp): + raise DistutilsSetupError( + "Distribution contains no modules or packages for " + + "namespace package %r" % nsp + ) + parent, sep, child = nsp.rpartition('.') + if parent and parent not in ns_packages: + distutils.log.warn( + "WARNING: %r is declared as a package namespace, but %r" + " is not: please correct this in setup.py", nsp, parent + ) + + +def check_extras(dist, attr, value): + """Verify that extras_require mapping is valid""" + try: + list(itertools.starmap(_check_extra, value.items())) + except (TypeError, ValueError, AttributeError): + raise DistutilsSetupError( + "'extras_require' must be a dictionary whose values are " + "strings or lists of strings containing valid project/version " + "requirement specifiers." + ) + + +def _check_extra(extra, reqs): + name, sep, marker = extra.partition(':') + if marker and pkg_resources.invalid_marker(marker): + raise DistutilsSetupError("Invalid environment marker: " + marker) + list(pkg_resources.parse_requirements(reqs)) + + +def assert_bool(dist, attr, value): + """Verify that value is True, False, 0, or 1""" + if bool(value) != value: + tmpl = "{attr!r} must be a boolean value (got {value!r})" + raise DistutilsSetupError(tmpl.format(attr=attr, value=value)) + + +def check_requirements(dist, attr, value): + """Verify that install_requires is a valid requirements list""" + try: + list(pkg_resources.parse_requirements(value)) + if isinstance(value, (dict, set)): + raise TypeError("Unordered types are not allowed") + except (TypeError, ValueError) as error: + tmpl = ( + "{attr!r} must be a string or list of strings " + "containing valid project/version requirement specifiers; {error}" + ) + raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) + + +def check_specifier(dist, attr, value): + """Verify that value is a valid version specifier""" + try: + packaging.specifiers.SpecifierSet(value) + except packaging.specifiers.InvalidSpecifier as error: + tmpl = ( + "{attr!r} must be a string " + "containing valid version specifiers; {error}" + ) + raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) + + +def check_entry_points(dist, attr, value): + """Verify that entry_points map is parseable""" + try: + pkg_resources.EntryPoint.parse_map(value) + except ValueError as e: + raise DistutilsSetupError(e) + + +def check_test_suite(dist, attr, value): + if not isinstance(value, six.string_types): + raise DistutilsSetupError("test_suite must be a string") + + +def check_package_data(dist, attr, value): + """Verify that value is a dictionary of package names to glob lists""" + if isinstance(value, dict): + for k, v in value.items(): + if not isinstance(k, str): + break + try: + iter(v) + except TypeError: + break + else: + return + raise DistutilsSetupError( + attr + " must be a dictionary mapping package names to lists of " + "wildcard patterns" + ) + + +def check_packages(dist, attr, value): + for pkgname in value: + if not re.match(r'\w+(\.\w+)*', pkgname): + distutils.log.warn( + "WARNING: %r not a valid package name; please use only " + ".-separated package names in setup.py", pkgname + ) + + +_Distribution = get_unpatched(distutils.core.Distribution) + + +class Distribution(Distribution_parse_config_files, _Distribution): + """Distribution with support for features, tests, and package data + + This is an enhanced version of 'distutils.dist.Distribution' that + effectively adds the following new optional keyword arguments to 'setup()': + + 'install_requires' -- a string or sequence of strings specifying project + versions that the distribution requires when installed, in the format + used by 'pkg_resources.require()'. They will be installed + automatically when the package is installed. If you wish to use + packages that are not available in PyPI, or want to give your users an + alternate download location, you can add a 'find_links' option to the + '[easy_install]' section of your project's 'setup.cfg' file, and then + setuptools will scan the listed web pages for links that satisfy the + requirements. + + 'extras_require' -- a dictionary mapping names of optional "extras" to the + additional requirement(s) that using those extras incurs. For example, + this:: + + extras_require = dict(reST = ["docutils>=0.3", "reSTedit"]) + + indicates that the distribution can optionally provide an extra + capability called "reST", but it can only be used if docutils and + reSTedit are installed. If the user installs your package using + EasyInstall and requests one of your extras, the corresponding + additional requirements will be installed if needed. + + 'features' **deprecated** -- a dictionary mapping option names to + 'setuptools.Feature' + objects. Features are a portion of the distribution that can be + included or excluded based on user options, inter-feature dependencies, + and availability on the current system. Excluded features are omitted + from all setup commands, including source and binary distributions, so + you can create multiple distributions from the same source tree. + Feature names should be valid Python identifiers, except that they may + contain the '-' (minus) sign. Features can be included or excluded + via the command line options '--with-X' and '--without-X', where 'X' is + the name of the feature. Whether a feature is included by default, and + whether you are allowed to control this from the command line, is + determined by the Feature object. See the 'Feature' class for more + information. + + 'test_suite' -- the name of a test suite to run for the 'test' command. + If the user runs 'python setup.py test', the package will be installed, + and the named test suite will be run. The format is the same as + would be used on a 'unittest.py' command line. That is, it is the + dotted name of an object to import and call to generate a test suite. + + 'package_data' -- a dictionary mapping package names to lists of filenames + or globs to use to find data files contained in the named packages. + If the dictionary has filenames or globs listed under '""' (the empty + string), those names will be searched for in every package, in addition + to any names for the specific package. Data files found using these + names/globs will be installed along with the package, in the same + location as the package. Note that globs are allowed to reference + the contents of non-package subdirectories, as long as you use '/' as + a path separator. (Globs are automatically converted to + platform-specific paths at runtime.) + + In addition to these new keywords, this class also has several new methods + for manipulating the distribution's contents. For example, the 'include()' + and 'exclude()' methods can be thought of as in-place add and subtract + commands that add or remove packages, modules, extensions, and so on from + the distribution. They are used by the feature subsystem to configure the + distribution for the included and excluded features. + """ + + _DISTUTILS_UNSUPPORTED_METADATA = { + 'long_description_content_type': None, + 'project_urls': dict, + 'provides_extras': set, + } + + _patched_dist = None + + def patch_missing_pkg_info(self, attrs): + # Fake up a replacement for the data that would normally come from + # PKG-INFO, but which might not yet be built if this is a fresh + # checkout. + # + if not attrs or 'name' not in attrs or 'version' not in attrs: + return + key = pkg_resources.safe_name(str(attrs['name'])).lower() + dist = pkg_resources.working_set.by_key.get(key) + if dist is not None and not dist.has_metadata('PKG-INFO'): + dist._version = pkg_resources.safe_version(str(attrs['version'])) + self._patched_dist = dist + + def __init__(self, attrs=None): + have_package_data = hasattr(self, "package_data") + if not have_package_data: + self.package_data = {} + attrs = attrs or {} + if 'features' in attrs or 'require_features' in attrs: + Feature.warn_deprecated() + self.require_features = [] + self.features = {} + self.dist_files = [] + # Filter-out setuptools' specific options. + self.src_root = attrs.pop("src_root", None) + self.patch_missing_pkg_info(attrs) + self.dependency_links = attrs.pop('dependency_links', []) + self.setup_requires = attrs.pop('setup_requires', []) + for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): + vars(self).setdefault(ep.name, None) + _Distribution.__init__(self, { + k: v for k, v in attrs.items() + if k not in self._DISTUTILS_UNSUPPORTED_METADATA + }) + + # Fill-in missing metadata fields not supported by distutils. + # Note some fields may have been set by other tools (e.g. pbr) + # above; they are taken preferrentially to setup() arguments + for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items(): + for source in self.metadata.__dict__, attrs: + if option in source: + value = source[option] + break + else: + value = default() if default else None + setattr(self.metadata, option, value) + + if isinstance(self.metadata.version, numbers.Number): + # Some people apparently take "version number" too literally :) + self.metadata.version = str(self.metadata.version) + + if self.metadata.version is not None: + try: + ver = packaging.version.Version(self.metadata.version) + normalized_version = str(ver) + if self.metadata.version != normalized_version: + warnings.warn( + "Normalizing '%s' to '%s'" % ( + self.metadata.version, + normalized_version, + ) + ) + self.metadata.version = normalized_version + except (packaging.version.InvalidVersion, TypeError): + warnings.warn( + "The version specified (%r) is an invalid version, this " + "may not work as expected with newer versions of " + "setuptools, pip, and PyPI. Please see PEP 440 for more " + "details." % self.metadata.version + ) + self._finalize_requires() + + def _finalize_requires(self): + """ + Set `metadata.python_requires` and fix environment markers + in `install_requires` and `extras_require`. + """ + if getattr(self, 'python_requires', None): + self.metadata.python_requires = self.python_requires + + if getattr(self, 'extras_require', None): + for extra in self.extras_require.keys(): + # Since this gets called multiple times at points where the + # keys have become 'converted' extras, ensure that we are only + # truly adding extras we haven't seen before here. + extra = extra.split(':')[0] + if extra: + self.metadata.provides_extras.add(extra) + + self._convert_extras_requirements() + self._move_install_requirements_markers() + + def _convert_extras_requirements(self): + """ + Convert requirements in `extras_require` of the form + `"extra": ["barbazquux; {marker}"]` to + `"extra:{marker}": ["barbazquux"]`. + """ + spec_ext_reqs = getattr(self, 'extras_require', None) or {} + self._tmp_extras_require = defaultdict(list) + for section, v in spec_ext_reqs.items(): + # Do not strip empty sections. + self._tmp_extras_require[section] + for r in pkg_resources.parse_requirements(v): + suffix = self._suffix_for(r) + self._tmp_extras_require[section + suffix].append(r) + + @staticmethod + def _suffix_for(req): + """ + For a requirement, return the 'extras_require' suffix for + that requirement. + """ + return ':' + str(req.marker) if req.marker else '' + + def _move_install_requirements_markers(self): + """ + Move requirements in `install_requires` that are using environment + markers `extras_require`. + """ + + # divide the install_requires into two sets, simple ones still + # handled by install_requires and more complex ones handled + # by extras_require. + + def is_simple_req(req): + return not req.marker + + spec_inst_reqs = getattr(self, 'install_requires', None) or () + inst_reqs = list(pkg_resources.parse_requirements(spec_inst_reqs)) + simple_reqs = filter(is_simple_req, inst_reqs) + complex_reqs = filterfalse(is_simple_req, inst_reqs) + self.install_requires = list(map(str, simple_reqs)) + + for r in complex_reqs: + self._tmp_extras_require[':' + str(r.marker)].append(r) + self.extras_require = dict( + (k, [str(r) for r in map(self._clean_req, v)]) + for k, v in self._tmp_extras_require.items() + ) + + def _clean_req(self, req): + """ + Given a Requirement, remove environment markers and return it. + """ + req.marker = None + return req + + def parse_config_files(self, filenames=None, ignore_option_errors=False): + """Parses configuration files from various levels + and loads configuration. + + """ + _Distribution.parse_config_files(self, filenames=filenames) + + parse_configuration(self, self.command_options, + ignore_option_errors=ignore_option_errors) + self._finalize_requires() + + def parse_command_line(self): + """Process features after parsing command line options""" + result = _Distribution.parse_command_line(self) + if self.features: + self._finalize_features() + return result + + def _feature_attrname(self, name): + """Convert feature name to corresponding option attribute name""" + return 'with_' + name.replace('-', '_') + + def fetch_build_eggs(self, requires): + """Resolve pre-setup requirements""" + resolved_dists = pkg_resources.working_set.resolve( + pkg_resources.parse_requirements(requires), + installer=self.fetch_build_egg, + replace_conflicting=True, + ) + for dist in resolved_dists: + pkg_resources.working_set.add(dist, replace=True) + return resolved_dists + + def finalize_options(self): + _Distribution.finalize_options(self) + if self.features: + self._set_global_opts_from_features() + + for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): + value = getattr(self, ep.name, None) + if value is not None: + ep.require(installer=self.fetch_build_egg) + ep.load()(self, ep.name, value) + if getattr(self, 'convert_2to3_doctests', None): + # XXX may convert to set here when we can rely on set being builtin + self.convert_2to3_doctests = [ + os.path.abspath(p) + for p in self.convert_2to3_doctests + ] + else: + self.convert_2to3_doctests = [] + + def get_egg_cache_dir(self): + egg_cache_dir = os.path.join(os.curdir, '.eggs') + if not os.path.exists(egg_cache_dir): + os.mkdir(egg_cache_dir) + windows_support.hide_file(egg_cache_dir) + readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt') + with open(readme_txt_filename, 'w') as f: + f.write('This directory contains eggs that were downloaded ' + 'by setuptools to build, test, and run plug-ins.\n\n') + f.write('This directory caches those eggs to prevent ' + 'repeated downloads.\n\n') + f.write('However, it is safe to delete this directory.\n\n') + + return egg_cache_dir + + def fetch_build_egg(self, req): + """Fetch an egg needed for building""" + from setuptools.command.easy_install import easy_install + dist = self.__class__({'script_args': ['easy_install']}) + opts = dist.get_option_dict('easy_install') + opts.clear() + opts.update( + (k, v) + for k, v in self.get_option_dict('easy_install').items() + if k in ( + # don't use any other settings + 'find_links', 'site_dirs', 'index_url', + 'optimize', 'site_dirs', 'allow_hosts', + )) + if self.dependency_links: + links = self.dependency_links[:] + if 'find_links' in opts: + links = opts['find_links'][1] + links + opts['find_links'] = ('setup', links) + install_dir = self.get_egg_cache_dir() + cmd = easy_install( + dist, args=["x"], install_dir=install_dir, + exclude_scripts=True, + always_copy=False, build_directory=None, editable=False, + upgrade=False, multi_version=True, no_report=True, user=False + ) + cmd.ensure_finalized() + return cmd.easy_install(req) + + def _set_global_opts_from_features(self): + """Add --with-X/--without-X options based on optional features""" + + go = [] + no = self.negative_opt.copy() + + for name, feature in self.features.items(): + self._set_feature(name, None) + feature.validate(self) + + if feature.optional: + descr = feature.description + incdef = ' (default)' + excdef = '' + if not feature.include_by_default(): + excdef, incdef = incdef, excdef + + new = ( + ('with-' + name, None, 'include ' + descr + incdef), + ('without-' + name, None, 'exclude ' + descr + excdef), + ) + go.extend(new) + no['without-' + name] = 'with-' + name + + self.global_options = self.feature_options = go + self.global_options + self.negative_opt = self.feature_negopt = no + + def _finalize_features(self): + """Add/remove features and resolve dependencies between them""" + + # First, flag all the enabled items (and thus their dependencies) + for name, feature in self.features.items(): + enabled = self.feature_is_included(name) + if enabled or (enabled is None and feature.include_by_default()): + feature.include_in(self) + self._set_feature(name, 1) + + # Then disable the rest, so that off-by-default features don't + # get flagged as errors when they're required by an enabled feature + for name, feature in self.features.items(): + if not self.feature_is_included(name): + feature.exclude_from(self) + self._set_feature(name, 0) + + def get_command_class(self, command): + """Pluggable version of get_command_class()""" + if command in self.cmdclass: + return self.cmdclass[command] + + eps = pkg_resources.iter_entry_points('distutils.commands', command) + for ep in eps: + ep.require(installer=self.fetch_build_egg) + self.cmdclass[command] = cmdclass = ep.load() + return cmdclass + else: + return _Distribution.get_command_class(self, command) + + def print_commands(self): + for ep in pkg_resources.iter_entry_points('distutils.commands'): + if ep.name not in self.cmdclass: + # don't require extras as the commands won't be invoked + cmdclass = ep.resolve() + self.cmdclass[ep.name] = cmdclass + return _Distribution.print_commands(self) + + def get_command_list(self): + for ep in pkg_resources.iter_entry_points('distutils.commands'): + if ep.name not in self.cmdclass: + # don't require extras as the commands won't be invoked + cmdclass = ep.resolve() + self.cmdclass[ep.name] = cmdclass + return _Distribution.get_command_list(self) + + def _set_feature(self, name, status): + """Set feature's inclusion status""" + setattr(self, self._feature_attrname(name), status) + + def feature_is_included(self, name): + """Return 1 if feature is included, 0 if excluded, 'None' if unknown""" + return getattr(self, self._feature_attrname(name)) + + def include_feature(self, name): + """Request inclusion of feature named 'name'""" + + if self.feature_is_included(name) == 0: + descr = self.features[name].description + raise DistutilsOptionError( + descr + " is required, but was excluded or is not available" + ) + self.features[name].include_in(self) + self._set_feature(name, 1) + + def include(self, **attrs): + """Add items to distribution that are named in keyword arguments + + For example, 'dist.exclude(py_modules=["x"])' would add 'x' to + the distribution's 'py_modules' attribute, if it was not already + there. + + Currently, this method only supports inclusion for attributes that are + lists or tuples. If you need to add support for adding to other + attributes in this or a subclass, you can add an '_include_X' method, + where 'X' is the name of the attribute. The method will be called with + the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})' + will try to call 'dist._include_foo({"bar":"baz"})', which can then + handle whatever special inclusion logic is needed. + """ + for k, v in attrs.items(): + include = getattr(self, '_include_' + k, None) + if include: + include(v) + else: + self._include_misc(k, v) + + def exclude_package(self, package): + """Remove packages, modules, and extensions in named package""" + + pfx = package + '.' + if self.packages: + self.packages = [ + p for p in self.packages + if p != package and not p.startswith(pfx) + ] + + if self.py_modules: + self.py_modules = [ + p for p in self.py_modules + if p != package and not p.startswith(pfx) + ] + + if self.ext_modules: + self.ext_modules = [ + p for p in self.ext_modules + if p.name != package and not p.name.startswith(pfx) + ] + + def has_contents_for(self, package): + """Return true if 'exclude_package(package)' would do something""" + + pfx = package + '.' + + for p in self.iter_distribution_names(): + if p == package or p.startswith(pfx): + return True + + def _exclude_misc(self, name, value): + """Handle 'exclude()' for list/tuple attrs without a special handler""" + if not isinstance(value, sequence): + raise DistutilsSetupError( + "%s: setting must be a list or tuple (%r)" % (name, value) + ) + try: + old = getattr(self, name) + except AttributeError: + raise DistutilsSetupError( + "%s: No such distribution setting" % name + ) + if old is not None and not isinstance(old, sequence): + raise DistutilsSetupError( + name + ": this setting cannot be changed via include/exclude" + ) + elif old: + setattr(self, name, [item for item in old if item not in value]) + + def _include_misc(self, name, value): + """Handle 'include()' for list/tuple attrs without a special handler""" + + if not isinstance(value, sequence): + raise DistutilsSetupError( + "%s: setting must be a list (%r)" % (name, value) + ) + try: + old = getattr(self, name) + except AttributeError: + raise DistutilsSetupError( + "%s: No such distribution setting" % name + ) + if old is None: + setattr(self, name, value) + elif not isinstance(old, sequence): + raise DistutilsSetupError( + name + ": this setting cannot be changed via include/exclude" + ) + else: + new = [item for item in value if item not in old] + setattr(self, name, old + new) + + def exclude(self, **attrs): + """Remove items from distribution that are named in keyword arguments + + For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from + the distribution's 'py_modules' attribute. Excluding packages uses + the 'exclude_package()' method, so all of the package's contained + packages, modules, and extensions are also excluded. + + Currently, this method only supports exclusion from attributes that are + lists or tuples. If you need to add support for excluding from other + attributes in this or a subclass, you can add an '_exclude_X' method, + where 'X' is the name of the attribute. The method will be called with + the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})' + will try to call 'dist._exclude_foo({"bar":"baz"})', which can then + handle whatever special exclusion logic is needed. + """ + for k, v in attrs.items(): + exclude = getattr(self, '_exclude_' + k, None) + if exclude: + exclude(v) + else: + self._exclude_misc(k, v) + + def _exclude_packages(self, packages): + if not isinstance(packages, sequence): + raise DistutilsSetupError( + "packages: setting must be a list or tuple (%r)" % (packages,) + ) + list(map(self.exclude_package, packages)) + + def _parse_command_opts(self, parser, args): + # Remove --with-X/--without-X options when processing command args + self.global_options = self.__class__.global_options + self.negative_opt = self.__class__.negative_opt + + # First, expand any aliases + command = args[0] + aliases = self.get_option_dict('aliases') + while command in aliases: + src, alias = aliases[command] + del aliases[command] # ensure each alias can expand only once! + import shlex + args[:1] = shlex.split(alias, True) + command = args[0] + + nargs = _Distribution._parse_command_opts(self, parser, args) + + # Handle commands that want to consume all remaining arguments + cmd_class = self.get_command_class(command) + if getattr(cmd_class, 'command_consumes_arguments', None): + self.get_option_dict(command)['args'] = ("command line", nargs) + if nargs is not None: + return [] + + return nargs + + def get_cmdline_options(self): + """Return a '{cmd: {opt:val}}' map of all command-line options + + Option names are all long, but do not include the leading '--', and + contain dashes rather than underscores. If the option doesn't take + an argument (e.g. '--quiet'), the 'val' is 'None'. + + Note that options provided by config files are intentionally excluded. + """ + + d = {} + + for cmd, opts in self.command_options.items(): + + for opt, (src, val) in opts.items(): + + if src != "command line": + continue + + opt = opt.replace('_', '-') + + if val == 0: + cmdobj = self.get_command_obj(cmd) + neg_opt = self.negative_opt.copy() + neg_opt.update(getattr(cmdobj, 'negative_opt', {})) + for neg, pos in neg_opt.items(): + if pos == opt: + opt = neg + val = None + break + else: + raise AssertionError("Shouldn't be able to get here") + + elif val == 1: + val = None + + d.setdefault(cmd, {})[opt] = val + + return d + + def iter_distribution_names(self): + """Yield all packages, modules, and extension names in distribution""" + + for pkg in self.packages or (): + yield pkg + + for module in self.py_modules or (): + yield module + + for ext in self.ext_modules or (): + if isinstance(ext, tuple): + name, buildinfo = ext + else: + name = ext.name + if name.endswith('module'): + name = name[:-6] + yield name + + def handle_display_options(self, option_order): + """If there were any non-global "display-only" options + (--help-commands or the metadata display options) on the command + line, display the requested info and return true; else return + false. + """ + import sys + + if six.PY2 or self.help_commands: + return _Distribution.handle_display_options(self, option_order) + + # Stdout may be StringIO (e.g. in tests) + import io + if not isinstance(sys.stdout, io.TextIOWrapper): + return _Distribution.handle_display_options(self, option_order) + + # Don't wrap stdout if utf-8 is already the encoding. Provides + # workaround for #334. + if sys.stdout.encoding.lower() in ('utf-8', 'utf8'): + return _Distribution.handle_display_options(self, option_order) + + # Print metadata in UTF-8 no matter the platform + encoding = sys.stdout.encoding + errors = sys.stdout.errors + newline = sys.platform != 'win32' and '\n' or None + line_buffering = sys.stdout.line_buffering + + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), 'utf-8', errors, newline, line_buffering) + try: + return _Distribution.handle_display_options(self, option_order) + finally: + sys.stdout = io.TextIOWrapper( + sys.stdout.detach(), encoding, errors, newline, line_buffering) + + +class Feature: + """ + **deprecated** -- The `Feature` facility was never completely implemented + or supported, `has reported issues + <https://github.com/pypa/setuptools/issues/58>`_ and will be removed in + a future version. + + A subset of the distribution that can be excluded if unneeded/wanted + + Features are created using these keyword arguments: + + 'description' -- a short, human readable description of the feature, to + be used in error messages, and option help messages. + + 'standard' -- if true, the feature is included by default if it is + available on the current system. Otherwise, the feature is only + included if requested via a command line '--with-X' option, or if + another included feature requires it. The default setting is 'False'. + + 'available' -- if true, the feature is available for installation on the + current system. The default setting is 'True'. + + 'optional' -- if true, the feature's inclusion can be controlled from the + command line, using the '--with-X' or '--without-X' options. If + false, the feature's inclusion status is determined automatically, + based on 'availabile', 'standard', and whether any other feature + requires it. The default setting is 'True'. + + 'require_features' -- a string or sequence of strings naming features + that should also be included if this feature is included. Defaults to + empty list. May also contain 'Require' objects that should be + added/removed from the distribution. + + 'remove' -- a string or list of strings naming packages to be removed + from the distribution if this feature is *not* included. If the + feature *is* included, this argument is ignored. This argument exists + to support removing features that "crosscut" a distribution, such as + defining a 'tests' feature that removes all the 'tests' subpackages + provided by other features. The default for this argument is an empty + list. (Note: the named package(s) or modules must exist in the base + distribution when the 'setup()' function is initially called.) + + other keywords -- any other keyword arguments are saved, and passed to + the distribution's 'include()' and 'exclude()' methods when the + feature is included or excluded, respectively. So, for example, you + could pass 'packages=["a","b"]' to cause packages 'a' and 'b' to be + added or removed from the distribution as appropriate. + + A feature must include at least one 'requires', 'remove', or other + keyword argument. Otherwise, it can't affect the distribution in any way. + Note also that you can subclass 'Feature' to create your own specialized + feature types that modify the distribution in other ways when included or + excluded. See the docstrings for the various methods here for more detail. + Aside from the methods, the only feature attributes that distributions look + at are 'description' and 'optional'. + """ + + @staticmethod + def warn_deprecated(): + msg = ( + "Features are deprecated and will be removed in a future " + "version. See https://github.com/pypa/setuptools/issues/65." + ) + warnings.warn(msg, DistDeprecationWarning, stacklevel=3) + + def __init__( + self, description, standard=False, available=True, + optional=True, require_features=(), remove=(), **extras): + self.warn_deprecated() + + self.description = description + self.standard = standard + self.available = available + self.optional = optional + if isinstance(require_features, (str, Require)): + require_features = require_features, + + self.require_features = [ + r for r in require_features if isinstance(r, str) + ] + er = [r for r in require_features if not isinstance(r, str)] + if er: + extras['require_features'] = er + + if isinstance(remove, str): + remove = remove, + self.remove = remove + self.extras = extras + + if not remove and not require_features and not extras: + raise DistutilsSetupError( + "Feature %s: must define 'require_features', 'remove', or " + "at least one of 'packages', 'py_modules', etc." + ) + + def include_by_default(self): + """Should this feature be included by default?""" + return self.available and self.standard + + def include_in(self, dist): + """Ensure feature and its requirements are included in distribution + + You may override this in a subclass to perform additional operations on + the distribution. Note that this method may be called more than once + per feature, and so should be idempotent. + + """ + + if not self.available: + raise DistutilsPlatformError( + self.description + " is required, " + "but is not available on this platform" + ) + + dist.include(**self.extras) + + for f in self.require_features: + dist.include_feature(f) + + def exclude_from(self, dist): + """Ensure feature is excluded from distribution + + You may override this in a subclass to perform additional operations on + the distribution. This method will be called at most once per + feature, and only after all included features have been asked to + include themselves. + """ + + dist.exclude(**self.extras) + + if self.remove: + for item in self.remove: + dist.exclude_package(item) + + def validate(self, dist): + """Verify that feature makes sense in context of distribution + + This method is called by the distribution just before it parses its + command line. It checks to ensure that the 'remove' attribute, if any, + contains only valid package/module names that are present in the base + distribution when 'setup()' is called. You may override it in a + subclass to perform any other required validation of the feature + against a target distribution. + """ + + for item in self.remove: + if not dist.has_contents_for(item): + raise DistutilsSetupError( + "%s wants to be able to remove %s, but the distribution" + " doesn't contain any packages or modules under %s" + % (self.description, item, item) + ) + + +class DistDeprecationWarning(SetuptoolsDeprecationWarning): + """Class for warning about deprecations in dist in setuptools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/lib/setuptools/extension.py b/lib/setuptools/extension.py new file mode 100644 index 0000000..2946889 --- /dev/null +++ b/lib/setuptools/extension.py @@ -0,0 +1,57 @@ +import re +import functools +import distutils.core +import distutils.errors +import distutils.extension + +from setuptools.extern.six.moves import map + +from .monkey import get_unpatched + + +def _have_cython(): + """ + Return True if Cython can be imported. + """ + cython_impl = 'Cython.Distutils.build_ext' + try: + # from (cython_impl) import build_ext + __import__(cython_impl, fromlist=['build_ext']).build_ext + return True + except Exception: + pass + return False + + +# for compatibility +have_pyrex = _have_cython + +_Extension = get_unpatched(distutils.core.Extension) + + +class Extension(_Extension): + """Extension that uses '.c' files in place of '.pyx' files""" + + def __init__(self, name, sources, *args, **kw): + # The *args is needed for compatibility as calls may use positional + # arguments. py_limited_api may be set only via keyword. + self.py_limited_api = kw.pop("py_limited_api", False) + _Extension.__init__(self, name, sources, *args, **kw) + + def _convert_pyx_sources_to_lang(self): + """ + Replace sources with .pyx extensions to sources with the target + language extension. This mechanism allows language authors to supply + pre-converted sources but to prefer the .pyx sources. + """ + if _have_cython(): + # the build has Cython, so allow it to compile the .pyx files + return + lang = self.language or '' + target_ext = '.cpp' if lang.lower() == 'c++' else '.c' + sub = functools.partial(re.sub, '.pyx$', target_ext) + self.sources = list(map(sub, self.sources)) + + +class Library(Extension): + """Just like a regular Extension, but built as a library instead""" diff --git a/lib/setuptools/extern/__init__.py b/lib/setuptools/extern/__init__.py new file mode 100644 index 0000000..cb2fa32 --- /dev/null +++ b/lib/setuptools/extern/__init__.py @@ -0,0 +1,73 @@ +import sys + + +class VendorImporter: + """ + A PEP 302 meta path importer for finding optionally-vendored + or otherwise naturally-installed packages from root_name. + """ + + def __init__(self, root_name, vendored_names=(), vendor_pkg=None): + self.root_name = root_name + self.vendored_names = set(vendored_names) + self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') + + @property + def search_path(self): + """ + Search first the vendor package then as a natural package. + """ + yield self.vendor_pkg + '.' + yield '' + + def find_module(self, fullname, path=None): + """ + Return self when fullname starts with root_name and the + target module is one vendored through this importer. + """ + root, base, target = fullname.partition(self.root_name + '.') + if root: + return + if not any(map(target.startswith, self.vendored_names)): + return + return self + + def load_module(self, fullname): + """ + Iterate over the search path to locate and load fullname. + """ + root, base, target = fullname.partition(self.root_name + '.') + for prefix in self.search_path: + try: + extant = prefix + target + __import__(extant) + mod = sys.modules[extant] + sys.modules[fullname] = mod + # mysterious hack: + # Remove the reference to the extant package/module + # on later Python versions to cause relative imports + # in the vendor package to resolve the same modules + # as those going through this importer. + if sys.version_info >= (3, ): + del sys.modules[extant] + return mod + except ImportError: + pass + else: + raise ImportError( + "The '{target}' package is required; " + "normally this is bundled with this package so if you get " + "this warning, consult the packager of your " + "distribution.".format(**locals()) + ) + + def install(self): + """ + Install this importer into sys.meta_path if not already present. + """ + if self not in sys.meta_path: + sys.meta_path.append(self) + + +names = 'six', 'packaging', 'pyparsing', +VendorImporter(__name__, names, 'setuptools._vendor').install() diff --git a/lib/setuptools/glibc.py b/lib/setuptools/glibc.py new file mode 100644 index 0000000..a134591 --- /dev/null +++ b/lib/setuptools/glibc.py @@ -0,0 +1,86 @@ +# This file originally from pip: +# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/utils/glibc.py +from __future__ import absolute_import + +import ctypes +import re +import warnings + + +def glibc_version_string(): + "Returns glibc version string, or None if not using glibc." + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + process_namespace = ctypes.CDLL(None) + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +# Separated out from have_compatible_glibc for easier unit testing +def check_glibc_version(version_str, required_major, minimum_minor): + # Parse string and check against requested version. + # + # We use a regexp instead of str.split because we want to discard any + # random junk that might come after the minor version -- this might happen + # in patched/forked versions of glibc (e.g. Linaro's version of glibc + # uses version strings like "2.20-2014.11"). See gh-3588. + m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str) + if not m: + warnings.warn("Expected glibc version with 2 components major.minor," + " got: %s" % version_str, RuntimeWarning) + return False + return (int(m.group("major")) == required_major and + int(m.group("minor")) >= minimum_minor) + + +def have_compatible_glibc(required_major, minimum_minor): + version_str = glibc_version_string() + if version_str is None: + return False + return check_glibc_version(version_str, required_major, minimum_minor) + + +# platform.libc_ver regularly returns completely nonsensical glibc +# versions. E.g. on my computer, platform says: +# +# ~$ python2.7 -c 'import platform; print(platform.libc_ver())' +# ('glibc', '2.7') +# ~$ python3.5 -c 'import platform; print(platform.libc_ver())' +# ('glibc', '2.9') +# +# But the truth is: +# +# ~$ ldd --version +# ldd (Debian GLIBC 2.22-11) 2.22 +# +# This is unfortunate, because it means that the linehaul data on libc +# versions that was generated by pip 8.1.2 and earlier is useless and +# misleading. Solution: instead of using platform, use our code that actually +# works. +def libc_ver(): + """Try to determine the glibc version + + Returns a tuple of strings (lib, version) which default to empty strings + in case the lookup fails. + """ + glibc_version = glibc_version_string() + if glibc_version is None: + return ("", "") + else: + return ("glibc", glibc_version) diff --git a/lib/setuptools/glob.py b/lib/setuptools/glob.py new file mode 100644 index 0000000..9d7cbc5 --- /dev/null +++ b/lib/setuptools/glob.py @@ -0,0 +1,174 @@ +""" +Filename globbing utility. Mostly a copy of `glob` from Python 3.5. + +Changes include: + * `yield from` and PEP3102 `*` removed. + * Hidden files are not ignored. +""" + +import os +import re +import fnmatch + +__all__ = ["glob", "iglob", "escape"] + + +def glob(pathname, recursive=False): + """Return a list of paths matching a pathname pattern. + + The pattern may contain simple shell-style wildcards a la + fnmatch. However, unlike fnmatch, filenames starting with a + dot are special cases that are not matched by '*' and '?' + patterns. + + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. + """ + return list(iglob(pathname, recursive=recursive)) + + +def iglob(pathname, recursive=False): + """Return an iterator which yields the paths matching a pathname pattern. + + The pattern may contain simple shell-style wildcards a la + fnmatch. However, unlike fnmatch, filenames starting with a + dot are special cases that are not matched by '*' and '?' + patterns. + + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. + """ + it = _iglob(pathname, recursive) + if recursive and _isrecursive(pathname): + s = next(it) # skip empty string + assert not s + return it + + +def _iglob(pathname, recursive): + dirname, basename = os.path.split(pathname) + if not has_magic(pathname): + if basename: + if os.path.lexists(pathname): + yield pathname + else: + # Patterns ending with a slash should match only directories + if os.path.isdir(dirname): + yield pathname + return + if not dirname: + if recursive and _isrecursive(basename): + for x in glob2(dirname, basename): + yield x + else: + for x in glob1(dirname, basename): + yield x + return + # `os.path.split()` returns the argument itself as a dirname if it is a + # drive or UNC path. Prevent an infinite recursion if a drive or UNC path + # contains magic characters (i.e. r'\\?\C:'). + if dirname != pathname and has_magic(dirname): + dirs = _iglob(dirname, recursive) + else: + dirs = [dirname] + if has_magic(basename): + if recursive and _isrecursive(basename): + glob_in_dir = glob2 + else: + glob_in_dir = glob1 + else: + glob_in_dir = glob0 + for dirname in dirs: + for name in glob_in_dir(dirname, basename): + yield os.path.join(dirname, name) + + +# These 2 helper functions non-recursively glob inside a literal directory. +# They return a list of basenames. `glob1` accepts a pattern while `glob0` +# takes a literal basename (so it only has to check for its existence). + + +def glob1(dirname, pattern): + if not dirname: + if isinstance(pattern, bytes): + dirname = os.curdir.encode('ASCII') + else: + dirname = os.curdir + try: + names = os.listdir(dirname) + except OSError: + return [] + return fnmatch.filter(names, pattern) + + +def glob0(dirname, basename): + if not basename: + # `os.path.split()` returns an empty basename for paths ending with a + # directory separator. 'q*x/' should match only directories. + if os.path.isdir(dirname): + return [basename] + else: + if os.path.lexists(os.path.join(dirname, basename)): + return [basename] + return [] + + +# This helper function recursively yields relative pathnames inside a literal +# directory. + + +def glob2(dirname, pattern): + assert _isrecursive(pattern) + yield pattern[:0] + for x in _rlistdir(dirname): + yield x + + +# Recursively yields relative pathnames inside a literal directory. +def _rlistdir(dirname): + if not dirname: + if isinstance(dirname, bytes): + dirname = os.curdir.encode('ASCII') + else: + dirname = os.curdir + try: + names = os.listdir(dirname) + except os.error: + return + for x in names: + yield x + path = os.path.join(dirname, x) if dirname else x + for y in _rlistdir(path): + yield os.path.join(x, y) + + +magic_check = re.compile('([*?[])') +magic_check_bytes = re.compile(b'([*?[])') + + +def has_magic(s): + if isinstance(s, bytes): + match = magic_check_bytes.search(s) + else: + match = magic_check.search(s) + return match is not None + + +def _isrecursive(pattern): + if isinstance(pattern, bytes): + return pattern == b'**' + else: + return pattern == '**' + + +def escape(pathname): + """Escape all special characters. + """ + # Escaping is done by wrapping any of "*?[" between square brackets. + # Metacharacters do not work in the drive part and shouldn't be escaped. + drive, pathname = os.path.splitdrive(pathname) + if isinstance(pathname, bytes): + pathname = magic_check_bytes.sub(br'[\1]', pathname) + else: + pathname = magic_check.sub(r'[\1]', pathname) + return drive + pathname diff --git a/lib/setuptools/gui-32.exe b/lib/setuptools/gui-32.exe new file mode 100644 index 0000000000000000000000000000000000000000..f8d3509653ba8f80ca7f3aa7f95616142ba83a94 GIT binary patch literal 65536 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 literal 0 HcmV?d00001 diff --git a/lib/setuptools/gui-64.exe b/lib/setuptools/gui-64.exe new file mode 100644 index 0000000000000000000000000000000000000000..330c51a5dde15a0bb610a48cd0ca11770c914dae GIT binary patch literal 75264 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 literal 0 HcmV?d00001 diff --git a/lib/setuptools/gui.exe b/lib/setuptools/gui.exe new file mode 100644 index 0000000000000000000000000000000000000000..f8d3509653ba8f80ca7f3aa7f95616142ba83a94 GIT binary patch literal 65536 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 literal 0 HcmV?d00001 diff --git a/lib/setuptools/launch.py b/lib/setuptools/launch.py new file mode 100644 index 0000000..308283e --- /dev/null +++ b/lib/setuptools/launch.py @@ -0,0 +1,35 @@ +""" +Launch the Python script on the command line after +setuptools is bootstrapped via import. +""" + +# Note that setuptools gets imported implicitly by the +# invocation of this script using python -m setuptools.launch + +import tokenize +import sys + + +def run(): + """ + Run the script in sys.argv[1] as if it had + been invoked naturally. + """ + __builtins__ + script_name = sys.argv[1] + namespace = dict( + __file__=script_name, + __name__='__main__', + __doc__=None, + ) + sys.argv[:] = sys.argv[1:] + + open_ = getattr(tokenize, 'open', open) + script = open_(script_name).read() + norm_script = script.replace('\\r\\n', '\\n') + code = compile(norm_script, script_name, 'exec') + exec(code, namespace) + + +if __name__ == '__main__': + run() diff --git a/lib/setuptools/lib2to3_ex.py b/lib/setuptools/lib2to3_ex.py new file mode 100644 index 0000000..4b1a73f --- /dev/null +++ b/lib/setuptools/lib2to3_ex.py @@ -0,0 +1,62 @@ +""" +Customized Mixin2to3 support: + + - adds support for converting doctests + + +This module raises an ImportError on Python 2. +""" + +from distutils.util import Mixin2to3 as _Mixin2to3 +from distutils import log +from lib2to3.refactor import RefactoringTool, get_fixers_from_package + +import setuptools + + +class DistutilsRefactoringTool(RefactoringTool): + def log_error(self, msg, *args, **kw): + log.error(msg, *args) + + def log_message(self, msg, *args): + log.info(msg, *args) + + def log_debug(self, msg, *args): + log.debug(msg, *args) + + +class Mixin2to3(_Mixin2to3): + def run_2to3(self, files, doctests=False): + # See of the distribution option has been set, otherwise check the + # setuptools default. + if self.distribution.use_2to3 is not True: + return + if not files: + return + log.info("Fixing " + " ".join(files)) + self.__build_fixer_names() + self.__exclude_fixers() + if doctests: + if setuptools.run_2to3_on_doctests: + r = DistutilsRefactoringTool(self.fixer_names) + r.refactor(files, write=True, doctests_only=True) + else: + _Mixin2to3.run_2to3(self, files) + + def __build_fixer_names(self): + if self.fixer_names: + return + self.fixer_names = [] + for p in setuptools.lib2to3_fixer_packages: + self.fixer_names.extend(get_fixers_from_package(p)) + if self.distribution.use_2to3_fixers is not None: + for p in self.distribution.use_2to3_fixers: + self.fixer_names.extend(get_fixers_from_package(p)) + + def __exclude_fixers(self): + excluded_fixers = getattr(self, 'exclude_fixers', []) + if self.distribution.use_2to3_exclude_fixers is not None: + excluded_fixers.extend(self.distribution.use_2to3_exclude_fixers) + for fixer_name in excluded_fixers: + if fixer_name in self.fixer_names: + self.fixer_names.remove(fixer_name) diff --git a/lib/setuptools/monkey.py b/lib/setuptools/monkey.py new file mode 100644 index 0000000..3c77f8c --- /dev/null +++ b/lib/setuptools/monkey.py @@ -0,0 +1,179 @@ +""" +Monkey patching of distutils. +""" + +import sys +import distutils.filelist +import platform +import types +import functools +from importlib import import_module +import inspect + +from setuptools.extern import six + +import setuptools + +__all__ = [] +""" +Everything is private. Contact the project team +if you think you need this functionality. +""" + + +def _get_mro(cls): + """ + Returns the bases classes for cls sorted by the MRO. + + Works around an issue on Jython where inspect.getmro will not return all + base classes if multiple classes share the same name. Instead, this + function will return a tuple containing the class itself, and the contents + of cls.__bases__. See https://github.com/pypa/setuptools/issues/1024. + """ + if platform.python_implementation() == "Jython": + return (cls,) + cls.__bases__ + return inspect.getmro(cls) + + +def get_unpatched(item): + lookup = ( + get_unpatched_class if isinstance(item, six.class_types) else + get_unpatched_function if isinstance(item, types.FunctionType) else + lambda item: None + ) + return lookup(item) + + +def get_unpatched_class(cls): + """Protect against re-patching the distutils if reloaded + + Also ensures that no other distutils extension monkeypatched the distutils + first. + """ + external_bases = ( + cls + for cls in _get_mro(cls) + if not cls.__module__.startswith('setuptools') + ) + base = next(external_bases) + if not base.__module__.startswith('distutils'): + msg = "distutils has already been patched by %r" % cls + raise AssertionError(msg) + return base + + +def patch_all(): + # we can't patch distutils.cmd, alas + distutils.core.Command = setuptools.Command + + has_issue_12885 = sys.version_info <= (3, 5, 3) + + if has_issue_12885: + # fix findall bug in distutils (http://bugs.python.org/issue12885) + distutils.filelist.findall = setuptools.findall + + needs_warehouse = ( + sys.version_info < (2, 7, 13) + or + (3, 4) < sys.version_info < (3, 4, 6) + or + (3, 5) < sys.version_info <= (3, 5, 3) + ) + + if needs_warehouse: + warehouse = 'https://upload.pypi.org/legacy/' + distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse + + _patch_distribution_metadata() + + # Install Distribution throughout the distutils + for module in distutils.dist, distutils.core, distutils.cmd: + module.Distribution = setuptools.dist.Distribution + + # Install the patched Extension + distutils.core.Extension = setuptools.extension.Extension + distutils.extension.Extension = setuptools.extension.Extension + if 'distutils.command.build_ext' in sys.modules: + sys.modules['distutils.command.build_ext'].Extension = ( + setuptools.extension.Extension + ) + + patch_for_msvc_specialized_compiler() + + +def _patch_distribution_metadata(): + """Patch write_pkg_file and read_pkg_file for higher metadata standards""" + for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'): + new_val = getattr(setuptools.dist, attr) + setattr(distutils.dist.DistributionMetadata, attr, new_val) + + +def patch_func(replacement, target_mod, func_name): + """ + Patch func_name in target_mod with replacement + + Important - original must be resolved by name to avoid + patching an already patched function. + """ + original = getattr(target_mod, func_name) + + # set the 'unpatched' attribute on the replacement to + # point to the original. + vars(replacement).setdefault('unpatched', original) + + # replace the function in the original module + setattr(target_mod, func_name, replacement) + + +def get_unpatched_function(candidate): + return getattr(candidate, 'unpatched') + + +def patch_for_msvc_specialized_compiler(): + """ + Patch functions in distutils to use standalone Microsoft Visual C++ + compilers. + """ + # import late to avoid circular imports on Python < 3.5 + msvc = import_module('setuptools.msvc') + + if platform.system() != 'Windows': + # Compilers only availables on Microsoft Windows + return + + def patch_params(mod_name, func_name): + """ + Prepare the parameters for patch_func to patch indicated function. + """ + repl_prefix = 'msvc9_' if 'msvc9' in mod_name else 'msvc14_' + repl_name = repl_prefix + func_name.lstrip('_') + repl = getattr(msvc, repl_name) + mod = import_module(mod_name) + if not hasattr(mod, func_name): + raise ImportError(func_name) + return repl, mod, func_name + + # Python 2.7 to 3.4 + msvc9 = functools.partial(patch_params, 'distutils.msvc9compiler') + + # Python 3.5+ + msvc14 = functools.partial(patch_params, 'distutils._msvccompiler') + + try: + # Patch distutils.msvc9compiler + patch_func(*msvc9('find_vcvarsall')) + patch_func(*msvc9('query_vcvarsall')) + except ImportError: + pass + + try: + # Patch distutils._msvccompiler._get_vc_env + patch_func(*msvc14('_get_vc_env')) + except ImportError: + pass + + try: + # Patch distutils._msvccompiler.gen_lib_options for Numpy + patch_func(*msvc14('gen_lib_options')) + except ImportError: + pass diff --git a/lib/setuptools/msvc.py b/lib/setuptools/msvc.py new file mode 100644 index 0000000..b9c472f --- /dev/null +++ b/lib/setuptools/msvc.py @@ -0,0 +1,1301 @@ +""" +Improved support for Microsoft Visual C++ compilers. + +Known supported compilers: +-------------------------- +Microsoft Visual C++ 9.0: + Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) + Microsoft Windows SDK 6.1 (x86, x64, ia64) + Microsoft Windows SDK 7.0 (x86, x64, ia64) + +Microsoft Visual C++ 10.0: + Microsoft Windows SDK 7.1 (x86, x64, ia64) + +Microsoft Visual C++ 14.0: + Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) + Microsoft Visual Studio 2017 (x86, x64, arm, arm64) + Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) +""" + +import os +import sys +import platform +import itertools +import distutils.errors +from setuptools.extern.packaging.version import LegacyVersion + +from setuptools.extern.six.moves import filterfalse + +from .monkey import get_unpatched + +if platform.system() == 'Windows': + from setuptools.extern.six.moves import winreg + safe_env = os.environ +else: + """ + Mock winreg and environ so the module can be imported + on this platform. + """ + + class winreg: + HKEY_USERS = None + HKEY_CURRENT_USER = None + HKEY_LOCAL_MACHINE = None + HKEY_CLASSES_ROOT = None + + safe_env = dict() + +_msvc9_suppress_errors = ( + # msvc9compiler isn't available on some platforms + ImportError, + + # msvc9compiler raises DistutilsPlatformError in some + # environments. See #1118. + distutils.errors.DistutilsPlatformError, +) + +try: + from distutils.msvc9compiler import Reg +except _msvc9_suppress_errors: + pass + + +def msvc9_find_vcvarsall(version): + """ + Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone + compiler build for Python (VCForPython). Fall back to original behavior + when the standalone compiler is not available. + + Redirect the path of "vcvarsall.bat". + + Known supported compilers + ------------------------- + Microsoft Visual C++ 9.0: + Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) + + Parameters + ---------- + version: float + Required Microsoft Visual C++ version. + + Return + ------ + vcvarsall.bat path: str + """ + VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f' + key = VC_BASE % ('', version) + try: + # Per-user installs register the compiler path here + productdir = Reg.get_value(key, "installdir") + except KeyError: + try: + # All-user installs on a 64-bit system register here + key = VC_BASE % ('Wow6432Node\\', version) + productdir = Reg.get_value(key, "installdir") + except KeyError: + productdir = None + + if productdir: + vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat") + if os.path.isfile(vcvarsall): + return vcvarsall + + return get_unpatched(msvc9_find_vcvarsall)(version) + + +def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs): + """ + Patched "distutils.msvc9compiler.query_vcvarsall" for support extra + compilers. + + Set environment without use of "vcvarsall.bat". + + Known supported compilers + ------------------------- + Microsoft Visual C++ 9.0: + Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) + Microsoft Windows SDK 6.1 (x86, x64, ia64) + Microsoft Windows SDK 7.0 (x86, x64, ia64) + + Microsoft Visual C++ 10.0: + Microsoft Windows SDK 7.1 (x86, x64, ia64) + + Parameters + ---------- + ver: float + Required Microsoft Visual C++ version. + arch: str + Target architecture. + + Return + ------ + environment: dict + """ + # Try to get environement from vcvarsall.bat (Classical way) + try: + orig = get_unpatched(msvc9_query_vcvarsall) + return orig(ver, arch, *args, **kwargs) + except distutils.errors.DistutilsPlatformError: + # Pass error if Vcvarsall.bat is missing + pass + except ValueError: + # Pass error if environment not set after executing vcvarsall.bat + pass + + # If error, try to set environment directly + try: + return EnvironmentInfo(arch, ver).return_env() + except distutils.errors.DistutilsPlatformError as exc: + _augment_exception(exc, ver, arch) + raise + + +def msvc14_get_vc_env(plat_spec): + """ + Patched "distutils._msvccompiler._get_vc_env" for support extra + compilers. + + Set environment without use of "vcvarsall.bat". + + Known supported compilers + ------------------------- + Microsoft Visual C++ 14.0: + Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) + Microsoft Visual Studio 2017 (x86, x64, arm, arm64) + Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) + + Parameters + ---------- + plat_spec: str + Target architecture. + + Return + ------ + environment: dict + """ + # Try to get environment from vcvarsall.bat (Classical way) + try: + return get_unpatched(msvc14_get_vc_env)(plat_spec) + except distutils.errors.DistutilsPlatformError: + # Pass error Vcvarsall.bat is missing + pass + + # If error, try to set environment directly + try: + return EnvironmentInfo(plat_spec, vc_min_ver=14.0).return_env() + except distutils.errors.DistutilsPlatformError as exc: + _augment_exception(exc, 14.0) + raise + + +def msvc14_gen_lib_options(*args, **kwargs): + """ + Patched "distutils._msvccompiler.gen_lib_options" for fix + compatibility between "numpy.distutils" and "distutils._msvccompiler" + (for Numpy < 1.11.2) + """ + if "numpy.distutils" in sys.modules: + import numpy as np + if LegacyVersion(np.__version__) < LegacyVersion('1.11.2'): + return np.distutils.ccompiler.gen_lib_options(*args, **kwargs) + return get_unpatched(msvc14_gen_lib_options)(*args, **kwargs) + + +def _augment_exception(exc, version, arch=''): + """ + Add details to the exception message to help guide the user + as to what action will resolve it. + """ + # Error if MSVC++ directory not found or environment not set + message = exc.args[0] + + if "vcvarsall" in message.lower() or "visual c" in message.lower(): + # Special error message if MSVC++ not installed + tmpl = 'Microsoft Visual C++ {version:0.1f} is required.' + message = tmpl.format(**locals()) + msdownload = 'www.microsoft.com/download/details.aspx?id=%d' + if version == 9.0: + if arch.lower().find('ia64') > -1: + # For VC++ 9.0, if IA64 support is needed, redirect user + # to Windows SDK 7.0 + message += ' Get it with "Microsoft Windows SDK 7.0": ' + message += msdownload % 3138 + else: + # For VC++ 9.0 redirect user to Vc++ for Python 2.7 : + # This redirection link is maintained by Microsoft. + # Contact vspython@microsoft.com if it needs updating. + message += ' Get it from http://aka.ms/vcpython27' + elif version == 10.0: + # For VC++ 10.0 Redirect user to Windows SDK 7.1 + message += ' Get it with "Microsoft Windows SDK 7.1": ' + message += msdownload % 8279 + elif version >= 14.0: + # For VC++ 14.0 Redirect user to Visual C++ Build Tools + message += (' Get it with "Microsoft Visual C++ Build Tools": ' + r'https://visualstudio.microsoft.com/downloads/') + + exc.args = (message, ) + + +class PlatformInfo: + """ + Current and Target Architectures informations. + + Parameters + ---------- + arch: str + Target architecture. + """ + current_cpu = safe_env.get('processor_architecture', '').lower() + + def __init__(self, arch): + self.arch = arch.lower().replace('x64', 'amd64') + + @property + def target_cpu(self): + return self.arch[self.arch.find('_') + 1:] + + def target_is_x86(self): + return self.target_cpu == 'x86' + + def current_is_x86(self): + return self.current_cpu == 'x86' + + def current_dir(self, hidex86=False, x64=False): + """ + Current platform specific subfolder. + + Parameters + ---------- + hidex86: bool + return '' and not '\x86' if architecture is x86. + x64: bool + return '\x64' and not '\amd64' if architecture is amd64. + + Return + ------ + subfolder: str + '\target', or '' (see hidex86 parameter) + """ + return ( + '' if (self.current_cpu == 'x86' and hidex86) else + r'\x64' if (self.current_cpu == 'amd64' and x64) else + r'\%s' % self.current_cpu + ) + + def target_dir(self, hidex86=False, x64=False): + r""" + Target platform specific subfolder. + + Parameters + ---------- + hidex86: bool + return '' and not '\x86' if architecture is x86. + x64: bool + return '\x64' and not '\amd64' if architecture is amd64. + + Return + ------ + subfolder: str + '\current', or '' (see hidex86 parameter) + """ + return ( + '' if (self.target_cpu == 'x86' and hidex86) else + r'\x64' if (self.target_cpu == 'amd64' and x64) else + r'\%s' % self.target_cpu + ) + + def cross_dir(self, forcex86=False): + r""" + Cross platform specific subfolder. + + Parameters + ---------- + forcex86: bool + Use 'x86' as current architecture even if current acritecture is + not x86. + + Return + ------ + subfolder: str + '' if target architecture is current architecture, + '\current_target' if not. + """ + current = 'x86' if forcex86 else self.current_cpu + return ( + '' if self.target_cpu == current else + self.target_dir().replace('\\', '\\%s_' % current) + ) + + +class RegistryInfo: + """ + Microsoft Visual Studio related registry informations. + + Parameters + ---------- + platform_info: PlatformInfo + "PlatformInfo" instance. + """ + HKEYS = (winreg.HKEY_USERS, + winreg.HKEY_CURRENT_USER, + winreg.HKEY_LOCAL_MACHINE, + winreg.HKEY_CLASSES_ROOT) + + def __init__(self, platform_info): + self.pi = platform_info + + @property + def visualstudio(self): + """ + Microsoft Visual Studio root registry key. + """ + return 'VisualStudio' + + @property + def sxs(self): + """ + Microsoft Visual Studio SxS registry key. + """ + return os.path.join(self.visualstudio, 'SxS') + + @property + def vc(self): + """ + Microsoft Visual C++ VC7 registry key. + """ + return os.path.join(self.sxs, 'VC7') + + @property + def vs(self): + """ + Microsoft Visual Studio VS7 registry key. + """ + return os.path.join(self.sxs, 'VS7') + + @property + def vc_for_python(self): + """ + Microsoft Visual C++ for Python registry key. + """ + return r'DevDiv\VCForPython' + + @property + def microsoft_sdk(self): + """ + Microsoft SDK registry key. + """ + return 'Microsoft SDKs' + + @property + def windows_sdk(self): + """ + Microsoft Windows/Platform SDK registry key. + """ + return os.path.join(self.microsoft_sdk, 'Windows') + + @property + def netfx_sdk(self): + """ + Microsoft .NET Framework SDK registry key. + """ + return os.path.join(self.microsoft_sdk, 'NETFXSDK') + + @property + def windows_kits_roots(self): + """ + Microsoft Windows Kits Roots registry key. + """ + return r'Windows Kits\Installed Roots' + + def microsoft(self, key, x86=False): + """ + Return key in Microsoft software registry. + + Parameters + ---------- + key: str + Registry key path where look. + x86: str + Force x86 software registry. + + Return + ------ + str: value + """ + node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node' + return os.path.join('Software', node64, 'Microsoft', key) + + def lookup(self, key, name): + """ + Look for values in registry in Microsoft software registry. + + Parameters + ---------- + key: str + Registry key path where look. + name: str + Value name to find. + + Return + ------ + str: value + """ + KEY_READ = winreg.KEY_READ + openkey = winreg.OpenKey + ms = self.microsoft + for hkey in self.HKEYS: + try: + bkey = openkey(hkey, ms(key), 0, KEY_READ) + except (OSError, IOError): + if not self.pi.current_is_x86(): + try: + bkey = openkey(hkey, ms(key, True), 0, KEY_READ) + except (OSError, IOError): + continue + else: + continue + try: + return winreg.QueryValueEx(bkey, name)[0] + except (OSError, IOError): + pass + + +class SystemInfo: + """ + Microsoft Windows and Visual Studio related system inormations. + + Parameters + ---------- + registry_info: RegistryInfo + "RegistryInfo" instance. + vc_ver: float + Required Microsoft Visual C++ version. + """ + + # Variables and properties in this class use originals CamelCase variables + # names from Microsoft source files for more easy comparaison. + WinDir = safe_env.get('WinDir', '') + ProgramFiles = safe_env.get('ProgramFiles', '') + ProgramFilesx86 = safe_env.get('ProgramFiles(x86)', ProgramFiles) + + def __init__(self, registry_info, vc_ver=None): + self.ri = registry_info + self.pi = self.ri.pi + self.vc_ver = vc_ver or self._find_latest_available_vc_ver() + + def _find_latest_available_vc_ver(self): + try: + return self.find_available_vc_vers()[-1] + except IndexError: + err = 'No Microsoft Visual C++ version found' + raise distutils.errors.DistutilsPlatformError(err) + + def find_available_vc_vers(self): + """ + Find all available Microsoft Visual C++ versions. + """ + ms = self.ri.microsoft + vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs) + vc_vers = [] + for hkey in self.ri.HKEYS: + for key in vckeys: + try: + bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ) + except (OSError, IOError): + continue + subkeys, values, _ = winreg.QueryInfoKey(bkey) + for i in range(values): + try: + ver = float(winreg.EnumValue(bkey, i)[0]) + if ver not in vc_vers: + vc_vers.append(ver) + except ValueError: + pass + for i in range(subkeys): + try: + ver = float(winreg.EnumKey(bkey, i)) + if ver not in vc_vers: + vc_vers.append(ver) + except ValueError: + pass + return sorted(vc_vers) + + @property + def VSInstallDir(self): + """ + Microsoft Visual Studio directory. + """ + # Default path + name = 'Microsoft Visual Studio %0.1f' % self.vc_ver + default = os.path.join(self.ProgramFilesx86, name) + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vs, '%0.1f' % self.vc_ver) or default + + @property + def VCInstallDir(self): + """ + Microsoft Visual C++ directory. + """ + self.VSInstallDir + + guess_vc = self._guess_vc() or self._guess_vc_legacy() + + # Try to get "VC++ for Python" path from registry as default path + reg_path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) + python_vc = self.ri.lookup(reg_path, 'installdir') + default_vc = os.path.join(python_vc, 'VC') if python_vc else guess_vc + + # Try to get path from registry, if fail use default path + path = self.ri.lookup(self.ri.vc, '%0.1f' % self.vc_ver) or default_vc + + if not os.path.isdir(path): + msg = 'Microsoft Visual C++ directory not found' + raise distutils.errors.DistutilsPlatformError(msg) + + return path + + def _guess_vc(self): + """ + Locate Visual C for 2017 + """ + if self.vc_ver <= 14.0: + return + + default = r'VC\Tools\MSVC' + guess_vc = os.path.join(self.VSInstallDir, default) + # Subdir with VC exact version as name + try: + vc_exact_ver = os.listdir(guess_vc)[-1] + return os.path.join(guess_vc, vc_exact_ver) + except (OSError, IOError, IndexError): + pass + + def _guess_vc_legacy(self): + """ + Locate Visual C for versions prior to 2017 + """ + default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver + return os.path.join(self.ProgramFilesx86, default) + + @property + def WindowsSdkVersion(self): + """ + Microsoft Windows SDK versions for specified MSVC++ version. + """ + if self.vc_ver <= 9.0: + return ('7.0', '6.1', '6.0a') + elif self.vc_ver == 10.0: + return ('7.1', '7.0a') + elif self.vc_ver == 11.0: + return ('8.0', '8.0a') + elif self.vc_ver == 12.0: + return ('8.1', '8.1a') + elif self.vc_ver >= 14.0: + return ('10.0', '8.1') + + @property + def WindowsSdkLastVersion(self): + """ + Microsoft Windows SDK last version + """ + return self._use_last_dir_name(os.path.join( + self.WindowsSdkDir, 'lib')) + + @property + def WindowsSdkDir(self): + """ + Microsoft Windows SDK directory. + """ + sdkdir = '' + for ver in self.WindowsSdkVersion: + # Try to get it from registry + loc = os.path.join(self.ri.windows_sdk, 'v%s' % ver) + sdkdir = self.ri.lookup(loc, 'installationfolder') + if sdkdir: + break + if not sdkdir or not os.path.isdir(sdkdir): + # Try to get "VC++ for Python" version from registry + path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) + install_base = self.ri.lookup(path, 'installdir') + if install_base: + sdkdir = os.path.join(install_base, 'WinSDK') + if not sdkdir or not os.path.isdir(sdkdir): + # If fail, use default new path + for ver in self.WindowsSdkVersion: + intver = ver[:ver.rfind('.')] + path = r'Microsoft SDKs\Windows Kits\%s' % (intver) + d = os.path.join(self.ProgramFiles, path) + if os.path.isdir(d): + sdkdir = d + if not sdkdir or not os.path.isdir(sdkdir): + # If fail, use default old path + for ver in self.WindowsSdkVersion: + path = r'Microsoft SDKs\Windows\v%s' % ver + d = os.path.join(self.ProgramFiles, path) + if os.path.isdir(d): + sdkdir = d + if not sdkdir: + # If fail, use Platform SDK + sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK') + return sdkdir + + @property + def WindowsSDKExecutablePath(self): + """ + Microsoft Windows SDK executable directory. + """ + # Find WinSDK NetFx Tools registry dir name + if self.vc_ver <= 11.0: + netfxver = 35 + arch = '' + else: + netfxver = 40 + hidex86 = True if self.vc_ver <= 12.0 else False + arch = self.pi.current_dir(x64=True, hidex86=hidex86) + fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-')) + + # liste all possibles registry paths + regpaths = [] + if self.vc_ver >= 14.0: + for ver in self.NetFxSdkVersion: + regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)] + + for ver in self.WindowsSdkVersion: + regpaths += [os.path.join(self.ri.windows_sdk, 'v%sA' % ver, fx)] + + # Return installation folder from the more recent path + for path in regpaths: + execpath = self.ri.lookup(path, 'installationfolder') + if execpath: + break + return execpath + + @property + def FSharpInstallDir(self): + """ + Microsoft Visual F# directory. + """ + path = r'%0.1f\Setup\F#' % self.vc_ver + path = os.path.join(self.ri.visualstudio, path) + return self.ri.lookup(path, 'productdir') or '' + + @property + def UniversalCRTSdkDir(self): + """ + Microsoft Universal CRT SDK directory. + """ + # Set Kit Roots versions for specified MSVC++ version + if self.vc_ver >= 14.0: + vers = ('10', '81') + else: + vers = () + + # Find path of the more recent Kit + for ver in vers: + sdkdir = self.ri.lookup(self.ri.windows_kits_roots, + 'kitsroot%s' % ver) + if sdkdir: + break + return sdkdir or '' + + @property + def UniversalCRTSdkLastVersion(self): + """ + Microsoft Universal C Runtime SDK last version + """ + return self._use_last_dir_name(os.path.join( + self.UniversalCRTSdkDir, 'lib')) + + @property + def NetFxSdkVersion(self): + """ + Microsoft .NET Framework SDK versions. + """ + # Set FxSdk versions for specified MSVC++ version + if self.vc_ver >= 14.0: + return ('4.6.1', '4.6') + else: + return () + + @property + def NetFxSdkDir(self): + """ + Microsoft .NET Framework SDK directory. + """ + for ver in self.NetFxSdkVersion: + loc = os.path.join(self.ri.netfx_sdk, ver) + sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder') + if sdkdir: + break + return sdkdir or '' + + @property + def FrameworkDir32(self): + """ + Microsoft .NET Framework 32bit directory. + """ + # Default path + guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework') + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw + + @property + def FrameworkDir64(self): + """ + Microsoft .NET Framework 64bit directory. + """ + # Default path + guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64') + + # Try to get path from registry, if fail use default path + return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw + + @property + def FrameworkVersion32(self): + """ + Microsoft .NET Framework 32bit versions. + """ + return self._find_dot_net_versions(32) + + @property + def FrameworkVersion64(self): + """ + Microsoft .NET Framework 64bit versions. + """ + return self._find_dot_net_versions(64) + + def _find_dot_net_versions(self, bits): + """ + Find Microsoft .NET Framework versions. + + Parameters + ---------- + bits: int + Platform number of bits: 32 or 64. + """ + # Find actual .NET version in registry + reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits) + dot_net_dir = getattr(self, 'FrameworkDir%d' % bits) + ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or '' + + # Set .NET versions for specified MSVC++ version + if self.vc_ver >= 12.0: + frameworkver = (ver, 'v4.0') + elif self.vc_ver >= 10.0: + frameworkver = ('v4.0.30319' if ver.lower()[:2] != 'v4' else ver, + 'v3.5') + elif self.vc_ver == 9.0: + frameworkver = ('v3.5', 'v2.0.50727') + if self.vc_ver == 8.0: + frameworkver = ('v3.0', 'v2.0.50727') + return frameworkver + + def _use_last_dir_name(self, path, prefix=''): + """ + Return name of the last dir in path or '' if no dir found. + + Parameters + ---------- + path: str + Use dirs in this path + prefix: str + Use only dirs startings by this prefix + """ + matching_dirs = ( + dir_name + for dir_name in reversed(os.listdir(path)) + if os.path.isdir(os.path.join(path, dir_name)) and + dir_name.startswith(prefix) + ) + return next(matching_dirs, None) or '' + + +class EnvironmentInfo: + """ + Return environment variables for specified Microsoft Visual C++ version + and platform : Lib, Include, Path and libpath. + + This function is compatible with Microsoft Visual C++ 9.0 to 14.0. + + Script created by analysing Microsoft environment configuration files like + "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ... + + Parameters + ---------- + arch: str + Target architecture. + vc_ver: float + Required Microsoft Visual C++ version. If not set, autodetect the last + version. + vc_min_ver: float + Minimum Microsoft Visual C++ version. + """ + + # Variables and properties in this class use originals CamelCase variables + # names from Microsoft source files for more easy comparaison. + + def __init__(self, arch, vc_ver=None, vc_min_ver=0): + self.pi = PlatformInfo(arch) + self.ri = RegistryInfo(self.pi) + self.si = SystemInfo(self.ri, vc_ver) + + if self.vc_ver < vc_min_ver: + err = 'No suitable Microsoft Visual C++ version found' + raise distutils.errors.DistutilsPlatformError(err) + + @property + def vc_ver(self): + """ + Microsoft Visual C++ version. + """ + return self.si.vc_ver + + @property + def VSTools(self): + """ + Microsoft Visual Studio Tools + """ + paths = [r'Common7\IDE', r'Common7\Tools'] + + if self.vc_ver >= 14.0: + arch_subdir = self.pi.current_dir(hidex86=True, x64=True) + paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow'] + paths += [r'Team Tools\Performance Tools'] + paths += [r'Team Tools\Performance Tools%s' % arch_subdir] + + return [os.path.join(self.si.VSInstallDir, path) for path in paths] + + @property + def VCIncludes(self): + """ + Microsoft Visual C++ & Microsoft Foundation Class Includes + """ + return [os.path.join(self.si.VCInstallDir, 'Include'), + os.path.join(self.si.VCInstallDir, r'ATLMFC\Include')] + + @property + def VCLibraries(self): + """ + Microsoft Visual C++ & Microsoft Foundation Class Libraries + """ + if self.vc_ver >= 15.0: + arch_subdir = self.pi.target_dir(x64=True) + else: + arch_subdir = self.pi.target_dir(hidex86=True) + paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir] + + if self.vc_ver >= 14.0: + paths += [r'Lib\store%s' % arch_subdir] + + return [os.path.join(self.si.VCInstallDir, path) for path in paths] + + @property + def VCStoreRefs(self): + """ + Microsoft Visual C++ store references Libraries + """ + if self.vc_ver < 14.0: + return [] + return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')] + + @property + def VCTools(self): + """ + Microsoft Visual C++ Tools + """ + si = self.si + tools = [os.path.join(si.VCInstallDir, 'VCPackages')] + + forcex86 = True if self.vc_ver <= 10.0 else False + arch_subdir = self.pi.cross_dir(forcex86) + if arch_subdir: + tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)] + + if self.vc_ver == 14.0: + path = 'Bin%s' % self.pi.current_dir(hidex86=True) + tools += [os.path.join(si.VCInstallDir, path)] + + elif self.vc_ver >= 15.0: + host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else + r'bin\HostX64%s') + tools += [os.path.join( + si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))] + + if self.pi.current_cpu != self.pi.target_cpu: + tools += [os.path.join( + si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))] + + else: + tools += [os.path.join(si.VCInstallDir, 'Bin')] + + return tools + + @property + def OSLibraries(self): + """ + Microsoft Windows SDK Libraries + """ + if self.vc_ver <= 10.0: + arch_subdir = self.pi.target_dir(hidex86=True, x64=True) + return [os.path.join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)] + + else: + arch_subdir = self.pi.target_dir(x64=True) + lib = os.path.join(self.si.WindowsSdkDir, 'lib') + libver = self._sdk_subdir + return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))] + + @property + def OSIncludes(self): + """ + Microsoft Windows SDK Include + """ + include = os.path.join(self.si.WindowsSdkDir, 'include') + + if self.vc_ver <= 10.0: + return [include, os.path.join(include, 'gl')] + + else: + if self.vc_ver >= 14.0: + sdkver = self._sdk_subdir + else: + sdkver = '' + return [os.path.join(include, '%sshared' % sdkver), + os.path.join(include, '%sum' % sdkver), + os.path.join(include, '%swinrt' % sdkver)] + + @property + def OSLibpath(self): + """ + Microsoft Windows SDK Libraries Paths + """ + ref = os.path.join(self.si.WindowsSdkDir, 'References') + libpath = [] + + if self.vc_ver <= 9.0: + libpath += self.OSLibraries + + if self.vc_ver >= 11.0: + libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')] + + if self.vc_ver >= 14.0: + libpath += [ + ref, + os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'), + os.path.join( + ref, + 'Windows.Foundation.UniversalApiContract', + '1.0.0.0', + ), + os.path.join( + ref, + 'Windows.Foundation.FoundationContract', + '1.0.0.0', + ), + os.path.join( + ref, + 'Windows.Networking.Connectivity.WwanContract', + '1.0.0.0', + ), + os.path.join( + self.si.WindowsSdkDir, + 'ExtensionSDKs', + 'Microsoft.VCLibs', + '%0.1f' % self.vc_ver, + 'References', + 'CommonConfiguration', + 'neutral', + ), + ] + return libpath + + @property + def SdkTools(self): + """ + Microsoft Windows SDK Tools + """ + return list(self._sdk_tools()) + + def _sdk_tools(self): + """ + Microsoft Windows SDK Tools paths generator + """ + if self.vc_ver < 15.0: + bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86' + yield os.path.join(self.si.WindowsSdkDir, bin_dir) + + if not self.pi.current_is_x86(): + arch_subdir = self.pi.current_dir(x64=True) + path = 'Bin%s' % arch_subdir + yield os.path.join(self.si.WindowsSdkDir, path) + + if self.vc_ver == 10.0 or self.vc_ver == 11.0: + if self.pi.target_is_x86(): + arch_subdir = '' + else: + arch_subdir = self.pi.current_dir(hidex86=True, x64=True) + path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir + yield os.path.join(self.si.WindowsSdkDir, path) + + elif self.vc_ver >= 15.0: + path = os.path.join(self.si.WindowsSdkDir, 'Bin') + arch_subdir = self.pi.current_dir(x64=True) + sdkver = self.si.WindowsSdkLastVersion + yield os.path.join(path, '%s%s' % (sdkver, arch_subdir)) + + if self.si.WindowsSDKExecutablePath: + yield self.si.WindowsSDKExecutablePath + + @property + def _sdk_subdir(self): + """ + Microsoft Windows SDK version subdir + """ + ucrtver = self.si.WindowsSdkLastVersion + return ('%s\\' % ucrtver) if ucrtver else '' + + @property + def SdkSetup(self): + """ + Microsoft Windows SDK Setup + """ + if self.vc_ver > 9.0: + return [] + + return [os.path.join(self.si.WindowsSdkDir, 'Setup')] + + @property + def FxTools(self): + """ + Microsoft .NET Framework Tools + """ + pi = self.pi + si = self.si + + if self.vc_ver <= 10.0: + include32 = True + include64 = not pi.target_is_x86() and not pi.current_is_x86() + else: + include32 = pi.target_is_x86() or pi.current_is_x86() + include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64' + + tools = [] + if include32: + tools += [os.path.join(si.FrameworkDir32, ver) + for ver in si.FrameworkVersion32] + if include64: + tools += [os.path.join(si.FrameworkDir64, ver) + for ver in si.FrameworkVersion64] + return tools + + @property + def NetFxSDKLibraries(self): + """ + Microsoft .Net Framework SDK Libraries + """ + if self.vc_ver < 14.0 or not self.si.NetFxSdkDir: + return [] + + arch_subdir = self.pi.target_dir(x64=True) + return [os.path.join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)] + + @property + def NetFxSDKIncludes(self): + """ + Microsoft .Net Framework SDK Includes + """ + if self.vc_ver < 14.0 or not self.si.NetFxSdkDir: + return [] + + return [os.path.join(self.si.NetFxSdkDir, r'include\um')] + + @property + def VsTDb(self): + """ + Microsoft Visual Studio Team System Database + """ + return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')] + + @property + def MSBuild(self): + """ + Microsoft Build Engine + """ + if self.vc_ver < 12.0: + return [] + elif self.vc_ver < 15.0: + base_path = self.si.ProgramFilesx86 + arch_subdir = self.pi.current_dir(hidex86=True) + else: + base_path = self.si.VSInstallDir + arch_subdir = '' + + path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir) + build = [os.path.join(base_path, path)] + + if self.vc_ver >= 15.0: + # Add Roslyn C# & Visual Basic Compiler + build += [os.path.join(base_path, path, 'Roslyn')] + + return build + + @property + def HTMLHelpWorkshop(self): + """ + Microsoft HTML Help Workshop + """ + if self.vc_ver < 11.0: + return [] + + return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')] + + @property + def UCRTLibraries(self): + """ + Microsoft Universal C Runtime SDK Libraries + """ + if self.vc_ver < 14.0: + return [] + + arch_subdir = self.pi.target_dir(x64=True) + lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib') + ucrtver = self._ucrt_subdir + return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))] + + @property + def UCRTIncludes(self): + """ + Microsoft Universal C Runtime SDK Include + """ + if self.vc_ver < 14.0: + return [] + + include = os.path.join(self.si.UniversalCRTSdkDir, 'include') + return [os.path.join(include, '%sucrt' % self._ucrt_subdir)] + + @property + def _ucrt_subdir(self): + """ + Microsoft Universal C Runtime SDK version subdir + """ + ucrtver = self.si.UniversalCRTSdkLastVersion + return ('%s\\' % ucrtver) if ucrtver else '' + + @property + def FSharp(self): + """ + Microsoft Visual F# + """ + if self.vc_ver < 11.0 and self.vc_ver > 12.0: + return [] + + return self.si.FSharpInstallDir + + @property + def VCRuntimeRedist(self): + """ + Microsoft Visual C++ runtime redistribuable dll + """ + arch_subdir = self.pi.target_dir(x64=True) + if self.vc_ver < 15: + redist_path = self.si.VCInstallDir + vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll' + else: + redist_path = self.si.VCInstallDir.replace('\\Tools', '\\Redist') + vcruntime = 'onecore%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll' + + # Visual Studio 2017 is still Visual C++ 14.0 + dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver + + vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver) + return os.path.join(redist_path, vcruntime) + + def return_env(self, exists=True): + """ + Return environment dict. + + Parameters + ---------- + exists: bool + It True, only return existing paths. + """ + env = dict( + include=self._build_paths('include', + [self.VCIncludes, + self.OSIncludes, + self.UCRTIncludes, + self.NetFxSDKIncludes], + exists), + lib=self._build_paths('lib', + [self.VCLibraries, + self.OSLibraries, + self.FxTools, + self.UCRTLibraries, + self.NetFxSDKLibraries], + exists), + libpath=self._build_paths('libpath', + [self.VCLibraries, + self.FxTools, + self.VCStoreRefs, + self.OSLibpath], + exists), + path=self._build_paths('path', + [self.VCTools, + self.VSTools, + self.VsTDb, + self.SdkTools, + self.SdkSetup, + self.FxTools, + self.MSBuild, + self.HTMLHelpWorkshop, + self.FSharp], + exists), + ) + if self.vc_ver >= 14 and os.path.isfile(self.VCRuntimeRedist): + env['py_vcruntime_redist'] = self.VCRuntimeRedist + return env + + def _build_paths(self, name, spec_path_lists, exists): + """ + Given an environment variable name and specified paths, + return a pathsep-separated string of paths containing + unique, extant, directories from those paths and from + the environment variable. Raise an error if no paths + are resolved. + """ + # flatten spec_path_lists + spec_paths = itertools.chain.from_iterable(spec_path_lists) + env_paths = safe_env.get(name, '').split(os.pathsep) + paths = itertools.chain(spec_paths, env_paths) + extant_paths = list(filter(os.path.isdir, paths)) if exists else paths + if not extant_paths: + msg = "%s environment variable is empty" % name.upper() + raise distutils.errors.DistutilsPlatformError(msg) + unique_paths = self._unique_everseen(extant_paths) + return os.pathsep.join(unique_paths) + + # from Python docs + def _unique_everseen(self, iterable, key=None): + """ + List unique elements, preserving order. + Remember all elements ever seen. + + _unique_everseen('AAAABBBCCDAABBB') --> A B C D + + _unique_everseen('ABBCcAD', str.lower) --> A B C D + """ + seen = set() + seen_add = seen.add + if key is None: + for element in filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element diff --git a/lib/setuptools/namespaces.py b/lib/setuptools/namespaces.py new file mode 100644 index 0000000..dc16106 --- /dev/null +++ b/lib/setuptools/namespaces.py @@ -0,0 +1,107 @@ +import os +from distutils import log +import itertools + +from setuptools.extern.six.moves import map + + +flatten = itertools.chain.from_iterable + + +class Installer: + + nspkg_ext = '-nspkg.pth' + + def install_namespaces(self): + nsp = self._get_all_ns_packages() + if not nsp: + return + filename, ext = os.path.splitext(self._get_target()) + filename += self.nspkg_ext + self.outputs.append(filename) + log.info("Installing %s", filename) + lines = map(self._gen_nspkg_line, nsp) + + if self.dry_run: + # always generate the lines, even in dry run + list(lines) + return + + with open(filename, 'wt') as f: + f.writelines(lines) + + def uninstall_namespaces(self): + filename, ext = os.path.splitext(self._get_target()) + filename += self.nspkg_ext + if not os.path.exists(filename): + return + log.info("Removing %s", filename) + os.remove(filename) + + def _get_target(self): + return self.target + + _nspkg_tmpl = ( + "import sys, types, os", + "has_mfs = sys.version_info > (3, 5)", + "p = os.path.join(%(root)s, *%(pth)r)", + "importlib = has_mfs and __import__('importlib.util')", + "has_mfs and __import__('importlib.machinery')", + "m = has_mfs and " + "sys.modules.setdefault(%(pkg)r, " + "importlib.util.module_from_spec(" + "importlib.machinery.PathFinder.find_spec(%(pkg)r, " + "[os.path.dirname(p)])))", + "m = m or " + "sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))", + "mp = (m or []) and m.__dict__.setdefault('__path__',[])", + "(p not in mp) and mp.append(p)", + ) + "lines for the namespace installer" + + _nspkg_tmpl_multi = ( + 'm and setattr(sys.modules[%(parent)r], %(child)r, m)', + ) + "additional line(s) when a parent package is indicated" + + def _get_root(self): + return "sys._getframe(1).f_locals['sitedir']" + + def _gen_nspkg_line(self, pkg): + # ensure pkg is not a unicode string under Python 2.7 + pkg = str(pkg) + pth = tuple(pkg.split('.')) + root = self._get_root() + tmpl_lines = self._nspkg_tmpl + parent, sep, child = pkg.rpartition('.') + if parent: + tmpl_lines += self._nspkg_tmpl_multi + return ';'.join(tmpl_lines) % locals() + '\n' + + def _get_all_ns_packages(self): + """Return sorted list of all package namespaces""" + pkgs = self.distribution.namespace_packages or [] + return sorted(flatten(map(self._pkg_names, pkgs))) + + @staticmethod + def _pkg_names(pkg): + """ + Given a namespace package, yield the components of that + package. + + >>> names = Installer._pkg_names('a.b.c') + >>> set(names) == set(['a', 'a.b', 'a.b.c']) + True + """ + parts = pkg.split('.') + while parts: + yield '.'.join(parts) + parts.pop() + + +class DevelopInstaller(Installer): + def _get_root(self): + return repr(str(self.egg_path)) + + def _get_target(self): + return self.egg_link diff --git a/lib/setuptools/package_index.py b/lib/setuptools/package_index.py new file mode 100644 index 0000000..1608b91 --- /dev/null +++ b/lib/setuptools/package_index.py @@ -0,0 +1,1128 @@ +"""PyPI and direct package downloading""" +import sys +import os +import re +import shutil +import socket +import base64 +import hashlib +import itertools +import warnings +from functools import wraps + +from setuptools.extern import six +from setuptools.extern.six.moves import urllib, http_client, configparser, map + +import setuptools +from pkg_resources import ( + CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST, + Environment, find_distributions, safe_name, safe_version, + to_filename, Requirement, DEVELOP_DIST, EGG_DIST, +) +from setuptools import ssl_support +from distutils import log +from distutils.errors import DistutilsError +from fnmatch import translate +from setuptools.py27compat import get_all_headers +from setuptools.py33compat import unescape +from setuptools.wheel import Wheel + +__metaclass__ = type + +EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$') +HREF = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I) +PYPI_MD5 = re.compile( + r'<a href="([^"#]+)">([^<]+)</a>\n\s+\(<a (?:title="MD5 hash"\n\s+)' + r'href="[^?]+\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\)' +) +URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match +EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split() + +__all__ = [ + 'PackageIndex', 'distros_for_url', 'parse_bdist_wininst', + 'interpret_distro_name', +] + +_SOCKET_TIMEOUT = 15 + +_tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}" +user_agent = _tmpl.format(py_major=sys.version[:3], setuptools=setuptools) + + +def parse_requirement_arg(spec): + try: + return Requirement.parse(spec) + except ValueError: + raise DistutilsError( + "Not a URL, existing file, or requirement spec: %r" % (spec,) + ) + + +def parse_bdist_wininst(name): + """Return (base,pyversion) or (None,None) for possible .exe name""" + + lower = name.lower() + base, py_ver, plat = None, None, None + + if lower.endswith('.exe'): + if lower.endswith('.win32.exe'): + base = name[:-10] + plat = 'win32' + elif lower.startswith('.win32-py', -16): + py_ver = name[-7:-4] + base = name[:-16] + plat = 'win32' + elif lower.endswith('.win-amd64.exe'): + base = name[:-14] + plat = 'win-amd64' + elif lower.startswith('.win-amd64-py', -20): + py_ver = name[-7:-4] + base = name[:-20] + plat = 'win-amd64' + return base, py_ver, plat + + +def egg_info_for_url(url): + parts = urllib.parse.urlparse(url) + scheme, server, path, parameters, query, fragment = parts + base = urllib.parse.unquote(path.split('/')[-1]) + if server == 'sourceforge.net' and base == 'download': # XXX Yuck + base = urllib.parse.unquote(path.split('/')[-2]) + if '#' in base: + base, fragment = base.split('#', 1) + return base, fragment + + +def distros_for_url(url, metadata=None): + """Yield egg or source distribution objects that might be found at a URL""" + base, fragment = egg_info_for_url(url) + for dist in distros_for_location(url, base, metadata): + yield dist + if fragment: + match = EGG_FRAGMENT.match(fragment) + if match: + for dist in interpret_distro_name( + url, match.group(1), metadata, precedence=CHECKOUT_DIST + ): + yield dist + + +def distros_for_location(location, basename, metadata=None): + """Yield egg or source distribution objects based on basename""" + if basename.endswith('.egg.zip'): + basename = basename[:-4] # strip the .zip + if basename.endswith('.egg') and '-' in basename: + # only one, unambiguous interpretation + return [Distribution.from_location(location, basename, metadata)] + if basename.endswith('.whl') and '-' in basename: + wheel = Wheel(basename) + if not wheel.is_compatible(): + return [] + return [Distribution( + location=location, + project_name=wheel.project_name, + version=wheel.version, + # Increase priority over eggs. + precedence=EGG_DIST + 1, + )] + if basename.endswith('.exe'): + win_base, py_ver, platform = parse_bdist_wininst(basename) + if win_base is not None: + return interpret_distro_name( + location, win_base, metadata, py_ver, BINARY_DIST, platform + ) + # Try source distro extensions (.zip, .tgz, etc.) + # + for ext in EXTENSIONS: + if basename.endswith(ext): + basename = basename[:-len(ext)] + return interpret_distro_name(location, basename, metadata) + return [] # no extension matched + + +def distros_for_filename(filename, metadata=None): + """Yield possible egg or source distribution objects based on a filename""" + return distros_for_location( + normalize_path(filename), os.path.basename(filename), metadata + ) + + +def interpret_distro_name( + location, basename, metadata, py_version=None, precedence=SOURCE_DIST, + platform=None +): + """Generate alternative interpretations of a source distro name + + Note: if `location` is a filesystem filename, you should call + ``pkg_resources.normalize_path()`` on it before passing it to this + routine! + """ + # Generate alternative interpretations of a source distro name + # Because some packages are ambiguous as to name/versions split + # e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc. + # So, we generate each possible interepretation (e.g. "adns, python-1.1.0" + # "adns-python, 1.1.0", and "adns-python-1.1.0, no version"). In practice, + # the spurious interpretations should be ignored, because in the event + # there's also an "adns" package, the spurious "python-1.1.0" version will + # compare lower than any numeric version number, and is therefore unlikely + # to match a request for it. It's still a potential problem, though, and + # in the long run PyPI and the distutils should go for "safe" names and + # versions in distribution archive names (sdist and bdist). + + parts = basename.split('-') + if not py_version and any(re.match(r'py\d\.\d$', p) for p in parts[2:]): + # it is a bdist_dumb, not an sdist -- bail out + return + + for p in range(1, len(parts) + 1): + yield Distribution( + location, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]), + py_version=py_version, precedence=precedence, + platform=platform + ) + + +# From Python 2.7 docs +def unique_everseen(iterable, key=None): + "List unique elements, preserving order. Remember all elements ever seen." + # unique_everseen('AAAABBBCCDAABBB') --> A B C D + # unique_everseen('ABBCcAD', str.lower) --> A B C D + seen = set() + seen_add = seen.add + if key is None: + for element in six.moves.filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element + + +def unique_values(func): + """ + Wrap a function returning an iterable such that the resulting iterable + only ever yields unique items. + """ + + @wraps(func) + def wrapper(*args, **kwargs): + return unique_everseen(func(*args, **kwargs)) + + return wrapper + + +REL = re.compile(r"""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I) +# this line is here to fix emacs' cruddy broken syntax highlighting + + +@unique_values +def find_external_links(url, page): + """Find rel="homepage" and rel="download" links in `page`, yielding URLs""" + + for match in REL.finditer(page): + tag, rel = match.groups() + rels = set(map(str.strip, rel.lower().split(','))) + if 'homepage' in rels or 'download' in rels: + for match in HREF.finditer(tag): + yield urllib.parse.urljoin(url, htmldecode(match.group(1))) + + for tag in ("<th>Home Page", "<th>Download URL"): + pos = page.find(tag) + if pos != -1: + match = HREF.search(page, pos) + if match: + yield urllib.parse.urljoin(url, htmldecode(match.group(1))) + + +class ContentChecker: + """ + A null content checker that defines the interface for checking content + """ + + def feed(self, block): + """ + Feed a block of data to the hash. + """ + return + + def is_valid(self): + """ + Check the hash. Return False if validation fails. + """ + return True + + def report(self, reporter, template): + """ + Call reporter with information about the checker (hash name) + substituted into the template. + """ + return + + +class HashChecker(ContentChecker): + pattern = re.compile( + r'(?P<hash_name>sha1|sha224|sha384|sha256|sha512|md5)=' + r'(?P<expected>[a-f0-9]+)' + ) + + def __init__(self, hash_name, expected): + self.hash_name = hash_name + self.hash = hashlib.new(hash_name) + self.expected = expected + + @classmethod + def from_url(cls, url): + "Construct a (possibly null) ContentChecker from a URL" + fragment = urllib.parse.urlparse(url)[-1] + if not fragment: + return ContentChecker() + match = cls.pattern.search(fragment) + if not match: + return ContentChecker() + return cls(**match.groupdict()) + + def feed(self, block): + self.hash.update(block) + + def is_valid(self): + return self.hash.hexdigest() == self.expected + + def report(self, reporter, template): + msg = template % self.hash_name + return reporter(msg) + + +class PackageIndex(Environment): + """A distribution index that scans web pages for download URLs""" + + def __init__( + self, index_url="https://pypi.org/simple/", hosts=('*',), + ca_bundle=None, verify_ssl=True, *args, **kw + ): + Environment.__init__(self, *args, **kw) + self.index_url = index_url + "/" [:not index_url.endswith('/')] + self.scanned_urls = {} + self.fetched_urls = {} + self.package_pages = {} + self.allows = re.compile('|'.join(map(translate, hosts))).match + self.to_scan = [] + use_ssl = ( + verify_ssl + and ssl_support.is_available + and (ca_bundle or ssl_support.find_ca_bundle()) + ) + if use_ssl: + self.opener = ssl_support.opener_for(ca_bundle) + else: + self.opener = urllib.request.urlopen + + def process_url(self, url, retrieve=False): + """Evaluate a URL as a possible download, and maybe retrieve it""" + if url in self.scanned_urls and not retrieve: + return + self.scanned_urls[url] = True + if not URL_SCHEME(url): + self.process_filename(url) + return + else: + dists = list(distros_for_url(url)) + if dists: + if not self.url_ok(url): + return + self.debug("Found link: %s", url) + + if dists or not retrieve or url in self.fetched_urls: + list(map(self.add, dists)) + return # don't need the actual page + + if not self.url_ok(url): + self.fetched_urls[url] = True + return + + self.info("Reading %s", url) + self.fetched_urls[url] = True # prevent multiple fetch attempts + tmpl = "Download error on %s: %%s -- Some packages may not be found!" + f = self.open_url(url, tmpl % url) + if f is None: + return + self.fetched_urls[f.url] = True + if 'html' not in f.headers.get('content-type', '').lower(): + f.close() # not html, we can't process it + return + + base = f.url # handle redirects + page = f.read() + if not isinstance(page, str): + # In Python 3 and got bytes but want str. + if isinstance(f, urllib.error.HTTPError): + # Errors have no charset, assume latin1: + charset = 'latin-1' + else: + charset = f.headers.get_param('charset') or 'latin-1' + page = page.decode(charset, "ignore") + f.close() + for match in HREF.finditer(page): + link = urllib.parse.urljoin(base, htmldecode(match.group(1))) + self.process_url(link) + if url.startswith(self.index_url) and getattr(f, 'code', None) != 404: + page = self.process_index(url, page) + + def process_filename(self, fn, nested=False): + # process filenames or directories + if not os.path.exists(fn): + self.warn("Not found: %s", fn) + return + + if os.path.isdir(fn) and not nested: + path = os.path.realpath(fn) + for item in os.listdir(path): + self.process_filename(os.path.join(path, item), True) + + dists = distros_for_filename(fn) + if dists: + self.debug("Found: %s", fn) + list(map(self.add, dists)) + + def url_ok(self, url, fatal=False): + s = URL_SCHEME(url) + is_file = s and s.group(1).lower() == 'file' + if is_file or self.allows(urllib.parse.urlparse(url)[1]): + return True + msg = ( + "\nNote: Bypassing %s (disallowed host; see " + "http://bit.ly/2hrImnY for details).\n") + if fatal: + raise DistutilsError(msg % url) + else: + self.warn(msg, url) + + def scan_egg_links(self, search_path): + dirs = filter(os.path.isdir, search_path) + egg_links = ( + (path, entry) + for path in dirs + for entry in os.listdir(path) + if entry.endswith('.egg-link') + ) + list(itertools.starmap(self.scan_egg_link, egg_links)) + + def scan_egg_link(self, path, entry): + with open(os.path.join(path, entry)) as raw_lines: + # filter non-empty lines + lines = list(filter(None, map(str.strip, raw_lines))) + + if len(lines) != 2: + # format is not recognized; punt + return + + egg_path, setup_path = lines + + for dist in find_distributions(os.path.join(path, egg_path)): + dist.location = os.path.join(path, *lines) + dist.precedence = SOURCE_DIST + self.add(dist) + + def process_index(self, url, page): + """Process the contents of a PyPI page""" + + def scan(link): + # Process a URL to see if it's for a package page + if link.startswith(self.index_url): + parts = list(map( + urllib.parse.unquote, link[len(self.index_url):].split('/') + )) + if len(parts) == 2 and '#' not in parts[1]: + # it's a package page, sanitize and index it + pkg = safe_name(parts[0]) + ver = safe_version(parts[1]) + self.package_pages.setdefault(pkg.lower(), {})[link] = True + return to_filename(pkg), to_filename(ver) + return None, None + + # process an index page into the package-page index + for match in HREF.finditer(page): + try: + scan(urllib.parse.urljoin(url, htmldecode(match.group(1)))) + except ValueError: + pass + + pkg, ver = scan(url) # ensure this page is in the page index + if pkg: + # process individual package page + for new_url in find_external_links(url, page): + # Process the found URL + base, frag = egg_info_for_url(new_url) + if base.endswith('.py') and not frag: + if ver: + new_url += '#egg=%s-%s' % (pkg, ver) + else: + self.need_version_info(url) + self.scan_url(new_url) + + return PYPI_MD5.sub( + lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1, 3, 2), page + ) + else: + return "" # no sense double-scanning non-package pages + + def need_version_info(self, url): + self.scan_all( + "Page at %s links to .py file(s) without version info; an index " + "scan is required.", url + ) + + def scan_all(self, msg=None, *args): + if self.index_url not in self.fetched_urls: + if msg: + self.warn(msg, *args) + self.info( + "Scanning index of all packages (this may take a while)" + ) + self.scan_url(self.index_url) + + def find_packages(self, requirement): + self.scan_url(self.index_url + requirement.unsafe_name + '/') + + if not self.package_pages.get(requirement.key): + # Fall back to safe version of the name + self.scan_url(self.index_url + requirement.project_name + '/') + + if not self.package_pages.get(requirement.key): + # We couldn't find the target package, so search the index page too + self.not_found_in_index(requirement) + + for url in list(self.package_pages.get(requirement.key, ())): + # scan each page that might be related to the desired package + self.scan_url(url) + + def obtain(self, requirement, installer=None): + self.prescan() + self.find_packages(requirement) + for dist in self[requirement.key]: + if dist in requirement: + return dist + self.debug("%s does not match %s", requirement, dist) + return super(PackageIndex, self).obtain(requirement, installer) + + def check_hash(self, checker, filename, tfp): + """ + checker is a ContentChecker + """ + checker.report( + self.debug, + "Validating %%s checksum for %s" % filename) + if not checker.is_valid(): + tfp.close() + os.unlink(filename) + raise DistutilsError( + "%s validation failed for %s; " + "possible download problem?" + % (checker.hash.name, os.path.basename(filename)) + ) + + def add_find_links(self, urls): + """Add `urls` to the list that will be prescanned for searches""" + for url in urls: + if ( + self.to_scan is None # if we have already "gone online" + or not URL_SCHEME(url) # or it's a local file/directory + or url.startswith('file:') + or list(distros_for_url(url)) # or a direct package link + ): + # then go ahead and process it now + self.scan_url(url) + else: + # otherwise, defer retrieval till later + self.to_scan.append(url) + + def prescan(self): + """Scan urls scheduled for prescanning (e.g. --find-links)""" + if self.to_scan: + list(map(self.scan_url, self.to_scan)) + self.to_scan = None # from now on, go ahead and process immediately + + def not_found_in_index(self, requirement): + if self[requirement.key]: # we've seen at least one distro + meth, msg = self.info, "Couldn't retrieve index page for %r" + else: # no distros seen for this name, might be misspelled + meth, msg = ( + self.warn, + "Couldn't find index page for %r (maybe misspelled?)") + meth(msg, requirement.unsafe_name) + self.scan_all() + + def download(self, spec, tmpdir): + """Locate and/or download `spec` to `tmpdir`, returning a local path + + `spec` may be a ``Requirement`` object, or a string containing a URL, + an existing local filename, or a project/version requirement spec + (i.e. the string form of a ``Requirement`` object). If it is the URL + of a .py file with an unambiguous ``#egg=name-version`` tag (i.e., one + that escapes ``-`` as ``_`` throughout), a trivial ``setup.py`` is + automatically created alongside the downloaded file. + + If `spec` is a ``Requirement`` object or a string containing a + project/version requirement spec, this method returns the location of + a matching distribution (possibly after downloading it to `tmpdir`). + If `spec` is a locally existing file or directory name, it is simply + returned unchanged. If `spec` is a URL, it is downloaded to a subpath + of `tmpdir`, and the local filename is returned. Various errors may be + raised if a problem occurs during downloading. + """ + if not isinstance(spec, Requirement): + scheme = URL_SCHEME(spec) + if scheme: + # It's a url, download it to tmpdir + found = self._download_url(scheme.group(1), spec, tmpdir) + base, fragment = egg_info_for_url(spec) + if base.endswith('.py'): + found = self.gen_setup(found, fragment, tmpdir) + return found + elif os.path.exists(spec): + # Existing file or directory, just return it + return spec + else: + spec = parse_requirement_arg(spec) + return getattr(self.fetch_distribution(spec, tmpdir), 'location', None) + + def fetch_distribution( + self, requirement, tmpdir, force_scan=False, source=False, + develop_ok=False, local_index=None): + """Obtain a distribution suitable for fulfilling `requirement` + + `requirement` must be a ``pkg_resources.Requirement`` instance. + If necessary, or if the `force_scan` flag is set, the requirement is + searched for in the (online) package index as well as the locally + installed packages. If a distribution matching `requirement` is found, + the returned distribution's ``location`` is the value you would have + gotten from calling the ``download()`` method with the matching + distribution's URL or filename. If no matching distribution is found, + ``None`` is returned. + + If the `source` flag is set, only source distributions and source + checkout links will be considered. Unless the `develop_ok` flag is + set, development and system eggs (i.e., those using the ``.egg-info`` + format) will be ignored. + """ + # process a Requirement + self.info("Searching for %s", requirement) + skipped = {} + dist = None + + def find(req, env=None): + if env is None: + env = self + # Find a matching distribution; may be called more than once + + for dist in env[req.key]: + + if dist.precedence == DEVELOP_DIST and not develop_ok: + if dist not in skipped: + self.warn( + "Skipping development or system egg: %s", dist, + ) + skipped[dist] = 1 + continue + + test = ( + dist in req + and (dist.precedence <= SOURCE_DIST or not source) + ) + if test: + loc = self.download(dist.location, tmpdir) + dist.download_location = loc + if os.path.exists(dist.download_location): + return dist + + if force_scan: + self.prescan() + self.find_packages(requirement) + dist = find(requirement) + + if not dist and local_index is not None: + dist = find(requirement, local_index) + + if dist is None: + if self.to_scan is not None: + self.prescan() + dist = find(requirement) + + if dist is None and not force_scan: + self.find_packages(requirement) + dist = find(requirement) + + if dist is None: + self.warn( + "No local packages or working download links found for %s%s", + (source and "a source distribution of " or ""), + requirement, + ) + else: + self.info("Best match: %s", dist) + return dist.clone(location=dist.download_location) + + def fetch(self, requirement, tmpdir, force_scan=False, source=False): + """Obtain a file suitable for fulfilling `requirement` + + DEPRECATED; use the ``fetch_distribution()`` method now instead. For + backward compatibility, this routine is identical but returns the + ``location`` of the downloaded distribution instead of a distribution + object. + """ + dist = self.fetch_distribution(requirement, tmpdir, force_scan, source) + if dist is not None: + return dist.location + return None + + def gen_setup(self, filename, fragment, tmpdir): + match = EGG_FRAGMENT.match(fragment) + dists = match and [ + d for d in + interpret_distro_name(filename, match.group(1), None) if d.version + ] or [] + + if len(dists) == 1: # unambiguous ``#egg`` fragment + basename = os.path.basename(filename) + + # Make sure the file has been downloaded to the temp dir. + if os.path.dirname(filename) != tmpdir: + dst = os.path.join(tmpdir, basename) + from setuptools.command.easy_install import samefile + if not samefile(filename, dst): + shutil.copy2(filename, dst) + filename = dst + + with open(os.path.join(tmpdir, 'setup.py'), 'w') as file: + file.write( + "from setuptools import setup\n" + "setup(name=%r, version=%r, py_modules=[%r])\n" + % ( + dists[0].project_name, dists[0].version, + os.path.splitext(basename)[0] + ) + ) + return filename + + elif match: + raise DistutilsError( + "Can't unambiguously interpret project/version identifier %r; " + "any dashes in the name or version should be escaped using " + "underscores. %r" % (fragment, dists) + ) + else: + raise DistutilsError( + "Can't process plain .py files without an '#egg=name-version'" + " suffix to enable automatic setup script generation." + ) + + dl_blocksize = 8192 + + def _download_to(self, url, filename): + self.info("Downloading %s", url) + # Download the file + fp = None + try: + checker = HashChecker.from_url(url) + fp = self.open_url(url) + if isinstance(fp, urllib.error.HTTPError): + raise DistutilsError( + "Can't download %s: %s %s" % (url, fp.code, fp.msg) + ) + headers = fp.info() + blocknum = 0 + bs = self.dl_blocksize + size = -1 + if "content-length" in headers: + # Some servers return multiple Content-Length headers :( + sizes = get_all_headers(headers, 'Content-Length') + size = max(map(int, sizes)) + self.reporthook(url, filename, blocknum, bs, size) + with open(filename, 'wb') as tfp: + while True: + block = fp.read(bs) + if block: + checker.feed(block) + tfp.write(block) + blocknum += 1 + self.reporthook(url, filename, blocknum, bs, size) + else: + break + self.check_hash(checker, filename, tfp) + return headers + finally: + if fp: + fp.close() + + def reporthook(self, url, filename, blocknum, blksize, size): + pass # no-op + + def open_url(self, url, warning=None): + if url.startswith('file:'): + return local_open(url) + try: + return open_with_auth(url, self.opener) + except (ValueError, http_client.InvalidURL) as v: + msg = ' '.join([str(arg) for arg in v.args]) + if warning: + self.warn(warning, msg) + else: + raise DistutilsError('%s %s' % (url, msg)) + except urllib.error.HTTPError as v: + return v + except urllib.error.URLError as v: + if warning: + self.warn(warning, v.reason) + else: + raise DistutilsError("Download error for %s: %s" + % (url, v.reason)) + except http_client.BadStatusLine as v: + if warning: + self.warn(warning, v.line) + else: + raise DistutilsError( + '%s returned a bad status line. The server might be ' + 'down, %s' % + (url, v.line) + ) + except (http_client.HTTPException, socket.error) as v: + if warning: + self.warn(warning, v) + else: + raise DistutilsError("Download error for %s: %s" + % (url, v)) + + def _download_url(self, scheme, url, tmpdir): + # Determine download filename + # + name, fragment = egg_info_for_url(url) + if name: + while '..' in name: + name = name.replace('..', '.').replace('\\', '_') + else: + name = "__downloaded__" # default if URL has no path contents + + if name.endswith('.egg.zip'): + name = name[:-4] # strip the extra .zip before download + + filename = os.path.join(tmpdir, name) + + # Download the file + # + if scheme == 'svn' or scheme.startswith('svn+'): + return self._download_svn(url, filename) + elif scheme == 'git' or scheme.startswith('git+'): + return self._download_git(url, filename) + elif scheme.startswith('hg+'): + return self._download_hg(url, filename) + elif scheme == 'file': + return urllib.request.url2pathname(urllib.parse.urlparse(url)[2]) + else: + self.url_ok(url, True) # raises error if not allowed + return self._attempt_download(url, filename) + + def scan_url(self, url): + self.process_url(url, True) + + def _attempt_download(self, url, filename): + headers = self._download_to(url, filename) + if 'html' in headers.get('content-type', '').lower(): + return self._download_html(url, headers, filename) + else: + return filename + + def _download_html(self, url, headers, filename): + file = open(filename) + for line in file: + if line.strip(): + # Check for a subversion index page + if re.search(r'<title>([^- ]+ - )?Revision \d+:', line): + # it's a subversion index page: + file.close() + os.unlink(filename) + return self._download_svn(url, filename) + break # not an index page + file.close() + os.unlink(filename) + raise DistutilsError("Unexpected HTML page found at " + url) + + def _download_svn(self, url, filename): + warnings.warn("SVN download support is deprecated", UserWarning) + url = url.split('#', 1)[0] # remove any fragment for svn's sake + creds = '' + if url.lower().startswith('svn:') and '@' in url: + scheme, netloc, path, p, q, f = urllib.parse.urlparse(url) + if not netloc and path.startswith('//') and '/' in path[2:]: + netloc, path = path[2:].split('/', 1) + auth, host = urllib.parse.splituser(netloc) + if auth: + if ':' in auth: + user, pw = auth.split(':', 1) + creds = " --username=%s --password=%s" % (user, pw) + else: + creds = " --username=" + auth + netloc = host + parts = scheme, netloc, url, p, q, f + url = urllib.parse.urlunparse(parts) + self.info("Doing subversion checkout from %s to %s", url, filename) + os.system("svn checkout%s -q %s %s" % (creds, url, filename)) + return filename + + @staticmethod + def _vcs_split_rev_from_url(url, pop_prefix=False): + scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) + + scheme = scheme.split('+', 1)[-1] + + # Some fragment identification fails + path = path.split('#', 1)[0] + + rev = None + if '@' in path: + path, rev = path.rsplit('@', 1) + + # Also, discard fragment + url = urllib.parse.urlunsplit((scheme, netloc, path, query, '')) + + return url, rev + + def _download_git(self, url, filename): + filename = filename.split('#', 1)[0] + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) + + self.info("Doing git clone from %s to %s", url, filename) + os.system("git clone --quiet %s %s" % (url, filename)) + + if rev is not None: + self.info("Checking out %s", rev) + os.system("(cd %s && git checkout --quiet %s)" % ( + filename, + rev, + )) + + return filename + + def _download_hg(self, url, filename): + filename = filename.split('#', 1)[0] + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) + + self.info("Doing hg clone from %s to %s", url, filename) + os.system("hg clone --quiet %s %s" % (url, filename)) + + if rev is not None: + self.info("Updating to %s", rev) + os.system("(cd %s && hg up -C -r %s -q)" % ( + filename, + rev, + )) + + return filename + + def debug(self, msg, *args): + log.debug(msg, *args) + + def info(self, msg, *args): + log.info(msg, *args) + + def warn(self, msg, *args): + log.warn(msg, *args) + + +# This pattern matches a character entity reference (a decimal numeric +# references, a hexadecimal numeric reference, or a named reference). +entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub + + +def decode_entity(match): + what = match.group(0) + return unescape(what) + + +def htmldecode(text): + """ + Decode HTML entities in the given text. + + >>> htmldecode( + ... 'https://../package_name-0.1.2.tar.gz' + ... '?tokena=A&amp;tokenb=B">package_name-0.1.2.tar.gz') + 'https://../package_name-0.1.2.tar.gz?tokena=A&tokenb=B">package_name-0.1.2.tar.gz' + """ + return entity_sub(decode_entity, text) + + +def socket_timeout(timeout=15): + def _socket_timeout(func): + def _socket_timeout(*args, **kwargs): + old_timeout = socket.getdefaulttimeout() + socket.setdefaulttimeout(timeout) + try: + return func(*args, **kwargs) + finally: + socket.setdefaulttimeout(old_timeout) + + return _socket_timeout + + return _socket_timeout + + +def _encode_auth(auth): + """ + A function compatible with Python 2.3-3.3 that will encode + auth from a URL suitable for an HTTP header. + >>> str(_encode_auth('username%3Apassword')) + 'dXNlcm5hbWU6cGFzc3dvcmQ=' + + Long auth strings should not cause a newline to be inserted. + >>> long_auth = 'username:' + 'password'*10 + >>> chr(10) in str(_encode_auth(long_auth)) + False + """ + auth_s = urllib.parse.unquote(auth) + # convert to bytes + auth_bytes = auth_s.encode() + encoded_bytes = base64.b64encode(auth_bytes) + # convert back to a string + encoded = encoded_bytes.decode() + # strip the trailing carriage return + return encoded.replace('\n', '') + + +class Credential: + """ + A username/password pair. Use like a namedtuple. + """ + + def __init__(self, username, password): + self.username = username + self.password = password + + def __iter__(self): + yield self.username + yield self.password + + def __str__(self): + return '%(username)s:%(password)s' % vars(self) + + +class PyPIConfig(configparser.RawConfigParser): + def __init__(self): + """ + Load from ~/.pypirc + """ + defaults = dict.fromkeys(['username', 'password', 'repository'], '') + configparser.RawConfigParser.__init__(self, defaults) + + rc = os.path.join(os.path.expanduser('~'), '.pypirc') + if os.path.exists(rc): + self.read(rc) + + @property + def creds_by_repository(self): + sections_with_repositories = [ + section for section in self.sections() + if self.get(section, 'repository').strip() + ] + + return dict(map(self._get_repo_cred, sections_with_repositories)) + + def _get_repo_cred(self, section): + repo = self.get(section, 'repository').strip() + return repo, Credential( + self.get(section, 'username').strip(), + self.get(section, 'password').strip(), + ) + + def find_credential(self, url): + """ + If the URL indicated appears to be a repository defined in this + config, return the credential for that repository. + """ + for repository, cred in self.creds_by_repository.items(): + if url.startswith(repository): + return cred + + +def open_with_auth(url, opener=urllib.request.urlopen): + """Open a urllib2 request, handling HTTP authentication""" + + scheme, netloc, path, params, query, frag = urllib.parse.urlparse(url) + + # Double scheme does not raise on Mac OS X as revealed by a + # failing test. We would expect "nonnumeric port". Refs #20. + if netloc.endswith(':'): + raise http_client.InvalidURL("nonnumeric port: ''") + + if scheme in ('http', 'https'): + auth, host = urllib.parse.splituser(netloc) + else: + auth = None + + if not auth: + cred = PyPIConfig().find_credential(url) + if cred: + auth = str(cred) + info = cred.username, url + log.info('Authenticating as %s for %s (from .pypirc)', *info) + + if auth: + auth = "Basic " + _encode_auth(auth) + parts = scheme, host, path, params, query, frag + new_url = urllib.parse.urlunparse(parts) + request = urllib.request.Request(new_url) + request.add_header("Authorization", auth) + else: + request = urllib.request.Request(url) + + request.add_header('User-Agent', user_agent) + fp = opener(request) + + if auth: + # Put authentication info back into request URL if same host, + # so that links found on the page will work + s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url) + if s2 == scheme and h2 == host: + parts = s2, netloc, path2, param2, query2, frag2 + fp.url = urllib.parse.urlunparse(parts) + + return fp + + +# adding a timeout to avoid freezing package_index +open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth) + + +def fix_sf_url(url): + return url # backward compatibility + + +def local_open(url): + """Read a local path, with special support for directories""" + scheme, server, path, param, query, frag = urllib.parse.urlparse(url) + filename = urllib.request.url2pathname(path) + if os.path.isfile(filename): + return urllib.request.urlopen(url) + elif path.endswith('/') and os.path.isdir(filename): + files = [] + for f in os.listdir(filename): + filepath = os.path.join(filename, f) + if f == 'index.html': + with open(filepath, 'r') as fp: + body = fp.read() + break + elif os.path.isdir(filepath): + f += '/' + files.append('<a href="{name}">{name}</a>'.format(name=f)) + else: + tmpl = ( + "<html><head><title>{url}</title>" + "</head><body>{files}</body></html>") + body = tmpl.format(url=url, files='\n'.join(files)) + status, message = 200, "OK" + else: + status, message, body = 404, "Path not found", "Not found" + + headers = {'content-type': 'text/html'} + body_stream = six.StringIO(body) + return urllib.error.HTTPError(url, status, message, headers, body_stream) diff --git a/lib/setuptools/pep425tags.py b/lib/setuptools/pep425tags.py new file mode 100644 index 0000000..8bf4277 --- /dev/null +++ b/lib/setuptools/pep425tags.py @@ -0,0 +1,319 @@ +# This file originally from pip: +# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/pep425tags.py +"""Generate and work with PEP 425 Compatibility Tags.""" +from __future__ import absolute_import + +import distutils.util +from distutils import log +import platform +import re +import sys +import sysconfig +import warnings +from collections import OrderedDict + +from .extern import six + +from . import glibc + +_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)') + + +def get_config_var(var): + try: + return sysconfig.get_config_var(var) + except IOError as e: # Issue #1074 + warnings.warn("{}".format(e), RuntimeWarning) + return None + + +def get_abbr_impl(): + """Return abbreviated implementation name.""" + if hasattr(sys, 'pypy_version_info'): + pyimpl = 'pp' + elif sys.platform.startswith('java'): + pyimpl = 'jy' + elif sys.platform == 'cli': + pyimpl = 'ip' + else: + pyimpl = 'cp' + return pyimpl + + +def get_impl_ver(): + """Return implementation version.""" + impl_ver = get_config_var("py_version_nodot") + if not impl_ver or get_abbr_impl() == 'pp': + impl_ver = ''.join(map(str, get_impl_version_info())) + return impl_ver + + +def get_impl_version_info(): + """Return sys.version_info-like tuple for use in decrementing the minor + version.""" + if get_abbr_impl() == 'pp': + # as per https://github.com/pypa/pip/issues/2882 + return (sys.version_info[0], sys.pypy_version_info.major, + sys.pypy_version_info.minor) + else: + return sys.version_info[0], sys.version_info[1] + + +def get_impl_tag(): + """ + Returns the Tag for this specific implementation. + """ + return "{}{}".format(get_abbr_impl(), get_impl_ver()) + + +def get_flag(var, fallback, expected=True, warn=True): + """Use a fallback method for determining SOABI flags if the needed config + var is unset or unavailable.""" + val = get_config_var(var) + if val is None: + if warn: + log.debug("Config variable '%s' is unset, Python ABI tag may " + "be incorrect", var) + return fallback() + return val == expected + + +def get_abi_tag(): + """Return the ABI tag based on SOABI (if available) or emulate SOABI + (CPython 2, PyPy).""" + soabi = get_config_var('SOABI') + impl = get_abbr_impl() + if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'): + d = '' + m = '' + u = '' + if get_flag('Py_DEBUG', + lambda: hasattr(sys, 'gettotalrefcount'), + warn=(impl == 'cp')): + d = 'd' + if get_flag('WITH_PYMALLOC', + lambda: impl == 'cp', + warn=(impl == 'cp')): + m = 'm' + if get_flag('Py_UNICODE_SIZE', + lambda: sys.maxunicode == 0x10ffff, + expected=4, + warn=(impl == 'cp' and + six.PY2)) \ + and six.PY2: + u = 'u' + abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u) + elif soabi and soabi.startswith('cpython-'): + abi = 'cp' + soabi.split('-')[1] + elif soabi: + abi = soabi.replace('.', '_').replace('-', '_') + else: + abi = None + return abi + + +def _is_running_32bit(): + return sys.maxsize == 2147483647 + + +def get_platform(): + """Return our platform name 'win32', 'linux_x86_64'""" + if sys.platform == 'darwin': + # distutils.util.get_platform() returns the release based on the value + # of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may + # be significantly older than the user's current machine. + release, _, machine = platform.mac_ver() + split_ver = release.split('.') + + if machine == "x86_64" and _is_running_32bit(): + machine = "i386" + elif machine == "ppc64" and _is_running_32bit(): + machine = "ppc" + + return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine) + + # XXX remove distutils dependency + result = distutils.util.get_platform().replace('.', '_').replace('-', '_') + if result == "linux_x86_64" and _is_running_32bit(): + # 32 bit Python program (running on a 64 bit Linux): pip should only + # install and run 32 bit compiled extensions in that case. + result = "linux_i686" + + return result + + +def is_manylinux1_compatible(): + # Only Linux, and only x86-64 / i686 + if get_platform() not in {"linux_x86_64", "linux_i686"}: + return False + + # Check for presence of _manylinux module + try: + import _manylinux + return bool(_manylinux.manylinux1_compatible) + except (ImportError, AttributeError): + # Fall through to heuristic check below + pass + + # Check glibc version. CentOS 5 uses glibc 2.5. + return glibc.have_compatible_glibc(2, 5) + + +def get_darwin_arches(major, minor, machine): + """Return a list of supported arches (including group arches) for + the given major, minor and machine architecture of an macOS machine. + """ + arches = [] + + def _supports_arch(major, minor, arch): + # Looking at the application support for macOS versions in the chart + # provided by https://en.wikipedia.org/wiki/OS_X#Versions it appears + # our timeline looks roughly like: + # + # 10.0 - Introduces ppc support. + # 10.4 - Introduces ppc64, i386, and x86_64 support, however the ppc64 + # and x86_64 support is CLI only, and cannot be used for GUI + # applications. + # 10.5 - Extends ppc64 and x86_64 support to cover GUI applications. + # 10.6 - Drops support for ppc64 + # 10.7 - Drops support for ppc + # + # Given that we do not know if we're installing a CLI or a GUI + # application, we must be conservative and assume it might be a GUI + # application and behave as if ppc64 and x86_64 support did not occur + # until 10.5. + # + # Note: The above information is taken from the "Application support" + # column in the chart not the "Processor support" since I believe + # that we care about what instruction sets an application can use + # not which processors the OS supports. + if arch == 'ppc': + return (major, minor) <= (10, 5) + if arch == 'ppc64': + return (major, minor) == (10, 5) + if arch == 'i386': + return (major, minor) >= (10, 4) + if arch == 'x86_64': + return (major, minor) >= (10, 5) + if arch in groups: + for garch in groups[arch]: + if _supports_arch(major, minor, garch): + return True + return False + + groups = OrderedDict([ + ("fat", ("i386", "ppc")), + ("intel", ("x86_64", "i386")), + ("fat64", ("x86_64", "ppc64")), + ("fat32", ("x86_64", "i386", "ppc")), + ]) + + if _supports_arch(major, minor, machine): + arches.append(machine) + + for garch in groups: + if machine in groups[garch] and _supports_arch(major, minor, garch): + arches.append(garch) + + arches.append('universal') + + return arches + + +def get_supported(versions=None, noarch=False, platform=None, + impl=None, abi=None): + """Return a list of supported tags for each version specified in + `versions`. + + :param versions: a list of string versions, of the form ["33", "32"], + or None. The first version will be assumed to support our ABI. + :param platform: specify the exact platform you want valid + tags for, or None. If None, use the local system platform. + :param impl: specify the exact implementation you want valid + tags for, or None. If None, use the local interpreter impl. + :param abi: specify the exact abi you want valid + tags for, or None. If None, use the local interpreter abi. + """ + supported = [] + + # Versions must be given with respect to the preference + if versions is None: + versions = [] + version_info = get_impl_version_info() + major = version_info[:-1] + # Support all previous minor Python versions. + for minor in range(version_info[-1], -1, -1): + versions.append(''.join(map(str, major + (minor,)))) + + impl = impl or get_abbr_impl() + + abis = [] + + abi = abi or get_abi_tag() + if abi: + abis[0:0] = [abi] + + abi3s = set() + import imp + for suffix in imp.get_suffixes(): + if suffix[0].startswith('.abi'): + abi3s.add(suffix[0].split('.', 2)[1]) + + abis.extend(sorted(list(abi3s))) + + abis.append('none') + + if not noarch: + arch = platform or get_platform() + if arch.startswith('macosx'): + # support macosx-10.6-intel on macosx-10.9-x86_64 + match = _osx_arch_pat.match(arch) + if match: + name, major, minor, actual_arch = match.groups() + tpl = '{}_{}_%i_%s'.format(name, major) + arches = [] + for m in reversed(range(int(minor) + 1)): + for a in get_darwin_arches(int(major), m, actual_arch): + arches.append(tpl % (m, a)) + else: + # arch pattern didn't match (?!) + arches = [arch] + elif platform is None and is_manylinux1_compatible(): + arches = [arch.replace('linux', 'manylinux1'), arch] + else: + arches = [arch] + + # Current version, current API (built specifically for our Python): + for abi in abis: + for arch in arches: + supported.append(('%s%s' % (impl, versions[0]), abi, arch)) + + # abi3 modules compatible with older version of Python + for version in versions[1:]: + # abi3 was introduced in Python 3.2 + if version in {'31', '30'}: + break + for abi in abi3s: # empty set if not Python 3 + for arch in arches: + supported.append(("%s%s" % (impl, version), abi, arch)) + + # Has binaries, does not use the Python API: + for arch in arches: + supported.append(('py%s' % (versions[0][0]), 'none', arch)) + + # No abi / arch, but requires our implementation: + supported.append(('%s%s' % (impl, versions[0]), 'none', 'any')) + # Tagged specifically as being cross-version compatible + # (with just the major version specified) + supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any')) + + # No abi / arch, generic Python + for i, version in enumerate(versions): + supported.append(('py%s' % (version,), 'none', 'any')) + if i == 0: + supported.append(('py%s' % (version[0]), 'none', 'any')) + + return supported + + +implementation_tag = get_impl_tag() diff --git a/lib/setuptools/py27compat.py b/lib/setuptools/py27compat.py new file mode 100644 index 0000000..2985011 --- /dev/null +++ b/lib/setuptools/py27compat.py @@ -0,0 +1,28 @@ +""" +Compatibility Support for Python 2.7 and earlier +""" + +import platform + +from setuptools.extern import six + + +def get_all_headers(message, key): + """ + Given an HTTPMessage, return all headers matching a given key. + """ + return message.get_all(key) + + +if six.PY2: + def get_all_headers(message, key): + return message.getheaders(key) + + +linux_py2_ascii = ( + platform.system() == 'Linux' and + six.PY2 +) + +rmtree_safe = str if linux_py2_ascii else lambda x: x +"""Workaround for http://bugs.python.org/issue24672""" diff --git a/lib/setuptools/py31compat.py b/lib/setuptools/py31compat.py new file mode 100644 index 0000000..1a0705e --- /dev/null +++ b/lib/setuptools/py31compat.py @@ -0,0 +1,32 @@ +__all__ = [] + +__metaclass__ = type + + +try: + # Python >=3.2 + from tempfile import TemporaryDirectory +except ImportError: + import shutil + import tempfile + + class TemporaryDirectory: + """ + Very simple temporary directory context manager. + Will try to delete afterward, but will also ignore OS and similar + errors on deletion. + """ + + def __init__(self): + self.name = None # Handle mkdtemp raising an exception + self.name = tempfile.mkdtemp() + + def __enter__(self): + return self.name + + def __exit__(self, exctype, excvalue, exctrace): + try: + shutil.rmtree(self.name, True) + except OSError: # removal errors are not the only possible + pass + self.name = None diff --git a/lib/setuptools/py33compat.py b/lib/setuptools/py33compat.py new file mode 100644 index 0000000..87cf539 --- /dev/null +++ b/lib/setuptools/py33compat.py @@ -0,0 +1,55 @@ +import dis +import array +import collections + +try: + import html +except ImportError: + html = None + +from setuptools.extern import six +from setuptools.extern.six.moves import html_parser + +__metaclass__ = type + +OpArg = collections.namedtuple('OpArg', 'opcode arg') + + +class Bytecode_compat: + def __init__(self, code): + self.code = code + + def __iter__(self): + """Yield '(op,arg)' pair for each operation in code object 'code'""" + + bytes = array.array('b', self.code.co_code) + eof = len(self.code.co_code) + + ptr = 0 + extended_arg = 0 + + while ptr < eof: + + op = bytes[ptr] + + if op >= dis.HAVE_ARGUMENT: + + arg = bytes[ptr + 1] + bytes[ptr + 2] * 256 + extended_arg + ptr += 3 + + if op == dis.EXTENDED_ARG: + long_type = six.integer_types[-1] + extended_arg = arg * long_type(65536) + continue + + else: + arg = None + ptr += 1 + + yield OpArg(op, arg) + + +Bytecode = getattr(dis, 'Bytecode', Bytecode_compat) + + +unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape) diff --git a/lib/setuptools/py36compat.py b/lib/setuptools/py36compat.py new file mode 100644 index 0000000..f527969 --- /dev/null +++ b/lib/setuptools/py36compat.py @@ -0,0 +1,82 @@ +import sys +from distutils.errors import DistutilsOptionError +from distutils.util import strtobool +from distutils.debug import DEBUG + + +class Distribution_parse_config_files: + """ + Mix-in providing forward-compatibility for functionality to be + included by default on Python 3.7. + + Do not edit the code in this class except to update functionality + as implemented in distutils. + """ + def parse_config_files(self, filenames=None): + from configparser import ConfigParser + + # Ignore install directory options if we have a venv + if sys.prefix != sys.base_prefix: + ignore_options = [ + 'install-base', 'install-platbase', 'install-lib', + 'install-platlib', 'install-purelib', 'install-headers', + 'install-scripts', 'install-data', 'prefix', 'exec-prefix', + 'home', 'user', 'root'] + else: + ignore_options = [] + + ignore_options = frozenset(ignore_options) + + if filenames is None: + filenames = self.find_config_files() + + if DEBUG: + self.announce("Distribution.parse_config_files():") + + parser = ConfigParser(interpolation=None) + for filename in filenames: + if DEBUG: + self.announce(" reading %s" % filename) + parser.read(filename) + for section in parser.sections(): + options = parser.options(section) + opt_dict = self.get_option_dict(section) + + for opt in options: + if opt != '__name__' and opt not in ignore_options: + val = parser.get(section,opt) + opt = opt.replace('-', '_') + opt_dict[opt] = (filename, val) + + # Make the ConfigParser forget everything (so we retain + # the original filenames that options come from) + parser.__init__() + + # If there was a "global" section in the config file, use it + # to set Distribution options. + + if 'global' in self.command_options: + for (opt, (src, val)) in self.command_options['global'].items(): + alias = self.negative_opt.get(opt) + try: + if alias: + setattr(self, alias, not strtobool(val)) + elif opt in ('verbose', 'dry_run'): # ugh! + setattr(self, opt, strtobool(val)) + else: + setattr(self, opt, val) + except ValueError as msg: + raise DistutilsOptionError(msg) + + +if sys.version_info < (3,): + # Python 2 behavior is sufficient + class Distribution_parse_config_files: + pass + + +if False: + # When updated behavior is available upstream, + # disable override here. + class Distribution_parse_config_files: + pass diff --git a/lib/setuptools/sandbox.py b/lib/setuptools/sandbox.py new file mode 100644 index 0000000..685f3f7 --- /dev/null +++ b/lib/setuptools/sandbox.py @@ -0,0 +1,491 @@ +import os +import sys +import tempfile +import operator +import functools +import itertools +import re +import contextlib +import pickle +import textwrap + +from setuptools.extern import six +from setuptools.extern.six.moves import builtins, map + +import pkg_resources.py31compat + +if sys.platform.startswith('java'): + import org.python.modules.posix.PosixModule as _os +else: + _os = sys.modules[os.name] +try: + _file = file +except NameError: + _file = None +_open = open +from distutils.errors import DistutilsError +from pkg_resources import working_set + + +__all__ = [ + "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup", +] + + +def _execfile(filename, globals, locals=None): + """ + Python 3 implementation of execfile. + """ + mode = 'rb' + with open(filename, mode) as stream: + script = stream.read() + if locals is None: + locals = globals + code = compile(script, filename, 'exec') + exec(code, globals, locals) + + +@contextlib.contextmanager +def save_argv(repl=None): + saved = sys.argv[:] + if repl is not None: + sys.argv[:] = repl + try: + yield saved + finally: + sys.argv[:] = saved + + +@contextlib.contextmanager +def save_path(): + saved = sys.path[:] + try: + yield saved + finally: + sys.path[:] = saved + + +@contextlib.contextmanager +def override_temp(replacement): + """ + Monkey-patch tempfile.tempdir with replacement, ensuring it exists + """ + pkg_resources.py31compat.makedirs(replacement, exist_ok=True) + + saved = tempfile.tempdir + + tempfile.tempdir = replacement + + try: + yield + finally: + tempfile.tempdir = saved + + +@contextlib.contextmanager +def pushd(target): + saved = os.getcwd() + os.chdir(target) + try: + yield saved + finally: + os.chdir(saved) + + +class UnpickleableException(Exception): + """ + An exception representing another Exception that could not be pickled. + """ + + @staticmethod + def dump(type, exc): + """ + Always return a dumped (pickled) type and exc. If exc can't be pickled, + wrap it in UnpickleableException first. + """ + try: + return pickle.dumps(type), pickle.dumps(exc) + except Exception: + # get UnpickleableException inside the sandbox + from setuptools.sandbox import UnpickleableException as cls + return cls.dump(cls, cls(repr(exc))) + + +class ExceptionSaver: + """ + A Context Manager that will save an exception, serialized, and restore it + later. + """ + + def __enter__(self): + return self + + def __exit__(self, type, exc, tb): + if not exc: + return + + # dump the exception + self._saved = UnpickleableException.dump(type, exc) + self._tb = tb + + # suppress the exception + return True + + def resume(self): + "restore and re-raise any exception" + + if '_saved' not in vars(self): + return + + type, exc = map(pickle.loads, self._saved) + six.reraise(type, exc, self._tb) + + +@contextlib.contextmanager +def save_modules(): + """ + Context in which imported modules are saved. + + Translates exceptions internal to the context into the equivalent exception + outside the context. + """ + saved = sys.modules.copy() + with ExceptionSaver() as saved_exc: + yield saved + + sys.modules.update(saved) + # remove any modules imported since + del_modules = ( + mod_name for mod_name in sys.modules + if mod_name not in saved + # exclude any encodings modules. See #285 + and not mod_name.startswith('encodings.') + ) + _clear_modules(del_modules) + + saved_exc.resume() + + +def _clear_modules(module_names): + for mod_name in list(module_names): + del sys.modules[mod_name] + + +@contextlib.contextmanager +def save_pkg_resources_state(): + saved = pkg_resources.__getstate__() + try: + yield saved + finally: + pkg_resources.__setstate__(saved) + + +@contextlib.contextmanager +def setup_context(setup_dir): + temp_dir = os.path.join(setup_dir, 'temp') + with save_pkg_resources_state(): + with save_modules(): + hide_setuptools() + with save_path(): + with save_argv(): + with override_temp(temp_dir): + with pushd(setup_dir): + # ensure setuptools commands are available + __import__('setuptools') + yield + + +def _needs_hiding(mod_name): + """ + >>> _needs_hiding('setuptools') + True + >>> _needs_hiding('pkg_resources') + True + >>> _needs_hiding('setuptools_plugin') + False + >>> _needs_hiding('setuptools.__init__') + True + >>> _needs_hiding('distutils') + True + >>> _needs_hiding('os') + False + >>> _needs_hiding('Cython') + True + """ + pattern = re.compile(r'(setuptools|pkg_resources|distutils|Cython)(\.|$)') + return bool(pattern.match(mod_name)) + + +def hide_setuptools(): + """ + Remove references to setuptools' modules from sys.modules to allow the + invocation to import the most appropriate setuptools. This technique is + necessary to avoid issues such as #315 where setuptools upgrading itself + would fail to find a function declared in the metadata. + """ + modules = filter(_needs_hiding, sys.modules) + _clear_modules(modules) + + +def run_setup(setup_script, args): + """Run a distutils setup script, sandboxed in its directory""" + setup_dir = os.path.abspath(os.path.dirname(setup_script)) + with setup_context(setup_dir): + try: + sys.argv[:] = [setup_script] + list(args) + sys.path.insert(0, setup_dir) + # reset to include setup dir, w/clean callback list + working_set.__init__() + working_set.callbacks.append(lambda dist: dist.activate()) + + # __file__ should be a byte string on Python 2 (#712) + dunder_file = ( + setup_script + if isinstance(setup_script, str) else + setup_script.encode(sys.getfilesystemencoding()) + ) + + with DirectorySandbox(setup_dir): + ns = dict(__file__=dunder_file, __name__='__main__') + _execfile(setup_script, ns) + except SystemExit as v: + if v.args and v.args[0]: + raise + # Normal exit, just return + + +class AbstractSandbox: + """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts""" + + _active = False + + def __init__(self): + self._attrs = [ + name for name in dir(_os) + if not name.startswith('_') and hasattr(self, name) + ] + + def _copy(self, source): + for name in self._attrs: + setattr(os, name, getattr(source, name)) + + def __enter__(self): + self._copy(self) + if _file: + builtins.file = self._file + builtins.open = self._open + self._active = True + + def __exit__(self, exc_type, exc_value, traceback): + self._active = False + if _file: + builtins.file = _file + builtins.open = _open + self._copy(_os) + + def run(self, func): + """Run 'func' under os sandboxing""" + with self: + return func() + + def _mk_dual_path_wrapper(name): + original = getattr(_os, name) + + def wrap(self, src, dst, *args, **kw): + if self._active: + src, dst = self._remap_pair(name, src, dst, *args, **kw) + return original(src, dst, *args, **kw) + + return wrap + + for name in ["rename", "link", "symlink"]: + if hasattr(_os, name): + locals()[name] = _mk_dual_path_wrapper(name) + + def _mk_single_path_wrapper(name, original=None): + original = original or getattr(_os, name) + + def wrap(self, path, *args, **kw): + if self._active: + path = self._remap_input(name, path, *args, **kw) + return original(path, *args, **kw) + + return wrap + + if _file: + _file = _mk_single_path_wrapper('file', _file) + _open = _mk_single_path_wrapper('open', _open) + for name in [ + "stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir", + "remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat", + "startfile", "mkfifo", "mknod", "pathconf", "access" + ]: + if hasattr(_os, name): + locals()[name] = _mk_single_path_wrapper(name) + + def _mk_single_with_return(name): + original = getattr(_os, name) + + def wrap(self, path, *args, **kw): + if self._active: + path = self._remap_input(name, path, *args, **kw) + return self._remap_output(name, original(path, *args, **kw)) + return original(path, *args, **kw) + + return wrap + + for name in ['readlink', 'tempnam']: + if hasattr(_os, name): + locals()[name] = _mk_single_with_return(name) + + def _mk_query(name): + original = getattr(_os, name) + + def wrap(self, *args, **kw): + retval = original(*args, **kw) + if self._active: + return self._remap_output(name, retval) + return retval + + return wrap + + for name in ['getcwd', 'tmpnam']: + if hasattr(_os, name): + locals()[name] = _mk_query(name) + + def _validate_path(self, path): + """Called to remap or validate any path, whether input or output""" + return path + + def _remap_input(self, operation, path, *args, **kw): + """Called for path inputs""" + return self._validate_path(path) + + def _remap_output(self, operation, path): + """Called for path outputs""" + return self._validate_path(path) + + def _remap_pair(self, operation, src, dst, *args, **kw): + """Called for path pairs like rename, link, and symlink operations""" + return ( + self._remap_input(operation + '-from', src, *args, **kw), + self._remap_input(operation + '-to', dst, *args, **kw) + ) + + +if hasattr(os, 'devnull'): + _EXCEPTIONS = [os.devnull,] +else: + _EXCEPTIONS = [] + + +class DirectorySandbox(AbstractSandbox): + """Restrict operations to a single subdirectory - pseudo-chroot""" + + write_ops = dict.fromkeys([ + "open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir", + "utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam", + ]) + + _exception_patterns = [ + # Allow lib2to3 to attempt to save a pickled grammar object (#121) + r'.*lib2to3.*\.pickle$', + ] + "exempt writing to paths that match the pattern" + + def __init__(self, sandbox, exceptions=_EXCEPTIONS): + self._sandbox = os.path.normcase(os.path.realpath(sandbox)) + self._prefix = os.path.join(self._sandbox, '') + self._exceptions = [ + os.path.normcase(os.path.realpath(path)) + for path in exceptions + ] + AbstractSandbox.__init__(self) + + def _violation(self, operation, *args, **kw): + from setuptools.sandbox import SandboxViolation + raise SandboxViolation(operation, args, kw) + + if _file: + + def _file(self, path, mode='r', *args, **kw): + if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): + self._violation("file", path, mode, *args, **kw) + return _file(path, mode, *args, **kw) + + def _open(self, path, mode='r', *args, **kw): + if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): + self._violation("open", path, mode, *args, **kw) + return _open(path, mode, *args, **kw) + + def tmpnam(self): + self._violation("tmpnam") + + def _ok(self, path): + active = self._active + try: + self._active = False + realpath = os.path.normcase(os.path.realpath(path)) + return ( + self._exempted(realpath) + or realpath == self._sandbox + or realpath.startswith(self._prefix) + ) + finally: + self._active = active + + def _exempted(self, filepath): + start_matches = ( + filepath.startswith(exception) + for exception in self._exceptions + ) + pattern_matches = ( + re.match(pattern, filepath) + for pattern in self._exception_patterns + ) + candidates = itertools.chain(start_matches, pattern_matches) + return any(candidates) + + def _remap_input(self, operation, path, *args, **kw): + """Called for path inputs""" + if operation in self.write_ops and not self._ok(path): + self._violation(operation, os.path.realpath(path), *args, **kw) + return path + + def _remap_pair(self, operation, src, dst, *args, **kw): + """Called for path pairs like rename, link, and symlink operations""" + if not self._ok(src) or not self._ok(dst): + self._violation(operation, src, dst, *args, **kw) + return (src, dst) + + def open(self, file, flags, mode=0o777, *args, **kw): + """Called for low-level os.open()""" + if flags & WRITE_FLAGS and not self._ok(file): + self._violation("os.open", file, flags, mode, *args, **kw) + return _os.open(file, flags, mode, *args, **kw) + + +WRITE_FLAGS = functools.reduce( + operator.or_, [getattr(_os, a, 0) for a in + "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()] +) + + +class SandboxViolation(DistutilsError): + """A setup script attempted to modify the filesystem outside the sandbox""" + + tmpl = textwrap.dedent(""" + SandboxViolation: {cmd}{args!r} {kwargs} + + The package setup script has attempted to modify files on your system + that are not within the EasyInstall build area, and has been aborted. + + This package cannot be safely installed by EasyInstall, and may not + support alternate installation locations even if you run its setup + script by hand. Please inform the package's author and the EasyInstall + maintainers to find out if a fix or workaround is available. + """).lstrip() + + def __str__(self): + cmd, args, kwargs = self.args + return self.tmpl.format(**locals()) diff --git a/lib/setuptools/script (dev).tmpl b/lib/setuptools/script (dev).tmpl new file mode 100644 index 0000000..39a24b0 --- /dev/null +++ b/lib/setuptools/script (dev).tmpl @@ -0,0 +1,6 @@ +# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r +__requires__ = %(spec)r +__import__('pkg_resources').require(%(spec)r) +__file__ = %(dev_path)r +with open(__file__) as f: + exec(compile(f.read(), __file__, 'exec')) diff --git a/lib/setuptools/script.tmpl b/lib/setuptools/script.tmpl new file mode 100644 index 0000000..ff5efbc --- /dev/null +++ b/lib/setuptools/script.tmpl @@ -0,0 +1,3 @@ +# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r +__requires__ = %(spec)r +__import__('pkg_resources').run_script(%(spec)r, %(script_name)r) diff --git a/lib/setuptools/site-patch.py b/lib/setuptools/site-patch.py new file mode 100644 index 0000000..40b00de --- /dev/null +++ b/lib/setuptools/site-patch.py @@ -0,0 +1,74 @@ +def __boot(): + import sys + import os + PYTHONPATH = os.environ.get('PYTHONPATH') + if PYTHONPATH is None or (sys.platform == 'win32' and not PYTHONPATH): + PYTHONPATH = [] + else: + PYTHONPATH = PYTHONPATH.split(os.pathsep) + + pic = getattr(sys, 'path_importer_cache', {}) + stdpath = sys.path[len(PYTHONPATH):] + mydir = os.path.dirname(__file__) + + for item in stdpath: + if item == mydir or not item: + continue # skip if current dir. on Windows, or my own directory + importer = pic.get(item) + if importer is not None: + loader = importer.find_module('site') + if loader is not None: + # This should actually reload the current module + loader.load_module('site') + break + else: + try: + import imp # Avoid import loop in Python 3 + stream, path, descr = imp.find_module('site', [item]) + except ImportError: + continue + if stream is None: + continue + try: + # This should actually reload the current module + imp.load_module('site', stream, path, descr) + finally: + stream.close() + break + else: + raise ImportError("Couldn't find the real 'site' module") + + known_paths = dict([(makepath(item)[1], 1) for item in sys.path]) # 2.2 comp + + oldpos = getattr(sys, '__egginsert', 0) # save old insertion position + sys.__egginsert = 0 # and reset the current one + + for item in PYTHONPATH: + addsitedir(item) + + sys.__egginsert += oldpos # restore effective old position + + d, nd = makepath(stdpath[0]) + insert_at = None + new_path = [] + + for item in sys.path: + p, np = makepath(item) + + if np == nd and insert_at is None: + # We've hit the first 'system' path entry, so added entries go here + insert_at = len(new_path) + + if np in known_paths or insert_at is None: + new_path.append(item) + else: + # new path after the insert point, back-insert it + new_path.insert(insert_at, item) + insert_at += 1 + + sys.path[:] = new_path + + +if __name__ == 'site': + __boot() + del __boot diff --git a/lib/setuptools/ssl_support.py b/lib/setuptools/ssl_support.py new file mode 100644 index 0000000..6362f1f --- /dev/null +++ b/lib/setuptools/ssl_support.py @@ -0,0 +1,260 @@ +import os +import socket +import atexit +import re +import functools + +from setuptools.extern.six.moves import urllib, http_client, map, filter + +from pkg_resources import ResolutionError, ExtractionError + +try: + import ssl +except ImportError: + ssl = None + +__all__ = [ + 'VerifyingHTTPSHandler', 'find_ca_bundle', 'is_available', 'cert_paths', + 'opener_for' +] + +cert_paths = """ +/etc/pki/tls/certs/ca-bundle.crt +/etc/ssl/certs/ca-certificates.crt +/usr/share/ssl/certs/ca-bundle.crt +/usr/local/share/certs/ca-root.crt +/etc/ssl/cert.pem +/System/Library/OpenSSL/certs/cert.pem +/usr/local/share/certs/ca-root-nss.crt +/etc/ssl/ca-bundle.pem +""".strip().split() + +try: + HTTPSHandler = urllib.request.HTTPSHandler + HTTPSConnection = http_client.HTTPSConnection +except AttributeError: + HTTPSHandler = HTTPSConnection = object + +is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection) + + +try: + from ssl import CertificateError, match_hostname +except ImportError: + try: + from backports.ssl_match_hostname import CertificateError + from backports.ssl_match_hostname import match_hostname + except ImportError: + CertificateError = None + match_hostname = None + +if not CertificateError: + + class CertificateError(ValueError): + pass + + +if not match_hostname: + + def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r'.') + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate") + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" + % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") + + +class VerifyingHTTPSHandler(HTTPSHandler): + """Simple verifying handler: no auth, subclasses, timeouts, etc.""" + + def __init__(self, ca_bundle): + self.ca_bundle = ca_bundle + HTTPSHandler.__init__(self) + + def https_open(self, req): + return self.do_open( + lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req + ) + + +class VerifyingHTTPSConn(HTTPSConnection): + """Simple verifying connection: no auth, subclasses, timeouts, etc.""" + + def __init__(self, host, ca_bundle, **kw): + HTTPSConnection.__init__(self, host, **kw) + self.ca_bundle = ca_bundle + + def connect(self): + sock = socket.create_connection( + (self.host, self.port), getattr(self, 'source_address', None) + ) + + # Handle the socket if a (proxy) tunnel is present + if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None): + self.sock = sock + self._tunnel() + # http://bugs.python.org/issue7776: Python>=3.4.1 and >=2.7.7 + # change self.host to mean the proxy server host when tunneling is + # being used. Adapt, since we are interested in the destination + # host for the match_hostname() comparison. + actual_host = self._tunnel_host + else: + actual_host = self.host + + if hasattr(ssl, 'create_default_context'): + ctx = ssl.create_default_context(cafile=self.ca_bundle) + self.sock = ctx.wrap_socket(sock, server_hostname=actual_host) + else: + # This is for python < 2.7.9 and < 3.4? + self.sock = ssl.wrap_socket( + sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle + ) + try: + match_hostname(self.sock.getpeercert(), actual_host) + except CertificateError: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + raise + + +def opener_for(ca_bundle=None): + """Get a urlopen() replacement that uses ca_bundle for verification""" + return urllib.request.build_opener( + VerifyingHTTPSHandler(ca_bundle or find_ca_bundle()) + ).open + + +# from jaraco.functools +def once(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not hasattr(func, 'always_returns'): + func.always_returns = func(*args, **kwargs) + return func.always_returns + return wrapper + + +@once +def get_win_certfile(): + try: + import wincertstore + except ImportError: + return None + + class CertFile(wincertstore.CertFile): + def __init__(self): + super(CertFile, self).__init__() + atexit.register(self.close) + + def close(self): + try: + super(CertFile, self).close() + except OSError: + pass + + _wincerts = CertFile() + _wincerts.addstore('CA') + _wincerts.addstore('ROOT') + return _wincerts.name + + +def find_ca_bundle(): + """Return an existing CA bundle path, or None""" + extant_cert_paths = filter(os.path.isfile, cert_paths) + return ( + get_win_certfile() + or next(extant_cert_paths, None) + or _certifi_where() + ) + + +def _certifi_where(): + try: + return __import__('certifi').where() + except (ImportError, ResolutionError, ExtractionError): + pass diff --git a/lib/setuptools/unicode_utils.py b/lib/setuptools/unicode_utils.py new file mode 100644 index 0000000..7c63efd --- /dev/null +++ b/lib/setuptools/unicode_utils.py @@ -0,0 +1,44 @@ +import unicodedata +import sys + +from setuptools.extern import six + + +# HFS Plus uses decomposed UTF-8 +def decompose(path): + if isinstance(path, six.text_type): + return unicodedata.normalize('NFD', path) + try: + path = path.decode('utf-8') + path = unicodedata.normalize('NFD', path) + path = path.encode('utf-8') + except UnicodeError: + pass # Not UTF-8 + return path + + +def filesys_decode(path): + """ + Ensure that the given path is decoded, + NONE when no expected encoding works + """ + + if isinstance(path, six.text_type): + return path + + fs_enc = sys.getfilesystemencoding() or 'utf-8' + candidates = fs_enc, 'utf-8' + + for enc in candidates: + try: + return path.decode(enc) + except UnicodeDecodeError: + continue + + +def try_encode(string, enc): + "turn unicode encoding into a functional routine" + try: + return string.encode(enc) + except UnicodeEncodeError: + return None diff --git a/lib/setuptools/version.py b/lib/setuptools/version.py new file mode 100644 index 0000000..95e1869 --- /dev/null +++ b/lib/setuptools/version.py @@ -0,0 +1,6 @@ +import pkg_resources + +try: + __version__ = pkg_resources.get_distribution('setuptools').version +except Exception: + __version__ = 'unknown' diff --git a/lib/setuptools/wheel.py b/lib/setuptools/wheel.py new file mode 100644 index 0000000..95a794a --- /dev/null +++ b/lib/setuptools/wheel.py @@ -0,0 +1,210 @@ +"""Wheels support.""" + +from distutils.util import get_platform +import email +import itertools +import os +import posixpath +import re +import zipfile + +from pkg_resources import Distribution, PathMetadata, parse_version +from setuptools.extern.packaging.utils import canonicalize_name +from setuptools.extern.six import PY3 +from setuptools import Distribution as SetuptoolsDistribution +from setuptools import pep425tags +from setuptools.command.egg_info import write_requirements + + +__metaclass__ = type + + +WHEEL_NAME = re.compile( + r"""^(?P<project_name>.+?)-(?P<version>\d.*?) + ((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?) + )\.whl$""", + re.VERBOSE).match + +NAMESPACE_PACKAGE_INIT = '''\ +try: + __import__('pkg_resources').declare_namespace(__name__) +except ImportError: + __path__ = __import__('pkgutil').extend_path(__path__, __name__) +''' + + +def unpack(src_dir, dst_dir): + '''Move everything under `src_dir` to `dst_dir`, and delete the former.''' + for dirpath, dirnames, filenames in os.walk(src_dir): + subdir = os.path.relpath(dirpath, src_dir) + for f in filenames: + src = os.path.join(dirpath, f) + dst = os.path.join(dst_dir, subdir, f) + os.renames(src, dst) + for n, d in reversed(list(enumerate(dirnames))): + src = os.path.join(dirpath, d) + dst = os.path.join(dst_dir, subdir, d) + if not os.path.exists(dst): + # Directory does not exist in destination, + # rename it and prune it from os.walk list. + os.renames(src, dst) + del dirnames[n] + # Cleanup. + for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True): + assert not filenames + os.rmdir(dirpath) + + +class Wheel: + + def __init__(self, filename): + match = WHEEL_NAME(os.path.basename(filename)) + if match is None: + raise ValueError('invalid wheel name: %r' % filename) + self.filename = filename + for k, v in match.groupdict().items(): + setattr(self, k, v) + + def tags(self): + '''List tags (py_version, abi, platform) supported by this wheel.''' + return itertools.product( + self.py_version.split('.'), + self.abi.split('.'), + self.platform.split('.'), + ) + + def is_compatible(self): + '''Is the wheel is compatible with the current platform?''' + supported_tags = pep425tags.get_supported() + return next((True for t in self.tags() if t in supported_tags), False) + + def egg_name(self): + return Distribution( + project_name=self.project_name, version=self.version, + platform=(None if self.platform == 'any' else get_platform()), + ).egg_name() + '.egg' + + def get_dist_info(self, zf): + # find the correct name of the .dist-info dir in the wheel file + for member in zf.namelist(): + dirname = posixpath.dirname(member) + if (dirname.endswith('.dist-info') and + canonicalize_name(dirname).startswith( + canonicalize_name(self.project_name))): + return dirname + raise ValueError("unsupported wheel format. .dist-info not found") + + def install_as_egg(self, destination_eggdir): + '''Install wheel as an egg directory.''' + with zipfile.ZipFile(self.filename) as zf: + self._install_as_egg(destination_eggdir, zf) + + def _install_as_egg(self, destination_eggdir, zf): + dist_basename = '%s-%s' % (self.project_name, self.version) + dist_info = self.get_dist_info(zf) + dist_data = '%s.data' % dist_basename + egg_info = os.path.join(destination_eggdir, 'EGG-INFO') + + self._convert_metadata(zf, destination_eggdir, dist_info, egg_info) + self._move_data_entries(destination_eggdir, dist_data) + self._fix_namespace_packages(egg_info, destination_eggdir) + + @staticmethod + def _convert_metadata(zf, destination_eggdir, dist_info, egg_info): + def get_metadata(name): + with zf.open(posixpath.join(dist_info, name)) as fp: + value = fp.read().decode('utf-8') if PY3 else fp.read() + return email.parser.Parser().parsestr(value) + + wheel_metadata = get_metadata('WHEEL') + # Check wheel format version is supported. + wheel_version = parse_version(wheel_metadata.get('Wheel-Version')) + wheel_v1 = ( + parse_version('1.0') <= wheel_version < parse_version('2.0dev0') + ) + if not wheel_v1: + raise ValueError( + 'unsupported wheel format version: %s' % wheel_version) + # Extract to target directory. + os.mkdir(destination_eggdir) + zf.extractall(destination_eggdir) + # Convert metadata. + dist_info = os.path.join(destination_eggdir, dist_info) + dist = Distribution.from_location( + destination_eggdir, dist_info, + metadata=PathMetadata(destination_eggdir, dist_info), + ) + + # Note: Evaluate and strip markers now, + # as it's difficult to convert back from the syntax: + # foobar; "linux" in sys_platform and extra == 'test' + def raw_req(req): + req.marker = None + return str(req) + install_requires = list(sorted(map(raw_req, dist.requires()))) + extras_require = { + extra: sorted( + req + for req in map(raw_req, dist.requires((extra,))) + if req not in install_requires + ) + for extra in dist.extras + } + os.rename(dist_info, egg_info) + os.rename( + os.path.join(egg_info, 'METADATA'), + os.path.join(egg_info, 'PKG-INFO'), + ) + setup_dist = SetuptoolsDistribution( + attrs=dict( + install_requires=install_requires, + extras_require=extras_require, + ), + ) + write_requirements( + setup_dist.get_command_obj('egg_info'), + None, + os.path.join(egg_info, 'requires.txt'), + ) + + @staticmethod + def _move_data_entries(destination_eggdir, dist_data): + """Move data entries to their correct location.""" + dist_data = os.path.join(destination_eggdir, dist_data) + dist_data_scripts = os.path.join(dist_data, 'scripts') + if os.path.exists(dist_data_scripts): + egg_info_scripts = os.path.join( + destination_eggdir, 'EGG-INFO', 'scripts') + os.mkdir(egg_info_scripts) + for entry in os.listdir(dist_data_scripts): + # Remove bytecode, as it's not properly handled + # during easy_install scripts install phase. + if entry.endswith('.pyc'): + os.unlink(os.path.join(dist_data_scripts, entry)) + else: + os.rename( + os.path.join(dist_data_scripts, entry), + os.path.join(egg_info_scripts, entry), + ) + os.rmdir(dist_data_scripts) + for subdir in filter(os.path.exists, ( + os.path.join(dist_data, d) + for d in ('data', 'headers', 'purelib', 'platlib') + )): + unpack(subdir, destination_eggdir) + if os.path.exists(dist_data): + os.rmdir(dist_data) + + @staticmethod + def _fix_namespace_packages(egg_info, destination_eggdir): + namespace_packages = os.path.join( + egg_info, 'namespace_packages.txt') + if os.path.exists(namespace_packages): + with open(namespace_packages) as fp: + namespace_packages = fp.read().split() + for mod in namespace_packages: + mod_dir = os.path.join(destination_eggdir, *mod.split('.')) + mod_init = os.path.join(mod_dir, '__init__.py') + if os.path.exists(mod_dir) and not os.path.exists(mod_init): + with open(mod_init, 'w') as fp: + fp.write(NAMESPACE_PACKAGE_INIT) diff --git a/lib/setuptools/windows_support.py b/lib/setuptools/windows_support.py new file mode 100644 index 0000000..cb977cf --- /dev/null +++ b/lib/setuptools/windows_support.py @@ -0,0 +1,29 @@ +import platform +import ctypes + + +def windows_only(func): + if platform.system() != 'Windows': + return lambda *args, **kwargs: None + return func + + +@windows_only +def hide_file(path): + """ + Set the hidden attribute on a file or directory. + + From http://stackoverflow.com/questions/19622133/ + + `path` must be text. + """ + __import__('ctypes.wintypes') + SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW + SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD + SetFileAttributes.restype = ctypes.wintypes.BOOL + + FILE_ATTRIBUTE_HIDDEN = 0x02 + + ret = SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN) + if not ret: + raise ctypes.WinError() diff --git a/lib/six.py b/lib/six.py new file mode 100644 index 0000000..6bf4fd3 --- /dev/null +++ b/lib/six.py @@ -0,0 +1,891 @@ +# Copyright (c) 2010-2017 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson <benjamin@python.org>" +__version__ = "1.11.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + try: + if from_value is None: + raise value + raise value from from_value + finally: + value = None +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/lib/typing.py b/lib/typing.py new file mode 100644 index 0000000..2189cd4 --- /dev/null +++ b/lib/typing.py @@ -0,0 +1,2413 @@ +import abc +from abc import abstractmethod, abstractproperty +import collections +import contextlib +import functools +import re as stdlib_re # Avoid confusion with the re we export. +import sys +import types +try: + import collections.abc as collections_abc +except ImportError: + import collections as collections_abc # Fallback for PY3.2. +if sys.version_info[:2] >= (3, 6): + import _collections_abc # Needed for private function _check_methods # noqa +try: + from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType +except ImportError: + WrapperDescriptorType = type(object.__init__) + MethodWrapperType = type(object().__str__) + MethodDescriptorType = type(str.join) + + +# Please keep __all__ alphabetized within each category. +__all__ = [ + # Super-special typing primitives. + 'Any', + 'Callable', + 'ClassVar', + 'Generic', + 'Optional', + 'Tuple', + 'Type', + 'TypeVar', + 'Union', + + # ABCs (from collections.abc). + 'AbstractSet', # collections.abc.Set. + 'GenericMeta', # subclass of abc.ABCMeta and a metaclass + # for 'Generic' and ABCs below. + 'ByteString', + 'Container', + 'ContextManager', + 'Hashable', + 'ItemsView', + 'Iterable', + 'Iterator', + 'KeysView', + 'Mapping', + 'MappingView', + 'MutableMapping', + 'MutableSequence', + 'MutableSet', + 'Sequence', + 'Sized', + 'ValuesView', + # The following are added depending on presence + # of their non-generic counterparts in stdlib: + # Awaitable, + # AsyncIterator, + # AsyncIterable, + # Coroutine, + # Collection, + # AsyncGenerator, + # AsyncContextManager + + # Structural checks, a.k.a. protocols. + 'Reversible', + 'SupportsAbs', + 'SupportsBytes', + 'SupportsComplex', + 'SupportsFloat', + 'SupportsInt', + 'SupportsRound', + + # Concrete collection types. + 'Counter', + 'Deque', + 'Dict', + 'DefaultDict', + 'List', + 'Set', + 'FrozenSet', + 'NamedTuple', # Not really a type. + 'Generator', + + # One-off things. + 'AnyStr', + 'cast', + 'get_type_hints', + 'NewType', + 'no_type_check', + 'no_type_check_decorator', + 'NoReturn', + 'overload', + 'Text', + 'TYPE_CHECKING', +] + +# The pseudo-submodules 're' and 'io' are part of the public +# namespace, but excluded from __all__ because they might stomp on +# legitimate imports of those modules. + + +def _qualname(x): + if sys.version_info[:2] >= (3, 3): + return x.__qualname__ + else: + # Fall back to just name. + return x.__name__ + + +def _trim_name(nm): + whitelist = ('_TypeAlias', '_ForwardRef', '_TypingBase', '_FinalTypingBase') + if nm.startswith('_') and nm not in whitelist: + nm = nm[1:] + return nm + + +class TypingMeta(type): + """Metaclass for most types defined in typing module + (not a part of public API). + + This overrides __new__() to require an extra keyword parameter + '_root', which serves as a guard against naive subclassing of the + typing classes. Any legitimate class defined using a metaclass + derived from TypingMeta must pass _root=True. + + This also defines a dummy constructor (all the work for most typing + constructs is done in __new__) and a nicer repr(). + """ + + _is_protocol = False + + def __new__(cls, name, bases, namespace, *, _root=False): + if not _root: + raise TypeError("Cannot subclass %s" % + (', '.join(map(_type_repr, bases)) or '()')) + return super().__new__(cls, name, bases, namespace) + + def __init__(self, *args, **kwds): + pass + + def _eval_type(self, globalns, localns): + """Override this in subclasses to interpret forward references. + + For example, List['C'] is internally stored as + List[_ForwardRef('C')], which should evaluate to List[C], + where C is an object found in globalns or localns (searching + localns first, of course). + """ + return self + + def _get_type_vars(self, tvars): + pass + + def __repr__(self): + qname = _trim_name(_qualname(self)) + return '%s.%s' % (self.__module__, qname) + + +class _TypingBase(metaclass=TypingMeta, _root=True): + """Internal indicator of special typing constructs.""" + + __slots__ = ('__weakref__',) + + def __init__(self, *args, **kwds): + pass + + def __new__(cls, *args, **kwds): + """Constructor. + + This only exists to give a better error message in case + someone tries to subclass a special typing object (not a good idea). + """ + if (len(args) == 3 and + isinstance(args[0], str) and + isinstance(args[1], tuple)): + # Close enough. + raise TypeError("Cannot subclass %r" % cls) + return super().__new__(cls) + + # Things that are not classes also need these. + def _eval_type(self, globalns, localns): + return self + + def _get_type_vars(self, tvars): + pass + + def __repr__(self): + cls = type(self) + qname = _trim_name(_qualname(cls)) + return '%s.%s' % (cls.__module__, qname) + + def __call__(self, *args, **kwds): + raise TypeError("Cannot instantiate %r" % type(self)) + + +class _FinalTypingBase(_TypingBase, _root=True): + """Internal mix-in class to prevent instantiation. + + Prevents instantiation unless _root=True is given in class call. + It is used to create pseudo-singleton instances Any, Union, Optional, etc. + """ + + __slots__ = () + + def __new__(cls, *args, _root=False, **kwds): + self = super().__new__(cls, *args, **kwds) + if _root is True: + return self + raise TypeError("Cannot instantiate %r" % cls) + + def __reduce__(self): + return _trim_name(type(self).__name__) + + +class _ForwardRef(_TypingBase, _root=True): + """Internal wrapper to hold a forward reference.""" + + __slots__ = ('__forward_arg__', '__forward_code__', + '__forward_evaluated__', '__forward_value__') + + def __init__(self, arg): + super().__init__(arg) + if not isinstance(arg, str): + raise TypeError('Forward reference must be a string -- got %r' % (arg,)) + try: + code = compile(arg, '<string>', 'eval') + except SyntaxError: + raise SyntaxError('Forward reference must be an expression -- got %r' % + (arg,)) + self.__forward_arg__ = arg + self.__forward_code__ = code + self.__forward_evaluated__ = False + self.__forward_value__ = None + + def _eval_type(self, globalns, localns): + if not self.__forward_evaluated__ or localns is not globalns: + if globalns is None and localns is None: + globalns = localns = {} + elif globalns is None: + globalns = localns + elif localns is None: + localns = globalns + self.__forward_value__ = _type_check( + eval(self.__forward_code__, globalns, localns), + "Forward references must evaluate to types.") + self.__forward_evaluated__ = True + return self.__forward_value__ + + def __eq__(self, other): + if not isinstance(other, _ForwardRef): + return NotImplemented + return (self.__forward_arg__ == other.__forward_arg__ and + self.__forward_value__ == other.__forward_value__) + + def __hash__(self): + return hash((self.__forward_arg__, self.__forward_value__)) + + def __instancecheck__(self, obj): + raise TypeError("Forward references cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Forward references cannot be used with issubclass().") + + def __repr__(self): + return '_ForwardRef(%r)' % (self.__forward_arg__,) + + +class _TypeAlias(_TypingBase, _root=True): + """Internal helper class for defining generic variants of concrete types. + + Note that this is not a type; let's call it a pseudo-type. It cannot + be used in instance and subclass checks in parameterized form, i.e. + ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning + ``False``. + """ + + __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') + + def __init__(self, name, type_var, impl_type, type_checker): + """Initializer. + + Args: + name: The name, e.g. 'Pattern'. + type_var: The type parameter, e.g. AnyStr, or the + specific type, e.g. str. + impl_type: The implementation type. + type_checker: Function that takes an impl_type instance. + and returns a value that should be a type_var instance. + """ + assert isinstance(name, str), repr(name) + assert isinstance(impl_type, type), repr(impl_type) + assert not isinstance(impl_type, TypingMeta), repr(impl_type) + assert isinstance(type_var, (type, _TypingBase)), repr(type_var) + self.name = name + self.type_var = type_var + self.impl_type = impl_type + self.type_checker = type_checker + + def __repr__(self): + return "%s[%s]" % (self.name, _type_repr(self.type_var)) + + def __getitem__(self, parameter): + if not isinstance(self.type_var, TypeVar): + raise TypeError("%s cannot be further parameterized." % self) + if self.type_var.__constraints__ and isinstance(parameter, type): + if not issubclass(parameter, self.type_var.__constraints__): + raise TypeError("%s is not a valid substitution for %s." % + (parameter, self.type_var)) + if isinstance(parameter, TypeVar) and parameter is not self.type_var: + raise TypeError("%s cannot be re-parameterized." % self) + return self.__class__(self.name, parameter, + self.impl_type, self.type_checker) + + def __eq__(self, other): + if not isinstance(other, _TypeAlias): + return NotImplemented + return self.name == other.name and self.type_var == other.type_var + + def __hash__(self): + return hash((self.name, self.type_var)) + + def __instancecheck__(self, obj): + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with isinstance().") + return isinstance(obj, self.impl_type) + + def __subclasscheck__(self, cls): + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with issubclass().") + return issubclass(cls, self.impl_type) + + +def _get_type_vars(types, tvars): + for t in types: + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): + t._get_type_vars(tvars) + + +def _type_vars(types): + tvars = [] + _get_type_vars(types, tvars) + return tuple(tvars) + + +def _eval_type(t, globalns, localns): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): + return t._eval_type(globalns, localns) + return t + + +def _type_check(arg, msg): + """Check that the argument is a type, and return it (internal helper). + + As a special case, accept None and return type(None) instead. + Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable. + + The msg argument is a human-readable error message, e.g. + + "Union[arg, ...]: arg should be a type." + + We append the repr() of the actual value (truncated to 100 chars). + """ + if arg is None: + return type(None) + if isinstance(arg, str): + arg = _ForwardRef(arg) + if ( + isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or + not isinstance(arg, (type, _TypingBase)) and not callable(arg) + ): + raise TypeError(msg + " Got %.100r." % (arg,)) + # Bare Union etc. are not valid as type arguments + if ( + type(arg).__name__ in ('_Union', '_Optional') and + not getattr(arg, '__origin__', None) or + isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol) + ): + raise TypeError("Plain %s is not valid as type argument" % arg) + return arg + + +def _type_repr(obj): + """Return the repr() of an object, special-casing types (internal helper). + + If obj is a type, we return a shorter version than the default + type.__repr__, based on the module and qualified name, which is + typically enough to uniquely identify a type. For everything + else, we fall back on repr(obj). + """ + if isinstance(obj, type) and not isinstance(obj, TypingMeta): + if obj.__module__ == 'builtins': + return _qualname(obj) + return '%s.%s' % (obj.__module__, _qualname(obj)) + if obj is ...: + return('...') + if isinstance(obj, types.FunctionType): + return obj.__name__ + return repr(obj) + + +class _Any(_FinalTypingBase, _root=True): + """Special type indicating an unconstrained type. + + - Any is compatible with every type. + - Any assumed to have all methods. + - All values assumed to be instances of Any. + + Note that all the above statements are true from the point of view of + static type checkers. At runtime, Any should not be used with instance + or class checks. + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("Any cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Any cannot be used with issubclass().") + + +Any = _Any(_root=True) + + +class _NoReturn(_FinalTypingBase, _root=True): + """Special type indicating functions that never return. + Example:: + + from typing import NoReturn + + def stop() -> NoReturn: + raise Exception('no way') + + This type is invalid in other positions, e.g., ``List[NoReturn]`` + will fail in static type checkers. + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("NoReturn cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("NoReturn cannot be used with issubclass().") + + +NoReturn = _NoReturn(_root=True) + + +class TypeVar(_TypingBase, _root=True): + """Type variable. + + Usage:: + + T = TypeVar('T') # Can be anything + A = TypeVar('A', str, bytes) # Must be str or bytes + + Type variables exist primarily for the benefit of static type + checkers. They serve as the parameters for generic types as well + as for generic function definitions. See class Generic for more + information on generic types. Generic functions work as follows: + + def repeat(x: T, n: int) -> List[T]: + '''Return a list containing n references to x.''' + return [x]*n + + def longest(x: A, y: A) -> A: + '''Return the longest of two strings.''' + return x if len(x) >= len(y) else y + + The latter example's signature is essentially the overloading + of (str, str) -> str and (bytes, bytes) -> bytes. Also note + that if the arguments are instances of some subclass of str, + the return type is still plain str. + + At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError. + + Type variables defined with covariant=True or contravariant=True + can be used do declare covariant or contravariant generic types. + See PEP 484 for more details. By default generic types are invariant + in all type variables. + + Type variables can be introspected. e.g.: + + T.__name__ == 'T' + T.__constraints__ == () + T.__covariant__ == False + T.__contravariant__ = False + A.__constraints__ == (str, bytes) + """ + + __slots__ = ('__name__', '__bound__', '__constraints__', + '__covariant__', '__contravariant__') + + def __init__(self, name, *constraints, bound=None, + covariant=False, contravariant=False): + super().__init__(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) + self.__name__ = name + if covariant and contravariant: + raise ValueError("Bivariant types are not supported.") + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if constraints and bound is not None: + raise TypeError("Constraints cannot be combined with bound=...") + if constraints and len(constraints) == 1: + raise TypeError("A single constraint is not allowed") + msg = "TypeVar(name, constraint, ...): constraints must be types." + self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) + if bound: + self.__bound__ = _type_check(bound, "Bound must be a type.") + else: + self.__bound__ = None + + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __instancecheck__(self, instance): + raise TypeError("Type variables cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Type variables cannot be used with issubclass().") + + +# Some unconstrained type variables. These are used by the container types. +# (These are not for export.) +T = TypeVar('T') # Any type. +KT = TypeVar('KT') # Key type. +VT = TypeVar('VT') # Value type. +T_co = TypeVar('T_co', covariant=True) # Any type covariant containers. +V_co = TypeVar('V_co', covariant=True) # Any type covariant containers. +VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers. +T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. + +# A useful type variable with constraints. This represents string types. +# (This one *is* for export!) +AnyStr = TypeVar('AnyStr', bytes, str) + + +def _replace_arg(arg, tvars, args): + """An internal helper function: replace arg if it is a type variable + found in tvars with corresponding substitution from args or + with corresponding substitution sub-tree if arg is a generic type. + """ + + if tvars is None: + tvars = [] + if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)): + return arg._subs_tree(tvars, args) + if isinstance(arg, TypeVar): + for i, tvar in enumerate(tvars): + if arg == tvar: + return args[i] + return arg + + +# Special typing constructs Union, Optional, Generic, Callable and Tuple +# use three special attributes for internal bookkeeping of generic types: +# * __parameters__ is a tuple of unique free type parameters of a generic +# type, for example, Dict[T, T].__parameters__ == (T,); +# * __origin__ keeps a reference to a type that was subscripted, +# e.g., Union[T, int].__origin__ == Union; +# * __args__ is a tuple of all arguments used in subscripting, +# e.g., Dict[T, int].__args__ == (T, int). + + +def _subs_tree(cls, tvars=None, args=None): + """An internal helper function: calculate substitution tree + for generic cls after replacing its type parameters with + substitutions in tvars -> args (if any). + Repeat the same following __origin__'s. + + Return a list of arguments with all possible substitutions + performed. Arguments that are generic classes themselves are represented + as tuples (so that no new classes are created by this function). + For example: _subs_tree(List[Tuple[int, T]][str]) == [(Tuple, int, str)] + """ + + if cls.__origin__ is None: + return cls + # Make of chain of origins (i.e. cls -> cls.__origin__) + current = cls.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + # Replace type variables in __args__ if asked ... + tree_args = [] + for arg in cls.__args__: + tree_args.append(_replace_arg(arg, tvars, args)) + # ... then continue replacing down the origin chain. + for ocls in orig_chain: + new_tree_args = [] + for arg in ocls.__args__: + new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) + tree_args = new_tree_args + return tree_args + + +def _remove_dups_flatten(parameters): + """An internal helper for Union creation and substitution: flatten Union's + among parameters, then remove duplicates and strict subclasses. + """ + + # Flatten out Union[Union[...], ...]. + params = [] + for p in parameters: + if isinstance(p, _Union) and p.__origin__ is Union: + params.extend(p.__args__) + elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union: + params.extend(p[1:]) + else: + params.append(p) + # Weed out strict duplicates, preserving the first of each occurrence. + all_params = set(params) + if len(all_params) < len(params): + new_params = [] + for t in params: + if t in all_params: + new_params.append(t) + all_params.remove(t) + params = new_params + assert not all_params, all_params + # Weed out subclasses. + # E.g. Union[int, Employee, Manager] == Union[int, Employee]. + # If object is present it will be sole survivor among proper classes. + # Never discard type variables. + # (In particular, Union[str, AnyStr] != AnyStr.) + all_params = set(params) + for t1 in params: + if not isinstance(t1, type): + continue + if any(isinstance(t2, type) and issubclass(t1, t2) + for t2 in all_params - {t1} + if not (isinstance(t2, GenericMeta) and + t2.__origin__ is not None)): + all_params.remove(t1) + return tuple(t for t in params if t in all_params) + + +def _check_generic(cls, parameters): + # Check correct count for parameters of a generic cls (internal helper). + if not cls.__parameters__: + raise TypeError("%s is not a generic class" % repr(cls)) + alen = len(parameters) + elen = len(cls.__parameters__) + if alen != elen: + raise TypeError("Too %s parameters for %s; actual %s, expected %s" % + ("many" if alen > elen else "few", repr(cls), alen, elen)) + + +_cleanups = [] + + +def _tp_cache(func): + """Internal wrapper caching __getitem__ of generic types with a fallback to + original function for non-hashable arguments. + """ + + cached = functools.lru_cache()(func) + _cleanups.append(cached.cache_clear) + + @functools.wraps(func) + def inner(*args, **kwds): + try: + return cached(*args, **kwds) + except TypeError: + pass # All real errors (not unhashable args) are raised below. + return func(*args, **kwds) + return inner + + +class _Union(_FinalTypingBase, _root=True): + """Union type; Union[X, Y] means either X or Y. + + To define a union, use e.g. Union[int, str]. Details: + + - The arguments must be types and there must be at least one. + + - None as an argument is a special case and is replaced by + type(None). + + - Unions of unions are flattened, e.g.:: + + Union[Union[int, str], float] == Union[int, str, float] + + - Unions of a single argument vanish, e.g.:: + + Union[int] == int # The constructor actually returns int + + - Redundant arguments are skipped, e.g.:: + + Union[int, str, int] == Union[int, str] + + - When comparing unions, the argument order is ignored, e.g.:: + + Union[int, str] == Union[str, int] + + - When two arguments have a subclass relationship, the least + derived argument is kept, e.g.:: + + class Employee: pass + class Manager(Employee): pass + Union[int, Employee, Manager] == Union[int, Employee] + Union[Manager, int, Employee] == Union[int, Employee] + Union[Employee, Manager] == Employee + + - Similar for object:: + + Union[int, object] == object + + - You cannot subclass or instantiate a union. + + - You can use Optional[X] as a shorthand for Union[X, None]. + """ + + __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') + + def __new__(cls, parameters=None, origin=None, *args, _root=False): + self = super().__new__(cls, parameters, origin, *args, _root=_root) + if origin is None: + self.__parameters__ = None + self.__args__ = None + self.__origin__ = None + self.__tree_hash__ = hash(frozenset(('Union',))) + return self + if not isinstance(parameters, tuple): + raise TypeError("Expected parameters=<tuple>") + if origin is Union: + parameters = _remove_dups_flatten(parameters) + # It's not a union if there's only one type left. + if len(parameters) == 1: + return parameters[0] + self.__parameters__ = _type_vars(parameters) + self.__args__ = parameters + self.__origin__ = origin + # Pre-calculate the __hash__ on instantiation. + # This improves speed for complex substitutions. + subs_tree = self._subs_tree() + if isinstance(subs_tree, tuple): + self.__tree_hash__ = hash(frozenset(subs_tree)) + else: + self.__tree_hash__ = hash(subs_tree) + return self + + def _eval_type(self, globalns, localns): + if self.__args__ is None: + return self + ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) + ev_origin = _eval_type(self.__origin__, globalns, localns) + if ev_args == self.__args__ and ev_origin == self.__origin__: + # Everything is already evaluated. + return self + return self.__class__(ev_args, ev_origin, _root=True) + + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) + + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + tree = self._subs_tree() + if not isinstance(tree, tuple): + return repr(tree) + return tree[0]._tree_repr(tree) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super().__repr__() + '[%s]' % ', '.join(arg_list) + + @_tp_cache + def __getitem__(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Union of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if self.__origin__ is None: + msg = "Union[arg, ...]: each arg must be a type." + else: + msg = "Parameters to generic types must be types." + parameters = tuple(_type_check(p, msg) for p in parameters) + if self is not Union: + _check_generic(self, parameters) + return self.__class__(parameters, origin=self, _root=True) + + def _subs_tree(self, tvars=None, args=None): + if self is Union: + return Union # Nothing to substitute + tree_args = _subs_tree(self, tvars, args) + tree_args = _remove_dups_flatten(tree_args) + if len(tree_args) == 1: + return tree_args[0] # Union of a single type is that type + return (Union,) + tree_args + + def __eq__(self, other): + if isinstance(other, _Union): + return self.__tree_hash__ == other.__tree_hash__ + elif self is not Union: + return self._subs_tree() == other + else: + return self is other + + def __hash__(self): + return self.__tree_hash__ + + def __instancecheck__(self, obj): + raise TypeError("Unions cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Unions cannot be used with issubclass().") + + +Union = _Union(_root=True) + + +class _Optional(_FinalTypingBase, _root=True): + """Optional type. + + Optional[X] is equivalent to Union[X, None]. + """ + + __slots__ = () + + @_tp_cache + def __getitem__(self, arg): + arg = _type_check(arg, "Optional[t] requires a single type.") + return Union[arg, type(None)] + + +Optional = _Optional(_root=True) + + +def _next_in_mro(cls): + """Helper for Generic.__new__. + + Returns the class after the last occurrence of Generic or + Generic[...] in cls.__mro__. + """ + next_in_mro = object + # Look for the last occurrence of Generic or Generic[...]. + for i, c in enumerate(cls.__mro__[:-1]): + if isinstance(c, GenericMeta) and c._gorg is Generic: + next_in_mro = cls.__mro__[i + 1] + return next_in_mro + + +def _make_subclasshook(cls): + """Construct a __subclasshook__ callable that incorporates + the associated __extra__ class in subclass checks performed + against cls. + """ + if isinstance(cls.__extra__, abc.ABCMeta): + # The logic mirrors that of ABCMeta.__subclasscheck__. + # Registered classes need not be checked here because + # cls and its extra share the same _abc_registry. + def __extrahook__(subclass): + res = cls.__extra__.__subclasshook__(subclass) + if res is not NotImplemented: + return res + if cls.__extra__ in subclass.__mro__: + return True + for scls in cls.__extra__.__subclasses__(): + if isinstance(scls, GenericMeta): + continue + if issubclass(subclass, scls): + return True + return NotImplemented + else: + # For non-ABC extras we'll just call issubclass(). + def __extrahook__(subclass): + if cls.__extra__ and issubclass(subclass, cls.__extra__): + return True + return NotImplemented + return __extrahook__ + + +def _no_slots_copy(dct): + """Internal helper: copy class __dict__ and clean slots class variables. + (They will be re-created if necessary by normal class machinery.) + """ + dict_copy = dict(dct) + if '__slots__' in dict_copy: + for slot in dict_copy['__slots__']: + dict_copy.pop(slot, None) + return dict_copy + + +class GenericMeta(TypingMeta, abc.ABCMeta): + """Metaclass for generic types. + + This is a metaclass for typing.Generic and generic ABCs defined in + typing module. User defined subclasses of GenericMeta can override + __new__ and invoke super().__new__. Note that GenericMeta.__new__ + has strict rules on what is allowed in its bases argument: + * plain Generic is disallowed in bases; + * Generic[...] should appear in bases at most once; + * if Generic[...] is present, then it should list all type variables + that appear in other bases. + In addition, type of all generic bases is erased, e.g., C[int] is + stripped to plain C. + """ + + def __new__(cls, name, bases, namespace, + tvars=None, args=None, origin=None, extra=None, orig_bases=None): + """Create a new generic class. GenericMeta.__new__ accepts + keyword arguments that are used for internal bookkeeping, therefore + an override should pass unused keyword arguments to super(). + """ + if tvars is not None: + # Called from __getitem__() below. + assert origin is not None + assert all(isinstance(t, TypeVar) for t in tvars), tvars + else: + # Called from class statement. + assert tvars is None, tvars + assert args is None, args + assert origin is None, origin + + # Get the full set of tvars from the bases. + tvars = _type_vars(bases) + # Look for Generic[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...]. + gvars = None + for base in bases: + if base is Generic: + raise TypeError("Cannot inherit from plain Generic") + if (isinstance(base, GenericMeta) and + base.__origin__ is Generic): + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + raise TypeError( + "Some type variables (%s) " + "are not listed in Generic[%s]" % + (", ".join(str(t) for t in tvars if t not in gvarset), + ", ".join(str(g) for g in gvars))) + tvars = gvars + + initial_bases = bases + if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: + bases = (extra,) + bases + bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) + + # remove bare Generic from bases if there are other generic bases + if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): + bases = tuple(b for b in bases if b is not Generic) + namespace.update({'__origin__': origin, '__extra__': extra, + '_gorg': None if not origin else origin._gorg}) + self = super().__new__(cls, name, bases, namespace, _root=True) + super(GenericMeta, self).__setattr__('_gorg', + self if not origin else origin._gorg) + self.__parameters__ = tvars + # Be prepared that GenericMeta will be subclassed by TupleMeta + # and CallableMeta, those two allow ..., (), or [] in __args___. + self.__args__ = tuple(... if a is _TypingEllipsis else + () if a is _TypingEmpty else + a for a in args) if args else None + # Speed hack (https://github.com/python/typing/issues/196). + self.__next_in_mro__ = _next_in_mro(self) + # Preserve base classes on subclassing (__bases__ are type erased now). + if orig_bases is None: + self.__orig_bases__ = initial_bases + + # This allows unparameterized generic collections to be used + # with issubclass() and isinstance() in the same way as their + # collections.abc counterparts (e.g., isinstance([], Iterable)). + if ( + '__subclasshook__' not in namespace and extra or + # allow overriding + getattr(self.__subclasshook__, '__name__', '') == '__extrahook__' + ): + self.__subclasshook__ = _make_subclasshook(self) + if isinstance(extra, abc.ABCMeta): + self._abc_registry = extra._abc_registry + self._abc_cache = extra._abc_cache + elif origin is not None: + self._abc_registry = origin._abc_registry + self._abc_cache = origin._abc_cache + + if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. + self.__qualname__ = origin.__qualname__ + self.__tree_hash__ = (hash(self._subs_tree()) if origin else + super(GenericMeta, self).__hash__()) + return self + + # _abc_negative_cache and _abc_negative_cache_version + # realised as descriptors, since GenClass[t1, t2, ...] always + # share subclass info with GenClass. + # This is an important memory optimization. + @property + def _abc_negative_cache(self): + if isinstance(self.__extra__, abc.ABCMeta): + return self.__extra__._abc_negative_cache + return self._gorg._abc_generic_negative_cache + + @_abc_negative_cache.setter + def _abc_negative_cache(self, value): + if self.__origin__ is None: + if isinstance(self.__extra__, abc.ABCMeta): + self.__extra__._abc_negative_cache = value + else: + self._abc_generic_negative_cache = value + + @property + def _abc_negative_cache_version(self): + if isinstance(self.__extra__, abc.ABCMeta): + return self.__extra__._abc_negative_cache_version + return self._gorg._abc_generic_negative_cache_version + + @_abc_negative_cache_version.setter + def _abc_negative_cache_version(self, value): + if self.__origin__ is None: + if isinstance(self.__extra__, abc.ABCMeta): + self.__extra__._abc_negative_cache_version = value + else: + self._abc_generic_negative_cache_version = value + + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) + + def _eval_type(self, globalns, localns): + ev_origin = (self.__origin__._eval_type(globalns, localns) + if self.__origin__ else None) + ev_args = tuple(_eval_type(a, globalns, localns) for a + in self.__args__) if self.__args__ else None + if ev_origin == self.__origin__ and ev_args == self.__args__: + return self + return self.__class__(self.__name__, + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=_type_vars(ev_args) if ev_args else None, + args=ev_args, + origin=ev_origin, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + return self._tree_repr(self._subs_tree()) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if arg == (): + arg_list.append('()') + elif not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super().__repr__() + '[%s]' % ', '.join(arg_list) + + def _subs_tree(self, tvars=None, args=None): + if self.__origin__ is None: + return self + tree_args = _subs_tree(self, tvars, args) + return (self._gorg,) + tuple(tree_args) + + def __eq__(self, other): + if not isinstance(other, GenericMeta): + return NotImplemented + if self.__origin__ is None or other.__origin__ is None: + return self is other + return self.__tree_hash__ == other.__tree_hash__ + + def __hash__(self): + return self.__tree_hash__ + + @_tp_cache + def __getitem__(self, params): + if not isinstance(params, tuple): + params = (params,) + if not params and self._gorg is not Tuple: + raise TypeError( + "Parameter list to %s[...] cannot be empty" % _qualname(self)) + msg = "Parameters to generic types must be types." + params = tuple(_type_check(p, msg) for p in params) + if self is Generic: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, TypeVar) for p in params): + raise TypeError( + "Parameters to Generic[...] must all be type variables") + if len(set(params)) != len(params): + raise TypeError( + "Parameters to Generic[...] must all be unique") + tvars = params + args = params + elif self in (Tuple, Callable): + tvars = _type_vars(params) + args = params + elif self is _Protocol: + # _Protocol is internal, don't check anything. + tvars = params + args = params + elif self.__origin__ in (Generic, _Protocol): + # Can't subscript Generic[...] or _Protocol[...]. + raise TypeError("Cannot subscript already-subscripted %s" % + repr(self)) + else: + # Subscripting a regular Generic subclass. + _check_generic(self, params) + tvars = _type_vars(params) + args = params + + prepend = (self,) if self.__origin__ is None else () + return self.__class__(self.__name__, + prepend + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=tvars, + args=args, + origin=self, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + + def __subclasscheck__(self, cls): + if self.__origin__ is not None: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") + return False + if self is Generic: + raise TypeError("Class %r cannot be used with class " + "or instance checks" % self) + return super().__subclasscheck__(cls) + + def __instancecheck__(self, instance): + # Since we extend ABC.__subclasscheck__ and + # ABC.__instancecheck__ inlines the cache checking done by the + # latter, we must extend __instancecheck__ too. For simplicity + # we just skip the cache check -- instance checks for generic + # classes are supposed to be rare anyways. + return issubclass(instance.__class__, self) + + def __setattr__(self, attr, value): + # We consider all the subscripted generics as proxies for original class + if ( + attr.startswith('__') and attr.endswith('__') or + attr.startswith('_abc_') or + self._gorg is None # The class is not fully created, see #typing/506 + ): + super(GenericMeta, self).__setattr__(attr, value) + else: + super(GenericMeta, self._gorg).__setattr__(attr, value) + + +# Prevent checks for Generic to crash when defining Generic. +Generic = None + + +def _generic_new(base_cls, cls, *args, **kwds): + # Assure type is erased on instantiation, + # but attempt to store it in __orig_class__ + if cls.__origin__ is None: + if (base_cls.__new__ is object.__new__ and + cls.__init__ is not object.__init__): + return base_cls.__new__(cls) + else: + return base_cls.__new__(cls, *args, **kwds) + else: + origin = cls._gorg + if (base_cls.__new__ is object.__new__ and + cls.__init__ is not object.__init__): + obj = base_cls.__new__(origin) + else: + obj = base_cls.__new__(origin, *args, **kwds) + try: + obj.__orig_class__ = cls + except AttributeError: + pass + obj.__init__(*args, **kwds) + return obj + + +class Generic(metaclass=GenericMeta): + """Abstract base class for generic types. + + A generic type is typically declared by inheriting from + this class parameterized with one or more type variables. + For example, a generic mapping type might be defined as:: + + class Mapping(Generic[KT, VT]): + def __getitem__(self, key: KT) -> VT: + ... + # Etc. + + This class can then be used as follows:: + + def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: + try: + return mapping[key] + except KeyError: + return default + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Generic: + raise TypeError("Type Generic cannot be instantiated; " + "it can be used only as a base class") + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +class _TypingEmpty: + """Internal placeholder for () or []. Used by TupleMeta and CallableMeta + to allow empty list/tuple in specific places, without allowing them + to sneak in where prohibited. + """ + + +class _TypingEllipsis: + """Internal placeholder for ... (ellipsis).""" + + +class TupleMeta(GenericMeta): + """Metaclass for Tuple (internal).""" + + @_tp_cache + def __getitem__(self, parameters): + if self.__origin__ is not None or self._gorg is not Tuple: + # Normal generic rules apply if this is not the first subscription + # or a subscription of a subclass. + return super().__getitem__(parameters) + if parameters == (): + return super().__getitem__((_TypingEmpty,)) + if not isinstance(parameters, tuple): + parameters = (parameters,) + if len(parameters) == 2 and parameters[1] is ...: + msg = "Tuple[t, ...]: t must be a type." + p = _type_check(parameters[0], msg) + return super().__getitem__((p, _TypingEllipsis)) + msg = "Tuple[t0, t1, ...]: each t must be a type." + parameters = tuple(_type_check(p, msg) for p in parameters) + return super().__getitem__(parameters) + + def __instancecheck__(self, obj): + if self.__args__ is None: + return isinstance(obj, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with isinstance().") + + def __subclasscheck__(self, cls): + if self.__args__ is None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") + + +class Tuple(tuple, extra=tuple, metaclass=TupleMeta): + """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + + Example: Tuple[T1, T2] is a tuple of two elements corresponding + to type variables T1 and T2. Tuple[int, float, str] is a tuple + of an int, a float and a string. + + To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Tuple: + raise TypeError("Type Tuple cannot be instantiated; " + "use tuple() instead") + return _generic_new(tuple, cls, *args, **kwds) + + +class CallableMeta(GenericMeta): + """Metaclass for Callable (internal).""" + + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + return self._tree_repr(self._subs_tree()) + + def _tree_repr(self, tree): + if self._gorg is not Callable: + return super()._tree_repr(tree) + # For actual Callable (not its subclass) we override + # super()._tree_repr() for nice formatting. + arg_list = [] + for arg in tree[1:]: + if not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + if arg_list[0] == '...': + return repr(tree[0]) + '[..., %s]' % arg_list[1] + return (repr(tree[0]) + + '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) + + def __getitem__(self, parameters): + """A thin wrapper around __getitem_inner__ to provide the latter + with hashable arguments to improve speed. + """ + + if self.__origin__ is not None or self._gorg is not Callable: + return super().__getitem__(parameters) + if not isinstance(parameters, tuple) or len(parameters) != 2: + raise TypeError("Callable must be used as " + "Callable[[arg, ...], result].") + args, result = parameters + if args is Ellipsis: + parameters = (Ellipsis, result) + else: + if not isinstance(args, list): + raise TypeError("Callable[args, result]: args must be a list." + " Got %.100r." % (args,)) + parameters = (tuple(args), result) + return self.__getitem_inner__(parameters) + + @_tp_cache + def __getitem_inner__(self, parameters): + args, result = parameters + msg = "Callable[args, result]: result must be a type." + result = _type_check(result, msg) + if args is Ellipsis: + return super().__getitem__((_TypingEllipsis, result)) + msg = "Callable[[arg, ...], result]: each arg must be a type." + args = tuple(_type_check(arg, msg) for arg in args) + parameters = args + (result,) + return super().__getitem__(parameters) + + +class Callable(extra=collections_abc.Callable, metaclass=CallableMeta): + """Callable type; Callable[[int], str] is a function of (int) -> str. + + The subscription syntax must always be used with exactly two + values: the argument list and the return type. The argument list + must be a list of types or ellipsis; the return type must be a single type. + + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Callable: + raise TypeError("Type Callable cannot be instantiated; " + "use a non-abstract subclass instead") + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +class _ClassVar(_FinalTypingBase, _root=True): + """Special type construct to mark class variables. + + An annotation wrapped in ClassVar indicates that a given + attribute is intended to be used as a class variable and + should not be set on instances of that class. Usage:: + + class Starship: + stats: ClassVar[Dict[str, int]] = {} # class variable + damage: int = 10 # instance variable + + ClassVar accepts only types and cannot be further subscribed. + + Note that ClassVar is not a class itself, and should not + be used with isinstance() or issubclass(). + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(_type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = _eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(_type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _ClassVar): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + +ClassVar = _ClassVar(_root=True) + + +def cast(typ, val): + """Cast a value to a type. + + This returns the value unchanged. To the type checker this + signals that the return value has the designated type, but at + runtime we intentionally don't check anything (we want this + to be as fast as possible). + """ + return val + + +def _get_defaults(func): + """Internal helper to extract the default arguments, by name.""" + try: + code = func.__code__ + except AttributeError: + # Some built-in functions don't have __code__, __defaults__, etc. + return {} + pos_count = code.co_argcount + arg_names = code.co_varnames + arg_names = arg_names[:pos_count] + defaults = func.__defaults__ or () + kwdefaults = func.__kwdefaults__ + res = dict(kwdefaults) if kwdefaults else {} + pos_offset = pos_count - len(defaults) + for name, value in zip(arg_names[pos_offset:], defaults): + assert name not in res + res[name] = value + return res + + +_allowed_types = (types.FunctionType, types.BuiltinFunctionType, + types.MethodType, types.ModuleType, + WrapperDescriptorType, MethodWrapperType, MethodDescriptorType) + + +def get_type_hints(obj, globalns=None, localns=None): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, and if necessary + adds Optional[t] if a default value equal to None is set. + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + + if getattr(obj, '__no_type_check__', None): + return {} + # Classes require a special treatment. + if isinstance(obj, type): + hints = {} + for base in reversed(obj.__mro__): + if globalns is None: + base_globals = sys.modules[base.__module__].__dict__ + else: + base_globals = globalns + ann = base.__dict__.get('__annotations__', {}) + for name, value in ann.items(): + if value is None: + value = type(None) + if isinstance(value, str): + value = _ForwardRef(value) + value = _eval_type(value, base_globals, localns) + hints[name] = value + return hints + + if globalns is None: + if isinstance(obj, types.ModuleType): + globalns = obj.__dict__ + else: + globalns = getattr(obj, '__globals__', {}) + if localns is None: + localns = globalns + elif localns is None: + localns = globalns + hints = getattr(obj, '__annotations__', None) + if hints is None: + # Return empty annotations for something that _could_ have them. + if isinstance(obj, _allowed_types): + return {} + else: + raise TypeError('{!r} is not a module, class, method, ' + 'or function.'.format(obj)) + defaults = _get_defaults(obj) + hints = dict(hints) + for name, value in hints.items(): + if value is None: + value = type(None) + if isinstance(value, str): + value = _ForwardRef(value) + value = _eval_type(value, globalns, localns) + if name in defaults and defaults[name] is None: + value = Optional[value] + hints[name] = value + return hints + + +def no_type_check(arg): + """Decorator to indicate that annotations are not type hints. + + The argument must be a class or function; if it is a class, it + applies recursively to all methods and classes defined in that class + (but not to methods defined in its superclasses or subclasses). + + This mutates the function(s) or class(es) in place. + """ + if isinstance(arg, type): + arg_attrs = arg.__dict__.copy() + for attr, val in arg.__dict__.items(): + if val in arg.__bases__ + (arg,): + arg_attrs.pop(attr) + for obj in arg_attrs.values(): + if isinstance(obj, types.FunctionType): + obj.__no_type_check__ = True + if isinstance(obj, type): + no_type_check(obj) + try: + arg.__no_type_check__ = True + except TypeError: # built-in classes + pass + return arg + + +def no_type_check_decorator(decorator): + """Decorator to give another decorator the @no_type_check effect. + + This wraps the decorator with something that wraps the decorated + function in @no_type_check. + """ + + @functools.wraps(decorator) + def wrapped_decorator(*args, **kwds): + func = decorator(*args, **kwds) + func = no_type_check(func) + return func + + return wrapped_decorator + + +def _overload_dummy(*args, **kwds): + """Helper for @overload to raise when called.""" + raise NotImplementedError( + "You should not call an overloaded function. " + "A series of @overload-decorated functions " + "outside a stub module should always be followed " + "by an implementation that is not @overload-ed.") + + +def overload(func): + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + """ + return _overload_dummy + + +class _ProtocolMeta(GenericMeta): + """Internal metaclass for _Protocol. + + This exists so _Protocol classes can be generic without deriving + from Generic. + """ + + def __instancecheck__(self, obj): + if _Protocol not in self.__bases__: + return super().__instancecheck__(obj) + raise TypeError("Protocols cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + if not self._is_protocol: + # No structural checks since this isn't a protocol. + return NotImplemented + + if self is _Protocol: + # Every class is a subclass of the empty protocol. + return True + + # Find all attributes defined in the protocol. + attrs = self._get_protocol_attrs() + + for attr in attrs: + if not any(attr in d.__dict__ for d in cls.__mro__): + return False + return True + + def _get_protocol_attrs(self): + # Get all Protocol base classes. + protocol_bases = [] + for c in self.__mro__: + if getattr(c, '_is_protocol', False) and c.__name__ != '_Protocol': + protocol_bases.append(c) + + # Get attributes included in protocol. + attrs = set() + for base in protocol_bases: + for attr in base.__dict__.keys(): + # Include attributes not defined in any non-protocol bases. + for c in self.__mro__: + if (c is not base and attr in c.__dict__ and + not getattr(c, '_is_protocol', False)): + break + else: + if (not attr.startswith('_abc_') and + attr != '__abstractmethods__' and + attr != '__annotations__' and + attr != '__weakref__' and + attr != '_is_protocol' and + attr != '_gorg' and + attr != '__dict__' and + attr != '__args__' and + attr != '__slots__' and + attr != '_get_protocol_attrs' and + attr != '__next_in_mro__' and + attr != '__parameters__' and + attr != '__origin__' and + attr != '__orig_bases__' and + attr != '__extra__' and + attr != '__tree_hash__' and + attr != '__module__'): + attrs.add(attr) + + return attrs + + +class _Protocol(metaclass=_ProtocolMeta): + """Internal base class for protocol classes. + + This implements a simple-minded structural issubclass check + (similar but more general than the one-offs in collections.abc + such as Hashable). + """ + + __slots__ = () + + _is_protocol = True + + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. + +Hashable = collections_abc.Hashable # Not generic. + + +if hasattr(collections_abc, 'Awaitable'): + class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): + __slots__ = () + + __all__.append('Awaitable') + + +if hasattr(collections_abc, 'Coroutine'): + class Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co], + extra=collections_abc.Coroutine): + __slots__ = () + + __all__.append('Coroutine') + + +if hasattr(collections_abc, 'AsyncIterable'): + + class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): + __slots__ = () + + class AsyncIterator(AsyncIterable[T_co], + extra=collections_abc.AsyncIterator): + __slots__ = () + + __all__.append('AsyncIterable') + __all__.append('AsyncIterator') + + +class Iterable(Generic[T_co], extra=collections_abc.Iterable): + __slots__ = () + + +class Iterator(Iterable[T_co], extra=collections_abc.Iterator): + __slots__ = () + + +class SupportsInt(_Protocol): + __slots__ = () + + @abstractmethod + def __int__(self) -> int: + pass + + +class SupportsFloat(_Protocol): + __slots__ = () + + @abstractmethod + def __float__(self) -> float: + pass + + +class SupportsComplex(_Protocol): + __slots__ = () + + @abstractmethod + def __complex__(self) -> complex: + pass + + +class SupportsBytes(_Protocol): + __slots__ = () + + @abstractmethod + def __bytes__(self) -> bytes: + pass + + +class SupportsAbs(_Protocol[T_co]): + __slots__ = () + + @abstractmethod + def __abs__(self) -> T_co: + pass + + +class SupportsRound(_Protocol[T_co]): + __slots__ = () + + @abstractmethod + def __round__(self, ndigits: int = 0) -> T_co: + pass + + +if hasattr(collections_abc, 'Reversible'): + class Reversible(Iterable[T_co], extra=collections_abc.Reversible): + __slots__ = () +else: + class Reversible(_Protocol[T_co]): + __slots__ = () + + @abstractmethod + def __reversed__(self) -> 'Iterator[T_co]': + pass + + +Sized = collections_abc.Sized # Not generic. + + +class Container(Generic[T_co], extra=collections_abc.Container): + __slots__ = () + + +if hasattr(collections_abc, 'Collection'): + class Collection(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Collection): + __slots__ = () + + __all__.append('Collection') + + +# Callable was defined earlier. + +if hasattr(collections_abc, 'Collection'): + class AbstractSet(Collection[T_co], + extra=collections_abc.Set): + __slots__ = () +else: + class AbstractSet(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Set): + __slots__ = () + + +class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): + __slots__ = () + + +# NOTE: It is only covariant in the value type. +if hasattr(collections_abc, 'Collection'): + class Mapping(Collection[KT], Generic[KT, VT_co], + extra=collections_abc.Mapping): + __slots__ = () +else: + class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], + extra=collections_abc.Mapping): + __slots__ = () + + +class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): + __slots__ = () + + +if hasattr(collections_abc, 'Reversible'): + if hasattr(collections_abc, 'Collection'): + class Sequence(Reversible[T_co], Collection[T_co], + extra=collections_abc.Sequence): + __slots__ = () + else: + class Sequence(Sized, Reversible[T_co], Container[T_co], + extra=collections_abc.Sequence): + __slots__ = () +else: + class Sequence(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Sequence): + __slots__ = () + + +class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): + __slots__ = () + + +class ByteString(Sequence[int], extra=collections_abc.ByteString): + __slots__ = () + + +class List(list, MutableSequence[T], extra=list): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is List: + raise TypeError("Type List cannot be instantiated; " + "use list() instead") + return _generic_new(list, cls, *args, **kwds) + + +class Deque(collections.deque, MutableSequence[T], extra=collections.deque): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Deque: + return collections.deque(*args, **kwds) + return _generic_new(collections.deque, cls, *args, **kwds) + + +class Set(set, MutableSet[T], extra=set): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Set: + raise TypeError("Type Set cannot be instantiated; " + "use set() instead") + return _generic_new(set, cls, *args, **kwds) + + +class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is FrozenSet: + raise TypeError("Type FrozenSet cannot be instantiated; " + "use frozenset() instead") + return _generic_new(frozenset, cls, *args, **kwds) + + +class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): + __slots__ = () + + +class KeysView(MappingView[KT], AbstractSet[KT], + extra=collections_abc.KeysView): + __slots__ = () + + +class ItemsView(MappingView[Tuple[KT, VT_co]], + AbstractSet[Tuple[KT, VT_co]], + Generic[KT, VT_co], + extra=collections_abc.ItemsView): + __slots__ = () + + +class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): + __slots__ = () + + +if hasattr(contextlib, 'AbstractContextManager'): + class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager): + __slots__ = () +else: + class ContextManager(Generic[T_co]): + __slots__ = () + + def __enter__(self): + return self + + @abc.abstractmethod + def __exit__(self, exc_type, exc_value, traceback): + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is ContextManager: + # In Python 3.6+, it is possible to set a method to None to + # explicitly indicate that the class does not implement an ABC + # (https://bugs.python.org/issue25958), but we do not support + # that pattern here because this fallback class is only used + # in Python 3.5 and earlier. + if (any("__enter__" in B.__dict__ for B in C.__mro__) and + any("__exit__" in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + + +if hasattr(contextlib, 'AbstractAsyncContextManager'): + class AsyncContextManager(Generic[T_co], + extra=contextlib.AbstractAsyncContextManager): + __slots__ = () + + __all__.append('AsyncContextManager') +elif sys.version_info[:2] >= (3, 5): + exec(""" +class AsyncContextManager(Generic[T_co]): + __slots__ = () + + async def __aenter__(self): + return self + + @abc.abstractmethod + async def __aexit__(self, exc_type, exc_value, traceback): + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncContextManager: + if sys.version_info[:2] >= (3, 6): + return _collections_abc._check_methods(C, "__aenter__", "__aexit__") + if (any("__aenter__" in B.__dict__ for B in C.__mro__) and + any("__aexit__" in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + +__all__.append('AsyncContextManager') +""") + + +class Dict(dict, MutableMapping[KT, VT], extra=dict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Dict: + raise TypeError("Type Dict cannot be instantiated; " + "use dict() instead") + return _generic_new(dict, cls, *args, **kwds) + + +class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], + extra=collections.defaultdict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is DefaultDict: + return collections.defaultdict(*args, **kwds) + return _generic_new(collections.defaultdict, cls, *args, **kwds) + + +class Counter(collections.Counter, Dict[T, int], extra=collections.Counter): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Counter: + return collections.Counter(*args, **kwds) + return _generic_new(collections.Counter, cls, *args, **kwds) + + +if hasattr(collections, 'ChainMap'): + # ChainMap only exists in 3.3+ + __all__.append('ChainMap') + + class ChainMap(collections.ChainMap, MutableMapping[KT, VT], + extra=collections.ChainMap): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is ChainMap: + return collections.ChainMap(*args, **kwds) + return _generic_new(collections.ChainMap, cls, *args, **kwds) + + +# Determine what base class to use for Generator. +if hasattr(collections_abc, 'Generator'): + # Sufficiently recent versions of 3.5 have a Generator ABC. + _G_base = collections_abc.Generator +else: + # Fall back on the exact type. + _G_base = types.GeneratorType + + +class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co], + extra=_G_base): + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Generator: + raise TypeError("Type Generator cannot be instantiated; " + "create a subclass instead") + return _generic_new(_G_base, cls, *args, **kwds) + + +if hasattr(collections_abc, 'AsyncGenerator'): + class AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra], + extra=collections_abc.AsyncGenerator): + __slots__ = () + + __all__.append('AsyncGenerator') + + +# Internal type variable used for Type[]. +CT_co = TypeVar('CT_co', covariant=True, bound=type) + + +# This is not a real generic class. Don't use outside annotations. +class Type(Generic[CT_co], extra=type): + """A special construct usable to annotate class objects. + + For example, suppose we have the following classes:: + + class User: ... # Abstract base for User classes + class BasicUser(User): ... + class ProUser(User): ... + class TeamUser(User): ... + + And a function that takes a class argument that's a subclass of + User and returns an instance of the corresponding class:: + + U = TypeVar('U', bound=User) + def new_user(user_class: Type[U]) -> U: + user = user_class() + # (Here we could write the user object to a database) + return user + + joe = new_user(BasicUser) + + At this point the type checker knows that joe has type BasicUser. + """ + + __slots__ = () + + +def _make_nmtuple(name, types): + msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" + types = [(n, _type_check(t, msg)) for n, t in types] + nm_tpl = collections.namedtuple(name, [n for n, t in types]) + # Prior to PEP 526, only _field_types attribute was assigned. + # Now, both __annotations__ and _field_types are used to maintain compatibility. + nm_tpl.__annotations__ = nm_tpl._field_types = collections.OrderedDict(types) + try: + nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + return nm_tpl + + +_PY36 = sys.version_info[:2] >= (3, 6) + +# attributes prohibited to set in NamedTuple class syntax +_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__', + '_fields', '_field_defaults', '_field_types', + '_make', '_replace', '_asdict', '_source') + +_special = ('__module__', '__name__', '__qualname__', '__annotations__') + + +class NamedTupleMeta(type): + + def __new__(cls, typename, bases, ns): + if ns.get('_root', False): + return super().__new__(cls, typename, bases, ns) + if not _PY36: + raise TypeError("Class syntax for NamedTuple is only supported" + " in Python 3.6+") + types = ns.get('__annotations__', {}) + nm_tpl = _make_nmtuple(typename, types.items()) + defaults = [] + defaults_dict = {} + for field_name in types: + if field_name in ns: + default_value = ns[field_name] + defaults.append(default_value) + defaults_dict[field_name] = default_value + elif defaults: + raise TypeError("Non-default namedtuple field {field_name} cannot " + "follow default field(s) {default_names}" + .format(field_name=field_name, + default_names=', '.join(defaults_dict.keys()))) + nm_tpl.__new__.__annotations__ = collections.OrderedDict(types) + nm_tpl.__new__.__defaults__ = tuple(defaults) + nm_tpl._field_defaults = defaults_dict + # update from user namespace without overriding special namedtuple attributes + for key in ns: + if key in _prohibited: + raise AttributeError("Cannot overwrite NamedTuple attribute " + key) + elif key not in _special and key not in nm_tpl._fields: + setattr(nm_tpl, key, ns[key]) + return nm_tpl + + +class NamedTuple(metaclass=NamedTupleMeta): + """Typed version of namedtuple. + + Usage in Python versions >= 3.6:: + + class Employee(NamedTuple): + name: str + id: int + + This is equivalent to:: + + Employee = collections.namedtuple('Employee', ['name', 'id']) + + The resulting class has extra __annotations__ and _field_types + attributes, giving an ordered dict mapping field names to types. + __annotations__ should be preferred, while _field_types + is kept to maintain pre PEP 526 compatibility. (The field names + are in the _fields attribute, which is part of the namedtuple + API.) Alternative equivalent keyword syntax is also accepted:: + + Employee = NamedTuple('Employee', name=str, id=int) + + In Python versions <= 3.5 use:: + + Employee = NamedTuple('Employee', [('name', str), ('id', int)]) + """ + _root = True + + def __new__(self, typename, fields=None, **kwargs): + if kwargs and not _PY36: + raise TypeError("Keyword syntax for NamedTuple is only supported" + " in Python 3.6+") + if fields is None: + fields = kwargs.items() + elif kwargs: + raise TypeError("Either list of fields or keywords" + " can be provided to NamedTuple, not both") + return _make_nmtuple(typename, fields) + + +def NewType(name, tp): + """NewType creates simple unique types with almost zero + runtime overhead. NewType(name, tp) is considered a subtype of tp + by static type checkers. At runtime, NewType(name, tp) returns + a dummy function that simply returns its argument. Usage:: + + UserId = NewType('UserId', int) + + def name_by_id(user_id: UserId) -> str: + ... + + UserId('user') # Fails type check + + name_by_id(42) # Fails type check + name_by_id(UserId(42)) # OK + + num = UserId(5) + 1 # type: int + """ + + def new_type(x): + return x + + new_type.__name__ = name + new_type.__supertype__ = tp + return new_type + + +# Python-version-specific alias (Python 2: unicode; Python 3: str) +Text = str + + +# Constant that's True when type checking, but False here. +TYPE_CHECKING = False + + +class IO(Generic[AnyStr]): + """Generic base class for TextIO and BinaryIO. + + This is an abstract, generic version of the return of open(). + + NOTE: This does not distinguish between the different possible + classes (text vs. binary, read vs. write vs. read/write, + append-only, unbuffered). The TextIO and BinaryIO subclasses + below capture the distinctions between text vs. binary, which is + pervasive in the interface; however we currently do not offer a + way to track the other distinctions in the type system. + """ + + __slots__ = () + + @abstractproperty + def mode(self) -> str: + pass + + @abstractproperty + def name(self) -> str: + pass + + @abstractmethod + def close(self) -> None: + pass + + @abstractproperty + def closed(self) -> bool: + pass + + @abstractmethod + def fileno(self) -> int: + pass + + @abstractmethod + def flush(self) -> None: + pass + + @abstractmethod + def isatty(self) -> bool: + pass + + @abstractmethod + def read(self, n: int = -1) -> AnyStr: + pass + + @abstractmethod + def readable(self) -> bool: + pass + + @abstractmethod + def readline(self, limit: int = -1) -> AnyStr: + pass + + @abstractmethod + def readlines(self, hint: int = -1) -> List[AnyStr]: + pass + + @abstractmethod + def seek(self, offset: int, whence: int = 0) -> int: + pass + + @abstractmethod + def seekable(self) -> bool: + pass + + @abstractmethod + def tell(self) -> int: + pass + + @abstractmethod + def truncate(self, size: int = None) -> int: + pass + + @abstractmethod + def writable(self) -> bool: + pass + + @abstractmethod + def write(self, s: AnyStr) -> int: + pass + + @abstractmethod + def writelines(self, lines: List[AnyStr]) -> None: + pass + + @abstractmethod + def __enter__(self) -> 'IO[AnyStr]': + pass + + @abstractmethod + def __exit__(self, type, value, traceback) -> None: + pass + + +class BinaryIO(IO[bytes]): + """Typed version of the return of open() in binary mode.""" + + __slots__ = () + + @abstractmethod + def write(self, s: Union[bytes, bytearray]) -> int: + pass + + @abstractmethod + def __enter__(self) -> 'BinaryIO': + pass + + +class TextIO(IO[str]): + """Typed version of the return of open() in text mode.""" + + __slots__ = () + + @abstractproperty + def buffer(self) -> BinaryIO: + pass + + @abstractproperty + def encoding(self) -> str: + pass + + @abstractproperty + def errors(self) -> Optional[str]: + pass + + @abstractproperty + def line_buffering(self) -> bool: + pass + + @abstractproperty + def newlines(self) -> Any: + pass + + @abstractmethod + def __enter__(self) -> 'TextIO': + pass + + +class io: + """Wrapper namespace for IO generic classes.""" + + __all__ = ['IO', 'TextIO', 'BinaryIO'] + IO = IO + TextIO = TextIO + BinaryIO = BinaryIO + + +io.__name__ = __name__ + '.io' +sys.modules[io.__name__] = io + + +Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')), + lambda p: p.pattern) +Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')), + lambda m: m.re.pattern) + + +class re: + """Wrapper namespace for re type aliases.""" + + __all__ = ['Pattern', 'Match'] + Pattern = Pattern + Match = Match + + +re.__name__ = __name__ + '.re' +sys.modules[re.__name__] = re diff --git a/lib/urllib3/__init__.py b/lib/urllib3/__init__.py new file mode 100644 index 0000000..148a9c3 --- /dev/null +++ b/lib/urllib3/__init__.py @@ -0,0 +1,92 @@ +""" +urllib3 - Thread-safe connection pooling and re-using. +""" + +from __future__ import absolute_import +import warnings + +from .connectionpool import ( + HTTPConnectionPool, + HTTPSConnectionPool, + connection_from_url +) + +from . import exceptions +from .filepost import encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import HTTPResponse +from .util.request import make_headers +from .util.url import get_host +from .util.timeout import Timeout +from .util.retry import Retry + + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' +__license__ = 'MIT' +__version__ = '1.24.1' + +__all__ = ( + 'HTTPConnectionPool', + 'HTTPSConnectionPool', + 'PoolManager', + 'ProxyManager', + 'HTTPResponse', + 'Retry', + 'Timeout', + 'add_stderr_logger', + 'connection_from_url', + 'disable_warnings', + 'encode_multipart_formdata', + 'get_host', + 'make_headers', + 'proxy_from_url', +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger(level=logging.DEBUG): + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug('Added a stderr logging handler to logger: %s', __name__) + return handler + + +# ... Clean up. +del NullHandler + + +# All warning filters *must* be appended unless you're really certain that they +# shouldn't be: otherwise, it's very hard for users to use most Python +# mechanisms to silence them. +# SecurityWarning's always go off by default. +warnings.simplefilter('always', exceptions.SecurityWarning, append=True) +# SubjectAltNameWarning's should go off once per host +warnings.simplefilter('default', exceptions.SubjectAltNameWarning, append=True) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter('default', exceptions.InsecurePlatformWarning, + append=True) +# SNIMissingWarnings should go off only once. +warnings.simplefilter('default', exceptions.SNIMissingWarning, append=True) + + +def disable_warnings(category=exceptions.HTTPWarning): + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter('ignore', category) diff --git a/lib/urllib3/_collections.py b/lib/urllib3/_collections.py new file mode 100644 index 0000000..34f2381 --- /dev/null +++ b/lib/urllib3/_collections.py @@ -0,0 +1,329 @@ +from __future__ import absolute_import +try: + from collections.abc import Mapping, MutableMapping +except ImportError: + from collections import Mapping, MutableMapping +try: + from threading import RLock +except ImportError: # Platform-specific: No threads available + class RLock: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + pass + + +from collections import OrderedDict +from .exceptions import InvalidHeader +from .packages.six import iterkeys, itervalues, PY3 + + +__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] + + +_Null = object() + + +class RecentlyUsedContainer(MutableMapping): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + ContainerCls = OrderedDict + + def __init__(self, maxsize=10, dispose_func=None): + self._maxsize = maxsize + self.dispose_func = dispose_func + + self._container = self.ContainerCls() + self.lock = RLock() + + def __getitem__(self, key): + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key, value): + evicted_value = _Null + with self.lock: + # Possibly evict the existing value of 'key' + evicted_value = self._container.get(key, _Null) + self._container[key] = value + + # If we didn't evict an existing value, we might have to evict the + # least recently used item from the beginning of the container. + if len(self._container) > self._maxsize: + _key, evicted_value = self._container.popitem(last=False) + + if self.dispose_func and evicted_value is not _Null: + self.dispose_func(evicted_value) + + def __delitem__(self, key): + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self): + with self.lock: + return len(self._container) + + def __iter__(self): + raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.') + + def clear(self): + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(itervalues(self._container)) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self): + with self.lock: + return list(iterkeys(self._container)) + + +class HTTPHeaderDict(MutableMapping): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + def __init__(self, headers=None, **kwargs): + super(HTTPHeaderDict, self).__init__() + self._container = OrderedDict() + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key, val): + self._container[key.lower()] = [key, val] + return self._container[key.lower()] + + def __getitem__(self, key): + val = self._container[key.lower()] + return ', '.join(val[1:]) + + def __delitem__(self, key): + del self._container[key.lower()] + + def __contains__(self, key): + return key.lower() in self._container + + def __eq__(self, other): + if not isinstance(other, Mapping) and not hasattr(other, 'keys'): + return False + if not isinstance(other, type(self)): + other = type(self)(other) + return (dict((k.lower(), v) for k, v in self.itermerged()) == + dict((k.lower(), v) for k, v in other.itermerged())) + + def __ne__(self, other): + return not self.__eq__(other) + + if not PY3: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + def __len__(self): + return len(self._container) + + def __iter__(self): + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def pop(self, key, default=__marker): + '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + ''' + # Using the MutableMapping function directly fails due to the private marker. + # Using ordinary dict.pop would expose the internal structures. + # So let's reinvent the wheel. + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def discard(self, key): + try: + del self[key] + except KeyError: + pass + + def add(self, key, val): + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + """ + key_lower = key.lower() + new_vals = [key, val] + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + vals.append(val) + + def extend(self, *args, **kwargs): + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError("extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args))) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, Mapping): + for key in other: + self.add(key, other[key]) + elif hasattr(other, "keys"): + for key in other.keys(): + self.add(key, other[key]) + else: + for key, value in other: + self.add(key, value) + + for key, value in kwargs.items(): + self.add(key, value) + + def getlist(self, key, default=__marker): + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + try: + vals = self._container[key.lower()] + except KeyError: + if default is self.__marker: + return [] + return default + else: + return vals[1:] + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self): + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) + + def _copy_from(self, other): + for key in other: + val = other.getlist(key) + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + self._container[key.lower()] = [key] + val + + def copy(self): + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self): + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self): + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ', '.join(val[1:]) + + def items(self): + return list(self.iteritems()) + + @classmethod + def from_httplib(cls, message): # Python 2 + """Read headers from a Python 2 httplib message object.""" + # python2.7 does not expose a proper API for exporting multiheaders + # efficiently. This function re-reads raw lines from the message + # object and extracts the multiheaders properly. + obs_fold_continued_leaders = (' ', '\t') + headers = [] + + for line in message.headers: + if line.startswith(obs_fold_continued_leaders): + if not headers: + # We received a header line that starts with OWS as described + # in RFC-7230 S3.2.4. This indicates a multiline header, but + # there exists no previous header to which we can attach it. + raise InvalidHeader( + 'Header continuation with no previous header: %s' % line + ) + else: + key, value = headers[-1] + headers[-1] = (key, value + ' ' + line.strip()) + continue + + key, value = line.split(':', 1) + headers.append((key, value.strip())) + + return cls(headers) diff --git a/lib/urllib3/connection.py b/lib/urllib3/connection.py new file mode 100644 index 0000000..02b3665 --- /dev/null +++ b/lib/urllib3/connection.py @@ -0,0 +1,391 @@ +from __future__ import absolute_import +import datetime +import logging +import os +import socket +from socket import error as SocketError, timeout as SocketTimeout +import warnings +from .packages import six +from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection +from .packages.six.moves.http_client import HTTPException # noqa: F401 + +try: # Compiled with SSL? + import ssl + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None + + class BaseSSLError(BaseException): + pass + + +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: # Python 2: + class ConnectionError(Exception): + pass + + +from .exceptions import ( + NewConnectionError, + ConnectTimeoutError, + SubjectAltNameWarning, + SystemTimeWarning, +) +from .packages.ssl_match_hostname import match_hostname, CertificateError + +from .util.ssl_ import ( + resolve_cert_reqs, + resolve_ssl_version, + assert_fingerprint, + create_urllib3_context, + ssl_wrap_socket +) + + +from .util import connection + +from ._collections import HTTPHeaderDict + +log = logging.getLogger(__name__) + +port_by_scheme = { + 'http': 80, + 'https': 443, +} + +# When updating RECENT_DATE, move it to within two years of the current date, +# and not less than 6 months ago. +# Example: if Today is 2018-01-01, then RECENT_DATE should be any date on or +# after 2016-01-01 (today - 2 years) AND before 2017-07-01 (today - 6 months) +RECENT_DATE = datetime.date(2017, 6, 30) + + +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + pass + + +class HTTPConnection(_HTTPConnection, object): + """ + Based on httplib.HTTPConnection but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass:: + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port = port_by_scheme['http'] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] + + #: Whether this connection verifies the host's certificate. + is_verified = False + + def __init__(self, *args, **kw): + if six.PY3: # Python 3 + kw.pop('strict', None) + + # Pre-set source_address. + self.source_address = kw.get('source_address') + + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop('socket_options', self.default_socket_options) + + _HTTPConnection.__init__(self, *args, **kw) + + @property + def host(self): + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip('.') + + @host.setter + def host(self, value): + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + + def _new_conn(self): + """ Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + extra_kw = {} + if self.source_address: + extra_kw['source_address'] = self.source_address + + if self.socket_options: + extra_kw['socket_options'] = self.socket_options + + try: + conn = connection.create_connection( + (self._dns_host, self.port), self.timeout, **extra_kw) + + except SocketTimeout as e: + raise ConnectTimeoutError( + self, "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout)) + + except SocketError as e: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e) + + return conn + + def _prepare_conn(self, conn): + self.sock = conn + if self._tunnel_host: + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + def request_chunked(self, method, url, body=None, headers=None): + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + headers = HTTPHeaderDict(headers if headers is not None else {}) + skip_accept_encoding = 'accept-encoding' in headers + skip_host = 'host' in headers + self.putrequest( + method, + url, + skip_accept_encoding=skip_accept_encoding, + skip_host=skip_host + ) + for header, value in headers.items(): + self.putheader(header, value) + if 'transfer-encoding' not in headers: + self.putheader('Transfer-Encoding', 'chunked') + self.endheaders() + + if body is not None: + stringish_types = six.string_types + (bytes,) + if isinstance(body, stringish_types): + body = (body,) + for chunk in body: + if not chunk: + continue + if not isinstance(chunk, bytes): + chunk = chunk.encode('utf8') + len_str = hex(len(chunk))[2:] + self.send(len_str.encode('utf-8')) + self.send(b'\r\n') + self.send(chunk) + self.send(b'\r\n') + + # After the if clause, to always have a closed body + self.send(b'0\r\n\r\n') + + +class HTTPSConnection(HTTPConnection): + default_port = port_by_scheme['https'] + + ssl_version = None + + def __init__(self, host, port=None, key_file=None, cert_file=None, + strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, server_hostname=None, **kw): + + HTTPConnection.__init__(self, host, port, strict=strict, + timeout=timeout, **kw) + + self.key_file = key_file + self.cert_file = cert_file + self.ssl_context = ssl_context + self.server_hostname = server_hostname + + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = 'https' + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + if self.ssl_context is None: + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(None), + cert_reqs=resolve_cert_reqs(None), + ) + + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + ssl_context=self.ssl_context, + server_hostname=self.server_hostname + ) + + +class VerifiedHTTPSConnection(HTTPSConnection): + """ + Based on httplib.HTTPSConnection but wraps the socket with + SSL certification. + """ + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ssl_version = None + assert_fingerprint = None + + def set_cert(self, key_file=None, cert_file=None, + cert_reqs=None, ca_certs=None, + assert_hostname=None, assert_fingerprint=None, + ca_cert_dir=None): + """ + This method should only be called once, before the connection is used. + """ + # If cert_reqs is not provided, we can try to guess. If the user gave + # us a cert database, we assume they want to use it: otherwise, if + # they gave us an SSL Context object we should use whatever is set for + # it. + if cert_reqs is None: + if ca_certs or ca_cert_dir: + cert_reqs = 'CERT_REQUIRED' + elif self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + + def connect(self): + # Add certificate verification + conn = self._new_conn() + hostname = self.host + + if self._tunnel_host: + self.sock = conn + # Calls self._set_hostport(), so self.host is + # self._tunnel_host below. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + + server_hostname = hostname + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn(( + 'System time is way off (before {0}). This will probably ' + 'lead to SSL verification errors').format(RECENT_DATE), + SystemTimeWarning + ) + + # Wrap socket using verification with the root certs in + # trusted_root_certs + if self.ssl_context is None: + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(self.ssl_version), + cert_reqs=resolve_cert_reqs(self.cert_reqs), + ) + + context = self.ssl_context + context.verify_mode = resolve_cert_reqs(self.cert_reqs) + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + server_hostname=server_hostname, + ssl_context=context) + + if self.assert_fingerprint: + assert_fingerprint(self.sock.getpeercert(binary_form=True), + self.assert_fingerprint) + elif context.verify_mode != ssl.CERT_NONE \ + and not getattr(context, 'check_hostname', False) \ + and self.assert_hostname is not False: + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = self.sock.getpeercert() + if not cert.get('subjectAltName', ()): + warnings.warn(( + 'Certificate for {0} has no `subjectAltName`, falling back to check for a ' + '`commonName` for now. This feature is being removed by major browsers and ' + 'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 ' + 'for details.)'.format(hostname)), + SubjectAltNameWarning + ) + _match_hostname(cert, self.assert_hostname or server_hostname) + + self.is_verified = ( + context.verify_mode == ssl.CERT_REQUIRED or + self.assert_fingerprint is not None + ) + + +def _match_hostname(cert, asserted_hostname): + try: + match_hostname(cert, asserted_hostname) + except CertificateError as e: + log.error( + 'Certificate did not match expected hostname: %s. ' + 'Certificate: %s', asserted_hostname, cert + ) + # Add cert to exception and reraise so client code can inspect + # the cert when catching the exception, if they want to + e._peer_cert = cert + raise + + +if ssl: + # Make a copy for testing. + UnverifiedHTTPSConnection = HTTPSConnection + HTTPSConnection = VerifiedHTTPSConnection +else: + HTTPSConnection = DummyConnection diff --git a/lib/urllib3/connectionpool.py b/lib/urllib3/connectionpool.py new file mode 100644 index 0000000..f7a8f19 --- /dev/null +++ b/lib/urllib3/connectionpool.py @@ -0,0 +1,896 @@ +from __future__ import absolute_import +import errno +import logging +import sys +import warnings + +from socket import error as SocketError, timeout as SocketTimeout +import socket + + +from .exceptions import ( + ClosedPoolError, + ProtocolError, + EmptyPoolError, + HeaderParsingError, + HostChangedError, + LocationValueError, + MaxRetryError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, + InsecureRequestWarning, + NewConnectionError, +) +from .packages.ssl_match_hostname import CertificateError +from .packages import six +from .packages.six.moves import queue +from .connection import ( + port_by_scheme, + DummyConnection, + HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, + HTTPException, BaseSSLError, +) +from .request import RequestMethods +from .response import HTTPResponse + +from .util.connection import is_connection_dropped +from .util.request import set_file_position +from .util.response import assert_header_parsing +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import get_host, Url, NORMALIZABLE_SCHEMES +from .util.queue import LifoQueue + + +xrange = six.moves.xrange + +log = logging.getLogger(__name__) + +_Default = object() + + +# Pool objects +class ConnectionPool(object): + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + """ + + scheme = None + QueueCls = LifoQueue + + def __init__(self, host, port=None): + if not host: + raise LocationValueError("No host specified.") + + self.host = _ipv6_host(host, self.scheme) + self._proxy_host = host.lower() + self.port = port + + def __str__(self): + return '%s(host=%r, port=%r)' % (type(self).__name__, + self.host, self.port) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + pass + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`httplib.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`httplib.HTTPConnection`. + + :param strict: + Causes BadStatusLine to be raised if the status line can't be parsed + as a valid HTTP/1.0 or 1.1 status line, passed into + :class:`httplib.HTTPConnection`. + + .. note:: + Only works in Python 2. This parameter is ignored in Python 3. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.connectionpool.ProxyManager`" + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.connectionpool.ProxyManager`" + + :param \\**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = 'http' + ConnectionCls = HTTPConnection + ResponseCls = HTTPResponse + + def __init__(self, host, port=None, strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, + headers=None, retries=None, + _proxy=None, _proxy_headers=None, + **conn_kw): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + self.strict = strict + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + + # Fill the queue up so that doing get() on it will block properly + for _ in xrange(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. + self.conn_kw.setdefault('socket_options', []) + + def _new_conn(self): + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.debug("Starting new HTTP connection (%d): %s:%s", + self.num_connections, self.host, self.port or "80") + + conn = self.ConnectionCls(host=self.host, port=self.port, + timeout=self.timeout.connect_timeout, + strict=self.strict, **self.conn_kw) + return conn + + def _get_conn(self, timeout=None): + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") + + except queue.Empty: + if self.block: + raise EmptyPoolError(self, + "Pool reached maximum size and no more " + "connections are allowed.") + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.debug("Resetting dropped connection: %s", self.host) + conn.close() + if getattr(conn, 'auto_open', 1) == 0: + # This is a proxied connection that has been mutated by + # httplib._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None + + return conn or self._new_conn() + + def _put_conn(self, conn): + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # This should never happen if self.block == True + log.warning( + "Connection pool is full, discarding connection: %s", + self.host) + + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + pass + + def _prepare_proxy(self, conn): + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout): + """ Helper that always returns a :class:`urllib3.util.Timeout` """ + if timeout is _Default: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout(self, err, url, timeout_value): + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error + if hasattr(err, 'errno') and err.errno in _blocking_errnos: + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python < 2.7.4 + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + def _make_request(self, conn, method, url, timeout=_Default, chunked=False, + **httplib_request_kw): + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param timeout: + Socket timeout in seconds for the request. This can be a + float or integer, which will set the same timeout value for + the socket connect and the socket read, or an instance of + :class:`urllib3.util.Timeout`, which gives you more fine-grained + control over your timeouts. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = timeout_obj.connect_timeout + + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # conn.request() calls httplib.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + # App Engine doesn't have a sock attr + if getattr(conn, 'sock', None): + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % read_timeout) + if read_timeout is Timeout.DEFAULT_TIMEOUT: + conn.sock.settimeout(socket.getdefaulttimeout()) + else: # None or a value + conn.sock.settimeout(read_timeout) + + # Receive the response from the server + try: + try: # Python 2.7, use buffering of HTTP responses + httplib_response = conn.getresponse(buffering=True) + except TypeError: # Python 3 + try: + httplib_response = conn.getresponse() + except Exception as e: + # Remove the TypeError from the exception chain in Python 3; + # otherwise it looks like a programming error was the cause. + six.raise_from(e, None) + except (SocketTimeout, BaseSSLError, SocketError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # AppEngine doesn't have a version attr. + http_version = getattr(conn, '_http_vsn_str', 'HTTP/?') + log.debug("%s://%s:%s \"%s %s %s\" %s %s", self.scheme, self.host, self.port, + method, url, http_version, httplib_response.status, + httplib_response.length) + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 + log.warning( + 'Failed to parse headers (url=%s): %s', + self._absolute_url(url), hpe, exc_info=True) + + return httplib_response + + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url + + def close(self): + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + try: + while True: + conn = old_pool.get(block=False) + if conn: + conn.close() + + except queue.Empty: + pass # Done. + + def is_same_host(self, url): + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith('/'): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, host, port = get_host(url) + + host = _ipv6_host(host, self.scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen(self, method, url, body=None, headers=None, retries=None, + redirect=True, assert_same_host=True, timeout=_Default, + pool_timeout=None, release_conn=None, chunked=False, + body_pos=None, **response_kw): + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method provided + by :class:`.RequestMethods`, such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param body: + Data to send in the request body (useful for creating + POST requests, see HTTPConnectionPool.post_url for + more convenience). + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When False, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of + ``response_kw.get('preload_content', True)``. + + :param chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param int body_pos: + Position to seek to in file-like body in the event of a retry or + redirect. Typically this won't need to be set because urllib3 will + auto-populate the value when needed. + + :param \\**response_kw: + Additional parameters are passed to + :meth:`urllib3.response.HTTPResponse.from_httplib` + """ + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = response_kw.get('preload_content', True) + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + conn = None + + # Track whether `conn` needs to be released before + # returning/raising/recursing. Update this variable if necessary, and + # leave `release_conn` constant throughout the function. That way, if + # the function recurses, the original value of `release_conn` will be + # passed down into the recursive call, and its value will be respected. + # + # See issue #651 [1] for details. + # + # [1] <https://github.com/shazow/urllib3/issues/651> + release_this_conn = release_conn + + # Merge the proxy headers. Only do this in HTTP. We have to copy the + # headers dict so we can safely change it without those changes being + # reflected in anyone else's copy. + if self.scheme == 'http': + headers = headers.copy() + headers.update(self.proxy_headers) + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + # Keep track of whether we cleanly exited the except block. This + # ensures we do proper cleanup in finally. + clean_exit = False + + # Rewind body position, if needed. Record current position + # for future rewinds in the event of a redirect/retry. + body_pos = set_file_position(body, body_pos) + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout + + is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None) + if is_new_proxy_conn: + self._prepare_proxy(conn) + + # Make the request on the httplib connection object. + httplib_response = self._make_request(conn, method, url, + timeout=timeout_obj, + body=body, headers=headers, + chunked=chunked) + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Pass method to Response for length checking + response_kw['request_method'] = method + + # Import httplib's response into our own wrapper object + response = self.ResponseCls.from_httplib(httplib_response, + pool=self, + connection=response_conn, + retries=retries, + **response_kw) + + # Everything went great! + clean_exit = True + + except queue.Empty: + # Timed out by queue. + raise EmptyPoolError(self, "No pool connections are available.") + + except (TimeoutError, HTTPException, SocketError, ProtocolError, + BaseSSLError, SSLError, CertificateError) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + if isinstance(e, (BaseSSLError, CertificateError)): + e = SSLError(e) + elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: + e = ProxyError('Cannot connect to proxy.', e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError('Connection aborted.', e) + + retries = retries.increment(method, url, error=e, _pool=self, + _stacktrace=sys.exc_info()[2]) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if not clean_exit: + # We hit some kind of exception, handled or otherwise. We need + # to throw the connection away unless explicitly told not to. + # Close the connection, set the variable to None, and make sure + # we put the None back in the pool to avoid leaking it. + conn = conn and conn.close() + release_this_conn = True + + if release_this_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning("Retrying (%r) after connection " + "broken by '%r': %s", retries, err, url) + return self.urlopen(method, url, body, headers, retries, + redirect, assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, body_pos=body_pos, + **response_kw) + + def drain_and_release_conn(response): + try: + # discard any remaining response body, the connection will be + # released back to the pool once the entire response is read + response.read() + except (TimeoutError, HTTPException, SocketError, ProtocolError, + BaseSSLError, SSLError) as e: + pass + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + method = 'GET' + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + # Drain and release the connection for this response, since + # we're not returning it to be released manually. + drain_and_release_conn(response) + raise + return response + + # drain and return the connection to the pool before recursing + drain_and_release_conn(response) + + retries.sleep_for_retry(response) + log.debug("Redirecting %s -> %s", url, redirect_location) + return self.urlopen( + method, redirect_location, body, headers, + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, body_pos=body_pos, + **response_kw) + + # Check if we should retry the HTTP response. + has_retry_after = bool(response.getheader('Retry-After')) + if retries.is_retry(method, response.status, has_retry_after): + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_status: + # Drain and release the connection for this response, since + # we're not returning it to be released manually. + drain_and_release_conn(response) + raise + return response + + # drain and return the connection to the pool before recursing + drain_and_release_conn(response) + + retries.sleep(response) + log.debug("Retry: %s", url) + return self.urlopen( + method, url, body, headers, + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, + body_pos=body_pos, **response_kw) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + When Python is compiled with the :mod:`ssl` module, then + :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates, + instead of :class:`.HTTPSConnection`. + + :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is + available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + """ + + scheme = 'https' + ConnectionCls = HTTPSConnection + + def __init__(self, host, port=None, + strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, + block=False, headers=None, retries=None, + _proxy=None, _proxy_headers=None, + key_file=None, cert_file=None, cert_reqs=None, + ca_certs=None, ssl_version=None, + assert_hostname=None, assert_fingerprint=None, + ca_cert_dir=None, **conn_kw): + + HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, + block, headers, retries, _proxy, _proxy_headers, + **conn_kw) + + if ca_certs and cert_reqs is None: + cert_reqs = 'CERT_REQUIRED' + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_conn(self, conn): + """ + Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` + and establish the tunnel if proxy is used. + """ + + if isinstance(conn, VerifiedHTTPSConnection): + conn.set_cert(key_file=self.key_file, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint) + conn.ssl_version = self.ssl_version + return conn + + def _prepare_proxy(self, conn): + """ + Establish tunnel connection early, because otherwise httplib + would improperly set Host: header to proxy's IP:port. + """ + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + conn.connect() + + def _new_conn(self): + """ + Return a fresh :class:`httplib.HTTPSConnection`. + """ + self.num_connections += 1 + log.debug("Starting new HTTPS connection (%d): %s:%s", + self.num_connections, self.host, self.port or "443") + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: + raise SSLError("Can't connect to HTTPS URL because the SSL " + "module is not available.") + + actual_host = self.host + actual_port = self.port + if self.proxy is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + conn = self.ConnectionCls(host=actual_host, port=actual_port, + timeout=self.timeout.connect_timeout, + strict=self.strict, **self.conn_kw) + + return self._prepare_conn(conn) + + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + super(HTTPSConnectionPool, self)._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if not getattr(conn, 'sock', None): # AppEngine might not have `.sock` + conn.connect() + + if not conn.is_verified: + warnings.warn(( + 'Unverified HTTPS request is being made. ' + 'Adding certificate verification is strongly advised. See: ' + 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' + '#ssl-warnings'), + InsecureRequestWarning) + + +def connection_from_url(url, **kw): + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \\**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, host, port = get_host(url) + port = port or port_by_scheme.get(scheme, 80) + if scheme == 'https': + return HTTPSConnectionPool(host, port=port, **kw) + else: + return HTTPConnectionPool(host, port=port, **kw) + + +def _ipv6_host(host, scheme): + """ + Process IPv6 address literals + """ + + # httplib doesn't like it when we include brackets in IPv6 addresses + # Specifically, if we include brackets but also pass the port then + # httplib crazily doubles up the square brackets on the Host header. + # Instead, we need to make sure we never pass ``None`` as the port. + # However, for backward compatibility reasons we can't actually + # *assert* that. See http://bugs.python.org/issue28539 + # + # Also if an IPv6 address literal has a zone identifier, the + # percent sign might be URIencoded, convert it back into ASCII + if host.startswith('[') and host.endswith(']'): + host = host.replace('%25', '%').strip('[]') + if scheme in NORMALIZABLE_SCHEMES: + host = host.lower() + return host diff --git a/lib/urllib3/contrib/__init__.py b/lib/urllib3/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/urllib3/contrib/_appengine_environ.py b/lib/urllib3/contrib/_appengine_environ.py new file mode 100644 index 0000000..f3e0094 --- /dev/null +++ b/lib/urllib3/contrib/_appengine_environ.py @@ -0,0 +1,30 @@ +""" +This module provides means to detect the App Engine environment. +""" + +import os + + +def is_appengine(): + return (is_local_appengine() or + is_prod_appengine() or + is_prod_appengine_mvms()) + + +def is_appengine_sandbox(): + return is_appengine() and not is_prod_appengine_mvms() + + +def is_local_appengine(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Development/' in os.environ['SERVER_SOFTWARE']) + + +def is_prod_appengine(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and + not is_prod_appengine_mvms()) + + +def is_prod_appengine_mvms(): + return os.environ.get('GAE_VM', False) == 'true' diff --git a/lib/urllib3/contrib/_securetransport/__init__.py b/lib/urllib3/contrib/_securetransport/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/urllib3/contrib/_securetransport/bindings.py b/lib/urllib3/contrib/_securetransport/bindings.py new file mode 100644 index 0000000..bcf41c0 --- /dev/null +++ b/lib/urllib3/contrib/_securetransport/bindings.py @@ -0,0 +1,593 @@ +""" +This module uses ctypes to bind a whole bunch of functions and constants from +SecureTransport. The goal here is to provide the low-level API to +SecureTransport. These are essentially the C-level functions and constants, and +they're pretty gross to work with. + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + + Copyright (c) 2015-2016 Will Bond <will@wbond.net> + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import platform +from ctypes.util import find_library +from ctypes import ( + c_void_p, c_int32, c_char_p, c_size_t, c_byte, c_uint32, c_ulong, c_long, + c_bool +) +from ctypes import CDLL, POINTER, CFUNCTYPE + + +security_path = find_library('Security') +if not security_path: + raise ImportError('The library Security could not be found') + + +core_foundation_path = find_library('CoreFoundation') +if not core_foundation_path: + raise ImportError('The library CoreFoundation could not be found') + + +version = platform.mac_ver()[0] +version_info = tuple(map(int, version.split('.'))) +if version_info < (10, 8): + raise OSError( + 'Only OS X 10.8 and newer are supported, not %s.%s' % ( + version_info[0], version_info[1] + ) + ) + +Security = CDLL(security_path, use_errno=True) +CoreFoundation = CDLL(core_foundation_path, use_errno=True) + +Boolean = c_bool +CFIndex = c_long +CFStringEncoding = c_uint32 +CFData = c_void_p +CFString = c_void_p +CFArray = c_void_p +CFMutableArray = c_void_p +CFDictionary = c_void_p +CFError = c_void_p +CFType = c_void_p +CFTypeID = c_ulong + +CFTypeRef = POINTER(CFType) +CFAllocatorRef = c_void_p + +OSStatus = c_int32 + +CFDataRef = POINTER(CFData) +CFStringRef = POINTER(CFString) +CFArrayRef = POINTER(CFArray) +CFMutableArrayRef = POINTER(CFMutableArray) +CFDictionaryRef = POINTER(CFDictionary) +CFArrayCallBacks = c_void_p +CFDictionaryKeyCallBacks = c_void_p +CFDictionaryValueCallBacks = c_void_p + +SecCertificateRef = POINTER(c_void_p) +SecExternalFormat = c_uint32 +SecExternalItemType = c_uint32 +SecIdentityRef = POINTER(c_void_p) +SecItemImportExportFlags = c_uint32 +SecItemImportExportKeyParameters = c_void_p +SecKeychainRef = POINTER(c_void_p) +SSLProtocol = c_uint32 +SSLCipherSuite = c_uint32 +SSLContextRef = POINTER(c_void_p) +SecTrustRef = POINTER(c_void_p) +SSLConnectionRef = c_uint32 +SecTrustResultType = c_uint32 +SecTrustOptionFlags = c_uint32 +SSLProtocolSide = c_uint32 +SSLConnectionType = c_uint32 +SSLSessionOption = c_uint32 + + +try: + Security.SecItemImport.argtypes = [ + CFDataRef, + CFStringRef, + POINTER(SecExternalFormat), + POINTER(SecExternalItemType), + SecItemImportExportFlags, + POINTER(SecItemImportExportKeyParameters), + SecKeychainRef, + POINTER(CFArrayRef), + ] + Security.SecItemImport.restype = OSStatus + + Security.SecCertificateGetTypeID.argtypes = [] + Security.SecCertificateGetTypeID.restype = CFTypeID + + Security.SecIdentityGetTypeID.argtypes = [] + Security.SecIdentityGetTypeID.restype = CFTypeID + + Security.SecKeyGetTypeID.argtypes = [] + Security.SecKeyGetTypeID.restype = CFTypeID + + Security.SecCertificateCreateWithData.argtypes = [ + CFAllocatorRef, + CFDataRef + ] + Security.SecCertificateCreateWithData.restype = SecCertificateRef + + Security.SecCertificateCopyData.argtypes = [ + SecCertificateRef + ] + Security.SecCertificateCopyData.restype = CFDataRef + + Security.SecCopyErrorMessageString.argtypes = [ + OSStatus, + c_void_p + ] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SecIdentityCreateWithCertificate.argtypes = [ + CFTypeRef, + SecCertificateRef, + POINTER(SecIdentityRef) + ] + Security.SecIdentityCreateWithCertificate.restype = OSStatus + + Security.SecKeychainCreate.argtypes = [ + c_char_p, + c_uint32, + c_void_p, + Boolean, + c_void_p, + POINTER(SecKeychainRef) + ] + Security.SecKeychainCreate.restype = OSStatus + + Security.SecKeychainDelete.argtypes = [ + SecKeychainRef + ] + Security.SecKeychainDelete.restype = OSStatus + + Security.SecPKCS12Import.argtypes = [ + CFDataRef, + CFDictionaryRef, + POINTER(CFArrayRef) + ] + Security.SecPKCS12Import.restype = OSStatus + + SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) + SSLWriteFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t)) + + Security.SSLSetIOFuncs.argtypes = [ + SSLContextRef, + SSLReadFunc, + SSLWriteFunc + ] + Security.SSLSetIOFuncs.restype = OSStatus + + Security.SSLSetPeerID.argtypes = [ + SSLContextRef, + c_char_p, + c_size_t + ] + Security.SSLSetPeerID.restype = OSStatus + + Security.SSLSetCertificate.argtypes = [ + SSLContextRef, + CFArrayRef + ] + Security.SSLSetCertificate.restype = OSStatus + + Security.SSLSetCertificateAuthorities.argtypes = [ + SSLContextRef, + CFTypeRef, + Boolean + ] + Security.SSLSetCertificateAuthorities.restype = OSStatus + + Security.SSLSetConnection.argtypes = [ + SSLContextRef, + SSLConnectionRef + ] + Security.SSLSetConnection.restype = OSStatus + + Security.SSLSetPeerDomainName.argtypes = [ + SSLContextRef, + c_char_p, + c_size_t + ] + Security.SSLSetPeerDomainName.restype = OSStatus + + Security.SSLHandshake.argtypes = [ + SSLContextRef + ] + Security.SSLHandshake.restype = OSStatus + + Security.SSLRead.argtypes = [ + SSLContextRef, + c_char_p, + c_size_t, + POINTER(c_size_t) + ] + Security.SSLRead.restype = OSStatus + + Security.SSLWrite.argtypes = [ + SSLContextRef, + c_char_p, + c_size_t, + POINTER(c_size_t) + ] + Security.SSLWrite.restype = OSStatus + + Security.SSLClose.argtypes = [ + SSLContextRef + ] + Security.SSLClose.restype = OSStatus + + Security.SSLGetNumberSupportedCiphers.argtypes = [ + SSLContextRef, + POINTER(c_size_t) + ] + Security.SSLGetNumberSupportedCiphers.restype = OSStatus + + Security.SSLGetSupportedCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t) + ] + Security.SSLGetSupportedCiphers.restype = OSStatus + + Security.SSLSetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + c_size_t + ] + Security.SSLSetEnabledCiphers.restype = OSStatus + + Security.SSLGetNumberEnabledCiphers.argtype = [ + SSLContextRef, + POINTER(c_size_t) + ] + Security.SSLGetNumberEnabledCiphers.restype = OSStatus + + Security.SSLGetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t) + ] + Security.SSLGetEnabledCiphers.restype = OSStatus + + Security.SSLGetNegotiatedCipher.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite) + ] + Security.SSLGetNegotiatedCipher.restype = OSStatus + + Security.SSLGetNegotiatedProtocolVersion.argtypes = [ + SSLContextRef, + POINTER(SSLProtocol) + ] + Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus + + Security.SSLCopyPeerTrust.argtypes = [ + SSLContextRef, + POINTER(SecTrustRef) + ] + Security.SSLCopyPeerTrust.restype = OSStatus + + Security.SecTrustSetAnchorCertificates.argtypes = [ + SecTrustRef, + CFArrayRef + ] + Security.SecTrustSetAnchorCertificates.restype = OSStatus + + Security.SecTrustSetAnchorCertificatesOnly.argstypes = [ + SecTrustRef, + Boolean + ] + Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus + + Security.SecTrustEvaluate.argtypes = [ + SecTrustRef, + POINTER(SecTrustResultType) + ] + Security.SecTrustEvaluate.restype = OSStatus + + Security.SecTrustGetCertificateCount.argtypes = [ + SecTrustRef + ] + Security.SecTrustGetCertificateCount.restype = CFIndex + + Security.SecTrustGetCertificateAtIndex.argtypes = [ + SecTrustRef, + CFIndex + ] + Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef + + Security.SSLCreateContext.argtypes = [ + CFAllocatorRef, + SSLProtocolSide, + SSLConnectionType + ] + Security.SSLCreateContext.restype = SSLContextRef + + Security.SSLSetSessionOption.argtypes = [ + SSLContextRef, + SSLSessionOption, + Boolean + ] + Security.SSLSetSessionOption.restype = OSStatus + + Security.SSLSetProtocolVersionMin.argtypes = [ + SSLContextRef, + SSLProtocol + ] + Security.SSLSetProtocolVersionMin.restype = OSStatus + + Security.SSLSetProtocolVersionMax.argtypes = [ + SSLContextRef, + SSLProtocol + ] + Security.SSLSetProtocolVersionMax.restype = OSStatus + + Security.SecCopyErrorMessageString.argtypes = [ + OSStatus, + c_void_p + ] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SSLReadFunc = SSLReadFunc + Security.SSLWriteFunc = SSLWriteFunc + Security.SSLContextRef = SSLContextRef + Security.SSLProtocol = SSLProtocol + Security.SSLCipherSuite = SSLCipherSuite + Security.SecIdentityRef = SecIdentityRef + Security.SecKeychainRef = SecKeychainRef + Security.SecTrustRef = SecTrustRef + Security.SecTrustResultType = SecTrustResultType + Security.SecExternalFormat = SecExternalFormat + Security.OSStatus = OSStatus + + Security.kSecImportExportPassphrase = CFStringRef.in_dll( + Security, 'kSecImportExportPassphrase' + ) + Security.kSecImportItemIdentity = CFStringRef.in_dll( + Security, 'kSecImportItemIdentity' + ) + + # CoreFoundation time! + CoreFoundation.CFRetain.argtypes = [ + CFTypeRef + ] + CoreFoundation.CFRetain.restype = CFTypeRef + + CoreFoundation.CFRelease.argtypes = [ + CFTypeRef + ] + CoreFoundation.CFRelease.restype = None + + CoreFoundation.CFGetTypeID.argtypes = [ + CFTypeRef + ] + CoreFoundation.CFGetTypeID.restype = CFTypeID + + CoreFoundation.CFStringCreateWithCString.argtypes = [ + CFAllocatorRef, + c_char_p, + CFStringEncoding + ] + CoreFoundation.CFStringCreateWithCString.restype = CFStringRef + + CoreFoundation.CFStringGetCStringPtr.argtypes = [ + CFStringRef, + CFStringEncoding + ] + CoreFoundation.CFStringGetCStringPtr.restype = c_char_p + + CoreFoundation.CFStringGetCString.argtypes = [ + CFStringRef, + c_char_p, + CFIndex, + CFStringEncoding + ] + CoreFoundation.CFStringGetCString.restype = c_bool + + CoreFoundation.CFDataCreate.argtypes = [ + CFAllocatorRef, + c_char_p, + CFIndex + ] + CoreFoundation.CFDataCreate.restype = CFDataRef + + CoreFoundation.CFDataGetLength.argtypes = [ + CFDataRef + ] + CoreFoundation.CFDataGetLength.restype = CFIndex + + CoreFoundation.CFDataGetBytePtr.argtypes = [ + CFDataRef + ] + CoreFoundation.CFDataGetBytePtr.restype = c_void_p + + CoreFoundation.CFDictionaryCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + POINTER(CFTypeRef), + CFIndex, + CFDictionaryKeyCallBacks, + CFDictionaryValueCallBacks + ] + CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef + + CoreFoundation.CFDictionaryGetValue.argtypes = [ + CFDictionaryRef, + CFTypeRef + ] + CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef + + CoreFoundation.CFArrayCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreate.restype = CFArrayRef + + CoreFoundation.CFArrayCreateMutable.argtypes = [ + CFAllocatorRef, + CFIndex, + CFArrayCallBacks + ] + CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef + + CoreFoundation.CFArrayAppendValue.argtypes = [ + CFMutableArrayRef, + c_void_p + ] + CoreFoundation.CFArrayAppendValue.restype = None + + CoreFoundation.CFArrayGetCount.argtypes = [ + CFArrayRef + ] + CoreFoundation.CFArrayGetCount.restype = CFIndex + + CoreFoundation.CFArrayGetValueAtIndex.argtypes = [ + CFArrayRef, + CFIndex + ] + CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p + + CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( + CoreFoundation, 'kCFAllocatorDefault' + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll(CoreFoundation, 'kCFTypeArrayCallBacks') + CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( + CoreFoundation, 'kCFTypeDictionaryKeyCallBacks' + ) + CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( + CoreFoundation, 'kCFTypeDictionaryValueCallBacks' + ) + + CoreFoundation.CFTypeRef = CFTypeRef + CoreFoundation.CFArrayRef = CFArrayRef + CoreFoundation.CFStringRef = CFStringRef + CoreFoundation.CFDictionaryRef = CFDictionaryRef + +except (AttributeError): + raise ImportError('Error initializing ctypes') + + +class CFConst(object): + """ + A class object that acts as essentially a namespace for CoreFoundation + constants. + """ + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) + + +class SecurityConst(object): + """ + A class object that acts as essentially a namespace for Security constants. + """ + kSSLSessionOptionBreakOnServerAuth = 0 + + kSSLProtocol2 = 1 + kSSLProtocol3 = 2 + kTLSProtocol1 = 4 + kTLSProtocol11 = 7 + kTLSProtocol12 = 8 + + kSSLClientSide = 1 + kSSLStreamType = 0 + + kSecFormatPEMSequence = 10 + + kSecTrustResultInvalid = 0 + kSecTrustResultProceed = 1 + # This gap is present on purpose: this was kSecTrustResultConfirm, which + # is deprecated. + kSecTrustResultDeny = 3 + kSecTrustResultUnspecified = 4 + kSecTrustResultRecoverableTrustFailure = 5 + kSecTrustResultFatalTrustFailure = 6 + kSecTrustResultOtherError = 7 + + errSSLProtocol = -9800 + errSSLWouldBlock = -9803 + errSSLClosedGraceful = -9805 + errSSLClosedNoNotify = -9816 + errSSLClosedAbort = -9806 + + errSSLXCertChainInvalid = -9807 + errSSLCrypto = -9809 + errSSLInternal = -9810 + errSSLCertExpired = -9814 + errSSLCertNotYetValid = -9815 + errSSLUnknownRootCert = -9812 + errSSLNoRootCert = -9813 + errSSLHostNameMismatch = -9843 + errSSLPeerHandshakeFail = -9824 + errSSLPeerUserCancelled = -9839 + errSSLWeakPeerEphemeralDHKey = -9850 + errSSLServerAuthCompleted = -9841 + errSSLRecordOverflow = -9847 + + errSecVerifyFailed = -67808 + errSecNoTrustSettings = -25263 + errSecItemNotFound = -25300 + errSecInvalidTrustSettings = -25262 + + # Cipher suites. We only pick the ones our default cipher string allows. + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F + TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3 + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F + TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2 + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B + TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 + TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032 + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + TLS_AES_128_GCM_SHA256 = 0x1301 + TLS_AES_256_GCM_SHA384 = 0x1302 + TLS_CHACHA20_POLY1305_SHA256 = 0x1303 diff --git a/lib/urllib3/contrib/_securetransport/low_level.py b/lib/urllib3/contrib/_securetransport/low_level.py new file mode 100644 index 0000000..b13cd9e --- /dev/null +++ b/lib/urllib3/contrib/_securetransport/low_level.py @@ -0,0 +1,346 @@ +""" +Low-level helpers for the SecureTransport bindings. + +These are Python functions that are not directly related to the high-level APIs +but are necessary to get them to work. They include a whole bunch of low-level +CoreFoundation messing about and memory management. The concerns in this module +are almost entirely about trying to avoid memory leaks and providing +appropriate and useful assistance to the higher-level code. +""" +import base64 +import ctypes +import itertools +import re +import os +import ssl +import tempfile + +from .bindings import Security, CoreFoundation, CFConst + + +# This regular expression is used to grab PEM data out of a PEM bundle. +_PEM_CERTS_RE = re.compile( + b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL +) + + +def _cf_data_from_bytes(bytestring): + """ + Given a bytestring, create a CFData object from it. This CFData object must + be CFReleased by the caller. + """ + return CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring) + ) + + +def _cf_dictionary_from_tuples(tuples): + """ + Given a list of Python tuples, create an associated CFDictionary. + """ + dictionary_size = len(tuples) + + # We need to get the dictionary keys and values out in the same order. + keys = (t[0] for t in tuples) + values = (t[1] for t in tuples) + cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys) + cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values) + + return CoreFoundation.CFDictionaryCreate( + CoreFoundation.kCFAllocatorDefault, + cf_keys, + cf_values, + dictionary_size, + CoreFoundation.kCFTypeDictionaryKeyCallBacks, + CoreFoundation.kCFTypeDictionaryValueCallBacks, + ) + + +def _cf_string_to_unicode(value): + """ + Creates a Unicode string from a CFString object. Used entirely for error + reporting. + + Yes, it annoys me quite a lot that this function is this complex. + """ + value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) + + string = CoreFoundation.CFStringGetCStringPtr( + value_as_void_p, + CFConst.kCFStringEncodingUTF8 + ) + if string is None: + buffer = ctypes.create_string_buffer(1024) + result = CoreFoundation.CFStringGetCString( + value_as_void_p, + buffer, + 1024, + CFConst.kCFStringEncodingUTF8 + ) + if not result: + raise OSError('Error copying C string from CFStringRef') + string = buffer.value + if string is not None: + string = string.decode('utf-8') + return string + + +def _assert_no_error(error, exception_class=None): + """ + Checks the return code and throws an exception if there is an error to + report + """ + if error == 0: + return + + cf_error_string = Security.SecCopyErrorMessageString(error, None) + output = _cf_string_to_unicode(cf_error_string) + CoreFoundation.CFRelease(cf_error_string) + + if output is None or output == u'': + output = u'OSStatus %s' % error + + if exception_class is None: + exception_class = ssl.SSLError + + raise exception_class(output) + + +def _cert_array_from_pem(pem_bundle): + """ + Given a bundle of certs in PEM format, turns them into a CFArray of certs + that can be used to validate a cert chain. + """ + # Normalize the PEM bundle's line endings. + pem_bundle = pem_bundle.replace(b"\r\n", b"\n") + + der_certs = [ + base64.b64decode(match.group(1)) + for match in _PEM_CERTS_RE.finditer(pem_bundle) + ] + if not der_certs: + raise ssl.SSLError("No root certificates specified") + + cert_array = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks) + ) + if not cert_array: + raise ssl.SSLError("Unable to allocate memory!") + + try: + for der_bytes in der_certs: + certdata = _cf_data_from_bytes(der_bytes) + if not certdata: + raise ssl.SSLError("Unable to allocate memory!") + cert = Security.SecCertificateCreateWithData( + CoreFoundation.kCFAllocatorDefault, certdata + ) + CoreFoundation.CFRelease(certdata) + if not cert: + raise ssl.SSLError("Unable to build cert object!") + + CoreFoundation.CFArrayAppendValue(cert_array, cert) + CoreFoundation.CFRelease(cert) + except Exception: + # We need to free the array before the exception bubbles further. + # We only want to do that if an error occurs: otherwise, the caller + # should free. + CoreFoundation.CFRelease(cert_array) + + return cert_array + + +def _is_cert(item): + """ + Returns True if a given CFTypeRef is a certificate. + """ + expected = Security.SecCertificateGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _is_identity(item): + """ + Returns True if a given CFTypeRef is an identity. + """ + expected = Security.SecIdentityGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _temporary_keychain(): + """ + This function creates a temporary Mac keychain that we can use to work with + credentials. This keychain uses a one-time password and a temporary file to + store the data. We expect to have one keychain per socket. The returned + SecKeychainRef must be freed by the caller, including calling + SecKeychainDelete. + + Returns a tuple of the SecKeychainRef and the path to the temporary + directory that contains it. + """ + # Unfortunately, SecKeychainCreate requires a path to a keychain. This + # means we cannot use mkstemp to use a generic temporary file. Instead, + # we're going to create a temporary directory and a filename to use there. + # This filename will be 8 random bytes expanded into base64. We also need + # some random bytes to password-protect the keychain we're creating, so we + # ask for 40 random bytes. + random_bytes = os.urandom(40) + filename = base64.b16encode(random_bytes[:8]).decode('utf-8') + password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 + tempdirectory = tempfile.mkdtemp() + + keychain_path = os.path.join(tempdirectory, filename).encode('utf-8') + + # We now want to create the keychain itself. + keychain = Security.SecKeychainRef() + status = Security.SecKeychainCreate( + keychain_path, + len(password), + password, + False, + None, + ctypes.byref(keychain) + ) + _assert_no_error(status) + + # Having created the keychain, we want to pass it off to the caller. + return keychain, tempdirectory + + +def _load_items_from_file(keychain, path): + """ + Given a single file, loads all the trust objects from it into arrays and + the keychain. + Returns a tuple of lists: the first list is a list of identities, the + second a list of certs. + """ + certificates = [] + identities = [] + result_array = None + + with open(path, 'rb') as f: + raw_filedata = f.read() + + try: + filedata = CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, + raw_filedata, + len(raw_filedata) + ) + result_array = CoreFoundation.CFArrayRef() + result = Security.SecItemImport( + filedata, # cert data + None, # Filename, leaving it out for now + None, # What the type of the file is, we don't care + None, # what's in the file, we don't care + 0, # import flags + None, # key params, can include passphrase in the future + keychain, # The keychain to insert into + ctypes.byref(result_array) # Results + ) + _assert_no_error(result) + + # A CFArray is not very useful to us as an intermediary + # representation, so we are going to extract the objects we want + # and then free the array. We don't need to keep hold of keys: the + # keychain already has them! + result_count = CoreFoundation.CFArrayGetCount(result_array) + for index in range(result_count): + item = CoreFoundation.CFArrayGetValueAtIndex( + result_array, index + ) + item = ctypes.cast(item, CoreFoundation.CFTypeRef) + + if _is_cert(item): + CoreFoundation.CFRetain(item) + certificates.append(item) + elif _is_identity(item): + CoreFoundation.CFRetain(item) + identities.append(item) + finally: + if result_array: + CoreFoundation.CFRelease(result_array) + + CoreFoundation.CFRelease(filedata) + + return (identities, certificates) + + +def _load_client_cert_chain(keychain, *paths): + """ + Load certificates and maybe keys from a number of files. Has the end goal + of returning a CFArray containing one SecIdentityRef, and then zero or more + SecCertificateRef objects, suitable for use as a client certificate trust + chain. + """ + # Ok, the strategy. + # + # This relies on knowing that macOS will not give you a SecIdentityRef + # unless you have imported a key into a keychain. This is a somewhat + # artificial limitation of macOS (for example, it doesn't necessarily + # affect iOS), but there is nothing inside Security.framework that lets you + # get a SecIdentityRef without having a key in a keychain. + # + # So the policy here is we take all the files and iterate them in order. + # Each one will use SecItemImport to have one or more objects loaded from + # it. We will also point at a keychain that macOS can use to work with the + # private key. + # + # Once we have all the objects, we'll check what we actually have. If we + # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise, + # we'll take the first certificate (which we assume to be our leaf) and + # ask the keychain to give us a SecIdentityRef with that cert's associated + # key. + # + # We'll then return a CFArray containing the trust chain: one + # SecIdentityRef and then zero-or-more SecCertificateRef objects. The + # responsibility for freeing this CFArray will be with the caller. This + # CFArray must remain alive for the entire connection, so in practice it + # will be stored with a single SSLSocket, along with the reference to the + # keychain. + certificates = [] + identities = [] + + # Filter out bad paths. + paths = (path for path in paths if path) + + try: + for file_path in paths: + new_identities, new_certs = _load_items_from_file( + keychain, file_path + ) + identities.extend(new_identities) + certificates.extend(new_certs) + + # Ok, we have everything. The question is: do we have an identity? If + # not, we want to grab one from the first cert we have. + if not identities: + new_identity = Security.SecIdentityRef() + status = Security.SecIdentityCreateWithCertificate( + keychain, + certificates[0], + ctypes.byref(new_identity) + ) + _assert_no_error(status) + identities.append(new_identity) + + # We now want to release the original certificate, as we no longer + # need it. + CoreFoundation.CFRelease(certificates.pop(0)) + + # We now need to build a new CFArray that holds the trust chain. + trust_chain = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + for item in itertools.chain(identities, certificates): + # ArrayAppendValue does a CFRetain on the item. That's fine, + # because the finally block will release our other refs to them. + CoreFoundation.CFArrayAppendValue(trust_chain, item) + + return trust_chain + finally: + for obj in itertools.chain(identities, certificates): + CoreFoundation.CFRelease(obj) diff --git a/lib/urllib3/contrib/appengine.py b/lib/urllib3/contrib/appengine.py new file mode 100644 index 0000000..2952f11 --- /dev/null +++ b/lib/urllib3/contrib/appengine.py @@ -0,0 +1,289 @@ +""" +This module provides a pool manager that uses Google App Engine's +`URLFetch Service <https://cloud.google.com/appengine/docs/python/urlfetch>`_. + +Example usage:: + + from urllib3 import PoolManager + from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox + + if is_appengine_sandbox(): + # AppEngineManager uses AppEngine's URLFetch API behind the scenes + http = AppEngineManager() + else: + # PoolManager uses a socket-level API behind the scenes + http = PoolManager() + + r = http.request('GET', 'https://google.com/') + +There are `limitations <https://cloud.google.com/appengine/docs/python/\ +urlfetch/#Python_Quotas_and_limits>`_ to the URLFetch service and it may not be +the best choice for your application. There are three options for using +urllib3 on Google App Engine: + +1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is + cost-effective in many circumstances as long as your usage is within the + limitations. +2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. + Sockets also have `limitations and restrictions + <https://cloud.google.com/appengine/docs/python/sockets/\ + #limitations-and-restrictions>`_ and have a lower free quota than URLFetch. + To use sockets, be sure to specify the following in your ``app.yaml``:: + + env_variables: + GAE_USE_SOCKETS_HTTPLIB : 'true' + +3. If you are using `App Engine Flexible +<https://cloud.google.com/appengine/docs/flexible/>`_, you can use the standard +:class:`PoolManager` without any configuration or special environment variables. +""" + +from __future__ import absolute_import +import io +import logging +import warnings +from ..packages.six.moves.urllib.parse import urljoin + +from ..exceptions import ( + HTTPError, + HTTPWarning, + MaxRetryError, + ProtocolError, + TimeoutError, + SSLError +) + +from ..request import RequestMethods +from ..response import HTTPResponse +from ..util.timeout import Timeout +from ..util.retry import Retry +from . import _appengine_environ + +try: + from google.appengine.api import urlfetch +except ImportError: + urlfetch = None + + +log = logging.getLogger(__name__) + + +class AppEnginePlatformWarning(HTTPWarning): + pass + + +class AppEnginePlatformError(HTTPError): + pass + + +class AppEngineManager(RequestMethods): + """ + Connection manager for Google App Engine sandbox applications. + + This manager uses the URLFetch service directly instead of using the + emulated httplib, and is subject to URLFetch limitations as described in + the App Engine documentation `here + <https://cloud.google.com/appengine/docs/python/urlfetch>`_. + + Notably it will raise an :class:`AppEnginePlatformError` if: + * URLFetch is not available. + * If you attempt to use this on App Engine Flexible, as full socket + support is available. + * If a request size is more than 10 megabytes. + * If a response size is more than 32 megabtyes. + * If you use an unsupported request method such as OPTIONS. + + Beyond those cases, it will raise normal urllib3 errors. + """ + + def __init__(self, headers=None, retries=None, validate_certificate=True, + urlfetch_retries=True): + if not urlfetch: + raise AppEnginePlatformError( + "URLFetch is not available in this environment.") + + if is_prod_appengine_mvms(): + raise AppEnginePlatformError( + "Use normal urllib3.PoolManager instead of AppEngineManager" + "on Managed VMs, as using URLFetch is not necessary in " + "this environment.") + + warnings.warn( + "urllib3 is using URLFetch on Google App Engine sandbox instead " + "of sockets. To use sockets directly instead of URLFetch see " + "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.", + AppEnginePlatformWarning) + + RequestMethods.__init__(self, headers) + self.validate_certificate = validate_certificate + self.urlfetch_retries = urlfetch_retries + + self.retries = retries or Retry.DEFAULT + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Return False to re-raise any potential exceptions + return False + + def urlopen(self, method, url, body=None, headers=None, + retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw): + + retries = self._get_retries(retries, redirect) + + try: + follow_redirects = ( + redirect and + retries.redirect != 0 and + retries.total) + response = urlfetch.fetch( + url, + payload=body, + method=method, + headers=headers or {}, + allow_truncated=False, + follow_redirects=self.urlfetch_retries and follow_redirects, + deadline=self._get_absolute_timeout(timeout), + validate_certificate=self.validate_certificate, + ) + except urlfetch.DeadlineExceededError as e: + raise TimeoutError(self, e) + + except urlfetch.InvalidURLError as e: + if 'too large' in str(e): + raise AppEnginePlatformError( + "URLFetch request too large, URLFetch only " + "supports requests up to 10mb in size.", e) + raise ProtocolError(e) + + except urlfetch.DownloadError as e: + if 'Too many redirects' in str(e): + raise MaxRetryError(self, url, reason=e) + raise ProtocolError(e) + + except urlfetch.ResponseTooLargeError as e: + raise AppEnginePlatformError( + "URLFetch response too large, URLFetch only supports" + "responses up to 32mb in size.", e) + + except urlfetch.SSLCertificateError as e: + raise SSLError(e) + + except urlfetch.InvalidMethodError as e: + raise AppEnginePlatformError( + "URLFetch does not support method: %s" % method, e) + + http_response = self._urlfetch_response_to_http_response( + response, retries=retries, **response_kw) + + # Handle redirect? + redirect_location = redirect and http_response.get_redirect_location() + if redirect_location: + # Check for redirect response + if (self.urlfetch_retries and retries.raise_on_redirect): + raise MaxRetryError(self, url, "too many redirects") + else: + if http_response.status == 303: + method = 'GET' + + try: + retries = retries.increment(method, url, response=http_response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + return http_response + + retries.sleep_for_retry(http_response) + log.debug("Redirecting %s -> %s", url, redirect_location) + redirect_url = urljoin(url, redirect_location) + return self.urlopen( + method, redirect_url, body, headers, + retries=retries, redirect=redirect, + timeout=timeout, **response_kw) + + # Check if we should retry the HTTP response. + has_retry_after = bool(http_response.getheader('Retry-After')) + if retries.is_retry(method, http_response.status, has_retry_after): + retries = retries.increment( + method, url, response=http_response, _pool=self) + log.debug("Retry: %s", url) + retries.sleep(http_response) + return self.urlopen( + method, url, + body=body, headers=headers, + retries=retries, redirect=redirect, + timeout=timeout, **response_kw) + + return http_response + + def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): + + if is_prod_appengine(): + # Production GAE handles deflate encoding automatically, but does + # not remove the encoding header. + content_encoding = urlfetch_resp.headers.get('content-encoding') + + if content_encoding == 'deflate': + del urlfetch_resp.headers['content-encoding'] + + transfer_encoding = urlfetch_resp.headers.get('transfer-encoding') + # We have a full response's content, + # so let's make sure we don't report ourselves as chunked data. + if transfer_encoding == 'chunked': + encodings = transfer_encoding.split(",") + encodings.remove('chunked') + urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings) + + original_response = HTTPResponse( + # In order for decoding to work, we must present the content as + # a file-like object. + body=io.BytesIO(urlfetch_resp.content), + msg=urlfetch_resp.header_msg, + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + **response_kw + ) + + return HTTPResponse( + body=io.BytesIO(urlfetch_resp.content), + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + original_response=original_response, + **response_kw + ) + + def _get_absolute_timeout(self, timeout): + if timeout is Timeout.DEFAULT_TIMEOUT: + return None # Defer to URLFetch's default. + if isinstance(timeout, Timeout): + if timeout._read is not None or timeout._connect is not None: + warnings.warn( + "URLFetch does not support granular timeout settings, " + "reverting to total or default URLFetch timeout.", + AppEnginePlatformWarning) + return timeout.total + return timeout + + def _get_retries(self, retries, redirect): + if not isinstance(retries, Retry): + retries = Retry.from_int( + retries, redirect=redirect, default=self.retries) + + if retries.connect or retries.read or retries.redirect: + warnings.warn( + "URLFetch only supports total retries and does not " + "recognize connect, read, or redirect retry parameters.", + AppEnginePlatformWarning) + + return retries + + +# Alias methods from _appengine_environ to maintain public API interface. + +is_appengine = _appengine_environ.is_appengine +is_appengine_sandbox = _appengine_environ.is_appengine_sandbox +is_local_appengine = _appengine_environ.is_local_appengine +is_prod_appengine = _appengine_environ.is_prod_appengine +is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms diff --git a/lib/urllib3/contrib/ntlmpool.py b/lib/urllib3/contrib/ntlmpool.py new file mode 100644 index 0000000..8ea127c --- /dev/null +++ b/lib/urllib3/contrib/ntlmpool.py @@ -0,0 +1,111 @@ +""" +NTLM authenticating pool, contributed by erikcederstran + +Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 +""" +from __future__ import absolute_import + +from logging import getLogger +from ntlm import ntlm + +from .. import HTTPSConnectionPool +from ..packages.six.moves.http_client import HTTPSConnection + + +log = getLogger(__name__) + + +class NTLMConnectionPool(HTTPSConnectionPool): + """ + Implements an NTLM authentication version of an urllib3 connection pool + """ + + scheme = 'https' + + def __init__(self, user, pw, authurl, *args, **kwargs): + """ + authurl is a random URL on the server that is protected by NTLM. + user is the Windows user, probably in the DOMAIN\\username format. + pw is the password for the user. + """ + super(NTLMConnectionPool, self).__init__(*args, **kwargs) + self.authurl = authurl + self.rawuser = user + user_parts = user.split('\\', 1) + self.domain = user_parts[0].upper() + self.user = user_parts[1] + self.pw = pw + + def _new_conn(self): + # Performs the NTLM handshake that secures the connection. The socket + # must be kept open while requests are performed. + self.num_connections += 1 + log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s', + self.num_connections, self.host, self.authurl) + + headers = {'Connection': 'Keep-Alive'} + req_header = 'Authorization' + resp_header = 'www-authenticate' + + conn = HTTPSConnection(host=self.host, port=self.port) + + # Send negotiation message + headers[req_header] = ( + 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser)) + log.debug('Request headers: %s', headers) + conn.request('GET', self.authurl, None, headers) + res = conn.getresponse() + reshdr = dict(res.getheaders()) + log.debug('Response status: %s %s', res.status, res.reason) + log.debug('Response headers: %s', reshdr) + log.debug('Response data: %s [...]', res.read(100)) + + # Remove the reference to the socket, so that it can not be closed by + # the response object (we want to keep the socket open) + res.fp = None + + # Server should respond with a challenge message + auth_header_values = reshdr[resp_header].split(', ') + auth_header_value = None + for s in auth_header_values: + if s[:5] == 'NTLM ': + auth_header_value = s[5:] + if auth_header_value is None: + raise Exception('Unexpected %s response header: %s' % + (resp_header, reshdr[resp_header])) + + # Send authentication message + ServerChallenge, NegotiateFlags = \ + ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, + self.user, + self.domain, + self.pw, + NegotiateFlags) + headers[req_header] = 'NTLM %s' % auth_msg + log.debug('Request headers: %s', headers) + conn.request('GET', self.authurl, None, headers) + res = conn.getresponse() + log.debug('Response status: %s %s', res.status, res.reason) + log.debug('Response headers: %s', dict(res.getheaders())) + log.debug('Response data: %s [...]', res.read()[:100]) + if res.status != 200: + if res.status == 401: + raise Exception('Server rejected request: wrong ' + 'username or password') + raise Exception('Wrong server response: %s %s' % + (res.status, res.reason)) + + res.fp = None + log.debug('Connection established') + return conn + + def urlopen(self, method, url, body=None, headers=None, retries=3, + redirect=True, assert_same_host=True): + if headers is None: + headers = {} + headers['Connection'] = 'Keep-Alive' + return super(NTLMConnectionPool, self).urlopen(method, url, body, + headers, retries, + redirect, + assert_same_host) diff --git a/lib/urllib3/contrib/pyopenssl.py b/lib/urllib3/contrib/pyopenssl.py new file mode 100644 index 0000000..7c0e946 --- /dev/null +++ b/lib/urllib3/contrib/pyopenssl.py @@ -0,0 +1,466 @@ +""" +SSL with SNI_-support for Python 2. Follow these instructions if you would +like to verify SSL certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. + +This needs the following packages installed: + +* pyOpenSSL (tested with 16.0.0) +* cryptography (minimum 1.3.4, from pyopenssl) +* idna (minimum 2.0, from cryptography) + +However, pyopenssl depends on cryptography, which depends on idna, so while we +use all three directly here we end up having relatively few packages required. + +You can install them with the following command: + + pip install pyopenssl cryptography idna + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this:: + + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + +Now you can use :mod:`urllib3` as you normally would, and it will support SNI +when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +compression in Python 2 (see `CRIME attack`_). + +If you want to configure the default list of supported cipher suites, you can +set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) +""" +from __future__ import absolute_import + +import OpenSSL.SSL +from cryptography import x509 +from cryptography.hazmat.backends.openssl import backend as openssl_backend +from cryptography.hazmat.backends.openssl.x509 import _Certificate +try: + from cryptography.x509 import UnsupportedExtension +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): + pass + +from socket import timeout, error as SocketError +from io import BytesIO + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +import logging +import ssl +from ..packages import six +import sys + +from .. import util + +__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] + +# SNI always works. +HAS_SNI = True + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions = { + ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + +try: + _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) +except AttributeError: + pass + +_stdlib_to_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: + OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} +_openssl_to_stdlib_verify = dict( + (v, k) for k, v in _stdlib_to_openssl_verify.items() +) + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3(): + 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.' + + _validate_dependencies_met() + + util.ssl_.SSLContext = PyOpenSSLContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3(): + 'Undo monkey-patching by :func:`inject_into_urllib3`.' + + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met(): + """ + Verifies that PyOpenSSL's package-level dependencies have been met. + Throws `ImportError` if they are not met. + """ + # Method added in `cryptography==1.1`; not available in older versions + from cryptography.x509.extensions import Extensions + if getattr(Extensions, "get_extension_for_class", None) is None: + raise ImportError("'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer.") + + # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 + # attribute is only present on those versions. + from OpenSSL.crypto import X509 + x509 = X509() + if getattr(x509, "_x509", None) is None: + raise ImportError("'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer.") + + +def _dnsname_to_stdlib(name): + """ + Converts a dNSName SubjectAlternativeName field to the form used by the + standard library on the given Python version. + + Cryptography produces a dNSName as a unicode string that was idna-decoded + from ASCII bytes. We need to idna-encode that string to get it back, and + then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib + uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). + + If the name cannot be idna-encoded then we return None signalling that + the name given should be skipped. + """ + def idna_encode(name): + """ + Borrowed wholesale from the Python Cryptography Project. It turns out + that we can't just safely call `idna.encode`: it can explode for + wildcard names. This avoids that problem. + """ + import idna + + try: + for prefix in [u'*.', u'.']: + if name.startswith(prefix): + name = name[len(prefix):] + return prefix.encode('ascii') + idna.encode(name) + return idna.encode(name) + except idna.core.IDNAError: + return None + + name = idna_encode(name) + if name is None: + return None + elif sys.version_info >= (3, 0): + name = name.decode('utf-8') + return name + + +def get_subj_alt_name(peer_cert): + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + # Pass the cert to cryptography, which has much better APIs for this. + if hasattr(peer_cert, "to_cryptography"): + cert = peer_cert.to_cryptography() + else: + # This is technically using private APIs, but should work across all + # relevant versions before PyOpenSSL got a proper API for this. + cert = _Certificate(openssl_backend, peer_cert._x509) + + # We want to find the SAN extension. Ask Cryptography to locate it (it's + # faster than looping in Python) + try: + ext = cert.extensions.get_extension_for_class( + x509.SubjectAlternativeName + ).value + except x509.ExtensionNotFound: + # No such extension, return the empty list. + return [] + except (x509.DuplicateExtension, UnsupportedExtension, + x509.UnsupportedGeneralNameType, UnicodeError) as e: + # A problem has been found with the quality of the certificate. Assume + # no SAN field is present. + log.warning( + "A problem was encountered with the certificate that prevented " + "urllib3 from finding the SubjectAlternativeName field. This can " + "affect certificate validation. The error was %s", + e, + ) + return [] + + # We want to return dNSName and iPAddress fields. We need to cast the IPs + # back to strings because the match_hostname function wants them as + # strings. + # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 + # decoded. This is pretty frustrating, but that's what the standard library + # does with certificates, and so we need to attempt to do the same. + # We also want to skip over names which cannot be idna encoded. + names = [ + ('DNS', name) for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + if name is not None + ] + names.extend( + ('IP Address', str(name)) + for name in ext.get_values_for_type(x509.IPAddress) + ) + + return names + + +class WrappedSocket(object): + '''API-compatibility wrapper for Python OpenSSL's Connection-class. + + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + ''' + + def __init__(self, connection, socket, suppress_ragged_eofs=True): + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._makefile_refs = 0 + self._closed = False + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args, **kwargs): + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): + return b'' + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError as e: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b'' + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout('The read operation timed out') + else: + return self.recv(*args, **kwargs) + else: + return data + + def recv_into(self, *args, **kwargs): + try: + return self.connection.recv_into(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): + return 0 + else: + raise SocketError(str(e)) + except OpenSSL.SSL.ZeroReturnError as e: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout('The read operation timed out') + else: + return self.recv_into(*args, **kwargs) + + def settimeout(self, timeout): + return self.socket.settimeout(timeout) + + def _send_until_done(self, data): + while True: + try: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise timeout() + continue + except OpenSSL.SSL.SysCallError as e: + raise SocketError(str(e)) + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + # FIXME rethrow compatible exceptions should we ever use this + self.connection.shutdown() + + def close(self): + if self._makefile_refs < 1: + try: + self._closed = True + return self.connection.close() + except OpenSSL.SSL.Error: + return + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 + + if binary_form: + return OpenSSL.crypto.dump_certificate( + OpenSSL.crypto.FILETYPE_ASN1, + x509) + + return { + 'subject': ( + (('commonName', x509.get_subject().CN),), + ), + 'subjectAltName': get_subj_alt_name(x509) + } + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) +else: # Platform-specific: Python 3 + makefile = backport_makefile + +WrappedSocket.makefile = makefile + + +class PyOpenSSLContext(object): + """ + I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible + for translating the interface of the standard library ``SSLContext`` object + to calls into PyOpenSSL. + """ + def __init__(self, protocol): + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + + @property + def options(self): + return self._options + + @options.setter + def options(self, value): + self._options = value + self._ctx.set_options(value) + + @property + def verify_mode(self): + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value): + self._ctx.set_verify( + _stdlib_to_openssl_verify[value], + _verify_callback + ) + + def set_default_verify_paths(self): + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers): + if isinstance(ciphers, six.text_type): + ciphers = ciphers.encode('utf-8') + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + if cafile is not None: + cafile = cafile.encode('utf-8') + if capath is not None: + capath = capath.encode('utf-8') + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password) + self._ctx.use_privatekey_file(keyfile or certfile) + + def wrap_socket(self, sock, server_side=False, + do_handshake_on_connect=True, suppress_ragged_eofs=True, + server_hostname=None): + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 + server_hostname = server_hostname.encode('utf-8') + + if server_hostname is not None: + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError: + if not util.wait_for_read(sock, sock.gettimeout()): + raise timeout('select timed out') + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError('bad handshake: %r' % e) + break + + return WrappedSocket(cnx, sock) + + +def _verify_callback(cnx, x509, err_no, err_depth, return_code): + return err_no == 0 diff --git a/lib/urllib3/contrib/securetransport.py b/lib/urllib3/contrib/securetransport.py new file mode 100644 index 0000000..77cb59e --- /dev/null +++ b/lib/urllib3/contrib/securetransport.py @@ -0,0 +1,804 @@ +""" +SecureTranport support for urllib3 via ctypes. + +This makes platform-native TLS available to urllib3 users on macOS without the +use of a compiler. This is an important feature because the Python Package +Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL +that ships with macOS is not capable of doing TLSv1.2. The only way to resolve +this is to give macOS users an alternative solution to the problem, and that +solution is to use SecureTransport. + +We use ctypes here because this solution must not require a compiler. That's +because pip is not allowed to require a compiler either. + +This is not intended to be a seriously long-term solution to this problem. +The hope is that PEP 543 will eventually solve this issue for us, at which +point we can retire this contrib module. But in the short term, we need to +solve the impending tire fire that is Python on Mac without this kind of +contrib module. So...here we are. + +To use this module, simply import and inject it:: + + import urllib3.contrib.securetransport + urllib3.contrib.securetransport.inject_into_urllib3() + +Happy TLSing! +""" +from __future__ import absolute_import + +import contextlib +import ctypes +import errno +import os.path +import shutil +import socket +import ssl +import threading +import weakref + +from .. import util +from ._securetransport.bindings import ( + Security, SecurityConst, CoreFoundation +) +from ._securetransport.low_level import ( + _assert_no_error, _cert_array_from_pem, _temporary_keychain, + _load_client_cert_chain +) + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] + +# SNI always works +HAS_SNI = True + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + +# This dictionary is used by the read callback to obtain a handle to the +# calling wrapped socket. This is a pretty silly approach, but for now it'll +# do. I feel like I should be able to smuggle a handle to the wrapped socket +# directly in the SSLConnectionRef, but for now this approach will work I +# guess. +# +# We need to lock around this structure for inserts, but we don't do it for +# reads/writes in the callbacks. The reasoning here goes as follows: +# +# 1. It is not possible to call into the callbacks before the dictionary is +# populated, so once in the callback the id must be in the dictionary. +# 2. The callbacks don't mutate the dictionary, they only read from it, and +# so cannot conflict with any of the insertions. +# +# This is good: if we had to lock in the callbacks we'd drastically slow down +# the performance of this code. +_connection_refs = weakref.WeakValueDictionary() +_connection_ref_lock = threading.Lock() + +# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over +# for no better reason than we need *a* limit, and this one is right there. +SSL_WRITE_BLOCKSIZE = 16384 + +# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to +# individual cipher suites. We need to do this because this is how +# SecureTransport wants them. +CIPHER_SUITES = [ + SecurityConst.TLS_AES_256_GCM_SHA384, + SecurityConst.TLS_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, +] + +# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of +# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. +_protocol_to_min_max = { + ssl.PROTOCOL_SSLv23: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), +} + +if hasattr(ssl, "PROTOCOL_SSLv2"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( + SecurityConst.kSSLProtocol2, SecurityConst.kSSLProtocol2 + ) +if hasattr(ssl, "PROTOCOL_SSLv3"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( + SecurityConst.kSSLProtocol3, SecurityConst.kSSLProtocol3 + ) +if hasattr(ssl, "PROTOCOL_TLSv1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( + SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol1 + ) +if hasattr(ssl, "PROTOCOL_TLSv1_1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( + SecurityConst.kTLSProtocol11, SecurityConst.kTLSProtocol11 + ) +if hasattr(ssl, "PROTOCOL_TLSv1_2"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( + SecurityConst.kTLSProtocol12, SecurityConst.kTLSProtocol12 + ) +if hasattr(ssl, "PROTOCOL_TLS"): + _protocol_to_min_max[ssl.PROTOCOL_TLS] = _protocol_to_min_max[ssl.PROTOCOL_SSLv23] + + +def inject_into_urllib3(): + """ + Monkey-patch urllib3 with SecureTransport-backed SSL-support. + """ + util.ssl_.SSLContext = SecureTransportContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_SECURETRANSPORT = True + util.ssl_.IS_SECURETRANSPORT = True + + +def extract_from_urllib3(): + """ + Undo monkey-patching by :func:`inject_into_urllib3`. + """ + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_SECURETRANSPORT = False + util.ssl_.IS_SECURETRANSPORT = False + + +def _read_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport read callback. This is called by ST to request that data + be returned from the socket. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + requested_length = data_length_pointer[0] + + timeout = wrapped_socket.gettimeout() + error = None + read_count = 0 + + try: + while read_count < requested_length: + if timeout is None or timeout >= 0: + if not util.wait_for_read(base_socket, timeout): + raise socket.error(errno.EAGAIN, 'timed out') + + remaining = requested_length - read_count + buffer = (ctypes.c_char * remaining).from_address( + data_buffer + read_count + ) + chunk_size = base_socket.recv_into(buffer, remaining) + read_count += chunk_size + if not chunk_size: + if not read_count: + return SecurityConst.errSSLClosedGraceful + break + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = read_count + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = read_count + + if read_count != requested_length: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +def _write_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport write callback. This is called by ST to request that data + actually be sent on the network. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + bytes_to_write = data_length_pointer[0] + data = ctypes.string_at(data_buffer, bytes_to_write) + + timeout = wrapped_socket.gettimeout() + error = None + sent = 0 + + try: + while sent < bytes_to_write: + if timeout is None or timeout >= 0: + if not util.wait_for_write(base_socket, timeout): + raise socket.error(errno.EAGAIN, 'timed out') + chunk_sent = base_socket.send(data) + sent += chunk_sent + + # This has some needless copying here, but I'm not sure there's + # much value in optimising this data path. + data = data[chunk_sent:] + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = sent + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = sent + + if sent != bytes_to_write: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +# We need to keep these two objects references alive: if they get GC'd while +# in use then SecureTransport could attempt to call a function that is in freed +# memory. That would be...uh...bad. Yeah, that's the word. Bad. +_read_callback_pointer = Security.SSLReadFunc(_read_callback) +_write_callback_pointer = Security.SSLWriteFunc(_write_callback) + + +class WrappedSocket(object): + """ + API-compatibility wrapper for Python's OpenSSL wrapped socket object. + + Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage + collector of PyPy. + """ + def __init__(self, socket): + self.socket = socket + self.context = None + self._makefile_refs = 0 + self._closed = False + self._exception = None + self._keychain = None + self._keychain_dir = None + self._client_cert_chain = None + + # We save off the previously-configured timeout and then set it to + # zero. This is done because we use select and friends to handle the + # timeouts, but if we leave the timeout set on the lower socket then + # Python will "kindly" call select on that socket again for us. Avoid + # that by forcing the timeout to zero. + self._timeout = self.socket.gettimeout() + self.socket.settimeout(0) + + @contextlib.contextmanager + def _raise_on_error(self): + """ + A context manager that can be used to wrap calls that do I/O from + SecureTransport. If any of the I/O callbacks hit an exception, this + context manager will correctly propagate the exception after the fact. + This avoids silently swallowing those exceptions. + + It also correctly forces the socket closed. + """ + self._exception = None + + # We explicitly don't catch around this yield because in the unlikely + # event that an exception was hit in the block we don't want to swallow + # it. + yield + if self._exception is not None: + exception, self._exception = self._exception, None + self.close() + raise exception + + def _set_ciphers(self): + """ + Sets up the allowed ciphers. By default this matches the set in + util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done + custom and doesn't allow changing at this time, mostly because parsing + OpenSSL cipher strings is going to be a freaking nightmare. + """ + ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) + result = Security.SSLSetEnabledCiphers( + self.context, ciphers, len(CIPHER_SUITES) + ) + _assert_no_error(result) + + def _custom_validate(self, verify, trust_bundle): + """ + Called when we have set custom validation. We do this in two cases: + first, when cert validation is entirely disabled; and second, when + using a custom trust DB. + """ + # If we disabled cert validation, just say: cool. + if not verify: + return + + # We want data in memory, so load it up. + if os.path.isfile(trust_bundle): + with open(trust_bundle, 'rb') as f: + trust_bundle = f.read() + + cert_array = None + trust = Security.SecTrustRef() + + try: + # Get a CFArray that contains the certs we want. + cert_array = _cert_array_from_pem(trust_bundle) + + # Ok, now the hard part. We want to get the SecTrustRef that ST has + # created for this connection, shove our CAs into it, tell ST to + # ignore everything else it knows, and then ask if it can build a + # chain. This is a buuuunch of code. + result = Security.SSLCopyPeerTrust( + self.context, ctypes.byref(trust) + ) + _assert_no_error(result) + if not trust: + raise ssl.SSLError("Failed to copy trust reference") + + result = Security.SecTrustSetAnchorCertificates(trust, cert_array) + _assert_no_error(result) + + result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) + _assert_no_error(result) + + trust_result = Security.SecTrustResultType() + result = Security.SecTrustEvaluate( + trust, ctypes.byref(trust_result) + ) + _assert_no_error(result) + finally: + if trust: + CoreFoundation.CFRelease(trust) + + if cert_array is not None: + CoreFoundation.CFRelease(cert_array) + + # Ok, now we can look at what the result was. + successes = ( + SecurityConst.kSecTrustResultUnspecified, + SecurityConst.kSecTrustResultProceed + ) + if trust_result.value not in successes: + raise ssl.SSLError( + "certificate verify failed, error code: %d" % + trust_result.value + ) + + def handshake(self, + server_hostname, + verify, + trust_bundle, + min_version, + max_version, + client_cert, + client_key, + client_key_passphrase): + """ + Actually performs the TLS handshake. This is run automatically by + wrapped socket, and shouldn't be needed in user code. + """ + # First, we do the initial bits of connection setup. We need to create + # a context, set its I/O funcs, and set the connection reference. + self.context = Security.SSLCreateContext( + None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType + ) + result = Security.SSLSetIOFuncs( + self.context, _read_callback_pointer, _write_callback_pointer + ) + _assert_no_error(result) + + # Here we need to compute the handle to use. We do this by taking the + # id of self modulo 2**31 - 1. If this is already in the dictionary, we + # just keep incrementing by one until we find a free space. + with _connection_ref_lock: + handle = id(self) % 2147483647 + while handle in _connection_refs: + handle = (handle + 1) % 2147483647 + _connection_refs[handle] = self + + result = Security.SSLSetConnection(self.context, handle) + _assert_no_error(result) + + # If we have a server hostname, we should set that too. + if server_hostname: + if not isinstance(server_hostname, bytes): + server_hostname = server_hostname.encode('utf-8') + + result = Security.SSLSetPeerDomainName( + self.context, server_hostname, len(server_hostname) + ) + _assert_no_error(result) + + # Setup the ciphers. + self._set_ciphers() + + # Set the minimum and maximum TLS versions. + result = Security.SSLSetProtocolVersionMin(self.context, min_version) + _assert_no_error(result) + result = Security.SSLSetProtocolVersionMax(self.context, max_version) + _assert_no_error(result) + + # If there's a trust DB, we need to use it. We do that by telling + # SecureTransport to break on server auth. We also do that if we don't + # want to validate the certs at all: we just won't actually do any + # authing in that case. + if not verify or trust_bundle is not None: + result = Security.SSLSetSessionOption( + self.context, + SecurityConst.kSSLSessionOptionBreakOnServerAuth, + True + ) + _assert_no_error(result) + + # If there's a client cert, we need to use it. + if client_cert: + self._keychain, self._keychain_dir = _temporary_keychain() + self._client_cert_chain = _load_client_cert_chain( + self._keychain, client_cert, client_key + ) + result = Security.SSLSetCertificate( + self.context, self._client_cert_chain + ) + _assert_no_error(result) + + while True: + with self._raise_on_error(): + result = Security.SSLHandshake(self.context) + + if result == SecurityConst.errSSLWouldBlock: + raise socket.timeout("handshake timed out") + elif result == SecurityConst.errSSLServerAuthCompleted: + self._custom_validate(verify, trust_bundle) + continue + else: + _assert_no_error(result) + break + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, bufsiz): + buffer = ctypes.create_string_buffer(bufsiz) + bytes_read = self.recv_into(buffer, bufsiz) + data = buffer[:bytes_read] + return data + + def recv_into(self, buffer, nbytes=None): + # Read short on EOF. + if self._closed: + return 0 + + if nbytes is None: + nbytes = len(buffer) + + buffer = (ctypes.c_char * nbytes).from_buffer(buffer) + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLRead( + self.context, buffer, nbytes, ctypes.byref(processed_bytes) + ) + + # There are some result codes that we want to treat as "not always + # errors". Specifically, those are errSSLWouldBlock, + # errSSLClosedGraceful, and errSSLClosedNoNotify. + if (result == SecurityConst.errSSLWouldBlock): + # If we didn't process any bytes, then this was just a time out. + # However, we can get errSSLWouldBlock in situations when we *did* + # read some data, and in those cases we should just read "short" + # and return. + if processed_bytes.value == 0: + # Timed out, no data read. + raise socket.timeout("recv timed out") + elif result in (SecurityConst.errSSLClosedGraceful, SecurityConst.errSSLClosedNoNotify): + # The remote peer has closed this connection. We should do so as + # well. Note that we don't actually return here because in + # principle this could actually be fired along with return data. + # It's unlikely though. + self.close() + else: + _assert_no_error(result) + + # Ok, we read and probably succeeded. We should return whatever data + # was actually read. + return processed_bytes.value + + def settimeout(self, timeout): + self._timeout = timeout + + def gettimeout(self): + return self._timeout + + def send(self, data): + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLWrite( + self.context, data, len(data), ctypes.byref(processed_bytes) + ) + + if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: + # Timed out + raise socket.timeout("send timed out") + else: + _assert_no_error(result) + + # We sent, and probably succeeded. Tell them how much we sent. + return processed_bytes.value + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self.send(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + with self._raise_on_error(): + Security.SSLClose(self.context) + + def close(self): + # TODO: should I do clean shutdown here? Do I have to? + if self._makefile_refs < 1: + self._closed = True + if self.context: + CoreFoundation.CFRelease(self.context) + self.context = None + if self._client_cert_chain: + CoreFoundation.CFRelease(self._client_cert_chain) + self._client_cert_chain = None + if self._keychain: + Security.SecKeychainDelete(self._keychain) + CoreFoundation.CFRelease(self._keychain) + shutil.rmtree(self._keychain_dir) + self._keychain = self._keychain_dir = None + return self.socket.close() + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + # Urgh, annoying. + # + # Here's how we do this: + # + # 1. Call SSLCopyPeerTrust to get hold of the trust object for this + # connection. + # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. + # 3. To get the CN, call SecCertificateCopyCommonName and process that + # string so that it's of the appropriate type. + # 4. To get the SAN, we need to do something a bit more complex: + # a. Call SecCertificateCopyValues to get the data, requesting + # kSecOIDSubjectAltName. + # b. Mess about with this dictionary to try to get the SANs out. + # + # This is gross. Really gross. It's going to be a few hundred LoC extra + # just to repeat something that SecureTransport can *already do*. So my + # operating assumption at this time is that what we want to do is + # instead to just flag to urllib3 that it shouldn't do its own hostname + # validation when using SecureTransport. + if not binary_form: + raise ValueError( + "SecureTransport only supports dumping binary certs" + ) + trust = Security.SecTrustRef() + certdata = None + der_bytes = None + + try: + # Grab the trust store. + result = Security.SSLCopyPeerTrust( + self.context, ctypes.byref(trust) + ) + _assert_no_error(result) + if not trust: + # Probably we haven't done the handshake yet. No biggie. + return None + + cert_count = Security.SecTrustGetCertificateCount(trust) + if not cert_count: + # Also a case that might happen if we haven't handshaked. + # Handshook? Handshaken? + return None + + leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) + assert leaf + + # Ok, now we want the DER bytes. + certdata = Security.SecCertificateCopyData(leaf) + assert certdata + + data_length = CoreFoundation.CFDataGetLength(certdata) + data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) + der_bytes = ctypes.string_at(data_buffer, data_length) + finally: + if certdata: + CoreFoundation.CFRelease(certdata) + if trust: + CoreFoundation.CFRelease(trust) + + return der_bytes + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) +else: # Platform-specific: Python 3 + def makefile(self, mode="r", buffering=None, *args, **kwargs): + # We disable buffering with SecureTransport because it conflicts with + # the buffering that ST does internally (see issue #1153 for more). + buffering = 0 + return backport_makefile(self, mode, buffering, *args, **kwargs) + +WrappedSocket.makefile = makefile + + +class SecureTransportContext(object): + """ + I am a wrapper class for the SecureTransport library, to translate the + interface of the standard library ``SSLContext`` object to calls into + SecureTransport. + """ + def __init__(self, protocol): + self._min_version, self._max_version = _protocol_to_min_max[protocol] + self._options = 0 + self._verify = False + self._trust_bundle = None + self._client_cert = None + self._client_key = None + self._client_key_passphrase = None + + @property + def check_hostname(self): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + return True + + @check_hostname.setter + def check_hostname(self, value): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + pass + + @property + def options(self): + # TODO: Well, crap. + # + # So this is the bit of the code that is the most likely to cause us + # trouble. Essentially we need to enumerate all of the SSL options that + # users might want to use and try to see if we can sensibly translate + # them, or whether we should just ignore them. + return self._options + + @options.setter + def options(self, value): + # TODO: Update in line with above. + self._options = value + + @property + def verify_mode(self): + return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE + + @verify_mode.setter + def verify_mode(self, value): + self._verify = True if value == ssl.CERT_REQUIRED else False + + def set_default_verify_paths(self): + # So, this has to do something a bit weird. Specifically, what it does + # is nothing. + # + # This means that, if we had previously had load_verify_locations + # called, this does not undo that. We need to do that because it turns + # out that the rest of the urllib3 code will attempt to load the + # default verify paths if it hasn't been told about any paths, even if + # the context itself was sometime earlier. We resolve that by just + # ignoring it. + pass + + def load_default_certs(self): + return self.set_default_verify_paths() + + def set_ciphers(self, ciphers): + # For now, we just require the default cipher string. + if ciphers != util.ssl_.DEFAULT_CIPHERS: + raise ValueError( + "SecureTransport doesn't support custom cipher strings" + ) + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + # OK, we only really support cadata and cafile. + if capath is not None: + raise ValueError( + "SecureTransport does not support cert directories" + ) + + self._trust_bundle = cafile or cadata + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._client_cert = certfile + self._client_key = keyfile + self._client_cert_passphrase = password + + def wrap_socket(self, sock, server_side=False, + do_handshake_on_connect=True, suppress_ragged_eofs=True, + server_hostname=None): + # So, what do we do here? Firstly, we assert some properties. This is a + # stripped down shim, so there is some functionality we don't support. + # See PEP 543 for the real deal. + assert not server_side + assert do_handshake_on_connect + assert suppress_ragged_eofs + + # Ok, we're good to go. Now we want to create the wrapped socket object + # and store it in the appropriate place. + wrapped_socket = WrappedSocket(sock) + + # Now we can handshake + wrapped_socket.handshake( + server_hostname, self._verify, self._trust_bundle, + self._min_version, self._max_version, self._client_cert, + self._client_key, self._client_key_passphrase + ) + return wrapped_socket diff --git a/lib/urllib3/contrib/socks.py b/lib/urllib3/contrib/socks.py new file mode 100644 index 0000000..811e312 --- /dev/null +++ b/lib/urllib3/contrib/socks.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +""" +This module contains provisional support for SOCKS proxies from within +urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and +SOCKS5. To enable its functionality, either install PySocks or install this +module with the ``socks`` extra. + +The SOCKS implementation supports the full range of urllib3 features. It also +supports the following SOCKS features: + +- SOCKS4 +- SOCKS4a +- SOCKS5 +- Usernames and passwords for the SOCKS proxy + +Known Limitations: + +- Currently PySocks does not support contacting remote websites via literal + IPv6 addresses. Any such connection attempt will fail. You must use a domain + name. +- Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any + such connection attempt will fail. +""" +from __future__ import absolute_import + +try: + import socks +except ImportError: + import warnings + from ..exceptions import DependencyWarning + + warnings.warn(( + 'SOCKS support in urllib3 requires the installation of optional ' + 'dependencies: specifically, PySocks. For more information, see ' + 'https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies' + ), + DependencyWarning + ) + raise + +from socket import error as SocketError, timeout as SocketTimeout + +from ..connection import ( + HTTPConnection, HTTPSConnection +) +from ..connectionpool import ( + HTTPConnectionPool, HTTPSConnectionPool +) +from ..exceptions import ConnectTimeoutError, NewConnectionError +from ..poolmanager import PoolManager +from ..util.url import parse_url + +try: + import ssl +except ImportError: + ssl = None + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + def __init__(self, *args, **kwargs): + self._socks_options = kwargs.pop('_socks_options') + super(SOCKSConnection, self).__init__(*args, **kwargs) + + def _new_conn(self): + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw = {} + if self.source_address: + extra_kw['source_address'] = self.source_address + + if self.socket_options: + extra_kw['socket_options'] = self.socket_options + + try: + conn = socks.create_connection( + (self.host, self.port), + proxy_type=self._socks_options['socks_version'], + proxy_addr=self._socks_options['proxy_host'], + proxy_port=self._socks_options['proxy_port'], + proxy_username=self._socks_options['username'], + proxy_password=self._socks_options['password'], + proxy_rdns=self._socks_options['rdns'], + timeout=self.timeout, + **extra_kw + ) + + except SocketTimeout as e: + raise ConnectTimeoutError( + self, "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout)) + + except socks.ProxyError as e: + # This is fragile as hell, but it seems to be the only way to raise + # useful errors here. + if e.socket_err: + error = e.socket_err + if isinstance(error, SocketTimeout): + raise ConnectTimeoutError( + self, + "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout) + ) + else: + raise NewConnectionError( + self, + "Failed to establish a new connection: %s" % error + ) + else: + raise NewConnectionError( + self, + "Failed to establish a new connection: %s" % e + ) + + except SocketError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e) + + return conn + + +# We don't need to duplicate the Verified/Unverified distinction from +# urllib3/connection.py here because the HTTPSConnection will already have been +# correctly set to either the Verified or Unverified form by that module. This +# means the SOCKSHTTPSConnection will automatically be the correct type. +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): + pass + + +class SOCKSHTTPConnectionPool(HTTPConnectionPool): + ConnectionCls = SOCKSConnection + + +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): + ConnectionCls = SOCKSHTTPSConnection + + +class SOCKSProxyManager(PoolManager): + """ + A version of the urllib3 ProxyManager that routes connections via the + defined SOCKS proxy. + """ + pool_classes_by_scheme = { + 'http': SOCKSHTTPConnectionPool, + 'https': SOCKSHTTPSConnectionPool, + } + + def __init__(self, proxy_url, username=None, password=None, + num_pools=10, headers=None, **connection_pool_kw): + parsed = parse_url(proxy_url) + + if username is None and password is None and parsed.auth is not None: + split = parsed.auth.split(':') + if len(split) == 2: + username, password = split + if parsed.scheme == 'socks5': + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = False + elif parsed.scheme == 'socks5h': + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = True + elif parsed.scheme == 'socks4': + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = False + elif parsed.scheme == 'socks4a': + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = True + else: + raise ValueError( + "Unable to determine SOCKS version from %s" % proxy_url + ) + + self.proxy_url = proxy_url + + socks_options = { + 'socks_version': socks_version, + 'proxy_host': parsed.host, + 'proxy_port': parsed.port, + 'username': username, + 'password': password, + 'rdns': rdns + } + connection_pool_kw['_socks_options'] = socks_options + + super(SOCKSProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw + ) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/lib/urllib3/exceptions.py b/lib/urllib3/exceptions.py new file mode 100644 index 0000000..7bbaa98 --- /dev/null +++ b/lib/urllib3/exceptions.py @@ -0,0 +1,246 @@ +from __future__ import absolute_import +from .packages.six.moves.http_client import ( + IncompleteRead as httplib_IncompleteRead +) +# Base Exceptions + + +class HTTPError(Exception): + "Base exception used by this module." + pass + + +class HTTPWarning(Warning): + "Base warning used by this module." + pass + + +class PoolError(HTTPError): + "Base exception for errors caused within a pool." + def __init__(self, pool, message): + self.pool = pool + HTTPError.__init__(self, "%s: %s" % (pool, message)) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, None) + + +class RequestError(PoolError): + "Base exception for PoolErrors that have associated URLs." + def __init__(self, pool, url, message): + self.url = url + PoolError.__init__(self, pool, message) + + def __reduce__(self): + # For pickling purposes. + return self.__class__, (None, self.url, None) + + +class SSLError(HTTPError): + "Raised when SSL certificate fails in an HTTPS connection." + pass + + +class ProxyError(HTTPError): + "Raised when the connection to a proxy fails." + pass + + +class DecodeError(HTTPError): + "Raised when automatic decoding based on Content-Type fails." + pass + + +class ProtocolError(HTTPError): + "Raised when something unexpected happens mid-request/response." + pass + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error + + """ + + def __init__(self, pool, url, reason=None): + self.reason = reason + + message = "Max retries exceeded with url: %s (Caused by %r)" % ( + url, reason) + + RequestError.__init__(self, pool, url, message) + + +class HostChangedError(RequestError): + "Raised when an existing pool gets a request for a foreign host." + + def __init__(self, pool, url, retries=3): + message = "Tried to open a foreign host with url: %s" % url + RequestError.__init__(self, pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """ Raised when passing an invalid state to a timeout """ + pass + + +class TimeoutError(HTTPError): + """ Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + <ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`. + """ + pass + + +class ReadTimeoutError(TimeoutError, RequestError): + "Raised when a socket timeout occurs while receiving data from a server" + pass + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + "Raised when a socket timeout occurs while connecting to a server" + pass + + +class NewConnectionError(ConnectTimeoutError, PoolError): + "Raised when we fail to establish a new connection. Usually ECONNREFUSED." + pass + + +class EmptyPoolError(PoolError): + "Raised when a pool runs out of connections and no more are allowed." + pass + + +class ClosedPoolError(PoolError): + "Raised when a request enters a pool after the pool has been closed." + pass + + +class LocationValueError(ValueError, HTTPError): + "Raised when there is something wrong with a given URL input." + pass + + +class LocationParseError(LocationValueError): + "Raised when get_host or similar fails to parse the URL input." + + def __init__(self, location): + message = "Failed to parse: %s" % location + HTTPError.__init__(self, message) + + self.location = location + + +class ResponseError(HTTPError): + "Used as a container for an error reason supplied in a MaxRetryError." + GENERIC_ERROR = 'too many error responses' + SPECIFIC_ERROR = 'too many {status_code} error responses' + + +class SecurityWarning(HTTPWarning): + "Warned when performing security reducing actions" + pass + + +class SubjectAltNameWarning(SecurityWarning): + "Warned when connecting to a host with a certificate missing a SAN." + pass + + +class InsecureRequestWarning(SecurityWarning): + "Warned when making an unverified HTTPS request." + pass + + +class SystemTimeWarning(SecurityWarning): + "Warned when system time is suspected to be wrong" + pass + + +class InsecurePlatformWarning(SecurityWarning): + "Warned when certain SSL configuration is not available on a platform." + pass + + +class SNIMissingWarning(HTTPWarning): + "Warned when making a HTTPS request without SNI available." + pass + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + pass + + +class ResponseNotChunked(ProtocolError, ValueError): + "Response needs to be chunked in order to read it as chunks." + pass + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be httplib.HTTPResponse like (have an fp attribute which + returns raw chunks) for read_chunked(). + """ + pass + + +class IncompleteRead(HTTPError, httplib_IncompleteRead): + """ + Response length doesn't match expected Content-Length + + Subclass of http_client.IncompleteRead to allow int value + for `partial` to avoid creating large objects on streamed + reads. + """ + def __init__(self, partial, expected): + super(IncompleteRead, self).__init__(partial, expected) + + def __repr__(self): + return ('IncompleteRead(%i bytes read, ' + '%i more expected)' % (self.partial, self.expected)) + + +class InvalidHeader(HTTPError): + "The header provided was somehow invalid." + pass + + +class ProxySchemeUnknown(AssertionError, ValueError): + "ProxyManager does not support the supplied scheme" + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme): + message = "Not supported proxy scheme %s" % scheme + super(ProxySchemeUnknown, self).__init__(message) + + +class HeaderParsingError(HTTPError): + "Raised by assert_header_parsing, but we convert it to a log.warning statement." + def __init__(self, defects, unparsed_data): + message = '%s, unparsed data: %r' % (defects or 'Unknown', unparsed_data) + super(HeaderParsingError, self).__init__(message) + + +class UnrewindableBodyError(HTTPError): + "urllib3 encountered an error when trying to rewind a body" + pass diff --git a/lib/urllib3/fields.py b/lib/urllib3/fields.py new file mode 100644 index 0000000..37fe64a --- /dev/null +++ b/lib/urllib3/fields.py @@ -0,0 +1,178 @@ +from __future__ import absolute_import +import email.utils +import mimetypes + +from .packages import six + + +def guess_content_type(filename, default='application/octet-stream'): + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param(name, value): + """ + Helper function to format and quote a single header parameter. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows RFC 2231, as + suggested by RFC 2388 Section 4.4. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + if not any(ch in value for ch in '"\\\r\n'): + result = '%s="%s"' % (name, value) + try: + result.encode('ascii') + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + if not six.PY3 and isinstance(value, six.text_type): # Python 2: + value = value.encode('utf-8') + value = email.utils.encode_rfc2231(value, 'utf-8') + value = '%s*=%s' % (name, value) + return value + + +class RequestField(object): + """ + A data container for request body parameters. + + :param name: + The name of this request field. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. + :param headers: + An optional dict-like object of headers to initially use for the field. + """ + def __init__(self, name, data, filename=None, headers=None): + self._name = name + self._filename = filename + self.data = data + self.headers = {} + if headers: + self.headers = dict(headers) + + @classmethod + def from_tuples(cls, fieldname, value): + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls(fieldname, data, filename=filename) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name, value): + """ + Overridable helper function to format a single header parameter. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as a unicode string. + """ + return format_header_param(name, value) + + def _render_parts(self, header_parts): + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + parts = [] + iterable = header_parts + if isinstance(header_parts, dict): + iterable = header_parts.items() + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return '; '.join(parts) + + def render_headers(self): + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location'] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append('%s: %s' % (sort_key, self.headers[sort_key])) + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append('%s: %s' % (header_name, header_value)) + + lines.append('\r\n') + return '\r\n'.join(lines) + + def make_multipart(self, content_disposition=None, content_type=None, + content_location=None): + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + self.headers['Content-Disposition'] = content_disposition or 'form-data' + self.headers['Content-Disposition'] += '; '.join([ + '', self._render_parts( + (('name', self._name), ('filename', self._filename)) + ) + ]) + self.headers['Content-Type'] = content_type + self.headers['Content-Location'] = content_location diff --git a/lib/urllib3/filepost.py b/lib/urllib3/filepost.py new file mode 100644 index 0000000..78f1e19 --- /dev/null +++ b/lib/urllib3/filepost.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import +import binascii +import codecs +import os + +from io import BytesIO + +from .packages import six +from .packages.six import b +from .fields import RequestField + +writer = codecs.lookup('utf-8')[3] + + +def choose_boundary(): + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + boundary = binascii.hexlify(os.urandom(16)) + if six.PY3: + boundary = boundary.decode('ascii') + return boundary + + +def iter_field_objects(fields): + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + if isinstance(fields, dict): + i = six.iteritems(fields) + else: + i = iter(fields) + + for field in i: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def iter_fields(fields): + """ + .. deprecated:: 1.6 + + Iterate over fields. + + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. + + Supports list of (k, v) tuples and dicts. + """ + if isinstance(fields, dict): + return ((k, v) for k, v in six.iteritems(fields)) + + return ((k, v) for k, v in fields) + + +def encode_multipart_formdata(fields, boundary=None): + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(b('--%s\r\n' % (boundary))) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, six.text_type): + writer(body).write(data) + else: + body.write(data) + + body.write(b'\r\n') + + body.write(b('--%s--\r\n' % (boundary))) + + content_type = str('multipart/form-data; boundary=%s' % boundary) + + return body.getvalue(), content_type diff --git a/lib/urllib3/packages/__init__.py b/lib/urllib3/packages/__init__.py new file mode 100644 index 0000000..170e974 --- /dev/null +++ b/lib/urllib3/packages/__init__.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import + +from . import ssl_match_hostname + +__all__ = ('ssl_match_hostname', ) diff --git a/lib/urllib3/packages/backports/__init__.py b/lib/urllib3/packages/backports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/urllib3/packages/backports/makefile.py b/lib/urllib3/packages/backports/makefile.py new file mode 100644 index 0000000..740db37 --- /dev/null +++ b/lib/urllib3/packages/backports/makefile.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +""" +backports.makefile +~~~~~~~~~~~~~~~~~~ + +Backports the Python 3 ``socket.makefile`` method for use with anything that +wants to create a "fake" socket object. +""" +import io + +from socket import SocketIO + + +def backport_makefile(self, mode="r", buffering=None, encoding=None, + errors=None, newline=None): + """ + Backport of ``socket.makefile`` from Python 3.5. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError( + "invalid mode %r (only r, w, b allowed)" % (mode,) + ) + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = SocketIO(self, rawmode) + self._makefile_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text diff --git a/lib/urllib3/packages/six.py b/lib/urllib3/packages/six.py new file mode 100644 index 0000000..190c023 --- /dev/null +++ b/lib/urllib3/packages/six.py @@ -0,0 +1,868 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +# Copyright (c) 2010-2015 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson <benjamin@python.org>" +__version__ = "1.10.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + if from_value is None: + raise value + raise value from from_value +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + raise value from from_value +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/lib/urllib3/packages/ssl_match_hostname/__init__.py b/lib/urllib3/packages/ssl_match_hostname/__init__.py new file mode 100644 index 0000000..d6594eb --- /dev/null +++ b/lib/urllib3/packages/ssl_match_hostname/__init__.py @@ -0,0 +1,19 @@ +import sys + +try: + # Our match_hostname function is the same as 3.5's, so we only want to + # import the match_hostname function if it's at least that good. + if sys.version_info < (3, 5): + raise ImportError("Fallback to vendored code") + + from ssl import CertificateError, match_hostname +except ImportError: + try: + # Backport of the function from a pypi module + from backports.ssl_match_hostname import CertificateError, match_hostname + except ImportError: + # Our vendored copy + from ._implementation import CertificateError, match_hostname + +# Not needed, but documenting what we provide. +__all__ = ('CertificateError', 'match_hostname') diff --git a/lib/urllib3/packages/ssl_match_hostname/_implementation.py b/lib/urllib3/packages/ssl_match_hostname/_implementation.py new file mode 100644 index 0000000..d6e66c0 --- /dev/null +++ b/lib/urllib3/packages/ssl_match_hostname/_implementation.py @@ -0,0 +1,156 @@ +"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html + +import re +import sys + +# ipaddress has been backported to 2.6+ in pypi. If it is installed on the +# system, use it to handle IPAddress ServerAltnames (this was added in +# python-3.5) otherwise only do DNS matching. This allows +# backports.ssl_match_hostname to continue to be used in Python 2.7. +try: + import ipaddress +except ImportError: + ipaddress = None + +__version__ = '3.5.0.1' + + +class CertificateError(ValueError): + pass + + +def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r'.') + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + +def _to_unicode(obj): + if isinstance(obj, str) and sys.version_info < (3,): + obj = unicode(obj, encoding='ascii', errors='strict') + return obj + +def _ipaddress_match(ipname, host_ip): + """Exact matching of IP addresses. + + RFC 6125 explicitly doesn't define an algorithm for this + (section 1.7.2 - "Out of Scope"). + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + # Divergence from upstream: ipaddress can't handle byte str + ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) + return ip == host_ip + + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED") + try: + # Divergence from upstream: ipaddress can't handle byte str + host_ip = ipaddress.ip_address(_to_unicode(hostname)) + except ValueError: + # Not an IP address (common case) + host_ip = None + except UnicodeError: + # Divergence from upstream: Have to deal with ipaddress not taking + # byte strings. addresses should be all ascii, so we consider it not + # an ipaddress in this case + host_ip = None + except AttributeError: + # Divergence from upstream: Make ipaddress library optional + if ipaddress is None: + host_ip = None + else: + raise + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == 'IP Address': + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError("hostname %r " + "doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError("hostname %r " + "doesn't match %r" + % (hostname, dnsnames[0])) + else: + raise CertificateError("no appropriate commonName or " + "subjectAltName fields were found") diff --git a/lib/urllib3/poolmanager.py b/lib/urllib3/poolmanager.py new file mode 100644 index 0000000..fe5491c --- /dev/null +++ b/lib/urllib3/poolmanager.py @@ -0,0 +1,450 @@ +from __future__ import absolute_import +import collections +import functools +import logging + +from ._collections import RecentlyUsedContainer +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from .connectionpool import port_by_scheme +from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown +from .packages.six.moves.urllib.parse import urljoin +from .request import RequestMethods +from .util.url import parse_url +from .util.retry import Retry + + +__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url'] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', + 'ssl_version', 'ca_cert_dir', 'ssl_context') + +# All known keyword arguments that could be provided to the pool manager, its +# pools, or the underlying connections. This is used to construct a pool key. +_key_fields = ( + 'key_scheme', # str + 'key_host', # str + 'key_port', # int + 'key_timeout', # int or float or Timeout + 'key_retries', # int or Retry + 'key_strict', # bool + 'key_block', # bool + 'key_source_address', # str + 'key_key_file', # str + 'key_cert_file', # str + 'key_cert_reqs', # str + 'key_ca_certs', # str + 'key_ssl_version', # str + 'key_ca_cert_dir', # str + 'key_ssl_context', # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + 'key_maxsize', # int + 'key_headers', # dict + 'key__proxy', # parsed proxy url + 'key__proxy_headers', # dict + 'key_socket_options', # list of (level (int), optname (int), value (int or str)) tuples + 'key__socks_options', # dict + 'key_assert_hostname', # bool or string + 'key_assert_fingerprint', # str + 'key_server_hostname', #str +) + +#: The namedtuple class used to construct keys for the connection pool. +#: All custom key schemes should include the fields in this key at a minimum. +PoolKey = collections.namedtuple('PoolKey', _key_fields) + + +def _default_key_normalizer(key_class, request_context): + """ + Create a pool key out of a request context dictionary. + + According to RFC 3986, both the scheme and host are case-insensitive. + Therefore, this function normalizes both before constructing the pool + key for an HTTPS request. If you wish to change this behaviour, provide + alternate callables to ``key_fn_by_scheme``. + + :param key_class: + The class to use when constructing the key. This should be a namedtuple + with the ``scheme`` and ``host`` keys at a minimum. + :type key_class: namedtuple + :param request_context: + A dictionary-like object that contain the context for a request. + :type request_context: dict + + :return: A namedtuple that can be used as a connection pool key. + :rtype: PoolKey + """ + # Since we mutate the dictionary, make a copy first + context = request_context.copy() + context['scheme'] = context['scheme'].lower() + context['host'] = context['host'].lower() + + # These are both dictionaries and need to be transformed into frozensets + for key in ('headers', '_proxy_headers', '_socks_options'): + if key in context and context[key] is not None: + context[key] = frozenset(context[key].items()) + + # The socket_options key may be a list and needs to be transformed into a + # tuple. + socket_opts = context.get('socket_options') + if socket_opts is not None: + context['socket_options'] = tuple(socket_opts) + + # Map the kwargs to the names in the namedtuple - this is necessary since + # namedtuples can't have fields starting with '_'. + for key in list(context.keys()): + context['key_' + key] = context.pop(key) + + # Default to ``None`` for keys missing from the context + for field in key_class._fields: + if field not in context: + context[field] = None + + return key_class(**context) + + +#: A dictionary that maps a scheme to a callable that creates a pool key. +#: This can be used to alter the way pool keys are constructed, if desired. +#: Each PoolManager makes a copy of this dictionary so they can be configured +#: globally here, or individually on the instance. +key_fn_by_scheme = { + 'http': functools.partial(_default_key_normalizer, PoolKey), + 'https': functools.partial(_default_key_normalizer, PoolKey), +} + +pool_classes_by_scheme = { + 'http': HTTPConnectionPool, + 'https': HTTPSConnectionPool, +} + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \\**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example:: + + >>> manager = PoolManager(num_pools=2) + >>> r = manager.request('GET', 'http://google.com/') + >>> r = manager.request('GET', 'http://google.com/mail') + >>> r = manager.request('GET', 'http://yahoo.com/') + >>> len(manager.pools) + 2 + + """ + + proxy = None + + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) + self.connection_pool_kw = connection_pool_kw + self.pools = RecentlyUsedContainer(num_pools, + dispose_func=lambda p: p.close()) + + # Locally set the pool classes and keys so other PoolManagers can + # override them. + self.pool_classes_by_scheme = pool_classes_by_scheme + self.key_fn_by_scheme = key_fn_by_scheme.copy() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool(self, scheme, host, port, request_context=None): + """ + Create a new :class:`ConnectionPool` based on host, port, scheme, and + any additional pool keyword arguments. + + If ``request_context`` is provided, it is provided as keyword arguments + to the pool class used. This method is used to actually create the + connection pools handed out by :meth:`connection_from_url` and + companion methods. It is intended to be overridden for customization. + """ + pool_cls = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Although the context has everything necessary to create the pool, + # this function has historically only used the scheme, host, and port + # in the positional args. When an API change is acceptable these can + # be removed. + for key in ('scheme', 'host', 'port'): + request_context.pop(key, None) + + if scheme == 'http': + for kw in SSL_KEYWORDS: + request_context.pop(kw, None) + + return pool_cls(host, port, **request_context) + + def clear(self): + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): + """ + Get a :class:`ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is + provided, it is merged with the instance's ``connection_pool_kw`` + variable and used to create the new connection pool, if one is + needed. + """ + + if not host: + raise LocationValueError("No host specified.") + + request_context = self._merge_pool_kwargs(pool_kwargs) + request_context['scheme'] = scheme or 'http' + if not port: + port = port_by_scheme.get(request_context['scheme'].lower(), 80) + request_context['port'] = port + request_context['host'] = host + + return self.connection_from_context(request_context) + + def connection_from_context(self, request_context): + """ + Get a :class:`ConnectionPool` based on the request context. + + ``request_context`` must at least contain the ``scheme`` key and its + value must be a key in ``key_fn_by_scheme`` instance variable. + """ + scheme = request_context['scheme'].lower() + pool_key_constructor = self.key_fn_by_scheme[scheme] + pool_key = pool_key_constructor(request_context) + + return self.connection_from_pool_key(pool_key, request_context=request_context) + + def connection_from_pool_key(self, pool_key, request_context=None): + """ + Get a :class:`ConnectionPool` based on the provided pool key. + + ``pool_key`` should be a namedtuple that only contains immutable + objects. At a minimum it must have the ``scheme``, ``host``, and + ``port`` fields. + """ + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + scheme = request_context['scheme'] + host = request_context['host'] + port = request_context['port'] + pool = self._new_pool(scheme, host, port, request_context=request_context) + self.pools[pool_key] = pool + + return pool + + def connection_from_url(self, url, pool_kwargs=None): + """ + Similar to :func:`urllib3.connectionpool.connection_from_url`. + + If ``pool_kwargs`` is not provided and a new pool needs to be + constructed, ``self.connection_pool_kw`` is used to initialize + the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` + is provided, it is used instead. Note that if a new pool does not + need to be created for the request, the provided ``pool_kwargs`` are + not used. + """ + u = parse_url(url) + return self.connection_from_host(u.host, port=u.port, scheme=u.scheme, + pool_kwargs=pool_kwargs) + + def _merge_pool_kwargs(self, override): + """ + Merge a dictionary of override values for self.connection_pool_kw. + + This does not modify self.connection_pool_kw and returns a new dict. + Any keys in the override dictionary with a value of ``None`` are + removed from the merged dictionary. + """ + base_pool_kwargs = self.connection_pool_kw.copy() + if override: + for key, value in override.items(): + if value is None: + try: + del base_pool_kwargs[key] + except KeyError: + pass + else: + base_pool_kwargs[key] = value + return base_pool_kwargs + + def urlopen(self, method, url, redirect=True, **kw): + """ + Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw['assert_same_host'] = False + kw['redirect'] = False + + if 'headers' not in kw: + kw['headers'] = self.headers.copy() + + if self.proxy is not None and u.scheme == "http": + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + # RFC 7231, Section 6.4.4 + if response.status == 303: + method = 'GET' + + retries = kw.get('retries') + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # conn.is_same_host() which may use socket.gethostbyname() in the future. + if (retries.remove_headers_on_redirect + and not conn.is_same_host(redirect_location)): + for header in retries.remove_headers_on_redirect: + kw['headers'].pop(header, None) + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + raise + return response + + kw['retries'] = retries + kw['redirect'] = redirect + + log.info("Redirecting %s -> %s", url, redirect_location) + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary containing headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + Example: + >>> proxy = urllib3.ProxyManager('http://localhost:3128/') + >>> r1 = proxy.request('GET', 'http://google.com/') + >>> r2 = proxy.request('GET', 'http://httpbin.org/') + >>> len(proxy.pools) + 1 + >>> r3 = proxy.request('GET', 'https://httpbin.org/') + >>> r4 = proxy.request('GET', 'https://twitter.com/') + >>> len(proxy.pools) + 3 + + """ + + def __init__(self, proxy_url, num_pools=10, headers=None, + proxy_headers=None, **connection_pool_kw): + + if isinstance(proxy_url, HTTPConnectionPool): + proxy_url = '%s://%s:%i' % (proxy_url.scheme, proxy_url.host, + proxy_url.port) + proxy = parse_url(proxy_url) + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + + connection_pool_kw['_proxy'] = self.proxy + connection_pool_kw['_proxy_headers'] = self.proxy_headers + + super(ProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw) + + def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): + if scheme == "https": + return super(ProxyManager, self).connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs) + + return super(ProxyManager, self).connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs) + + def _set_proxy_headers(self, url, headers=None): + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {'Accept': '*/*'} + + netloc = parse_url(url).netloc + if netloc: + headers_['Host'] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen(self, method, url, redirect=True, **kw): + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + + if u.scheme == "http": + # For proxied HTTPS requests, httplib sets the necessary headers + # on the CONNECT to the proxy. For HTTP, we'll definitely + # need to set 'Host' at the very least. + headers = kw.get('headers', self.headers) + kw['headers'] = self._set_proxy_headers(url, headers) + + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url, **kw): + return ProxyManager(proxy_url=url, **kw) diff --git a/lib/urllib3/request.py b/lib/urllib3/request.py new file mode 100644 index 0000000..8f2f44b --- /dev/null +++ b/lib/urllib3/request.py @@ -0,0 +1,150 @@ +from __future__ import absolute_import + +from .filepost import encode_multipart_formdata +from .packages.six.moves.urllib.parse import urlencode + + +__all__ = ['RequestMethods'] + + +class RequestMethods(object): + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`~urllib3.connectionpool.HTTPConnectionPool` and + :class:`~urllib3.poolmanager.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = {'DELETE', 'GET', 'HEAD', 'OPTIONS'} + + def __init__(self, headers=None): + self.headers = headers or {} + + def urlopen(self, method, url, body=None, headers=None, + encode_multipart=True, multipart_boundary=None, + **kw): # Abstract + raise NotImplementedError("Classes extending RequestMethods must implement " + "their own ``urlopen`` method.") + + def request(self, method, url, fields=None, headers=None, **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + """ + method = method.upper() + + urlopen_kw['request_url'] = url + + if method in self._encode_url_methods: + return self.request_encode_url(method, url, fields=fields, + headers=headers, + **urlopen_kw) + else: + return self.request_encode_body(method, url, fields=fields, + headers=headers, + **urlopen_kw) + + def request_encode_url(self, method, url, fields=None, headers=None, + **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + """ + if headers is None: + headers = self.headers + + extra_kw = {'headers': headers} + extra_kw.update(urlopen_kw) + + if fields: + url += '?' + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body(self, method, url, fields=None, headers=None, + encode_multipart=True, multipart_boundary=None, + **urlopen_kw): + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :meth:`urllib.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimic behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + """ + if headers is None: + headers = self.headers + + extra_kw = {'headers': {}} + + if fields: + if 'body' in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one.") + + if encode_multipart: + body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) + else: + body, content_type = urlencode(fields), 'application/x-www-form-urlencoded' + + extra_kw['body'] = body + extra_kw['headers'] = {'Content-Type': content_type} + + extra_kw['headers'].update(headers) + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/lib/urllib3/response.py b/lib/urllib3/response.py new file mode 100644 index 0000000..c112690 --- /dev/null +++ b/lib/urllib3/response.py @@ -0,0 +1,705 @@ +from __future__ import absolute_import +from contextlib import contextmanager +import zlib +import io +import logging +from socket import timeout as SocketTimeout +from socket import error as SocketError + +from ._collections import HTTPHeaderDict +from .exceptions import ( + BodyNotHttplibCompatible, ProtocolError, DecodeError, ReadTimeoutError, + ResponseNotChunked, IncompleteRead, InvalidHeader +) +from .packages.six import string_types as basestring, PY3 +from .packages.six.moves import http_client as httplib +from .connection import HTTPException, BaseSSLError +from .util.response import is_fp_closed, is_response_to_head + +log = logging.getLogger(__name__) + + +class DeflateDecoder(object): + + def __init__(self): + self._first_try = True + self._data = b'' + self._obj = zlib.decompressobj() + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: + return data + + if not self._first_try: + return self._obj.decompress(data) + + self._data += data + try: + decompressed = self._obj.decompress(data) + if decompressed: + self._first_try = False + self._data = None + return decompressed + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress(self._data) + finally: + self._data = None + + +class GzipDecoderState(object): + + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(object): + + def __init__(self): + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA or not data: + return bytes(ret) + while True: + try: + ret += self._obj.decompress(data) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + data = self._obj.unused_data + if not data: + return bytes(ret) + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + +class MultiDecoder(object): + """ + From RFC7231: + If one or more encodings have been applied to a representation, the + sender that applied the encodings MUST generate a Content-Encoding + header field that lists the content codings in the order in which + they were applied. + """ + + def __init__(self, modes): + self._decoders = [_get_decoder(m.strip()) for m in modes.split(',')] + + def flush(self): + return self._decoders[0].flush() + + def decompress(self, data): + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + +def _get_decoder(mode): + if ',' in mode: + return MultiDecoder(mode) + + if mode == 'gzip': + return GzipDecoder() + + return DeflateDecoder() + + +class HTTPResponse(io.IOBase): + """ + HTTP Response container. + + Backwards-compatible to httplib's HTTPResponse but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in httplib.HTTPResponse: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param original_response: + When this HTTPResponse wrapper is generated from an httplib.HTTPResponse + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + + :param retries: + The retries contains the last :class:`~urllib3.util.retry.Retry` that + was used during the request. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + + CONTENT_DECODERS = ['gzip', 'deflate'] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + def __init__(self, body='', headers=None, status=0, version=0, reason=None, + strict=0, preload_content=True, decode_content=True, + original_response=None, pool=None, connection=None, msg=None, + retries=None, enforce_content_length=False, + request_method=None, request_url=None): + + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) + self.status = status + self.version = version + self.reason = reason + self.strict = strict + self.decode_content = decode_content + self.retries = retries + self.enforce_content_length = enforce_content_length + + self._decoder = None + self._body = None + self._fp = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + self._request_url = request_url + + if body and isinstance(body, (basestring, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, 'read'): + self._fp = body + + # Are we using the chunked-style of transfer encoding? + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get('transfer-encoding', '').lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def get_redirect_location(self): + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get('location') + + return False + + def release_conn(self): + if not self._pool or not self._connection: + return + + self._pool._put_conn(self._connection) + self._connection = None + + @property + def data(self): + # For backwords-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body + + if self._fp: + return self.read(cache_content=True) + + @property + def connection(self): + return self._connection + + def isclosed(self): + return is_fp_closed(self._fp) + + def tell(self): + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:``HTTPResponse.read`` if bytes + are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method): + """ + Set initial length value for Response content if available. + """ + length = self.headers.get('content-length') + + if length is not None: + if self.chunked: + # This Response will fail with an IncompleteRead if it can't be + # received as chunked. This method falls back to attempt reading + # the response before raising an exception. + log.warning("Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked.") + return None + + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = set([int(val) for val in length.split(',')]) + if len(lengths) > 1: + raise InvalidHeader("Content-Length contained multiple " + "unmatching values (%s)" % length) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + # Convert status to int for comparison + # In some cases, httplib returns a status of "_UNKNOWN" + try: + status = int(self.status) + except ValueError: + status = 0 + + # Check for responses that shouldn't include a body + if status in (204, 304) or 100 <= status < 200 or request_method == 'HEAD': + length = 0 + + return length + + def _init_decoder(self): + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get('content-encoding', '').lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif ',' in content_encoding: + encodings = [e.strip() for e in content_encoding.split(',') if e.strip() in self.CONTENT_DECODERS] + if len(encodings): + self._decoder = _get_decoder(content_encoding) + + def _decode(self, data, decode_content, flush_decoder): + """ + Decode the data passed in and potentially flush the decoder. + """ + try: + if decode_content and self._decoder: + data = self._decoder.decompress(data) + except (IOError, zlib.error) as e: + content_encoding = self.headers.get('content-encoding', '').lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, e) + + if flush_decoder and decode_content: + data += self._flush_decoder() + + return data + + def _flush_decoder(self): + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + buf = self._decoder.decompress(b'') + return buf + self._decoder.flush() + + return b'' + + @contextmanager + def _error_catcher(self): + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + clean_exit = False + + try: + try: + yield + + except SocketTimeout: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, 'Read timed out.') + + except BaseSSLError as e: + # FIXME: Is there a better way to differentiate between SSLErrors? + if 'read operation timed out' not in str(e): # Defensive: + # This shouldn't happen but just in case we're missing an edge + # case, let's avoid swallowing SSL errors. + raise + + raise ReadTimeoutError(self._pool, None, 'Read timed out.') + + except (HTTPException, SocketError) as e: + # This includes IncompleteRead. + raise ProtocolError('Connection broken: %r' % e, e) + + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now to ensure that the connection is + # released back to the pool. + if self._original_response: + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection: + self._connection.close() + + # If we hold the original response but it's closed now, we should + # return the connection back to the pool. + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def read(self, amt=None, decode_content=None, cache_content=False): + """ + Similar to :meth:`httplib.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if self._fp is None: + return + + flush_decoder = False + data = None + + with self._error_catcher(): + if amt is None: + # cStringIO doesn't like amt=None + data = self._fp.read() + flush_decoder = True + else: + cache_content = False + data = self._fp.read(amt) + if amt != 0 and not data: # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + flush_decoder = True + if self.enforce_content_length and self.length_remaining not in (0, None): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + + data = self._decode(data, decode_content, flush_decoder) + + if cache_content: + self._body = data + + return data + + def stream(self, amt=2**16, decode_content=None): + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if self.chunked and self.supports_chunked_reads(): + for line in self.read_chunked(amt, decode_content=decode_content): + yield line + else: + while not is_fp_closed(self._fp): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + @classmethod + def from_httplib(ResponseCls, r, **response_kw): + """ + Given an :class:`httplib.HTTPResponse` instance ``r``, return a + corresponding :class:`urllib3.response.HTTPResponse` object. + + Remaining parameters are passed to the HTTPResponse constructor, along + with ``original_response=r``. + """ + headers = r.msg + + if not isinstance(headers, HTTPHeaderDict): + if PY3: # Python 3 + headers = HTTPHeaderDict(headers.items()) + else: # Python 2 + headers = HTTPHeaderDict.from_httplib(headers) + + # HTTPResponse objects in Python 3 don't have a .strict attribute + strict = getattr(r, 'strict', 0) + resp = ResponseCls(body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw) + return resp + + # Backwards-compatibility methods for httplib.HTTPResponse + def getheaders(self): + return self.headers + + def getheader(self, name, default=None): + return self.headers.get(name, default) + + # Backwards compatibility for http.cookiejar + def info(self): + return self.headers + + # Overrides from io.IOBase + def close(self): + if not self.closed: + self._fp.close() + + if self._connection: + self._connection.close() + + @property + def closed(self): + if self._fp is None: + return True + elif hasattr(self._fp, 'isclosed'): + return self._fp.isclosed() + elif hasattr(self._fp, 'closed'): + return self._fp.closed + else: + return True + + def fileno(self): + if self._fp is None: + raise IOError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise IOError("The file-like object this HTTPResponse is wrapped " + "around has no file descriptor") + + def flush(self): + if self._fp is not None and hasattr(self._fp, 'flush'): + return self._fp.flush() + + def readable(self): + # This method is required for `io` module compatibility. + return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[:len(temp)] = temp + return len(temp) + + def supports_chunked_reads(self): + """ + Checks if the underlying file-like object looks like a + httplib.HTTPResponse object. We do this by testing for the fp + attribute. If it is present we assume it returns raw chunks as + processed by read_chunked(). + """ + return hasattr(self._fp, 'fp') + + def _update_chunk_length(self): + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return + line = self._fp.fp.readline() + line = line.split(b';', 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + # Invalid chunked protocol response, abort. + self.close() + raise httplib.IncompleteRead(line) + + def _handle_chunk(self, amt): + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) + returned_chunk = chunk + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif amt < self.chunk_left: + value = self._fp._safe_read(amt) + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk + + def read_chunked(self, amt=None, decode_content=None): + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing.") + if not self.supports_chunked_reads(): + raise BodyNotHttplibCompatible( + "Body should be httplib.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks.") + + with self._error_catcher(): + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: + return + + while True: + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode(chunk, decode_content=decode_content, + flush_decoder=False) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while True: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b'\r\n': + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() + + def geturl(self): + """ + Returns the URL that was the source of this response. + If the request that generated this response redirected, this method + will return the final redirect location. + """ + if self.retries is not None and len(self.retries.history): + return self.retries.history[-1].redirect_location + else: + return self._request_url diff --git a/lib/urllib3/util/__init__.py b/lib/urllib3/util/__init__.py new file mode 100644 index 0000000..2f2770b --- /dev/null +++ b/lib/urllib3/util/__init__.py @@ -0,0 +1,54 @@ +from __future__ import absolute_import +# For backwards compatibility, provide imports that used to be here. +from .connection import is_connection_dropped +from .request import make_headers +from .response import is_fp_closed +from .ssl_ import ( + SSLContext, + HAS_SNI, + IS_PYOPENSSL, + IS_SECURETRANSPORT, + assert_fingerprint, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import ( + current_time, + Timeout, +) + +from .retry import Retry +from .url import ( + get_host, + parse_url, + split_first, + Url, +) +from .wait import ( + wait_for_read, + wait_for_write +) + +__all__ = ( + 'HAS_SNI', + 'IS_PYOPENSSL', + 'IS_SECURETRANSPORT', + 'SSLContext', + 'Retry', + 'Timeout', + 'Url', + 'assert_fingerprint', + 'current_time', + 'is_connection_dropped', + 'is_fp_closed', + 'get_host', + 'parse_url', + 'make_headers', + 'resolve_cert_reqs', + 'resolve_ssl_version', + 'split_first', + 'ssl_wrap_socket', + 'wait_for_read', + 'wait_for_write' +) diff --git a/lib/urllib3/util/connection.py b/lib/urllib3/util/connection.py new file mode 100644 index 0000000..5ad70b2 --- /dev/null +++ b/lib/urllib3/util/connection.py @@ -0,0 +1,134 @@ +from __future__ import absolute_import +import socket +from .wait import NoWayToWaitForSocketError, wait_for_read +from ..contrib import _appengine_environ + + +def is_connection_dropped(conn): # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + + :param conn: + :class:`httplib.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. + """ + sock = getattr(conn, 'sock', False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return True + try: + # Returns True if readable, which here means it's been dropped + return wait_for_read(sock, timeout=0.0) + except NoWayToWaitForSocketError: # Platform-specific: AppEngine + return False + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +# One additional modification is that we avoid binding to IPv6 servers +# discovered in DNS if the system doesn't have IPv6 functionality. +def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, socket_options=None): + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith('['): + host = host.strip('[]') + err = None + + # Using the value from allowed_gai_family() in the context of getaddrinfo lets + # us select whether to work with IPv4 DNS records, IPv6 records, or both. + # The original create_connection function always returns all records. + family = allowed_gai_family() + + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + _set_socket_options(sock, socket_options) + + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except socket.error as e: + err = e + if sock is not None: + sock.close() + sock = None + + if err is not None: + raise err + + raise socket.error("getaddrinfo returns an empty list") + + +def _set_socket_options(sock, options): + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family(): + """This function is designed to work in the context of + getaddrinfo, where family=socket.AF_UNSPEC is the default and + will perform a DNS search for both IPv6 and IPv4 records.""" + + family = socket.AF_INET + if HAS_IPV6: + family = socket.AF_UNSPEC + return family + + +def _has_ipv6(host): + """ Returns True if the system can bind an IPv6 address. """ + sock = None + has_ipv6 = False + + # App Engine doesn't support IPV6 sockets and actually has a quota on the + # number of sockets that can be used, so just early out here instead of + # creating a socket needlessly. + # See https://github.com/urllib3/urllib3/issues/1446 + if _appengine_environ.is_appengine_sandbox(): + return False + + if socket.has_ipv6: + # has_ipv6 returns true if cPython was compiled with IPv6 support. + # It does not tell us if the system has IPv6 support enabled. To + # determine that we must bind to an IPv6 address. + # https://github.com/shazow/urllib3/pull/611 + # https://bugs.python.org/issue658327 + try: + sock = socket.socket(socket.AF_INET6) + sock.bind((host, 0)) + has_ipv6 = True + except Exception: + pass + + if sock: + sock.close() + return has_ipv6 + + +HAS_IPV6 = _has_ipv6('::1') diff --git a/lib/urllib3/util/queue.py b/lib/urllib3/util/queue.py new file mode 100644 index 0000000..d3d379a --- /dev/null +++ b/lib/urllib3/util/queue.py @@ -0,0 +1,21 @@ +import collections +from ..packages import six +from ..packages.six.moves import queue + +if six.PY2: + # Queue is imported for side effects on MS Windows. See issue #229. + import Queue as _unused_module_Queue # noqa: F401 + + +class LifoQueue(queue.Queue): + def _init(self, _): + self.queue = collections.deque() + + def _qsize(self, len=len): + return len(self.queue) + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() diff --git a/lib/urllib3/util/request.py b/lib/urllib3/util/request.py new file mode 100644 index 0000000..3ddfcd5 --- /dev/null +++ b/lib/urllib3/util/request.py @@ -0,0 +1,118 @@ +from __future__ import absolute_import +from base64 import b64encode + +from ..packages.six import b, integer_types +from ..exceptions import UnrewindableBodyError + +ACCEPT_ENCODING = 'gzip,deflate' +_FAILEDTELL = object() + + +def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, + basic_auth=None, proxy_basic_auth=None, disable_cache=None): + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example:: + + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} + """ + headers = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ','.join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers['accept-encoding'] = accept_encoding + + if user_agent: + headers['user-agent'] = user_agent + + if keep_alive: + headers['connection'] = 'keep-alive' + + if basic_auth: + headers['authorization'] = 'Basic ' + \ + b64encode(b(basic_auth)).decode('utf-8') + + if proxy_basic_auth: + headers['proxy-authorization'] = 'Basic ' + \ + b64encode(b(proxy_basic_auth)).decode('utf-8') + + if disable_cache: + headers['cache-control'] = 'no-cache' + + return headers + + +def set_file_position(body, pos): + """ + If a position is provided, move file to that point. + Otherwise, we'll attempt to record a position for future use. + """ + if pos is not None: + rewind_body(body, pos) + elif getattr(body, 'tell', None) is not None: + try: + pos = body.tell() + except (IOError, OSError): + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body. + pos = _FAILEDTELL + + return pos + + +def rewind_body(body, body_pos): + """ + Attempt to rewind body to a certain position. + Primarily used for request redirects and retries. + + :param body: + File-like object that supports seek. + + :param int pos: + Position to seek to in file. + """ + body_seek = getattr(body, 'seek', None) + if body_seek is not None and isinstance(body_pos, integer_types): + try: + body_seek(body_pos) + except (IOError, OSError): + raise UnrewindableBodyError("An error occurred when rewinding request " + "body for redirect/retry.") + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError("Unable to record file position for rewinding " + "request body during a redirect/retry.") + else: + raise ValueError("body_pos must be of type integer, " + "instead it was %s." % type(body_pos)) diff --git a/lib/urllib3/util/response.py b/lib/urllib3/util/response.py new file mode 100644 index 0000000..3d54864 --- /dev/null +++ b/lib/urllib3/util/response.py @@ -0,0 +1,87 @@ +from __future__ import absolute_import +from ..packages.six.moves import http_client as httplib + +from ..exceptions import HeaderParsingError + + +def is_fp_closed(obj): + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check `isclosed()` first, in case Python3 doesn't set `closed`. + # GH Issue #928 + return obj.isclosed() + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers): + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param headers: Headers to verify. + :type headers: `httplib.HTTPMessage`. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError('expected httplib.Message, got {0}.'.format( + type(headers))) + + defects = getattr(headers, 'defects', None) + get_payload = getattr(headers, 'get_payload', None) + + unparsed_data = None + if get_payload: + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response): + """ + Checks whether the request of a response has been a HEAD-request. + Handles the quirks of AppEngine. + + :param conn: + :type conn: :class:`httplib.HTTPResponse` + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method = response._method + if isinstance(method, int): # Platform-specific: Appengine + return method == 3 + return method.upper() == 'HEAD' diff --git a/lib/urllib3/util/retry.py b/lib/urllib3/util/retry.py new file mode 100644 index 0000000..e7d0abd --- /dev/null +++ b/lib/urllib3/util/retry.py @@ -0,0 +1,411 @@ +from __future__ import absolute_import +import time +import logging +from collections import namedtuple +from itertools import takewhile +import email +import re + +from ..exceptions import ( + ConnectTimeoutError, + MaxRetryError, + ProtocolError, + ReadTimeoutError, + ResponseError, + InvalidHeader, +) +from ..packages import six + + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +RequestHistory = namedtuple('RequestHistory', ["method", "url", "error", + "status", "redirect_location"]) + + +class Retry(object): + """ Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool:: + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', retries=Retry(10)) + + Retries can be disabled by passing ``False``:: + + response = http.request('GET', 'http://example.com/', retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. It's a good idea to set this to some sensibly-high value to + account for unexpected edge cases and avoid infinite retry loops. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int status: + How many times to retry on bad status codes. + + These are retries made on responses, where status code matches + ``status_forcelist``. + + Set to ``0`` to fail on the first retry of this type. + + :param iterable method_whitelist: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + idempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. + + Set to a ``False`` value to retry on any verb. + + :param iterable status_forcelist: + A set of integer HTTP status codes that we should force a retry on. + A retry is initiated if the request method is in ``method_whitelist`` + and the response status code is in ``status_forcelist``. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts after the second try + (most errors are resolved immediately by a second try without a + delay). urllib3 will sleep for:: + + {backoff factor} * (2 ** ({number of total retries} - 1)) + + seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep + for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer + than :attr:`Retry.BACKOFF_MAX`. + + By default, backoff is disabled (set to 0). + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have + been exhausted. + + :param tuple history: The history of the request encountered during + each call to :meth:`~Retry.increment`. The list is in the order + the requests occurred. Each list item is of class :class:`RequestHistory`. + + :param bool respect_retry_after_header: + Whether to respect Retry-After header on status codes defined as + :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. + + :param iterable remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request. + """ + + DEFAULT_METHOD_WHITELIST = frozenset([ + 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) + + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(['Authorization']) + + #: Maximum backoff time. + BACKOFF_MAX = 120 + + def __init__(self, total=10, connect=None, read=None, redirect=None, status=None, + method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, + backoff_factor=0, raise_on_redirect=True, raise_on_status=True, + history=None, respect_retry_after_header=True, + remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST): + + self.total = total + self.connect = connect + self.read = read + self.status = status + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.method_whitelist = method_whitelist + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or tuple() + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = remove_headers_on_redirect + + def new(self, **kw): + params = dict( + total=self.total, + connect=self.connect, read=self.read, redirect=self.redirect, status=self.status, + method_whitelist=self.method_whitelist, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + history=self.history, + remove_headers_on_redirect=self.remove_headers_on_redirect + ) + params.update(kw) + return type(self)(**params) + + @classmethod + def from_int(cls, retries, redirect=True, default=None): + """ Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r", retries, new_retries) + return new_retries + + def get_backoff_time(self): + """ Formula for computing the current backoff + + :rtype: float + """ + # We want to consider only the last consecutive errors sequence (Ignore redirects). + consecutive_errors_len = len(list(takewhile(lambda x: x.redirect_location is None, + reversed(self.history)))) + if consecutive_errors_len <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) + return min(self.BACKOFF_MAX, backoff_value) + + def parse_retry_after(self, retry_after): + # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = int(retry_after) + else: + retry_date_tuple = email.utils.parsedate(retry_after) + if retry_date_tuple is None: + raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) + retry_date = time.mktime(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + def get_retry_after(self, response): + """ Get the value of Retry-After in seconds. """ + + retry_after = response.getheader("Retry-After") + + if retry_after is None: + return None + + return self.parse_retry_after(retry_after) + + def sleep_for_retry(self, response=None): + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self): + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response=None): + """ Sleep between retry attempts. + + This method will respect a server's ``Retry-After`` response header + and sleep the duration of the time requested. If that is not present, it + will use an exponential backoff. By default, the backoff factor is 0 and + this method will return immediately. + """ + + if response: + slept = self.sleep_for_retry(response) + if slept: + return + + self._sleep_backoff() + + def _is_connection_error(self, err): + """ Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err): + """ Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def _is_method_retryable(self, method): + """ Checks if a given HTTP method should be retried upon, depending if + it is included on the method whitelist. + """ + if self.method_whitelist and method.upper() not in self.method_whitelist: + return False + + return True + + def is_retry(self, method, status_code, has_retry_after=False): + """ Is this method/status code retryable? (Based on whitelists and control + variables such as the number of total retries to allow, whether to + respect the Retry-After header, whether this header is present, and + whether the returned status code is on the list of status codes to + be retried upon on the presence of the aforementioned header) + """ + if not self._is_method_retryable(method): + return False + + if self.status_forcelist and status_code in self.status_forcelist: + return True + + return (self.total and self.respect_retry_after_header and + has_retry_after and (status_code in self.RETRY_AFTER_STATUS_CODES)) + + def is_exhausted(self): + """ Are we out of retries? """ + retry_counts = (self.total, self.connect, self.read, self.redirect, self.status) + retry_counts = list(filter(None, retry_counts)) + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment(self, method=None, url=None, response=None, error=None, + _pool=None, _stacktrace=None): + """ Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.HTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise six.reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + connect = self.connect + read = self.read + redirect = self.redirect + status_count = self.status + cause = 'unknown' + status = None + redirect_location = None + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise six.reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False or not self._is_method_retryable(method): + raise six.reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = 'too many redirects' + redirect_location = response.get_redirect_location() + status = response.status + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and a the given method is in the whitelist + cause = ResponseError.GENERIC_ERROR + if response and response.status: + if status_count is not None: + status_count -= 1 + cause = ResponseError.SPECIFIC_ERROR.format( + status_code=response.status) + status = response.status + + history = self.history + (RequestHistory(method, url, error, status, redirect_location),) + + new_retry = self.new( + total=total, + connect=connect, read=read, redirect=redirect, status=status_count, + history=history) + + if new_retry.is_exhausted(): + raise MaxRetryError(_pool, url, error or ResponseError(cause)) + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self): + return ('{cls.__name__}(total={self.total}, connect={self.connect}, ' + 'read={self.read}, redirect={self.redirect}, status={self.status})').format( + cls=type(self), self=self) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/lib/urllib3/util/ssl_.py b/lib/urllib3/util/ssl_.py new file mode 100644 index 0000000..64ea192 --- /dev/null +++ b/lib/urllib3/util/ssl_.py @@ -0,0 +1,381 @@ +from __future__ import absolute_import +import errno +import warnings +import hmac +import socket + +from binascii import hexlify, unhexlify +from hashlib import md5, sha1, sha256 + +from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning +from ..packages import six + + +SSLContext = None +HAS_SNI = False +IS_PYOPENSSL = False +IS_SECURETRANSPORT = False + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = { + 32: md5, + 40: sha1, + 64: sha256, +} + + +def _const_compare_digest_backport(a, b): + """ + Compare two digests of equal length in constant time. + + The digests must be of type str/bytes. + Returns True if the digests match, and False otherwise. + """ + result = abs(len(a) - len(b)) + for l, r in zip(bytearray(a), bytearray(b)): + result |= l ^ r + return result == 0 + + +_const_compare_digest = getattr(hmac, 'compare_digest', + _const_compare_digest_backport) + + +try: # Test for SSL features + import ssl + from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass + + +try: + from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 + + +# Python 2.7 doesn't have inet_pton on non-Linux so we fallback on inet_aton in +# those cases. This means that we can only detect IPv4 addresses in this case. +if hasattr(socket, 'inet_pton'): + inet_pton = socket.inet_pton +else: + # Maybe we can use ipaddress if the user has urllib3[secure]? + try: + import ipaddress + + def inet_pton(_, host): + if isinstance(host, bytes): + host = host.decode('ascii') + return ipaddress.ip_address(host) + + except ImportError: # Platform-specific: Non-Linux + def inet_pton(_, host): + return socket.inet_aton(host) + + +# A secure default. +# Sources for more information on TLS ciphers: +# +# - https://wiki.mozilla.org/Security/Server_Side_TLS +# - https://www.ssllabs.com/projects/best-practices/index.html +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +# +# The general intent is: +# - Prefer TLS 1.3 cipher suites +# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), +# - prefer ECDHE over DHE for better performance, +# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and +# security, +# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, +# - disable NULL authentication, MD5 MACs and DSS for security reasons. +DEFAULT_CIPHERS = ':'.join([ + 'TLS13-AES-256-GCM-SHA384', + 'TLS13-CHACHA20-POLY1305-SHA256', + 'TLS13-AES-128-GCM-SHA256', + 'ECDH+AESGCM', + 'ECDH+CHACHA20', + 'DH+AESGCM', + 'DH+CHACHA20', + 'ECDH+AES256', + 'DH+AES256', + 'ECDH+AES128', + 'DH+AES', + 'RSA+AESGCM', + 'RSA+AES', + '!aNULL', + '!eNULL', + '!MD5', +]) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + import sys + + class SSLContext(object): # Platform-specific: Python 2 + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, cafile=None, capath=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") + + def set_ciphers(self, cipher_suite): + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None, server_side=False): + warnings.warn( + 'A true SSLContext object is not available. This prevents ' + 'urllib3 from configuring SSL appropriately and may cause ' + 'certain SSL connections to fail. You can upgrade to a newer ' + 'version of Python to solve this. For more information, see ' + 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' + '#ssl-warnings', + InsecurePlatformWarning + ) + kwargs = { + 'keyfile': self.keyfile, + 'certfile': self.certfile, + 'ca_certs': self.ca_certs, + 'cert_reqs': self.verify_mode, + 'ssl_version': self.protocol, + 'server_side': server_side, + } + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + + +def assert_fingerprint(cert, fingerprint): + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + fingerprint = fingerprint.replace(':', '').lower() + digest_length = len(fingerprint) + hashfunc = HASHFUNC_MAP.get(digest_length) + if not hashfunc: + raise SSLError( + 'Fingerprint of invalid length: {0}'.format(fingerprint)) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not _const_compare_digest(cert_digest, fingerprint_bytes): + raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' + .format(fingerprint, hexlify(cert_digest))) + + +def resolve_cert_reqs(candidate): + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_NONE`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbreviation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_NONE + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, 'CERT_' + candidate) + return res + + return candidate + + +def resolve_ssl_version(candidate): + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_SSLv23 + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, 'PROTOCOL_' + candidate) + return res + + return candidate + + +def create_urllib3_context(ssl_version=None, cert_reqs=None, + options=None, ciphers=None): + """All arguments have the same meaning as ``ssl_wrap_socket``. + + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: + + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers + + If you wish to enable SSLv3, you can do:: + + from urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. + :param ciphers: + Which cipher suites to allow the server to select. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) + + context.set_ciphers(ciphers or DEFAULT_CIPHERS) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + + context.options |= options + + context.verify_mode = cert_reqs + if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + return context + + +def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, + ca_certs=None, server_hostname=None, + ssl_version=None, ciphers=None, ssl_context=None, + ca_cert_dir=None): + """ + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + """ + context = ssl_context + if context is None: + # Note: This branch of code and all the variables in it are no longer + # used by urllib3 itself. We should consider deprecating and removing + # this code. + context = create_urllib3_context(ssl_version, cert_reqs, + ciphers=ciphers) + + if ca_certs or ca_cert_dir: + try: + context.load_verify_locations(ca_certs, ca_cert_dir) + except IOError as e: # Platform-specific: Python 2.7 + raise SSLError(e) + # Py33 raises FileNotFoundError which subclasses OSError + # These are not equivalent unless we check the errno attribute + except OSError as e: # Platform-specific: Python 3.3 and beyond + if e.errno == errno.ENOENT: + raise SSLError(e) + raise + elif getattr(context, 'load_default_certs', None) is not None: + # try to load OS default certs; works well on Windows (require Python3.4+) + context.load_default_certs() + + if certfile: + context.load_cert_chain(certfile, keyfile) + + # If we detect server_hostname is an IP address then the SNI + # extension should not be used according to RFC3546 Section 3.1 + # We shouldn't warn the user if SNI isn't available but we would + # not be using SNI anyways due to IP address for server_hostname. + if ((server_hostname is not None and not is_ipaddress(server_hostname)) + or IS_SECURETRANSPORT): + if HAS_SNI and server_hostname is not None: + return context.wrap_socket(sock, server_hostname=server_hostname) + + warnings.warn( + 'An HTTPS request has been made, but the SNI (Server Name ' + 'Indication) extension to TLS is not available on this platform. ' + 'This may cause the server to present an incorrect TLS ' + 'certificate, which can cause validation failures. You can upgrade to ' + 'a newer version of Python to solve this. For more information, see ' + 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' + '#ssl-warnings', + SNIMissingWarning + ) + + return context.wrap_socket(sock) + + +def is_ipaddress(hostname): + """Detects whether the hostname given is an IP address. + + :param str hostname: Hostname to examine. + :return: True if the hostname is an IP address, False otherwise. + """ + if six.PY3 and isinstance(hostname, bytes): + # IDN A-label bytes are ASCII compatible. + hostname = hostname.decode('ascii') + + families = [socket.AF_INET] + if hasattr(socket, 'AF_INET6'): + families.append(socket.AF_INET6) + + for af in families: + try: + inet_pton(af, hostname) + except (socket.error, ValueError, OSError): + pass + else: + return True + return False diff --git a/lib/urllib3/util/timeout.py b/lib/urllib3/util/timeout.py new file mode 100644 index 0000000..cec817e --- /dev/null +++ b/lib/urllib3/util/timeout.py @@ -0,0 +1,242 @@ +from __future__ import absolute_import +# The default socket timeout, used by httplib to indicate that no timeout was +# specified by the user +from socket import _GLOBAL_DEFAULT_TIMEOUT +import time + +from ..exceptions import TimeoutStateError + +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() + + +# Use time.monotonic if available. +current_time = getattr(time, "monotonic", time.time) + + +class Timeout(object): + """ Timeout configuration. + + Timeouts can be defined as a default for a pool:: + + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``:: + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: integer, float, or None + + :param connect: + The maximum amount of time to wait for a connection attempt to a server + to succeed. Omitting the parameter will default the connect timeout to + the system default, probably `the global default timeout in socket.py + <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. + None will set an infinite timeout for connection attempts. + + :type connect: integer, float, or None + + :param read: + The maximum amount of time to wait between consecutive + read operations for a response from the server. Omitting + the parameter will default the read timeout to the system + default, probably `the global default timeout in socket.py + <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. + None will set an infinite timeout. + + :type read: integer, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT + + def __init__(self, total=None, connect=_Default, read=_Default): + self._connect = self._validate_timeout(connect, 'connect') + self._read = self._validate_timeout(read, 'read') + self.total = self._validate_timeout(total, 'total') + self._start_connect = None + + def __str__(self): + return '%s(connect=%r, read=%r, total=%r)' % ( + type(self).__name__, self._connect, self._read, self.total) + + @classmethod + def _validate_timeout(cls, value, name): + """ Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If it is a numeric value less than or equal to + zero, or the type is not an integer, float, or None. + """ + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: + return value + + if isinstance(value, bool): + raise ValueError("Timeout cannot be a boolean value. It must " + "be an int, float or None.") + try: + float(value) + except (TypeError, ValueError): + raise ValueError("Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value)) + + try: + if value <= 0: + raise ValueError("Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value)) + except TypeError: # Python 3 + raise ValueError("Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value)) + + return value + + @classmethod + def from_float(cls, timeout): + """ Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, sentinel default object, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self): + """ Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, + total=self.total) + + def start_connect(self): + """ Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = current_time() + return self._start_connect + + def get_connect_duration(self): + """ Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError("Can't get connect duration for timer " + "that has not started.") + return current_time() - self._start_connect + + @property + def connect_timeout(self): + """ Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) + + @property + def read_timeout(self): + """ Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if (self.total is not None and + self.total is not self.DEFAULT_TIMEOUT and + self._read is not None and + self._read is not self.DEFAULT_TIMEOUT): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), + self._read)) + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self._read diff --git a/lib/urllib3/util/url.py b/lib/urllib3/util/url.py new file mode 100644 index 0000000..6b6f996 --- /dev/null +++ b/lib/urllib3/util/url.py @@ -0,0 +1,230 @@ +from __future__ import absolute_import +from collections import namedtuple + +from ..exceptions import LocationParseError + + +url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] + +# We only want to normalize urls with an HTTP(S) scheme. +# urllib3 infers URLs without a scheme (None) to be http. +NORMALIZABLE_SCHEMES = ('http', 'https', None) + + +class Url(namedtuple('Url', url_attrs)): + """ + Datastructure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. Both the scheme and host are normalized as they are + both case-insensitive according to RFC 3986. + """ + __slots__ = () + + def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, + query=None, fragment=None): + if path and not path.startswith('/'): + path = '/' + path + if scheme: + scheme = scheme.lower() + if host and scheme in NORMALIZABLE_SCHEMES: + host = host.lower() + return super(Url, cls).__new__(cls, scheme, auth, host, port, path, + query, fragment) + + @property + def hostname(self): + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self): + """Absolute path including the query string.""" + uri = self.path or '/' + + if self.query is not None: + uri += '?' + self.query + + return uri + + @property + def netloc(self): + """Network location including host and port""" + if self.port: + return '%s:%d' % (self.host, self.port) + return self.host + + @property + def url(self): + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: :: + + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' + """ + scheme, auth, host, port, path, query, fragment = self + url = '' + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + '://' + if auth is not None: + url += auth + '@' + if host is not None: + url += host + if port is not None: + url += ':' + str(port) + if path is not None: + url += path + if query is not None: + url += '?' + query + if fragment is not None: + url += '#' + fragment + + return url + + def __str__(self): + return self.url + + +def split_first(s, delims): + """ + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example:: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d + + if min_idx is None or min_idx < 0: + return s, '', None + + return s[:min_idx], s[min_idx + 1:], min_delim + + +def parse_url(url): + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + + Partly backwards-compatible with :mod:`urlparse`. + + Example:: + + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + + # While this code has overlap with stdlib's urlparse, it is much + # simplified for our needs and less annoying. + # Additionally, this implementations does silly things to be optimal + # on CPython. + + if not url: + # Empty + return Url() + + scheme = None + auth = None + host = None + port = None + path = None + fragment = None + query = None + + # Scheme + if '://' in url: + scheme, url = url.split('://', 1) + + # Find the earliest Authority Terminator + # (http://tools.ietf.org/html/rfc3986#section-3.2) + url, path_, delim = split_first(url, ['/', '?', '#']) + + if delim: + # Reassemble the path + path = delim + path_ + + # Auth + if '@' in url: + # Last '@' denotes end of auth part + auth, url = url.rsplit('@', 1) + + # IPv6 + if url and url[0] == '[': + host, url = url.split(']', 1) + host += ']' + + # Port + if ':' in url: + _host, port = url.split(':', 1) + + if not host: + host = _host + + if port: + # If given, ports must be integers. No whitespace, no plus or + # minus prefixes, no non-integer digits such as ^2 (superscript). + if not port.isdigit(): + raise LocationParseError(url) + try: + port = int(port) + except ValueError: + raise LocationParseError(url) + else: + # Blank ports are cool, too. (rfc3986#section-3.2.3) + port = None + + elif not host and url: + host = url + + if not path: + return Url(scheme, auth, host, port, path, query, fragment) + + # Fragment + if '#' in path: + path, fragment = path.split('#', 1) + + # Query + if '?' in path: + path, query = path.split('?', 1) + + return Url(scheme, auth, host, port, path, query, fragment) + + +def get_host(url): + """ + Deprecated. Use :func:`parse_url` instead. + """ + p = parse_url(url) + return p.scheme or 'http', p.hostname, p.port diff --git a/lib/urllib3/util/wait.py b/lib/urllib3/util/wait.py new file mode 100644 index 0000000..4db71ba --- /dev/null +++ b/lib/urllib3/util/wait.py @@ -0,0 +1,150 @@ +import errno +from functools import partial +import select +import sys +try: + from time import monotonic +except ImportError: + from time import time as monotonic + +__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] + + +class NoWayToWaitForSocketError(Exception): + pass + + +# How should we wait on sockets? +# +# There are two types of APIs you can use for waiting on sockets: the fancy +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like +# select/poll. The stateful APIs are more efficient when you have a lots of +# sockets to keep track of, because you can set them up once and then use them +# lots of times. But we only ever want to wait on a single socket at a time +# and don't want to keep track of state, so the stateless APIs are actually +# more efficient. So we want to use select() or poll(). +# +# Now, how do we choose between select() and poll()? On traditional Unixes, +# select() has a strange calling convention that makes it slow, or fail +# altogether, for high-numbered file descriptors. The point of poll() is to fix +# that, so on Unixes, we prefer poll(). +# +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper +# for it), but that's OK, because on Windows, select() doesn't have this +# strange calling convention; plain select() works fine. +# +# So: on Windows we use select(), and everywhere else we use poll(). We also +# fall back to select() in case poll() is somehow broken or missing. + +if sys.version_info >= (3, 5): + # Modern Python, that retries syscalls by default + def _retry_on_intr(fn, timeout): + return fn(timeout) +else: + # Old and broken Pythons. + def _retry_on_intr(fn, timeout): + if timeout is None: + deadline = float("inf") + else: + deadline = monotonic() + timeout + + while True: + try: + return fn(timeout) + # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 + except (OSError, select.error) as e: + # 'e.args[0]' incantation works for both OSError and select.error + if e.args[0] != errno.EINTR: + raise + else: + timeout = deadline - monotonic() + if timeout < 0: + timeout = 0 + if timeout == float("inf"): + timeout = None + continue + + +def select_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + rcheck = [] + wcheck = [] + if read: + rcheck.append(sock) + if write: + wcheck.append(sock) + # When doing a non-blocking connect, most systems signal success by + # marking the socket writable. Windows, though, signals success by marked + # it as "exceptional". We paper over the difference by checking the write + # sockets for both conditions. (The stdlib selectors module does the same + # thing.) + fn = partial(select.select, rcheck, wcheck, wcheck) + rready, wready, xready = _retry_on_intr(fn, timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket(sock, read=False, write=False, timeout=None): + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + mask = 0 + if read: + mask |= select.POLLIN + if write: + mask |= select.POLLOUT + poll_obj = select.poll() + poll_obj.register(sock, mask) + + # For some reason, poll() takes timeout in milliseconds + def do_poll(t): + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(_retry_on_intr(do_poll, timeout)) + + +def null_wait_for_socket(*args, **kwargs): + raise NoWayToWaitForSocketError("no select-equivalent available") + + +def _have_working_poll(): + # Apparently some systems have a select.poll that fails as soon as you try + # to use it, either due to strange configuration or broken monkeypatching + # from libraries like eventlet/greenlet. + try: + poll_obj = select.poll() + _retry_on_intr(poll_obj.poll, 0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket(*args, **kwargs): + # We delay choosing which implementation to use until the first time we're + # called. We could do it at import time, but then we might make the wrong + # decision if someone goes wild with monkeypatching select.poll after + # we're imported. + global wait_for_socket + if _have_working_poll(): + wait_for_socket = poll_wait_for_socket + elif hasattr(select, "select"): + wait_for_socket = select_wait_for_socket + else: # Platform-specific: Appengine. + wait_for_socket = null_wait_for_socket + return wait_for_socket(*args, **kwargs) + + +def wait_for_read(sock, timeout=None): + """ Waits for reading to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, read=True, timeout=timeout) + + +def wait_for_write(sock, timeout=None): + """ Waits for writing to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/lib/zope.interface-4.6.0-py3.7-nspkg.pth b/lib/zope.interface-4.6.0-py3.7-nspkg.pth new file mode 100644 index 0000000..4fa827e --- /dev/null +++ b/lib/zope.interface-4.6.0-py3.7-nspkg.pth @@ -0,0 +1 @@ +import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('zope',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('zope', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('zope', [os.path.dirname(p)])));m = m or sys.modules.setdefault('zope', types.ModuleType('zope'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p) diff --git a/lib/zope/interface/__init__.py b/lib/zope/interface/__init__.py new file mode 100644 index 0000000..605b706 --- /dev/null +++ b/lib/zope/interface/__init__.py @@ -0,0 +1,90 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Interfaces + +This package implements the Python "scarecrow" proposal. + +The package exports two objects, `Interface` and `Attribute` directly. It also +exports several helper methods. Interface is used to create an interface with +a class statement, as in: + + class IMyInterface(Interface): + '''Interface documentation + ''' + + def meth(arg1, arg2): + '''Documentation for meth + ''' + + # Note that there is no self argument + +To find out what you can do with interfaces, see the interface +interface, `IInterface` in the `interfaces` module. + +The package has several public modules: + + o `declarations` provides utilities to declare interfaces on objects. It + also provides a wide range of helpful utilities that aid in managing + declared interfaces. Most of its public names are however imported here. + + o `document` has a utility for documenting an interface as structured text. + + o `exceptions` has the interface-defined exceptions + + o `interfaces` contains a list of all public interfaces for this package. + + o `verify` has utilities for verifying implementations of interfaces. + +See the module doc strings for more information. +""" +__docformat__ = 'restructuredtext' + +from zope.interface.interface import Interface +from zope.interface.interface import _wire + +# Need to actually get the interface elements to implement the right interfaces +_wire() +del _wire + +from zope.interface.declarations import Declaration +from zope.interface.declarations import alsoProvides +from zope.interface.declarations import classImplements +from zope.interface.declarations import classImplementsOnly +from zope.interface.declarations import classProvides +from zope.interface.declarations import directlyProvidedBy +from zope.interface.declarations import directlyProvides +from zope.interface.declarations import implementedBy +from zope.interface.declarations import implementer +from zope.interface.declarations import implementer_only +from zope.interface.declarations import implements +from zope.interface.declarations import implementsOnly +from zope.interface.declarations import moduleProvides +from zope.interface.declarations import named +from zope.interface.declarations import noLongerProvides +from zope.interface.declarations import providedBy +from zope.interface.declarations import provider +from zope.interface.exceptions import Invalid +from zope.interface.interface import Attribute +from zope.interface.interface import invariant +from zope.interface.interface import taggedValue + +# The following are to make spec pickles cleaner +from zope.interface.declarations import Provides + + +from zope.interface.interfaces import IInterfaceDeclaration + +moduleProvides(IInterfaceDeclaration) + +__all__ = ('Interface', 'Attribute') + tuple(IInterfaceDeclaration) diff --git a/lib/zope/interface/_compat.py b/lib/zope/interface/_compat.py new file mode 100644 index 0000000..fb61e13 --- /dev/null +++ b/lib/zope/interface/_compat.py @@ -0,0 +1,58 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Basic components support +""" +import sys +import types + +if sys.version_info[0] < 3: + + def _normalize_name(name): + if isinstance(name, basestring): + return unicode(name) + raise TypeError("name must be a regular or unicode string") + + CLASS_TYPES = (type, types.ClassType) + STRING_TYPES = (basestring,) + + _BUILTINS = '__builtin__' + + PYTHON3 = False + PYTHON2 = True + +else: + + def _normalize_name(name): + if isinstance(name, bytes): + name = str(name, 'ascii') + if isinstance(name, str): + return name + raise TypeError("name must be a string or ASCII-only bytes") + + CLASS_TYPES = (type,) + STRING_TYPES = (str,) + + _BUILTINS = 'builtins' + + PYTHON3 = True + PYTHON2 = False + +def _skip_under_py3k(test_method): + import unittest + return unittest.skipIf(sys.version_info[0] >= 3, "Only on Python 2")(test_method) + + +def _skip_under_py2(test_method): + import unittest + return unittest.skipIf(sys.version_info[0] < 3, "Only on Python 3")(test_method) diff --git a/lib/zope/interface/_flatten.py b/lib/zope/interface/_flatten.py new file mode 100644 index 0000000..a80c2de --- /dev/null +++ b/lib/zope/interface/_flatten.py @@ -0,0 +1,35 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Adapter-style interface registry + +See Adapter class. +""" +from zope.interface import Declaration + +def _flatten(implements, include_None=0): + + try: + r = implements.flattened() + except AttributeError: + if implements is None: + r=() + else: + r = Declaration(implements).flattened() + + if not include_None: + return r + + r = list(r) + r.append(None) + return r diff --git a/lib/zope/interface/_zope_interface_coptimizations.c b/lib/zope/interface/_zope_interface_coptimizations.c new file mode 100644 index 0000000..b1e955e --- /dev/null +++ b/lib/zope/interface/_zope_interface_coptimizations.c @@ -0,0 +1,1726 @@ +/*########################################################################### + # + # Copyright (c) 2003 Zope Foundation and Contributors. + # All Rights Reserved. + # + # This software is subject to the provisions of the Zope Public License, + # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. + # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED + # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS + # FOR A PARTICULAR PURPOSE. + # + ############################################################################*/ + +#include "Python.h" +#include "structmember.h" + +#define TYPE(O) ((PyTypeObject*)(O)) +#define OBJECT(O) ((PyObject*)(O)) +#define CLASSIC(O) ((PyClassObject*)(O)) +#ifndef PyVarObject_HEAD_INIT +#define PyVarObject_HEAD_INIT(a, b) PyObject_HEAD_INIT(a) b, +#endif +#ifndef Py_TYPE +#define Py_TYPE(o) ((o)->ob_type) +#endif + +#if PY_MAJOR_VERSION >= 3 +#define PY3K +#endif + +static PyObject *str__dict__, *str__implemented__, *strextends; +static PyObject *BuiltinImplementationSpecifications, *str__provides__; +static PyObject *str__class__, *str__providedBy__; +static PyObject *empty, *fallback, *str_implied, *str_cls, *str_implements; +static PyObject *str__conform__, *str_call_conform, *adapter_hooks; +static PyObject *str_uncached_lookup, *str_uncached_lookupAll; +static PyObject *str_uncached_subscriptions; +static PyObject *str_registry, *strro, *str_generation, *strchanged; + +static PyTypeObject *Implements; + +static int imported_declarations = 0; + +static int +import_declarations(void) +{ + PyObject *declarations, *i; + + declarations = PyImport_ImportModule("zope.interface.declarations"); + if (declarations == NULL) + return -1; + + BuiltinImplementationSpecifications = PyObject_GetAttrString( + declarations, "BuiltinImplementationSpecifications"); + if (BuiltinImplementationSpecifications == NULL) + return -1; + + empty = PyObject_GetAttrString(declarations, "_empty"); + if (empty == NULL) + return -1; + + fallback = PyObject_GetAttrString(declarations, "implementedByFallback"); + if (fallback == NULL) + return -1; + + + + i = PyObject_GetAttrString(declarations, "Implements"); + if (i == NULL) + return -1; + + if (! PyType_Check(i)) + { + PyErr_SetString(PyExc_TypeError, + "zope.interface.declarations.Implements is not a type"); + return -1; + } + + Implements = (PyTypeObject *)i; + + Py_DECREF(declarations); + + imported_declarations = 1; + return 0; +} + +static PyTypeObject SpecType; /* Forward */ + +static PyObject * +implementedByFallback(PyObject *cls) +{ + if (imported_declarations == 0 && import_declarations() < 0) + return NULL; + + return PyObject_CallFunctionObjArgs(fallback, cls, NULL); +} + +static PyObject * +implementedBy(PyObject *ignored, PyObject *cls) +{ + /* Fast retrieval of implements spec, if possible, to optimize + common case. Use fallback code if we get stuck. + */ + + PyObject *dict = NULL, *spec; + + if (PyType_Check(cls)) + { + dict = TYPE(cls)->tp_dict; + Py_XINCREF(dict); + } + + if (dict == NULL) + dict = PyObject_GetAttr(cls, str__dict__); + + if (dict == NULL) + { + /* Probably a security proxied class, use more expensive fallback code */ + PyErr_Clear(); + return implementedByFallback(cls); + } + + spec = PyObject_GetItem(dict, str__implemented__); + Py_DECREF(dict); + if (spec) + { + if (imported_declarations == 0 && import_declarations() < 0) + return NULL; + + if (PyObject_TypeCheck(spec, Implements)) + return spec; + + /* Old-style declaration, use more expensive fallback code */ + Py_DECREF(spec); + return implementedByFallback(cls); + } + + PyErr_Clear(); + + /* Maybe we have a builtin */ + if (imported_declarations == 0 && import_declarations() < 0) + return NULL; + + spec = PyDict_GetItem(BuiltinImplementationSpecifications, cls); + if (spec != NULL) + { + Py_INCREF(spec); + return spec; + } + + /* We're stuck, use fallback */ + return implementedByFallback(cls); +} + +static PyObject * +getObjectSpecification(PyObject *ignored, PyObject *ob) +{ + PyObject *cls, *result; + + result = PyObject_GetAttr(ob, str__provides__); + if (result != NULL && PyObject_TypeCheck(result, &SpecType)) + return result; + + PyErr_Clear(); + + /* We do a getattr here so as not to be defeated by proxies */ + cls = PyObject_GetAttr(ob, str__class__); + if (cls == NULL) + { + PyErr_Clear(); + if (imported_declarations == 0 && import_declarations() < 0) + return NULL; + Py_INCREF(empty); + return empty; + } + + result = implementedBy(NULL, cls); + Py_DECREF(cls); + + return result; +} + +static PyObject * +providedBy(PyObject *ignored, PyObject *ob) +{ + PyObject *result, *cls, *cp; + + result = PyObject_GetAttr(ob, str__providedBy__); + if (result == NULL) + { + PyErr_Clear(); + return getObjectSpecification(NULL, ob); + } + + + /* We want to make sure we have a spec. We can't do a type check + because we may have a proxy, so we'll just try to get the + only attribute. + */ + if (PyObject_TypeCheck(result, &SpecType) + || + PyObject_HasAttr(result, strextends) + ) + return result; + + /* + The object's class doesn't understand descriptors. + Sigh. We need to get an object descriptor, but we have to be + careful. We want to use the instance's __provides__,l if + there is one, but only if it didn't come from the class. + */ + Py_DECREF(result); + + cls = PyObject_GetAttr(ob, str__class__); + if (cls == NULL) + return NULL; + + result = PyObject_GetAttr(ob, str__provides__); + if (result == NULL) + { + /* No __provides__, so just fall back to implementedBy */ + PyErr_Clear(); + result = implementedBy(NULL, cls); + Py_DECREF(cls); + return result; + } + + cp = PyObject_GetAttr(cls, str__provides__); + if (cp == NULL) + { + /* The the class has no provides, assume we're done: */ + PyErr_Clear(); + Py_DECREF(cls); + return result; + } + + if (cp == result) + { + /* + Oops, we got the provides from the class. This means + the object doesn't have it's own. We should use implementedBy + */ + Py_DECREF(result); + result = implementedBy(NULL, cls); + } + + Py_DECREF(cls); + Py_DECREF(cp); + + return result; +} + +/* + Get an attribute from an inst dict. Return a borrowed reference. + + This has a number of advantages: + + - It avoids layers of Python api + + - It doesn't waste time looking for descriptors + + - It fails wo raising an exception, although that shouldn't really + matter. + +*/ +static PyObject * +inst_attr(PyObject *self, PyObject *name) +{ + PyObject **dictp, *v; + + dictp = _PyObject_GetDictPtr(self); + if (dictp && *dictp && (v = PyDict_GetItem(*dictp, name))) + return v; + PyErr_SetObject(PyExc_AttributeError, name); + return NULL; +} + + +static PyObject * +Spec_extends(PyObject *self, PyObject *other) +{ + PyObject *implied; + + implied = inst_attr(self, str_implied); + if (implied == NULL) + return NULL; + +#ifdef Py_True + if (PyDict_GetItem(implied, other) != NULL) + { + Py_INCREF(Py_True); + return Py_True; + } + Py_INCREF(Py_False); + return Py_False; +#else + return PyInt_FromLong(PyDict_GetItem(implied, other) != NULL); +#endif +} + +static char Spec_extends__doc__[] = +"Test whether a specification is or extends another" +; + +static char Spec_providedBy__doc__[] = +"Test whether an interface is implemented by the specification" +; + +static PyObject * +Spec_call(PyObject *self, PyObject *args, PyObject *kw) +{ + PyObject *spec; + + if (! PyArg_ParseTuple(args, "O", &spec)) + return NULL; + return Spec_extends(self, spec); +} + +static PyObject * +Spec_providedBy(PyObject *self, PyObject *ob) +{ + PyObject *decl, *item; + + decl = providedBy(NULL, ob); + if (decl == NULL) + return NULL; + + if (PyObject_TypeCheck(decl, &SpecType)) + item = Spec_extends(decl, self); + else + /* decl is probably a security proxy. We have to go the long way + around. + */ + item = PyObject_CallFunctionObjArgs(decl, self, NULL); + + Py_DECREF(decl); + return item; +} + + +static char Spec_implementedBy__doc__[] = +"Test whether the specification is implemented by a class or factory.\n" +"Raise TypeError if argument is neither a class nor a callable." +; + +static PyObject * +Spec_implementedBy(PyObject *self, PyObject *cls) +{ + PyObject *decl, *item; + + decl = implementedBy(NULL, cls); + if (decl == NULL) + return NULL; + + if (PyObject_TypeCheck(decl, &SpecType)) + item = Spec_extends(decl, self); + else + item = PyObject_CallFunctionObjArgs(decl, self, NULL); + + Py_DECREF(decl); + return item; +} + +static struct PyMethodDef Spec_methods[] = { + {"providedBy", + (PyCFunction)Spec_providedBy, METH_O, + Spec_providedBy__doc__}, + {"implementedBy", + (PyCFunction)Spec_implementedBy, METH_O, + Spec_implementedBy__doc__}, + {"isOrExtends", (PyCFunction)Spec_extends, METH_O, + Spec_extends__doc__}, + + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject SpecType = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_interface_coptimizations." + "SpecificationBase", + /* tp_basicsize */ 0, + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)0, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)Spec_call, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + "Base type for Specification objects", + /* tp_traverse */ (traverseproc)0, + /* tp_clear */ (inquiry)0, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ Spec_methods, +}; + +static PyObject * +OSD_descr_get(PyObject *self, PyObject *inst, PyObject *cls) +{ + PyObject *provides; + + if (inst == NULL) + return getObjectSpecification(NULL, cls); + + provides = PyObject_GetAttr(inst, str__provides__); + if (provides != NULL) + return provides; + PyErr_Clear(); + return implementedBy(NULL, cls); +} + +static PyTypeObject OSDType = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_interface_coptimizations." + "ObjectSpecificationDescriptor", + /* tp_basicsize */ 0, + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)0, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)0, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE , + "Object Specification Descriptor", + /* tp_traverse */ (traverseproc)0, + /* tp_clear */ (inquiry)0, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ 0, + /* tp_members */ 0, + /* tp_getset */ 0, + /* tp_base */ 0, + /* tp_dict */ 0, /* internal use */ + /* tp_descr_get */ (descrgetfunc)OSD_descr_get, +}; + +static PyObject * +CPB_descr_get(PyObject *self, PyObject *inst, PyObject *cls) +{ + PyObject *mycls, *implements; + + mycls = inst_attr(self, str_cls); + if (mycls == NULL) + return NULL; + + if (cls == mycls) + { + if (inst == NULL) + { + Py_INCREF(self); + return OBJECT(self); + } + + implements = inst_attr(self, str_implements); + Py_XINCREF(implements); + return implements; + } + + PyErr_SetObject(PyExc_AttributeError, str__provides__); + return NULL; +} + +static PyTypeObject CPBType = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_interface_coptimizations." + "ClassProvidesBase", + /* tp_basicsize */ 0, + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)0, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)0, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + "C Base class for ClassProvides", + /* tp_traverse */ (traverseproc)0, + /* tp_clear */ (inquiry)0, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ 0, + /* tp_members */ 0, + /* tp_getset */ 0, + /* tp_base */ &SpecType, + /* tp_dict */ 0, /* internal use */ + /* tp_descr_get */ (descrgetfunc)CPB_descr_get, +}; + +/* ==================================================================== */ +/* ========== Begin: __call__ and __adapt__ =========================== */ + +/* + def __adapt__(self, obj): + """Adapt an object to the reciever + """ + if self.providedBy(obj): + return obj + + for hook in adapter_hooks: + adapter = hook(self, obj) + if adapter is not None: + return adapter + + +*/ +static PyObject * +__adapt__(PyObject *self, PyObject *obj) +{ + PyObject *decl, *args, *adapter; + int implements, i, l; + + decl = providedBy(NULL, obj); + if (decl == NULL) + return NULL; + + if (PyObject_TypeCheck(decl, &SpecType)) + { + PyObject *implied; + + implied = inst_attr(decl, str_implied); + if (implied == NULL) + { + Py_DECREF(decl); + return NULL; + } + + implements = PyDict_GetItem(implied, self) != NULL; + Py_DECREF(decl); + } + else + { + /* decl is probably a security proxy. We have to go the long way + around. + */ + PyObject *r; + r = PyObject_CallFunctionObjArgs(decl, self, NULL); + Py_DECREF(decl); + if (r == NULL) + return NULL; + implements = PyObject_IsTrue(r); + Py_DECREF(r); + } + + if (implements) + { + Py_INCREF(obj); + return obj; + } + + l = PyList_GET_SIZE(adapter_hooks); + args = PyTuple_New(2); + if (args == NULL) + return NULL; + Py_INCREF(self); + PyTuple_SET_ITEM(args, 0, self); + Py_INCREF(obj); + PyTuple_SET_ITEM(args, 1, obj); + for (i = 0; i < l; i++) + { + adapter = PyObject_CallObject(PyList_GET_ITEM(adapter_hooks, i), args); + if (adapter == NULL || adapter != Py_None) + { + Py_DECREF(args); + return adapter; + } + Py_DECREF(adapter); + } + + Py_DECREF(args); + + Py_INCREF(Py_None); + return Py_None; +} + +static struct PyMethodDef ib_methods[] = { + {"__adapt__", (PyCFunction)__adapt__, METH_O, + "Adapt an object to the reciever"}, + {NULL, NULL} /* sentinel */ +}; + +/* + def __call__(self, obj, alternate=_marker): + conform = getattr(obj, '__conform__', None) + if conform is not None: + adapter = self._call_conform(conform) + if adapter is not None: + return adapter + + adapter = self.__adapt__(obj) + + if adapter is not None: + return adapter + elif alternate is not _marker: + return alternate + else: + raise TypeError("Could not adapt", obj, self) +*/ +static PyObject * +ib_call(PyObject *self, PyObject *args, PyObject *kwargs) +{ + PyObject *conform, *obj, *alternate=NULL, *adapter; + + static char *kwlist[] = {"obj", "alternate", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, + &obj, &alternate)) + return NULL; + + conform = PyObject_GetAttr(obj, str__conform__); + if (conform != NULL) + { + adapter = PyObject_CallMethodObjArgs(self, str_call_conform, + conform, NULL); + Py_DECREF(conform); + if (adapter == NULL || adapter != Py_None) + return adapter; + Py_DECREF(adapter); + } + else + PyErr_Clear(); + + adapter = __adapt__(self, obj); + if (adapter == NULL || adapter != Py_None) + return adapter; + Py_DECREF(adapter); + + if (alternate != NULL) + { + Py_INCREF(alternate); + return alternate; + } + + adapter = Py_BuildValue("sOO", "Could not adapt", obj, self); + if (adapter != NULL) + { + PyErr_SetObject(PyExc_TypeError, adapter); + Py_DECREF(adapter); + } + return NULL; +} + +static PyTypeObject InterfaceBase = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_zope_interface_coptimizations." + "InterfaceBase", + /* tp_basicsize */ 0, + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)0, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)ib_call, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE , + /* tp_doc */ "Interface base type providing __call__ and __adapt__", + /* tp_traverse */ (traverseproc)0, + /* tp_clear */ (inquiry)0, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ ib_methods, +}; + +/* =================== End: __call__ and __adapt__ ==================== */ +/* ==================================================================== */ + +/* ==================================================================== */ +/* ========================== Begin: Lookup Bases ===================== */ + +typedef struct { + PyObject_HEAD + PyObject *_cache; + PyObject *_mcache; + PyObject *_scache; +} lookup; + +typedef struct { + PyObject_HEAD + PyObject *_cache; + PyObject *_mcache; + PyObject *_scache; + PyObject *_verify_ro; + PyObject *_verify_generations; +} verify; + +static int +lookup_traverse(lookup *self, visitproc visit, void *arg) +{ + int vret; + + if (self->_cache) { + vret = visit(self->_cache, arg); + if (vret != 0) + return vret; + } + + if (self->_mcache) { + vret = visit(self->_mcache, arg); + if (vret != 0) + return vret; + } + + if (self->_scache) { + vret = visit(self->_scache, arg); + if (vret != 0) + return vret; + } + + return 0; +} + +static int +lookup_clear(lookup *self) +{ + Py_CLEAR(self->_cache); + Py_CLEAR(self->_mcache); + Py_CLEAR(self->_scache); + return 0; +} + +static void +lookup_dealloc(lookup *self) +{ + PyObject_GC_UnTrack((PyObject *)self); + lookup_clear(self); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +/* + def changed(self, ignored=None): + self._cache.clear() + self._mcache.clear() + self._scache.clear() +*/ +static PyObject * +lookup_changed(lookup *self, PyObject *ignored) +{ + lookup_clear(self); + Py_INCREF(Py_None); + return Py_None; +} + +#define ASSURE_DICT(N) if (N == NULL) { N = PyDict_New(); \ + if (N == NULL) return NULL; \ + } + +/* + def _getcache(self, provided, name): + cache = self._cache.get(provided) + if cache is None: + cache = {} + self._cache[provided] = cache + if name: + c = cache.get(name) + if c is None: + c = {} + cache[name] = c + cache = c + return cache +*/ +static PyObject * +_subcache(PyObject *cache, PyObject *key) +{ + PyObject *subcache; + + subcache = PyDict_GetItem(cache, key); + if (subcache == NULL) + { + int status; + + subcache = PyDict_New(); + if (subcache == NULL) + return NULL; + status = PyDict_SetItem(cache, key, subcache); + Py_DECREF(subcache); + if (status < 0) + return NULL; + } + + return subcache; +} +static PyObject * +_getcache(lookup *self, PyObject *provided, PyObject *name) +{ + PyObject *cache; + + ASSURE_DICT(self->_cache); + cache = _subcache(self->_cache, provided); + if (cache == NULL) + return NULL; + + if (name != NULL && PyObject_IsTrue(name)) + cache = _subcache(cache, name); + + return cache; +} + + +/* + def lookup(self, required, provided, name=u'', default=None): + cache = self._getcache(provided, name) + if len(required) == 1: + result = cache.get(required[0], _not_in_mapping) + else: + result = cache.get(tuple(required), _not_in_mapping) + + if result is _not_in_mapping: + result = self._uncached_lookup(required, provided, name) + if len(required) == 1: + cache[required[0]] = result + else: + cache[tuple(required)] = result + + if result is None: + return default + + return result +*/ +static PyObject * +tuplefy(PyObject *v) +{ + if (! PyTuple_Check(v)) + { + v = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), v, NULL); + if (v == NULL) + return NULL; + } + else + Py_INCREF(v); + + return v; +} +static PyObject * +_lookup(lookup *self, + PyObject *required, PyObject *provided, PyObject *name, + PyObject *default_) +{ + PyObject *result, *key, *cache; + +#ifdef PY3K + if ( name && !PyUnicode_Check(name) ) +#else + if ( name && !PyString_Check(name) && !PyUnicode_Check(name) ) +#endif + { + PyErr_SetString(PyExc_ValueError, + "name is not a string or unicode"); + return NULL; + } + cache = _getcache(self, provided, name); + if (cache == NULL) + return NULL; + + required = tuplefy(required); + if (required == NULL) + return NULL; + + if (PyTuple_GET_SIZE(required) == 1) + key = PyTuple_GET_ITEM(required, 0); + else + key = required; + + result = PyDict_GetItem(cache, key); + if (result == NULL) + { + int status; + + result = PyObject_CallMethodObjArgs(OBJECT(self), str_uncached_lookup, + required, provided, name, NULL); + if (result == NULL) + { + Py_DECREF(required); + return NULL; + } + status = PyDict_SetItem(cache, key, result); + Py_DECREF(required); + if (status < 0) + { + Py_DECREF(result); + return NULL; + } + } + else + { + Py_INCREF(result); + Py_DECREF(required); + } + + if (result == Py_None && default_ != NULL) + { + Py_DECREF(Py_None); + Py_INCREF(default_); + return default_; + } + + return result; +} +static PyObject * +lookup_lookup(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", "name", "default", NULL}; + PyObject *required, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &required, &provided, &name, &default_)) + return NULL; + + return _lookup(self, required, provided, name, default_); +} + + +/* + def lookup1(self, required, provided, name=u'', default=None): + cache = self._getcache(provided, name) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + return self.lookup((required, ), provided, name, default) + + if result is None: + return default + + return result +*/ +static PyObject * +_lookup1(lookup *self, + PyObject *required, PyObject *provided, PyObject *name, + PyObject *default_) +{ + PyObject *result, *cache; + +#ifdef PY3K + if ( name && !PyUnicode_Check(name) ) +#else + if ( name && !PyString_Check(name) && !PyUnicode_Check(name) ) +#endif + { + PyErr_SetString(PyExc_ValueError, + "name is not a string or unicode"); + return NULL; + } + + cache = _getcache(self, provided, name); + if (cache == NULL) + return NULL; + + result = PyDict_GetItem(cache, required); + if (result == NULL) + { + PyObject *tup; + + tup = PyTuple_New(1); + if (tup == NULL) + return NULL; + Py_INCREF(required); + PyTuple_SET_ITEM(tup, 0, required); + result = _lookup(self, tup, provided, name, default_); + Py_DECREF(tup); + } + else + { + if (result == Py_None && default_ != NULL) + { + result = default_; + } + Py_INCREF(result); + } + + return result; +} +static PyObject * +lookup_lookup1(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", "name", "default", NULL}; + PyObject *required, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &required, &provided, &name, &default_)) + return NULL; + + return _lookup1(self, required, provided, name, default_); +} + +/* + def adapter_hook(self, provided, object, name=u'', default=None): + required = providedBy(object) + cache = self._getcache(provided, name) + factory = cache.get(required, _not_in_mapping) + if factory is _not_in_mapping: + factory = self.lookup((required, ), provided, name) + + if factory is not None: + result = factory(object) + if result is not None: + return result + + return default +*/ +static PyObject * +_adapter_hook(lookup *self, + PyObject *provided, PyObject *object, PyObject *name, + PyObject *default_) +{ + PyObject *required, *factory, *result; + +#ifdef PY3K + if ( name && !PyUnicode_Check(name) ) +#else + if ( name && !PyString_Check(name) && !PyUnicode_Check(name) ) +#endif + { + PyErr_SetString(PyExc_ValueError, + "name is not a string or unicode"); + return NULL; + } + + required = providedBy(NULL, object); + if (required == NULL) + return NULL; + + factory = _lookup1(self, required, provided, name, Py_None); + Py_DECREF(required); + if (factory == NULL) + return NULL; + + if (factory != Py_None) + { + result = PyObject_CallFunctionObjArgs(factory, object, NULL); + Py_DECREF(factory); + if (result == NULL || result != Py_None) + return result; + } + else + result = factory; /* None */ + + if (default_ == NULL || default_ == result) /* No default specified, */ + return result; /* Return None. result is owned None */ + + Py_DECREF(result); + Py_INCREF(default_); + + return default_; +} +static PyObject * +lookup_adapter_hook(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"provided", "object", "name", "default", NULL}; + PyObject *object, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &provided, &object, &name, &default_)) + return NULL; + + return _adapter_hook(self, provided, object, name, default_); +} + +static PyObject * +lookup_queryAdapter(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"object", "provided", "name", "default", NULL}; + PyObject *object, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &object, &provided, &name, &default_)) + return NULL; + + return _adapter_hook(self, provided, object, name, default_); +} + +/* + def lookupAll(self, required, provided): + cache = self._mcache.get(provided) + if cache is None: + cache = {} + self._mcache[provided] = cache + + required = tuple(required) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + result = self._uncached_lookupAll(required, provided) + cache[required] = result + + return result +*/ +static PyObject * +_lookupAll(lookup *self, PyObject *required, PyObject *provided) +{ + PyObject *cache, *result; + + ASSURE_DICT(self->_mcache); + cache = _subcache(self->_mcache, provided); + if (cache == NULL) + return NULL; + + required = tuplefy(required); + if (required == NULL) + return NULL; + + result = PyDict_GetItem(cache, required); + if (result == NULL) + { + int status; + + result = PyObject_CallMethodObjArgs(OBJECT(self), str_uncached_lookupAll, + required, provided, NULL); + if (result == NULL) + { + Py_DECREF(required); + return NULL; + } + status = PyDict_SetItem(cache, required, result); + Py_DECREF(required); + if (status < 0) + { + Py_DECREF(result); + return NULL; + } + } + else + { + Py_INCREF(result); + Py_DECREF(required); + } + + return result; +} +static PyObject * +lookup_lookupAll(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", NULL}; + PyObject *required, *provided; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, + &required, &provided)) + return NULL; + + return _lookupAll(self, required, provided); +} + +/* + def subscriptions(self, required, provided): + cache = self._scache.get(provided) + if cache is None: + cache = {} + self._scache[provided] = cache + + required = tuple(required) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + result = self._uncached_subscriptions(required, provided) + cache[required] = result + + return result +*/ +static PyObject * +_subscriptions(lookup *self, PyObject *required, PyObject *provided) +{ + PyObject *cache, *result; + + ASSURE_DICT(self->_scache); + cache = _subcache(self->_scache, provided); + if (cache == NULL) + return NULL; + + required = tuplefy(required); + if (required == NULL) + return NULL; + + result = PyDict_GetItem(cache, required); + if (result == NULL) + { + int status; + + result = PyObject_CallMethodObjArgs( + OBJECT(self), str_uncached_subscriptions, + required, provided, NULL); + if (result == NULL) + { + Py_DECREF(required); + return NULL; + } + status = PyDict_SetItem(cache, required, result); + Py_DECREF(required); + if (status < 0) + { + Py_DECREF(result); + return NULL; + } + } + else + { + Py_INCREF(result); + Py_DECREF(required); + } + + return result; +} +static PyObject * +lookup_subscriptions(lookup *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", NULL}; + PyObject *required, *provided; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, + &required, &provided)) + return NULL; + + return _subscriptions(self, required, provided); +} + +static struct PyMethodDef lookup_methods[] = { + {"changed", (PyCFunction)lookup_changed, METH_O, ""}, + {"lookup", (PyCFunction)lookup_lookup, METH_KEYWORDS | METH_VARARGS, ""}, + {"lookup1", (PyCFunction)lookup_lookup1, METH_KEYWORDS | METH_VARARGS, ""}, + {"queryAdapter", (PyCFunction)lookup_queryAdapter, METH_KEYWORDS | METH_VARARGS, ""}, + {"adapter_hook", (PyCFunction)lookup_adapter_hook, METH_KEYWORDS | METH_VARARGS, ""}, + {"lookupAll", (PyCFunction)lookup_lookupAll, METH_KEYWORDS | METH_VARARGS, ""}, + {"subscriptions", (PyCFunction)lookup_subscriptions, METH_KEYWORDS | METH_VARARGS, ""}, + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject LookupBase = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_zope_interface_coptimizations." + "LookupBase", + /* tp_basicsize */ sizeof(lookup), + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)&lookup_dealloc, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)0, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE + | Py_TPFLAGS_HAVE_GC, + /* tp_doc */ "", + /* tp_traverse */ (traverseproc)lookup_traverse, + /* tp_clear */ (inquiry)lookup_clear, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ lookup_methods, +}; + +static int +verifying_traverse(verify *self, visitproc visit, void *arg) +{ + int vret; + + vret = lookup_traverse((lookup *)self, visit, arg); + if (vret != 0) + return vret; + + if (self->_verify_ro) { + vret = visit(self->_verify_ro, arg); + if (vret != 0) + return vret; + } + if (self->_verify_generations) { + vret = visit(self->_verify_generations, arg); + if (vret != 0) + return vret; + } + + return 0; +} + +static int +verifying_clear(verify *self) +{ + lookup_clear((lookup *)self); + Py_CLEAR(self->_verify_generations); + Py_CLEAR(self->_verify_ro); + return 0; +} + + +static void +verifying_dealloc(verify *self) +{ + PyObject_GC_UnTrack((PyObject *)self); + verifying_clear(self); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +/* + def changed(self, originally_changed): + super(VerifyingBasePy, self).changed(originally_changed) + self._verify_ro = self._registry.ro[1:] + self._verify_generations = [r._generation for r in self._verify_ro] +*/ +static PyObject * +_generations_tuple(PyObject *ro) +{ + int i, l; + PyObject *generations; + + l = PyTuple_GET_SIZE(ro); + generations = PyTuple_New(l); + for (i=0; i < l; i++) + { + PyObject *generation; + + generation = PyObject_GetAttr(PyTuple_GET_ITEM(ro, i), str_generation); + if (generation == NULL) + { + Py_DECREF(generations); + return NULL; + } + PyTuple_SET_ITEM(generations, i, generation); + } + + return generations; +} +static PyObject * +verifying_changed(verify *self, PyObject *ignored) +{ + PyObject *t, *ro; + + verifying_clear(self); + + t = PyObject_GetAttr(OBJECT(self), str_registry); + if (t == NULL) + return NULL; + ro = PyObject_GetAttr(t, strro); + Py_DECREF(t); + if (ro == NULL) + return NULL; + + t = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), ro, NULL); + Py_DECREF(ro); + if (t == NULL) + return NULL; + + ro = PyTuple_GetSlice(t, 1, PyTuple_GET_SIZE(t)); + Py_DECREF(t); + if (ro == NULL) + return NULL; + + self->_verify_generations = _generations_tuple(ro); + if (self->_verify_generations == NULL) + { + Py_DECREF(ro); + return NULL; + } + + self->_verify_ro = ro; + + Py_INCREF(Py_None); + return Py_None; +} + +/* + def _verify(self): + if ([r._generation for r in self._verify_ro] + != self._verify_generations): + self.changed(None) +*/ +static int +_verify(verify *self) +{ + PyObject *changed_result; + + if (self->_verify_ro != NULL && self->_verify_generations != NULL) + { + PyObject *generations; + int changed; + + generations = _generations_tuple(self->_verify_ro); + if (generations == NULL) + return -1; + + changed = PyObject_RichCompareBool(self->_verify_generations, + generations, Py_NE); + Py_DECREF(generations); + if (changed == -1) + return -1; + + if (changed == 0) + return 0; + } + + changed_result = PyObject_CallMethodObjArgs(OBJECT(self), strchanged, + Py_None, NULL); + if (changed_result == NULL) + return -1; + + Py_DECREF(changed_result); + return 0; +} + +static PyObject * +verifying_lookup(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", "name", "default", NULL}; + PyObject *required, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &required, &provided, &name, &default_)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _lookup((lookup *)self, required, provided, name, default_); +} + +static PyObject * +verifying_lookup1(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", "name", "default", NULL}; + PyObject *required, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &required, &provided, &name, &default_)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _lookup1((lookup *)self, required, provided, name, default_); +} + +static PyObject * +verifying_adapter_hook(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"provided", "object", "name", "default", NULL}; + PyObject *object, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &provided, &object, &name, &default_)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _adapter_hook((lookup *)self, provided, object, name, default_); +} + +static PyObject * +verifying_queryAdapter(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"object", "provided", "name", "default", NULL}; + PyObject *object, *provided, *name=NULL, *default_=NULL; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + &object, &provided, &name, &default_)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _adapter_hook((lookup *)self, provided, object, name, default_); +} + +static PyObject * +verifying_lookupAll(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", NULL}; + PyObject *required, *provided; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, + &required, &provided)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _lookupAll((lookup *)self, required, provided); +} + +static PyObject * +verifying_subscriptions(verify *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"required", "provided", NULL}; + PyObject *required, *provided; + + if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, + &required, &provided)) + return NULL; + + if (_verify(self) < 0) + return NULL; + + return _subscriptions((lookup *)self, required, provided); +} + +static struct PyMethodDef verifying_methods[] = { + {"changed", (PyCFunction)verifying_changed, METH_O, ""}, + {"lookup", (PyCFunction)verifying_lookup, METH_KEYWORDS | METH_VARARGS, ""}, + {"lookup1", (PyCFunction)verifying_lookup1, METH_KEYWORDS | METH_VARARGS, ""}, + {"queryAdapter", (PyCFunction)verifying_queryAdapter, METH_KEYWORDS | METH_VARARGS, ""}, + {"adapter_hook", (PyCFunction)verifying_adapter_hook, METH_KEYWORDS | METH_VARARGS, ""}, + {"lookupAll", (PyCFunction)verifying_lookupAll, METH_KEYWORDS | METH_VARARGS, ""}, + {"subscriptions", (PyCFunction)verifying_subscriptions, METH_KEYWORDS | METH_VARARGS, ""}, + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject VerifyingBase = { + PyVarObject_HEAD_INIT(NULL, 0) + /* tp_name */ "_zope_interface_coptimizations." + "VerifyingBase", + /* tp_basicsize */ sizeof(verify), + /* tp_itemsize */ 0, + /* tp_dealloc */ (destructor)&verifying_dealloc, + /* tp_print */ (printfunc)0, + /* tp_getattr */ (getattrfunc)0, + /* tp_setattr */ (setattrfunc)0, + /* tp_compare */ 0, + /* tp_repr */ (reprfunc)0, + /* tp_as_number */ 0, + /* tp_as_sequence */ 0, + /* tp_as_mapping */ 0, + /* tp_hash */ (hashfunc)0, + /* tp_call */ (ternaryfunc)0, + /* tp_str */ (reprfunc)0, + /* tp_getattro */ (getattrofunc)0, + /* tp_setattro */ (setattrofunc)0, + /* tp_as_buffer */ 0, + /* tp_flags */ Py_TPFLAGS_DEFAULT + | Py_TPFLAGS_BASETYPE + | Py_TPFLAGS_HAVE_GC, + /* tp_doc */ "", + /* tp_traverse */ (traverseproc)verifying_traverse, + /* tp_clear */ (inquiry)verifying_clear, + /* tp_richcompare */ (richcmpfunc)0, + /* tp_weaklistoffset */ (long)0, + /* tp_iter */ (getiterfunc)0, + /* tp_iternext */ (iternextfunc)0, + /* tp_methods */ verifying_methods, + /* tp_members */ 0, + /* tp_getset */ 0, + /* tp_base */ &LookupBase, +}; + +/* ========================== End: Lookup Bases ======================= */ +/* ==================================================================== */ + + + +static struct PyMethodDef m_methods[] = { + {"implementedBy", (PyCFunction)implementedBy, METH_O, + "Interfaces implemented by a class or factory.\n" + "Raises TypeError if argument is neither a class nor a callable."}, + {"getObjectSpecification", (PyCFunction)getObjectSpecification, METH_O, + "Get an object's interfaces (internal api)"}, + {"providedBy", (PyCFunction)providedBy, METH_O, + "Get an object's interfaces"}, + + {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ +}; + +#if PY_MAJOR_VERSION >= 3 +static char module_doc[] = "C optimizations for zope.interface\n\n"; + +static struct PyModuleDef _zic_module = { + PyModuleDef_HEAD_INIT, + "_zope_interface_coptimizations", + module_doc, + -1, + m_methods, + NULL, + NULL, + NULL, + NULL +}; +#endif + +static PyObject * +init(void) +{ + PyObject *m; + +#if PY_MAJOR_VERSION < 3 +#define DEFINE_STRING(S) \ + if(! (str ## S = PyString_FromString(# S))) return NULL +#else +#define DEFINE_STRING(S) \ + if(! (str ## S = PyUnicode_FromString(# S))) return NULL +#endif + + DEFINE_STRING(__dict__); + DEFINE_STRING(__implemented__); + DEFINE_STRING(__provides__); + DEFINE_STRING(__class__); + DEFINE_STRING(__providedBy__); + DEFINE_STRING(extends); + DEFINE_STRING(_implied); + DEFINE_STRING(_implements); + DEFINE_STRING(_cls); + DEFINE_STRING(__conform__); + DEFINE_STRING(_call_conform); + DEFINE_STRING(_uncached_lookup); + DEFINE_STRING(_uncached_lookupAll); + DEFINE_STRING(_uncached_subscriptions); + DEFINE_STRING(_registry); + DEFINE_STRING(_generation); + DEFINE_STRING(ro); + DEFINE_STRING(changed); +#undef DEFINE_STRING + adapter_hooks = PyList_New(0); + if (adapter_hooks == NULL) + return NULL; + + /* Initialize types: */ + SpecType.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&SpecType) < 0) + return NULL; + OSDType.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&OSDType) < 0) + return NULL; + CPBType.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&CPBType) < 0) + return NULL; + + InterfaceBase.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&InterfaceBase) < 0) + return NULL; + + LookupBase.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&LookupBase) < 0) + return NULL; + + VerifyingBase.tp_new = PyBaseObject_Type.tp_new; + if (PyType_Ready(&VerifyingBase) < 0) + return NULL; + + #if PY_MAJOR_VERSION < 3 + /* Create the module and add the functions */ + m = Py_InitModule3("_zope_interface_coptimizations", m_methods, + "C optimizations for zope.interface\n\n"); + #else + m = PyModule_Create(&_zic_module); + #endif + if (m == NULL) + return NULL; + + /* Add types: */ + if (PyModule_AddObject(m, "SpecificationBase", OBJECT(&SpecType)) < 0) + return NULL; + if (PyModule_AddObject(m, "ObjectSpecificationDescriptor", + (PyObject *)&OSDType) < 0) + return NULL; + if (PyModule_AddObject(m, "ClassProvidesBase", OBJECT(&CPBType)) < 0) + return NULL; + if (PyModule_AddObject(m, "InterfaceBase", OBJECT(&InterfaceBase)) < 0) + return NULL; + if (PyModule_AddObject(m, "LookupBase", OBJECT(&LookupBase)) < 0) + return NULL; + if (PyModule_AddObject(m, "VerifyingBase", OBJECT(&VerifyingBase)) < 0) + return NULL; + if (PyModule_AddObject(m, "adapter_hooks", adapter_hooks) < 0) + return NULL; + return m; +} + +PyMODINIT_FUNC +#if PY_MAJOR_VERSION < 3 +init_zope_interface_coptimizations(void) +{ + init(); +} +#else +PyInit__zope_interface_coptimizations(void) +{ + return init(); +} +#endif diff --git a/lib/zope/interface/_zope_interface_coptimizations.cp37-win32.pyd b/lib/zope/interface/_zope_interface_coptimizations.cp37-win32.pyd new file mode 100644 index 0000000000000000000000000000000000000000..c959bc07d6f835e5cd25e677833482217181dc4f GIT binary patch literal 22528 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 WjbY=E@tE<r@dR|})9aIQ;Qs;?k$~3# literal 0 HcmV?d00001 diff --git a/lib/zope/interface/adapter.py b/lib/zope/interface/adapter.py new file mode 100644 index 0000000..aae3155 --- /dev/null +++ b/lib/zope/interface/adapter.py @@ -0,0 +1,712 @@ +############################################################################## +# +# Copyright (c) 2004 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Adapter management +""" +import weakref + +from zope.interface import implementer +from zope.interface import providedBy +from zope.interface import Interface +from zope.interface import ro +from zope.interface.interfaces import IAdapterRegistry + +from zope.interface._compat import _normalize_name +from zope.interface._compat import STRING_TYPES + +_BLANK = u'' + +class BaseAdapterRegistry(object): + + # List of methods copied from lookup sub-objects: + _delegated = ('lookup', 'queryMultiAdapter', 'lookup1', 'queryAdapter', + 'adapter_hook', 'lookupAll', 'names', + 'subscriptions', 'subscribers') + + # All registries maintain a generation that can be used by verifying + # registries + _generation = 0 + + def __init__(self, bases=()): + + # The comments here could be improved. Possibly this bit needs + # explaining in a separate document, as the comments here can + # be quite confusing. /regebro + + # {order -> {required -> {provided -> {name -> value}}}} + # Here "order" is actually an index in a list, "required" and + # "provided" are interfaces, and "required" is really a nested + # key. So, for example: + # for order == 0 (that is, self._adapters[0]), we have: + # {provided -> {name -> value}} + # but for order == 2 (that is, self._adapters[2]), we have: + # {r1 -> {r2 -> {provided -> {name -> value}}}} + # + self._adapters = [] + + # {order -> {required -> {provided -> {name -> [value]}}}} + # where the remarks about adapters above apply + self._subscribers = [] + + # Set, with a reference count, keeping track of the interfaces + # for which we have provided components: + self._provided = {} + + # Create ``_v_lookup`` object to perform lookup. We make this a + # separate object to to make it easier to implement just the + # lookup functionality in C. This object keeps track of cache + # invalidation data in two kinds of registries. + + # Invalidating registries have caches that are invalidated + # when they or their base registies change. An invalidating + # registry can only have invalidating registries as bases. + # See LookupBaseFallback below for the pertinent logic. + + # Verifying registies can't rely on getting invalidation messages, + # so have to check the generations of base registries to determine + # if their cache data are current. See VerifyingBasePy below + # for the pertinent object. + self._createLookup() + + # Setting the bases causes the registries described above + # to be initialized (self._setBases -> self.changed -> + # self._v_lookup.changed). + + self.__bases__ = bases + + def _setBases(self, bases): + self.__dict__['__bases__'] = bases + self.ro = ro.ro(self) + self.changed(self) + + __bases__ = property(lambda self: self.__dict__['__bases__'], + lambda self, bases: self._setBases(bases), + ) + + def _createLookup(self): + self._v_lookup = self.LookupClass(self) + for name in self._delegated: + self.__dict__[name] = getattr(self._v_lookup, name) + + def changed(self, originally_changed): + self._generation += 1 + self._v_lookup.changed(originally_changed) + + def register(self, required, provided, name, value): + if not isinstance(name, STRING_TYPES): + raise ValueError('name is not a string') + if value is None: + self.unregister(required, provided, name, value) + return + + required = tuple(map(_convert_None_to_Interface, required)) + name = _normalize_name(name) + order = len(required) + byorder = self._adapters + while len(byorder) <= order: + byorder.append({}) + components = byorder[order] + key = required + (provided,) + + for k in key: + d = components.get(k) + if d is None: + d = {} + components[k] = d + components = d + + if components.get(name) is value: + return + + components[name] = value + + n = self._provided.get(provided, 0) + 1 + self._provided[provided] = n + if n == 1: + self._v_lookup.add_extendor(provided) + + self.changed(self) + + def registered(self, required, provided, name=_BLANK): + required = tuple(map(_convert_None_to_Interface, required)) + name = _normalize_name(name) + order = len(required) + byorder = self._adapters + if len(byorder) <= order: + return None + + components = byorder[order] + key = required + (provided,) + + for k in key: + d = components.get(k) + if d is None: + return None + components = d + + return components.get(name) + + def unregister(self, required, provided, name, value=None): + required = tuple(map(_convert_None_to_Interface, required)) + order = len(required) + byorder = self._adapters + if order >= len(byorder): + return False + components = byorder[order] + key = required + (provided,) + + # Keep track of how we got to `components`: + lookups = [] + for k in key: + d = components.get(k) + if d is None: + return + lookups.append((components, k)) + components = d + + old = components.get(name) + if old is None: + return + if (value is not None) and (old is not value): + return + + del components[name] + if not components: + # Clean out empty containers, since we don't want our keys + # to reference global objects (interfaces) unnecessarily. + # This is often a problem when an interface is slated for + # removal; a hold-over entry in the registry can make it + # difficult to remove such interfaces. + for comp, k in reversed(lookups): + d = comp[k] + if d: + break + else: + del comp[k] + while byorder and not byorder[-1]: + del byorder[-1] + n = self._provided[provided] - 1 + if n == 0: + del self._provided[provided] + self._v_lookup.remove_extendor(provided) + else: + self._provided[provided] = n + + self.changed(self) + + def subscribe(self, required, provided, value): + required = tuple(map(_convert_None_to_Interface, required)) + name = _BLANK + order = len(required) + byorder = self._subscribers + while len(byorder) <= order: + byorder.append({}) + components = byorder[order] + key = required + (provided,) + + for k in key: + d = components.get(k) + if d is None: + d = {} + components[k] = d + components = d + + components[name] = components.get(name, ()) + (value, ) + + if provided is not None: + n = self._provided.get(provided, 0) + 1 + self._provided[provided] = n + if n == 1: + self._v_lookup.add_extendor(provided) + + self.changed(self) + + def unsubscribe(self, required, provided, value=None): + required = tuple(map(_convert_None_to_Interface, required)) + order = len(required) + byorder = self._subscribers + if order >= len(byorder): + return + components = byorder[order] + key = required + (provided,) + + # Keep track of how we got to `components`: + lookups = [] + for k in key: + d = components.get(k) + if d is None: + return + lookups.append((components, k)) + components = d + + old = components.get(_BLANK) + if not old: + # this is belt-and-suspenders against the failure of cleanup below + return # pragma: no cover + + if value is None: + new = () + else: + new = tuple([v for v in old if v != value]) + + if new == old: + return + + if new: + components[_BLANK] = new + else: + # Instead of setting components[_BLANK] = new, we clean out + # empty containers, since we don't want our keys to + # reference global objects (interfaces) unnecessarily. This + # is often a problem when an interface is slated for + # removal; a hold-over entry in the registry can make it + # difficult to remove such interfaces. + del components[_BLANK] + for comp, k in reversed(lookups): + d = comp[k] + if d: + break + else: + del comp[k] + while byorder and not byorder[-1]: + del byorder[-1] + + if provided is not None: + n = self._provided[provided] + len(new) - len(old) + if n == 0: + del self._provided[provided] + self._v_lookup.remove_extendor(provided) + + self.changed(self) + + # XXX hack to fake out twisted's use of a private api. We need to get them + # to use the new registed method. + def get(self, _): # pragma: no cover + class XXXTwistedFakeOut: + selfImplied = {} + return XXXTwistedFakeOut + + +_not_in_mapping = object() +class LookupBaseFallback(object): + + def __init__(self): + self._cache = {} + self._mcache = {} + self._scache = {} + + def changed(self, ignored=None): + self._cache.clear() + self._mcache.clear() + self._scache.clear() + + def _getcache(self, provided, name): + cache = self._cache.get(provided) + if cache is None: + cache = {} + self._cache[provided] = cache + if name: + c = cache.get(name) + if c is None: + c = {} + cache[name] = c + cache = c + return cache + + def lookup(self, required, provided, name=_BLANK, default=None): + if not isinstance(name, STRING_TYPES): + raise ValueError('name is not a string') + cache = self._getcache(provided, name) + required = tuple(required) + if len(required) == 1: + result = cache.get(required[0], _not_in_mapping) + else: + result = cache.get(tuple(required), _not_in_mapping) + + if result is _not_in_mapping: + result = self._uncached_lookup(required, provided, name) + if len(required) == 1: + cache[required[0]] = result + else: + cache[tuple(required)] = result + + if result is None: + return default + + return result + + def lookup1(self, required, provided, name=_BLANK, default=None): + if not isinstance(name, STRING_TYPES): + raise ValueError('name is not a string') + cache = self._getcache(provided, name) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + return self.lookup((required, ), provided, name, default) + + if result is None: + return default + + return result + + def queryAdapter(self, object, provided, name=_BLANK, default=None): + return self.adapter_hook(provided, object, name, default) + + def adapter_hook(self, provided, object, name=_BLANK, default=None): + if not isinstance(name, STRING_TYPES): + raise ValueError('name is not a string') + required = providedBy(object) + cache = self._getcache(provided, name) + factory = cache.get(required, _not_in_mapping) + if factory is _not_in_mapping: + factory = self.lookup((required, ), provided, name) + + if factory is not None: + result = factory(object) + if result is not None: + return result + + return default + + def lookupAll(self, required, provided): + cache = self._mcache.get(provided) + if cache is None: + cache = {} + self._mcache[provided] = cache + + required = tuple(required) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + result = self._uncached_lookupAll(required, provided) + cache[required] = result + + return result + + + def subscriptions(self, required, provided): + cache = self._scache.get(provided) + if cache is None: + cache = {} + self._scache[provided] = cache + + required = tuple(required) + result = cache.get(required, _not_in_mapping) + if result is _not_in_mapping: + result = self._uncached_subscriptions(required, provided) + cache[required] = result + + return result + +LookupBasePy = LookupBaseFallback # BBB + +try: + from zope.interface._zope_interface_coptimizations import LookupBase +except ImportError: + LookupBase = LookupBaseFallback + + +class VerifyingBaseFallback(LookupBaseFallback): + # Mixin for lookups against registries which "chain" upwards, and + # whose lookups invalidate their own caches whenever a parent registry + # bumps its own '_generation' counter. E.g., used by + # zope.component.persistentregistry + + def changed(self, originally_changed): + LookupBaseFallback.changed(self, originally_changed) + self._verify_ro = self._registry.ro[1:] + self._verify_generations = [r._generation for r in self._verify_ro] + + def _verify(self): + if ([r._generation for r in self._verify_ro] + != self._verify_generations): + self.changed(None) + + def _getcache(self, provided, name): + self._verify() + return LookupBaseFallback._getcache(self, provided, name) + + def lookupAll(self, required, provided): + self._verify() + return LookupBaseFallback.lookupAll(self, required, provided) + + def subscriptions(self, required, provided): + self._verify() + return LookupBaseFallback.subscriptions(self, required, provided) + +VerifyingBasePy = VerifyingBaseFallback #BBB + +try: + from zope.interface._zope_interface_coptimizations import VerifyingBase +except ImportError: + VerifyingBase = VerifyingBaseFallback + + +class AdapterLookupBase(object): + + def __init__(self, registry): + self._registry = registry + self._required = {} + self.init_extendors() + super(AdapterLookupBase, self).__init__() + + def changed(self, ignored=None): + super(AdapterLookupBase, self).changed(None) + for r in self._required.keys(): + r = r() + if r is not None: + r.unsubscribe(self) + self._required.clear() + + + # Extendors + # --------- + + # When given an target interface for an adapter lookup, we need to consider + # adapters for interfaces that extend the target interface. This is + # what the extendors dictionary is about. It tells us all of the + # interfaces that extend an interface for which there are adapters + # registered. + + # We could separate this by order and name, thus reducing the + # number of provided interfaces to search at run time. The tradeoff, + # however, is that we have to store more information. For example, + # if the same interface is provided for multiple names and if the + # interface extends many interfaces, we'll have to keep track of + # a fair bit of information for each name. It's better to + # be space efficient here and be time efficient in the cache + # implementation. + + # TODO: add invalidation when a provided interface changes, in case + # the interface's __iro__ has changed. This is unlikely enough that + # we'll take our chances for now. + + def init_extendors(self): + self._extendors = {} + for p in self._registry._provided: + self.add_extendor(p) + + def add_extendor(self, provided): + _extendors = self._extendors + for i in provided.__iro__: + extendors = _extendors.get(i, ()) + _extendors[i] = ( + [e for e in extendors if provided.isOrExtends(e)] + + + [provided] + + + [e for e in extendors if not provided.isOrExtends(e)] + ) + + def remove_extendor(self, provided): + _extendors = self._extendors + for i in provided.__iro__: + _extendors[i] = [e for e in _extendors.get(i, ()) + if e != provided] + + + def _subscribe(self, *required): + _refs = self._required + for r in required: + ref = r.weakref() + if ref not in _refs: + r.subscribe(self) + _refs[ref] = 1 + + def _uncached_lookup(self, required, provided, name=_BLANK): + required = tuple(required) + result = None + order = len(required) + for registry in self._registry.ro: + byorder = registry._adapters + if order >= len(byorder): + continue + + extendors = registry._v_lookup._extendors.get(provided) + if not extendors: + continue + + components = byorder[order] + result = _lookup(components, required, extendors, name, 0, + order) + if result is not None: + break + + self._subscribe(*required) + + return result + + def queryMultiAdapter(self, objects, provided, name=_BLANK, default=None): + factory = self.lookup(map(providedBy, objects), provided, name) + if factory is None: + return default + + result = factory(*objects) + if result is None: + return default + + return result + + def _uncached_lookupAll(self, required, provided): + required = tuple(required) + order = len(required) + result = {} + for registry in reversed(self._registry.ro): + byorder = registry._adapters + if order >= len(byorder): + continue + extendors = registry._v_lookup._extendors.get(provided) + if not extendors: + continue + components = byorder[order] + _lookupAll(components, required, extendors, result, 0, order) + + self._subscribe(*required) + + return tuple(result.items()) + + def names(self, required, provided): + return [c[0] for c in self.lookupAll(required, provided)] + + def _uncached_subscriptions(self, required, provided): + required = tuple(required) + order = len(required) + result = [] + for registry in reversed(self._registry.ro): + byorder = registry._subscribers + if order >= len(byorder): + continue + + if provided is None: + extendors = (provided, ) + else: + extendors = registry._v_lookup._extendors.get(provided) + if extendors is None: + continue + + _subscriptions(byorder[order], required, extendors, _BLANK, + result, 0, order) + + self._subscribe(*required) + + return result + + def subscribers(self, objects, provided): + subscriptions = self.subscriptions(map(providedBy, objects), provided) + if provided is None: + result = () + for subscription in subscriptions: + subscription(*objects) + else: + result = [] + for subscription in subscriptions: + subscriber = subscription(*objects) + if subscriber is not None: + result.append(subscriber) + return result + +class AdapterLookup(AdapterLookupBase, LookupBase): + pass + +@implementer(IAdapterRegistry) +class AdapterRegistry(BaseAdapterRegistry): + + LookupClass = AdapterLookup + + def __init__(self, bases=()): + # AdapterRegisties are invalidating registries, so + # we need to keep track of out invalidating subregistries. + self._v_subregistries = weakref.WeakKeyDictionary() + + super(AdapterRegistry, self).__init__(bases) + + def _addSubregistry(self, r): + self._v_subregistries[r] = 1 + + def _removeSubregistry(self, r): + if r in self._v_subregistries: + del self._v_subregistries[r] + + def _setBases(self, bases): + old = self.__dict__.get('__bases__', ()) + for r in old: + if r not in bases: + r._removeSubregistry(self) + for r in bases: + if r not in old: + r._addSubregistry(self) + + super(AdapterRegistry, self)._setBases(bases) + + def changed(self, originally_changed): + super(AdapterRegistry, self).changed(originally_changed) + + for sub in self._v_subregistries.keys(): + sub.changed(originally_changed) + + +class VerifyingAdapterLookup(AdapterLookupBase, VerifyingBase): + pass + +@implementer(IAdapterRegistry) +class VerifyingAdapterRegistry(BaseAdapterRegistry): + + LookupClass = VerifyingAdapterLookup + +def _convert_None_to_Interface(x): + if x is None: + return Interface + else: + return x + +def _lookup(components, specs, provided, name, i, l): + if i < l: + for spec in specs[i].__sro__: + comps = components.get(spec) + if comps: + r = _lookup(comps, specs, provided, name, i+1, l) + if r is not None: + return r + else: + for iface in provided: + comps = components.get(iface) + if comps: + r = comps.get(name) + if r is not None: + return r + + return None + +def _lookupAll(components, specs, provided, result, i, l): + if i < l: + for spec in reversed(specs[i].__sro__): + comps = components.get(spec) + if comps: + _lookupAll(comps, specs, provided, result, i+1, l) + else: + for iface in reversed(provided): + comps = components.get(iface) + if comps: + result.update(comps) + +def _subscriptions(components, specs, provided, name, result, i, l): + if i < l: + for spec in reversed(specs[i].__sro__): + comps = components.get(spec) + if comps: + _subscriptions(comps, specs, provided, name, result, i+1, l) + else: + for iface in reversed(provided): + comps = components.get(iface) + if comps: + comps = comps.get(name) + if comps: + result.extend(comps) diff --git a/lib/zope/interface/advice.py b/lib/zope/interface/advice.py new file mode 100644 index 0000000..e55930d --- /dev/null +++ b/lib/zope/interface/advice.py @@ -0,0 +1,205 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Class advice. + +This module was adapted from 'protocols.advice', part of the Python +Enterprise Application Kit (PEAK). Please notify the PEAK authors +(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or +Zope-specific changes are required, so that the PEAK version of this module +can be kept in sync. + +PEAK is a Python application framework that interoperates with (but does +not require) Zope 3 and Twisted. It provides tools for manipulating UML +models, object-relational persistence, aspect-oriented programming, and more. +Visit the PEAK home page at http://peak.telecommunity.com for more information. +""" + +from types import FunctionType +try: + from types import ClassType +except ImportError: + __python3 = True +else: + __python3 = False + +import sys + +def getFrameInfo(frame): + """Return (kind,module,locals,globals) for a frame + + 'kind' is one of "exec", "module", "class", "function call", or "unknown". + """ + + f_locals = frame.f_locals + f_globals = frame.f_globals + + sameNamespace = f_locals is f_globals + hasModule = '__module__' in f_locals + hasName = '__name__' in f_globals + + sameName = hasModule and hasName + sameName = sameName and f_globals['__name__']==f_locals['__module__'] + + module = hasName and sys.modules.get(f_globals['__name__']) or None + + namespaceIsModule = module and module.__dict__ is f_globals + + if not namespaceIsModule: + # some kind of funky exec + kind = "exec" + elif sameNamespace and not hasModule: + kind = "module" + elif sameName and not sameNamespace: + kind = "class" + elif not sameNamespace: + kind = "function call" + else: # pragma: no cover + # How can you have f_locals is f_globals, and have '__module__' set? + # This is probably module-level code, but with a '__module__' variable. + kind = "unknown" + return kind, module, f_locals, f_globals + + +def addClassAdvisor(callback, depth=2): + """Set up 'callback' to be passed the containing class upon creation + + This function is designed to be called by an "advising" function executed + in a class suite. The "advising" function supplies a callback that it + wishes to have executed when the containing class is created. The + callback will be given one argument: the newly created containing class. + The return value of the callback will be used in place of the class, so + the callback should return the input if it does not wish to replace the + class. + + The optional 'depth' argument to this function determines the number of + frames between this function and the targeted class suite. 'depth' + defaults to 2, since this skips this function's frame and one calling + function frame. If you use this function from a function called directly + in the class suite, the default will be correct, otherwise you will need + to determine the correct depth yourself. + + This function works by installing a special class factory function in + place of the '__metaclass__' of the containing class. Therefore, only + callbacks *after* the last '__metaclass__' assignment in the containing + class will be executed. Be sure that classes using "advising" functions + declare any '__metaclass__' *first*, to ensure all callbacks are run.""" + # This entire approach is invalid under Py3K. Don't even try to fix + # the coverage for this block there. :( + if __python3: # pragma: no cover + raise TypeError('Class advice impossible in Python3') + + frame = sys._getframe(depth) + kind, module, caller_locals, caller_globals = getFrameInfo(frame) + + # This causes a problem when zope interfaces are used from doctest. + # In these cases, kind == "exec". + # + #if kind != "class": + # raise SyntaxError( + # "Advice must be in the body of a class statement" + # ) + + previousMetaclass = caller_locals.get('__metaclass__') + if __python3: # pragma: no cover + defaultMetaclass = caller_globals.get('__metaclass__', type) + else: + defaultMetaclass = caller_globals.get('__metaclass__', ClassType) + + + def advise(name, bases, cdict): + + if '__metaclass__' in cdict: + del cdict['__metaclass__'] + + if previousMetaclass is None: + if bases: + # find best metaclass or use global __metaclass__ if no bases + meta = determineMetaclass(bases) + else: + meta = defaultMetaclass + + elif isClassAdvisor(previousMetaclass): + # special case: we can't compute the "true" metaclass here, + # so we need to invoke the previous metaclass and let it + # figure it out for us (and apply its own advice in the process) + meta = previousMetaclass + + else: + meta = determineMetaclass(bases, previousMetaclass) + + newClass = meta(name,bases,cdict) + + # this lets the callback replace the class completely, if it wants to + return callback(newClass) + + # introspection data only, not used by inner function + advise.previousMetaclass = previousMetaclass + advise.callback = callback + + # install the advisor + caller_locals['__metaclass__'] = advise + + +def isClassAdvisor(ob): + """True if 'ob' is a class advisor function""" + return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass') + + +def determineMetaclass(bases, explicit_mc=None): + """Determine metaclass from 1+ bases and optional explicit __metaclass__""" + + meta = [getattr(b,'__class__',type(b)) for b in bases] + + if explicit_mc is not None: + # The explicit metaclass needs to be verified for compatibility + # as well, and allowed to resolve the incompatible bases, if any + meta.append(explicit_mc) + + if len(meta)==1: + # easy case + return meta[0] + + candidates = minimalBases(meta) # minimal set of metaclasses + + if not candidates: # pragma: no cover + # they're all "classic" classes + assert(not __python3) # This should not happen under Python 3 + return ClassType + + elif len(candidates)>1: + # We could auto-combine, but for now we won't... + raise TypeError("Incompatible metatypes",bases) + + # Just one, return it + return candidates[0] + + +def minimalBases(classes): + """Reduce a list of base classes to its ordered minimum equivalent""" + + if not __python3: # pragma: no cover + classes = [c for c in classes if c is not ClassType] + candidates = [] + + for m in classes: + for n in classes: + if issubclass(n,m) and m is not n: + break + else: + # m has no subclasses in 'classes' + if m in candidates: + candidates.remove(m) # ensure that we're later in the list + candidates.append(m) + + return candidates diff --git a/lib/zope/interface/common/__init__.py b/lib/zope/interface/common/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/lib/zope/interface/common/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/lib/zope/interface/common/idatetime.py b/lib/zope/interface/common/idatetime.py new file mode 100644 index 0000000..82f0059 --- /dev/null +++ b/lib/zope/interface/common/idatetime.py @@ -0,0 +1,606 @@ +############################################################################## +# Copyright (c) 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +############################################################################## +"""Datetime interfaces. + +This module is called idatetime because if it were called datetime the import +of the real datetime would fail. +""" +from datetime import timedelta, date, datetime, time, tzinfo + +from zope.interface import Interface, Attribute +from zope.interface import classImplements + + +class ITimeDeltaClass(Interface): + """This is the timedelta class interface. + + This is symbolic; this module does **not** make + `datetime.timedelta` provide this interface. + """ + + min = Attribute("The most negative timedelta object") + + max = Attribute("The most positive timedelta object") + + resolution = Attribute( + "The smallest difference between non-equal timedelta objects") + + +class ITimeDelta(ITimeDeltaClass): + """Represent the difference between two datetime objects. + + Implemented by `datetime.timedelta`. + + Supported operators: + + - add, subtract timedelta + - unary plus, minus, abs + - compare to timedelta + - multiply, divide by int/long + + In addition, `.datetime` supports subtraction of two `.datetime` objects + returning a `.timedelta`, and addition or subtraction of a `.datetime` + and a `.timedelta` giving a `.datetime`. + + Representation: (days, seconds, microseconds). + """ + + days = Attribute("Days between -999999999 and 999999999 inclusive") + + seconds = Attribute("Seconds between 0 and 86399 inclusive") + + microseconds = Attribute("Microseconds between 0 and 999999 inclusive") + + +class IDateClass(Interface): + """This is the date class interface. + + This is symbolic; this module does **not** make + `datetime.date` provide this interface. + """ + + min = Attribute("The earliest representable date") + + max = Attribute("The latest representable date") + + resolution = Attribute( + "The smallest difference between non-equal date objects") + + def today(): + """Return the current local time. + + This is equivalent to ``date.fromtimestamp(time.time())``""" + + def fromtimestamp(timestamp): + """Return the local date from a POSIX timestamp (like time.time()) + + This may raise `ValueError`, if the timestamp is out of the range of + values supported by the platform C ``localtime()`` function. It's common + for this to be restricted to years from 1970 through 2038. Note that + on non-POSIX systems that include leap seconds in their notion of a + timestamp, leap seconds are ignored by `fromtimestamp`. + """ + + def fromordinal(ordinal): + """Return the date corresponding to the proleptic Gregorian ordinal. + + January 1 of year 1 has ordinal 1. `ValueError` is raised unless + 1 <= ordinal <= date.max.toordinal(). + + For any date *d*, ``date.fromordinal(d.toordinal()) == d``. + """ + + +class IDate(IDateClass): + """Represents a date (year, month and day) in an idealized calendar. + + Implemented by `datetime.date`. + + Operators: + + __repr__, __str__ + __cmp__, __hash__ + __add__, __radd__, __sub__ (add/radd only with timedelta arg) + """ + + year = Attribute("Between MINYEAR and MAXYEAR inclusive.") + + month = Attribute("Between 1 and 12 inclusive") + + day = Attribute( + "Between 1 and the number of days in the given month of the given year.") + + def replace(year, month, day): + """Return a date with the same value. + + Except for those members given new values by whichever keyword + arguments are specified. For example, if ``d == date(2002, 12, 31)``, then + ``d.replace(day=26) == date(2000, 12, 26)``. + """ + + def timetuple(): + """Return a 9-element tuple of the form returned by `time.localtime`. + + The hours, minutes and seconds are 0, and the DST flag is -1. + ``d.timetuple()`` is equivalent to + ``(d.year, d.month, d.day, 0, 0, 0, d.weekday(), d.toordinal() - + date(d.year, 1, 1).toordinal() + 1, -1)`` + """ + + def toordinal(): + """Return the proleptic Gregorian ordinal of the date + + January 1 of year 1 has ordinal 1. For any date object *d*, + ``date.fromordinal(d.toordinal()) == d``. + """ + + def weekday(): + """Return the day of the week as an integer. + + Monday is 0 and Sunday is 6. For example, + ``date(2002, 12, 4).weekday() == 2``, a Wednesday. + + .. seealso:: `isoweekday`. + """ + + def isoweekday(): + """Return the day of the week as an integer. + + Monday is 1 and Sunday is 7. For example, + date(2002, 12, 4).isoweekday() == 3, a Wednesday. + + .. seealso:: `weekday`, `isocalendar`. + """ + + def isocalendar(): + """Return a 3-tuple, (ISO year, ISO week number, ISO weekday). + + The ISO calendar is a widely used variant of the Gregorian calendar. + See http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm for a good + explanation. + + The ISO year consists of 52 or 53 full weeks, and where a week starts + on a Monday and ends on a Sunday. The first week of an ISO year is the + first (Gregorian) calendar week of a year containing a Thursday. This + is called week number 1, and the ISO year of that Thursday is the same + as its Gregorian year. + + For example, 2004 begins on a Thursday, so the first week of ISO year + 2004 begins on Monday, 29 Dec 2003 and ends on Sunday, 4 Jan 2004, so + that ``date(2003, 12, 29).isocalendar() == (2004, 1, 1)`` and + ``date(2004, 1, 4).isocalendar() == (2004, 1, 7)``. + """ + + def isoformat(): + """Return a string representing the date in ISO 8601 format. + + This is 'YYYY-MM-DD'. + For example, ``date(2002, 12, 4).isoformat() == '2002-12-04'``. + """ + + def __str__(): + """For a date *d*, ``str(d)`` is equivalent to ``d.isoformat()``.""" + + def ctime(): + """Return a string representing the date. + + For example date(2002, 12, 4).ctime() == 'Wed Dec 4 00:00:00 2002'. + d.ctime() is equivalent to time.ctime(time.mktime(d.timetuple())) + on platforms where the native C ctime() function + (which `time.ctime` invokes, but which date.ctime() does not invoke) + conforms to the C standard. + """ + + def strftime(format): + """Return a string representing the date. + + Controlled by an explicit format string. Format codes referring to + hours, minutes or seconds will see 0 values. + """ + + +class IDateTimeClass(Interface): + """This is the datetime class interface. + + This is symbolic; this module does **not** make + `datetime.datetime` provide this interface. + """ + + min = Attribute("The earliest representable datetime") + + max = Attribute("The latest representable datetime") + + resolution = Attribute( + "The smallest possible difference between non-equal datetime objects") + + def today(): + """Return the current local datetime, with tzinfo None. + + This is equivalent to ``datetime.fromtimestamp(time.time())``. + + .. seealso:: `now`, `fromtimestamp`. + """ + + def now(tz=None): + """Return the current local date and time. + + If optional argument *tz* is None or not specified, this is like `today`, + but, if possible, supplies more precision than can be gotten from going + through a `time.time` timestamp (for example, this may be possible on + platforms supplying the C ``gettimeofday()`` function). + + Else tz must be an instance of a class tzinfo subclass, and the current + date and time are converted to tz's time zone. In this case the result + is equivalent to tz.fromutc(datetime.utcnow().replace(tzinfo=tz)). + + .. seealso:: `today`, `utcnow`. + """ + + def utcnow(): + """Return the current UTC date and time, with tzinfo None. + + This is like `now`, but returns the current UTC date and time, as a + naive datetime object. + + .. seealso:: `now`. + """ + + def fromtimestamp(timestamp, tz=None): + """Return the local date and time corresponding to the POSIX timestamp. + + Same as is returned by time.time(). If optional argument tz is None or + not specified, the timestamp is converted to the platform's local date + and time, and the returned datetime object is naive. + + Else tz must be an instance of a class tzinfo subclass, and the + timestamp is converted to tz's time zone. In this case the result is + equivalent to + ``tz.fromutc(datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz))``. + + fromtimestamp() may raise `ValueError`, if the timestamp is out of the + range of values supported by the platform C localtime() or gmtime() + functions. It's common for this to be restricted to years in 1970 + through 2038. Note that on non-POSIX systems that include leap seconds + in their notion of a timestamp, leap seconds are ignored by + fromtimestamp(), and then it's possible to have two timestamps + differing by a second that yield identical datetime objects. + + .. seealso:: `utcfromtimestamp`. + """ + + def utcfromtimestamp(timestamp): + """Return the UTC datetime from the POSIX timestamp with tzinfo None. + + This may raise `ValueError`, if the timestamp is out of the range of + values supported by the platform C ``gmtime()`` function. It's common for + this to be restricted to years in 1970 through 2038. + + .. seealso:: `fromtimestamp`. + """ + + def fromordinal(ordinal): + """Return the datetime from the proleptic Gregorian ordinal. + + January 1 of year 1 has ordinal 1. `ValueError` is raised unless + 1 <= ordinal <= datetime.max.toordinal(). + The hour, minute, second and microsecond of the result are all 0, and + tzinfo is None. + """ + + def combine(date, time): + """Return a new datetime object. + + Its date members are equal to the given date object's, and whose time + and tzinfo members are equal to the given time object's. For any + datetime object *d*, ``d == datetime.combine(d.date(), d.timetz())``. + If date is a datetime object, its time and tzinfo members are ignored. + """ + + +class IDateTime(IDate, IDateTimeClass): + """Object contains all the information from a date object and a time object. + + Implemented by `datetime.datetime`. + """ + + year = Attribute("Year between MINYEAR and MAXYEAR inclusive") + + month = Attribute("Month between 1 and 12 inclusive") + + day = Attribute( + "Day between 1 and the number of days in the given month of the year") + + hour = Attribute("Hour in range(24)") + + minute = Attribute("Minute in range(60)") + + second = Attribute("Second in range(60)") + + microsecond = Attribute("Microsecond in range(1000000)") + + tzinfo = Attribute( + """The object passed as the tzinfo argument to the datetime constructor + or None if none was passed""") + + def date(): + """Return date object with same year, month and day.""" + + def time(): + """Return time object with same hour, minute, second, microsecond. + + tzinfo is None. + + .. seealso:: Method :meth:`timetz`. + """ + + def timetz(): + """Return time object with same hour, minute, second, microsecond, + and tzinfo. + + .. seealso:: Method :meth:`time`. + """ + + def replace(year, month, day, hour, minute, second, microsecond, tzinfo): + """Return a datetime with the same members, except for those members + given new values by whichever keyword arguments are specified. + + Note that ``tzinfo=None`` can be specified to create a naive datetime from + an aware datetime with no conversion of date and time members. + """ + + def astimezone(tz): + """Return a datetime object with new tzinfo member tz, adjusting the + date and time members so the result is the same UTC time as self, but + in tz's local time. + + tz must be an instance of a tzinfo subclass, and its utcoffset() and + dst() methods must not return None. self must be aware (self.tzinfo + must not be None, and self.utcoffset() must not return None). + + If self.tzinfo is tz, self.astimezone(tz) is equal to self: no + adjustment of date or time members is performed. Else the result is + local time in time zone tz, representing the same UTC time as self: + + after astz = dt.astimezone(tz), astz - astz.utcoffset() + + will usually have the same date and time members as dt - dt.utcoffset(). + The discussion of class `datetime.tzinfo` explains the cases at Daylight Saving + Time transition boundaries where this cannot be achieved (an issue only + if tz models both standard and daylight time). + + If you merely want to attach a time zone object *tz* to a datetime *dt* + without adjustment of date and time members, use ``dt.replace(tzinfo=tz)``. + If you merely want to remove the time zone object from an aware + datetime dt without conversion of date and time members, use + ``dt.replace(tzinfo=None)``. + + Note that the default `tzinfo.fromutc` method can be overridden in a + tzinfo subclass to effect the result returned by `astimezone`. + """ + + def utcoffset(): + """Return the timezone offset in minutes east of UTC (negative west of + UTC).""" + + def dst(): + """Return 0 if DST is not in effect, or the DST offset (in minutes + eastward) if DST is in effect. + """ + + def tzname(): + """Return the timezone name.""" + + def timetuple(): + """Return a 9-element tuple of the form returned by `time.localtime`.""" + + def utctimetuple(): + """Return UTC time tuple compatilble with `time.gmtime`.""" + + def toordinal(): + """Return the proleptic Gregorian ordinal of the date. + + The same as self.date().toordinal(). + """ + + def weekday(): + """Return the day of the week as an integer. + + Monday is 0 and Sunday is 6. The same as self.date().weekday(). + See also isoweekday(). + """ + + def isoweekday(): + """Return the day of the week as an integer. + + Monday is 1 and Sunday is 7. The same as self.date().isoweekday. + + .. seealso:: `weekday`, `isocalendar`. + """ + + def isocalendar(): + """Return a 3-tuple, (ISO year, ISO week number, ISO weekday). + + The same as self.date().isocalendar(). + """ + + def isoformat(sep='T'): + """Return a string representing the date and time in ISO 8601 format. + + YYYY-MM-DDTHH:MM:SS.mmmmmm or YYYY-MM-DDTHH:MM:SS if microsecond is 0 + + If `utcoffset` does not return None, a 6-character string is appended, + giving the UTC offset in (signed) hours and minutes: + + YYYY-MM-DDTHH:MM:SS.mmmmmm+HH:MM or YYYY-MM-DDTHH:MM:SS+HH:MM + if microsecond is 0. + + The optional argument sep (default 'T') is a one-character separator, + placed between the date and time portions of the result. + """ + + def __str__(): + """For a datetime instance *d*, ``str(d)`` is equivalent to ``d.isoformat(' ')``. + """ + + def ctime(): + """Return a string representing the date and time. + + ``datetime(2002, 12, 4, 20, 30, 40).ctime() == 'Wed Dec 4 20:30:40 2002'``. + ``d.ctime()`` is equivalent to ``time.ctime(time.mktime(d.timetuple()))`` on + platforms where the native C ``ctime()`` function (which `time.ctime` + invokes, but which `datetime.ctime` does not invoke) conforms to the + C standard. + """ + + def strftime(format): + """Return a string representing the date and time. + + This is controlled by an explicit format string. + """ + + +class ITimeClass(Interface): + """This is the time class interface. + + This is symbolic; this module does **not** make + `datetime.time` provide this interface. + + """ + + min = Attribute("The earliest representable time") + + max = Attribute("The latest representable time") + + resolution = Attribute( + "The smallest possible difference between non-equal time objects") + + +class ITime(ITimeClass): + """Represent time with time zone. + + Implemented by `datetime.time`. + + Operators: + + __repr__, __str__ + __cmp__, __hash__ + """ + + hour = Attribute("Hour in range(24)") + + minute = Attribute("Minute in range(60)") + + second = Attribute("Second in range(60)") + + microsecond = Attribute("Microsecond in range(1000000)") + + tzinfo = Attribute( + """The object passed as the tzinfo argument to the time constructor + or None if none was passed.""") + + def replace(hour, minute, second, microsecond, tzinfo): + """Return a time with the same value. + + Except for those members given new values by whichever keyword + arguments are specified. Note that tzinfo=None can be specified + to create a naive time from an aware time, without conversion of the + time members. + """ + + def isoformat(): + """Return a string representing the time in ISO 8601 format. + + That is HH:MM:SS.mmmmmm or, if self.microsecond is 0, HH:MM:SS + If utcoffset() does not return None, a 6-character string is appended, + giving the UTC offset in (signed) hours and minutes: + HH:MM:SS.mmmmmm+HH:MM or, if self.microsecond is 0, HH:MM:SS+HH:MM + """ + + def __str__(): + """For a time t, str(t) is equivalent to t.isoformat().""" + + def strftime(format): + """Return a string representing the time. + + This is controlled by an explicit format string. + """ + + def utcoffset(): + """Return the timezone offset in minutes east of UTC (negative west of + UTC). + + If tzinfo is None, returns None, else returns + self.tzinfo.utcoffset(None), and raises an exception if the latter + doesn't return None or a timedelta object representing a whole number + of minutes with magnitude less than one day. + """ + + def dst(): + """Return 0 if DST is not in effect, or the DST offset (in minutes + eastward) if DST is in effect. + + If tzinfo is None, returns None, else returns self.tzinfo.dst(None), + and raises an exception if the latter doesn't return None, or a + timedelta object representing a whole number of minutes with + magnitude less than one day. + """ + + def tzname(): + """Return the timezone name. + + If tzinfo is None, returns None, else returns self.tzinfo.tzname(None), + or raises an exception if the latter doesn't return None or a string + object. + """ + + +class ITZInfo(Interface): + """Time zone info class. + """ + + def utcoffset(dt): + """Return offset of local time from UTC, in minutes east of UTC. + + If local time is west of UTC, this should be negative. + Note that this is intended to be the total offset from UTC; + for example, if a tzinfo object represents both time zone and DST + adjustments, utcoffset() should return their sum. If the UTC offset + isn't known, return None. Else the value returned must be a timedelta + object specifying a whole number of minutes in the range -1439 to 1439 + inclusive (1440 = 24*60; the magnitude of the offset must be less + than one day). + """ + + def dst(dt): + """Return the daylight saving time (DST) adjustment, in minutes east + of UTC, or None if DST information isn't known. + """ + + def tzname(dt): + """Return the time zone name corresponding to the datetime object as + a string. + """ + + def fromutc(dt): + """Return an equivalent datetime in self's local time.""" + + +classImplements(timedelta, ITimeDelta) +classImplements(date, IDate) +classImplements(datetime, IDateTime) +classImplements(time, ITime) +classImplements(tzinfo, ITZInfo) + +## directlyProvides(timedelta, ITimeDeltaClass) +## directlyProvides(date, IDateClass) +## directlyProvides(datetime, IDateTimeClass) +## directlyProvides(time, ITimeClass) diff --git a/lib/zope/interface/common/interfaces.py b/lib/zope/interface/common/interfaces.py new file mode 100644 index 0000000..4308e0a --- /dev/null +++ b/lib/zope/interface/common/interfaces.py @@ -0,0 +1,212 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Interfaces for standard python exceptions +""" +from zope.interface import Interface +from zope.interface import classImplements + +class IException(Interface): + "Interface for `Exception`" +classImplements(Exception, IException) + + +class IStandardError(IException): + "Interface for `StandardError` (Python 2 only.)" +try: + classImplements(StandardError, IStandardError) +except NameError: #pragma NO COVER + pass # StandardError does not exist in Python 3 + + +class IWarning(IException): + "Interface for `Warning`" +classImplements(Warning, IWarning) + + +class ISyntaxError(IStandardError): + "Interface for `SyntaxError`" +classImplements(SyntaxError, ISyntaxError) + + +class ILookupError(IStandardError): + "Interface for `LookupError`" +classImplements(LookupError, ILookupError) + + +class IValueError(IStandardError): + "Interface for `ValueError`" +classImplements(ValueError, IValueError) + + +class IRuntimeError(IStandardError): + "Interface for `RuntimeError`" +classImplements(RuntimeError, IRuntimeError) + + +class IArithmeticError(IStandardError): + "Interface for `ArithmeticError`" +classImplements(ArithmeticError, IArithmeticError) + + +class IAssertionError(IStandardError): + "Interface for `AssertionError`" +classImplements(AssertionError, IAssertionError) + + +class IAttributeError(IStandardError): + "Interface for `AttributeError`" +classImplements(AttributeError, IAttributeError) + + +class IDeprecationWarning(IWarning): + "Interface for `DeprecationWarning`" +classImplements(DeprecationWarning, IDeprecationWarning) + + +class IEOFError(IStandardError): + "Interface for `EOFError`" +classImplements(EOFError, IEOFError) + + +class IEnvironmentError(IStandardError): + "Interface for `EnvironmentError`" +classImplements(EnvironmentError, IEnvironmentError) + + +class IFloatingPointError(IArithmeticError): + "Interface for `FloatingPointError`" +classImplements(FloatingPointError, IFloatingPointError) + + +class IIOError(IEnvironmentError): + "Interface for `IOError`" +classImplements(IOError, IIOError) + + +class IImportError(IStandardError): + "Interface for `ImportError`" +classImplements(ImportError, IImportError) + + +class IIndentationError(ISyntaxError): + "Interface for `IndentationError`" +classImplements(IndentationError, IIndentationError) + + +class IIndexError(ILookupError): + "Interface for `IndexError`" +classImplements(IndexError, IIndexError) + + +class IKeyError(ILookupError): + "Interface for `KeyError`" +classImplements(KeyError, IKeyError) + + +class IKeyboardInterrupt(IStandardError): + "Interface for `KeyboardInterrupt`" +classImplements(KeyboardInterrupt, IKeyboardInterrupt) + + +class IMemoryError(IStandardError): + "Interface for `MemoryError`" +classImplements(MemoryError, IMemoryError) + + +class INameError(IStandardError): + "Interface for `NameError`" +classImplements(NameError, INameError) + + +class INotImplementedError(IRuntimeError): + "Interface for `NotImplementedError`" +classImplements(NotImplementedError, INotImplementedError) + + +class IOSError(IEnvironmentError): + "Interface for `OSError`" +classImplements(OSError, IOSError) + + +class IOverflowError(IArithmeticError): + "Interface for `ArithmeticError`" +classImplements(OverflowError, IOverflowError) + + +class IOverflowWarning(IWarning): + """Deprecated, no standard class implements this. + + This was the interface for ``OverflowWarning`` prior to Python 2.5, + but that class was removed for all versions after that. + """ + + +class IReferenceError(IStandardError): + "Interface for `ReferenceError`" +classImplements(ReferenceError, IReferenceError) + + +class IRuntimeWarning(IWarning): + "Interface for `RuntimeWarning`" +classImplements(RuntimeWarning, IRuntimeWarning) + + +class IStopIteration(IException): + "Interface for `StopIteration`" +classImplements(StopIteration, IStopIteration) + + +class ISyntaxWarning(IWarning): + "Interface for `SyntaxWarning`" +classImplements(SyntaxWarning, ISyntaxWarning) + + +class ISystemError(IStandardError): + "Interface for `SystemError`" +classImplements(SystemError, ISystemError) + + +class ISystemExit(IException): + "Interface for `SystemExit`" +classImplements(SystemExit, ISystemExit) + + +class ITabError(IIndentationError): + "Interface for `TabError`" +classImplements(TabError, ITabError) + + +class ITypeError(IStandardError): + "Interface for `TypeError`" +classImplements(TypeError, ITypeError) + + +class IUnboundLocalError(INameError): + "Interface for `UnboundLocalError`" +classImplements(UnboundLocalError, IUnboundLocalError) + + +class IUnicodeError(IValueError): + "Interface for `UnicodeError`" +classImplements(UnicodeError, IUnicodeError) + + +class IUserWarning(IWarning): + "Interface for `UserWarning`" +classImplements(UserWarning, IUserWarning) + + +class IZeroDivisionError(IArithmeticError): + "Interface for `ZeroDivisionError`" +classImplements(ZeroDivisionError, IZeroDivisionError) diff --git a/lib/zope/interface/common/mapping.py b/lib/zope/interface/common/mapping.py new file mode 100644 index 0000000..1c5661a --- /dev/null +++ b/lib/zope/interface/common/mapping.py @@ -0,0 +1,150 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Mapping Interfaces. + +Importing this module does *not* mark any standard classes +as implementing any of these interfaces. +""" +from zope.interface import Interface + +class IItemMapping(Interface): + """Simplest readable mapping object + """ + + def __getitem__(key): + """Get a value for a key + + A `KeyError` is raised if there is no value for the key. + """ + + +class IReadMapping(IItemMapping): + """Basic mapping interface + """ + + def get(key, default=None): + """Get a value for a key + + The default is returned if there is no value for the key. + """ + + def __contains__(key): + """Tell if a key exists in the mapping.""" + + +class IWriteMapping(Interface): + """Mapping methods for changing data""" + + def __delitem__(key): + """Delete a value from the mapping using the key.""" + + def __setitem__(key, value): + """Set a new item in the mapping.""" + + +class IEnumerableMapping(IReadMapping): + """Mapping objects whose items can be enumerated. + """ + + def keys(): + """Return the keys of the mapping object. + """ + + def __iter__(): + """Return an iterator for the keys of the mapping object. + """ + + def values(): + """Return the values of the mapping object. + """ + + def items(): + """Return the items of the mapping object. + """ + + def __len__(): + """Return the number of items. + """ + +class IMapping(IWriteMapping, IEnumerableMapping): + ''' Simple mapping interface ''' + +class IIterableMapping(IEnumerableMapping): + """A mapping that has distinct methods for iterating + without copying. + + On Python 2, a `dict` has these methods, but on Python 3 + the methods defined in `IEnumerableMapping` already iterate + without copying. + """ + + def iterkeys(): + "iterate over keys; equivalent to ``__iter__``" + + def itervalues(): + "iterate over values" + + def iteritems(): + "iterate over items" + +class IClonableMapping(Interface): + """Something that can produce a copy of itself. + + This is available in `dict`. + """ + + def copy(): + "return copy of dict" + +class IExtendedReadMapping(IIterableMapping): + """ + Something with a particular method equivalent to ``__contains__``. + + On Python 2, `dict` provides this method, but it was removed + in Python 3. + """ + + def has_key(key): + """Tell if a key exists in the mapping; equivalent to ``__contains__``""" + +class IExtendedWriteMapping(IWriteMapping): + """Additional mutation methods. + + These are all provided by `dict`. + """ + + def clear(): + "delete all items" + + def update(d): + " Update D from E: for k in E.keys(): D[k] = E[k]" + + def setdefault(key, default=None): + "D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D" + + def pop(k, *args): + """Remove specified key and return the corresponding value. + + ``*args`` may contain a single default value, or may not be supplied. + If key is not found, default is returned if given, otherwise + `KeyError` is raised""" + + def popitem(): + """remove and return some (key, value) pair as a + 2-tuple; but raise KeyError if mapping is empty""" + +class IFullMapping( + IExtendedReadMapping, IExtendedWriteMapping, IClonableMapping, IMapping): + ''' Full mapping interface ''' # IMapping included so tests for IMapping + # succeed with IFullMapping diff --git a/lib/zope/interface/common/sequence.py b/lib/zope/interface/common/sequence.py new file mode 100644 index 0000000..393918e --- /dev/null +++ b/lib/zope/interface/common/sequence.py @@ -0,0 +1,165 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Sequence Interfaces + +Importing this module does *not* mark any standard classes +as implementing any of these interfaces. +""" + +__docformat__ = 'restructuredtext' +from zope.interface import Interface + +class IMinimalSequence(Interface): + """Most basic sequence interface. + + All sequences are iterable. This requires at least one of the + following: + + - a `__getitem__()` method that takes a single argument; integer + values starting at 0 must be supported, and `IndexError` should + be raised for the first index for which there is no value, or + + - an `__iter__()` method that returns an iterator as defined in + the Python documentation (http://docs.python.org/lib/typeiter.html). + + """ + + def __getitem__(index): + """``x.__getitem__(index) <==> x[index]`` + + Declaring this interface does not specify whether `__getitem__` + supports slice objects.""" + +class IFiniteSequence(IMinimalSequence): + + def __len__(): + """``x.__len__() <==> len(x)``""" + +class IReadSequence(IFiniteSequence): + """read interface shared by tuple and list""" + + def __contains__(item): + """``x.__contains__(item) <==> item in x``""" + + def __lt__(other): + """``x.__lt__(other) <==> x < other``""" + + def __le__(other): + """``x.__le__(other) <==> x <= other``""" + + def __eq__(other): + """``x.__eq__(other) <==> x == other``""" + + def __ne__(other): + """``x.__ne__(other) <==> x != other``""" + + def __gt__(other): + """``x.__gt__(other) <==> x > other``""" + + def __ge__(other): + """``x.__ge__(other) <==> x >= other``""" + + def __add__(other): + """``x.__add__(other) <==> x + other``""" + + def __mul__(n): + """``x.__mul__(n) <==> x * n``""" + + def __rmul__(n): + """``x.__rmul__(n) <==> n * x``""" + + def __getslice__(i, j): + """``x.__getslice__(i, j) <==> x[i:j]`` + + Use of negative indices is not supported. + + Deprecated since Python 2.0 but still a part of `UserList`. + """ + +class IExtendedReadSequence(IReadSequence): + """Full read interface for lists""" + + def count(item): + """Return number of occurrences of value""" + + def index(item, *args): + """index(value, [start, [stop]]) -> int + + Return first index of *value* + """ + +class IUniqueMemberWriteSequence(Interface): + """The write contract for a sequence that may enforce unique members""" + + def __setitem__(index, item): + """``x.__setitem__(index, item) <==> x[index] = item`` + + Declaring this interface does not specify whether `__setitem__` + supports slice objects. + """ + + def __delitem__(index): + """``x.__delitem__(index) <==> del x[index]`` + + Declaring this interface does not specify whether `__delitem__` + supports slice objects. + """ + + def __setslice__(i, j, other): + """``x.__setslice__(i, j, other) <==> x[i:j] = other`` + + Use of negative indices is not supported. + + Deprecated since Python 2.0 but still a part of `UserList`. + """ + + def __delslice__(i, j): + """``x.__delslice__(i, j) <==> del x[i:j]`` + + Use of negative indices is not supported. + + Deprecated since Python 2.0 but still a part of `UserList`. + """ + def __iadd__(y): + """``x.__iadd__(y) <==> x += y``""" + + def append(item): + """Append item to end""" + + def insert(index, item): + """Insert item before index""" + + def pop(index=-1): + """Remove and return item at index (default last)""" + + def remove(item): + """Remove first occurrence of value""" + + def reverse(): + """Reverse *IN PLACE*""" + + def sort(cmpfunc=None): + """Stable sort *IN PLACE*; `cmpfunc(x, y)` -> -1, 0, 1""" + + def extend(iterable): + """Extend list by appending elements from the iterable""" + +class IWriteSequence(IUniqueMemberWriteSequence): + """Full write contract for sequences""" + + def __imul__(n): + """``x.__imul__(n) <==> x *= n``""" + +class ISequence(IReadSequence, IWriteSequence): + """Full sequence contract""" diff --git a/lib/zope/interface/common/tests/__init__.py b/lib/zope/interface/common/tests/__init__.py new file mode 100644 index 0000000..b711d36 --- /dev/null +++ b/lib/zope/interface/common/tests/__init__.py @@ -0,0 +1,2 @@ +# +# This file is necessary to make this directory a package. diff --git a/lib/zope/interface/common/tests/basemapping.py b/lib/zope/interface/common/tests/basemapping.py new file mode 100644 index 0000000..b756dca --- /dev/null +++ b/lib/zope/interface/common/tests/basemapping.py @@ -0,0 +1,107 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Base Mapping tests +""" +from operator import __getitem__ + +def testIReadMapping(self, inst, state, absent): + for key in state: + self.assertEqual(inst[key], state[key]) + self.assertEqual(inst.get(key, None), state[key]) + self.assertTrue(key in inst) + + for key in absent: + self.assertEqual(inst.get(key, None), None) + self.assertEqual(inst.get(key), None) + self.assertEqual(inst.get(key, self), self) + self.assertRaises(KeyError, __getitem__, inst, key) + + +def test_keys(self, inst, state): + # Return the keys of the mapping object + inst_keys = list(inst.keys()); inst_keys.sort() + state_keys = list(state.keys()) ; state_keys.sort() + self.assertEqual(inst_keys, state_keys) + +def test_iter(self, inst, state): + # Return the keys of the mapping object + inst_keys = list(inst); inst_keys.sort() + state_keys = list(state.keys()) ; state_keys.sort() + self.assertEqual(inst_keys, state_keys) + +def test_values(self, inst, state): + # Return the values of the mapping object + inst_values = list(inst.values()); inst_values.sort() + state_values = list(state.values()) ; state_values.sort() + self.assertEqual(inst_values, state_values) + +def test_items(self, inst, state): + # Return the items of the mapping object + inst_items = list(inst.items()); inst_items.sort() + state_items = list(state.items()) ; state_items.sort() + self.assertEqual(inst_items, state_items) + +def test___len__(self, inst, state): + # Return the number of items + self.assertEqual(len(inst), len(state)) + +def testIEnumerableMapping(self, inst, state): + test_keys(self, inst, state) + test_items(self, inst, state) + test_values(self, inst, state) + test___len__(self, inst, state) + + +class BaseTestIReadMapping(object): + def testIReadMapping(self): + inst = self._IReadMapping__sample() + state = self._IReadMapping__stateDict() + absent = self._IReadMapping__absentKeys() + testIReadMapping(self, inst, state, absent) + + +class BaseTestIEnumerableMapping(BaseTestIReadMapping): + # Mapping objects whose items can be enumerated + def test_keys(self): + # Return the keys of the mapping object + inst = self._IEnumerableMapping__sample() + state = self._IEnumerableMapping__stateDict() + test_keys(self, inst, state) + + def test_values(self): + # Return the values of the mapping object + inst = self._IEnumerableMapping__sample() + state = self._IEnumerableMapping__stateDict() + test_values(self, inst, state) + + def test_items(self): + # Return the items of the mapping object + inst = self._IEnumerableMapping__sample() + state = self._IEnumerableMapping__stateDict() + test_items(self, inst, state) + + def test___len__(self): + # Return the number of items + inst = self._IEnumerableMapping__sample() + state = self._IEnumerableMapping__stateDict() + test___len__(self, inst, state) + + def _IReadMapping__stateDict(self): + return self._IEnumerableMapping__stateDict() + + def _IReadMapping__sample(self): + return self._IEnumerableMapping__sample() + + def _IReadMapping__absentKeys(self): + return self._IEnumerableMapping__absentKeys() diff --git a/lib/zope/interface/common/tests/test_idatetime.py b/lib/zope/interface/common/tests/test_idatetime.py new file mode 100644 index 0000000..496a5c9 --- /dev/null +++ b/lib/zope/interface/common/tests/test_idatetime.py @@ -0,0 +1,37 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Test for datetime interfaces +""" + +import unittest + +from zope.interface.verify import verifyObject, verifyClass +from zope.interface.common.idatetime import ITimeDelta, ITimeDeltaClass +from zope.interface.common.idatetime import IDate, IDateClass +from zope.interface.common.idatetime import IDateTime, IDateTimeClass +from zope.interface.common.idatetime import ITime, ITimeClass, ITZInfo +from datetime import timedelta, date, datetime, time, tzinfo + +class TestDateTimeInterfaces(unittest.TestCase): + + def test_interfaces(self): + verifyObject(ITimeDelta, timedelta(minutes=20)) + verifyObject(IDate, date(2000, 1, 2)) + verifyObject(IDateTime, datetime(2000, 1, 2, 10, 20)) + verifyObject(ITime, time(20, 30, 15, 1234)) + verifyObject(ITZInfo, tzinfo()) + verifyClass(ITimeDeltaClass, timedelta) + verifyClass(IDateClass, date) + verifyClass(IDateTimeClass, datetime) + verifyClass(ITimeClass, time) diff --git a/lib/zope/interface/common/tests/test_import_interfaces.py b/lib/zope/interface/common/tests/test_import_interfaces.py new file mode 100644 index 0000000..fe3766f --- /dev/null +++ b/lib/zope/interface/common/tests/test_import_interfaces.py @@ -0,0 +1,20 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +import unittest + +class TestInterfaceImport(unittest.TestCase): + + def test_import(self): + import zope.interface.common.interfaces as x + self.assertIsNotNone(x) diff --git a/lib/zope/interface/declarations.py b/lib/zope/interface/declarations.py new file mode 100644 index 0000000..b80245f --- /dev/null +++ b/lib/zope/interface/declarations.py @@ -0,0 +1,929 @@ +############################################################################## +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +############################################################################## +"""Implementation of interface declarations + +There are three flavors of declarations: + + - Declarations are used to simply name declared interfaces. + + - ImplementsDeclarations are used to express the interfaces that a + class implements (that instances of the class provides). + + Implements specifications support inheriting interfaces. + + - ProvidesDeclarations are used to express interfaces directly + provided by objects. + +""" +__docformat__ = 'restructuredtext' + +import sys +from types import FunctionType +from types import MethodType +from types import ModuleType +import weakref + +from zope.interface.advice import addClassAdvisor +from zope.interface.interface import InterfaceClass +from zope.interface.interface import SpecificationBase +from zope.interface.interface import Specification +from zope.interface._compat import CLASS_TYPES as DescriptorAwareMetaClasses +from zope.interface._compat import PYTHON3 + +# Registry of class-implementation specifications +BuiltinImplementationSpecifications = {} + +_ADVICE_ERROR = ('Class advice impossible in Python3. ' + 'Use the @%s class decorator instead.') + +_ADVICE_WARNING = ('The %s API is deprecated, and will not work in Python3 ' + 'Use the @%s class decorator instead.') + +class named(object): + + def __init__(self, name): + self.name = name + + def __call__(self, ob): + ob.__component_name__ = self.name + return ob + +class Declaration(Specification): + """Interface declarations""" + + def __init__(self, *interfaces): + Specification.__init__(self, _normalizeargs(interfaces)) + + def changed(self, originally_changed): + Specification.changed(self, originally_changed) + try: + del self._v_attrs + except AttributeError: + pass + + def __contains__(self, interface): + """Test whether an interface is in the specification + """ + + return self.extends(interface) and interface in self.interfaces() + + def __iter__(self): + """Return an iterator for the interfaces in the specification + """ + return self.interfaces() + + def flattened(self): + """Return an iterator of all included and extended interfaces + """ + return iter(self.__iro__) + + def __sub__(self, other): + """Remove interfaces from a specification + """ + return Declaration( + *[i for i in self.interfaces() + if not [j for j in other.interfaces() + if i.extends(j, 0)] + ] + ) + + def __add__(self, other): + """Add two specifications or a specification and an interface + """ + seen = {} + result = [] + for i in self.interfaces(): + seen[i] = 1 + result.append(i) + for i in other.interfaces(): + if i not in seen: + seen[i] = 1 + result.append(i) + + return Declaration(*result) + + __radd__ = __add__ + + +############################################################################## +# +# Implementation specifications +# +# These specify interfaces implemented by instances of classes + +class Implements(Declaration): + + # class whose specification should be used as additional base + inherit = None + + # interfaces actually declared for a class + declared = () + + __name__ = '?' + + @classmethod + def named(cls, name, *interfaces): + # Implementation method: Produce an Implements interface with + # a fully fleshed out __name__ before calling the constructor, which + # sets bases to the given interfaces and which may pass this object to + # other objects (e.g., to adjust dependents). If they're sorting or comparing + # by name, this needs to be set. + inst = cls.__new__(cls) + inst.__name__ = name + inst.__init__(*interfaces) + return inst + + def __repr__(self): + return '<implementedBy %s>' % (self.__name__) + + def __reduce__(self): + return implementedBy, (self.inherit, ) + + def __cmp(self, other): + # Yes, I did mean to name this __cmp, rather than __cmp__. + # It is a private method used by __lt__ and __gt__. + # This is based on, and compatible with, InterfaceClass. + # (The two must be mutually comparable to be able to work in e.g., BTrees.) + # Instances of this class generally don't have a __module__ other than + # `zope.interface.declarations`, whereas they *do* have a __name__ that is the + # fully qualified name of the object they are representing. + + # Note, though, that equality and hashing are still identity based. This + # accounts for things like nested objects that have the same name (typically + # only in tests) and is consistent with pickling. As far as comparisons to InterfaceClass + # goes, we'll never have equal name and module to those, so we're still consistent there. + # Instances of this class are essentially intended to be unique and are + # heavily cached (note how our __reduce__ handles this) so having identity + # based hash and eq should also work. + if other is None: + return -1 + + n1 = (self.__name__, self.__module__) + n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', '')) + + # This spelling works under Python3, which doesn't have cmp(). + return (n1 > n2) - (n1 < n2) + + def __hash__(self): + return Declaration.__hash__(self) + + # We want equality to be based on identity. However, we can't actually + # implement __eq__/__ne__ to do this because sometimes we get wrapped in a proxy. + # We need to let the proxy types implement these methods so they can handle unwrapping + # and then rely on: (1) the interpreter automatically changing `implements == proxy` into + # `proxy == implements` (which will call proxy.__eq__ to do the unwrapping) and then + # (2) the default equality semantics being identity based. + + def __lt__(self, other): + c = self.__cmp(other) + return c < 0 + + def __le__(self, other): + c = self.__cmp(other) + return c <= 0 + + def __gt__(self, other): + c = self.__cmp(other) + return c > 0 + + def __ge__(self, other): + c = self.__cmp(other) + return c >= 0 + +def _implements_name(ob): + # Return the __name__ attribute to be used by its __implemented__ + # property. + # This must be stable for the "same" object across processes + # because it is used for sorting. It needn't be unique, though, in cases + # like nested classes named Foo created by different functions, because + # equality and hashing is still based on identity. + # It might be nice to use __qualname__ on Python 3, but that would produce + # different values between Py2 and Py3. + return (getattr(ob, '__module__', '?') or '?') + \ + '.' + (getattr(ob, '__name__', '?') or '?') + +def implementedByFallback(cls): + """Return the interfaces implemented for a class' instances + + The value returned is an `~zope.interface.interfaces.IDeclaration`. + """ + try: + spec = cls.__dict__.get('__implemented__') + except AttributeError: + + # we can't get the class dict. This is probably due to a + # security proxy. If this is the case, then probably no + # descriptor was installed for the class. + + # We don't want to depend directly on zope.security in + # zope.interface, but we'll try to make reasonable + # accommodations in an indirect way. + + # We'll check to see if there's an implements: + + spec = getattr(cls, '__implemented__', None) + if spec is None: + # There's no spec stred in the class. Maybe its a builtin: + spec = BuiltinImplementationSpecifications.get(cls) + if spec is not None: + return spec + return _empty + + if spec.__class__ == Implements: + # we defaulted to _empty or there was a spec. Good enough. + # Return it. + return spec + + # TODO: need old style __implements__ compatibility? + # Hm, there's an __implemented__, but it's not a spec. Must be + # an old-style declaration. Just compute a spec for it + return Declaration(*_normalizeargs((spec, ))) + + if isinstance(spec, Implements): + return spec + + if spec is None: + spec = BuiltinImplementationSpecifications.get(cls) + if spec is not None: + return spec + + # TODO: need old style __implements__ compatibility? + spec_name = _implements_name(cls) + if spec is not None: + # old-style __implemented__ = foo declaration + spec = (spec, ) # tuplefy, as it might be just an int + spec = Implements.named(spec_name, *_normalizeargs(spec)) + spec.inherit = None # old-style implies no inherit + del cls.__implemented__ # get rid of the old-style declaration + else: + try: + bases = cls.__bases__ + except AttributeError: + if not callable(cls): + raise TypeError("ImplementedBy called for non-factory", cls) + bases = () + + spec = Implements.named(spec_name, *[implementedBy(c) for c in bases]) + spec.inherit = cls + + try: + cls.__implemented__ = spec + if not hasattr(cls, '__providedBy__'): + cls.__providedBy__ = objectSpecificationDescriptor + + if (isinstance(cls, DescriptorAwareMetaClasses) + and + '__provides__' not in cls.__dict__): + # Make sure we get a __provides__ descriptor + cls.__provides__ = ClassProvides( + cls, + getattr(cls, '__class__', type(cls)), + ) + + except TypeError: + if not isinstance(cls, type): + raise TypeError("ImplementedBy called for non-type", cls) + BuiltinImplementationSpecifications[cls] = spec + + return spec + +implementedBy = implementedByFallback + +def classImplementsOnly(cls, *interfaces): + """Declare the only interfaces implemented by instances of a class + + The arguments after the class are one or more interfaces or interface + specifications (`~zope.interface.interfaces.IDeclaration` objects). + + The interfaces given (including the interfaces in the specifications) + replace any previous declarations. + """ + spec = implementedBy(cls) + spec.declared = () + spec.inherit = None + classImplements(cls, *interfaces) + +def classImplements(cls, *interfaces): + """Declare additional interfaces implemented for instances of a class + + The arguments after the class are one or more interfaces or + interface specifications (`~zope.interface.interfaces.IDeclaration` objects). + + The interfaces given (including the interfaces in the specifications) + are added to any interfaces previously declared. + """ + spec = implementedBy(cls) + spec.declared += tuple(_normalizeargs(interfaces)) + + # compute the bases + bases = [] + seen = {} + for b in spec.declared: + if b not in seen: + seen[b] = 1 + bases.append(b) + + if spec.inherit is not None: + + for c in spec.inherit.__bases__: + b = implementedBy(c) + if b not in seen: + seen[b] = 1 + bases.append(b) + + spec.__bases__ = tuple(bases) + +def _implements_advice(cls): + interfaces, classImplements = cls.__dict__['__implements_advice_data__'] + del cls.__implements_advice_data__ + classImplements(cls, *interfaces) + return cls + + +class implementer(object): + """Declare the interfaces implemented by instances of a class. + + This function is called as a class decorator. + + The arguments are one or more interfaces or interface + specifications (`~zope.interface.interfaces.IDeclaration` objects). + + The interfaces given (including the interfaces in the + specifications) are added to any interfaces previously + declared. + + Previous declarations include declarations for base classes + unless implementsOnly was used. + + This function is provided for convenience. It provides a more + convenient way to call `classImplements`. For example:: + + @implementer(I1) + class C(object): + pass + + is equivalent to calling:: + + classImplements(C, I1) + + after the class has been created. + """ + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + if isinstance(ob, DescriptorAwareMetaClasses): + classImplements(ob, *self.interfaces) + return ob + + spec_name = _implements_name(ob) + spec = Implements.named(spec_name, *self.interfaces) + try: + ob.__implemented__ = spec + except AttributeError: + raise TypeError("Can't declare implements", ob) + return ob + +class implementer_only(object): + """Declare the only interfaces implemented by instances of a class + + This function is called as a class decorator. + + The arguments are one or more interfaces or interface + specifications (`~zope.interface.interfaces.IDeclaration` objects). + + Previous declarations including declarations for base classes + are overridden. + + This function is provided for convenience. It provides a more + convenient way to call `classImplementsOnly`. For example:: + + @implementer_only(I1) + class C(object): pass + + is equivalent to calling:: + + classImplementsOnly(I1) + + after the class has been created. + """ + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + if isinstance(ob, (FunctionType, MethodType)): + # XXX Does this decorator make sense for anything but classes? + # I don't think so. There can be no inheritance of interfaces + # on a method pr function.... + raise ValueError('The implementer_only decorator is not ' + 'supported for methods or functions.') + else: + # Assume it's a class: + classImplementsOnly(ob, *self.interfaces) + return ob + +def _implements(name, interfaces, classImplements): + # This entire approach is invalid under Py3K. Don't even try to fix + # the coverage for this block there. :( + frame = sys._getframe(2) + locals = frame.f_locals + + # Try to make sure we were called from a class def. In 2.2.0 we can't + # check for __module__ since it doesn't seem to be added to the locals + # until later on. + if locals is frame.f_globals or '__module__' not in locals: + raise TypeError(name+" can be used only from a class definition.") + + if '__implements_advice_data__' in locals: + raise TypeError(name+" can be used only once in a class definition.") + + locals['__implements_advice_data__'] = interfaces, classImplements + addClassAdvisor(_implements_advice, depth=3) + +def implements(*interfaces): + """Declare interfaces implemented by instances of a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (`~zope.interface.interfaces.IDeclaration` objects). + + The interfaces given (including the interfaces in the + specifications) are added to any interfaces previously + declared. + + Previous declarations include declarations for base classes + unless `implementsOnly` was used. + + This function is provided for convenience. It provides a more + convenient way to call `classImplements`. For example:: + + implements(I1) + + is equivalent to calling:: + + classImplements(C, I1) + + after the class has been created. + """ + # This entire approach is invalid under Py3K. Don't even try to fix + # the coverage for this block there. :( + if PYTHON3: + raise TypeError(_ADVICE_ERROR % 'implementer') + _implements("implements", interfaces, classImplements) + +def implementsOnly(*interfaces): + """Declare the only interfaces implemented by instances of a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (`~zope.interface.interfaces.IDeclaration` objects). + + Previous declarations including declarations for base classes + are overridden. + + This function is provided for convenience. It provides a more + convenient way to call `classImplementsOnly`. For example:: + + implementsOnly(I1) + + is equivalent to calling:: + + classImplementsOnly(I1) + + after the class has been created. + """ + # This entire approach is invalid under Py3K. Don't even try to fix + # the coverage for this block there. :( + if PYTHON3: + raise TypeError(_ADVICE_ERROR % 'implementer_only') + _implements("implementsOnly", interfaces, classImplementsOnly) + +############################################################################## +# +# Instance declarations + +class Provides(Declaration): # Really named ProvidesClass + """Implement ``__provides__``, the instance-specific specification + + When an object is pickled, we pickle the interfaces that it implements. + """ + + def __init__(self, cls, *interfaces): + self.__args = (cls, ) + interfaces + self._cls = cls + Declaration.__init__(self, *(interfaces + (implementedBy(cls), ))) + + def __reduce__(self): + return Provides, self.__args + + __module__ = 'zope.interface' + + def __get__(self, inst, cls): + """Make sure that a class __provides__ doesn't leak to an instance + """ + if inst is None and cls is self._cls: + # We were accessed through a class, so we are the class' + # provides spec. Just return this object, but only if we are + # being called on the same class that we were defined for: + return self + + raise AttributeError('__provides__') + +ProvidesClass = Provides + +# Registry of instance declarations +# This is a memory optimization to allow objects to share specifications. +InstanceDeclarations = weakref.WeakValueDictionary() + +def Provides(*interfaces): + """Cache instance declarations + + Instance declarations are shared among instances that have the same + declaration. The declarations are cached in a weak value dictionary. + """ + spec = InstanceDeclarations.get(interfaces) + if spec is None: + spec = ProvidesClass(*interfaces) + InstanceDeclarations[interfaces] = spec + + return spec + +Provides.__safe_for_unpickling__ = True + + +def directlyProvides(object, *interfaces): + """Declare interfaces declared directly for an object + + The arguments after the object are one or more interfaces or interface + specifications (`~zope.interface.interfaces.IDeclaration` objects). + + The interfaces given (including the interfaces in the specifications) + replace interfaces previously declared for the object. + """ + cls = getattr(object, '__class__', None) + if cls is not None and getattr(cls, '__class__', None) is cls: + # It's a meta class (well, at least it it could be an extension class) + # Note that we can't get here from Py3k tests: there is no normal + # class which isn't descriptor aware. + if not isinstance(object, + DescriptorAwareMetaClasses): + raise TypeError("Attempt to make an interface declaration on a " + "non-descriptor-aware class") + + interfaces = _normalizeargs(interfaces) + if cls is None: + cls = type(object) + + issub = False + for damc in DescriptorAwareMetaClasses: + if issubclass(cls, damc): + issub = True + break + if issub: + # we have a class or type. We'll use a special descriptor + # that provides some extra caching + object.__provides__ = ClassProvides(object, cls, *interfaces) + else: + object.__provides__ = Provides(cls, *interfaces) + + +def alsoProvides(object, *interfaces): + """Declare interfaces declared directly for an object + + The arguments after the object are one or more interfaces or interface + specifications (`~zope.interface.interfaces.IDeclaration` objects). + + The interfaces given (including the interfaces in the specifications) are + added to the interfaces previously declared for the object. + """ + directlyProvides(object, directlyProvidedBy(object), *interfaces) + +def noLongerProvides(object, interface): + """ Removes a directly provided interface from an object. + """ + directlyProvides(object, directlyProvidedBy(object) - interface) + if interface.providedBy(object): + raise ValueError("Can only remove directly provided interfaces.") + +class ClassProvidesBaseFallback(object): + + def __get__(self, inst, cls): + if cls is self._cls: + # We only work if called on the class we were defined for + + if inst is None: + # We were accessed through a class, so we are the class' + # provides spec. Just return this object as is: + return self + + return self._implements + + raise AttributeError('__provides__') + +ClassProvidesBasePy = ClassProvidesBaseFallback # BBB +ClassProvidesBase = ClassProvidesBaseFallback + +# Try to get C base: +try: + import zope.interface._zope_interface_coptimizations +except ImportError: + pass +else: + from zope.interface._zope_interface_coptimizations import ClassProvidesBase + + +class ClassProvides(Declaration, ClassProvidesBase): + """Special descriptor for class ``__provides__`` + + The descriptor caches the implementedBy info, so that + we can get declarations for objects without instance-specific + interfaces a bit quicker. + """ + def __init__(self, cls, metacls, *interfaces): + self._cls = cls + self._implements = implementedBy(cls) + self.__args = (cls, metacls, ) + interfaces + Declaration.__init__(self, *(interfaces + (implementedBy(metacls), ))) + + def __reduce__(self): + return self.__class__, self.__args + + # Copy base-class method for speed + __get__ = ClassProvidesBase.__get__ + +def directlyProvidedBy(object): + """Return the interfaces directly provided by the given object + + The value returned is an `~zope.interface.interfaces.IDeclaration`. + """ + provides = getattr(object, "__provides__", None) + if (provides is None # no spec + or + # We might have gotten the implements spec, as an + # optimization. If so, it's like having only one base, that we + # lop off to exclude class-supplied declarations: + isinstance(provides, Implements) + ): + return _empty + + # Strip off the class part of the spec: + return Declaration(provides.__bases__[:-1]) + +def classProvides(*interfaces): + """Declare interfaces provided directly by a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface specifications + (`~zope.interface.interfaces.IDeclaration` objects). + + The given interfaces (including the interfaces in the specifications) + are used to create the class's direct-object interface specification. + An error will be raised if the module class has an direct interface + specification. In other words, it is an error to call this function more + than once in a class definition. + + Note that the given interfaces have nothing to do with the interfaces + implemented by instances of the class. + + This function is provided for convenience. It provides a more convenient + way to call `directlyProvides` for a class. For example:: + + classProvides(I1) + + is equivalent to calling:: + + directlyProvides(theclass, I1) + + after the class has been created. + """ + # This entire approach is invalid under Py3K. Don't even try to fix + # the coverage for this block there. :( + + if PYTHON3: + raise TypeError(_ADVICE_ERROR % 'provider') + + frame = sys._getframe(1) + locals = frame.f_locals + + # Try to make sure we were called from a class def + if (locals is frame.f_globals) or ('__module__' not in locals): + raise TypeError("classProvides can be used only from a " + "class definition.") + + if '__provides__' in locals: + raise TypeError( + "classProvides can only be used once in a class definition.") + + locals["__provides__"] = _normalizeargs(interfaces) + + addClassAdvisor(_classProvides_advice, depth=2) + +def _classProvides_advice(cls): + # This entire approach is invalid under Py3K. Don't even try to fix + # the coverage for this block there. :( + interfaces = cls.__dict__['__provides__'] + del cls.__provides__ + directlyProvides(cls, *interfaces) + return cls + +class provider(object): + """Class decorator version of classProvides""" + + def __init__(self, *interfaces): + self.interfaces = interfaces + + def __call__(self, ob): + directlyProvides(ob, *self.interfaces) + return ob + +def moduleProvides(*interfaces): + """Declare interfaces provided by a module + + This function is used in a module definition. + + The arguments are one or more interfaces or interface specifications + (`~zope.interface.interfaces.IDeclaration` objects). + + The given interfaces (including the interfaces in the specifications) are + used to create the module's direct-object interface specification. An + error will be raised if the module already has an interface specification. + In other words, it is an error to call this function more than once in a + module definition. + + This function is provided for convenience. It provides a more convenient + way to call directlyProvides. For example:: + + moduleImplements(I1) + + is equivalent to:: + + directlyProvides(sys.modules[__name__], I1) + """ + frame = sys._getframe(1) + locals = frame.f_locals + + # Try to make sure we were called from a class def + if (locals is not frame.f_globals) or ('__name__' not in locals): + raise TypeError( + "moduleProvides can only be used from a module definition.") + + if '__provides__' in locals: + raise TypeError( + "moduleProvides can only be used once in a module definition.") + + locals["__provides__"] = Provides(ModuleType, + *_normalizeargs(interfaces)) + +############################################################################## +# +# Declaration querying support + +# XXX: is this a fossil? Nobody calls it, no unit tests exercise it, no +# doctests import it, and the package __init__ doesn't import it. +def ObjectSpecification(direct, cls): + """Provide object specifications + + These combine information for the object and for it's classes. + """ + return Provides(cls, direct) # pragma: no cover fossil + +def getObjectSpecificationFallback(ob): + + provides = getattr(ob, '__provides__', None) + if provides is not None: + if isinstance(provides, SpecificationBase): + return provides + + try: + cls = ob.__class__ + except AttributeError: + # We can't get the class, so just consider provides + return _empty + + return implementedBy(cls) + +getObjectSpecification = getObjectSpecificationFallback + +def providedByFallback(ob): + + # Here we have either a special object, an old-style declaration + # or a descriptor + + # Try to get __providedBy__ + try: + r = ob.__providedBy__ + except AttributeError: + # Not set yet. Fall back to lower-level thing that computes it + return getObjectSpecification(ob) + + try: + # We might have gotten a descriptor from an instance of a + # class (like an ExtensionClass) that doesn't support + # descriptors. We'll make sure we got one by trying to get + # the only attribute, which all specs have. + r.extends + + except AttributeError: + + # The object's class doesn't understand descriptors. + # Sigh. We need to get an object descriptor, but we have to be + # careful. We want to use the instance's __provides__, if + # there is one, but only if it didn't come from the class. + + try: + r = ob.__provides__ + except AttributeError: + # No __provides__, so just fall back to implementedBy + return implementedBy(ob.__class__) + + # We need to make sure we got the __provides__ from the + # instance. We'll do this by making sure we don't get the same + # thing from the class: + + try: + cp = ob.__class__.__provides__ + except AttributeError: + # The ob doesn't have a class or the class has no + # provides, assume we're done: + return r + + if r is cp: + # Oops, we got the provides from the class. This means + # the object doesn't have it's own. We should use implementedBy + return implementedBy(ob.__class__) + + return r +providedBy = providedByFallback + +class ObjectSpecificationDescriptorFallback(object): + """Implement the `__providedBy__` attribute + + The `__providedBy__` attribute computes the interfaces peovided by + an object. + """ + + def __get__(self, inst, cls): + """Get an object specification for an object + """ + if inst is None: + return getObjectSpecification(cls) + + provides = getattr(inst, '__provides__', None) + if provides is not None: + return provides + + return implementedBy(cls) + +ObjectSpecificationDescriptor = ObjectSpecificationDescriptorFallback + +############################################################################## + +def _normalizeargs(sequence, output = None): + """Normalize declaration arguments + + Normalization arguments might contain Declarions, tuples, or single + interfaces. + + Anything but individial interfaces or implements specs will be expanded. + """ + if output is None: + output = [] + + cls = sequence.__class__ + if InterfaceClass in cls.__mro__ or Implements in cls.__mro__: + output.append(sequence) + else: + for v in sequence: + _normalizeargs(v, output) + + return output + +_empty = Declaration() + +try: + import zope.interface._zope_interface_coptimizations +except ImportError: + pass +else: + from zope.interface._zope_interface_coptimizations import implementedBy + from zope.interface._zope_interface_coptimizations import providedBy + from zope.interface._zope_interface_coptimizations import ( + getObjectSpecification) + from zope.interface._zope_interface_coptimizations import ( + ObjectSpecificationDescriptor) + +objectSpecificationDescriptor = ObjectSpecificationDescriptor() diff --git a/lib/zope/interface/document.py b/lib/zope/interface/document.py new file mode 100644 index 0000000..e6d6e88 --- /dev/null +++ b/lib/zope/interface/document.py @@ -0,0 +1,120 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" Pretty-Print an Interface object as structured text (Yum) + +This module provides a function, asStructuredText, for rendering an +interface as structured text. +""" +import zope.interface + + +def asStructuredText(I, munge=0, rst=False): + """ Output structured text format. Note, this will whack any existing + 'structured' format of the text. + + If `rst=True`, then the output will quote all code as inline literals in + accordance with 'reStructuredText' markup principles. + """ + + if rst: + inline_literal = lambda s: "``%s``" % (s,) + else: + inline_literal = lambda s: s + + r = [inline_literal(I.getName())] + outp = r.append + level = 1 + + if I.getDoc(): + outp(_justify_and_indent(_trim_doc_string(I.getDoc()), level)) + + bases = [base + for base in I.__bases__ + if base is not zope.interface.Interface + ] + if bases: + outp(_justify_and_indent("This interface extends:", level, munge)) + level += 1 + for b in bases: + item = "o %s" % inline_literal(b.getName()) + outp(_justify_and_indent(_trim_doc_string(item), level, munge)) + level -= 1 + + namesAndDescriptions = sorted(I.namesAndDescriptions()) + + outp(_justify_and_indent("Attributes:", level, munge)) + level += 1 + for name, desc in namesAndDescriptions: + if not hasattr(desc, 'getSignatureString'): # ugh... + item = "%s -- %s" % (inline_literal(desc.getName()), + desc.getDoc() or 'no documentation') + outp(_justify_and_indent(_trim_doc_string(item), level, munge)) + level -= 1 + + outp(_justify_and_indent("Methods:", level, munge)) + level += 1 + for name, desc in namesAndDescriptions: + if hasattr(desc, 'getSignatureString'): # ugh... + _call = "%s%s" % (desc.getName(), desc.getSignatureString()) + item = "%s -- %s" % (inline_literal(_call), + desc.getDoc() or 'no documentation') + outp(_justify_and_indent(_trim_doc_string(item), level, munge)) + + return "\n\n".join(r) + "\n\n" + + +def asReStructuredText(I, munge=0): + """ Output reStructuredText format. Note, this will whack any existing + 'structured' format of the text.""" + return asStructuredText(I, munge=munge, rst=True) + + +def _trim_doc_string(text): + """ Trims a doc string to make it format + correctly with structured text. """ + + lines = text.replace('\r\n', '\n').split('\n') + nlines = [lines.pop(0)] + if lines: + min_indent = min([len(line) - len(line.lstrip()) + for line in lines]) + for line in lines: + nlines.append(line[min_indent:]) + + return '\n'.join(nlines) + + +def _justify_and_indent(text, level, munge=0, width=72): + """ indent and justify text, rejustify (munge) if specified """ + + indent = " " * level + + if munge: + lines = [] + line = indent + text = text.split() + + for word in text: + line = ' '.join([line, word]) + if len(line) > width: + lines.append(line) + line = indent + else: + lines.append(line) + + return '\n'.join(lines) + + else: + return indent + \ + text.strip().replace("\r\n", "\n") .replace("\n", "\n" + indent) diff --git a/lib/zope/interface/exceptions.py b/lib/zope/interface/exceptions.py new file mode 100644 index 0000000..e9a4788 --- /dev/null +++ b/lib/zope/interface/exceptions.py @@ -0,0 +1,67 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Interface-specific exceptions +""" + +class Invalid(Exception): + """A specification is violated + """ + +class DoesNotImplement(Invalid): + """ This object does not implement """ + def __init__(self, interface): + self.interface = interface + + def __str__(self): + return """An object does not implement interface %(interface)s + + """ % self.__dict__ + +class BrokenImplementation(Invalid): + """An attribute is not completely implemented. + """ + + def __init__(self, interface, name): + self.interface=interface + self.name=name + + def __str__(self): + return """An object has failed to implement interface %(interface)s + + The %(name)s attribute was not provided. + """ % self.__dict__ + +class BrokenMethodImplementation(Invalid): + """An method is not completely implemented. + """ + + def __init__(self, method, mess): + self.method=method + self.mess=mess + + def __str__(self): + return """The implementation of %(method)s violates its contract + because %(mess)s. + """ % self.__dict__ + +class InvalidInterface(Exception): + """The interface has invalid contents + """ + +class BadImplements(TypeError): + """An implementation assertion is invalid + + because it doesn't contain an interface or a sequence of valid + implementation assertions. + """ diff --git a/lib/zope/interface/interface.py b/lib/zope/interface/interface.py new file mode 100644 index 0000000..d4e5a94 --- /dev/null +++ b/lib/zope/interface/interface.py @@ -0,0 +1,687 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Interface object implementation +""" +from __future__ import generators + +import sys +from types import MethodType +from types import FunctionType +import warnings +import weakref + +from zope.interface.exceptions import Invalid +from zope.interface.ro import ro + + +CO_VARARGS = 4 +CO_VARKEYWORDS = 8 +TAGGED_DATA = '__interface_tagged_values__' + +_decorator_non_return = object() + +def invariant(call): + f_locals = sys._getframe(1).f_locals + tags = f_locals.setdefault(TAGGED_DATA, {}) + invariants = tags.setdefault('invariants', []) + invariants.append(call) + return _decorator_non_return + + +def taggedValue(key, value): + """Attaches a tagged value to an interface at definition time.""" + f_locals = sys._getframe(1).f_locals + tagged_values = f_locals.setdefault(TAGGED_DATA, {}) + tagged_values[key] = value + return _decorator_non_return + + +class Element(object): + """ + Default implementation of `zope.interface.interfaces.IElement`. + """ + + # We can't say this yet because we don't have enough + # infrastructure in place. + # + #implements(IElement) + + def __init__(self, __name__, __doc__=''): + if not __doc__ and __name__.find(' ') >= 0: + __doc__ = __name__ + __name__ = None + + self.__name__=__name__ + self.__doc__=__doc__ + self.__tagged_values = {} + + def getName(self): + """ Returns the name of the object. """ + return self.__name__ + + def getDoc(self): + """ Returns the documentation for the object. """ + return self.__doc__ + + def getTaggedValue(self, tag): + """ Returns the value associated with 'tag'. """ + return self.__tagged_values[tag] + + def queryTaggedValue(self, tag, default=None): + """ Returns the value associated with 'tag'. """ + return self.__tagged_values.get(tag, default) + + def getTaggedValueTags(self): + """ Returns a list of all tags. """ + return self.__tagged_values.keys() + + def setTaggedValue(self, tag, value): + """ Associates 'value' with 'key'. """ + self.__tagged_values[tag] = value + +class SpecificationBasePy(object): + + def providedBy(self, ob): + """Is the interface implemented by an object + """ + spec = providedBy(ob) + return self in spec._implied + + def implementedBy(self, cls): + """Test whether the specification is implemented by a class or factory. + + Raise TypeError if argument is neither a class nor a callable. + """ + spec = implementedBy(cls) + return self in spec._implied + + def isOrExtends(self, interface): + """Is the interface the same as or extend the given interface + """ + return interface in self._implied + + __call__ = isOrExtends + +SpecificationBase = SpecificationBasePy +try: + from zope.interface._zope_interface_coptimizations import SpecificationBase +except ImportError: + pass + +_marker = object() +class InterfaceBasePy(object): + """Base class that wants to be replaced with a C base :) + """ + + def __call__(self, obj, alternate=_marker): + """Adapt an object to the interface + """ + conform = getattr(obj, '__conform__', None) + if conform is not None: + adapter = self._call_conform(conform) + if adapter is not None: + return adapter + + adapter = self.__adapt__(obj) + + if adapter is not None: + return adapter + elif alternate is not _marker: + return alternate + else: + raise TypeError("Could not adapt", obj, self) + + def __adapt__(self, obj): + """Adapt an object to the reciever + """ + if self.providedBy(obj): + return obj + + for hook in adapter_hooks: + adapter = hook(self, obj) + if adapter is not None: + return adapter + + +InterfaceBase = InterfaceBasePy +try: + from zope.interface._zope_interface_coptimizations import InterfaceBase +except ImportError: + pass + + +adapter_hooks = [] +try: + from zope.interface._zope_interface_coptimizations import adapter_hooks +except ImportError: + pass + + +class Specification(SpecificationBase): + """Specifications + + An interface specification is used to track interface declarations + and component registrations. + + This class is a base class for both interfaces themselves and for + interface specifications (declarations). + + Specifications are mutable. If you reassign their bases, their + relations with other specifications are adjusted accordingly. + """ + + # Copy some base class methods for speed + isOrExtends = SpecificationBase.isOrExtends + providedBy = SpecificationBase.providedBy + + def __init__(self, bases=()): + self._implied = {} + self.dependents = weakref.WeakKeyDictionary() + self.__bases__ = tuple(bases) + + def subscribe(self, dependent): + self.dependents[dependent] = self.dependents.get(dependent, 0) + 1 + + def unsubscribe(self, dependent): + n = self.dependents.get(dependent, 0) - 1 + if not n: + del self.dependents[dependent] + elif n > 0: + self.dependents[dependent] = n + else: + raise KeyError(dependent) + + def __setBases(self, bases): + # Register ourselves as a dependent of our old bases + for b in self.__bases__: + b.unsubscribe(self) + + # Register ourselves as a dependent of our bases + self.__dict__['__bases__'] = bases + for b in bases: + b.subscribe(self) + + self.changed(self) + + __bases__ = property( + + lambda self: self.__dict__.get('__bases__', ()), + __setBases, + ) + + def changed(self, originally_changed): + """We, or something we depend on, have changed + """ + try: + del self._v_attrs + except AttributeError: + pass + + implied = self._implied + implied.clear() + + ancestors = ro(self) + + try: + if Interface not in ancestors: + ancestors.append(Interface) + except NameError: + pass # defining Interface itself + + self.__sro__ = tuple(ancestors) + self.__iro__ = tuple([ancestor for ancestor in ancestors + if isinstance(ancestor, InterfaceClass) + ]) + + for ancestor in ancestors: + # We directly imply our ancestors: + implied[ancestor] = () + + # Now, advise our dependents of change: + for dependent in tuple(self.dependents.keys()): + dependent.changed(originally_changed) + + + def interfaces(self): + """Return an iterator for the interfaces in the specification. + """ + seen = {} + for base in self.__bases__: + for interface in base.interfaces(): + if interface not in seen: + seen[interface] = 1 + yield interface + + + def extends(self, interface, strict=True): + """Does the specification extend the given interface? + + Test whether an interface in the specification extends the + given interface + """ + return ((interface in self._implied) + and + ((not strict) or (self != interface)) + ) + + def weakref(self, callback=None): + return weakref.ref(self, callback) + + def get(self, name, default=None): + """Query for an attribute description + """ + try: + attrs = self._v_attrs + except AttributeError: + attrs = self._v_attrs = {} + attr = attrs.get(name) + if attr is None: + for iface in self.__iro__: + attr = iface.direct(name) + if attr is not None: + attrs[name] = attr + break + + if attr is None: + return default + else: + return attr + +class InterfaceClass(Element, InterfaceBase, Specification): + """Prototype (scarecrow) Interfaces Implementation.""" + + # We can't say this yet because we don't have enough + # infrastructure in place. + # + #implements(IInterface) + + def __init__(self, name, bases=(), attrs=None, __doc__=None, + __module__=None): + + if attrs is None: + attrs = {} + + if __module__ is None: + __module__ = attrs.get('__module__') + if isinstance(__module__, str): + del attrs['__module__'] + else: + try: + # Figure out what module defined the interface. + # This is how cPython figures out the module of + # a class, but of course it does it in C. :-/ + __module__ = sys._getframe(1).f_globals['__name__'] + except (AttributeError, KeyError): # pragma: no cover + pass + + self.__module__ = __module__ + + d = attrs.get('__doc__') + if d is not None: + if not isinstance(d, Attribute): + if __doc__ is None: + __doc__ = d + del attrs['__doc__'] + + if __doc__ is None: + __doc__ = '' + + Element.__init__(self, name, __doc__) + + tagged_data = attrs.pop(TAGGED_DATA, None) + if tagged_data is not None: + for key, val in tagged_data.items(): + self.setTaggedValue(key, val) + + for base in bases: + if not isinstance(base, InterfaceClass): + raise TypeError('Expected base interfaces') + + Specification.__init__(self, bases) + + # Make sure that all recorded attributes (and methods) are of type + # `Attribute` and `Method` + for name, attr in list(attrs.items()): + if name in ('__locals__', '__qualname__', '__annotations__'): + # __locals__: Python 3 sometimes adds this. + # __qualname__: PEP 3155 (Python 3.3+) + # __annotations__: PEP 3107 (Python 3.0+) + del attrs[name] + continue + if isinstance(attr, Attribute): + attr.interface = self + if not attr.__name__: + attr.__name__ = name + elif isinstance(attr, FunctionType): + attrs[name] = fromFunction(attr, self, name=name) + elif attr is _decorator_non_return: + del attrs[name] + else: + raise InvalidInterface("Concrete attribute, " + name) + + self.__attrs = attrs + + self.__identifier__ = "%s.%s" % (self.__module__, self.__name__) + + def interfaces(self): + """Return an iterator for the interfaces in the specification. + """ + yield self + + def getBases(self): + return self.__bases__ + + def isEqualOrExtendedBy(self, other): + """Same interface or extends?""" + return self == other or other.extends(self) + + def names(self, all=False): + """Return the attribute names defined by the interface.""" + if not all: + return self.__attrs.keys() + + r = self.__attrs.copy() + + for base in self.__bases__: + r.update(dict.fromkeys(base.names(all))) + + return r.keys() + + def __iter__(self): + return iter(self.names(all=True)) + + def namesAndDescriptions(self, all=False): + """Return attribute names and descriptions defined by interface.""" + if not all: + return self.__attrs.items() + + r = {} + for base in self.__bases__[::-1]: + r.update(dict(base.namesAndDescriptions(all))) + + r.update(self.__attrs) + + return r.items() + + def getDescriptionFor(self, name): + """Return the attribute description for the given name.""" + r = self.get(name) + if r is not None: + return r + + raise KeyError(name) + + __getitem__ = getDescriptionFor + + def __contains__(self, name): + return self.get(name) is not None + + def direct(self, name): + return self.__attrs.get(name) + + def queryDescriptionFor(self, name, default=None): + return self.get(name, default) + + def validateInvariants(self, obj, errors=None): + """validate object to defined invariants.""" + for call in self.queryTaggedValue('invariants', []): + try: + call(obj) + except Invalid as e: + if errors is None: + raise + else: + errors.append(e) + for base in self.__bases__: + try: + base.validateInvariants(obj, errors) + except Invalid: + if errors is None: + raise + if errors: + raise Invalid(errors) + + def __repr__(self): # pragma: no cover + try: + return self._v_repr + except AttributeError: + name = self.__name__ + m = self.__module__ + if m: + name = '%s.%s' % (m, name) + r = "<%s %s>" % (self.__class__.__name__, name) + self._v_repr = r + return r + + def _call_conform(self, conform): + try: + return conform(self) + except TypeError: # pragma: no cover + # We got a TypeError. It might be an error raised by + # the __conform__ implementation, or *we* may have + # made the TypeError by calling an unbound method + # (object is a class). In the later case, we behave + # as though there is no __conform__ method. We can + # detect this case by checking whether there is more + # than one traceback object in the traceback chain: + if sys.exc_info()[2].tb_next is not None: + # There is more than one entry in the chain, so + # reraise the error: + raise + # This clever trick is from Phillip Eby + + return None # pragma: no cover + + def __reduce__(self): + return self.__name__ + + def __cmp(self, other): + # Yes, I did mean to name this __cmp, rather than __cmp__. + # It is a private method used by __lt__ and __gt__. + # I don't want to override __eq__ because I want the default + # __eq__, which is really fast. + """Make interfaces sortable + + TODO: It would ne nice if: + + More specific interfaces should sort before less specific ones. + Otherwise, sort on name and module. + + But this is too complicated, and we're going to punt on it + for now. + + For now, sort on interface and module name. + + None is treated as a pseudo interface that implies the loosest + contact possible, no contract. For that reason, all interfaces + sort before None. + + """ + if other is None: + return -1 + + n1 = (getattr(self, '__name__', ''), getattr(self, '__module__', '')) + n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', '')) + + # This spelling works under Python3, which doesn't have cmp(). + return (n1 > n2) - (n1 < n2) + + def __hash__(self): + d = self.__dict__ + if '__module__' not in d or '__name__' not in d: # pragma: no cover + warnings.warn('Hashing uninitialized InterfaceClass instance') + return 1 + return hash((self.__name__, self.__module__)) + + def __eq__(self, other): + c = self.__cmp(other) + return c == 0 + + def __ne__(self, other): + c = self.__cmp(other) + return c != 0 + + def __lt__(self, other): + c = self.__cmp(other) + return c < 0 + + def __le__(self, other): + c = self.__cmp(other) + return c <= 0 + + def __gt__(self, other): + c = self.__cmp(other) + return c > 0 + + def __ge__(self, other): + c = self.__cmp(other) + return c >= 0 + + +Interface = InterfaceClass("Interface", __module__ = 'zope.interface') + +class Attribute(Element): + """Attribute descriptions + """ + + # We can't say this yet because we don't have enough + # infrastructure in place. + # + # implements(IAttribute) + + interface = None + + +class Method(Attribute): + """Method interfaces + + The idea here is that you have objects that describe methods. + This provides an opportunity for rich meta-data. + """ + + # We can't say this yet because we don't have enough + # infrastructure in place. + # + # implements(IMethod) + + positional = required = () + _optional = varargs = kwargs = None + def _get_optional(self): + if self._optional is None: + return {} + return self._optional + def _set_optional(self, opt): + self._optional = opt + def _del_optional(self): + self._optional = None + optional = property(_get_optional, _set_optional, _del_optional) + + def __call__(self, *args, **kw): + raise BrokenImplementation(self.interface, self.__name__) + + def getSignatureInfo(self): + return {'positional': self.positional, + 'required': self.required, + 'optional': self.optional, + 'varargs': self.varargs, + 'kwargs': self.kwargs, + } + + def getSignatureString(self): + sig = [] + for v in self.positional: + sig.append(v) + if v in self.optional.keys(): + sig[-1] += "=" + repr(self.optional[v]) + if self.varargs: + sig.append("*" + self.varargs) + if self.kwargs: + sig.append("**" + self.kwargs) + + return "(%s)" % ", ".join(sig) + +def fromFunction(func, interface=None, imlevel=0, name=None): + name = name or func.__name__ + method = Method(name, func.__doc__) + defaults = getattr(func, '__defaults__', None) or () + code = func.__code__ + # Number of positional arguments + na = code.co_argcount-imlevel + names = code.co_varnames[imlevel:] + opt = {} + # Number of required arguments + nr = na-len(defaults) + if nr < 0: + defaults=defaults[-nr:] + nr = 0 + + # Determine the optional arguments. + opt.update(dict(zip(names[nr:], defaults))) + + method.positional = names[:na] + method.required = names[:nr] + method.optional = opt + + argno = na + + # Determine the function's variable argument's name (i.e. *args) + if code.co_flags & CO_VARARGS: + method.varargs = names[argno] + argno = argno + 1 + else: + method.varargs = None + + # Determine the function's keyword argument's name (i.e. **kw) + if code.co_flags & CO_VARKEYWORDS: + method.kwargs = names[argno] + else: + method.kwargs = None + + method.interface = interface + + for key, value in func.__dict__.items(): + method.setTaggedValue(key, value) + + return method + + +def fromMethod(meth, interface=None, name=None): + if isinstance(meth, MethodType): + func = meth.__func__ + else: + func = meth + return fromFunction(func, interface, imlevel=1, name=name) + + +# Now we can create the interesting interfaces and wire them up: +def _wire(): + from zope.interface.declarations import classImplements + + from zope.interface.interfaces import IAttribute + classImplements(Attribute, IAttribute) + + from zope.interface.interfaces import IMethod + classImplements(Method, IMethod) + + from zope.interface.interfaces import IInterface + classImplements(InterfaceClass, IInterface) + + from zope.interface.interfaces import ISpecification + classImplements(Specification, ISpecification) + +# We import this here to deal with module dependencies. +from zope.interface.declarations import implementedBy +from zope.interface.declarations import providedBy +from zope.interface.exceptions import InvalidInterface +from zope.interface.exceptions import BrokenImplementation diff --git a/lib/zope/interface/interfaces.py b/lib/zope/interface/interfaces.py new file mode 100644 index 0000000..27e64e9 --- /dev/null +++ b/lib/zope/interface/interfaces.py @@ -0,0 +1,1282 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Interface Package Interfaces +""" +__docformat__ = 'restructuredtext' + +from zope.interface.interface import Attribute +from zope.interface.interface import Interface +from zope.interface.declarations import implementer + + +_BLANK = u'' + +class IElement(Interface): + """Objects that have basic documentation and tagged values. + """ + + __name__ = Attribute('__name__', 'The object name') + __doc__ = Attribute('__doc__', 'The object doc string') + + def getTaggedValue(tag): + """Returns the value associated with `tag`. + + Raise a `KeyError` of the tag isn't set. + """ + + def queryTaggedValue(tag, default=None): + """Returns the value associated with `tag`. + + Return the default value of the tag isn't set. + """ + + def getTaggedValueTags(): + """Returns a list of all tags.""" + + def setTaggedValue(tag, value): + """Associates `value` with `key`.""" + + +class IAttribute(IElement): + """Attribute descriptors""" + + interface = Attribute('interface', + 'Stores the interface instance in which the ' + 'attribute is located.') + + +class IMethod(IAttribute): + """Method attributes""" + + def getSignatureInfo(): + """Returns the signature information. + + This method returns a dictionary with the following keys: + + o `positional` - All positional arguments. + + o `required` - A list of all required arguments. + + o `optional` - A list of all optional arguments. + + o `varargs` - The name of the varargs argument. + + o `kwargs` - The name of the kwargs argument. + """ + + def getSignatureString(): + """Return a signature string suitable for inclusion in documentation. + + This method returns the function signature string. For example, if you + have `func(a, b, c=1, d='f')`, then the signature string is `(a, b, + c=1, d='f')`. + """ + +class ISpecification(Interface): + """Object Behavioral specifications""" + + def providedBy(object): + """Test whether the interface is implemented by the object + + Return true of the object asserts that it implements the + interface, including asserting that it implements an extended + interface. + """ + + def implementedBy(class_): + """Test whether the interface is implemented by instances of the class + + Return true of the class asserts that its instances implement the + interface, including asserting that they implement an extended + interface. + """ + + def isOrExtends(other): + """Test whether the specification is or extends another + """ + + def extends(other, strict=True): + """Test whether a specification extends another + + The specification extends other if it has other as a base + interface or if one of it's bases extends other. + + If strict is false, then the specification extends itself. + """ + + def weakref(callback=None): + """Return a weakref to the specification + + This method is, regrettably, needed to allow weakrefs to be + computed to security-proxied specifications. While the + zope.interface package does not require zope.security or + zope.proxy, it has to be able to coexist with it. + + """ + + __bases__ = Attribute("""Base specifications + + A tuple if specifications from which this specification is + directly derived. + + """) + + __sro__ = Attribute("""Specification-resolution order + + A tuple of the specification and all of it's ancestor + specifications from most specific to least specific. + + (This is similar to the method-resolution order for new-style classes.) + """) + + __iro__ = Attribute("""Interface-resolution order + + A tuple of the of the specification's ancestor interfaces from + most specific to least specific. The specification itself is + included if it is an interface. + + (This is similar to the method-resolution order for new-style classes.) + """) + + def get(name, default=None): + """Look up the description for a name + + If the named attribute is not defined, the default is + returned. + """ + + +class IInterface(ISpecification, IElement): + """Interface objects + + Interface objects describe the behavior of an object by containing + useful information about the object. This information includes: + + - Prose documentation about the object. In Python terms, this + is called the "doc string" of the interface. In this element, + you describe how the object works in prose language and any + other useful information about the object. + + - Descriptions of attributes. Attribute descriptions include + the name of the attribute and prose documentation describing + the attributes usage. + + - Descriptions of methods. Method descriptions can include: + + - Prose "doc string" documentation about the method and its + usage. + + - A description of the methods arguments; how many arguments + are expected, optional arguments and their default values, + the position or arguments in the signature, whether the + method accepts arbitrary arguments and whether the method + accepts arbitrary keyword arguments. + + - Optional tagged data. Interface objects (and their attributes and + methods) can have optional, application specific tagged data + associated with them. Examples uses for this are examples, + security assertions, pre/post conditions, and other possible + information you may want to associate with an Interface or its + attributes. + + Not all of this information is mandatory. For example, you may + only want the methods of your interface to have prose + documentation and not describe the arguments of the method in + exact detail. Interface objects are flexible and let you give or + take any of these components. + + Interfaces are created with the Python class statement using + either `zope.interface.Interface` or another interface, as in:: + + from zope.interface import Interface + + class IMyInterface(Interface): + '''Interface documentation''' + + def meth(arg1, arg2): + '''Documentation for meth''' + + # Note that there is no self argument + + class IMySubInterface(IMyInterface): + '''Interface documentation''' + + def meth2(): + '''Documentation for meth2''' + + You use interfaces in two ways: + + - You assert that your object implement the interfaces. + + There are several ways that you can assert that an object + implements an interface: + + 1. Call `zope.interface.implements` in your class definition. + + 2. Call `zope.interfaces.directlyProvides` on your object. + + 3. Call `zope.interface.classImplements` to assert that instances + of a class implement an interface. + + For example:: + + from zope.interface import classImplements + + classImplements(some_class, some_interface) + + This approach is useful when it is not an option to modify + the class source. Note that this doesn't affect what the + class itself implements, but only what its instances + implement. + + - You query interface meta-data. See the IInterface methods and + attributes for details. + + """ + + def names(all=False): + """Get the interface attribute names + + Return a sequence of the names of the attributes, including + methods, included in the interface definition. + + Normally, only directly defined attributes are included. If + a true positional or keyword argument is given, then + attributes defined by base classes will be included. + """ + + def namesAndDescriptions(all=False): + """Get the interface attribute names and descriptions + + Return a sequence of the names and descriptions of the + attributes, including methods, as name-value pairs, included + in the interface definition. + + Normally, only directly defined attributes are included. If + a true positional or keyword argument is given, then + attributes defined by base classes will be included. + """ + + def __getitem__(name): + """Get the description for a name + + If the named attribute is not defined, a `KeyError` is raised. + """ + + def direct(name): + """Get the description for the name if it was defined by the interface + + If the interface doesn't define the name, returns None. + """ + + def validateInvariants(obj, errors=None): + """Validate invariants + + Validate object to defined invariants. If errors is None, + raises first Invalid error; if errors is a list, appends all errors + to list, then raises Invalid with the errors as the first element + of the "args" tuple.""" + + def __contains__(name): + """Test whether the name is defined by the interface""" + + def __iter__(): + """Return an iterator over the names defined by the interface + + The names iterated include all of the names defined by the + interface directly and indirectly by base interfaces. + """ + + __module__ = Attribute("""The name of the module defining the interface""") + +class IDeclaration(ISpecification): + """Interface declaration + + Declarations are used to express the interfaces implemented by + classes or provided by objects. + """ + + def __contains__(interface): + """Test whether an interface is in the specification + + Return true if the given interface is one of the interfaces in + the specification and false otherwise. + """ + + def __iter__(): + """Return an iterator for the interfaces in the specification + """ + + def flattened(): + """Return an iterator of all included and extended interfaces + + An iterator is returned for all interfaces either included in + or extended by interfaces included in the specifications + without duplicates. The interfaces are in "interface + resolution order". The interface resolution order is such that + base interfaces are listed after interfaces that extend them + and, otherwise, interfaces are included in the order that they + were defined in the specification. + """ + + def __sub__(interfaces): + """Create an interface specification with some interfaces excluded + + The argument can be an interface or an interface + specifications. The interface or interfaces given in a + specification are subtracted from the interface specification. + + Removing an interface that is not in the specification does + not raise an error. Doing so has no effect. + + Removing an interface also removes sub-interfaces of the interface. + + """ + + def __add__(interfaces): + """Create an interface specification with some interfaces added + + The argument can be an interface or an interface + specifications. The interface or interfaces given in a + specification are added to the interface specification. + + Adding an interface that is already in the specification does + not raise an error. Doing so has no effect. + """ + + def __nonzero__(): + """Return a true value of the interface specification is non-empty + """ + +class IInterfaceDeclaration(Interface): + """Declare and check the interfaces of objects + + The functions defined in this interface are used to declare the + interfaces that objects provide and to query the interfaces that have + been declared. + + Interfaces can be declared for objects in two ways: + + - Interfaces are declared for instances of the object's class + + - Interfaces are declared for the object directly. + + The interfaces declared for an object are, therefore, the union of + interfaces declared for the object directly and the interfaces + declared for instances of the object's class. + + Note that we say that a class implements the interfaces provided + by it's instances. An instance can also provide interfaces + directly. The interfaces provided by an object are the union of + the interfaces provided directly and the interfaces implemented by + the class. + """ + + def providedBy(ob): + """Return the interfaces provided by an object + + This is the union of the interfaces directly provided by an + object and interfaces implemented by it's class. + + The value returned is an `IDeclaration`. + """ + + def implementedBy(class_): + """Return the interfaces implemented for a class' instances + + The value returned is an `IDeclaration`. + """ + + def classImplements(class_, *interfaces): + """Declare additional interfaces implemented for instances of a class + + The arguments after the class are one or more interfaces or + interface specifications (`IDeclaration` objects). + + The interfaces given (including the interfaces in the + specifications) are added to any interfaces previously + declared. + + Consider the following example:: + + class C(A, B): + ... + + classImplements(C, I1, I2) + + + Instances of ``C`` provide ``I1``, ``I2``, and whatever interfaces + instances of ``A`` and ``B`` provide. + """ + + def implementer(*interfaces): + """Create a decorator for declaring interfaces implemented by a factory. + + A callable is returned that makes an implements declaration on + objects passed to it. + """ + + def classImplementsOnly(class_, *interfaces): + """Declare the only interfaces implemented by instances of a class + + The arguments after the class are one or more interfaces or + interface specifications (`IDeclaration` objects). + + The interfaces given (including the interfaces in the + specifications) replace any previous declarations. + + Consider the following example:: + + class C(A, B): + ... + + classImplements(C, IA, IB. IC) + classImplementsOnly(C. I1, I2) + + Instances of ``C`` provide only ``I1``, ``I2``, and regardless of + whatever interfaces instances of ``A`` and ``B`` implement. + """ + + def implementer_only(*interfaces): + """Create a decorator for declaring the only interfaces implemented + + A callable is returned that makes an implements declaration on + objects passed to it. + """ + + def directlyProvidedBy(object): + """Return the interfaces directly provided by the given object + + The value returned is an `IDeclaration`. + """ + + def directlyProvides(object, *interfaces): + """Declare interfaces declared directly for an object + + The arguments after the object are one or more interfaces or + interface specifications (`IDeclaration` objects). + + The interfaces given (including the interfaces in the + specifications) replace interfaces previously + declared for the object. + + Consider the following example:: + + class C(A, B): + ... + + ob = C() + directlyProvides(ob, I1, I2) + + The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces + instances have been declared for instances of ``C``. + + To remove directly provided interfaces, use `directlyProvidedBy` and + subtract the unwanted interfaces. For example:: + + directlyProvides(ob, directlyProvidedBy(ob)-I2) + + removes I2 from the interfaces directly provided by + ``ob``. The object, ``ob`` no longer directly provides ``I2``, + although it might still provide ``I2`` if it's class + implements ``I2``. + + To add directly provided interfaces, use `directlyProvidedBy` and + include additional interfaces. For example:: + + directlyProvides(ob, directlyProvidedBy(ob), I2) + + adds I2 to the interfaces directly provided by ob. + """ + + def alsoProvides(object, *interfaces): + """Declare additional interfaces directly for an object:: + + alsoProvides(ob, I1) + + is equivalent to:: + + directlyProvides(ob, directlyProvidedBy(ob), I1) + """ + + def noLongerProvides(object, interface): + """Remove an interface from the list of an object's directly + provided interfaces:: + + noLongerProvides(ob, I1) + + is equivalent to:: + + directlyProvides(ob, directlyProvidedBy(ob) - I1) + + with the exception that if ``I1`` is an interface that is + provided by ``ob`` through the class's implementation, + `ValueError` is raised. + """ + + def implements(*interfaces): + """Declare interfaces implemented by instances of a class + + This function is called in a class definition (Python 2.x only). + + The arguments are one or more interfaces or interface + specifications (`IDeclaration` objects). + + The interfaces given (including the interfaces in the + specifications) are added to any interfaces previously + declared. + + Previous declarations include declarations for base classes + unless implementsOnly was used. + + This function is provided for convenience. It provides a more + convenient way to call `classImplements`. For example:: + + implements(I1) + + is equivalent to calling:: + + classImplements(C, I1) + + after the class has been created. + + Consider the following example (Python 2.x only):: + + class C(A, B): + implements(I1, I2) + + + Instances of ``C`` implement ``I1``, ``I2``, and whatever interfaces + instances of ``A`` and ``B`` implement. + """ + + def implementsOnly(*interfaces): + """Declare the only interfaces implemented by instances of a class + + This function is called in a class definition (Python 2.x only). + + The arguments are one or more interfaces or interface + specifications (`IDeclaration` objects). + + Previous declarations including declarations for base classes + are overridden. + + This function is provided for convenience. It provides a more + convenient way to call `classImplementsOnly`. For example:: + + implementsOnly(I1) + + is equivalent to calling:: + + classImplementsOnly(I1) + + after the class has been created. + + Consider the following example (Python 2.x only):: + + class C(A, B): + implementsOnly(I1, I2) + + + Instances of ``C`` implement ``I1``, ``I2``, regardless of what + instances of ``A`` and ``B`` implement. + """ + + def classProvides(*interfaces): + """Declare interfaces provided directly by a class + + This function is called in a class definition. + + The arguments are one or more interfaces or interface + specifications (`IDeclaration` objects). + + The given interfaces (including the interfaces in the + specifications) are used to create the class's direct-object + interface specification. An error will be raised if the module + class has an direct interface specification. In other words, it is + an error to call this function more than once in a class + definition. + + Note that the given interfaces have nothing to do with the + interfaces implemented by instances of the class. + + This function is provided for convenience. It provides a more + convenient way to call `directlyProvides` for a class. For example:: + + classProvides(I1) + + is equivalent to calling:: + + directlyProvides(theclass, I1) + + after the class has been created. + """ + def provider(*interfaces): + """A class decorator version of `classProvides`""" + + def moduleProvides(*interfaces): + """Declare interfaces provided by a module + + This function is used in a module definition. + + The arguments are one or more interfaces or interface + specifications (`IDeclaration` objects). + + The given interfaces (including the interfaces in the + specifications) are used to create the module's direct-object + interface specification. An error will be raised if the module + already has an interface specification. In other words, it is + an error to call this function more than once in a module + definition. + + This function is provided for convenience. It provides a more + convenient way to call `directlyProvides` for a module. For example:: + + moduleImplements(I1) + + is equivalent to:: + + directlyProvides(sys.modules[__name__], I1) + """ + + def Declaration(*interfaces): + """Create an interface specification + + The arguments are one or more interfaces or interface + specifications (`IDeclaration` objects). + + A new interface specification (`IDeclaration`) with + the given interfaces is returned. + """ + +class IAdapterRegistry(Interface): + """Provide an interface-based registry for adapters + + This registry registers objects that are in some sense "from" a + sequence of specification to an interface and a name. + + No specific semantics are assumed for the registered objects, + however, the most common application will be to register factories + that adapt objects providing required specifications to a provided + interface. + """ + + def register(required, provided, name, value): + """Register a value + + A value is registered for a *sequence* of required specifications, a + provided interface, and a name, which must be text. + """ + + def registered(required, provided, name=_BLANK): + """Return the component registered for the given interfaces and name + + name must be text. + + Unlike the lookup method, this methods won't retrieve + components registered for more specific required interfaces or + less specific provided interfaces. + + If no component was registered exactly for the given + interfaces and name, then None is returned. + + """ + + def lookup(required, provided, name='', default=None): + """Lookup a value + + A value is looked up based on a *sequence* of required + specifications, a provided interface, and a name, which must be + text. + """ + + def queryMultiAdapter(objects, provided, name=_BLANK, default=None): + """Adapt a sequence of objects to a named, provided, interface + """ + + def lookup1(required, provided, name=_BLANK, default=None): + """Lookup a value using a single required interface + + A value is looked up based on a single required + specifications, a provided interface, and a name, which must be + text. + """ + + def queryAdapter(object, provided, name=_BLANK, default=None): + """Adapt an object using a registered adapter factory. + """ + + def adapter_hook(provided, object, name=_BLANK, default=None): + """Adapt an object using a registered adapter factory. + + name must be text. + """ + + def lookupAll(required, provided): + """Find all adapters from the required to the provided interfaces + + An iterable object is returned that provides name-value two-tuples. + """ + + def names(required, provided): + """Return the names for which there are registered objects + """ + + def subscribe(required, provided, subscriber, name=_BLANK): + """Register a subscriber + + A subscriber is registered for a *sequence* of required + specifications, a provided interface, and a name. + + Multiple subscribers may be registered for the same (or + equivalent) interfaces. + """ + + def subscriptions(required, provided, name=_BLANK): + """Get a sequence of subscribers + + Subscribers for a *sequence* of required interfaces, and a provided + interface are returned. + """ + + def subscribers(objects, provided, name=_BLANK): + """Get a sequence of subscription adapters + """ + +# begin formerly in zope.component + +class ComponentLookupError(LookupError): + """A component could not be found.""" + +class Invalid(Exception): + """A component doesn't satisfy a promise.""" + +class IObjectEvent(Interface): + """An event related to an object. + + The object that generated this event is not necessarily the object + refered to by location. + """ + + object = Attribute("The subject of the event.") + + +@implementer(IObjectEvent) +class ObjectEvent(object): + + def __init__(self, object): + self.object = object + +class IComponentLookup(Interface): + """Component Manager for a Site + + This object manages the components registered at a particular site. The + definition of a site is intentionally vague. + """ + + adapters = Attribute( + "Adapter Registry to manage all registered adapters.") + + utilities = Attribute( + "Adapter Registry to manage all registered utilities.") + + def queryAdapter(object, interface, name=_BLANK, default=None): + """Look for a named adapter to an interface for an object + + If a matching adapter cannot be found, returns the default. + """ + + def getAdapter(object, interface, name=_BLANK): + """Look for a named adapter to an interface for an object + + If a matching adapter cannot be found, a `ComponentLookupError` + is raised. + """ + + def queryMultiAdapter(objects, interface, name=_BLANK, default=None): + """Look for a multi-adapter to an interface for multiple objects + + If a matching adapter cannot be found, returns the default. + """ + + def getMultiAdapter(objects, interface, name=_BLANK): + """Look for a multi-adapter to an interface for multiple objects + + If a matching adapter cannot be found, a `ComponentLookupError` + is raised. + """ + + def getAdapters(objects, provided): + """Look for all matching adapters to a provided interface for objects + + Return an iterable of name-adapter pairs for adapters that + provide the given interface. + """ + + def subscribers(objects, provided): + """Get subscribers + + Subscribers are returned that provide the provided interface + and that depend on and are comuted from the sequence of + required objects. + """ + + def handle(*objects): + """Call handlers for the given objects + + Handlers registered for the given objects are called. + """ + + def queryUtility(interface, name='', default=None): + """Look up a utility that provides an interface. + + If one is not found, returns default. + """ + + def getUtilitiesFor(interface): + """Look up the registered utilities that provide an interface. + + Returns an iterable of name-utility pairs. + """ + + def getAllUtilitiesRegisteredFor(interface): + """Return all registered utilities for an interface + + This includes overridden utilities. + + An iterable of utility instances is returned. No names are + returned. + """ + +class IRegistration(Interface): + """A registration-information object + """ + + registry = Attribute("The registry having the registration") + + name = Attribute("The registration name") + + info = Attribute("""Information about the registration + + This is information deemed useful to people browsing the + configuration of a system. It could, for example, include + commentary or information about the source of the configuration. + """) + +class IUtilityRegistration(IRegistration): + """Information about the registration of a utility + """ + + factory = Attribute("The factory used to create the utility. Optional.") + component = Attribute("The object registered") + provided = Attribute("The interface provided by the component") + +class _IBaseAdapterRegistration(IRegistration): + """Information about the registration of an adapter + """ + + factory = Attribute("The factory used to create adapters") + + required = Attribute("""The adapted interfaces + + This is a sequence of interfaces adapters by the registered + factory. The factory will be caled with a sequence of objects, as + positional arguments, that provide these interfaces. + """) + + provided = Attribute("""The interface provided by the adapters. + + This interface is implemented by the factory + """) + +class IAdapterRegistration(_IBaseAdapterRegistration): + """Information about the registration of an adapter + """ + +class ISubscriptionAdapterRegistration(_IBaseAdapterRegistration): + """Information about the registration of a subscription adapter + """ + +class IHandlerRegistration(IRegistration): + + handler = Attribute("An object called used to handle an event") + + required = Attribute("""The handled interfaces + + This is a sequence of interfaces handled by the registered + handler. The handler will be caled with a sequence of objects, as + positional arguments, that provide these interfaces. + """) + +class IRegistrationEvent(IObjectEvent): + """An event that involves a registration""" + + +@implementer(IRegistrationEvent) +class RegistrationEvent(ObjectEvent): + """There has been a change in a registration + """ + def __repr__(self): + return "%s event:\n%r" % (self.__class__.__name__, self.object) + +class IRegistered(IRegistrationEvent): + """A component or factory was registered + """ + +@implementer(IRegistered) +class Registered(RegistrationEvent): + pass + +class IUnregistered(IRegistrationEvent): + """A component or factory was unregistered + """ + +@implementer(IUnregistered) +class Unregistered(RegistrationEvent): + """A component or factory was unregistered + """ + pass + +class IComponentRegistry(Interface): + """Register components + """ + + def registerUtility(component=None, provided=None, name=_BLANK, + info=_BLANK, factory=None): + """Register a utility + + :param factory: + Factory for the component to be registered. + + :param component: + The registered component + + :param provided: + This is the interface provided by the utility. If the + component provides a single interface, then this + argument is optional and the component-implemented + interface will be used. + + :param name: + The utility name. + + :param info: + An object that can be converted to a string to provide + information about the registration. + + Only one of *component* and *factory* can be used. + + A `IRegistered` event is generated with an `IUtilityRegistration`. + """ + + def unregisterUtility(component=None, provided=None, name=_BLANK, + factory=None): + """Unregister a utility + + :returns: + A boolean is returned indicating whether the registry was + changed. If the given *component* is None and there is no + component registered, or if the given *component* is not + None and is not registered, then the function returns + False, otherwise it returns True. + + :param factory: + Factory for the component to be unregistered. + + :param component: + The registered component The given component can be + None, in which case any component registered to provide + the given provided interface with the given name is + unregistered. + + :param provided: + This is the interface provided by the utility. If the + component is not None and provides a single interface, + then this argument is optional and the + component-implemented interface will be used. + + :param name: + The utility name. + + Only one of *component* and *factory* can be used. + An `IUnregistered` event is generated with an `IUtilityRegistration`. + """ + + def registeredUtilities(): + """Return an iterable of `IUtilityRegistration` instances. + + These registrations describe the current utility registrations + in the object. + """ + + def registerAdapter(factory, required=None, provided=None, name=_BLANK, + info=_BLANK): + """Register an adapter factory + + :param factory: + The object used to compute the adapter + + :param required: + This is a sequence of specifications for objects to be + adapted. If omitted, then the value of the factory's + ``__component_adapts__`` attribute will be used. The + ``__component_adapts__`` attribute is + normally set in class definitions using + the `.adapter` + decorator. If the factory doesn't have a + ``__component_adapts__`` adapts attribute, then this + argument is required. + + :param provided: + This is the interface provided by the adapter and + implemented by the factory. If the factory + implements a single interface, then this argument is + optional and the factory-implemented interface will be + used. + + :param name: + The adapter name. + + :param info: + An object that can be converted to a string to provide + information about the registration. + + A `IRegistered` event is generated with an `IAdapterRegistration`. + """ + + def unregisterAdapter(factory=None, required=None, + provided=None, name=_BLANK): + """Unregister an adapter factory + + :returns: + A boolean is returned indicating whether the registry was + changed. If the given component is None and there is no + component registered, or if the given component is not + None and is not registered, then the function returns + False, otherwise it returns True. + + :param factory: + This is the object used to compute the adapter. The + factory can be None, in which case any factory + registered to implement the given provided interface + for the given required specifications with the given + name is unregistered. + + :param required: + This is a sequence of specifications for objects to be + adapted. If the factory is not None and the required + arguments is omitted, then the value of the factory's + __component_adapts__ attribute will be used. The + __component_adapts__ attribute attribute is normally + set in class definitions using adapts function, or for + callables using the adapter decorator. If the factory + is None or doesn't have a __component_adapts__ adapts + attribute, then this argument is required. + + :param provided: + This is the interface provided by the adapter and + implemented by the factory. If the factory is not + None and implements a single interface, then this + argument is optional and the factory-implemented + interface will be used. + + :param name: + The adapter name. + + An `IUnregistered` event is generated with an `IAdapterRegistration`. + """ + + def registeredAdapters(): + """Return an iterable of `IAdapterRegistration` instances. + + These registrations describe the current adapter registrations + in the object. + """ + + def registerSubscriptionAdapter(factory, required=None, provides=None, + name=_BLANK, info=''): + """Register a subscriber factory + + :param factory: + The object used to compute the adapter + + :param required: + This is a sequence of specifications for objects to be + adapted. If omitted, then the value of the factory's + ``__component_adapts__`` attribute will be used. The + ``__component_adapts__`` attribute is + normally set using the adapter + decorator. If the factory doesn't have a + ``__component_adapts__`` adapts attribute, then this + argument is required. + + :param provided: + This is the interface provided by the adapter and + implemented by the factory. If the factory implements + a single interface, then this argument is optional and + the factory-implemented interface will be used. + + :param name: + The adapter name. + + Currently, only the empty string is accepted. Other + strings will be accepted in the future when support for + named subscribers is added. + + :param info: + An object that can be converted to a string to provide + information about the registration. + + A `IRegistered` event is generated with an + `ISubscriptionAdapterRegistration`. + """ + + def unregisterSubscriptionAdapter(factory=None, required=None, + provides=None, name=_BLANK): + """Unregister a subscriber factory. + + :returns: + A boolean is returned indicating whether the registry was + changed. If the given component is None and there is no + component registered, or if the given component is not + None and is not registered, then the function returns + False, otherwise it returns True. + + :param factory: + This is the object used to compute the adapter. The + factory can be None, in which case any factories + registered to implement the given provided interface + for the given required specifications with the given + name are unregistered. + + :param required: + This is a sequence of specifications for objects to be + adapted. If omitted, then the value of the factory's + ``__component_adapts__`` attribute will be used. The + ``__component_adapts__`` attribute is + normally set using the adapter + decorator. If the factory doesn't have a + ``__component_adapts__`` adapts attribute, then this + argument is required. + + :param provided: + This is the interface provided by the adapter and + implemented by the factory. If the factory is not + None implements a single interface, then this argument + is optional and the factory-implemented interface will + be used. + + :param name: + The adapter name. + + Currently, only the empty string is accepted. Other + strings will be accepted in the future when support for + named subscribers is added. + + An `IUnregistered` event is generated with an + `ISubscriptionAdapterRegistration`. + """ + + def registeredSubscriptionAdapters(): + """Return an iterable of `ISubscriptionAdapterRegistration` instances. + + These registrations describe the current subscription adapter + registrations in the object. + """ + + def registerHandler(handler, required=None, name=_BLANK, info=''): + """Register a handler. + + A handler is a subscriber that doesn't compute an adapter + but performs some function when called. + + :param handler: + The object used to handle some event represented by + the objects passed to it. + + :param required: + This is a sequence of specifications for objects to be + adapted. If omitted, then the value of the factory's + ``__component_adapts__`` attribute will be used. The + ``__component_adapts__`` attribute is + normally set using the adapter + decorator. If the factory doesn't have a + ``__component_adapts__`` adapts attribute, then this + argument is required. + + :param name: + The handler name. + + Currently, only the empty string is accepted. Other + strings will be accepted in the future when support for + named handlers is added. + + :param info: + An object that can be converted to a string to provide + information about the registration. + + + A `IRegistered` event is generated with an `IHandlerRegistration`. + """ + + def unregisterHandler(handler=None, required=None, name=_BLANK): + """Unregister a handler. + + A handler is a subscriber that doesn't compute an adapter + but performs some function when called. + + :returns: A boolean is returned indicating whether the registry was + changed. + + :param handler: + This is the object used to handle some event + represented by the objects passed to it. The handler + can be None, in which case any handlers registered for + the given required specifications with the given are + unregistered. + + :param required: + This is a sequence of specifications for objects to be + adapted. If omitted, then the value of the factory's + ``__component_adapts__`` attribute will be used. The + ``__component_adapts__`` attribute is + normally set using the adapter + decorator. If the factory doesn't have a + ``__component_adapts__`` adapts attribute, then this + argument is required. + + :param name: + The handler name. + + Currently, only the empty string is accepted. Other + strings will be accepted in the future when support for + named handlers is added. + + An `IUnregistered` event is generated with an `IHandlerRegistration`. + """ + + def registeredHandlers(): + """Return an iterable of `IHandlerRegistration` instances. + + These registrations describe the current handler registrations + in the object. + """ + + +class IComponents(IComponentLookup, IComponentRegistry): + """Component registration and access + """ + + +# end formerly in zope.component diff --git a/lib/zope/interface/registry.py b/lib/zope/interface/registry.py new file mode 100644 index 0000000..bba0267 --- /dev/null +++ b/lib/zope/interface/registry.py @@ -0,0 +1,654 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Basic components support +""" +from collections import defaultdict + +try: + from zope.event import notify +except ImportError: # pragma: no cover + def notify(*arg, **kw): pass + +from zope.interface.interfaces import ISpecification +from zope.interface.interfaces import ComponentLookupError +from zope.interface.interfaces import IAdapterRegistration +from zope.interface.interfaces import IComponents +from zope.interface.interfaces import IHandlerRegistration +from zope.interface.interfaces import ISubscriptionAdapterRegistration +from zope.interface.interfaces import IUtilityRegistration +from zope.interface.interfaces import Registered +from zope.interface.interfaces import Unregistered + +from zope.interface.interface import Interface +from zope.interface.declarations import implementedBy +from zope.interface.declarations import implementer +from zope.interface.declarations import implementer_only +from zope.interface.declarations import providedBy +from zope.interface.adapter import AdapterRegistry +from zope.interface._compat import CLASS_TYPES +from zope.interface._compat import STRING_TYPES + + +class _UnhashableComponentCounter(object): + # defaultdict(int)-like object for unhashable components + + def __init__(self, otherdict): + # [(component, count)] + self._data = [item for item in otherdict.items()] + + def __getitem__(self, key): + for component, count in self._data: + if component == key: + return count + return 0 + + def __setitem__(self, component, count): + for i, data in enumerate(self._data): + if data[0] == component: + self._data[i] = component, count + return + self._data.append((component, count)) + + def __delitem__(self, component): + for i, data in enumerate(self._data): + if data[0] == component: + del self._data[i] + return + raise KeyError(component) # pragma: no cover + +def _defaultdict_int(): + return defaultdict(int) + +class _UtilityRegistrations(object): + + def __init__(self, utilities, utility_registrations): + # {provided -> {component: count}} + self._cache = defaultdict(_defaultdict_int) + self._utilities = utilities + self._utility_registrations = utility_registrations + + self.__populate_cache() + + def __populate_cache(self): + for ((p, _), data) in iter(self._utility_registrations.items()): + component = data[0] + self.__cache_utility(p, component) + + def __cache_utility(self, provided, component): + try: + self._cache[provided][component] += 1 + except TypeError: + # The component is not hashable, and we have a dict. Switch to a strategy + # that doesn't use hashing. + prov = self._cache[provided] = _UnhashableComponentCounter(self._cache[provided]) + prov[component] += 1 + + def __uncache_utility(self, provided, component): + provided = self._cache[provided] + # It seems like this line could raise a TypeError if component isn't + # hashable and we haven't yet switched to _UnhashableComponentCounter. However, + # we can't actually get in that situation. In order to get here, we would + # have had to cache the utility already which would have switched + # the datastructure if needed. + count = provided[component] + count -= 1 + if count == 0: + del provided[component] + else: + provided[component] = count + return count > 0 + + def _is_utility_subscribed(self, provided, component): + try: + return self._cache[provided][component] > 0 + except TypeError: + # Not hashable and we're still using a dict + return False + + def registerUtility(self, provided, name, component, info, factory): + subscribed = self._is_utility_subscribed(provided, component) + + self._utility_registrations[(provided, name)] = component, info, factory + self._utilities.register((), provided, name, component) + + if not subscribed: + self._utilities.subscribe((), provided, component) + + self.__cache_utility(provided, component) + + def unregisterUtility(self, provided, name, component): + del self._utility_registrations[(provided, name)] + self._utilities.unregister((), provided, name) + + subscribed = self.__uncache_utility(provided, component) + + if not subscribed: + self._utilities.unsubscribe((), provided, component) + + +@implementer(IComponents) +class Components(object): + + _v_utility_registrations_cache = None + + def __init__(self, name='', bases=()): + # __init__ is used for test cleanup as well as initialization. + # XXX add a separate API for test cleanup. + assert isinstance(name, STRING_TYPES) + self.__name__ = name + self._init_registries() + self._init_registrations() + self.__bases__ = tuple(bases) + self._v_utility_registrations_cache = None + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, self.__name__) + + def __reduce__(self): + # Mimic what a persistent.Persistent object does and elide + # _v_ attributes so that they don't get saved in ZODB. + # This allows us to store things that cannot be pickled in such + # attributes. + reduction = super(Components, self).__reduce__() + # (callable, args, state, listiter, dictiter) + # We assume the state is always a dict; the last three items + # are technically optional and can be missing or None. + filtered_state = {k: v for k, v in reduction[2].items() + if not k.startswith('_v_')} + reduction = list(reduction) + reduction[2] = filtered_state + return tuple(reduction) + + def _init_registries(self): + # Subclasses have never been required to call this method + # if they override it, merely to fill in these two attributes. + self.adapters = AdapterRegistry() + self.utilities = AdapterRegistry() + + def _init_registrations(self): + self._utility_registrations = {} + self._adapter_registrations = {} + self._subscription_registrations = [] + self._handler_registrations = [] + + @property + def _utility_registrations_cache(self): + # We use a _v_ attribute internally so that data aren't saved in ZODB, + # because this object cannot be pickled. + cache = self._v_utility_registrations_cache + if (cache is None + or cache._utilities is not self.utilities + or cache._utility_registrations is not self._utility_registrations): + cache = self._v_utility_registrations_cache = _UtilityRegistrations( + self.utilities, + self._utility_registrations) + return cache + + def _getBases(self): + # Subclasses might override + return self.__dict__.get('__bases__', ()) + + def _setBases(self, bases): + # Subclasses might override + self.adapters.__bases__ = tuple([ + base.adapters for base in bases]) + self.utilities.__bases__ = tuple([ + base.utilities for base in bases]) + self.__dict__['__bases__'] = tuple(bases) + + __bases__ = property( + lambda self: self._getBases(), + lambda self, bases: self._setBases(bases), + ) + + def registerUtility(self, component=None, provided=None, name=u'', + info=u'', event=True, factory=None): + if factory: + if component: + raise TypeError("Can't specify factory and component.") + component = factory() + + if provided is None: + provided = _getUtilityProvided(component) + + if name == u'': + name = _getName(component) + + reg = self._utility_registrations.get((provided, name)) + if reg is not None: + if reg[:2] == (component, info): + # already registered + return + self.unregisterUtility(reg[0], provided, name) + + self._utility_registrations_cache.registerUtility( + provided, name, component, info, factory) + + if event: + notify(Registered( + UtilityRegistration(self, provided, name, component, info, + factory) + )) + + def unregisterUtility(self, component=None, provided=None, name=u'', + factory=None): + if factory: + if component: + raise TypeError("Can't specify factory and component.") + component = factory() + + if provided is None: + if component is None: + raise TypeError("Must specify one of component, factory and " + "provided") + provided = _getUtilityProvided(component) + + old = self._utility_registrations.get((provided, name)) + if (old is None) or ((component is not None) and + (component != old[0])): + return False + + if component is None: + component = old[0] + + # Note that component is now the old thing registered + self._utility_registrations_cache.unregisterUtility( + provided, name, component) + + notify(Unregistered( + UtilityRegistration(self, provided, name, component, *old[1:]) + )) + + return True + + def registeredUtilities(self): + for ((provided, name), data + ) in iter(self._utility_registrations.items()): + yield UtilityRegistration(self, provided, name, *data) + + def queryUtility(self, provided, name=u'', default=None): + return self.utilities.lookup((), provided, name, default) + + def getUtility(self, provided, name=u''): + utility = self.utilities.lookup((), provided, name) + if utility is None: + raise ComponentLookupError(provided, name) + return utility + + def getUtilitiesFor(self, interface): + for name, utility in self.utilities.lookupAll((), interface): + yield name, utility + + def getAllUtilitiesRegisteredFor(self, interface): + return self.utilities.subscriptions((), interface) + + def registerAdapter(self, factory, required=None, provided=None, + name=u'', info=u'', event=True): + if provided is None: + provided = _getAdapterProvided(factory) + required = _getAdapterRequired(factory, required) + if name == u'': + name = _getName(factory) + self._adapter_registrations[(required, provided, name) + ] = factory, info + self.adapters.register(required, provided, name, factory) + + if event: + notify(Registered( + AdapterRegistration(self, required, provided, name, + factory, info) + )) + + + def unregisterAdapter(self, factory=None, + required=None, provided=None, name=u'', + ): + if provided is None: + if factory is None: + raise TypeError("Must specify one of factory and provided") + provided = _getAdapterProvided(factory) + + if (required is None) and (factory is None): + raise TypeError("Must specify one of factory and required") + + required = _getAdapterRequired(factory, required) + old = self._adapter_registrations.get((required, provided, name)) + if (old is None) or ((factory is not None) and + (factory != old[0])): + return False + + del self._adapter_registrations[(required, provided, name)] + self.adapters.unregister(required, provided, name) + + notify(Unregistered( + AdapterRegistration(self, required, provided, name, + *old) + )) + + return True + + def registeredAdapters(self): + for ((required, provided, name), (component, info) + ) in iter(self._adapter_registrations.items()): + yield AdapterRegistration(self, required, provided, name, + component, info) + + def queryAdapter(self, object, interface, name=u'', default=None): + return self.adapters.queryAdapter(object, interface, name, default) + + def getAdapter(self, object, interface, name=u''): + adapter = self.adapters.queryAdapter(object, interface, name) + if adapter is None: + raise ComponentLookupError(object, interface, name) + return adapter + + def queryMultiAdapter(self, objects, interface, name=u'', + default=None): + return self.adapters.queryMultiAdapter( + objects, interface, name, default) + + def getMultiAdapter(self, objects, interface, name=u''): + adapter = self.adapters.queryMultiAdapter(objects, interface, name) + if adapter is None: + raise ComponentLookupError(objects, interface, name) + return adapter + + def getAdapters(self, objects, provided): + for name, factory in self.adapters.lookupAll( + list(map(providedBy, objects)), + provided): + adapter = factory(*objects) + if adapter is not None: + yield name, adapter + + def registerSubscriptionAdapter(self, + factory, required=None, provided=None, + name=u'', info=u'', + event=True): + if name: + raise TypeError("Named subscribers are not yet supported") + if provided is None: + provided = _getAdapterProvided(factory) + required = _getAdapterRequired(factory, required) + self._subscription_registrations.append( + (required, provided, name, factory, info) + ) + self.adapters.subscribe(required, provided, factory) + + if event: + notify(Registered( + SubscriptionRegistration(self, required, provided, name, + factory, info) + )) + + def registeredSubscriptionAdapters(self): + for data in self._subscription_registrations: + yield SubscriptionRegistration(self, *data) + + def unregisterSubscriptionAdapter(self, factory=None, + required=None, provided=None, name=u'', + ): + if name: + raise TypeError("Named subscribers are not yet supported") + if provided is None: + if factory is None: + raise TypeError("Must specify one of factory and provided") + provided = _getAdapterProvided(factory) + + if (required is None) and (factory is None): + raise TypeError("Must specify one of factory and required") + + required = _getAdapterRequired(factory, required) + + if factory is None: + new = [(r, p, n, f, i) + for (r, p, n, f, i) + in self._subscription_registrations + if not (r == required and p == provided) + ] + else: + new = [(r, p, n, f, i) + for (r, p, n, f, i) + in self._subscription_registrations + if not (r == required and p == provided and f == factory) + ] + + if len(new) == len(self._subscription_registrations): + return False + + + self._subscription_registrations[:] = new + self.adapters.unsubscribe(required, provided, factory) + + notify(Unregistered( + SubscriptionRegistration(self, required, provided, name, + factory, '') + )) + + return True + + def subscribers(self, objects, provided): + return self.adapters.subscribers(objects, provided) + + def registerHandler(self, + factory, required=None, + name=u'', info=u'', + event=True): + if name: + raise TypeError("Named handlers are not yet supported") + required = _getAdapterRequired(factory, required) + self._handler_registrations.append( + (required, name, factory, info) + ) + self.adapters.subscribe(required, None, factory) + + if event: + notify(Registered( + HandlerRegistration(self, required, name, factory, info) + )) + + def registeredHandlers(self): + for data in self._handler_registrations: + yield HandlerRegistration(self, *data) + + def unregisterHandler(self, factory=None, required=None, name=u''): + if name: + raise TypeError("Named subscribers are not yet supported") + + if (required is None) and (factory is None): + raise TypeError("Must specify one of factory and required") + + required = _getAdapterRequired(factory, required) + + if factory is None: + new = [(r, n, f, i) + for (r, n, f, i) + in self._handler_registrations + if r != required + ] + else: + new = [(r, n, f, i) + for (r, n, f, i) + in self._handler_registrations + if not (r == required and f == factory) + ] + + if len(new) == len(self._handler_registrations): + return False + + self._handler_registrations[:] = new + self.adapters.unsubscribe(required, None, factory) + + notify(Unregistered( + HandlerRegistration(self, required, name, factory, '') + )) + + return True + + def handle(self, *objects): + self.adapters.subscribers(objects, None) + + +def _getName(component): + try: + return component.__component_name__ + except AttributeError: + return u'' + +def _getUtilityProvided(component): + provided = list(providedBy(component)) + if len(provided) == 1: + return provided[0] + raise TypeError( + "The utility doesn't provide a single interface " + "and no provided interface was specified.") + +def _getAdapterProvided(factory): + provided = list(implementedBy(factory)) + if len(provided) == 1: + return provided[0] + raise TypeError( + "The adapter factory doesn't implement a single interface " + "and no provided interface was specified.") + +def _getAdapterRequired(factory, required): + if required is None: + try: + required = factory.__component_adapts__ + except AttributeError: + raise TypeError( + "The adapter factory doesn't have a __component_adapts__ " + "attribute and no required specifications were specified" + ) + elif ISpecification.providedBy(required): + raise TypeError("the required argument should be a list of " + "interfaces, not a single interface") + + result = [] + for r in required: + if r is None: + r = Interface + elif not ISpecification.providedBy(r): + if isinstance(r, CLASS_TYPES): + r = implementedBy(r) + else: + raise TypeError("Required specification must be a " + "specification or class." + ) + result.append(r) + return tuple(result) + + +@implementer(IUtilityRegistration) +class UtilityRegistration(object): + + def __init__(self, registry, provided, name, component, doc, factory=None): + (self.registry, self.provided, self.name, self.component, self.info, + self.factory + ) = registry, provided, name, component, doc, factory + + def __repr__(self): + return '%s(%r, %s, %r, %s, %r, %r)' % ( + self.__class__.__name__, + self.registry, + getattr(self.provided, '__name__', None), self.name, + getattr(self.component, '__name__', repr(self.component)), + self.factory, self.info, + ) + + def __hash__(self): + return id(self) + + def __eq__(self, other): + return repr(self) == repr(other) + + def __ne__(self, other): + return repr(self) != repr(other) + + def __lt__(self, other): + return repr(self) < repr(other) + + def __le__(self, other): + return repr(self) <= repr(other) + + def __gt__(self, other): + return repr(self) > repr(other) + + def __ge__(self, other): + return repr(self) >= repr(other) + +@implementer(IAdapterRegistration) +class AdapterRegistration(object): + + def __init__(self, registry, required, provided, name, component, doc): + (self.registry, self.required, self.provided, self.name, + self.factory, self.info + ) = registry, required, provided, name, component, doc + + def __repr__(self): + return '%s(%r, %s, %s, %r, %s, %r)' % ( + self.__class__.__name__, + self.registry, + '[' + ", ".join([r.__name__ for r in self.required]) + ']', + getattr(self.provided, '__name__', None), self.name, + getattr(self.factory, '__name__', repr(self.factory)), self.info, + ) + + def __hash__(self): + return id(self) + + def __eq__(self, other): + return repr(self) == repr(other) + + def __ne__(self, other): + return repr(self) != repr(other) + + def __lt__(self, other): + return repr(self) < repr(other) + + def __le__(self, other): + return repr(self) <= repr(other) + + def __gt__(self, other): + return repr(self) > repr(other) + + def __ge__(self, other): + return repr(self) >= repr(other) + +@implementer_only(ISubscriptionAdapterRegistration) +class SubscriptionRegistration(AdapterRegistration): + pass + + +@implementer_only(IHandlerRegistration) +class HandlerRegistration(AdapterRegistration): + + def __init__(self, registry, required, name, handler, doc): + (self.registry, self.required, self.name, self.handler, self.info + ) = registry, required, name, handler, doc + + @property + def factory(self): + return self.handler + + provided = None + + def __repr__(self): + return '%s(%r, %s, %r, %s, %r)' % ( + self.__class__.__name__, + self.registry, + '[' + ", ".join([r.__name__ for r in self.required]) + ']', + self.name, + getattr(self.factory, '__name__', repr(self.factory)), self.info, + ) diff --git a/lib/zope/interface/ro.py b/lib/zope/interface/ro.py new file mode 100644 index 0000000..84f10dc --- /dev/null +++ b/lib/zope/interface/ro.py @@ -0,0 +1,64 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Compute a resolution order for an object and its bases +""" +__docformat__ = 'restructuredtext' + +def _mergeOrderings(orderings): + """Merge multiple orderings so that within-ordering order is preserved + + Orderings are constrained in such a way that if an object appears + in two or more orderings, then the suffix that begins with the + object must be in both orderings. + + For example: + + >>> _mergeOrderings([ + ... ['x', 'y', 'z'], + ... ['q', 'z'], + ... [1, 3, 5], + ... ['z'] + ... ]) + ['x', 'y', 'q', 1, 3, 5, 'z'] + + """ + + seen = {} + result = [] + for ordering in reversed(orderings): + for o in reversed(ordering): + if o not in seen: + seen[o] = 1 + result.insert(0, o) + + return result + +def _flatten(ob): + result = [ob] + i = 0 + for ob in iter(result): + i += 1 + # The recursive calls can be avoided by inserting the base classes + # into the dynamically growing list directly after the currently + # considered object; the iterator makes sure this will keep working + # in the future, since it cannot rely on the length of the list + # by definition. + result[i:i] = ob.__bases__ + return result + + +def ro(object): + """Compute a "resolution order" for an object + """ + return _mergeOrderings([_flatten(object)]) diff --git a/lib/zope/interface/tests/__init__.py b/lib/zope/interface/tests/__init__.py new file mode 100644 index 0000000..15259c1 --- /dev/null +++ b/lib/zope/interface/tests/__init__.py @@ -0,0 +1 @@ +# Make this directory a package. diff --git a/lib/zope/interface/tests/advisory_testing.py b/lib/zope/interface/tests/advisory_testing.py new file mode 100644 index 0000000..b159e93 --- /dev/null +++ b/lib/zope/interface/tests/advisory_testing.py @@ -0,0 +1,42 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +import sys + +from zope.interface.advice import addClassAdvisor +from zope.interface.advice import getFrameInfo + +my_globals = globals() + +def ping(log, value): + + def pong(klass): + log.append((value,klass)) + return [klass] + + addClassAdvisor(pong) + +try: + from types import ClassType + + class ClassicClass: + __metaclass__ = ClassType + classLevelFrameInfo = getFrameInfo(sys._getframe()) +except ImportError: + ClassicClass = None + +class NewStyleClass: + __metaclass__ = type + classLevelFrameInfo = getFrameInfo(sys._getframe()) + +moduleLevelFrameInfo = getFrameInfo(sys._getframe()) diff --git a/lib/zope/interface/tests/dummy.py b/lib/zope/interface/tests/dummy.py new file mode 100644 index 0000000..6b142ff --- /dev/null +++ b/lib/zope/interface/tests/dummy.py @@ -0,0 +1,23 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" Dummy Module +""" +from zope.interface import moduleProvides +from zope.interface.tests.idummy import IDummyModule + +moduleProvides(IDummyModule) + +def bar(baz): + # Note: no 'self', because the module provides the interface directly. + raise NotImplementedError() diff --git a/lib/zope/interface/tests/idummy.py b/lib/zope/interface/tests/idummy.py new file mode 100644 index 0000000..1e34fe0 --- /dev/null +++ b/lib/zope/interface/tests/idummy.py @@ -0,0 +1,23 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" Interface describing API of zope.interface.tests.dummy test module +""" +from zope.interface import Interface + +class IDummyModule(Interface): + """ Dummy interface for unit tests. + """ + def bar(baz): + """ Just a note. + """ diff --git a/lib/zope/interface/tests/ifoo.py b/lib/zope/interface/tests/ifoo.py new file mode 100644 index 0000000..29a7877 --- /dev/null +++ b/lib/zope/interface/tests/ifoo.py @@ -0,0 +1,26 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""IFoo test module +""" +from zope.interface import Interface + +class IFoo(Interface): + """ + Dummy interface for unit tests. + """ + + def bar(baz): + """ + Just a note. + """ diff --git a/lib/zope/interface/tests/ifoo_other.py b/lib/zope/interface/tests/ifoo_other.py new file mode 100644 index 0000000..29a7877 --- /dev/null +++ b/lib/zope/interface/tests/ifoo_other.py @@ -0,0 +1,26 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""IFoo test module +""" +from zope.interface import Interface + +class IFoo(Interface): + """ + Dummy interface for unit tests. + """ + + def bar(baz): + """ + Just a note. + """ diff --git a/lib/zope/interface/tests/m1.py b/lib/zope/interface/tests/m1.py new file mode 100644 index 0000000..d311fb4 --- /dev/null +++ b/lib/zope/interface/tests/m1.py @@ -0,0 +1,21 @@ +############################################################################## +# +# Copyright (c) 2004 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Test module that declares an interface +""" +from zope.interface import Interface, moduleProvides + +class I1(Interface): pass +class I2(Interface): pass + +moduleProvides(I1, I2) diff --git a/lib/zope/interface/tests/m2.py b/lib/zope/interface/tests/m2.py new file mode 100644 index 0000000..511cd9c --- /dev/null +++ b/lib/zope/interface/tests/m2.py @@ -0,0 +1,15 @@ +############################################################################## +# +# Copyright (c) 2004 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Test module that doesn't declare an interface +""" diff --git a/lib/zope/interface/tests/odd.py b/lib/zope/interface/tests/odd.py new file mode 100644 index 0000000..74c6158 --- /dev/null +++ b/lib/zope/interface/tests/odd.py @@ -0,0 +1,128 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Odd meta class that doesn't subclass type. + +This is used for testing support for ExtensionClass in new interfaces. + + >>> class A(object): + ... __metaclass__ = MetaClass + ... a = 1 + ... + >>> A.__name__ + 'A' + >>> A.__bases__ == (object,) + True + >>> class B(object): + ... __metaclass__ = MetaClass + ... b = 1 + ... + >>> class C(A, B): pass + ... + >>> C.__name__ + 'C' + >>> int(C.__bases__ == (A, B)) + 1 + >>> a = A() + >>> aa = A() + >>> a.a + 1 + >>> aa.a + 1 + >>> aa.a = 2 + >>> a.a + 1 + >>> aa.a + 2 + >>> c = C() + >>> c.a + 1 + >>> c.b + 1 + >>> c.b = 2 + >>> c.b + 2 + >>> C.c = 1 + >>> c.c + 1 + >>> import sys + >>> if sys.version[0] == '2': # This test only makes sense under Python 2.x + ... from types import ClassType + ... assert not isinstance(C, (type, ClassType)) + + >>> int(C.__class__.__class__ is C.__class__) + 1 +""" + +# class OddClass is an odd meta class + +class MetaMetaClass(type): + + def __getattribute__(cls, name): + if name == '__class__': + return cls + # Under Python 3.6, __prepare__ gets requested + return type.__getattribute__(cls, name) + + +class MetaClass(object): + """Odd classes + """ + + def __init__(self, name, bases, dict): + self.__name__ = name + self.__bases__ = bases + self.__dict__.update(dict) + + def __call__(self): + return OddInstance(self) + + def __getattr__(self, name): + for b in self.__bases__: + v = getattr(b, name, self) + if v is not self: + return v + raise AttributeError(name) + + def __repr__(self): # pragma: no cover + return "<odd class %s at %s>" % (self.__name__, hex(id(self))) + + +MetaClass = MetaMetaClass('MetaClass', + MetaClass.__bases__, + {k: v for k, v in MetaClass.__dict__.items() + if k not in ('__dict__',)}) + +class OddInstance(object): + + def __init__(self, cls): + self.__dict__['__class__'] = cls + + def __getattribute__(self, name): + dict = object.__getattribute__(self, '__dict__') + if name == '__dict__': + return dict + v = dict.get(name, self) + if v is not self: + return v + return getattr(dict['__class__'], name) + + def __setattr__(self, name, v): + self.__dict__[name] = v + + def __delattr__(self, name): + raise NotImplementedError() + + def __repr__(self): # pragma: no cover + return "<odd %s instance at %s>" % ( + self.__class__.__name__, hex(id(self))) diff --git a/lib/zope/interface/tests/test_adapter.py b/lib/zope/interface/tests/test_adapter.py new file mode 100644 index 0000000..41c618c --- /dev/null +++ b/lib/zope/interface/tests/test_adapter.py @@ -0,0 +1,1419 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Adapter registry tests +""" +import unittest + + +def _makeInterfaces(): + from zope.interface import Interface + + class IB0(Interface): pass + class IB1(IB0): pass + class IB2(IB0): pass + class IB3(IB2, IB1): pass + class IB4(IB1, IB2): pass + + class IF0(Interface): pass + class IF1(IF0): pass + + class IR0(Interface): pass + class IR1(IR0): pass + + return IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 + + +class BaseAdapterRegistryTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.adapter import BaseAdapterRegistry + class _CUT(BaseAdapterRegistry): + class LookupClass(object): + _changed = _extendors = () + def __init__(self, reg): + pass + def changed(self, orig): + self._changed += (orig,) + def add_extendor(self, provided): + self._extendors += (provided,) + def remove_extendor(self, provided): + self._extendors = tuple([x for x in self._extendors + if x != provided]) + for name in BaseAdapterRegistry._delegated: + setattr(_CUT.LookupClass, name, object()) + return _CUT + + def _makeOne(self): + return self._getTargetClass()() + + def test_lookup_delegation(self): + CUT = self._getTargetClass() + registry = CUT() + for name in CUT._delegated: + self.assertTrue( + getattr(registry, name) is getattr(registry._v_lookup, name)) + + def test__generation_on_first_creation(self): + registry = self._makeOne() + # Bumped to 1 in BaseAdapterRegistry.__init__ + self.assertEqual(registry._generation, 1) + + def test__generation_after_calling_changed(self): + registry = self._makeOne() + orig = object() + registry.changed(orig) + # Bumped to 1 in BaseAdapterRegistry.__init__ + self.assertEqual(registry._generation, 2) + self.assertEqual(registry._v_lookup._changed, (registry, orig,)) + + def test__generation_after_changing___bases__(self): + class _Base(object): pass + registry = self._makeOne() + registry.__bases__ = (_Base,) + self.assertEqual(registry._generation, 2) + + def test_register(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + registry.register([IB0], IR0, '', 'A1') + self.assertEqual(registry.registered([IB0], IR0, ''), 'A1') + self.assertEqual(len(registry._adapters), 2) #order 0 and order 1 + self.assertEqual(registry._generation, 2) + + def test_register_with_invalid_name(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + with self.assertRaises(ValueError): + registry.register([IB0], IR0, object(), 'A1') + + def test_register_with_value_None_unregisters(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + registry.register([None], IR0, '', 'A1') + registry.register([None], IR0, '', None) + self.assertEqual(len(registry._adapters), 0) + + def test_register_with_same_value(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + _value = object() + registry.register([None], IR0, '', _value) + _before = registry._generation + registry.register([None], IR0, '', _value) + self.assertEqual(registry._generation, _before) # skipped changed() + + def test_registered_empty(self): + registry = self._makeOne() + self.assertEqual(registry.registered([None], None, ''), None) + + def test_registered_non_empty_miss(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + registry.register([IB1], None, '', 'A1') + self.assertEqual(registry.registered([IB2], None, ''), None) + + def test_registered_non_empty_hit(self): + registry = self._makeOne() + registry.register([None], None, '', 'A1') + self.assertEqual(registry.registered([None], None, ''), 'A1') + + def test_unregister_empty(self): + registry = self._makeOne() + registry.unregister([None], None, '') #doesn't raise + self.assertEqual(registry.registered([None], None, ''), None) + + def test_unregister_non_empty_miss_on_required(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + registry.register([IB1], None, '', 'A1') + registry.unregister([IB2], None, '') #doesn't raise + self.assertEqual(registry.registered([IB1], None, ''), 'A1') + + def test_unregister_non_empty_miss_on_name(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + registry.register([IB1], None, '', 'A1') + registry.unregister([IB1], None, 'nonesuch') #doesn't raise + self.assertEqual(registry.registered([IB1], None, ''), 'A1') + + def test_unregister_with_value_not_None_miss(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + orig = object() + nomatch = object() + registry.register([IB1], None, '', orig) + registry.unregister([IB1], None, '', nomatch) #doesn't raise + self.assertTrue(registry.registered([IB1], None, '') is orig) + + def test_unregister_hit_clears_empty_subcomponents(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + one = object() + another = object() + registry.register([IB1, IB2], None, '', one) + registry.register([IB1, IB3], None, '', another) + self.assertTrue(IB2 in registry._adapters[2][IB1]) + self.assertTrue(IB3 in registry._adapters[2][IB1]) + registry.unregister([IB1, IB3], None, '', another) + self.assertTrue(IB2 in registry._adapters[2][IB1]) + self.assertFalse(IB3 in registry._adapters[2][IB1]) + + def test_unsubscribe_empty(self): + registry = self._makeOne() + registry.unsubscribe([None], None, '') #doesn't raise + self.assertEqual(registry.registered([None], None, ''), None) + + def test_unsubscribe_hit(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + orig = object() + registry.subscribe([IB1], None, orig) + registry.unsubscribe([IB1], None, orig) #doesn't raise + self.assertEqual(len(registry._subscribers), 0) + + def test_unsubscribe_after_multiple(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + first = object() + second = object() + third = object() + fourth = object() + registry.subscribe([IB1], None, first) + registry.subscribe([IB1], None, second) + registry.subscribe([IB1], IR0, third) + registry.subscribe([IB1], IR0, fourth) + registry.unsubscribe([IB1], IR0, fourth) + registry.unsubscribe([IB1], IR0, third) + registry.unsubscribe([IB1], None, second) + registry.unsubscribe([IB1], None, first) + self.assertEqual(len(registry._subscribers), 0) + + def test_unsubscribe_w_None_after_multiple(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + first = object() + second = object() + third = object() + registry.subscribe([IB1], None, first) + registry.subscribe([IB1], None, second) + registry.unsubscribe([IB1], None) + self.assertEqual(len(registry._subscribers), 0) + + def test_unsubscribe_non_empty_miss_on_required(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + registry.subscribe([IB1], None, 'A1') + self.assertEqual(len(registry._subscribers), 2) + registry.unsubscribe([IB2], None, '') #doesn't raise + self.assertEqual(len(registry._subscribers), 2) + + def test_unsubscribe_non_empty_miss_on_value(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + registry.subscribe([IB1], None, 'A1') + self.assertEqual(len(registry._subscribers), 2) + registry.unsubscribe([IB1], None, 'A2') #doesn't raise + self.assertEqual(len(registry._subscribers), 2) + + def test_unsubscribe_with_value_not_None_miss(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + orig = object() + nomatch = object() + registry.subscribe([IB1], None, orig) + registry.unsubscribe([IB1], None, nomatch) #doesn't raise + self.assertEqual(len(registry._subscribers), 2) + + def _instance_method_notify_target(self): + self.fail("Example method, not intended to be called.") + + def test_unsubscribe_instance_method(self): + IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() + registry = self._makeOne() + self.assertEqual(len(registry._subscribers), 0) + registry.subscribe([IB1], None, self._instance_method_notify_target) + registry.unsubscribe([IB1], None, self._instance_method_notify_target) + self.assertEqual(len(registry._subscribers), 0) + + +class LookupBaseFallbackTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.adapter import LookupBaseFallback + return LookupBaseFallback + + def _makeOne(self, uc_lookup=None, uc_lookupAll=None, + uc_subscriptions=None): + if uc_lookup is None: + def uc_lookup(self, required, provided, name): + pass + if uc_lookupAll is None: + def uc_lookupAll(self, required, provided): + raise NotImplementedError() + if uc_subscriptions is None: + def uc_subscriptions(self, required, provided): + raise NotImplementedError() + class Derived(self._getTargetClass()): + _uncached_lookup = uc_lookup + _uncached_lookupAll = uc_lookupAll + _uncached_subscriptions = uc_subscriptions + return Derived() + + def test_lookup_w_invalid_name(self): + def _lookup(self, required, provided, name): + self.fail("This should never be called") + lb = self._makeOne(uc_lookup=_lookup) + with self.assertRaises(ValueError): + lb.lookup(('A',), 'B', object()) + + def test_lookup_miss_no_default(self): + _called_with = [] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return None + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup(('A',), 'B', 'C') + self.assertTrue(found is None) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + + def test_lookup_miss_w_default(self): + _called_with = [] + _default = object() + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return None + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup(('A',), 'B', 'C', _default) + self.assertTrue(found is _default) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + + def test_lookup_not_cached(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup(('A',), 'B', 'C') + self.assertTrue(found is a) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + self.assertEqual(_results, [b, c]) + + def test_lookup_cached(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup(('A',), 'B', 'C') + found = lb.lookup(('A',), 'B', 'C') + self.assertTrue(found is a) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + self.assertEqual(_results, [b, c]) + + def test_lookup_not_cached_multi_required(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup(('A', 'D'), 'B', 'C') + self.assertTrue(found is a) + self.assertEqual(_called_with, [(('A', 'D'), 'B', 'C')]) + self.assertEqual(_results, [b, c]) + + def test_lookup_cached_multi_required(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup(('A', 'D'), 'B', 'C') + found = lb.lookup(('A', 'D'), 'B', 'C') + self.assertTrue(found is a) + self.assertEqual(_called_with, [(('A', 'D'), 'B', 'C')]) + self.assertEqual(_results, [b, c]) + + def test_lookup_not_cached_after_changed(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup(('A',), 'B', 'C') + lb.changed(lb) + found = lb.lookup(('A',), 'B', 'C') + self.assertTrue(found is b) + self.assertEqual(_called_with, + [(('A',), 'B', 'C'), (('A',), 'B', 'C')]) + self.assertEqual(_results, [c]) + + def test_lookup1_w_invalid_name(self): + def _lookup(self, required, provided, name): + self.fail("This should never be called") + + lb = self._makeOne(uc_lookup=_lookup) + with self.assertRaises(ValueError): + lb.lookup1('A', 'B', object()) + + def test_lookup1_miss_no_default(self): + _called_with = [] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return None + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup1('A', 'B', 'C') + self.assertTrue(found is None) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + + def test_lookup1_miss_w_default(self): + _called_with = [] + _default = object() + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return None + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup1('A', 'B', 'C', _default) + self.assertTrue(found is _default) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + + def test_lookup1_miss_w_default_negative_cache(self): + _called_with = [] + _default = object() + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return None + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup1('A', 'B', 'C', _default) + self.assertTrue(found is _default) + found = lb.lookup1('A', 'B', 'C', _default) + self.assertTrue(found is _default) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + + def test_lookup1_not_cached(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup1('A', 'B', 'C') + self.assertTrue(found is a) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + self.assertEqual(_results, [b, c]) + + def test_lookup1_cached(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup1('A', 'B', 'C') + found = lb.lookup1('A', 'B', 'C') + self.assertTrue(found is a) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + self.assertEqual(_results, [b, c]) + + def test_lookup1_not_cached_after_changed(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + lb = self._makeOne(uc_lookup=_lookup) + found = lb.lookup1('A', 'B', 'C') + lb.changed(lb) + found = lb.lookup1('A', 'B', 'C') + self.assertTrue(found is b) + self.assertEqual(_called_with, + [(('A',), 'B', 'C'), (('A',), 'B', 'C')]) + self.assertEqual(_results, [c]) + + def test_adapter_hook_w_invalid_name(self): + req, prv = object(), object() + lb = self._makeOne() + with self.assertRaises(ValueError): + lb.adapter_hook(prv, req, object()) + + def test_adapter_hook_miss_no_default(self): + req, prv = object(), object() + lb = self._makeOne() + found = lb.adapter_hook(prv, req, '') + self.assertTrue(found is None) + + def test_adapter_hook_miss_w_default(self): + req, prv, _default = object(), object(), object() + lb = self._makeOne() + found = lb.adapter_hook(prv, req, '', _default) + self.assertTrue(found is _default) + + def test_adapter_hook_hit_factory_returns_None(self): + _f_called_with = [] + def _factory(context): + _f_called_with.append(context) + return None + def _lookup(self, required, provided, name): + return _factory + req, prv, _default = object(), object(), object() + lb = self._makeOne(uc_lookup=_lookup) + adapted = lb.adapter_hook(prv, req, 'C', _default) + self.assertTrue(adapted is _default) + self.assertEqual(_f_called_with, [req]) + + def test_adapter_hook_hit_factory_returns_adapter(self): + _f_called_with = [] + _adapter = object() + def _factory(context): + _f_called_with.append(context) + return _adapter + def _lookup(self, required, provided, name): + return _factory + req, prv, _default = object(), object(), object() + lb = self._makeOne(uc_lookup=_lookup) + adapted = lb.adapter_hook(prv, req, 'C', _default) + self.assertTrue(adapted is _adapter) + self.assertEqual(_f_called_with, [req]) + + def test_queryAdapter(self): + _f_called_with = [] + _adapter = object() + def _factory(context): + _f_called_with.append(context) + return _adapter + def _lookup(self, required, provided, name): + return _factory + req, prv, _default = object(), object(), object() + lb = self._makeOne(uc_lookup=_lookup) + adapted = lb.queryAdapter(req, prv, 'C', _default) + self.assertTrue(adapted is _adapter) + self.assertEqual(_f_called_with, [req]) + + def test_lookupAll_uncached(self): + _called_with = [] + _results = [object(), object(), object()] + def _lookupAll(self, required, provided): + _called_with.append((required, provided)) + return tuple(_results) + lb = self._makeOne(uc_lookupAll=_lookupAll) + found = lb.lookupAll('A', 'B') + self.assertEqual(found, tuple(_results)) + self.assertEqual(_called_with, [(('A',), 'B')]) + + def test_lookupAll_cached(self): + _called_with = [] + _results = [object(), object(), object()] + def _lookupAll(self, required, provided): + _called_with.append((required, provided)) + return tuple(_results) + lb = self._makeOne(uc_lookupAll=_lookupAll) + found = lb.lookupAll('A', 'B') + found = lb.lookupAll('A', 'B') + self.assertEqual(found, tuple(_results)) + self.assertEqual(_called_with, [(('A',), 'B')]) + + def test_subscriptions_uncached(self): + _called_with = [] + _results = [object(), object(), object()] + def _subscriptions(self, required, provided): + _called_with.append((required, provided)) + return tuple(_results) + lb = self._makeOne(uc_subscriptions=_subscriptions) + found = lb.subscriptions('A', 'B') + self.assertEqual(found, tuple(_results)) + self.assertEqual(_called_with, [(('A',), 'B')]) + + def test_subscriptions_cached(self): + _called_with = [] + _results = [object(), object(), object()] + def _subscriptions(self, required, provided): + _called_with.append((required, provided)) + return tuple(_results) + lb = self._makeOne(uc_subscriptions=_subscriptions) + found = lb.subscriptions('A', 'B') + found = lb.subscriptions('A', 'B') + self.assertEqual(found, tuple(_results)) + self.assertEqual(_called_with, [(('A',), 'B')]) + + +class LookupBaseTests(LookupBaseFallbackTests): + + def _getTargetClass(self): + from zope.interface.adapter import LookupBase + return LookupBase + + def test_optimizations(self): + from zope.interface.adapter import LookupBaseFallback + try: + import zope.interface._zope_interface_coptimizations + except ImportError: + self.assertIs(self._getTargetClass(), LookupBaseFallback) + else: + self.assertIsNot(self._getTargetClass(), LookupBaseFallback) + + +class VerifyingBaseFallbackTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.adapter import VerifyingBaseFallback + return VerifyingBaseFallback + + def _makeOne(self, registry, uc_lookup=None, uc_lookupAll=None, + uc_subscriptions=None): + if uc_lookup is None: + def uc_lookup(self, required, provided, name): + raise NotImplementedError() + if uc_lookupAll is None: + def uc_lookupAll(self, required, provided): + raise NotImplementedError() + if uc_subscriptions is None: + def uc_subscriptions(self, required, provided): + raise NotImplementedError() + class Derived(self._getTargetClass()): + _uncached_lookup = uc_lookup + _uncached_lookupAll = uc_lookupAll + _uncached_subscriptions = uc_subscriptions + def __init__(self, registry): + super(Derived, self).__init__() + self._registry = registry + derived = Derived(registry) + derived.changed(derived) # init. '_verify_ro' / '_verify_generations' + return derived + + def _makeRegistry(self, depth): + class WithGeneration(object): + _generation = 1 + class Registry: + def __init__(self, depth): + self.ro = [WithGeneration() for i in range(depth)] + return Registry(depth) + + def test_lookup(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + reg = self._makeRegistry(3) + lb = self._makeOne(reg, uc_lookup=_lookup) + found = lb.lookup(('A',), 'B', 'C') + found = lb.lookup(('A',), 'B', 'C') + self.assertTrue(found is a) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + self.assertEqual(_results, [b, c]) + reg.ro[1]._generation += 1 + found = lb.lookup(('A',), 'B', 'C') + self.assertTrue(found is b) + self.assertEqual(_called_with, + [(('A',), 'B', 'C'), (('A',), 'B', 'C')]) + self.assertEqual(_results, [c]) + + def test_lookup1(self): + _called_with = [] + a, b, c = object(), object(), object() + _results = [a, b, c] + def _lookup(self, required, provided, name): + _called_with.append((required, provided, name)) + return _results.pop(0) + reg = self._makeRegistry(3) + lb = self._makeOne(reg, uc_lookup=_lookup) + found = lb.lookup1('A', 'B', 'C') + found = lb.lookup1('A', 'B', 'C') + self.assertTrue(found is a) + self.assertEqual(_called_with, [(('A',), 'B', 'C')]) + self.assertEqual(_results, [b, c]) + reg.ro[1]._generation += 1 + found = lb.lookup1('A', 'B', 'C') + self.assertTrue(found is b) + self.assertEqual(_called_with, + [(('A',), 'B', 'C'), (('A',), 'B', 'C')]) + self.assertEqual(_results, [c]) + + def test_adapter_hook(self): + a, b, _c = [object(), object(), object()] + def _factory1(context): + return a + def _factory2(context): + return b + def _factory3(context): + self.fail("This should never be called") + _factories = [_factory1, _factory2, _factory3] + def _lookup(self, required, provided, name): + return _factories.pop(0) + req, prv, _default = object(), object(), object() + reg = self._makeRegistry(3) + lb = self._makeOne(reg, uc_lookup=_lookup) + adapted = lb.adapter_hook(prv, req, 'C', _default) + self.assertTrue(adapted is a) + adapted = lb.adapter_hook(prv, req, 'C', _default) + self.assertTrue(adapted is a) + reg.ro[1]._generation += 1 + adapted = lb.adapter_hook(prv, req, 'C', _default) + self.assertTrue(adapted is b) + + def test_queryAdapter(self): + a, b, _c = [object(), object(), object()] + def _factory1(context): + return a + def _factory2(context): + return b + def _factory3(context): + self.fail("This should never be called") + _factories = [_factory1, _factory2, _factory3] + def _lookup(self, required, provided, name): + return _factories.pop(0) + req, prv, _default = object(), object(), object() + reg = self._makeRegistry(3) + lb = self._makeOne(reg, uc_lookup=_lookup) + adapted = lb.queryAdapter(req, prv, 'C', _default) + self.assertTrue(adapted is a) + adapted = lb.queryAdapter(req, prv, 'C', _default) + self.assertTrue(adapted is a) + reg.ro[1]._generation += 1 + adapted = lb.adapter_hook(prv, req, 'C', _default) + self.assertTrue(adapted is b) + + def test_lookupAll(self): + _results_1 = [object(), object(), object()] + _results_2 = [object(), object(), object()] + _results = [_results_1, _results_2] + def _lookupAll(self, required, provided): + return tuple(_results.pop(0)) + reg = self._makeRegistry(3) + lb = self._makeOne(reg, uc_lookupAll=_lookupAll) + found = lb.lookupAll('A', 'B') + self.assertEqual(found, tuple(_results_1)) + found = lb.lookupAll('A', 'B') + self.assertEqual(found, tuple(_results_1)) + reg.ro[1]._generation += 1 + found = lb.lookupAll('A', 'B') + self.assertEqual(found, tuple(_results_2)) + + def test_subscriptions(self): + _results_1 = [object(), object(), object()] + _results_2 = [object(), object(), object()] + _results = [_results_1, _results_2] + def _subscriptions(self, required, provided): + return tuple(_results.pop(0)) + reg = self._makeRegistry(3) + lb = self._makeOne(reg, uc_subscriptions=_subscriptions) + found = lb.subscriptions('A', 'B') + self.assertEqual(found, tuple(_results_1)) + found = lb.subscriptions('A', 'B') + self.assertEqual(found, tuple(_results_1)) + reg.ro[1]._generation += 1 + found = lb.subscriptions('A', 'B') + self.assertEqual(found, tuple(_results_2)) + + +class VerifyingBaseTests(VerifyingBaseFallbackTests): + + def _getTargetClass(self): + from zope.interface.adapter import VerifyingBase + return VerifyingBase + + def test_optimizations(self): + from zope.interface.adapter import VerifyingBaseFallback + try: + import zope.interface._zope_interface_coptimizations + except ImportError: + self.assertIs(self._getTargetClass(), VerifyingBaseFallback) + else: + self.assertIsNot(self._getTargetClass(), VerifyingBaseFallback) + + +class AdapterLookupBaseTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.adapter import AdapterLookupBase + return AdapterLookupBase + + def _makeOne(self, registry): + return self._getTargetClass()(registry) + + def _makeSubregistry(self, *provided): + class Subregistry: + def __init__(self): + self._adapters = [] + self._subscribers = [] + return Subregistry() + + def _makeRegistry(self, *provided): + class Registry: + def __init__(self, provided): + self._provided = provided + self.ro = [] + return Registry(provided) + + def test_ctor_empty_registry(self): + registry = self._makeRegistry() + alb = self._makeOne(registry) + self.assertEqual(alb._extendors, {}) + + def test_ctor_w_registry_provided(self): + from zope.interface import Interface + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + alb = self._makeOne(registry) + self.assertEqual(sorted(alb._extendors.keys()), + sorted([IBar, IFoo, Interface])) + self.assertEqual(alb._extendors[IFoo], [IFoo]) + self.assertEqual(alb._extendors[IBar], [IBar]) + self.assertEqual(sorted(alb._extendors[Interface]), + sorted([IFoo, IBar])) + + def test_changed_empty_required(self): + # ALB.changed expects to call a mixed in changed. + class Mixin(object): + def changed(self, *other): + pass + class Derived(self._getTargetClass(), Mixin): + pass + registry = self._makeRegistry() + alb = Derived(registry) + alb.changed(alb) + + def test_changed_w_required(self): + # ALB.changed expects to call a mixed in changed. + class Mixin(object): + def changed(self, *other): + pass + class Derived(self._getTargetClass(), Mixin): + pass + class FauxWeakref(object): + _unsub = None + def __init__(self, here): + self._here = here + def __call__(self): + if self._here: + return self + def unsubscribe(self, target): + self._unsub = target + gone = FauxWeakref(False) + here = FauxWeakref(True) + registry = self._makeRegistry() + alb = Derived(registry) + alb._required[gone] = 1 + alb._required[here] = 1 + alb.changed(alb) + self.assertEqual(len(alb._required), 0) + self.assertEqual(gone._unsub, None) + self.assertEqual(here._unsub, alb) + + def test_init_extendors_after_registry_update(self): + from zope.interface import Interface + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry() + alb = self._makeOne(registry) + registry._provided = [IFoo, IBar] + alb.init_extendors() + self.assertEqual(sorted(alb._extendors.keys()), + sorted([IBar, IFoo, Interface])) + self.assertEqual(alb._extendors[IFoo], [IFoo]) + self.assertEqual(alb._extendors[IBar], [IBar]) + self.assertEqual(sorted(alb._extendors[Interface]), + sorted([IFoo, IBar])) + + def test_add_extendor(self): + from zope.interface import Interface + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry() + alb = self._makeOne(registry) + alb.add_extendor(IFoo) + alb.add_extendor(IBar) + self.assertEqual(sorted(alb._extendors.keys()), + sorted([IBar, IFoo, Interface])) + self.assertEqual(alb._extendors[IFoo], [IFoo]) + self.assertEqual(alb._extendors[IBar], [IBar]) + self.assertEqual(sorted(alb._extendors[Interface]), + sorted([IFoo, IBar])) + + def test_remove_extendor(self): + from zope.interface import Interface + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + alb = self._makeOne(registry) + alb.remove_extendor(IFoo) + self.assertEqual(sorted(alb._extendors.keys()), + sorted([IFoo, IBar, Interface])) + self.assertEqual(alb._extendors[IFoo], []) + self.assertEqual(alb._extendors[IBar], [IBar]) + self.assertEqual(sorted(alb._extendors[Interface]), + sorted([IBar])) + + # test '_subscribe' via its callers, '_uncached_lookup', etc. + + def test__uncached_lookup_empty_ro(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry() + alb = self._makeOne(registry) + result = alb._uncached_lookup((IFoo,), IBar) + self.assertEqual(result, None) + self.assertEqual(len(alb._required), 1) + self.assertTrue(IFoo.weakref() in alb._required) + + def test__uncached_lookup_order_miss(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + registry.ro.append(subr) + alb = self._makeOne(registry) + result = alb._uncached_lookup((IFoo,), IBar) + self.assertEqual(result, None) + + def test__uncached_lookup_extendors_miss(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry() + subr = self._makeSubregistry() + subr._adapters = [{}, {}] #utilities, single adapters + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_lookup((IFoo,), IBar) + self.assertEqual(result, None) + + def test__uncached_lookup_components_miss_wrong_iface(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + IQux = InterfaceClass('IQux') + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + irrelevant = object() + subr._adapters = [ #utilities, single adapters + {}, + {IFoo: {IQux: {'': irrelevant}, + }}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_lookup((IFoo,), IBar) + self.assertEqual(result, None) + + def test__uncached_lookup_components_miss_wrong_name(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + irrelevant = object() + wrongname = object() + subr._adapters = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'wrongname': wrongname}, + }}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_lookup((IFoo,), IBar) + self.assertEqual(result, None) + + def test__uncached_lookup_simple_hit(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + _expected = object() + subr._adapters = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'': _expected}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_lookup((IFoo,), IBar) + self.assertTrue(result is _expected) + + def test__uncached_lookup_repeated_hit(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + _expected = object() + subr._adapters = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'': _expected}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_lookup((IFoo,), IBar) + result2 = alb._uncached_lookup((IFoo,), IBar) + self.assertTrue(result is _expected) + self.assertTrue(result2 is _expected) + + def test_queryMultiAdaptor_lookup_miss(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + registry = self._makeRegistry() + subr = self._makeSubregistry() + subr._adapters = [ #utilities, single adapters + {}, + {}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + alb.lookup = alb._uncached_lookup # provided by derived + subr._v_lookup = alb + _default = object() + result = alb.queryMultiAdapter((foo,), IBar, default=_default) + self.assertTrue(result is _default) + + def test_queryMultiAdaptor_factory_miss(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + _expected = object() + _called_with = [] + def _factory(context): + _called_with.append(context) + return None + subr._adapters = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'': _factory}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + alb.lookup = alb._uncached_lookup # provided by derived + subr._v_lookup = alb + _default = object() + result = alb.queryMultiAdapter((foo,), IBar, default=_default) + self.assertTrue(result is _default) + self.assertEqual(_called_with, [foo]) + + def test_queryMultiAdaptor_factory_hit(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + _expected = object() + _called_with = [] + def _factory(context): + _called_with.append(context) + return _expected + subr._adapters = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'': _factory}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + alb.lookup = alb._uncached_lookup # provided by derived + subr._v_lookup = alb + _default = object() + result = alb.queryMultiAdapter((foo,), IBar, default=_default) + self.assertTrue(result is _expected) + self.assertEqual(_called_with, [foo]) + + def test__uncached_lookupAll_empty_ro(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry() + alb = self._makeOne(registry) + result = alb._uncached_lookupAll((IFoo,), IBar) + self.assertEqual(result, ()) + self.assertEqual(len(alb._required), 1) + self.assertTrue(IFoo.weakref() in alb._required) + + def test__uncached_lookupAll_order_miss(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_lookupAll((IFoo,), IBar) + self.assertEqual(result, ()) + + def test__uncached_lookupAll_extendors_miss(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry() + subr = self._makeSubregistry() + subr._adapters = [{}, {}] #utilities, single adapters + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_lookupAll((IFoo,), IBar) + self.assertEqual(result, ()) + + def test__uncached_lookupAll_components_miss(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + IQux = InterfaceClass('IQux') + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + irrelevant = object() + subr._adapters = [ #utilities, single adapters + {}, + {IFoo: {IQux: {'': irrelevant}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_lookupAll((IFoo,), IBar) + self.assertEqual(result, ()) + + def test__uncached_lookupAll_simple_hit(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + _expected = object() + _named = object() + subr._adapters = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'': _expected, 'named': _named}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_lookupAll((IFoo,), IBar) + self.assertEqual(sorted(result), [('', _expected), ('named', _named)]) + + def test_names(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + _expected = object() + _named = object() + subr._adapters = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'': _expected, 'named': _named}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + alb.lookupAll = alb._uncached_lookupAll + subr._v_lookup = alb + result = alb.names((IFoo,), IBar) + self.assertEqual(sorted(result), ['', 'named']) + + def test__uncached_subscriptions_empty_ro(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry() + alb = self._makeOne(registry) + result = alb._uncached_subscriptions((IFoo,), IBar) + self.assertEqual(result, []) + self.assertEqual(len(alb._required), 1) + self.assertTrue(IFoo.weakref() in alb._required) + + def test__uncached_subscriptions_order_miss(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_subscriptions((IFoo,), IBar) + self.assertEqual(result, []) + + def test__uncached_subscriptions_extendors_miss(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry() + subr = self._makeSubregistry() + subr._subscribers = [{}, {}] #utilities, single adapters + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_subscriptions((IFoo,), IBar) + self.assertEqual(result, []) + + def test__uncached_subscriptions_components_miss_wrong_iface(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + IQux = InterfaceClass('IQux') + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + irrelevant = object() + subr._subscribers = [ #utilities, single adapters + {}, + {IFoo: {IQux: {'': irrelevant}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_subscriptions((IFoo,), IBar) + self.assertEqual(result, []) + + def test__uncached_subscriptions_components_miss_wrong_name(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + wrongname = object() + subr._subscribers = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'wrongname': wrongname}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_subscriptions((IFoo,), IBar) + self.assertEqual(result, []) + + def test__uncached_subscriptions_simple_hit(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + class Foo(object): + def __lt__(self, other): + return True + _exp1, _exp2 = Foo(), Foo() + subr._subscribers = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'': (_exp1, _exp2)}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + subr._v_lookup = alb + result = alb._uncached_subscriptions((IFoo,), IBar) + self.assertEqual(sorted(result), sorted([_exp1, _exp2])) + + def test_subscribers_wo_provided(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + registry = self._makeRegistry(IFoo, IBar) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + _called = {} + def _factory1(context): + _called.setdefault('_factory1', []).append(context) + def _factory2(context): + _called.setdefault('_factory2', []).append(context) + subr._subscribers = [ #utilities, single adapters + {}, + {IFoo: {None: {'': (_factory1, _factory2)}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + alb.subscriptions = alb._uncached_subscriptions + subr._v_lookup = alb + result = alb.subscribers((foo,), None) + self.assertEqual(result, ()) + self.assertEqual(_called, {'_factory1': [foo], '_factory2': [foo]}) + + def test_subscribers_w_provided(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', IFoo) + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + registry = self._makeRegistry(IFoo, IBar) + registry = self._makeRegistry(IFoo, IBar) + subr = self._makeSubregistry() + _called = {} + _exp1, _exp2 = object(), object() + def _factory1(context): + _called.setdefault('_factory1', []).append(context) + return _exp1 + def _factory2(context): + _called.setdefault('_factory2', []).append(context) + return _exp2 + def _side_effect_only(context): + _called.setdefault('_side_effect_only', []).append(context) + return None + subr._subscribers = [ #utilities, single adapters + {}, + {IFoo: {IBar: {'': (_factory1, _factory2, _side_effect_only)}}}, + ] + registry.ro.append(subr) + alb = self._makeOne(registry) + alb.subscriptions = alb._uncached_subscriptions + subr._v_lookup = alb + result = alb.subscribers((foo,), IBar) + self.assertEqual(result, [_exp1, _exp2]) + self.assertEqual(_called, + {'_factory1': [foo], + '_factory2': [foo], + '_side_effect_only': [foo], + }) + + +class AdapterRegistryTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.adapter import AdapterRegistry + return AdapterRegistry + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor_no_bases(self): + ar = self._makeOne() + self.assertEqual(len(ar._v_subregistries), 0) + + def test_ctor_w_bases(self): + base = self._makeOne() + sub = self._makeOne([base]) + self.assertEqual(len(sub._v_subregistries), 0) + self.assertEqual(len(base._v_subregistries), 1) + self.assertTrue(sub in base._v_subregistries) + + # test _addSubregistry / _removeSubregistry via only caller, _setBases + + def test__setBases_removing_existing_subregistry(self): + before = self._makeOne() + after = self._makeOne() + sub = self._makeOne([before]) + sub.__bases__ = [after] + self.assertEqual(len(before._v_subregistries), 0) + self.assertEqual(len(after._v_subregistries), 1) + self.assertTrue(sub in after._v_subregistries) + + def test__setBases_wo_stray_entry(self): + before = self._makeOne() + stray = self._makeOne() + after = self._makeOne() + sub = self._makeOne([before]) + sub.__dict__['__bases__'].append(stray) + sub.__bases__ = [after] + self.assertEqual(len(before._v_subregistries), 0) + self.assertEqual(len(after._v_subregistries), 1) + self.assertTrue(sub in after._v_subregistries) + + def test__setBases_w_existing_entry_continuing(self): + before = self._makeOne() + after = self._makeOne() + sub = self._makeOne([before]) + sub.__bases__ = [before, after] + self.assertEqual(len(before._v_subregistries), 1) + self.assertEqual(len(after._v_subregistries), 1) + self.assertTrue(sub in before._v_subregistries) + self.assertTrue(sub in after._v_subregistries) + + def test_changed_w_subregistries(self): + base = self._makeOne() + class Derived(object): + _changed = None + def changed(self, originally_changed): + self._changed = originally_changed + derived1, derived2 = Derived(), Derived() + base._addSubregistry(derived1) + base._addSubregistry(derived2) + orig = object() + base.changed(orig) + self.assertTrue(derived1._changed is orig) + self.assertTrue(derived2._changed is orig) + + +class Test_utils(unittest.TestCase): + + def test__convert_None_to_Interface_w_None(self): + from zope.interface.adapter import _convert_None_to_Interface + from zope.interface.interface import Interface + self.assertTrue(_convert_None_to_Interface(None) is Interface) + + def test__convert_None_to_Interface_w_other(self): + from zope.interface.adapter import _convert_None_to_Interface + other = object() + self.assertTrue(_convert_None_to_Interface(other) is other) + + def test__normalize_name_str(self): + import sys + from zope.interface.adapter import _normalize_name + STR = b'str' + if sys.version_info[0] < 3: + self.assertEqual(_normalize_name(STR), unicode(STR)) + else: + self.assertEqual(_normalize_name(STR), str(STR, 'ascii')) + + def test__normalize_name_unicode(self): + from zope.interface.adapter import _normalize_name + + USTR = u'ustr' + self.assertEqual(_normalize_name(USTR), USTR) + + def test__normalize_name_other(self): + from zope.interface.adapter import _normalize_name + for other in 1, 1.0, (), [], {}, object(): + self.assertRaises(TypeError, _normalize_name, other) + + # _lookup, _lookupAll, and _subscriptions tested via their callers + # (AdapterLookupBase.{lookup,lookupAll,subscriptions}). diff --git a/lib/zope/interface/tests/test_advice.py b/lib/zope/interface/tests/test_advice.py new file mode 100644 index 0000000..0739ac1 --- /dev/null +++ b/lib/zope/interface/tests/test_advice.py @@ -0,0 +1,355 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Tests for advice + +This module was adapted from 'protocols.tests.advice', part of the Python +Enterprise Application Kit (PEAK). Please notify the PEAK authors +(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or +Zope-specific changes are required, so that the PEAK version of this module +can be kept in sync. + +PEAK is a Python application framework that interoperates with (but does +not require) Zope 3 and Twisted. It provides tools for manipulating UML +models, object-relational persistence, aspect-oriented programming, and more. +Visit the PEAK home page at http://peak.telecommunity.com for more information. +""" + +import unittest +import sys + +from zope.interface._compat import _skip_under_py2 +from zope.interface._compat import _skip_under_py3k + + +class FrameInfoTest(unittest.TestCase): + + def test_w_module(self): + from zope.interface.tests import advisory_testing + (kind, module, + f_locals, f_globals) = advisory_testing.moduleLevelFrameInfo + self.assertEqual(kind, "module") + for d in module.__dict__, f_locals, f_globals: + self.assertTrue(d is advisory_testing.my_globals) + + @_skip_under_py3k + def test_w_ClassicClass(self): + from zope.interface.tests import advisory_testing + (kind, + module, + f_locals, + f_globals) = advisory_testing.ClassicClass.classLevelFrameInfo + self.assertEqual(kind, "class") + + self.assertTrue( + f_locals is advisory_testing.ClassicClass.__dict__) # ??? + for d in module.__dict__, f_globals: + self.assertTrue(d is advisory_testing.my_globals) + + def test_w_NewStyleClass(self): + from zope.interface.tests import advisory_testing + (kind, + module, + f_locals, + f_globals) = advisory_testing.NewStyleClass.classLevelFrameInfo + self.assertEqual(kind, "class") + + for d in module.__dict__, f_globals: + self.assertTrue(d is advisory_testing.my_globals) + + def test_inside_function_call(self): + from zope.interface.advice import getFrameInfo + kind, module, f_locals, f_globals = getFrameInfo(sys._getframe()) + self.assertEqual(kind, "function call") + self.assertTrue(f_locals is locals()) # ??? + for d in module.__dict__, f_globals: + self.assertTrue(d is globals()) + + def test_inside_exec(self): + from zope.interface.advice import getFrameInfo + _globals = {'getFrameInfo': getFrameInfo} + _locals = {} + exec(_FUNKY_EXEC, _globals, _locals) + self.assertEqual(_locals['kind'], "exec") + self.assertTrue(_locals['f_locals'] is _locals) + self.assertTrue(_locals['module'] is None) + self.assertTrue(_locals['f_globals'] is _globals) + + +_FUNKY_EXEC = """\ +import sys +kind, module, f_locals, f_globals = getFrameInfo(sys._getframe()) +""" + +class AdviceTests(unittest.TestCase): + + @_skip_under_py3k + def test_order(self): + from zope.interface.tests.advisory_testing import ping + log = [] + class Foo(object): + ping(log, 1) + ping(log, 2) + ping(log, 3) + + # Strip the list nesting + for i in 1, 2, 3: + self.assertTrue(isinstance(Foo, list)) + Foo, = Foo + + self.assertEqual(log, [(1, Foo), (2, [Foo]), (3, [[Foo]])]) + + @_skip_under_py3k + def test_single_explicit_meta(self): + from zope.interface.tests.advisory_testing import ping + + class Metaclass(type): + pass + + class Concrete(Metaclass): + __metaclass__ = Metaclass + ping([],1) + + Concrete, = Concrete + self.assertTrue(Concrete.__class__ is Metaclass) + + + @_skip_under_py3k + def test_mixed_metas(self): + from zope.interface.tests.advisory_testing import ping + + class Metaclass1(type): + pass + + class Metaclass2(type): + pass + + class Base1: + __metaclass__ = Metaclass1 + + class Base2: + __metaclass__ = Metaclass2 + + try: + class Derived(Base1, Base2): + ping([], 1) + self.fail("Should have gotten incompatibility error") + except TypeError: + pass + + class Metaclass3(Metaclass1, Metaclass2): + pass + + class Derived(Base1, Base2): + __metaclass__ = Metaclass3 + ping([], 1) + + self.assertTrue(isinstance(Derived, list)) + Derived, = Derived + self.assertTrue(isinstance(Derived, Metaclass3)) + + @_skip_under_py3k + def test_meta_no_bases(self): + from zope.interface.tests.advisory_testing import ping + from types import ClassType + class Thing: + ping([], 1) + klass, = Thing # unpack list created by pong + self.assertEqual(type(klass), ClassType) + + +class Test_isClassAdvisor(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.advice import isClassAdvisor + return isClassAdvisor(*args, **kw) + + def test_w_non_function(self): + self.assertEqual(self._callFUT(self), False) + + def test_w_normal_function(self): + def foo(): + raise NotImplementedError() + self.assertEqual(self._callFUT(foo), False) + + def test_w_advisor_function(self): + def bar(): + raise NotImplementedError() + bar.previousMetaclass = object() + self.assertEqual(self._callFUT(bar), True) + + +class Test_determineMetaclass(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.advice import determineMetaclass + return determineMetaclass(*args, **kw) + + @_skip_under_py3k + def test_empty(self): + from types import ClassType + self.assertEqual(self._callFUT(()), ClassType) + + def test_empty_w_explicit_metatype(self): + class Meta(type): + pass + self.assertEqual(self._callFUT((), Meta), Meta) + + def test_single(self): + class Meta(type): + pass + self.assertEqual(self._callFUT((Meta,)), type) + + @_skip_under_py3k + def test_meta_of_class(self): + class Metameta(type): + pass + + class Meta(type): + __metaclass__ = Metameta + + self.assertEqual(self._callFUT((Meta, type)), Metameta) + + @_skip_under_py2 + def test_meta_of_class_py3k(self): + # Work around SyntaxError under Python2. + EXEC = '\n'.join([ + 'class Metameta(type):', + ' pass', + 'class Meta(type, metaclass=Metameta):', + ' pass', + ]) + globs = {} + exec(EXEC, globs) + Meta = globs['Meta'] + Metameta = globs['Metameta'] + + self.assertEqual(self._callFUT((Meta, type)), Metameta) + + @_skip_under_py3k + def test_multiple_in_hierarchy(self): + class Meta_A(type): + pass + class Meta_B(Meta_A): + pass + class A(type): + __metaclass__ = Meta_A + class B(type): + __metaclass__ = Meta_B + self.assertEqual(self._callFUT((A, B,)), Meta_B) + + @_skip_under_py2 + def test_multiple_in_hierarchy_py3k(self): + # Work around SyntaxError under Python2. + EXEC = '\n'.join([ + 'class Meta_A(type):', + ' pass', + 'class Meta_B(Meta_A):', + ' pass', + 'class A(type, metaclass=Meta_A):', + ' pass', + 'class B(type, metaclass=Meta_B):', + ' pass', + ]) + globs = {} + exec(EXEC, globs) + Meta_A = globs['Meta_A'] + Meta_B = globs['Meta_B'] + A = globs['A'] + B = globs['B'] + self.assertEqual(self._callFUT((A, B)), Meta_B) + + @_skip_under_py3k + def test_multiple_not_in_hierarchy(self): + class Meta_A(type): + pass + class Meta_B(type): + pass + class A(type): + __metaclass__ = Meta_A + class B(type): + __metaclass__ = Meta_B + self.assertRaises(TypeError, self._callFUT, (A, B,)) + + @_skip_under_py2 + def test_multiple_not_in_hierarchy_py3k(self): + # Work around SyntaxError under Python2. + EXEC = '\n'.join([ + 'class Meta_A(type):', + ' pass', + 'class Meta_B(type):', + ' pass', + 'class A(type, metaclass=Meta_A):', + ' pass', + 'class B(type, metaclass=Meta_B):', + ' pass', + ]) + globs = {} + exec(EXEC, globs) + Meta_A = globs['Meta_A'] + Meta_B = globs['Meta_B'] + A = globs['A'] + B = globs['B'] + self.assertRaises(TypeError, self._callFUT, (A, B)) + + +class Test_minimalBases(unittest.TestCase): + + def _callFUT(self, klasses): + from zope.interface.advice import minimalBases + return minimalBases(klasses) + + def test_empty(self): + self.assertEqual(self._callFUT([]), []) + + @_skip_under_py3k + def test_w_oldstyle_meta(self): + class C: + pass + self.assertEqual(self._callFUT([type(C)]), []) + + @_skip_under_py3k + def test_w_oldstyle_class(self): + class C: + pass + self.assertEqual(self._callFUT([C]), [C]) + + def test_w_newstyle_meta(self): + self.assertEqual(self._callFUT([type]), [type]) + + def test_w_newstyle_class(self): + class C(object): + pass + self.assertEqual(self._callFUT([C]), [C]) + + def test_simple_hierarchy_skips_implied(self): + class A(object): + pass + class B(A): + pass + class C(B): + pass + class D(object): + pass + self.assertEqual(self._callFUT([A, B, C]), [C]) + self.assertEqual(self._callFUT([A, C]), [C]) + self.assertEqual(self._callFUT([B, C]), [C]) + self.assertEqual(self._callFUT([A, B]), [B]) + self.assertEqual(self._callFUT([D, B, D]), [B, D]) + + def test_repeats_kicked_to_end_of_queue(self): + class A(object): + pass + class B(object): + pass + self.assertEqual(self._callFUT([A, B, A]), [B, A]) diff --git a/lib/zope/interface/tests/test_declarations.py b/lib/zope/interface/tests/test_declarations.py new file mode 100644 index 0000000..43f95c8 --- /dev/null +++ b/lib/zope/interface/tests/test_declarations.py @@ -0,0 +1,1658 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Test the new API for making and checking interface declarations +""" +import unittest + +from zope.interface._compat import _skip_under_py3k + + +class _Py3ClassAdvice(object): + + def _run_generated_code(self, code, globs, locs, + fails_under_py3k=True, + ): + import warnings + from zope.interface._compat import PYTHON3 + with warnings.catch_warnings(record=True) as log: + warnings.resetwarnings() + if not PYTHON3: + exec(code, globs, locs) + self.assertEqual(len(log), 0) # no longer warn + return True + else: + try: + exec(code, globs, locs) + except TypeError: + return False + else: + if fails_under_py3k: + self.fail("Didn't raise TypeError") + + +class NamedTests(unittest.TestCase): + + def test_class(self): + from zope.interface.declarations import named + + @named(u'foo') + class Foo(object): + pass + + self.assertEqual(Foo.__component_name__, u'foo') + + def test_function(self): + from zope.interface.declarations import named + + @named(u'foo') + def doFoo(o): + raise NotImplementedError() + + self.assertEqual(doFoo.__component_name__, u'foo') + + def test_instance(self): + from zope.interface.declarations import named + + class Foo(object): + pass + foo = Foo() + named(u'foo')(foo) + + self.assertEqual(foo.__component_name__, u'foo') + + +class DeclarationTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import Declaration + return Declaration + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor_no_bases(self): + decl = self._makeOne() + self.assertEqual(list(decl.__bases__), []) + + def test_ctor_w_interface_in_bases(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + decl = self._makeOne(IFoo) + self.assertEqual(list(decl.__bases__), [IFoo]) + + def test_ctor_w_implements_in_bases(self): + from zope.interface.declarations import Implements + impl = Implements() + decl = self._makeOne(impl) + self.assertEqual(list(decl.__bases__), [impl]) + + def test_changed_wo_existing__v_attrs(self): + decl = self._makeOne() + decl.changed(decl) # doesn't raise + self.assertFalse('_v_attrs' in decl.__dict__) + + def test_changed_w_existing__v_attrs(self): + decl = self._makeOne() + decl._v_attrs = object() + decl.changed(decl) + self.assertFalse('_v_attrs' in decl.__dict__) + + def test___contains__w_self(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + decl = self._makeOne() + self.assertFalse(decl in decl) + + def test___contains__w_unrelated_iface(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + decl = self._makeOne() + self.assertFalse(IFoo in decl) + + def test___contains__w_base_interface(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + decl = self._makeOne(IFoo) + self.assertTrue(IFoo in decl) + + def test___iter___empty(self): + decl = self._makeOne() + self.assertEqual(list(decl), []) + + def test___iter___single_base(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + decl = self._makeOne(IFoo) + self.assertEqual(list(decl), [IFoo]) + + def test___iter___multiple_bases(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + decl = self._makeOne(IFoo, IBar) + self.assertEqual(list(decl), [IFoo, IBar]) + + def test___iter___inheritance(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', (IFoo,)) + decl = self._makeOne(IBar) + self.assertEqual(list(decl), [IBar]) #IBar.interfaces() omits bases + + def test___iter___w_nested_sequence_overlap(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + decl = self._makeOne(IBar, (IFoo, IBar)) + self.assertEqual(list(decl), [IBar, IFoo]) + + def test_flattened_empty(self): + from zope.interface.interface import Interface + decl = self._makeOne() + self.assertEqual(list(decl.flattened()), [Interface]) + + def test_flattened_single_base(self): + from zope.interface.interface import Interface + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + decl = self._makeOne(IFoo) + self.assertEqual(list(decl.flattened()), [IFoo, Interface]) + + def test_flattened_multiple_bases(self): + from zope.interface.interface import Interface + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + decl = self._makeOne(IFoo, IBar) + self.assertEqual(list(decl.flattened()), [IFoo, IBar, Interface]) + + def test_flattened_inheritance(self): + from zope.interface.interface import Interface + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', (IFoo,)) + decl = self._makeOne(IBar) + self.assertEqual(list(decl.flattened()), [IBar, IFoo, Interface]) + + def test_flattened_w_nested_sequence_overlap(self): + from zope.interface.interface import Interface + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + decl = self._makeOne(IBar, (IFoo, IBar)) + # Note that decl.__iro__ has IFoo first. + self.assertEqual(list(decl.flattened()), [IFoo, IBar, Interface]) + + def test___sub___unrelated_interface(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + before = self._makeOne(IFoo) + after = before - IBar + self.assertTrue(isinstance(after, self._getTargetClass())) + self.assertEqual(list(after), [IFoo]) + + def test___sub___related_interface(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + before = self._makeOne(IFoo) + after = before - IFoo + self.assertEqual(list(after), []) + + def test___sub___related_interface_by_inheritance(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar', (IFoo,)) + before = self._makeOne(IBar) + after = before - IBar + self.assertEqual(list(after), []) + + def test___add___unrelated_interface(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + before = self._makeOne(IFoo) + after = before + IBar + self.assertTrue(isinstance(after, self._getTargetClass())) + self.assertEqual(list(after), [IFoo, IBar]) + + def test___add___related_interface(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + IBaz = InterfaceClass('IBaz') + before = self._makeOne(IFoo, IBar) + other = self._makeOne(IBar, IBaz) + after = before + other + self.assertEqual(list(after), [IFoo, IBar, IBaz]) + + +class TestImplements(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import Implements + return Implements + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor_no_bases(self): + impl = self._makeOne() + self.assertEqual(impl.inherit, None) + self.assertEqual(impl.declared, ()) + self.assertEqual(impl.__name__, '?') + self.assertEqual(list(impl.__bases__), []) + + def test___repr__(self): + impl = self._makeOne() + impl.__name__ = 'Testing' + self.assertEqual(repr(impl), '<implementedBy Testing>') + + def test___reduce__(self): + from zope.interface.declarations import implementedBy + impl = self._makeOne() + self.assertEqual(impl.__reduce__(), (implementedBy, (None,))) + + def test_sort(self): + from zope.interface.declarations import implementedBy + class A(object): + pass + class B(object): + pass + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + + self.assertEqual(implementedBy(A), implementedBy(A)) + self.assertEqual(hash(implementedBy(A)), hash(implementedBy(A))) + self.assertTrue(implementedBy(A) < None) + self.assertTrue(None > implementedBy(A)) + self.assertTrue(implementedBy(A) < implementedBy(B)) + self.assertTrue(implementedBy(A) > IFoo) + self.assertTrue(implementedBy(A) <= implementedBy(B)) + self.assertTrue(implementedBy(A) >= IFoo) + self.assertTrue(implementedBy(A) != IFoo) + + def test_proxy_equality(self): + # https://github.com/zopefoundation/zope.interface/issues/55 + class Proxy(object): + def __init__(self, wrapped): + self._wrapped = wrapped + + def __getattr__(self, name): + raise NotImplementedError() + + def __eq__(self, other): + return self._wrapped == other + + def __ne__(self, other): + return self._wrapped != other + + from zope.interface.declarations import implementedBy + class A(object): + pass + + class B(object): + pass + + implementedByA = implementedBy(A) + implementedByB = implementedBy(B) + proxy = Proxy(implementedByA) + + # The order of arguments to the operators matters, + # test both + self.assertTrue(implementedByA == implementedByA) + self.assertTrue(implementedByA != implementedByB) + self.assertTrue(implementedByB != implementedByA) + + self.assertTrue(proxy == implementedByA) + self.assertTrue(implementedByA == proxy) + self.assertFalse(proxy != implementedByA) + self.assertFalse(implementedByA != proxy) + + self.assertTrue(proxy != implementedByB) + self.assertTrue(implementedByB != proxy) + + +class Test_implementedByFallback(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import implementedByFallback + return implementedByFallback(*args, **kw) + + def test_dictless_wo_existing_Implements_wo_registrations(self): + class Foo(object): + __slots__ = ('__implemented__',) + foo = Foo() + foo.__implemented__ = None + self.assertEqual(list(self._callFUT(foo)), []) + + def test_dictless_wo_existing_Implements_cant_assign___implemented__(self): + class Foo(object): + def _get_impl(self): + raise NotImplementedError() + def _set_impl(self, val): + raise TypeError + __implemented__ = property(_get_impl, _set_impl) + def __call__(self): + # act like a factory + raise NotImplementedError() + foo = Foo() + self.assertRaises(TypeError, self._callFUT, foo) + + def test_dictless_wo_existing_Implements_w_registrations(self): + from zope.interface import declarations + class Foo(object): + __slots__ = ('__implemented__',) + foo = Foo() + foo.__implemented__ = None + reg = object() + with _MonkeyDict(declarations, + 'BuiltinImplementationSpecifications') as specs: + specs[foo] = reg + self.assertTrue(self._callFUT(foo) is reg) + + def test_dictless_w_existing_Implements(self): + from zope.interface.declarations import Implements + impl = Implements() + class Foo(object): + __slots__ = ('__implemented__',) + foo = Foo() + foo.__implemented__ = impl + self.assertTrue(self._callFUT(foo) is impl) + + def test_dictless_w_existing_not_Implements(self): + from zope.interface.interface import InterfaceClass + class Foo(object): + __slots__ = ('__implemented__',) + foo = Foo() + IFoo = InterfaceClass('IFoo') + foo.__implemented__ = (IFoo,) + self.assertEqual(list(self._callFUT(foo)), [IFoo]) + + def test_w_existing_attr_as_Implements(self): + from zope.interface.declarations import Implements + impl = Implements() + class Foo(object): + __implemented__ = impl + self.assertTrue(self._callFUT(Foo) is impl) + + def test_builtins_added_to_cache(self): + from zope.interface import declarations + from zope.interface.declarations import Implements + from zope.interface._compat import _BUILTINS + with _MonkeyDict(declarations, + 'BuiltinImplementationSpecifications') as specs: + self.assertEqual(list(self._callFUT(tuple)), []) + self.assertEqual(list(self._callFUT(list)), []) + self.assertEqual(list(self._callFUT(dict)), []) + for typ in (tuple, list, dict): + spec = specs[typ] + self.assertTrue(isinstance(spec, Implements)) + self.assertEqual(repr(spec), + '<implementedBy %s.%s>' + % (_BUILTINS, typ.__name__)) + + def test_builtins_w_existing_cache(self): + from zope.interface import declarations + t_spec, l_spec, d_spec = object(), object(), object() + with _MonkeyDict(declarations, + 'BuiltinImplementationSpecifications') as specs: + specs[tuple] = t_spec + specs[list] = l_spec + specs[dict] = d_spec + self.assertTrue(self._callFUT(tuple) is t_spec) + self.assertTrue(self._callFUT(list) is l_spec) + self.assertTrue(self._callFUT(dict) is d_spec) + + def test_oldstyle_class_no_assertions(self): + # TODO: Figure out P3 story + class Foo: + pass + self.assertEqual(list(self._callFUT(Foo)), []) + + def test_no_assertions(self): + # TODO: Figure out P3 story + class Foo(object): + pass + self.assertEqual(list(self._callFUT(Foo)), []) + + def test_w_None_no_bases_not_factory(self): + class Foo(object): + __implemented__ = None + foo = Foo() + self.assertRaises(TypeError, self._callFUT, foo) + + def test_w_None_no_bases_w_factory(self): + from zope.interface.declarations import objectSpecificationDescriptor + class Foo(object): + __implemented__ = None + def __call__(self): + raise NotImplementedError() + + foo = Foo() + foo.__name__ = 'foo' + spec = self._callFUT(foo) + self.assertEqual(spec.__name__, + 'zope.interface.tests.test_declarations.foo') + self.assertTrue(spec.inherit is foo) + self.assertTrue(foo.__implemented__ is spec) + self.assertTrue(foo.__providedBy__ is objectSpecificationDescriptor) + self.assertFalse('__provides__' in foo.__dict__) + + def test_w_None_no_bases_w_class(self): + from zope.interface.declarations import ClassProvides + class Foo(object): + __implemented__ = None + spec = self._callFUT(Foo) + self.assertEqual(spec.__name__, + 'zope.interface.tests.test_declarations.Foo') + self.assertTrue(spec.inherit is Foo) + self.assertTrue(Foo.__implemented__ is spec) + self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) + self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) + self.assertEqual(Foo.__provides__, Foo.__providedBy__) + + def test_w_existing_Implements(self): + from zope.interface.declarations import Implements + impl = Implements() + class Foo(object): + __implemented__ = impl + self.assertTrue(self._callFUT(Foo) is impl) + + +class Test_implementedBy(Test_implementedByFallback): + # Repeat tests for C optimizations + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import implementedBy + return implementedBy(*args, **kw) + + def test_optimizations(self): + from zope.interface.declarations import implementedByFallback + from zope.interface.declarations import implementedBy + try: + import zope.interface._zope_interface_coptimizations + except ImportError: + self.assertIs(implementedBy, implementedByFallback) + else: + self.assertIsNot(implementedBy, implementedByFallback) + + +class Test_classImplementsOnly(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import classImplementsOnly + return classImplementsOnly(*args, **kw) + + def test_no_existing(self): + from zope.interface.declarations import ClassProvides + from zope.interface.interface import InterfaceClass + class Foo(object): + pass + ifoo = InterfaceClass('IFoo') + self._callFUT(Foo, ifoo) + spec = Foo.__implemented__ + self.assertEqual(spec.__name__, + 'zope.interface.tests.test_declarations.Foo') + self.assertTrue(spec.inherit is None) + self.assertTrue(Foo.__implemented__ is spec) + self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) + self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) + self.assertEqual(Foo.__provides__, Foo.__providedBy__) + + def test_w_existing_Implements(self): + from zope.interface.declarations import Implements + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + impl = Implements(IFoo) + impl.declared = (IFoo,) + class Foo(object): + __implemented__ = impl + impl.inherit = Foo + self._callFUT(Foo, IBar) + # Same spec, now different values + self.assertTrue(Foo.__implemented__ is impl) + self.assertEqual(impl.inherit, None) + self.assertEqual(impl.declared, (IBar,)) + + +class Test_classImplements(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import classImplements + return classImplements(*args, **kw) + + def test_no_existing(self): + from zope.interface.declarations import ClassProvides + from zope.interface.interface import InterfaceClass + class Foo(object): + pass + IFoo = InterfaceClass('IFoo') + self._callFUT(Foo, IFoo) + spec = Foo.__implemented__ + self.assertEqual(spec.__name__, + 'zope.interface.tests.test_declarations.Foo') + self.assertTrue(spec.inherit is Foo) + self.assertTrue(Foo.__implemented__ is spec) + self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) + self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) + self.assertEqual(Foo.__provides__, Foo.__providedBy__) + + def test_w_existing_Implements(self): + from zope.interface.declarations import Implements + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + impl = Implements(IFoo) + impl.declared = (IFoo,) + class Foo(object): + __implemented__ = impl + impl.inherit = Foo + self._callFUT(Foo, IBar) + # Same spec, now different values + self.assertTrue(Foo.__implemented__ is impl) + self.assertEqual(impl.inherit, Foo) + self.assertEqual(impl.declared, (IFoo, IBar,)) + + def test_w_existing_Implements_w_bases(self): + from zope.interface.declarations import Implements + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + IBaz = InterfaceClass('IBaz', IFoo) + b_impl = Implements(IBaz) + impl = Implements(IFoo) + impl.declared = (IFoo,) + class Base1(object): + __implemented__ = b_impl + class Base2(object): + __implemented__ = b_impl + class Foo(Base1, Base2): + __implemented__ = impl + impl.inherit = Foo + self._callFUT(Foo, IBar) + # Same spec, now different values + self.assertTrue(Foo.__implemented__ is impl) + self.assertEqual(impl.inherit, Foo) + self.assertEqual(impl.declared, (IFoo, IBar,)) + self.assertEqual(impl.__bases__, (IFoo, IBar, b_impl)) + + +class Test__implements_advice(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import _implements_advice + return _implements_advice(*args, **kw) + + def test_no_existing_implements(self): + from zope.interface.declarations import classImplements + from zope.interface.declarations import Implements + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + class Foo(object): + __implements_advice_data__ = ((IFoo,), classImplements) + self._callFUT(Foo) + self.assertFalse('__implements_advice_data__' in Foo.__dict__) + self.assertTrue(isinstance(Foo.__implemented__, Implements)) + self.assertEqual(list(Foo.__implemented__), [IFoo]) + + +class Test_implementer(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import implementer + return implementer + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_oldstyle_class(self): + # TODO Py3 story + from zope.interface.declarations import ClassProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + class Foo: + pass + decorator = self._makeOne(IFoo) + returned = decorator(Foo) + self.assertTrue(returned is Foo) + spec = Foo.__implemented__ + self.assertEqual(spec.__name__, + 'zope.interface.tests.test_declarations.Foo') + self.assertTrue(spec.inherit is Foo) + self.assertTrue(Foo.__implemented__ is spec) + self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) + self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) + self.assertEqual(Foo.__provides__, Foo.__providedBy__) + + def test_newstyle_class(self): + from zope.interface.declarations import ClassProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + class Foo(object): + pass + decorator = self._makeOne(IFoo) + returned = decorator(Foo) + self.assertTrue(returned is Foo) + spec = Foo.__implemented__ + self.assertEqual(spec.__name__, + 'zope.interface.tests.test_declarations.Foo') + self.assertTrue(spec.inherit is Foo) + self.assertTrue(Foo.__implemented__ is spec) + self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) + self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) + self.assertEqual(Foo.__provides__, Foo.__providedBy__) + + def test_nonclass_cannot_assign_attr(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + decorator = self._makeOne(IFoo) + self.assertRaises(TypeError, decorator, object()) + + def test_nonclass_can_assign_attr(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + class Foo(object): + pass + foo = Foo() + decorator = self._makeOne(IFoo) + returned = decorator(foo) + self.assertTrue(returned is foo) + spec = foo.__implemented__ + self.assertEqual(spec.__name__, 'zope.interface.tests.test_declarations.?') + self.assertTrue(spec.inherit is None) + self.assertTrue(foo.__implemented__ is spec) + + +class Test_implementer_only(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import implementer_only + return implementer_only + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_function(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + decorator = self._makeOne(IFoo) + def _function(): + raise NotImplementedError() + self.assertRaises(ValueError, decorator, _function) + + def test_method(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + decorator = self._makeOne(IFoo) + class Bar: + def _method(): + raise NotImplementedError() + self.assertRaises(ValueError, decorator, Bar._method) + + def test_oldstyle_class(self): + # TODO Py3 story + from zope.interface.declarations import Implements + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + old_spec = Implements(IBar) + class Foo: + __implemented__ = old_spec + decorator = self._makeOne(IFoo) + returned = decorator(Foo) + self.assertTrue(returned is Foo) + spec = Foo.__implemented__ + self.assertEqual(spec.__name__, '?') + self.assertTrue(spec.inherit is None) + self.assertTrue(Foo.__implemented__ is spec) + + def test_newstyle_class(self): + from zope.interface.declarations import Implements + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass('IFoo') + IBar = InterfaceClass('IBar') + old_spec = Implements(IBar) + class Foo(object): + __implemented__ = old_spec + decorator = self._makeOne(IFoo) + returned = decorator(Foo) + self.assertTrue(returned is Foo) + spec = Foo.__implemented__ + self.assertEqual(spec.__name__, '?') + self.assertTrue(spec.inherit is None) + self.assertTrue(Foo.__implemented__ is spec) + + +# Test '_implements' by way of 'implements{,Only}', its only callers. + +class Test_implementsOnly(unittest.TestCase, _Py3ClassAdvice): + + def test_simple(self): + import warnings + from zope.interface.declarations import implementsOnly + from zope.interface._compat import PYTHON3 + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + globs = {'implementsOnly': implementsOnly, + 'IFoo': IFoo, + } + locs = {} + CODE = "\n".join([ + 'class Foo(object):' + ' implementsOnly(IFoo)', + ]) + with warnings.catch_warnings(record=True) as log: + warnings.resetwarnings() + try: + exec(CODE, globs, locs) + except TypeError: + self.assertTrue(PYTHON3, "Must be Python 3") + else: + if PYTHON3: + self.fail("Didn't raise TypeError") + Foo = locs['Foo'] + spec = Foo.__implemented__ + self.assertEqual(list(spec), [IFoo]) + self.assertEqual(len(log), 0) # no longer warn + + def test_called_once_from_class_w_bases(self): + from zope.interface.declarations import implements + from zope.interface.declarations import implementsOnly + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + globs = {'implements': implements, + 'implementsOnly': implementsOnly, + 'IFoo': IFoo, + 'IBar': IBar, + } + locs = {} + CODE = "\n".join([ + 'class Foo(object):', + ' implements(IFoo)', + 'class Bar(Foo):' + ' implementsOnly(IBar)', + ]) + if self._run_generated_code(CODE, globs, locs): + Bar = locs['Bar'] + spec = Bar.__implemented__ + self.assertEqual(list(spec), [IBar]) + + +class Test_implements(unittest.TestCase, _Py3ClassAdvice): + + def test_called_from_function(self): + import warnings + from zope.interface.declarations import implements + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + globs = {'implements': implements, 'IFoo': IFoo} + locs = {} + CODE = "\n".join([ + 'def foo():', + ' implements(IFoo)' + ]) + if self._run_generated_code(CODE, globs, locs, False): + foo = locs['foo'] + with warnings.catch_warnings(record=True) as log: + warnings.resetwarnings() + self.assertRaises(TypeError, foo) + self.assertEqual(len(log), 0) # no longer warn + + def test_called_twice_from_class(self): + import warnings + from zope.interface.declarations import implements + from zope.interface.interface import InterfaceClass + from zope.interface._compat import PYTHON3 + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + globs = {'implements': implements, 'IFoo': IFoo, 'IBar': IBar} + locs = {} + CODE = "\n".join([ + 'class Foo(object):', + ' implements(IFoo)', + ' implements(IBar)', + ]) + with warnings.catch_warnings(record=True) as log: + warnings.resetwarnings() + try: + exec(CODE, globs, locs) + except TypeError: + if not PYTHON3: + self.assertEqual(len(log), 0) # no longer warn + else: + self.fail("Didn't raise TypeError") + + def test_called_once_from_class(self): + from zope.interface.declarations import implements + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + globs = {'implements': implements, 'IFoo': IFoo} + locs = {} + CODE = "\n".join([ + 'class Foo(object):', + ' implements(IFoo)', + ]) + if self._run_generated_code(CODE, globs, locs): + Foo = locs['Foo'] + spec = Foo.__implemented__ + self.assertEqual(list(spec), [IFoo]) + + +class ProvidesClassTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import ProvidesClass + return ProvidesClass + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_simple_class_one_interface(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + spec = self._makeOne(Foo, IFoo) + self.assertEqual(list(spec), [IFoo]) + + def test___reduce__(self): + from zope.interface.declarations import Provides # the function + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + spec = self._makeOne(Foo, IFoo) + klass, args = spec.__reduce__() + self.assertTrue(klass is Provides) + self.assertEqual(args, (Foo, IFoo)) + + def test___get___class(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + spec = self._makeOne(Foo, IFoo) + Foo.__provides__ = spec + self.assertTrue(Foo.__provides__ is spec) + + def test___get___instance(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + spec = self._makeOne(Foo, IFoo) + Foo.__provides__ = spec + def _test(): + foo = Foo() + return foo.__provides__ + self.assertRaises(AttributeError, _test) + + +class Test_Provides(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import Provides + return Provides(*args, **kw) + + def test_no_cached_spec(self): + from zope.interface import declarations + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + cache = {} + class Foo(object): + pass + with _Monkey(declarations, InstanceDeclarations=cache): + spec = self._callFUT(Foo, IFoo) + self.assertEqual(list(spec), [IFoo]) + self.assertTrue(cache[(Foo, IFoo)] is spec) + + def test_w_cached_spec(self): + from zope.interface import declarations + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + prior = object() + class Foo(object): + pass + cache = {(Foo, IFoo): prior} + with _Monkey(declarations, InstanceDeclarations=cache): + spec = self._callFUT(Foo, IFoo) + self.assertTrue(spec is prior) + + +class Test_directlyProvides(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import directlyProvides + return directlyProvides(*args, **kw) + + def test_w_normal_object(self): + from zope.interface.declarations import ProvidesClass + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + obj = Foo() + self._callFUT(obj, IFoo) + self.assertTrue(isinstance(obj.__provides__, ProvidesClass)) + self.assertEqual(list(obj.__provides__), [IFoo]) + + def test_w_class(self): + from zope.interface.declarations import ClassProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + self._callFUT(Foo, IFoo) + self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) + self.assertEqual(list(Foo.__provides__), [IFoo]) + + @_skip_under_py3k + def test_w_non_descriptor_aware_metaclass(self): + # There are no non-descriptor-aware types in Py3k + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class MetaClass(type): + def __getattribute__(cls, name): + # Emulate metaclass whose base is not the type object. + if name == '__class__': + return cls + # Under certain circumstances, the implementedByFallback + # can get here for __dict__ + return type.__getattribute__(cls, name) # pragma: no cover + + class Foo(object): + __metaclass__ = MetaClass + obj = Foo() + self.assertRaises(TypeError, self._callFUT, obj, IFoo) + + def test_w_classless_object(self): + from zope.interface.declarations import ProvidesClass + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + the_dict = {} + class Foo(object): + def __getattribute__(self, name): + # Emulate object w/o any class + if name == '__class__': + return None + raise NotImplementedError(name) + def __setattr__(self, name, value): + the_dict[name] = value + obj = Foo() + self._callFUT(obj, IFoo) + self.assertTrue(isinstance(the_dict['__provides__'], ProvidesClass)) + self.assertEqual(list(the_dict['__provides__']), [IFoo]) + + +class Test_alsoProvides(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import alsoProvides + return alsoProvides(*args, **kw) + + def test_wo_existing_provides(self): + from zope.interface.declarations import ProvidesClass + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + obj = Foo() + self._callFUT(obj, IFoo) + self.assertTrue(isinstance(obj.__provides__, ProvidesClass)) + self.assertEqual(list(obj.__provides__), [IFoo]) + + def test_w_existing_provides(self): + from zope.interface.declarations import directlyProvides + from zope.interface.declarations import ProvidesClass + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + class Foo(object): + pass + obj = Foo() + directlyProvides(obj, IFoo) + self._callFUT(obj, IBar) + self.assertTrue(isinstance(obj.__provides__, ProvidesClass)) + self.assertEqual(list(obj.__provides__), [IFoo, IBar]) + + +class Test_noLongerProvides(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import noLongerProvides + return noLongerProvides(*args, **kw) + + def test_wo_existing_provides(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + obj = Foo() + self._callFUT(obj, IFoo) + self.assertEqual(list(obj.__provides__), []) + + def test_w_existing_provides_hit(self): + from zope.interface.declarations import directlyProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + obj = Foo() + directlyProvides(obj, IFoo) + self._callFUT(obj, IFoo) + self.assertEqual(list(obj.__provides__), []) + + def test_w_existing_provides_miss(self): + from zope.interface.declarations import directlyProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + class Foo(object): + pass + obj = Foo() + directlyProvides(obj, IFoo) + self._callFUT(obj, IBar) + self.assertEqual(list(obj.__provides__), [IFoo]) + + def test_w_iface_implemented_by_class(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + @implementer(IFoo) + class Foo(object): + pass + obj = Foo() + self.assertRaises(ValueError, self._callFUT, obj, IFoo) + + +class ClassProvidesBaseFallbackTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import ClassProvidesBaseFallback + return ClassProvidesBaseFallback + + def _makeOne(self, klass, implements): + # Don't instantiate directly: the C version can't have attributes + # assigned. + class Derived(self._getTargetClass()): + def __init__(self, k, i): + self._cls = k + self._implements = i + return Derived(klass, implements) + + def test_w_same_class_via_class(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + cpbp = Foo.__provides__ = self._makeOne(Foo, IFoo) + self.assertTrue(Foo.__provides__ is cpbp) + + def test_w_same_class_via_instance(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + foo = Foo() + cpbp = Foo.__provides__ = self._makeOne(Foo, IFoo) + self.assertTrue(foo.__provides__ is IFoo) + + def test_w_different_class(self): + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + class Bar(Foo): + pass + bar = Bar() + cpbp = Foo.__provides__ = self._makeOne(Foo, IFoo) + self.assertRaises(AttributeError, getattr, Bar, '__provides__') + self.assertRaises(AttributeError, getattr, bar, '__provides__') + + +class ClassProvidesBaseTests(ClassProvidesBaseFallbackTests): + # Repeat tests for C optimizations + + def _getTargetClass(self): + from zope.interface.declarations import ClassProvidesBase + return ClassProvidesBase + + def test_optimizations(self): + from zope.interface.declarations import ClassProvidesBaseFallback + try: + import zope.interface._zope_interface_coptimizations + except ImportError: + self.assertIs(self._getTargetClass(), ClassProvidesBaseFallback) + else: + self.assertIsNot(self._getTargetClass(), ClassProvidesBaseFallback) + + +class ClassProvidesTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import ClassProvides + return ClassProvides + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_w_simple_metaclass(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + @implementer(IFoo) + class Foo(object): + pass + cp = Foo.__provides__ = self._makeOne(Foo, type(Foo), IBar) + self.assertTrue(Foo.__provides__ is cp) + self.assertEqual(list(Foo().__provides__), [IFoo]) + + def test___reduce__(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + @implementer(IFoo) + class Foo(object): + pass + cp = Foo.__provides__ = self._makeOne(Foo, type(Foo), IBar) + self.assertEqual(cp.__reduce__(), + (self._getTargetClass(), (Foo, type(Foo), IBar))) + + +class Test_directlyProvidedBy(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import directlyProvidedBy + return directlyProvidedBy(*args, **kw) + + def test_wo_declarations_in_class_or_instance(self): + class Foo(object): + pass + foo = Foo() + self.assertEqual(list(self._callFUT(foo)), []) + + def test_w_declarations_in_class_but_not_instance(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + self.assertEqual(list(self._callFUT(foo)), []) + + def test_w_declarations_in_instance_but_not_class(self): + from zope.interface.declarations import directlyProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + foo = Foo() + directlyProvides(foo, IFoo) + self.assertEqual(list(self._callFUT(foo)), [IFoo]) + + def test_w_declarations_in_instance_and_class(self): + from zope.interface.declarations import directlyProvides + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + directlyProvides(foo, IBar) + self.assertEqual(list(self._callFUT(foo)), [IBar]) + + +class Test_classProvides(unittest.TestCase, _Py3ClassAdvice): + + def test_called_from_function(self): + import warnings + from zope.interface.declarations import classProvides + from zope.interface.interface import InterfaceClass + from zope.interface._compat import PYTHON3 + IFoo = InterfaceClass("IFoo") + globs = {'classProvides': classProvides, 'IFoo': IFoo} + locs = {} + CODE = "\n".join([ + 'def foo():', + ' classProvides(IFoo)' + ]) + exec(CODE, globs, locs) + foo = locs['foo'] + with warnings.catch_warnings(record=True) as log: + warnings.resetwarnings() + self.assertRaises(TypeError, foo) + if not PYTHON3: + self.assertEqual(len(log), 0) # no longer warn + + def test_called_twice_from_class(self): + import warnings + from zope.interface.declarations import classProvides + from zope.interface.interface import InterfaceClass + from zope.interface._compat import PYTHON3 + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + globs = {'classProvides': classProvides, 'IFoo': IFoo, 'IBar': IBar} + locs = {} + CODE = "\n".join([ + 'class Foo(object):', + ' classProvides(IFoo)', + ' classProvides(IBar)', + ]) + with warnings.catch_warnings(record=True) as log: + warnings.resetwarnings() + try: + exec(CODE, globs, locs) + except TypeError: + if not PYTHON3: + self.assertEqual(len(log), 0) # no longer warn + else: + self.fail("Didn't raise TypeError") + + def test_called_once_from_class(self): + from zope.interface.declarations import classProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + globs = {'classProvides': classProvides, 'IFoo': IFoo} + locs = {} + CODE = "\n".join([ + 'class Foo(object):', + ' classProvides(IFoo)', + ]) + if self._run_generated_code(CODE, globs, locs): + Foo = locs['Foo'] + spec = Foo.__providedBy__ + self.assertEqual(list(spec), [IFoo]) + +# Test _classProvides_advice through classProvides, its only caller. + + +class Test_provider(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations import provider + return provider + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_w_class(self): + from zope.interface.declarations import ClassProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + @self._makeOne(IFoo) + class Foo(object): + pass + self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) + self.assertEqual(list(Foo.__provides__), [IFoo]) + + +class Test_moduleProvides(unittest.TestCase): + + def test_called_from_function(self): + from zope.interface.declarations import moduleProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + globs = {'__name__': 'zope.interface.tests.foo', + 'moduleProvides': moduleProvides, 'IFoo': IFoo} + locs = {} + CODE = "\n".join([ + 'def foo():', + ' moduleProvides(IFoo)' + ]) + exec(CODE, globs, locs) + foo = locs['foo'] + self.assertRaises(TypeError, foo) + + def test_called_from_class(self): + from zope.interface.declarations import moduleProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + globs = {'__name__': 'zope.interface.tests.foo', + 'moduleProvides': moduleProvides, 'IFoo': IFoo} + locs = {} + CODE = "\n".join([ + 'class Foo(object):', + ' moduleProvides(IFoo)', + ]) + with self.assertRaises(TypeError): + exec(CODE, globs, locs) + + def test_called_once_from_module_scope(self): + from zope.interface.declarations import moduleProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + globs = {'__name__': 'zope.interface.tests.foo', + 'moduleProvides': moduleProvides, 'IFoo': IFoo} + CODE = "\n".join([ + 'moduleProvides(IFoo)', + ]) + exec(CODE, globs) + spec = globs['__provides__'] + self.assertEqual(list(spec), [IFoo]) + + def test_called_twice_from_module_scope(self): + from zope.interface.declarations import moduleProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + globs = {'__name__': 'zope.interface.tests.foo', + 'moduleProvides': moduleProvides, 'IFoo': IFoo} + locs = {} + CODE = "\n".join([ + 'moduleProvides(IFoo)', + 'moduleProvides(IFoo)', + ]) + with self.assertRaises(TypeError): + exec(CODE, globs) + + +class Test_getObjectSpecificationFallback(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import getObjectSpecificationFallback + return getObjectSpecificationFallback(*args, **kw) + + def test_wo_existing_provides_classless(self): + the_dict = {} + class Foo(object): + def __getattribute__(self, name): + # Emulate object w/o any class + if name == '__class__': + raise AttributeError(name) + try: + return the_dict[name] + except KeyError: + raise AttributeError(name) + def __setattr__(self, name, value): + raise NotImplementedError() + foo = Foo() + spec = self._callFUT(foo) + self.assertEqual(list(spec), []) + + def test_existing_provides_is_spec(self): + from zope.interface.declarations import directlyProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + def foo(): + raise NotImplementedError() + directlyProvides(foo, IFoo) + spec = self._callFUT(foo) + self.assertTrue(spec is foo.__provides__) + + def test_existing_provides_is_not_spec(self): + def foo(): + raise NotImplementedError() + foo.__provides__ = object() # not a valid spec + spec = self._callFUT(foo) + self.assertEqual(list(spec), []) + + def test_existing_provides(self): + from zope.interface.declarations import directlyProvides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + foo = Foo() + directlyProvides(foo, IFoo) + spec = self._callFUT(foo) + self.assertEqual(list(spec), [IFoo]) + + def test_wo_provides_on_class_w_implements(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + spec = self._callFUT(foo) + self.assertEqual(list(spec), [IFoo]) + + def test_wo_provides_on_class_wo_implements(self): + class Foo(object): + pass + foo = Foo() + spec = self._callFUT(foo) + self.assertEqual(list(spec), []) + + +class Test_getObjectSpecification(Test_getObjectSpecificationFallback): + # Repeat tests for C optimizations + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import getObjectSpecification + return getObjectSpecification(*args, **kw) + + def test_optimizations(self): + from zope.interface.declarations import getObjectSpecificationFallback + from zope.interface.declarations import getObjectSpecification + try: + import zope.interface._zope_interface_coptimizations + except ImportError: + self.assertIs(getObjectSpecification, + getObjectSpecificationFallback) + else: + self.assertIsNot(getObjectSpecification, + getObjectSpecificationFallback) + + +class Test_providedByFallback(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import providedByFallback + return providedByFallback(*args, **kw) + + def test_wo_providedBy_on_class_wo_implements(self): + class Foo(object): + pass + foo = Foo() + spec = self._callFUT(foo) + self.assertEqual(list(spec), []) + + def test_w_providedBy_valid_spec(self): + from zope.interface.declarations import Provides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + foo = Foo() + foo.__providedBy__ = Provides(Foo, IFoo) + spec = self._callFUT(foo) + self.assertEqual(list(spec), [IFoo]) + + def test_w_providedBy_invalid_spec(self): + class Foo(object): + pass + foo = Foo() + foo.__providedBy__ = object() + spec = self._callFUT(foo) + self.assertEqual(list(spec), []) + + def test_w_providedBy_invalid_spec_class_w_implements(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + foo.__providedBy__ = object() + spec = self._callFUT(foo) + self.assertEqual(list(spec), [IFoo]) + + def test_w_providedBy_invalid_spec_w_provides_no_provides_on_class(self): + class Foo(object): + pass + foo = Foo() + foo.__providedBy__ = object() + expected = foo.__provides__ = object() + spec = self._callFUT(foo) + self.assertTrue(spec is expected) + + def test_w_providedBy_invalid_spec_w_provides_diff_provides_on_class(self): + class Foo(object): + pass + foo = Foo() + foo.__providedBy__ = object() + expected = foo.__provides__ = object() + Foo.__provides__ = object() + spec = self._callFUT(foo) + self.assertTrue(spec is expected) + + def test_w_providedBy_invalid_spec_w_provides_same_provides_on_class(self): + from zope.interface.declarations import implementer + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + @implementer(IFoo) + class Foo(object): + pass + foo = Foo() + foo.__providedBy__ = object() + foo.__provides__ = Foo.__provides__ = object() + spec = self._callFUT(foo) + self.assertEqual(list(spec), [IFoo]) + + +class Test_providedBy(Test_providedByFallback): + # Repeat tests for C optimizations + + def _callFUT(self, *args, **kw): + from zope.interface.declarations import providedBy + return providedBy(*args, **kw) + + def test_optimizations(self): + from zope.interface.declarations import providedByFallback + from zope.interface.declarations import providedBy + try: + import zope.interface._zope_interface_coptimizations + except ImportError: + self.assertIs(providedBy, providedByFallback) + else: + self.assertIsNot(providedBy, providedByFallback) + + +class ObjectSpecificationDescriptorFallbackTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.declarations \ + import ObjectSpecificationDescriptorFallback + return ObjectSpecificationDescriptorFallback + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_accessed_via_class(self): + from zope.interface.declarations import Provides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + class Foo(object): + pass + Foo.__provides__ = Provides(Foo, IFoo) + Foo.__providedBy__ = self._makeOne() + self.assertEqual(list(Foo.__providedBy__), [IFoo]) + + def test_accessed_via_inst_wo_provides(self): + from zope.interface.declarations import implementer + from zope.interface.declarations import Provides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + @implementer(IFoo) + class Foo(object): + pass + Foo.__provides__ = Provides(Foo, IBar) + Foo.__providedBy__ = self._makeOne() + foo = Foo() + self.assertEqual(list(foo.__providedBy__), [IFoo]) + + def test_accessed_via_inst_w_provides(self): + from zope.interface.declarations import directlyProvides + from zope.interface.declarations import implementer + from zope.interface.declarations import Provides + from zope.interface.interface import InterfaceClass + IFoo = InterfaceClass("IFoo") + IBar = InterfaceClass("IBar") + IBaz = InterfaceClass("IBaz") + @implementer(IFoo) + class Foo(object): + pass + Foo.__provides__ = Provides(Foo, IBar) + Foo.__providedBy__ = self._makeOne() + foo = Foo() + directlyProvides(foo, IBaz) + self.assertEqual(list(foo.__providedBy__), [IBaz, IFoo]) + + +class ObjectSpecificationDescriptorTests( + ObjectSpecificationDescriptorFallbackTests): + # Repeat tests for C optimizations + + def _getTargetClass(self): + from zope.interface.declarations import ObjectSpecificationDescriptor + return ObjectSpecificationDescriptor + + def test_optimizations(self): + from zope.interface.declarations import ( + ObjectSpecificationDescriptorFallback) + try: + import zope.interface._zope_interface_coptimizations + except ImportError: + self.assertIs(self._getTargetClass(), + ObjectSpecificationDescriptorFallback) + else: + self.assertIsNot(self._getTargetClass(), + ObjectSpecificationDescriptorFallback) + + +# Test _normalizeargs through its callers. + + +class _Monkey(object): + # context-manager for replacing module names in the scope of a test. + def __init__(self, module, **kw): + self.module = module + self.to_restore = dict([(key, getattr(module, key)) for key in kw]) + for key, value in kw.items(): + setattr(module, key, value) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + for key, value in self.to_restore.items(): + setattr(self.module, key, value) + + +class _MonkeyDict(object): + # context-manager for restoring a dict w/in a module in the scope of a test. + def __init__(self, module, attrname, **kw): + self.module = module + self.target = getattr(module, attrname) + self.to_restore = self.target.copy() + self.target.clear() + self.target.update(kw) + + def __enter__(self): + return self.target + + def __exit__(self, exc_type, exc_val, exc_tb): + self.target.clear() + self.target.update(self.to_restore) diff --git a/lib/zope/interface/tests/test_document.py b/lib/zope/interface/tests/test_document.py new file mode 100644 index 0000000..bffe6a2 --- /dev/null +++ b/lib/zope/interface/tests/test_document.py @@ -0,0 +1,505 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Documentation tests. +""" +import unittest + + +class Test_asStructuredText(unittest.TestCase): + + def _callFUT(self, iface): + from zope.interface.document import asStructuredText + return asStructuredText(iface) + + def test_asStructuredText_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "INoDocstring", + " Attributes:", + " Methods:", + "" + ]) + class INoDocstring(Interface): + pass + self.assertEqual(self._callFUT(INoDocstring), EXPECTED) + + def test_asStructuredText_empty_with_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "IEmpty", + " This is an empty interface.", + " Attributes:", + " Methods:", + "" + ]) + class IEmpty(Interface): + """ This is an empty interface. + """ + self.assertEqual(self._callFUT(IEmpty), EXPECTED) + + def test_asStructuredText_empty_with_multiline_docstring(self): + from zope.interface import Interface + EXPECTED = '\n'.join([ + "IEmpty", + "", + " This is an empty interface.", + " ", + (" It can be used to annotate any class or object, " + "because it promises"), + " nothing.", + "", + " Attributes:", + "", + " Methods:", + "", + "" + ]) + class IEmpty(Interface): + """ This is an empty interface. + + It can be used to annotate any class or object, because it promises + nothing. + """ + self.assertEqual(self._callFUT(IEmpty), EXPECTED) + + def test_asStructuredText_with_attribute_no_docstring(self): + from zope.interface import Attribute + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "IHasAttribute", + " This interface has an attribute.", + " Attributes:", + " an_attribute -- no documentation", + " Methods:", + "" + ]) + class IHasAttribute(Interface): + """ This interface has an attribute. + """ + an_attribute = Attribute('an_attribute') + + self.assertEqual(self._callFUT(IHasAttribute), EXPECTED) + + def test_asStructuredText_with_attribute_with_docstring(self): + from zope.interface import Attribute + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "IHasAttribute", + " This interface has an attribute.", + " Attributes:", + " an_attribute -- This attribute is documented.", + " Methods:", + "" + ]) + class IHasAttribute(Interface): + """ This interface has an attribute. + """ + an_attribute = Attribute('an_attribute', + 'This attribute is documented.') + + self.assertEqual(self._callFUT(IHasAttribute), EXPECTED) + + def test_asStructuredText_with_method_no_args_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "IHasMethod", + " This interface has a method.", + " Attributes:", + " Methods:", + " aMethod() -- no documentation", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(): + pass + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asStructuredText_with_method_positional_args_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "IHasMethod", + " This interface has a method.", + " Attributes:", + " Methods:", + " aMethod(first, second) -- no documentation", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(first, second): + pass + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asStructuredText_with_method_starargs_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "IHasMethod", + " This interface has a method.", + " Attributes:", + " Methods:", + " aMethod(first, second, *rest) -- no documentation", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(first, second, *rest): + pass + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asStructuredText_with_method_kwargs_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "IHasMethod", + " This interface has a method.", + " Attributes:", + " Methods:", + " aMethod(first, second, **kw) -- no documentation", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(first, second, **kw): + pass + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asStructuredText_with_method_with_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "IHasMethod", + " This interface has a method.", + " Attributes:", + " Methods:", + " aMethod() -- This method is documented.", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(): + """This method is documented. + """ + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asStructuredText_derived_ignores_base(self): + from zope.interface import Attribute + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "IDerived", + " IDerived doc", + " This interface extends:", + " o IBase", + " Attributes:", + " attr1 -- no documentation", + " attr2 -- attr2 doc", + " Methods:", + " method3() -- method3 doc", + " method4() -- no documentation", + " method5() -- method5 doc", + "", + ]) + + class IBase(Interface): + def method1(): + pass + def method2(): + pass + + class IDerived(IBase): + "IDerived doc" + attr1 = Attribute('attr1') + attr2 = Attribute('attr2', 'attr2 doc') + + def method3(): + "method3 doc" + def method4(): + pass + def method5(): + "method5 doc" + + self.assertEqual(self._callFUT(IDerived), EXPECTED) + + +class Test_asReStructuredText(unittest.TestCase): + + def _callFUT(self, iface): + from zope.interface.document import asReStructuredText + return asReStructuredText(iface) + + def test_asReStructuredText_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``INoDocstring``", + " Attributes:", + " Methods:", + "" + ]) + class INoDocstring(Interface): + pass + self.assertEqual(self._callFUT(INoDocstring), EXPECTED) + + def test_asReStructuredText_empty_with_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``IEmpty``", + " This is an empty interface.", + " Attributes:", + " Methods:", + "" + ]) + class IEmpty(Interface): + """ This is an empty interface. + """ + self.assertEqual(self._callFUT(IEmpty), EXPECTED) + + def test_asReStructuredText_empty_with_multiline_docstring(self): + from zope.interface import Interface + EXPECTED = '\n'.join([ + "``IEmpty``", + "", + " This is an empty interface.", + " ", + (" It can be used to annotate any class or object, " + "because it promises"), + " nothing.", + "", + " Attributes:", + "", + " Methods:", + "", + "" + ]) + class IEmpty(Interface): + """ This is an empty interface. + + It can be used to annotate any class or object, because it promises + nothing. + """ + self.assertEqual(self._callFUT(IEmpty), EXPECTED) + + def test_asReStructuredText_with_attribute_no_docstring(self): + from zope.interface import Attribute + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``IHasAttribute``", + " This interface has an attribute.", + " Attributes:", + " ``an_attribute`` -- no documentation", + " Methods:", + "" + ]) + class IHasAttribute(Interface): + """ This interface has an attribute. + """ + an_attribute = Attribute('an_attribute') + + self.assertEqual(self._callFUT(IHasAttribute), EXPECTED) + + def test_asReStructuredText_with_attribute_with_docstring(self): + from zope.interface import Attribute + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``IHasAttribute``", + " This interface has an attribute.", + " Attributes:", + " ``an_attribute`` -- This attribute is documented.", + " Methods:", + "" + ]) + class IHasAttribute(Interface): + """ This interface has an attribute. + """ + an_attribute = Attribute('an_attribute', + 'This attribute is documented.') + + self.assertEqual(self._callFUT(IHasAttribute), EXPECTED) + + def test_asReStructuredText_with_method_no_args_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``IHasMethod``", + " This interface has a method.", + " Attributes:", + " Methods:", + " ``aMethod()`` -- no documentation", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(): + pass + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asReStructuredText_with_method_positional_args_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``IHasMethod``", + " This interface has a method.", + " Attributes:", + " Methods:", + " ``aMethod(first, second)`` -- no documentation", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(first, second): + pass + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asReStructuredText_with_method_starargs_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``IHasMethod``", + " This interface has a method.", + " Attributes:", + " Methods:", + " ``aMethod(first, second, *rest)`` -- no documentation", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(first, second, *rest): + pass + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asReStructuredText_with_method_kwargs_no_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``IHasMethod``", + " This interface has a method.", + " Attributes:", + " Methods:", + " ``aMethod(first, second, **kw)`` -- no documentation", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(first, second, **kw): + pass + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asReStructuredText_with_method_with_docstring(self): + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``IHasMethod``", + " This interface has a method.", + " Attributes:", + " Methods:", + " ``aMethod()`` -- This method is documented.", + "" + ]) + class IHasMethod(Interface): + """ This interface has a method. + """ + def aMethod(): + """This method is documented. + """ + + self.assertEqual(self._callFUT(IHasMethod), EXPECTED) + + def test_asReStructuredText_derived_ignores_base(self): + from zope.interface import Attribute + from zope.interface import Interface + EXPECTED = '\n\n'.join([ + "``IDerived``", + " IDerived doc", + " This interface extends:", + " o ``IBase``", + " Attributes:", + " ``attr1`` -- no documentation", + " ``attr2`` -- attr2 doc", + " Methods:", + " ``method3()`` -- method3 doc", + " ``method4()`` -- no documentation", + " ``method5()`` -- method5 doc", + "", + ]) + + class IBase(Interface): + def method1(): + pass + def method2(): + pass + + class IDerived(IBase): + "IDerived doc" + attr1 = Attribute('attr1') + attr2 = Attribute('attr2', 'attr2 doc') + + def method3(): + "method3 doc" + def method4(): + pass + def method5(): + "method5 doc" + + self.assertEqual(self._callFUT(IDerived), EXPECTED) + + +class Test__justify_and_indent(unittest.TestCase): + + def _callFUT(self, text, level, **kw): + from zope.interface.document import _justify_and_indent + return _justify_and_indent(text, level, **kw) + + def test_simple_level_0(self): + LINES = ['Three blind mice', 'See how they run'] + text = '\n'.join(LINES) + self.assertEqual(self._callFUT(text, 0), text) + + def test_simple_level_1(self): + LINES = ['Three blind mice', 'See how they run'] + text = '\n'.join(LINES) + self.assertEqual(self._callFUT(text, 1), + '\n'.join([' ' + line for line in LINES])) + + def test_simple_level_2(self): + LINES = ['Three blind mice', 'See how they run'] + text = '\n'.join(LINES) + self.assertEqual(self._callFUT(text, 1), + '\n'.join([' ' + line for line in LINES])) + + def test_simple_w_CRLF(self): + LINES = ['Three blind mice', 'See how they run'] + text = '\r\n'.join(LINES) + self.assertEqual(self._callFUT(text, 1), + '\n'.join([' ' + line for line in LINES])) + + def test_with_munge(self): + TEXT = ("This is a piece of text longer than 15 characters, \n" + "and split across multiple lines.") + EXPECTED = (" This is a piece\n" + " of text longer\n" + " than 15 characters,\n" + " and split across\n" + " multiple lines.\n" + " ") + self.assertEqual(self._callFUT(TEXT, 1, munge=1, width=15), EXPECTED) diff --git a/lib/zope/interface/tests/test_element.py b/lib/zope/interface/tests/test_element.py new file mode 100644 index 0000000..eb003cd --- /dev/null +++ b/lib/zope/interface/tests/test_element.py @@ -0,0 +1,31 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Test Element meta-class. +""" + +import unittest +from zope.interface.interface import Element + +class TestElement(unittest.TestCase): + + def test_taggedValues(self): + """Test that we can update tagged values of more than one element + """ + + e1 = Element("foo") + e2 = Element("bar") + e1.setTaggedValue("x", 1) + e2.setTaggedValue("x", 2) + self.assertEqual(e1.getTaggedValue("x"), 1) + self.assertEqual(e2.getTaggedValue("x"), 2) diff --git a/lib/zope/interface/tests/test_exceptions.py b/lib/zope/interface/tests/test_exceptions.py new file mode 100644 index 0000000..ae73f9c --- /dev/null +++ b/lib/zope/interface/tests/test_exceptions.py @@ -0,0 +1,72 @@ +############################################################################## +# +# Copyright (c) 2010 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" zope.interface.exceptions unit tests +""" +import unittest + +def _makeIface(): + from zope.interface import Interface + class IDummy(Interface): + pass + return IDummy + +class DoesNotImplementTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.exceptions import DoesNotImplement + return DoesNotImplement + + def _makeOne(self): + iface = _makeIface() + return self._getTargetClass()(iface) + + def test___str__(self): + dni = self._makeOne() + # XXX The trailing newlines and blank spaces are a stupid artifact. + self.assertEqual(str(dni), + 'An object does not implement interface <InterfaceClass ' + 'zope.interface.tests.test_exceptions.IDummy>\n\n ') + +class BrokenImplementationTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.exceptions import BrokenImplementation + return BrokenImplementation + + def _makeOne(self, name='missing'): + iface = _makeIface() + return self._getTargetClass()(iface, name) + + def test___str__(self): + dni = self._makeOne() + # XXX The trailing newlines and blank spaces are a stupid artifact. + self.assertEqual(str(dni), + 'An object has failed to implement interface <InterfaceClass ' + 'zope.interface.tests.test_exceptions.IDummy>\n\n' + ' The missing attribute was not provided.\n ') + +class BrokenMethodImplementationTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.exceptions import BrokenMethodImplementation + return BrokenMethodImplementation + + def _makeOne(self, method='aMethod', mess='I said so'): + return self._getTargetClass()(method, mess) + + def test___str__(self): + dni = self._makeOne() + self.assertEqual(str(dni), + 'The implementation of aMethod violates its contract\n' + ' because I said so.\n ') diff --git a/lib/zope/interface/tests/test_interface.py b/lib/zope/interface/tests/test_interface.py new file mode 100644 index 0000000..2bb3d1c --- /dev/null +++ b/lib/zope/interface/tests/test_interface.py @@ -0,0 +1,2123 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Test Interface implementation +""" +# pylint:disable=protected-access +import unittest + +from zope.interface._compat import _skip_under_py3k + +_marker = object() + + +class Test_invariant(unittest.TestCase): + + def test_w_single(self): + from zope.interface.interface import invariant + from zope.interface.interface import TAGGED_DATA + + def _check(*args, **kw): + raise NotImplementedError() + + class Foo(object): + invariant(_check) + + self.assertEqual(getattr(Foo, TAGGED_DATA, None), + {'invariants': [_check]}) + + def test_w_multiple(self): + from zope.interface.interface import invariant + from zope.interface.interface import TAGGED_DATA + + def _check(*args, **kw): + raise NotImplementedError() + + def _another_check(*args, **kw): + raise NotImplementedError() + + class Foo(object): + invariant(_check) + invariant(_another_check) + + self.assertEqual(getattr(Foo, TAGGED_DATA, None), + {'invariants': [_check, _another_check]}) + + +class Test_taggedValue(unittest.TestCase): + + def test_w_single(self): + from zope.interface.interface import taggedValue + from zope.interface.interface import TAGGED_DATA + + class Foo(object): + taggedValue('bar', ['baz']) + + self.assertEqual(getattr(Foo, TAGGED_DATA, None), + {'bar': ['baz']}) + + def test_w_multiple(self): + from zope.interface.interface import taggedValue + from zope.interface.interface import TAGGED_DATA + + class Foo(object): + taggedValue('bar', ['baz']) + taggedValue('qux', 'spam') + + self.assertEqual(getattr(Foo, TAGGED_DATA, None), + {'bar': ['baz'], 'qux': 'spam'}) + + def test_w_multiple_overwriting(self): + from zope.interface.interface import taggedValue + from zope.interface.interface import TAGGED_DATA + + class Foo(object): + taggedValue('bar', ['baz']) + taggedValue('qux', 'spam') + taggedValue('bar', 'frob') + + self.assertEqual(getattr(Foo, TAGGED_DATA, None), + {'bar': 'frob', 'qux': 'spam'}) + + +class ElementTests(unittest.TestCase): + + DEFAULT_NAME = 'AnElement' + + def _getTargetClass(self): + from zope.interface.interface import Element + return Element + + def _makeOne(self, name=None): + if name is None: + name = self.DEFAULT_NAME + return self._getTargetClass()(name) + + def test_ctor_defaults(self): + element = self._makeOne() + self.assertEqual(element.__name__, self.DEFAULT_NAME) + self.assertEqual(element.getName(), self.DEFAULT_NAME) + self.assertEqual(element.__doc__, '') + self.assertEqual(element.getDoc(), '') + self.assertEqual(list(element.getTaggedValueTags()), []) + + def test_ctor_no_doc_space_in_name(self): + element = self._makeOne('An Element') + self.assertEqual(element.__name__, None) + self.assertEqual(element.__doc__, 'An Element') + + def test_getTaggedValue_miss(self): + element = self._makeOne() + self.assertRaises(KeyError, element.getTaggedValue, 'nonesuch') + + def test_queryTaggedValue_miss(self): + element = self._makeOne() + self.assertEqual(element.queryTaggedValue('nonesuch'), None) + + def test_queryTaggedValue_miss_w_default(self): + element = self._makeOne() + self.assertEqual(element.queryTaggedValue('nonesuch', 'bar'), 'bar') + + def test_setTaggedValue(self): + element = self._makeOne() + element.setTaggedValue('foo', 'bar') + self.assertEqual(list(element.getTaggedValueTags()), ['foo']) + self.assertEqual(element.getTaggedValue('foo'), 'bar') + self.assertEqual(element.queryTaggedValue('foo'), 'bar') + + +class SpecificationBasePyTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.interface import SpecificationBasePy + return SpecificationBasePy + + def _makeOne(self): + return self._getTargetClass()() + + def test_providedBy_miss(self): + from zope.interface import interface + from zope.interface.declarations import _empty + sb = self._makeOne() + def _providedBy(obj): + return _empty + with _Monkey(interface, providedBy=_providedBy): + self.assertFalse(sb.providedBy(object())) + + def test_providedBy_hit(self): + from zope.interface import interface + sb = self._makeOne() + class _Decl(object): + _implied = {sb: {},} + def _providedBy(obj): + return _Decl() + with _Monkey(interface, providedBy=_providedBy): + self.assertTrue(sb.providedBy(object())) + + def test_implementedBy_miss(self): + from zope.interface import interface + from zope.interface.declarations import _empty + sb = self._makeOne() + def _implementedBy(obj): + return _empty + with _Monkey(interface, implementedBy=_implementedBy): + self.assertFalse(sb.implementedBy(object())) + + def test_implementedBy_hit(self): + from zope.interface import interface + sb = self._makeOne() + class _Decl(object): + _implied = {sb: {},} + def _implementedBy(obj): + return _Decl() + with _Monkey(interface, implementedBy=_implementedBy): + self.assertTrue(sb.implementedBy(object())) + + def test_isOrExtends_miss(self): + sb = self._makeOne() + sb._implied = {} # not defined by SpecificationBasePy + self.assertFalse(sb.isOrExtends(object())) + + def test_isOrExtends_hit(self): + sb = self._makeOne() + testing = object() + sb._implied = {testing: {}} # not defined by SpecificationBasePy + self.assertTrue(sb(testing)) + + def test___call___miss(self): + sb = self._makeOne() + sb._implied = {} # not defined by SpecificationBasePy + self.assertFalse(sb.isOrExtends(object())) + + def test___call___hit(self): + sb = self._makeOne() + testing = object() + sb._implied = {testing: {}} # not defined by SpecificationBasePy + self.assertTrue(sb(testing)) + + +class SpecificationBaseTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.interface import SpecificationBase + return SpecificationBase + + def test_optimizations(self): + from zope.interface.interface import SpecificationBasePy + try: + import zope.interface._zope_interface_coptimizations + except ImportError: + self.assertIs(self._getTargetClass(), SpecificationBasePy) + else: + self.assertIsNot(self._getTargetClass(), SpecificationBasePy) + + +class InterfaceBasePyTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.interface import InterfaceBasePy + return InterfaceBasePy + + def _makeOne(self, object_should_provide): + class IB(self._getTargetClass()): + def _call_conform(self, conform): + return conform(self) + def providedBy(self, obj): + return object_should_provide + return IB() + + def test___call___w___conform___returning_value(self): + ib = self._makeOne(False) + conformed = object() + class _Adapted(object): + def __conform__(self, iface): + return conformed + self.assertTrue(ib(_Adapted()) is conformed) + + def test___call___w___conform___miss_ob_provides(self): + ib = self._makeOne(True) + class _Adapted(object): + def __conform__(self, iface): + return None + adapted = _Adapted() + self.assertTrue(ib(adapted) is adapted) + + def test___call___wo___conform___ob_no_provides_w_alternate(self): + ib = self._makeOne(False) + adapted = object() + alternate = object() + self.assertTrue(ib(adapted, alternate) is alternate) + + def test___call___w___conform___ob_no_provides_wo_alternate(self): + ib = self._makeOne(False) + adapted = object() + self.assertRaises(TypeError, ib, adapted) + + def test___adapt___ob_provides(self): + ib = self._makeOne(True) + adapted = object() + self.assertTrue(ib.__adapt__(adapted) is adapted) + + def test___adapt___ob_no_provides_uses_hooks(self): + from zope.interface import interface + ib = self._makeOne(False) + adapted = object() + _missed = [] + def _hook_miss(iface, obj): + _missed.append((iface, obj)) + return None + def _hook_hit(iface, obj): + return obj + with _Monkey(interface, adapter_hooks=[_hook_miss, _hook_hit]): + self.assertTrue(ib.__adapt__(adapted) is adapted) + self.assertEqual(_missed, [(ib, adapted)]) + + +class InterfaceBaseTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.interface import InterfaceBase + return InterfaceBase + + def test_optimizations(self): + from zope.interface.interface import InterfaceBasePy + try: + import zope.interface._zope_interface_coptimizations + except ImportError: + self.assertIs(self._getTargetClass(), InterfaceBasePy) + else: + self.assertIsNot(self._getTargetClass(), InterfaceBasePy) + + +class SpecificationTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.interface import Specification + return Specification + + def _makeOne(self, bases=_marker): + if bases is _marker: + return self._getTargetClass()() + return self._getTargetClass()(bases) + + def test_ctor(self): + from zope.interface.interface import Interface + spec = self._makeOne() + self.assertEqual(spec.__bases__, ()) + self.assertEqual(len(spec._implied), 2) + self.assertTrue(spec in spec._implied) + self.assertTrue(Interface in spec._implied) + self.assertEqual(len(spec.dependents), 0) + + def test_subscribe_first_time(self): + spec = self._makeOne() + dep = DummyDependent() + spec.subscribe(dep) + self.assertEqual(len(spec.dependents), 1) + self.assertEqual(spec.dependents[dep], 1) + + def test_subscribe_again(self): + spec = self._makeOne() + dep = DummyDependent() + spec.subscribe(dep) + spec.subscribe(dep) + self.assertEqual(spec.dependents[dep], 2) + + def test_unsubscribe_miss(self): + spec = self._makeOne() + dep = DummyDependent() + self.assertRaises(KeyError, spec.unsubscribe, dep) + + def test_unsubscribe(self): + spec = self._makeOne() + dep = DummyDependent() + spec.subscribe(dep) + spec.subscribe(dep) + spec.unsubscribe(dep) + self.assertEqual(spec.dependents[dep], 1) + spec.unsubscribe(dep) + self.assertFalse(dep in spec.dependents) + + def test___setBases_subscribes_bases_and_notifies_dependents(self): + from zope.interface.interface import Interface + spec = self._makeOne() + dep = DummyDependent() + spec.subscribe(dep) + class I(Interface): + pass + class J(Interface): + pass + spec.__bases__ = (I,) + self.assertEqual(dep._changed, [spec]) + self.assertEqual(I.dependents[spec], 1) + spec.__bases__ = (J,) + self.assertEqual(I.dependents.get(spec), None) + self.assertEqual(J.dependents[spec], 1) + + def test_changed_clears_volatiles_and_implied(self): + from zope.interface.interface import Interface + class I(Interface): + pass + spec = self._makeOne() + spec._v_attrs = 'Foo' + spec._implied[I] = () + spec.changed(spec) + self.assertTrue(getattr(spec, '_v_attrs', self) is self) + self.assertFalse(I in spec._implied) + + def test_interfaces_skips_already_seen(self): + from zope.interface.interface import Interface + class IFoo(Interface): + pass + spec = self._makeOne([IFoo, IFoo]) + self.assertEqual(list(spec.interfaces()), [IFoo]) + + def test_extends_strict_wo_self(self): + from zope.interface.interface import Interface + class IFoo(Interface): + pass + spec = self._makeOne(IFoo) + self.assertFalse(spec.extends(IFoo, strict=True)) + + def test_extends_strict_w_self(self): + spec = self._makeOne() + self.assertFalse(spec.extends(spec, strict=True)) + + def test_extends_non_strict_w_self(self): + spec = self._makeOne() + self.assertTrue(spec.extends(spec, strict=False)) + + def test_get_hit_w__v_attrs(self): + spec = self._makeOne() + foo = object() + spec._v_attrs = {'foo': foo} + self.assertTrue(spec.get('foo') is foo) + + def test_get_hit_from_base_wo__v_attrs(self): + from zope.interface.interface import Attribute + from zope.interface.interface import Interface + class IFoo(Interface): + foo = Attribute('foo') + class IBar(Interface): + bar = Attribute('bar') + spec = self._makeOne([IFoo, IBar]) + self.assertTrue(spec.get('foo') is IFoo.get('foo')) + self.assertTrue(spec.get('bar') is IBar.get('bar')) + +class InterfaceClassTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.interface import InterfaceClass + return InterfaceClass + + def _makeOne(self, name='ITest', bases=(), attrs=None, __doc__=None, + __module__=None): + return self._getTargetClass()(name, bases, attrs, __doc__, __module__) + + def test_ctor_defaults(self): + klass = self._getTargetClass() + inst = klass('ITesting') + self.assertEqual(inst.__name__, 'ITesting') + self.assertEqual(inst.__doc__, '') + self.assertEqual(inst.__bases__, ()) + self.assertEqual(inst.getBases(), ()) + + def test_ctor_bad_bases(self): + klass = self._getTargetClass() + self.assertRaises(TypeError, klass, 'ITesting', (object(),)) + + def test_ctor_w_attrs_attrib_methods(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + klass = self._getTargetClass() + inst = klass('ITesting', attrs=ATTRS) + self.assertEqual(inst.__name__, 'ITesting') + self.assertEqual(inst.__doc__, '') + self.assertEqual(inst.__bases__, ()) + self.assertEqual(inst.names(), ATTRS.keys()) + + def test_ctor_attrs_w___locals__(self): + ATTRS = {'__locals__': {}} + klass = self._getTargetClass() + inst = klass('ITesting', attrs=ATTRS) + self.assertEqual(inst.__name__, 'ITesting') + self.assertEqual(inst.__doc__, '') + self.assertEqual(inst.__bases__, ()) + self.assertEqual(inst.names(), ATTRS.keys()) + + def test_ctor_attrs_w___annotations__(self): + ATTRS = {'__annotations__': {}} + klass = self._getTargetClass() + inst = klass('ITesting', attrs=ATTRS) + self.assertEqual(inst.__name__, 'ITesting') + self.assertEqual(inst.__doc__, '') + self.assertEqual(inst.__bases__, ()) + self.assertEqual(inst.names(), ATTRS.keys()) + + def test_ctor_attrs_w__decorator_non_return(self): + from zope.interface.interface import _decorator_non_return + ATTRS = {'dropme': _decorator_non_return} + klass = self._getTargetClass() + inst = klass('ITesting', attrs=ATTRS) + self.assertEqual(inst.__name__, 'ITesting') + self.assertEqual(inst.__doc__, '') + self.assertEqual(inst.__bases__, ()) + self.assertEqual(list(inst.names()), []) + + def test_ctor_attrs_w_invalid_attr_type(self): + from zope.interface.exceptions import InvalidInterface + ATTRS = {'invalid': object()} + klass = self._getTargetClass() + self.assertRaises(InvalidInterface, klass, 'ITesting', attrs=ATTRS) + + def test_ctor_w_explicit___doc__(self): + ATTRS = {'__doc__': 'ATTR'} + klass = self._getTargetClass() + inst = klass('ITesting', attrs=ATTRS, __doc__='EXPLICIT') + self.assertEqual(inst.__doc__, 'EXPLICIT') + + def test_interfaces(self): + iface = self._makeOne() + self.assertEqual(list(iface.interfaces()), [iface]) + + def test_getBases(self): + iface = self._makeOne() + sub = self._makeOne('ISub', bases=(iface,)) + self.assertEqual(sub.getBases(), (iface,)) + + def test_isEqualOrExtendedBy_identity(self): + iface = self._makeOne() + self.assertTrue(iface.isEqualOrExtendedBy(iface)) + + def test_isEqualOrExtendedBy_subiface(self): + iface = self._makeOne() + sub = self._makeOne('ISub', bases=(iface,)) + self.assertTrue(iface.isEqualOrExtendedBy(sub)) + self.assertFalse(sub.isEqualOrExtendedBy(iface)) + + def test_isEqualOrExtendedBy_unrelated(self): + one = self._makeOne('One') + another = self._makeOne('Another') + self.assertFalse(one.isEqualOrExtendedBy(another)) + self.assertFalse(another.isEqualOrExtendedBy(one)) + + def test_names_w_all_False_ignores_bases(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + BASE_ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + DERIVED_ATTRS = {'baz': Attribute('Baz', ''), + } + base = self._makeOne('IBase', attrs=BASE_ATTRS) + derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) + self.assertEqual(sorted(derived.names(all=False)), ['baz']) + + def test_names_w_all_True_no_bases(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + one = self._makeOne(attrs=ATTRS) + self.assertEqual(sorted(one.names(all=True)), ['bar', 'foo']) + + def test_names_w_all_True_w_bases_simple(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + BASE_ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + DERIVED_ATTRS = {'baz': Attribute('Baz', ''), + } + base = self._makeOne('IBase', attrs=BASE_ATTRS) + derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) + self.assertEqual(sorted(derived.names(all=True)), ['bar', 'baz', 'foo']) + + def test_names_w_all_True_bases_w_same_names(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + def _foo(): + """DOCSTRING""" + BASE_ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + DERIVED_ATTRS = {'foo': fromFunction(_foo), + 'baz': Attribute('Baz', ''), + } + base = self._makeOne('IBase', attrs=BASE_ATTRS) + derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) + self.assertEqual(sorted(derived.names(all=True)), ['bar', 'baz', 'foo']) + + def test___iter__(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + def _foo(): + """DOCSTRING""" + BASE_ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + DERIVED_ATTRS = {'foo': fromFunction(_foo), + 'baz': Attribute('Baz', ''), + } + base = self._makeOne('IBase', attrs=BASE_ATTRS) + derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) + self.assertEqual(sorted(derived), ['bar', 'baz', 'foo']) + + def test_namesAndDescriptions_w_all_False_ignores_bases(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + BASE_ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + DERIVED_ATTRS = {'baz': Attribute('Baz', ''), + } + base = self._makeOne('IBase', attrs=BASE_ATTRS) + derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) + self.assertEqual(sorted(derived.namesAndDescriptions(all=False)), + [('baz', DERIVED_ATTRS['baz']), + ]) + + def test_namesAndDescriptions_w_all_True_no_bases(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + one = self._makeOne(attrs=ATTRS) + self.assertEqual(sorted(one.namesAndDescriptions(all=False)), + [('bar', ATTRS['bar']), + ('foo', ATTRS['foo']), + ]) + + def test_namesAndDescriptions_w_all_True_simple(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + BASE_ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + DERIVED_ATTRS = {'baz': Attribute('Baz', ''), + } + base = self._makeOne('IBase', attrs=BASE_ATTRS) + derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) + self.assertEqual(sorted(derived.namesAndDescriptions(all=True)), + [('bar', BASE_ATTRS['bar']), + ('baz', DERIVED_ATTRS['baz']), + ('foo', BASE_ATTRS['foo']), + ]) + + def test_namesAndDescriptions_w_all_True_bases_w_same_names(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + def _foo(): + """DOCSTRING""" + BASE_ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + DERIVED_ATTRS = {'foo': fromFunction(_foo), + 'baz': Attribute('Baz', ''), + } + base = self._makeOne('IBase', attrs=BASE_ATTRS) + derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) + self.assertEqual(sorted(derived.namesAndDescriptions(all=True)), + [('bar', BASE_ATTRS['bar']), + ('baz', DERIVED_ATTRS['baz']), + ('foo', DERIVED_ATTRS['foo']), + ]) + + def test_getDescriptionFor_miss(self): + one = self._makeOne() + self.assertRaises(KeyError, one.getDescriptionFor, 'nonesuch') + + def test_getDescriptionFor_hit(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + one = self._makeOne(attrs=ATTRS) + self.assertEqual(one.getDescriptionFor('foo'), ATTRS['foo']) + self.assertEqual(one.getDescriptionFor('bar'), ATTRS['bar']) + + def test___getitem___miss(self): + one = self._makeOne() + def _test(): + return one['nonesuch'] + self.assertRaises(KeyError, _test) + + def test___getitem___hit(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + one = self._makeOne(attrs=ATTRS) + self.assertEqual(one['foo'], ATTRS['foo']) + self.assertEqual(one['bar'], ATTRS['bar']) + + def test___contains___miss(self): + one = self._makeOne() + self.assertFalse('nonesuch' in one) + + def test___contains___hit(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + one = self._makeOne(attrs=ATTRS) + self.assertTrue('foo' in one) + self.assertTrue('bar' in one) + + def test_direct_miss(self): + one = self._makeOne() + self.assertEqual(one.direct('nonesuch'), None) + + def test_direct_hit_local_miss_bases(self): + from zope.interface.interface import Attribute + from zope.interface.interface import fromFunction + def _bar(): + """DOCSTRING""" + def _foo(): + """DOCSTRING""" + BASE_ATTRS = {'foo': Attribute('Foo', ''), + 'bar': fromFunction(_bar), + } + DERIVED_ATTRS = {'foo': fromFunction(_foo), + 'baz': Attribute('Baz', ''), + } + base = self._makeOne('IBase', attrs=BASE_ATTRS) + derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) + self.assertEqual(derived.direct('foo'), DERIVED_ATTRS['foo']) + self.assertEqual(derived.direct('baz'), DERIVED_ATTRS['baz']) + self.assertEqual(derived.direct('bar'), None) + + def test_queryDescriptionFor_miss(self): + iface = self._makeOne() + self.assertEqual(iface.queryDescriptionFor('nonesuch'), None) + + def test_queryDescriptionFor_hit(self): + from zope.interface import Attribute + ATTRS = {'attr': Attribute('Title', 'Description')} + iface = self._makeOne(attrs=ATTRS) + self.assertEqual(iface.queryDescriptionFor('attr'), ATTRS['attr']) + + def test_validateInvariants_pass(self): + _called_with = [] + def _passable(*args, **kw): + _called_with.append((args, kw)) + return True + iface = self._makeOne() + obj = object() + iface.setTaggedValue('invariants', [_passable]) + self.assertEqual(iface.validateInvariants(obj), None) + self.assertEqual(_called_with, [((obj,), {})]) + + def test_validateInvariants_fail_wo_errors_passed(self): + from zope.interface.exceptions import Invalid + _passable_called_with = [] + def _passable(*args, **kw): + _passable_called_with.append((args, kw)) + return True + _fail_called_with = [] + def _fail(*args, **kw): + _fail_called_with.append((args, kw)) + raise Invalid + iface = self._makeOne() + obj = object() + iface.setTaggedValue('invariants', [_passable, _fail]) + self.assertRaises(Invalid, iface.validateInvariants, obj) + self.assertEqual(_passable_called_with, [((obj,), {})]) + self.assertEqual(_fail_called_with, [((obj,), {})]) + + def test_validateInvariants_fail_w_errors_passed(self): + from zope.interface.exceptions import Invalid + _errors = [] + _fail_called_with = [] + def _fail(*args, **kw): + _fail_called_with.append((args, kw)) + raise Invalid + iface = self._makeOne() + obj = object() + iface.setTaggedValue('invariants', [_fail]) + self.assertRaises(Invalid, iface.validateInvariants, obj, _errors) + self.assertEqual(_fail_called_with, [((obj,), {})]) + self.assertEqual(len(_errors), 1) + self.assertTrue(isinstance(_errors[0], Invalid)) + + def test_validateInvariants_fail_in_base_wo_errors_passed(self): + from zope.interface.exceptions import Invalid + _passable_called_with = [] + def _passable(*args, **kw): + _passable_called_with.append((args, kw)) + return True + _fail_called_with = [] + def _fail(*args, **kw): + _fail_called_with.append((args, kw)) + raise Invalid + base = self._makeOne('IBase') + derived = self._makeOne('IDerived', (base,)) + obj = object() + base.setTaggedValue('invariants', [_fail]) + derived.setTaggedValue('invariants', [_passable]) + self.assertRaises(Invalid, derived.validateInvariants, obj) + self.assertEqual(_passable_called_with, [((obj,), {})]) + self.assertEqual(_fail_called_with, [((obj,), {})]) + + def test_validateInvariants_fail_in_base_w_errors_passed(self): + from zope.interface.exceptions import Invalid + _errors = [] + _passable_called_with = [] + def _passable(*args, **kw): + _passable_called_with.append((args, kw)) + return True + _fail_called_with = [] + def _fail(*args, **kw): + _fail_called_with.append((args, kw)) + raise Invalid + base = self._makeOne('IBase') + derived = self._makeOne('IDerived', (base,)) + obj = object() + base.setTaggedValue('invariants', [_fail]) + derived.setTaggedValue('invariants', [_passable]) + self.assertRaises(Invalid, derived.validateInvariants, obj, _errors) + self.assertEqual(_passable_called_with, [((obj,), {})]) + self.assertEqual(_fail_called_with, [((obj,), {})]) + self.assertEqual(len(_errors), 1) + self.assertTrue(isinstance(_errors[0], Invalid)) + + def test___reduce__(self): + iface = self._makeOne('PickleMe') + self.assertEqual(iface.__reduce__(), 'PickleMe') + + def test___hash___normal(self): + iface = self._makeOne('HashMe') + self.assertEqual(hash(iface), + hash((('HashMe', + 'zope.interface.tests.test_interface')))) + + def test___hash___missing_required_attrs(self): + import warnings + from warnings import catch_warnings + + class Derived(self._getTargetClass()): + def __init__(self): + pass # Don't call base class. + derived = Derived() + with catch_warnings(record=True) as warned: + warnings.simplefilter('always') # see LP #825249 + self.assertEqual(hash(derived), 1) + self.assertEqual(len(warned), 1) + self.assertTrue(warned[0].category is UserWarning) + self.assertEqual(str(warned[0].message), + 'Hashing uninitialized InterfaceClass instance') + + def test_comparison_with_None(self): + iface = self._makeOne() + self.assertTrue(iface < None) + self.assertTrue(iface <= None) + self.assertFalse(iface == None) + self.assertTrue(iface != None) + self.assertFalse(iface >= None) + self.assertFalse(iface > None) + + self.assertFalse(None < iface) + self.assertFalse(None <= iface) + self.assertFalse(None == iface) + self.assertTrue(None != iface) + self.assertTrue(None >= iface) + self.assertTrue(None > iface) + + def test_comparison_with_same_instance(self): + iface = self._makeOne() + + self.assertFalse(iface < iface) + self.assertTrue(iface <= iface) + self.assertTrue(iface == iface) + self.assertFalse(iface != iface) + self.assertTrue(iface >= iface) + self.assertFalse(iface > iface) + + def test_comparison_with_same_named_instance_in_other_module(self): + + one = self._makeOne('IName', __module__='zope.interface.tests.one') + other = self._makeOne('IName', __module__='zope.interface.tests.other') + + self.assertTrue(one < other) + self.assertFalse(other < one) + self.assertTrue(one <= other) + self.assertFalse(other <= one) + self.assertFalse(one == other) + self.assertFalse(other == one) + self.assertTrue(one != other) + self.assertTrue(other != one) + self.assertFalse(one >= other) + self.assertTrue(other >= one) + self.assertFalse(one > other) + self.assertTrue(other > one) + + +class InterfaceTests(unittest.TestCase): + + def test_attributes_link_to_interface(self): + from zope.interface import Interface + from zope.interface import Attribute + + class I1(Interface): + attr = Attribute("My attr") + + self.assertTrue(I1['attr'].interface is I1) + + def test_methods_link_to_interface(self): + from zope.interface import Interface + + class I1(Interface): + + def method(foo, bar, bingo): + "A method" + + self.assertTrue(I1['method'].interface is I1) + + def test_classImplements_simple(self): + from zope.interface import Interface + from zope.interface import implementedBy + from zope.interface import providedBy + + class ICurrent(Interface): + def method1(a, b): + pass + def method2(a, b): + pass + + class IOther(Interface): + pass + + class Current(object): + __implemented__ = ICurrent + def method1(self, a, b): + raise NotImplementedError() + def method2(self, a, b): + raise NotImplementedError() + + current = Current() + + self.assertTrue(ICurrent.implementedBy(Current)) + self.assertFalse(IOther.implementedBy(Current)) + self.assertTrue(ICurrent in implementedBy(Current)) + self.assertFalse(IOther in implementedBy(Current)) + self.assertTrue(ICurrent in providedBy(current)) + self.assertFalse(IOther in providedBy(current)) + + def test_classImplements_base_not_derived(self): + from zope.interface import Interface + from zope.interface import implementedBy + from zope.interface import providedBy + class IBase(Interface): + def method(): + pass + class IDerived(IBase): + pass + class Current(): + __implemented__ = IBase + def method(self): + raise NotImplementedError() + current = Current() + + self.assertTrue(IBase.implementedBy(Current)) + self.assertFalse(IDerived.implementedBy(Current)) + self.assertTrue(IBase in implementedBy(Current)) + self.assertFalse(IDerived in implementedBy(Current)) + self.assertTrue(IBase in providedBy(current)) + self.assertFalse(IDerived in providedBy(current)) + + def test_classImplements_base_and_derived(self): + from zope.interface import Interface + from zope.interface import implementedBy + from zope.interface import providedBy + + class IBase(Interface): + def method(): + pass + + class IDerived(IBase): + pass + + class Current(object): + __implemented__ = IDerived + def method(self): + raise NotImplementedError() + + current = Current() + + self.assertTrue(IBase.implementedBy(Current)) + self.assertTrue(IDerived.implementedBy(Current)) + self.assertFalse(IBase in implementedBy(Current)) + self.assertTrue(IBase in implementedBy(Current).flattened()) + self.assertTrue(IDerived in implementedBy(Current)) + self.assertFalse(IBase in providedBy(current)) + self.assertTrue(IBase in providedBy(current).flattened()) + self.assertTrue(IDerived in providedBy(current)) + + def test_classImplements_multiple(self): + from zope.interface import Interface + from zope.interface import implementedBy + from zope.interface import providedBy + + class ILeft(Interface): + def method(): + pass + + class IRight(ILeft): + pass + + class Left(object): + __implemented__ = ILeft + + def method(self): + raise NotImplementedError() + + class Right(object): + __implemented__ = IRight + + class Ambi(Left, Right): + pass + + ambi = Ambi() + + self.assertTrue(ILeft.implementedBy(Ambi)) + self.assertTrue(IRight.implementedBy(Ambi)) + self.assertTrue(ILeft in implementedBy(Ambi)) + self.assertTrue(IRight in implementedBy(Ambi)) + self.assertTrue(ILeft in providedBy(ambi)) + self.assertTrue(IRight in providedBy(ambi)) + + def test_classImplements_multiple_w_explict_implements(self): + from zope.interface import Interface + from zope.interface import implementedBy + from zope.interface import providedBy + + class ILeft(Interface): + + def method(): + pass + + class IRight(ILeft): + pass + + class IOther(Interface): + pass + + class Left(): + __implemented__ = ILeft + + def method(self): + raise NotImplementedError() + + class Right(object): + __implemented__ = IRight + + class Other(object): + __implemented__ = IOther + + class Mixed(Left, Right): + __implemented__ = Left.__implemented__, Other.__implemented__ + + mixed = Mixed() + + self.assertTrue(ILeft.implementedBy(Mixed)) + self.assertFalse(IRight.implementedBy(Mixed)) + self.assertTrue(IOther.implementedBy(Mixed)) + self.assertTrue(ILeft in implementedBy(Mixed)) + self.assertFalse(IRight in implementedBy(Mixed)) + self.assertTrue(IOther in implementedBy(Mixed)) + self.assertTrue(ILeft in providedBy(mixed)) + self.assertFalse(IRight in providedBy(mixed)) + self.assertTrue(IOther in providedBy(mixed)) + + def testInterfaceExtendsInterface(self): + from zope.interface import Interface + + new = Interface.__class__ + FunInterface = new('FunInterface') + BarInterface = new('BarInterface', [FunInterface]) + BobInterface = new('BobInterface') + BazInterface = new('BazInterface', [BobInterface, BarInterface]) + + self.assertTrue(BazInterface.extends(BobInterface)) + self.assertTrue(BazInterface.extends(BarInterface)) + self.assertTrue(BazInterface.extends(FunInterface)) + self.assertFalse(BobInterface.extends(FunInterface)) + self.assertFalse(BobInterface.extends(BarInterface)) + self.assertTrue(BarInterface.extends(FunInterface)) + self.assertFalse(BarInterface.extends(BazInterface)) + + def test_verifyClass(self): + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface.verify import verifyClass + + + class ICheckMe(Interface): + attr = Attribute(u'My attr') + + def method(): + "A method" + + class CheckMe(object): + __implemented__ = ICheckMe + attr = 'value' + + def method(self): + raise NotImplementedError() + + self.assertTrue(verifyClass(ICheckMe, CheckMe)) + + def test_verifyObject(self): + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface.verify import verifyObject + + + class ICheckMe(Interface): + attr = Attribute(u'My attr') + + def method(): + "A method" + + class CheckMe(object): + __implemented__ = ICheckMe + attr = 'value' + + def method(self): + raise NotImplementedError() + + check_me = CheckMe() + + self.assertTrue(verifyObject(ICheckMe, check_me)) + + def test_interface_object_provides_Interface(self): + from zope.interface import Interface + + class AnInterface(Interface): + pass + + self.assertTrue(Interface.providedBy(AnInterface)) + + def test_names_simple(self): + from zope.interface import Attribute + from zope.interface import Interface + + + class ISimple(Interface): + attr = Attribute(u'My attr') + + def method(): + pass + + self.assertEqual(sorted(ISimple.names()), ['attr', 'method']) + + def test_names_derived(self): + from zope.interface import Attribute + from zope.interface import Interface + + + class IBase(Interface): + attr = Attribute(u'My attr') + + def method(): + pass + + class IDerived(IBase): + attr2 = Attribute(u'My attr2') + + def method(): + pass + + def method2(): + pass + + self.assertEqual(sorted(IDerived.names()), + ['attr2', 'method', 'method2']) + self.assertEqual(sorted(IDerived.names(all=True)), + ['attr', 'attr2', 'method', 'method2']) + + def test_namesAndDescriptions_simple(self): + from zope.interface import Attribute + from zope.interface.interface import Method + from zope.interface import Interface + + + class ISimple(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + name_values = sorted(ISimple.namesAndDescriptions()) + + self.assertEqual(len(name_values), 2) + self.assertEqual(name_values[0][0], 'attr') + self.assertTrue(isinstance(name_values[0][1], Attribute)) + self.assertEqual(name_values[0][1].__name__, 'attr') + self.assertEqual(name_values[0][1].__doc__, 'My attr') + self.assertEqual(name_values[1][0], 'method') + self.assertTrue(isinstance(name_values[1][1], Method)) + self.assertEqual(name_values[1][1].__name__, 'method') + self.assertEqual(name_values[1][1].__doc__, 'My method') + + def test_namesAndDescriptions_derived(self): + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface.interface import Method + + + class IBase(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + class IDerived(IBase): + attr2 = Attribute(u'My attr2') + + def method(): + "My method, overridden" + + def method2(): + "My method2" + + name_values = sorted(IDerived.namesAndDescriptions()) + + self.assertEqual(len(name_values), 3) + self.assertEqual(name_values[0][0], 'attr2') + self.assertTrue(isinstance(name_values[0][1], Attribute)) + self.assertEqual(name_values[0][1].__name__, 'attr2') + self.assertEqual(name_values[0][1].__doc__, 'My attr2') + self.assertEqual(name_values[1][0], 'method') + self.assertTrue(isinstance(name_values[1][1], Method)) + self.assertEqual(name_values[1][1].__name__, 'method') + self.assertEqual(name_values[1][1].__doc__, 'My method, overridden') + self.assertEqual(name_values[2][0], 'method2') + self.assertTrue(isinstance(name_values[2][1], Method)) + self.assertEqual(name_values[2][1].__name__, 'method2') + self.assertEqual(name_values[2][1].__doc__, 'My method2') + + name_values = sorted(IDerived.namesAndDescriptions(all=True)) + + self.assertEqual(len(name_values), 4) + self.assertEqual(name_values[0][0], 'attr') + self.assertTrue(isinstance(name_values[0][1], Attribute)) + self.assertEqual(name_values[0][1].__name__, 'attr') + self.assertEqual(name_values[0][1].__doc__, 'My attr') + self.assertEqual(name_values[1][0], 'attr2') + self.assertTrue(isinstance(name_values[1][1], Attribute)) + self.assertEqual(name_values[1][1].__name__, 'attr2') + self.assertEqual(name_values[1][1].__doc__, 'My attr2') + self.assertEqual(name_values[2][0], 'method') + self.assertTrue(isinstance(name_values[2][1], Method)) + self.assertEqual(name_values[2][1].__name__, 'method') + self.assertEqual(name_values[2][1].__doc__, 'My method, overridden') + self.assertEqual(name_values[3][0], 'method2') + self.assertTrue(isinstance(name_values[3][1], Method)) + self.assertEqual(name_values[3][1].__name__, 'method2') + self.assertEqual(name_values[3][1].__doc__, 'My method2') + + def test_getDescriptionFor_nonesuch_no_default(self): + from zope.interface import Interface + + class IEmpty(Interface): + pass + + self.assertRaises(KeyError, IEmpty.getDescriptionFor, 'nonesuch') + + def test_getDescriptionFor_simple(self): + from zope.interface import Attribute + from zope.interface.interface import Method + from zope.interface import Interface + + + class ISimple(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + a_desc = ISimple.getDescriptionFor('attr') + self.assertTrue(isinstance(a_desc, Attribute)) + self.assertEqual(a_desc.__name__, 'attr') + self.assertEqual(a_desc.__doc__, 'My attr') + + m_desc = ISimple.getDescriptionFor('method') + self.assertTrue(isinstance(m_desc, Method)) + self.assertEqual(m_desc.__name__, 'method') + self.assertEqual(m_desc.__doc__, 'My method') + + def test_getDescriptionFor_derived(self): + from zope.interface import Attribute + from zope.interface.interface import Method + from zope.interface import Interface + + + class IBase(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + class IDerived(IBase): + attr2 = Attribute(u'My attr2') + + def method(): + "My method, overridden" + + def method2(): + "My method2" + + a_desc = IDerived.getDescriptionFor('attr') + self.assertTrue(isinstance(a_desc, Attribute)) + self.assertEqual(a_desc.__name__, 'attr') + self.assertEqual(a_desc.__doc__, 'My attr') + + m_desc = IDerived.getDescriptionFor('method') + self.assertTrue(isinstance(m_desc, Method)) + self.assertEqual(m_desc.__name__, 'method') + self.assertEqual(m_desc.__doc__, 'My method, overridden') + + a2_desc = IDerived.getDescriptionFor('attr2') + self.assertTrue(isinstance(a2_desc, Attribute)) + self.assertEqual(a2_desc.__name__, 'attr2') + self.assertEqual(a2_desc.__doc__, 'My attr2') + + m2_desc = IDerived.getDescriptionFor('method2') + self.assertTrue(isinstance(m2_desc, Method)) + self.assertEqual(m2_desc.__name__, 'method2') + self.assertEqual(m2_desc.__doc__, 'My method2') + + def test___getitem__nonesuch(self): + from zope.interface import Interface + + class IEmpty(Interface): + pass + + self.assertRaises(KeyError, IEmpty.__getitem__, 'nonesuch') + + def test___getitem__simple(self): + from zope.interface import Attribute + from zope.interface.interface import Method + from zope.interface import Interface + + + class ISimple(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + a_desc = ISimple['attr'] + self.assertTrue(isinstance(a_desc, Attribute)) + self.assertEqual(a_desc.__name__, 'attr') + self.assertEqual(a_desc.__doc__, 'My attr') + + m_desc = ISimple['method'] + self.assertTrue(isinstance(m_desc, Method)) + self.assertEqual(m_desc.__name__, 'method') + self.assertEqual(m_desc.__doc__, 'My method') + + def test___getitem___derived(self): + from zope.interface import Attribute + from zope.interface.interface import Method + from zope.interface import Interface + + + class IBase(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + class IDerived(IBase): + attr2 = Attribute(u'My attr2') + + def method(): + "My method, overridden" + + def method2(): + "My method2" + + a_desc = IDerived['attr'] + self.assertTrue(isinstance(a_desc, Attribute)) + self.assertEqual(a_desc.__name__, 'attr') + self.assertEqual(a_desc.__doc__, 'My attr') + + m_desc = IDerived['method'] + self.assertTrue(isinstance(m_desc, Method)) + self.assertEqual(m_desc.__name__, 'method') + self.assertEqual(m_desc.__doc__, 'My method, overridden') + + a2_desc = IDerived['attr2'] + self.assertTrue(isinstance(a2_desc, Attribute)) + self.assertEqual(a2_desc.__name__, 'attr2') + self.assertEqual(a2_desc.__doc__, 'My attr2') + + m2_desc = IDerived['method2'] + self.assertTrue(isinstance(m2_desc, Method)) + self.assertEqual(m2_desc.__name__, 'method2') + self.assertEqual(m2_desc.__doc__, 'My method2') + + def test___contains__nonesuch(self): + from zope.interface import Interface + + class IEmpty(Interface): + pass + + self.assertFalse('nonesuch' in IEmpty) + + def test___contains__simple(self): + from zope.interface import Attribute + from zope.interface import Interface + + + class ISimple(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + self.assertTrue('attr' in ISimple) + self.assertTrue('method' in ISimple) + + def test___contains__derived(self): + from zope.interface import Attribute + from zope.interface import Interface + + + class IBase(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + class IDerived(IBase): + attr2 = Attribute(u'My attr2') + + def method(): + "My method, overridden" + + def method2(): + "My method2" + + self.assertTrue('attr' in IDerived) + self.assertTrue('method' in IDerived) + self.assertTrue('attr2' in IDerived) + self.assertTrue('method2' in IDerived) + + def test___iter__empty(self): + from zope.interface import Interface + + class IEmpty(Interface): + pass + + self.assertEqual(list(IEmpty), []) + + def test___iter__simple(self): + from zope.interface import Attribute + from zope.interface import Interface + + + class ISimple(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + self.assertEqual(sorted(list(ISimple)), ['attr', 'method']) + + def test___iter__derived(self): + from zope.interface import Attribute + from zope.interface import Interface + + + class IBase(Interface): + attr = Attribute(u'My attr') + + def method(): + "My method" + + class IDerived(IBase): + attr2 = Attribute(u'My attr2') + + def method(): + "My method, overridden" + + def method2(): + "My method2" + + self.assertEqual(sorted(list(IDerived)), + ['attr', 'attr2', 'method', 'method2']) + + def test_function_attributes_become_tagged_values(self): + from zope.interface import Interface + + class ITagMe(Interface): + def method(): + pass + method.optional = 1 + + method = ITagMe['method'] + self.assertEqual(method.getTaggedValue('optional'), 1) + + def test___doc___non_element(self): + from zope.interface import Interface + + class IHaveADocString(Interface): + "xxx" + + self.assertEqual(IHaveADocString.__doc__, "xxx") + self.assertEqual(list(IHaveADocString), []) + + def test___doc___as_element(self): + from zope.interface import Attribute + from zope.interface import Interface + + class IHaveADocString(Interface): + "xxx" + __doc__ = Attribute('the doc') + + self.assertEqual(IHaveADocString.__doc__, "") + self.assertEqual(list(IHaveADocString), ['__doc__']) + + def _errorsEqual(self, has_invariant, error_len, error_msgs, iface): + from zope.interface.exceptions import Invalid + self.assertRaises(Invalid, iface.validateInvariants, has_invariant) + e = [] + try: + iface.validateInvariants(has_invariant, e) + self.fail("validateInvariants should always raise") + except Invalid as error: + self.assertEqual(error.args[0], e) + + self.assertEqual(len(e), error_len) + msgs = [error.args[0] for error in e] + msgs.sort() + for msg in msgs: + self.assertEqual(msg, error_msgs.pop(0)) + + def test_invariant_simple(self): + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface import directlyProvides + from zope.interface import invariant + + class IInvariant(Interface): + foo = Attribute('foo') + bar = Attribute('bar; must eval to Boolean True if foo does') + invariant(_ifFooThenBar) + + class HasInvariant(object): + pass + + # set up + has_invariant = HasInvariant() + directlyProvides(has_invariant, IInvariant) + + # the tests + self.assertEqual(IInvariant.getTaggedValue('invariants'), + [_ifFooThenBar]) + self.assertEqual(IInvariant.validateInvariants(has_invariant), None) + has_invariant.bar = 27 + self.assertEqual(IInvariant.validateInvariants(has_invariant), None) + has_invariant.foo = 42 + self.assertEqual(IInvariant.validateInvariants(has_invariant), None) + del has_invariant.bar + self._errorsEqual(has_invariant, 1, ['If Foo, then Bar!'], + IInvariant) + + def test_invariant_nested(self): + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface import directlyProvides + from zope.interface import invariant + + class IInvariant(Interface): + foo = Attribute('foo') + bar = Attribute('bar; must eval to Boolean True if foo does') + invariant(_ifFooThenBar) + + class ISubInvariant(IInvariant): + invariant(_barGreaterThanFoo) + + class HasInvariant(object): + pass + + # nested interfaces with invariants: + self.assertEqual(ISubInvariant.getTaggedValue('invariants'), + [_barGreaterThanFoo]) + has_invariant = HasInvariant() + directlyProvides(has_invariant, ISubInvariant) + has_invariant.foo = 42 + # even though the interface has changed, we should still only have one + # error. + self._errorsEqual(has_invariant, 1, ['If Foo, then Bar!'], + ISubInvariant) + # however, if we set foo to 0 (Boolean False) and bar to a negative + # number then we'll get the new error + has_invariant.foo = 2 + has_invariant.bar = 1 + self._errorsEqual(has_invariant, 1, + ['Please, Boo MUST be greater than Foo!'], + ISubInvariant) + # and if we set foo to a positive number and boo to 0, we'll + # get both errors! + has_invariant.foo = 1 + has_invariant.bar = 0 + self._errorsEqual(has_invariant, 2, + ['If Foo, then Bar!', + 'Please, Boo MUST be greater than Foo!'], + ISubInvariant) + # for a happy ending, we'll make the invariants happy + has_invariant.foo = 1 + has_invariant.bar = 2 + self.assertEqual(IInvariant.validateInvariants(has_invariant), None) + + def test_invariant_mutandis(self): + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface import directlyProvides + from zope.interface import invariant + + class IInvariant(Interface): + foo = Attribute('foo') + bar = Attribute('bar; must eval to Boolean True if foo does') + invariant(_ifFooThenBar) + + class HasInvariant(object): + pass + + # now we'll do two invariants on the same interface, + # just to make sure that a small + # multi-invariant interface is at least minimally tested. + has_invariant = HasInvariant() + directlyProvides(has_invariant, IInvariant) + has_invariant.foo = 42 + + # if you really need to mutate, then this would be the way to do it. + # Probably a bad idea, though. :-) + old_invariants = IInvariant.getTaggedValue('invariants') + invariants = old_invariants[:] + invariants.append(_barGreaterThanFoo) + IInvariant.setTaggedValue('invariants', invariants) + + # even though the interface has changed, we should still only have one + # error. + self._errorsEqual(has_invariant, 1, ['If Foo, then Bar!'], + IInvariant) + # however, if we set foo to 0 (Boolean False) and bar to a negative + # number then we'll get the new error + has_invariant.foo = 2 + has_invariant.bar = 1 + self._errorsEqual(has_invariant, 1, + ['Please, Boo MUST be greater than Foo!'], IInvariant) + # and if we set foo to a positive number and boo to 0, we'll + # get both errors! + has_invariant.foo = 1 + has_invariant.bar = 0 + self._errorsEqual(has_invariant, 2, + ['If Foo, then Bar!', + 'Please, Boo MUST be greater than Foo!'], + IInvariant) + # for another happy ending, we'll make the invariants happy again + has_invariant.foo = 1 + has_invariant.bar = 2 + self.assertEqual(IInvariant.validateInvariants(has_invariant), None) + # clean up + IInvariant.setTaggedValue('invariants', old_invariants) + + def test___doc___element(self): + from zope.interface import Interface + from zope.interface import Attribute + class I(Interface): + "xxx" + + self.assertEqual(I.__doc__, "xxx") + self.assertEqual(list(I), []) + + class I(Interface): + "xxx" + + __doc__ = Attribute('the doc') + + self.assertEqual(I.__doc__, "") + self.assertEqual(list(I), ['__doc__']) + + @_skip_under_py3k + def testIssue228(self): + # Test for http://collector.zope.org/Zope3-dev/228 + # Old style classes don't have a '__class__' attribute + # No old style classes in Python 3, so the test becomes moot. + import sys + + from zope.interface import Interface + + class I(Interface): + "xxx" + + class OldStyle: + __providedBy__ = None + + self.assertRaises(AttributeError, I.providedBy, OldStyle) + + def test_invariant_as_decorator(self): + from zope.interface import Interface + from zope.interface import Attribute + from zope.interface import implementer + from zope.interface import invariant + from zope.interface.exceptions import Invalid + + class IRange(Interface): + min = Attribute("Lower bound") + max = Attribute("Upper bound") + + @invariant + def range_invariant(ob): + if ob.max < ob.min: + raise Invalid('max < min') + + @implementer(IRange) + class Range(object): + + def __init__(self, min, max): + self.min, self.max = min, max + + IRange.validateInvariants(Range(1,2)) + IRange.validateInvariants(Range(1,1)) + try: + IRange.validateInvariants(Range(2,1)) + except Invalid as e: + self.assertEqual(str(e), 'max < min') + + def test_taggedValue(self): + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface import taggedValue + + class ITagged(Interface): + foo = Attribute('foo') + bar = Attribute('bar; must eval to Boolean True if foo does') + taggedValue('qux', 'Spam') + + class HasInvariant(object): + pass + + self.assertEqual(ITagged.getTaggedValue('qux'), 'Spam') + self.assertTrue('qux' in ITagged.getTaggedValueTags()) + + def test_description_cache_management(self): + # See https://bugs.launchpad.net/zope.interface/+bug/185974 + # There was a bug where the cache used by Specification.get() was not + # cleared when the bases were changed. + from zope.interface import Interface + from zope.interface import Attribute + + class I1(Interface): + a = Attribute('a') + + class I2(I1): + pass + + class I3(I2): + pass + + self.assertTrue(I3.get('a') is I1.get('a')) + + I2.__bases__ = (Interface,) + self.assertTrue(I3.get('a') is None) + + def test___call___defers_to___conform___(self): + from zope.interface import Interface + from zope.interface import implementer + + class I(Interface): + pass + + @implementer(I) + class C(object): + def __conform__(self, proto): + return 0 + + self.assertEqual(I(C()), 0) + + def test___call___object_implements(self): + from zope.interface import Interface + from zope.interface import implementer + + class I(Interface): + pass + + @implementer(I) + class C(object): + pass + + c = C() + self.assertTrue(I(c) is c) + + def test___call___miss_wo_alternate(self): + from zope.interface import Interface + + class I(Interface): + pass + + class C(object): + pass + + c = C() + self.assertRaises(TypeError, I, c) + + def test___call___miss_w_alternate(self): + from zope.interface import Interface + + class I(Interface): + pass + + class C(object): + pass + + c = C() + self.assertTrue(I(c, self) is self) + + def test___call___w_adapter_hook(self): + from zope.interface import Interface + from zope.interface.interface import adapter_hooks + old_hooks = adapter_hooks[:] + + def _miss(iface, obj): + pass + + def _hit(iface, obj): + return self + + class I(Interface): + pass + + class C(object): + pass + + c = C() + + old_adapter_hooks = adapter_hooks[:] + adapter_hooks[:] = [_miss, _hit] + try: + self.assertTrue(I(c) is self) + finally: + adapter_hooks[:] = old_adapter_hooks + + +class AttributeTests(ElementTests): + + DEFAULT_NAME = 'TestAttribute' + + def _getTargetClass(self): + from zope.interface.interface import Attribute + return Attribute + + +class MethodTests(AttributeTests): + + DEFAULT_NAME = 'TestMethod' + + def _getTargetClass(self): + from zope.interface.interface import Method + return Method + + def test_optional_as_property(self): + method = self._makeOne() + self.assertEqual(method.optional, {}) + method.optional = {'foo': 'bar'} + self.assertEqual(method.optional, {'foo': 'bar'}) + del method.optional + self.assertEqual(method.optional, {}) + + def test___call___raises_BrokenImplementation(self): + from zope.interface.exceptions import BrokenImplementation + method = self._makeOne() + try: + method() + except BrokenImplementation as e: + self.assertEqual(e.interface, None) + self.assertEqual(e.name, self.DEFAULT_NAME) + else: + self.fail('__call__ should raise BrokenImplementation') + + def test_getSignatureInfo_bare(self): + method = self._makeOne() + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), []) + self.assertEqual(list(info['required']), []) + self.assertEqual(info['optional'], {}) + self.assertEqual(info['varargs'], None) + self.assertEqual(info['kwargs'], None) + + def test_getSignatureString_bare(self): + method = self._makeOne() + self.assertEqual(method.getSignatureString(), '()') + + def test_getSignatureString_w_only_required(self): + method = self._makeOne() + method.positional = method.required = ['foo'] + self.assertEqual(method.getSignatureString(), '(foo)') + + def test_getSignatureString_w_optional(self): + method = self._makeOne() + method.positional = method.required = ['foo'] + method.optional = {'foo': 'bar'} + self.assertEqual(method.getSignatureString(), "(foo='bar')") + + def test_getSignatureString_w_varargs(self): + method = self._makeOne() + method.varargs = 'args' + self.assertEqual(method.getSignatureString(), "(*args)") + + def test_getSignatureString_w_kwargs(self): + method = self._makeOne() + method.kwargs = 'kw' + self.assertEqual(method.getSignatureString(), "(**kw)") + + +class Test_fromFunction(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.interface import fromFunction + return fromFunction(*args, **kw) + + def test_bare(self): + def _func(): + "DOCSTRING" + method = self._callFUT(_func) + self.assertEqual(method.getName(), '_func') + self.assertEqual(method.getDoc(), 'DOCSTRING') + self.assertEqual(method.interface, None) + self.assertEqual(list(method.getTaggedValueTags()), []) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), []) + self.assertEqual(list(info['required']), []) + self.assertEqual(info['optional'], {}) + self.assertEqual(info['varargs'], None) + self.assertEqual(info['kwargs'], None) + + def test_w_interface(self): + from zope.interface.interface import InterfaceClass + class IFoo(InterfaceClass): + pass + def _func(): + "DOCSTRING" + method = self._callFUT(_func, interface=IFoo) + self.assertEqual(method.interface, IFoo) + + def test_w_name(self): + def _func(): + "DOCSTRING" + method = self._callFUT(_func, name='anotherName') + self.assertEqual(method.getName(), 'anotherName') + + def test_w_only_required(self): + def _func(foo): + "DOCSTRING" + method = self._callFUT(_func) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), ['foo']) + self.assertEqual(list(info['required']), ['foo']) + self.assertEqual(info['optional'], {}) + self.assertEqual(info['varargs'], None) + self.assertEqual(info['kwargs'], None) + + def test_w_optional(self): + def _func(foo='bar'): + "DOCSTRING" + method = self._callFUT(_func) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), ['foo']) + self.assertEqual(list(info['required']), []) + self.assertEqual(info['optional'], {'foo': 'bar'}) + self.assertEqual(info['varargs'], None) + self.assertEqual(info['kwargs'], None) + + def test_w_optional_self(self): + # XXX This is a weird case, trying to cover the following code in + # FUT:: + # + # nr = na-len(defaults) + # if nr < 0: + # defaults=defaults[-nr:] + # nr = 0 + def _func(self='bar'): + "DOCSTRING" + method = self._callFUT(_func, imlevel=1) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), []) + self.assertEqual(list(info['required']), []) + self.assertEqual(info['optional'], {}) + self.assertEqual(info['varargs'], None) + self.assertEqual(info['kwargs'], None) + + def test_w_varargs(self): + def _func(*args): + "DOCSTRING" + method = self._callFUT(_func) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), []) + self.assertEqual(list(info['required']), []) + self.assertEqual(info['optional'], {}) + self.assertEqual(info['varargs'], 'args') + self.assertEqual(info['kwargs'], None) + + def test_w_kwargs(self): + def _func(**kw): + "DOCSTRING" + method = self._callFUT(_func) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), []) + self.assertEqual(list(info['required']), []) + self.assertEqual(info['optional'], {}) + self.assertEqual(info['varargs'], None) + self.assertEqual(info['kwargs'], 'kw') + + def test_full_spectrum(self): + def _func(foo, bar='baz', *args, **kw): + "DOCSTRING" + method = self._callFUT(_func) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), ['foo', 'bar']) + self.assertEqual(list(info['required']), ['foo']) + self.assertEqual(info['optional'], {'bar': 'baz'}) + self.assertEqual(info['varargs'], 'args') + self.assertEqual(info['kwargs'], 'kw') + + +class Test_fromMethod(unittest.TestCase): + + def _callFUT(self, *args, **kw): + from zope.interface.interface import fromMethod + return fromMethod(*args, **kw) + + def test_no_args(self): + class Foo(object): + def bar(self): + "DOCSTRING" + method = self._callFUT(Foo.bar) + self.assertEqual(method.getName(), 'bar') + self.assertEqual(method.getDoc(), 'DOCSTRING') + self.assertEqual(method.interface, None) + self.assertEqual(list(method.getTaggedValueTags()), []) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), []) + self.assertEqual(list(info['required']), []) + self.assertEqual(info['optional'], {}) + self.assertEqual(info['varargs'], None) + self.assertEqual(info['kwargs'], None) + + def test_full_spectrum(self): + class Foo(object): + def bar(self, foo, bar='baz', *args, **kw): + "DOCSTRING" + method = self._callFUT(Foo.bar) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), ['foo', 'bar']) + self.assertEqual(list(info['required']), ['foo']) + self.assertEqual(info['optional'], {'bar': 'baz'}) + self.assertEqual(info['varargs'], 'args') + self.assertEqual(info['kwargs'], 'kw') + + def test_w_non_method(self): + def foo(): + "DOCSTRING" + method = self._callFUT(foo) + self.assertEqual(method.getName(), 'foo') + self.assertEqual(method.getDoc(), 'DOCSTRING') + self.assertEqual(method.interface, None) + self.assertEqual(list(method.getTaggedValueTags()), []) + info = method.getSignatureInfo() + self.assertEqual(list(info['positional']), []) + self.assertEqual(list(info['required']), []) + self.assertEqual(info['optional'], {}) + self.assertEqual(info['varargs'], None) + self.assertEqual(info['kwargs'], None) + +class DummyDependent(object): + + def __init__(self): + self._changed = [] + + def changed(self, originally_changed): + self._changed.append(originally_changed) + + +def _barGreaterThanFoo(obj): + from zope.interface.exceptions import Invalid + foo = getattr(obj, 'foo', None) + bar = getattr(obj, 'bar', None) + if foo is not None and isinstance(foo, type(bar)): + # type checking should be handled elsewhere (like, say, + # schema); these invariants should be intra-interface + # constraints. This is a hacky way to do it, maybe, but you + # get the idea + if not bar > foo: + raise Invalid('Please, Boo MUST be greater than Foo!') + +def _ifFooThenBar(obj): + from zope.interface.exceptions import Invalid + if getattr(obj, 'foo', None) and not getattr(obj, 'bar', None): + raise Invalid('If Foo, then Bar!') + + +class _Monkey(object): + # context-manager for replacing module names in the scope of a test. + def __init__(self, module, **kw): + self.module = module + self.to_restore = dict([(key, getattr(module, key)) for key in kw]) + for key, value in kw.items(): + setattr(module, key, value) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + for key, value in self.to_restore.items(): + setattr(self.module, key, value) diff --git a/lib/zope/interface/tests/test_interfaces.py b/lib/zope/interface/tests/test_interfaces.py new file mode 100644 index 0000000..285d857 --- /dev/null +++ b/lib/zope/interface/tests/test_interfaces.py @@ -0,0 +1,95 @@ +import unittest + + +class _ConformsToIObjectEvent(object): + + def _makeOne(self, target=None): + if target is None: + target = object() + return self._getTargetClass()(target) + + def test_class_conforms_to_IObjectEvent(self): + from zope.interface.interfaces import IObjectEvent + from zope.interface.verify import verifyClass + verifyClass(IObjectEvent, self._getTargetClass()) + + def test_instance_conforms_to_IObjectEvent(self): + from zope.interface.interfaces import IObjectEvent + from zope.interface.verify import verifyObject + verifyObject(IObjectEvent, self._makeOne()) + + +class _ConformsToIRegistrationEvent(_ConformsToIObjectEvent): + + def test_class_conforms_to_IRegistrationEvent(self): + from zope.interface.interfaces import IRegistrationEvent + from zope.interface.verify import verifyClass + verifyClass(IRegistrationEvent, self._getTargetClass()) + + def test_instance_conforms_to_IRegistrationEvent(self): + from zope.interface.interfaces import IRegistrationEvent + from zope.interface.verify import verifyObject + verifyObject(IRegistrationEvent, self._makeOne()) + + +class ObjectEventTests(unittest.TestCase, _ConformsToIObjectEvent): + + def _getTargetClass(self): + from zope.interface.interfaces import ObjectEvent + return ObjectEvent + + def test_ctor(self): + target = object() + event = self._makeOne(target) + self.assertTrue(event.object is target) + + +class RegistrationEventTests(unittest.TestCase, + _ConformsToIRegistrationEvent): + + def _getTargetClass(self): + from zope.interface.interfaces import RegistrationEvent + return RegistrationEvent + + def test___repr__(self): + target = object() + event = self._makeOne(target) + r = repr(event) + self.assertEqual(r.splitlines(), + ['RegistrationEvent event:', repr(target)]) + + +class RegisteredTests(unittest.TestCase, + _ConformsToIRegistrationEvent): + + def _getTargetClass(self): + from zope.interface.interfaces import Registered + return Registered + + def test_class_conforms_to_IRegistered(self): + from zope.interface.interfaces import IRegistered + from zope.interface.verify import verifyClass + verifyClass(IRegistered, self._getTargetClass()) + + def test_instance_conforms_to_IRegistered(self): + from zope.interface.interfaces import IRegistered + from zope.interface.verify import verifyObject + verifyObject(IRegistered, self._makeOne()) + + +class UnregisteredTests(unittest.TestCase, + _ConformsToIRegistrationEvent): + + def _getTargetClass(self): + from zope.interface.interfaces import Unregistered + return Unregistered + + def test_class_conforms_to_IUnregistered(self): + from zope.interface.interfaces import IUnregistered + from zope.interface.verify import verifyClass + verifyClass(IUnregistered, self._getTargetClass()) + + def test_instance_conforms_to_IUnregistered(self): + from zope.interface.interfaces import IUnregistered + from zope.interface.verify import verifyObject + verifyObject(IUnregistered, self._makeOne()) diff --git a/lib/zope/interface/tests/test_odd_declarations.py b/lib/zope/interface/tests/test_odd_declarations.py new file mode 100644 index 0000000..46e7675 --- /dev/null +++ b/lib/zope/interface/tests/test_odd_declarations.py @@ -0,0 +1,268 @@ +############################################################################## +# +# Copyright (c) 2003 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Test interface declarations against ExtensionClass-like classes. + +These tests are to make sure we do something sane in the presence of +classic ExtensionClass classes and instances. +""" +import unittest + +from zope.interface.tests import odd +from zope.interface import Interface +from zope.interface import implementer +from zope.interface import directlyProvides +from zope.interface import providedBy +from zope.interface import directlyProvidedBy +from zope.interface import classImplements +from zope.interface import classImplementsOnly +from zope.interface import implementedBy +from zope.interface._compat import _skip_under_py3k + +class I1(Interface): pass +class I2(Interface): pass +class I3(Interface): pass +class I31(I3): pass +class I4(Interface): pass +class I5(Interface): pass + +class Odd(object): + pass +Odd = odd.MetaClass('Odd', Odd.__bases__, {}) + + +class B(Odd): __implemented__ = I2 + + +# TODO: We are going to need more magic to make classProvides work with odd +# classes. This will work in the next iteration. For now, we'll use +# a different mechanism. + +# from zope.interface import classProvides +class A(Odd): + pass +classImplements(A, I1) + +class C(A, B): + pass +classImplements(C, I31) + + +class Test(unittest.TestCase): + + def test_ObjectSpecification(self): + c = C() + directlyProvides(c, I4) + self.assertEqual([i.getName() for i in providedBy(c)], + ['I4', 'I31', 'I1', 'I2'] + ) + self.assertEqual([i.getName() for i in providedBy(c).flattened()], + ['I4', 'I31', 'I3', 'I1', 'I2', 'Interface'] + ) + self.assertTrue(I1 in providedBy(c)) + self.assertFalse(I3 in providedBy(c)) + self.assertTrue(providedBy(c).extends(I3)) + self.assertTrue(providedBy(c).extends(I31)) + self.assertFalse(providedBy(c).extends(I5)) + + class COnly(A, B): + pass + classImplementsOnly(COnly, I31) + + class D(COnly): + pass + classImplements(D, I5) + + classImplements(D, I5) + + c = D() + directlyProvides(c, I4) + self.assertEqual([i.getName() for i in providedBy(c)], + ['I4', 'I5', 'I31']) + self.assertEqual([i.getName() for i in providedBy(c).flattened()], + ['I4', 'I5', 'I31', 'I3', 'Interface']) + self.assertFalse(I1 in providedBy(c)) + self.assertFalse(I3 in providedBy(c)) + self.assertTrue(providedBy(c).extends(I3)) + self.assertFalse(providedBy(c).extends(I1)) + self.assertTrue(providedBy(c).extends(I31)) + self.assertTrue(providedBy(c).extends(I5)) + + class COnly(A, B): __implemented__ = I31 + class D(COnly): + pass + classImplements(D, I5) + + classImplements(D, I5) + c = D() + directlyProvides(c, I4) + self.assertEqual([i.getName() for i in providedBy(c)], + ['I4', 'I5', 'I31']) + self.assertEqual([i.getName() for i in providedBy(c).flattened()], + ['I4', 'I5', 'I31', 'I3', 'Interface']) + self.assertFalse(I1 in providedBy(c)) + self.assertFalse(I3 in providedBy(c)) + self.assertTrue(providedBy(c).extends(I3)) + self.assertFalse(providedBy(c).extends(I1)) + self.assertTrue(providedBy(c).extends(I31)) + self.assertTrue(providedBy(c).extends(I5)) + + def test_classImplements(self): + + @implementer(I3) + class A(Odd): + pass + + @implementer(I4) + class B(Odd): + pass + + class C(A, B): + pass + classImplements(C, I1, I2) + self.assertEqual([i.getName() for i in implementedBy(C)], + ['I1', 'I2', 'I3', 'I4']) + classImplements(C, I5) + self.assertEqual([i.getName() for i in implementedBy(C)], + ['I1', 'I2', 'I5', 'I3', 'I4']) + + def test_classImplementsOnly(self): + @implementer(I3) + class A(Odd): + pass + + @implementer(I4) + class B(Odd): + pass + + class C(A, B): + pass + classImplementsOnly(C, I1, I2) + self.assertEqual([i.__name__ for i in implementedBy(C)], + ['I1', 'I2']) + + + def test_directlyProvides(self): + class IA1(Interface): pass + class IA2(Interface): pass + class IB(Interface): pass + class IC(Interface): pass + class A(Odd): + pass + classImplements(A, IA1, IA2) + + class B(Odd): + pass + classImplements(B, IB) + + class C(A, B): + pass + classImplements(C, IC) + + + ob = C() + directlyProvides(ob, I1, I2) + self.assertTrue(I1 in providedBy(ob)) + self.assertTrue(I2 in providedBy(ob)) + self.assertTrue(IA1 in providedBy(ob)) + self.assertTrue(IA2 in providedBy(ob)) + self.assertTrue(IB in providedBy(ob)) + self.assertTrue(IC in providedBy(ob)) + + directlyProvides(ob, directlyProvidedBy(ob)-I2) + self.assertTrue(I1 in providedBy(ob)) + self.assertFalse(I2 in providedBy(ob)) + self.assertFalse(I2 in providedBy(ob)) + directlyProvides(ob, directlyProvidedBy(ob), I2) + self.assertTrue(I2 in providedBy(ob)) + + @_skip_under_py3k + def test_directlyProvides_fails_for_odd_class(self): + self.assertRaises(TypeError, directlyProvides, C, I5) + + # see above + #def TODO_test_classProvides_fails_for_odd_class(self): + # try: + # class A(Odd): + # classProvides(I1) + # except TypeError: + # pass # Sucess + # self.assert_(False, + # "Shouldn't be able to use directlyProvides on odd class." + # ) + + def test_implementedBy(self): + class I2(I1): pass + + class C1(Odd): + pass + classImplements(C1, I2) + + class C2(C1): + pass + classImplements(C2, I3) + + self.assertEqual([i.getName() for i in implementedBy(C2)], + ['I3', 'I2']) + + def test_odd_metaclass_that_doesnt_subclass_type(self): + # This was originally a doctest in odd.py. + # It verifies that the metaclass the rest of these tests use + # works as expected. + + # This is used for testing support for ExtensionClass in new interfaces. + + class A(object): + a = 1 + + A = odd.MetaClass('A', A.__bases__, A.__dict__) + + class B(object): + b = 1 + + B = odd.MetaClass('B', B.__bases__, B.__dict__) + + class C(A, B): + pass + + self.assertEqual(C.__bases__, (A, B)) + + a = A() + aa = A() + self.assertEqual(a.a, 1) + self.assertEqual(aa.a, 1) + + aa.a = 2 + self.assertEqual(a.a, 1) + self.assertEqual(aa.a, 2) + + c = C() + self.assertEqual(c.a, 1) + self.assertEqual(c.b, 1) + + c.b = 2 + self.assertEqual(c.b, 2) + + C.c = 1 + self.assertEqual(c.c, 1) + c.c + + try: + from types import ClassType + except ImportError: + pass + else: + # This test only makes sense under Python 2.x + assert not isinstance(C, (type, ClassType)) + + self.assertIs(C.__class__.__class__, C.__class__) diff --git a/lib/zope/interface/tests/test_registry.py b/lib/zope/interface/tests/test_registry.py new file mode 100644 index 0000000..e5a8eb0 --- /dev/null +++ b/lib/zope/interface/tests/test_registry.py @@ -0,0 +1,2788 @@ +############################################################################## +# +# Copyright (c) 2001, 2002, 2009 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Component Registry Tests""" +# pylint:disable=protected-access +import unittest + +from zope.interface import Interface +from zope.interface.adapter import VerifyingAdapterRegistry + +from zope.interface.registry import Components + +class ComponentsTests(unittest.TestCase): + + def _getTargetClass(self): + return Components + + def _makeOne(self, name='test', *args, **kw): + return self._getTargetClass()(name, *args, **kw) + + def _wrapEvents(self): + from zope.interface import registry + _events = [] + def _notify(*args, **kw): + _events.append((args, kw)) + _monkey = _Monkey(registry, notify=_notify) + return _monkey, _events + + def test_ctor_no_bases(self): + from zope.interface.adapter import AdapterRegistry + comp = self._makeOne('testing') + self.assertEqual(comp.__name__, 'testing') + self.assertEqual(comp.__bases__, ()) + self.assertTrue(isinstance(comp.adapters, AdapterRegistry)) + self.assertTrue(isinstance(comp.utilities, AdapterRegistry)) + self.assertEqual(comp.adapters.__bases__, ()) + self.assertEqual(comp.utilities.__bases__, ()) + self.assertEqual(comp._utility_registrations, {}) + self.assertEqual(comp._adapter_registrations, {}) + self.assertEqual(comp._subscription_registrations, []) + self.assertEqual(comp._handler_registrations, []) + + def test_ctor_w_base(self): + base = self._makeOne('base') + comp = self._makeOne('testing', (base,)) + self.assertEqual(comp.__name__, 'testing') + self.assertEqual(comp.__bases__, (base,)) + self.assertEqual(comp.adapters.__bases__, (base.adapters,)) + self.assertEqual(comp.utilities.__bases__, (base.utilities,)) + + def test___repr__(self): + comp = self._makeOne('testing') + self.assertEqual(repr(comp), '<Components testing>') + + # test _init_registries / _init_registrations via only caller, __init__. + + def test_assign_to___bases__(self): + base1 = self._makeOne('base1') + base2 = self._makeOne('base2') + comp = self._makeOne() + comp.__bases__ = (base1, base2) + self.assertEqual(comp.__bases__, (base1, base2)) + self.assertEqual(comp.adapters.__bases__, + (base1.adapters, base2.adapters)) + self.assertEqual(comp.utilities.__bases__, + (base1.utilities, base2.utilities)) + + def test_registerUtility_with_component_name(self): + from zope.interface.declarations import named, InterfaceClass + + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + + @named(u'foo') + class Foo(object): + pass + foo = Foo() + _info = u'info' + + comp = self._makeOne() + comp.registerUtility(foo, ifoo, info=_info) + self.assertEqual( + comp._utility_registrations[ifoo, u'foo'], + (foo, _info, None)) + + def test_registerUtility_both_factory_and_component(self): + def _factory(): + raise NotImplementedError() + _to_reg = object() + comp = self._makeOne() + self.assertRaises(TypeError, comp.registerUtility, + component=_to_reg, factory=_factory) + + def test_registerUtility_w_component(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Registered + from zope.interface.registry import UtilityRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + _to_reg = object() + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerUtility(_to_reg, ifoo, _name, _info) + self.assertTrue(comp.utilities._adapters[0][ifoo][_name] is _to_reg) + self.assertEqual(comp._utility_registrations[ifoo, _name], + (_to_reg, _info, None)) + self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, UtilityRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.component is _to_reg) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is None) + + def test_registerUtility_w_factory(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Registered + from zope.interface.registry import UtilityRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + _to_reg = object() + def _factory(): + return _to_reg + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerUtility(None, ifoo, _name, _info, factory=_factory) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, UtilityRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.component is _to_reg) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _factory) + + def test_registerUtility_no_provided_available(self): + class Foo(object): + pass + + _info = u'info' + _name = u'name' + _to_reg = Foo() + comp = self._makeOne() + self.assertRaises(TypeError, + comp.registerUtility, _to_reg, None, _name, _info) + + def test_registerUtility_wo_provided(self): + from zope.interface.declarations import directlyProvides + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Registered + from zope.interface.registry import UtilityRegistration + + class IFoo(InterfaceClass): + pass + class Foo(object): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + _to_reg = Foo() + directlyProvides(_to_reg, ifoo) + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerUtility(_to_reg, None, _name, _info) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, UtilityRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.component is _to_reg) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is None) + + def test_registerUtility_duplicates_existing_reg(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerUtility(_to_reg, ifoo, _name, _info) + self.assertEqual(len(_events), 0) + + def test_registerUtility_w_different_info(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info1 = u'info1' + _info2 = u'info2' + _name = u'name' + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name, _info1) + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerUtility(_to_reg, ifoo, _name, _info2) + self.assertEqual(len(_events), 2) # unreg, reg + self.assertEqual(comp._utility_registrations[(ifoo, _name)], + (_to_reg, _info2, None)) # replaced + self.assertEqual(comp.utilities._subscribers[0][ifoo][u''], + (_to_reg,)) + + def test_registerUtility_w_different_names_same_component(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + _other_reg = object() + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_other_reg, ifoo, _name1, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerUtility(_to_reg, ifoo, _name2, _info) + self.assertEqual(len(_events), 1) # reg + self.assertEqual(comp._utility_registrations[(ifoo, _name1)], + (_other_reg, _info, None)) + self.assertEqual(comp._utility_registrations[(ifoo, _name2)], + (_to_reg, _info, None)) + self.assertEqual(comp.utilities._subscribers[0][ifoo][u''], + (_other_reg, _to_reg,)) + + def test_registerUtility_replaces_existing_reg(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.interfaces import Registered + from zope.interface.registry import UtilityRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + _before, _after = object(), object() + comp = self._makeOne() + comp.registerUtility(_before, ifoo, _name, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerUtility(_after, ifoo, _name, _info) + self.assertEqual(len(_events), 2) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, UtilityRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.component is _before) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is None) + args, kw = _events[1] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, UtilityRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.component is _after) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is None) + + def test_registerUtility_w_existing_subscr(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name1, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerUtility(_to_reg, ifoo, _name2, _info) + self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) + + def test_registerUtility_wo_event(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + _to_reg = object() + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerUtility(_to_reg, ifoo, _name, _info, False) + self.assertEqual(len(_events), 0) + + def test_registerUtility_changes_object_identity_after(self): + # If a subclass changes the identity of the _utility_registrations, + # the cache is updated and the right thing still happens. + class CompThatChangesAfter1Reg(self._getTargetClass()): + reg_count = 0 + def registerUtility(self, *args): + self.reg_count += 1 + super(CompThatChangesAfter1Reg, self).registerUtility(*args) + if self.reg_count == 1: + self._utility_registrations = dict(self._utility_registrations) + + comp = CompThatChangesAfter1Reg() + comp.registerUtility(object(), Interface) + + self.assertEqual(len(list(comp.registeredUtilities())), 1) + + class IFoo(Interface): + pass + + comp.registerUtility(object(), IFoo) + self.assertEqual(len(list(comp.registeredUtilities())), 2) + + def test_registerUtility_changes_object_identity_before(self): + # If a subclass changes the identity of the _utility_registrations, + # the cache is updated and the right thing still happens. + class CompThatChangesAfter2Reg(self._getTargetClass()): + reg_count = 0 + def registerUtility(self, *args): + self.reg_count += 1 + if self.reg_count == 2: + self._utility_registrations = dict(self._utility_registrations) + + super(CompThatChangesAfter2Reg, self).registerUtility(*args) + + comp = CompThatChangesAfter2Reg() + comp.registerUtility(object(), Interface) + + self.assertEqual(len(list(comp.registeredUtilities())), 1) + + class IFoo(Interface): + pass + + comp.registerUtility(object(), IFoo) + self.assertEqual(len(list(comp.registeredUtilities())), 2) + + + class IBar(Interface): + pass + + comp.registerUtility(object(), IBar) + self.assertEqual(len(list(comp.registeredUtilities())), 3) + + + def test_unregisterUtility_neither_factory_nor_component_nor_provided(self): + comp = self._makeOne() + self.assertRaises(TypeError, comp.unregisterUtility, + component=None, provided=None, factory=None) + + def test_unregisterUtility_both_factory_and_component(self): + def _factory(): + raise NotImplementedError() + _to_reg = object() + comp = self._makeOne() + self.assertRaises(TypeError, comp.unregisterUtility, + component=_to_reg, factory=_factory) + + def test_unregisterUtility_w_component_miss(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _name = u'name' + _to_reg = object() + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterUtility(_to_reg, ifoo, _name) + self.assertFalse(unreg) + self.assertFalse(_events) + + def test_unregisterUtility_w_component(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import UtilityRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _name = u'name' + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterUtility(_to_reg, ifoo, _name) + self.assertTrue(unreg) + self.assertFalse(comp.utilities._adapters) # all erased + self.assertFalse((ifoo, _name) in comp._utility_registrations) + self.assertFalse(comp.utilities._subscribers) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, UtilityRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.component is _to_reg) + self.assertTrue(event.object.factory is None) + + def test_unregisterUtility_w_factory(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import UtilityRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + _to_reg = object() + def _factory(): + return _to_reg + comp = self._makeOne() + comp.registerUtility(None, ifoo, _name, _info, factory=_factory) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterUtility(None, ifoo, _name, factory=_factory) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, UtilityRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.component is _to_reg) + self.assertTrue(event.object.factory is _factory) + + def test_unregisterUtility_wo_explicit_provided(self): + from zope.interface.declarations import directlyProvides + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import UtilityRegistration + + class IFoo(InterfaceClass): + pass + class Foo(object): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + _to_reg = Foo() + directlyProvides(_to_reg, ifoo) + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterUtility(_to_reg, None, _name) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, UtilityRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.component is _to_reg) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is None) + + def test_unregisterUtility_wo_component_or_factory(self): + from zope.interface.declarations import directlyProvides + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import UtilityRegistration + + class IFoo(InterfaceClass): + pass + class Foo(object): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + _to_reg = Foo() + directlyProvides(_to_reg, ifoo) + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + # Just pass the interface / name + unreg = comp.unregisterUtility(provided=ifoo, name=_name) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, UtilityRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.component is _to_reg) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is None) + + def test_unregisterUtility_w_existing_subscr(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name1, _info) + comp.registerUtility(_to_reg, ifoo, _name2, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + comp.unregisterUtility(_to_reg, ifoo, _name2) + self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) + + def test_unregisterUtility_w_existing_subscr_non_hashable(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + _to_reg = dict() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name1, _info) + comp.registerUtility(_to_reg, ifoo, _name2, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + comp.unregisterUtility(_to_reg, ifoo, _name2) + self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) + + def test_unregisterUtility_w_existing_subscr_non_hashable_fresh_cache(self): + # We correctly populate the cache of registrations if it has gone away + # (for example, the Components was unpickled) + from zope.interface.declarations import InterfaceClass + from zope.interface.registry import _UtilityRegistrations + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + _to_reg = dict() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name1, _info) + comp.registerUtility(_to_reg, ifoo, _name2, _info) + + _monkey, _events = self._wrapEvents() + with _monkey: + comp.unregisterUtility(_to_reg, ifoo, _name2) + self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) + + def test_unregisterUtility_w_existing_subscr_non_hashable_reinitted(self): + # We correctly populate the cache of registrations if the base objects change + # out from under us + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + _to_reg = dict() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name1, _info) + comp.registerUtility(_to_reg, ifoo, _name2, _info) + + # zope.component.testing does this + comp.__init__('base') + + comp.registerUtility(_to_reg, ifoo, _name2, _info) + + _monkey, _events = self._wrapEvents() + with _monkey: + # Nothing to do, but we don't break either + comp.unregisterUtility(_to_reg, ifoo, _name2) + self.assertEqual(0, len(comp.utilities._subscribers)) + + def test_unregisterUtility_w_existing_subscr_other_component(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + _other_reg = object() + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_other_reg, ifoo, _name1, _info) + comp.registerUtility(_to_reg, ifoo, _name2, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + comp.unregisterUtility(_to_reg, ifoo, _name2) + self.assertEqual(comp.utilities._subscribers[0][ifoo][''], + (_other_reg,)) + + def test_unregisterUtility_w_existing_subscr_other_component_mixed_hash(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + # First register something hashable + _other_reg = object() + # Then it transfers to something unhashable + _to_reg = dict() + comp = self._makeOne() + comp.registerUtility(_other_reg, ifoo, _name1, _info) + comp.registerUtility(_to_reg, ifoo, _name2, _info) + _monkey, _events = self._wrapEvents() + with _monkey: + comp.unregisterUtility(_to_reg, ifoo, _name2) + self.assertEqual(comp.utilities._subscribers[0][ifoo][''], + (_other_reg,)) + + def test_registeredUtilities_empty(self): + comp = self._makeOne() + self.assertEqual(list(comp.registeredUtilities()), []) + + def test_registeredUtilities_notempty(self): + from zope.interface.declarations import InterfaceClass + + from zope.interface.registry import UtilityRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, _name1, _info) + comp.registerUtility(_to_reg, ifoo, _name2, _info) + reg = sorted(comp.registeredUtilities(), key=lambda r: r.name) + self.assertEqual(len(reg), 2) + self.assertTrue(isinstance(reg[0], UtilityRegistration)) + self.assertTrue(reg[0].registry is comp) + self.assertTrue(reg[0].provided is ifoo) + self.assertTrue(reg[0].name is _name1) + self.assertTrue(reg[0].component is _to_reg) + self.assertTrue(reg[0].info is _info) + self.assertTrue(reg[0].factory is None) + self.assertTrue(isinstance(reg[1], UtilityRegistration)) + self.assertTrue(reg[1].registry is comp) + self.assertTrue(reg[1].provided is ifoo) + self.assertTrue(reg[1].name is _name2) + self.assertTrue(reg[1].component is _to_reg) + self.assertTrue(reg[1].info is _info) + self.assertTrue(reg[1].factory is None) + + def test_queryUtility_miss_no_default(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + self.assertTrue(comp.queryUtility(ifoo) is None) + + def test_queryUtility_miss_w_default(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + _default = object() + self.assertTrue(comp.queryUtility(ifoo, default=_default) is _default) + + def test_queryUtility_hit(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo) + self.assertTrue(comp.queryUtility(ifoo) is _to_reg) + + def test_getUtility_miss(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import ComponentLookupError + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + self.assertRaises(ComponentLookupError, comp.getUtility, ifoo) + + def test_getUtility_hit(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo) + self.assertTrue(comp.getUtility(ifoo) is _to_reg) + + def test_getUtilitiesFor_miss(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + self.assertEqual(list(comp.getUtilitiesFor(ifoo)), []) + + def test_getUtilitiesFor_hit(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _name1 = u'name1' + _name2 = u'name2' + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, name=_name1) + comp.registerUtility(_to_reg, ifoo, name=_name2) + self.assertEqual(sorted(comp.getUtilitiesFor(ifoo)), + [(_name1, _to_reg), (_name2, _to_reg)]) + + def test_getAllUtilitiesRegisteredFor_miss(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + self.assertEqual(list(comp.getAllUtilitiesRegisteredFor(ifoo)), []) + + def test_getAllUtilitiesRegisteredFor_hit(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _name1 = u'name1' + _name2 = u'name2' + _to_reg = object() + comp = self._makeOne() + comp.registerUtility(_to_reg, ifoo, name=_name1) + comp.registerUtility(_to_reg, ifoo, name=_name2) + self.assertEqual(list(comp.getAllUtilitiesRegisteredFor(ifoo)), + [_to_reg]) + + def test_registerAdapter_with_component_name(self): + from zope.interface.declarations import named, InterfaceClass + + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + + @named(u'foo') + class Foo(object): + pass + _info = u'info' + + comp = self._makeOne() + comp.registerAdapter(Foo, (ibar,), ifoo, info=_info) + + self.assertEqual( + comp._adapter_registrations[(ibar,), ifoo, u'foo'], + (Foo, _info)) + + def test_registerAdapter_w_explicit_provided_and_required(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Registered + from zope.interface.registry import AdapterRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _info = u'info' + _name = u'name' + + def _factory(context): + raise NotImplementedError() + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerAdapter(_factory, (ibar,), ifoo, _name, _info) + self.assertTrue(comp.adapters._adapters[1][ibar][ifoo][_name] + is _factory) + self.assertEqual(comp._adapter_registrations[(ibar,), ifoo, _name], + (_factory, _info)) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, AdapterRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _factory) + + def test_registerAdapter_no_provided_available(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + + ibar = IFoo('IBar') + _info = u'info' + _name = u'name' + _to_reg = object() + class _Factory(object): + pass + + comp = self._makeOne() + self.assertRaises(TypeError, comp.registerAdapter, _Factory, (ibar,), + name=_name, info=_info) + + def test_registerAdapter_wo_explicit_provided(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + from zope.interface.interfaces import Registered + from zope.interface.registry import AdapterRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _info = u'info' + _name = u'name' + _to_reg = object() + + @implementer(ifoo) + class _Factory(object): + pass + + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerAdapter(_Factory, (ibar,), name=_name, info=_info) + self.assertTrue(comp.adapters._adapters[1][ibar][ifoo][_name] + is _Factory) + self.assertEqual(comp._adapter_registrations[(ibar,), ifoo, _name], + (_Factory, _info)) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, AdapterRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _Factory) + + def test_registerAdapter_no_required_available(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + + _info = u'info' + _name = u'name' + class _Factory(object): + pass + + comp = self._makeOne() + self.assertRaises(TypeError, comp.registerAdapter, _Factory, + provided=ifoo, name=_name, info=_info) + + def test_registerAdapter_w_invalid_required(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _info = u'info' + _name = u'name' + class _Factory(object): + pass + comp = self._makeOne() + self.assertRaises(TypeError, comp.registerAdapter, _Factory, + ibar, provided=ifoo, name=_name, info=_info) + + def test_registerAdapter_w_required_containing_None(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interface import Interface + from zope.interface.interfaces import Registered + from zope.interface.registry import AdapterRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _name = u'name' + class _Factory(object): + pass + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerAdapter(_Factory, [None], provided=ifoo, + name=_name, info=_info) + self.assertTrue(comp.adapters._adapters[1][Interface][ifoo][_name] + is _Factory) + self.assertEqual(comp._adapter_registrations[(Interface,), ifoo, _name], + (_Factory, _info)) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, AdapterRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (Interface,)) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _Factory) + + def test_registerAdapter_w_required_containing_class(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + from zope.interface.declarations import implementedBy + from zope.interface.interfaces import Registered + from zope.interface.registry import AdapterRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _info = u'info' + _name = u'name' + class _Factory(object): + pass + + @implementer(ibar) + class _Context(object): + pass + _ctx_impl = implementedBy(_Context) + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerAdapter(_Factory, [_Context], provided=ifoo, + name=_name, info=_info) + self.assertTrue(comp.adapters._adapters[1][_ctx_impl][ifoo][_name] + is _Factory) + self.assertEqual(comp._adapter_registrations[(_ctx_impl,), ifoo, _name], + (_Factory, _info)) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, AdapterRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (_ctx_impl,)) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _Factory) + + def test_registerAdapter_w_required_containing_junk(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + + _info = u'info' + _name = u'name' + class _Factory(object): + pass + comp = self._makeOne() + self.assertRaises(TypeError, comp.registerAdapter, _Factory, [object()], + provided=ifoo, name=_name, info=_info) + + def test_registerAdapter_wo_explicit_required(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Registered + from zope.interface.registry import AdapterRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _info = u'info' + _name = u'name' + class _Factory(object): + __component_adapts__ = (ibar,) + + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerAdapter(_Factory, provided=ifoo, name=_name, + info=_info) + self.assertTrue(comp.adapters._adapters[1][ibar][ifoo][_name] + is _Factory) + self.assertEqual(comp._adapter_registrations[(ibar,), ifoo, _name], + (_Factory, _info)) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, AdapterRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertTrue(event.object.name is _name) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _Factory) + + def test_registerAdapter_wo_event(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _info = u'info' + _name = u'name' + + def _factory(context): + raise NotImplementedError() + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerAdapter(_factory, (ibar,), ifoo, _name, _info, + event=False) + self.assertEqual(len(_events), 0) + + def test_unregisterAdapter_neither_factory_nor_provided(self): + comp = self._makeOne() + self.assertRaises(TypeError, comp.unregisterAdapter, + factory=None, provided=None) + + def test_unregisterAdapter_neither_factory_nor_required(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + self.assertRaises(TypeError, comp.unregisterAdapter, + factory=None, provided=ifoo, required=None) + + def test_unregisterAdapter_miss(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + pass + + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterAdapter(_Factory, (ibar,), ifoo) + self.assertFalse(unreg) + + def test_unregisterAdapter_hit_w_explicit_provided_and_required(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import AdapterRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + pass + + comp = self._makeOne() + comp.registerAdapter(_Factory, (ibar,), ifoo) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterAdapter(_Factory, (ibar,), ifoo) + self.assertTrue(unreg) + self.assertFalse(comp.adapters._adapters) + self.assertFalse(comp._adapter_registrations) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, AdapterRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, '') + self.assertEqual(event.object.info, '') + self.assertTrue(event.object.factory is _Factory) + + def test_unregisterAdapter_wo_explicit_provided(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + from zope.interface.interfaces import Unregistered + from zope.interface.registry import AdapterRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + @implementer(ifoo) + class _Factory(object): + pass + + comp = self._makeOne() + comp.registerAdapter(_Factory, (ibar,), ifoo) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterAdapter(_Factory, (ibar,)) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, AdapterRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, '') + self.assertEqual(event.object.info, '') + self.assertTrue(event.object.factory is _Factory) + + def test_unregisterAdapter_wo_explicit_required(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import AdapterRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + __component_adapts__ = (ibar,) + + comp = self._makeOne() + comp.registerAdapter(_Factory, (ibar,), ifoo) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterAdapter(_Factory, provided=ifoo) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, AdapterRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, '') + self.assertEqual(event.object.info, '') + self.assertTrue(event.object.factory is _Factory) + + def test_registeredAdapters_empty(self): + comp = self._makeOne() + self.assertEqual(list(comp.registeredAdapters()), []) + + def test_registeredAdapters_notempty(self): + from zope.interface.declarations import InterfaceClass + + from zope.interface.registry import AdapterRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IFoo') + _info = u'info' + _name1 = u'name1' + _name2 = u'name2' + class _Factory(object): + pass + + comp = self._makeOne() + comp.registerAdapter(_Factory, (ibar,), ifoo, _name1, _info) + comp.registerAdapter(_Factory, (ibar,), ifoo, _name2, _info) + reg = sorted(comp.registeredAdapters(), key=lambda r: r.name) + self.assertEqual(len(reg), 2) + self.assertTrue(isinstance(reg[0], AdapterRegistration)) + self.assertTrue(reg[0].registry is comp) + self.assertTrue(reg[0].provided is ifoo) + self.assertEqual(reg[0].required, (ibar,)) + self.assertTrue(reg[0].name is _name1) + self.assertTrue(reg[0].info is _info) + self.assertTrue(reg[0].factory is _Factory) + self.assertTrue(isinstance(reg[1], AdapterRegistration)) + self.assertTrue(reg[1].registry is comp) + self.assertTrue(reg[1].provided is ifoo) + self.assertEqual(reg[1].required, (ibar,)) + self.assertTrue(reg[1].name is _name2) + self.assertTrue(reg[1].info is _info) + self.assertTrue(reg[1].factory is _Factory) + + def test_queryAdapter_miss_no_default(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + _context = object() + self.assertTrue(comp.queryAdapter(_context, ifoo) is None) + + def test_queryAdapter_miss_w_default(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + _context = object() + _default = object() + self.assertTrue( + comp.queryAdapter(_context, ifoo, default=_default) is _default) + + def test_queryAdapter_hit(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + def __init__(self, context): + self.context = context + @implementer(ibar) + class _Context(object): + pass + _context = _Context() + comp = self._makeOne() + comp.registerAdapter(_Factory, (ibar,), ifoo) + adapter = comp.queryAdapter(_context, ifoo) + self.assertTrue(isinstance(adapter, _Factory)) + self.assertTrue(adapter.context is _context) + + def test_getAdapter_miss(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + from zope.interface.interfaces import ComponentLookupError + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + @implementer(ibar) + class _Context(object): + pass + _context = _Context() + comp = self._makeOne() + self.assertRaises(ComponentLookupError, + comp.getAdapter, _context, ifoo) + + def test_getAdapter_hit(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + def __init__(self, context): + self.context = context + @implementer(ibar) + class _Context(object): + pass + _context = _Context() + comp = self._makeOne() + comp.registerAdapter(_Factory, (ibar,), ifoo) + adapter = comp.getAdapter(_context, ifoo) + self.assertTrue(isinstance(adapter, _Factory)) + self.assertTrue(adapter.context is _context) + + def test_queryMultiAdapter_miss(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + ibaz = IFoo('IBaz') + @implementer(ibar) + class _Context1(object): + pass + @implementer(ibaz) + class _Context2(object): + pass + _context1 = _Context1() + _context2 = _Context2() + comp = self._makeOne() + self.assertEqual(comp.queryMultiAdapter((_context1, _context2), ifoo), + None) + + def test_queryMultiAdapter_miss_w_default(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + ibaz = IFoo('IBaz') + @implementer(ibar) + class _Context1(object): + pass + @implementer(ibaz) + class _Context2(object): + pass + _context1 = _Context1() + _context2 = _Context2() + _default = object() + comp = self._makeOne() + self.assertTrue( + comp.queryMultiAdapter((_context1, _context2), ifoo, + default=_default) is _default) + + def test_queryMultiAdapter_hit(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + ibaz = IFoo('IBaz') + @implementer(ibar) + class _Context1(object): + pass + @implementer(ibaz) + class _Context2(object): + pass + _context1 = _Context1() + _context2 = _Context2() + class _Factory(object): + def __init__(self, context1, context2): + self.context = context1, context2 + comp = self._makeOne() + comp.registerAdapter(_Factory, (ibar, ibaz), ifoo) + adapter = comp.queryMultiAdapter((_context1, _context2), ifoo) + self.assertTrue(isinstance(adapter, _Factory)) + self.assertEqual(adapter.context, (_context1, _context2)) + + def test_getMultiAdapter_miss(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + from zope.interface.interfaces import ComponentLookupError + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + ibaz = IFoo('IBaz') + @implementer(ibar) + class _Context1(object): + pass + @implementer(ibaz) + class _Context2(object): + pass + _context1 = _Context1() + _context2 = _Context2() + comp = self._makeOne() + self.assertRaises(ComponentLookupError, + comp.getMultiAdapter, (_context1, _context2), ifoo) + + def test_getMultiAdapter_hit(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + ibaz = IFoo('IBaz') + @implementer(ibar) + class _Context1(object): + pass + @implementer(ibaz) + class _Context2(object): + pass + _context1 = _Context1() + _context2 = _Context2() + class _Factory(object): + def __init__(self, context1, context2): + self.context = context1, context2 + comp = self._makeOne() + comp.registerAdapter(_Factory, (ibar, ibaz), ifoo) + adapter = comp.getMultiAdapter((_context1, _context2), ifoo) + self.assertTrue(isinstance(adapter, _Factory)) + self.assertEqual(adapter.context, (_context1, _context2)) + + def test_getAdapters_empty(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + ibaz = IFoo('IBaz') + @implementer(ibar) + class _Context1(object): + pass + @implementer(ibaz) + class _Context2(object): + pass + _context1 = _Context1() + _context2 = _Context2() + comp = self._makeOne() + self.assertEqual( + list(comp.getAdapters((_context1, _context2), ifoo)), []) + + def test_getAdapters_factory_returns_None(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + ibaz = IFoo('IBaz') + @implementer(ibar) + class _Context1(object): + pass + @implementer(ibaz) + class _Context2(object): + pass + _context1 = _Context1() + _context2 = _Context2() + comp = self._makeOne() + _called_with = [] + def _side_effect_only(context1, context2): + _called_with.append((context1, context2)) + return None + comp.registerAdapter(_side_effect_only, (ibar, ibaz), ifoo) + self.assertEqual( + list(comp.getAdapters((_context1, _context2), ifoo)), []) + self.assertEqual(_called_with, [(_context1, _context2)]) + + def test_getAdapters_non_empty(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + ibaz = IFoo('IBaz') + @implementer(ibar) + class _Context1(object): + pass + @implementer(ibaz) + class _Context2(object): + pass + _context1 = _Context1() + _context2 = _Context2() + class _Factory1(object): + def __init__(self, context1, context2): + self.context = context1, context2 + class _Factory2(object): + def __init__(self, context1, context2): + self.context = context1, context2 + _name1 = u'name1' + _name2 = u'name2' + comp = self._makeOne() + comp.registerAdapter(_Factory1, (ibar, ibaz), ifoo, name=_name1) + comp.registerAdapter(_Factory2, (ibar, ibaz), ifoo, name=_name2) + found = sorted(comp.getAdapters((_context1, _context2), ifoo)) + self.assertEqual(len(found), 2) + self.assertEqual(found[0][0], _name1) + self.assertTrue(isinstance(found[0][1], _Factory1)) + self.assertEqual(found[1][0], _name2) + self.assertTrue(isinstance(found[1][1], _Factory2)) + + def test_registerSubscriptionAdapter_w_nonblank_name(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _name = u'name' + _info = u'info' + def _factory(context): + raise NotImplementedError() + + comp = self._makeOne() + self.assertRaises(TypeError, comp.registerSubscriptionAdapter, + _factory, (ibar,), ifoo, _name, _info) + + def test_registerSubscriptionAdapter_w_explicit_provided_and_required(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Registered + from zope.interface.registry import SubscriptionRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _blank = u'' + _info = u'info' + def _factory(context): + raise NotImplementedError() + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerSubscriptionAdapter(_factory, (ibar,), ifoo, + info=_info) + reg = comp.adapters._subscribers[1][ibar][ifoo][_blank] + self.assertEqual(len(reg), 1) + self.assertTrue(reg[0] is _factory) + self.assertEqual(comp._subscription_registrations, + [((ibar,), ifoo, _blank, _factory, _info)]) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, SubscriptionRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, _blank) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _factory) + + def test_registerSubscriptionAdapter_wo_explicit_provided(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + from zope.interface.interfaces import Registered + from zope.interface.registry import SubscriptionRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _info = u'info' + _blank = u'' + + @implementer(ifoo) + class _Factory(object): + pass + + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerSubscriptionAdapter(_Factory, (ibar,), info=_info) + reg = comp.adapters._subscribers[1][ibar][ifoo][_blank] + self.assertEqual(len(reg), 1) + self.assertTrue(reg[0] is _Factory) + self.assertEqual(comp._subscription_registrations, + [((ibar,), ifoo, _blank, _Factory, _info)]) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, SubscriptionRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, _blank) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _Factory) + + def test_registerSubscriptionAdapter_wo_explicit_required(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Registered + from zope.interface.registry import SubscriptionRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _info = u'info' + _blank = u'' + class _Factory(object): + __component_adapts__ = (ibar,) + + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerSubscriptionAdapter( + _Factory, provided=ifoo, info=_info) + reg = comp.adapters._subscribers[1][ibar][ifoo][_blank] + self.assertEqual(len(reg), 1) + self.assertTrue(reg[0] is _Factory) + self.assertEqual(comp._subscription_registrations, + [((ibar,), ifoo, _blank, _Factory, _info)]) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, SubscriptionRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, _blank) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _Factory) + + def test_registerSubscriptionAdapter_wo_event(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _blank = u'' + _info = u'info' + + def _factory(context): + raise NotImplementedError() + + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerSubscriptionAdapter(_factory, (ibar,), ifoo, + info=_info, event=False) + self.assertEqual(len(_events), 0) + + def test_registeredSubscriptionAdapters_empty(self): + comp = self._makeOne() + self.assertEqual(list(comp.registeredSubscriptionAdapters()), []) + + def test_registeredSubscriptionAdapters_notempty(self): + from zope.interface.declarations import InterfaceClass + + from zope.interface.registry import SubscriptionRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IFoo') + _info = u'info' + _blank = u'' + class _Factory(object): + pass + + comp = self._makeOne() + comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo, info=_info) + comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo, info=_info) + reg = list(comp.registeredSubscriptionAdapters()) + self.assertEqual(len(reg), 2) + self.assertTrue(isinstance(reg[0], SubscriptionRegistration)) + self.assertTrue(reg[0].registry is comp) + self.assertTrue(reg[0].provided is ifoo) + self.assertEqual(reg[0].required, (ibar,)) + self.assertEqual(reg[0].name, _blank) + self.assertTrue(reg[0].info is _info) + self.assertTrue(reg[0].factory is _Factory) + self.assertTrue(isinstance(reg[1], SubscriptionRegistration)) + self.assertTrue(reg[1].registry is comp) + self.assertTrue(reg[1].provided is ifoo) + self.assertEqual(reg[1].required, (ibar,)) + self.assertEqual(reg[1].name, _blank) + self.assertTrue(reg[1].info is _info) + self.assertTrue(reg[1].factory is _Factory) + + def test_unregisterSubscriptionAdapter_w_nonblank_name(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + _nonblank = u'nonblank' + comp = self._makeOne() + self.assertRaises(TypeError, comp.unregisterSubscriptionAdapter, + required=ifoo, provided=ibar, name=_nonblank) + + def test_unregisterSubscriptionAdapter_neither_factory_nor_provided(self): + comp = self._makeOne() + self.assertRaises(TypeError, comp.unregisterSubscriptionAdapter, + factory=None, provided=None) + + def test_unregisterSubscriptionAdapter_neither_factory_nor_required(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + self.assertRaises(TypeError, comp.unregisterSubscriptionAdapter, + factory=None, provided=ifoo, required=None) + + def test_unregisterSubscriptionAdapter_miss(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + pass + + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterSubscriptionAdapter(_Factory, (ibar,), ifoo) + self.assertFalse(unreg) + self.assertFalse(_events) + + def test_unregisterSubscriptionAdapter_hit_wo_factory(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import SubscriptionRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + pass + + comp = self._makeOne() + comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterSubscriptionAdapter(None, (ibar,), ifoo) + self.assertTrue(unreg) + self.assertFalse(comp.adapters._subscribers) + self.assertFalse(comp._subscription_registrations) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, SubscriptionRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, '') + self.assertEqual(event.object.info, '') + self.assertTrue(event.object.factory is None) + + def test_unregisterSubscriptionAdapter_hit_w_factory(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import SubscriptionRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + pass + + comp = self._makeOne() + comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterSubscriptionAdapter(_Factory, (ibar,), ifoo) + self.assertTrue(unreg) + self.assertFalse(comp.adapters._subscribers) + self.assertFalse(comp._subscription_registrations) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, SubscriptionRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, '') + self.assertEqual(event.object.info, '') + self.assertTrue(event.object.factory is _Factory) + + def test_unregisterSubscriptionAdapter_wo_explicit_provided(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + from zope.interface.interfaces import Unregistered + from zope.interface.registry import SubscriptionRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + @implementer(ifoo) + class _Factory(object): + pass + + comp = self._makeOne() + comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterSubscriptionAdapter(_Factory, (ibar,)) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, SubscriptionRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, '') + self.assertEqual(event.object.info, '') + self.assertTrue(event.object.factory is _Factory) + + def test_unregisterSubscriptionAdapter_wo_explicit_required(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import SubscriptionRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + __component_adapts__ = (ibar,) + + comp = self._makeOne() + comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterSubscriptionAdapter(_Factory, provided=ifoo) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, SubscriptionRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertTrue(event.object.provided is ifoo) + self.assertEqual(event.object.required, (ibar,)) + self.assertEqual(event.object.name, '') + self.assertEqual(event.object.info, '') + self.assertTrue(event.object.factory is _Factory) + + def test_subscribers_empty(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + comp = self._makeOne() + @implementer(ibar) + class Bar(object): + pass + bar = Bar() + self.assertEqual(list(comp.subscribers((bar,), ifoo)), []) + + def test_subscribers_non_empty(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Factory(object): + __component_adapts__ = (ibar,) + def __init__(self, context): + self._context = context + class _Derived(_Factory): + pass + comp = self._makeOne() + comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) + comp.registerSubscriptionAdapter(_Derived, (ibar,), ifoo) + @implementer(ibar) + class Bar(object): + pass + bar = Bar() + subscribers = comp.subscribers((bar,), ifoo) + def _klassname(x): + return x.__class__.__name__ + subscribers = sorted(subscribers, key=_klassname) + self.assertEqual(len(subscribers), 2) + self.assertTrue(isinstance(subscribers[0], _Derived)) + self.assertTrue(isinstance(subscribers[1], _Factory)) + + def test_registerHandler_w_nonblank_name(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _nonblank = u'nonblank' + comp = self._makeOne() + def _factory(context): + raise NotImplementedError() + + self.assertRaises(TypeError, comp.registerHandler, _factory, + required=ifoo, name=_nonblank) + + def test_registerHandler_w_explicit_required(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Registered + from zope.interface.registry import HandlerRegistration + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _blank = u'' + _info = u'info' + def _factory(context): + raise NotImplementedError() + + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerHandler(_factory, (ifoo,), info=_info) + reg = comp.adapters._subscribers[1][ifoo][None][_blank] + self.assertEqual(len(reg), 1) + self.assertTrue(reg[0] is _factory) + self.assertEqual(comp._handler_registrations, + [((ifoo,), _blank, _factory, _info)]) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Registered)) + self.assertTrue(isinstance(event.object, HandlerRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertEqual(event.object.required, (ifoo,)) + self.assertEqual(event.object.name, _blank) + self.assertTrue(event.object.info is _info) + self.assertTrue(event.object.factory is _factory) + + def test_registerHandler_wo_explicit_required_no_event(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _info = u'info' + _blank = u'' + class _Factory(object): + __component_adapts__ = (ifoo,) + pass + + comp = self._makeOne() + _monkey, _events = self._wrapEvents() + with _monkey: + comp.registerHandler(_Factory, info=_info, event=False) + reg = comp.adapters._subscribers[1][ifoo][None][_blank] + self.assertEqual(len(reg), 1) + self.assertTrue(reg[0] is _Factory) + self.assertEqual(comp._handler_registrations, + [((ifoo,), _blank, _Factory, _info)]) + self.assertEqual(len(_events), 0) + + def test_registeredHandlers_empty(self): + comp = self._makeOne() + self.assertFalse(list(comp.registeredHandlers())) + + def test_registeredHandlers_non_empty(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.registry import HandlerRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + def _factory1(context): + raise NotImplementedError() + def _factory2(context): + raise NotImplementedError() + comp = self._makeOne() + comp.registerHandler(_factory1, (ifoo,)) + comp.registerHandler(_factory2, (ifoo,)) + def _factory_name(x): + return x.factory.__code__.co_name + subscribers = sorted(comp.registeredHandlers(), key=_factory_name) + self.assertEqual(len(subscribers), 2) + self.assertTrue(isinstance(subscribers[0], HandlerRegistration)) + self.assertEqual(subscribers[0].required, (ifoo,)) + self.assertEqual(subscribers[0].name, '') + self.assertEqual(subscribers[0].factory, _factory1) + self.assertEqual(subscribers[0].info, '') + self.assertTrue(isinstance(subscribers[1], HandlerRegistration)) + self.assertEqual(subscribers[1].required, (ifoo,)) + self.assertEqual(subscribers[1].name, '') + self.assertEqual(subscribers[1].factory, _factory2) + self.assertEqual(subscribers[1].info, '') + + def test_unregisterHandler_w_nonblank_name(self): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _nonblank = u'nonblank' + comp = self._makeOne() + self.assertRaises(TypeError, comp.unregisterHandler, + required=(ifoo,), name=_nonblank) + + def test_unregisterHandler_neither_factory_nor_required(self): + comp = self._makeOne() + self.assertRaises(TypeError, comp.unregisterHandler) + + def test_unregisterHandler_miss(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + unreg = comp.unregisterHandler(required=(ifoo,)) + self.assertFalse(unreg) + + def test_unregisterHandler_hit_w_factory_and_explicit_provided(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import HandlerRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + def _factory(context): + raise NotImplementedError() + comp = self._makeOne() + comp.registerHandler(_factory, (ifoo,)) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterHandler(_factory, (ifoo,)) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, HandlerRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertEqual(event.object.required, (ifoo,)) + self.assertEqual(event.object.name, '') + self.assertTrue(event.object.factory is _factory) + + def test_unregisterHandler_hit_w_only_explicit_provided(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import HandlerRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + def _factory(context): + raise NotImplementedError() + comp = self._makeOne() + comp.registerHandler(_factory, (ifoo,)) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterHandler(required=(ifoo,)) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, HandlerRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertEqual(event.object.required, (ifoo,)) + self.assertEqual(event.object.name, '') + self.assertTrue(event.object.factory is None) + + def test_unregisterHandler_wo_explicit_required(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.interfaces import Unregistered + from zope.interface.registry import HandlerRegistration + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + class _Factory(object): + __component_adapts__ = (ifoo,) + + comp = self._makeOne() + comp.registerHandler(_Factory) + _monkey, _events = self._wrapEvents() + with _monkey: + unreg = comp.unregisterHandler(_Factory) + self.assertTrue(unreg) + self.assertEqual(len(_events), 1) + args, kw = _events[0] + event, = args + self.assertEqual(kw, {}) + self.assertTrue(isinstance(event, Unregistered)) + self.assertTrue(isinstance(event.object, HandlerRegistration)) + self.assertTrue(event.object.registry is comp) + self.assertEqual(event.object.required, (ifoo,)) + self.assertEqual(event.object.name, '') + self.assertEqual(event.object.info, '') + self.assertTrue(event.object.factory is _Factory) + + def test_handle_empty(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + comp = self._makeOne() + @implementer(ifoo) + class Bar(object): + pass + bar = Bar() + comp.handle((bar,)) # doesn't raise + + def test_handle_non_empty(self): + from zope.interface.declarations import InterfaceClass + from zope.interface.declarations import implementer + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + _called_1 = [] + def _factory_1(context): + _called_1.append(context) + _called_2 = [] + def _factory_2(context): + _called_2.append(context) + comp = self._makeOne() + comp.registerHandler(_factory_1, (ifoo,)) + comp.registerHandler(_factory_2, (ifoo,)) + @implementer(ifoo) + class Bar(object): + pass + bar = Bar() + comp.handle(bar) + self.assertEqual(_called_1, [bar]) + self.assertEqual(_called_2, [bar]) + + +class UnhashableComponentsTests(ComponentsTests): + + def _getTargetClass(self): + # Mimic what pyramid does to create an unhashable + # registry + class Components(super(UnhashableComponentsTests, self)._getTargetClass(), dict): + pass + return Components + +# Test _getUtilityProvided, _getAdapterProvided, _getAdapterRequired via their +# callers (Component.registerUtility, Component.registerAdapter). + + +class UtilityRegistrationTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.registry import UtilityRegistration + return UtilityRegistration + + def _makeOne(self, component=None, factory=None): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + class _Registry(object): + def __repr__(self): + return '_REGISTRY' + registry = _Registry() + name = u'name' + doc = 'DOCSTRING' + klass = self._getTargetClass() + return (klass(registry, ifoo, name, component, doc, factory), + registry, + name, + ) + + def test_class_conforms_to_IUtilityRegistration(self): + from zope.interface.verify import verifyClass + from zope.interface.interfaces import IUtilityRegistration + verifyClass(IUtilityRegistration, self._getTargetClass()) + + def test_instance_conforms_to_IUtilityRegistration(self): + from zope.interface.verify import verifyObject + from zope.interface.interfaces import IUtilityRegistration + ur, _, _ = self._makeOne() + verifyObject(IUtilityRegistration, ur) + + def test___repr__(self): + class _Component(object): + __name__ = 'TEST' + _component = _Component() + ur, _registry, _name = self._makeOne(_component) + self.assertEqual(repr(ur), + "UtilityRegistration(_REGISTRY, IFoo, %r, TEST, None, 'DOCSTRING')" + % (_name)) + + def test___repr___provided_wo_name(self): + class _Component(object): + def __repr__(self): + return 'TEST' + _component = _Component() + ur, _registry, _name = self._makeOne(_component) + ur.provided = object() + self.assertEqual(repr(ur), + "UtilityRegistration(_REGISTRY, None, %r, TEST, None, 'DOCSTRING')" + % (_name)) + + def test___repr___component_wo_name(self): + class _Component(object): + def __repr__(self): + return 'TEST' + _component = _Component() + ur, _registry, _name = self._makeOne(_component) + ur.provided = object() + self.assertEqual(repr(ur), + "UtilityRegistration(_REGISTRY, None, %r, TEST, None, 'DOCSTRING')" + % (_name)) + + def test___hash__(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + self.assertEqual(ur.__hash__(), id(ur)) + + def test___eq___identity(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + self.assertTrue(ur == ur) + + def test___eq___hit(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component) + self.assertTrue(ur == ur2) + + def test___eq___miss(self): + _component = object() + _component2 = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component2) + self.assertFalse(ur == ur2) + + def test___ne___identity(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + self.assertFalse(ur != ur) + + def test___ne___hit(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component) + self.assertFalse(ur != ur2) + + def test___ne___miss(self): + _component = object() + _component2 = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component2) + self.assertTrue(ur != ur2) + + def test___lt___identity(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + self.assertFalse(ur < ur) + + def test___lt___hit(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component) + self.assertFalse(ur < ur2) + + def test___lt___miss(self): + _component = object() + _component2 = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component2) + ur2.name = _name + '2' + self.assertTrue(ur < ur2) + + def test___le___identity(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + self.assertTrue(ur <= ur) + + def test___le___hit(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component) + self.assertTrue(ur <= ur2) + + def test___le___miss(self): + _component = object() + _component2 = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component2) + ur2.name = _name + '2' + self.assertTrue(ur <= ur2) + + def test___gt___identity(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + self.assertFalse(ur > ur) + + def test___gt___hit(self): + _component = object() + _component2 = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component2) + ur2.name = _name + '2' + self.assertTrue(ur2 > ur) + + def test___gt___miss(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component) + self.assertFalse(ur2 > ur) + + def test___ge___identity(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + self.assertTrue(ur >= ur) + + def test___ge___miss(self): + _component = object() + _component2 = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component2) + ur2.name = _name + '2' + self.assertFalse(ur >= ur2) + + def test___ge___hit(self): + _component = object() + ur, _registry, _name = self._makeOne(_component) + ur2, _, _ = self._makeOne(_component) + ur2.name = _name + '2' + self.assertTrue(ur2 >= ur) + + +class AdapterRegistrationTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.registry import AdapterRegistration + return AdapterRegistration + + def _makeOne(self, component=None): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Registry(object): + def __repr__(self): + return '_REGISTRY' + registry = _Registry() + name = u'name' + doc = 'DOCSTRING' + klass = self._getTargetClass() + return (klass(registry, (ibar,), ifoo, name, component, doc), + registry, + name, + ) + + def test_class_conforms_to_IAdapterRegistration(self): + from zope.interface.verify import verifyClass + from zope.interface.interfaces import IAdapterRegistration + verifyClass(IAdapterRegistration, self._getTargetClass()) + + def test_instance_conforms_to_IAdapterRegistration(self): + from zope.interface.verify import verifyObject + from zope.interface.interfaces import IAdapterRegistration + ar, _, _ = self._makeOne() + verifyObject(IAdapterRegistration, ar) + + def test___repr__(self): + class _Component(object): + __name__ = 'TEST' + _component = _Component() + ar, _registry, _name = self._makeOne(_component) + self.assertEqual(repr(ar), + ("AdapterRegistration(_REGISTRY, [IBar], IFoo, %r, TEST, " + + "'DOCSTRING')") % (_name)) + + def test___repr___provided_wo_name(self): + class _Component(object): + def __repr__(self): + return 'TEST' + _component = _Component() + ar, _registry, _name = self._makeOne(_component) + ar.provided = object() + self.assertEqual(repr(ar), + ("AdapterRegistration(_REGISTRY, [IBar], None, %r, TEST, " + + "'DOCSTRING')") % (_name)) + + def test___repr___component_wo_name(self): + class _Component(object): + def __repr__(self): + return 'TEST' + _component = _Component() + ar, _registry, _name = self._makeOne(_component) + ar.provided = object() + self.assertEqual(repr(ar), + ("AdapterRegistration(_REGISTRY, [IBar], None, %r, TEST, " + + "'DOCSTRING')") % (_name)) + + def test___hash__(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + self.assertEqual(ar.__hash__(), id(ar)) + + def test___eq___identity(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + self.assertTrue(ar == ar) + + def test___eq___hit(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component) + self.assertTrue(ar == ar2) + + def test___eq___miss(self): + _component = object() + _component2 = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component2) + self.assertFalse(ar == ar2) + + def test___ne___identity(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + self.assertFalse(ar != ar) + + def test___ne___miss(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component) + self.assertFalse(ar != ar2) + + def test___ne___hit_component(self): + _component = object() + _component2 = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component2) + self.assertTrue(ar != ar2) + + def test___ne___hit_provided(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ibaz = IFoo('IBaz') + _component = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component) + ar2.provided = ibaz + self.assertTrue(ar != ar2) + + def test___ne___hit_required(self): + from zope.interface.declarations import InterfaceClass + class IFoo(InterfaceClass): + pass + ibaz = IFoo('IBaz') + _component = object() + _component2 = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component2) + ar2.required = (ibaz,) + self.assertTrue(ar != ar2) + + def test___lt___identity(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + self.assertFalse(ar < ar) + + def test___lt___hit(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component) + self.assertFalse(ar < ar2) + + def test___lt___miss(self): + _component = object() + _component2 = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component2) + ar2.name = _name + '2' + self.assertTrue(ar < ar2) + + def test___le___identity(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + self.assertTrue(ar <= ar) + + def test___le___hit(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component) + self.assertTrue(ar <= ar2) + + def test___le___miss(self): + _component = object() + _component2 = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component2) + ar2.name = _name + '2' + self.assertTrue(ar <= ar2) + + def test___gt___identity(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + self.assertFalse(ar > ar) + + def test___gt___hit(self): + _component = object() + _component2 = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component2) + ar2.name = _name + '2' + self.assertTrue(ar2 > ar) + + def test___gt___miss(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component) + self.assertFalse(ar2 > ar) + + def test___ge___identity(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + self.assertTrue(ar >= ar) + + def test___ge___miss(self): + _component = object() + _component2 = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component2) + ar2.name = _name + '2' + self.assertFalse(ar >= ar2) + + def test___ge___hit(self): + _component = object() + ar, _registry, _name = self._makeOne(_component) + ar2, _, _ = self._makeOne(_component) + ar2.name = _name + '2' + self.assertTrue(ar2 >= ar) + + +class SubscriptionRegistrationTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.registry import SubscriptionRegistration + return SubscriptionRegistration + + def _makeOne(self, component=None): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + ibar = IFoo('IBar') + class _Registry(object): + def __repr__(self): # pragma: no cover + return '_REGISTRY' + registry = _Registry() + name = u'name' + doc = 'DOCSTRING' + klass = self._getTargetClass() + return (klass(registry, (ibar,), ifoo, name, component, doc), + registry, + name, + ) + + def test_class_conforms_to_ISubscriptionAdapterRegistration(self): + from zope.interface.verify import verifyClass + from zope.interface.interfaces import ISubscriptionAdapterRegistration + verifyClass(ISubscriptionAdapterRegistration, self._getTargetClass()) + + def test_instance_conforms_to_ISubscriptionAdapterRegistration(self): + from zope.interface.verify import verifyObject + from zope.interface.interfaces import ISubscriptionAdapterRegistration + sar, _, _ = self._makeOne() + verifyObject(ISubscriptionAdapterRegistration, sar) + + +class HandlerRegistrationTests(unittest.TestCase): + + def _getTargetClass(self): + from zope.interface.registry import HandlerRegistration + return HandlerRegistration + + def _makeOne(self, component=None): + from zope.interface.declarations import InterfaceClass + + class IFoo(InterfaceClass): + pass + ifoo = IFoo('IFoo') + class _Registry(object): + def __repr__(self): + return '_REGISTRY' + registry = _Registry() + name = u'name' + doc = 'DOCSTRING' + klass = self._getTargetClass() + return (klass(registry, (ifoo,), name, component, doc), + registry, + name, + ) + + def test_class_conforms_to_IHandlerRegistration(self): + from zope.interface.verify import verifyClass + from zope.interface.interfaces import IHandlerRegistration + verifyClass(IHandlerRegistration, self._getTargetClass()) + + def test_instance_conforms_to_IHandlerRegistration(self): + from zope.interface.verify import verifyObject + from zope.interface.interfaces import IHandlerRegistration + hr, _, _ = self._makeOne() + verifyObject(IHandlerRegistration, hr) + + def test_properties(self): + def _factory(context): + raise NotImplementedError() + hr, _, _ = self._makeOne(_factory) + self.assertTrue(hr.handler is _factory) + self.assertTrue(hr.factory is hr.handler) + self.assertTrue(hr.provided is None) + + def test___repr___factory_w_name(self): + class _Factory(object): + __name__ = 'TEST' + hr, _registry, _name = self._makeOne(_Factory()) + self.assertEqual(repr(hr), + ("HandlerRegistration(_REGISTRY, [IFoo], %r, TEST, " + + "'DOCSTRING')") % (_name)) + + def test___repr___factory_wo_name(self): + class _Factory(object): + def __repr__(self): + return 'TEST' + hr, _registry, _name = self._makeOne(_Factory()) + self.assertEqual(repr(hr), + ("HandlerRegistration(_REGISTRY, [IFoo], %r, TEST, " + + "'DOCSTRING')") % (_name)) + +class PersistentAdapterRegistry(VerifyingAdapterRegistry): + + def __getstate__(self): + state = self.__dict__.copy() + for k in list(state): + if k in self._delegated or k.startswith('_v'): + state.pop(k) + state.pop('ro', None) + return state + + def __setstate__(self, state): + bases = state.pop('__bases__', ()) + self.__dict__.update(state) + self._createLookup() + self.__bases__ = bases + self._v_lookup.changed(self) + +class PersistentComponents(Components): + # Mimic zope.component.persistentregistry.PersistentComponents: + # we should be picklalable, but not persistent.Persistent ourself. + + def _init_registries(self): + self.adapters = PersistentAdapterRegistry() + self.utilities = PersistentAdapterRegistry() + +class PersistentDictComponents(PersistentComponents, dict): + # Like Pyramid's Registry, we subclass Components and dict + pass + + +class PersistentComponentsDict(dict, PersistentComponents): + # Like the above, but inheritance is flipped + def __init__(self, name): + dict.__init__(self) + PersistentComponents.__init__(self, name) + +class TestPersistentComponents(unittest.TestCase): + + def _makeOne(self): + return PersistentComponents('test') + + def _check_equality_after_pickle(self, made): + pass + + def test_pickles_empty(self): + import pickle + comp = self._makeOne() + pickle.dumps(comp) + comp2 = pickle.loads(pickle.dumps(comp)) + + self.assertEqual(comp2.__name__, 'test') + + def test_pickles_with_utility_registration(self): + import pickle + comp = self._makeOne() + utility = object() + comp.registerUtility( + utility, + Interface) + + self.assertIs(utility, + comp.getUtility(Interface)) + + comp2 = pickle.loads(pickle.dumps(comp)) + self.assertEqual(comp2.__name__, 'test') + + # The utility is still registered + self.assertIsNotNone(comp2.getUtility(Interface)) + + # We can register another one + comp2.registerUtility( + utility, + Interface) + self.assertIs(utility, + comp2.getUtility(Interface)) + + self._check_equality_after_pickle(comp2) + + +class TestPersistentDictComponents(TestPersistentComponents): + + def _getTargetClass(self): + return PersistentDictComponents + + def _makeOne(self): + comp = self._getTargetClass()(name='test') + comp['key'] = 42 + return comp + + def _check_equality_after_pickle(self, made): + self.assertIn('key', made) + self.assertEqual(made['key'], 42) + +class TestPersistentComponentsDict(TestPersistentDictComponents): + + def _getTargetClass(self): + return PersistentComponentsDict + +class _Monkey(object): + # context-manager for replacing module names in the scope of a test. + def __init__(self, module, **kw): + self.module = module + self.to_restore = dict([(key, getattr(module, key)) for key in kw]) + for key, value in kw.items(): + setattr(module, key, value) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + for key, value in self.to_restore.items(): + setattr(self.module, key, value) diff --git a/lib/zope/interface/tests/test_ro.py b/lib/zope/interface/tests/test_ro.py new file mode 100644 index 0000000..0756c6d --- /dev/null +++ b/lib/zope/interface/tests/test_ro.py @@ -0,0 +1,115 @@ +############################################################################## +# +# Copyright (c) 2014 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Resolution ordering utility tests""" +import unittest + + +class Test__mergeOrderings(unittest.TestCase): + + def _callFUT(self, orderings): + from zope.interface.ro import _mergeOrderings + return _mergeOrderings(orderings) + + def test_empty(self): + self.assertEqual(self._callFUT([]), []) + + def test_single(self): + self.assertEqual(self._callFUT(['a', 'b', 'c']), ['a', 'b', 'c']) + + def test_w_duplicates(self): + self.assertEqual(self._callFUT([['a'], ['b', 'a']]), ['b', 'a']) + + def test_suffix_across_multiple_duplicats(self): + O1 = ['x', 'y', 'z'] + O2 = ['q', 'z'] + O3 = [1, 3, 5] + O4 = ['z'] + self.assertEqual(self._callFUT([O1, O2, O3, O4]), + ['x', 'y', 'q', 1, 3, 5, 'z']) + + +class Test__flatten(unittest.TestCase): + + def _callFUT(self, ob): + from zope.interface.ro import _flatten + return _flatten(ob) + + def test_w_empty_bases(self): + class Foo(object): + pass + foo = Foo() + foo.__bases__ = () + self.assertEqual(self._callFUT(foo), [foo]) + + def test_w_single_base(self): + class Foo(object): + pass + self.assertEqual(self._callFUT(Foo), [Foo, object]) + + def test_w_bases(self): + class Foo(object): + pass + class Bar(Foo): + pass + self.assertEqual(self._callFUT(Bar), [Bar, Foo, object]) + + def test_w_diamond(self): + class Foo(object): + pass + class Bar(Foo): + pass + class Baz(Foo): + pass + class Qux(Bar, Baz): + pass + self.assertEqual(self._callFUT(Qux), + [Qux, Bar, Foo, object, Baz, Foo, object]) + + +class Test_ro(unittest.TestCase): + + def _callFUT(self, ob): + from zope.interface.ro import ro + return ro(ob) + + def test_w_empty_bases(self): + class Foo(object): + pass + foo = Foo() + foo.__bases__ = () + self.assertEqual(self._callFUT(foo), [foo]) + + def test_w_single_base(self): + class Foo(object): + pass + self.assertEqual(self._callFUT(Foo), [Foo, object]) + + def test_w_bases(self): + class Foo(object): + pass + class Bar(Foo): + pass + self.assertEqual(self._callFUT(Bar), [Bar, Foo, object]) + + def test_w_diamond(self): + class Foo(object): + pass + class Bar(Foo): + pass + class Baz(Foo): + pass + class Qux(Bar, Baz): + pass + self.assertEqual(self._callFUT(Qux), + [Qux, Bar, Baz, Foo, object]) diff --git a/lib/zope/interface/tests/test_sorting.py b/lib/zope/interface/tests/test_sorting.py new file mode 100644 index 0000000..73613d0 --- /dev/null +++ b/lib/zope/interface/tests/test_sorting.py @@ -0,0 +1,47 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Test interface sorting +""" + +import unittest + +from zope.interface import Interface + +class I1(Interface): pass +class I2(I1): pass +class I3(I1): pass +class I4(Interface): pass +class I5(I4): pass +class I6(I2): pass + + +class Test(unittest.TestCase): + + def test(self): + l = [I1, I3, I5, I6, I4, I2] + l.sort() + self.assertEqual(l, [I1, I2, I3, I4, I5, I6]) + + def test_w_None(self): + l = [I1, None, I3, I5, I6, I4, I2] + l.sort() + self.assertEqual(l, [I1, I2, I3, I4, I5, I6, None]) + + def test_w_equal_names(self): + # interfaces with equal names but different modules should sort by + # module name + from zope.interface.tests.m1 import I1 as m1_I1 + l = [I1, m1_I1] + l.sort() + self.assertEqual(l, [m1_I1, I1]) diff --git a/lib/zope/interface/tests/test_verify.py b/lib/zope/interface/tests/test_verify.py new file mode 100644 index 0000000..5ad8bff --- /dev/null +++ b/lib/zope/interface/tests/test_verify.py @@ -0,0 +1,582 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +""" zope.interface.verify unit tests +""" +import unittest + + +class Test_verifyClass(unittest.TestCase): + + def _callFUT(self, iface, klass): + from zope.interface.verify import verifyClass + return verifyClass(iface, klass) + + def test_class_doesnt_implement(self): + from zope.interface import Interface + from zope.interface.exceptions import DoesNotImplement + + class ICurrent(Interface): + pass + + class Current(object): + pass + + self.assertRaises(DoesNotImplement, self._callFUT, ICurrent, Current) + + def test_class_doesnt_implement_but_classImplements_later(self): + from zope.interface import Interface + from zope.interface import classImplements + + class ICurrent(Interface): + pass + + class Current(object): + pass + + classImplements(Current, ICurrent) + + self._callFUT(ICurrent, Current) + + def test_class_doesnt_have_required_method_simple(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenImplementation + + class ICurrent(Interface): + def method(): pass + + @implementer(ICurrent) + class Current(object): + pass + + self.assertRaises(BrokenImplementation, + self._callFUT, ICurrent, Current) + + def test_class_has_required_method_simple(self): + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + def method(): pass + + @implementer(ICurrent) + class Current(object): + + def method(self): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_class_doesnt_have_required_method_derived(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenImplementation + + class IBase(Interface): + def method(): + pass + + class IDerived(IBase): + pass + + @implementer(IDerived) + class Current(object): + pass + + self.assertRaises(BrokenImplementation, + self._callFUT, IDerived, Current) + + def test_class_has_required_method_derived(self): + from zope.interface import Interface + from zope.interface import implementer + + class IBase(Interface): + def method(): + pass + + class IDerived(IBase): + pass + + @implementer(IDerived) + class Current(object): + + def method(self): + raise NotImplementedError() + + self._callFUT(IDerived, Current) + + def test_method_takes_wrong_arg_names_but_OK(self): + # We no longer require names to match. + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + + def method(a): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, b): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_method_takes_not_enough_args(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenMethodImplementation + + class ICurrent(Interface): + + def method(a): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self): + raise NotImplementedError() + + self.assertRaises(BrokenMethodImplementation, + self._callFUT, ICurrent, Current) + + def test_method_doesnt_take_required_starargs(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenMethodImplementation + + class ICurrent(Interface): + + def method(*args): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self): + raise NotImplementedError() + + self.assertRaises(BrokenMethodImplementation, + self._callFUT, ICurrent, Current) + + def test_method_doesnt_take_required_only_kwargs(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenMethodImplementation + + class ICurrent(Interface): + + def method(**kw): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self): + raise NotImplementedError() + + self.assertRaises(BrokenMethodImplementation, + self._callFUT, ICurrent, Current) + + def test_method_takes_extra_arg(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenMethodImplementation + + class ICurrent(Interface): + + def method(a): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, a, b): + raise NotImplementedError() + + self.assertRaises(BrokenMethodImplementation, + self._callFUT, ICurrent, Current) + + def test_method_takes_extra_arg_with_default(self): + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + + def method(a): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, a, b=None): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_method_takes_only_positional_args(self): + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + + def method(a): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, *args): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_method_takes_only_kwargs(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenMethodImplementation + + class ICurrent(Interface): + + def method(a): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, **kw): + raise NotImplementedError() + + self.assertRaises(BrokenMethodImplementation, + self._callFUT, ICurrent, Current) + + def test_method_takes_extra_starargs(self): + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + + def method(a): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, a, *args): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_method_takes_extra_starargs_and_kwargs(self): + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + + def method(a): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, a, *args, **kw): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_method_doesnt_take_required_positional_and_starargs(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenMethodImplementation + + class ICurrent(Interface): + + def method(a, *args): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, a): + raise NotImplementedError() + + self.assertRaises(BrokenMethodImplementation, + self._callFUT, ICurrent, Current) + + def test_method_takes_required_positional_and_starargs(self): + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + + def method(a, *args): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, a, *args): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_method_takes_only_starargs(self): + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + + def method(a, *args): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, *args): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_method_takes_required_kwargs(self): + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + + def method(**kwargs): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, **kw): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_method_takes_positional_plus_required_starargs(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenMethodImplementation + + class ICurrent(Interface): + + def method(*args): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, a, *args): + raise NotImplementedError() + + self.assertRaises(BrokenMethodImplementation, + self._callFUT, ICurrent, Current) + + + def test_method_doesnt_take_required_kwargs(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenMethodImplementation + + class ICurrent(Interface): + + def method(**kwargs): + pass + + @implementer(ICurrent) + class Current(object): + + def method(self, a): + raise NotImplementedError() + + self.assertRaises(BrokenMethodImplementation, + self._callFUT, ICurrent, Current) + + + def test_class_has_method_for_iface_attr(self): + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + attr = Attribute("The foo Attribute") + + @implementer(ICurrent) + class Current: + + def attr(self): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + + def test_class_has_nonmethod_for_method(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenMethodImplementation + + class ICurrent(Interface): + def method(): + pass + + @implementer(ICurrent) + class Current: + method = 1 + + self.assertRaises(BrokenMethodImplementation, + self._callFUT, ICurrent, Current) + + def test_class_has_attribute_for_attribute(self): + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + attr = Attribute("The foo Attribute") + + @implementer(ICurrent) + class Current: + + attr = 1 + + self._callFUT(ICurrent, Current) + + def test_class_misses_attribute_for_attribute(self): + # This check *passes* for verifyClass + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface import implementer + + class ICurrent(Interface): + attr = Attribute("The foo Attribute") + + @implementer(ICurrent) + class Current: + pass + + self._callFUT(ICurrent, Current) + + def test_w_callable_non_func_method(self): + from zope.interface.interface import Method + from zope.interface import Interface + from zope.interface import implementer + + class QuasiMethod(Method): + def __call__(self, *args, **kw): + raise NotImplementedError() + + class QuasiCallable(object): + def __call__(self, *args, **kw): + raise NotImplementedError() + + class ICurrent(Interface): + attr = QuasiMethod('This is callable') + + @implementer(ICurrent) + class Current: + attr = QuasiCallable() + + self._callFUT(ICurrent, Current) + + + def test_w_decorated_method(self): + from zope.interface import Interface + from zope.interface import implementer + + def decorator(func): + # this is, in fact, zope.proxy.non_overridable + return property(lambda self: func.__get__(self)) + + class ICurrent(Interface): + + def method(a): + pass + + @implementer(ICurrent) + class Current(object): + + @decorator + def method(self, a): + raise NotImplementedError() + + self._callFUT(ICurrent, Current) + +class Test_verifyObject(Test_verifyClass): + + def _callFUT(self, iface, target): + from zope.interface.verify import verifyObject + if isinstance(target, (type, type(OldSkool))): + target = target() + return verifyObject(iface, target) + + def test_class_misses_attribute_for_attribute(self): + # This check *fails* for verifyObject + from zope.interface import Attribute + from zope.interface import Interface + from zope.interface import implementer + from zope.interface.exceptions import BrokenImplementation + + class ICurrent(Interface): + attr = Attribute("The foo Attribute") + + @implementer(ICurrent) + class Current: + pass + + self.assertRaises(BrokenImplementation, + self._callFUT, ICurrent, Current) + + def test_module_hit(self): + from zope.interface.tests.idummy import IDummyModule + from zope.interface.tests import dummy + + self._callFUT(IDummyModule, dummy) + + def test_module_miss(self): + from zope.interface import Interface + from zope.interface.tests import dummy + from zope.interface.exceptions import DoesNotImplement + + # same name, different object + class IDummyModule(Interface): + pass + + self.assertRaises(DoesNotImplement, + self._callFUT, IDummyModule, dummy) + + def test_staticmethod_hit_on_class(self): + from zope.interface import Interface + from zope.interface import provider + from zope.interface.verify import verifyObject + + class IFoo(Interface): + + def bar(a, b): + "The bar method" + + @provider(IFoo) + class Foo(object): + + @staticmethod + def bar(a, b): + raise AssertionError("We're never actually called") + + # Don't use self._callFUT, we don't want to instantiate the + # class. + verifyObject(IFoo, Foo) + +class OldSkool: + pass diff --git a/lib/zope/interface/verify.py b/lib/zope/interface/verify.py new file mode 100644 index 0000000..62bb64c --- /dev/null +++ b/lib/zope/interface/verify.py @@ -0,0 +1,123 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Verify interface implementations +""" +from zope.interface.exceptions import BrokenImplementation, DoesNotImplement +from zope.interface.exceptions import BrokenMethodImplementation +from types import FunctionType, MethodType +from zope.interface.interface import fromMethod, fromFunction, Method +import sys + +# This will be monkey-patched when running under Zope 2, so leave this +# here: +MethodTypes = (MethodType, ) + + +def _verify(iface, candidate, tentative=0, vtype=None): + """Verify that 'candidate' might correctly implements 'iface'. + + This involves: + + o Making sure the candidate defines all the necessary methods + + o Making sure the methods have the correct signature + + o Making sure the candidate asserts that it implements the interface + + Note that this isn't the same as verifying that the class does + implement the interface. + + If optional tentative is true, suppress the "is implemented by" test. + """ + + if vtype == 'c': + tester = iface.implementedBy + else: + tester = iface.providedBy + + if not tentative and not tester(candidate): + raise DoesNotImplement(iface) + + # Here the `desc` is either an `Attribute` or `Method` instance + for name, desc in iface.namesAndDescriptions(1): + try: + attr = getattr(candidate, name) + except AttributeError: + if (not isinstance(desc, Method)) and vtype == 'c': + # We can't verify non-methods on classes, since the + # class may provide attrs in it's __init__. + continue + + raise BrokenImplementation(iface, name) + + if not isinstance(desc, Method): + # If it's not a method, there's nothing else we can test + continue + + if isinstance(attr, FunctionType): + if sys.version_info[0] >= 3 and isinstance(candidate, type) and vtype == 'c': + # This is an "unbound method" in Python 3. + # Only unwrap this if we're verifying implementedBy; + # otherwise we can unwrap @staticmethod on classes that directly + # provide an interface. + meth = fromFunction(attr, iface, name=name, + imlevel=1) + else: + # Nope, just a normal function + meth = fromFunction(attr, iface, name=name) + elif (isinstance(attr, MethodTypes) + and type(attr.__func__) is FunctionType): + meth = fromMethod(attr, iface, name) + elif isinstance(attr, property) and vtype == 'c': + # We without an instance we cannot be sure it's not a + # callable. + continue + else: + if not callable(attr): + raise BrokenMethodImplementation(name, "Not a method") + # sigh, it's callable, but we don't know how to introspect it, so + # we have to give it a pass. + continue + + # Make sure that the required and implemented method signatures are + # the same. + desc = desc.getSignatureInfo() + meth = meth.getSignatureInfo() + + mess = _incompat(desc, meth) + if mess: + raise BrokenMethodImplementation(name, mess) + + return True + +def verifyClass(iface, candidate, tentative=0): + return _verify(iface, candidate, tentative, vtype='c') + +def verifyObject(iface, candidate, tentative=0): + return _verify(iface, candidate, tentative, vtype='o') + +def _incompat(required, implemented): + #if (required['positional'] != + # implemented['positional'][:len(required['positional'])] + # and implemented['kwargs'] is None): + # return 'imlementation has different argument names' + if len(implemented['required']) > len(required['required']): + return 'implementation requires too many arguments' + if ((len(implemented['positional']) < len(required['positional'])) + and not implemented['varargs']): + return "implementation doesn't allow enough arguments" + if required['kwargs'] and not implemented['kwargs']: + return "implementation doesn't support keyword arguments" + if required['varargs'] and not implemented['varargs']: + return "implementation doesn't support variable arguments" diff --git a/requirements.txt b/requirements.txt index 760382e..401cf01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,7 @@ requests>=2.20.1 geoip2>=2.9.0 influxdb>=5.2.0 -schedule>=0.5.0 \ No newline at end of file +schedule>=0.5.0 +configparser>=3.5.0 +datetime>=4.3 +typing>=3.6.6 diff --git a/varken.py b/varken.py index 2d68b86..ecf0461 100644 --- a/varken.py +++ b/varken.py @@ -1,3 +1,7 @@ +from sys import path +from os.path import abspath, dirname, join +path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) + import schedule import threading from time import sleep From ac352c67b528a9bc69fb7cf1299314ed071fc7da Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 2 Dec 2018 13:42:47 -0600 Subject: [PATCH 055/120] removed typing from requirements --- lib/bin/chardetect.exe | Bin 93027 -> 93027 bytes lib/bin/easy_install-3.7.exe | Bin 93036 -> 93036 bytes lib/bin/easy_install.exe | Bin 93036 -> 93036 bytes lib/typing.py | 2413 ---------------------------------- requirements.txt | 2 +- 5 files changed, 1 insertion(+), 2414 deletions(-) delete mode 100644 lib/typing.py diff --git a/lib/bin/chardetect.exe b/lib/bin/chardetect.exe index de0cc0b540e59140cfc47c77857d9d89f61d5dc0..a9fc779a1076cdc69541383d5cf5e89547c8ddab 100644 GIT binary patch delta 27 gcmaESjrH+0)`l&N8d*&Gxzn|>7{7sNrEEq=0HRO|2mk;8 delta 27 gcmaESjrH+0)`l&N8d*$YIn%YW7{7sNrEEq=0H78L)Bpeg diff --git a/lib/bin/easy_install-3.7.exe b/lib/bin/easy_install-3.7.exe index 4a61442578c13486b0fa276596393bcea183507a..d0bb68f3c1865069f51e2905948ac1581d6b1a76 100644 GIT binary patch delta 27 gcmaEJjrGkn)`l&N8d*%nxzn|>82^K4?QBLz0Ho*&Gynhq delta 27 gcmaEJjrGkn)`l&N8d*$IIn%YW82^K4?QBLz0HUr600000 diff --git a/lib/bin/easy_install.exe b/lib/bin/easy_install.exe index 4a61442578c13486b0fa276596393bcea183507a..d0bb68f3c1865069f51e2905948ac1581d6b1a76 100644 GIT binary patch delta 27 gcmaEJjrGkn)`l&N8d*%nxzn|>82^K4?QBLz0Ho*&Gynhq delta 27 gcmaEJjrGkn)`l&N8d*$IIn%YW82^K4?QBLz0HUr600000 diff --git a/lib/typing.py b/lib/typing.py deleted file mode 100644 index 2189cd4..0000000 --- a/lib/typing.py +++ /dev/null @@ -1,2413 +0,0 @@ -import abc -from abc import abstractmethod, abstractproperty -import collections -import contextlib -import functools -import re as stdlib_re # Avoid confusion with the re we export. -import sys -import types -try: - import collections.abc as collections_abc -except ImportError: - import collections as collections_abc # Fallback for PY3.2. -if sys.version_info[:2] >= (3, 6): - import _collections_abc # Needed for private function _check_methods # noqa -try: - from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType -except ImportError: - WrapperDescriptorType = type(object.__init__) - MethodWrapperType = type(object().__str__) - MethodDescriptorType = type(str.join) - - -# Please keep __all__ alphabetized within each category. -__all__ = [ - # Super-special typing primitives. - 'Any', - 'Callable', - 'ClassVar', - 'Generic', - 'Optional', - 'Tuple', - 'Type', - 'TypeVar', - 'Union', - - # ABCs (from collections.abc). - 'AbstractSet', # collections.abc.Set. - 'GenericMeta', # subclass of abc.ABCMeta and a metaclass - # for 'Generic' and ABCs below. - 'ByteString', - 'Container', - 'ContextManager', - 'Hashable', - 'ItemsView', - 'Iterable', - 'Iterator', - 'KeysView', - 'Mapping', - 'MappingView', - 'MutableMapping', - 'MutableSequence', - 'MutableSet', - 'Sequence', - 'Sized', - 'ValuesView', - # The following are added depending on presence - # of their non-generic counterparts in stdlib: - # Awaitable, - # AsyncIterator, - # AsyncIterable, - # Coroutine, - # Collection, - # AsyncGenerator, - # AsyncContextManager - - # Structural checks, a.k.a. protocols. - 'Reversible', - 'SupportsAbs', - 'SupportsBytes', - 'SupportsComplex', - 'SupportsFloat', - 'SupportsInt', - 'SupportsRound', - - # Concrete collection types. - 'Counter', - 'Deque', - 'Dict', - 'DefaultDict', - 'List', - 'Set', - 'FrozenSet', - 'NamedTuple', # Not really a type. - 'Generator', - - # One-off things. - 'AnyStr', - 'cast', - 'get_type_hints', - 'NewType', - 'no_type_check', - 'no_type_check_decorator', - 'NoReturn', - 'overload', - 'Text', - 'TYPE_CHECKING', -] - -# The pseudo-submodules 're' and 'io' are part of the public -# namespace, but excluded from __all__ because they might stomp on -# legitimate imports of those modules. - - -def _qualname(x): - if sys.version_info[:2] >= (3, 3): - return x.__qualname__ - else: - # Fall back to just name. - return x.__name__ - - -def _trim_name(nm): - whitelist = ('_TypeAlias', '_ForwardRef', '_TypingBase', '_FinalTypingBase') - if nm.startswith('_') and nm not in whitelist: - nm = nm[1:] - return nm - - -class TypingMeta(type): - """Metaclass for most types defined in typing module - (not a part of public API). - - This overrides __new__() to require an extra keyword parameter - '_root', which serves as a guard against naive subclassing of the - typing classes. Any legitimate class defined using a metaclass - derived from TypingMeta must pass _root=True. - - This also defines a dummy constructor (all the work for most typing - constructs is done in __new__) and a nicer repr(). - """ - - _is_protocol = False - - def __new__(cls, name, bases, namespace, *, _root=False): - if not _root: - raise TypeError("Cannot subclass %s" % - (', '.join(map(_type_repr, bases)) or '()')) - return super().__new__(cls, name, bases, namespace) - - def __init__(self, *args, **kwds): - pass - - def _eval_type(self, globalns, localns): - """Override this in subclasses to interpret forward references. - - For example, List['C'] is internally stored as - List[_ForwardRef('C')], which should evaluate to List[C], - where C is an object found in globalns or localns (searching - localns first, of course). - """ - return self - - def _get_type_vars(self, tvars): - pass - - def __repr__(self): - qname = _trim_name(_qualname(self)) - return '%s.%s' % (self.__module__, qname) - - -class _TypingBase(metaclass=TypingMeta, _root=True): - """Internal indicator of special typing constructs.""" - - __slots__ = ('__weakref__',) - - def __init__(self, *args, **kwds): - pass - - def __new__(cls, *args, **kwds): - """Constructor. - - This only exists to give a better error message in case - someone tries to subclass a special typing object (not a good idea). - """ - if (len(args) == 3 and - isinstance(args[0], str) and - isinstance(args[1], tuple)): - # Close enough. - raise TypeError("Cannot subclass %r" % cls) - return super().__new__(cls) - - # Things that are not classes also need these. - def _eval_type(self, globalns, localns): - return self - - def _get_type_vars(self, tvars): - pass - - def __repr__(self): - cls = type(self) - qname = _trim_name(_qualname(cls)) - return '%s.%s' % (cls.__module__, qname) - - def __call__(self, *args, **kwds): - raise TypeError("Cannot instantiate %r" % type(self)) - - -class _FinalTypingBase(_TypingBase, _root=True): - """Internal mix-in class to prevent instantiation. - - Prevents instantiation unless _root=True is given in class call. - It is used to create pseudo-singleton instances Any, Union, Optional, etc. - """ - - __slots__ = () - - def __new__(cls, *args, _root=False, **kwds): - self = super().__new__(cls, *args, **kwds) - if _root is True: - return self - raise TypeError("Cannot instantiate %r" % cls) - - def __reduce__(self): - return _trim_name(type(self).__name__) - - -class _ForwardRef(_TypingBase, _root=True): - """Internal wrapper to hold a forward reference.""" - - __slots__ = ('__forward_arg__', '__forward_code__', - '__forward_evaluated__', '__forward_value__') - - def __init__(self, arg): - super().__init__(arg) - if not isinstance(arg, str): - raise TypeError('Forward reference must be a string -- got %r' % (arg,)) - try: - code = compile(arg, '<string>', 'eval') - except SyntaxError: - raise SyntaxError('Forward reference must be an expression -- got %r' % - (arg,)) - self.__forward_arg__ = arg - self.__forward_code__ = code - self.__forward_evaluated__ = False - self.__forward_value__ = None - - def _eval_type(self, globalns, localns): - if not self.__forward_evaluated__ or localns is not globalns: - if globalns is None and localns is None: - globalns = localns = {} - elif globalns is None: - globalns = localns - elif localns is None: - localns = globalns - self.__forward_value__ = _type_check( - eval(self.__forward_code__, globalns, localns), - "Forward references must evaluate to types.") - self.__forward_evaluated__ = True - return self.__forward_value__ - - def __eq__(self, other): - if not isinstance(other, _ForwardRef): - return NotImplemented - return (self.__forward_arg__ == other.__forward_arg__ and - self.__forward_value__ == other.__forward_value__) - - def __hash__(self): - return hash((self.__forward_arg__, self.__forward_value__)) - - def __instancecheck__(self, obj): - raise TypeError("Forward references cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Forward references cannot be used with issubclass().") - - def __repr__(self): - return '_ForwardRef(%r)' % (self.__forward_arg__,) - - -class _TypeAlias(_TypingBase, _root=True): - """Internal helper class for defining generic variants of concrete types. - - Note that this is not a type; let's call it a pseudo-type. It cannot - be used in instance and subclass checks in parameterized form, i.e. - ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning - ``False``. - """ - - __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') - - def __init__(self, name, type_var, impl_type, type_checker): - """Initializer. - - Args: - name: The name, e.g. 'Pattern'. - type_var: The type parameter, e.g. AnyStr, or the - specific type, e.g. str. - impl_type: The implementation type. - type_checker: Function that takes an impl_type instance. - and returns a value that should be a type_var instance. - """ - assert isinstance(name, str), repr(name) - assert isinstance(impl_type, type), repr(impl_type) - assert not isinstance(impl_type, TypingMeta), repr(impl_type) - assert isinstance(type_var, (type, _TypingBase)), repr(type_var) - self.name = name - self.type_var = type_var - self.impl_type = impl_type - self.type_checker = type_checker - - def __repr__(self): - return "%s[%s]" % (self.name, _type_repr(self.type_var)) - - def __getitem__(self, parameter): - if not isinstance(self.type_var, TypeVar): - raise TypeError("%s cannot be further parameterized." % self) - if self.type_var.__constraints__ and isinstance(parameter, type): - if not issubclass(parameter, self.type_var.__constraints__): - raise TypeError("%s is not a valid substitution for %s." % - (parameter, self.type_var)) - if isinstance(parameter, TypeVar) and parameter is not self.type_var: - raise TypeError("%s cannot be re-parameterized." % self) - return self.__class__(self.name, parameter, - self.impl_type, self.type_checker) - - def __eq__(self, other): - if not isinstance(other, _TypeAlias): - return NotImplemented - return self.name == other.name and self.type_var == other.type_var - - def __hash__(self): - return hash((self.name, self.type_var)) - - def __instancecheck__(self, obj): - if not isinstance(self.type_var, TypeVar): - raise TypeError("Parameterized type aliases cannot be used " - "with isinstance().") - return isinstance(obj, self.impl_type) - - def __subclasscheck__(self, cls): - if not isinstance(self.type_var, TypeVar): - raise TypeError("Parameterized type aliases cannot be used " - "with issubclass().") - return issubclass(cls, self.impl_type) - - -def _get_type_vars(types, tvars): - for t in types: - if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): - t._get_type_vars(tvars) - - -def _type_vars(types): - tvars = [] - _get_type_vars(types, tvars) - return tuple(tvars) - - -def _eval_type(t, globalns, localns): - if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): - return t._eval_type(globalns, localns) - return t - - -def _type_check(arg, msg): - """Check that the argument is a type, and return it (internal helper). - - As a special case, accept None and return type(None) instead. - Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable. - - The msg argument is a human-readable error message, e.g. - - "Union[arg, ...]: arg should be a type." - - We append the repr() of the actual value (truncated to 100 chars). - """ - if arg is None: - return type(None) - if isinstance(arg, str): - arg = _ForwardRef(arg) - if ( - isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or - not isinstance(arg, (type, _TypingBase)) and not callable(arg) - ): - raise TypeError(msg + " Got %.100r." % (arg,)) - # Bare Union etc. are not valid as type arguments - if ( - type(arg).__name__ in ('_Union', '_Optional') and - not getattr(arg, '__origin__', None) or - isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol) - ): - raise TypeError("Plain %s is not valid as type argument" % arg) - return arg - - -def _type_repr(obj): - """Return the repr() of an object, special-casing types (internal helper). - - If obj is a type, we return a shorter version than the default - type.__repr__, based on the module and qualified name, which is - typically enough to uniquely identify a type. For everything - else, we fall back on repr(obj). - """ - if isinstance(obj, type) and not isinstance(obj, TypingMeta): - if obj.__module__ == 'builtins': - return _qualname(obj) - return '%s.%s' % (obj.__module__, _qualname(obj)) - if obj is ...: - return('...') - if isinstance(obj, types.FunctionType): - return obj.__name__ - return repr(obj) - - -class _Any(_FinalTypingBase, _root=True): - """Special type indicating an unconstrained type. - - - Any is compatible with every type. - - Any assumed to have all methods. - - All values assumed to be instances of Any. - - Note that all the above statements are true from the point of view of - static type checkers. At runtime, Any should not be used with instance - or class checks. - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("Any cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Any cannot be used with issubclass().") - - -Any = _Any(_root=True) - - -class _NoReturn(_FinalTypingBase, _root=True): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - -NoReturn = _NoReturn(_root=True) - - -class TypeVar(_TypingBase, _root=True): - """Type variable. - - Usage:: - - T = TypeVar('T') # Can be anything - A = TypeVar('A', str, bytes) # Must be str or bytes - - Type variables exist primarily for the benefit of static type - checkers. They serve as the parameters for generic types as well - as for generic function definitions. See class Generic for more - information on generic types. Generic functions work as follows: - - def repeat(x: T, n: int) -> List[T]: - '''Return a list containing n references to x.''' - return [x]*n - - def longest(x: A, y: A) -> A: - '''Return the longest of two strings.''' - return x if len(x) >= len(y) else y - - The latter example's signature is essentially the overloading - of (str, str) -> str and (bytes, bytes) -> bytes. Also note - that if the arguments are instances of some subclass of str, - the return type is still plain str. - - At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError. - - Type variables defined with covariant=True or contravariant=True - can be used do declare covariant or contravariant generic types. - See PEP 484 for more details. By default generic types are invariant - in all type variables. - - Type variables can be introspected. e.g.: - - T.__name__ == 'T' - T.__constraints__ == () - T.__covariant__ == False - T.__contravariant__ = False - A.__constraints__ == (str, bytes) - """ - - __slots__ = ('__name__', '__bound__', '__constraints__', - '__covariant__', '__contravariant__') - - def __init__(self, name, *constraints, bound=None, - covariant=False, contravariant=False): - super().__init__(name, *constraints, bound=bound, - covariant=covariant, contravariant=contravariant) - self.__name__ = name - if covariant and contravariant: - raise ValueError("Bivariant types are not supported.") - self.__covariant__ = bool(covariant) - self.__contravariant__ = bool(contravariant) - if constraints and bound is not None: - raise TypeError("Constraints cannot be combined with bound=...") - if constraints and len(constraints) == 1: - raise TypeError("A single constraint is not allowed") - msg = "TypeVar(name, constraint, ...): constraints must be types." - self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) - if bound: - self.__bound__ = _type_check(bound, "Bound must be a type.") - else: - self.__bound__ = None - - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - - def __repr__(self): - if self.__covariant__: - prefix = '+' - elif self.__contravariant__: - prefix = '-' - else: - prefix = '~' - return prefix + self.__name__ - - def __instancecheck__(self, instance): - raise TypeError("Type variables cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Type variables cannot be used with issubclass().") - - -# Some unconstrained type variables. These are used by the container types. -# (These are not for export.) -T = TypeVar('T') # Any type. -KT = TypeVar('KT') # Key type. -VT = TypeVar('VT') # Value type. -T_co = TypeVar('T_co', covariant=True) # Any type covariant containers. -V_co = TypeVar('V_co', covariant=True) # Any type covariant containers. -VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers. -T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. - -# A useful type variable with constraints. This represents string types. -# (This one *is* for export!) -AnyStr = TypeVar('AnyStr', bytes, str) - - -def _replace_arg(arg, tvars, args): - """An internal helper function: replace arg if it is a type variable - found in tvars with corresponding substitution from args or - with corresponding substitution sub-tree if arg is a generic type. - """ - - if tvars is None: - tvars = [] - if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)): - return arg._subs_tree(tvars, args) - if isinstance(arg, TypeVar): - for i, tvar in enumerate(tvars): - if arg == tvar: - return args[i] - return arg - - -# Special typing constructs Union, Optional, Generic, Callable and Tuple -# use three special attributes for internal bookkeeping of generic types: -# * __parameters__ is a tuple of unique free type parameters of a generic -# type, for example, Dict[T, T].__parameters__ == (T,); -# * __origin__ keeps a reference to a type that was subscripted, -# e.g., Union[T, int].__origin__ == Union; -# * __args__ is a tuple of all arguments used in subscripting, -# e.g., Dict[T, int].__args__ == (T, int). - - -def _subs_tree(cls, tvars=None, args=None): - """An internal helper function: calculate substitution tree - for generic cls after replacing its type parameters with - substitutions in tvars -> args (if any). - Repeat the same following __origin__'s. - - Return a list of arguments with all possible substitutions - performed. Arguments that are generic classes themselves are represented - as tuples (so that no new classes are created by this function). - For example: _subs_tree(List[Tuple[int, T]][str]) == [(Tuple, int, str)] - """ - - if cls.__origin__ is None: - return cls - # Make of chain of origins (i.e. cls -> cls.__origin__) - current = cls.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ - # Replace type variables in __args__ if asked ... - tree_args = [] - for arg in cls.__args__: - tree_args.append(_replace_arg(arg, tvars, args)) - # ... then continue replacing down the origin chain. - for ocls in orig_chain: - new_tree_args = [] - for arg in ocls.__args__: - new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) - tree_args = new_tree_args - return tree_args - - -def _remove_dups_flatten(parameters): - """An internal helper for Union creation and substitution: flatten Union's - among parameters, then remove duplicates and strict subclasses. - """ - - # Flatten out Union[Union[...], ...]. - params = [] - for p in parameters: - if isinstance(p, _Union) and p.__origin__ is Union: - params.extend(p.__args__) - elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union: - params.extend(p[1:]) - else: - params.append(p) - # Weed out strict duplicates, preserving the first of each occurrence. - all_params = set(params) - if len(all_params) < len(params): - new_params = [] - for t in params: - if t in all_params: - new_params.append(t) - all_params.remove(t) - params = new_params - assert not all_params, all_params - # Weed out subclasses. - # E.g. Union[int, Employee, Manager] == Union[int, Employee]. - # If object is present it will be sole survivor among proper classes. - # Never discard type variables. - # (In particular, Union[str, AnyStr] != AnyStr.) - all_params = set(params) - for t1 in params: - if not isinstance(t1, type): - continue - if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} - if not (isinstance(t2, GenericMeta) and - t2.__origin__ is not None)): - all_params.remove(t1) - return tuple(t for t in params if t in all_params) - - -def _check_generic(cls, parameters): - # Check correct count for parameters of a generic cls (internal helper). - if not cls.__parameters__: - raise TypeError("%s is not a generic class" % repr(cls)) - alen = len(parameters) - elen = len(cls.__parameters__) - if alen != elen: - raise TypeError("Too %s parameters for %s; actual %s, expected %s" % - ("many" if alen > elen else "few", repr(cls), alen, elen)) - - -_cleanups = [] - - -def _tp_cache(func): - """Internal wrapper caching __getitem__ of generic types with a fallback to - original function for non-hashable arguments. - """ - - cached = functools.lru_cache()(func) - _cleanups.append(cached.cache_clear) - - @functools.wraps(func) - def inner(*args, **kwds): - try: - return cached(*args, **kwds) - except TypeError: - pass # All real errors (not unhashable args) are raised below. - return func(*args, **kwds) - return inner - - -class _Union(_FinalTypingBase, _root=True): - """Union type; Union[X, Y] means either X or Y. - - To define a union, use e.g. Union[int, str]. Details: - - - The arguments must be types and there must be at least one. - - - None as an argument is a special case and is replaced by - type(None). - - - Unions of unions are flattened, e.g.:: - - Union[Union[int, str], float] == Union[int, str, float] - - - Unions of a single argument vanish, e.g.:: - - Union[int] == int # The constructor actually returns int - - - Redundant arguments are skipped, e.g.:: - - Union[int, str, int] == Union[int, str] - - - When comparing unions, the argument order is ignored, e.g.:: - - Union[int, str] == Union[str, int] - - - When two arguments have a subclass relationship, the least - derived argument is kept, e.g.:: - - class Employee: pass - class Manager(Employee): pass - Union[int, Employee, Manager] == Union[int, Employee] - Union[Manager, int, Employee] == Union[int, Employee] - Union[Employee, Manager] == Employee - - - Similar for object:: - - Union[int, object] == object - - - You cannot subclass or instantiate a union. - - - You can use Optional[X] as a shorthand for Union[X, None]. - """ - - __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') - - def __new__(cls, parameters=None, origin=None, *args, _root=False): - self = super().__new__(cls, parameters, origin, *args, _root=_root) - if origin is None: - self.__parameters__ = None - self.__args__ = None - self.__origin__ = None - self.__tree_hash__ = hash(frozenset(('Union',))) - return self - if not isinstance(parameters, tuple): - raise TypeError("Expected parameters=<tuple>") - if origin is Union: - parameters = _remove_dups_flatten(parameters) - # It's not a union if there's only one type left. - if len(parameters) == 1: - return parameters[0] - self.__parameters__ = _type_vars(parameters) - self.__args__ = parameters - self.__origin__ = origin - # Pre-calculate the __hash__ on instantiation. - # This improves speed for complex substitutions. - subs_tree = self._subs_tree() - if isinstance(subs_tree, tuple): - self.__tree_hash__ = hash(frozenset(subs_tree)) - else: - self.__tree_hash__ = hash(subs_tree) - return self - - def _eval_type(self, globalns, localns): - if self.__args__ is None: - return self - ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) - ev_origin = _eval_type(self.__origin__, globalns, localns) - if ev_args == self.__args__ and ev_origin == self.__origin__: - # Everything is already evaluated. - return self - return self.__class__(ev_args, ev_origin, _root=True) - - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - _get_type_vars(self.__parameters__, tvars) - - def __repr__(self): - if self.__origin__ is None: - return super().__repr__() - tree = self._subs_tree() - if not isinstance(tree, tuple): - return repr(tree) - return tree[0]._tree_repr(tree) - - def _tree_repr(self, tree): - arg_list = [] - for arg in tree[1:]: - if not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - return super().__repr__() + '[%s]' % ', '.join(arg_list) - - @_tp_cache - def __getitem__(self, parameters): - if parameters == (): - raise TypeError("Cannot take a Union of no types.") - if not isinstance(parameters, tuple): - parameters = (parameters,) - if self.__origin__ is None: - msg = "Union[arg, ...]: each arg must be a type." - else: - msg = "Parameters to generic types must be types." - parameters = tuple(_type_check(p, msg) for p in parameters) - if self is not Union: - _check_generic(self, parameters) - return self.__class__(parameters, origin=self, _root=True) - - def _subs_tree(self, tvars=None, args=None): - if self is Union: - return Union # Nothing to substitute - tree_args = _subs_tree(self, tvars, args) - tree_args = _remove_dups_flatten(tree_args) - if len(tree_args) == 1: - return tree_args[0] # Union of a single type is that type - return (Union,) + tree_args - - def __eq__(self, other): - if isinstance(other, _Union): - return self.__tree_hash__ == other.__tree_hash__ - elif self is not Union: - return self._subs_tree() == other - else: - return self is other - - def __hash__(self): - return self.__tree_hash__ - - def __instancecheck__(self, obj): - raise TypeError("Unions cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Unions cannot be used with issubclass().") - - -Union = _Union(_root=True) - - -class _Optional(_FinalTypingBase, _root=True): - """Optional type. - - Optional[X] is equivalent to Union[X, None]. - """ - - __slots__ = () - - @_tp_cache - def __getitem__(self, arg): - arg = _type_check(arg, "Optional[t] requires a single type.") - return Union[arg, type(None)] - - -Optional = _Optional(_root=True) - - -def _next_in_mro(cls): - """Helper for Generic.__new__. - - Returns the class after the last occurrence of Generic or - Generic[...] in cls.__mro__. - """ - next_in_mro = object - # Look for the last occurrence of Generic or Generic[...]. - for i, c in enumerate(cls.__mro__[:-1]): - if isinstance(c, GenericMeta) and c._gorg is Generic: - next_in_mro = cls.__mro__[i + 1] - return next_in_mro - - -def _make_subclasshook(cls): - """Construct a __subclasshook__ callable that incorporates - the associated __extra__ class in subclass checks performed - against cls. - """ - if isinstance(cls.__extra__, abc.ABCMeta): - # The logic mirrors that of ABCMeta.__subclasscheck__. - # Registered classes need not be checked here because - # cls and its extra share the same _abc_registry. - def __extrahook__(subclass): - res = cls.__extra__.__subclasshook__(subclass) - if res is not NotImplemented: - return res - if cls.__extra__ in subclass.__mro__: - return True - for scls in cls.__extra__.__subclasses__(): - if isinstance(scls, GenericMeta): - continue - if issubclass(subclass, scls): - return True - return NotImplemented - else: - # For non-ABC extras we'll just call issubclass(). - def __extrahook__(subclass): - if cls.__extra__ and issubclass(subclass, cls.__extra__): - return True - return NotImplemented - return __extrahook__ - - -def _no_slots_copy(dct): - """Internal helper: copy class __dict__ and clean slots class variables. - (They will be re-created if necessary by normal class machinery.) - """ - dict_copy = dict(dct) - if '__slots__' in dict_copy: - for slot in dict_copy['__slots__']: - dict_copy.pop(slot, None) - return dict_copy - - -class GenericMeta(TypingMeta, abc.ABCMeta): - """Metaclass for generic types. - - This is a metaclass for typing.Generic and generic ABCs defined in - typing module. User defined subclasses of GenericMeta can override - __new__ and invoke super().__new__. Note that GenericMeta.__new__ - has strict rules on what is allowed in its bases argument: - * plain Generic is disallowed in bases; - * Generic[...] should appear in bases at most once; - * if Generic[...] is present, then it should list all type variables - that appear in other bases. - In addition, type of all generic bases is erased, e.g., C[int] is - stripped to plain C. - """ - - def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None, orig_bases=None): - """Create a new generic class. GenericMeta.__new__ accepts - keyword arguments that are used for internal bookkeeping, therefore - an override should pass unused keyword arguments to super(). - """ - if tvars is not None: - # Called from __getitem__() below. - assert origin is not None - assert all(isinstance(t, TypeVar) for t in tvars), tvars - else: - # Called from class statement. - assert tvars is None, tvars - assert args is None, args - assert origin is None, origin - - # Get the full set of tvars from the bases. - tvars = _type_vars(bases) - # Look for Generic[T1, ..., Tn]. - # If found, tvars must be a subset of it. - # If not found, tvars is it. - # Also check for and reject plain Generic, - # and reject multiple Generic[...]. - gvars = None - for base in bases: - if base is Generic: - raise TypeError("Cannot inherit from plain Generic") - if (isinstance(base, GenericMeta) and - base.__origin__ is Generic): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] multiple types.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - raise TypeError( - "Some type variables (%s) " - "are not listed in Generic[%s]" % - (", ".join(str(t) for t in tvars if t not in gvarset), - ", ".join(str(g) for g in gvars))) - tvars = gvars - - initial_bases = bases - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) - - # remove bare Generic from bases if there are other generic bases - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) - namespace.update({'__origin__': origin, '__extra__': extra, - '_gorg': None if not origin else origin._gorg}) - self = super().__new__(cls, name, bases, namespace, _root=True) - super(GenericMeta, self).__setattr__('_gorg', - self if not origin else origin._gorg) - self.__parameters__ = tvars - # Be prepared that GenericMeta will be subclassed by TupleMeta - # and CallableMeta, those two allow ..., (), or [] in __args___. - self.__args__ = tuple(... if a is _TypingEllipsis else - () if a is _TypingEmpty else - a for a in args) if args else None - # Speed hack (https://github.com/python/typing/issues/196). - self.__next_in_mro__ = _next_in_mro(self) - # Preserve base classes on subclassing (__bases__ are type erased now). - if orig_bases is None: - self.__orig_bases__ = initial_bases - - # This allows unparameterized generic collections to be used - # with issubclass() and isinstance() in the same way as their - # collections.abc counterparts (e.g., isinstance([], Iterable)). - if ( - '__subclasshook__' not in namespace and extra or - # allow overriding - getattr(self.__subclasshook__, '__name__', '') == '__extrahook__' - ): - self.__subclasshook__ = _make_subclasshook(self) - if isinstance(extra, abc.ABCMeta): - self._abc_registry = extra._abc_registry - self._abc_cache = extra._abc_cache - elif origin is not None: - self._abc_registry = origin._abc_registry - self._abc_cache = origin._abc_cache - - if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. - self.__qualname__ = origin.__qualname__ - self.__tree_hash__ = (hash(self._subs_tree()) if origin else - super(GenericMeta, self).__hash__()) - return self - - # _abc_negative_cache and _abc_negative_cache_version - # realised as descriptors, since GenClass[t1, t2, ...] always - # share subclass info with GenClass. - # This is an important memory optimization. - @property - def _abc_negative_cache(self): - if isinstance(self.__extra__, abc.ABCMeta): - return self.__extra__._abc_negative_cache - return self._gorg._abc_generic_negative_cache - - @_abc_negative_cache.setter - def _abc_negative_cache(self, value): - if self.__origin__ is None: - if isinstance(self.__extra__, abc.ABCMeta): - self.__extra__._abc_negative_cache = value - else: - self._abc_generic_negative_cache = value - - @property - def _abc_negative_cache_version(self): - if isinstance(self.__extra__, abc.ABCMeta): - return self.__extra__._abc_negative_cache_version - return self._gorg._abc_generic_negative_cache_version - - @_abc_negative_cache_version.setter - def _abc_negative_cache_version(self, value): - if self.__origin__ is None: - if isinstance(self.__extra__, abc.ABCMeta): - self.__extra__._abc_negative_cache_version = value - else: - self._abc_generic_negative_cache_version = value - - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - _get_type_vars(self.__parameters__, tvars) - - def _eval_type(self, globalns, localns): - ev_origin = (self.__origin__._eval_type(globalns, localns) - if self.__origin__ else None) - ev_args = tuple(_eval_type(a, globalns, localns) for a - in self.__args__) if self.__args__ else None - if ev_origin == self.__origin__ and ev_args == self.__args__: - return self - return self.__class__(self.__name__, - self.__bases__, - _no_slots_copy(self.__dict__), - tvars=_type_vars(ev_args) if ev_args else None, - args=ev_args, - origin=ev_origin, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - def __repr__(self): - if self.__origin__ is None: - return super().__repr__() - return self._tree_repr(self._subs_tree()) - - def _tree_repr(self, tree): - arg_list = [] - for arg in tree[1:]: - if arg == (): - arg_list.append('()') - elif not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - return super().__repr__() + '[%s]' % ', '.join(arg_list) - - def _subs_tree(self, tvars=None, args=None): - if self.__origin__ is None: - return self - tree_args = _subs_tree(self, tvars, args) - return (self._gorg,) + tuple(tree_args) - - def __eq__(self, other): - if not isinstance(other, GenericMeta): - return NotImplemented - if self.__origin__ is None or other.__origin__ is None: - return self is other - return self.__tree_hash__ == other.__tree_hash__ - - def __hash__(self): - return self.__tree_hash__ - - @_tp_cache - def __getitem__(self, params): - if not isinstance(params, tuple): - params = (params,) - if not params and self._gorg is not Tuple: - raise TypeError( - "Parameter list to %s[...] cannot be empty" % _qualname(self)) - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self is Generic: - # Generic can only be subscripted with unique type variables. - if not all(isinstance(p, TypeVar) for p in params): - raise TypeError( - "Parameters to Generic[...] must all be type variables") - if len(set(params)) != len(params): - raise TypeError( - "Parameters to Generic[...] must all be unique") - tvars = params - args = params - elif self in (Tuple, Callable): - tvars = _type_vars(params) - args = params - elif self is _Protocol: - # _Protocol is internal, don't check anything. - tvars = params - args = params - elif self.__origin__ in (Generic, _Protocol): - # Can't subscript Generic[...] or _Protocol[...]. - raise TypeError("Cannot subscript already-subscripted %s" % - repr(self)) - else: - # Subscripting a regular Generic subclass. - _check_generic(self, params) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__(self.__name__, - prepend + self.__bases__, - _no_slots_copy(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - def __subclasscheck__(self, cls): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if self is Generic: - raise TypeError("Class %r cannot be used with class " - "or instance checks" % self) - return super().__subclasscheck__(cls) - - def __instancecheck__(self, instance): - # Since we extend ABC.__subclasscheck__ and - # ABC.__instancecheck__ inlines the cache checking done by the - # latter, we must extend __instancecheck__ too. For simplicity - # we just skip the cache check -- instance checks for generic - # classes are supposed to be rare anyways. - return issubclass(instance.__class__, self) - - def __setattr__(self, attr, value): - # We consider all the subscripted generics as proxies for original class - if ( - attr.startswith('__') and attr.endswith('__') or - attr.startswith('_abc_') or - self._gorg is None # The class is not fully created, see #typing/506 - ): - super(GenericMeta, self).__setattr__(attr, value) - else: - super(GenericMeta, self._gorg).__setattr__(attr, value) - - -# Prevent checks for Generic to crash when defining Generic. -Generic = None - - -def _generic_new(base_cls, cls, *args, **kwds): - # Assure type is erased on instantiation, - # but attempt to store it in __orig_class__ - if cls.__origin__ is None: - if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): - return base_cls.__new__(cls) - else: - return base_cls.__new__(cls, *args, **kwds) - else: - origin = cls._gorg - if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): - obj = base_cls.__new__(origin) - else: - obj = base_cls.__new__(origin, *args, **kwds) - try: - obj.__orig_class__ = cls - except AttributeError: - pass - obj.__init__(*args, **kwds) - return obj - - -class Generic(metaclass=GenericMeta): - """Abstract base class for generic types. - - A generic type is typically declared by inheriting from - this class parameterized with one or more type variables. - For example, a generic mapping type might be defined as:: - - class Mapping(Generic[KT, VT]): - def __getitem__(self, key: KT) -> VT: - ... - # Etc. - - This class can then be used as follows:: - - def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: - try: - return mapping[key] - except KeyError: - return default - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Generic: - raise TypeError("Type Generic cannot be instantiated; " - "it can be used only as a base class") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -class _TypingEmpty: - """Internal placeholder for () or []. Used by TupleMeta and CallableMeta - to allow empty list/tuple in specific places, without allowing them - to sneak in where prohibited. - """ - - -class _TypingEllipsis: - """Internal placeholder for ... (ellipsis).""" - - -class TupleMeta(GenericMeta): - """Metaclass for Tuple (internal).""" - - @_tp_cache - def __getitem__(self, parameters): - if self.__origin__ is not None or self._gorg is not Tuple: - # Normal generic rules apply if this is not the first subscription - # or a subscription of a subclass. - return super().__getitem__(parameters) - if parameters == (): - return super().__getitem__((_TypingEmpty,)) - if not isinstance(parameters, tuple): - parameters = (parameters,) - if len(parameters) == 2 and parameters[1] is ...: - msg = "Tuple[t, ...]: t must be a type." - p = _type_check(parameters[0], msg) - return super().__getitem__((p, _TypingEllipsis)) - msg = "Tuple[t0, t1, ...]: each t must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) - return super().__getitem__(parameters) - - def __instancecheck__(self, obj): - if self.__args__ is None: - return isinstance(obj, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with isinstance().") - - def __subclasscheck__(self, cls): - if self.__args__ is None: - return issubclass(cls, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with issubclass().") - - -class Tuple(tuple, extra=tuple, metaclass=TupleMeta): - """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. - - Example: Tuple[T1, T2] is a tuple of two elements corresponding - to type variables T1 and T2. Tuple[int, float, str] is a tuple - of an int, a float and a string. - - To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Tuple: - raise TypeError("Type Tuple cannot be instantiated; " - "use tuple() instead") - return _generic_new(tuple, cls, *args, **kwds) - - -class CallableMeta(GenericMeta): - """Metaclass for Callable (internal).""" - - def __repr__(self): - if self.__origin__ is None: - return super().__repr__() - return self._tree_repr(self._subs_tree()) - - def _tree_repr(self, tree): - if self._gorg is not Callable: - return super()._tree_repr(tree) - # For actual Callable (not its subclass) we override - # super()._tree_repr() for nice formatting. - arg_list = [] - for arg in tree[1:]: - if not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - if arg_list[0] == '...': - return repr(tree[0]) + '[..., %s]' % arg_list[1] - return (repr(tree[0]) + - '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) - - def __getitem__(self, parameters): - """A thin wrapper around __getitem_inner__ to provide the latter - with hashable arguments to improve speed. - """ - - if self.__origin__ is not None or self._gorg is not Callable: - return super().__getitem__(parameters) - if not isinstance(parameters, tuple) or len(parameters) != 2: - raise TypeError("Callable must be used as " - "Callable[[arg, ...], result].") - args, result = parameters - if args is Ellipsis: - parameters = (Ellipsis, result) - else: - if not isinstance(args, list): - raise TypeError("Callable[args, result]: args must be a list." - " Got %.100r." % (args,)) - parameters = (tuple(args), result) - return self.__getitem_inner__(parameters) - - @_tp_cache - def __getitem_inner__(self, parameters): - args, result = parameters - msg = "Callable[args, result]: result must be a type." - result = _type_check(result, msg) - if args is Ellipsis: - return super().__getitem__((_TypingEllipsis, result)) - msg = "Callable[[arg, ...], result]: each arg must be a type." - args = tuple(_type_check(arg, msg) for arg in args) - parameters = args + (result,) - return super().__getitem__(parameters) - - -class Callable(extra=collections_abc.Callable, metaclass=CallableMeta): - """Callable type; Callable[[int], str] is a function of (int) -> str. - - The subscription syntax must always be used with exactly two - values: the argument list and the return type. The argument list - must be a list of types or ellipsis; the return type must be a single type. - - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Callable: - raise TypeError("Type Callable cannot be instantiated; " - "use a non-abstract subclass instead") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -class _ClassVar(_FinalTypingBase, _root=True): - """Special type construct to mark class variables. - - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats: ClassVar[Dict[str, int]] = {} # class variable - damage: int = 10 # instance variable - - ClassVar accepts only types and cannot be further subscribed. - - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(_type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = _eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(_type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - -ClassVar = _ClassVar(_root=True) - - -def cast(typ, val): - """Cast a value to a type. - - This returns the value unchanged. To the type checker this - signals that the return value has the designated type, but at - runtime we intentionally don't check anything (we want this - to be as fast as possible). - """ - return val - - -def _get_defaults(func): - """Internal helper to extract the default arguments, by name.""" - try: - code = func.__code__ - except AttributeError: - # Some built-in functions don't have __code__, __defaults__, etc. - return {} - pos_count = code.co_argcount - arg_names = code.co_varnames - arg_names = arg_names[:pos_count] - defaults = func.__defaults__ or () - kwdefaults = func.__kwdefaults__ - res = dict(kwdefaults) if kwdefaults else {} - pos_offset = pos_count - len(defaults) - for name, value in zip(arg_names[pos_offset:], defaults): - assert name not in res - res[name] = value - return res - - -_allowed_types = (types.FunctionType, types.BuiltinFunctionType, - types.MethodType, types.ModuleType, - WrapperDescriptorType, MethodWrapperType, MethodDescriptorType) - - -def get_type_hints(obj, globalns=None, localns=None): - """Return type hints for an object. - - This is often the same as obj.__annotations__, but it handles - forward references encoded as string literals, and if necessary - adds Optional[t] if a default value equal to None is set. - - The argument may be a module, class, method, or function. The annotations - are returned as a dictionary. For classes, annotations include also - inherited members. - - TypeError is raised if the argument is not of a type that can contain - annotations, and an empty dictionary is returned if no annotations are - present. - - BEWARE -- the behavior of globalns and localns is counterintuitive - (unless you are familiar with how eval() and exec() work). The - search order is locals first, then globals. - - - If no dict arguments are passed, an attempt is made to use the - globals from obj (or the respective module's globals for classes), - and these are also used as the locals. If the object does not appear - to have globals, an empty dictionary is used. - - - If one dict argument is passed, it is used for both globals and - locals. - - - If two dict arguments are passed, they specify globals and - locals, respectively. - """ - - if getattr(obj, '__no_type_check__', None): - return {} - # Classes require a special treatment. - if isinstance(obj, type): - hints = {} - for base in reversed(obj.__mro__): - if globalns is None: - base_globals = sys.modules[base.__module__].__dict__ - else: - base_globals = globalns - ann = base.__dict__.get('__annotations__', {}) - for name, value in ann.items(): - if value is None: - value = type(None) - if isinstance(value, str): - value = _ForwardRef(value) - value = _eval_type(value, base_globals, localns) - hints[name] = value - return hints - - if globalns is None: - if isinstance(obj, types.ModuleType): - globalns = obj.__dict__ - else: - globalns = getattr(obj, '__globals__', {}) - if localns is None: - localns = globalns - elif localns is None: - localns = globalns - hints = getattr(obj, '__annotations__', None) - if hints is None: - # Return empty annotations for something that _could_ have them. - if isinstance(obj, _allowed_types): - return {} - else: - raise TypeError('{!r} is not a module, class, method, ' - 'or function.'.format(obj)) - defaults = _get_defaults(obj) - hints = dict(hints) - for name, value in hints.items(): - if value is None: - value = type(None) - if isinstance(value, str): - value = _ForwardRef(value) - value = _eval_type(value, globalns, localns) - if name in defaults and defaults[name] is None: - value = Optional[value] - hints[name] = value - return hints - - -def no_type_check(arg): - """Decorator to indicate that annotations are not type hints. - - The argument must be a class or function; if it is a class, it - applies recursively to all methods and classes defined in that class - (but not to methods defined in its superclasses or subclasses). - - This mutates the function(s) or class(es) in place. - """ - if isinstance(arg, type): - arg_attrs = arg.__dict__.copy() - for attr, val in arg.__dict__.items(): - if val in arg.__bases__ + (arg,): - arg_attrs.pop(attr) - for obj in arg_attrs.values(): - if isinstance(obj, types.FunctionType): - obj.__no_type_check__ = True - if isinstance(obj, type): - no_type_check(obj) - try: - arg.__no_type_check__ = True - except TypeError: # built-in classes - pass - return arg - - -def no_type_check_decorator(decorator): - """Decorator to give another decorator the @no_type_check effect. - - This wraps the decorator with something that wraps the decorated - function in @no_type_check. - """ - - @functools.wraps(decorator) - def wrapped_decorator(*args, **kwds): - func = decorator(*args, **kwds) - func = no_type_check(func) - return func - - return wrapped_decorator - - -def _overload_dummy(*args, **kwds): - """Helper for @overload to raise when called.""" - raise NotImplementedError( - "You should not call an overloaded function. " - "A series of @overload-decorated functions " - "outside a stub module should always be followed " - "by an implementation that is not @overload-ed.") - - -def overload(func): - """Decorator for overloaded functions/methods. - - In a stub file, place two or more stub definitions for the same - function in a row, each decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - - In a non-stub file (i.e. a regular .py file), do the same but - follow it with an implementation. The implementation should *not* - be decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - def utf8(value): - # implementation goes here - """ - return _overload_dummy - - -class _ProtocolMeta(GenericMeta): - """Internal metaclass for _Protocol. - - This exists so _Protocol classes can be generic without deriving - from Generic. - """ - - def __instancecheck__(self, obj): - if _Protocol not in self.__bases__: - return super().__instancecheck__(obj) - raise TypeError("Protocols cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - if not self._is_protocol: - # No structural checks since this isn't a protocol. - return NotImplemented - - if self is _Protocol: - # Every class is a subclass of the empty protocol. - return True - - # Find all attributes defined in the protocol. - attrs = self._get_protocol_attrs() - - for attr in attrs: - if not any(attr in d.__dict__ for d in cls.__mro__): - return False - return True - - def _get_protocol_attrs(self): - # Get all Protocol base classes. - protocol_bases = [] - for c in self.__mro__: - if getattr(c, '_is_protocol', False) and c.__name__ != '_Protocol': - protocol_bases.append(c) - - # Get attributes included in protocol. - attrs = set() - for base in protocol_bases: - for attr in base.__dict__.keys(): - # Include attributes not defined in any non-protocol bases. - for c in self.__mro__: - if (c is not base and attr in c.__dict__ and - not getattr(c, '_is_protocol', False)): - break - else: - if (not attr.startswith('_abc_') and - attr != '__abstractmethods__' and - attr != '__annotations__' and - attr != '__weakref__' and - attr != '_is_protocol' and - attr != '_gorg' and - attr != '__dict__' and - attr != '__args__' and - attr != '__slots__' and - attr != '_get_protocol_attrs' and - attr != '__next_in_mro__' and - attr != '__parameters__' and - attr != '__origin__' and - attr != '__orig_bases__' and - attr != '__extra__' and - attr != '__tree_hash__' and - attr != '__module__'): - attrs.add(attr) - - return attrs - - -class _Protocol(metaclass=_ProtocolMeta): - """Internal base class for protocol classes. - - This implements a simple-minded structural issubclass check - (similar but more general than the one-offs in collections.abc - such as Hashable). - """ - - __slots__ = () - - _is_protocol = True - - -# Various ABCs mimicking those in collections.abc. -# A few are simply re-exported for completeness. - -Hashable = collections_abc.Hashable # Not generic. - - -if hasattr(collections_abc, 'Awaitable'): - class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): - __slots__ = () - - __all__.append('Awaitable') - - -if hasattr(collections_abc, 'Coroutine'): - class Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co], - extra=collections_abc.Coroutine): - __slots__ = () - - __all__.append('Coroutine') - - -if hasattr(collections_abc, 'AsyncIterable'): - - class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): - __slots__ = () - - class AsyncIterator(AsyncIterable[T_co], - extra=collections_abc.AsyncIterator): - __slots__ = () - - __all__.append('AsyncIterable') - __all__.append('AsyncIterator') - - -class Iterable(Generic[T_co], extra=collections_abc.Iterable): - __slots__ = () - - -class Iterator(Iterable[T_co], extra=collections_abc.Iterator): - __slots__ = () - - -class SupportsInt(_Protocol): - __slots__ = () - - @abstractmethod - def __int__(self) -> int: - pass - - -class SupportsFloat(_Protocol): - __slots__ = () - - @abstractmethod - def __float__(self) -> float: - pass - - -class SupportsComplex(_Protocol): - __slots__ = () - - @abstractmethod - def __complex__(self) -> complex: - pass - - -class SupportsBytes(_Protocol): - __slots__ = () - - @abstractmethod - def __bytes__(self) -> bytes: - pass - - -class SupportsAbs(_Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __abs__(self) -> T_co: - pass - - -class SupportsRound(_Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __round__(self, ndigits: int = 0) -> T_co: - pass - - -if hasattr(collections_abc, 'Reversible'): - class Reversible(Iterable[T_co], extra=collections_abc.Reversible): - __slots__ = () -else: - class Reversible(_Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __reversed__(self) -> 'Iterator[T_co]': - pass - - -Sized = collections_abc.Sized # Not generic. - - -class Container(Generic[T_co], extra=collections_abc.Container): - __slots__ = () - - -if hasattr(collections_abc, 'Collection'): - class Collection(Sized, Iterable[T_co], Container[T_co], - extra=collections_abc.Collection): - __slots__ = () - - __all__.append('Collection') - - -# Callable was defined earlier. - -if hasattr(collections_abc, 'Collection'): - class AbstractSet(Collection[T_co], - extra=collections_abc.Set): - __slots__ = () -else: - class AbstractSet(Sized, Iterable[T_co], Container[T_co], - extra=collections_abc.Set): - __slots__ = () - - -class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): - __slots__ = () - - -# NOTE: It is only covariant in the value type. -if hasattr(collections_abc, 'Collection'): - class Mapping(Collection[KT], Generic[KT, VT_co], - extra=collections_abc.Mapping): - __slots__ = () -else: - class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], - extra=collections_abc.Mapping): - __slots__ = () - - -class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): - __slots__ = () - - -if hasattr(collections_abc, 'Reversible'): - if hasattr(collections_abc, 'Collection'): - class Sequence(Reversible[T_co], Collection[T_co], - extra=collections_abc.Sequence): - __slots__ = () - else: - class Sequence(Sized, Reversible[T_co], Container[T_co], - extra=collections_abc.Sequence): - __slots__ = () -else: - class Sequence(Sized, Iterable[T_co], Container[T_co], - extra=collections_abc.Sequence): - __slots__ = () - - -class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): - __slots__ = () - - -class ByteString(Sequence[int], extra=collections_abc.ByteString): - __slots__ = () - - -class List(list, MutableSequence[T], extra=list): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is List: - raise TypeError("Type List cannot be instantiated; " - "use list() instead") - return _generic_new(list, cls, *args, **kwds) - - -class Deque(collections.deque, MutableSequence[T], extra=collections.deque): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Deque: - return collections.deque(*args, **kwds) - return _generic_new(collections.deque, cls, *args, **kwds) - - -class Set(set, MutableSet[T], extra=set): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Set: - raise TypeError("Type Set cannot be instantiated; " - "use set() instead") - return _generic_new(set, cls, *args, **kwds) - - -class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is FrozenSet: - raise TypeError("Type FrozenSet cannot be instantiated; " - "use frozenset() instead") - return _generic_new(frozenset, cls, *args, **kwds) - - -class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): - __slots__ = () - - -class KeysView(MappingView[KT], AbstractSet[KT], - extra=collections_abc.KeysView): - __slots__ = () - - -class ItemsView(MappingView[Tuple[KT, VT_co]], - AbstractSet[Tuple[KT, VT_co]], - Generic[KT, VT_co], - extra=collections_abc.ItemsView): - __slots__ = () - - -class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): - __slots__ = () - - -if hasattr(contextlib, 'AbstractContextManager'): - class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager): - __slots__ = () -else: - class ContextManager(Generic[T_co]): - __slots__ = () - - def __enter__(self): - return self - - @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is ContextManager: - # In Python 3.6+, it is possible to set a method to None to - # explicitly indicate that the class does not implement an ABC - # (https://bugs.python.org/issue25958), but we do not support - # that pattern here because this fallback class is only used - # in Python 3.5 and earlier. - if (any("__enter__" in B.__dict__ for B in C.__mro__) and - any("__exit__" in B.__dict__ for B in C.__mro__)): - return True - return NotImplemented - - -if hasattr(contextlib, 'AbstractAsyncContextManager'): - class AsyncContextManager(Generic[T_co], - extra=contextlib.AbstractAsyncContextManager): - __slots__ = () - - __all__.append('AsyncContextManager') -elif sys.version_info[:2] >= (3, 5): - exec(""" -class AsyncContextManager(Generic[T_co]): - __slots__ = () - - async def __aenter__(self): - return self - - @abc.abstractmethod - async def __aexit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncContextManager: - if sys.version_info[:2] >= (3, 6): - return _collections_abc._check_methods(C, "__aenter__", "__aexit__") - if (any("__aenter__" in B.__dict__ for B in C.__mro__) and - any("__aexit__" in B.__dict__ for B in C.__mro__)): - return True - return NotImplemented - -__all__.append('AsyncContextManager') -""") - - -class Dict(dict, MutableMapping[KT, VT], extra=dict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Dict: - raise TypeError("Type Dict cannot be instantiated; " - "use dict() instead") - return _generic_new(dict, cls, *args, **kwds) - - -class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], - extra=collections.defaultdict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is DefaultDict: - return collections.defaultdict(*args, **kwds) - return _generic_new(collections.defaultdict, cls, *args, **kwds) - - -class Counter(collections.Counter, Dict[T, int], extra=collections.Counter): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Counter: - return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - - -if hasattr(collections, 'ChainMap'): - # ChainMap only exists in 3.3+ - __all__.append('ChainMap') - - class ChainMap(collections.ChainMap, MutableMapping[KT, VT], - extra=collections.ChainMap): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is ChainMap: - return collections.ChainMap(*args, **kwds) - return _generic_new(collections.ChainMap, cls, *args, **kwds) - - -# Determine what base class to use for Generator. -if hasattr(collections_abc, 'Generator'): - # Sufficiently recent versions of 3.5 have a Generator ABC. - _G_base = collections_abc.Generator -else: - # Fall back on the exact type. - _G_base = types.GeneratorType - - -class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co], - extra=_G_base): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Generator: - raise TypeError("Type Generator cannot be instantiated; " - "create a subclass instead") - return _generic_new(_G_base, cls, *args, **kwds) - - -if hasattr(collections_abc, 'AsyncGenerator'): - class AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra], - extra=collections_abc.AsyncGenerator): - __slots__ = () - - __all__.append('AsyncGenerator') - - -# Internal type variable used for Type[]. -CT_co = TypeVar('CT_co', covariant=True, bound=type) - - -# This is not a real generic class. Don't use outside annotations. -class Type(Generic[CT_co], extra=type): - """A special construct usable to annotate class objects. - - For example, suppose we have the following classes:: - - class User: ... # Abstract base for User classes - class BasicUser(User): ... - class ProUser(User): ... - class TeamUser(User): ... - - And a function that takes a class argument that's a subclass of - User and returns an instance of the corresponding class:: - - U = TypeVar('U', bound=User) - def new_user(user_class: Type[U]) -> U: - user = user_class() - # (Here we could write the user object to a database) - return user - - joe = new_user(BasicUser) - - At this point the type checker knows that joe has type BasicUser. - """ - - __slots__ = () - - -def _make_nmtuple(name, types): - msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" - types = [(n, _type_check(t, msg)) for n, t in types] - nm_tpl = collections.namedtuple(name, [n for n, t in types]) - # Prior to PEP 526, only _field_types attribute was assigned. - # Now, both __annotations__ and _field_types are used to maintain compatibility. - nm_tpl.__annotations__ = nm_tpl._field_types = collections.OrderedDict(types) - try: - nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - return nm_tpl - - -_PY36 = sys.version_info[:2] >= (3, 6) - -# attributes prohibited to set in NamedTuple class syntax -_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__', - '_fields', '_field_defaults', '_field_types', - '_make', '_replace', '_asdict', '_source') - -_special = ('__module__', '__name__', '__qualname__', '__annotations__') - - -class NamedTupleMeta(type): - - def __new__(cls, typename, bases, ns): - if ns.get('_root', False): - return super().__new__(cls, typename, bases, ns) - if not _PY36: - raise TypeError("Class syntax for NamedTuple is only supported" - " in Python 3.6+") - types = ns.get('__annotations__', {}) - nm_tpl = _make_nmtuple(typename, types.items()) - defaults = [] - defaults_dict = {} - for field_name in types: - if field_name in ns: - default_value = ns[field_name] - defaults.append(default_value) - defaults_dict[field_name] = default_value - elif defaults: - raise TypeError("Non-default namedtuple field {field_name} cannot " - "follow default field(s) {default_names}" - .format(field_name=field_name, - default_names=', '.join(defaults_dict.keys()))) - nm_tpl.__new__.__annotations__ = collections.OrderedDict(types) - nm_tpl.__new__.__defaults__ = tuple(defaults) - nm_tpl._field_defaults = defaults_dict - # update from user namespace without overriding special namedtuple attributes - for key in ns: - if key in _prohibited: - raise AttributeError("Cannot overwrite NamedTuple attribute " + key) - elif key not in _special and key not in nm_tpl._fields: - setattr(nm_tpl, key, ns[key]) - return nm_tpl - - -class NamedTuple(metaclass=NamedTupleMeta): - """Typed version of namedtuple. - - Usage in Python versions >= 3.6:: - - class Employee(NamedTuple): - name: str - id: int - - This is equivalent to:: - - Employee = collections.namedtuple('Employee', ['name', 'id']) - - The resulting class has extra __annotations__ and _field_types - attributes, giving an ordered dict mapping field names to types. - __annotations__ should be preferred, while _field_types - is kept to maintain pre PEP 526 compatibility. (The field names - are in the _fields attribute, which is part of the namedtuple - API.) Alternative equivalent keyword syntax is also accepted:: - - Employee = NamedTuple('Employee', name=str, id=int) - - In Python versions <= 3.5 use:: - - Employee = NamedTuple('Employee', [('name', str), ('id', int)]) - """ - _root = True - - def __new__(self, typename, fields=None, **kwargs): - if kwargs and not _PY36: - raise TypeError("Keyword syntax for NamedTuple is only supported" - " in Python 3.6+") - if fields is None: - fields = kwargs.items() - elif kwargs: - raise TypeError("Either list of fields or keywords" - " can be provided to NamedTuple, not both") - return _make_nmtuple(typename, fields) - - -def NewType(name, tp): - """NewType creates simple unique types with almost zero - runtime overhead. NewType(name, tp) is considered a subtype of tp - by static type checkers. At runtime, NewType(name, tp) returns - a dummy function that simply returns its argument. Usage:: - - UserId = NewType('UserId', int) - - def name_by_id(user_id: UserId) -> str: - ... - - UserId('user') # Fails type check - - name_by_id(42) # Fails type check - name_by_id(UserId(42)) # OK - - num = UserId(5) + 1 # type: int - """ - - def new_type(x): - return x - - new_type.__name__ = name - new_type.__supertype__ = tp - return new_type - - -# Python-version-specific alias (Python 2: unicode; Python 3: str) -Text = str - - -# Constant that's True when type checking, but False here. -TYPE_CHECKING = False - - -class IO(Generic[AnyStr]): - """Generic base class for TextIO and BinaryIO. - - This is an abstract, generic version of the return of open(). - - NOTE: This does not distinguish between the different possible - classes (text vs. binary, read vs. write vs. read/write, - append-only, unbuffered). The TextIO and BinaryIO subclasses - below capture the distinctions between text vs. binary, which is - pervasive in the interface; however we currently do not offer a - way to track the other distinctions in the type system. - """ - - __slots__ = () - - @abstractproperty - def mode(self) -> str: - pass - - @abstractproperty - def name(self) -> str: - pass - - @abstractmethod - def close(self) -> None: - pass - - @abstractproperty - def closed(self) -> bool: - pass - - @abstractmethod - def fileno(self) -> int: - pass - - @abstractmethod - def flush(self) -> None: - pass - - @abstractmethod - def isatty(self) -> bool: - pass - - @abstractmethod - def read(self, n: int = -1) -> AnyStr: - pass - - @abstractmethod - def readable(self) -> bool: - pass - - @abstractmethod - def readline(self, limit: int = -1) -> AnyStr: - pass - - @abstractmethod - def readlines(self, hint: int = -1) -> List[AnyStr]: - pass - - @abstractmethod - def seek(self, offset: int, whence: int = 0) -> int: - pass - - @abstractmethod - def seekable(self) -> bool: - pass - - @abstractmethod - def tell(self) -> int: - pass - - @abstractmethod - def truncate(self, size: int = None) -> int: - pass - - @abstractmethod - def writable(self) -> bool: - pass - - @abstractmethod - def write(self, s: AnyStr) -> int: - pass - - @abstractmethod - def writelines(self, lines: List[AnyStr]) -> None: - pass - - @abstractmethod - def __enter__(self) -> 'IO[AnyStr]': - pass - - @abstractmethod - def __exit__(self, type, value, traceback) -> None: - pass - - -class BinaryIO(IO[bytes]): - """Typed version of the return of open() in binary mode.""" - - __slots__ = () - - @abstractmethod - def write(self, s: Union[bytes, bytearray]) -> int: - pass - - @abstractmethod - def __enter__(self) -> 'BinaryIO': - pass - - -class TextIO(IO[str]): - """Typed version of the return of open() in text mode.""" - - __slots__ = () - - @abstractproperty - def buffer(self) -> BinaryIO: - pass - - @abstractproperty - def encoding(self) -> str: - pass - - @abstractproperty - def errors(self) -> Optional[str]: - pass - - @abstractproperty - def line_buffering(self) -> bool: - pass - - @abstractproperty - def newlines(self) -> Any: - pass - - @abstractmethod - def __enter__(self) -> 'TextIO': - pass - - -class io: - """Wrapper namespace for IO generic classes.""" - - __all__ = ['IO', 'TextIO', 'BinaryIO'] - IO = IO - TextIO = TextIO - BinaryIO = BinaryIO - - -io.__name__ = __name__ + '.io' -sys.modules[io.__name__] = io - - -Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')), - lambda p: p.pattern) -Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')), - lambda m: m.re.pattern) - - -class re: - """Wrapper namespace for re type aliases.""" - - __all__ = ['Pattern', 'Match'] - Pattern = Pattern - Match = Match - - -re.__name__ = __name__ + '.re' -sys.modules[re.__name__] = re diff --git a/requirements.txt b/requirements.txt index 401cf01..f6af404 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ influxdb>=5.2.0 schedule>=0.5.0 configparser>=3.5.0 datetime>=4.3 -typing>=3.6.6 + From e55f04c63f5b2c33997f99c8ba99e98c63b755b1 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 2 Dec 2018 14:23:17 -0600 Subject: [PATCH 056/120] Revert "removed typing from requirements" This reverts commit 47538f52436c1e77d5d7f3138a7a19f2b082160a. --- lib/bin/chardetect.exe | Bin 93027 -> 93027 bytes lib/bin/easy_install-3.7.exe | Bin 93036 -> 93036 bytes lib/bin/easy_install.exe | Bin 93036 -> 93036 bytes lib/typing.py | 2413 ++++++++++++++++++++++++++++++++++ requirements.txt | 2 +- 5 files changed, 2414 insertions(+), 1 deletion(-) create mode 100644 lib/typing.py diff --git a/lib/bin/chardetect.exe b/lib/bin/chardetect.exe index a9fc779a1076cdc69541383d5cf5e89547c8ddab..de0cc0b540e59140cfc47c77857d9d89f61d5dc0 100644 GIT binary patch delta 27 gcmaESjrH+0)`l&N8d*$YIn%YW7{7sNrEEq=0H78L)Bpeg delta 27 gcmaESjrH+0)`l&N8d*&Gxzn|>7{7sNrEEq=0HRO|2mk;8 diff --git a/lib/bin/easy_install-3.7.exe b/lib/bin/easy_install-3.7.exe index d0bb68f3c1865069f51e2905948ac1581d6b1a76..4a61442578c13486b0fa276596393bcea183507a 100644 GIT binary patch delta 27 gcmaEJjrGkn)`l&N8d*$IIn%YW82^K4?QBLz0HUr600000 delta 27 gcmaEJjrGkn)`l&N8d*%nxzn|>82^K4?QBLz0Ho*&Gynhq diff --git a/lib/bin/easy_install.exe b/lib/bin/easy_install.exe index d0bb68f3c1865069f51e2905948ac1581d6b1a76..4a61442578c13486b0fa276596393bcea183507a 100644 GIT binary patch delta 27 gcmaEJjrGkn)`l&N8d*$IIn%YW82^K4?QBLz0HUr600000 delta 27 gcmaEJjrGkn)`l&N8d*%nxzn|>82^K4?QBLz0Ho*&Gynhq diff --git a/lib/typing.py b/lib/typing.py new file mode 100644 index 0000000..2189cd4 --- /dev/null +++ b/lib/typing.py @@ -0,0 +1,2413 @@ +import abc +from abc import abstractmethod, abstractproperty +import collections +import contextlib +import functools +import re as stdlib_re # Avoid confusion with the re we export. +import sys +import types +try: + import collections.abc as collections_abc +except ImportError: + import collections as collections_abc # Fallback for PY3.2. +if sys.version_info[:2] >= (3, 6): + import _collections_abc # Needed for private function _check_methods # noqa +try: + from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType +except ImportError: + WrapperDescriptorType = type(object.__init__) + MethodWrapperType = type(object().__str__) + MethodDescriptorType = type(str.join) + + +# Please keep __all__ alphabetized within each category. +__all__ = [ + # Super-special typing primitives. + 'Any', + 'Callable', + 'ClassVar', + 'Generic', + 'Optional', + 'Tuple', + 'Type', + 'TypeVar', + 'Union', + + # ABCs (from collections.abc). + 'AbstractSet', # collections.abc.Set. + 'GenericMeta', # subclass of abc.ABCMeta and a metaclass + # for 'Generic' and ABCs below. + 'ByteString', + 'Container', + 'ContextManager', + 'Hashable', + 'ItemsView', + 'Iterable', + 'Iterator', + 'KeysView', + 'Mapping', + 'MappingView', + 'MutableMapping', + 'MutableSequence', + 'MutableSet', + 'Sequence', + 'Sized', + 'ValuesView', + # The following are added depending on presence + # of their non-generic counterparts in stdlib: + # Awaitable, + # AsyncIterator, + # AsyncIterable, + # Coroutine, + # Collection, + # AsyncGenerator, + # AsyncContextManager + + # Structural checks, a.k.a. protocols. + 'Reversible', + 'SupportsAbs', + 'SupportsBytes', + 'SupportsComplex', + 'SupportsFloat', + 'SupportsInt', + 'SupportsRound', + + # Concrete collection types. + 'Counter', + 'Deque', + 'Dict', + 'DefaultDict', + 'List', + 'Set', + 'FrozenSet', + 'NamedTuple', # Not really a type. + 'Generator', + + # One-off things. + 'AnyStr', + 'cast', + 'get_type_hints', + 'NewType', + 'no_type_check', + 'no_type_check_decorator', + 'NoReturn', + 'overload', + 'Text', + 'TYPE_CHECKING', +] + +# The pseudo-submodules 're' and 'io' are part of the public +# namespace, but excluded from __all__ because they might stomp on +# legitimate imports of those modules. + + +def _qualname(x): + if sys.version_info[:2] >= (3, 3): + return x.__qualname__ + else: + # Fall back to just name. + return x.__name__ + + +def _trim_name(nm): + whitelist = ('_TypeAlias', '_ForwardRef', '_TypingBase', '_FinalTypingBase') + if nm.startswith('_') and nm not in whitelist: + nm = nm[1:] + return nm + + +class TypingMeta(type): + """Metaclass for most types defined in typing module + (not a part of public API). + + This overrides __new__() to require an extra keyword parameter + '_root', which serves as a guard against naive subclassing of the + typing classes. Any legitimate class defined using a metaclass + derived from TypingMeta must pass _root=True. + + This also defines a dummy constructor (all the work for most typing + constructs is done in __new__) and a nicer repr(). + """ + + _is_protocol = False + + def __new__(cls, name, bases, namespace, *, _root=False): + if not _root: + raise TypeError("Cannot subclass %s" % + (', '.join(map(_type_repr, bases)) or '()')) + return super().__new__(cls, name, bases, namespace) + + def __init__(self, *args, **kwds): + pass + + def _eval_type(self, globalns, localns): + """Override this in subclasses to interpret forward references. + + For example, List['C'] is internally stored as + List[_ForwardRef('C')], which should evaluate to List[C], + where C is an object found in globalns or localns (searching + localns first, of course). + """ + return self + + def _get_type_vars(self, tvars): + pass + + def __repr__(self): + qname = _trim_name(_qualname(self)) + return '%s.%s' % (self.__module__, qname) + + +class _TypingBase(metaclass=TypingMeta, _root=True): + """Internal indicator of special typing constructs.""" + + __slots__ = ('__weakref__',) + + def __init__(self, *args, **kwds): + pass + + def __new__(cls, *args, **kwds): + """Constructor. + + This only exists to give a better error message in case + someone tries to subclass a special typing object (not a good idea). + """ + if (len(args) == 3 and + isinstance(args[0], str) and + isinstance(args[1], tuple)): + # Close enough. + raise TypeError("Cannot subclass %r" % cls) + return super().__new__(cls) + + # Things that are not classes also need these. + def _eval_type(self, globalns, localns): + return self + + def _get_type_vars(self, tvars): + pass + + def __repr__(self): + cls = type(self) + qname = _trim_name(_qualname(cls)) + return '%s.%s' % (cls.__module__, qname) + + def __call__(self, *args, **kwds): + raise TypeError("Cannot instantiate %r" % type(self)) + + +class _FinalTypingBase(_TypingBase, _root=True): + """Internal mix-in class to prevent instantiation. + + Prevents instantiation unless _root=True is given in class call. + It is used to create pseudo-singleton instances Any, Union, Optional, etc. + """ + + __slots__ = () + + def __new__(cls, *args, _root=False, **kwds): + self = super().__new__(cls, *args, **kwds) + if _root is True: + return self + raise TypeError("Cannot instantiate %r" % cls) + + def __reduce__(self): + return _trim_name(type(self).__name__) + + +class _ForwardRef(_TypingBase, _root=True): + """Internal wrapper to hold a forward reference.""" + + __slots__ = ('__forward_arg__', '__forward_code__', + '__forward_evaluated__', '__forward_value__') + + def __init__(self, arg): + super().__init__(arg) + if not isinstance(arg, str): + raise TypeError('Forward reference must be a string -- got %r' % (arg,)) + try: + code = compile(arg, '<string>', 'eval') + except SyntaxError: + raise SyntaxError('Forward reference must be an expression -- got %r' % + (arg,)) + self.__forward_arg__ = arg + self.__forward_code__ = code + self.__forward_evaluated__ = False + self.__forward_value__ = None + + def _eval_type(self, globalns, localns): + if not self.__forward_evaluated__ or localns is not globalns: + if globalns is None and localns is None: + globalns = localns = {} + elif globalns is None: + globalns = localns + elif localns is None: + localns = globalns + self.__forward_value__ = _type_check( + eval(self.__forward_code__, globalns, localns), + "Forward references must evaluate to types.") + self.__forward_evaluated__ = True + return self.__forward_value__ + + def __eq__(self, other): + if not isinstance(other, _ForwardRef): + return NotImplemented + return (self.__forward_arg__ == other.__forward_arg__ and + self.__forward_value__ == other.__forward_value__) + + def __hash__(self): + return hash((self.__forward_arg__, self.__forward_value__)) + + def __instancecheck__(self, obj): + raise TypeError("Forward references cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Forward references cannot be used with issubclass().") + + def __repr__(self): + return '_ForwardRef(%r)' % (self.__forward_arg__,) + + +class _TypeAlias(_TypingBase, _root=True): + """Internal helper class for defining generic variants of concrete types. + + Note that this is not a type; let's call it a pseudo-type. It cannot + be used in instance and subclass checks in parameterized form, i.e. + ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning + ``False``. + """ + + __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') + + def __init__(self, name, type_var, impl_type, type_checker): + """Initializer. + + Args: + name: The name, e.g. 'Pattern'. + type_var: The type parameter, e.g. AnyStr, or the + specific type, e.g. str. + impl_type: The implementation type. + type_checker: Function that takes an impl_type instance. + and returns a value that should be a type_var instance. + """ + assert isinstance(name, str), repr(name) + assert isinstance(impl_type, type), repr(impl_type) + assert not isinstance(impl_type, TypingMeta), repr(impl_type) + assert isinstance(type_var, (type, _TypingBase)), repr(type_var) + self.name = name + self.type_var = type_var + self.impl_type = impl_type + self.type_checker = type_checker + + def __repr__(self): + return "%s[%s]" % (self.name, _type_repr(self.type_var)) + + def __getitem__(self, parameter): + if not isinstance(self.type_var, TypeVar): + raise TypeError("%s cannot be further parameterized." % self) + if self.type_var.__constraints__ and isinstance(parameter, type): + if not issubclass(parameter, self.type_var.__constraints__): + raise TypeError("%s is not a valid substitution for %s." % + (parameter, self.type_var)) + if isinstance(parameter, TypeVar) and parameter is not self.type_var: + raise TypeError("%s cannot be re-parameterized." % self) + return self.__class__(self.name, parameter, + self.impl_type, self.type_checker) + + def __eq__(self, other): + if not isinstance(other, _TypeAlias): + return NotImplemented + return self.name == other.name and self.type_var == other.type_var + + def __hash__(self): + return hash((self.name, self.type_var)) + + def __instancecheck__(self, obj): + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with isinstance().") + return isinstance(obj, self.impl_type) + + def __subclasscheck__(self, cls): + if not isinstance(self.type_var, TypeVar): + raise TypeError("Parameterized type aliases cannot be used " + "with issubclass().") + return issubclass(cls, self.impl_type) + + +def _get_type_vars(types, tvars): + for t in types: + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): + t._get_type_vars(tvars) + + +def _type_vars(types): + tvars = [] + _get_type_vars(types, tvars) + return tuple(tvars) + + +def _eval_type(t, globalns, localns): + if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): + return t._eval_type(globalns, localns) + return t + + +def _type_check(arg, msg): + """Check that the argument is a type, and return it (internal helper). + + As a special case, accept None and return type(None) instead. + Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable. + + The msg argument is a human-readable error message, e.g. + + "Union[arg, ...]: arg should be a type." + + We append the repr() of the actual value (truncated to 100 chars). + """ + if arg is None: + return type(None) + if isinstance(arg, str): + arg = _ForwardRef(arg) + if ( + isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or + not isinstance(arg, (type, _TypingBase)) and not callable(arg) + ): + raise TypeError(msg + " Got %.100r." % (arg,)) + # Bare Union etc. are not valid as type arguments + if ( + type(arg).__name__ in ('_Union', '_Optional') and + not getattr(arg, '__origin__', None) or + isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol) + ): + raise TypeError("Plain %s is not valid as type argument" % arg) + return arg + + +def _type_repr(obj): + """Return the repr() of an object, special-casing types (internal helper). + + If obj is a type, we return a shorter version than the default + type.__repr__, based on the module and qualified name, which is + typically enough to uniquely identify a type. For everything + else, we fall back on repr(obj). + """ + if isinstance(obj, type) and not isinstance(obj, TypingMeta): + if obj.__module__ == 'builtins': + return _qualname(obj) + return '%s.%s' % (obj.__module__, _qualname(obj)) + if obj is ...: + return('...') + if isinstance(obj, types.FunctionType): + return obj.__name__ + return repr(obj) + + +class _Any(_FinalTypingBase, _root=True): + """Special type indicating an unconstrained type. + + - Any is compatible with every type. + - Any assumed to have all methods. + - All values assumed to be instances of Any. + + Note that all the above statements are true from the point of view of + static type checkers. At runtime, Any should not be used with instance + or class checks. + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("Any cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Any cannot be used with issubclass().") + + +Any = _Any(_root=True) + + +class _NoReturn(_FinalTypingBase, _root=True): + """Special type indicating functions that never return. + Example:: + + from typing import NoReturn + + def stop() -> NoReturn: + raise Exception('no way') + + This type is invalid in other positions, e.g., ``List[NoReturn]`` + will fail in static type checkers. + """ + + __slots__ = () + + def __instancecheck__(self, obj): + raise TypeError("NoReturn cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("NoReturn cannot be used with issubclass().") + + +NoReturn = _NoReturn(_root=True) + + +class TypeVar(_TypingBase, _root=True): + """Type variable. + + Usage:: + + T = TypeVar('T') # Can be anything + A = TypeVar('A', str, bytes) # Must be str or bytes + + Type variables exist primarily for the benefit of static type + checkers. They serve as the parameters for generic types as well + as for generic function definitions. See class Generic for more + information on generic types. Generic functions work as follows: + + def repeat(x: T, n: int) -> List[T]: + '''Return a list containing n references to x.''' + return [x]*n + + def longest(x: A, y: A) -> A: + '''Return the longest of two strings.''' + return x if len(x) >= len(y) else y + + The latter example's signature is essentially the overloading + of (str, str) -> str and (bytes, bytes) -> bytes. Also note + that if the arguments are instances of some subclass of str, + the return type is still plain str. + + At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError. + + Type variables defined with covariant=True or contravariant=True + can be used do declare covariant or contravariant generic types. + See PEP 484 for more details. By default generic types are invariant + in all type variables. + + Type variables can be introspected. e.g.: + + T.__name__ == 'T' + T.__constraints__ == () + T.__covariant__ == False + T.__contravariant__ = False + A.__constraints__ == (str, bytes) + """ + + __slots__ = ('__name__', '__bound__', '__constraints__', + '__covariant__', '__contravariant__') + + def __init__(self, name, *constraints, bound=None, + covariant=False, contravariant=False): + super().__init__(name, *constraints, bound=bound, + covariant=covariant, contravariant=contravariant) + self.__name__ = name + if covariant and contravariant: + raise ValueError("Bivariant types are not supported.") + self.__covariant__ = bool(covariant) + self.__contravariant__ = bool(contravariant) + if constraints and bound is not None: + raise TypeError("Constraints cannot be combined with bound=...") + if constraints and len(constraints) == 1: + raise TypeError("A single constraint is not allowed") + msg = "TypeVar(name, constraint, ...): constraints must be types." + self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) + if bound: + self.__bound__ = _type_check(bound, "Bound must be a type.") + else: + self.__bound__ = None + + def _get_type_vars(self, tvars): + if self not in tvars: + tvars.append(self) + + def __repr__(self): + if self.__covariant__: + prefix = '+' + elif self.__contravariant__: + prefix = '-' + else: + prefix = '~' + return prefix + self.__name__ + + def __instancecheck__(self, instance): + raise TypeError("Type variables cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Type variables cannot be used with issubclass().") + + +# Some unconstrained type variables. These are used by the container types. +# (These are not for export.) +T = TypeVar('T') # Any type. +KT = TypeVar('KT') # Key type. +VT = TypeVar('VT') # Value type. +T_co = TypeVar('T_co', covariant=True) # Any type covariant containers. +V_co = TypeVar('V_co', covariant=True) # Any type covariant containers. +VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers. +T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. + +# A useful type variable with constraints. This represents string types. +# (This one *is* for export!) +AnyStr = TypeVar('AnyStr', bytes, str) + + +def _replace_arg(arg, tvars, args): + """An internal helper function: replace arg if it is a type variable + found in tvars with corresponding substitution from args or + with corresponding substitution sub-tree if arg is a generic type. + """ + + if tvars is None: + tvars = [] + if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)): + return arg._subs_tree(tvars, args) + if isinstance(arg, TypeVar): + for i, tvar in enumerate(tvars): + if arg == tvar: + return args[i] + return arg + + +# Special typing constructs Union, Optional, Generic, Callable and Tuple +# use three special attributes for internal bookkeeping of generic types: +# * __parameters__ is a tuple of unique free type parameters of a generic +# type, for example, Dict[T, T].__parameters__ == (T,); +# * __origin__ keeps a reference to a type that was subscripted, +# e.g., Union[T, int].__origin__ == Union; +# * __args__ is a tuple of all arguments used in subscripting, +# e.g., Dict[T, int].__args__ == (T, int). + + +def _subs_tree(cls, tvars=None, args=None): + """An internal helper function: calculate substitution tree + for generic cls after replacing its type parameters with + substitutions in tvars -> args (if any). + Repeat the same following __origin__'s. + + Return a list of arguments with all possible substitutions + performed. Arguments that are generic classes themselves are represented + as tuples (so that no new classes are created by this function). + For example: _subs_tree(List[Tuple[int, T]][str]) == [(Tuple, int, str)] + """ + + if cls.__origin__ is None: + return cls + # Make of chain of origins (i.e. cls -> cls.__origin__) + current = cls.__origin__ + orig_chain = [] + while current.__origin__ is not None: + orig_chain.append(current) + current = current.__origin__ + # Replace type variables in __args__ if asked ... + tree_args = [] + for arg in cls.__args__: + tree_args.append(_replace_arg(arg, tvars, args)) + # ... then continue replacing down the origin chain. + for ocls in orig_chain: + new_tree_args = [] + for arg in ocls.__args__: + new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) + tree_args = new_tree_args + return tree_args + + +def _remove_dups_flatten(parameters): + """An internal helper for Union creation and substitution: flatten Union's + among parameters, then remove duplicates and strict subclasses. + """ + + # Flatten out Union[Union[...], ...]. + params = [] + for p in parameters: + if isinstance(p, _Union) and p.__origin__ is Union: + params.extend(p.__args__) + elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union: + params.extend(p[1:]) + else: + params.append(p) + # Weed out strict duplicates, preserving the first of each occurrence. + all_params = set(params) + if len(all_params) < len(params): + new_params = [] + for t in params: + if t in all_params: + new_params.append(t) + all_params.remove(t) + params = new_params + assert not all_params, all_params + # Weed out subclasses. + # E.g. Union[int, Employee, Manager] == Union[int, Employee]. + # If object is present it will be sole survivor among proper classes. + # Never discard type variables. + # (In particular, Union[str, AnyStr] != AnyStr.) + all_params = set(params) + for t1 in params: + if not isinstance(t1, type): + continue + if any(isinstance(t2, type) and issubclass(t1, t2) + for t2 in all_params - {t1} + if not (isinstance(t2, GenericMeta) and + t2.__origin__ is not None)): + all_params.remove(t1) + return tuple(t for t in params if t in all_params) + + +def _check_generic(cls, parameters): + # Check correct count for parameters of a generic cls (internal helper). + if not cls.__parameters__: + raise TypeError("%s is not a generic class" % repr(cls)) + alen = len(parameters) + elen = len(cls.__parameters__) + if alen != elen: + raise TypeError("Too %s parameters for %s; actual %s, expected %s" % + ("many" if alen > elen else "few", repr(cls), alen, elen)) + + +_cleanups = [] + + +def _tp_cache(func): + """Internal wrapper caching __getitem__ of generic types with a fallback to + original function for non-hashable arguments. + """ + + cached = functools.lru_cache()(func) + _cleanups.append(cached.cache_clear) + + @functools.wraps(func) + def inner(*args, **kwds): + try: + return cached(*args, **kwds) + except TypeError: + pass # All real errors (not unhashable args) are raised below. + return func(*args, **kwds) + return inner + + +class _Union(_FinalTypingBase, _root=True): + """Union type; Union[X, Y] means either X or Y. + + To define a union, use e.g. Union[int, str]. Details: + + - The arguments must be types and there must be at least one. + + - None as an argument is a special case and is replaced by + type(None). + + - Unions of unions are flattened, e.g.:: + + Union[Union[int, str], float] == Union[int, str, float] + + - Unions of a single argument vanish, e.g.:: + + Union[int] == int # The constructor actually returns int + + - Redundant arguments are skipped, e.g.:: + + Union[int, str, int] == Union[int, str] + + - When comparing unions, the argument order is ignored, e.g.:: + + Union[int, str] == Union[str, int] + + - When two arguments have a subclass relationship, the least + derived argument is kept, e.g.:: + + class Employee: pass + class Manager(Employee): pass + Union[int, Employee, Manager] == Union[int, Employee] + Union[Manager, int, Employee] == Union[int, Employee] + Union[Employee, Manager] == Employee + + - Similar for object:: + + Union[int, object] == object + + - You cannot subclass or instantiate a union. + + - You can use Optional[X] as a shorthand for Union[X, None]. + """ + + __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') + + def __new__(cls, parameters=None, origin=None, *args, _root=False): + self = super().__new__(cls, parameters, origin, *args, _root=_root) + if origin is None: + self.__parameters__ = None + self.__args__ = None + self.__origin__ = None + self.__tree_hash__ = hash(frozenset(('Union',))) + return self + if not isinstance(parameters, tuple): + raise TypeError("Expected parameters=<tuple>") + if origin is Union: + parameters = _remove_dups_flatten(parameters) + # It's not a union if there's only one type left. + if len(parameters) == 1: + return parameters[0] + self.__parameters__ = _type_vars(parameters) + self.__args__ = parameters + self.__origin__ = origin + # Pre-calculate the __hash__ on instantiation. + # This improves speed for complex substitutions. + subs_tree = self._subs_tree() + if isinstance(subs_tree, tuple): + self.__tree_hash__ = hash(frozenset(subs_tree)) + else: + self.__tree_hash__ = hash(subs_tree) + return self + + def _eval_type(self, globalns, localns): + if self.__args__ is None: + return self + ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) + ev_origin = _eval_type(self.__origin__, globalns, localns) + if ev_args == self.__args__ and ev_origin == self.__origin__: + # Everything is already evaluated. + return self + return self.__class__(ev_args, ev_origin, _root=True) + + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) + + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + tree = self._subs_tree() + if not isinstance(tree, tuple): + return repr(tree) + return tree[0]._tree_repr(tree) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super().__repr__() + '[%s]' % ', '.join(arg_list) + + @_tp_cache + def __getitem__(self, parameters): + if parameters == (): + raise TypeError("Cannot take a Union of no types.") + if not isinstance(parameters, tuple): + parameters = (parameters,) + if self.__origin__ is None: + msg = "Union[arg, ...]: each arg must be a type." + else: + msg = "Parameters to generic types must be types." + parameters = tuple(_type_check(p, msg) for p in parameters) + if self is not Union: + _check_generic(self, parameters) + return self.__class__(parameters, origin=self, _root=True) + + def _subs_tree(self, tvars=None, args=None): + if self is Union: + return Union # Nothing to substitute + tree_args = _subs_tree(self, tvars, args) + tree_args = _remove_dups_flatten(tree_args) + if len(tree_args) == 1: + return tree_args[0] # Union of a single type is that type + return (Union,) + tree_args + + def __eq__(self, other): + if isinstance(other, _Union): + return self.__tree_hash__ == other.__tree_hash__ + elif self is not Union: + return self._subs_tree() == other + else: + return self is other + + def __hash__(self): + return self.__tree_hash__ + + def __instancecheck__(self, obj): + raise TypeError("Unions cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + raise TypeError("Unions cannot be used with issubclass().") + + +Union = _Union(_root=True) + + +class _Optional(_FinalTypingBase, _root=True): + """Optional type. + + Optional[X] is equivalent to Union[X, None]. + """ + + __slots__ = () + + @_tp_cache + def __getitem__(self, arg): + arg = _type_check(arg, "Optional[t] requires a single type.") + return Union[arg, type(None)] + + +Optional = _Optional(_root=True) + + +def _next_in_mro(cls): + """Helper for Generic.__new__. + + Returns the class after the last occurrence of Generic or + Generic[...] in cls.__mro__. + """ + next_in_mro = object + # Look for the last occurrence of Generic or Generic[...]. + for i, c in enumerate(cls.__mro__[:-1]): + if isinstance(c, GenericMeta) and c._gorg is Generic: + next_in_mro = cls.__mro__[i + 1] + return next_in_mro + + +def _make_subclasshook(cls): + """Construct a __subclasshook__ callable that incorporates + the associated __extra__ class in subclass checks performed + against cls. + """ + if isinstance(cls.__extra__, abc.ABCMeta): + # The logic mirrors that of ABCMeta.__subclasscheck__. + # Registered classes need not be checked here because + # cls and its extra share the same _abc_registry. + def __extrahook__(subclass): + res = cls.__extra__.__subclasshook__(subclass) + if res is not NotImplemented: + return res + if cls.__extra__ in subclass.__mro__: + return True + for scls in cls.__extra__.__subclasses__(): + if isinstance(scls, GenericMeta): + continue + if issubclass(subclass, scls): + return True + return NotImplemented + else: + # For non-ABC extras we'll just call issubclass(). + def __extrahook__(subclass): + if cls.__extra__ and issubclass(subclass, cls.__extra__): + return True + return NotImplemented + return __extrahook__ + + +def _no_slots_copy(dct): + """Internal helper: copy class __dict__ and clean slots class variables. + (They will be re-created if necessary by normal class machinery.) + """ + dict_copy = dict(dct) + if '__slots__' in dict_copy: + for slot in dict_copy['__slots__']: + dict_copy.pop(slot, None) + return dict_copy + + +class GenericMeta(TypingMeta, abc.ABCMeta): + """Metaclass for generic types. + + This is a metaclass for typing.Generic and generic ABCs defined in + typing module. User defined subclasses of GenericMeta can override + __new__ and invoke super().__new__. Note that GenericMeta.__new__ + has strict rules on what is allowed in its bases argument: + * plain Generic is disallowed in bases; + * Generic[...] should appear in bases at most once; + * if Generic[...] is present, then it should list all type variables + that appear in other bases. + In addition, type of all generic bases is erased, e.g., C[int] is + stripped to plain C. + """ + + def __new__(cls, name, bases, namespace, + tvars=None, args=None, origin=None, extra=None, orig_bases=None): + """Create a new generic class. GenericMeta.__new__ accepts + keyword arguments that are used for internal bookkeeping, therefore + an override should pass unused keyword arguments to super(). + """ + if tvars is not None: + # Called from __getitem__() below. + assert origin is not None + assert all(isinstance(t, TypeVar) for t in tvars), tvars + else: + # Called from class statement. + assert tvars is None, tvars + assert args is None, args + assert origin is None, origin + + # Get the full set of tvars from the bases. + tvars = _type_vars(bases) + # Look for Generic[T1, ..., Tn]. + # If found, tvars must be a subset of it. + # If not found, tvars is it. + # Also check for and reject plain Generic, + # and reject multiple Generic[...]. + gvars = None + for base in bases: + if base is Generic: + raise TypeError("Cannot inherit from plain Generic") + if (isinstance(base, GenericMeta) and + base.__origin__ is Generic): + if gvars is not None: + raise TypeError( + "Cannot inherit from Generic[...] multiple types.") + gvars = base.__parameters__ + if gvars is None: + gvars = tvars + else: + tvarset = set(tvars) + gvarset = set(gvars) + if not tvarset <= gvarset: + raise TypeError( + "Some type variables (%s) " + "are not listed in Generic[%s]" % + (", ".join(str(t) for t in tvars if t not in gvarset), + ", ".join(str(g) for g in gvars))) + tvars = gvars + + initial_bases = bases + if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: + bases = (extra,) + bases + bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) + + # remove bare Generic from bases if there are other generic bases + if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): + bases = tuple(b for b in bases if b is not Generic) + namespace.update({'__origin__': origin, '__extra__': extra, + '_gorg': None if not origin else origin._gorg}) + self = super().__new__(cls, name, bases, namespace, _root=True) + super(GenericMeta, self).__setattr__('_gorg', + self if not origin else origin._gorg) + self.__parameters__ = tvars + # Be prepared that GenericMeta will be subclassed by TupleMeta + # and CallableMeta, those two allow ..., (), or [] in __args___. + self.__args__ = tuple(... if a is _TypingEllipsis else + () if a is _TypingEmpty else + a for a in args) if args else None + # Speed hack (https://github.com/python/typing/issues/196). + self.__next_in_mro__ = _next_in_mro(self) + # Preserve base classes on subclassing (__bases__ are type erased now). + if orig_bases is None: + self.__orig_bases__ = initial_bases + + # This allows unparameterized generic collections to be used + # with issubclass() and isinstance() in the same way as their + # collections.abc counterparts (e.g., isinstance([], Iterable)). + if ( + '__subclasshook__' not in namespace and extra or + # allow overriding + getattr(self.__subclasshook__, '__name__', '') == '__extrahook__' + ): + self.__subclasshook__ = _make_subclasshook(self) + if isinstance(extra, abc.ABCMeta): + self._abc_registry = extra._abc_registry + self._abc_cache = extra._abc_cache + elif origin is not None: + self._abc_registry = origin._abc_registry + self._abc_cache = origin._abc_cache + + if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. + self.__qualname__ = origin.__qualname__ + self.__tree_hash__ = (hash(self._subs_tree()) if origin else + super(GenericMeta, self).__hash__()) + return self + + # _abc_negative_cache and _abc_negative_cache_version + # realised as descriptors, since GenClass[t1, t2, ...] always + # share subclass info with GenClass. + # This is an important memory optimization. + @property + def _abc_negative_cache(self): + if isinstance(self.__extra__, abc.ABCMeta): + return self.__extra__._abc_negative_cache + return self._gorg._abc_generic_negative_cache + + @_abc_negative_cache.setter + def _abc_negative_cache(self, value): + if self.__origin__ is None: + if isinstance(self.__extra__, abc.ABCMeta): + self.__extra__._abc_negative_cache = value + else: + self._abc_generic_negative_cache = value + + @property + def _abc_negative_cache_version(self): + if isinstance(self.__extra__, abc.ABCMeta): + return self.__extra__._abc_negative_cache_version + return self._gorg._abc_generic_negative_cache_version + + @_abc_negative_cache_version.setter + def _abc_negative_cache_version(self, value): + if self.__origin__ is None: + if isinstance(self.__extra__, abc.ABCMeta): + self.__extra__._abc_negative_cache_version = value + else: + self._abc_generic_negative_cache_version = value + + def _get_type_vars(self, tvars): + if self.__origin__ and self.__parameters__: + _get_type_vars(self.__parameters__, tvars) + + def _eval_type(self, globalns, localns): + ev_origin = (self.__origin__._eval_type(globalns, localns) + if self.__origin__ else None) + ev_args = tuple(_eval_type(a, globalns, localns) for a + in self.__args__) if self.__args__ else None + if ev_origin == self.__origin__ and ev_args == self.__args__: + return self + return self.__class__(self.__name__, + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=_type_vars(ev_args) if ev_args else None, + args=ev_args, + origin=ev_origin, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + return self._tree_repr(self._subs_tree()) + + def _tree_repr(self, tree): + arg_list = [] + for arg in tree[1:]: + if arg == (): + arg_list.append('()') + elif not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + return super().__repr__() + '[%s]' % ', '.join(arg_list) + + def _subs_tree(self, tvars=None, args=None): + if self.__origin__ is None: + return self + tree_args = _subs_tree(self, tvars, args) + return (self._gorg,) + tuple(tree_args) + + def __eq__(self, other): + if not isinstance(other, GenericMeta): + return NotImplemented + if self.__origin__ is None or other.__origin__ is None: + return self is other + return self.__tree_hash__ == other.__tree_hash__ + + def __hash__(self): + return self.__tree_hash__ + + @_tp_cache + def __getitem__(self, params): + if not isinstance(params, tuple): + params = (params,) + if not params and self._gorg is not Tuple: + raise TypeError( + "Parameter list to %s[...] cannot be empty" % _qualname(self)) + msg = "Parameters to generic types must be types." + params = tuple(_type_check(p, msg) for p in params) + if self is Generic: + # Generic can only be subscripted with unique type variables. + if not all(isinstance(p, TypeVar) for p in params): + raise TypeError( + "Parameters to Generic[...] must all be type variables") + if len(set(params)) != len(params): + raise TypeError( + "Parameters to Generic[...] must all be unique") + tvars = params + args = params + elif self in (Tuple, Callable): + tvars = _type_vars(params) + args = params + elif self is _Protocol: + # _Protocol is internal, don't check anything. + tvars = params + args = params + elif self.__origin__ in (Generic, _Protocol): + # Can't subscript Generic[...] or _Protocol[...]. + raise TypeError("Cannot subscript already-subscripted %s" % + repr(self)) + else: + # Subscripting a regular Generic subclass. + _check_generic(self, params) + tvars = _type_vars(params) + args = params + + prepend = (self,) if self.__origin__ is None else () + return self.__class__(self.__name__, + prepend + self.__bases__, + _no_slots_copy(self.__dict__), + tvars=tvars, + args=args, + origin=self, + extra=self.__extra__, + orig_bases=self.__orig_bases__) + + def __subclasscheck__(self, cls): + if self.__origin__ is not None: + if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: + raise TypeError("Parameterized generics cannot be used with class " + "or instance checks") + return False + if self is Generic: + raise TypeError("Class %r cannot be used with class " + "or instance checks" % self) + return super().__subclasscheck__(cls) + + def __instancecheck__(self, instance): + # Since we extend ABC.__subclasscheck__ and + # ABC.__instancecheck__ inlines the cache checking done by the + # latter, we must extend __instancecheck__ too. For simplicity + # we just skip the cache check -- instance checks for generic + # classes are supposed to be rare anyways. + return issubclass(instance.__class__, self) + + def __setattr__(self, attr, value): + # We consider all the subscripted generics as proxies for original class + if ( + attr.startswith('__') and attr.endswith('__') or + attr.startswith('_abc_') or + self._gorg is None # The class is not fully created, see #typing/506 + ): + super(GenericMeta, self).__setattr__(attr, value) + else: + super(GenericMeta, self._gorg).__setattr__(attr, value) + + +# Prevent checks for Generic to crash when defining Generic. +Generic = None + + +def _generic_new(base_cls, cls, *args, **kwds): + # Assure type is erased on instantiation, + # but attempt to store it in __orig_class__ + if cls.__origin__ is None: + if (base_cls.__new__ is object.__new__ and + cls.__init__ is not object.__init__): + return base_cls.__new__(cls) + else: + return base_cls.__new__(cls, *args, **kwds) + else: + origin = cls._gorg + if (base_cls.__new__ is object.__new__ and + cls.__init__ is not object.__init__): + obj = base_cls.__new__(origin) + else: + obj = base_cls.__new__(origin, *args, **kwds) + try: + obj.__orig_class__ = cls + except AttributeError: + pass + obj.__init__(*args, **kwds) + return obj + + +class Generic(metaclass=GenericMeta): + """Abstract base class for generic types. + + A generic type is typically declared by inheriting from + this class parameterized with one or more type variables. + For example, a generic mapping type might be defined as:: + + class Mapping(Generic[KT, VT]): + def __getitem__(self, key: KT) -> VT: + ... + # Etc. + + This class can then be used as follows:: + + def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: + try: + return mapping[key] + except KeyError: + return default + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Generic: + raise TypeError("Type Generic cannot be instantiated; " + "it can be used only as a base class") + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +class _TypingEmpty: + """Internal placeholder for () or []. Used by TupleMeta and CallableMeta + to allow empty list/tuple in specific places, without allowing them + to sneak in where prohibited. + """ + + +class _TypingEllipsis: + """Internal placeholder for ... (ellipsis).""" + + +class TupleMeta(GenericMeta): + """Metaclass for Tuple (internal).""" + + @_tp_cache + def __getitem__(self, parameters): + if self.__origin__ is not None or self._gorg is not Tuple: + # Normal generic rules apply if this is not the first subscription + # or a subscription of a subclass. + return super().__getitem__(parameters) + if parameters == (): + return super().__getitem__((_TypingEmpty,)) + if not isinstance(parameters, tuple): + parameters = (parameters,) + if len(parameters) == 2 and parameters[1] is ...: + msg = "Tuple[t, ...]: t must be a type." + p = _type_check(parameters[0], msg) + return super().__getitem__((p, _TypingEllipsis)) + msg = "Tuple[t0, t1, ...]: each t must be a type." + parameters = tuple(_type_check(p, msg) for p in parameters) + return super().__getitem__(parameters) + + def __instancecheck__(self, obj): + if self.__args__ is None: + return isinstance(obj, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with isinstance().") + + def __subclasscheck__(self, cls): + if self.__args__ is None: + return issubclass(cls, tuple) + raise TypeError("Parameterized Tuple cannot be used " + "with issubclass().") + + +class Tuple(tuple, extra=tuple, metaclass=TupleMeta): + """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. + + Example: Tuple[T1, T2] is a tuple of two elements corresponding + to type variables T1 and T2. Tuple[int, float, str] is a tuple + of an int, a float and a string. + + To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Tuple: + raise TypeError("Type Tuple cannot be instantiated; " + "use tuple() instead") + return _generic_new(tuple, cls, *args, **kwds) + + +class CallableMeta(GenericMeta): + """Metaclass for Callable (internal).""" + + def __repr__(self): + if self.__origin__ is None: + return super().__repr__() + return self._tree_repr(self._subs_tree()) + + def _tree_repr(self, tree): + if self._gorg is not Callable: + return super()._tree_repr(tree) + # For actual Callable (not its subclass) we override + # super()._tree_repr() for nice formatting. + arg_list = [] + for arg in tree[1:]: + if not isinstance(arg, tuple): + arg_list.append(_type_repr(arg)) + else: + arg_list.append(arg[0]._tree_repr(arg)) + if arg_list[0] == '...': + return repr(tree[0]) + '[..., %s]' % arg_list[1] + return (repr(tree[0]) + + '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) + + def __getitem__(self, parameters): + """A thin wrapper around __getitem_inner__ to provide the latter + with hashable arguments to improve speed. + """ + + if self.__origin__ is not None or self._gorg is not Callable: + return super().__getitem__(parameters) + if not isinstance(parameters, tuple) or len(parameters) != 2: + raise TypeError("Callable must be used as " + "Callable[[arg, ...], result].") + args, result = parameters + if args is Ellipsis: + parameters = (Ellipsis, result) + else: + if not isinstance(args, list): + raise TypeError("Callable[args, result]: args must be a list." + " Got %.100r." % (args,)) + parameters = (tuple(args), result) + return self.__getitem_inner__(parameters) + + @_tp_cache + def __getitem_inner__(self, parameters): + args, result = parameters + msg = "Callable[args, result]: result must be a type." + result = _type_check(result, msg) + if args is Ellipsis: + return super().__getitem__((_TypingEllipsis, result)) + msg = "Callable[[arg, ...], result]: each arg must be a type." + args = tuple(_type_check(arg, msg) for arg in args) + parameters = args + (result,) + return super().__getitem__(parameters) + + +class Callable(extra=collections_abc.Callable, metaclass=CallableMeta): + """Callable type; Callable[[int], str] is a function of (int) -> str. + + The subscription syntax must always be used with exactly two + values: the argument list and the return type. The argument list + must be a list of types or ellipsis; the return type must be a single type. + + There is no syntax to indicate optional or keyword arguments, + such function types are rarely used as callback types. + """ + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Callable: + raise TypeError("Type Callable cannot be instantiated; " + "use a non-abstract subclass instead") + return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) + + +class _ClassVar(_FinalTypingBase, _root=True): + """Special type construct to mark class variables. + + An annotation wrapped in ClassVar indicates that a given + attribute is intended to be used as a class variable and + should not be set on instances of that class. Usage:: + + class Starship: + stats: ClassVar[Dict[str, int]] = {} # class variable + damage: int = 10 # instance variable + + ClassVar accepts only types and cannot be further subscribed. + + Note that ClassVar is not a class itself, and should not + be used with isinstance() or issubclass(). + """ + + __slots__ = ('__type__',) + + def __init__(self, tp=None, **kwds): + self.__type__ = tp + + def __getitem__(self, item): + cls = type(self) + if self.__type__ is None: + return cls(_type_check(item, + '{} accepts only single type.'.format(cls.__name__[1:])), + _root=True) + raise TypeError('{} cannot be further subscripted' + .format(cls.__name__[1:])) + + def _eval_type(self, globalns, localns): + new_tp = _eval_type(self.__type__, globalns, localns) + if new_tp == self.__type__: + return self + return type(self)(new_tp, _root=True) + + def __repr__(self): + r = super().__repr__() + if self.__type__ is not None: + r += '[{}]'.format(_type_repr(self.__type__)) + return r + + def __hash__(self): + return hash((type(self).__name__, self.__type__)) + + def __eq__(self, other): + if not isinstance(other, _ClassVar): + return NotImplemented + if self.__type__ is not None: + return self.__type__ == other.__type__ + return self is other + + +ClassVar = _ClassVar(_root=True) + + +def cast(typ, val): + """Cast a value to a type. + + This returns the value unchanged. To the type checker this + signals that the return value has the designated type, but at + runtime we intentionally don't check anything (we want this + to be as fast as possible). + """ + return val + + +def _get_defaults(func): + """Internal helper to extract the default arguments, by name.""" + try: + code = func.__code__ + except AttributeError: + # Some built-in functions don't have __code__, __defaults__, etc. + return {} + pos_count = code.co_argcount + arg_names = code.co_varnames + arg_names = arg_names[:pos_count] + defaults = func.__defaults__ or () + kwdefaults = func.__kwdefaults__ + res = dict(kwdefaults) if kwdefaults else {} + pos_offset = pos_count - len(defaults) + for name, value in zip(arg_names[pos_offset:], defaults): + assert name not in res + res[name] = value + return res + + +_allowed_types = (types.FunctionType, types.BuiltinFunctionType, + types.MethodType, types.ModuleType, + WrapperDescriptorType, MethodWrapperType, MethodDescriptorType) + + +def get_type_hints(obj, globalns=None, localns=None): + """Return type hints for an object. + + This is often the same as obj.__annotations__, but it handles + forward references encoded as string literals, and if necessary + adds Optional[t] if a default value equal to None is set. + + The argument may be a module, class, method, or function. The annotations + are returned as a dictionary. For classes, annotations include also + inherited members. + + TypeError is raised if the argument is not of a type that can contain + annotations, and an empty dictionary is returned if no annotations are + present. + + BEWARE -- the behavior of globalns and localns is counterintuitive + (unless you are familiar with how eval() and exec() work). The + search order is locals first, then globals. + + - If no dict arguments are passed, an attempt is made to use the + globals from obj (or the respective module's globals for classes), + and these are also used as the locals. If the object does not appear + to have globals, an empty dictionary is used. + + - If one dict argument is passed, it is used for both globals and + locals. + + - If two dict arguments are passed, they specify globals and + locals, respectively. + """ + + if getattr(obj, '__no_type_check__', None): + return {} + # Classes require a special treatment. + if isinstance(obj, type): + hints = {} + for base in reversed(obj.__mro__): + if globalns is None: + base_globals = sys.modules[base.__module__].__dict__ + else: + base_globals = globalns + ann = base.__dict__.get('__annotations__', {}) + for name, value in ann.items(): + if value is None: + value = type(None) + if isinstance(value, str): + value = _ForwardRef(value) + value = _eval_type(value, base_globals, localns) + hints[name] = value + return hints + + if globalns is None: + if isinstance(obj, types.ModuleType): + globalns = obj.__dict__ + else: + globalns = getattr(obj, '__globals__', {}) + if localns is None: + localns = globalns + elif localns is None: + localns = globalns + hints = getattr(obj, '__annotations__', None) + if hints is None: + # Return empty annotations for something that _could_ have them. + if isinstance(obj, _allowed_types): + return {} + else: + raise TypeError('{!r} is not a module, class, method, ' + 'or function.'.format(obj)) + defaults = _get_defaults(obj) + hints = dict(hints) + for name, value in hints.items(): + if value is None: + value = type(None) + if isinstance(value, str): + value = _ForwardRef(value) + value = _eval_type(value, globalns, localns) + if name in defaults and defaults[name] is None: + value = Optional[value] + hints[name] = value + return hints + + +def no_type_check(arg): + """Decorator to indicate that annotations are not type hints. + + The argument must be a class or function; if it is a class, it + applies recursively to all methods and classes defined in that class + (but not to methods defined in its superclasses or subclasses). + + This mutates the function(s) or class(es) in place. + """ + if isinstance(arg, type): + arg_attrs = arg.__dict__.copy() + for attr, val in arg.__dict__.items(): + if val in arg.__bases__ + (arg,): + arg_attrs.pop(attr) + for obj in arg_attrs.values(): + if isinstance(obj, types.FunctionType): + obj.__no_type_check__ = True + if isinstance(obj, type): + no_type_check(obj) + try: + arg.__no_type_check__ = True + except TypeError: # built-in classes + pass + return arg + + +def no_type_check_decorator(decorator): + """Decorator to give another decorator the @no_type_check effect. + + This wraps the decorator with something that wraps the decorated + function in @no_type_check. + """ + + @functools.wraps(decorator) + def wrapped_decorator(*args, **kwds): + func = decorator(*args, **kwds) + func = no_type_check(func) + return func + + return wrapped_decorator + + +def _overload_dummy(*args, **kwds): + """Helper for @overload to raise when called.""" + raise NotImplementedError( + "You should not call an overloaded function. " + "A series of @overload-decorated functions " + "outside a stub module should always be followed " + "by an implementation that is not @overload-ed.") + + +def overload(func): + """Decorator for overloaded functions/methods. + + In a stub file, place two or more stub definitions for the same + function in a row, each decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + + In a non-stub file (i.e. a regular .py file), do the same but + follow it with an implementation. The implementation should *not* + be decorated with @overload. For example: + + @overload + def utf8(value: None) -> None: ... + @overload + def utf8(value: bytes) -> bytes: ... + @overload + def utf8(value: str) -> bytes: ... + def utf8(value): + # implementation goes here + """ + return _overload_dummy + + +class _ProtocolMeta(GenericMeta): + """Internal metaclass for _Protocol. + + This exists so _Protocol classes can be generic without deriving + from Generic. + """ + + def __instancecheck__(self, obj): + if _Protocol not in self.__bases__: + return super().__instancecheck__(obj) + raise TypeError("Protocols cannot be used with isinstance().") + + def __subclasscheck__(self, cls): + if not self._is_protocol: + # No structural checks since this isn't a protocol. + return NotImplemented + + if self is _Protocol: + # Every class is a subclass of the empty protocol. + return True + + # Find all attributes defined in the protocol. + attrs = self._get_protocol_attrs() + + for attr in attrs: + if not any(attr in d.__dict__ for d in cls.__mro__): + return False + return True + + def _get_protocol_attrs(self): + # Get all Protocol base classes. + protocol_bases = [] + for c in self.__mro__: + if getattr(c, '_is_protocol', False) and c.__name__ != '_Protocol': + protocol_bases.append(c) + + # Get attributes included in protocol. + attrs = set() + for base in protocol_bases: + for attr in base.__dict__.keys(): + # Include attributes not defined in any non-protocol bases. + for c in self.__mro__: + if (c is not base and attr in c.__dict__ and + not getattr(c, '_is_protocol', False)): + break + else: + if (not attr.startswith('_abc_') and + attr != '__abstractmethods__' and + attr != '__annotations__' and + attr != '__weakref__' and + attr != '_is_protocol' and + attr != '_gorg' and + attr != '__dict__' and + attr != '__args__' and + attr != '__slots__' and + attr != '_get_protocol_attrs' and + attr != '__next_in_mro__' and + attr != '__parameters__' and + attr != '__origin__' and + attr != '__orig_bases__' and + attr != '__extra__' and + attr != '__tree_hash__' and + attr != '__module__'): + attrs.add(attr) + + return attrs + + +class _Protocol(metaclass=_ProtocolMeta): + """Internal base class for protocol classes. + + This implements a simple-minded structural issubclass check + (similar but more general than the one-offs in collections.abc + such as Hashable). + """ + + __slots__ = () + + _is_protocol = True + + +# Various ABCs mimicking those in collections.abc. +# A few are simply re-exported for completeness. + +Hashable = collections_abc.Hashable # Not generic. + + +if hasattr(collections_abc, 'Awaitable'): + class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): + __slots__ = () + + __all__.append('Awaitable') + + +if hasattr(collections_abc, 'Coroutine'): + class Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co], + extra=collections_abc.Coroutine): + __slots__ = () + + __all__.append('Coroutine') + + +if hasattr(collections_abc, 'AsyncIterable'): + + class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): + __slots__ = () + + class AsyncIterator(AsyncIterable[T_co], + extra=collections_abc.AsyncIterator): + __slots__ = () + + __all__.append('AsyncIterable') + __all__.append('AsyncIterator') + + +class Iterable(Generic[T_co], extra=collections_abc.Iterable): + __slots__ = () + + +class Iterator(Iterable[T_co], extra=collections_abc.Iterator): + __slots__ = () + + +class SupportsInt(_Protocol): + __slots__ = () + + @abstractmethod + def __int__(self) -> int: + pass + + +class SupportsFloat(_Protocol): + __slots__ = () + + @abstractmethod + def __float__(self) -> float: + pass + + +class SupportsComplex(_Protocol): + __slots__ = () + + @abstractmethod + def __complex__(self) -> complex: + pass + + +class SupportsBytes(_Protocol): + __slots__ = () + + @abstractmethod + def __bytes__(self) -> bytes: + pass + + +class SupportsAbs(_Protocol[T_co]): + __slots__ = () + + @abstractmethod + def __abs__(self) -> T_co: + pass + + +class SupportsRound(_Protocol[T_co]): + __slots__ = () + + @abstractmethod + def __round__(self, ndigits: int = 0) -> T_co: + pass + + +if hasattr(collections_abc, 'Reversible'): + class Reversible(Iterable[T_co], extra=collections_abc.Reversible): + __slots__ = () +else: + class Reversible(_Protocol[T_co]): + __slots__ = () + + @abstractmethod + def __reversed__(self) -> 'Iterator[T_co]': + pass + + +Sized = collections_abc.Sized # Not generic. + + +class Container(Generic[T_co], extra=collections_abc.Container): + __slots__ = () + + +if hasattr(collections_abc, 'Collection'): + class Collection(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Collection): + __slots__ = () + + __all__.append('Collection') + + +# Callable was defined earlier. + +if hasattr(collections_abc, 'Collection'): + class AbstractSet(Collection[T_co], + extra=collections_abc.Set): + __slots__ = () +else: + class AbstractSet(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Set): + __slots__ = () + + +class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): + __slots__ = () + + +# NOTE: It is only covariant in the value type. +if hasattr(collections_abc, 'Collection'): + class Mapping(Collection[KT], Generic[KT, VT_co], + extra=collections_abc.Mapping): + __slots__ = () +else: + class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], + extra=collections_abc.Mapping): + __slots__ = () + + +class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): + __slots__ = () + + +if hasattr(collections_abc, 'Reversible'): + if hasattr(collections_abc, 'Collection'): + class Sequence(Reversible[T_co], Collection[T_co], + extra=collections_abc.Sequence): + __slots__ = () + else: + class Sequence(Sized, Reversible[T_co], Container[T_co], + extra=collections_abc.Sequence): + __slots__ = () +else: + class Sequence(Sized, Iterable[T_co], Container[T_co], + extra=collections_abc.Sequence): + __slots__ = () + + +class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): + __slots__ = () + + +class ByteString(Sequence[int], extra=collections_abc.ByteString): + __slots__ = () + + +class List(list, MutableSequence[T], extra=list): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is List: + raise TypeError("Type List cannot be instantiated; " + "use list() instead") + return _generic_new(list, cls, *args, **kwds) + + +class Deque(collections.deque, MutableSequence[T], extra=collections.deque): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Deque: + return collections.deque(*args, **kwds) + return _generic_new(collections.deque, cls, *args, **kwds) + + +class Set(set, MutableSet[T], extra=set): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Set: + raise TypeError("Type Set cannot be instantiated; " + "use set() instead") + return _generic_new(set, cls, *args, **kwds) + + +class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is FrozenSet: + raise TypeError("Type FrozenSet cannot be instantiated; " + "use frozenset() instead") + return _generic_new(frozenset, cls, *args, **kwds) + + +class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): + __slots__ = () + + +class KeysView(MappingView[KT], AbstractSet[KT], + extra=collections_abc.KeysView): + __slots__ = () + + +class ItemsView(MappingView[Tuple[KT, VT_co]], + AbstractSet[Tuple[KT, VT_co]], + Generic[KT, VT_co], + extra=collections_abc.ItemsView): + __slots__ = () + + +class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): + __slots__ = () + + +if hasattr(contextlib, 'AbstractContextManager'): + class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager): + __slots__ = () +else: + class ContextManager(Generic[T_co]): + __slots__ = () + + def __enter__(self): + return self + + @abc.abstractmethod + def __exit__(self, exc_type, exc_value, traceback): + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is ContextManager: + # In Python 3.6+, it is possible to set a method to None to + # explicitly indicate that the class does not implement an ABC + # (https://bugs.python.org/issue25958), but we do not support + # that pattern here because this fallback class is only used + # in Python 3.5 and earlier. + if (any("__enter__" in B.__dict__ for B in C.__mro__) and + any("__exit__" in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + + +if hasattr(contextlib, 'AbstractAsyncContextManager'): + class AsyncContextManager(Generic[T_co], + extra=contextlib.AbstractAsyncContextManager): + __slots__ = () + + __all__.append('AsyncContextManager') +elif sys.version_info[:2] >= (3, 5): + exec(""" +class AsyncContextManager(Generic[T_co]): + __slots__ = () + + async def __aenter__(self): + return self + + @abc.abstractmethod + async def __aexit__(self, exc_type, exc_value, traceback): + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncContextManager: + if sys.version_info[:2] >= (3, 6): + return _collections_abc._check_methods(C, "__aenter__", "__aexit__") + if (any("__aenter__" in B.__dict__ for B in C.__mro__) and + any("__aexit__" in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + +__all__.append('AsyncContextManager') +""") + + +class Dict(dict, MutableMapping[KT, VT], extra=dict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Dict: + raise TypeError("Type Dict cannot be instantiated; " + "use dict() instead") + return _generic_new(dict, cls, *args, **kwds) + + +class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], + extra=collections.defaultdict): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is DefaultDict: + return collections.defaultdict(*args, **kwds) + return _generic_new(collections.defaultdict, cls, *args, **kwds) + + +class Counter(collections.Counter, Dict[T, int], extra=collections.Counter): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Counter: + return collections.Counter(*args, **kwds) + return _generic_new(collections.Counter, cls, *args, **kwds) + + +if hasattr(collections, 'ChainMap'): + # ChainMap only exists in 3.3+ + __all__.append('ChainMap') + + class ChainMap(collections.ChainMap, MutableMapping[KT, VT], + extra=collections.ChainMap): + + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is ChainMap: + return collections.ChainMap(*args, **kwds) + return _generic_new(collections.ChainMap, cls, *args, **kwds) + + +# Determine what base class to use for Generator. +if hasattr(collections_abc, 'Generator'): + # Sufficiently recent versions of 3.5 have a Generator ABC. + _G_base = collections_abc.Generator +else: + # Fall back on the exact type. + _G_base = types.GeneratorType + + +class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co], + extra=_G_base): + __slots__ = () + + def __new__(cls, *args, **kwds): + if cls._gorg is Generator: + raise TypeError("Type Generator cannot be instantiated; " + "create a subclass instead") + return _generic_new(_G_base, cls, *args, **kwds) + + +if hasattr(collections_abc, 'AsyncGenerator'): + class AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra], + extra=collections_abc.AsyncGenerator): + __slots__ = () + + __all__.append('AsyncGenerator') + + +# Internal type variable used for Type[]. +CT_co = TypeVar('CT_co', covariant=True, bound=type) + + +# This is not a real generic class. Don't use outside annotations. +class Type(Generic[CT_co], extra=type): + """A special construct usable to annotate class objects. + + For example, suppose we have the following classes:: + + class User: ... # Abstract base for User classes + class BasicUser(User): ... + class ProUser(User): ... + class TeamUser(User): ... + + And a function that takes a class argument that's a subclass of + User and returns an instance of the corresponding class:: + + U = TypeVar('U', bound=User) + def new_user(user_class: Type[U]) -> U: + user = user_class() + # (Here we could write the user object to a database) + return user + + joe = new_user(BasicUser) + + At this point the type checker knows that joe has type BasicUser. + """ + + __slots__ = () + + +def _make_nmtuple(name, types): + msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" + types = [(n, _type_check(t, msg)) for n, t in types] + nm_tpl = collections.namedtuple(name, [n for n, t in types]) + # Prior to PEP 526, only _field_types attribute was assigned. + # Now, both __annotations__ and _field_types are used to maintain compatibility. + nm_tpl.__annotations__ = nm_tpl._field_types = collections.OrderedDict(types) + try: + nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + return nm_tpl + + +_PY36 = sys.version_info[:2] >= (3, 6) + +# attributes prohibited to set in NamedTuple class syntax +_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__', + '_fields', '_field_defaults', '_field_types', + '_make', '_replace', '_asdict', '_source') + +_special = ('__module__', '__name__', '__qualname__', '__annotations__') + + +class NamedTupleMeta(type): + + def __new__(cls, typename, bases, ns): + if ns.get('_root', False): + return super().__new__(cls, typename, bases, ns) + if not _PY36: + raise TypeError("Class syntax for NamedTuple is only supported" + " in Python 3.6+") + types = ns.get('__annotations__', {}) + nm_tpl = _make_nmtuple(typename, types.items()) + defaults = [] + defaults_dict = {} + for field_name in types: + if field_name in ns: + default_value = ns[field_name] + defaults.append(default_value) + defaults_dict[field_name] = default_value + elif defaults: + raise TypeError("Non-default namedtuple field {field_name} cannot " + "follow default field(s) {default_names}" + .format(field_name=field_name, + default_names=', '.join(defaults_dict.keys()))) + nm_tpl.__new__.__annotations__ = collections.OrderedDict(types) + nm_tpl.__new__.__defaults__ = tuple(defaults) + nm_tpl._field_defaults = defaults_dict + # update from user namespace without overriding special namedtuple attributes + for key in ns: + if key in _prohibited: + raise AttributeError("Cannot overwrite NamedTuple attribute " + key) + elif key not in _special and key not in nm_tpl._fields: + setattr(nm_tpl, key, ns[key]) + return nm_tpl + + +class NamedTuple(metaclass=NamedTupleMeta): + """Typed version of namedtuple. + + Usage in Python versions >= 3.6:: + + class Employee(NamedTuple): + name: str + id: int + + This is equivalent to:: + + Employee = collections.namedtuple('Employee', ['name', 'id']) + + The resulting class has extra __annotations__ and _field_types + attributes, giving an ordered dict mapping field names to types. + __annotations__ should be preferred, while _field_types + is kept to maintain pre PEP 526 compatibility. (The field names + are in the _fields attribute, which is part of the namedtuple + API.) Alternative equivalent keyword syntax is also accepted:: + + Employee = NamedTuple('Employee', name=str, id=int) + + In Python versions <= 3.5 use:: + + Employee = NamedTuple('Employee', [('name', str), ('id', int)]) + """ + _root = True + + def __new__(self, typename, fields=None, **kwargs): + if kwargs and not _PY36: + raise TypeError("Keyword syntax for NamedTuple is only supported" + " in Python 3.6+") + if fields is None: + fields = kwargs.items() + elif kwargs: + raise TypeError("Either list of fields or keywords" + " can be provided to NamedTuple, not both") + return _make_nmtuple(typename, fields) + + +def NewType(name, tp): + """NewType creates simple unique types with almost zero + runtime overhead. NewType(name, tp) is considered a subtype of tp + by static type checkers. At runtime, NewType(name, tp) returns + a dummy function that simply returns its argument. Usage:: + + UserId = NewType('UserId', int) + + def name_by_id(user_id: UserId) -> str: + ... + + UserId('user') # Fails type check + + name_by_id(42) # Fails type check + name_by_id(UserId(42)) # OK + + num = UserId(5) + 1 # type: int + """ + + def new_type(x): + return x + + new_type.__name__ = name + new_type.__supertype__ = tp + return new_type + + +# Python-version-specific alias (Python 2: unicode; Python 3: str) +Text = str + + +# Constant that's True when type checking, but False here. +TYPE_CHECKING = False + + +class IO(Generic[AnyStr]): + """Generic base class for TextIO and BinaryIO. + + This is an abstract, generic version of the return of open(). + + NOTE: This does not distinguish between the different possible + classes (text vs. binary, read vs. write vs. read/write, + append-only, unbuffered). The TextIO and BinaryIO subclasses + below capture the distinctions between text vs. binary, which is + pervasive in the interface; however we currently do not offer a + way to track the other distinctions in the type system. + """ + + __slots__ = () + + @abstractproperty + def mode(self) -> str: + pass + + @abstractproperty + def name(self) -> str: + pass + + @abstractmethod + def close(self) -> None: + pass + + @abstractproperty + def closed(self) -> bool: + pass + + @abstractmethod + def fileno(self) -> int: + pass + + @abstractmethod + def flush(self) -> None: + pass + + @abstractmethod + def isatty(self) -> bool: + pass + + @abstractmethod + def read(self, n: int = -1) -> AnyStr: + pass + + @abstractmethod + def readable(self) -> bool: + pass + + @abstractmethod + def readline(self, limit: int = -1) -> AnyStr: + pass + + @abstractmethod + def readlines(self, hint: int = -1) -> List[AnyStr]: + pass + + @abstractmethod + def seek(self, offset: int, whence: int = 0) -> int: + pass + + @abstractmethod + def seekable(self) -> bool: + pass + + @abstractmethod + def tell(self) -> int: + pass + + @abstractmethod + def truncate(self, size: int = None) -> int: + pass + + @abstractmethod + def writable(self) -> bool: + pass + + @abstractmethod + def write(self, s: AnyStr) -> int: + pass + + @abstractmethod + def writelines(self, lines: List[AnyStr]) -> None: + pass + + @abstractmethod + def __enter__(self) -> 'IO[AnyStr]': + pass + + @abstractmethod + def __exit__(self, type, value, traceback) -> None: + pass + + +class BinaryIO(IO[bytes]): + """Typed version of the return of open() in binary mode.""" + + __slots__ = () + + @abstractmethod + def write(self, s: Union[bytes, bytearray]) -> int: + pass + + @abstractmethod + def __enter__(self) -> 'BinaryIO': + pass + + +class TextIO(IO[str]): + """Typed version of the return of open() in text mode.""" + + __slots__ = () + + @abstractproperty + def buffer(self) -> BinaryIO: + pass + + @abstractproperty + def encoding(self) -> str: + pass + + @abstractproperty + def errors(self) -> Optional[str]: + pass + + @abstractproperty + def line_buffering(self) -> bool: + pass + + @abstractproperty + def newlines(self) -> Any: + pass + + @abstractmethod + def __enter__(self) -> 'TextIO': + pass + + +class io: + """Wrapper namespace for IO generic classes.""" + + __all__ = ['IO', 'TextIO', 'BinaryIO'] + IO = IO + TextIO = TextIO + BinaryIO = BinaryIO + + +io.__name__ = __name__ + '.io' +sys.modules[io.__name__] = io + + +Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')), + lambda p: p.pattern) +Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')), + lambda m: m.re.pattern) + + +class re: + """Wrapper namespace for re type aliases.""" + + __all__ = ['Pattern', 'Match'] + Pattern = Pattern + Match = Match + + +re.__name__ = __name__ + '.re' +sys.modules[re.__name__] = re diff --git a/requirements.txt b/requirements.txt index f6af404..401cf01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,4 @@ influxdb>=5.2.0 schedule>=0.5.0 configparser>=3.5.0 datetime>=4.3 - +typing>=3.6.6 From e0e6133e12bf9a0558c71b53344c3100c761a320 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 2 Dec 2018 14:23:42 -0600 Subject: [PATCH 057/120] Revert "added forced package imports" This reverts commit fa15eca8cd4d71c19ad1beac6233b687c0b5f1b7. --- Varken/dbmanager.py | 5 - Varken/helpers.py | 16 +- Varken/iniparser.py | 18 +- Varken/ombi.py | 4 - Varken/radarr.py | 4 - Varken/sonarr.py | 4 - Varken/tautulli.py | 7 +- lib/DateTime/DateTime.py | 1940 ---- lib/DateTime/DateTime.txt | 785 -- lib/DateTime/__init__.py | 17 - lib/DateTime/interfaces.py | 375 - lib/DateTime/pytz.txt | 192 - lib/DateTime/pytz_support.py | 259 - lib/DateTime/tests/__init__.py | 15 - lib/DateTime/tests/julian_testdata.txt | 57 - lib/DateTime/tests/test_datetime.py | 686 -- lib/backports/configparser/__init__.py | 1390 --- lib/backports/configparser/helpers.py | 171 - lib/bin/chardetect.exe | Bin 93027 -> 0 bytes lib/bin/easy_install-3.7.exe | Bin 93036 -> 0 bytes lib/bin/easy_install.exe | Bin 93036 -> 0 bytes lib/certifi/__init__.py | 3 - lib/certifi/__main__.py | 2 - lib/certifi/cacert.pem | 4512 --------- lib/certifi/core.py | 20 - lib/chardet/__init__.py | 39 - lib/chardet/big5freq.py | 386 - lib/chardet/big5prober.py | 47 - lib/chardet/chardistribution.py | 233 - lib/chardet/charsetgroupprober.py | 106 - lib/chardet/charsetprober.py | 145 - lib/chardet/cli/__init__.py | 1 - lib/chardet/cli/chardetect.py | 85 - lib/chardet/codingstatemachine.py | 88 - lib/chardet/compat.py | 34 - lib/chardet/cp949prober.py | 49 - lib/chardet/enums.py | 76 - lib/chardet/escprober.py | 101 - lib/chardet/escsm.py | 246 - lib/chardet/eucjpprober.py | 92 - lib/chardet/euckrfreq.py | 195 - lib/chardet/euckrprober.py | 47 - lib/chardet/euctwfreq.py | 387 - lib/chardet/euctwprober.py | 46 - lib/chardet/gb2312freq.py | 283 - lib/chardet/gb2312prober.py | 46 - lib/chardet/hebrewprober.py | 292 - lib/chardet/jisfreq.py | 325 - lib/chardet/jpcntx.py | 233 - lib/chardet/langbulgarianmodel.py | 228 - lib/chardet/langcyrillicmodel.py | 333 - lib/chardet/langgreekmodel.py | 225 - lib/chardet/langhebrewmodel.py | 200 - lib/chardet/langhungarianmodel.py | 225 - lib/chardet/langthaimodel.py | 199 - lib/chardet/langturkishmodel.py | 193 - lib/chardet/latin1prober.py | 145 - lib/chardet/mbcharsetprober.py | 91 - lib/chardet/mbcsgroupprober.py | 54 - lib/chardet/mbcssm.py | 572 -- lib/chardet/sbcharsetprober.py | 132 - lib/chardet/sbcsgroupprober.py | 73 - lib/chardet/sjisprober.py | 92 - lib/chardet/universaldetector.py | 286 - lib/chardet/utf8prober.py | 82 - lib/chardet/version.py | 9 - lib/configparser-3.5.0-py3.7-nspkg.pth | 1 - lib/dateutil/__init__.py | 8 - lib/dateutil/_common.py | 43 - lib/dateutil/_version.py | 4 - lib/dateutil/easter.py | 89 - lib/dateutil/parser/__init__.py | 60 - lib/dateutil/parser/_parser.py | 1578 ---- lib/dateutil/parser/isoparser.py | 406 - lib/dateutil/relativedelta.py | 590 -- lib/dateutil/rrule.py | 1672 ---- lib/dateutil/tz/__init__.py | 17 - lib/dateutil/tz/_common.py | 415 - lib/dateutil/tz/_factories.py | 49 - lib/dateutil/tz/tz.py | 1785 ---- lib/dateutil/tz/win.py | 331 - lib/dateutil/tzwin.py | 2 - lib/dateutil/utils.py | 71 - lib/dateutil/zoneinfo/__init__.py | 167 - .../zoneinfo/dateutil-zoneinfo.tar.gz | Bin 154226 -> 0 bytes lib/dateutil/zoneinfo/rebuild.py | 53 - lib/easy_install.py | 5 - lib/geoip2/__init__.py | 7 - lib/geoip2/compat.py | 19 - lib/geoip2/database.py | 214 - lib/geoip2/errors.py | 51 - lib/geoip2/mixins.py | 16 - lib/geoip2/models.py | 502 - lib/geoip2/records.py | 675 -- lib/geoip2/webservice.py | 235 - lib/idna/__init__.py | 2 - lib/idna/codec.py | 118 - lib/idna/compat.py | 12 - lib/idna/core.py | 399 - lib/idna/idnadata.py | 1893 ---- lib/idna/intranges.py | 53 - lib/idna/package_data.py | 2 - lib/idna/uts46data.py | 8179 ----------------- lib/influxdb/__init__.py | 21 - lib/influxdb/_dataframe_client.py | 452 - lib/influxdb/chunked_json.py | 27 - lib/influxdb/client.py | 980 -- lib/influxdb/dataframe_client.py | 28 - lib/influxdb/exceptions.py | 35 - lib/influxdb/helper.py | 184 - lib/influxdb/influxdb08/__init__.py | 18 - lib/influxdb/influxdb08/chunked_json.py | 27 - lib/influxdb/influxdb08/client.py | 843 -- lib/influxdb/influxdb08/dataframe_client.py | 177 - lib/influxdb/influxdb08/helper.py | 153 - lib/influxdb/line_protocol.py | 172 - lib/influxdb/resultset.py | 206 - lib/influxdb/tests/__init__.py | 21 - lib/influxdb/tests/chunked_json_test.py | 51 - lib/influxdb/tests/client_test.py | 1094 --- lib/influxdb/tests/dataframe_client_test.py | 711 -- lib/influxdb/tests/helper_test.py | 367 - lib/influxdb/tests/influxdb08/__init__.py | 2 - lib/influxdb/tests/influxdb08/client_test.py | 904 -- .../tests/influxdb08/dataframe_client_test.py | 331 - lib/influxdb/tests/influxdb08/helper_test.py | 228 - lib/influxdb/tests/misc.py | 50 - lib/influxdb/tests/resultset_test.py | 202 - lib/influxdb/tests/server_tests/__init__.py | 1 - lib/influxdb/tests/server_tests/base.py | 84 - .../server_tests/client_test_with_server.py | 825 -- .../tests/server_tests/influxdb_instance.py | 198 - lib/influxdb/tests/test_line_protocol.py | 147 - lib/maxminddb/__init__.py | 54 - lib/maxminddb/compat.py | 43 - lib/maxminddb/const.py | 8 - lib/maxminddb/decoder.py | 172 - lib/maxminddb/errors.py | 10 - lib/maxminddb/extension/maxminddb.c | 602 -- lib/maxminddb/file.py | 65 - lib/maxminddb/reader.py | 309 - lib/pkg_resources/__init__.py | 3171 ------- lib/pkg_resources/_vendor/__init__.py | 0 lib/pkg_resources/_vendor/appdirs.py | 608 -- .../_vendor/packaging/__about__.py | 21 - .../_vendor/packaging/__init__.py | 14 - .../_vendor/packaging/_compat.py | 30 - .../_vendor/packaging/_structures.py | 68 - .../_vendor/packaging/markers.py | 301 - .../_vendor/packaging/requirements.py | 127 - .../_vendor/packaging/specifiers.py | 774 -- lib/pkg_resources/_vendor/packaging/utils.py | 14 - .../_vendor/packaging/version.py | 393 - lib/pkg_resources/_vendor/pyparsing.py | 5742 ------------ lib/pkg_resources/_vendor/six.py | 868 -- lib/pkg_resources/extern/__init__.py | 73 - lib/pkg_resources/py31compat.py | 23 - lib/pytz/__init__.py | 1527 --- lib/pytz/exceptions.py | 48 - lib/pytz/lazy.py | 172 - lib/pytz/reference.py | 140 - lib/pytz/tzfile.py | 134 - lib/pytz/tzinfo.py | 577 -- lib/pytz/zoneinfo/Africa/Abidjan | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Accra | Bin 828 -> 0 bytes lib/pytz/zoneinfo/Africa/Addis_Ababa | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Africa/Algiers | Bin 751 -> 0 bytes lib/pytz/zoneinfo/Africa/Asmara | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Africa/Asmera | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Africa/Bamako | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Bangui | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Banjul | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Bissau | Bin 194 -> 0 bytes lib/pytz/zoneinfo/Africa/Blantyre | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Brazzaville | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Bujumbura | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Cairo | Bin 1963 -> 0 bytes lib/pytz/zoneinfo/Africa/Casablanca | Bin 969 -> 0 bytes lib/pytz/zoneinfo/Africa/Ceuta | Bin 2050 -> 0 bytes lib/pytz/zoneinfo/Africa/Conakry | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Dakar | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Dar_es_Salaam | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Africa/Djibouti | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Africa/Douala | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/El_Aaiun | Bin 839 -> 0 bytes lib/pytz/zoneinfo/Africa/Freetown | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Gaborone | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Harare | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Johannesburg | Bin 262 -> 0 bytes lib/pytz/zoneinfo/Africa/Juba | Bin 669 -> 0 bytes lib/pytz/zoneinfo/Africa/Kampala | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Africa/Khartoum | Bin 699 -> 0 bytes lib/pytz/zoneinfo/Africa/Kigali | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Kinshasa | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Lagos | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Libreville | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Lome | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Luanda | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Lubumbashi | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Lusaka | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Malabo | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Maputo | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Maseru | Bin 262 -> 0 bytes lib/pytz/zoneinfo/Africa/Mbabane | Bin 262 -> 0 bytes lib/pytz/zoneinfo/Africa/Mogadishu | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Africa/Monrovia | Bin 224 -> 0 bytes lib/pytz/zoneinfo/Africa/Nairobi | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Africa/Ndjamena | Bin 211 -> 0 bytes lib/pytz/zoneinfo/Africa/Niamey | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Nouakchott | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Ouagadougou | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Porto-Novo | Bin 157 -> 0 bytes lib/pytz/zoneinfo/Africa/Sao_Tome | Bin 225 -> 0 bytes lib/pytz/zoneinfo/Africa/Timbuktu | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Africa/Tripoli | Bin 641 -> 0 bytes lib/pytz/zoneinfo/Africa/Tunis | Bin 701 -> 0 bytes lib/pytz/zoneinfo/Africa/Windhoek | Bin 979 -> 0 bytes lib/pytz/zoneinfo/America/Adak | Bin 2356 -> 0 bytes lib/pytz/zoneinfo/America/Anchorage | Bin 2371 -> 0 bytes lib/pytz/zoneinfo/America/Anguilla | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Antigua | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Araguaina | Bin 896 -> 0 bytes .../zoneinfo/America/Argentina/Buenos_Aires | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/Catamarca | Bin 1100 -> 0 bytes .../zoneinfo/America/Argentina/ComodRivadavia | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/Cordoba | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/Jujuy | Bin 1072 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/La_Rioja | Bin 1114 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/Mendoza | Bin 1100 -> 0 bytes .../zoneinfo/America/Argentina/Rio_Gallegos | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/Salta | Bin 1072 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/San_Juan | Bin 1114 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/San_Luis | Bin 1130 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/Tucuman | Bin 1128 -> 0 bytes lib/pytz/zoneinfo/America/Argentina/Ushuaia | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Aruba | Bin 198 -> 0 bytes lib/pytz/zoneinfo/America/Asuncion | Bin 2068 -> 0 bytes lib/pytz/zoneinfo/America/Atikokan | Bin 336 -> 0 bytes lib/pytz/zoneinfo/America/Atka | Bin 2356 -> 0 bytes lib/pytz/zoneinfo/America/Bahia | Bin 1036 -> 0 bytes lib/pytz/zoneinfo/America/Bahia_Banderas | Bin 1574 -> 0 bytes lib/pytz/zoneinfo/America/Barbados | Bin 330 -> 0 bytes lib/pytz/zoneinfo/America/Belem | Bin 588 -> 0 bytes lib/pytz/zoneinfo/America/Belize | Bin 964 -> 0 bytes lib/pytz/zoneinfo/America/Blanc-Sablon | Bin 298 -> 0 bytes lib/pytz/zoneinfo/America/Boa_Vista | Bin 644 -> 0 bytes lib/pytz/zoneinfo/America/Bogota | Bin 262 -> 0 bytes lib/pytz/zoneinfo/America/Boise | Bin 2394 -> 0 bytes lib/pytz/zoneinfo/America/Buenos_Aires | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Cambridge_Bay | Bin 2084 -> 0 bytes lib/pytz/zoneinfo/America/Campo_Grande | Bin 2002 -> 0 bytes lib/pytz/zoneinfo/America/Cancun | Bin 802 -> 0 bytes lib/pytz/zoneinfo/America/Caracas | Bin 280 -> 0 bytes lib/pytz/zoneinfo/America/Catamarca | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Cayenne | Bin 210 -> 0 bytes lib/pytz/zoneinfo/America/Cayman | Bin 194 -> 0 bytes lib/pytz/zoneinfo/America/Chicago | Bin 3576 -> 0 bytes lib/pytz/zoneinfo/America/Chihuahua | Bin 1508 -> 0 bytes lib/pytz/zoneinfo/America/Coral_Harbour | Bin 336 -> 0 bytes lib/pytz/zoneinfo/America/Cordoba | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Costa_Rica | Bin 332 -> 0 bytes lib/pytz/zoneinfo/America/Creston | Bin 224 -> 0 bytes lib/pytz/zoneinfo/America/Cuiaba | Bin 1974 -> 0 bytes lib/pytz/zoneinfo/America/Curacao | Bin 198 -> 0 bytes lib/pytz/zoneinfo/America/Danmarkshavn | Bin 698 -> 0 bytes lib/pytz/zoneinfo/America/Dawson | Bin 2084 -> 0 bytes lib/pytz/zoneinfo/America/Dawson_Creek | Bin 1050 -> 0 bytes lib/pytz/zoneinfo/America/Denver | Bin 2444 -> 0 bytes lib/pytz/zoneinfo/America/Detroit | Bin 2174 -> 0 bytes lib/pytz/zoneinfo/America/Dominica | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Edmonton | Bin 2388 -> 0 bytes lib/pytz/zoneinfo/America/Eirunepe | Bin 676 -> 0 bytes lib/pytz/zoneinfo/America/El_Salvador | Bin 236 -> 0 bytes lib/pytz/zoneinfo/America/Ensenada | Bin 2342 -> 0 bytes lib/pytz/zoneinfo/America/Fort_Nelson | Bin 2240 -> 0 bytes lib/pytz/zoneinfo/America/Fort_Wayne | Bin 1666 -> 0 bytes lib/pytz/zoneinfo/America/Fortaleza | Bin 728 -> 0 bytes lib/pytz/zoneinfo/America/Glace_Bay | Bin 2192 -> 0 bytes lib/pytz/zoneinfo/America/Godthab | Bin 1878 -> 0 bytes lib/pytz/zoneinfo/America/Goose_Bay | Bin 3210 -> 0 bytes lib/pytz/zoneinfo/America/Grand_Turk | Bin 1872 -> 0 bytes lib/pytz/zoneinfo/America/Grenada | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Guadeloupe | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Guatemala | Bin 292 -> 0 bytes lib/pytz/zoneinfo/America/Guayaquil | Bin 262 -> 0 bytes lib/pytz/zoneinfo/America/Guyana | Bin 252 -> 0 bytes lib/pytz/zoneinfo/America/Halifax | Bin 3424 -> 0 bytes lib/pytz/zoneinfo/America/Havana | Bin 2428 -> 0 bytes lib/pytz/zoneinfo/America/Hermosillo | Bin 440 -> 0 bytes .../zoneinfo/America/Indiana/Indianapolis | Bin 1666 -> 0 bytes lib/pytz/zoneinfo/America/Indiana/Knox | Bin 2428 -> 0 bytes lib/pytz/zoneinfo/America/Indiana/Marengo | Bin 1722 -> 0 bytes lib/pytz/zoneinfo/America/Indiana/Petersburg | Bin 1904 -> 0 bytes lib/pytz/zoneinfo/America/Indiana/Tell_City | Bin 1726 -> 0 bytes lib/pytz/zoneinfo/America/Indiana/Vevay | Bin 1414 -> 0 bytes lib/pytz/zoneinfo/America/Indiana/Vincennes | Bin 1694 -> 0 bytes lib/pytz/zoneinfo/America/Indiana/Winamac | Bin 1778 -> 0 bytes lib/pytz/zoneinfo/America/Indianapolis | Bin 1666 -> 0 bytes lib/pytz/zoneinfo/America/Inuvik | Bin 1914 -> 0 bytes lib/pytz/zoneinfo/America/Iqaluit | Bin 2032 -> 0 bytes lib/pytz/zoneinfo/America/Jamaica | Bin 498 -> 0 bytes lib/pytz/zoneinfo/America/Jujuy | Bin 1072 -> 0 bytes lib/pytz/zoneinfo/America/Juneau | Bin 2353 -> 0 bytes lib/pytz/zoneinfo/America/Kentucky/Louisville | Bin 2772 -> 0 bytes lib/pytz/zoneinfo/America/Kentucky/Monticello | Bin 2352 -> 0 bytes lib/pytz/zoneinfo/America/Knox_IN | Bin 2428 -> 0 bytes lib/pytz/zoneinfo/America/Kralendijk | Bin 198 -> 0 bytes lib/pytz/zoneinfo/America/La_Paz | Bin 248 -> 0 bytes lib/pytz/zoneinfo/America/Lima | Bin 422 -> 0 bytes lib/pytz/zoneinfo/America/Los_Angeles | Bin 2836 -> 0 bytes lib/pytz/zoneinfo/America/Louisville | Bin 2772 -> 0 bytes lib/pytz/zoneinfo/America/Lower_Princes | Bin 198 -> 0 bytes lib/pytz/zoneinfo/America/Maceio | Bin 756 -> 0 bytes lib/pytz/zoneinfo/America/Managua | Bin 454 -> 0 bytes lib/pytz/zoneinfo/America/Manaus | Bin 616 -> 0 bytes lib/pytz/zoneinfo/America/Marigot | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Martinique | Bin 248 -> 0 bytes lib/pytz/zoneinfo/America/Matamoros | Bin 1402 -> 0 bytes lib/pytz/zoneinfo/America/Mazatlan | Bin 1550 -> 0 bytes lib/pytz/zoneinfo/America/Mendoza | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Menominee | Bin 2274 -> 0 bytes lib/pytz/zoneinfo/America/Merida | Bin 1442 -> 0 bytes lib/pytz/zoneinfo/America/Metlakatla | Bin 1409 -> 0 bytes lib/pytz/zoneinfo/America/Mexico_City | Bin 1604 -> 0 bytes lib/pytz/zoneinfo/America/Miquelon | Bin 1682 -> 0 bytes lib/pytz/zoneinfo/America/Moncton | Bin 3154 -> 0 bytes lib/pytz/zoneinfo/America/Monterrey | Bin 1402 -> 0 bytes lib/pytz/zoneinfo/America/Montevideo | Bin 1550 -> 0 bytes lib/pytz/zoneinfo/America/Montreal | Bin 3494 -> 0 bytes lib/pytz/zoneinfo/America/Montserrat | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Nassau | Bin 2270 -> 0 bytes lib/pytz/zoneinfo/America/New_York | Bin 3536 -> 0 bytes lib/pytz/zoneinfo/America/Nipigon | Bin 2122 -> 0 bytes lib/pytz/zoneinfo/America/Nome | Bin 2367 -> 0 bytes lib/pytz/zoneinfo/America/Noronha | Bin 728 -> 0 bytes lib/pytz/zoneinfo/America/North_Dakota/Beulah | Bin 2380 -> 0 bytes lib/pytz/zoneinfo/America/North_Dakota/Center | Bin 2380 -> 0 bytes .../zoneinfo/America/North_Dakota/New_Salem | Bin 2380 -> 0 bytes lib/pytz/zoneinfo/America/Ojinaga | Bin 1508 -> 0 bytes lib/pytz/zoneinfo/America/Panama | Bin 194 -> 0 bytes lib/pytz/zoneinfo/America/Pangnirtung | Bin 2094 -> 0 bytes lib/pytz/zoneinfo/America/Paramaribo | Bin 282 -> 0 bytes lib/pytz/zoneinfo/America/Phoenix | Bin 344 -> 0 bytes lib/pytz/zoneinfo/America/Port-au-Prince | Bin 1446 -> 0 bytes lib/pytz/zoneinfo/America/Port_of_Spain | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Porto_Acre | Bin 648 -> 0 bytes lib/pytz/zoneinfo/America/Porto_Velho | Bin 588 -> 0 bytes lib/pytz/zoneinfo/America/Puerto_Rico | Bin 246 -> 0 bytes lib/pytz/zoneinfo/America/Punta_Arenas | Bin 1902 -> 0 bytes lib/pytz/zoneinfo/America/Rainy_River | Bin 2122 -> 0 bytes lib/pytz/zoneinfo/America/Rankin_Inlet | Bin 1916 -> 0 bytes lib/pytz/zoneinfo/America/Recife | Bin 728 -> 0 bytes lib/pytz/zoneinfo/America/Regina | Bin 980 -> 0 bytes lib/pytz/zoneinfo/America/Resolute | Bin 1916 -> 0 bytes lib/pytz/zoneinfo/America/Rio_Branco | Bin 648 -> 0 bytes lib/pytz/zoneinfo/America/Rosario | Bin 1100 -> 0 bytes lib/pytz/zoneinfo/America/Santa_Isabel | Bin 2342 -> 0 bytes lib/pytz/zoneinfo/America/Santarem | Bin 618 -> 0 bytes lib/pytz/zoneinfo/America/Santiago | Bin 2529 -> 0 bytes lib/pytz/zoneinfo/America/Santo_Domingo | Bin 482 -> 0 bytes lib/pytz/zoneinfo/America/Sao_Paulo | Bin 2002 -> 0 bytes lib/pytz/zoneinfo/America/Scoresbysund | Bin 1916 -> 0 bytes lib/pytz/zoneinfo/America/Shiprock | Bin 2444 -> 0 bytes lib/pytz/zoneinfo/America/Sitka | Bin 2329 -> 0 bytes lib/pytz/zoneinfo/America/St_Barthelemy | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/St_Johns | Bin 3655 -> 0 bytes lib/pytz/zoneinfo/America/St_Kitts | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/St_Lucia | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/St_Thomas | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/St_Vincent | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Swift_Current | Bin 560 -> 0 bytes lib/pytz/zoneinfo/America/Tegucigalpa | Bin 264 -> 0 bytes lib/pytz/zoneinfo/America/Thule | Bin 1514 -> 0 bytes lib/pytz/zoneinfo/America/Thunder_Bay | Bin 2202 -> 0 bytes lib/pytz/zoneinfo/America/Tijuana | Bin 2342 -> 0 bytes lib/pytz/zoneinfo/America/Toronto | Bin 3494 -> 0 bytes lib/pytz/zoneinfo/America/Tortola | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Vancouver | Bin 2892 -> 0 bytes lib/pytz/zoneinfo/America/Virgin | Bin 156 -> 0 bytes lib/pytz/zoneinfo/America/Whitehorse | Bin 2084 -> 0 bytes lib/pytz/zoneinfo/America/Winnipeg | Bin 2882 -> 0 bytes lib/pytz/zoneinfo/America/Yakutat | Bin 2305 -> 0 bytes lib/pytz/zoneinfo/America/Yellowknife | Bin 1966 -> 0 bytes lib/pytz/zoneinfo/Antarctica/Casey | Bin 297 -> 0 bytes lib/pytz/zoneinfo/Antarctica/Davis | Bin 297 -> 0 bytes lib/pytz/zoneinfo/Antarctica/DumontDUrville | Bin 202 -> 0 bytes lib/pytz/zoneinfo/Antarctica/Macquarie | Bin 1534 -> 0 bytes lib/pytz/zoneinfo/Antarctica/Mawson | Bin 211 -> 0 bytes lib/pytz/zoneinfo/Antarctica/McMurdo | Bin 2451 -> 0 bytes lib/pytz/zoneinfo/Antarctica/Palmer | Bin 1418 -> 0 bytes lib/pytz/zoneinfo/Antarctica/Rothera | Bin 172 -> 0 bytes lib/pytz/zoneinfo/Antarctica/South_Pole | Bin 2451 -> 0 bytes lib/pytz/zoneinfo/Antarctica/Syowa | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Antarctica/Troll | Bin 1162 -> 0 bytes lib/pytz/zoneinfo/Antarctica/Vostok | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Arctic/Longyearbyen | Bin 2242 -> 0 bytes lib/pytz/zoneinfo/Asia/Aden | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Asia/Almaty | Bin 1017 -> 0 bytes lib/pytz/zoneinfo/Asia/Amman | Bin 1863 -> 0 bytes lib/pytz/zoneinfo/Asia/Anadyr | Bin 1208 -> 0 bytes lib/pytz/zoneinfo/Asia/Aqtau | Bin 1003 -> 0 bytes lib/pytz/zoneinfo/Asia/Aqtobe | Bin 1033 -> 0 bytes lib/pytz/zoneinfo/Asia/Ashgabat | Bin 637 -> 0 bytes lib/pytz/zoneinfo/Asia/Ashkhabad | Bin 637 -> 0 bytes lib/pytz/zoneinfo/Asia/Atyrau | Bin 1011 -> 0 bytes lib/pytz/zoneinfo/Asia/Baghdad | Bin 995 -> 0 bytes lib/pytz/zoneinfo/Asia/Bahrain | Bin 211 -> 0 bytes lib/pytz/zoneinfo/Asia/Baku | Bin 1255 -> 0 bytes lib/pytz/zoneinfo/Asia/Bangkok | Bin 211 -> 0 bytes lib/pytz/zoneinfo/Asia/Barnaul | Bin 1241 -> 0 bytes lib/pytz/zoneinfo/Asia/Beirut | Bin 2166 -> 0 bytes lib/pytz/zoneinfo/Asia/Bishkek | Bin 999 -> 0 bytes lib/pytz/zoneinfo/Asia/Brunei | Bin 215 -> 0 bytes lib/pytz/zoneinfo/Asia/Calcutta | Bin 303 -> 0 bytes lib/pytz/zoneinfo/Asia/Chita | Bin 1243 -> 0 bytes lib/pytz/zoneinfo/Asia/Choibalsan | Bin 977 -> 0 bytes lib/pytz/zoneinfo/Asia/Chongqing | Bin 545 -> 0 bytes lib/pytz/zoneinfo/Asia/Chungking | Bin 545 -> 0 bytes lib/pytz/zoneinfo/Asia/Colombo | Bin 404 -> 0 bytes lib/pytz/zoneinfo/Asia/Dacca | Bin 361 -> 0 bytes lib/pytz/zoneinfo/Asia/Damascus | Bin 2306 -> 0 bytes lib/pytz/zoneinfo/Asia/Dhaka | Bin 361 -> 0 bytes lib/pytz/zoneinfo/Asia/Dili | Bin 239 -> 0 bytes lib/pytz/zoneinfo/Asia/Dubai | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Asia/Dushanbe | Bin 607 -> 0 bytes lib/pytz/zoneinfo/Asia/Famagusta | Bin 2028 -> 0 bytes lib/pytz/zoneinfo/Asia/Gaza | Bin 2286 -> 0 bytes lib/pytz/zoneinfo/Asia/Harbin | Bin 545 -> 0 bytes lib/pytz/zoneinfo/Asia/Hebron | Bin 2314 -> 0 bytes lib/pytz/zoneinfo/Asia/Ho_Chi_Minh | Bin 375 -> 0 bytes lib/pytz/zoneinfo/Asia/Hong_Kong | Bin 1175 -> 0 bytes lib/pytz/zoneinfo/Asia/Hovd | Bin 907 -> 0 bytes lib/pytz/zoneinfo/Asia/Irkutsk | Bin 1267 -> 0 bytes lib/pytz/zoneinfo/Asia/Istanbul | Bin 2157 -> 0 bytes lib/pytz/zoneinfo/Asia/Jakarta | Bin 383 -> 0 bytes lib/pytz/zoneinfo/Asia/Jayapura | Bin 237 -> 0 bytes lib/pytz/zoneinfo/Asia/Jerusalem | Bin 2256 -> 0 bytes lib/pytz/zoneinfo/Asia/Kabul | Bin 220 -> 0 bytes lib/pytz/zoneinfo/Asia/Kamchatka | Bin 1184 -> 0 bytes lib/pytz/zoneinfo/Asia/Karachi | Bin 403 -> 0 bytes lib/pytz/zoneinfo/Asia/Kashgar | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Asia/Kathmandu | Bin 224 -> 0 bytes lib/pytz/zoneinfo/Asia/Katmandu | Bin 224 -> 0 bytes lib/pytz/zoneinfo/Asia/Khandyga | Bin 1297 -> 0 bytes lib/pytz/zoneinfo/Asia/Kolkata | Bin 303 -> 0 bytes lib/pytz/zoneinfo/Asia/Krasnoyarsk | Bin 1229 -> 0 bytes lib/pytz/zoneinfo/Asia/Kuala_Lumpur | Bin 415 -> 0 bytes lib/pytz/zoneinfo/Asia/Kuching | Bin 507 -> 0 bytes lib/pytz/zoneinfo/Asia/Kuwait | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Asia/Macao | Bin 1241 -> 0 bytes lib/pytz/zoneinfo/Asia/Macau | Bin 1241 -> 0 bytes lib/pytz/zoneinfo/Asia/Magadan | Bin 1244 -> 0 bytes lib/pytz/zoneinfo/Asia/Makassar | Bin 274 -> 0 bytes lib/pytz/zoneinfo/Asia/Manila | Bin 350 -> 0 bytes lib/pytz/zoneinfo/Asia/Muscat | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Asia/Nicosia | Bin 2002 -> 0 bytes lib/pytz/zoneinfo/Asia/Novokuznetsk | Bin 1183 -> 0 bytes lib/pytz/zoneinfo/Asia/Novosibirsk | Bin 1241 -> 0 bytes lib/pytz/zoneinfo/Asia/Omsk | Bin 1229 -> 0 bytes lib/pytz/zoneinfo/Asia/Oral | Bin 1025 -> 0 bytes lib/pytz/zoneinfo/Asia/Phnom_Penh | Bin 211 -> 0 bytes lib/pytz/zoneinfo/Asia/Pontianak | Bin 381 -> 0 bytes lib/pytz/zoneinfo/Asia/Pyongyang | Bin 253 -> 0 bytes lib/pytz/zoneinfo/Asia/Qatar | Bin 211 -> 0 bytes lib/pytz/zoneinfo/Asia/Qyzylorda | Bin 1017 -> 0 bytes lib/pytz/zoneinfo/Asia/Rangoon | Bin 288 -> 0 bytes lib/pytz/zoneinfo/Asia/Riyadh | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Asia/Saigon | Bin 375 -> 0 bytes lib/pytz/zoneinfo/Asia/Sakhalin | Bin 1220 -> 0 bytes lib/pytz/zoneinfo/Asia/Samarkand | Bin 605 -> 0 bytes lib/pytz/zoneinfo/Asia/Seoul | Bin 517 -> 0 bytes lib/pytz/zoneinfo/Asia/Shanghai | Bin 545 -> 0 bytes lib/pytz/zoneinfo/Asia/Singapore | Bin 415 -> 0 bytes lib/pytz/zoneinfo/Asia/Srednekolymsk | Bin 1230 -> 0 bytes lib/pytz/zoneinfo/Asia/Taipei | Bin 781 -> 0 bytes lib/pytz/zoneinfo/Asia/Tashkent | Bin 621 -> 0 bytes lib/pytz/zoneinfo/Asia/Tbilisi | Bin 1071 -> 0 bytes lib/pytz/zoneinfo/Asia/Tehran | Bin 1704 -> 0 bytes lib/pytz/zoneinfo/Asia/Tel_Aviv | Bin 2256 -> 0 bytes lib/pytz/zoneinfo/Asia/Thimbu | Bin 215 -> 0 bytes lib/pytz/zoneinfo/Asia/Thimphu | Bin 215 -> 0 bytes lib/pytz/zoneinfo/Asia/Tokyo | Bin 309 -> 0 bytes lib/pytz/zoneinfo/Asia/Tomsk | Bin 1241 -> 0 bytes lib/pytz/zoneinfo/Asia/Ujung_Pandang | Bin 274 -> 0 bytes lib/pytz/zoneinfo/Asia/Ulaanbaatar | Bin 907 -> 0 bytes lib/pytz/zoneinfo/Asia/Ulan_Bator | Bin 907 -> 0 bytes lib/pytz/zoneinfo/Asia/Urumqi | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Asia/Ust-Nera | Bin 1276 -> 0 bytes lib/pytz/zoneinfo/Asia/Vientiane | Bin 211 -> 0 bytes lib/pytz/zoneinfo/Asia/Vladivostok | Bin 1230 -> 0 bytes lib/pytz/zoneinfo/Asia/Yakutsk | Bin 1229 -> 0 bytes lib/pytz/zoneinfo/Asia/Yangon | Bin 288 -> 0 bytes lib/pytz/zoneinfo/Asia/Yekaterinburg | Bin 1267 -> 0 bytes lib/pytz/zoneinfo/Asia/Yerevan | Bin 1199 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Azores | Bin 3484 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Bermuda | Bin 1990 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Canary | Bin 1897 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Cape_Verde | Bin 270 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Faeroe | Bin 1815 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Faroe | Bin 1815 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Jan_Mayen | Bin 2242 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Madeira | Bin 3475 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Reykjavik | Bin 1174 -> 0 bytes lib/pytz/zoneinfo/Atlantic/South_Georgia | Bin 172 -> 0 bytes lib/pytz/zoneinfo/Atlantic/St_Helena | Bin 156 -> 0 bytes lib/pytz/zoneinfo/Atlantic/Stanley | Bin 1242 -> 0 bytes lib/pytz/zoneinfo/Australia/ACT | Bin 2214 -> 0 bytes lib/pytz/zoneinfo/Australia/Adelaide | Bin 2233 -> 0 bytes lib/pytz/zoneinfo/Australia/Brisbane | Bin 443 -> 0 bytes lib/pytz/zoneinfo/Australia/Broken_Hill | Bin 2269 -> 0 bytes lib/pytz/zoneinfo/Australia/Canberra | Bin 2214 -> 0 bytes lib/pytz/zoneinfo/Australia/Currie | Bin 2214 -> 0 bytes lib/pytz/zoneinfo/Australia/Darwin | Bin 318 -> 0 bytes lib/pytz/zoneinfo/Australia/Eucla | Bin 494 -> 0 bytes lib/pytz/zoneinfo/Australia/Hobart | Bin 2326 -> 0 bytes lib/pytz/zoneinfo/Australia/LHI | Bin 1880 -> 0 bytes lib/pytz/zoneinfo/Australia/Lindeman | Bin 513 -> 0 bytes lib/pytz/zoneinfo/Australia/Lord_Howe | Bin 1880 -> 0 bytes lib/pytz/zoneinfo/Australia/Melbourne | Bin 2214 -> 0 bytes lib/pytz/zoneinfo/Australia/NSW | Bin 2214 -> 0 bytes lib/pytz/zoneinfo/Australia/North | Bin 318 -> 0 bytes lib/pytz/zoneinfo/Australia/Perth | Bin 470 -> 0 bytes lib/pytz/zoneinfo/Australia/Queensland | Bin 443 -> 0 bytes lib/pytz/zoneinfo/Australia/South | Bin 2233 -> 0 bytes lib/pytz/zoneinfo/Australia/Sydney | Bin 2214 -> 0 bytes lib/pytz/zoneinfo/Australia/Tasmania | Bin 2326 -> 0 bytes lib/pytz/zoneinfo/Australia/Victoria | Bin 2214 -> 0 bytes lib/pytz/zoneinfo/Australia/West | Bin 470 -> 0 bytes lib/pytz/zoneinfo/Australia/Yancowinna | Bin 2269 -> 0 bytes lib/pytz/zoneinfo/Brazil/Acre | Bin 648 -> 0 bytes lib/pytz/zoneinfo/Brazil/DeNoronha | Bin 728 -> 0 bytes lib/pytz/zoneinfo/Brazil/East | Bin 2002 -> 0 bytes lib/pytz/zoneinfo/Brazil/West | Bin 616 -> 0 bytes lib/pytz/zoneinfo/CET | Bin 2102 -> 0 bytes lib/pytz/zoneinfo/CST6CDT | Bin 2294 -> 0 bytes lib/pytz/zoneinfo/Canada/Atlantic | Bin 3424 -> 0 bytes lib/pytz/zoneinfo/Canada/Central | Bin 2882 -> 0 bytes lib/pytz/zoneinfo/Canada/Eastern | Bin 3494 -> 0 bytes lib/pytz/zoneinfo/Canada/Mountain | Bin 2388 -> 0 bytes lib/pytz/zoneinfo/Canada/Newfoundland | Bin 3655 -> 0 bytes lib/pytz/zoneinfo/Canada/Pacific | Bin 2892 -> 0 bytes lib/pytz/zoneinfo/Canada/Saskatchewan | Bin 980 -> 0 bytes lib/pytz/zoneinfo/Canada/Yukon | Bin 2084 -> 0 bytes lib/pytz/zoneinfo/Chile/Continental | Bin 2529 -> 0 bytes lib/pytz/zoneinfo/Chile/EasterIsland | Bin 2233 -> 0 bytes lib/pytz/zoneinfo/Cuba | Bin 2428 -> 0 bytes lib/pytz/zoneinfo/EET | Bin 1876 -> 0 bytes lib/pytz/zoneinfo/EST | Bin 118 -> 0 bytes lib/pytz/zoneinfo/EST5EDT | Bin 2294 -> 0 bytes lib/pytz/zoneinfo/Egypt | Bin 1963 -> 0 bytes lib/pytz/zoneinfo/Eire | Bin 3522 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+0 | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+1 | Bin 120 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+10 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+11 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+12 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+2 | Bin 120 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+3 | Bin 120 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+4 | Bin 120 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+5 | Bin 120 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+6 | Bin 120 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+7 | Bin 120 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+8 | Bin 120 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT+9 | Bin 120 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-0 | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-1 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-10 | Bin 122 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-11 | Bin 122 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-12 | Bin 122 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-13 | Bin 122 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-14 | Bin 122 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-2 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-3 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-4 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-5 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-6 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-7 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-8 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT-9 | Bin 121 -> 0 bytes lib/pytz/zoneinfo/Etc/GMT0 | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Etc/Greenwich | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Etc/UCT | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Etc/UTC | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Etc/Universal | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Etc/Zulu | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Europe/Amsterdam | Bin 2940 -> 0 bytes lib/pytz/zoneinfo/Europe/Andorra | Bin 1742 -> 0 bytes lib/pytz/zoneinfo/Europe/Astrakhan | Bin 1183 -> 0 bytes lib/pytz/zoneinfo/Europe/Athens | Bin 2262 -> 0 bytes lib/pytz/zoneinfo/Europe/Belfast | Bin 3678 -> 0 bytes lib/pytz/zoneinfo/Europe/Belgrade | Bin 1948 -> 0 bytes lib/pytz/zoneinfo/Europe/Berlin | Bin 2326 -> 0 bytes lib/pytz/zoneinfo/Europe/Bratislava | Bin 2329 -> 0 bytes lib/pytz/zoneinfo/Europe/Brussels | Bin 2961 -> 0 bytes lib/pytz/zoneinfo/Europe/Bucharest | Bin 2212 -> 0 bytes lib/pytz/zoneinfo/Europe/Budapest | Bin 2396 -> 0 bytes lib/pytz/zoneinfo/Europe/Busingen | Bin 1909 -> 0 bytes lib/pytz/zoneinfo/Europe/Chisinau | Bin 2436 -> 0 bytes lib/pytz/zoneinfo/Europe/Copenhagen | Bin 2151 -> 0 bytes lib/pytz/zoneinfo/Europe/Dublin | Bin 3522 -> 0 bytes lib/pytz/zoneinfo/Europe/Gibraltar | Bin 3052 -> 0 bytes lib/pytz/zoneinfo/Europe/Guernsey | Bin 3678 -> 0 bytes lib/pytz/zoneinfo/Europe/Helsinki | Bin 1900 -> 0 bytes lib/pytz/zoneinfo/Europe/Isle_of_Man | Bin 3678 -> 0 bytes lib/pytz/zoneinfo/Europe/Istanbul | Bin 2157 -> 0 bytes lib/pytz/zoneinfo/Europe/Jersey | Bin 3678 -> 0 bytes lib/pytz/zoneinfo/Europe/Kaliningrad | Bin 1509 -> 0 bytes lib/pytz/zoneinfo/Europe/Kiev | Bin 2088 -> 0 bytes lib/pytz/zoneinfo/Europe/Kirov | Bin 1153 -> 0 bytes lib/pytz/zoneinfo/Europe/Lisbon | Bin 3469 -> 0 bytes lib/pytz/zoneinfo/Europe/Ljubljana | Bin 1948 -> 0 bytes lib/pytz/zoneinfo/Europe/London | Bin 3678 -> 0 bytes lib/pytz/zoneinfo/Europe/Luxembourg | Bin 2960 -> 0 bytes lib/pytz/zoneinfo/Europe/Madrid | Bin 2628 -> 0 bytes lib/pytz/zoneinfo/Europe/Malta | Bin 2620 -> 0 bytes lib/pytz/zoneinfo/Europe/Mariehamn | Bin 1900 -> 0 bytes lib/pytz/zoneinfo/Europe/Minsk | Bin 1361 -> 0 bytes lib/pytz/zoneinfo/Europe/Monaco | Bin 2944 -> 0 bytes lib/pytz/zoneinfo/Europe/Moscow | Bin 1535 -> 0 bytes lib/pytz/zoneinfo/Europe/Nicosia | Bin 2002 -> 0 bytes lib/pytz/zoneinfo/Europe/Oslo | Bin 2242 -> 0 bytes lib/pytz/zoneinfo/Europe/Paris | Bin 2962 -> 0 bytes lib/pytz/zoneinfo/Europe/Podgorica | Bin 1948 -> 0 bytes lib/pytz/zoneinfo/Europe/Prague | Bin 2329 -> 0 bytes lib/pytz/zoneinfo/Europe/Riga | Bin 2226 -> 0 bytes lib/pytz/zoneinfo/Europe/Rome | Bin 2683 -> 0 bytes lib/pytz/zoneinfo/Europe/Samara | Bin 1215 -> 0 bytes lib/pytz/zoneinfo/Europe/San_Marino | Bin 2683 -> 0 bytes lib/pytz/zoneinfo/Europe/Sarajevo | Bin 1948 -> 0 bytes lib/pytz/zoneinfo/Europe/Saratov | Bin 1183 -> 0 bytes lib/pytz/zoneinfo/Europe/Simferopol | Bin 1481 -> 0 bytes lib/pytz/zoneinfo/Europe/Skopje | Bin 1948 -> 0 bytes lib/pytz/zoneinfo/Europe/Sofia | Bin 2121 -> 0 bytes lib/pytz/zoneinfo/Europe/Stockholm | Bin 1909 -> 0 bytes lib/pytz/zoneinfo/Europe/Tallinn | Bin 2178 -> 0 bytes lib/pytz/zoneinfo/Europe/Tirane | Bin 2084 -> 0 bytes lib/pytz/zoneinfo/Europe/Tiraspol | Bin 2436 -> 0 bytes lib/pytz/zoneinfo/Europe/Ulyanovsk | Bin 1267 -> 0 bytes lib/pytz/zoneinfo/Europe/Uzhgorod | Bin 2094 -> 0 bytes lib/pytz/zoneinfo/Europe/Vaduz | Bin 1909 -> 0 bytes lib/pytz/zoneinfo/Europe/Vatican | Bin 2683 -> 0 bytes lib/pytz/zoneinfo/Europe/Vienna | Bin 2228 -> 0 bytes lib/pytz/zoneinfo/Europe/Vilnius | Bin 2190 -> 0 bytes lib/pytz/zoneinfo/Europe/Volgograd | Bin 1183 -> 0 bytes lib/pytz/zoneinfo/Europe/Warsaw | Bin 2696 -> 0 bytes lib/pytz/zoneinfo/Europe/Zagreb | Bin 1948 -> 0 bytes lib/pytz/zoneinfo/Europe/Zaporozhye | Bin 2106 -> 0 bytes lib/pytz/zoneinfo/Europe/Zurich | Bin 1909 -> 0 bytes lib/pytz/zoneinfo/Factory | Bin 120 -> 0 bytes lib/pytz/zoneinfo/GB | Bin 3678 -> 0 bytes lib/pytz/zoneinfo/GB-Eire | Bin 3678 -> 0 bytes lib/pytz/zoneinfo/GMT | Bin 118 -> 0 bytes lib/pytz/zoneinfo/GMT+0 | Bin 118 -> 0 bytes lib/pytz/zoneinfo/GMT-0 | Bin 118 -> 0 bytes lib/pytz/zoneinfo/GMT0 | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Greenwich | Bin 118 -> 0 bytes lib/pytz/zoneinfo/HST | Bin 119 -> 0 bytes lib/pytz/zoneinfo/Hongkong | Bin 1175 -> 0 bytes lib/pytz/zoneinfo/Iceland | Bin 1174 -> 0 bytes lib/pytz/zoneinfo/Indian/Antananarivo | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Indian/Chagos | Bin 211 -> 0 bytes lib/pytz/zoneinfo/Indian/Christmas | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Indian/Cocos | Bin 182 -> 0 bytes lib/pytz/zoneinfo/Indian/Comoro | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Indian/Kerguelen | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Indian/Mahe | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Indian/Maldives | Bin 211 -> 0 bytes lib/pytz/zoneinfo/Indian/Mauritius | Bin 253 -> 0 bytes lib/pytz/zoneinfo/Indian/Mayotte | Bin 271 -> 0 bytes lib/pytz/zoneinfo/Indian/Reunion | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Iran | Bin 1704 -> 0 bytes lib/pytz/zoneinfo/Israel | Bin 2256 -> 0 bytes lib/pytz/zoneinfo/Jamaica | Bin 498 -> 0 bytes lib/pytz/zoneinfo/Japan | Bin 309 -> 0 bytes lib/pytz/zoneinfo/Kwajalein | Bin 250 -> 0 bytes lib/pytz/zoneinfo/Libya | Bin 641 -> 0 bytes lib/pytz/zoneinfo/MET | Bin 2102 -> 0 bytes lib/pytz/zoneinfo/MST | Bin 118 -> 0 bytes lib/pytz/zoneinfo/MST7MDT | Bin 2294 -> 0 bytes lib/pytz/zoneinfo/Mexico/BajaNorte | Bin 2342 -> 0 bytes lib/pytz/zoneinfo/Mexico/BajaSur | Bin 1550 -> 0 bytes lib/pytz/zoneinfo/Mexico/General | Bin 1604 -> 0 bytes lib/pytz/zoneinfo/NZ | Bin 2451 -> 0 bytes lib/pytz/zoneinfo/NZ-CHAT | Bin 2078 -> 0 bytes lib/pytz/zoneinfo/Navajo | Bin 2444 -> 0 bytes lib/pytz/zoneinfo/PRC | Bin 545 -> 0 bytes lib/pytz/zoneinfo/PST8PDT | Bin 2294 -> 0 bytes lib/pytz/zoneinfo/Pacific/Apia | Bin 1125 -> 0 bytes lib/pytz/zoneinfo/Pacific/Auckland | Bin 2451 -> 0 bytes lib/pytz/zoneinfo/Pacific/Bougainville | Bin 286 -> 0 bytes lib/pytz/zoneinfo/Pacific/Chatham | Bin 2078 -> 0 bytes lib/pytz/zoneinfo/Pacific/Chuuk | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Pacific/Easter | Bin 2233 -> 0 bytes lib/pytz/zoneinfo/Pacific/Efate | Bin 478 -> 0 bytes lib/pytz/zoneinfo/Pacific/Enderbury | Bin 250 -> 0 bytes lib/pytz/zoneinfo/Pacific/Fakaofo | Bin 212 -> 0 bytes lib/pytz/zoneinfo/Pacific/Fiji | Bin 1090 -> 0 bytes lib/pytz/zoneinfo/Pacific/Funafuti | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Pacific/Galapagos | Bin 254 -> 0 bytes lib/pytz/zoneinfo/Pacific/Gambier | Bin 172 -> 0 bytes lib/pytz/zoneinfo/Pacific/Guadalcanal | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Pacific/Guam | Bin 216 -> 0 bytes lib/pytz/zoneinfo/Pacific/Honolulu | Bin 329 -> 0 bytes lib/pytz/zoneinfo/Pacific/Johnston | Bin 329 -> 0 bytes lib/pytz/zoneinfo/Pacific/Kiritimati | Bin 254 -> 0 bytes lib/pytz/zoneinfo/Pacific/Kosrae | Bin 242 -> 0 bytes lib/pytz/zoneinfo/Pacific/Kwajalein | Bin 250 -> 0 bytes lib/pytz/zoneinfo/Pacific/Majuro | Bin 212 -> 0 bytes lib/pytz/zoneinfo/Pacific/Marquesas | Bin 181 -> 0 bytes lib/pytz/zoneinfo/Pacific/Midway | Bin 187 -> 0 bytes lib/pytz/zoneinfo/Pacific/Nauru | Bin 268 -> 0 bytes lib/pytz/zoneinfo/Pacific/Niue | Bin 257 -> 0 bytes lib/pytz/zoneinfo/Pacific/Norfolk | Bin 314 -> 0 bytes lib/pytz/zoneinfo/Pacific/Noumea | Bin 314 -> 0 bytes lib/pytz/zoneinfo/Pacific/Pago_Pago | Bin 187 -> 0 bytes lib/pytz/zoneinfo/Pacific/Palau | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Pacific/Pitcairn | Bin 214 -> 0 bytes lib/pytz/zoneinfo/Pacific/Pohnpei | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Pacific/Ponape | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Pacific/Port_Moresby | Bin 196 -> 0 bytes lib/pytz/zoneinfo/Pacific/Rarotonga | Bin 593 -> 0 bytes lib/pytz/zoneinfo/Pacific/Saipan | Bin 216 -> 0 bytes lib/pytz/zoneinfo/Pacific/Samoa | Bin 187 -> 0 bytes lib/pytz/zoneinfo/Pacific/Tahiti | Bin 173 -> 0 bytes lib/pytz/zoneinfo/Pacific/Tarawa | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Pacific/Tongatapu | Bin 384 -> 0 bytes lib/pytz/zoneinfo/Pacific/Truk | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Pacific/Wake | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Pacific/Wallis | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Pacific/Yap | Bin 174 -> 0 bytes lib/pytz/zoneinfo/Poland | Bin 2696 -> 0 bytes lib/pytz/zoneinfo/Portugal | Bin 3469 -> 0 bytes lib/pytz/zoneinfo/ROC | Bin 781 -> 0 bytes lib/pytz/zoneinfo/ROK | Bin 517 -> 0 bytes lib/pytz/zoneinfo/Singapore | Bin 415 -> 0 bytes lib/pytz/zoneinfo/Turkey | Bin 2157 -> 0 bytes lib/pytz/zoneinfo/UCT | Bin 118 -> 0 bytes lib/pytz/zoneinfo/US/Alaska | Bin 2371 -> 0 bytes lib/pytz/zoneinfo/US/Aleutian | Bin 2356 -> 0 bytes lib/pytz/zoneinfo/US/Arizona | Bin 344 -> 0 bytes lib/pytz/zoneinfo/US/Central | Bin 3576 -> 0 bytes lib/pytz/zoneinfo/US/East-Indiana | Bin 1666 -> 0 bytes lib/pytz/zoneinfo/US/Eastern | Bin 3536 -> 0 bytes lib/pytz/zoneinfo/US/Hawaii | Bin 329 -> 0 bytes lib/pytz/zoneinfo/US/Indiana-Starke | Bin 2428 -> 0 bytes lib/pytz/zoneinfo/US/Michigan | Bin 2174 -> 0 bytes lib/pytz/zoneinfo/US/Mountain | Bin 2444 -> 0 bytes lib/pytz/zoneinfo/US/Pacific | Bin 2836 -> 0 bytes lib/pytz/zoneinfo/US/Samoa | Bin 187 -> 0 bytes lib/pytz/zoneinfo/UTC | Bin 118 -> 0 bytes lib/pytz/zoneinfo/Universal | Bin 118 -> 0 bytes lib/pytz/zoneinfo/W-SU | Bin 1535 -> 0 bytes lib/pytz/zoneinfo/WET | Bin 1873 -> 0 bytes lib/pytz/zoneinfo/Zulu | Bin 118 -> 0 bytes lib/pytz/zoneinfo/iso3166.tab | 274 - lib/pytz/zoneinfo/leapseconds | 66 - lib/pytz/zoneinfo/posixrules | Bin 3536 -> 0 bytes lib/pytz/zoneinfo/tzdata.zi | 4177 --------- lib/pytz/zoneinfo/zone.tab | 448 - lib/pytz/zoneinfo/zone1970.tab | 382 - lib/requests/__init__.py | 131 - lib/requests/__version__.py | 14 - lib/requests/_internal_utils.py | 42 - lib/requests/adapters.py | 533 -- lib/requests/api.py | 158 - lib/requests/auth.py | 305 - lib/requests/certs.py | 18 - lib/requests/compat.py | 70 - lib/requests/cookies.py | 549 -- lib/requests/exceptions.py | 126 - lib/requests/help.py | 119 - lib/requests/hooks.py | 34 - lib/requests/models.py | 953 -- lib/requests/packages.py | 14 - lib/requests/sessions.py | 770 -- lib/requests/status_codes.py | 120 - lib/requests/structures.py | 103 - lib/requests/utils.py | 977 -- lib/schedule/__init__.py | 528 -- lib/setuptools/__init__.py | 195 - lib/setuptools/_deprecation_warning.py | 7 - lib/setuptools/_vendor/__init__.py | 0 lib/setuptools/_vendor/packaging/__about__.py | 21 - lib/setuptools/_vendor/packaging/__init__.py | 14 - lib/setuptools/_vendor/packaging/_compat.py | 30 - .../_vendor/packaging/_structures.py | 68 - lib/setuptools/_vendor/packaging/markers.py | 301 - .../_vendor/packaging/requirements.py | 127 - .../_vendor/packaging/specifiers.py | 774 -- lib/setuptools/_vendor/packaging/utils.py | 14 - lib/setuptools/_vendor/packaging/version.py | 393 - lib/setuptools/_vendor/pyparsing.py | 5742 ------------ lib/setuptools/_vendor/six.py | 868 -- lib/setuptools/archive_util.py | 173 - lib/setuptools/build_meta.py | 182 - lib/setuptools/cli-32.exe | Bin 65536 -> 0 bytes lib/setuptools/cli-64.exe | Bin 74752 -> 0 bytes lib/setuptools/cli.exe | Bin 65536 -> 0 bytes lib/setuptools/command/__init__.py | 18 - lib/setuptools/command/alias.py | 80 - lib/setuptools/command/bdist_egg.py | 502 - lib/setuptools/command/bdist_rpm.py | 43 - lib/setuptools/command/bdist_wininst.py | 21 - lib/setuptools/command/build_clib.py | 98 - lib/setuptools/command/build_ext.py | 321 - lib/setuptools/command/build_py.py | 270 - lib/setuptools/command/develop.py | 218 - lib/setuptools/command/dist_info.py | 36 - lib/setuptools/command/easy_install.py | 2342 ----- lib/setuptools/command/egg_info.py | 716 -- lib/setuptools/command/install.py | 125 - lib/setuptools/command/install_egg_info.py | 62 - lib/setuptools/command/install_lib.py | 121 - lib/setuptools/command/install_scripts.py | 65 - lib/setuptools/command/launcher manifest.xml | 15 - lib/setuptools/command/py36compat.py | 136 - lib/setuptools/command/register.py | 18 - lib/setuptools/command/rotate.py | 66 - lib/setuptools/command/saveopts.py | 22 - lib/setuptools/command/sdist.py | 200 - lib/setuptools/command/setopt.py | 149 - lib/setuptools/command/test.py | 270 - lib/setuptools/command/upload.py | 196 - lib/setuptools/command/upload_docs.py | 206 - lib/setuptools/config.py | 635 -- lib/setuptools/dep_util.py | 23 - lib/setuptools/depends.py | 186 - lib/setuptools/dist.py | 1147 --- lib/setuptools/extension.py | 57 - lib/setuptools/extern/__init__.py | 73 - lib/setuptools/glibc.py | 86 - lib/setuptools/glob.py | 174 - lib/setuptools/gui-32.exe | Bin 65536 -> 0 bytes lib/setuptools/gui-64.exe | Bin 75264 -> 0 bytes lib/setuptools/gui.exe | Bin 65536 -> 0 bytes lib/setuptools/launch.py | 35 - lib/setuptools/lib2to3_ex.py | 62 - lib/setuptools/monkey.py | 179 - lib/setuptools/msvc.py | 1301 --- lib/setuptools/namespaces.py | 107 - lib/setuptools/package_index.py | 1128 --- lib/setuptools/pep425tags.py | 319 - lib/setuptools/py27compat.py | 28 - lib/setuptools/py31compat.py | 32 - lib/setuptools/py33compat.py | 55 - lib/setuptools/py36compat.py | 82 - lib/setuptools/sandbox.py | 491 - lib/setuptools/script (dev).tmpl | 6 - lib/setuptools/script.tmpl | 3 - lib/setuptools/site-patch.py | 74 - lib/setuptools/ssl_support.py | 260 - lib/setuptools/unicode_utils.py | 44 - lib/setuptools/version.py | 6 - lib/setuptools/wheel.py | 210 - lib/setuptools/windows_support.py | 29 - lib/six.py | 891 -- lib/typing.py | 2413 ----- lib/urllib3/__init__.py | 92 - lib/urllib3/_collections.py | 329 - lib/urllib3/connection.py | 391 - lib/urllib3/connectionpool.py | 896 -- lib/urllib3/contrib/__init__.py | 0 lib/urllib3/contrib/_appengine_environ.py | 30 - .../contrib/_securetransport/__init__.py | 0 .../contrib/_securetransport/bindings.py | 593 -- .../contrib/_securetransport/low_level.py | 346 - lib/urllib3/contrib/appengine.py | 289 - lib/urllib3/contrib/ntlmpool.py | 111 - lib/urllib3/contrib/pyopenssl.py | 466 - lib/urllib3/contrib/securetransport.py | 804 -- lib/urllib3/contrib/socks.py | 192 - lib/urllib3/exceptions.py | 246 - lib/urllib3/fields.py | 178 - lib/urllib3/filepost.py | 98 - lib/urllib3/packages/__init__.py | 5 - lib/urllib3/packages/backports/__init__.py | 0 lib/urllib3/packages/backports/makefile.py | 53 - lib/urllib3/packages/six.py | 868 -- .../packages/ssl_match_hostname/__init__.py | 19 - .../ssl_match_hostname/_implementation.py | 156 - lib/urllib3/poolmanager.py | 450 - lib/urllib3/request.py | 150 - lib/urllib3/response.py | 705 -- lib/urllib3/util/__init__.py | 54 - lib/urllib3/util/connection.py | 134 - lib/urllib3/util/queue.py | 21 - lib/urllib3/util/request.py | 118 - lib/urllib3/util/response.py | 87 - lib/urllib3/util/retry.py | 411 - lib/urllib3/util/ssl_.py | 381 - lib/urllib3/util/timeout.py | 242 - lib/urllib3/util/url.py | 230 - lib/urllib3/util/wait.py | 150 - lib/zope.interface-4.6.0-py3.7-nspkg.pth | 1 - lib/zope/interface/__init__.py | 90 - lib/zope/interface/_compat.py | 58 - lib/zope/interface/_flatten.py | 35 - .../_zope_interface_coptimizations.c | 1726 ---- ...pe_interface_coptimizations.cp37-win32.pyd | Bin 22528 -> 0 bytes lib/zope/interface/adapter.py | 712 -- lib/zope/interface/advice.py | 205 - lib/zope/interface/common/__init__.py | 2 - lib/zope/interface/common/idatetime.py | 606 -- lib/zope/interface/common/interfaces.py | 212 - lib/zope/interface/common/mapping.py | 150 - lib/zope/interface/common/sequence.py | 165 - lib/zope/interface/common/tests/__init__.py | 2 - .../interface/common/tests/basemapping.py | 107 - .../interface/common/tests/test_idatetime.py | 37 - .../common/tests/test_import_interfaces.py | 20 - lib/zope/interface/declarations.py | 929 -- lib/zope/interface/document.py | 120 - lib/zope/interface/exceptions.py | 67 - lib/zope/interface/interface.py | 687 -- lib/zope/interface/interfaces.py | 1282 --- lib/zope/interface/registry.py | 654 -- lib/zope/interface/ro.py | 64 - lib/zope/interface/tests/__init__.py | 1 - lib/zope/interface/tests/advisory_testing.py | 42 - lib/zope/interface/tests/dummy.py | 23 - lib/zope/interface/tests/idummy.py | 23 - lib/zope/interface/tests/ifoo.py | 26 - lib/zope/interface/tests/ifoo_other.py | 26 - lib/zope/interface/tests/m1.py | 21 - lib/zope/interface/tests/m2.py | 15 - lib/zope/interface/tests/odd.py | 128 - lib/zope/interface/tests/test_adapter.py | 1419 --- lib/zope/interface/tests/test_advice.py | 355 - lib/zope/interface/tests/test_declarations.py | 1658 ---- lib/zope/interface/tests/test_document.py | 505 - lib/zope/interface/tests/test_element.py | 31 - lib/zope/interface/tests/test_exceptions.py | 72 - lib/zope/interface/tests/test_interface.py | 2123 ----- lib/zope/interface/tests/test_interfaces.py | 95 - .../interface/tests/test_odd_declarations.py | 268 - lib/zope/interface/tests/test_registry.py | 2788 ------ lib/zope/interface/tests/test_ro.py | 115 - lib/zope/interface/tests/test_sorting.py | 47 - lib/zope/interface/tests/test_verify.py | 582 -- lib/zope/interface/verify.py | 123 - requirements.txt | 5 +- varken.py | 4 - 943 files changed, 16 insertions(+), 125530 deletions(-) delete mode 100644 lib/DateTime/DateTime.py delete mode 100644 lib/DateTime/DateTime.txt delete mode 100644 lib/DateTime/__init__.py delete mode 100644 lib/DateTime/interfaces.py delete mode 100644 lib/DateTime/pytz.txt delete mode 100644 lib/DateTime/pytz_support.py delete mode 100644 lib/DateTime/tests/__init__.py delete mode 100644 lib/DateTime/tests/julian_testdata.txt delete mode 100644 lib/DateTime/tests/test_datetime.py delete mode 100644 lib/backports/configparser/__init__.py delete mode 100644 lib/backports/configparser/helpers.py delete mode 100644 lib/bin/chardetect.exe delete mode 100644 lib/bin/easy_install-3.7.exe delete mode 100644 lib/bin/easy_install.exe delete mode 100644 lib/certifi/__init__.py delete mode 100644 lib/certifi/__main__.py delete mode 100644 lib/certifi/cacert.pem delete mode 100644 lib/certifi/core.py delete mode 100644 lib/chardet/__init__.py delete mode 100644 lib/chardet/big5freq.py delete mode 100644 lib/chardet/big5prober.py delete mode 100644 lib/chardet/chardistribution.py delete mode 100644 lib/chardet/charsetgroupprober.py delete mode 100644 lib/chardet/charsetprober.py delete mode 100644 lib/chardet/cli/__init__.py delete mode 100644 lib/chardet/cli/chardetect.py delete mode 100644 lib/chardet/codingstatemachine.py delete mode 100644 lib/chardet/compat.py delete mode 100644 lib/chardet/cp949prober.py delete mode 100644 lib/chardet/enums.py delete mode 100644 lib/chardet/escprober.py delete mode 100644 lib/chardet/escsm.py delete mode 100644 lib/chardet/eucjpprober.py delete mode 100644 lib/chardet/euckrfreq.py delete mode 100644 lib/chardet/euckrprober.py delete mode 100644 lib/chardet/euctwfreq.py delete mode 100644 lib/chardet/euctwprober.py delete mode 100644 lib/chardet/gb2312freq.py delete mode 100644 lib/chardet/gb2312prober.py delete mode 100644 lib/chardet/hebrewprober.py delete mode 100644 lib/chardet/jisfreq.py delete mode 100644 lib/chardet/jpcntx.py delete mode 100644 lib/chardet/langbulgarianmodel.py delete mode 100644 lib/chardet/langcyrillicmodel.py delete mode 100644 lib/chardet/langgreekmodel.py delete mode 100644 lib/chardet/langhebrewmodel.py delete mode 100644 lib/chardet/langhungarianmodel.py delete mode 100644 lib/chardet/langthaimodel.py delete mode 100644 lib/chardet/langturkishmodel.py delete mode 100644 lib/chardet/latin1prober.py delete mode 100644 lib/chardet/mbcharsetprober.py delete mode 100644 lib/chardet/mbcsgroupprober.py delete mode 100644 lib/chardet/mbcssm.py delete mode 100644 lib/chardet/sbcharsetprober.py delete mode 100644 lib/chardet/sbcsgroupprober.py delete mode 100644 lib/chardet/sjisprober.py delete mode 100644 lib/chardet/universaldetector.py delete mode 100644 lib/chardet/utf8prober.py delete mode 100644 lib/chardet/version.py delete mode 100644 lib/configparser-3.5.0-py3.7-nspkg.pth delete mode 100644 lib/dateutil/__init__.py delete mode 100644 lib/dateutil/_common.py delete mode 100644 lib/dateutil/_version.py delete mode 100644 lib/dateutil/easter.py delete mode 100644 lib/dateutil/parser/__init__.py delete mode 100644 lib/dateutil/parser/_parser.py delete mode 100644 lib/dateutil/parser/isoparser.py delete mode 100644 lib/dateutil/relativedelta.py delete mode 100644 lib/dateutil/rrule.py delete mode 100644 lib/dateutil/tz/__init__.py delete mode 100644 lib/dateutil/tz/_common.py delete mode 100644 lib/dateutil/tz/_factories.py delete mode 100644 lib/dateutil/tz/tz.py delete mode 100644 lib/dateutil/tz/win.py delete mode 100644 lib/dateutil/tzwin.py delete mode 100644 lib/dateutil/utils.py delete mode 100644 lib/dateutil/zoneinfo/__init__.py delete mode 100644 lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz delete mode 100644 lib/dateutil/zoneinfo/rebuild.py delete mode 100644 lib/easy_install.py delete mode 100644 lib/geoip2/__init__.py delete mode 100644 lib/geoip2/compat.py delete mode 100644 lib/geoip2/database.py delete mode 100644 lib/geoip2/errors.py delete mode 100644 lib/geoip2/mixins.py delete mode 100644 lib/geoip2/models.py delete mode 100644 lib/geoip2/records.py delete mode 100644 lib/geoip2/webservice.py delete mode 100644 lib/idna/__init__.py delete mode 100644 lib/idna/codec.py delete mode 100644 lib/idna/compat.py delete mode 100644 lib/idna/core.py delete mode 100644 lib/idna/idnadata.py delete mode 100644 lib/idna/intranges.py delete mode 100644 lib/idna/package_data.py delete mode 100644 lib/idna/uts46data.py delete mode 100644 lib/influxdb/__init__.py delete mode 100644 lib/influxdb/_dataframe_client.py delete mode 100644 lib/influxdb/chunked_json.py delete mode 100644 lib/influxdb/client.py delete mode 100644 lib/influxdb/dataframe_client.py delete mode 100644 lib/influxdb/exceptions.py delete mode 100644 lib/influxdb/helper.py delete mode 100644 lib/influxdb/influxdb08/__init__.py delete mode 100644 lib/influxdb/influxdb08/chunked_json.py delete mode 100644 lib/influxdb/influxdb08/client.py delete mode 100644 lib/influxdb/influxdb08/dataframe_client.py delete mode 100644 lib/influxdb/influxdb08/helper.py delete mode 100644 lib/influxdb/line_protocol.py delete mode 100644 lib/influxdb/resultset.py delete mode 100644 lib/influxdb/tests/__init__.py delete mode 100644 lib/influxdb/tests/chunked_json_test.py delete mode 100644 lib/influxdb/tests/client_test.py delete mode 100644 lib/influxdb/tests/dataframe_client_test.py delete mode 100644 lib/influxdb/tests/helper_test.py delete mode 100644 lib/influxdb/tests/influxdb08/__init__.py delete mode 100644 lib/influxdb/tests/influxdb08/client_test.py delete mode 100644 lib/influxdb/tests/influxdb08/dataframe_client_test.py delete mode 100644 lib/influxdb/tests/influxdb08/helper_test.py delete mode 100644 lib/influxdb/tests/misc.py delete mode 100644 lib/influxdb/tests/resultset_test.py delete mode 100644 lib/influxdb/tests/server_tests/__init__.py delete mode 100644 lib/influxdb/tests/server_tests/base.py delete mode 100644 lib/influxdb/tests/server_tests/client_test_with_server.py delete mode 100644 lib/influxdb/tests/server_tests/influxdb_instance.py delete mode 100644 lib/influxdb/tests/test_line_protocol.py delete mode 100644 lib/maxminddb/__init__.py delete mode 100644 lib/maxminddb/compat.py delete mode 100644 lib/maxminddb/const.py delete mode 100644 lib/maxminddb/decoder.py delete mode 100644 lib/maxminddb/errors.py delete mode 100644 lib/maxminddb/extension/maxminddb.c delete mode 100644 lib/maxminddb/file.py delete mode 100644 lib/maxminddb/reader.py delete mode 100644 lib/pkg_resources/__init__.py delete mode 100644 lib/pkg_resources/_vendor/__init__.py delete mode 100644 lib/pkg_resources/_vendor/appdirs.py delete mode 100644 lib/pkg_resources/_vendor/packaging/__about__.py delete mode 100644 lib/pkg_resources/_vendor/packaging/__init__.py delete mode 100644 lib/pkg_resources/_vendor/packaging/_compat.py delete mode 100644 lib/pkg_resources/_vendor/packaging/_structures.py delete mode 100644 lib/pkg_resources/_vendor/packaging/markers.py delete mode 100644 lib/pkg_resources/_vendor/packaging/requirements.py delete mode 100644 lib/pkg_resources/_vendor/packaging/specifiers.py delete mode 100644 lib/pkg_resources/_vendor/packaging/utils.py delete mode 100644 lib/pkg_resources/_vendor/packaging/version.py delete mode 100644 lib/pkg_resources/_vendor/pyparsing.py delete mode 100644 lib/pkg_resources/_vendor/six.py delete mode 100644 lib/pkg_resources/extern/__init__.py delete mode 100644 lib/pkg_resources/py31compat.py delete mode 100644 lib/pytz/__init__.py delete mode 100644 lib/pytz/exceptions.py delete mode 100644 lib/pytz/lazy.py delete mode 100644 lib/pytz/reference.py delete mode 100644 lib/pytz/tzfile.py delete mode 100644 lib/pytz/tzinfo.py delete mode 100644 lib/pytz/zoneinfo/Africa/Abidjan delete mode 100644 lib/pytz/zoneinfo/Africa/Accra delete mode 100644 lib/pytz/zoneinfo/Africa/Addis_Ababa delete mode 100644 lib/pytz/zoneinfo/Africa/Algiers delete mode 100644 lib/pytz/zoneinfo/Africa/Asmara delete mode 100644 lib/pytz/zoneinfo/Africa/Asmera delete mode 100644 lib/pytz/zoneinfo/Africa/Bamako delete mode 100644 lib/pytz/zoneinfo/Africa/Bangui delete mode 100644 lib/pytz/zoneinfo/Africa/Banjul delete mode 100644 lib/pytz/zoneinfo/Africa/Bissau delete mode 100644 lib/pytz/zoneinfo/Africa/Blantyre delete mode 100644 lib/pytz/zoneinfo/Africa/Brazzaville delete mode 100644 lib/pytz/zoneinfo/Africa/Bujumbura delete mode 100644 lib/pytz/zoneinfo/Africa/Cairo delete mode 100644 lib/pytz/zoneinfo/Africa/Casablanca delete mode 100644 lib/pytz/zoneinfo/Africa/Ceuta delete mode 100644 lib/pytz/zoneinfo/Africa/Conakry delete mode 100644 lib/pytz/zoneinfo/Africa/Dakar delete mode 100644 lib/pytz/zoneinfo/Africa/Dar_es_Salaam delete mode 100644 lib/pytz/zoneinfo/Africa/Djibouti delete mode 100644 lib/pytz/zoneinfo/Africa/Douala delete mode 100644 lib/pytz/zoneinfo/Africa/El_Aaiun delete mode 100644 lib/pytz/zoneinfo/Africa/Freetown delete mode 100644 lib/pytz/zoneinfo/Africa/Gaborone delete mode 100644 lib/pytz/zoneinfo/Africa/Harare delete mode 100644 lib/pytz/zoneinfo/Africa/Johannesburg delete mode 100644 lib/pytz/zoneinfo/Africa/Juba delete mode 100644 lib/pytz/zoneinfo/Africa/Kampala delete mode 100644 lib/pytz/zoneinfo/Africa/Khartoum delete mode 100644 lib/pytz/zoneinfo/Africa/Kigali delete mode 100644 lib/pytz/zoneinfo/Africa/Kinshasa delete mode 100644 lib/pytz/zoneinfo/Africa/Lagos delete mode 100644 lib/pytz/zoneinfo/Africa/Libreville delete mode 100644 lib/pytz/zoneinfo/Africa/Lome delete mode 100644 lib/pytz/zoneinfo/Africa/Luanda delete mode 100644 lib/pytz/zoneinfo/Africa/Lubumbashi delete mode 100644 lib/pytz/zoneinfo/Africa/Lusaka delete mode 100644 lib/pytz/zoneinfo/Africa/Malabo delete mode 100644 lib/pytz/zoneinfo/Africa/Maputo delete mode 100644 lib/pytz/zoneinfo/Africa/Maseru delete mode 100644 lib/pytz/zoneinfo/Africa/Mbabane delete mode 100644 lib/pytz/zoneinfo/Africa/Mogadishu delete mode 100644 lib/pytz/zoneinfo/Africa/Monrovia delete mode 100644 lib/pytz/zoneinfo/Africa/Nairobi delete mode 100644 lib/pytz/zoneinfo/Africa/Ndjamena delete mode 100644 lib/pytz/zoneinfo/Africa/Niamey delete mode 100644 lib/pytz/zoneinfo/Africa/Nouakchott delete mode 100644 lib/pytz/zoneinfo/Africa/Ouagadougou delete mode 100644 lib/pytz/zoneinfo/Africa/Porto-Novo delete mode 100644 lib/pytz/zoneinfo/Africa/Sao_Tome delete mode 100644 lib/pytz/zoneinfo/Africa/Timbuktu delete mode 100644 lib/pytz/zoneinfo/Africa/Tripoli delete mode 100644 lib/pytz/zoneinfo/Africa/Tunis delete mode 100644 lib/pytz/zoneinfo/Africa/Windhoek delete mode 100644 lib/pytz/zoneinfo/America/Adak delete mode 100644 lib/pytz/zoneinfo/America/Anchorage delete mode 100644 lib/pytz/zoneinfo/America/Anguilla delete mode 100644 lib/pytz/zoneinfo/America/Antigua delete mode 100644 lib/pytz/zoneinfo/America/Araguaina delete mode 100644 lib/pytz/zoneinfo/America/Argentina/Buenos_Aires delete mode 100644 lib/pytz/zoneinfo/America/Argentina/Catamarca delete mode 100644 lib/pytz/zoneinfo/America/Argentina/ComodRivadavia delete mode 100644 lib/pytz/zoneinfo/America/Argentina/Cordoba delete mode 100644 lib/pytz/zoneinfo/America/Argentina/Jujuy delete mode 100644 lib/pytz/zoneinfo/America/Argentina/La_Rioja delete mode 100644 lib/pytz/zoneinfo/America/Argentina/Mendoza delete mode 100644 lib/pytz/zoneinfo/America/Argentina/Rio_Gallegos delete mode 100644 lib/pytz/zoneinfo/America/Argentina/Salta delete mode 100644 lib/pytz/zoneinfo/America/Argentina/San_Juan delete mode 100644 lib/pytz/zoneinfo/America/Argentina/San_Luis delete mode 100644 lib/pytz/zoneinfo/America/Argentina/Tucuman delete mode 100644 lib/pytz/zoneinfo/America/Argentina/Ushuaia delete mode 100644 lib/pytz/zoneinfo/America/Aruba delete mode 100644 lib/pytz/zoneinfo/America/Asuncion delete mode 100644 lib/pytz/zoneinfo/America/Atikokan delete mode 100644 lib/pytz/zoneinfo/America/Atka delete mode 100644 lib/pytz/zoneinfo/America/Bahia delete mode 100644 lib/pytz/zoneinfo/America/Bahia_Banderas delete mode 100644 lib/pytz/zoneinfo/America/Barbados delete mode 100644 lib/pytz/zoneinfo/America/Belem delete mode 100644 lib/pytz/zoneinfo/America/Belize delete mode 100644 lib/pytz/zoneinfo/America/Blanc-Sablon delete mode 100644 lib/pytz/zoneinfo/America/Boa_Vista delete mode 100644 lib/pytz/zoneinfo/America/Bogota delete mode 100644 lib/pytz/zoneinfo/America/Boise delete mode 100644 lib/pytz/zoneinfo/America/Buenos_Aires delete mode 100644 lib/pytz/zoneinfo/America/Cambridge_Bay delete mode 100644 lib/pytz/zoneinfo/America/Campo_Grande delete mode 100644 lib/pytz/zoneinfo/America/Cancun delete mode 100644 lib/pytz/zoneinfo/America/Caracas delete mode 100644 lib/pytz/zoneinfo/America/Catamarca delete mode 100644 lib/pytz/zoneinfo/America/Cayenne delete mode 100644 lib/pytz/zoneinfo/America/Cayman delete mode 100644 lib/pytz/zoneinfo/America/Chicago delete mode 100644 lib/pytz/zoneinfo/America/Chihuahua delete mode 100644 lib/pytz/zoneinfo/America/Coral_Harbour delete mode 100644 lib/pytz/zoneinfo/America/Cordoba delete mode 100644 lib/pytz/zoneinfo/America/Costa_Rica delete mode 100644 lib/pytz/zoneinfo/America/Creston delete mode 100644 lib/pytz/zoneinfo/America/Cuiaba delete mode 100644 lib/pytz/zoneinfo/America/Curacao delete mode 100644 lib/pytz/zoneinfo/America/Danmarkshavn delete mode 100644 lib/pytz/zoneinfo/America/Dawson delete mode 100644 lib/pytz/zoneinfo/America/Dawson_Creek delete mode 100644 lib/pytz/zoneinfo/America/Denver delete mode 100644 lib/pytz/zoneinfo/America/Detroit delete mode 100644 lib/pytz/zoneinfo/America/Dominica delete mode 100644 lib/pytz/zoneinfo/America/Edmonton delete mode 100644 lib/pytz/zoneinfo/America/Eirunepe delete mode 100644 lib/pytz/zoneinfo/America/El_Salvador delete mode 100644 lib/pytz/zoneinfo/America/Ensenada delete mode 100644 lib/pytz/zoneinfo/America/Fort_Nelson delete mode 100644 lib/pytz/zoneinfo/America/Fort_Wayne delete mode 100644 lib/pytz/zoneinfo/America/Fortaleza delete mode 100644 lib/pytz/zoneinfo/America/Glace_Bay delete mode 100644 lib/pytz/zoneinfo/America/Godthab delete mode 100644 lib/pytz/zoneinfo/America/Goose_Bay delete mode 100644 lib/pytz/zoneinfo/America/Grand_Turk delete mode 100644 lib/pytz/zoneinfo/America/Grenada delete mode 100644 lib/pytz/zoneinfo/America/Guadeloupe delete mode 100644 lib/pytz/zoneinfo/America/Guatemala delete mode 100644 lib/pytz/zoneinfo/America/Guayaquil delete mode 100644 lib/pytz/zoneinfo/America/Guyana delete mode 100644 lib/pytz/zoneinfo/America/Halifax delete mode 100644 lib/pytz/zoneinfo/America/Havana delete mode 100644 lib/pytz/zoneinfo/America/Hermosillo delete mode 100644 lib/pytz/zoneinfo/America/Indiana/Indianapolis delete mode 100644 lib/pytz/zoneinfo/America/Indiana/Knox delete mode 100644 lib/pytz/zoneinfo/America/Indiana/Marengo delete mode 100644 lib/pytz/zoneinfo/America/Indiana/Petersburg delete mode 100644 lib/pytz/zoneinfo/America/Indiana/Tell_City delete mode 100644 lib/pytz/zoneinfo/America/Indiana/Vevay delete mode 100644 lib/pytz/zoneinfo/America/Indiana/Vincennes delete mode 100644 lib/pytz/zoneinfo/America/Indiana/Winamac delete mode 100644 lib/pytz/zoneinfo/America/Indianapolis delete mode 100644 lib/pytz/zoneinfo/America/Inuvik delete mode 100644 lib/pytz/zoneinfo/America/Iqaluit delete mode 100644 lib/pytz/zoneinfo/America/Jamaica delete mode 100644 lib/pytz/zoneinfo/America/Jujuy delete mode 100644 lib/pytz/zoneinfo/America/Juneau delete mode 100644 lib/pytz/zoneinfo/America/Kentucky/Louisville delete mode 100644 lib/pytz/zoneinfo/America/Kentucky/Monticello delete mode 100644 lib/pytz/zoneinfo/America/Knox_IN delete mode 100644 lib/pytz/zoneinfo/America/Kralendijk delete mode 100644 lib/pytz/zoneinfo/America/La_Paz delete mode 100644 lib/pytz/zoneinfo/America/Lima delete mode 100644 lib/pytz/zoneinfo/America/Los_Angeles delete mode 100644 lib/pytz/zoneinfo/America/Louisville delete mode 100644 lib/pytz/zoneinfo/America/Lower_Princes delete mode 100644 lib/pytz/zoneinfo/America/Maceio delete mode 100644 lib/pytz/zoneinfo/America/Managua delete mode 100644 lib/pytz/zoneinfo/America/Manaus delete mode 100644 lib/pytz/zoneinfo/America/Marigot delete mode 100644 lib/pytz/zoneinfo/America/Martinique delete mode 100644 lib/pytz/zoneinfo/America/Matamoros delete mode 100644 lib/pytz/zoneinfo/America/Mazatlan delete mode 100644 lib/pytz/zoneinfo/America/Mendoza delete mode 100644 lib/pytz/zoneinfo/America/Menominee delete mode 100644 lib/pytz/zoneinfo/America/Merida delete mode 100644 lib/pytz/zoneinfo/America/Metlakatla delete mode 100644 lib/pytz/zoneinfo/America/Mexico_City delete mode 100644 lib/pytz/zoneinfo/America/Miquelon delete mode 100644 lib/pytz/zoneinfo/America/Moncton delete mode 100644 lib/pytz/zoneinfo/America/Monterrey delete mode 100644 lib/pytz/zoneinfo/America/Montevideo delete mode 100644 lib/pytz/zoneinfo/America/Montreal delete mode 100644 lib/pytz/zoneinfo/America/Montserrat delete mode 100644 lib/pytz/zoneinfo/America/Nassau delete mode 100644 lib/pytz/zoneinfo/America/New_York delete mode 100644 lib/pytz/zoneinfo/America/Nipigon delete mode 100644 lib/pytz/zoneinfo/America/Nome delete mode 100644 lib/pytz/zoneinfo/America/Noronha delete mode 100644 lib/pytz/zoneinfo/America/North_Dakota/Beulah delete mode 100644 lib/pytz/zoneinfo/America/North_Dakota/Center delete mode 100644 lib/pytz/zoneinfo/America/North_Dakota/New_Salem delete mode 100644 lib/pytz/zoneinfo/America/Ojinaga delete mode 100644 lib/pytz/zoneinfo/America/Panama delete mode 100644 lib/pytz/zoneinfo/America/Pangnirtung delete mode 100644 lib/pytz/zoneinfo/America/Paramaribo delete mode 100644 lib/pytz/zoneinfo/America/Phoenix delete mode 100644 lib/pytz/zoneinfo/America/Port-au-Prince delete mode 100644 lib/pytz/zoneinfo/America/Port_of_Spain delete mode 100644 lib/pytz/zoneinfo/America/Porto_Acre delete mode 100644 lib/pytz/zoneinfo/America/Porto_Velho delete mode 100644 lib/pytz/zoneinfo/America/Puerto_Rico delete mode 100644 lib/pytz/zoneinfo/America/Punta_Arenas delete mode 100644 lib/pytz/zoneinfo/America/Rainy_River delete mode 100644 lib/pytz/zoneinfo/America/Rankin_Inlet delete mode 100644 lib/pytz/zoneinfo/America/Recife delete mode 100644 lib/pytz/zoneinfo/America/Regina delete mode 100644 lib/pytz/zoneinfo/America/Resolute delete mode 100644 lib/pytz/zoneinfo/America/Rio_Branco delete mode 100644 lib/pytz/zoneinfo/America/Rosario delete mode 100644 lib/pytz/zoneinfo/America/Santa_Isabel delete mode 100644 lib/pytz/zoneinfo/America/Santarem delete mode 100644 lib/pytz/zoneinfo/America/Santiago delete mode 100644 lib/pytz/zoneinfo/America/Santo_Domingo delete mode 100644 lib/pytz/zoneinfo/America/Sao_Paulo delete mode 100644 lib/pytz/zoneinfo/America/Scoresbysund delete mode 100644 lib/pytz/zoneinfo/America/Shiprock delete mode 100644 lib/pytz/zoneinfo/America/Sitka delete mode 100644 lib/pytz/zoneinfo/America/St_Barthelemy delete mode 100644 lib/pytz/zoneinfo/America/St_Johns delete mode 100644 lib/pytz/zoneinfo/America/St_Kitts delete mode 100644 lib/pytz/zoneinfo/America/St_Lucia delete mode 100644 lib/pytz/zoneinfo/America/St_Thomas delete mode 100644 lib/pytz/zoneinfo/America/St_Vincent delete mode 100644 lib/pytz/zoneinfo/America/Swift_Current delete mode 100644 lib/pytz/zoneinfo/America/Tegucigalpa delete mode 100644 lib/pytz/zoneinfo/America/Thule delete mode 100644 lib/pytz/zoneinfo/America/Thunder_Bay delete mode 100644 lib/pytz/zoneinfo/America/Tijuana delete mode 100644 lib/pytz/zoneinfo/America/Toronto delete mode 100644 lib/pytz/zoneinfo/America/Tortola delete mode 100644 lib/pytz/zoneinfo/America/Vancouver delete mode 100644 lib/pytz/zoneinfo/America/Virgin delete mode 100644 lib/pytz/zoneinfo/America/Whitehorse delete mode 100644 lib/pytz/zoneinfo/America/Winnipeg delete mode 100644 lib/pytz/zoneinfo/America/Yakutat delete mode 100644 lib/pytz/zoneinfo/America/Yellowknife delete mode 100644 lib/pytz/zoneinfo/Antarctica/Casey delete mode 100644 lib/pytz/zoneinfo/Antarctica/Davis delete mode 100644 lib/pytz/zoneinfo/Antarctica/DumontDUrville delete mode 100644 lib/pytz/zoneinfo/Antarctica/Macquarie delete mode 100644 lib/pytz/zoneinfo/Antarctica/Mawson delete mode 100644 lib/pytz/zoneinfo/Antarctica/McMurdo delete mode 100644 lib/pytz/zoneinfo/Antarctica/Palmer delete mode 100644 lib/pytz/zoneinfo/Antarctica/Rothera delete mode 100644 lib/pytz/zoneinfo/Antarctica/South_Pole delete mode 100644 lib/pytz/zoneinfo/Antarctica/Syowa delete mode 100644 lib/pytz/zoneinfo/Antarctica/Troll delete mode 100644 lib/pytz/zoneinfo/Antarctica/Vostok delete mode 100644 lib/pytz/zoneinfo/Arctic/Longyearbyen delete mode 100644 lib/pytz/zoneinfo/Asia/Aden delete mode 100644 lib/pytz/zoneinfo/Asia/Almaty delete mode 100644 lib/pytz/zoneinfo/Asia/Amman delete mode 100644 lib/pytz/zoneinfo/Asia/Anadyr delete mode 100644 lib/pytz/zoneinfo/Asia/Aqtau delete mode 100644 lib/pytz/zoneinfo/Asia/Aqtobe delete mode 100644 lib/pytz/zoneinfo/Asia/Ashgabat delete mode 100644 lib/pytz/zoneinfo/Asia/Ashkhabad delete mode 100644 lib/pytz/zoneinfo/Asia/Atyrau delete mode 100644 lib/pytz/zoneinfo/Asia/Baghdad delete mode 100644 lib/pytz/zoneinfo/Asia/Bahrain delete mode 100644 lib/pytz/zoneinfo/Asia/Baku delete mode 100644 lib/pytz/zoneinfo/Asia/Bangkok delete mode 100644 lib/pytz/zoneinfo/Asia/Barnaul delete mode 100644 lib/pytz/zoneinfo/Asia/Beirut delete mode 100644 lib/pytz/zoneinfo/Asia/Bishkek delete mode 100644 lib/pytz/zoneinfo/Asia/Brunei delete mode 100644 lib/pytz/zoneinfo/Asia/Calcutta delete mode 100644 lib/pytz/zoneinfo/Asia/Chita delete mode 100644 lib/pytz/zoneinfo/Asia/Choibalsan delete mode 100644 lib/pytz/zoneinfo/Asia/Chongqing delete mode 100644 lib/pytz/zoneinfo/Asia/Chungking delete mode 100644 lib/pytz/zoneinfo/Asia/Colombo delete mode 100644 lib/pytz/zoneinfo/Asia/Dacca delete mode 100644 lib/pytz/zoneinfo/Asia/Damascus delete mode 100644 lib/pytz/zoneinfo/Asia/Dhaka delete mode 100644 lib/pytz/zoneinfo/Asia/Dili delete mode 100644 lib/pytz/zoneinfo/Asia/Dubai delete mode 100644 lib/pytz/zoneinfo/Asia/Dushanbe delete mode 100644 lib/pytz/zoneinfo/Asia/Famagusta delete mode 100644 lib/pytz/zoneinfo/Asia/Gaza delete mode 100644 lib/pytz/zoneinfo/Asia/Harbin delete mode 100644 lib/pytz/zoneinfo/Asia/Hebron delete mode 100644 lib/pytz/zoneinfo/Asia/Ho_Chi_Minh delete mode 100644 lib/pytz/zoneinfo/Asia/Hong_Kong delete mode 100644 lib/pytz/zoneinfo/Asia/Hovd delete mode 100644 lib/pytz/zoneinfo/Asia/Irkutsk delete mode 100644 lib/pytz/zoneinfo/Asia/Istanbul delete mode 100644 lib/pytz/zoneinfo/Asia/Jakarta delete mode 100644 lib/pytz/zoneinfo/Asia/Jayapura delete mode 100644 lib/pytz/zoneinfo/Asia/Jerusalem delete mode 100644 lib/pytz/zoneinfo/Asia/Kabul delete mode 100644 lib/pytz/zoneinfo/Asia/Kamchatka delete mode 100644 lib/pytz/zoneinfo/Asia/Karachi delete mode 100644 lib/pytz/zoneinfo/Asia/Kashgar delete mode 100644 lib/pytz/zoneinfo/Asia/Kathmandu delete mode 100644 lib/pytz/zoneinfo/Asia/Katmandu delete mode 100644 lib/pytz/zoneinfo/Asia/Khandyga delete mode 100644 lib/pytz/zoneinfo/Asia/Kolkata delete mode 100644 lib/pytz/zoneinfo/Asia/Krasnoyarsk delete mode 100644 lib/pytz/zoneinfo/Asia/Kuala_Lumpur delete mode 100644 lib/pytz/zoneinfo/Asia/Kuching delete mode 100644 lib/pytz/zoneinfo/Asia/Kuwait delete mode 100644 lib/pytz/zoneinfo/Asia/Macao delete mode 100644 lib/pytz/zoneinfo/Asia/Macau delete mode 100644 lib/pytz/zoneinfo/Asia/Magadan delete mode 100644 lib/pytz/zoneinfo/Asia/Makassar delete mode 100644 lib/pytz/zoneinfo/Asia/Manila delete mode 100644 lib/pytz/zoneinfo/Asia/Muscat delete mode 100644 lib/pytz/zoneinfo/Asia/Nicosia delete mode 100644 lib/pytz/zoneinfo/Asia/Novokuznetsk delete mode 100644 lib/pytz/zoneinfo/Asia/Novosibirsk delete mode 100644 lib/pytz/zoneinfo/Asia/Omsk delete mode 100644 lib/pytz/zoneinfo/Asia/Oral delete mode 100644 lib/pytz/zoneinfo/Asia/Phnom_Penh delete mode 100644 lib/pytz/zoneinfo/Asia/Pontianak delete mode 100644 lib/pytz/zoneinfo/Asia/Pyongyang delete mode 100644 lib/pytz/zoneinfo/Asia/Qatar delete mode 100644 lib/pytz/zoneinfo/Asia/Qyzylorda delete mode 100644 lib/pytz/zoneinfo/Asia/Rangoon delete mode 100644 lib/pytz/zoneinfo/Asia/Riyadh delete mode 100644 lib/pytz/zoneinfo/Asia/Saigon delete mode 100644 lib/pytz/zoneinfo/Asia/Sakhalin delete mode 100644 lib/pytz/zoneinfo/Asia/Samarkand delete mode 100644 lib/pytz/zoneinfo/Asia/Seoul delete mode 100644 lib/pytz/zoneinfo/Asia/Shanghai delete mode 100644 lib/pytz/zoneinfo/Asia/Singapore delete mode 100644 lib/pytz/zoneinfo/Asia/Srednekolymsk delete mode 100644 lib/pytz/zoneinfo/Asia/Taipei delete mode 100644 lib/pytz/zoneinfo/Asia/Tashkent delete mode 100644 lib/pytz/zoneinfo/Asia/Tbilisi delete mode 100644 lib/pytz/zoneinfo/Asia/Tehran delete mode 100644 lib/pytz/zoneinfo/Asia/Tel_Aviv delete mode 100644 lib/pytz/zoneinfo/Asia/Thimbu delete mode 100644 lib/pytz/zoneinfo/Asia/Thimphu delete mode 100644 lib/pytz/zoneinfo/Asia/Tokyo delete mode 100644 lib/pytz/zoneinfo/Asia/Tomsk delete mode 100644 lib/pytz/zoneinfo/Asia/Ujung_Pandang delete mode 100644 lib/pytz/zoneinfo/Asia/Ulaanbaatar delete mode 100644 lib/pytz/zoneinfo/Asia/Ulan_Bator delete mode 100644 lib/pytz/zoneinfo/Asia/Urumqi delete mode 100644 lib/pytz/zoneinfo/Asia/Ust-Nera delete mode 100644 lib/pytz/zoneinfo/Asia/Vientiane delete mode 100644 lib/pytz/zoneinfo/Asia/Vladivostok delete mode 100644 lib/pytz/zoneinfo/Asia/Yakutsk delete mode 100644 lib/pytz/zoneinfo/Asia/Yangon delete mode 100644 lib/pytz/zoneinfo/Asia/Yekaterinburg delete mode 100644 lib/pytz/zoneinfo/Asia/Yerevan delete mode 100644 lib/pytz/zoneinfo/Atlantic/Azores delete mode 100644 lib/pytz/zoneinfo/Atlantic/Bermuda delete mode 100644 lib/pytz/zoneinfo/Atlantic/Canary delete mode 100644 lib/pytz/zoneinfo/Atlantic/Cape_Verde delete mode 100644 lib/pytz/zoneinfo/Atlantic/Faeroe delete mode 100644 lib/pytz/zoneinfo/Atlantic/Faroe delete mode 100644 lib/pytz/zoneinfo/Atlantic/Jan_Mayen delete mode 100644 lib/pytz/zoneinfo/Atlantic/Madeira delete mode 100644 lib/pytz/zoneinfo/Atlantic/Reykjavik delete mode 100644 lib/pytz/zoneinfo/Atlantic/South_Georgia delete mode 100644 lib/pytz/zoneinfo/Atlantic/St_Helena delete mode 100644 lib/pytz/zoneinfo/Atlantic/Stanley delete mode 100644 lib/pytz/zoneinfo/Australia/ACT delete mode 100644 lib/pytz/zoneinfo/Australia/Adelaide delete mode 100644 lib/pytz/zoneinfo/Australia/Brisbane delete mode 100644 lib/pytz/zoneinfo/Australia/Broken_Hill delete mode 100644 lib/pytz/zoneinfo/Australia/Canberra delete mode 100644 lib/pytz/zoneinfo/Australia/Currie delete mode 100644 lib/pytz/zoneinfo/Australia/Darwin delete mode 100644 lib/pytz/zoneinfo/Australia/Eucla delete mode 100644 lib/pytz/zoneinfo/Australia/Hobart delete mode 100644 lib/pytz/zoneinfo/Australia/LHI delete mode 100644 lib/pytz/zoneinfo/Australia/Lindeman delete mode 100644 lib/pytz/zoneinfo/Australia/Lord_Howe delete mode 100644 lib/pytz/zoneinfo/Australia/Melbourne delete mode 100644 lib/pytz/zoneinfo/Australia/NSW delete mode 100644 lib/pytz/zoneinfo/Australia/North delete mode 100644 lib/pytz/zoneinfo/Australia/Perth delete mode 100644 lib/pytz/zoneinfo/Australia/Queensland delete mode 100644 lib/pytz/zoneinfo/Australia/South delete mode 100644 lib/pytz/zoneinfo/Australia/Sydney delete mode 100644 lib/pytz/zoneinfo/Australia/Tasmania delete mode 100644 lib/pytz/zoneinfo/Australia/Victoria delete mode 100644 lib/pytz/zoneinfo/Australia/West delete mode 100644 lib/pytz/zoneinfo/Australia/Yancowinna delete mode 100644 lib/pytz/zoneinfo/Brazil/Acre delete mode 100644 lib/pytz/zoneinfo/Brazil/DeNoronha delete mode 100644 lib/pytz/zoneinfo/Brazil/East delete mode 100644 lib/pytz/zoneinfo/Brazil/West delete mode 100644 lib/pytz/zoneinfo/CET delete mode 100644 lib/pytz/zoneinfo/CST6CDT delete mode 100644 lib/pytz/zoneinfo/Canada/Atlantic delete mode 100644 lib/pytz/zoneinfo/Canada/Central delete mode 100644 lib/pytz/zoneinfo/Canada/Eastern delete mode 100644 lib/pytz/zoneinfo/Canada/Mountain delete mode 100644 lib/pytz/zoneinfo/Canada/Newfoundland delete mode 100644 lib/pytz/zoneinfo/Canada/Pacific delete mode 100644 lib/pytz/zoneinfo/Canada/Saskatchewan delete mode 100644 lib/pytz/zoneinfo/Canada/Yukon delete mode 100644 lib/pytz/zoneinfo/Chile/Continental delete mode 100644 lib/pytz/zoneinfo/Chile/EasterIsland delete mode 100644 lib/pytz/zoneinfo/Cuba delete mode 100644 lib/pytz/zoneinfo/EET delete mode 100644 lib/pytz/zoneinfo/EST delete mode 100644 lib/pytz/zoneinfo/EST5EDT delete mode 100644 lib/pytz/zoneinfo/Egypt delete mode 100644 lib/pytz/zoneinfo/Eire delete mode 100644 lib/pytz/zoneinfo/Etc/GMT delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+0 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+1 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+10 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+11 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+12 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+2 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+3 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+4 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+5 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+6 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+7 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+8 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT+9 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-0 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-1 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-10 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-11 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-12 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-13 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-14 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-2 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-3 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-4 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-5 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-6 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-7 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-8 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT-9 delete mode 100644 lib/pytz/zoneinfo/Etc/GMT0 delete mode 100644 lib/pytz/zoneinfo/Etc/Greenwich delete mode 100644 lib/pytz/zoneinfo/Etc/UCT delete mode 100644 lib/pytz/zoneinfo/Etc/UTC delete mode 100644 lib/pytz/zoneinfo/Etc/Universal delete mode 100644 lib/pytz/zoneinfo/Etc/Zulu delete mode 100644 lib/pytz/zoneinfo/Europe/Amsterdam delete mode 100644 lib/pytz/zoneinfo/Europe/Andorra delete mode 100644 lib/pytz/zoneinfo/Europe/Astrakhan delete mode 100644 lib/pytz/zoneinfo/Europe/Athens delete mode 100644 lib/pytz/zoneinfo/Europe/Belfast delete mode 100644 lib/pytz/zoneinfo/Europe/Belgrade delete mode 100644 lib/pytz/zoneinfo/Europe/Berlin delete mode 100644 lib/pytz/zoneinfo/Europe/Bratislava delete mode 100644 lib/pytz/zoneinfo/Europe/Brussels delete mode 100644 lib/pytz/zoneinfo/Europe/Bucharest delete mode 100644 lib/pytz/zoneinfo/Europe/Budapest delete mode 100644 lib/pytz/zoneinfo/Europe/Busingen delete mode 100644 lib/pytz/zoneinfo/Europe/Chisinau delete mode 100644 lib/pytz/zoneinfo/Europe/Copenhagen delete mode 100644 lib/pytz/zoneinfo/Europe/Dublin delete mode 100644 lib/pytz/zoneinfo/Europe/Gibraltar delete mode 100644 lib/pytz/zoneinfo/Europe/Guernsey delete mode 100644 lib/pytz/zoneinfo/Europe/Helsinki delete mode 100644 lib/pytz/zoneinfo/Europe/Isle_of_Man delete mode 100644 lib/pytz/zoneinfo/Europe/Istanbul delete mode 100644 lib/pytz/zoneinfo/Europe/Jersey delete mode 100644 lib/pytz/zoneinfo/Europe/Kaliningrad delete mode 100644 lib/pytz/zoneinfo/Europe/Kiev delete mode 100644 lib/pytz/zoneinfo/Europe/Kirov delete mode 100644 lib/pytz/zoneinfo/Europe/Lisbon delete mode 100644 lib/pytz/zoneinfo/Europe/Ljubljana delete mode 100644 lib/pytz/zoneinfo/Europe/London delete mode 100644 lib/pytz/zoneinfo/Europe/Luxembourg delete mode 100644 lib/pytz/zoneinfo/Europe/Madrid delete mode 100644 lib/pytz/zoneinfo/Europe/Malta delete mode 100644 lib/pytz/zoneinfo/Europe/Mariehamn delete mode 100644 lib/pytz/zoneinfo/Europe/Minsk delete mode 100644 lib/pytz/zoneinfo/Europe/Monaco delete mode 100644 lib/pytz/zoneinfo/Europe/Moscow delete mode 100644 lib/pytz/zoneinfo/Europe/Nicosia delete mode 100644 lib/pytz/zoneinfo/Europe/Oslo delete mode 100644 lib/pytz/zoneinfo/Europe/Paris delete mode 100644 lib/pytz/zoneinfo/Europe/Podgorica delete mode 100644 lib/pytz/zoneinfo/Europe/Prague delete mode 100644 lib/pytz/zoneinfo/Europe/Riga delete mode 100644 lib/pytz/zoneinfo/Europe/Rome delete mode 100644 lib/pytz/zoneinfo/Europe/Samara delete mode 100644 lib/pytz/zoneinfo/Europe/San_Marino delete mode 100644 lib/pytz/zoneinfo/Europe/Sarajevo delete mode 100644 lib/pytz/zoneinfo/Europe/Saratov delete mode 100644 lib/pytz/zoneinfo/Europe/Simferopol delete mode 100644 lib/pytz/zoneinfo/Europe/Skopje delete mode 100644 lib/pytz/zoneinfo/Europe/Sofia delete mode 100644 lib/pytz/zoneinfo/Europe/Stockholm delete mode 100644 lib/pytz/zoneinfo/Europe/Tallinn delete mode 100644 lib/pytz/zoneinfo/Europe/Tirane delete mode 100644 lib/pytz/zoneinfo/Europe/Tiraspol delete mode 100644 lib/pytz/zoneinfo/Europe/Ulyanovsk delete mode 100644 lib/pytz/zoneinfo/Europe/Uzhgorod delete mode 100644 lib/pytz/zoneinfo/Europe/Vaduz delete mode 100644 lib/pytz/zoneinfo/Europe/Vatican delete mode 100644 lib/pytz/zoneinfo/Europe/Vienna delete mode 100644 lib/pytz/zoneinfo/Europe/Vilnius delete mode 100644 lib/pytz/zoneinfo/Europe/Volgograd delete mode 100644 lib/pytz/zoneinfo/Europe/Warsaw delete mode 100644 lib/pytz/zoneinfo/Europe/Zagreb delete mode 100644 lib/pytz/zoneinfo/Europe/Zaporozhye delete mode 100644 lib/pytz/zoneinfo/Europe/Zurich delete mode 100644 lib/pytz/zoneinfo/Factory delete mode 100644 lib/pytz/zoneinfo/GB delete mode 100644 lib/pytz/zoneinfo/GB-Eire delete mode 100644 lib/pytz/zoneinfo/GMT delete mode 100644 lib/pytz/zoneinfo/GMT+0 delete mode 100644 lib/pytz/zoneinfo/GMT-0 delete mode 100644 lib/pytz/zoneinfo/GMT0 delete mode 100644 lib/pytz/zoneinfo/Greenwich delete mode 100644 lib/pytz/zoneinfo/HST delete mode 100644 lib/pytz/zoneinfo/Hongkong delete mode 100644 lib/pytz/zoneinfo/Iceland delete mode 100644 lib/pytz/zoneinfo/Indian/Antananarivo delete mode 100644 lib/pytz/zoneinfo/Indian/Chagos delete mode 100644 lib/pytz/zoneinfo/Indian/Christmas delete mode 100644 lib/pytz/zoneinfo/Indian/Cocos delete mode 100644 lib/pytz/zoneinfo/Indian/Comoro delete mode 100644 lib/pytz/zoneinfo/Indian/Kerguelen delete mode 100644 lib/pytz/zoneinfo/Indian/Mahe delete mode 100644 lib/pytz/zoneinfo/Indian/Maldives delete mode 100644 lib/pytz/zoneinfo/Indian/Mauritius delete mode 100644 lib/pytz/zoneinfo/Indian/Mayotte delete mode 100644 lib/pytz/zoneinfo/Indian/Reunion delete mode 100644 lib/pytz/zoneinfo/Iran delete mode 100644 lib/pytz/zoneinfo/Israel delete mode 100644 lib/pytz/zoneinfo/Jamaica delete mode 100644 lib/pytz/zoneinfo/Japan delete mode 100644 lib/pytz/zoneinfo/Kwajalein delete mode 100644 lib/pytz/zoneinfo/Libya delete mode 100644 lib/pytz/zoneinfo/MET delete mode 100644 lib/pytz/zoneinfo/MST delete mode 100644 lib/pytz/zoneinfo/MST7MDT delete mode 100644 lib/pytz/zoneinfo/Mexico/BajaNorte delete mode 100644 lib/pytz/zoneinfo/Mexico/BajaSur delete mode 100644 lib/pytz/zoneinfo/Mexico/General delete mode 100644 lib/pytz/zoneinfo/NZ delete mode 100644 lib/pytz/zoneinfo/NZ-CHAT delete mode 100644 lib/pytz/zoneinfo/Navajo delete mode 100644 lib/pytz/zoneinfo/PRC delete mode 100644 lib/pytz/zoneinfo/PST8PDT delete mode 100644 lib/pytz/zoneinfo/Pacific/Apia delete mode 100644 lib/pytz/zoneinfo/Pacific/Auckland delete mode 100644 lib/pytz/zoneinfo/Pacific/Bougainville delete mode 100644 lib/pytz/zoneinfo/Pacific/Chatham delete mode 100644 lib/pytz/zoneinfo/Pacific/Chuuk delete mode 100644 lib/pytz/zoneinfo/Pacific/Easter delete mode 100644 lib/pytz/zoneinfo/Pacific/Efate delete mode 100644 lib/pytz/zoneinfo/Pacific/Enderbury delete mode 100644 lib/pytz/zoneinfo/Pacific/Fakaofo delete mode 100644 lib/pytz/zoneinfo/Pacific/Fiji delete mode 100644 lib/pytz/zoneinfo/Pacific/Funafuti delete mode 100644 lib/pytz/zoneinfo/Pacific/Galapagos delete mode 100644 lib/pytz/zoneinfo/Pacific/Gambier delete mode 100644 lib/pytz/zoneinfo/Pacific/Guadalcanal delete mode 100644 lib/pytz/zoneinfo/Pacific/Guam delete mode 100644 lib/pytz/zoneinfo/Pacific/Honolulu delete mode 100644 lib/pytz/zoneinfo/Pacific/Johnston delete mode 100644 lib/pytz/zoneinfo/Pacific/Kiritimati delete mode 100644 lib/pytz/zoneinfo/Pacific/Kosrae delete mode 100644 lib/pytz/zoneinfo/Pacific/Kwajalein delete mode 100644 lib/pytz/zoneinfo/Pacific/Majuro delete mode 100644 lib/pytz/zoneinfo/Pacific/Marquesas delete mode 100644 lib/pytz/zoneinfo/Pacific/Midway delete mode 100644 lib/pytz/zoneinfo/Pacific/Nauru delete mode 100644 lib/pytz/zoneinfo/Pacific/Niue delete mode 100644 lib/pytz/zoneinfo/Pacific/Norfolk delete mode 100644 lib/pytz/zoneinfo/Pacific/Noumea delete mode 100644 lib/pytz/zoneinfo/Pacific/Pago_Pago delete mode 100644 lib/pytz/zoneinfo/Pacific/Palau delete mode 100644 lib/pytz/zoneinfo/Pacific/Pitcairn delete mode 100644 lib/pytz/zoneinfo/Pacific/Pohnpei delete mode 100644 lib/pytz/zoneinfo/Pacific/Ponape delete mode 100644 lib/pytz/zoneinfo/Pacific/Port_Moresby delete mode 100644 lib/pytz/zoneinfo/Pacific/Rarotonga delete mode 100644 lib/pytz/zoneinfo/Pacific/Saipan delete mode 100644 lib/pytz/zoneinfo/Pacific/Samoa delete mode 100644 lib/pytz/zoneinfo/Pacific/Tahiti delete mode 100644 lib/pytz/zoneinfo/Pacific/Tarawa delete mode 100644 lib/pytz/zoneinfo/Pacific/Tongatapu delete mode 100644 lib/pytz/zoneinfo/Pacific/Truk delete mode 100644 lib/pytz/zoneinfo/Pacific/Wake delete mode 100644 lib/pytz/zoneinfo/Pacific/Wallis delete mode 100644 lib/pytz/zoneinfo/Pacific/Yap delete mode 100644 lib/pytz/zoneinfo/Poland delete mode 100644 lib/pytz/zoneinfo/Portugal delete mode 100644 lib/pytz/zoneinfo/ROC delete mode 100644 lib/pytz/zoneinfo/ROK delete mode 100644 lib/pytz/zoneinfo/Singapore delete mode 100644 lib/pytz/zoneinfo/Turkey delete mode 100644 lib/pytz/zoneinfo/UCT delete mode 100644 lib/pytz/zoneinfo/US/Alaska delete mode 100644 lib/pytz/zoneinfo/US/Aleutian delete mode 100644 lib/pytz/zoneinfo/US/Arizona delete mode 100644 lib/pytz/zoneinfo/US/Central delete mode 100644 lib/pytz/zoneinfo/US/East-Indiana delete mode 100644 lib/pytz/zoneinfo/US/Eastern delete mode 100644 lib/pytz/zoneinfo/US/Hawaii delete mode 100644 lib/pytz/zoneinfo/US/Indiana-Starke delete mode 100644 lib/pytz/zoneinfo/US/Michigan delete mode 100644 lib/pytz/zoneinfo/US/Mountain delete mode 100644 lib/pytz/zoneinfo/US/Pacific delete mode 100644 lib/pytz/zoneinfo/US/Samoa delete mode 100644 lib/pytz/zoneinfo/UTC delete mode 100644 lib/pytz/zoneinfo/Universal delete mode 100644 lib/pytz/zoneinfo/W-SU delete mode 100644 lib/pytz/zoneinfo/WET delete mode 100644 lib/pytz/zoneinfo/Zulu delete mode 100644 lib/pytz/zoneinfo/iso3166.tab delete mode 100644 lib/pytz/zoneinfo/leapseconds delete mode 100644 lib/pytz/zoneinfo/posixrules delete mode 100644 lib/pytz/zoneinfo/tzdata.zi delete mode 100644 lib/pytz/zoneinfo/zone.tab delete mode 100644 lib/pytz/zoneinfo/zone1970.tab delete mode 100644 lib/requests/__init__.py delete mode 100644 lib/requests/__version__.py delete mode 100644 lib/requests/_internal_utils.py delete mode 100644 lib/requests/adapters.py delete mode 100644 lib/requests/api.py delete mode 100644 lib/requests/auth.py delete mode 100644 lib/requests/certs.py delete mode 100644 lib/requests/compat.py delete mode 100644 lib/requests/cookies.py delete mode 100644 lib/requests/exceptions.py delete mode 100644 lib/requests/help.py delete mode 100644 lib/requests/hooks.py delete mode 100644 lib/requests/models.py delete mode 100644 lib/requests/packages.py delete mode 100644 lib/requests/sessions.py delete mode 100644 lib/requests/status_codes.py delete mode 100644 lib/requests/structures.py delete mode 100644 lib/requests/utils.py delete mode 100644 lib/schedule/__init__.py delete mode 100644 lib/setuptools/__init__.py delete mode 100644 lib/setuptools/_deprecation_warning.py delete mode 100644 lib/setuptools/_vendor/__init__.py delete mode 100644 lib/setuptools/_vendor/packaging/__about__.py delete mode 100644 lib/setuptools/_vendor/packaging/__init__.py delete mode 100644 lib/setuptools/_vendor/packaging/_compat.py delete mode 100644 lib/setuptools/_vendor/packaging/_structures.py delete mode 100644 lib/setuptools/_vendor/packaging/markers.py delete mode 100644 lib/setuptools/_vendor/packaging/requirements.py delete mode 100644 lib/setuptools/_vendor/packaging/specifiers.py delete mode 100644 lib/setuptools/_vendor/packaging/utils.py delete mode 100644 lib/setuptools/_vendor/packaging/version.py delete mode 100644 lib/setuptools/_vendor/pyparsing.py delete mode 100644 lib/setuptools/_vendor/six.py delete mode 100644 lib/setuptools/archive_util.py delete mode 100644 lib/setuptools/build_meta.py delete mode 100644 lib/setuptools/cli-32.exe delete mode 100644 lib/setuptools/cli-64.exe delete mode 100644 lib/setuptools/cli.exe delete mode 100644 lib/setuptools/command/__init__.py delete mode 100644 lib/setuptools/command/alias.py delete mode 100644 lib/setuptools/command/bdist_egg.py delete mode 100644 lib/setuptools/command/bdist_rpm.py delete mode 100644 lib/setuptools/command/bdist_wininst.py delete mode 100644 lib/setuptools/command/build_clib.py delete mode 100644 lib/setuptools/command/build_ext.py delete mode 100644 lib/setuptools/command/build_py.py delete mode 100644 lib/setuptools/command/develop.py delete mode 100644 lib/setuptools/command/dist_info.py delete mode 100644 lib/setuptools/command/easy_install.py delete mode 100644 lib/setuptools/command/egg_info.py delete mode 100644 lib/setuptools/command/install.py delete mode 100644 lib/setuptools/command/install_egg_info.py delete mode 100644 lib/setuptools/command/install_lib.py delete mode 100644 lib/setuptools/command/install_scripts.py delete mode 100644 lib/setuptools/command/launcher manifest.xml delete mode 100644 lib/setuptools/command/py36compat.py delete mode 100644 lib/setuptools/command/register.py delete mode 100644 lib/setuptools/command/rotate.py delete mode 100644 lib/setuptools/command/saveopts.py delete mode 100644 lib/setuptools/command/sdist.py delete mode 100644 lib/setuptools/command/setopt.py delete mode 100644 lib/setuptools/command/test.py delete mode 100644 lib/setuptools/command/upload.py delete mode 100644 lib/setuptools/command/upload_docs.py delete mode 100644 lib/setuptools/config.py delete mode 100644 lib/setuptools/dep_util.py delete mode 100644 lib/setuptools/depends.py delete mode 100644 lib/setuptools/dist.py delete mode 100644 lib/setuptools/extension.py delete mode 100644 lib/setuptools/extern/__init__.py delete mode 100644 lib/setuptools/glibc.py delete mode 100644 lib/setuptools/glob.py delete mode 100644 lib/setuptools/gui-32.exe delete mode 100644 lib/setuptools/gui-64.exe delete mode 100644 lib/setuptools/gui.exe delete mode 100644 lib/setuptools/launch.py delete mode 100644 lib/setuptools/lib2to3_ex.py delete mode 100644 lib/setuptools/monkey.py delete mode 100644 lib/setuptools/msvc.py delete mode 100644 lib/setuptools/namespaces.py delete mode 100644 lib/setuptools/package_index.py delete mode 100644 lib/setuptools/pep425tags.py delete mode 100644 lib/setuptools/py27compat.py delete mode 100644 lib/setuptools/py31compat.py delete mode 100644 lib/setuptools/py33compat.py delete mode 100644 lib/setuptools/py36compat.py delete mode 100644 lib/setuptools/sandbox.py delete mode 100644 lib/setuptools/script (dev).tmpl delete mode 100644 lib/setuptools/script.tmpl delete mode 100644 lib/setuptools/site-patch.py delete mode 100644 lib/setuptools/ssl_support.py delete mode 100644 lib/setuptools/unicode_utils.py delete mode 100644 lib/setuptools/version.py delete mode 100644 lib/setuptools/wheel.py delete mode 100644 lib/setuptools/windows_support.py delete mode 100644 lib/six.py delete mode 100644 lib/typing.py delete mode 100644 lib/urllib3/__init__.py delete mode 100644 lib/urllib3/_collections.py delete mode 100644 lib/urllib3/connection.py delete mode 100644 lib/urllib3/connectionpool.py delete mode 100644 lib/urllib3/contrib/__init__.py delete mode 100644 lib/urllib3/contrib/_appengine_environ.py delete mode 100644 lib/urllib3/contrib/_securetransport/__init__.py delete mode 100644 lib/urllib3/contrib/_securetransport/bindings.py delete mode 100644 lib/urllib3/contrib/_securetransport/low_level.py delete mode 100644 lib/urllib3/contrib/appengine.py delete mode 100644 lib/urllib3/contrib/ntlmpool.py delete mode 100644 lib/urllib3/contrib/pyopenssl.py delete mode 100644 lib/urllib3/contrib/securetransport.py delete mode 100644 lib/urllib3/contrib/socks.py delete mode 100644 lib/urllib3/exceptions.py delete mode 100644 lib/urllib3/fields.py delete mode 100644 lib/urllib3/filepost.py delete mode 100644 lib/urllib3/packages/__init__.py delete mode 100644 lib/urllib3/packages/backports/__init__.py delete mode 100644 lib/urllib3/packages/backports/makefile.py delete mode 100644 lib/urllib3/packages/six.py delete mode 100644 lib/urllib3/packages/ssl_match_hostname/__init__.py delete mode 100644 lib/urllib3/packages/ssl_match_hostname/_implementation.py delete mode 100644 lib/urllib3/poolmanager.py delete mode 100644 lib/urllib3/request.py delete mode 100644 lib/urllib3/response.py delete mode 100644 lib/urllib3/util/__init__.py delete mode 100644 lib/urllib3/util/connection.py delete mode 100644 lib/urllib3/util/queue.py delete mode 100644 lib/urllib3/util/request.py delete mode 100644 lib/urllib3/util/response.py delete mode 100644 lib/urllib3/util/retry.py delete mode 100644 lib/urllib3/util/ssl_.py delete mode 100644 lib/urllib3/util/timeout.py delete mode 100644 lib/urllib3/util/url.py delete mode 100644 lib/urllib3/util/wait.py delete mode 100644 lib/zope.interface-4.6.0-py3.7-nspkg.pth delete mode 100644 lib/zope/interface/__init__.py delete mode 100644 lib/zope/interface/_compat.py delete mode 100644 lib/zope/interface/_flatten.py delete mode 100644 lib/zope/interface/_zope_interface_coptimizations.c delete mode 100644 lib/zope/interface/_zope_interface_coptimizations.cp37-win32.pyd delete mode 100644 lib/zope/interface/adapter.py delete mode 100644 lib/zope/interface/advice.py delete mode 100644 lib/zope/interface/common/__init__.py delete mode 100644 lib/zope/interface/common/idatetime.py delete mode 100644 lib/zope/interface/common/interfaces.py delete mode 100644 lib/zope/interface/common/mapping.py delete mode 100644 lib/zope/interface/common/sequence.py delete mode 100644 lib/zope/interface/common/tests/__init__.py delete mode 100644 lib/zope/interface/common/tests/basemapping.py delete mode 100644 lib/zope/interface/common/tests/test_idatetime.py delete mode 100644 lib/zope/interface/common/tests/test_import_interfaces.py delete mode 100644 lib/zope/interface/declarations.py delete mode 100644 lib/zope/interface/document.py delete mode 100644 lib/zope/interface/exceptions.py delete mode 100644 lib/zope/interface/interface.py delete mode 100644 lib/zope/interface/interfaces.py delete mode 100644 lib/zope/interface/registry.py delete mode 100644 lib/zope/interface/ro.py delete mode 100644 lib/zope/interface/tests/__init__.py delete mode 100644 lib/zope/interface/tests/advisory_testing.py delete mode 100644 lib/zope/interface/tests/dummy.py delete mode 100644 lib/zope/interface/tests/idummy.py delete mode 100644 lib/zope/interface/tests/ifoo.py delete mode 100644 lib/zope/interface/tests/ifoo_other.py delete mode 100644 lib/zope/interface/tests/m1.py delete mode 100644 lib/zope/interface/tests/m2.py delete mode 100644 lib/zope/interface/tests/odd.py delete mode 100644 lib/zope/interface/tests/test_adapter.py delete mode 100644 lib/zope/interface/tests/test_advice.py delete mode 100644 lib/zope/interface/tests/test_declarations.py delete mode 100644 lib/zope/interface/tests/test_document.py delete mode 100644 lib/zope/interface/tests/test_element.py delete mode 100644 lib/zope/interface/tests/test_exceptions.py delete mode 100644 lib/zope/interface/tests/test_interface.py delete mode 100644 lib/zope/interface/tests/test_interfaces.py delete mode 100644 lib/zope/interface/tests/test_odd_declarations.py delete mode 100644 lib/zope/interface/tests/test_registry.py delete mode 100644 lib/zope/interface/tests/test_ro.py delete mode 100644 lib/zope/interface/tests/test_sorting.py delete mode 100644 lib/zope/interface/tests/test_verify.py delete mode 100644 lib/zope/interface/verify.py diff --git a/Varken/dbmanager.py b/Varken/dbmanager.py index 8f081d6..de5dc1d 100644 --- a/Varken/dbmanager.py +++ b/Varken/dbmanager.py @@ -1,10 +1,5 @@ -from sys import path -from os.path import abspath, dirname, join -path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) - from influxdb import InfluxDBClient - class DBManager(object): def __init__(self, server): self.server = server diff --git a/Varken/helpers.py b/Varken/helpers.py index 1560073..e464b89 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -1,12 +1,9 @@ -from sys import path -from os.path import abspath, basename, join, dirname -path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) - +import os import time import tarfile import geoip2.database -from os import stat, remove from typing import NamedTuple +from os.path import abspath, join from urllib.request import urlretrieve @@ -342,10 +339,9 @@ def geoip_download(): tar = tarfile.open(tar_dbfile, "r:gz") for files in tar.getmembers(): if 'GeoLite2-City.mmdb' in files.name: - files.name = basename(files.name) + files.name = os.path.basename(files.name) tar.extract(files, abspath(join('.', 'data'))) - remove(tar_dbfile) - + os.remove(tar_dbfile) def geo_lookup(ipaddress): @@ -353,10 +349,10 @@ def geo_lookup(ipaddress): now = time.time() try: - dbinfo = stat(dbfile) + dbinfo = os.stat(dbfile) db_age = now - dbinfo.st_ctime if db_age > (35 * 86400): - remove(dbfile) + os.remove(dbfile) geoip_download() except FileNotFoundError: geoip_download() diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 79ec04d..9a21e11 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,14 +1,12 @@ -from sys import path, exit -from os.path import abspath, dirname, join -path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) - -from configparser import ConfigParser +import sys +import configparser +from os.path import abspath, join from Varken.helpers import OmbiServer, TautulliServer, SonarrServer, InfluxServer, RadarrServer class INIParser(object): def __init__(self): - self.config = ConfigParser() + self.config = configparser.ConfigParser() self.influx_server = InfluxServer() @@ -47,7 +45,7 @@ class INIParser(object): # Parse Sonarr options try: if not self.config.getboolean('global', 'sonarr_server_ids'): - exit('server_ids must be either false, or a comma-separated list of server ids') + sys.exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'sonarr_server_ids'): self.sonarr_enabled = True except ValueError: @@ -77,7 +75,7 @@ class INIParser(object): # Parse Radarr options try: if not self.config.getboolean('global', 'radarr_server_ids'): - exit('server_ids must be either false, or a comma-separated list of server ids') + sys.exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'radarr_server_ids'): self.radarr_enabled = True except ValueError: @@ -104,7 +102,7 @@ class INIParser(object): # Parse Tautulli options try: if not self.config.getboolean('global', 'tautulli_server_ids'): - exit('server_ids must be either false, or a comma-separated list of server ids') + sys.exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'tautulli_server_ids'): self.tautulli_enabled = True except ValueError: @@ -132,7 +130,7 @@ class INIParser(object): # Parse Ombi Options try: if not self.config.getboolean('global', 'ombi_server_ids'): - exit('server_ids must be either false, or a comma-separated list of server ids') + sys.exit('server_ids must be either false, or a comma-separated list of server ids') elif self.config.getint('global', 'ombi_server_ids'): self.ombi_enabled = True except ValueError: diff --git a/Varken/ombi.py b/Varken/ombi.py index 3e703bb..3981250 100644 --- a/Varken/ombi.py +++ b/Varken/ombi.py @@ -1,7 +1,3 @@ -from sys import path -from os.path import abspath, dirname, join -path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) - from requests import Session from datetime import datetime, timezone diff --git a/Varken/radarr.py b/Varken/radarr.py index e81cfdf..091bb77 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -1,7 +1,3 @@ -from sys import path -from os.path import abspath, dirname, join -path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) - from requests import Session from datetime import datetime, timezone diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 1e9ae7c..ae09c2e 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -1,7 +1,3 @@ -from sys import path -from os.path import abspath, dirname, join -path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) - from requests import Session from datetime import datetime, timezone, date, timedelta diff --git a/Varken/tautulli.py b/Varken/tautulli.py index b77b2e3..62f42a1 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -1,11 +1,6 @@ -from sys import path -from os.path import abspath, dirname, join -path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) - -from requests import Session from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError - +from requests import Session from Varken.helpers import TautulliStream, geo_lookup from Varken.logger import logging diff --git a/lib/DateTime/DateTime.py b/lib/DateTime/DateTime.py deleted file mode 100644 index cc6ca78..0000000 --- a/lib/DateTime/DateTime.py +++ /dev/null @@ -1,1940 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## - -import math -import re -import sys -from time import altzone -from time import daylight -from time import gmtime -from time import localtime -from time import time -from time import timezone -from time import tzname -from datetime import datetime - -from zope.interface import implementer - -from .interfaces import IDateTime -from .interfaces import DateTimeError -from .interfaces import SyntaxError -from .interfaces import DateError -from .interfaces import TimeError -from .pytz_support import PytzCache - -if sys.version_info > (3, ): - import copyreg as copy_reg - basestring = str - long = int - explicit_unicode_type = type(None) -else: - import copy_reg - explicit_unicode_type = unicode - -default_datefmt = None - - -def getDefaultDateFormat(): - global default_datefmt - if default_datefmt is None: - try: - from App.config import getConfiguration - default_datefmt = getConfiguration().datetime_format - return default_datefmt - except Exception: - return 'us' - else: - return default_datefmt - -# To control rounding errors, we round system time to the nearest -# microsecond. Then delicate calculations can rely on that the -# maximum precision that needs to be preserved is known. -_system_time = time - - -def time(): - return round(_system_time(), 6) - -# Determine machine epoch -tm = ((0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334), - (0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335)) -yr, mo, dy, hr, mn, sc = gmtime(0)[:6] -i = int(yr - 1) -to_year = int(i * 365 + i // 4 - i // 100 + i // 400 - 693960.0) -to_month = tm[yr % 4 == 0 and (yr % 100 != 0 or yr % 400 == 0)][mo] -EPOCH = ((to_year + to_month + dy + - (hr / 24.0 + mn / 1440.0 + sc / 86400.0)) * 86400) -jd1901 = 2415385 - -_TZINFO = PytzCache() - -INT_PATTERN = re.compile(r'([0-9]+)') -FLT_PATTERN = re.compile(r':([0-9]+\.[0-9]+)') -NAME_PATTERN = re.compile(r'([a-zA-Z]+)', re.I) -SPACE_CHARS = ' \t\n' -DELIMITERS = '-/.:,+' - -_MONTH_LEN = ((0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31), - (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)) -_MONTHS = ('', 'January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December') -_MONTHS_A = ('', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') -_MONTHS_P = ('', 'Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'June', - 'July', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.') -_MONTHMAP = {'january': 1, 'jan': 1, - 'february': 2, 'feb': 2, - 'march': 3, 'mar': 3, - 'april': 4, 'apr': 4, - 'may': 5, - 'june': 6, 'jun': 6, - 'july': 7, 'jul': 7, - 'august': 8, 'aug': 8, - 'september': 9, 'sep': 9, 'sept': 9, - 'october': 10, 'oct': 10, - 'november': 11, 'nov': 11, - 'december': 12, 'dec': 12} -_DAYS = ('Sunday', 'Monday', 'Tuesday', 'Wednesday', - 'Thursday', 'Friday', 'Saturday') -_DAYS_A = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat') -_DAYS_P = ('Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.') -_DAYMAP = {'sunday': 1, 'sun': 1, - 'monday': 2, 'mon': 2, - 'tuesday': 3, 'tues': 3, 'tue': 3, - 'wednesday': 4, 'wed': 4, - 'thursday': 5, 'thurs': 5, 'thur': 5, 'thu': 5, - 'friday': 6, 'fri': 6, - 'saturday': 7, 'sat': 7} - -numericTimeZoneMatch = re.compile(r'[+-][0-9][0-9][0-9][0-9]').match -iso8601Match = re.compile(r''' - (?P<year>\d\d\d\d) # four digits year - (?:-? # one optional dash - (?: # followed by: - (?P<year_day>\d\d\d # three digits year day - (?!\d)) # when there is no fourth digit - | # or: - W # one W - (?P<week>\d\d) # two digits week - (?:-? # one optional dash - (?P<week_day>\d) # one digit week day - )? # week day is optional - | # or: - (?P<month>\d\d)? # two digits month - (?:-? # one optional dash - (?P<day>\d\d)? # two digits day - )? # after day is optional - ) # - )? # after year is optional - (?:[T ] # one T or one whitespace - (?P<hour>\d\d) # two digits hour - (?::? # one optional colon - (?P<minute>\d\d)? # two digits minute - (?::? # one optional colon - (?P<second>\d\d)? # two digits second - (?:[.,] # one dot or one comma - (?P<fraction>\d+) # n digits fraction - )? # after second is optional - )? # after minute is optional - )? # after hour is optional - (?: # timezone: - (?P<Z>Z) # one Z - | # or: - (?P<signal>[-+]) # one plus or one minus as signal - (?P<hour_off>\d # one digit for hour offset... - (?:\d(?!\d$) # ...or two, if not the last two digits - )?) # second hour offset digit is optional - (?::? # one optional colon - (?P<min_off>\d\d) # two digits minute offset - )? # after hour offset is optional - )? # timezone is optional - )? # time is optional - (?P<garbage>.*) # store the extra garbage -''', re.VERBOSE).match - - -def _findLocalTimeZoneName(isDST): - if not daylight: - # Daylight savings does not occur in this time zone. - isDST = 0 - try: - # Get the name of the current time zone depending - # on DST. - _localzone = PytzCache._zmap[tzname[isDST].lower()] - except: - try: - # Generate a GMT-offset zone name. - if isDST: - localzone = altzone - else: - localzone = timezone - offset = (-localzone / 3600.0) - majorOffset = int(offset) - if majorOffset != 0: - minorOffset = abs(int((offset % majorOffset) * 60.0)) - else: - minorOffset = 0 - m = majorOffset >= 0 and '+' or '' - lz = '%s%0.02d%0.02d' % (m, majorOffset, minorOffset) - _localzone = PytzCache._zmap[('GMT%s' % lz).lower()] - except: - _localzone = '' - return _localzone - -_localzone0 = _findLocalTimeZoneName(0) -_localzone1 = _findLocalTimeZoneName(1) -_multipleZones = (_localzone0 != _localzone1) - -# Some utility functions for calculating dates: - - -def _calcSD(t): - # Returns timezone-independent days since epoch and the fractional - # part of the days. - dd = t + EPOCH - 86400.0 - d = dd / 86400.0 - s = d - math.floor(d) - return s, d - - -def _calcDependentSecond(tz, t): - # Calculates the timezone-dependent second (integer part only) - # from the timezone-independent second. - fset = _tzoffset(tz, t) - return fset + long(math.floor(t)) + long(EPOCH) - 86400 - - -def _calcDependentSecond2(yr, mo, dy, hr, mn, sc): - # Calculates the timezone-dependent second (integer part only) - # from the date given. - ss = int(hr) * 3600 + int(mn) * 60 + int(sc) - x = long(_julianday(yr, mo, dy) - jd1901) * 86400 + ss - return x - - -def _calcIndependentSecondEtc(tz, x, ms): - # Derive the timezone-independent second from the timezone - # dependent second. - fsetAtEpoch = _tzoffset(tz, 0.0) - nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms - # nearTime is now within an hour of being correct. - # Recalculate t according to DST. - fset = long(_tzoffset(tz, nearTime)) - d = (x - fset) / 86400.0 + (ms / 86400.0) - t = x - fset - long(EPOCH) + 86400 + ms - micros = (x + 86400 - fset) * 1000000 + \ - long(round(ms * 1000000.0)) - long(EPOCH * 1000000.0) - s = d - math.floor(d) - return (s, d, t, micros) - - -def _calcHMS(x, ms): - # hours, minutes, seconds from integer and float. - hr = x // 3600 - x = x - hr * 3600 - mn = x // 60 - sc = x - mn * 60 + ms - return (hr, mn, sc) - - -def _calcYMDHMS(x, ms): - # x is a timezone-dependent integer of seconds. - # Produces yr,mo,dy,hr,mn,sc. - yr, mo, dy = _calendarday(x // 86400 + jd1901) - x = int(x - (x // 86400) * 86400) - hr = x // 3600 - x = x - hr * 3600 - mn = x // 60 - sc = x - mn * 60 + ms - return (yr, mo, dy, hr, mn, sc) - - -def _julianday(yr, mo, dy): - y, m, d = long(yr), long(mo), long(dy) - if m > 12: - y = y + m // 12 - m = m % 12 - elif m < 1: - m = -m - y = y - m // 12 - 1 - m = 12 - m % 12 - if y > 0: - yr_correct = 0 - else: - yr_correct = 3 - if m < 3: - y, m = y - 1, m + 12 - if y * 10000 + m * 100 + d > 15821014: - b = 2 - y // 100 + y // 400 - else: - b = 0 - return ((1461 * y - yr_correct) // 4 + - 306001 * (m + 1) // 10000 + d + 1720994 + b) - - -def _calendarday(j): - j = long(j) - if (j < 2299160): - b = j + 1525 - else: - a = (4 * j - 7468861) // 146097 - b = j + 1526 + a - a // 4 - c = (20 * b - 2442) // 7305 - d = 1461 * c // 4 - e = 10000 * (b - d) // 306001 - dy = int(b - d - 306001 * e // 10000) - mo = (e < 14) and int(e - 1) or int(e - 13) - yr = (mo > 2) and (c - 4716) or (c - 4715) - return (int(yr), int(mo), int(dy)) - - -def _tzoffset(tz, t): - """Returns the offset in seconds to GMT from a specific timezone (tz) at - a specific time (t). NB! The _tzoffset result is the same same sign as - the time zone, i.e. GMT+2 has a 7200 second offset. This is the opposite - sign of time.timezone which (confusingly) is -7200 for GMT+2.""" - try: - return _TZINFO[tz].info(t)[0] - except Exception: - if numericTimeZoneMatch(tz) is not None: - return int(tz[0:3]) * 3600 + int(tz[0] + tz[3:5]) * 60 - else: - return 0 # ?? - - -def _correctYear(year): - # Y2K patch. - if year >= 0 and year < 100: - # 00-69 means 2000-2069, 70-99 means 1970-1999. - if year < 70: - year = 2000 + year - else: - year = 1900 + year - return year - - -def safegmtime(t): - '''gmtime with a safety zone.''' - try: - return gmtime(t) - except (ValueError, OverflowError): - raise TimeError('The time %f is beyond the range of this Python ' - 'implementation.' % float(t)) - - -def safelocaltime(t): - '''localtime with a safety zone.''' - try: - return localtime(t) - except (ValueError, OverflowError): - raise TimeError('The time %f is beyond the range of this Python ' - 'implementation.' % float(t)) - - -def _tzoffset2rfc822zone(seconds): - """Takes an offset, such as from _tzoffset(), and returns an rfc822 - compliant zone specification. Please note that the result of - _tzoffset() is the negative of what time.localzone and time.altzone is. - """ - return "%+03d%02d" % divmod((seconds // 60), 60) - - -def _tzoffset2iso8601zone(seconds): - """Takes an offset, such as from _tzoffset(), and returns an ISO 8601 - compliant zone specification. Please note that the result of - _tzoffset() is the negative of what time.localzone and time.altzone is. - """ - return "%+03d:%02d" % divmod((seconds // 60), 60) - - -def Timezones(): - """Return the list of recognized timezone names""" - return sorted(list(PytzCache._zmap.values())) - - -class strftimeFormatter(object): - - def __init__(self, dt, format): - self.dt = dt - self.format = format - - def __call__(self): - return self.dt.strftime(self.format) - - -@implementer(IDateTime) -class DateTime(object): - """DateTime objects represent instants in time and provide - interfaces for controlling its representation without - affecting the absolute value of the object. - - DateTime objects may be created from a wide variety of string - or numeric data, or may be computed from other DateTime objects. - DateTimes support the ability to convert their representations - to many major timezones, as well as the ablility to create a - DateTime object in the context of a given timezone. - - DateTime objects provide partial numerical behavior: - - - Two date-time objects can be subtracted to obtain a time, - in days between the two. - - - A date-time object and a positive or negative number may - be added to obtain a new date-time object that is the given - number of days later than the input date-time object. - - - A positive or negative number and a date-time object may - be added to obtain a new date-time object that is the given - number of days later than the input date-time object. - - - A positive or negative number may be subtracted from a - date-time object to obtain a new date-time object that is - the given number of days earlier than the input date-time - object. - - DateTime objects may be converted to integer, long, or float - numbers of days since January 1, 1901, using the standard int, - long, and float functions (Compatibility Note: int, long and - float return the number of days since 1901 in GMT rather than - local machine timezone). DateTime objects also provide access - to their value in a float format usable with the python time - module, provided that the value of the object falls in the - range of the epoch-based time module, and as a datetime.datetime - object. - - A DateTime object should be considered immutable; all conversion - and numeric operations return a new DateTime object rather than - modify the current object.""" - - # For security machinery: - __roles__ = None - __allow_access_to_unprotected_subobjects__ = 1 - - # Limit the amount of instance attributes - __slots__ = ( - '_timezone_naive', - '_tz', - '_dayoffset', - '_year', - '_month', - '_day', - '_hour', - '_minute', - '_second', - '_nearsec', - '_d', - '_micros', - 'time', - ) - - def __init__(self, *args, **kw): - """Return a new date-time object""" - try: - return self._parse_args(*args, **kw) - except (DateError, TimeError, DateTimeError): - raise - except Exception: - raise SyntaxError('Unable to parse %s, %s' % (args, kw)) - - def __getstate__(self): - # We store a float of _micros, instead of the _micros long, as we most - # often don't have any sub-second resolution and can save those bytes - return (self._micros / 1000000.0, - getattr(self, '_timezone_naive', False), - self._tz) - - def __setstate__(self, value): - if isinstance(value, tuple): - self._parse_args(value[0], value[2]) - self._micros = long(value[0] * 1000000) - self._timezone_naive = value[1] - else: - for k, v in value.items(): - if k in self.__slots__: - setattr(self, k, v) - # BBB: support for very old DateTime pickles - if '_micros' not in value: - self._micros = long(value['_t'] * 1000000) - if '_timezone_naive' not in value: - self._timezone_naive = False - - def _parse_args(self, *args, **kw): - """Return a new date-time object. - - A DateTime object always maintains its value as an absolute - UTC time, and is represented in the context of some timezone - based on the arguments used to create the object. A DateTime - object's methods return values based on the timezone context. - - Note that in all cases the local machine timezone is used for - representation if no timezone is specified. - - DateTimes may be created with from zero to seven arguments. - - - If the function is called with no arguments or with None, - then the current date/time is returned, represented in the - timezone of the local machine. - - - If the function is invoked with a single string argument - which is a recognized timezone name, an object representing - the current time is returned, represented in the specified - timezone. - - - If the function is invoked with a single string argument - representing a valid date/time, an object representing - that date/time will be returned. - - As a general rule, any date-time representation that is - recognized and unambigous to a resident of North America - is acceptable. The reason for this qualification is that - in North America, a date like: 2/1/1994 is interpreted - as February 1, 1994, while in some parts of the world, - it is interpreted as January 2, 1994. - - A date/time string consists of two components, a date - component and an optional time component, separated by one - or more spaces. If the time component is omited, 12:00am is - assumed. Any recognized timezone name specified as the final - element of the date/time string will be used for computing - the date/time value. If you create a DateTime with the - string 'Mar 9, 1997 1:45pm US/Pacific', the value will - essentially be the same as if you had captured time.time() - at the specified date and time on a machine in that timezone: - - <PRE> - e=DateTime('US/Eastern') - # returns current date/time, represented in US/Eastern. - - x=DateTime('1997/3/9 1:45pm') - # returns specified time, represented in local machine zone. - - y=DateTime('Mar 9, 1997 13:45:00') - # y is equal to x - </PRE> - - The date component consists of year, month, and day - values. The year value must be a one-, two-, or - four-digit integer. If a one- or two-digit year is - used, the year is assumed to be in the twentieth - century. The month may be an integer, from 1 to 12, a - month name, or a month abreviation, where a period may - optionally follow the abreviation. The day must be an - integer from 1 to the number of days in the month. The - year, month, and day values may be separated by - periods, hyphens, forward, shashes, or spaces. Extra - spaces are permitted around the delimiters. Year, - month, and day values may be given in any order as long - as it is possible to distinguish the components. If all - three components are numbers that are less than 13, - then a a month-day-year ordering is assumed. - - The time component consists of hour, minute, and second - values separated by colons. The hour value must be an - integer between 0 and 23 inclusively. The minute value - must be an integer between 0 and 59 inclusively. The - second value may be an integer value between 0 and - 59.999 inclusively. The second value or both the minute - and second values may be ommitted. The time may be - followed by am or pm in upper or lower case, in which - case a 12-hour clock is assumed. - - New in Zope 2.4: - The DateTime constructor automatically detects and handles - ISO8601 compliant dates (YYYY-MM-DDThh:ss:mmTZD). - - New in Zope 2.9.6: - The existing ISO8601 parser was extended to support almost - the whole ISO8601 specification. New formats includes: - - <PRE> - y=DateTime('1993-045') - # returns the 45th day from 1993, which is 14th February - - w=DateTime('1993-W06-7') - # returns the 7th day from the 6th week from 1993, which - # is also 14th February - </PRE> - - See http://en.wikipedia.org/wiki/ISO_8601 for full specs. - - Note that the Zope DateTime parser assumes timezone naive ISO - strings to be in UTC rather than local time as specified. - - - If the DateTime function is invoked with a single Numeric - argument, the number is assumed to be a floating point value - such as that returned by time.time(). - - A DateTime object is returned that represents the GMT value - of the time.time() float represented in the local machine's - timezone. - - - If the DateTime function is invoked with a single argument - that is a DateTime instane, a copy of the passed object will - be created. - - - New in 2.11: - The DateTime function may now be invoked with a single argument - that is a datetime.datetime instance. DateTimes may be converted - back to datetime.datetime objects with asdatetime(). - DateTime instances may be converted to a timezone naive - datetime.datetime in UTC with utcdatetime(). - - - If the function is invoked with two numeric arguments, then - the first is taken to be an integer year and the second - argument is taken to be an offset in days from the beginning - of the year, in the context of the local machine timezone. - - The date-time value returned is the given offset number of - days from the beginning of the given year, represented in - the timezone of the local machine. The offset may be positive - or negative. - - Two-digit years are assumed to be in the twentieth - century. - - - If the function is invoked with two arguments, the first - a float representing a number of seconds past the epoch - in gmt (such as those returned by time.time()) and the - second a string naming a recognized timezone, a DateTime - with a value of that gmt time will be returned, represented - in the given timezone. - - <PRE> - import time - t=time.time() - - now_east=DateTime(t,'US/Eastern') - # Time t represented as US/Eastern - - now_west=DateTime(t,'US/Pacific') - # Time t represented as US/Pacific - - # now_east == now_west - # only their representations are different - </PRE> - - - If the function is invoked with three or more numeric - arguments, then the first is taken to be an integer - year, the second is taken to be an integer month, and - the third is taken to be an integer day. If the - combination of values is not valid, then a - DateError is raised. Two-digit years are assumed - to be in the twentieth century. The fourth, fifth, and - sixth arguments specify a time in hours, minutes, and - seconds; hours and minutes should be positive integers - and seconds is a positive floating point value, all of - these default to zero if not given. An optional string may - be given as the final argument to indicate timezone (the - effect of this is as if you had taken the value of time.time() - at that time on a machine in the specified timezone). - - New in Zope 2.7: - A new keyword parameter "datefmt" can be passed to the - constructor. If set to "international", the constructor - is forced to treat ambigious dates as "days before month - before year". This useful if you need to parse non-US - dates in a reliable way - - In any case that a floating point number of seconds is given - or derived, it's rounded to the nearest millisecond. - - If a string argument passed to the DateTime constructor cannot be - parsed, it will raise DateTime.SyntaxError. Invalid date components - will raise a DateError, while invalid time or timezone components - will raise a DateTimeError. - - The module function Timezones() will return a list of the (common) - timezones recognized by the DateTime module. Recognition of - timezone names is case-insensitive. - """ - - datefmt = kw.get('datefmt', getDefaultDateFormat()) - d = t = s = None - ac = len(args) - microsecs = None - - if ac == 10: - # Internal format called only by DateTime - yr, mo, dy, hr, mn, sc, tz, t, d, s = args - elif ac == 11: - # Internal format that includes milliseconds (from the epoch) - yr, mo, dy, hr, mn, sc, tz, t, d, s, millisecs = args - microsecs = millisecs * 1000 - - elif ac == 12: - # Internal format that includes microseconds (from the epoch) and a - # flag indicating whether this was constructed in a timezone naive - # manner - yr, mo, dy, hr, mn, sc, tz, t, d, s, microsecs, tznaive = args - if tznaive is not None: # preserve this information - self._timezone_naive = tznaive - - elif not args or (ac and args[0] is None): - # Current time, to be displayed in local timezone - t = time() - lt = safelocaltime(t) - tz = self.localZone(lt) - ms = (t - math.floor(t)) - s, d = _calcSD(t) - yr, mo, dy, hr, mn, sc = lt[:6] - sc = sc + ms - self._timezone_naive = False - - elif ac == 1: - arg = args[0] - - if arg == '': - raise SyntaxError(arg) - - if isinstance(arg, DateTime): - """Construct a new DateTime instance from a given - DateTime instance. - """ - t = arg.timeTime() - s, d = _calcSD(t) - yr, mo, dy, hr, mn, sc, tz = arg.parts() - - elif isinstance(arg, datetime): - yr, mo, dy, hr, mn, sc, numerictz, tznaive = \ - self._parse_iso8601_preserving_tznaive(arg.isoformat()) - if arg.tzinfo is None: - self._timezone_naive = True - tz = None - else: - self._timezone_naive = False - # if we have a pytz tzinfo, use the `zone` attribute - # as a key - tz = getattr(arg.tzinfo, 'zone', numerictz) - ms = sc - math.floor(sc) - x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc) - - if tz: - try: - zone = _TZINFO[tz] - except DateTimeError: - try: - zone = _TZINFO[numerictz] - except DateTimeError: - raise DateTimeError( - 'Unknown time zone in date: %s' % arg) - tz = zone.tzinfo.zone - else: - tz = self._calcTimezoneName(x, ms) - s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms) - - elif (isinstance(arg, basestring) and - arg.lower() in _TZINFO._zidx): - # Current time, to be displayed in specified timezone - t, tz = time(), _TZINFO._zmap[arg.lower()] - ms = (t - math.floor(t)) - # Use integer arithmetic as much as possible. - s, d = _calcSD(t) - x = _calcDependentSecond(tz, t) - yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms) - - elif isinstance(arg, basestring): - # Date/time string - iso8601 = iso8601Match(arg.strip()) - fields_iso8601 = iso8601 and iso8601.groupdict() or {} - if fields_iso8601 and not fields_iso8601.get('garbage'): - yr, mo, dy, hr, mn, sc, tz, tznaive = \ - self._parse_iso8601_preserving_tznaive(arg) - self._timezone_naive = tznaive - else: - yr, mo, dy, hr, mn, sc, tz = self._parse(arg, datefmt) - - if not self._validDate(yr, mo, dy): - raise DateError('Invalid date: %s' % arg) - if not self._validTime(hr, mn, int(sc)): - raise TimeError('Invalid time: %s' % arg) - ms = sc - math.floor(sc) - x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc) - - if tz: - try: - tz = _TZINFO._zmap[tz.lower()] - except KeyError: - if numericTimeZoneMatch(tz) is None: - raise DateTimeError( - 'Unknown time zone in date: %s' % arg) - else: - tz = self._calcTimezoneName(x, ms) - s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms) - - else: - # Seconds from epoch, gmt - t = arg - lt = safelocaltime(t) - tz = self.localZone(lt) - ms = (t - math.floor(t)) - s, d = _calcSD(t) - yr, mo, dy, hr, mn, sc = lt[:6] - sc = sc + ms - - elif ac == 2: - if isinstance(args[1], basestring): - # Seconds from epoch (gmt) and timezone - t, tz = args - ms = (t - math.floor(t)) - try: - tz = _TZINFO._zmap[tz.lower()] - except KeyError: - if numericTimeZoneMatch(tz) is None: - raise DateTimeError('Unknown time zone: %s' % tz) - # Use integer arithmetic as much as possible. - s, d = _calcSD(t) - x = _calcDependentSecond(tz, t) - yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms) - else: - # Year, julian expressed in local zone - t = time() - lt = safelocaltime(t) - tz = self.localZone(lt) - yr, jul = args - yr = _correctYear(yr) - d = (_julianday(yr, 1, 0) - jd1901) + jul - x_float = d * 86400.0 - x_floor = math.floor(x_float) - ms = x_float - x_floor - x = long(x_floor) - yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms) - s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms) - else: - # Explicit format - yr, mo, dy = args[:3] - hr, mn, sc, tz = 0, 0, 0, 0 - yr = _correctYear(yr) - if not self._validDate(yr, mo, dy): - raise DateError('Invalid date: %s' % (args, )) - args = args[3:] - if args: - hr, args = args[0], args[1:] - if args: - mn, args = args[0], args[1:] - if args: - sc, args = args[0], args[1:] - if args: - tz, args = args[0], args[1:] - if args: - raise DateTimeError('Too many arguments') - if not self._validTime(hr, mn, sc): - raise TimeError('Invalid time: %s' % repr(args)) - - x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc) - ms = sc - math.floor(sc) - if tz: - try: - tz = _TZINFO._zmap[tz.lower()] - except KeyError: - if numericTimeZoneMatch(tz) is None: - raise DateTimeError('Unknown time zone: %s' % tz) - else: - # Get local time zone name - tz = self._calcTimezoneName(x, ms) - s, d, t, microsecs = _calcIndependentSecondEtc(tz, x, ms) - - self._dayoffset = int((_julianday(yr, mo, dy) + 2) % 7) - # Round to nearest microsecond in platform-independent way. You - # cannot rely on C sprintf (Python '%') formatting to round - # consistently; doing it ourselves ensures that all but truly - # horrid C sprintf implementations will yield the same result - # x-platform, provided the format asks for exactly 6 digits after - # the decimal point. - sc = round(sc, 6) - if sc >= 60.0: # can happen if, e.g., orig sc was 59.9999999 - sc = 59.999999 - self._nearsec = math.floor(sc) - self._year, self._month, self._day = yr, mo, dy - self._hour, self._minute, self._second = hr, mn, sc - self.time, self._d, self._tz = s, d, tz - # self._micros is the time since the epoch - # in long integer microseconds. - if microsecs is None: - microsecs = long(math.floor(t * 1000000.0)) - self._micros = microsecs - - def localZone(self, ltm=None): - '''Returns the time zone on the given date. The time zone - can change according to daylight savings.''' - if not _multipleZones: - return _localzone0 - if ltm is None: - ltm = localtime(time()) - isDST = ltm[8] - lz = isDST and _localzone1 or _localzone0 - return lz - - def _calcTimezoneName(self, x, ms): - # Derive the name of the local time zone at the given - # timezone-dependent second. - if not _multipleZones: - return _localzone0 - fsetAtEpoch = _tzoffset(_localzone0, 0.0) - nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms - # nearTime is within an hour of being correct. - try: - ltm = safelocaltime(nearTime) - except: - # We are beyond the range of Python's date support. - # Hopefully we can assume that daylight savings schedules - # repeat every 28 years. Calculate the name of the - # time zone using a supported range of years. - yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, 0) - yr = ((yr - 1970) % 28) + 1970 - x = _calcDependentSecond2(yr, mo, dy, hr, mn, sc) - nearTime = x - fsetAtEpoch - long(EPOCH) + 86400 + ms - - # nearTime might still be negative if we are east of Greenwich. - # But we can asume on 1969/12/31 were no timezone changes. - nearTime = max(0, nearTime) - - ltm = safelocaltime(nearTime) - tz = self.localZone(ltm) - return tz - - def _parse(self, st, datefmt=getDefaultDateFormat()): - # Parse date-time components from a string - month = year = tz = tm = None - ValidZones = _TZINFO._zidx - TimeModifiers = ['am', 'pm'] - - # Find timezone first, since it should always be the last - # element, and may contain a slash, confusing the parser. - st = st.strip() - sp = st.split() - tz = sp[-1] - if tz and (tz.lower() in ValidZones): - self._timezone_naive = False - st = ' '.join(sp[:-1]) - else: - self._timezone_naive = True - tz = None # Decide later, since the default time zone - # could depend on the date. - - ints = [] - i = 0 - l = len(st) - while i < l: - while i < l and st[i] in SPACE_CHARS: - i += 1 - if i < l and st[i] in DELIMITERS: - d = st[i] - i += 1 - else: - d = '' - while i < l and st[i] in SPACE_CHARS: - i += 1 - - # The float pattern needs to look back 1 character, because it - # actually looks for a preceding colon like ':33.33'. This is - # needed to avoid accidentally matching the date part of a - # dot-separated date string such as '1999.12.31'. - if i > 0: - b = i - 1 - else: - b = i - - ts_results = FLT_PATTERN.match(st, b) - if ts_results: - s = ts_results.group(1) - i = i + len(s) - ints.append(float(s)) - continue - - #AJ - ts_results = INT_PATTERN.match(st, i) - if ts_results: - s = ts_results.group(0) - - ls = len(s) - i = i + ls - if (ls == 4 and d and d in '+-' and - (len(ints) + (not not month) >= 3)): - tz = '%s%s' % (d, s) - else: - v = int(s) - ints.append(v) - continue - - ts_results = NAME_PATTERN.match(st, i) - if ts_results: - s = ts_results.group(0).lower() - i = i + len(s) - if i < l and st[i] == '.': - i += 1 - # Check for month name: - _v = _MONTHMAP.get(s) - if _v is not None: - if month is None: - month = _v - else: - raise SyntaxError(st) - continue - # Check for time modifier: - if s in TimeModifiers: - if tm is None: - tm = s - else: - raise SyntaxError(st) - continue - # Check for and skip day of week: - if s in _DAYMAP: - continue - - raise SyntaxError(st) - - day = None - if ints[-1] > 60 and d not in ('.', ':', '/') and len(ints) > 2: - year = ints[-1] - del ints[-1] - if month: - day = ints[0] - del ints[:1] - else: - if datefmt == "us": - month = ints[0] - day = ints[1] - else: - month = ints[1] - day = ints[0] - del ints[:2] - elif month: - if len(ints) > 1: - if ints[0] > 31: - year = ints[0] - day = ints[1] - else: - year = ints[1] - day = ints[0] - del ints[:2] - elif len(ints) > 2: - if ints[0] > 31: - year = ints[0] - if ints[1] > 12: - day = ints[1] - month = ints[2] - else: - day = ints[2] - month = ints[1] - if ints[1] > 31: - year = ints[1] - if ints[0] > 12 and ints[2] <= 12: - day = ints[0] - month = ints[2] - elif ints[2] > 12 and ints[0] <= 12: - day = ints[2] - month = ints[0] - elif ints[2] > 31: - year = ints[2] - if ints[0] > 12: - day = ints[0] - month = ints[1] - else: - if datefmt == "us": - day = ints[1] - month = ints[0] - else: - day = ints[0] - month = ints[1] - - elif ints[0] <= 12: - month = ints[0] - day = ints[1] - year = ints[2] - del ints[:3] - - if day is None: - # Use today's date. - year, month, day = localtime(time())[:3] - - year = _correctYear(year) - if year < 1000: - raise SyntaxError(st) - - leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) - try: - if not day or day > _MONTH_LEN[leap][month]: - raise DateError(st) - except IndexError: - raise DateError(st) - - tod = 0 - if ints: - i = ints[0] - # Modify hour to reflect am/pm - if tm and (tm == 'pm') and i < 12: - i += 12 - if tm and (tm == 'am') and i == 12: - i = 0 - if i > 24: - raise TimeError(st) - tod = tod + int(i) * 3600 - del ints[0] - if ints: - i = ints[0] - if i > 60: - raise TimeError(st) - tod = tod + int(i) * 60 - del ints[0] - if ints: - i = ints[0] - if i > 60: - raise TimeError(st) - tod = tod + i - del ints[0] - if ints: - raise SyntaxError(st) - - tod_int = int(math.floor(tod)) - ms = tod - tod_int - hr, mn, sc = _calcHMS(tod_int, ms) - if not tz: - # Figure out what time zone it is in the local area - # on the given date. - x = _calcDependentSecond2(year, month, day, hr, mn, sc) - tz = self._calcTimezoneName(x, ms) - - return year, month, day, hr, mn, sc, tz - - # Internal methods - def _validDate(self, y, m, d): - if m < 1 or m > 12 or y < 0 or d < 1 or d > 31: - return 0 - return d <= _MONTH_LEN[ - (y % 4 == 0 and (y % 100 != 0 or y % 400 == 0))][m] - - def _validTime(self, h, m, s): - return h >= 0 and h <= 23 and m >= 0 and m <= 59 and s >= 0 and s < 60 - - def __getattr__(self, name): - if '%' in name: - return strftimeFormatter(self, name) - raise AttributeError(name) - - # Conversion and comparison methods - - def timeTime(self): - """Return the date/time as a floating-point number in UTC, - in the format used by the python time module. - - Note that it is possible to create date/time values with - DateTime that have no meaningful value to the time module. - """ - return self._micros / 1000000.0 - - def toZone(self, z): - """Return a DateTime with the value as the current - object, represented in the indicated timezone. - """ - t, tz = self._t, _TZINFO._zmap[z.lower()] - micros = self.micros() - tznaive = False # you're performing a timzone change, can't be naive - - try: - # Try to use time module for speed. - yr, mo, dy, hr, mn, sc = safegmtime(t + _tzoffset(tz, t))[:6] - sc = self._second - return self.__class__(yr, mo, dy, hr, mn, sc, tz, t, - self._d, self.time, micros, tznaive) - except Exception: - # gmtime can't perform the calculation in the given range. - # Calculate the difference between the two time zones. - tzdiff = _tzoffset(tz, t) - _tzoffset(self._tz, t) - if tzdiff == 0: - return self - sc = self._second - ms = sc - math.floor(sc) - x = _calcDependentSecond2(self._year, self._month, self._day, - self._hour, self._minute, sc) - x_new = x + tzdiff - yr, mo, dy, hr, mn, sc = _calcYMDHMS(x_new, ms) - return self.__class__(yr, mo, dy, hr, mn, sc, tz, t, - self._d, self.time, micros, tznaive) - - def isFuture(self): - """Return true if this object represents a date/time - later than the time of the call. - """ - return (self._t > time()) - - def isPast(self): - """Return true if this object represents a date/time - earlier than the time of the call. - """ - return (self._t < time()) - - def isCurrentYear(self): - """Return true if this object represents a date/time - that falls within the current year, in the context - of this object\'s timezone representation. - """ - t = time() - return safegmtime(t + _tzoffset(self._tz, t))[0] == self._year - - def isCurrentMonth(self): - """Return true if this object represents a date/time - that falls within the current month, in the context - of this object\'s timezone representation. - """ - t = time() - gmt = safegmtime(t + _tzoffset(self._tz, t)) - return gmt[0] == self._year and gmt[1] == self._month - - def isCurrentDay(self): - """Return true if this object represents a date/time - that falls within the current day, in the context - of this object\'s timezone representation. - """ - t = time() - gmt = safegmtime(t + _tzoffset(self._tz, t)) - return (gmt[0] == self._year and gmt[1] == self._month and - gmt[2] == self._day) - - def isCurrentHour(self): - """Return true if this object represents a date/time - that falls within the current hour, in the context - of this object\'s timezone representation. - """ - t = time() - gmt = safegmtime(t + _tzoffset(self._tz, t)) - return (gmt[0] == self._year and gmt[1] == self._month and - gmt[2] == self._day and gmt[3] == self._hour) - - def isCurrentMinute(self): - """Return true if this object represents a date/time - that falls within the current minute, in the context - of this object\'s timezone representation. - """ - t = time() - gmt = safegmtime(t + _tzoffset(self._tz, t)) - return (gmt[0] == self._year and gmt[1] == self._month and - gmt[2] == self._day and gmt[3] == self._hour and - gmt[4] == self._minute) - - def earliestTime(self): - """Return a new DateTime object that represents the earliest - possible time (in whole seconds) that still falls within - the current object\'s day, in the object\'s timezone context. - """ - return self.__class__( - self._year, self._month, self._day, 0, 0, 0, self._tz) - - def latestTime(self): - """Return a new DateTime object that represents the latest - possible time (in whole seconds) that still falls within - the current object\'s day, in the object\'s timezone context. - """ - return self.__class__( - self._year, self._month, self._day, 23, 59, 59, self._tz) - - def greaterThan(self, t): - """Compare this DateTime object to another DateTime object - OR a floating point number such as that which is returned - by the python time module. - - Returns true if the object represents a date/time greater - than the specified DateTime or time module style time. - - Revised to give more correct results through comparison of - long integer microseconds. - """ - if t is None: - t = 0 - if isinstance(t, float): - return self._micros > long(t * 1000000) - try: - return self._micros > t._micros - except AttributeError: - return self._micros > t - - __gt__ = greaterThan - - def greaterThanEqualTo(self, t): - """Compare this DateTime object to another DateTime object - OR a floating point number such as that which is returned - by the python time module. - - Returns true if the object represents a date/time greater - than or equal to the specified DateTime or time module style - time. - - Revised to give more correct results through comparison of - long integer microseconds. - """ - if t is None: - t = 0 - if isinstance(t, float): - return self._micros >= long(t * 1000000) - try: - return self._micros >= t._micros - except AttributeError: - return self._micros >= t - - __ge__ = greaterThanEqualTo - - def equalTo(self, t): - """Compare this DateTime object to another DateTime object - OR a floating point number such as that which is returned - by the python time module. - - Returns true if the object represents a date/time equal to - the specified DateTime or time module style time. - - Revised to give more correct results through comparison of - long integer microseconds. - """ - if t is None: - t = 0 - if isinstance(t, float): - return self._micros == long(t * 1000000) - try: - return self._micros == t._micros - except AttributeError: - return self._micros == t - - def notEqualTo(self, t): - """Compare this DateTime object to another DateTime object - OR a floating point number such as that which is returned - by the python time module. - - Returns true if the object represents a date/time not equal - to the specified DateTime or time module style time. - - Revised to give more correct results through comparison of - long integer microseconds. - """ - return not self.equalTo(t) - - def __eq__(self, t): - """Compare this DateTime object to another DateTime object. - Return True if their internal state is the same. Two objects - representing the same time in different timezones are regared as - unequal. Use the equalTo method if you are only interested in them - refering to the same moment in time. - """ - if not isinstance(t, DateTime): - return False - return (self._micros, self._tz) == (t._micros, t._tz) - - def __ne__(self, t): - return not self.__eq__(t) - - def lessThan(self, t): - """Compare this DateTime object to another DateTime object - OR a floating point number such as that which is returned - by the python time module. - - Returns true if the object represents a date/time less than - the specified DateTime or time module style time. - - Revised to give more correct results through comparison of - long integer microseconds. - """ - if t is None: - t = 0 - if isinstance(t, float): - return self._micros < long(t * 1000000) - try: - return self._micros < t._micros - except AttributeError: - return self._micros < t - - __lt__ = lessThan - - def lessThanEqualTo(self, t): - """Compare this DateTime object to another DateTime object - OR a floating point number such as that which is returned - by the python time module. - - Returns true if the object represents a date/time less than - or equal to the specified DateTime or time module style time. - - Revised to give more correct results through comparison of - long integer microseconds. - """ - if t is None: - t = 0 - if isinstance(t, float): - return self._micros <= long(t * 1000000) - try: - return self._micros <= t._micros - except AttributeError: - return self._micros <= t - - __le__ = lessThanEqualTo - - def isLeapYear(self): - """Return true if the current year (in the context of the - object\'s timezone) is a leap year. - """ - return (self._year % 4 == 0 and - (self._year % 100 != 0 or self._year % 400 == 0)) - - def dayOfYear(self): - """Return the day of the year, in context of the timezone - representation of the object. - """ - d = int(self._d + (_tzoffset(self._tz, self._t) / 86400.0)) - return int((d + jd1901) - _julianday(self._year, 1, 0)) - - # Component access - def parts(self): - """Return a tuple containing the calendar year, month, - day, hour, minute second and timezone of the object. - """ - return (self._year, self._month, self._day, self._hour, - self._minute, self._second, self._tz) - - def timezone(self): - """Return the timezone in which the object is represented.""" - return self._tz - - def tzoffset(self): - """Return the timezone offset for the objects timezone.""" - return _tzoffset(self._tz, self._t) - - def year(self): - """Return the calendar year of the object.""" - return self._year - - def month(self): - """Return the month of the object as an integer.""" - return self._month - - @property - def _fmon(self): - return _MONTHS[self._month] - - def Month(self): - """Return the full month name.""" - return self._fmon - - @property - def _amon(self): - return _MONTHS_A[self._month] - - def aMonth(self): - """Return the abreviated month name.""" - return self._amon - - def Mon(self): - """Compatibility: see aMonth.""" - return self._amon - - @property - def _pmon(self): - return _MONTHS_P[self._month] - - def pMonth(self): - """Return the abreviated (with period) month name.""" - return self._pmon - - def Mon_(self): - """Compatibility: see pMonth.""" - return self._pmon - - def day(self): - """Return the integer day.""" - return self._day - - @property - def _fday(self): - return _DAYS[self._dayoffset] - - def Day(self): - """Return the full name of the day of the week.""" - return self._fday - - def DayOfWeek(self): - """Compatibility: see Day.""" - return self._fday - - @property - def _aday(self): - return _DAYS_A[self._dayoffset] - - def aDay(self): - """Return the abreviated name of the day of the week.""" - return self._aday - - @property - def _pday(self): - return _DAYS_P[self._dayoffset] - - def pDay(self): - """Return the abreviated (with period) name of the day of the week.""" - return self._pday - - def Day_(self): - """Compatibility: see pDay.""" - return self._pday - - def dow(self): - """Return the integer day of the week, where sunday is 0.""" - return self._dayoffset - - def dow_1(self): - """Return the integer day of the week, where sunday is 1.""" - return self._dayoffset + 1 - - @property - def _pmhour(self): - hr = self._hour - if hr > 12: - return hr - 12 - return hr or 12 - - def h_12(self): - """Return the 12-hour clock representation of the hour.""" - return self._pmhour - - def h_24(self): - """Return the 24-hour clock representation of the hour.""" - return self._hour - - @property - def _pm(self): - hr = self._hour - if hr >= 12: - return 'pm' - return 'am' - - def ampm(self): - """Return the appropriate time modifier (am or pm).""" - return self._pm - - def hour(self): - """Return the 24-hour clock representation of the hour.""" - return self._hour - - def minute(self): - """Return the minute.""" - return self._minute - - def second(self): - """Return the second.""" - return self._second - - def millis(self): - """Return the millisecond since the epoch in GMT.""" - return self._micros // 1000 - - def micros(self): - """Return the microsecond since the epoch in GMT.""" - return self._micros - - def timezoneNaive(self): - """The python datetime module introduces the idea of distinguishing - between timezone aware and timezone naive datetime values. For lossless - conversion to and from datetime.datetime record if we record this - information using True / False. DateTime makes no distinction, when we - don't have any information we return None here. - """ - try: - return self._timezone_naive - except AttributeError: - return None - - def strftime(self, format): - """Format the date/time using the *current timezone representation*.""" - x = _calcDependentSecond2(self._year, self._month, self._day, - self._hour, self._minute, self._second) - ltz = self._calcTimezoneName(x, 0) - tzdiff = _tzoffset(ltz, self._t) - _tzoffset(self._tz, self._t) - zself = self + tzdiff / 86400.0 - microseconds = int((zself._second - zself._nearsec) * 1000000) - unicode_format = False - if isinstance(format, explicit_unicode_type): - format = format.encode('utf-8') - unicode_format = True - ds = datetime(zself._year, zself._month, zself._day, zself._hour, - zself._minute, int(zself._nearsec), - microseconds).strftime(format) - if unicode_format: - return ds.decode('utf-8') - return ds - - # General formats from previous DateTime - def Date(self): - """Return the date string for the object.""" - return "%s/%2.2d/%2.2d" % (self._year, self._month, self._day) - - def Time(self): - """Return the time string for an object to the nearest second.""" - return '%2.2d:%2.2d:%2.2d' % (self._hour, self._minute, self._nearsec) - - def TimeMinutes(self): - """Return the time string for an object not showing seconds.""" - return '%2.2d:%2.2d' % (self._hour, self._minute) - - def AMPM(self): - """Return the time string for an object to the nearest second.""" - return '%2.2d:%2.2d:%2.2d %s' % ( - self._pmhour, self._minute, self._nearsec, self._pm) - - def AMPMMinutes(self): - """Return the time string for an object not showing seconds.""" - return '%2.2d:%2.2d %s' % (self._pmhour, self._minute, self._pm) - - def PreciseTime(self): - """Return the time string for the object.""" - return '%2.2d:%2.2d:%06.3f' % (self._hour, self._minute, self._second) - - def PreciseAMPM(self): - """Return the time string for the object.""" - return '%2.2d:%2.2d:%06.3f %s' % ( - self._pmhour, self._minute, self._second, self._pm) - - def yy(self): - """Return calendar year as a 2 digit string.""" - return str(self._year)[-2:] - - def mm(self): - """Return month as a 2 digit string.""" - return '%02d' % self._month - - def dd(self): - """Return day as a 2 digit string.""" - return '%02d' % self._day - - def rfc822(self): - """Return the date in RFC 822 format.""" - tzoffset = _tzoffset2rfc822zone(_tzoffset(self._tz, self._t)) - return '%s, %2.2d %s %d %2.2d:%2.2d:%2.2d %s' % ( - self._aday, self._day, self._amon, self._year, - self._hour, self._minute, self._nearsec, tzoffset) - - # New formats - def fCommon(self): - """Return a string representing the object\'s value - in the format: March 1, 1997 1:45 pm. - """ - return '%s %s, %4.4d %s:%2.2d %s' % ( - self._fmon, self._day, self._year, self._pmhour, - self._minute, self._pm) - - def fCommonZ(self): - """Return a string representing the object\'s value - in the format: March 1, 1997 1:45 pm US/Eastern. - """ - return '%s %s, %4.4d %d:%2.2d %s %s' % ( - self._fmon, self._day, self._year, self._pmhour, - self._minute, self._pm, self._tz) - - def aCommon(self): - """Return a string representing the object\'s value - in the format: Mar 1, 1997 1:45 pm. - """ - return '%s %s, %4.4d %s:%2.2d %s' % ( - self._amon, self._day, self._year, self._pmhour, - self._minute, self._pm) - - def aCommonZ(self): - """Return a string representing the object\'s value - in the format: Mar 1, 1997 1:45 pm US/Eastern. - """ - return '%s %s, %4.4d %d:%2.2d %s %s' % ( - self._amon, self._day, self._year, self._pmhour, - self._minute, self._pm, self._tz) - - def pCommon(self): - """Return a string representing the object\'s value - in the format: Mar. 1, 1997 1:45 pm. - """ - return '%s %s, %4.4d %s:%2.2d %s' % ( - self._pmon, self._day, self._year, self._pmhour, - self._minute, self._pm) - - def pCommonZ(self): - """Return a string representing the object\'s value - in the format: Mar. 1, 1997 1:45 pm US/Eastern. - """ - return '%s %s, %4.4d %d:%2.2d %s %s' % ( - self._pmon, self._day, self._year, self._pmhour, - self._minute, self._pm, self._tz) - - def ISO(self): - """Return the object in ISO standard format. - - Note: this is *not* ISO 8601-format! See the ISO8601 and - HTML4 methods below for ISO 8601-compliant output. - - Dates are output as: YYYY-MM-DD HH:MM:SS - """ - return "%.4d-%.2d-%.2d %.2d:%.2d:%.2d" % ( - self._year, self._month, self._day, - self._hour, self._minute, self._second) - - def ISO8601(self): - """Return the object in ISO 8601-compatible format containing the - date, time with seconds-precision and the time zone identifier. - - See: http://www.w3.org/TR/NOTE-datetime - - Dates are output as: YYYY-MM-DDTHH:MM:SSTZD - T is a literal character. - TZD is Time Zone Designator, format +HH:MM or -HH:MM - - If the instance is timezone naive (it was not specified with a timezone - when it was constructed) then the timezone is ommitted. - - The HTML4 method below offers the same formatting, but converts - to UTC before returning the value and sets the TZD "Z". - """ - if self.timezoneNaive(): - return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d" % ( - self._year, self._month, self._day, - self._hour, self._minute, self._second) - tzoffset = _tzoffset2iso8601zone(_tzoffset(self._tz, self._t)) - return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d%s" % ( - self._year, self._month, self._day, - self._hour, self._minute, self._second, tzoffset) - - def HTML4(self): - """Return the object in the format used in the HTML4.0 specification, - one of the standard forms in ISO8601. - - See: http://www.w3.org/TR/NOTE-datetime - - Dates are output as: YYYY-MM-DDTHH:MM:SSZ - T, Z are literal characters. - The time is in UTC. - """ - newdate = self.toZone('UTC') - return "%0.4d-%0.2d-%0.2dT%0.2d:%0.2d:%0.2dZ" % ( - newdate._year, newdate._month, newdate._day, - newdate._hour, newdate._minute, newdate._second) - - def asdatetime(self): - """Return a standard libary datetime.datetime - """ - tznaive = self.timezoneNaive() - if tznaive: - tzinfo = None - else: - tzinfo = _TZINFO[self._tz].tzinfo - second = int(self._second) - microsec = self.micros() % 1000000 - dt = datetime(self._year, self._month, self._day, self._hour, - self._minute, second, microsec, tzinfo) - return dt - - def utcdatetime(self): - """Convert the time to UTC then return a timezone naive datetime object - """ - utc = self.toZone('UTC') - second = int(utc._second) - microsec = utc.micros() % 1000000 - dt = datetime(utc._year, utc._month, utc._day, utc._hour, - utc._minute, second, microsec) - return dt - - def __add__(self, other): - """A DateTime may be added to a number and a number may be - added to a DateTime; two DateTimes cannot be added. - """ - if hasattr(other, '_t'): - raise DateTimeError('Cannot add two DateTimes') - o = float(other) - tz = self._tz - omicros = round(o * 86400000000) - tmicros = self.micros() + omicros - t = tmicros / 1000000.0 - d = (tmicros + long(EPOCH * 1000000)) / 86400000000.0 - s = d - math.floor(d) - ms = t - math.floor(t) - x = _calcDependentSecond(tz, t) - yr, mo, dy, hr, mn, sc = _calcYMDHMS(x, ms) - return self.__class__(yr, mo, dy, hr, mn, sc, self._tz, - t, d, s, None, self.timezoneNaive()) - - __radd__ = __add__ - - def __sub__(self, other): - """Either a DateTime or a number may be subtracted from a - DateTime, however, a DateTime may not be subtracted from - a number. - """ - if hasattr(other, '_d'): - return (self.micros() - other.micros()) / 86400000000.0 - else: - return self.__add__(-(other)) - - def __repr__(self): - """Convert a DateTime to a string that looks like a Python - expression. - """ - return '%s(\'%s\')' % (self.__class__.__name__, str(self)) - - def __str__(self): - """Convert a DateTime to a string.""" - y, m, d = self._year, self._month, self._day - h, mn, s, t = self._hour, self._minute, self._second, self._tz - if s == int(s): - # A whole number of seconds -- suppress milliseconds. - return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%2.2d %s' % ( - y, m, d, h, mn, s, t) - else: - # s is already rounded to the nearest microsecond, and - # it's not a whole number of seconds. Be sure to print - # 2 digits before the decimal point. - return '%4.4d/%2.2d/%2.2d %2.2d:%2.2d:%06.6f %s' % ( - y, m, d, h, mn, s, t) - - def __hash__(self): - """Compute a hash value for a DateTime.""" - return int(((self._year % 100 * 12 + self._month) * 31 + - self._day + self.time) * 100) - - def __int__(self): - """Convert to an integer number of seconds since the epoch (gmt).""" - return int(self.micros() // 1000000) - - def __long__(self): - """Convert to a long-int number of seconds since the epoch (gmt).""" - return long(self.micros() // 1000000) - - def __float__(self): - """Convert to floating-point number of seconds since the epoch (gmt). - """ - return self.micros() / 1000000.0 - - @property - def _t(self): - return self._micros / 1000000.0 - - def _parse_iso8601(self, s): - # preserve the previously implied contract - # who know where this could be used... - return self._parse_iso8601_preserving_tznaive(s)[:7] - - def _parse_iso8601_preserving_tznaive(self, s): - try: - return self.__parse_iso8601(s) - except IndexError: - raise SyntaxError( - 'Not an ISO 8601 compliant date string: "%s"' % s) - - def __parse_iso8601(self, s): - """Parse an ISO 8601 compliant date. - - See: http://en.wikipedia.org/wiki/ISO_8601 - """ - month = day = week_day = 1 - year = hour = minute = seconds = hour_off = min_off = 0 - tznaive = True - - iso8601 = iso8601Match(s.strip()) - fields = iso8601 and iso8601.groupdict() or {} - if not iso8601 or fields.get('garbage'): - raise IndexError - - if fields['year']: - year = int(fields['year']) - if fields['month']: - month = int(fields['month']) - if fields['day']: - day = int(fields['day']) - - if fields['year_day']: - d = DateTime('%s-01-01' % year) + int(fields['year_day']) - 1 - month = d.month() - day = d.day() - - if fields['week']: - week = int(fields['week']) - if fields['week_day']: - week_day = int(fields['week_day']) - d = DateTime('%s-01-04' % year) - d = d - (d.dow() + 6) % 7 + week * 7 + week_day - 8 - month = d.month() - day = d.day() - - if fields['hour']: - hour = int(fields['hour']) - - if fields['minute']: - minute = int(fields['minute']) - elif fields['fraction']: - minute = 60.0 * float('0.%s' % fields['fraction']) - seconds, minute = math.modf(minute) - minute = int(minute) - seconds = 60.0 * seconds - # Avoid reprocess when handling seconds, bellow - fields['fraction'] = None - - if fields['second']: - seconds = int(fields['second']) - if fields['fraction']: - seconds = seconds + float('0.%s' % fields['fraction']) - elif fields['fraction']: - seconds = 60.0 * float('0.%s' % fields['fraction']) - - if fields['hour_off']: - hour_off = int(fields['hour_off']) - if fields['signal'] == '-': - hour_off *= -1 - - if fields['min_off']: - min_off = int(fields['min_off']) - - if fields['signal'] or fields['Z']: - tznaive = False - else: - tznaive = True - - # Differ from the specification here. To preserve backwards - # compatibility assume a default timezone == UTC. - tz = 'GMT%+03d%02d' % (hour_off, min_off) - - return year, month, day, hour, minute, seconds, tz, tznaive - - def JulianDay(self): - """Return the Julian day. - - See: http://www.tondering.dk/claus/cal/node3.html#sec-calcjd - """ - a = (14 - self._month) // 12 - y = self._year + 4800 - a - m = self._month + (12 * a) - 3 - return (self._day + (153 * m + 2) // 5 + 365 * y + - y // 4 - y // 100 + y // 400 - 32045) - - def week(self): - """Return the week number according to ISO. - - See: http://www.tondering.dk/claus/cal/node6.html - """ - J = self.JulianDay() - d4 = (J + 31741 - (J % 7)) % 146097 % 36524 % 1461 - L = d4 // 1460 - d1 = ((d4 - L) % 365) + L - return d1 // 7 + 1 - - def encode(self, out): - """Encode value for XML-RPC.""" - out.write('<value><dateTime.iso8601>') - out.write(self.ISO8601()) - out.write('</dateTime.iso8601></value>\n') - - -# Provide the _dt_reconstructor function here, in case something -# accidentally creates a reference to this function - -orig_reconstructor = copy_reg._reconstructor - - -def _dt_reconstructor(cls, base, state): - if cls is DateTime: - return cls(state) - return orig_reconstructor(cls, base, state) diff --git a/lib/DateTime/DateTime.txt b/lib/DateTime/DateTime.txt deleted file mode 100644 index 5467047..0000000 --- a/lib/DateTime/DateTime.txt +++ /dev/null @@ -1,785 +0,0 @@ -The DateTime package -==================== - -Encapsulation of date/time values. - - -Function Timezones() --------------------- - -Returns the list of recognized timezone names: - - >>> from DateTime import Timezones - >>> zones = set(Timezones()) - -Almost all of the standard pytz timezones are included, with the exception -of some commonly-used but ambiguous abbreviations, where historical Zope -usage conflicts with the name used by pytz: - - >>> import pytz - >>> [x for x in pytz.all_timezones if x not in zones] - ['CET', 'EET', 'EST', 'MET', 'MST', 'WET'] - -Class DateTime --------------- - -DateTime objects represent instants in time and provide interfaces for -controlling its representation without affecting the absolute value of -the object. - -DateTime objects may be created from a wide variety of string or -numeric data, or may be computed from other DateTime objects. -DateTimes support the ability to convert their representations to many -major timezones, as well as the ablility to create a DateTime object -in the context of a given timezone. - -DateTime objects provide partial numerical behavior: - -* Two date-time objects can be subtracted to obtain a time, in days - between the two. - -* A date-time object and a positive or negative number may be added to - obtain a new date-time object that is the given number of days later - than the input date-time object. - -* A positive or negative number and a date-time object may be added to - obtain a new date-time object that is the given number of days later - than the input date-time object. - -* A positive or negative number may be subtracted from a date-time - object to obtain a new date-time object that is the given number of - days earlier than the input date-time object. - -DateTime objects may be converted to integer, long, or float numbers -of days since January 1, 1901, using the standard int, long, and float -functions (Compatibility Note: int, long and float return the number -of days since 1901 in GMT rather than local machine timezone). -DateTime objects also provide access to their value in a float format -usable with the python time module, provided that the value of the -object falls in the range of the epoch-based time module. - -A DateTime object should be considered immutable; all conversion and numeric -operations return a new DateTime object rather than modify the current object. - -A DateTime object always maintains its value as an absolute UTC time, -and is represented in the context of some timezone based on the -arguments used to create the object. A DateTime object's methods -return values based on the timezone context. - -Note that in all cases the local machine timezone is used for -representation if no timezone is specified. - -Constructor for DateTime ------------------------- - -DateTime() returns a new date-time object. DateTimes may be created -with from zero to seven arguments: - -* If the function is called with no arguments, then the current date/ - time is returned, represented in the timezone of the local machine. - -* If the function is invoked with a single string argument which is a - recognized timezone name, an object representing the current time is - returned, represented in the specified timezone. - -* If the function is invoked with a single string argument - representing a valid date/time, an object representing that date/ - time will be returned. - - As a general rule, any date-time representation that is recognized - and unambigous to a resident of North America is acceptable. (The - reason for this qualification is that in North America, a date like: - 2/1/1994 is interpreted as February 1, 1994, while in some parts of - the world, it is interpreted as January 2, 1994.) A date/ time - string consists of two components, a date component and an optional - time component, separated by one or more spaces. If the time - component is omited, 12:00am is assumed. - - Any recognized timezone name specified as the final element of the - date/time string will be used for computing the date/time value. - (If you create a DateTime with the string, - "Mar 9, 1997 1:45pm US/Pacific", the value will essentially be the - same as if you had captured time.time() at the specified date and - time on a machine in that timezone). If no timezone is passed, then - the timezone configured on the local machine will be used, **except** - that if the date format matches ISO 8601 ('YYYY-MM-DD'), the instance - will use UTC / CMT+0 as the timezone. - - o Returns current date/time, represented in US/Eastern: - - >>> from DateTime import DateTime - >>> e = DateTime('US/Eastern') - >>> e.timezone() - 'US/Eastern' - - o Returns specified time, represented in local machine zone: - - >>> x = DateTime('1997/3/9 1:45pm') - >>> x.parts() # doctest: +ELLIPSIS - (1997, 3, 9, 13, 45, ...) - - o Specified time in local machine zone, verbose format: - - >>> y = DateTime('Mar 9, 1997 13:45:00') - >>> y.parts() # doctest: +ELLIPSIS - (1997, 3, 9, 13, 45, ...) - >>> y == x - True - - o Specified time in UTC via ISO 8601 rule: - - >>> z = DateTime('2014-03-24') - >>> z.parts() # doctest: +ELLIPSIS - (2014, 3, 24, 0, 0, ...) - >>> z.timezone() - 'GMT+0' - - The date component consists of year, month, and day values. The - year value must be a one-, two-, or four-digit integer. If a one- - or two-digit year is used, the year is assumed to be in the - twentieth century. The month may an integer, from 1 to 12, a month - name, or a month abreviation, where a period may optionally follow - the abreviation. The day must be an integer from 1 to the number of - days in the month. The year, month, and day values may be separated - by periods, hyphens, forward, shashes, or spaces. Extra spaces are - permitted around the delimiters. Year, month, and day values may be - given in any order as long as it is possible to distinguish the - components. If all three components are numbers that are less than - 13, then a a month-day-year ordering is assumed. - - The time component consists of hour, minute, and second values - separated by colons. The hour value must be an integer between 0 - and 23 inclusively. The minute value must be an integer between 0 - and 59 inclusively. The second value may be an integer value - between 0 and 59.999 inclusively. The second value or both the - minute and second values may be ommitted. The time may be followed - by am or pm in upper or lower case, in which case a 12-hour clock is - assumed. - -* If the DateTime function is invoked with a single Numeric argument, - the number is assumed to be either a floating point value such as - that returned by time.time() , or a number of days after January 1, - 1901 00:00:00 UTC. - - A DateTime object is returned that represents either the gmt value - of the time.time() float represented in the local machine's - timezone, or that number of days after January 1, 1901. Note that - the number of days after 1901 need to be expressed from the - viewpoint of the local machine's timezone. A negative argument will - yield a date-time value before 1901. - -* If the function is invoked with two numeric arguments, then the - first is taken to be an integer year and the second argument is - taken to be an offset in days from the beginning of the year, in the - context of the local machine timezone. The date-time value returned - is the given offset number of days from the beginning of the given - year, represented in the timezone of the local machine. The offset - may be positive or negative. Two-digit years are assumed to be in - the twentieth century. - -* If the function is invoked with two arguments, the first a float - representing a number of seconds past the epoch in gmt (such as - those returned by time.time()) and the second a string naming a - recognized timezone, a DateTime with a value of that gmt time will - be returned, represented in the given timezone. - - >>> import time - >>> t = time.time() - - Time t represented as US/Eastern: - - >>> now_east = DateTime(t, 'US/Eastern') - - Time t represented as US/Pacific: - - >>> now_west = DateTime(t, 'US/Pacific') - - Only their representations are different: - - >>> now_east.equalTo(now_west) - True - -* If the function is invoked with three or more numeric arguments, - then the first is taken to be an integer year, the second is taken - to be an integer month, and the third is taken to be an integer day. - If the combination of values is not valid, then a DateTimeError is - raised. One- or two-digit years up to 69 are assumed to be in the - 21st century, whereas values 70-99 are assumed to be 20th century. - The fourth, fifth, and sixth arguments are floating point, positive - or negative offsets in units of hours, minutes, and days, and - default to zero if not given. An optional string may be given as - the final argument to indicate timezone (the effect of this is as if - you had taken the value of time.time() at that time on a machine in - the specified timezone). - -If a string argument passed to the DateTime constructor cannot be -parsed, it will raise SyntaxError. Invalid date, time, or -timezone components will raise a DateTimeError. - -The module function Timezones() will return a list of the timezones -recognized by the DateTime module. Recognition of timezone names is -case-insensitive. - -Instance Methods for DateTime (IDateTime interface) ---------------------------------------------------- - -Conversion and comparison methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* ``timeTime()`` returns the date/time as a floating-point number in - UTC, in the format used by the python time module. Note that it is - possible to create date /time values with DateTime that have no - meaningful value to the time module, and in such cases a - DateTimeError is raised. A DateTime object's value must generally - be between Jan 1, 1970 (or your local machine epoch) and Jan 2038 to - produce a valid time.time() style value. - - >>> dt = DateTime('Mar 9, 1997 13:45:00 US/Eastern') - >>> dt.timeTime() - 857933100.0 - - >>> DateTime('2040/01/01 UTC').timeTime() - 2208988800.0 - - >>> DateTime('1900/01/01 UTC').timeTime() - -2208988800.0 - -* ``toZone(z)`` returns a DateTime with the value as the current - object, represented in the indicated timezone: - - >>> dt.toZone('UTC') - DateTime('1997/03/09 18:45:00 UTC') - - >>> dt.toZone('UTC').equalTo(dt) - True - -* ``isFuture()`` returns true if this object represents a date/time - later than the time of the call: - - >>> dt.isFuture() - False - >>> DateTime('Jan 1 3000').isFuture() # not time-machine safe! - True - -* ``isPast()`` returns true if this object represents a date/time - earlier than the time of the call: - - >>> dt.isPast() - True - >>> DateTime('Jan 1 3000').isPast() # not time-machine safe! - False - -* ``isCurrentYear()`` returns true if this object represents a - date/time that falls within the current year, in the context of this - object's timezone representation: - - >>> dt.isCurrentYear() - False - >>> DateTime().isCurrentYear() - True - -* ``isCurrentMonth()`` returns true if this object represents a - date/time that falls within the current month, in the context of - this object's timezone representation: - - >>> dt.isCurrentMonth() - False - >>> DateTime().isCurrentMonth() - True - -* ``isCurrentDay()`` returns true if this object represents a - date/time that falls within the current day, in the context of this - object's timezone representation: - - >>> dt.isCurrentDay() - False - >>> DateTime().isCurrentDay() - True - -* ``isCurrentHour()`` returns true if this object represents a - date/time that falls within the current hour, in the context of this - object's timezone representation: - - >>> dt.isCurrentHour() - False - - >>> DateTime().isCurrentHour() - True - -* ``isCurrentMinute()`` returns true if this object represents a - date/time that falls within the current minute, in the context of - this object's timezone representation: - - >>> dt.isCurrentMinute() - False - >>> DateTime().isCurrentMinute() - True - -* ``isLeapYear()`` returns true if the current year (in the context of - the object's timezone) is a leap year: - - >>> dt.isLeapYear() - False - >>> DateTime('Mar 8 2004').isLeapYear() - True - -* ``earliestTime()`` returns a new DateTime object that represents the - earliest possible time (in whole seconds) that still falls within - the current object's day, in the object's timezone context: - - >>> dt.earliestTime() - DateTime('1997/03/09 00:00:00 US/Eastern') - -* ``latestTime()`` return a new DateTime object that represents the - latest possible time (in whole seconds) that still falls within the - current object's day, in the object's timezone context - - >>> dt.latestTime() - DateTime('1997/03/09 23:59:59 US/Eastern') - -Component access -~~~~~~~~~~~~~~~~ - -* ``parts()`` returns a tuple containing the calendar year, month, - day, hour, minute second and timezone of the object - - >>> dt.parts() # doctest: +ELLIPSIS - (1997, 3, 9, 13, 45, ... 'US/Eastern') - -* ``timezone()`` returns the timezone in which the object is represented: - - >>> dt.timezone() in Timezones() - True - -* ``tzoffset()`` returns the timezone offset for the objects timezone: - - >>> dt.tzoffset() - -18000 - -* ``year()`` returns the calendar year of the object: - - >>> dt.year() - 1997 - -* ``month()`` retursn the month of the object as an integer: - - >>> dt.month() - 3 - -* ``Month()`` returns the full month name: - - >>> dt.Month() - 'March' - -* ``aMonth()`` returns the abreviated month name: - - >>> dt.aMonth() - 'Mar' - -* ``pMonth()`` returns the abreviated (with period) month name: - - >>> dt.pMonth() - 'Mar.' - -* ``day()`` returns the integer day: - - >>> dt.day() - 9 - -* ``Day()`` returns the full name of the day of the week: - - >>> dt.Day() - 'Sunday' - -* ``dayOfYear()`` returns the day of the year, in context of the - timezone representation of the object: - - >>> dt.dayOfYear() - 68 - -* ``aDay()`` returns the abreviated name of the day of the week: - - >>> dt.aDay() - 'Sun' - -* ``pDay()`` returns the abreviated (with period) name of the day of - the week: - - >>> dt.pDay() - 'Sun.' - -* ``dow()`` returns the integer day of the week, where Sunday is 0: - - >>> dt.dow() - 0 - -* ``dow_1()`` returns the integer day of the week, where sunday is 1: - - >>> dt.dow_1() - 1 - -* ``h_12()`` returns the 12-hour clock representation of the hour: - - >>> dt.h_12() - 1 - -* ``h_24()`` returns the 24-hour clock representation of the hour: - - >>> dt.h_24() - 13 - -* ``ampm()`` returns the appropriate time modifier (am or pm): - - >>> dt.ampm() - 'pm' - -* ``hour()`` returns the 24-hour clock representation of the hour: - - >>> dt.hour() - 13 - -* ``minute()`` returns the minute: - - >>> dt.minute() - 45 - -* ``second()`` returns the second: - - >>> dt.second() == 0 - True - -* ``millis()`` returns the milliseconds since the epoch in GMT. - - >>> dt.millis() == 857933100000 - True - -strftime() -~~~~~~~~~~ - -See ``tests/test_datetime.py``. - -General formats from previous DateTime -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* ``Date()`` return the date string for the object: - - >>> dt.Date() - '1997/03/09' - -* ``Time()`` returns the time string for an object to the nearest - second: - - >>> dt.Time() - '13:45:00' - -* ``TimeMinutes()`` returns the time string for an object not showing - seconds: - - >>> dt.TimeMinutes() - '13:45' - -* ``AMPM()`` returns the time string for an object to the nearest second: - - >>> dt.AMPM() - '01:45:00 pm' - -* ``AMPMMinutes()`` returns the time string for an object not showing - seconds: - - >>> dt.AMPMMinutes() - '01:45 pm' - -* ``PreciseTime()`` returns the time string for the object: - - >>> dt.PreciseTime() - '13:45:00.000' - -* ``PreciseAMPM()`` returns the time string for the object: - - >>> dt.PreciseAMPM() - '01:45:00.000 pm' - -* ``yy()`` returns the calendar year as a 2 digit string - - >>> dt.yy() - '97' - -* ``mm()`` returns the month as a 2 digit string - - >>> dt.mm() - '03' - -* ``dd()`` returns the day as a 2 digit string: - - >>> dt.dd() - '09' - -* ``rfc822()`` returns the date in RFC 822 format: - - >>> dt.rfc822() - 'Sun, 09 Mar 1997 13:45:00 -0500' - -New formats -~~~~~~~~~~~ - -* ``fCommon()`` returns a string representing the object's value in - the format: March 9, 1997 1:45 pm: - - >>> dt.fCommon() - 'March 9, 1997 1:45 pm' - -* ``fCommonZ()`` returns a string representing the object's value in - the format: March 9, 1997 1:45 pm US/Eastern: - - >>> dt.fCommonZ() - 'March 9, 1997 1:45 pm US/Eastern' - -* ``aCommon()`` returns a string representing the object's value in - the format: Mar 9, 1997 1:45 pm: - - >>> dt.aCommon() - 'Mar 9, 1997 1:45 pm' - -* ``aCommonZ()`` return a string representing the object's value in - the format: Mar 9, 1997 1:45 pm US/Eastern: - - >>> dt.aCommonZ() - 'Mar 9, 1997 1:45 pm US/Eastern' - -* ``pCommon()`` returns a string representing the object's value in - the format Mar. 9, 1997 1:45 pm: - - >>> dt.pCommon() - 'Mar. 9, 1997 1:45 pm' - -* ``pCommonZ()`` returns a string representing the object's value in - the format: Mar. 9, 1997 1:45 pm US/Eastern: - - >>> dt.pCommonZ() - 'Mar. 9, 1997 1:45 pm US/Eastern' - -* ``ISO()`` returns a string with the date/time in ISO format. Note: - this is not ISO 8601-format! See the ISO8601 and HTML4 methods below - for ISO 8601-compliant output. Dates are output as: YYYY-MM-DD HH:MM:SS - - >>> dt.ISO() - '1997-03-09 13:45:00' - -* ``ISO8601()`` returns the object in ISO 8601-compatible format - containing the date, time with seconds-precision and the time zone - identifier - see http://www.w3.org/TR/NOTE-datetime. Dates are - output as: YYYY-MM-DDTHH:MM:SSTZD (T is a literal character, TZD is - Time Zone Designator, format +HH:MM or -HH:MM). - - The ``HTML4()`` method below offers the same formatting, but - converts to UTC before returning the value and sets the TZD"Z" - - >>> dt.ISO8601() - '1997-03-09T13:45:00-05:00' - - -* ``HTML4()`` returns the object in the format used in the HTML4.0 - specification, one of the standard forms in ISO8601. See - http://www.w3.org/TR/NOTE-datetime. Dates are output as: - YYYY-MM-DDTHH:MM:SSZ (T, Z are literal characters, the time is in - UTC.): - - >>> dt.HTML4() - '1997-03-09T18:45:00Z' - -* ``JulianDay()`` returns the Julian day according to - http://www.tondering.dk/claus/cal/node3.html#sec-calcjd - - >>> dt.JulianDay() - 2450517 - -* ``week()`` returns the week number according to ISO - see http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 - - >>> dt.week() - 10 - -Deprecated API -~~~~~~~~~~~~~~ - -* DayOfWeek(): see Day() - -* Day_(): see pDay() - -* Mon(): see aMonth() - -* Mon_(): see pMonth - -General Services Provided by DateTime -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -DateTimes can be repr()'ed; the result will be a string indicating how -to make a DateTime object like this: - - >>> repr(dt) - "DateTime('1997/03/09 13:45:00 US/Eastern')" - -When we convert them into a string, we get a nicer string that could -actually be shown to a user: - - >>> str(dt) - '1997/03/09 13:45:00 US/Eastern' - -The hash value of a DateTime is based on the date and time and is -equal for different representations of the DateTime: - - >>> hash(dt) - 3618678 - >>> hash(dt.toZone('UTC')) - 3618678 - -DateTime objects can be compared to other DateTime objects OR floating -point numbers such as the ones which are returned by the python time -module by using the equalTo method. Using this API, True is returned if the -object represents a date/time equal to the specified DateTime or time module -style time: - - >>> dt.equalTo(dt) - True - >>> dt.equalTo(dt.toZone('UTC')) - True - >>> dt.equalTo(dt.timeTime()) - True - >>> dt.equalTo(DateTime()) - False - -Same goes for inequalities: - - >>> dt.notEqualTo(dt) - False - >>> dt.notEqualTo(dt.toZone('UTC')) - False - >>> dt.notEqualTo(dt.timeTime()) - False - >>> dt.notEqualTo(DateTime()) - True - -Normal equality operations only work with datetime objects and take the -timezone setting into account: - - >>> dt == dt - True - >>> dt == dt.toZone('UTC') - False - >>> dt == DateTime() - False - - >>> dt != dt - False - >>> dt != dt.toZone('UTC') - True - >>> dt != DateTime() - True - -But the other comparison operations compare the referenced moment in time and -not the representation itself: - - >>> dt > dt - False - >>> DateTime() > dt - True - >>> dt > DateTime().timeTime() - False - >>> DateTime().timeTime() > dt - True - - >>> dt.greaterThan(dt) - False - >>> DateTime().greaterThan(dt) - True - >>> dt.greaterThan(DateTime().timeTime()) - False - - >>> dt >= dt - True - >>> DateTime() >= dt - True - >>> dt >= DateTime().timeTime() - False - >>> DateTime().timeTime() >= dt - True - - >>> dt.greaterThanEqualTo(dt) - True - >>> DateTime().greaterThanEqualTo(dt) - True - >>> dt.greaterThanEqualTo(DateTime().timeTime()) - False - - >>> dt < dt - False - >>> DateTime() < dt - False - >>> dt < DateTime().timeTime() - True - >>> DateTime().timeTime() < dt - False - - >>> dt.lessThan(dt) - False - >>> DateTime().lessThan(dt) - False - >>> dt.lessThan(DateTime().timeTime()) - True - - >>> dt <= dt - True - >>> DateTime() <= dt - False - >>> dt <= DateTime().timeTime() - True - >>> DateTime().timeTime() <= dt - False - - >>> dt.lessThanEqualTo(dt) - True - >>> DateTime().lessThanEqualTo(dt) - False - >>> dt.lessThanEqualTo(DateTime().timeTime()) - True - -Numeric Services Provided by DateTime -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A DateTime may be added to a number and a number may be added to a -DateTime: - - >>> dt + 5 - DateTime('1997/03/14 13:45:00 US/Eastern') - >>> 5 + dt - DateTime('1997/03/14 13:45:00 US/Eastern') - -Two DateTimes cannot be added: - - >>> from DateTime.interfaces import DateTimeError - >>> try: - ... dt + dt - ... print('fail') - ... except DateTimeError: - ... print('ok') - ok - -Either a DateTime or a number may be subtracted from a DateTime, -however, a DateTime may not be subtracted from a number: - - >>> DateTime('1997/03/10 13:45 US/Eastern') - dt - 1.0 - >>> dt - 1 - DateTime('1997/03/08 13:45:00 US/Eastern') - >>> 1 - dt - Traceback (most recent call last): - ... - TypeError: unsupported operand type(s) for -: 'int' and 'DateTime' - -DateTimes can also be converted to integers (number of seconds since -the epoch) and floats: - - >>> int(dt) - 857933100 - >>> float(dt) - 857933100.0 diff --git a/lib/DateTime/__init__.py b/lib/DateTime/__init__.py deleted file mode 100644 index b4181ad..0000000 --- a/lib/DateTime/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## - -from .DateTime import DateTime -from .DateTime import Timezones - -__all__ = ('DateTime', 'Timezones') diff --git a/lib/DateTime/interfaces.py b/lib/DateTime/interfaces.py deleted file mode 100644 index 5f29cff..0000000 --- a/lib/DateTime/interfaces.py +++ /dev/null @@ -1,375 +0,0 @@ -############################################################################## -# -# Copyright (c) 2005 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## -from zope.interface import Interface - - -class DateTimeError(Exception): - pass - - -class SyntaxError(DateTimeError): - pass - - -class DateError(DateTimeError): - pass - - -class TimeError(DateTimeError): - pass - - -class IDateTime(Interface): - # Conversion and comparison methods - - #TODO determine whether this method really is part of the public API - def localZone(ltm=None): - '''Returns the time zone on the given date. The time zone - can change according to daylight savings.''' - - def timeTime(): - """Return the date/time as a floating-point number in UTC, in - the format used by the python time module. Note that it is - possible to create date/time values with DateTime that have no - meaningful value to the time module.""" - - def toZone(z): - """Return a DateTime with the value as the current object, - represented in the indicated timezone.""" - - def isFuture(): - """Return true if this object represents a date/time later - than the time of the call""" - - def isPast(): - """Return true if this object represents a date/time earlier - than the time of the call""" - - def isCurrentYear(): - """Return true if this object represents a date/time that - falls within the current year, in the context of this - object's timezone representation""" - - def isCurrentMonth(): - """Return true if this object represents a date/time that - falls within the current month, in the context of this - object's timezone representation""" - - def isCurrentDay(): - """Return true if this object represents a date/time that - falls within the current day, in the context of this object's - timezone representation""" - - def isCurrentHour(): - """Return true if this object represents a date/time that - falls within the current hour, in the context of this object's - timezone representation""" - - def isCurrentMinute(): - """Return true if this object represents a date/time that - falls within the current minute, in the context of this - object's timezone representation""" - - def isLeapYear(): - """Return true if the current year (in the context of the - object's timezone) is a leap year""" - - def earliestTime(): - """Return a new DateTime object that represents the earliest - possible time (in whole seconds) that still falls within the - current object's day, in the object's timezone context""" - - def latestTime(): - """Return a new DateTime object that represents the latest - possible time (in whole seconds) that still falls within the - current object's day, in the object's timezone context""" - - def greaterThan(t): - """Compare this DateTime object to another DateTime object OR - a floating point number such as that which is returned by the - python time module. Returns true if the object represents a - date/time greater than the specified DateTime or time module - style time. Revised to give more correct results through - comparison of long integer milliseconds.""" - - __gt__ = greaterThan - - def greaterThanEqualTo(t): - """Compare this DateTime object to another DateTime object OR - a floating point number such as that which is returned by the - python time module. Returns true if the object represents a - date/time greater than or equal to the specified DateTime or - time module style time. Revised to give more correct results - through comparison of long integer milliseconds.""" - - __ge__ = greaterThanEqualTo - - def equalTo(t): - """Compare this DateTime object to another DateTime object OR - a floating point number such as that which is returned by the - python time module. Returns true if the object represents a - date/time equal to the specified DateTime or time module style - time. Revised to give more correct results through comparison - of long integer milliseconds.""" - - __eq__ = equalTo - - def notEqualTo(t): - """Compare this DateTime object to another DateTime object OR - a floating point number such as that which is returned by the - python time module. Returns true if the object represents a - date/time not equal to the specified DateTime or time module - style time. Revised to give more correct results through - comparison of long integer milliseconds.""" - - __ne__ = notEqualTo - - def lessThan(t): - """Compare this DateTime object to another DateTime object OR - a floating point number such as that which is returned by the - python time module. Returns true if the object represents a - date/time less than the specified DateTime or time module - style time. Revised to give more correct results through - comparison of long integer milliseconds.""" - - __lt__ = lessThan - - def lessThanEqualTo(t): - """Compare this DateTime object to another DateTime object OR - a floating point number such as that which is returned by the - python time module. Returns true if the object represents a - date/time less than or equal to the specified DateTime or time - module style time. Revised to give more correct results - through comparison of long integer milliseconds.""" - - __le__ = lessThanEqualTo - - # Component access - - def parts(): - """Return a tuple containing the calendar year, month, day, - hour, minute second and timezone of the object""" - - def timezone(): - """Return the timezone in which the object is represented.""" - - def tzoffset(): - """Return the timezone offset for the objects timezone.""" - - def year(): - """Return the calendar year of the object""" - - def month(): - """Return the month of the object as an integer""" - - def Month(): - """Return the full month name""" - - def aMonth(): - """Return the abreviated month name.""" - - def Mon(): - """Compatibility: see aMonth""" - - def pMonth(): - """Return the abreviated (with period) month name.""" - - def Mon_(): - """Compatibility: see pMonth""" - - def day(): - """Return the integer day""" - - def Day(): - """Return the full name of the day of the week""" - - def DayOfWeek(): - """Compatibility: see Day""" - - def dayOfYear(): - """Return the day of the year, in context of the timezone - representation of the object""" - - def aDay(): - """Return the abreviated name of the day of the week""" - - def pDay(): - """Return the abreviated (with period) name of the day of the - week""" - - def Day_(): - """Compatibility: see pDay""" - - def dow(): - """Return the integer day of the week, where sunday is 0""" - - def dow_1(): - """Return the integer day of the week, where sunday is 1""" - - def h_12(): - """Return the 12-hour clock representation of the hour""" - - def h_24(): - """Return the 24-hour clock representation of the hour""" - - def ampm(): - """Return the appropriate time modifier (am or pm)""" - - def hour(): - """Return the 24-hour clock representation of the hour""" - - def minute(): - """Return the minute""" - - def second(): - """Return the second""" - - def millis(): - """Return the millisecond since the epoch in GMT.""" - - def strftime(format): - """Format the date/time using the *current timezone representation*.""" - - # General formats from previous DateTime - - def Date(): - """Return the date string for the object.""" - - def Time(): - """Return the time string for an object to the nearest second.""" - - def TimeMinutes(): - """Return the time string for an object not showing seconds.""" - - def AMPM(): - """Return the time string for an object to the nearest second.""" - - def AMPMMinutes(): - """Return the time string for an object not showing seconds.""" - - def PreciseTime(): - """Return the time string for the object.""" - - def PreciseAMPM(): - """Return the time string for the object.""" - - def yy(): - """Return calendar year as a 2 digit string""" - - def mm(): - """Return month as a 2 digit string""" - - def dd(): - """Return day as a 2 digit string""" - - def rfc822(): - """Return the date in RFC 822 format""" - - # New formats - - def fCommon(): - """Return a string representing the object's value in the - format: March 1, 1997 1:45 pm""" - - def fCommonZ(): - """Return a string representing the object's value in the - format: March 1, 1997 1:45 pm US/Eastern""" - - def aCommon(): - """Return a string representing the object's value in the - format: Mar 1, 1997 1:45 pm""" - - def aCommonZ(): - """Return a string representing the object's value in the - format: Mar 1, 1997 1:45 pm US/Eastern""" - - def pCommon(): - """Return a string representing the object's value in the - format: Mar. 1, 1997 1:45 pm""" - - def pCommonZ(): - """Return a string representing the object's value - in the format: Mar. 1, 1997 1:45 pm US/Eastern""" - - def ISO(): - """Return the object in ISO standard format. Note: this is - *not* ISO 8601-format! See the ISO8601 and HTML4 methods below - for ISO 8601-compliant output - - Dates are output as: YYYY-MM-DD HH:MM:SS - """ - - def ISO8601(): - """Return the object in ISO 8601-compatible format containing - the date, time with seconds-precision and the time zone - identifier - see http://www.w3.org/TR/NOTE-datetime - - Dates are output as: YYYY-MM-DDTHH:MM:SSTZD - T is a literal character. - TZD is Time Zone Designator, format +HH:MM or -HH:MM - - The HTML4 method below offers the same formatting, but - converts to UTC before returning the value and sets the TZD"Z" - """ - - def HTML4(): - """Return the object in the format used in the HTML4.0 - specification, one of the standard forms in ISO8601. See - http://www.w3.org/TR/NOTE-datetime - - Dates are output as: YYYY-MM-DDTHH:MM:SSZ - T, Z are literal characters. - The time is in UTC. - """ - - def JulianDay(): - """Return the Julian day according to - http://www.tondering.dk/claus/cal/node3.html#sec-calcjd - """ - - def week(): - """Return the week number according to ISO - see http://www.tondering.dk/claus/cal/node6.html#SECTION00670000000000000000 - """ - - # Python operator and conversion API - - def __add__(other): - """A DateTime may be added to a number and a number may be - added to a DateTime; two DateTimes cannot be added.""" - - __radd__ = __add__ - - def __sub__(other): - """Either a DateTime or a number may be subtracted from a - DateTime, however, a DateTime may not be subtracted from a - number.""" - - def __repr__(): - """Convert a DateTime to a string that looks like a Python - expression.""" - - def __str__(): - """Convert a DateTime to a string.""" - - def __hash__(): - """Compute a hash value for a DateTime""" - - def __int__(): - """Convert to an integer number of seconds since the epoch (gmt)""" - - def __long__(): - """Convert to a long-int number of seconds since the epoch (gmt)""" - - def __float__(): - """Convert to floating-point number of seconds since the epoch (gmt)""" diff --git a/lib/DateTime/pytz.txt b/lib/DateTime/pytz.txt deleted file mode 100644 index 33de811..0000000 --- a/lib/DateTime/pytz.txt +++ /dev/null @@ -1,192 +0,0 @@ -Pytz Support -============ - -Allows the pytz package to be used for time zone information. The -advantage of using pytz is that it has a more complete and up to date -time zone and daylight savings time database. - -Usage ------ -You don't have to do anything special to make it work. - - >>> from DateTime import DateTime, Timezones - >>> d = DateTime('March 11, 2007 US/Eastern') - -Daylight Savings ----------------- -In 2007 daylight savings time in the US was changed. The Energy Policy -Act of 2005 mandates that DST will start on the second Sunday in March -and end on the first Sunday in November. - -In 2007, the start and stop dates are March 11 and November 4, -respectively. These dates are different from previous DST start and -stop dates. In 2006, the dates were the first Sunday in April (April -2, 2006) and the last Sunday in October (October 29, 2006). - -Let's make sure that DateTime can deal with this, since the primary -motivation to use pytz for time zone information is the fact that it -is kept up to date with daylight savings changes. - - >>> DateTime('March 11, 2007 US/Eastern').tzoffset() - -18000 - >>> DateTime('March 12, 2007 US/Eastern').tzoffset() - -14400 - >>> DateTime('November 4, 2007 US/Eastern').tzoffset() - -14400 - >>> DateTime('November 5, 2007 US/Eastern').tzoffset() - -18000 - -Let's compare this to 2006. - - >>> DateTime('April 2, 2006 US/Eastern').tzoffset() - -18000 - >>> DateTime('April 3, 2006 US/Eastern').tzoffset() - -14400 - >>> DateTime('October 29, 2006 US/Eastern').tzoffset() - -14400 - >>> DateTime('October 30, 2006 US/Eastern').tzoffset() - -18000 - -Time Zones ---------- -DateTime can use pytz's large database of time zones. Here are some -examples: - - >>> d = DateTime('Pacific/Kwajalein') - >>> d = DateTime('America/Shiprock') - >>> d = DateTime('Africa/Ouagadougou') - -Of course pytz doesn't know about everything. - - >>> from DateTime.interfaces import SyntaxError - >>> try: - ... d = DateTime('July 21, 1969 Moon/Eastern') - ... print('fail') - ... except SyntaxError: - ... print('ok') - ok - -You can still use zone names that DateTime defines that aren't part of -the pytz database. - - >>> d = DateTime('eet') - >>> d = DateTime('iceland') - -These time zones use DateTimes database. So it's preferable to use the -official time zone name. - -One trickiness is that DateTime supports some zone name -abbreviations. Some of these map to pytz names, so these abbreviations -will give you time zone date from pytz. Notable among abbreviations -that work this way are 'est', 'cst', 'mst', and 'pst'. - -Let's verify that 'est' picks up the 2007 daylight savings time changes. - - >>> DateTime('March 11, 2007 est').tzoffset() - -18000 - >>> DateTime('March 12, 2007 est').tzoffset() - -14400 - >>> DateTime('November 4, 2007 est').tzoffset() - -14400 - >>> DateTime('November 5, 2007 est').tzoffset() - -18000 - -You can get a list of time zones supported by calling the Timezones() function. - - >>> Timezones() #doctest: +ELLIPSIS - ['Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', ...] - -Note that you can mess with this list without hurting things. - - >>> t = Timezones() - >>> t.remove('US/Eastern') - >>> d = DateTime('US/Eastern') - - -Internal Components -------------------- - -The following are tests of internal components. - -Cache -~~~~~ - -The DateTime class uses a new time zone cache. - - >>> from DateTime.DateTime import _TZINFO - >>> _TZINFO #doctest: +ELLIPSIS - <DateTime.pytz_support.PytzCache ...> - -The cache maps time zone names to time zone instances. - - >>> cache = _TZINFO - >>> tz = cache['GMT+730'] - >>> tz = cache['US/Mountain'] - -The cache also must provide a few attributes for use by the DateTime -class. - -The _zlst attribute is a list of supported time zone names. - - >>> cache._zlst #doctest: +ELLIPSIS - ['Africa/Abidjan'... 'Africa/Accra'... 'IDLE'... 'NZST'... 'NZT'...] - -The _zidx attribute is a list of lower-case and possibly abbreviated -time zone names that can be mapped to offical zone names. - - >>> 'australia/yancowinna' in cache._zidx - True - >>> 'europe/isle_of_man' in cache._zidx - True - >>> 'gmt+0500' in cache._zidx - True - -Note that there are more items in _zidx than in _zlst since there are -multiple names for some time zones. - - >>> len(cache._zidx) > len(cache._zlst) - True - -Each entry in _zlst should also be present in _zidx in lower case form. - - >>> for name in cache._zlst: - ... if not name.lower() in cache._zidx: - ... print("Error %s not in _zidx" % name.lower()) - -The _zmap attribute maps the names in _zidx to official names in _zlst. - - >>> cache._zmap['africa/abidjan'] - 'Africa/Abidjan' - >>> cache._zmap['gmt+1'] - 'GMT+1' - >>> cache._zmap['gmt+0100'] - 'GMT+1' - >>> cache._zmap['utc'] - 'UTC' - -Let's make sure that _zmap and _zidx agree. - - >>> idx = set(cache._zidx) - >>> keys = set(cache._zmap.keys()) - >>> idx == keys - True - -Timezone objects -~~~~~~~~~~~~~~~~ -The timezone instances have only one public method info(). It returns -a tuple of (offset, is_dst, name). The method takes a timestamp, which -is used to determine dst information. - - >>> t1 = DateTime('November 4, 00:00 2007 US/Mountain').timeTime() - >>> t2 = DateTime('November 4, 02:00 2007 US/Mountain').timeTime() - >>> tz.info(t1) - (-21600, 1, 'MDT') - >>> tz.info(t2) - (-25200, 0, 'MST') - -If you don't pass any arguments to info it provides daylight savings -time information as of today. - - >>> tz.info() in ((-21600, 1, 'MDT'), (-25200, 0, 'MST')) - True - diff --git a/lib/DateTime/pytz_support.py b/lib/DateTime/pytz_support.py deleted file mode 100644 index 8cfbfc5..0000000 --- a/lib/DateTime/pytz_support.py +++ /dev/null @@ -1,259 +0,0 @@ -############################################################################## -# -# Copyright (c) 2007 Zope Foundation and Contributors. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE -# -############################################################################## - -from datetime import datetime, timedelta - -import pytz -import pytz.reference -from pytz.tzinfo import StaticTzInfo, memorized_timedelta - -from .interfaces import DateTimeError - -EPOCH = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc) - -_numeric_timezone_data = { - 'GMT': ('GMT', 0, 1, [], '', [(0, 0, 0)], 'GMT\000'), - 'GMT+0': ('GMT+0', 0, 1, [], '', [(0, 0, 0)], 'GMT+0000\000'), - 'GMT+1': ('GMT+1', 0, 1, [], '', [(3600, 0, 0)], 'GMT+0100\000'), - 'GMT+2': ('GMT+2', 0, 1, [], '', [(7200, 0, 0)], 'GMT+0200\000'), - 'GMT+3': ('GMT+3', 0, 1, [], '', [(10800, 0, 0)], 'GMT+0300\000'), - 'GMT+4': ('GMT+4', 0, 1, [], '', [(14400, 0, 0)], 'GMT+0400\000'), - 'GMT+5': ('GMT+5', 0, 1, [], '', [(18000, 0, 0)], 'GMT+0500\000'), - 'GMT+6': ('GMT+6', 0, 1, [], '', [(21600, 0, 0)], 'GMT+0600\000'), - 'GMT+7': ('GMT+7', 0, 1, [], '', [(25200, 0, 0)], 'GMT+0700\000'), - 'GMT+8': ('GMT+8', 0, 1, [], '', [(28800, 0, 0)], 'GMT+0800\000'), - 'GMT+9': ('GMT+9', 0, 1, [], '', [(32400, 0, 0)], 'GMT+0900\000'), - 'GMT+10': ('GMT+10', 0, 1, [], '', [(36000, 0, 0)], 'GMT+1000\000'), - 'GMT+11': ('GMT+11', 0, 1, [], '', [(39600, 0, 0)], 'GMT+1100\000'), - 'GMT+12': ('GMT+12', 0, 1, [], '', [(43200, 0, 0)], 'GMT+1200\000'), - 'GMT+13': ('GMT+13', 0, 1, [], '', [(46800, 0, 0)], 'GMT+1300\000'), - - 'GMT-1': ('GMT-1', 0, 1, [], '', [(-3600, 0, 0)], 'GMT-0100\000'), - 'GMT-2': ('GMT-2', 0, 1, [], '', [(-7200, 0, 0)], 'GMT-0200\000'), - 'GMT-3': ('GMT-3', 0, 1, [], '', [(-10800, 0, 0)], 'GMT-0300\000'), - 'GMT-4': ('GMT-4', 0, 1, [], '', [(-14400, 0, 0)], 'GMT-0400\000'), - 'GMT-5': ('GMT-5', 0, 1, [], '', [(-18000, 0, 0)], 'GMT-0500\000'), - 'GMT-6': ('GMT-6', 0, 1, [], '', [(-21600, 0, 0)], 'GMT-0600\000'), - 'GMT-7': ('GMT-7', 0, 1, [], '', [(-25200, 0, 0)], 'GMT-0700\000'), - 'GMT-8': ('GMT-8', 0, 1, [], '', [(-28800, 0, 0)], 'GMT-0800\000'), - 'GMT-9': ('GMT-9', 0, 1, [], '', [(-32400, 0, 0)], 'GMT-0900\000'), - 'GMT-10': ('GMT-10', 0, 1, [], '', [(-36000, 0, 0)], 'GMT-1000\000'), - 'GMT-11': ('GMT-11', 0, 1, [], '', [(-39600, 0, 0)], 'GMT-1100\000'), - 'GMT-12': ('GMT-12', 0, 1, [], '', [(-43200, 0, 0)], 'GMT-1200\000'), - - 'GMT+0130': ('GMT+0130', 0, 1, [], '', [(5400, 0, 0)], 'GMT+0130\000'), - 'GMT+0230': ('GMT+0230', 0, 1, [], '', [(9000, 0, 0)], 'GMT+0230\000'), - 'GMT+0330': ('GMT+0330', 0, 1, [], '', [(12600, 0, 0)], 'GMT+0330\000'), - 'GMT+0430': ('GMT+0430', 0, 1, [], '', [(16200, 0, 0)], 'GMT+0430\000'), - 'GMT+0530': ('GMT+0530', 0, 1, [], '', [(19800, 0, 0)], 'GMT+0530\000'), - 'GMT+0630': ('GMT+0630', 0, 1, [], '', [(23400, 0, 0)], 'GMT+0630\000'), - 'GMT+0730': ('GMT+0730', 0, 1, [], '', [(27000, 0, 0)], 'GMT+0730\000'), - 'GMT+0830': ('GMT+0830', 0, 1, [], '', [(30600, 0, 0)], 'GMT+0830\000'), - 'GMT+0930': ('GMT+0930', 0, 1, [], '', [(34200, 0, 0)], 'GMT+0930\000'), - 'GMT+1030': ('GMT+1030', 0, 1, [], '', [(37800, 0, 0)], 'GMT+1030\000'), - 'GMT+1130': ('GMT+1130', 0, 1, [], '', [(41400, 0, 0)], 'GMT+1130\000'), - 'GMT+1230': ('GMT+1230', 0, 1, [], '', [(45000, 0, 0)], 'GMT+1230\000'), - - 'GMT-0130': ('GMT-0130', 0, 1, [], '', [(-5400, 0, 0)], 'GMT-0130\000'), - 'GMT-0230': ('GMT-0230', 0, 1, [], '', [(-9000, 0, 0)], 'GMT-0230\000'), - 'GMT-0330': ('GMT-0330', 0, 1, [], '', [(-12600, 0, 0)], 'GMT-0330\000'), - 'GMT-0430': ('GMT-0430', 0, 1, [], '', [(-16200, 0, 0)], 'GMT-0430\000'), - 'GMT-0530': ('GMT-0530', 0, 1, [], '', [(-19800, 0, 0)], 'GMT-0530\000'), - 'GMT-0630': ('GMT-0630', 0, 1, [], '', [(-23400, 0, 0)], 'GMT-0630\000'), - 'GMT-0730': ('GMT-0730', 0, 1, [], '', [(-27000, 0, 0)], 'GMT-0730\000'), - 'GMT-0830': ('GMT-0830', 0, 1, [], '', [(-30600, 0, 0)], 'GMT-0830\000'), - 'GMT-0930': ('GMT-0930', 0, 1, [], '', [(-34200, 0, 0)], 'GMT-0930\000'), - 'GMT-1030': ('GMT-1030', 0, 1, [], '', [(-37800, 0, 0)], 'GMT-1030\000'), - 'GMT-1130': ('GMT-1130', 0, 1, [], '', [(-41400, 0, 0)], 'GMT-1130\000'), - 'GMT-1230': ('GMT-1230', 0, 1, [], '', [(-45000, 0, 0)], 'GMT-1230\000'), -} - -# These are the timezones not in pytz.common_timezones -_old_zlst = [ - 'AST', 'AT', 'BST', 'BT', 'CCT', - 'CET', 'CST', 'Cuba', 'EADT', 'EAST', - 'EEST', 'EET', 'EST', 'Egypt', 'FST', - 'FWT', 'GB-Eire', 'GMT+0100', 'GMT+0130', 'GMT+0200', - 'GMT+0230', 'GMT+0300', 'GMT+0330', 'GMT+0400', 'GMT+0430', - 'GMT+0500', 'GMT+0530', 'GMT+0600', 'GMT+0630', 'GMT+0700', - 'GMT+0730', 'GMT+0800', 'GMT+0830', 'GMT+0900', 'GMT+0930', - 'GMT+1', 'GMT+1000', 'GMT+1030', 'GMT+1100', 'GMT+1130', - 'GMT+1200', 'GMT+1230', 'GMT+1300', 'GMT-0100', 'GMT-0130', - 'GMT-0200', 'GMT-0300', 'GMT-0400', 'GMT-0500', 'GMT-0600', - 'GMT-0630', 'GMT-0700', 'GMT-0730', 'GMT-0800', 'GMT-0830', - 'GMT-0900', 'GMT-0930', 'GMT-1000', 'GMT-1030', 'GMT-1100', - 'GMT-1130', 'GMT-1200', 'GMT-1230', 'GST', 'Greenwich', - 'Hongkong', 'IDLE', 'IDLW', 'Iceland', 'Iran', - 'Israel', 'JST', 'Jamaica', 'Japan', 'MEST', - 'MET', 'MEWT', 'MST', 'NT', 'NZDT', - 'NZST', 'NZT', 'PST', 'Poland', 'SST', - 'SWT', 'Singapore', 'Turkey', 'UCT', 'UT', - 'Universal', 'WADT', 'WAST', 'WAT', 'WET', - 'ZP4', 'ZP5', 'ZP6', -] - -_old_zmap = { - 'aest': 'GMT+10', 'aedt': 'GMT+11', - 'aus eastern standard time': 'GMT+10', - 'sydney standard time': 'GMT+10', - 'tasmania standard time': 'GMT+10', - 'e. australia standard time': 'GMT+10', - 'aus central standard time': 'GMT+0930', - 'cen. australia standard time': 'GMT+0930', - 'w. australia standard time': 'GMT+8', - - 'central europe standard time': 'GMT+1', - 'eastern standard time': 'US/Eastern', - 'us eastern standard time': 'US/Eastern', - 'central standard time': 'US/Central', - 'mountain standard time': 'US/Mountain', - 'pacific standard time': 'US/Pacific', - 'mst': 'US/Mountain', 'pst': 'US/Pacific', - 'cst': 'US/Central', 'est': 'US/Eastern', - - 'gmt+0000': 'GMT+0', 'gmt+0': 'GMT+0', - - 'gmt+0100': 'GMT+1', 'gmt+0200': 'GMT+2', 'gmt+0300': 'GMT+3', - 'gmt+0400': 'GMT+4', 'gmt+0500': 'GMT+5', 'gmt+0600': 'GMT+6', - 'gmt+0700': 'GMT+7', 'gmt+0800': 'GMT+8', 'gmt+0900': 'GMT+9', - 'gmt+1000': 'GMT+10', 'gmt+1100': 'GMT+11', 'gmt+1200': 'GMT+12', - 'gmt+1300': 'GMT+13', - 'gmt-0100': 'GMT-1', 'gmt-0200': 'GMT-2', 'gmt-0300': 'GMT-3', - 'gmt-0400': 'GMT-4', 'gmt-0500': 'GMT-5', 'gmt-0600': 'GMT-6', - 'gmt-0700': 'GMT-7', 'gmt-0800': 'GMT-8', 'gmt-0900': 'GMT-9', - 'gmt-1000': 'GMT-10', 'gmt-1100': 'GMT-11', 'gmt-1200': 'GMT-12', - - 'gmt+1': 'GMT+1', 'gmt+2': 'GMT+2', 'gmt+3': 'GMT+3', - 'gmt+4': 'GMT+4', 'gmt+5': 'GMT+5', 'gmt+6': 'GMT+6', - 'gmt+7': 'GMT+7', 'gmt+8': 'GMT+8', 'gmt+9': 'GMT+9', - 'gmt+10': 'GMT+10', 'gmt+11': 'GMT+11', 'gmt+12': 'GMT+12', - 'gmt+13': 'GMT+13', - 'gmt-1': 'GMT-1', 'gmt-2': 'GMT-2', 'gmt-3': 'GMT-3', - 'gmt-4': 'GMT-4', 'gmt-5': 'GMT-5', 'gmt-6': 'GMT-6', - 'gmt-7': 'GMT-7', 'gmt-8': 'GMT-8', 'gmt-9': 'GMT-9', - 'gmt-10': 'GMT-10', 'gmt-11': 'GMT-11', 'gmt-12': 'GMT-12', - - 'gmt+130': 'GMT+0130', 'gmt+0130': 'GMT+0130', - 'gmt+230': 'GMT+0230', 'gmt+0230': 'GMT+0230', - 'gmt+330': 'GMT+0330', 'gmt+0330': 'GMT+0330', - 'gmt+430': 'GMT+0430', 'gmt+0430': 'GMT+0430', - 'gmt+530': 'GMT+0530', 'gmt+0530': 'GMT+0530', - 'gmt+630': 'GMT+0630', 'gmt+0630': 'GMT+0630', - 'gmt+730': 'GMT+0730', 'gmt+0730': 'GMT+0730', - 'gmt+830': 'GMT+0830', 'gmt+0830': 'GMT+0830', - 'gmt+930': 'GMT+0930', 'gmt+0930': 'GMT+0930', - 'gmt+1030': 'GMT+1030', - 'gmt+1130': 'GMT+1130', - 'gmt+1230': 'GMT+1230', - - 'gmt-130': 'GMT-0130', 'gmt-0130': 'GMT-0130', - 'gmt-230': 'GMT-0230', 'gmt-0230': 'GMT-0230', - 'gmt-330': 'GMT-0330', 'gmt-0330': 'GMT-0330', - 'gmt-430': 'GMT-0430', 'gmt-0430': 'GMT-0430', - 'gmt-530': 'GMT-0530', 'gmt-0530': 'GMT-0530', - 'gmt-630': 'GMT-0630', 'gmt-0630': 'GMT-0630', - 'gmt-730': 'GMT-0730', 'gmt-0730': 'GMT-0730', - 'gmt-830': 'GMT-0830', 'gmt-0830': 'GMT-0830', - 'gmt-930': 'GMT-0930', 'gmt-0930': 'GMT-0930', - 'gmt-1030': 'GMT-1030', - 'gmt-1130': 'GMT-1130', - 'gmt-1230': 'GMT-1230', - - 'ut': 'Universal', - 'bst': 'GMT+1', 'mest': 'GMT+2', 'sst': 'GMT+2', - 'fst': 'GMT+2', 'wadt': 'GMT+8', 'eadt': 'GMT+11', 'nzdt': 'GMT+13', - 'wet': 'GMT', 'wat': 'GMT-1', 'at': 'GMT-2', 'ast': 'GMT-4', - 'nt': 'GMT-11', 'idlw': 'GMT-12', 'cet': 'GMT+1', 'cest': 'GMT+2', - 'met': 'GMT+1', - 'mewt': 'GMT+1', 'swt': 'GMT+1', 'fwt': 'GMT+1', 'eet': 'GMT+2', - 'eest': 'GMT+3', - 'bt': 'GMT+3', 'zp4': 'GMT+4', 'zp5': 'GMT+5', 'zp6': 'GMT+6', - 'wast': 'GMT+7', 'cct': 'GMT+8', 'jst': 'GMT+9', 'east': 'GMT+10', - 'gst': 'GMT+10', 'nzt': 'GMT+12', 'nzst': 'GMT+12', 'idle': 'GMT+12', - 'ret': 'GMT+4', 'ist': 'GMT+0530', 'edt': 'GMT-4', - -} - - -# some timezone definitions of the "-0400" are not working -# when upgrading -for hour in range(0, 13): - hour = hour - fhour = str(hour) - if len(fhour) == 1: - fhour = '0' + fhour - _old_zmap['-%s00' % fhour] = 'GMT-%i' % hour - _old_zmap['+%s00' % fhour] = 'GMT+%i' % hour - - -def _static_timezone_factory(data): - zone = data[0] - cls = type(zone, (StaticTzInfo,), dict( - zone=zone, - _utcoffset=memorized_timedelta(data[5][0][0]), - _tzname=data[6][:-1])) # strip the trailing null - return cls() - -_numeric_timezones = dict((key, _static_timezone_factory(data)) - for key, data in _numeric_timezone_data.items()) - - -class Timezone: - """ - Timezone information returned by PytzCache.__getitem__ - Adapts datetime.tzinfo object to DateTime._timezone interface - """ - def __init__(self, tzinfo): - self.tzinfo = tzinfo - - def info(self, t=None): - if t is None: - dt = datetime.utcnow().replace(tzinfo=pytz.utc) - else: - # can't use utcfromtimestamp past 2038 - dt = EPOCH + timedelta(0, t) - - # need to normalize tzinfo for the datetime to deal with - # daylight savings time. - normalized_dt = self.tzinfo.normalize(dt.astimezone(self.tzinfo)) - normalized_tzinfo = normalized_dt.tzinfo - - offset = normalized_tzinfo.utcoffset(normalized_dt) - secs = offset.days * 24 * 60 * 60 + offset.seconds - dst = normalized_tzinfo.dst(normalized_dt) - if dst == timedelta(0): - is_dst = 0 - else: - is_dst = 1 - return secs, is_dst, normalized_tzinfo.tzname(normalized_dt) - - -class PytzCache: - """ - Reimplementation of the DateTime._cache class that uses for timezone info - """ - - _zlst = pytz.common_timezones + _old_zlst # used by DateTime.TimeZones - _zmap = dict((name.lower(), name) for name in pytz.all_timezones) - _zmap.update(_old_zmap) # These must take priority - _zidx = _zmap.keys() - - def __getitem__(self, key): - name = self._zmap.get(key.lower(), key) # fallback to key - try: - return Timezone(pytz.timezone(name)) - except pytz.UnknownTimeZoneError: - try: - return Timezone(_numeric_timezones[name]) - except KeyError: - raise DateTimeError('Unrecognized timezone: %s' % key) diff --git a/lib/DateTime/tests/__init__.py b/lib/DateTime/tests/__init__.py deleted file mode 100644 index e67bcb6..0000000 --- a/lib/DateTime/tests/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## - -# This file is needed to make this a package. diff --git a/lib/DateTime/tests/julian_testdata.txt b/lib/DateTime/tests/julian_testdata.txt deleted file mode 100644 index 386c3da..0000000 --- a/lib/DateTime/tests/julian_testdata.txt +++ /dev/null @@ -1,57 +0,0 @@ -1970-01-01 (1970, 1, 4) -1970-01-02 (1970, 1, 5) -1970-01-30 (1970, 5, 5) -1970-01-31 (1970, 5, 6) -1970-02-01 (1970, 5, 7) -1970-02-02 (1970, 6, 1) -1970-02-28 (1970, 9, 6) -1970-03-01 (1970, 9, 7) -1970-03-30 (1970, 14, 1) -1970-03-31 (1970, 14, 2) -1970-04-01 (1970, 14, 3) -1970-09-30 (1970, 40, 3) -1970-10-01 (1970, 40, 4) -1970-10-02 (1970, 40, 5) -1970-10-03 (1970, 40, 6) -1970-10-04 (1970, 40, 7) -1970-10-05 (1970, 41, 1) -1971-01-02 (1970, 53, 6) -1971-01-03 (1970, 53, 7) -1971-01-04 (1971, 1, 1) -1971-01-05 (1971, 1, 2) -1971-12-31 (1971, 52, 5) -1972-01-01 (1971, 52, 6) -1972-01-02 (1971, 52, 7) -1972-01-03 (1972, 1, 1) -1972-01-04 (1972, 1, 2) -1972-12-30 (1972, 52, 6) -1972-12-31 (1972, 52, 7) -1973-01-01 (1973, 1, 1) -1973-01-02 (1973, 1, 2) -1973-12-29 (1973, 52, 6) -1973-12-30 (1973, 52, 7) -1973-12-31 (1974, 1, 1) -1974-01-01 (1974, 1, 2) -1998-12-30 (1998, 53, 3) -1998-12-31 (1998, 53, 4) -1999-01-01 (1998, 53, 5) -1999-01-02 (1998, 53, 6) -1999-01-03 (1998, 53, 7) -1999-01-04 (1999, 1, 1) -1999-01-05 (1999, 1, 2) -1999-12-30 (1999, 52, 4) -1999-12-31 (1999, 52, 5) -2000-01-01 (1999, 52, 6) -2000-01-02 (1999, 52, 7) -2000-01-03 (2000, 1, 1) -2000-01-04 (2000, 1, 2) -2000-01-05 (2000, 1, 3) -2000-01-06 (2000, 1, 4) -2000-01-07 (2000, 1, 5) -2000-01-08 (2000, 1, 6) -2000-01-09 (2000, 1, 7) -2000-01-10 (2000, 2, 1) -2019-12-28 (2019, 52, 6) -2019-12-29 (2019, 52, 7) -2019-12-30 (2020, 1, 1) -2019-12-31 (2020, 1, 2) diff --git a/lib/DateTime/tests/test_datetime.py b/lib/DateTime/tests/test_datetime.py deleted file mode 100644 index 7172adb..0000000 --- a/lib/DateTime/tests/test_datetime.py +++ /dev/null @@ -1,686 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## - -from datetime import date, datetime, tzinfo, timedelta -import math -import platform -import os -import sys -import time -import unittest - -import pytz - -from DateTime.DateTime import _findLocalTimeZoneName -from DateTime import DateTime - -if sys.version_info > (3, ): - import pickle - unicode = str - PY3K = True -else: - import cPickle as pickle - PY3K = False - -try: - __file__ -except NameError: - f = sys.argv[0] -else: - f = __file__ - -IS_PYPY = getattr(platform, 'python_implementation', lambda: None)() == 'PyPy' - -DATADIR = os.path.dirname(os.path.abspath(f)) -del f - -ZERO = timedelta(0) - - -class FixedOffset(tzinfo): - """Fixed offset in minutes east from UTC.""" - - def __init__(self, offset, name): - self.__offset = timedelta(minutes=offset) - self.__name = name - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return ZERO - - -class DateTimeTests(unittest.TestCase): - - def _compare(self, dt1, dt2): - '''Compares the internal representation of dt1 with - the representation in dt2. Allows sub-millisecond variations. - Primarily for testing.''' - self.assertEqual(round(dt1._t, 3), round(dt2._t, 3)) - self.assertEqual(round(dt1._d, 9), round(dt2._d, 9)) - self.assertEqual(round(dt1.time, 9), round(dt2.time, 9)) - self.assertEqual(dt1.millis(), dt2.millis()) - self.assertEqual(dt1._micros, dt2._micros) - - def testBug1203(self): - # 01:59:60 occurred in old DateTime - dt = DateTime(7200, 'GMT') - self.assertTrue(str(dt).find('60') < 0, dt) - - def testDSTInEffect(self): - # Checks GMT offset for a DST date in the US/Eastern time zone - dt = DateTime(2000, 5, 9, 15, 0, 0, 'US/Eastern') - self.assertEqual(dt.toZone('GMT').hour(), 19, - (dt, dt.toZone('GMT'))) - - def testDSTNotInEffect(self): - # Checks GMT offset for a non-DST date in the US/Eastern time zone - dt = DateTime(2000, 11, 9, 15, 0, 0, 'US/Eastern') - self.assertEqual(dt.toZone('GMT').hour(), 20, - (dt, dt.toZone('GMT'))) - - def testAddPrecision(self): - # Precision of serial additions - dt = DateTime() - self.assertEqual(str(dt + 0.10 + 3.14 + 6.76 - 10), str(dt), - dt) - - def testConstructor3(self): - # Constructor from date/time string - dt = DateTime() - dt1s = '%d/%d/%d %d:%d:%f %s' % ( - dt.year(), - dt.month(), - dt.day(), - dt.hour(), - dt.minute(), - dt.second(), - dt.timezone()) - dt1 = DateTime(dt1s) - # Compare representations as it's the - # only way to compare the dates to the same accuracy - self.assertEqual(repr(dt), repr(dt1)) - - def testConstructor4(self): - # Constructor from time float - dt = DateTime() - dt1 = DateTime(float(dt)) - self._compare(dt, dt1) - - def testConstructor5(self): - # Constructor from time float and timezone - dt = DateTime() - dt1 = DateTime(float(dt), dt.timezone()) - self.assertEqual(str(dt), str(dt1), (dt, dt1)) - dt1 = DateTime(float(dt), unicode(dt.timezone())) - self.assertEqual(str(dt), str(dt1), (dt, dt1)) - - def testConstructor6(self): - # Constructor from year and julian date - # This test must normalize the time zone, or it *will* break when - # DST changes! - dt1 = DateTime(2000, 5.500000578705) - dt = DateTime('2000/1/5 12:00:00.050 pm %s' % dt1.localZone()) - self._compare(dt, dt1) - - def testConstructor7(self): - # Constructor from parts - dt = DateTime() - dt1 = DateTime( - dt.year(), - dt.month(), - dt.day(), - dt.hour(), - dt.minute(), - dt.second(), - dt.timezone()) - # Compare representations as it's the - # only way to compare the dates to the same accuracy - self.assertEqual(repr(dt), repr(dt1)) - - def testDayOfWeek(self): - # Compare to the datetime.date value to make it locale independent - expected = date(2000, 6, 16).strftime('%A') - # strftime() used to always be passed a day of week of 0 - dt = DateTime('2000/6/16') - s = dt.strftime('%A') - self.assertEqual(s, expected, (dt, s)) - - def testOldDate(self): - # Fails when an 1800 date is displayed with negative signs - dt = DateTime('1830/5/6 12:31:46.213 pm') - dt1 = dt.toZone('GMT+6') - self.assertTrue(str(dt1).find('-') < 0, (dt, dt1)) - - def testSubtraction(self): - # Reconstruction of a DateTime from its parts, with subtraction - # this also tests the accuracy of addition and reconstruction - dt = DateTime() - dt1 = dt - 3.141592653 - dt2 = DateTime( - dt.year(), - dt.month(), - dt.day(), - dt.hour(), - dt.minute(), - dt.second()) - dt3 = dt2 - 3.141592653 - self.assertEqual(dt1, dt3, (dt, dt1, dt2, dt3)) - - def testTZ1add(self): - # Time zone manipulation: add to a date - dt = DateTime('1997/3/8 1:45am GMT-4') - dt1 = DateTime('1997/3/9 1:45pm GMT+8') - self.assertTrue((dt + 1.0).equalTo(dt1)) - - def testTZ1sub(self): - # Time zone manipulation: subtract from a date - dt = DateTime('1997/3/8 1:45am GMT-4') - dt1 = DateTime('1997/3/9 1:45pm GMT+8') - self.assertTrue((dt1 - 1.0).equalTo(dt)) - - def testTZ1diff(self): - # Time zone manipulation: diff two dates - dt = DateTime('1997/3/8 1:45am GMT-4') - dt1 = DateTime('1997/3/9 1:45pm GMT+8') - self.assertEqual(dt1 - dt, 1.0, (dt, dt1)) - - def test_compare_methods(self): - # Compare two dates using several methods - dt = DateTime('1997/1/1') - dt1 = DateTime('1997/2/2') - self.assertTrue(dt1.greaterThan(dt)) - self.assertTrue(dt1.greaterThanEqualTo(dt)) - self.assertTrue(dt.lessThan(dt1)) - self.assertTrue(dt.lessThanEqualTo(dt1)) - self.assertTrue(dt.notEqualTo(dt1)) - self.assertFalse(dt.equalTo(dt1)) - - def test_compare_methods_none(self): - # Compare a date to None - dt = DateTime('1997/1/1') - self.assertTrue(dt.greaterThan(None)) - self.assertTrue(dt.greaterThanEqualTo(None)) - self.assertFalse(dt.lessThan(None)) - self.assertFalse(dt.lessThanEqualTo(None)) - self.assertTrue(dt.notEqualTo(None)) - self.assertFalse(dt.equalTo(None)) - - def test_pickle(self): - dt = DateTime() - data = pickle.dumps(dt, 1) - new = pickle.loads(data) - for key in DateTime.__slots__: - self.assertEqual(getattr(dt, key), getattr(new, key)) - - def test_pickle_with_tz(self): - dt = DateTime('2002/5/2 8:00am GMT+8') - data = pickle.dumps(dt, 1) - new = pickle.loads(data) - for key in DateTime.__slots__: - self.assertEqual(getattr(dt, key), getattr(new, key)) - - def test_pickle_with_numerical_tz(self): - for dt_str in ('2007/01/02 12:34:56.789 +0300', - '2007/01/02 12:34:56.789 +0430', - '2007/01/02 12:34:56.789 -1234'): - dt = DateTime(dt_str) - data = pickle.dumps(dt, 1) - new = pickle.loads(data) - for key in DateTime.__slots__: - self.assertEqual(getattr(dt, key), getattr(new, key)) - - def test_pickle_with_micros(self): - dt = DateTime('2002/5/2 8:00:14.123 GMT+8') - data = pickle.dumps(dt, 1) - new = pickle.loads(data) - for key in DateTime.__slots__: - self.assertEqual(getattr(dt, key), getattr(new, key)) - - def test_pickle_old(self): - dt = DateTime('2002/5/2 8:00am GMT+0') - data = ('(cDateTime.DateTime\nDateTime\nq\x01Noq\x02}q\x03(U\x05' - '_amonq\x04U\x03Mayq\x05U\x05_adayq\x06U\x03Thuq\x07U\x05_pmonq' - '\x08h\x05U\x05_hourq\tK\x08U\x05_fmonq\nh\x05U\x05_pdayq\x0bU' - '\x04Thu.q\x0cU\x05_fdayq\rU\x08Thursdayq\x0eU\x03_pmq\x0fU\x02amq' - '\x10U\x02_tq\x11GA\xcehy\x00\x00\x00\x00U\x07_minuteq\x12K\x00U' - '\x07_microsq\x13L1020326400000000L\nU\x02_dq\x14G@\xe2\x12j\xaa' - '\xaa\xaa\xabU\x07_secondq\x15G\x00\x00\x00\x00\x00\x00\x00\x00U' - '\x03_tzq\x16U\x05GMT+0q\x17U\x06_monthq\x18K\x05U' - '\x0f_timezone_naiveq\x19I00\nU\x04_dayq\x1aK\x02U\x05_yearq' - '\x1bM\xd2\x07U\x08_nearsecq\x1cG\x00\x00\x00\x00\x00\x00\x00' - '\x00U\x07_pmhourq\x1dK\x08U\n_dayoffsetq\x1eK\x04U\x04timeq' - '\x1fG?\xd5UUUV\x00\x00ub.') - if PY3K: - data = data.encode('latin-1') - new = pickle.loads(data) - for key in DateTime.__slots__: - self.assertEqual(getattr(dt, key), getattr(new, key)) - - def test_pickle_old_without_micros(self): - dt = DateTime('2002/5/2 8:00am GMT+0') - data = ('(cDateTime.DateTime\nDateTime\nq\x01Noq\x02}q\x03(U\x05' - '_amonq\x04U\x03Mayq\x05U\x05_adayq\x06U\x03Thuq\x07U\x05_pmonq' - '\x08h\x05U\x05_hourq\tK\x08U\x05_fmonq\nh\x05U\x05_pdayq\x0bU' - '\x04Thu.q\x0cU\x05_fdayq\rU\x08Thursdayq\x0eU\x03_pmq\x0fU' - '\x02amq\x10U\x02_tq\x11GA\xcehy\x00\x00\x00\x00U\x07_minuteq' - '\x12K\x00U\x02_dq\x13G@\xe2\x12j\xaa\xaa\xaa\xabU\x07_secondq' - '\x14G\x00\x00\x00\x00\x00\x00\x00\x00U\x03_tzq\x15U\x05GMT+0q' - '\x16U\x06_monthq\x17K\x05U\x0f_timezone_naiveq\x18I00\nU' - '\x04_dayq\x19K\x02U\x05_yearq\x1aM\xd2\x07U\x08_nearsecq' - '\x1bG\x00\x00\x00\x00\x00\x00\x00\x00U\x07_pmhourq\x1cK\x08U' - '\n_dayoffsetq\x1dK\x04U\x04timeq\x1eG?\xd5UUUV\x00\x00ub.') - if PY3K: - data = data.encode('latin-1') - new = pickle.loads(data) - for key in DateTime.__slots__: - self.assertEqual(getattr(dt, key), getattr(new, key)) - - def testTZ2(self): - # Time zone manipulation test 2 - dt = DateTime() - dt1 = dt.toZone('GMT') - s = dt.second() - s1 = dt1.second() - self.assertEqual(s, s1, (dt, dt1, s, s1)) - - def testTZDiffDaylight(self): - # Diff dates across daylight savings dates - dt = DateTime('2000/6/8 1:45am US/Eastern') - dt1 = DateTime('2000/12/8 12:45am US/Eastern') - self.assertEqual(dt1 - dt, 183, (dt, dt1, dt1 - dt)) - - def testY10KDate(self): - # Comparison of a Y10K date and a Y2K date - dt = DateTime('10213/09/21') - dt1 = DateTime(2000, 1, 1) - - dsec = (dt.millis() - dt1.millis()) / 1000.0 - ddays = math.floor((dsec / 86400.0) + 0.5) - - self.assertEqual(ddays, 3000000, ddays) - - def test_tzoffset(self): - # Test time-zone given as an offset - - # GMT - dt = DateTime('Tue, 10 Sep 2001 09:41:03 GMT') - self.assertEqual(dt.tzoffset(), 0) - - # Timezone by name, a timezone that hasn't got daylightsaving. - dt = DateTime('Tue, 2 Mar 2001 09:41:03 GMT+3') - self.assertEqual(dt.tzoffset(), 10800) - - # Timezone by name, has daylightsaving but is not in effect. - dt = DateTime('Tue, 21 Jan 2001 09:41:03 PST') - self.assertEqual(dt.tzoffset(), -28800) - - # Timezone by name, with daylightsaving in effect - dt = DateTime('Tue, 24 Aug 2001 09:41:03 PST') - self.assertEqual(dt.tzoffset(), -25200) - - # A negative numerical timezone - dt = DateTime('Tue, 24 Jul 2001 09:41:03 -0400') - self.assertEqual(dt.tzoffset(), -14400) - - # A positive numerical timzone - dt = DateTime('Tue, 6 Dec 1966 01:41:03 +0200') - self.assertEqual(dt.tzoffset(), 7200) - - # A negative numerical timezone with minutes. - dt = DateTime('Tue, 24 Jul 2001 09:41:03 -0637') - self.assertEqual(dt.tzoffset(), -23820) - - # A positive numerical timezone with minutes. - dt = DateTime('Tue, 24 Jul 2001 09:41:03 +0425') - self.assertEqual(dt.tzoffset(), 15900) - - def testISO8601(self): - # ISO8601 reference dates - ref0 = DateTime('2002/5/2 8:00am GMT') - ref1 = DateTime('2002/5/2 8:00am US/Eastern') - ref2 = DateTime('2006/11/6 10:30 GMT') - ref3 = DateTime('2004/06/14 14:30:15 GMT-3') - ref4 = DateTime('2006/01/01 GMT') - - # Basic tests - # Though this is timezone naive and according to specification should - # be interpreted in the local timezone, to preserve backwards - # compatibility with previously expected behaviour. - isoDt = DateTime('2002-05-02T08:00:00') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2002-05-02T08:00:00Z') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2002-05-02T08:00:00+00:00') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2002-05-02T08:00:00-04:00') - self.assertTrue(ref1.equalTo(isoDt)) - isoDt = DateTime('2002-05-02 08:00:00-04:00') - self.assertTrue(ref1.equalTo(isoDt)) - - # Bug 1386: the colon in the timezone offset is optional - isoDt = DateTime('2002-05-02T08:00:00-0400') - self.assertTrue(ref1.equalTo(isoDt)) - - # Bug 2191: date reduced formats - isoDt = DateTime('2006-01-01') - self.assertTrue(ref4.equalTo(isoDt)) - isoDt = DateTime('200601-01') - self.assertTrue(ref4.equalTo(isoDt)) - isoDt = DateTime('20060101') - self.assertTrue(ref4.equalTo(isoDt)) - isoDt = DateTime('2006-01') - self.assertTrue(ref4.equalTo(isoDt)) - isoDt = DateTime('200601') - self.assertTrue(ref4.equalTo(isoDt)) - isoDt = DateTime('2006') - self.assertTrue(ref4.equalTo(isoDt)) - - # Bug 2191: date/time separators are also optional - isoDt = DateTime('20020502T08:00:00') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2002-05-02T080000') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('20020502T080000') - self.assertTrue(ref0.equalTo(isoDt)) - - # Bug 2191: timezones with only one digit for hour - isoDt = DateTime('20020502T080000+0') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('20020502 080000-4') - self.assertTrue(ref1.equalTo(isoDt)) - isoDt = DateTime('20020502T080000-400') - self.assertTrue(ref1.equalTo(isoDt)) - isoDt = DateTime('20020502T080000-4:00') - self.assertTrue(ref1.equalTo(isoDt)) - - # Bug 2191: optional seconds/minutes - isoDt = DateTime('2002-05-02T0800') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2002-05-02T08') - self.assertTrue(ref0.equalTo(isoDt)) - - # Bug 2191: week format - isoDt = DateTime('2002-W18-4T0800') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2002-W184T0800') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2002W18-4T0800') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2002W184T08') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2004-W25-1T14:30:15-03:00') - self.assertTrue(ref3.equalTo(isoDt)) - isoDt = DateTime('2004-W25T14:30:15-03:00') - self.assertTrue(ref3.equalTo(isoDt)) - - # Bug 2191: day of year format - isoDt = DateTime('2002-122T0800') - self.assertTrue(ref0.equalTo(isoDt)) - isoDt = DateTime('2002122T0800') - self.assertTrue(ref0.equalTo(isoDt)) - - # Bug 2191: hours/minutes fractions - isoDt = DateTime('2006-11-06T10.5') - self.assertTrue(ref2.equalTo(isoDt)) - isoDt = DateTime('2006-11-06T10,5') - self.assertTrue(ref2.equalTo(isoDt)) - isoDt = DateTime('20040614T1430.25-3') - self.assertTrue(ref3.equalTo(isoDt)) - isoDt = DateTime('2004-06-14T1430,25-3') - self.assertTrue(ref3.equalTo(isoDt)) - isoDt = DateTime('2004-06-14T14:30.25-3') - self.assertTrue(ref3.equalTo(isoDt)) - isoDt = DateTime('20040614T14:30,25-3') - self.assertTrue(ref3.equalTo(isoDt)) - - # ISO8601 standard format - iso8601_string = '2002-05-02T08:00:00-04:00' - iso8601DT = DateTime(iso8601_string) - self.assertEqual(iso8601_string, iso8601DT.ISO8601()) - - # ISO format with no timezone - isoDt = DateTime('2006-01-01 00:00:00') - self.assertTrue(ref4.equalTo(isoDt)) - - def testJulianWeek(self): - # Check JulianDayWeek function - fn = os.path.join(DATADIR, 'julian_testdata.txt') - with open(fn, 'r') as fd: - lines = fd.readlines() - for line in lines: - d = DateTime(line[:10]) - result_from_mx = tuple(map(int, line[12:-2].split(','))) - self.assertEqual(result_from_mx[1], d.week()) - - def testCopyConstructor(self): - d = DateTime('2004/04/04') - self.assertEqual(DateTime(d), d) - self.assertEqual(str(DateTime(d)), str(d)) - d2 = DateTime('1999/04/12 01:00:00') - self.assertEqual(DateTime(d2), d2) - self.assertEqual(str(DateTime(d2)), str(d2)) - - def testCopyConstructorPreservesTimezone(self): - # test for https://bugs.launchpad.net/zope2/+bug/200007 - # This always worked in the local timezone, so we need at least - # two tests with different zones to be sure at least one of them - # is not local. - d = DateTime('2004/04/04') - self.assertEqual(DateTime(d).timezone(), d.timezone()) - d2 = DateTime('2008/04/25 12:00:00 EST') - self.assertEqual(DateTime(d2).timezone(), d2.timezone()) - self.assertEqual(str(DateTime(d2)), str(d2)) - d3 = DateTime('2008/04/25 12:00:00 PST') - self.assertEqual(DateTime(d3).timezone(), d3.timezone()) - self.assertEqual(str(DateTime(d3)), str(d3)) - - def testRFC822(self): - # rfc822 conversion - dt = DateTime('2002-05-02T08:00:00+00:00') - self.assertEqual(dt.rfc822(), 'Thu, 02 May 2002 08:00:00 +0000') - - dt = DateTime('2002-05-02T08:00:00+02:00') - self.assertEqual(dt.rfc822(), 'Thu, 02 May 2002 08:00:00 +0200') - - dt = DateTime('2002-05-02T08:00:00-02:00') - self.assertEqual(dt.rfc822(), 'Thu, 02 May 2002 08:00:00 -0200') - - # Checking that conversion from local time is working. - dt = DateTime() - dts = dt.rfc822().split(' ') - times = dts[4].split(':') - _isDST = time.localtime(time.time())[8] - if _isDST: - offset = time.altzone - else: - offset = time.timezone - self.assertEqual(dts[0], dt.aDay() + ',') - self.assertEqual(int(dts[1]), dt.day()) - self.assertEqual(dts[2], dt.aMonth()) - self.assertEqual(int(dts[3]), dt.year()) - self.assertEqual(int(times[0]), dt.h_24()) - self.assertEqual(int(times[1]), dt.minute()) - self.assertEqual(int(times[2]), int(dt.second())) - self.assertEqual(dts[5], "%+03d%02d" % divmod((-offset / 60), 60)) - - def testInternationalDateformat(self): - for year in (1990, 2001, 2020): - for month in (1, 12): - for day in (1, 12, 28, 31): - try: - d_us = DateTime("%d/%d/%d" % (year, month, day)) - except Exception: - continue - - d_int = DateTime("%d.%d.%d" % (day, month, year), - datefmt="international") - self.assertEqual(d_us, d_int) - - d_int = DateTime("%d/%d/%d" % (day, month, year), - datefmt="international") - self.assertEqual(d_us, d_int) - - def test_intl_format_hyphen(self): - d_jan = DateTime('2011-01-11 GMT') - d_nov = DateTime('2011-11-01 GMT') - d_us = DateTime('11-01-2011 GMT') - d_int = DateTime('11-01-2011 GMT', datefmt="international") - self.assertNotEqual(d_us, d_int) - self.assertEqual(d_us, d_nov) - self.assertEqual(d_int, d_jan) - - def test_calcTimezoneName(self): - from DateTime.interfaces import TimeError - timezone_dependent_epoch = 2177452800 - try: - DateTime()._calcTimezoneName(timezone_dependent_epoch, 0) - except TimeError: - self.fail('Zope Collector issue #484 (negative time bug): ' - 'TimeError raised') - - def testStrftimeTZhandling(self): - # strftime timezone testing - # This is a test for collector issue #1127 - format = '%Y-%m-%d %H:%M %Z' - dt = DateTime('Wed, 19 Nov 2003 18:32:07 -0215') - dt_string = dt.strftime(format) - dt_local = dt.toZone(_findLocalTimeZoneName(0)) - dt_localstring = dt_local.strftime(format) - self.assertEqual(dt_string, dt_localstring) - - def testStrftimeFarDates(self): - # Checks strftime in dates <= 1900 or >= 2038 - dt = DateTime('1900/01/30') - self.assertEqual(dt.strftime('%d/%m/%Y'), '30/01/1900') - dt = DateTime('2040/01/30') - self.assertEqual(dt.strftime('%d/%m/%Y'), '30/01/2040') - - def testZoneInFarDates(self): - # Checks time zone in dates <= 1900 or >= 2038 - dt1 = DateTime('2040/01/30 14:33 GMT+1') - dt2 = DateTime('2040/01/30 11:33 GMT-2') - self.assertEqual(dt1.strftime('%d/%m/%Y %H:%M'), - dt2.strftime('%d/%m/%Y %H:%M')) - - def testStrftimeUnicode(self): - if IS_PYPY: - # Using Non-Ascii characters for strftime doesn't work in PyPy - # https://bitbucket.org/pypy/pypy/issues/2161/pypy3-strftime-does-not-accept-unicode - return - dt = DateTime('2002-05-02T08:00:00+00:00') - uchar = b'\xc3\xa0'.decode('utf-8') - ok = dt.strftime('Le %d/%m/%Y a %Hh%M').replace('a', uchar) - ustr = b'Le %d/%m/%Y \xc3\xa0 %Hh%M'.decode('utf-8') - self.assertEqual(dt.strftime(ustr), ok) - - def testTimezoneNaiveHandling(self): - # checks that we assign timezone naivity correctly - dt = DateTime('2007-10-04T08:00:00+00:00') - self.assertFalse(dt.timezoneNaive(), - 'error with naivity handling in __parse_iso8601') - dt = DateTime('2007-10-04T08:00:00Z') - self.assertFalse(dt.timezoneNaive(), - 'error with naivity handling in __parse_iso8601') - dt = DateTime('2007-10-04T08:00:00') - self.assertTrue(dt.timezoneNaive(), - 'error with naivity handling in __parse_iso8601') - dt = DateTime('2007/10/04 15:12:33.487618 GMT+1') - self.assertFalse(dt.timezoneNaive(), - 'error with naivity handling in _parse') - dt = DateTime('2007/10/04 15:12:33.487618') - self.assertTrue(dt.timezoneNaive(), - 'error with naivity handling in _parse') - dt = DateTime() - self.assertFalse(dt.timezoneNaive(), - 'error with naivity for current time') - s = '2007-10-04T08:00:00' - dt = DateTime(s) - self.assertEqual(s, dt.ISO8601()) - s = '2007-10-04T08:00:00+00:00' - dt = DateTime(s) - self.assertEqual(s, dt.ISO8601()) - - def testConversions(self): - sdt0 = datetime.now() # this is a timezone naive datetime - dt0 = DateTime(sdt0) - self.assertTrue(dt0.timezoneNaive(), (sdt0, dt0)) - sdt1 = datetime(2007, 10, 4, 18, 14, 42, 580, pytz.utc) - dt1 = DateTime(sdt1) - self.assertFalse(dt1.timezoneNaive(), (sdt1, dt1)) - - # convert back - sdt2 = dt0.asdatetime() - self.assertEqual(sdt0, sdt2) - sdt3 = dt1.utcdatetime() # this returns a timezone naive datetime - self.assertEqual(sdt1.hour, sdt3.hour) - - dt4 = DateTime('2007-10-04T10:00:00+05:00') - sdt4 = datetime(2007, 10, 4, 5, 0) - self.assertEqual(dt4.utcdatetime(), sdt4) - self.assertEqual(dt4.asdatetime(), sdt4.replace(tzinfo=pytz.utc)) - - dt5 = DateTime('2007-10-23 10:00:00 US/Eastern') - tz = pytz.timezone('US/Eastern') - sdt5 = datetime(2007, 10, 23, 10, 0, tzinfo=tz) - dt6 = DateTime(sdt5) - self.assertEqual(dt5.asdatetime(), sdt5) - self.assertEqual(dt6.asdatetime(), sdt5) - self.assertEqual(dt5, dt6) - self.assertEqual(dt5.asdatetime().tzinfo, tz) - self.assertEqual(dt6.asdatetime().tzinfo, tz) - - def testBasicTZ(self): - # psycopg2 supplies it's own tzinfo instances, with no `zone` attribute - tz = FixedOffset(60, 'GMT+1') - dt1 = datetime(2008, 8, 5, 12, 0, tzinfo=tz) - DT = DateTime(dt1) - dt2 = DT.asdatetime() - offset1 = dt1.tzinfo.utcoffset(dt1) - offset2 = dt2.tzinfo.utcoffset(dt2) - self.assertEqual(offset1, offset2) - - def testEDTTimezone(self): - # should be able to parse EDT timezones: see lp:599856. - dt = DateTime("Mon, 28 Jun 2010 10:12:25 EDT") - self.assertEqual(dt.Day(), 'Monday') - self.assertEqual(dt.day(), 28) - self.assertEqual(dt.Month(), 'June') - self.assertEqual(dt.timezone(), 'GMT-4') - - def testParseISO8601(self): - parsed = DateTime()._parse_iso8601('2010-10-10') - self.assertEqual(parsed, (2010, 10, 10, 0, 0, 0, 'GMT+0000')) - - def test_interface(self): - from DateTime.interfaces import IDateTime - self.assertTrue(IDateTime.providedBy(DateTime())) - - def test_security(self): - dt = DateTime() - self.assertEqual(dt.__roles__, None) - self.assertEqual(dt.__allow_access_to_unprotected_subobjects__, 1) - - -def test_suite(): - import doctest - return unittest.TestSuite([ - unittest.makeSuite(DateTimeTests), - doctest.DocFileSuite('DateTime.txt', package='DateTime'), - doctest.DocFileSuite('pytz.txt', package='DateTime'), - ]) diff --git a/lib/backports/configparser/__init__.py b/lib/backports/configparser/__init__.py deleted file mode 100644 index 06d7a08..0000000 --- a/lib/backports/configparser/__init__.py +++ /dev/null @@ -1,1390 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Configuration file parser. - -A configuration file consists of sections, lead by a "[section]" header, -and followed by "name: value" entries, with continuations and such in -the style of RFC 822. - -Intrinsic defaults can be specified by passing them into the -ConfigParser constructor as a dictionary. - -class: - -ConfigParser -- responsible for parsing a list of - configuration files, and managing the parsed database. - - methods: - - __init__(defaults=None, dict_type=_default_dict, allow_no_value=False, - delimiters=('=', ':'), comment_prefixes=('#', ';'), - inline_comment_prefixes=None, strict=True, - empty_lines_in_values=True, default_section='DEFAULT', - interpolation=<unset>, converters=<unset>): - Create the parser. When `defaults' is given, it is initialized into the - dictionary or intrinsic defaults. The keys must be strings, the values - must be appropriate for %()s string interpolation. - - When `dict_type' is given, it will be used to create the dictionary - objects for the list of sections, for the options within a section, and - for the default values. - - When `delimiters' is given, it will be used as the set of substrings - that divide keys from values. - - When `comment_prefixes' is given, it will be used as the set of - substrings that prefix comments in empty lines. Comments can be - indented. - - When `inline_comment_prefixes' is given, it will be used as the set of - substrings that prefix comments in non-empty lines. - - When `strict` is True, the parser won't allow for any section or option - duplicates while reading from a single source (file, string or - dictionary). Default is True. - - When `empty_lines_in_values' is False (default: True), each empty line - marks the end of an option. Otherwise, internal empty lines of - a multiline option are kept as part of the value. - - When `allow_no_value' is True (default: False), options without - values are accepted; the value presented for these is None. - - sections() - Return all the configuration section names, sans DEFAULT. - - has_section(section) - Return whether the given section exists. - - has_option(section, option) - Return whether the given option exists in the given section. - - options(section) - Return list of configuration options for the named section. - - read(filenames, encoding=None) - Read and parse the list of named configuration files, given by - name. A single filename is also allowed. Non-existing files - are ignored. Return list of successfully read files. - - read_file(f, filename=None) - Read and parse one configuration file, given as a file object. - The filename defaults to f.name; it is only used in error - messages (if f has no `name' attribute, the string `<???>' is used). - - read_string(string) - Read configuration from a given string. - - read_dict(dictionary) - Read configuration from a dictionary. Keys are section names, - values are dictionaries with keys and values that should be present - in the section. If the used dictionary type preserves order, sections - and their keys will be added in order. Values are automatically - converted to strings. - - get(section, option, raw=False, vars=None, fallback=_UNSET) - Return a string value for the named option. All % interpolations are - expanded in the return values, based on the defaults passed into the - constructor and the DEFAULT section. Additional substitutions may be - provided using the `vars' argument, which must be a dictionary whose - contents override any pre-existing defaults. If `option' is a key in - `vars', the value from `vars' is used. - - getint(section, options, raw=False, vars=None, fallback=_UNSET) - Like get(), but convert value to an integer. - - getfloat(section, options, raw=False, vars=None, fallback=_UNSET) - Like get(), but convert value to a float. - - getboolean(section, options, raw=False, vars=None, fallback=_UNSET) - Like get(), but convert value to a boolean (currently case - insensitively defined as 0, false, no, off for False, and 1, true, - yes, on for True). Returns False or True. - - items(section=_UNSET, raw=False, vars=None) - If section is given, return a list of tuples with (name, value) for - each option in the section. Otherwise, return a list of tuples with - (section_name, section_proxy) for each section, including DEFAULTSECT. - - remove_section(section) - Remove the given file section and all its options. - - remove_option(section, option) - Remove the given option from the given section. - - set(section, option, value) - Set the given option. - - write(fp, space_around_delimiters=True) - Write the configuration state in .ini format. If - `space_around_delimiters' is True (the default), delimiters - between keys and values are surrounded by spaces. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from collections import MutableMapping -import functools -import io -import itertools -import re -import sys -import warnings - -from backports.configparser.helpers import OrderedDict as _default_dict -from backports.configparser.helpers import ChainMap as _ChainMap -from backports.configparser.helpers import from_none, open, str, PY2 - -__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", - "NoOptionError", "InterpolationError", "InterpolationDepthError", - "InterpolationMissingOptionError", "InterpolationSyntaxError", - "ParsingError", "MissingSectionHeaderError", - "ConfigParser", "SafeConfigParser", "RawConfigParser", - "Interpolation", "BasicInterpolation", "ExtendedInterpolation", - "LegacyInterpolation", "SectionProxy", "ConverterMapping", - "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] - -DEFAULTSECT = "DEFAULT" - -MAX_INTERPOLATION_DEPTH = 10 - - -# exception classes -class Error(Exception): - """Base class for ConfigParser exceptions.""" - - def __init__(self, msg=''): - self.message = msg - Exception.__init__(self, msg) - - def __repr__(self): - return self.message - - __str__ = __repr__ - - -class NoSectionError(Error): - """Raised when no section matches a requested option.""" - - def __init__(self, section): - Error.__init__(self, 'No section: %r' % (section,)) - self.section = section - self.args = (section, ) - - -class DuplicateSectionError(Error): - """Raised when a section is repeated in an input source. - - Possible repetitions that raise this exception are: multiple creation - using the API or in strict parsers when a section is found more than once - in a single input file, string or dictionary. - """ - - def __init__(self, section, source=None, lineno=None): - msg = [repr(section), " already exists"] - if source is not None: - message = ["While reading from ", repr(source)] - if lineno is not None: - message.append(" [line {0:2d}]".format(lineno)) - message.append(": section ") - message.extend(msg) - msg = message - else: - msg.insert(0, "Section ") - Error.__init__(self, "".join(msg)) - self.section = section - self.source = source - self.lineno = lineno - self.args = (section, source, lineno) - - -class DuplicateOptionError(Error): - """Raised by strict parsers when an option is repeated in an input source. - - Current implementation raises this exception only when an option is found - more than once in a single file, string or dictionary. - """ - - def __init__(self, section, option, source=None, lineno=None): - msg = [repr(option), " in section ", repr(section), - " already exists"] - if source is not None: - message = ["While reading from ", repr(source)] - if lineno is not None: - message.append(" [line {0:2d}]".format(lineno)) - message.append(": option ") - message.extend(msg) - msg = message - else: - msg.insert(0, "Option ") - Error.__init__(self, "".join(msg)) - self.section = section - self.option = option - self.source = source - self.lineno = lineno - self.args = (section, option, source, lineno) - - -class NoOptionError(Error): - """A requested option was not found.""" - - def __init__(self, option, section): - Error.__init__(self, "No option %r in section: %r" % - (option, section)) - self.option = option - self.section = section - self.args = (option, section) - - -class InterpolationError(Error): - """Base class for interpolation-related exceptions.""" - - def __init__(self, option, section, msg): - Error.__init__(self, msg) - self.option = option - self.section = section - self.args = (option, section, msg) - - -class InterpolationMissingOptionError(InterpolationError): - """A string substitution required a setting which was not available.""" - - def __init__(self, option, section, rawval, reference): - msg = ("Bad value substitution: option {0!r} in section {1!r} contains " - "an interpolation key {2!r} which is not a valid option name. " - "Raw value: {3!r}".format(option, section, reference, rawval)) - InterpolationError.__init__(self, option, section, msg) - self.reference = reference - self.args = (option, section, rawval, reference) - - -class InterpolationSyntaxError(InterpolationError): - """Raised when the source text contains invalid syntax. - - Current implementation raises this exception when the source text into - which substitutions are made does not conform to the required syntax. - """ - - -class InterpolationDepthError(InterpolationError): - """Raised when substitutions are nested too deeply.""" - - def __init__(self, option, section, rawval): - msg = ("Recursion limit exceeded in value substitution: option {0!r} " - "in section {1!r} contains an interpolation key which " - "cannot be substituted in {2} steps. Raw value: {3!r}" - "".format(option, section, MAX_INTERPOLATION_DEPTH, - rawval)) - InterpolationError.__init__(self, option, section, msg) - self.args = (option, section, rawval) - - -class ParsingError(Error): - """Raised when a configuration file does not follow legal syntax.""" - - def __init__(self, source=None, filename=None): - # Exactly one of `source'/`filename' arguments has to be given. - # `filename' kept for compatibility. - if filename and source: - raise ValueError("Cannot specify both `filename' and `source'. " - "Use `source'.") - elif not filename and not source: - raise ValueError("Required argument `source' not given.") - elif filename: - source = filename - Error.__init__(self, 'Source contains parsing errors: %r' % source) - self.source = source - self.errors = [] - self.args = (source, ) - - @property - def filename(self): - """Deprecated, use `source'.""" - warnings.warn( - "The 'filename' attribute will be removed in future versions. " - "Use 'source' instead.", - DeprecationWarning, stacklevel=2 - ) - return self.source - - @filename.setter - def filename(self, value): - """Deprecated, user `source'.""" - warnings.warn( - "The 'filename' attribute will be removed in future versions. " - "Use 'source' instead.", - DeprecationWarning, stacklevel=2 - ) - self.source = value - - def append(self, lineno, line): - self.errors.append((lineno, line)) - self.message += '\n\t[line %2d]: %s' % (lineno, line) - - -class MissingSectionHeaderError(ParsingError): - """Raised when a key-value pair is found before any section header.""" - - def __init__(self, filename, lineno, line): - Error.__init__( - self, - 'File contains no section headers.\nfile: %r, line: %d\n%r' % - (filename, lineno, line)) - self.source = filename - self.lineno = lineno - self.line = line - self.args = (filename, lineno, line) - - -# Used in parser getters to indicate the default behaviour when a specific -# option is not found it to raise an exception. Created to enable `None' as -# a valid fallback value. -_UNSET = object() - - -class Interpolation(object): - """Dummy interpolation that passes the value through with no changes.""" - - def before_get(self, parser, section, option, value, defaults): - return value - - def before_set(self, parser, section, option, value): - return value - - def before_read(self, parser, section, option, value): - return value - - def before_write(self, parser, section, option, value): - return value - - -class BasicInterpolation(Interpolation): - """Interpolation as implemented in the classic ConfigParser. - - The option values can contain format strings which refer to other values in - the same section, or values in the special default section. - - For example: - - something: %(dir)s/whatever - - would resolve the "%(dir)s" to the value of dir. All reference - expansions are done late, on demand. If a user needs to use a bare % in - a configuration file, she can escape it by writing %%. Other % usage - is considered a user error and raises `InterpolationSyntaxError'.""" - - _KEYCRE = re.compile(r"%\(([^)]+)\)s") - - def before_get(self, parser, section, option, value, defaults): - L = [] - self._interpolate_some(parser, option, L, value, section, defaults, 1) - return ''.join(L) - - def before_set(self, parser, section, option, value): - tmp_value = value.replace('%%', '') # escaped percent signs - tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax - if '%' in tmp_value: - raise ValueError("invalid interpolation syntax in %r at " - "position %d" % (value, tmp_value.find('%'))) - return value - - def _interpolate_some(self, parser, option, accum, rest, section, map, - depth): - rawval = parser.get(section, option, raw=True, fallback=rest) - if depth > MAX_INTERPOLATION_DEPTH: - raise InterpolationDepthError(option, section, rawval) - while rest: - p = rest.find("%") - if p < 0: - accum.append(rest) - return - if p > 0: - accum.append(rest[:p]) - rest = rest[p:] - # p is no longer used - c = rest[1:2] - if c == "%": - accum.append("%") - rest = rest[2:] - elif c == "(": - m = self._KEYCRE.match(rest) - if m is None: - raise InterpolationSyntaxError(option, section, - "bad interpolation variable reference %r" % rest) - var = parser.optionxform(m.group(1)) - rest = rest[m.end():] - try: - v = map[var] - except KeyError: - raise from_none(InterpolationMissingOptionError( - option, section, rawval, var)) - if "%" in v: - self._interpolate_some(parser, option, accum, v, - section, map, depth + 1) - else: - accum.append(v) - else: - raise InterpolationSyntaxError( - option, section, - "'%%' must be followed by '%%' or '(', " - "found: %r" % (rest,)) - - -class ExtendedInterpolation(Interpolation): - """Advanced variant of interpolation, supports the syntax used by - `zc.buildout'. Enables interpolation between sections.""" - - _KEYCRE = re.compile(r"\$\{([^}]+)\}") - - def before_get(self, parser, section, option, value, defaults): - L = [] - self._interpolate_some(parser, option, L, value, section, defaults, 1) - return ''.join(L) - - def before_set(self, parser, section, option, value): - tmp_value = value.replace('$$', '') # escaped dollar signs - tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax - if '$' in tmp_value: - raise ValueError("invalid interpolation syntax in %r at " - "position %d" % (value, tmp_value.find('$'))) - return value - - def _interpolate_some(self, parser, option, accum, rest, section, map, - depth): - rawval = parser.get(section, option, raw=True, fallback=rest) - if depth > MAX_INTERPOLATION_DEPTH: - raise InterpolationDepthError(option, section, rawval) - while rest: - p = rest.find("$") - if p < 0: - accum.append(rest) - return - if p > 0: - accum.append(rest[:p]) - rest = rest[p:] - # p is no longer used - c = rest[1:2] - if c == "$": - accum.append("$") - rest = rest[2:] - elif c == "{": - m = self._KEYCRE.match(rest) - if m is None: - raise InterpolationSyntaxError(option, section, - "bad interpolation variable reference %r" % rest) - path = m.group(1).split(':') - rest = rest[m.end():] - sect = section - opt = option - try: - if len(path) == 1: - opt = parser.optionxform(path[0]) - v = map[opt] - elif len(path) == 2: - sect = path[0] - opt = parser.optionxform(path[1]) - v = parser.get(sect, opt, raw=True) - else: - raise InterpolationSyntaxError( - option, section, - "More than one ':' found: %r" % (rest,)) - except (KeyError, NoSectionError, NoOptionError): - raise from_none(InterpolationMissingOptionError( - option, section, rawval, ":".join(path))) - if "$" in v: - self._interpolate_some(parser, opt, accum, v, sect, - dict(parser.items(sect, raw=True)), - depth + 1) - else: - accum.append(v) - else: - raise InterpolationSyntaxError( - option, section, - "'$' must be followed by '$' or '{', " - "found: %r" % (rest,)) - - -class LegacyInterpolation(Interpolation): - """Deprecated interpolation used in old versions of ConfigParser. - Use BasicInterpolation or ExtendedInterpolation instead.""" - - _KEYCRE = re.compile(r"%\(([^)]*)\)s|.") - - def before_get(self, parser, section, option, value, vars): - rawval = value - depth = MAX_INTERPOLATION_DEPTH - while depth: # Loop through this until it's done - depth -= 1 - if value and "%(" in value: - replace = functools.partial(self._interpolation_replace, - parser=parser) - value = self._KEYCRE.sub(replace, value) - try: - value = value % vars - except KeyError as e: - raise from_none(InterpolationMissingOptionError( - option, section, rawval, e.args[0])) - else: - break - if value and "%(" in value: - raise InterpolationDepthError(option, section, rawval) - return value - - def before_set(self, parser, section, option, value): - return value - - @staticmethod - def _interpolation_replace(match, parser): - s = match.group(1) - if s is None: - return match.group() - else: - return "%%(%s)s" % parser.optionxform(s) - - -class RawConfigParser(MutableMapping): - """ConfigParser that does not do interpolation.""" - - # Regular expressions for parsing section headers and options - _SECT_TMPL = r""" - \[ # [ - (?P<header>[^]]+) # very permissive! - \] # ] - """ - _OPT_TMPL = r""" - (?P<option>.*?) # very permissive! - \s*(?P<vi>{delim})\s* # any number of space/tab, - # followed by any of the - # allowed delimiters, - # followed by any space/tab - (?P<value>.*)$ # everything up to eol - """ - _OPT_NV_TMPL = r""" - (?P<option>.*?) # very permissive! - \s*(?: # any number of space/tab, - (?P<vi>{delim})\s* # optionally followed by - # any of the allowed - # delimiters, followed by any - # space/tab - (?P<value>.*))?$ # everything up to eol - """ - # Interpolation algorithm to be used if the user does not specify another - _DEFAULT_INTERPOLATION = Interpolation() - # Compiled regular expression for matching sections - SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE) - # Compiled regular expression for matching options with typical separators - OPTCRE = re.compile(_OPT_TMPL.format(delim="=|:"), re.VERBOSE) - # Compiled regular expression for matching options with optional values - # delimited using typical separators - OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE) - # Compiled regular expression for matching leading whitespace in a line - NONSPACECRE = re.compile(r"\S") - # Possible boolean values in the configuration. - BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True, - '0': False, 'no': False, 'false': False, 'off': False} - - def __init__(self, defaults=None, dict_type=_default_dict, - allow_no_value=False, **kwargs): - - # keyword-only arguments - delimiters = kwargs.get('delimiters', ('=', ':')) - comment_prefixes = kwargs.get('comment_prefixes', ('#', ';')) - inline_comment_prefixes = kwargs.get('inline_comment_prefixes', None) - strict = kwargs.get('strict', True) - empty_lines_in_values = kwargs.get('empty_lines_in_values', True) - default_section = kwargs.get('default_section', DEFAULTSECT) - interpolation = kwargs.get('interpolation', _UNSET) - converters = kwargs.get('converters', _UNSET) - - self._dict = dict_type - self._sections = self._dict() - self._defaults = self._dict() - self._converters = ConverterMapping(self) - self._proxies = self._dict() - self._proxies[default_section] = SectionProxy(self, default_section) - if defaults: - for key, value in defaults.items(): - self._defaults[self.optionxform(key)] = value - self._delimiters = tuple(delimiters) - if delimiters == ('=', ':'): - self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE - else: - d = "|".join(re.escape(d) for d in delimiters) - if allow_no_value: - self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d), - re.VERBOSE) - else: - self._optcre = re.compile(self._OPT_TMPL.format(delim=d), - re.VERBOSE) - self._comment_prefixes = tuple(comment_prefixes or ()) - self._inline_comment_prefixes = tuple(inline_comment_prefixes or ()) - self._strict = strict - self._allow_no_value = allow_no_value - self._empty_lines_in_values = empty_lines_in_values - self.default_section=default_section - self._interpolation = interpolation - if self._interpolation is _UNSET: - self._interpolation = self._DEFAULT_INTERPOLATION - if self._interpolation is None: - self._interpolation = Interpolation() - if converters is not _UNSET: - self._converters.update(converters) - - def defaults(self): - return self._defaults - - def sections(self): - """Return a list of section names, excluding [DEFAULT]""" - # self._sections will never have [DEFAULT] in it - return list(self._sections.keys()) - - def add_section(self, section): - """Create a new section in the configuration. - - Raise DuplicateSectionError if a section by the specified name - already exists. Raise ValueError if name is DEFAULT. - """ - if section == self.default_section: - raise ValueError('Invalid section name: %r' % section) - - if section in self._sections: - raise DuplicateSectionError(section) - self._sections[section] = self._dict() - self._proxies[section] = SectionProxy(self, section) - - def has_section(self, section): - """Indicate whether the named section is present in the configuration. - - The DEFAULT section is not acknowledged. - """ - return section in self._sections - - def options(self, section): - """Return a list of option names for the given section name.""" - try: - opts = self._sections[section].copy() - except KeyError: - raise from_none(NoSectionError(section)) - opts.update(self._defaults) - return list(opts.keys()) - - def read(self, filenames, encoding=None): - """Read and parse a filename or a list of filenames. - - Files that cannot be opened are silently ignored; this is - designed so that you can specify a list of potential - configuration file locations (e.g. current directory, user's - home directory, systemwide directory), and all existing - configuration files in the list will be read. A single - filename may also be given. - - Return list of successfully read files. - """ - if PY2 and isinstance(filenames, bytes): - # we allow for a little unholy magic for Python 2 so that - # people not using unicode_literals can still use the library - # conveniently - warnings.warn( - "You passed a bytestring as `filenames`. This will not work" - " on Python 3. Use `cp.read_file()` or switch to using Unicode" - " strings across the board.", - DeprecationWarning, - stacklevel=2, - ) - filenames = [filenames] - elif isinstance(filenames, str): - filenames = [filenames] - read_ok = [] - for filename in filenames: - try: - with open(filename, encoding=encoding) as fp: - self._read(fp, filename) - except IOError: - continue - read_ok.append(filename) - return read_ok - - def read_file(self, f, source=None): - """Like read() but the argument must be a file-like object. - - The `f' argument must be iterable, returning one line at a time. - Optional second argument is the `source' specifying the name of the - file being read. If not given, it is taken from f.name. If `f' has no - `name' attribute, `<???>' is used. - """ - if source is None: - try: - source = f.name - except AttributeError: - source = '<???>' - self._read(f, source) - - def read_string(self, string, source='<string>'): - """Read configuration from a given string.""" - sfile = io.StringIO(string) - self.read_file(sfile, source) - - def read_dict(self, dictionary, source='<dict>'): - """Read configuration from a dictionary. - - Keys are section names, values are dictionaries with keys and values - that should be present in the section. If the used dictionary type - preserves order, sections and their keys will be added in order. - - All types held in the dictionary are converted to strings during - reading, including section names, option names and keys. - - Optional second argument is the `source' specifying the name of the - dictionary being read. - """ - elements_added = set() - for section, keys in dictionary.items(): - section = str(section) - try: - self.add_section(section) - except (DuplicateSectionError, ValueError): - if self._strict and section in elements_added: - raise - elements_added.add(section) - for key, value in keys.items(): - key = self.optionxform(str(key)) - if value is not None: - value = str(value) - if self._strict and (section, key) in elements_added: - raise DuplicateOptionError(section, key, source) - elements_added.add((section, key)) - self.set(section, key, value) - - def readfp(self, fp, filename=None): - """Deprecated, use read_file instead.""" - warnings.warn( - "This method will be removed in future versions. " - "Use 'parser.read_file()' instead.", - DeprecationWarning, stacklevel=2 - ) - self.read_file(fp, source=filename) - - def get(self, section, option, **kwargs): - """Get an option value for a given section. - - If `vars' is provided, it must be a dictionary. The option is looked up - in `vars' (if provided), `section', and in `DEFAULTSECT' in that order. - If the key is not found and `fallback' is provided, it is used as - a fallback value. `None' can be provided as a `fallback' value. - - If interpolation is enabled and the optional argument `raw' is False, - all interpolations are expanded in the return values. - - Arguments `raw', `vars', and `fallback' are keyword only. - - The section DEFAULT is special. - """ - # keyword-only arguments - raw = kwargs.get('raw', False) - vars = kwargs.get('vars', None) - fallback = kwargs.get('fallback', _UNSET) - - try: - d = self._unify_values(section, vars) - except NoSectionError: - if fallback is _UNSET: - raise - else: - return fallback - option = self.optionxform(option) - try: - value = d[option] - except KeyError: - if fallback is _UNSET: - raise NoOptionError(option, section) - else: - return fallback - - if raw or value is None: - return value - else: - return self._interpolation.before_get(self, section, option, value, - d) - - def _get(self, section, conv, option, **kwargs): - return conv(self.get(section, option, **kwargs)) - - def _get_conv(self, section, option, conv, **kwargs): - # keyword-only arguments - kwargs.setdefault('raw', False) - kwargs.setdefault('vars', None) - fallback = kwargs.pop('fallback', _UNSET) - try: - return self._get(section, conv, option, **kwargs) - except (NoSectionError, NoOptionError): - if fallback is _UNSET: - raise - return fallback - - # getint, getfloat and getboolean provided directly for backwards compat - def getint(self, section, option, **kwargs): - # keyword-only arguments - kwargs.setdefault('raw', False) - kwargs.setdefault('vars', None) - kwargs.setdefault('fallback', _UNSET) - return self._get_conv(section, option, int, **kwargs) - - def getfloat(self, section, option, **kwargs): - # keyword-only arguments - kwargs.setdefault('raw', False) - kwargs.setdefault('vars', None) - kwargs.setdefault('fallback', _UNSET) - return self._get_conv(section, option, float, **kwargs) - - def getboolean(self, section, option, **kwargs): - # keyword-only arguments - kwargs.setdefault('raw', False) - kwargs.setdefault('vars', None) - kwargs.setdefault('fallback', _UNSET) - return self._get_conv(section, option, self._convert_to_boolean, - **kwargs) - - def items(self, section=_UNSET, raw=False, vars=None): - """Return a list of (name, value) tuples for each option in a section. - - All % interpolations are expanded in the return values, based on the - defaults passed into the constructor, unless the optional argument - `raw' is true. Additional substitutions may be provided using the - `vars' argument, which must be a dictionary whose contents overrides - any pre-existing defaults. - - The section DEFAULT is special. - """ - if section is _UNSET: - return super(RawConfigParser, self).items() - d = self._defaults.copy() - try: - d.update(self._sections[section]) - except KeyError: - if section != self.default_section: - raise NoSectionError(section) - # Update with the entry specific variables - if vars: - for key, value in vars.items(): - d[self.optionxform(key)] = value - value_getter = lambda option: self._interpolation.before_get(self, - section, option, d[option], d) - if raw: - value_getter = lambda option: d[option] - return [(option, value_getter(option)) for option in d.keys()] - - def popitem(self): - """Remove a section from the parser and return it as - a (section_name, section_proxy) tuple. If no section is present, raise - KeyError. - - The section DEFAULT is never returned because it cannot be removed. - """ - for key in self.sections(): - value = self[key] - del self[key] - return key, value - raise KeyError - - def optionxform(self, optionstr): - return optionstr.lower() - - def has_option(self, section, option): - """Check for the existence of a given option in a given section. - If the specified `section' is None or an empty string, DEFAULT is - assumed. If the specified `section' does not exist, returns False.""" - if not section or section == self.default_section: - option = self.optionxform(option) - return option in self._defaults - elif section not in self._sections: - return False - else: - option = self.optionxform(option) - return (option in self._sections[section] - or option in self._defaults) - - def set(self, section, option, value=None): - """Set an option.""" - if value: - value = self._interpolation.before_set(self, section, option, - value) - if not section or section == self.default_section: - sectdict = self._defaults - else: - try: - sectdict = self._sections[section] - except KeyError: - raise from_none(NoSectionError(section)) - sectdict[self.optionxform(option)] = value - - def write(self, fp, space_around_delimiters=True): - """Write an .ini-format representation of the configuration state. - - If `space_around_delimiters' is True (the default), delimiters - between keys and values are surrounded by spaces. - """ - if space_around_delimiters: - d = " {0} ".format(self._delimiters[0]) - else: - d = self._delimiters[0] - if self._defaults: - self._write_section(fp, self.default_section, - self._defaults.items(), d) - for section in self._sections: - self._write_section(fp, section, - self._sections[section].items(), d) - - def _write_section(self, fp, section_name, section_items, delimiter): - """Write a single section to the specified `fp'.""" - fp.write("[{0}]\n".format(section_name)) - for key, value in section_items: - value = self._interpolation.before_write(self, section_name, key, - value) - if value is not None or not self._allow_no_value: - value = delimiter + str(value).replace('\n', '\n\t') - else: - value = "" - fp.write("{0}{1}\n".format(key, value)) - fp.write("\n") - - def remove_option(self, section, option): - """Remove an option.""" - if not section or section == self.default_section: - sectdict = self._defaults - else: - try: - sectdict = self._sections[section] - except KeyError: - raise from_none(NoSectionError(section)) - option = self.optionxform(option) - existed = option in sectdict - if existed: - del sectdict[option] - return existed - - def remove_section(self, section): - """Remove a file section.""" - existed = section in self._sections - if existed: - del self._sections[section] - del self._proxies[section] - return existed - - def __getitem__(self, key): - if key != self.default_section and not self.has_section(key): - raise KeyError(key) - return self._proxies[key] - - def __setitem__(self, key, value): - # To conform with the mapping protocol, overwrites existing values in - # the section. - - # XXX this is not atomic if read_dict fails at any point. Then again, - # no update method in configparser is atomic in this implementation. - if key == self.default_section: - self._defaults.clear() - elif key in self._sections: - self._sections[key].clear() - self.read_dict({key: value}) - - def __delitem__(self, key): - if key == self.default_section: - raise ValueError("Cannot remove the default section.") - if not self.has_section(key): - raise KeyError(key) - self.remove_section(key) - - def __contains__(self, key): - return key == self.default_section or self.has_section(key) - - def __len__(self): - return len(self._sections) + 1 # the default section - - def __iter__(self): - # XXX does it break when underlying container state changed? - return itertools.chain((self.default_section,), self._sections.keys()) - - def _read(self, fp, fpname): - """Parse a sectioned configuration file. - - Each section in a configuration file contains a header, indicated by - a name in square brackets (`[]'), plus key/value options, indicated by - `name' and `value' delimited with a specific substring (`=' or `:' by - default). - - Values can span multiple lines, as long as they are indented deeper - than the first line of the value. Depending on the parser's mode, blank - lines may be treated as parts of multiline values or ignored. - - Configuration files may include comments, prefixed by specific - characters (`#' and `;' by default). Comments may appear on their own - in an otherwise empty line or may be entered in lines holding values or - section names. - """ - elements_added = set() - cursect = None # None, or a dictionary - sectname = None - optname = None - lineno = 0 - indent_level = 0 - e = None # None, or an exception - for lineno, line in enumerate(fp, start=1): - comment_start = sys.maxsize - # strip inline comments - inline_prefixes = dict( - (p, -1) for p in self._inline_comment_prefixes) - while comment_start == sys.maxsize and inline_prefixes: - next_prefixes = {} - for prefix, index in inline_prefixes.items(): - index = line.find(prefix, index+1) - if index == -1: - continue - next_prefixes[prefix] = index - if index == 0 or (index > 0 and line[index-1].isspace()): - comment_start = min(comment_start, index) - inline_prefixes = next_prefixes - # strip full line comments - for prefix in self._comment_prefixes: - if line.strip().startswith(prefix): - comment_start = 0 - break - if comment_start == sys.maxsize: - comment_start = None - value = line[:comment_start].strip() - if not value: - if self._empty_lines_in_values: - # add empty line to the value, but only if there was no - # comment on the line - if (comment_start is None and - cursect is not None and - optname and - cursect[optname] is not None): - cursect[optname].append('') # newlines added at join - else: - # empty line marks end of value - indent_level = sys.maxsize - continue - # continuation line? - first_nonspace = self.NONSPACECRE.search(line) - cur_indent_level = first_nonspace.start() if first_nonspace else 0 - if (cursect is not None and optname and - cur_indent_level > indent_level): - cursect[optname].append(value) - # a section header or option header? - else: - indent_level = cur_indent_level - # is it a section header? - mo = self.SECTCRE.match(value) - if mo: - sectname = mo.group('header') - if sectname in self._sections: - if self._strict and sectname in elements_added: - raise DuplicateSectionError(sectname, fpname, - lineno) - cursect = self._sections[sectname] - elements_added.add(sectname) - elif sectname == self.default_section: - cursect = self._defaults - else: - cursect = self._dict() - self._sections[sectname] = cursect - self._proxies[sectname] = SectionProxy(self, sectname) - elements_added.add(sectname) - # So sections can't start with a continuation line - optname = None - # no section header in the file? - elif cursect is None: - raise MissingSectionHeaderError(fpname, lineno, line) - # an option line? - else: - mo = self._optcre.match(value) - if mo: - optname, vi, optval = mo.group('option', 'vi', 'value') - if not optname: - e = self._handle_error(e, fpname, lineno, line) - optname = self.optionxform(optname.rstrip()) - if (self._strict and - (sectname, optname) in elements_added): - raise DuplicateOptionError(sectname, optname, - fpname, lineno) - elements_added.add((sectname, optname)) - # This check is fine because the OPTCRE cannot - # match if it would set optval to None - if optval is not None: - optval = optval.strip() - cursect[optname] = [optval] - else: - # valueless option handling - cursect[optname] = None - else: - # a non-fatal parsing error occurred. set up the - # exception but keep going. the exception will be - # raised at the end of the file and will contain a - # list of all bogus lines - e = self._handle_error(e, fpname, lineno, line) - # if any parsing errors occurred, raise an exception - if e: - raise e - self._join_multiline_values() - - def _join_multiline_values(self): - defaults = self.default_section, self._defaults - all_sections = itertools.chain((defaults,), - self._sections.items()) - for section, options in all_sections: - for name, val in options.items(): - if isinstance(val, list): - val = '\n'.join(val).rstrip() - options[name] = self._interpolation.before_read(self, - section, - name, val) - - def _handle_error(self, exc, fpname, lineno, line): - if not exc: - exc = ParsingError(fpname) - exc.append(lineno, repr(line)) - return exc - - def _unify_values(self, section, vars): - """Create a sequence of lookups with 'vars' taking priority over - the 'section' which takes priority over the DEFAULTSECT. - - """ - sectiondict = {} - try: - sectiondict = self._sections[section] - except KeyError: - if section != self.default_section: - raise NoSectionError(section) - # Update with the entry specific variables - vardict = {} - if vars: - for key, value in vars.items(): - if value is not None: - value = str(value) - vardict[self.optionxform(key)] = value - return _ChainMap(vardict, sectiondict, self._defaults) - - def _convert_to_boolean(self, value): - """Return a boolean value translating from other types if necessary. - """ - if value.lower() not in self.BOOLEAN_STATES: - raise ValueError('Not a boolean: %s' % value) - return self.BOOLEAN_STATES[value.lower()] - - def _validate_value_types(self, **kwargs): - """Raises a TypeError for non-string values. - - The only legal non-string value if we allow valueless - options is None, so we need to check if the value is a - string if: - - we do not allow valueless options, or - - we allow valueless options but the value is not None - - For compatibility reasons this method is not used in classic set() - for RawConfigParsers. It is invoked in every case for mapping protocol - access and in ConfigParser.set(). - """ - # keyword-only arguments - section = kwargs.get('section', "") - option = kwargs.get('option', "") - value = kwargs.get('value', "") - - if PY2 and bytes in (type(section), type(option), type(value)): - # we allow for a little unholy magic for Python 2 so that - # people not using unicode_literals can still use the library - # conveniently - warnings.warn( - "You passed a bytestring. Implicitly decoding as UTF-8 string." - " This will not work on Python 3. Please switch to using" - " Unicode strings across the board.", - DeprecationWarning, - stacklevel=2, - ) - if isinstance(section, bytes): - section = section.decode('utf8') - if isinstance(option, bytes): - option = option.decode('utf8') - if isinstance(value, bytes): - value = value.decode('utf8') - - if not isinstance(section, str): - raise TypeError("section names must be strings") - if not isinstance(option, str): - raise TypeError("option keys must be strings") - if not self._allow_no_value or value: - if not isinstance(value, str): - raise TypeError("option values must be strings") - - return section, option, value - - @property - def converters(self): - return self._converters - - -class ConfigParser(RawConfigParser): - """ConfigParser implementing interpolation.""" - - _DEFAULT_INTERPOLATION = BasicInterpolation() - - def set(self, section, option, value=None): - """Set an option. Extends RawConfigParser.set by validating type and - interpolation syntax on the value.""" - _, option, value = self._validate_value_types(option=option, value=value) - super(ConfigParser, self).set(section, option, value) - - def add_section(self, section): - """Create a new section in the configuration. Extends - RawConfigParser.add_section by validating if the section name is - a string.""" - section, _, _ = self._validate_value_types(section=section) - super(ConfigParser, self).add_section(section) - - -class SafeConfigParser(ConfigParser): - """ConfigParser alias for backwards compatibility purposes.""" - - def __init__(self, *args, **kwargs): - super(SafeConfigParser, self).__init__(*args, **kwargs) - warnings.warn( - "The SafeConfigParser class has been renamed to ConfigParser " - "in Python 3.2. This alias will be removed in future versions." - " Use ConfigParser directly instead.", - DeprecationWarning, stacklevel=2 - ) - - -class SectionProxy(MutableMapping): - """A proxy for a single section from a parser.""" - - def __init__(self, parser, name): - """Creates a view on a section of the specified `name` in `parser`.""" - self._parser = parser - self._name = name - for conv in parser.converters: - key = 'get' + conv - getter = functools.partial(self.get, _impl=getattr(parser, key)) - setattr(self, key, getter) - - def __repr__(self): - return '<Section: {0}>'.format(self._name) - - def __getitem__(self, key): - if not self._parser.has_option(self._name, key): - raise KeyError(key) - return self._parser.get(self._name, key) - - def __setitem__(self, key, value): - _, key, value = self._parser._validate_value_types(option=key, value=value) - return self._parser.set(self._name, key, value) - - def __delitem__(self, key): - if not (self._parser.has_option(self._name, key) and - self._parser.remove_option(self._name, key)): - raise KeyError(key) - - def __contains__(self, key): - return self._parser.has_option(self._name, key) - - def __len__(self): - return len(self._options()) - - def __iter__(self): - return self._options().__iter__() - - def _options(self): - if self._name != self._parser.default_section: - return self._parser.options(self._name) - else: - return self._parser.defaults() - - @property - def parser(self): - # The parser object of the proxy is read-only. - return self._parser - - @property - def name(self): - # The name of the section on a proxy is read-only. - return self._name - - def get(self, option, fallback=None, **kwargs): - """Get an option value. - - Unless `fallback` is provided, `None` will be returned if the option - is not found. - - """ - # keyword-only arguments - kwargs.setdefault('raw', False) - kwargs.setdefault('vars', None) - _impl = kwargs.pop('_impl', None) - # If `_impl` is provided, it should be a getter method on the parser - # object that provides the desired type conversion. - if not _impl: - _impl = self._parser.get - return _impl(self._name, option, fallback=fallback, **kwargs) - - -class ConverterMapping(MutableMapping): - """Enables reuse of get*() methods between the parser and section proxies. - - If a parser class implements a getter directly, the value for the given - key will be ``None``. The presence of the converter name here enables - section proxies to find and use the implementation on the parser class. - """ - - GETTERCRE = re.compile(r"^get(?P<name>.+)$") - - def __init__(self, parser): - self._parser = parser - self._data = {} - for getter in dir(self._parser): - m = self.GETTERCRE.match(getter) - if not m or not callable(getattr(self._parser, getter)): - continue - self._data[m.group('name')] = None # See class docstring. - - def __getitem__(self, key): - return self._data[key] - - def __setitem__(self, key, value): - try: - k = 'get' + key - except TypeError: - raise ValueError('Incompatible key: {} (type: {})' - ''.format(key, type(key))) - if k == 'get': - raise ValueError('Incompatible key: cannot use "" as a name') - self._data[key] = value - func = functools.partial(self._parser._get_conv, conv=value) - func.converter = value - setattr(self._parser, k, func) - for proxy in self._parser.values(): - getter = functools.partial(proxy.get, _impl=func) - setattr(proxy, k, getter) - - def __delitem__(self, key): - try: - k = 'get' + (key or None) - except TypeError: - raise KeyError(key) - del self._data[key] - for inst in itertools.chain((self._parser,), self._parser.values()): - try: - delattr(inst, k) - except AttributeError: - # don't raise since the entry was present in _data, silently - # clean up - continue - - def __iter__(self): - return iter(self._data) - - def __len__(self): - return len(self._data) diff --git a/lib/backports/configparser/helpers.py b/lib/backports/configparser/helpers.py deleted file mode 100644 index c47662f..0000000 --- a/lib/backports/configparser/helpers.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from collections import MutableMapping -try: - from collections import UserDict -except ImportError: - from UserDict import UserDict - -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict - -from io import open -import sys -try: - from thread import get_ident -except ImportError: - try: - from _thread import get_ident - except ImportError: - from _dummy_thread import get_ident - - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -str = type('str') - - -def from_none(exc): - """raise from_none(ValueError('a')) == raise ValueError('a') from None""" - exc.__cause__ = None - exc.__suppress_context__ = True - return exc - - -# from reprlib 3.2.1 -def recursive_repr(fillvalue='...'): - 'Decorator to make a repr function return fillvalue for a recursive call' - - def decorating_function(user_function): - repr_running = set() - - def wrapper(self): - key = id(self), get_ident() - if key in repr_running: - return fillvalue - repr_running.add(key) - try: - result = user_function(self) - finally: - repr_running.discard(key) - return result - - # Can't use functools.wraps() here because of bootstrap issues - wrapper.__module__ = getattr(user_function, '__module__') - wrapper.__doc__ = getattr(user_function, '__doc__') - wrapper.__name__ = getattr(user_function, '__name__') - wrapper.__annotations__ = getattr(user_function, '__annotations__', {}) - return wrapper - - return decorating_function - -# from collections 3.2.1 -class _ChainMap(MutableMapping): - ''' A ChainMap groups multiple dicts (or other mappings) together - to create a single, updateable view. - - The underlying mappings are stored in a list. That list is public and can - accessed or updated using the *maps* attribute. There is no other state. - - Lookups search the underlying mappings successively until a key is found. - In contrast, writes, updates, and deletions only operate on the first - mapping. - - ''' - - def __init__(self, *maps): - '''Initialize a ChainMap by setting *maps* to the given mappings. - If no mappings are provided, a single empty dictionary is used. - - ''' - self.maps = list(maps) or [{}] # always at least one map - - def __missing__(self, key): - raise KeyError(key) - - def __getitem__(self, key): - for mapping in self.maps: - try: - return mapping[key] # can't use 'key in mapping' with defaultdict - except KeyError: - pass - return self.__missing__(key) # support subclasses that define __missing__ - - def get(self, key, default=None): - return self[key] if key in self else default - - def __len__(self): - return len(set().union(*self.maps)) # reuses stored hash values if possible - - def __iter__(self): - return iter(set().union(*self.maps)) - - def __contains__(self, key): - return any(key in m for m in self.maps) - - @recursive_repr() - def __repr__(self): - return '{0.__class__.__name__}({1})'.format( - self, ', '.join(map(repr, self.maps))) - - @classmethod - def fromkeys(cls, iterable, *args): - 'Create a ChainMap with a single dict created from the iterable.' - return cls(dict.fromkeys(iterable, *args)) - - def copy(self): - 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' - return self.__class__(self.maps[0].copy(), *self.maps[1:]) - - __copy__ = copy - - def new_child(self): # like Django's Context.push() - 'New ChainMap with a new dict followed by all previous maps.' - return self.__class__({}, *self.maps) - - @property - def parents(self): # like Django's Context.pop() - 'New ChainMap from maps[1:].' - return self.__class__(*self.maps[1:]) - - def __setitem__(self, key, value): - self.maps[0][key] = value - - def __delitem__(self, key): - try: - del self.maps[0][key] - except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) - - def popitem(self): - 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' - try: - return self.maps[0].popitem() - except KeyError: - raise KeyError('No keys found in the first mapping.') - - def pop(self, key, *args): - 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' - try: - return self.maps[0].pop(key, *args) - except KeyError: - raise KeyError('Key not found in the first mapping: {!r}'.format(key)) - - def clear(self): - 'Clear maps[0], leaving maps[1:] intact.' - self.maps[0].clear() - - -try: - from collections import ChainMap -except ImportError: - ChainMap = _ChainMap diff --git a/lib/bin/chardetect.exe b/lib/bin/chardetect.exe deleted file mode 100644 index de0cc0b540e59140cfc47c77857d9d89f61d5dc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93027 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 diff --git a/lib/bin/easy_install-3.7.exe b/lib/bin/easy_install-3.7.exe deleted file mode 100644 index 4a61442578c13486b0fa276596393bcea183507a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93036 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 JT@0T0{0sTFBbNXG diff --git a/lib/bin/easy_install.exe b/lib/bin/easy_install.exe deleted file mode 100644 index 4a61442578c13486b0fa276596393bcea183507a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93036 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 JT@0T0{0sTFBbNXG diff --git a/lib/certifi/__init__.py b/lib/certifi/__init__.py deleted file mode 100644 index ef71f3a..0000000 --- a/lib/certifi/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .core import where - -__version__ = "2018.11.29" diff --git a/lib/certifi/__main__.py b/lib/certifi/__main__.py deleted file mode 100644 index 5f1da0d..0000000 --- a/lib/certifi/__main__.py +++ /dev/null @@ -1,2 +0,0 @@ -from certifi import where -print(where()) diff --git a/lib/certifi/cacert.pem b/lib/certifi/cacert.pem deleted file mode 100644 index db68797..0000000 --- a/lib/certifi/cacert.pem +++ /dev/null @@ -1,4512 +0,0 @@ - -# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Label: "GlobalSign Root CA" -# Serial: 4835703278459707669005204 -# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a -# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c -# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG -A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv -b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw -MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i -YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT -aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ -jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp -xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp -1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG -snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ -U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 -9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B -AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz -yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE -38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP -AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad -DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME -HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Label: "GlobalSign Root CA - R2" -# Serial: 4835703278459682885658125 -# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 -# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe -# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e ------BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 -MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL -v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 -eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq -tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd -C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa -zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB -mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH -V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n -bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG -3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs -J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO -291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS -ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd -AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 -TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 3 Public Primary Certification Authority - G3" -# Serial: 206684696279472310254277870180966723415 -# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 -# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 -# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b -N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t -KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu -kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm -CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ -Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu -imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te -2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe -DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p -F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt -TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Premium 2048 Secure Server CA" -# Serial: 946069240 -# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 -# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 -# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML -RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp -bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 -IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 -MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 -LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp -YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG -A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq -K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe -sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX -MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT -XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ -HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH -4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub -j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo -U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf -zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b -u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ -bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er -fF6adulZkMV8gzURZVE= ------END CERTIFICATE----- - -# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Label: "Baltimore CyberTrust Root" -# Serial: 33554617 -# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 -# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 -# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ -RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD -VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX -DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y -ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy -VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr -mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr -IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK -mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu -XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy -dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye -jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 -BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 -9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx -jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 -Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz -ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS -R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE----- - -# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Label: "AddTrust External Root" -# Serial: 1 -# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f -# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 -# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Label: "Entrust Root Certification Authority" -# Serial: 1164660820 -# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 -# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 -# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c ------BEGIN CERTIFICATE----- -MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 -Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW -KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl -cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw -NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw -NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy -ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV -BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo -Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 -4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 -KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI -rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi -94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB -sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi -gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo -kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE -vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA -A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t -O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua -AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP -9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ -eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m -0vdXcDazv/wor3ElhVsT/h5/WrQ8 ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. -# Label: "GeoTrust Global CA" -# Serial: 144470 -# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 -# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 -# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i -YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg -R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 -9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq -fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv -iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU -1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ -bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW -MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA -ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l -uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn -Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS -tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF -PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un -hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV -5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Label: "GeoTrust Universal CA" -# Serial: 1 -# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 -# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 -# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 ------BEGIN CERTIFICATE----- -MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy -c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE -BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 -IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV -VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 -cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT -QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh -F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v -c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w -mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd -VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX -teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ -f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe -Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ -nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY -MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG -9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc -aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX -IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn -ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z -uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN -Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja -QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW -koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 -ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt -DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm -bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Universal CA 2" -# Serial: 1 -# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 -# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 -# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS ------END CERTIFICATE----- - -# Issuer: CN=AAA Certificate Services O=Comodo CA Limited -# Subject: CN=AAA Certificate Services O=Comodo CA Limited -# Label: "Comodo AAA Services root" -# Serial: 1 -# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 -# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 -# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 ------BEGIN CERTIFICATE----- -MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj -YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM -GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua -BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe -3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 -YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR -rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm -ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU -oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF -MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v -QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t -b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF -AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q -GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz -Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 -G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi -l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 -smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Label: "QuoVadis Root CA" -# Serial: 985026699 -# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 -# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 -# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 ------BEGIN CERTIFICATE----- -MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz -MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw -IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR -dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp -li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D -rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ -WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug -F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU -xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC -Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv -dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw -ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl -IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh -c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy -ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh -Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI -KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T -KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq -y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p -dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD -VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL -MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk -fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 -7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R -cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y -mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW -xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK -SnQ2+Q== ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2" -# Serial: 1289 -# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b -# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 -# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 ------BEGIN CERTIFICATE----- -MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa -GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg -Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J -WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB -rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp -+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 -ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i -Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz -PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og -/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH -oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI -yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud -EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 -A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL -MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT -ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f -BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn -g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl -fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K -WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha -B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc -hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR -TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD -mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z -ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y -4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza -8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3" -# Serial: 1478 -# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf -# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 -# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 ------BEGIN CERTIFICATE----- -MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM -V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB -4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr -H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd -8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv -vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT -mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe -btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc -T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt -WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ -c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A -4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD -VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG -CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 -aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 -aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu -dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw -czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G -A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg -Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 -7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem -d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd -+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B -4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN -t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x -DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 -k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s -zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j -Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT -mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK -4SVhM7JZG+Ju1zdXtg2pEto= ------END CERTIFICATE----- - -# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 -# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 -# Label: "Security Communication Root CA" -# Serial: 0 -# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a -# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 -# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c ------BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY -MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t -dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 -WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD -VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 -9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ -DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 -Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N -QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ -xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G -A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG -kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr -Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 -Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU -JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot -RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== ------END CERTIFICATE----- - -# Issuer: CN=Sonera Class2 CA O=Sonera -# Subject: CN=Sonera Class2 CA O=Sonera -# Label: "Sonera Class 2 Root CA" -# Serial: 29 -# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb -# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 -# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 ------BEGIN CERTIFICATE----- -MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP -MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx -MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV -BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o -Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt -5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s -3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej -vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu -8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw -DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG -MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil -zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ -3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD -FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 -Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 -ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M ------END CERTIFICATE----- - -# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Label: "XRamp Global CA Root" -# Serial: 107108908803651509692980124233745014957 -# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 -# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 -# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 ------BEGIN CERTIFICATE----- -MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB -gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk -MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY -UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx -NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 -dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy -dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB -dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 -38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP -KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q -DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 -qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa -JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi -PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P -BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs -jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 -eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD -ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR -vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt -qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa -IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy -i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ -O+7ETPTsJ3xCwnR8gooJybQDJbw= ------END CERTIFICATE----- - -# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Label: "Go Daddy Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 -# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 -# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 ------BEGIN CERTIFICATE----- -MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh -MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE -YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 -MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo -ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg -MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN -ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA -PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w -wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi -EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY -avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ -YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE -sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h -/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 -IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD -ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy -OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P -TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ -HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER -dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf -ReYNnyicsbkqWletNw+vHX/bvZ8= ------END CERTIFICATE----- - -# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Label: "Starfield Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 -# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a -# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl -MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp -U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw -NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE -ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp -ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 -DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf -8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN -+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 -X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa -K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA -1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G -A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR -zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 -YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD -bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w -DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 -L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D -eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl -xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp -VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY -WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= ------END CERTIFICATE----- - -# Issuer: O=Government Root Certification Authority -# Subject: O=Government Root Certification Authority -# Label: "Taiwan GRCA" -# Serial: 42023070807708724159991140556527066870 -# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e -# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 -# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 ------BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ -MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow -PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR -IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q -gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy -yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts -F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 -jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx -ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC -VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK -YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH -EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN -Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud -DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE -MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK -UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ -TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf -qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK -ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE -JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 -hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 -EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm -nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX -udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz -ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe -LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl -pYYsfPQS ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root CA" -# Serial: 17154717934120587862167794914071425081 -# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 -# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 -# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c ------BEGIN CERTIFICATE----- -MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c -JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP -mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ -wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 -VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ -AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB -AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun -pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC -dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf -fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm -NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx -H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe -+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root CA" -# Serial: 10944719598952040374951832963794454346 -# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e -# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 -# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert High Assurance EV Root CA" -# Serial: 3553400076410547919724730734378100087 -# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a -# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 -# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug -RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm -+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW -PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM -xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB -Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 -hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg -EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA -FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec -nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z -eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF -hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 -Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe -vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep -+OkuE6N36B9K ------END CERTIFICATE----- - -# Issuer: CN=Class 2 Primary CA O=Certplus -# Subject: CN=Class 2 Primary CA O=Certplus -# Label: "Certplus Class 2 Primary CA" -# Serial: 177770208045934040241468760488327595043 -# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b -# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb -# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb ------BEGIN CERTIFICATE----- -MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw -PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz -cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 -MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz -IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ -ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR -VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL -kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd -EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas -H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 -HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud -DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 -QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu -Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ -AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 -yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR -FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA -ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB -kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 -l7+ijrRU ------END CERTIFICATE----- - -# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. -# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. -# Label: "DST Root CA X3" -# Serial: 91299735575339953335919266965803778155 -# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 -# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 -# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 ------BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow -PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD -Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O -rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq -OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b -xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw -7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD -aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG -SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 -ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr -AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz -R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 -JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo -Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ------END CERTIFICATE----- - -# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Label: "SwissSign Gold CA - G2" -# Serial: 13492815561806991280 -# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 -# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 -# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- - -# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Label: "SwissSign Silver CA - G2" -# Serial: 5700383053117599563 -# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 -# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb -# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Label: "GeoTrust Primary Certification Authority" -# Serial: 32798226551256963324313806436981982369 -# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf -# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 -# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY -MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo -R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx -MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK -Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 -AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA -ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 -7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W -kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI -mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ -KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 -6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl -4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K -oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj -UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU -AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA" -# Serial: 69529181992039203566298953787712940909 -# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 -# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 -# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" -# Serial: 33037644167568058970164719475676101450 -# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c -# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 -# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df ------BEGIN CERTIFICATE----- -MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW -ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 -nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex -t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz -SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG -BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ -rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ -NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH -BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy -aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv -MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE -p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y -5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK -WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ -4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N -hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq ------END CERTIFICATE----- - -# Issuer: CN=SecureTrust CA O=SecureTrust Corporation -# Subject: CN=SecureTrust CA O=SecureTrust Corporation -# Label: "SecureTrust CA" -# Serial: 17199774589125277788362757014266862032 -# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 -# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 -# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 ------BEGIN CERTIFICATE----- -MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz -MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv -cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz -Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO -0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao -wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj -7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS -8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT -BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg -JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 -6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ -3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm -D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS -CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR -3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= ------END CERTIFICATE----- - -# Issuer: CN=Secure Global CA O=SecureTrust Corporation -# Subject: CN=Secure Global CA O=SecureTrust Corporation -# Label: "Secure Global CA" -# Serial: 9751836167731051554232119481456978597 -# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de -# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b -# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 ------BEGIN CERTIFICATE----- -MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx -MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg -Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ -iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa -/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ -jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI -HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 -sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w -gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw -KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG -AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L -URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO -H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm -I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY -iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc -f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW ------END CERTIFICATE----- - -# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO Certification Authority O=COMODO CA Limited -# Label: "COMODO Certification Authority" -# Serial: 104350513648249232941998508985834464573 -# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 -# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b -# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 ------BEGIN CERTIFICATE----- -MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB -gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV -BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw -MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl -YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P -RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 -UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI -2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 -Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp -+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ -DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O -nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW -/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g -PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u -QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY -SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv -IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ -RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 -zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd -BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB -ZQ== ------END CERTIFICATE----- - -# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Label: "Network Solutions Certificate Authority" -# Serial: 116697915152937497490437556386812487904 -# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e -# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce -# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c ------BEGIN CERTIFICATE----- -MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi -MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu -MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp -dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV -UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO -ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz -c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP -OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl -mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF -BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 -qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw -gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu -bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp -dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 -6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ -h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH -/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv -wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN -pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey ------END CERTIFICATE----- - -# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Label: "COMODO ECC Certification Authority" -# Serial: 41578283867086692638256921589707938090 -# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 -# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 -# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 ------BEGIN CERTIFICATE----- -MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT -IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw -MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy -ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N -T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv -biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR -FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J -cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW -BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ -BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm -fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv -GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= ------END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GA CA" -# Serial: 86718877871133159090080555911823548314 -# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 -# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 -# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 ------BEGIN CERTIFICATE----- -MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB -ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly -aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl -ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w -NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G -A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD -VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX -SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR -VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 -w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF -mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg -4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 -4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw -EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx -SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 -ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 -vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa -hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi -Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ -/L7fCg0= ------END CERTIFICATE----- - -# Issuer: CN=Certigna O=Dhimyotis -# Subject: CN=Certigna O=Dhimyotis -# Label: "Certigna" -# Serial: 18364802974209362175 -# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff -# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 -# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d ------BEGIN CERTIFICATE----- -MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV -BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X -DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ -BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 -QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny -gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw -zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q -130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 -JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw -ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT -AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj -AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG -9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h -bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc -fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu -HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w -t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw -WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== ------END CERTIFICATE----- - -# Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center -# Label: "Deutsche Telekom Root CA 2" -# Serial: 38 -# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 -# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf -# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 ------BEGIN CERTIFICATE----- -MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc -MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj -IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB -IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE -RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl -U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 -IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU -ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC -QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr -rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S -NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc -QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH -txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP -BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC -AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp -tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa -IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl -6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ -xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU -Cm26OWMohpLzGITY+9HPBVZkVw== ------END CERTIFICATE----- - -# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc -# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc -# Label: "Cybertrust Global Root" -# Serial: 4835703278459682877484360 -# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 -# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 -# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 ------BEGIN CERTIFICATE----- -MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG -A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh -bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE -ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS -b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 -7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS -J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y -HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP -t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz -FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY -XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ -MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw -hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js -MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA -A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj -Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx -XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o -omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc -A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW -WL1WMRJOEcgh4LMRkWXbtKaIOM5V ------END CERTIFICATE----- - -# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Label: "ePKI Root Certification Authority" -# Serial: 28956088682735189655030529057352760477 -# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 -# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 -# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 ------BEGIN CERTIFICATE----- -MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe -MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 -ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw -IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL -SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH -SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh -ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X -DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 -TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ -fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA -sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU -WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS -nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH -dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip -NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC -AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF -MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH -ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB -uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl -PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP -JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ -gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 -j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 -5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB -o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS -/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z -Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE -W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D -hNQ+IIX3Sj0rnP0qCglN6oH4EZw= ------END CERTIFICATE----- - -# Issuer: O=certSIGN OU=certSIGN ROOT CA -# Subject: O=certSIGN OU=certSIGN ROOT CA -# Label: "certSIGN ROOT CA" -# Serial: 35210227249154 -# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 -# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b -# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb ------BEGIN CERTIFICATE----- -MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT -AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD -QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP -MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do -0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ -UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d -RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ -OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv -JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C -AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O -BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ -LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY -MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ -44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I -Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw -i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN -9u6wWk5JRFRYX0KD ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G3" -# Serial: 28809105769928564313984085209975885599 -# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 -# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd -# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 ------BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB -mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT -MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s -eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ -BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 -BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz -+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm -hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn -5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W -JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL -DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC -huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB -AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB -zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN -kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD -AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH -SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G -spki4cErx5z481+oghLrGREt ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G2" -# Serial: 71758320672825410020661621085256472406 -# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f -# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 -# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G3" -# Serial: 127614157056681299805556476275995414779 -# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 -# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 -# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB -rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV -BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa -Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl -LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u -MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl -ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm -gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 -YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf -b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 -9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S -zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk -OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV -HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA -2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW -oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu -t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c -KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM -m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu -MdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G2" -# Serial: 80682863203381065782177908751794619243 -# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a -# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 -# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL -MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj -KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 -MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV -BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw -NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV -BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH -MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL -So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal -tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG -CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT -qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz -rD6ogRLQy7rQkgu2npaqBA+K ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Universal Root Certification Authority" -# Serial: 85209574734084581917763752644031726877 -# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 -# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 -# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB -vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W -ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 -IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y -IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh -bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF -9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH -H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H -LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN -/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT -rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw -WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs -exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 -sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ -seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz -4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ -BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR -lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 -7M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" -# Serial: 63143484348153506665311985501458640051 -# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 -# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a -# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------END CERTIFICATE----- - -# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) -# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) -# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" -# Serial: 80544274841616 -# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 -# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 -# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 ------BEGIN CERTIFICATE----- -MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG -EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 -MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl -cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR -dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB -pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM -b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm -aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz -IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT -lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz -AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 -VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG -ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 -BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG -AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M -U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh -bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C -+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC -bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F -uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 -XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= ------END CERTIFICATE----- - -# Issuer: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G2" -# Serial: 10000012 -# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a -# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 -# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f ------BEGIN CERTIFICATE----- -MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX -DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 -qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp -uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU -Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE -pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp -5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M -UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN -GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy -5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv -6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK -eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 -B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ -BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov -L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG -SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS -CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen -5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 -IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK -gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL -+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL -vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm -bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk -N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC -Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z -ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== ------END CERTIFICATE----- - -# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Label: "Hongkong Post Root CA 1" -# Serial: 1000 -# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca -# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 -# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 ------BEGIN CERTIFICATE----- -MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx -FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg -Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG -A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr -b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ -jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn -PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh -ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 -nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h -q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED -MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC -mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 -7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB -oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs -EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO -fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi -AmvZWg== ------END CERTIFICATE----- - -# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Label: "SecureSign RootCA11" -# Serial: 1 -# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 -# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 -# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 ------BEGIN CERTIFICATE----- -MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr -MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG -A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 -MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp -Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD -QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz -i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 -h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV -MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 -UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni -8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC -h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD -VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB -AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm -KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ -X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr -QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 -pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN -QSdJQO7e5iNEOdyhIta6A/I= ------END CERTIFICATE----- - -# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Label: "Microsec e-Szigno Root CA 2009" -# Serial: 14014712776195784473 -# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 -# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e -# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 ------BEGIN CERTIFICATE----- -MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD -VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 -ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G -CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y -OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx -FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp -Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o -dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP -kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc -cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U -fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 -N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC -xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 -+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G -A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM -Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG -SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h -mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk -ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 -tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c -2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t -HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Label: "GlobalSign Root CA - R3" -# Serial: 4835703278459759426209954 -# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 -# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad -# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b ------BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 -MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 -RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT -gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm -KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd -QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ -XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o -LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU -RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp -jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK -6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX -mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs -Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH -WD9f ------END CERTIFICATE----- - -# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" -# Serial: 6047274297262753887 -# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 -# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa -# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE -BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h -cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy -MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg -Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 -thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM -cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG -L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i -NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h -X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b -m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy -Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja -EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T -KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF -6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh -OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD -VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD -VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp -cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv -ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl -AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF -661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 -am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 -ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 -PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS -3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k -SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF -3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM -ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g -StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz -Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB -jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V ------END CERTIFICATE----- - -# Issuer: CN=Izenpe.com O=IZENPE S.A. -# Subject: CN=Izenpe.com O=IZENPE S.A. -# Label: "Izenpe.com" -# Serial: 917563065490389241595536686991402621 -# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 -# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 -# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- - -# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Label: "Chambers of Commerce Root - 2008" -# Serial: 11806822484801597146 -# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 -# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c -# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 ------BEGIN CERTIFICATE----- -MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz -IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz -MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj -dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw -EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp -MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 -28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq -VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q -DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR -5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL -ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a -Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl -UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s -+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 -Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj -ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx -hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV -HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 -+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN -YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t -L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy -ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt -IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV -HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w -DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW -PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF -5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 -glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH -FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 -pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD -xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG -tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq -jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De -fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg -OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ -d0jQ ------END CERTIFICATE----- - -# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Label: "Global Chambersign Root - 2008" -# Serial: 14541511773111788494 -# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 -# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c -# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca ------BEGIN CERTIFICATE----- -MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD -aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx -MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy -cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG -A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl -BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI -hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed -KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 -G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 -zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 -ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG -HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 -Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V -yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e -beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r -6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh -wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog -zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW -BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr -ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp -ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk -cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt -YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC -CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow -KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI -hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ -UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz -X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x -fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz -a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd -Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd -SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O -AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso -M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge -v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z -09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B ------END CERTIFICATE----- - -# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Label: "Go Daddy Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 -# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b -# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT -EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp -ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz -NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH -EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE -AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD -E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH -/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy -DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh -GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR -tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA -AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE -FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX -WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu -9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr -gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo -2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO -LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI -4uJEvlz36hz1 ------END CERTIFICATE----- - -# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 -# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e -# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs -ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw -MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 -b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj -aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp -Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg -nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 -HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N -Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN -dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 -HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G -CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU -sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 -4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg -8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K -pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 -mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 ------END CERTIFICATE----- - -# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Services Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 -# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f -# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 ------BEGIN CERTIFICATE----- -MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs -ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 -MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD -VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy -ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy -dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p -OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 -8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K -Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe -hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk -6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw -DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q -AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI -bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB -ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z -qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd -iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn -0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN -sSi6 ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Commercial O=AffirmTrust -# Subject: CN=AffirmTrust Commercial O=AffirmTrust -# Label: "AffirmTrust Commercial" -# Serial: 8608355977964138876 -# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 -# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 -# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 ------BEGIN CERTIFICATE----- -MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz -dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL -MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp -cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP -Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr -ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL -MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 -yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr -VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ -nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ -KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG -XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj -vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt -Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g -N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC -nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Networking O=AffirmTrust -# Subject: CN=AffirmTrust Networking O=AffirmTrust -# Label: "AffirmTrust Networking" -# Serial: 8957382827206547757 -# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f -# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f -# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b ------BEGIN CERTIFICATE----- -MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz -dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL -MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp -cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y -YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua -kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL -QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp -6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG -yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i -QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ -KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO -tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu -QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ -Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u -olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 -x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium O=AffirmTrust -# Subject: CN=AffirmTrust Premium O=AffirmTrust -# Label: "AffirmTrust Premium" -# Serial: 7893706540734352110 -# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 -# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 -# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a ------BEGIN CERTIFICATE----- -MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz -dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG -A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U -cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf -qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ -JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ -+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS -s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 -HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 -70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG -V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S -qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S -5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia -C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX -OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE -FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ -BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 -KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg -Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B -8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ -MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc -0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ -u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF -u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH -YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 -GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO -RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e -KeC2uAloGRwYQw== ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust -# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust -# Label: "AffirmTrust Premium ECC" -# Serial: 8401224907861490260 -# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d -# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb -# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 ------BEGIN CERTIFICATE----- -MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC -VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ -cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ -BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt -VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D -0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 -ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G -A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs -aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I -flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== ------END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA" -# Serial: 279744 -# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 -# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e -# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e ------BEGIN CERTIFICATE----- -MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM -MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D -ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU -cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 -WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg -Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw -IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH -UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM -TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU -BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM -kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x -AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV -HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y -sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL -I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 -J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY -VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI -03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= ------END CERTIFICATE----- - -# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Label: "TWCA Root Certification Authority" -# Serial: 1 -# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 -# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 -# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 ------BEGIN CERTIFICATE----- -MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES -MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU -V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz -WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO -LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE -AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH -K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX -RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z -rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx -3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq -hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC -MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls -XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D -lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn -aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ -YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== ------END CERTIFICATE----- - -# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Label: "Security Communication RootCA2" -# Serial: 0 -# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 -# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 -# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl -MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe -U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX -DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy -dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj -YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV -OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr -zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM -VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ -hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO -ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw -awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs -OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 -DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF -coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc -okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 -t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy -1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ -SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 ------END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions RootCA 2011" -# Serial: 0 -# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 -# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d -# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 ------BEGIN CERTIFICATE----- -MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix -RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 -dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p -YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw -NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK -EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl -cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl -c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz -dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ -fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns -bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD -75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP -FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV -HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp -5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu -b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA -A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p -6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 -TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 -dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys -Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI -l7WdmplNsDz4SgCbZN2fOUvRJ9e4 ------END CERTIFICATE----- - -# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Label: "Actalis Authentication Root CA" -# Serial: 6271844772424770508 -# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 -# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac -# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 ------BEGIN CERTIFICATE----- -MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE -BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w -MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 -IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC -SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 -ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv -UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX -4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 -KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ -gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb -rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ -51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F -be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe -KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F -v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn -fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 -jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz -ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt -ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL -e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 -jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz -WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V -SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j -pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX -X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok -fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R -K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU -ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU -LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT -LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== ------END CERTIFICATE----- - -# Issuer: O=Trustis Limited OU=Trustis FPS Root CA -# Subject: O=Trustis Limited OU=Trustis FPS Root CA -# Label: "Trustis FPS Root CA" -# Serial: 36053640375399034304724988975563710553 -# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d -# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 -# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d ------BEGIN CERTIFICATE----- -MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF -MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL -ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx -MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc -MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ -AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH -iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj -vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA -0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB -OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ -BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E -FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 -GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW -zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 -1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE -f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F -jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN -ZetX2fNXlrtIzYE= ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 2 Root CA" -# Serial: 2 -# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 -# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 -# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 ------BEGIN CERTIFICATE----- -MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg -Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow -TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw -HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB -BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr -6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV -L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 -1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx -MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ -QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB -arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr -Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi -FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS -P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN -9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP -AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz -uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h -9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s -A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t -OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo -+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 -KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 -DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us -H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ -I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 -5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h -3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz -Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 3 Root CA" -# Serial: 2 -# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec -# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 -# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d ------BEGIN CERTIFICATE----- -MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg -Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow -TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw -HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB -BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y -ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E -N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 -tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX -0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c -/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X -KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY -zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS -O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D -34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP -K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 -AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv -Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj -QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV -cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS -IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 -HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa -O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv -033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u -dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE -kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 -3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD -u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq -4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= ------END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 3" -# Serial: 1 -# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef -# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 -# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd ------BEGIN CERTIFICATE----- -MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx -KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd -BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl -YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 -OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy -aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 -ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN -8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ -RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 -hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 -ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM -EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 -A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy -WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ -1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 -6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT -91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml -e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p -TpPDpFQUWw== ------END CERTIFICATE----- - -# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Label: "EE Certification Centre Root CA" -# Serial: 112324828676200291871926431888494945866 -# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f -# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 -# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 -czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG -CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy -MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl -ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS -b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy -euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO -bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw -WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d -MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE -1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ -zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB -BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF -BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV -v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG -E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u -uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW -iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v -GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 2009" -# Serial: 623603 -# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f -# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 -# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 ------BEGIN CERTIFICATE----- -MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF -MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD -bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha -ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM -HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 -UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 -tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R -ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM -lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp -/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G -A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G -A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj -dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy -MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl -cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js -L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL -BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni -acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 -o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K -zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 -PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y -Johw1+qRzT65ysCQblrGXnRl11z+o+I= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 EV 2009" -# Serial: 623604 -# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 -# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 -# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 ------BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF -MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD -bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw -NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV -BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn -ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 -3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z -qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR -p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 -HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw -ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea -HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw -Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh -c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E -RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt -dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku -Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp -3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 -nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF -CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na -xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX -KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 ------END CERTIFICATE----- - -# Issuer: CN=CA Disig Root R2 O=Disig a.s. -# Subject: CN=CA Disig Root R2 O=Disig a.s. -# Label: "CA Disig Root R2" -# Serial: 10572350602393338211 -# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 -# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 -# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 ------BEGIN CERTIFICATE----- -MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV -BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu -MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy -MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx -EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw -ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe -NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH -PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I -x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe -QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR -yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO -QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 -H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ -QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD -i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs -nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 -rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud -DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI -hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM -tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf -GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb -lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka -+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal -TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i -nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 -gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr -G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os -zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x -L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL ------END CERTIFICATE----- - -# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Label: "ACCVRAIZ1" -# Serial: 6828503384748696800 -# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 -# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 -# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 ------BEGIN CERTIFICATE----- -MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE -AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw -CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ -BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND -VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb -qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY -HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo -G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA -lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr -IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ -0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH -k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 -4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO -m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa -cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl -uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI -KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls -ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG -AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 -VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT -VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG -CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA -cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA -QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA -7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA -cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA -QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA -czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu -aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt -aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud -DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF -BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp -D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU -JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m -AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD -vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms -tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH -7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h -I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA -h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF -d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H -pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 ------END CERTIFICATE----- - -# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Label: "TWCA Global Root CA" -# Serial: 3262 -# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 -# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 -# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b ------BEGIN CERTIFICATE----- -MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx -EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT -VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 -NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT -B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF -10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz -0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh -MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH -zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc -46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 -yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi -laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP -oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA -BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE -qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm -4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB -/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL -1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn -LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF -H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo -RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ -nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh -15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW -6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW -nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j -wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz -aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy -KwbQBM0= ------END CERTIFICATE----- - -# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Label: "TeliaSonera Root CA v1" -# Serial: 199041966741090107964904287217786801558 -# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c -# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 -# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 ------BEGIN CERTIFICATE----- -MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw -NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv -b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD -VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F -VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 -7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X -Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ -/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs -81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm -dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe -Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu -sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 -pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs -slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ -arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD -VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG -9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl -dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx -0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj -TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed -Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 -Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI -OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 -vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW -t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn -HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx -SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= ------END CERTIFICATE----- - -# Issuer: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi -# Subject: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi -# Label: "E-Tugra Certification Authority" -# Serial: 7667447206703254355 -# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 -# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 -# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c ------BEGIN CERTIFICATE----- -MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV -BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC -aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV -BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 -Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz -MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ -BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp -em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN -ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY -B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH -D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF -Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo -q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D -k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH -fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut -dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM -ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 -zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn -rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX -U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 -Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 -XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF -Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR -HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY -GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c -77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 -+GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK -vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 -FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl -yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P -AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD -y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d -NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== ------END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 2" -# Serial: 1 -# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a -# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 -# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 ------BEGIN CERTIFICATE----- -MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx -KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd -BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl -YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 -OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy -aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 -ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd -AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC -FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi -1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq -jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ -wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ -WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy -NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC -uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw -IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 -g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN -9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP -BSeOE6Fuwg== ------END CERTIFICATE----- - -# Issuer: CN=Atos TrustedRoot 2011 O=Atos -# Subject: CN=Atos TrustedRoot 2011 O=Atos -# Label: "Atos TrustedRoot 2011" -# Serial: 6643877497813316402 -# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 -# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 -# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE -AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG -EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM -FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC -REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp -Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM -VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ -SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ -4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L -cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi -eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG -A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 -DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j -vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP -DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc -maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D -lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv -KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 1 G3" -# Serial: 687049649626669250736271037606554624078720034195 -# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab -# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 -# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 -MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV -wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe -rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 -68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh -4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp -UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o -abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc -3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G -KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt -hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO -Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt -zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD -ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC -MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 -cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN -qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 -YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv -b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 -8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k -NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj -ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp -q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt -nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2 G3" -# Serial: 390156079458959257446133169266079962026824725800 -# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 -# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 -# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 -MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf -qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW -n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym -c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ -O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 -o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j -IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq -IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz -8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh -vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l -7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG -cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD -ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 -AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC -roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga -W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n -lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE -+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV -csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd -dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg -KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM -HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 -WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3 G3" -# Serial: 268090761170461462463995952157327242137089239581 -# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 -# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d -# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 -MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR -/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu -FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR -U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c -ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR -FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k -A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw -eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl -sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp -VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q -A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ -ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD -ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px -KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI -FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv -oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg -u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP -0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf -3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl -8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ -DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN -PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ -ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G2" -# Serial: 15385348160840213938643033620894905419 -# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d -# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f -# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 ------BEGIN CERTIFICATE----- -MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA -n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc -biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp -EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA -bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu -YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB -AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW -BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI -QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I -0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni -lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 -B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv -ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo -IhNzbM8m9Yop5w== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G3" -# Serial: 15459312981008553731928384953135426796 -# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb -# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 -# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 ------BEGIN CERTIFICATE----- -MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw -CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu -ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg -RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV -UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu -Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq -hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf -Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q -RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ -BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD -AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY -JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv -6pZjamVFkpUBtA== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G2" -# Serial: 4293743540046975378534879503202253541 -# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 -# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 -# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f ------BEGIN CERTIFICATE----- -MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH -MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI -2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx -1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ -q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz -tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ -vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP -BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV -5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY -1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 -NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG -Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 -8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe -pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl -MrY= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G3" -# Serial: 7089244469030293291760083333884364146 -# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca -# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e -# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 ------BEGIN CERTIFICATE----- -MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw -CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu -ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe -Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw -EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x -IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG -fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO -Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd -BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx -AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ -oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 -sycX ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Trusted Root G4" -# Serial: 7451500558977370777930084869016614236 -# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 -# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 -# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 ------BEGIN CERTIFICATE----- -MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg -RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV -UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu -Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y -ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If -xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV -ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO -DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ -jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ -CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi -EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM -fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY -uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK -chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t -9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD -ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 -SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd -+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc -fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa -sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N -cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N -0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie -4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI -r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 -/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm -gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ ------END CERTIFICATE----- - -# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Label: "COMODO RSA Certification Authority" -# Serial: 101909084537582093308941363524873193117 -# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 -# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 -# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 ------BEGIN CERTIFICATE----- -MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB -hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV -BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 -MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT -EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR -Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR -6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X -pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC -9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV -/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf -Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z -+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w -qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah -SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC -u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf -Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq -crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E -FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB -/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl -wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM -4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV -2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna -FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ -CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK -boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke -jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL -S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb -QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl -0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB -NVOFBkpdn627G190 ------END CERTIFICATE----- - -# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Label: "USERTrust RSA Certification Authority" -# Serial: 2645093764781058787591871645665788717 -# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 -# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e -# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 ------BEGIN CERTIFICATE----- -MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB -iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl -cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV -BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw -MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV -BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU -aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B -3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY -tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ -Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 -VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT -79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 -c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT -Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l -c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee -UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE -Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd -BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF -Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO -VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 -ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs -8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR -iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze -Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ -XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ -qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB -VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB -L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG -jjxDah2nGN59PRbxYvnKkKj9 ------END CERTIFICATE----- - -# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Label: "USERTrust ECC Certification Authority" -# Serial: 123013823720199481456569720443997572134 -# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 -# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 -# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a ------BEGIN CERTIFICATE----- -MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL -MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl -eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT -JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx -MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT -Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg -VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo -I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng -o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G -A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB -zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW -RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Label: "GlobalSign ECC Root CA - R4" -# Serial: 14367148294922964480859022125800977897474 -# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e -# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb -# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c ------BEGIN CERTIFICATE----- -MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk -MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH -bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX -DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD -QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ -FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F -uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX -kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs -ewv4n4Q= ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Label: "GlobalSign ECC Root CA - R5" -# Serial: 32785792099990507226680698011560947931244 -# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 -# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa -# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 ------BEGIN CERTIFICATE----- -MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk -MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH -bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX -DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD -QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu -MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc -8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke -hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI -KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg -515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO -xwy8p2Fp8fc74SrL+SvzZpA3 ------END CERTIFICATE----- - -# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G3" -# Serial: 10003001 -# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 -# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc -# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 ------BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX -DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP -cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW -IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX -xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy -KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR -9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az -5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 -6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 -Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP -bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt -BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt -XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF -MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd -INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD -U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp -LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 -Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp -gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh -/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw -0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A -fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq -4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR -1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ -QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM -94B7IWcnMFk= ------END CERTIFICATE----- - -# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden -# Label: "Staat der Nederlanden EV Root CA" -# Serial: 10000013 -# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba -# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb -# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a ------BEGIN CERTIFICATE----- -MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y -MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg -TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS -b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS -M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC -UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d -Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p -rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l -pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb -j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC -KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS -/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X -cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH -1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP -px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB -/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 -MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI -eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u -2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS -v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC -wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy -CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e -vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 -Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa -Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL -eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 -FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc -7uzXLg== ------END CERTIFICATE----- - -# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust -# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust -# Label: "IdenTrust Commercial Root CA 1" -# Serial: 13298821034946342390520003877796839426 -# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 -# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 -# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK -MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu -VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw -MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw -JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT -3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU -+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp -S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 -bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi -T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL -vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK -Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK -dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT -c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv -l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N -iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD -ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH -6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt -LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 -nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 -+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK -W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT -AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq -l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG -4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ -mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A -7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H ------END CERTIFICATE----- - -# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust -# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust -# Label: "IdenTrust Public Sector Root CA 1" -# Serial: 13298821034946342390521976156843933698 -# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba -# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd -# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f ------BEGIN CERTIFICATE----- -MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN -MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu -VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN -MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 -MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 -ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy -RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS -bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF -/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R -3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw -EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy -9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V -GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ -2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV -WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD -W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ -BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN -AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj -t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV -DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 -TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G -lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW -mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df -WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 -+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ -tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA -GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv -8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - G2" -# Serial: 1246989352 -# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 -# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 -# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 ------BEGIN CERTIFICATE----- -MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 -cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs -IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz -dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy -NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu -dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt -dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 -aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T -RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN -cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW -wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 -U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 -jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP -BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN -BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ -jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ -Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v -1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R -nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH -VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - EC1" -# Serial: 51543124481930649114116133369 -# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc -# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 -# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 ------BEGIN CERTIFICATE----- -MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG -A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 -d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu -dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq -RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy -MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD -VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 -L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g -Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD -ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi -A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt -ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH -Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O -BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC -R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX -hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G ------END CERTIFICATE----- - -# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority -# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority -# Label: "CFCA EV ROOT" -# Serial: 407555286 -# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 -# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 -# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd ------BEGIN CERTIFICATE----- -MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD -TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y -aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx -MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j -aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP -T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 -sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL -TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 -/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp -7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz -EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt -hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP -a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot -aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg -TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV -PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv -cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL -tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd -BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB -ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT -ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL -jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS -ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy -P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 -xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d -Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN -5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe -/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z -AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ -5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su ------END CERTIFICATE----- - -# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 -# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 -# Label: "Certinomis - Root CA" -# Serial: 1 -# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f -# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 -# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 ------BEGIN CERTIFICATE----- -MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET -MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb -BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz -MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx -FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g -Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 -fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl -LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV -WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF -TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb -5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc -CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri -wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ -wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG -m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 -F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng -WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 -2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF -AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ -0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw -F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS -g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj -qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN -h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ -ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V -btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj -Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ -8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW -gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= ------END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GB CA" -# Serial: 157768595616588414422159278966750757568 -# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d -# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed -# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 ------BEGIN CERTIFICATE----- -MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt -MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg -Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i -YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x -CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG -b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh -bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 -HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx -WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX -1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk -u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P -99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r -M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB -BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh -cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 -gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO -ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf -aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic -Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= ------END CERTIFICATE----- - -# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. -# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. -# Label: "SZAFIR ROOT CA2" -# Serial: 357043034767186914217277344587386743377558296292 -# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 -# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de -# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe ------BEGIN CERTIFICATE----- -MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL -BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 -ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw -NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L -cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg -Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN -QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT -3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw -3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 -3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 -BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN -XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD -AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF -AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw -8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG -nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP -oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy -d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg -LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== ------END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA 2" -# Serial: 44979900017204383099463764357512596969 -# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 -# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 -# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 ------BEGIN CERTIFICATE----- -MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB -gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu -QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG -A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz -OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ -VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 -b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA -DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn -0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB -OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE -fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E -Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m -o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i -sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW -OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez -Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS -adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n -3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD -AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC -AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ -F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf -CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 -XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm -djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ -WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb -AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq -P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko -b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj -XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P -5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi -DrW5viSP ------END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions RootCA 2015" -# Serial: 0 -# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce -# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 -# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 ------BEGIN CERTIFICATE----- -MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix -DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k -IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT -N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v -dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG -A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh -ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx -QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 -dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA -4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 -AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 -4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C -ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV -9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD -gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 -Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq -NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko -LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc -Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV -HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd -ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I -XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI -M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot -9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V -Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea -j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh -X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ -l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf -bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 -pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK -e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 -vm9qp/UsQu0yrbYhnr68 ------END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" -# Serial: 0 -# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef -# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 -# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 ------BEGIN CERTIFICATE----- -MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN -BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl -c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl -bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv -b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ -BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj -YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 -MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 -dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg -QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa -jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC -MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi -C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep -lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof -TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR ------END CERTIFICATE----- - -# Issuer: CN=ISRG Root X1 O=Internet Security Research Group -# Subject: CN=ISRG Root X1 O=Internet Security Research Group -# Label: "ISRG Root X1" -# Serial: 172886928669790476064670243504169061120 -# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e -# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 -# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- - -# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM -# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM -# Label: "AC RAIZ FNMT-RCM" -# Serial: 485876308206448804701554682760554759 -# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d -# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 -# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa ------BEGIN CERTIFICATE----- -MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx -CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ -WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ -BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG -Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ -yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf -BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz -WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF -tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z -374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC -IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL -mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 -wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS -MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 -ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet -UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H -YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 -LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD -nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 -RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM -LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf -77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N -JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm -fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp -6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp -1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B -9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok -RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv -uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 1 O=Amazon -# Subject: CN=Amazon Root CA 1 O=Amazon -# Label: "Amazon Root CA 1" -# Serial: 143266978916655856878034712317230054538369994 -# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 -# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 -# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e ------BEGIN CERTIFICATE----- -MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF -ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 -b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL -MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv -b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj -ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM -9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw -IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 -VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L -93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm -jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA -A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI -U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs -N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv -o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU -5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy -rqXRfboQnoZsG4q5WTP468SQvvG5 ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 2 O=Amazon -# Subject: CN=Amazon Root CA 2 O=Amazon -# Label: "Amazon Root CA 2" -# Serial: 143266982885963551818349160658925006970653239 -# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 -# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a -# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 ------BEGIN CERTIFICATE----- -MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF -ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 -b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL -MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv -b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK -gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ -W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg -1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K -8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r -2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me -z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR -8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj -mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz -7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 -+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI -0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB -Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm -UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 -LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY -+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS -k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl -7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm -btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl -urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ -fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 -n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE -76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H -9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT -4PsJYGw= ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 3 O=Amazon -# Subject: CN=Amazon Root CA 3 O=Amazon -# Label: "Amazon Root CA 3" -# Serial: 143266986699090766294700635381230934788665930 -# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 -# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e -# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 ------BEGIN CERTIFICATE----- -MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 -MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g -Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG -A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg -Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl -ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr -ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr -BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM -YyRIHN8wfdVoOw== ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 4 O=Amazon -# Subject: CN=Amazon Root CA 4 O=Amazon -# Label: "Amazon Root CA 4" -# Serial: 143266989758080763974105200630763877849284878 -# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd -# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be -# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 ------BEGIN CERTIFICATE----- -MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 -MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g -Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG -A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg -Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi -9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk -M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB -/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB -MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw -CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW -1KyLa2tJElMzrdfkviT8tQp21KW8EA== ------END CERTIFICATE----- - -# Issuer: CN=LuxTrust Global Root 2 O=LuxTrust S.A. -# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A. -# Label: "LuxTrust Global Root 2" -# Serial: 59914338225734147123941058376788110305822489521 -# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c -# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f -# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5 ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- - -# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM -# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM -# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" -# Serial: 1 -# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 -# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca -# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 ------BEGIN CERTIFICATE----- -MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx -GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp -bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w -KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 -BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy -dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG -EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll -IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU -QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT -TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg -LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 -a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr -LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr -N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X -YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ -iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f -AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH -V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL -BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh -AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf -IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 -lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c -8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf -lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= ------END CERTIFICATE----- - -# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. -# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. -# Label: "GDCA TrustAUTH R5 ROOT" -# Serial: 9009899650740120186 -# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 -# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 -# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 ------BEGIN CERTIFICATE----- -MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE -BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ -IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 -MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV -BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w -HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj -Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj -TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u -KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj -qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm -MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 -ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP -zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk -L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC -jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA -HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC -AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB -/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg -p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm -DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 -COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry -L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf -JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg -IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io -2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV -09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ -XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq -T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe -MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== ------END CERTIFICATE----- - -# Issuer: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Subject: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Label: "TrustCor RootCert CA-1" -# Serial: 15752444095811006489 -# MD5 Fingerprint: 6e:85:f1:dc:1a:00:d3:22:d5:b2:b2:ac:6b:37:05:45 -# SHA1 Fingerprint: ff:bd:cd:e7:82:c8:43:5e:3c:6f:26:86:5c:ca:a8:3a:45:5b:c3:0a -# SHA256 Fingerprint: d4:0e:9c:86:cd:8f:e4:68:c1:77:69:59:f4:9e:a7:74:fa:54:86:84:b6:c4:06:f3:90:92:61:f4:dc:e2:57:5c ------BEGIN CERTIFICATE----- -MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD -VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk -MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U -cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29y -IFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkxMjMxMTcyMzE2WjCB -pDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFuYW1h -IENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUG -A1UECwweVHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZU -cnVzdENvciBSb290Q2VydCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAv463leLCJhJrMxnHQFgKq1mqjQCj/IDHUHuO1CAmujIS2CNUSSUQIpid -RtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4pQa81QBeCQryJ3pS/C3V -seq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0JEsq1pme -9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CV -EY4hgLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorW -hnAbJN7+KIor0Gqw/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/ -DeOxCbeKyKsZn3MzUOcwHwYDVR0jBBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcw -DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD -ggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5mDo4Nvu7Zp5I -/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf -ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZ -yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts -L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN -zl/HHk484IkzlQsPpTLWPFp5LBk= ------END CERTIFICATE----- - -# Issuer: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Subject: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Label: "TrustCor RootCert CA-2" -# Serial: 2711694510199101698 -# MD5 Fingerprint: a2:e1:f8:18:0b:ba:45:d5:c7:41:2a:bb:37:52:45:64 -# SHA1 Fingerprint: b8:be:6d:cb:56:f1:55:b9:63:d4:12:ca:4e:06:34:c7:94:b2:1c:c0 -# SHA256 Fingerprint: 07:53:e9:40:37:8c:1b:d5:e3:83:6e:39:5d:ae:a5:cb:83:9e:50:46:f1:bd:0e:ae:19:51:cf:10:fe:c7:c9:65 ------BEGIN CERTIFICATE----- -MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV -BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw -IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy -dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEfMB0GA1UEAwwWVHJ1c3RDb3Ig -Um9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEyMzExNzI2MzlaMIGk -MQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEg -Q2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYD -VQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRy -dXN0Q29yIFJvb3RDZXJ0IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCnIG7CKqJiJJWQdsg4foDSq8GbZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+ -QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9NkRvRUqdw6VC0xK5mC8tkq -1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1oYxOdqHp -2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nK -DOObXUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hape -az6LMvYHL1cEksr1/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF -3wP+TfSvPd9cW436cOGlfifHhi5qjxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88 -oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQPeSghYA2FFn3XVDjxklb9tTNM -g9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+CtgrKAmrhQhJ8Z3 -mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh -8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAd -BgNVHQ4EFgQU2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6U -nrybPZx9mCAZ5YwwYrIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw -DQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/hOsh80QA9z+LqBrWyOrsGS2h60COX -dKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnpkpfbsEZC89NiqpX+ -MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv2wnL -/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RX -CI/hOWB3S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYa -ZH9bDTMJBzN7Bj8RpFxwPIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW -2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dvDDqPys/cA8GiCcjl/YBeyGBCARsaU1q7 -N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYURpFHmygk71dSTlxCnKr3 -Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANExdqtvArB -As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp -5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu -1uwJ ------END CERTIFICATE----- - -# Issuer: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Subject: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Label: "TrustCor ECA-1" -# Serial: 9548242946988625984 -# MD5 Fingerprint: 27:92:23:1d:0a:f5:40:7c:e9:e6:6b:9d:d8:f5:e7:6c -# SHA1 Fingerprint: 58:d1:df:95:95:67:6b:63:c0:f0:5b:1c:17:4d:8b:84:0b:c8:78:bd -# SHA256 Fingerprint: 5a:88:5d:b1:9c:01:d9:12:c5:75:93:88:93:8c:af:bb:df:03:1a:b2:d4:8e:91:ee:15:58:9b:42:97:1d:03:9c ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD -VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk -MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U -cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29y -IEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3MjgwN1owgZwxCzAJBgNV -BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw -IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy -dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3Ig -RUNBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb -3w9U73NjKYKtR8aja+3+XzP4Q1HpGjORMRegdMTUpwHmspI+ap3tDvl0mEDTPwOA -BoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23xFUfJ3zSCNV2HykVh0A5 -3ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmcp0yJF4Ou -owReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/ -wZ0+fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZF -ZtS6mFjBAgMBAAGjYzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAf -BgNVHSMEGDAWgBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/ -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEABT41XBVwm8nHc2Fv -civUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u/ukZMjgDfxT2 -AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F -hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50 -soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI -WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi -tJ/X5g== ------END CERTIFICATE----- - -# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation -# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation -# Label: "SSL.com Root Certification Authority RSA" -# Serial: 8875640296558310041 -# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 -# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb -# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 ------BEGIN CERTIFICATE----- -MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE -BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK -DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz -OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv -dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv -bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R -xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX -qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC -C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 -6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh -/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF -YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E -JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc -US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 -ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm -+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi -M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G -A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV -cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc -Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs -PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ -q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 -cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr -a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I -H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y -K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu -nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf -oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY -Ic2wBlX7Jz9TkHCpBB5XJ7k= ------END CERTIFICATE----- - -# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation -# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation -# Label: "SSL.com Root Certification Authority ECC" -# Serial: 8495723813297216424 -# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e -# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a -# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 ------BEGIN CERTIFICATE----- -MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC -VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T -U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 -aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz -WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 -b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS -b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB -BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI -7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg -CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud -EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD -VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T -kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ -gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl ------END CERTIFICATE----- - -# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation -# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation -# Label: "SSL.com EV Root Certification Authority RSA R2" -# Serial: 6248227494352943350 -# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 -# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a -# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c ------BEGIN CERTIFICATE----- -MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV -BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE -CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy -MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G -A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD -DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq -M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf -OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa -4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 -HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR -aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA -b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ -Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV -PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO -pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu -UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY -MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV -HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 -9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW -s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 -Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg -cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM -79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz -/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt -ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm -Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK -QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ -w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi -S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 -mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== ------END CERTIFICATE----- - -# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation -# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation -# Label: "SSL.com EV Root Certification Authority ECC" -# Serial: 3182246526754555285 -# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 -# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d -# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 ------BEGIN CERTIFICATE----- -MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC -VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T -U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx -NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv -dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv -bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 -AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA -VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku -WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP -MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX -5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ -ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg -h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 -# Label: "GlobalSign Root CA - R6" -# Serial: 1417766617973444989252670301619537 -# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae -# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 -# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 ------BEGIN CERTIFICATE----- -MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg -MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh -bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx -MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET -MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ -KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI -xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k -ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD -aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw -LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw -1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX -k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 -SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h -bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n -WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY -rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce -MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD -AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu -bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN -nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt -Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 -55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj -vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf -cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz -oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp -nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs -pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v -JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R -8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 -5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= ------END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GC CA" -# Serial: 44084345621038548146064804565436152554 -# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 -# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 -# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d ------BEGIN CERTIFICATE----- -MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw -CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 -bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg -Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ -BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu -ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS -b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni -eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W -p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E -BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T -rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV -57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg -Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R1 O=Google Trust Services LLC -# Subject: CN=GTS Root R1 O=Google Trust Services LLC -# Label: "GTS Root R1" -# Serial: 146587175971765017618439757810265552097 -# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85 -# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8 -# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH -MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM -QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy -MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl -cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM -f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX -mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7 -zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P -fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc -vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4 -Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp -zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO -Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW -k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+ -DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF -lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW -Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 -d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z -XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR -gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3 -d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv -J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg -DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM -+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy -F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 -SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws -E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R2 O=Google Trust Services LLC -# Subject: CN=GTS Root R2 O=Google Trust Services LLC -# Label: "GTS Root R2" -# Serial: 146587176055767053814479386953112547951 -# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b -# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d -# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH -MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM -QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy -MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl -cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv -CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg -GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu -XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd -re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu -PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1 -mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K -8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj -x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR -nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0 -kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok -twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp -8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT -vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT -z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA -pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb -pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB -R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R -RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk -0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC -5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF -izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn -yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R3 O=Google Trust Services LLC -# Subject: CN=GTS Root R3 O=Google Trust Services LLC -# Label: "GTS Root R3" -# Serial: 146587176140553309517047991083707763997 -# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25 -# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5 -# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5 ------BEGIN CERTIFICATE----- -MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA -IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout -736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A -DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud -DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk -fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA -njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R4 O=Google Trust Services LLC -# Subject: CN=GTS Root R4 O=Google Trust Services LLC -# Label: "GTS Root R4" -# Serial: 146587176229350439916519468929765261721 -# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26 -# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb -# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd ------BEGIN CERTIFICATE----- -MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA -IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu -hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l -xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud -DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0 -CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx -sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w== ------END CERTIFICATE----- - -# Issuer: CN=UCA Global G2 Root O=UniTrust -# Subject: CN=UCA Global G2 Root O=UniTrust -# Label: "UCA Global G2 Root" -# Serial: 124779693093741543919145257850076631279 -# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 -# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a -# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c ------BEGIN CERTIFICATE----- -MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 -MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH -bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x -CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds -b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr -b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 -kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm -VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R -VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc -C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj -tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY -D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv -j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl -NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 -iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP -O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV -ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj -L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 -1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl -1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU -b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV -PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj -y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb -EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg -DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI -+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy -YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX -UB+K+wb1whnw0A== ------END CERTIFICATE----- - -# Issuer: CN=UCA Extended Validation Root O=UniTrust -# Subject: CN=UCA Extended Validation Root O=UniTrust -# Label: "UCA Extended Validation Root" -# Serial: 106100277556486529736699587978573607008 -# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 -# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a -# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH -MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF -eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx -MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV -BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog -D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS -sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop -O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk -sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi -c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj -VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz -KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ -TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G -sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs -1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD -fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T -AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN -l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR -ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ -VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 -c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp -4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s -t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj -2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO -vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C -xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx -cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM -fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax ------END CERTIFICATE----- - -# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 -# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 -# Label: "Certigna Root CA" -# Serial: 269714418870597844693661054334862075617 -# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 -# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 -# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 ------BEGIN CERTIFICATE----- -MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw -WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw -MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x -MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD -VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX -BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw -ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO -ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M -CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu -I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm -TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh -C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf -ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz -IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT -Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k -JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 -hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB -GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE -FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of -1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov -L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo -dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr -aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq -hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L -6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG -HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 -0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB -lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi -o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 -gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v -faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 -Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh -jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw -3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= ------END CERTIFICATE----- diff --git a/lib/certifi/core.py b/lib/certifi/core.py deleted file mode 100644 index 2d02ea4..0000000 --- a/lib/certifi/core.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -certifi.py -~~~~~~~~~~ - -This module returns the installation location of cacert.pem. -""" -import os - - -def where(): - f = os.path.dirname(__file__) - - return os.path.join(f, 'cacert.pem') - - -if __name__ == '__main__': - print(where()) diff --git a/lib/chardet/__init__.py b/lib/chardet/__init__.py deleted file mode 100644 index 0f9f820..0000000 --- a/lib/chardet/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - - -from .compat import PY2, PY3 -from .universaldetector import UniversalDetector -from .version import __version__, VERSION - - -def detect(byte_str): - """ - Detect the encoding of the given byte string. - - :param byte_str: The byte sequence to examine. - :type byte_str: ``bytes`` or ``bytearray`` - """ - if not isinstance(byte_str, bytearray): - if not isinstance(byte_str, bytes): - raise TypeError('Expected object of type bytes or bytearray, got: ' - '{0}'.format(type(byte_str))) - else: - byte_str = bytearray(byte_str) - detector = UniversalDetector() - detector.feed(byte_str) - return detector.close() diff --git a/lib/chardet/big5freq.py b/lib/chardet/big5freq.py deleted file mode 100644 index 38f3251..0000000 --- a/lib/chardet/big5freq.py +++ /dev/null @@ -1,386 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# Big5 frequency table -# by Taiwan's Mandarin Promotion Council -# <http://www.edu.tw:81/mandr/> -# -# 128 --> 0.42261 -# 256 --> 0.57851 -# 512 --> 0.74851 -# 1024 --> 0.89384 -# 2048 --> 0.97583 -# -# Ideal Distribution Ratio = 0.74851/(1-0.74851) =2.98 -# Random Distribution Ration = 512/(5401-512)=0.105 -# -# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR - -BIG5_TYPICAL_DISTRIBUTION_RATIO = 0.75 - -#Char to FreqOrder table -BIG5_TABLE_SIZE = 5376 - -BIG5_CHAR_TO_FREQ_ORDER = ( - 1,1801,1506, 255,1431, 198, 9, 82, 6,5008, 177, 202,3681,1256,2821, 110, # 16 -3814, 33,3274, 261, 76, 44,2114, 16,2946,2187,1176, 659,3971, 26,3451,2653, # 32 -1198,3972,3350,4202, 410,2215, 302, 590, 361,1964, 8, 204, 58,4510,5009,1932, # 48 - 63,5010,5011, 317,1614, 75, 222, 159,4203,2417,1480,5012,3555,3091, 224,2822, # 64 -3682, 3, 10,3973,1471, 29,2787,1135,2866,1940, 873, 130,3275,1123, 312,5013, # 80 -4511,2052, 507, 252, 682,5014, 142,1915, 124, 206,2947, 34,3556,3204, 64, 604, # 96 -5015,2501,1977,1978, 155,1991, 645, 641,1606,5016,3452, 337, 72, 406,5017, 80, # 112 - 630, 238,3205,1509, 263, 939,1092,2654, 756,1440,1094,3453, 449, 69,2987, 591, # 128 - 179,2096, 471, 115,2035,1844, 60, 50,2988, 134, 806,1869, 734,2036,3454, 180, # 144 - 995,1607, 156, 537,2907, 688,5018, 319,1305, 779,2145, 514,2379, 298,4512, 359, # 160 -2502, 90,2716,1338, 663, 11, 906,1099,2553, 20,2441, 182, 532,1716,5019, 732, # 176 -1376,4204,1311,1420,3206, 25,2317,1056, 113, 399, 382,1950, 242,3455,2474, 529, # 192 -3276, 475,1447,3683,5020, 117, 21, 656, 810,1297,2300,2334,3557,5021, 126,4205, # 208 - 706, 456, 150, 613,4513, 71,1118,2037,4206, 145,3092, 85, 835, 486,2115,1246, # 224 -1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,5022,2128,2359, 347,3815, 221, # 240 -3558,3135,5023,1956,1153,4207, 83, 296,1199,3093, 192, 624, 93,5024, 822,1898, # 256 -2823,3136, 795,2065, 991,1554,1542,1592, 27, 43,2867, 859, 139,1456, 860,4514, # 272 - 437, 712,3974, 164,2397,3137, 695, 211,3037,2097, 195,3975,1608,3559,3560,3684, # 288 -3976, 234, 811,2989,2098,3977,2233,1441,3561,1615,2380, 668,2077,1638, 305, 228, # 304 -1664,4515, 467, 415,5025, 262,2099,1593, 239, 108, 300, 200,1033, 512,1247,2078, # 320 -5026,5027,2176,3207,3685,2682, 593, 845,1062,3277, 88,1723,2038,3978,1951, 212, # 336 - 266, 152, 149, 468,1899,4208,4516, 77, 187,5028,3038, 37, 5,2990,5029,3979, # 352 -5030,5031, 39,2524,4517,2908,3208,2079, 55, 148, 74,4518, 545, 483,1474,1029, # 368 -1665, 217,1870,1531,3138,1104,2655,4209, 24, 172,3562, 900,3980,3563,3564,4519, # 384 - 32,1408,2824,1312, 329, 487,2360,2251,2717, 784,2683, 4,3039,3351,1427,1789, # 400 - 188, 109, 499,5032,3686,1717,1790, 888,1217,3040,4520,5033,3565,5034,3352,1520, # 416 -3687,3981, 196,1034, 775,5035,5036, 929,1816, 249, 439, 38,5037,1063,5038, 794, # 432 -3982,1435,2301, 46, 178,3278,2066,5039,2381,5040, 214,1709,4521, 804, 35, 707, # 448 - 324,3688,1601,2554, 140, 459,4210,5041,5042,1365, 839, 272, 978,2262,2580,3456, # 464 -2129,1363,3689,1423, 697, 100,3094, 48, 70,1231, 495,3139,2196,5043,1294,5044, # 480 -2080, 462, 586,1042,3279, 853, 256, 988, 185,2382,3457,1698, 434,1084,5045,3458, # 496 - 314,2625,2788,4522,2335,2336, 569,2285, 637,1817,2525, 757,1162,1879,1616,3459, # 512 - 287,1577,2116, 768,4523,1671,2868,3566,2526,1321,3816, 909,2418,5046,4211, 933, # 528 -3817,4212,2053,2361,1222,4524, 765,2419,1322, 786,4525,5047,1920,1462,1677,2909, # 544 -1699,5048,4526,1424,2442,3140,3690,2600,3353,1775,1941,3460,3983,4213, 309,1369, # 560 -1130,2825, 364,2234,1653,1299,3984,3567,3985,3986,2656, 525,1085,3041, 902,2001, # 576 -1475, 964,4527, 421,1845,1415,1057,2286, 940,1364,3141, 376,4528,4529,1381, 7, # 592 -2527, 983,2383, 336,1710,2684,1846, 321,3461, 559,1131,3042,2752,1809,1132,1313, # 608 - 265,1481,1858,5049, 352,1203,2826,3280, 167,1089, 420,2827, 776, 792,1724,3568, # 624 -4214,2443,3281,5050,4215,5051, 446, 229, 333,2753, 901,3818,1200,1557,4530,2657, # 640 -1921, 395,2754,2685,3819,4216,1836, 125, 916,3209,2626,4531,5052,5053,3820,5054, # 656 -5055,5056,4532,3142,3691,1133,2555,1757,3462,1510,2318,1409,3569,5057,2146, 438, # 672 -2601,2910,2384,3354,1068, 958,3043, 461, 311,2869,2686,4217,1916,3210,4218,1979, # 688 - 383, 750,2755,2627,4219, 274, 539, 385,1278,1442,5058,1154,1965, 384, 561, 210, # 704 - 98,1295,2556,3570,5059,1711,2420,1482,3463,3987,2911,1257, 129,5060,3821, 642, # 720 - 523,2789,2790,2658,5061, 141,2235,1333, 68, 176, 441, 876, 907,4220, 603,2602, # 736 - 710, 171,3464, 404, 549, 18,3143,2398,1410,3692,1666,5062,3571,4533,2912,4534, # 752 -5063,2991, 368,5064, 146, 366, 99, 871,3693,1543, 748, 807,1586,1185, 22,2263, # 768 - 379,3822,3211,5065,3212, 505,1942,2628,1992,1382,2319,5066, 380,2362, 218, 702, # 784 -1818,1248,3465,3044,3572,3355,3282,5067,2992,3694, 930,3283,3823,5068, 59,5069, # 800 - 585, 601,4221, 497,3466,1112,1314,4535,1802,5070,1223,1472,2177,5071, 749,1837, # 816 - 690,1900,3824,1773,3988,1476, 429,1043,1791,2236,2117, 917,4222, 447,1086,1629, # 832 -5072, 556,5073,5074,2021,1654, 844,1090, 105, 550, 966,1758,2828,1008,1783, 686, # 848 -1095,5075,2287, 793,1602,5076,3573,2603,4536,4223,2948,2302,4537,3825, 980,2503, # 864 - 544, 353, 527,4538, 908,2687,2913,5077, 381,2629,1943,1348,5078,1341,1252, 560, # 880 -3095,5079,3467,2870,5080,2054, 973, 886,2081, 143,4539,5081,5082, 157,3989, 496, # 896 -4224, 57, 840, 540,2039,4540,4541,3468,2118,1445, 970,2264,1748,1966,2082,4225, # 912 -3144,1234,1776,3284,2829,3695, 773,1206,2130,1066,2040,1326,3990,1738,1725,4226, # 928 - 279,3145, 51,1544,2604, 423,1578,2131,2067, 173,4542,1880,5083,5084,1583, 264, # 944 - 610,3696,4543,2444, 280, 154,5085,5086,5087,1739, 338,1282,3096, 693,2871,1411, # 960 -1074,3826,2445,5088,4544,5089,5090,1240, 952,2399,5091,2914,1538,2688, 685,1483, # 976 -4227,2475,1436, 953,4228,2055,4545, 671,2400, 79,4229,2446,3285, 608, 567,2689, # 992 -3469,4230,4231,1691, 393,1261,1792,2401,5092,4546,5093,5094,5095,5096,1383,1672, # 1008 -3827,3213,1464, 522,1119, 661,1150, 216, 675,4547,3991,1432,3574, 609,4548,2690, # 1024 -2402,5097,5098,5099,4232,3045, 0,5100,2476, 315, 231,2447, 301,3356,4549,2385, # 1040 -5101, 233,4233,3697,1819,4550,4551,5102, 96,1777,1315,2083,5103, 257,5104,1810, # 1056 -3698,2718,1139,1820,4234,2022,1124,2164,2791,1778,2659,5105,3097, 363,1655,3214, # 1072 -5106,2993,5107,5108,5109,3992,1567,3993, 718, 103,3215, 849,1443, 341,3357,2949, # 1088 -1484,5110,1712, 127, 67, 339,4235,2403, 679,1412, 821,5111,5112, 834, 738, 351, # 1104 -2994,2147, 846, 235,1497,1881, 418,1993,3828,2719, 186,1100,2148,2756,3575,1545, # 1120 -1355,2950,2872,1377, 583,3994,4236,2581,2995,5113,1298,3699,1078,2557,3700,2363, # 1136 - 78,3829,3830, 267,1289,2100,2002,1594,4237, 348, 369,1274,2197,2178,1838,4552, # 1152 -1821,2830,3701,2757,2288,2003,4553,2951,2758, 144,3358, 882,4554,3995,2759,3470, # 1168 -4555,2915,5114,4238,1726, 320,5115,3996,3046, 788,2996,5116,2831,1774,1327,2873, # 1184 -3997,2832,5117,1306,4556,2004,1700,3831,3576,2364,2660, 787,2023, 506, 824,3702, # 1200 - 534, 323,4557,1044,3359,2024,1901, 946,3471,5118,1779,1500,1678,5119,1882,4558, # 1216 - 165, 243,4559,3703,2528, 123, 683,4239, 764,4560, 36,3998,1793, 589,2916, 816, # 1232 - 626,1667,3047,2237,1639,1555,1622,3832,3999,5120,4000,2874,1370,1228,1933, 891, # 1248 -2084,2917, 304,4240,5121, 292,2997,2720,3577, 691,2101,4241,1115,4561, 118, 662, # 1264 -5122, 611,1156, 854,2386,1316,2875, 2, 386, 515,2918,5123,5124,3286, 868,2238, # 1280 -1486, 855,2661, 785,2216,3048,5125,1040,3216,3578,5126,3146, 448,5127,1525,5128, # 1296 -2165,4562,5129,3833,5130,4242,2833,3579,3147, 503, 818,4001,3148,1568, 814, 676, # 1312 -1444, 306,1749,5131,3834,1416,1030, 197,1428, 805,2834,1501,4563,5132,5133,5134, # 1328 -1994,5135,4564,5136,5137,2198, 13,2792,3704,2998,3149,1229,1917,5138,3835,2132, # 1344 -5139,4243,4565,2404,3580,5140,2217,1511,1727,1120,5141,5142, 646,3836,2448, 307, # 1360 -5143,5144,1595,3217,5145,5146,5147,3705,1113,1356,4002,1465,2529,2530,5148, 519, # 1376 -5149, 128,2133, 92,2289,1980,5150,4003,1512, 342,3150,2199,5151,2793,2218,1981, # 1392 -3360,4244, 290,1656,1317, 789, 827,2365,5152,3837,4566, 562, 581,4004,5153, 401, # 1408 -4567,2252, 94,4568,5154,1399,2794,5155,1463,2025,4569,3218,1944,5156, 828,1105, # 1424 -4245,1262,1394,5157,4246, 605,4570,5158,1784,2876,5159,2835, 819,2102, 578,2200, # 1440 -2952,5160,1502, 436,3287,4247,3288,2836,4005,2919,3472,3473,5161,2721,2320,5162, # 1456 -5163,2337,2068, 23,4571, 193, 826,3838,2103, 699,1630,4248,3098, 390,1794,1064, # 1472 -3581,5164,1579,3099,3100,1400,5165,4249,1839,1640,2877,5166,4572,4573, 137,4250, # 1488 - 598,3101,1967, 780, 104, 974,2953,5167, 278, 899, 253, 402, 572, 504, 493,1339, # 1504 -5168,4006,1275,4574,2582,2558,5169,3706,3049,3102,2253, 565,1334,2722, 863, 41, # 1520 -5170,5171,4575,5172,1657,2338, 19, 463,2760,4251, 606,5173,2999,3289,1087,2085, # 1536 -1323,2662,3000,5174,1631,1623,1750,4252,2691,5175,2878, 791,2723,2663,2339, 232, # 1552 -2421,5176,3001,1498,5177,2664,2630, 755,1366,3707,3290,3151,2026,1609, 119,1918, # 1568 -3474, 862,1026,4253,5178,4007,3839,4576,4008,4577,2265,1952,2477,5179,1125, 817, # 1584 -4254,4255,4009,1513,1766,2041,1487,4256,3050,3291,2837,3840,3152,5180,5181,1507, # 1600 -5182,2692, 733, 40,1632,1106,2879, 345,4257, 841,2531, 230,4578,3002,1847,3292, # 1616 -3475,5183,1263, 986,3476,5184, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562, # 1632 -4010,4011,2954, 967,2761,2665,1349, 592,2134,1692,3361,3003,1995,4258,1679,4012, # 1648 -1902,2188,5185, 739,3708,2724,1296,1290,5186,4259,2201,2202,1922,1563,2605,2559, # 1664 -1871,2762,3004,5187, 435,5188, 343,1108, 596, 17,1751,4579,2239,3477,3709,5189, # 1680 -4580, 294,3582,2955,1693, 477, 979, 281,2042,3583, 643,2043,3710,2631,2795,2266, # 1696 -1031,2340,2135,2303,3584,4581, 367,1249,2560,5190,3585,5191,4582,1283,3362,2005, # 1712 - 240,1762,3363,4583,4584, 836,1069,3153, 474,5192,2149,2532, 268,3586,5193,3219, # 1728 -1521,1284,5194,1658,1546,4260,5195,3587,3588,5196,4261,3364,2693,1685,4262, 961, # 1744 -1673,2632, 190,2006,2203,3841,4585,4586,5197, 570,2504,3711,1490,5198,4587,2633, # 1760 -3293,1957,4588, 584,1514, 396,1045,1945,5199,4589,1968,2449,5200,5201,4590,4013, # 1776 - 619,5202,3154,3294, 215,2007,2796,2561,3220,4591,3221,4592, 763,4263,3842,4593, # 1792 -5203,5204,1958,1767,2956,3365,3712,1174, 452,1477,4594,3366,3155,5205,2838,1253, # 1808 -2387,2189,1091,2290,4264, 492,5206, 638,1169,1825,2136,1752,4014, 648, 926,1021, # 1824 -1324,4595, 520,4596, 997, 847,1007, 892,4597,3843,2267,1872,3713,2405,1785,4598, # 1840 -1953,2957,3103,3222,1728,4265,2044,3714,4599,2008,1701,3156,1551, 30,2268,4266, # 1856 -5207,2027,4600,3589,5208, 501,5209,4267, 594,3478,2166,1822,3590,3479,3591,3223, # 1872 - 829,2839,4268,5210,1680,3157,1225,4269,5211,3295,4601,4270,3158,2341,5212,4602, # 1888 -4271,5213,4015,4016,5214,1848,2388,2606,3367,5215,4603, 374,4017, 652,4272,4273, # 1904 - 375,1140, 798,5216,5217,5218,2366,4604,2269, 546,1659, 138,3051,2450,4605,5219, # 1920 -2254, 612,1849, 910, 796,3844,1740,1371, 825,3845,3846,5220,2920,2562,5221, 692, # 1936 - 444,3052,2634, 801,4606,4274,5222,1491, 244,1053,3053,4275,4276, 340,5223,4018, # 1952 -1041,3005, 293,1168, 87,1357,5224,1539, 959,5225,2240, 721, 694,4277,3847, 219, # 1968 -1478, 644,1417,3368,2666,1413,1401,1335,1389,4019,5226,5227,3006,2367,3159,1826, # 1984 - 730,1515, 184,2840, 66,4607,5228,1660,2958, 246,3369, 378,1457, 226,3480, 975, # 2000 -4020,2959,1264,3592, 674, 696,5229, 163,5230,1141,2422,2167, 713,3593,3370,4608, # 2016 -4021,5231,5232,1186, 15,5233,1079,1070,5234,1522,3224,3594, 276,1050,2725, 758, # 2032 -1126, 653,2960,3296,5235,2342, 889,3595,4022,3104,3007, 903,1250,4609,4023,3481, # 2048 -3596,1342,1681,1718, 766,3297, 286, 89,2961,3715,5236,1713,5237,2607,3371,3008, # 2064 -5238,2962,2219,3225,2880,5239,4610,2505,2533, 181, 387,1075,4024, 731,2190,3372, # 2080 -5240,3298, 310, 313,3482,2304, 770,4278, 54,3054, 189,4611,3105,3848,4025,5241, # 2096 -1230,1617,1850, 355,3597,4279,4612,3373, 111,4280,3716,1350,3160,3483,3055,4281, # 2112 -2150,3299,3598,5242,2797,4026,4027,3009, 722,2009,5243,1071, 247,1207,2343,2478, # 2128 -1378,4613,2010, 864,1437,1214,4614, 373,3849,1142,2220, 667,4615, 442,2763,2563, # 2144 -3850,4028,1969,4282,3300,1840, 837, 170,1107, 934,1336,1883,5244,5245,2119,4283, # 2160 -2841, 743,1569,5246,4616,4284, 582,2389,1418,3484,5247,1803,5248, 357,1395,1729, # 2176 -3717,3301,2423,1564,2241,5249,3106,3851,1633,4617,1114,2086,4285,1532,5250, 482, # 2192 -2451,4618,5251,5252,1492, 833,1466,5253,2726,3599,1641,2842,5254,1526,1272,3718, # 2208 -4286,1686,1795, 416,2564,1903,1954,1804,5255,3852,2798,3853,1159,2321,5256,2881, # 2224 -4619,1610,1584,3056,2424,2764, 443,3302,1163,3161,5257,5258,4029,5259,4287,2506, # 2240 -3057,4620,4030,3162,2104,1647,3600,2011,1873,4288,5260,4289, 431,3485,5261, 250, # 2256 - 97, 81,4290,5262,1648,1851,1558, 160, 848,5263, 866, 740,1694,5264,2204,2843, # 2272 -3226,4291,4621,3719,1687, 950,2479, 426, 469,3227,3720,3721,4031,5265,5266,1188, # 2288 - 424,1996, 861,3601,4292,3854,2205,2694, 168,1235,3602,4293,5267,2087,1674,4622, # 2304 -3374,3303, 220,2565,1009,5268,3855, 670,3010, 332,1208, 717,5269,5270,3603,2452, # 2320 -4032,3375,5271, 513,5272,1209,2882,3376,3163,4623,1080,5273,5274,5275,5276,2534, # 2336 -3722,3604, 815,1587,4033,4034,5277,3605,3486,3856,1254,4624,1328,3058,1390,4035, # 2352 -1741,4036,3857,4037,5278, 236,3858,2453,3304,5279,5280,3723,3859,1273,3860,4625, # 2368 -5281, 308,5282,4626, 245,4627,1852,2480,1307,2583, 430, 715,2137,2454,5283, 270, # 2384 - 199,2883,4038,5284,3606,2727,1753, 761,1754, 725,1661,1841,4628,3487,3724,5285, # 2400 -5286, 587, 14,3305, 227,2608, 326, 480,2270, 943,2765,3607, 291, 650,1884,5287, # 2416 -1702,1226, 102,1547, 62,3488, 904,4629,3489,1164,4294,5288,5289,1224,1548,2766, # 2432 - 391, 498,1493,5290,1386,1419,5291,2056,1177,4630, 813, 880,1081,2368, 566,1145, # 2448 -4631,2291,1001,1035,2566,2609,2242, 394,1286,5292,5293,2069,5294, 86,1494,1730, # 2464 -4039, 491,1588, 745, 897,2963, 843,3377,4040,2767,2884,3306,1768, 998,2221,2070, # 2480 - 397,1827,1195,1970,3725,3011,3378, 284,5295,3861,2507,2138,2120,1904,5296,4041, # 2496 -2151,4042,4295,1036,3490,1905, 114,2567,4296, 209,1527,5297,5298,2964,2844,2635, # 2512 -2390,2728,3164, 812,2568,5299,3307,5300,1559, 737,1885,3726,1210, 885, 28,2695, # 2528 -3608,3862,5301,4297,1004,1780,4632,5302, 346,1982,2222,2696,4633,3863,1742, 797, # 2544 -1642,4043,1934,1072,1384,2152, 896,4044,3308,3727,3228,2885,3609,5303,2569,1959, # 2560 -4634,2455,1786,5304,5305,5306,4045,4298,1005,1308,3728,4299,2729,4635,4636,1528, # 2576 -2610, 161,1178,4300,1983, 987,4637,1101,4301, 631,4046,1157,3229,2425,1343,1241, # 2592 -1016,2243,2570, 372, 877,2344,2508,1160, 555,1935, 911,4047,5307, 466,1170, 169, # 2608 -1051,2921,2697,3729,2481,3012,1182,2012,2571,1251,2636,5308, 992,2345,3491,1540, # 2624 -2730,1201,2071,2406,1997,2482,5309,4638, 528,1923,2191,1503,1874,1570,2369,3379, # 2640 -3309,5310, 557,1073,5311,1828,3492,2088,2271,3165,3059,3107, 767,3108,2799,4639, # 2656 -1006,4302,4640,2346,1267,2179,3730,3230, 778,4048,3231,2731,1597,2667,5312,4641, # 2672 -5313,3493,5314,5315,5316,3310,2698,1433,3311, 131, 95,1504,4049, 723,4303,3166, # 2688 -1842,3610,2768,2192,4050,2028,2105,3731,5317,3013,4051,1218,5318,3380,3232,4052, # 2704 -4304,2584, 248,1634,3864, 912,5319,2845,3732,3060,3865, 654, 53,5320,3014,5321, # 2720 -1688,4642, 777,3494,1032,4053,1425,5322, 191, 820,2121,2846, 971,4643, 931,3233, # 2736 - 135, 664, 783,3866,1998, 772,2922,1936,4054,3867,4644,2923,3234, 282,2732, 640, # 2752 -1372,3495,1127, 922, 325,3381,5323,5324, 711,2045,5325,5326,4055,2223,2800,1937, # 2768 -4056,3382,2224,2255,3868,2305,5327,4645,3869,1258,3312,4057,3235,2139,2965,4058, # 2784 -4059,5328,2225, 258,3236,4646, 101,1227,5329,3313,1755,5330,1391,3314,5331,2924, # 2800 -2057, 893,5332,5333,5334,1402,4305,2347,5335,5336,3237,3611,5337,5338, 878,1325, # 2816 -1781,2801,4647, 259,1385,2585, 744,1183,2272,4648,5339,4060,2509,5340, 684,1024, # 2832 -4306,5341, 472,3612,3496,1165,3315,4061,4062, 322,2153, 881, 455,1695,1152,1340, # 2848 - 660, 554,2154,4649,1058,4650,4307, 830,1065,3383,4063,4651,1924,5342,1703,1919, # 2864 -5343, 932,2273, 122,5344,4652, 947, 677,5345,3870,2637, 297,1906,1925,2274,4653, # 2880 -2322,3316,5346,5347,4308,5348,4309, 84,4310, 112, 989,5349, 547,1059,4064, 701, # 2896 -3613,1019,5350,4311,5351,3497, 942, 639, 457,2306,2456, 993,2966, 407, 851, 494, # 2912 -4654,3384, 927,5352,1237,5353,2426,3385, 573,4312, 680, 921,2925,1279,1875, 285, # 2928 - 790,1448,1984, 719,2168,5354,5355,4655,4065,4066,1649,5356,1541, 563,5357,1077, # 2944 -5358,3386,3061,3498, 511,3015,4067,4068,3733,4069,1268,2572,3387,3238,4656,4657, # 2960 -5359, 535,1048,1276,1189,2926,2029,3167,1438,1373,2847,2967,1134,2013,5360,4313, # 2976 -1238,2586,3109,1259,5361, 700,5362,2968,3168,3734,4314,5363,4315,1146,1876,1907, # 2992 -4658,2611,4070, 781,2427, 132,1589, 203, 147, 273,2802,2407, 898,1787,2155,4071, # 3008 -4072,5364,3871,2803,5365,5366,4659,4660,5367,3239,5368,1635,3872, 965,5369,1805, # 3024 -2699,1516,3614,1121,1082,1329,3317,4073,1449,3873, 65,1128,2848,2927,2769,1590, # 3040 -3874,5370,5371, 12,2668, 45, 976,2587,3169,4661, 517,2535,1013,1037,3240,5372, # 3056 -3875,2849,5373,3876,5374,3499,5375,2612, 614,1999,2323,3877,3110,2733,2638,5376, # 3072 -2588,4316, 599,1269,5377,1811,3735,5378,2700,3111, 759,1060, 489,1806,3388,3318, # 3088 -1358,5379,5380,2391,1387,1215,2639,2256, 490,5381,5382,4317,1759,2392,2348,5383, # 3104 -4662,3878,1908,4074,2640,1807,3241,4663,3500,3319,2770,2349, 874,5384,5385,3501, # 3120 -3736,1859, 91,2928,3737,3062,3879,4664,5386,3170,4075,2669,5387,3502,1202,1403, # 3136 -3880,2969,2536,1517,2510,4665,3503,2511,5388,4666,5389,2701,1886,1495,1731,4076, # 3152 -2370,4667,5390,2030,5391,5392,4077,2702,1216, 237,2589,4318,2324,4078,3881,4668, # 3168 -4669,2703,3615,3504, 445,4670,5393,5394,5395,5396,2771, 61,4079,3738,1823,4080, # 3184 -5397, 687,2046, 935, 925, 405,2670, 703,1096,1860,2734,4671,4081,1877,1367,2704, # 3200 -3389, 918,2106,1782,2483, 334,3320,1611,1093,4672, 564,3171,3505,3739,3390, 945, # 3216 -2641,2058,4673,5398,1926, 872,4319,5399,3506,2705,3112, 349,4320,3740,4082,4674, # 3232 -3882,4321,3741,2156,4083,4675,4676,4322,4677,2408,2047, 782,4084, 400, 251,4323, # 3248 -1624,5400,5401, 277,3742, 299,1265, 476,1191,3883,2122,4324,4325,1109, 205,5402, # 3264 -2590,1000,2157,3616,1861,5403,5404,5405,4678,5406,4679,2573, 107,2484,2158,4085, # 3280 -3507,3172,5407,1533, 541,1301, 158, 753,4326,2886,3617,5408,1696, 370,1088,4327, # 3296 -4680,3618, 579, 327, 440, 162,2244, 269,1938,1374,3508, 968,3063, 56,1396,3113, # 3312 -2107,3321,3391,5409,1927,2159,4681,3016,5410,3619,5411,5412,3743,4682,2485,5413, # 3328 -2804,5414,1650,4683,5415,2613,5416,5417,4086,2671,3392,1149,3393,4087,3884,4088, # 3344 -5418,1076, 49,5419, 951,3242,3322,3323, 450,2850, 920,5420,1812,2805,2371,4328, # 3360 -1909,1138,2372,3885,3509,5421,3243,4684,1910,1147,1518,2428,4685,3886,5422,4686, # 3376 -2393,2614, 260,1796,3244,5423,5424,3887,3324, 708,5425,3620,1704,5426,3621,1351, # 3392 -1618,3394,3017,1887, 944,4329,3395,4330,3064,3396,4331,5427,3744, 422, 413,1714, # 3408 -3325, 500,2059,2350,4332,2486,5428,1344,1911, 954,5429,1668,5430,5431,4089,2409, # 3424 -4333,3622,3888,4334,5432,2307,1318,2512,3114, 133,3115,2887,4687, 629, 31,2851, # 3440 -2706,3889,4688, 850, 949,4689,4090,2970,1732,2089,4335,1496,1853,5433,4091, 620, # 3456 -3245, 981,1242,3745,3397,1619,3746,1643,3326,2140,2457,1971,1719,3510,2169,5434, # 3472 -3246,5435,5436,3398,1829,5437,1277,4690,1565,2048,5438,1636,3623,3116,5439, 869, # 3488 -2852, 655,3890,3891,3117,4092,3018,3892,1310,3624,4691,5440,5441,5442,1733, 558, # 3504 -4692,3747, 335,1549,3065,1756,4336,3748,1946,3511,1830,1291,1192, 470,2735,2108, # 3520 -2806, 913,1054,4093,5443,1027,5444,3066,4094,4693, 982,2672,3399,3173,3512,3247, # 3536 -3248,1947,2807,5445, 571,4694,5446,1831,5447,3625,2591,1523,2429,5448,2090, 984, # 3552 -4695,3749,1960,5449,3750, 852, 923,2808,3513,3751, 969,1519, 999,2049,2325,1705, # 3568 -5450,3118, 615,1662, 151, 597,4095,2410,2326,1049, 275,4696,3752,4337, 568,3753, # 3584 -3626,2487,4338,3754,5451,2430,2275, 409,3249,5452,1566,2888,3514,1002, 769,2853, # 3600 - 194,2091,3174,3755,2226,3327,4339, 628,1505,5453,5454,1763,2180,3019,4096, 521, # 3616 -1161,2592,1788,2206,2411,4697,4097,1625,4340,4341, 412, 42,3119, 464,5455,2642, # 3632 -4698,3400,1760,1571,2889,3515,2537,1219,2207,3893,2643,2141,2373,4699,4700,3328, # 3648 -1651,3401,3627,5456,5457,3628,2488,3516,5458,3756,5459,5460,2276,2092, 460,5461, # 3664 -4701,5462,3020, 962, 588,3629, 289,3250,2644,1116, 52,5463,3067,1797,5464,5465, # 3680 -5466,1467,5467,1598,1143,3757,4342,1985,1734,1067,4702,1280,3402, 465,4703,1572, # 3696 - 510,5468,1928,2245,1813,1644,3630,5469,4704,3758,5470,5471,2673,1573,1534,5472, # 3712 -5473, 536,1808,1761,3517,3894,3175,2645,5474,5475,5476,4705,3518,2929,1912,2809, # 3728 -5477,3329,1122, 377,3251,5478, 360,5479,5480,4343,1529, 551,5481,2060,3759,1769, # 3744 -2431,5482,2930,4344,3330,3120,2327,2109,2031,4706,1404, 136,1468,1479, 672,1171, # 3760 -3252,2308, 271,3176,5483,2772,5484,2050, 678,2736, 865,1948,4707,5485,2014,4098, # 3776 -2971,5486,2737,2227,1397,3068,3760,4708,4709,1735,2931,3403,3631,5487,3895, 509, # 3792 -2854,2458,2890,3896,5488,5489,3177,3178,4710,4345,2538,4711,2309,1166,1010, 552, # 3808 - 681,1888,5490,5491,2972,2973,4099,1287,1596,1862,3179, 358, 453, 736, 175, 478, # 3824 -1117, 905,1167,1097,5492,1854,1530,5493,1706,5494,2181,3519,2292,3761,3520,3632, # 3840 -4346,2093,4347,5495,3404,1193,2489,4348,1458,2193,2208,1863,1889,1421,3331,2932, # 3856 -3069,2182,3521, 595,2123,5496,4100,5497,5498,4349,1707,2646, 223,3762,1359, 751, # 3872 -3121, 183,3522,5499,2810,3021, 419,2374, 633, 704,3897,2394, 241,5500,5501,5502, # 3888 - 838,3022,3763,2277,2773,2459,3898,1939,2051,4101,1309,3122,2246,1181,5503,1136, # 3904 -2209,3899,2375,1446,4350,2310,4712,5504,5505,4351,1055,2615, 484,3764,5506,4102, # 3920 - 625,4352,2278,3405,1499,4353,4103,5507,4104,4354,3253,2279,2280,3523,5508,5509, # 3936 -2774, 808,2616,3765,3406,4105,4355,3123,2539, 526,3407,3900,4356, 955,5510,1620, # 3952 -4357,2647,2432,5511,1429,3766,1669,1832, 994, 928,5512,3633,1260,5513,5514,5515, # 3968 -1949,2293, 741,2933,1626,4358,2738,2460, 867,1184, 362,3408,1392,5516,5517,4106, # 3984 -4359,1770,1736,3254,2934,4713,4714,1929,2707,1459,1158,5518,3070,3409,2891,1292, # 4000 -1930,2513,2855,3767,1986,1187,2072,2015,2617,4360,5519,2574,2514,2170,3768,2490, # 4016 -3332,5520,3769,4715,5521,5522, 666,1003,3023,1022,3634,4361,5523,4716,1814,2257, # 4032 - 574,3901,1603, 295,1535, 705,3902,4362, 283, 858, 417,5524,5525,3255,4717,4718, # 4048 -3071,1220,1890,1046,2281,2461,4107,1393,1599, 689,2575, 388,4363,5526,2491, 802, # 4064 -5527,2811,3903,2061,1405,2258,5528,4719,3904,2110,1052,1345,3256,1585,5529, 809, # 4080 -5530,5531,5532, 575,2739,3524, 956,1552,1469,1144,2328,5533,2329,1560,2462,3635, # 4096 -3257,4108, 616,2210,4364,3180,2183,2294,5534,1833,5535,3525,4720,5536,1319,3770, # 4112 -3771,1211,3636,1023,3258,1293,2812,5537,5538,5539,3905, 607,2311,3906, 762,2892, # 4128 -1439,4365,1360,4721,1485,3072,5540,4722,1038,4366,1450,2062,2648,4367,1379,4723, # 4144 -2593,5541,5542,4368,1352,1414,2330,2935,1172,5543,5544,3907,3908,4724,1798,1451, # 4160 -5545,5546,5547,5548,2936,4109,4110,2492,2351, 411,4111,4112,3637,3333,3124,4725, # 4176 -1561,2674,1452,4113,1375,5549,5550, 47,2974, 316,5551,1406,1591,2937,3181,5552, # 4192 -1025,2142,3125,3182, 354,2740, 884,2228,4369,2412, 508,3772, 726,3638, 996,2433, # 4208 -3639, 729,5553, 392,2194,1453,4114,4726,3773,5554,5555,2463,3640,2618,1675,2813, # 4224 - 919,2352,2975,2353,1270,4727,4115, 73,5556,5557, 647,5558,3259,2856,2259,1550, # 4240 -1346,3024,5559,1332, 883,3526,5560,5561,5562,5563,3334,2775,5564,1212, 831,1347, # 4256 -4370,4728,2331,3909,1864,3073, 720,3910,4729,4730,3911,5565,4371,5566,5567,4731, # 4272 -5568,5569,1799,4732,3774,2619,4733,3641,1645,2376,4734,5570,2938, 669,2211,2675, # 4288 -2434,5571,2893,5572,5573,1028,3260,5574,4372,2413,5575,2260,1353,5576,5577,4735, # 4304 -3183, 518,5578,4116,5579,4373,1961,5580,2143,4374,5581,5582,3025,2354,2355,3912, # 4320 - 516,1834,1454,4117,2708,4375,4736,2229,2620,1972,1129,3642,5583,2776,5584,2976, # 4336 -1422, 577,1470,3026,1524,3410,5585,5586, 432,4376,3074,3527,5587,2594,1455,2515, # 4352 -2230,1973,1175,5588,1020,2741,4118,3528,4737,5589,2742,5590,1743,1361,3075,3529, # 4368 -2649,4119,4377,4738,2295, 895, 924,4378,2171, 331,2247,3076, 166,1627,3077,1098, # 4384 -5591,1232,2894,2231,3411,4739, 657, 403,1196,2377, 542,3775,3412,1600,4379,3530, # 4400 -5592,4740,2777,3261, 576, 530,1362,4741,4742,2540,2676,3776,4120,5593, 842,3913, # 4416 -5594,2814,2032,1014,4121, 213,2709,3413, 665, 621,4380,5595,3777,2939,2435,5596, # 4432 -2436,3335,3643,3414,4743,4381,2541,4382,4744,3644,1682,4383,3531,1380,5597, 724, # 4448 -2282, 600,1670,5598,1337,1233,4745,3126,2248,5599,1621,4746,5600, 651,4384,5601, # 4464 -1612,4385,2621,5602,2857,5603,2743,2312,3078,5604, 716,2464,3079, 174,1255,2710, # 4480 -4122,3645, 548,1320,1398, 728,4123,1574,5605,1891,1197,3080,4124,5606,3081,3082, # 4496 -3778,3646,3779, 747,5607, 635,4386,4747,5608,5609,5610,4387,5611,5612,4748,5613, # 4512 -3415,4749,2437, 451,5614,3780,2542,2073,4388,2744,4389,4125,5615,1764,4750,5616, # 4528 -4390, 350,4751,2283,2395,2493,5617,4391,4126,2249,1434,4127, 488,4752, 458,4392, # 4544 -4128,3781, 771,1330,2396,3914,2576,3184,2160,2414,1553,2677,3185,4393,5618,2494, # 4560 -2895,2622,1720,2711,4394,3416,4753,5619,2543,4395,5620,3262,4396,2778,5621,2016, # 4576 -2745,5622,1155,1017,3782,3915,5623,3336,2313, 201,1865,4397,1430,5624,4129,5625, # 4592 -5626,5627,5628,5629,4398,1604,5630, 414,1866, 371,2595,4754,4755,3532,2017,3127, # 4608 -4756,1708, 960,4399, 887, 389,2172,1536,1663,1721,5631,2232,4130,2356,2940,1580, # 4624 -5632,5633,1744,4757,2544,4758,4759,5634,4760,5635,2074,5636,4761,3647,3417,2896, # 4640 -4400,5637,4401,2650,3418,2815, 673,2712,2465, 709,3533,4131,3648,4402,5638,1148, # 4656 - 502, 634,5639,5640,1204,4762,3649,1575,4763,2623,3783,5641,3784,3128, 948,3263, # 4672 - 121,1745,3916,1110,5642,4403,3083,2516,3027,4132,3785,1151,1771,3917,1488,4133, # 4688 -1987,5643,2438,3534,5644,5645,2094,5646,4404,3918,1213,1407,2816, 531,2746,2545, # 4704 -3264,1011,1537,4764,2779,4405,3129,1061,5647,3786,3787,1867,2897,5648,2018, 120, # 4720 -4406,4407,2063,3650,3265,2314,3919,2678,3419,1955,4765,4134,5649,3535,1047,2713, # 4736 -1266,5650,1368,4766,2858, 649,3420,3920,2546,2747,1102,2859,2679,5651,5652,2000, # 4752 -5653,1111,3651,2977,5654,2495,3921,3652,2817,1855,3421,3788,5655,5656,3422,2415, # 4768 -2898,3337,3266,3653,5657,2577,5658,3654,2818,4135,1460, 856,5659,3655,5660,2899, # 4784 -2978,5661,2900,3922,5662,4408, 632,2517, 875,3923,1697,3924,2296,5663,5664,4767, # 4800 -3028,1239, 580,4768,4409,5665, 914, 936,2075,1190,4136,1039,2124,5666,5667,5668, # 4816 -5669,3423,1473,5670,1354,4410,3925,4769,2173,3084,4137, 915,3338,4411,4412,3339, # 4832 -1605,1835,5671,2748, 398,3656,4413,3926,4138, 328,1913,2860,4139,3927,1331,4414, # 4848 -3029, 937,4415,5672,3657,4140,4141,3424,2161,4770,3425, 524, 742, 538,3085,1012, # 4864 -5673,5674,3928,2466,5675, 658,1103, 225,3929,5676,5677,4771,5678,4772,5679,3267, # 4880 -1243,5680,4142, 963,2250,4773,5681,2714,3658,3186,5682,5683,2596,2332,5684,4774, # 4896 -5685,5686,5687,3536, 957,3426,2547,2033,1931,2941,2467, 870,2019,3659,1746,2780, # 4912 -2781,2439,2468,5688,3930,5689,3789,3130,3790,3537,3427,3791,5690,1179,3086,5691, # 4928 -3187,2378,4416,3792,2548,3188,3131,2749,4143,5692,3428,1556,2549,2297, 977,2901, # 4944 -2034,4144,1205,3429,5693,1765,3430,3189,2125,1271, 714,1689,4775,3538,5694,2333, # 4960 -3931, 533,4417,3660,2184, 617,5695,2469,3340,3539,2315,5696,5697,3190,5698,5699, # 4976 -3932,1988, 618, 427,2651,3540,3431,5700,5701,1244,1690,5702,2819,4418,4776,5703, # 4992 -3541,4777,5704,2284,1576, 473,3661,4419,3432, 972,5705,3662,5706,3087,5707,5708, # 5008 -4778,4779,5709,3793,4145,4146,5710, 153,4780, 356,5711,1892,2902,4420,2144, 408, # 5024 - 803,2357,5712,3933,5713,4421,1646,2578,2518,4781,4782,3934,5714,3935,4422,5715, # 5040 -2416,3433, 752,5716,5717,1962,3341,2979,5718, 746,3030,2470,4783,4423,3794, 698, # 5056 -4784,1893,4424,3663,2550,4785,3664,3936,5719,3191,3434,5720,1824,1302,4147,2715, # 5072 -3937,1974,4425,5721,4426,3192, 823,1303,1288,1236,2861,3542,4148,3435, 774,3938, # 5088 -5722,1581,4786,1304,2862,3939,4787,5723,2440,2162,1083,3268,4427,4149,4428, 344, # 5104 -1173, 288,2316, 454,1683,5724,5725,1461,4788,4150,2597,5726,5727,4789, 985, 894, # 5120 -5728,3436,3193,5729,1914,2942,3795,1989,5730,2111,1975,5731,4151,5732,2579,1194, # 5136 - 425,5733,4790,3194,1245,3796,4429,5734,5735,2863,5736, 636,4791,1856,3940, 760, # 5152 -1800,5737,4430,2212,1508,4792,4152,1894,1684,2298,5738,5739,4793,4431,4432,2213, # 5168 - 479,5740,5741, 832,5742,4153,2496,5743,2980,2497,3797, 990,3132, 627,1815,2652, # 5184 -4433,1582,4434,2126,2112,3543,4794,5744, 799,4435,3195,5745,4795,2113,1737,3031, # 5200 -1018, 543, 754,4436,3342,1676,4796,4797,4154,4798,1489,5746,3544,5747,2624,2903, # 5216 -4155,5748,5749,2981,5750,5751,5752,5753,3196,4799,4800,2185,1722,5754,3269,3270, # 5232 -1843,3665,1715, 481, 365,1976,1857,5755,5756,1963,2498,4801,5757,2127,3666,3271, # 5248 - 433,1895,2064,2076,5758, 602,2750,5759,5760,5761,5762,5763,3032,1628,3437,5764, # 5264 -3197,4802,4156,2904,4803,2519,5765,2551,2782,5766,5767,5768,3343,4804,2905,5769, # 5280 -4805,5770,2864,4806,4807,1221,2982,4157,2520,5771,5772,5773,1868,1990,5774,5775, # 5296 -5776,1896,5777,5778,4808,1897,4158, 318,5779,2095,4159,4437,5780,5781, 485,5782, # 5312 - 938,3941, 553,2680, 116,5783,3942,3667,5784,3545,2681,2783,3438,3344,2820,5785, # 5328 -3668,2943,4160,1747,2944,2983,5786,5787, 207,5788,4809,5789,4810,2521,5790,3033, # 5344 - 890,3669,3943,5791,1878,3798,3439,5792,2186,2358,3440,1652,5793,5794,5795, 941, # 5360 -2299, 208,3546,4161,2020, 330,4438,3944,2906,2499,3799,4439,4811,5796,5797,5798, # 5376 -) - diff --git a/lib/chardet/big5prober.py b/lib/chardet/big5prober.py deleted file mode 100644 index 98f9970..0000000 --- a/lib/chardet/big5prober.py +++ /dev/null @@ -1,47 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import Big5DistributionAnalysis -from .mbcssm import BIG5_SM_MODEL - - -class Big5Prober(MultiByteCharSetProber): - def __init__(self): - super(Big5Prober, self).__init__() - self.coding_sm = CodingStateMachine(BIG5_SM_MODEL) - self.distribution_analyzer = Big5DistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "Big5" - - @property - def language(self): - return "Chinese" diff --git a/lib/chardet/chardistribution.py b/lib/chardet/chardistribution.py deleted file mode 100644 index c0395f4..0000000 --- a/lib/chardet/chardistribution.py +++ /dev/null @@ -1,233 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .euctwfreq import (EUCTW_CHAR_TO_FREQ_ORDER, EUCTW_TABLE_SIZE, - EUCTW_TYPICAL_DISTRIBUTION_RATIO) -from .euckrfreq import (EUCKR_CHAR_TO_FREQ_ORDER, EUCKR_TABLE_SIZE, - EUCKR_TYPICAL_DISTRIBUTION_RATIO) -from .gb2312freq import (GB2312_CHAR_TO_FREQ_ORDER, GB2312_TABLE_SIZE, - GB2312_TYPICAL_DISTRIBUTION_RATIO) -from .big5freq import (BIG5_CHAR_TO_FREQ_ORDER, BIG5_TABLE_SIZE, - BIG5_TYPICAL_DISTRIBUTION_RATIO) -from .jisfreq import (JIS_CHAR_TO_FREQ_ORDER, JIS_TABLE_SIZE, - JIS_TYPICAL_DISTRIBUTION_RATIO) - - -class CharDistributionAnalysis(object): - ENOUGH_DATA_THRESHOLD = 1024 - SURE_YES = 0.99 - SURE_NO = 0.01 - MINIMUM_DATA_THRESHOLD = 3 - - def __init__(self): - # Mapping table to get frequency order from char order (get from - # GetOrder()) - self._char_to_freq_order = None - self._table_size = None # Size of above table - # This is a constant value which varies from language to language, - # used in calculating confidence. See - # http://www.mozilla.org/projects/intl/UniversalCharsetDetection.html - # for further detail. - self.typical_distribution_ratio = None - self._done = None - self._total_chars = None - self._freq_chars = None - self.reset() - - def reset(self): - """reset analyser, clear any state""" - # If this flag is set to True, detection is done and conclusion has - # been made - self._done = False - self._total_chars = 0 # Total characters encountered - # The number of characters whose frequency order is less than 512 - self._freq_chars = 0 - - def feed(self, char, char_len): - """feed a character with known length""" - if char_len == 2: - # we only care about 2-bytes character in our distribution analysis - order = self.get_order(char) - else: - order = -1 - if order >= 0: - self._total_chars += 1 - # order is valid - if order < self._table_size: - if 512 > self._char_to_freq_order[order]: - self._freq_chars += 1 - - def get_confidence(self): - """return confidence based on existing data""" - # if we didn't receive any character in our consideration range, - # return negative answer - if self._total_chars <= 0 or self._freq_chars <= self.MINIMUM_DATA_THRESHOLD: - return self.SURE_NO - - if self._total_chars != self._freq_chars: - r = (self._freq_chars / ((self._total_chars - self._freq_chars) - * self.typical_distribution_ratio)) - if r < self.SURE_YES: - return r - - # normalize confidence (we don't want to be 100% sure) - return self.SURE_YES - - def got_enough_data(self): - # It is not necessary to receive all data to draw conclusion. - # For charset detection, certain amount of data is enough - return self._total_chars > self.ENOUGH_DATA_THRESHOLD - - def get_order(self, byte_str): - # We do not handle characters based on the original encoding string, - # but convert this encoding string to a number, here called order. - # This allows multiple encodings of a language to share one frequency - # table. - return -1 - - -class EUCTWDistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(EUCTWDistributionAnalysis, self).__init__() - self._char_to_freq_order = EUCTW_CHAR_TO_FREQ_ORDER - self._table_size = EUCTW_TABLE_SIZE - self.typical_distribution_ratio = EUCTW_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for euc-TW encoding, we are interested - # first byte range: 0xc4 -- 0xfe - # second byte range: 0xa1 -- 0xfe - # no validation needed here. State machine has done that - first_char = byte_str[0] - if first_char >= 0xC4: - return 94 * (first_char - 0xC4) + byte_str[1] - 0xA1 - else: - return -1 - - -class EUCKRDistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(EUCKRDistributionAnalysis, self).__init__() - self._char_to_freq_order = EUCKR_CHAR_TO_FREQ_ORDER - self._table_size = EUCKR_TABLE_SIZE - self.typical_distribution_ratio = EUCKR_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for euc-KR encoding, we are interested - # first byte range: 0xb0 -- 0xfe - # second byte range: 0xa1 -- 0xfe - # no validation needed here. State machine has done that - first_char = byte_str[0] - if first_char >= 0xB0: - return 94 * (first_char - 0xB0) + byte_str[1] - 0xA1 - else: - return -1 - - -class GB2312DistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(GB2312DistributionAnalysis, self).__init__() - self._char_to_freq_order = GB2312_CHAR_TO_FREQ_ORDER - self._table_size = GB2312_TABLE_SIZE - self.typical_distribution_ratio = GB2312_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for GB2312 encoding, we are interested - # first byte range: 0xb0 -- 0xfe - # second byte range: 0xa1 -- 0xfe - # no validation needed here. State machine has done that - first_char, second_char = byte_str[0], byte_str[1] - if (first_char >= 0xB0) and (second_char >= 0xA1): - return 94 * (first_char - 0xB0) + second_char - 0xA1 - else: - return -1 - - -class Big5DistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(Big5DistributionAnalysis, self).__init__() - self._char_to_freq_order = BIG5_CHAR_TO_FREQ_ORDER - self._table_size = BIG5_TABLE_SIZE - self.typical_distribution_ratio = BIG5_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for big5 encoding, we are interested - # first byte range: 0xa4 -- 0xfe - # second byte range: 0x40 -- 0x7e , 0xa1 -- 0xfe - # no validation needed here. State machine has done that - first_char, second_char = byte_str[0], byte_str[1] - if first_char >= 0xA4: - if second_char >= 0xA1: - return 157 * (first_char - 0xA4) + second_char - 0xA1 + 63 - else: - return 157 * (first_char - 0xA4) + second_char - 0x40 - else: - return -1 - - -class SJISDistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(SJISDistributionAnalysis, self).__init__() - self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER - self._table_size = JIS_TABLE_SIZE - self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for sjis encoding, we are interested - # first byte range: 0x81 -- 0x9f , 0xe0 -- 0xfe - # second byte range: 0x40 -- 0x7e, 0x81 -- oxfe - # no validation needed here. State machine has done that - first_char, second_char = byte_str[0], byte_str[1] - if (first_char >= 0x81) and (first_char <= 0x9F): - order = 188 * (first_char - 0x81) - elif (first_char >= 0xE0) and (first_char <= 0xEF): - order = 188 * (first_char - 0xE0 + 31) - else: - return -1 - order = order + second_char - 0x40 - if second_char > 0x7F: - order = -1 - return order - - -class EUCJPDistributionAnalysis(CharDistributionAnalysis): - def __init__(self): - super(EUCJPDistributionAnalysis, self).__init__() - self._char_to_freq_order = JIS_CHAR_TO_FREQ_ORDER - self._table_size = JIS_TABLE_SIZE - self.typical_distribution_ratio = JIS_TYPICAL_DISTRIBUTION_RATIO - - def get_order(self, byte_str): - # for euc-JP encoding, we are interested - # first byte range: 0xa0 -- 0xfe - # second byte range: 0xa1 -- 0xfe - # no validation needed here. State machine has done that - char = byte_str[0] - if char >= 0xA0: - return 94 * (char - 0xA1) + byte_str[1] - 0xa1 - else: - return -1 diff --git a/lib/chardet/charsetgroupprober.py b/lib/chardet/charsetgroupprober.py deleted file mode 100644 index 8b3738e..0000000 --- a/lib/chardet/charsetgroupprober.py +++ /dev/null @@ -1,106 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .enums import ProbingState -from .charsetprober import CharSetProber - - -class CharSetGroupProber(CharSetProber): - def __init__(self, lang_filter=None): - super(CharSetGroupProber, self).__init__(lang_filter=lang_filter) - self._active_num = 0 - self.probers = [] - self._best_guess_prober = None - - def reset(self): - super(CharSetGroupProber, self).reset() - self._active_num = 0 - for prober in self.probers: - if prober: - prober.reset() - prober.active = True - self._active_num += 1 - self._best_guess_prober = None - - @property - def charset_name(self): - if not self._best_guess_prober: - self.get_confidence() - if not self._best_guess_prober: - return None - return self._best_guess_prober.charset_name - - @property - def language(self): - if not self._best_guess_prober: - self.get_confidence() - if not self._best_guess_prober: - return None - return self._best_guess_prober.language - - def feed(self, byte_str): - for prober in self.probers: - if not prober: - continue - if not prober.active: - continue - state = prober.feed(byte_str) - if not state: - continue - if state == ProbingState.FOUND_IT: - self._best_guess_prober = prober - return self.state - elif state == ProbingState.NOT_ME: - prober.active = False - self._active_num -= 1 - if self._active_num <= 0: - self._state = ProbingState.NOT_ME - return self.state - return self.state - - def get_confidence(self): - state = self.state - if state == ProbingState.FOUND_IT: - return 0.99 - elif state == ProbingState.NOT_ME: - return 0.01 - best_conf = 0.0 - self._best_guess_prober = None - for prober in self.probers: - if not prober: - continue - if not prober.active: - self.logger.debug('%s not active', prober.charset_name) - continue - conf = prober.get_confidence() - self.logger.debug('%s %s confidence = %s', prober.charset_name, prober.language, conf) - if best_conf < conf: - best_conf = conf - self._best_guess_prober = prober - if not self._best_guess_prober: - return 0.0 - return best_conf diff --git a/lib/chardet/charsetprober.py b/lib/chardet/charsetprober.py deleted file mode 100644 index eac4e59..0000000 --- a/lib/chardet/charsetprober.py +++ /dev/null @@ -1,145 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -import logging -import re - -from .enums import ProbingState - - -class CharSetProber(object): - - SHORTCUT_THRESHOLD = 0.95 - - def __init__(self, lang_filter=None): - self._state = None - self.lang_filter = lang_filter - self.logger = logging.getLogger(__name__) - - def reset(self): - self._state = ProbingState.DETECTING - - @property - def charset_name(self): - return None - - def feed(self, buf): - pass - - @property - def state(self): - return self._state - - def get_confidence(self): - return 0.0 - - @staticmethod - def filter_high_byte_only(buf): - buf = re.sub(b'([\x00-\x7F])+', b' ', buf) - return buf - - @staticmethod - def filter_international_words(buf): - """ - We define three types of bytes: - alphabet: english alphabets [a-zA-Z] - international: international characters [\x80-\xFF] - marker: everything else [^a-zA-Z\x80-\xFF] - - The input buffer can be thought to contain a series of words delimited - by markers. This function works to filter all words that contain at - least one international character. All contiguous sequences of markers - are replaced by a single space ascii character. - - This filter applies to all scripts which do not use English characters. - """ - filtered = bytearray() - - # This regex expression filters out only words that have at-least one - # international character. The word may include one marker character at - # the end. - words = re.findall(b'[a-zA-Z]*[\x80-\xFF]+[a-zA-Z]*[^a-zA-Z\x80-\xFF]?', - buf) - - for word in words: - filtered.extend(word[:-1]) - - # If the last character in the word is a marker, replace it with a - # space as markers shouldn't affect our analysis (they are used - # similarly across all languages and may thus have similar - # frequencies). - last_char = word[-1:] - if not last_char.isalpha() and last_char < b'\x80': - last_char = b' ' - filtered.extend(last_char) - - return filtered - - @staticmethod - def filter_with_english_letters(buf): - """ - Returns a copy of ``buf`` that retains only the sequences of English - alphabet and high byte characters that are not between <> characters. - Also retains English alphabet and high byte characters immediately - before occurrences of >. - - This filter can be applied to all scripts which contain both English - characters and extended ASCII characters, but is currently only used by - ``Latin1Prober``. - """ - filtered = bytearray() - in_tag = False - prev = 0 - - for curr in range(len(buf)): - # Slice here to get bytes instead of an int with Python 3 - buf_char = buf[curr:curr + 1] - # Check if we're coming out of or entering an HTML tag - if buf_char == b'>': - in_tag = False - elif buf_char == b'<': - in_tag = True - - # If current character is not extended-ASCII and not alphabetic... - if buf_char < b'\x80' and not buf_char.isalpha(): - # ...and we're not in a tag - if curr > prev and not in_tag: - # Keep everything after last non-extended-ASCII, - # non-alphabetic character - filtered.extend(buf[prev:curr]) - # Output a space to delimit stretch we kept - filtered.extend(b' ') - prev = curr + 1 - - # If we're not in a tag... - if not in_tag: - # Keep everything after last non-extended-ASCII, non-alphabetic - # character - filtered.extend(buf[prev:]) - - return filtered diff --git a/lib/chardet/cli/__init__.py b/lib/chardet/cli/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/lib/chardet/cli/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/chardet/cli/chardetect.py b/lib/chardet/cli/chardetect.py deleted file mode 100644 index f0a4cc5..0000000 --- a/lib/chardet/cli/chardetect.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -""" -Script which takes one or more file paths and reports on their detected -encodings - -Example:: - - % chardetect somefile someotherfile - somefile: windows-1252 with confidence 0.5 - someotherfile: ascii with confidence 1.0 - -If no paths are provided, it takes its input from stdin. - -""" - -from __future__ import absolute_import, print_function, unicode_literals - -import argparse -import sys - -from chardet import __version__ -from chardet.compat import PY2 -from chardet.universaldetector import UniversalDetector - - -def description_of(lines, name='stdin'): - """ - Return a string describing the probable encoding of a file or - list of strings. - - :param lines: The lines to get the encoding of. - :type lines: Iterable of bytes - :param name: Name of file or collection of lines - :type name: str - """ - u = UniversalDetector() - for line in lines: - line = bytearray(line) - u.feed(line) - # shortcut out of the loop to save reading further - particularly useful if we read a BOM. - if u.done: - break - u.close() - result = u.result - if PY2: - name = name.decode(sys.getfilesystemencoding(), 'ignore') - if result['encoding']: - return '{0}: {1} with confidence {2}'.format(name, result['encoding'], - result['confidence']) - else: - return '{0}: no result'.format(name) - - -def main(argv=None): - """ - Handles command line arguments and gets things started. - - :param argv: List of arguments, as if specified on the command-line. - If None, ``sys.argv[1:]`` is used instead. - :type argv: list of str - """ - # Get command line arguments - parser = argparse.ArgumentParser( - description="Takes one or more file paths and reports their detected \ - encodings") - parser.add_argument('input', - help='File whose encoding we would like to determine. \ - (default: stdin)', - type=argparse.FileType('rb'), nargs='*', - default=[sys.stdin if PY2 else sys.stdin.buffer]) - parser.add_argument('--version', action='version', - version='%(prog)s {0}'.format(__version__)) - args = parser.parse_args(argv) - - for f in args.input: - if f.isatty(): - print("You are running chardetect interactively. Press " + - "CTRL-D twice at the start of a blank line to signal the " + - "end of your input. If you want help, run chardetect " + - "--help\n", file=sys.stderr) - print(description_of(f, f.name)) - - -if __name__ == '__main__': - main() diff --git a/lib/chardet/codingstatemachine.py b/lib/chardet/codingstatemachine.py deleted file mode 100644 index 68fba44..0000000 --- a/lib/chardet/codingstatemachine.py +++ /dev/null @@ -1,88 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -import logging - -from .enums import MachineState - - -class CodingStateMachine(object): - """ - A state machine to verify a byte sequence for a particular encoding. For - each byte the detector receives, it will feed that byte to every active - state machine available, one byte at a time. The state machine changes its - state based on its previous state and the byte it receives. There are 3 - states in a state machine that are of interest to an auto-detector: - - START state: This is the state to start with, or a legal byte sequence - (i.e. a valid code point) for character has been identified. - - ME state: This indicates that the state machine identified a byte sequence - that is specific to the charset it is designed for and that - there is no other possible encoding which can contain this byte - sequence. This will to lead to an immediate positive answer for - the detector. - - ERROR state: This indicates the state machine identified an illegal byte - sequence for that encoding. This will lead to an immediate - negative answer for this encoding. Detector will exclude this - encoding from consideration from here on. - """ - def __init__(self, sm): - self._model = sm - self._curr_byte_pos = 0 - self._curr_char_len = 0 - self._curr_state = None - self.logger = logging.getLogger(__name__) - self.reset() - - def reset(self): - self._curr_state = MachineState.START - - def next_state(self, c): - # for each byte we get its class - # if it is first byte, we also get byte length - byte_class = self._model['class_table'][c] - if self._curr_state == MachineState.START: - self._curr_byte_pos = 0 - self._curr_char_len = self._model['char_len_table'][byte_class] - # from byte's class and state_table, we get its next state - curr_state = (self._curr_state * self._model['class_factor'] - + byte_class) - self._curr_state = self._model['state_table'][curr_state] - self._curr_byte_pos += 1 - return self._curr_state - - def get_current_charlen(self): - return self._curr_char_len - - def get_coding_state_machine(self): - return self._model['name'] - - @property - def language(self): - return self._model['language'] diff --git a/lib/chardet/compat.py b/lib/chardet/compat.py deleted file mode 100644 index ddd7468..0000000 --- a/lib/chardet/compat.py +++ /dev/null @@ -1,34 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# Contributor(s): -# Dan Blanchard -# Ian Cordasco -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -import sys - - -if sys.version_info < (3, 0): - PY2 = True - PY3 = False - base_str = (str, unicode) - text_type = unicode -else: - PY2 = False - PY3 = True - base_str = (bytes, str) - text_type = str diff --git a/lib/chardet/cp949prober.py b/lib/chardet/cp949prober.py deleted file mode 100644 index efd793a..0000000 --- a/lib/chardet/cp949prober.py +++ /dev/null @@ -1,49 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .chardistribution import EUCKRDistributionAnalysis -from .codingstatemachine import CodingStateMachine -from .mbcharsetprober import MultiByteCharSetProber -from .mbcssm import CP949_SM_MODEL - - -class CP949Prober(MultiByteCharSetProber): - def __init__(self): - super(CP949Prober, self).__init__() - self.coding_sm = CodingStateMachine(CP949_SM_MODEL) - # NOTE: CP949 is a superset of EUC-KR, so the distribution should be - # not different. - self.distribution_analyzer = EUCKRDistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "CP949" - - @property - def language(self): - return "Korean" diff --git a/lib/chardet/enums.py b/lib/chardet/enums.py deleted file mode 100644 index 0451207..0000000 --- a/lib/chardet/enums.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -All of the Enums that are used throughout the chardet package. - -:author: Dan Blanchard (dan.blanchard@gmail.com) -""" - - -class InputState(object): - """ - This enum represents the different states a universal detector can be in. - """ - PURE_ASCII = 0 - ESC_ASCII = 1 - HIGH_BYTE = 2 - - -class LanguageFilter(object): - """ - This enum represents the different language filters we can apply to a - ``UniversalDetector``. - """ - CHINESE_SIMPLIFIED = 0x01 - CHINESE_TRADITIONAL = 0x02 - JAPANESE = 0x04 - KOREAN = 0x08 - NON_CJK = 0x10 - ALL = 0x1F - CHINESE = CHINESE_SIMPLIFIED | CHINESE_TRADITIONAL - CJK = CHINESE | JAPANESE | KOREAN - - -class ProbingState(object): - """ - This enum represents the different states a prober can be in. - """ - DETECTING = 0 - FOUND_IT = 1 - NOT_ME = 2 - - -class MachineState(object): - """ - This enum represents the different states a state machine can be in. - """ - START = 0 - ERROR = 1 - ITS_ME = 2 - - -class SequenceLikelihood(object): - """ - This enum represents the likelihood of a character following the previous one. - """ - NEGATIVE = 0 - UNLIKELY = 1 - LIKELY = 2 - POSITIVE = 3 - - @classmethod - def get_num_categories(cls): - """:returns: The number of likelihood categories in the enum.""" - return 4 - - -class CharacterCategory(object): - """ - This enum represents the different categories language models for - ``SingleByteCharsetProber`` put characters into. - - Anything less than CONTROL is considered a letter. - """ - UNDEFINED = 255 - LINE_BREAK = 254 - SYMBOL = 253 - DIGIT = 252 - CONTROL = 251 diff --git a/lib/chardet/escprober.py b/lib/chardet/escprober.py deleted file mode 100644 index c70493f..0000000 --- a/lib/chardet/escprober.py +++ /dev/null @@ -1,101 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .codingstatemachine import CodingStateMachine -from .enums import LanguageFilter, ProbingState, MachineState -from .escsm import (HZ_SM_MODEL, ISO2022CN_SM_MODEL, ISO2022JP_SM_MODEL, - ISO2022KR_SM_MODEL) - - -class EscCharSetProber(CharSetProber): - """ - This CharSetProber uses a "code scheme" approach for detecting encodings, - whereby easily recognizable escape or shift sequences are relied on to - identify these encodings. - """ - - def __init__(self, lang_filter=None): - super(EscCharSetProber, self).__init__(lang_filter=lang_filter) - self.coding_sm = [] - if self.lang_filter & LanguageFilter.CHINESE_SIMPLIFIED: - self.coding_sm.append(CodingStateMachine(HZ_SM_MODEL)) - self.coding_sm.append(CodingStateMachine(ISO2022CN_SM_MODEL)) - if self.lang_filter & LanguageFilter.JAPANESE: - self.coding_sm.append(CodingStateMachine(ISO2022JP_SM_MODEL)) - if self.lang_filter & LanguageFilter.KOREAN: - self.coding_sm.append(CodingStateMachine(ISO2022KR_SM_MODEL)) - self.active_sm_count = None - self._detected_charset = None - self._detected_language = None - self._state = None - self.reset() - - def reset(self): - super(EscCharSetProber, self).reset() - for coding_sm in self.coding_sm: - if not coding_sm: - continue - coding_sm.active = True - coding_sm.reset() - self.active_sm_count = len(self.coding_sm) - self._detected_charset = None - self._detected_language = None - - @property - def charset_name(self): - return self._detected_charset - - @property - def language(self): - return self._detected_language - - def get_confidence(self): - if self._detected_charset: - return 0.99 - else: - return 0.00 - - def feed(self, byte_str): - for c in byte_str: - for coding_sm in self.coding_sm: - if not coding_sm or not coding_sm.active: - continue - coding_state = coding_sm.next_state(c) - if coding_state == MachineState.ERROR: - coding_sm.active = False - self.active_sm_count -= 1 - if self.active_sm_count <= 0: - self._state = ProbingState.NOT_ME - return self.state - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - self._detected_charset = coding_sm.get_coding_state_machine() - self._detected_language = coding_sm.language - return self.state - - return self.state diff --git a/lib/chardet/escsm.py b/lib/chardet/escsm.py deleted file mode 100644 index 0069523..0000000 --- a/lib/chardet/escsm.py +++ /dev/null @@ -1,246 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .enums import MachineState - -HZ_CLS = ( -1,0,0,0,0,0,0,0, # 00 - 07 -0,0,0,0,0,0,0,0, # 08 - 0f -0,0,0,0,0,0,0,0, # 10 - 17 -0,0,0,1,0,0,0,0, # 18 - 1f -0,0,0,0,0,0,0,0, # 20 - 27 -0,0,0,0,0,0,0,0, # 28 - 2f -0,0,0,0,0,0,0,0, # 30 - 37 -0,0,0,0,0,0,0,0, # 38 - 3f -0,0,0,0,0,0,0,0, # 40 - 47 -0,0,0,0,0,0,0,0, # 48 - 4f -0,0,0,0,0,0,0,0, # 50 - 57 -0,0,0,0,0,0,0,0, # 58 - 5f -0,0,0,0,0,0,0,0, # 60 - 67 -0,0,0,0,0,0,0,0, # 68 - 6f -0,0,0,0,0,0,0,0, # 70 - 77 -0,0,0,4,0,5,2,0, # 78 - 7f -1,1,1,1,1,1,1,1, # 80 - 87 -1,1,1,1,1,1,1,1, # 88 - 8f -1,1,1,1,1,1,1,1, # 90 - 97 -1,1,1,1,1,1,1,1, # 98 - 9f -1,1,1,1,1,1,1,1, # a0 - a7 -1,1,1,1,1,1,1,1, # a8 - af -1,1,1,1,1,1,1,1, # b0 - b7 -1,1,1,1,1,1,1,1, # b8 - bf -1,1,1,1,1,1,1,1, # c0 - c7 -1,1,1,1,1,1,1,1, # c8 - cf -1,1,1,1,1,1,1,1, # d0 - d7 -1,1,1,1,1,1,1,1, # d8 - df -1,1,1,1,1,1,1,1, # e0 - e7 -1,1,1,1,1,1,1,1, # e8 - ef -1,1,1,1,1,1,1,1, # f0 - f7 -1,1,1,1,1,1,1,1, # f8 - ff -) - -HZ_ST = ( -MachineState.START,MachineState.ERROR, 3,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f -MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START, 4,MachineState.ERROR,# 10-17 - 5,MachineState.ERROR, 6,MachineState.ERROR, 5, 5, 4,MachineState.ERROR,# 18-1f - 4,MachineState.ERROR, 4, 4, 4,MachineState.ERROR, 4,MachineState.ERROR,# 20-27 - 4,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 28-2f -) - -HZ_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) - -HZ_SM_MODEL = {'class_table': HZ_CLS, - 'class_factor': 6, - 'state_table': HZ_ST, - 'char_len_table': HZ_CHAR_LEN_TABLE, - 'name': "HZ-GB-2312", - 'language': 'Chinese'} - -ISO2022CN_CLS = ( -2,0,0,0,0,0,0,0, # 00 - 07 -0,0,0,0,0,0,0,0, # 08 - 0f -0,0,0,0,0,0,0,0, # 10 - 17 -0,0,0,1,0,0,0,0, # 18 - 1f -0,0,0,0,0,0,0,0, # 20 - 27 -0,3,0,0,0,0,0,0, # 28 - 2f -0,0,0,0,0,0,0,0, # 30 - 37 -0,0,0,0,0,0,0,0, # 38 - 3f -0,0,0,4,0,0,0,0, # 40 - 47 -0,0,0,0,0,0,0,0, # 48 - 4f -0,0,0,0,0,0,0,0, # 50 - 57 -0,0,0,0,0,0,0,0, # 58 - 5f -0,0,0,0,0,0,0,0, # 60 - 67 -0,0,0,0,0,0,0,0, # 68 - 6f -0,0,0,0,0,0,0,0, # 70 - 77 -0,0,0,0,0,0,0,0, # 78 - 7f -2,2,2,2,2,2,2,2, # 80 - 87 -2,2,2,2,2,2,2,2, # 88 - 8f -2,2,2,2,2,2,2,2, # 90 - 97 -2,2,2,2,2,2,2,2, # 98 - 9f -2,2,2,2,2,2,2,2, # a0 - a7 -2,2,2,2,2,2,2,2, # a8 - af -2,2,2,2,2,2,2,2, # b0 - b7 -2,2,2,2,2,2,2,2, # b8 - bf -2,2,2,2,2,2,2,2, # c0 - c7 -2,2,2,2,2,2,2,2, # c8 - cf -2,2,2,2,2,2,2,2, # d0 - d7 -2,2,2,2,2,2,2,2, # d8 - df -2,2,2,2,2,2,2,2, # e0 - e7 -2,2,2,2,2,2,2,2, # e8 - ef -2,2,2,2,2,2,2,2, # f0 - f7 -2,2,2,2,2,2,2,2, # f8 - ff -) - -ISO2022CN_ST = ( -MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07 -MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f -MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17 -MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,# 18-1f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 20-27 - 5, 6,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 28-2f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 30-37 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,# 38-3f -) - -ISO2022CN_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0) - -ISO2022CN_SM_MODEL = {'class_table': ISO2022CN_CLS, - 'class_factor': 9, - 'state_table': ISO2022CN_ST, - 'char_len_table': ISO2022CN_CHAR_LEN_TABLE, - 'name': "ISO-2022-CN", - 'language': 'Chinese'} - -ISO2022JP_CLS = ( -2,0,0,0,0,0,0,0, # 00 - 07 -0,0,0,0,0,0,2,2, # 08 - 0f -0,0,0,0,0,0,0,0, # 10 - 17 -0,0,0,1,0,0,0,0, # 18 - 1f -0,0,0,0,7,0,0,0, # 20 - 27 -3,0,0,0,0,0,0,0, # 28 - 2f -0,0,0,0,0,0,0,0, # 30 - 37 -0,0,0,0,0,0,0,0, # 38 - 3f -6,0,4,0,8,0,0,0, # 40 - 47 -0,9,5,0,0,0,0,0, # 48 - 4f -0,0,0,0,0,0,0,0, # 50 - 57 -0,0,0,0,0,0,0,0, # 58 - 5f -0,0,0,0,0,0,0,0, # 60 - 67 -0,0,0,0,0,0,0,0, # 68 - 6f -0,0,0,0,0,0,0,0, # 70 - 77 -0,0,0,0,0,0,0,0, # 78 - 7f -2,2,2,2,2,2,2,2, # 80 - 87 -2,2,2,2,2,2,2,2, # 88 - 8f -2,2,2,2,2,2,2,2, # 90 - 97 -2,2,2,2,2,2,2,2, # 98 - 9f -2,2,2,2,2,2,2,2, # a0 - a7 -2,2,2,2,2,2,2,2, # a8 - af -2,2,2,2,2,2,2,2, # b0 - b7 -2,2,2,2,2,2,2,2, # b8 - bf -2,2,2,2,2,2,2,2, # c0 - c7 -2,2,2,2,2,2,2,2, # c8 - cf -2,2,2,2,2,2,2,2, # d0 - d7 -2,2,2,2,2,2,2,2, # d8 - df -2,2,2,2,2,2,2,2, # e0 - e7 -2,2,2,2,2,2,2,2, # e8 - ef -2,2,2,2,2,2,2,2, # f0 - f7 -2,2,2,2,2,2,2,2, # f8 - ff -) - -ISO2022JP_ST = ( -MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 00-07 -MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 08-0f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 10-17 -MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,# 18-1f -MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 20-27 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 6,MachineState.ITS_ME,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,# 28-2f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,# 30-37 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 38-3f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.START,# 40-47 -) - -ISO2022JP_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) - -ISO2022JP_SM_MODEL = {'class_table': ISO2022JP_CLS, - 'class_factor': 10, - 'state_table': ISO2022JP_ST, - 'char_len_table': ISO2022JP_CHAR_LEN_TABLE, - 'name': "ISO-2022-JP", - 'language': 'Japanese'} - -ISO2022KR_CLS = ( -2,0,0,0,0,0,0,0, # 00 - 07 -0,0,0,0,0,0,0,0, # 08 - 0f -0,0,0,0,0,0,0,0, # 10 - 17 -0,0,0,1,0,0,0,0, # 18 - 1f -0,0,0,0,3,0,0,0, # 20 - 27 -0,4,0,0,0,0,0,0, # 28 - 2f -0,0,0,0,0,0,0,0, # 30 - 37 -0,0,0,0,0,0,0,0, # 38 - 3f -0,0,0,5,0,0,0,0, # 40 - 47 -0,0,0,0,0,0,0,0, # 48 - 4f -0,0,0,0,0,0,0,0, # 50 - 57 -0,0,0,0,0,0,0,0, # 58 - 5f -0,0,0,0,0,0,0,0, # 60 - 67 -0,0,0,0,0,0,0,0, # 68 - 6f -0,0,0,0,0,0,0,0, # 70 - 77 -0,0,0,0,0,0,0,0, # 78 - 7f -2,2,2,2,2,2,2,2, # 80 - 87 -2,2,2,2,2,2,2,2, # 88 - 8f -2,2,2,2,2,2,2,2, # 90 - 97 -2,2,2,2,2,2,2,2, # 98 - 9f -2,2,2,2,2,2,2,2, # a0 - a7 -2,2,2,2,2,2,2,2, # a8 - af -2,2,2,2,2,2,2,2, # b0 - b7 -2,2,2,2,2,2,2,2, # b8 - bf -2,2,2,2,2,2,2,2, # c0 - c7 -2,2,2,2,2,2,2,2, # c8 - cf -2,2,2,2,2,2,2,2, # d0 - d7 -2,2,2,2,2,2,2,2, # d8 - df -2,2,2,2,2,2,2,2, # e0 - e7 -2,2,2,2,2,2,2,2, # e8 - ef -2,2,2,2,2,2,2,2, # f0 - f7 -2,2,2,2,2,2,2,2, # f8 - ff -) - -ISO2022KR_ST = ( -MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,# 00-07 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,# 08-0f -MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 4,MachineState.ERROR,MachineState.ERROR,# 10-17 -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,# 18-1f -MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.START,MachineState.START,MachineState.START,MachineState.START,# 20-27 -) - -ISO2022KR_CHAR_LEN_TABLE = (0, 0, 0, 0, 0, 0) - -ISO2022KR_SM_MODEL = {'class_table': ISO2022KR_CLS, - 'class_factor': 6, - 'state_table': ISO2022KR_ST, - 'char_len_table': ISO2022KR_CHAR_LEN_TABLE, - 'name': "ISO-2022-KR", - 'language': 'Korean'} - - diff --git a/lib/chardet/eucjpprober.py b/lib/chardet/eucjpprober.py deleted file mode 100644 index 20ce8f7..0000000 --- a/lib/chardet/eucjpprober.py +++ /dev/null @@ -1,92 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .enums import ProbingState, MachineState -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import EUCJPDistributionAnalysis -from .jpcntx import EUCJPContextAnalysis -from .mbcssm import EUCJP_SM_MODEL - - -class EUCJPProber(MultiByteCharSetProber): - def __init__(self): - super(EUCJPProber, self).__init__() - self.coding_sm = CodingStateMachine(EUCJP_SM_MODEL) - self.distribution_analyzer = EUCJPDistributionAnalysis() - self.context_analyzer = EUCJPContextAnalysis() - self.reset() - - def reset(self): - super(EUCJPProber, self).reset() - self.context_analyzer.reset() - - @property - def charset_name(self): - return "EUC-JP" - - @property - def language(self): - return "Japanese" - - def feed(self, byte_str): - for i in range(len(byte_str)): - # PY3K: byte_str is a byte array, so byte_str[i] is an int, not a byte - coding_state = self.coding_sm.next_state(byte_str[i]) - if coding_state == MachineState.ERROR: - self.logger.debug('%s %s prober hit error at byte %s', - self.charset_name, self.language, i) - self._state = ProbingState.NOT_ME - break - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - break - elif coding_state == MachineState.START: - char_len = self.coding_sm.get_current_charlen() - if i == 0: - self._last_char[1] = byte_str[0] - self.context_analyzer.feed(self._last_char, char_len) - self.distribution_analyzer.feed(self._last_char, char_len) - else: - self.context_analyzer.feed(byte_str[i - 1:i + 1], - char_len) - self.distribution_analyzer.feed(byte_str[i - 1:i + 1], - char_len) - - self._last_char[0] = byte_str[-1] - - if self.state == ProbingState.DETECTING: - if (self.context_analyzer.got_enough_data() and - (self.get_confidence() > self.SHORTCUT_THRESHOLD)): - self._state = ProbingState.FOUND_IT - - return self.state - - def get_confidence(self): - context_conf = self.context_analyzer.get_confidence() - distrib_conf = self.distribution_analyzer.get_confidence() - return max(context_conf, distrib_conf) diff --git a/lib/chardet/euckrfreq.py b/lib/chardet/euckrfreq.py deleted file mode 100644 index b68078c..0000000 --- a/lib/chardet/euckrfreq.py +++ /dev/null @@ -1,195 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# Sampling from about 20M text materials include literature and computer technology - -# 128 --> 0.79 -# 256 --> 0.92 -# 512 --> 0.986 -# 1024 --> 0.99944 -# 2048 --> 0.99999 -# -# Idea Distribution Ratio = 0.98653 / (1-0.98653) = 73.24 -# Random Distribution Ration = 512 / (2350-512) = 0.279. -# -# Typical Distribution Ratio - -EUCKR_TYPICAL_DISTRIBUTION_RATIO = 6.0 - -EUCKR_TABLE_SIZE = 2352 - -# Char to FreqOrder table , -EUCKR_CHAR_TO_FREQ_ORDER = ( - 13, 130, 120,1396, 481,1719,1720, 328, 609, 212,1721, 707, 400, 299,1722, 87, -1397,1723, 104, 536,1117,1203,1724,1267, 685,1268, 508,1725,1726,1727,1728,1398, -1399,1729,1730,1731, 141, 621, 326,1057, 368,1732, 267, 488, 20,1733,1269,1734, - 945,1400,1735, 47, 904,1270,1736,1737, 773, 248,1738, 409, 313, 786, 429,1739, - 116, 987, 813,1401, 683, 75,1204, 145,1740,1741,1742,1743, 16, 847, 667, 622, - 708,1744,1745,1746, 966, 787, 304, 129,1747, 60, 820, 123, 676,1748,1749,1750, -1751, 617,1752, 626,1753,1754,1755,1756, 653,1757,1758,1759,1760,1761,1762, 856, - 344,1763,1764,1765,1766, 89, 401, 418, 806, 905, 848,1767,1768,1769, 946,1205, - 709,1770,1118,1771, 241,1772,1773,1774,1271,1775, 569,1776, 999,1777,1778,1779, -1780, 337, 751,1058, 28, 628, 254,1781, 177, 906, 270, 349, 891,1079,1782, 19, -1783, 379,1784, 315,1785, 629, 754,1402, 559,1786, 636, 203,1206,1787, 710, 567, -1788, 935, 814,1789,1790,1207, 766, 528,1791,1792,1208,1793,1794,1795,1796,1797, -1403,1798,1799, 533,1059,1404,1405,1156,1406, 936, 884,1080,1800, 351,1801,1802, -1803,1804,1805, 801,1806,1807,1808,1119,1809,1157, 714, 474,1407,1810, 298, 899, - 885,1811,1120, 802,1158,1812, 892,1813,1814,1408, 659,1815,1816,1121,1817,1818, -1819,1820,1821,1822, 319,1823, 594, 545,1824, 815, 937,1209,1825,1826, 573,1409, -1022,1827,1210,1828,1829,1830,1831,1832,1833, 556, 722, 807,1122,1060,1834, 697, -1835, 900, 557, 715,1836,1410, 540,1411, 752,1159, 294, 597,1211, 976, 803, 770, -1412,1837,1838, 39, 794,1413, 358,1839, 371, 925,1840, 453, 661, 788, 531, 723, - 544,1023,1081, 869, 91,1841, 392, 430, 790, 602,1414, 677,1082, 457,1415,1416, -1842,1843, 475, 327,1024,1417, 795, 121,1844, 733, 403,1418,1845,1846,1847, 300, - 119, 711,1212, 627,1848,1272, 207,1849,1850, 796,1213, 382,1851, 519,1852,1083, - 893,1853,1854,1855, 367, 809, 487, 671,1856, 663,1857,1858, 956, 471, 306, 857, -1859,1860,1160,1084,1861,1862,1863,1864,1865,1061,1866,1867,1868,1869,1870,1871, - 282, 96, 574,1872, 502,1085,1873,1214,1874, 907,1875,1876, 827, 977,1419,1420, -1421, 268,1877,1422,1878,1879,1880, 308,1881, 2, 537,1882,1883,1215,1884,1885, - 127, 791,1886,1273,1423,1887, 34, 336, 404, 643,1888, 571, 654, 894, 840,1889, - 0, 886,1274, 122, 575, 260, 908, 938,1890,1275, 410, 316,1891,1892, 100,1893, -1894,1123, 48,1161,1124,1025,1895, 633, 901,1276,1896,1897, 115, 816,1898, 317, -1899, 694,1900, 909, 734,1424, 572, 866,1425, 691, 85, 524,1010, 543, 394, 841, -1901,1902,1903,1026,1904,1905,1906,1907,1908,1909, 30, 451, 651, 988, 310,1910, -1911,1426, 810,1216, 93,1912,1913,1277,1217,1914, 858, 759, 45, 58, 181, 610, - 269,1915,1916, 131,1062, 551, 443,1000, 821,1427, 957, 895,1086,1917,1918, 375, -1919, 359,1920, 687,1921, 822,1922, 293,1923,1924, 40, 662, 118, 692, 29, 939, - 887, 640, 482, 174,1925, 69,1162, 728,1428, 910,1926,1278,1218,1279, 386, 870, - 217, 854,1163, 823,1927,1928,1929,1930, 834,1931, 78,1932, 859,1933,1063,1934, -1935,1936,1937, 438,1164, 208, 595,1938,1939,1940,1941,1219,1125,1942, 280, 888, -1429,1430,1220,1431,1943,1944,1945,1946,1947,1280, 150, 510,1432,1948,1949,1950, -1951,1952,1953,1954,1011,1087,1955,1433,1043,1956, 881,1957, 614, 958,1064,1065, -1221,1958, 638,1001, 860, 967, 896,1434, 989, 492, 553,1281,1165,1959,1282,1002, -1283,1222,1960,1961,1962,1963, 36, 383, 228, 753, 247, 454,1964, 876, 678,1965, -1966,1284, 126, 464, 490, 835, 136, 672, 529, 940,1088,1435, 473,1967,1968, 467, - 50, 390, 227, 587, 279, 378, 598, 792, 968, 240, 151, 160, 849, 882,1126,1285, - 639,1044, 133, 140, 288, 360, 811, 563,1027, 561, 142, 523,1969,1970,1971, 7, - 103, 296, 439, 407, 506, 634, 990,1972,1973,1974,1975, 645,1976,1977,1978,1979, -1980,1981, 236,1982,1436,1983,1984,1089, 192, 828, 618, 518,1166, 333,1127,1985, - 818,1223,1986,1987,1988,1989,1990,1991,1992,1993, 342,1128,1286, 746, 842,1994, -1995, 560, 223,1287, 98, 8, 189, 650, 978,1288,1996,1437,1997, 17, 345, 250, - 423, 277, 234, 512, 226, 97, 289, 42, 167,1998, 201,1999,2000, 843, 836, 824, - 532, 338, 783,1090, 182, 576, 436,1438,1439, 527, 500,2001, 947, 889,2002,2003, -2004,2005, 262, 600, 314, 447,2006, 547,2007, 693, 738,1129,2008, 71,1440, 745, - 619, 688,2009, 829,2010,2011, 147,2012, 33, 948,2013,2014, 74, 224,2015, 61, - 191, 918, 399, 637,2016,1028,1130, 257, 902,2017,2018,2019,2020,2021,2022,2023, -2024,2025,2026, 837,2027,2028,2029,2030, 179, 874, 591, 52, 724, 246,2031,2032, -2033,2034,1167, 969,2035,1289, 630, 605, 911,1091,1168,2036,2037,2038,1441, 912, -2039, 623,2040,2041, 253,1169,1290,2042,1442, 146, 620, 611, 577, 433,2043,1224, - 719,1170, 959, 440, 437, 534, 84, 388, 480,1131, 159, 220, 198, 679,2044,1012, - 819,1066,1443, 113,1225, 194, 318,1003,1029,2045,2046,2047,2048,1067,2049,2050, -2051,2052,2053, 59, 913, 112,2054, 632,2055, 455, 144, 739,1291,2056, 273, 681, - 499,2057, 448,2058,2059, 760,2060,2061, 970, 384, 169, 245,1132,2062,2063, 414, -1444,2064,2065, 41, 235,2066, 157, 252, 877, 568, 919, 789, 580,2067, 725,2068, -2069,1292,2070,2071,1445,2072,1446,2073,2074, 55, 588, 66,1447, 271,1092,2075, -1226,2076, 960,1013, 372,2077,2078,2079,2080,2081,1293,2082,2083,2084,2085, 850, -2086,2087,2088,2089,2090, 186,2091,1068, 180,2092,2093,2094, 109,1227, 522, 606, -2095, 867,1448,1093, 991,1171, 926, 353,1133,2096, 581,2097,2098,2099,1294,1449, -1450,2100, 596,1172,1014,1228,2101,1451,1295,1173,1229,2102,2103,1296,1134,1452, - 949,1135,2104,2105,1094,1453,1454,1455,2106,1095,2107,2108,2109,2110,2111,2112, -2113,2114,2115,2116,2117, 804,2118,2119,1230,1231, 805,1456, 405,1136,2120,2121, -2122,2123,2124, 720, 701,1297, 992,1457, 927,1004,2125,2126,2127,2128,2129,2130, - 22, 417,2131, 303,2132, 385,2133, 971, 520, 513,2134,1174, 73,1096, 231, 274, - 962,1458, 673,2135,1459,2136, 152,1137,2137,2138,2139,2140,1005,1138,1460,1139, -2141,2142,2143,2144, 11, 374, 844,2145, 154,1232, 46,1461,2146, 838, 830, 721, -1233, 106,2147, 90, 428, 462, 578, 566,1175, 352,2148,2149, 538,1234, 124,1298, -2150,1462, 761, 565,2151, 686,2152, 649,2153, 72, 173,2154, 460, 415,2155,1463, -2156,1235, 305,2157,2158,2159,2160,2161,2162, 579,2163,2164,2165,2166,2167, 747, -2168,2169,2170,2171,1464, 669,2172,2173,2174,2175,2176,1465,2177, 23, 530, 285, -2178, 335, 729,2179, 397,2180,2181,2182,1030,2183,2184, 698,2185,2186, 325,2187, -2188, 369,2189, 799,1097,1015, 348,2190,1069, 680,2191, 851,1466,2192,2193, 10, -2194, 613, 424,2195, 979, 108, 449, 589, 27, 172, 81,1031, 80, 774, 281, 350, -1032, 525, 301, 582,1176,2196, 674,1045,2197,2198,1467, 730, 762,2199,2200,2201, -2202,1468,2203, 993,2204,2205, 266,1070, 963,1140,2206,2207,2208, 664,1098, 972, -2209,2210,2211,1177,1469,1470, 871,2212,2213,2214,2215,2216,1471,2217,2218,2219, -2220,2221,2222,2223,2224,2225,2226,2227,1472,1236,2228,2229,2230,2231,2232,2233, -2234,2235,1299,2236,2237, 200,2238, 477, 373,2239,2240, 731, 825, 777,2241,2242, -2243, 521, 486, 548,2244,2245,2246,1473,1300, 53, 549, 137, 875, 76, 158,2247, -1301,1474, 469, 396,1016, 278, 712,2248, 321, 442, 503, 767, 744, 941,1237,1178, -1475,2249, 82, 178,1141,1179, 973,2250,1302,2251, 297,2252,2253, 570,2254,2255, -2256, 18, 450, 206,2257, 290, 292,1142,2258, 511, 162, 99, 346, 164, 735,2259, -1476,1477, 4, 554, 343, 798,1099,2260,1100,2261, 43, 171,1303, 139, 215,2262, -2263, 717, 775,2264,1033, 322, 216,2265, 831,2266, 149,2267,1304,2268,2269, 702, -1238, 135, 845, 347, 309,2270, 484,2271, 878, 655, 238,1006,1478,2272, 67,2273, - 295,2274,2275, 461,2276, 478, 942, 412,2277,1034,2278,2279,2280, 265,2281, 541, -2282,2283,2284,2285,2286, 70, 852,1071,2287,2288,2289,2290, 21, 56, 509, 117, - 432,2291,2292, 331, 980, 552,1101, 148, 284, 105, 393,1180,1239, 755,2293, 187, -2294,1046,1479,2295, 340,2296, 63,1047, 230,2297,2298,1305, 763,1306, 101, 800, - 808, 494,2299,2300,2301, 903,2302, 37,1072, 14, 5,2303, 79, 675,2304, 312, -2305,2306,2307,2308,2309,1480, 6,1307,2310,2311,2312, 1, 470, 35, 24, 229, -2313, 695, 210, 86, 778, 15, 784, 592, 779, 32, 77, 855, 964,2314, 259,2315, - 501, 380,2316,2317, 83, 981, 153, 689,1308,1481,1482,1483,2318,2319, 716,1484, -2320,2321,2322,2323,2324,2325,1485,2326,2327, 128, 57, 68, 261,1048, 211, 170, -1240, 31,2328, 51, 435, 742,2329,2330,2331, 635,2332, 264, 456,2333,2334,2335, - 425,2336,1486, 143, 507, 263, 943,2337, 363, 920,1487, 256,1488,1102, 243, 601, -1489,2338,2339,2340,2341,2342,2343,2344, 861,2345,2346,2347,2348,2349,2350, 395, -2351,1490,1491, 62, 535, 166, 225,2352,2353, 668, 419,1241, 138, 604, 928,2354, -1181,2355,1492,1493,2356,2357,2358,1143,2359, 696,2360, 387, 307,1309, 682, 476, -2361,2362, 332, 12, 222, 156,2363, 232,2364, 641, 276, 656, 517,1494,1495,1035, - 416, 736,1496,2365,1017, 586,2366,2367,2368,1497,2369, 242,2370,2371,2372,1498, -2373, 965, 713,2374,2375,2376,2377, 740, 982,1499, 944,1500,1007,2378,2379,1310, -1501,2380,2381,2382, 785, 329,2383,2384,1502,2385,2386,2387, 932,2388,1503,2389, -2390,2391,2392,1242,2393,2394,2395,2396,2397, 994, 950,2398,2399,2400,2401,1504, -1311,2402,2403,2404,2405,1049, 749,2406,2407, 853, 718,1144,1312,2408,1182,1505, -2409,2410, 255, 516, 479, 564, 550, 214,1506,1507,1313, 413, 239, 444, 339,1145, -1036,1508,1509,1314,1037,1510,1315,2411,1511,2412,2413,2414, 176, 703, 497, 624, - 593, 921, 302,2415, 341, 165,1103,1512,2416,1513,2417,2418,2419, 376,2420, 700, -2421,2422,2423, 258, 768,1316,2424,1183,2425, 995, 608,2426,2427,2428,2429, 221, -2430,2431,2432,2433,2434,2435,2436,2437, 195, 323, 726, 188, 897, 983,1317, 377, - 644,1050, 879,2438, 452,2439,2440,2441,2442,2443,2444, 914,2445,2446,2447,2448, - 915, 489,2449,1514,1184,2450,2451, 515, 64, 427, 495,2452, 583,2453, 483, 485, -1038, 562, 213,1515, 748, 666,2454,2455,2456,2457, 334,2458, 780, 996,1008, 705, -1243,2459,2460,2461,2462,2463, 114,2464, 493,1146, 366, 163,1516, 961,1104,2465, - 291,2466,1318,1105,2467,1517, 365,2468, 355, 951,1244,2469,1319,2470, 631,2471, -2472, 218,1320, 364, 320, 756,1518,1519,1321,1520,1322,2473,2474,2475,2476, 997, -2477,2478,2479,2480, 665,1185,2481, 916,1521,2482,2483,2484, 584, 684,2485,2486, - 797,2487,1051,1186,2488,2489,2490,1522,2491,2492, 370,2493,1039,1187, 65,2494, - 434, 205, 463,1188,2495, 125, 812, 391, 402, 826, 699, 286, 398, 155, 781, 771, - 585,2496, 590, 505,1073,2497, 599, 244, 219, 917,1018, 952, 646,1523,2498,1323, -2499,2500, 49, 984, 354, 741,2501, 625,2502,1324,2503,1019, 190, 357, 757, 491, - 95, 782, 868,2504,2505,2506,2507,2508,2509, 134,1524,1074, 422,1525, 898,2510, - 161,2511,2512,2513,2514, 769,2515,1526,2516,2517, 411,1325,2518, 472,1527,2519, -2520,2521,2522,2523,2524, 985,2525,2526,2527,2528,2529,2530, 764,2531,1245,2532, -2533, 25, 204, 311,2534, 496,2535,1052,2536,2537,2538,2539,2540,2541,2542, 199, - 704, 504, 468, 758, 657,1528, 196, 44, 839,1246, 272, 750,2543, 765, 862,2544, -2545,1326,2546, 132, 615, 933,2547, 732,2548,2549,2550,1189,1529,2551, 283,1247, -1053, 607, 929,2552,2553,2554, 930, 183, 872, 616,1040,1147,2555,1148,1020, 441, - 249,1075,2556,2557,2558, 466, 743,2559,2560,2561, 92, 514, 426, 420, 526,2562, -2563,2564,2565,2566,2567,2568, 185,2569,2570,2571,2572, 776,1530, 658,2573, 362, -2574, 361, 922,1076, 793,2575,2576,2577,2578,2579,2580,1531, 251,2581,2582,2583, -2584,1532, 54, 612, 237,1327,2585,2586, 275, 408, 647, 111,2587,1533,1106, 465, - 3, 458, 9, 38,2588, 107, 110, 890, 209, 26, 737, 498,2589,1534,2590, 431, - 202, 88,1535, 356, 287,1107, 660,1149,2591, 381,1536, 986,1150, 445,1248,1151, - 974,2592,2593, 846,2594, 446, 953, 184,1249,1250, 727,2595, 923, 193, 883,2596, -2597,2598, 102, 324, 539, 817,2599, 421,1041,2600, 832,2601, 94, 175, 197, 406, -2602, 459,2603,2604,2605,2606,2607, 330, 555,2608,2609,2610, 706,1108, 389,2611, -2612,2613,2614, 233,2615, 833, 558, 931, 954,1251,2616,2617,1537, 546,2618,2619, -1009,2620,2621,2622,1538, 690,1328,2623, 955,2624,1539,2625,2626, 772,2627,2628, -2629,2630,2631, 924, 648, 863, 603,2632,2633, 934,1540, 864, 865,2634, 642,1042, - 670,1190,2635,2636,2637,2638, 168,2639, 652, 873, 542,1054,1541,2640,2641,2642, # 512, 256 -) - diff --git a/lib/chardet/euckrprober.py b/lib/chardet/euckrprober.py deleted file mode 100644 index 345a060..0000000 --- a/lib/chardet/euckrprober.py +++ /dev/null @@ -1,47 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import EUCKRDistributionAnalysis -from .mbcssm import EUCKR_SM_MODEL - - -class EUCKRProber(MultiByteCharSetProber): - def __init__(self): - super(EUCKRProber, self).__init__() - self.coding_sm = CodingStateMachine(EUCKR_SM_MODEL) - self.distribution_analyzer = EUCKRDistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "EUC-KR" - - @property - def language(self): - return "Korean" diff --git a/lib/chardet/euctwfreq.py b/lib/chardet/euctwfreq.py deleted file mode 100644 index ed7a995..0000000 --- a/lib/chardet/euctwfreq.py +++ /dev/null @@ -1,387 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# EUCTW frequency table -# Converted from big5 work -# by Taiwan's Mandarin Promotion Council -# <http:#www.edu.tw:81/mandr/> - -# 128 --> 0.42261 -# 256 --> 0.57851 -# 512 --> 0.74851 -# 1024 --> 0.89384 -# 2048 --> 0.97583 -# -# Idea Distribution Ratio = 0.74851/(1-0.74851) =2.98 -# Random Distribution Ration = 512/(5401-512)=0.105 -# -# Typical Distribution Ratio about 25% of Ideal one, still much higher than RDR - -EUCTW_TYPICAL_DISTRIBUTION_RATIO = 0.75 - -# Char to FreqOrder table , -EUCTW_TABLE_SIZE = 5376 - -EUCTW_CHAR_TO_FREQ_ORDER = ( - 1,1800,1506, 255,1431, 198, 9, 82, 6,7310, 177, 202,3615,1256,2808, 110, # 2742 -3735, 33,3241, 261, 76, 44,2113, 16,2931,2184,1176, 659,3868, 26,3404,2643, # 2758 -1198,3869,3313,4060, 410,2211, 302, 590, 361,1963, 8, 204, 58,4296,7311,1931, # 2774 - 63,7312,7313, 317,1614, 75, 222, 159,4061,2412,1480,7314,3500,3068, 224,2809, # 2790 -3616, 3, 10,3870,1471, 29,2774,1135,2852,1939, 873, 130,3242,1123, 312,7315, # 2806 -4297,2051, 507, 252, 682,7316, 142,1914, 124, 206,2932, 34,3501,3173, 64, 604, # 2822 -7317,2494,1976,1977, 155,1990, 645, 641,1606,7318,3405, 337, 72, 406,7319, 80, # 2838 - 630, 238,3174,1509, 263, 939,1092,2644, 756,1440,1094,3406, 449, 69,2969, 591, # 2854 - 179,2095, 471, 115,2034,1843, 60, 50,2970, 134, 806,1868, 734,2035,3407, 180, # 2870 - 995,1607, 156, 537,2893, 688,7320, 319,1305, 779,2144, 514,2374, 298,4298, 359, # 2886 -2495, 90,2707,1338, 663, 11, 906,1099,2545, 20,2436, 182, 532,1716,7321, 732, # 2902 -1376,4062,1311,1420,3175, 25,2312,1056, 113, 399, 382,1949, 242,3408,2467, 529, # 2918 -3243, 475,1447,3617,7322, 117, 21, 656, 810,1297,2295,2329,3502,7323, 126,4063, # 2934 - 706, 456, 150, 613,4299, 71,1118,2036,4064, 145,3069, 85, 835, 486,2114,1246, # 2950 -1426, 428, 727,1285,1015, 800, 106, 623, 303,1281,7324,2127,2354, 347,3736, 221, # 2966 -3503,3110,7325,1955,1153,4065, 83, 296,1199,3070, 192, 624, 93,7326, 822,1897, # 2982 -2810,3111, 795,2064, 991,1554,1542,1592, 27, 43,2853, 859, 139,1456, 860,4300, # 2998 - 437, 712,3871, 164,2392,3112, 695, 211,3017,2096, 195,3872,1608,3504,3505,3618, # 3014 -3873, 234, 811,2971,2097,3874,2229,1441,3506,1615,2375, 668,2076,1638, 305, 228, # 3030 -1664,4301, 467, 415,7327, 262,2098,1593, 239, 108, 300, 200,1033, 512,1247,2077, # 3046 -7328,7329,2173,3176,3619,2673, 593, 845,1062,3244, 88,1723,2037,3875,1950, 212, # 3062 - 266, 152, 149, 468,1898,4066,4302, 77, 187,7330,3018, 37, 5,2972,7331,3876, # 3078 -7332,7333, 39,2517,4303,2894,3177,2078, 55, 148, 74,4304, 545, 483,1474,1029, # 3094 -1665, 217,1869,1531,3113,1104,2645,4067, 24, 172,3507, 900,3877,3508,3509,4305, # 3110 - 32,1408,2811,1312, 329, 487,2355,2247,2708, 784,2674, 4,3019,3314,1427,1788, # 3126 - 188, 109, 499,7334,3620,1717,1789, 888,1217,3020,4306,7335,3510,7336,3315,1520, # 3142 -3621,3878, 196,1034, 775,7337,7338, 929,1815, 249, 439, 38,7339,1063,7340, 794, # 3158 -3879,1435,2296, 46, 178,3245,2065,7341,2376,7342, 214,1709,4307, 804, 35, 707, # 3174 - 324,3622,1601,2546, 140, 459,4068,7343,7344,1365, 839, 272, 978,2257,2572,3409, # 3190 -2128,1363,3623,1423, 697, 100,3071, 48, 70,1231, 495,3114,2193,7345,1294,7346, # 3206 -2079, 462, 586,1042,3246, 853, 256, 988, 185,2377,3410,1698, 434,1084,7347,3411, # 3222 - 314,2615,2775,4308,2330,2331, 569,2280, 637,1816,2518, 757,1162,1878,1616,3412, # 3238 - 287,1577,2115, 768,4309,1671,2854,3511,2519,1321,3737, 909,2413,7348,4069, 933, # 3254 -3738,7349,2052,2356,1222,4310, 765,2414,1322, 786,4311,7350,1919,1462,1677,2895, # 3270 -1699,7351,4312,1424,2437,3115,3624,2590,3316,1774,1940,3413,3880,4070, 309,1369, # 3286 -1130,2812, 364,2230,1653,1299,3881,3512,3882,3883,2646, 525,1085,3021, 902,2000, # 3302 -1475, 964,4313, 421,1844,1415,1057,2281, 940,1364,3116, 376,4314,4315,1381, 7, # 3318 -2520, 983,2378, 336,1710,2675,1845, 321,3414, 559,1131,3022,2742,1808,1132,1313, # 3334 - 265,1481,1857,7352, 352,1203,2813,3247, 167,1089, 420,2814, 776, 792,1724,3513, # 3350 -4071,2438,3248,7353,4072,7354, 446, 229, 333,2743, 901,3739,1200,1557,4316,2647, # 3366 -1920, 395,2744,2676,3740,4073,1835, 125, 916,3178,2616,4317,7355,7356,3741,7357, # 3382 -7358,7359,4318,3117,3625,1133,2547,1757,3415,1510,2313,1409,3514,7360,2145, 438, # 3398 -2591,2896,2379,3317,1068, 958,3023, 461, 311,2855,2677,4074,1915,3179,4075,1978, # 3414 - 383, 750,2745,2617,4076, 274, 539, 385,1278,1442,7361,1154,1964, 384, 561, 210, # 3430 - 98,1295,2548,3515,7362,1711,2415,1482,3416,3884,2897,1257, 129,7363,3742, 642, # 3446 - 523,2776,2777,2648,7364, 141,2231,1333, 68, 176, 441, 876, 907,4077, 603,2592, # 3462 - 710, 171,3417, 404, 549, 18,3118,2393,1410,3626,1666,7365,3516,4319,2898,4320, # 3478 -7366,2973, 368,7367, 146, 366, 99, 871,3627,1543, 748, 807,1586,1185, 22,2258, # 3494 - 379,3743,3180,7368,3181, 505,1941,2618,1991,1382,2314,7369, 380,2357, 218, 702, # 3510 -1817,1248,3418,3024,3517,3318,3249,7370,2974,3628, 930,3250,3744,7371, 59,7372, # 3526 - 585, 601,4078, 497,3419,1112,1314,4321,1801,7373,1223,1472,2174,7374, 749,1836, # 3542 - 690,1899,3745,1772,3885,1476, 429,1043,1790,2232,2116, 917,4079, 447,1086,1629, # 3558 -7375, 556,7376,7377,2020,1654, 844,1090, 105, 550, 966,1758,2815,1008,1782, 686, # 3574 -1095,7378,2282, 793,1602,7379,3518,2593,4322,4080,2933,2297,4323,3746, 980,2496, # 3590 - 544, 353, 527,4324, 908,2678,2899,7380, 381,2619,1942,1348,7381,1341,1252, 560, # 3606 -3072,7382,3420,2856,7383,2053, 973, 886,2080, 143,4325,7384,7385, 157,3886, 496, # 3622 -4081, 57, 840, 540,2038,4326,4327,3421,2117,1445, 970,2259,1748,1965,2081,4082, # 3638 -3119,1234,1775,3251,2816,3629, 773,1206,2129,1066,2039,1326,3887,1738,1725,4083, # 3654 - 279,3120, 51,1544,2594, 423,1578,2130,2066, 173,4328,1879,7386,7387,1583, 264, # 3670 - 610,3630,4329,2439, 280, 154,7388,7389,7390,1739, 338,1282,3073, 693,2857,1411, # 3686 -1074,3747,2440,7391,4330,7392,7393,1240, 952,2394,7394,2900,1538,2679, 685,1483, # 3702 -4084,2468,1436, 953,4085,2054,4331, 671,2395, 79,4086,2441,3252, 608, 567,2680, # 3718 -3422,4087,4088,1691, 393,1261,1791,2396,7395,4332,7396,7397,7398,7399,1383,1672, # 3734 -3748,3182,1464, 522,1119, 661,1150, 216, 675,4333,3888,1432,3519, 609,4334,2681, # 3750 -2397,7400,7401,7402,4089,3025, 0,7403,2469, 315, 231,2442, 301,3319,4335,2380, # 3766 -7404, 233,4090,3631,1818,4336,4337,7405, 96,1776,1315,2082,7406, 257,7407,1809, # 3782 -3632,2709,1139,1819,4091,2021,1124,2163,2778,1777,2649,7408,3074, 363,1655,3183, # 3798 -7409,2975,7410,7411,7412,3889,1567,3890, 718, 103,3184, 849,1443, 341,3320,2934, # 3814 -1484,7413,1712, 127, 67, 339,4092,2398, 679,1412, 821,7414,7415, 834, 738, 351, # 3830 -2976,2146, 846, 235,1497,1880, 418,1992,3749,2710, 186,1100,2147,2746,3520,1545, # 3846 -1355,2935,2858,1377, 583,3891,4093,2573,2977,7416,1298,3633,1078,2549,3634,2358, # 3862 - 78,3750,3751, 267,1289,2099,2001,1594,4094, 348, 369,1274,2194,2175,1837,4338, # 3878 -1820,2817,3635,2747,2283,2002,4339,2936,2748, 144,3321, 882,4340,3892,2749,3423, # 3894 -4341,2901,7417,4095,1726, 320,7418,3893,3026, 788,2978,7419,2818,1773,1327,2859, # 3910 -3894,2819,7420,1306,4342,2003,1700,3752,3521,2359,2650, 787,2022, 506, 824,3636, # 3926 - 534, 323,4343,1044,3322,2023,1900, 946,3424,7421,1778,1500,1678,7422,1881,4344, # 3942 - 165, 243,4345,3637,2521, 123, 683,4096, 764,4346, 36,3895,1792, 589,2902, 816, # 3958 - 626,1667,3027,2233,1639,1555,1622,3753,3896,7423,3897,2860,1370,1228,1932, 891, # 3974 -2083,2903, 304,4097,7424, 292,2979,2711,3522, 691,2100,4098,1115,4347, 118, 662, # 3990 -7425, 611,1156, 854,2381,1316,2861, 2, 386, 515,2904,7426,7427,3253, 868,2234, # 4006 -1486, 855,2651, 785,2212,3028,7428,1040,3185,3523,7429,3121, 448,7430,1525,7431, # 4022 -2164,4348,7432,3754,7433,4099,2820,3524,3122, 503, 818,3898,3123,1568, 814, 676, # 4038 -1444, 306,1749,7434,3755,1416,1030, 197,1428, 805,2821,1501,4349,7435,7436,7437, # 4054 -1993,7438,4350,7439,7440,2195, 13,2779,3638,2980,3124,1229,1916,7441,3756,2131, # 4070 -7442,4100,4351,2399,3525,7443,2213,1511,1727,1120,7444,7445, 646,3757,2443, 307, # 4086 -7446,7447,1595,3186,7448,7449,7450,3639,1113,1356,3899,1465,2522,2523,7451, 519, # 4102 -7452, 128,2132, 92,2284,1979,7453,3900,1512, 342,3125,2196,7454,2780,2214,1980, # 4118 -3323,7455, 290,1656,1317, 789, 827,2360,7456,3758,4352, 562, 581,3901,7457, 401, # 4134 -4353,2248, 94,4354,1399,2781,7458,1463,2024,4355,3187,1943,7459, 828,1105,4101, # 4150 -1262,1394,7460,4102, 605,4356,7461,1783,2862,7462,2822, 819,2101, 578,2197,2937, # 4166 -7463,1502, 436,3254,4103,3255,2823,3902,2905,3425,3426,7464,2712,2315,7465,7466, # 4182 -2332,2067, 23,4357, 193, 826,3759,2102, 699,1630,4104,3075, 390,1793,1064,3526, # 4198 -7467,1579,3076,3077,1400,7468,4105,1838,1640,2863,7469,4358,4359, 137,4106, 598, # 4214 -3078,1966, 780, 104, 974,2938,7470, 278, 899, 253, 402, 572, 504, 493,1339,7471, # 4230 -3903,1275,4360,2574,2550,7472,3640,3029,3079,2249, 565,1334,2713, 863, 41,7473, # 4246 -7474,4361,7475,1657,2333, 19, 463,2750,4107, 606,7476,2981,3256,1087,2084,1323, # 4262 -2652,2982,7477,1631,1623,1750,4108,2682,7478,2864, 791,2714,2653,2334, 232,2416, # 4278 -7479,2983,1498,7480,2654,2620, 755,1366,3641,3257,3126,2025,1609, 119,1917,3427, # 4294 - 862,1026,4109,7481,3904,3760,4362,3905,4363,2260,1951,2470,7482,1125, 817,4110, # 4310 -4111,3906,1513,1766,2040,1487,4112,3030,3258,2824,3761,3127,7483,7484,1507,7485, # 4326 -2683, 733, 40,1632,1106,2865, 345,4113, 841,2524, 230,4364,2984,1846,3259,3428, # 4342 -7486,1263, 986,3429,7487, 735, 879, 254,1137, 857, 622,1300,1180,1388,1562,3907, # 4358 -3908,2939, 967,2751,2655,1349, 592,2133,1692,3324,2985,1994,4114,1679,3909,1901, # 4374 -2185,7488, 739,3642,2715,1296,1290,7489,4115,2198,2199,1921,1563,2595,2551,1870, # 4390 -2752,2986,7490, 435,7491, 343,1108, 596, 17,1751,4365,2235,3430,3643,7492,4366, # 4406 - 294,3527,2940,1693, 477, 979, 281,2041,3528, 643,2042,3644,2621,2782,2261,1031, # 4422 -2335,2134,2298,3529,4367, 367,1249,2552,7493,3530,7494,4368,1283,3325,2004, 240, # 4438 -1762,3326,4369,4370, 836,1069,3128, 474,7495,2148,2525, 268,3531,7496,3188,1521, # 4454 -1284,7497,1658,1546,4116,7498,3532,3533,7499,4117,3327,2684,1685,4118, 961,1673, # 4470 -2622, 190,2005,2200,3762,4371,4372,7500, 570,2497,3645,1490,7501,4373,2623,3260, # 4486 -1956,4374, 584,1514, 396,1045,1944,7502,4375,1967,2444,7503,7504,4376,3910, 619, # 4502 -7505,3129,3261, 215,2006,2783,2553,3189,4377,3190,4378, 763,4119,3763,4379,7506, # 4518 -7507,1957,1767,2941,3328,3646,1174, 452,1477,4380,3329,3130,7508,2825,1253,2382, # 4534 -2186,1091,2285,4120, 492,7509, 638,1169,1824,2135,1752,3911, 648, 926,1021,1324, # 4550 -4381, 520,4382, 997, 847,1007, 892,4383,3764,2262,1871,3647,7510,2400,1784,4384, # 4566 -1952,2942,3080,3191,1728,4121,2043,3648,4385,2007,1701,3131,1551, 30,2263,4122, # 4582 -7511,2026,4386,3534,7512, 501,7513,4123, 594,3431,2165,1821,3535,3432,3536,3192, # 4598 - 829,2826,4124,7514,1680,3132,1225,4125,7515,3262,4387,4126,3133,2336,7516,4388, # 4614 -4127,7517,3912,3913,7518,1847,2383,2596,3330,7519,4389, 374,3914, 652,4128,4129, # 4630 - 375,1140, 798,7520,7521,7522,2361,4390,2264, 546,1659, 138,3031,2445,4391,7523, # 4646 -2250, 612,1848, 910, 796,3765,1740,1371, 825,3766,3767,7524,2906,2554,7525, 692, # 4662 - 444,3032,2624, 801,4392,4130,7526,1491, 244,1053,3033,4131,4132, 340,7527,3915, # 4678 -1041,2987, 293,1168, 87,1357,7528,1539, 959,7529,2236, 721, 694,4133,3768, 219, # 4694 -1478, 644,1417,3331,2656,1413,1401,1335,1389,3916,7530,7531,2988,2362,3134,1825, # 4710 - 730,1515, 184,2827, 66,4393,7532,1660,2943, 246,3332, 378,1457, 226,3433, 975, # 4726 -3917,2944,1264,3537, 674, 696,7533, 163,7534,1141,2417,2166, 713,3538,3333,4394, # 4742 -3918,7535,7536,1186, 15,7537,1079,1070,7538,1522,3193,3539, 276,1050,2716, 758, # 4758 -1126, 653,2945,3263,7539,2337, 889,3540,3919,3081,2989, 903,1250,4395,3920,3434, # 4774 -3541,1342,1681,1718, 766,3264, 286, 89,2946,3649,7540,1713,7541,2597,3334,2990, # 4790 -7542,2947,2215,3194,2866,7543,4396,2498,2526, 181, 387,1075,3921, 731,2187,3335, # 4806 -7544,3265, 310, 313,3435,2299, 770,4134, 54,3034, 189,4397,3082,3769,3922,7545, # 4822 -1230,1617,1849, 355,3542,4135,4398,3336, 111,4136,3650,1350,3135,3436,3035,4137, # 4838 -2149,3266,3543,7546,2784,3923,3924,2991, 722,2008,7547,1071, 247,1207,2338,2471, # 4854 -1378,4399,2009, 864,1437,1214,4400, 373,3770,1142,2216, 667,4401, 442,2753,2555, # 4870 -3771,3925,1968,4138,3267,1839, 837, 170,1107, 934,1336,1882,7548,7549,2118,4139, # 4886 -2828, 743,1569,7550,4402,4140, 582,2384,1418,3437,7551,1802,7552, 357,1395,1729, # 4902 -3651,3268,2418,1564,2237,7553,3083,3772,1633,4403,1114,2085,4141,1532,7554, 482, # 4918 -2446,4404,7555,7556,1492, 833,1466,7557,2717,3544,1641,2829,7558,1526,1272,3652, # 4934 -4142,1686,1794, 416,2556,1902,1953,1803,7559,3773,2785,3774,1159,2316,7560,2867, # 4950 -4405,1610,1584,3036,2419,2754, 443,3269,1163,3136,7561,7562,3926,7563,4143,2499, # 4966 -3037,4406,3927,3137,2103,1647,3545,2010,1872,4144,7564,4145, 431,3438,7565, 250, # 4982 - 97, 81,4146,7566,1648,1850,1558, 160, 848,7567, 866, 740,1694,7568,2201,2830, # 4998 -3195,4147,4407,3653,1687, 950,2472, 426, 469,3196,3654,3655,3928,7569,7570,1188, # 5014 - 424,1995, 861,3546,4148,3775,2202,2685, 168,1235,3547,4149,7571,2086,1674,4408, # 5030 -3337,3270, 220,2557,1009,7572,3776, 670,2992, 332,1208, 717,7573,7574,3548,2447, # 5046 -3929,3338,7575, 513,7576,1209,2868,3339,3138,4409,1080,7577,7578,7579,7580,2527, # 5062 -3656,3549, 815,1587,3930,3931,7581,3550,3439,3777,1254,4410,1328,3038,1390,3932, # 5078 -1741,3933,3778,3934,7582, 236,3779,2448,3271,7583,7584,3657,3780,1273,3781,4411, # 5094 -7585, 308,7586,4412, 245,4413,1851,2473,1307,2575, 430, 715,2136,2449,7587, 270, # 5110 - 199,2869,3935,7588,3551,2718,1753, 761,1754, 725,1661,1840,4414,3440,3658,7589, # 5126 -7590, 587, 14,3272, 227,2598, 326, 480,2265, 943,2755,3552, 291, 650,1883,7591, # 5142 -1702,1226, 102,1547, 62,3441, 904,4415,3442,1164,4150,7592,7593,1224,1548,2756, # 5158 - 391, 498,1493,7594,1386,1419,7595,2055,1177,4416, 813, 880,1081,2363, 566,1145, # 5174 -4417,2286,1001,1035,2558,2599,2238, 394,1286,7596,7597,2068,7598, 86,1494,1730, # 5190 -3936, 491,1588, 745, 897,2948, 843,3340,3937,2757,2870,3273,1768, 998,2217,2069, # 5206 - 397,1826,1195,1969,3659,2993,3341, 284,7599,3782,2500,2137,2119,1903,7600,3938, # 5222 -2150,3939,4151,1036,3443,1904, 114,2559,4152, 209,1527,7601,7602,2949,2831,2625, # 5238 -2385,2719,3139, 812,2560,7603,3274,7604,1559, 737,1884,3660,1210, 885, 28,2686, # 5254 -3553,3783,7605,4153,1004,1779,4418,7606, 346,1981,2218,2687,4419,3784,1742, 797, # 5270 -1642,3940,1933,1072,1384,2151, 896,3941,3275,3661,3197,2871,3554,7607,2561,1958, # 5286 -4420,2450,1785,7608,7609,7610,3942,4154,1005,1308,3662,4155,2720,4421,4422,1528, # 5302 -2600, 161,1178,4156,1982, 987,4423,1101,4157, 631,3943,1157,3198,2420,1343,1241, # 5318 -1016,2239,2562, 372, 877,2339,2501,1160, 555,1934, 911,3944,7611, 466,1170, 169, # 5334 -1051,2907,2688,3663,2474,2994,1182,2011,2563,1251,2626,7612, 992,2340,3444,1540, # 5350 -2721,1201,2070,2401,1996,2475,7613,4424, 528,1922,2188,1503,1873,1570,2364,3342, # 5366 -3276,7614, 557,1073,7615,1827,3445,2087,2266,3140,3039,3084, 767,3085,2786,4425, # 5382 -1006,4158,4426,2341,1267,2176,3664,3199, 778,3945,3200,2722,1597,2657,7616,4427, # 5398 -7617,3446,7618,7619,7620,3277,2689,1433,3278, 131, 95,1504,3946, 723,4159,3141, # 5414 -1841,3555,2758,2189,3947,2027,2104,3665,7621,2995,3948,1218,7622,3343,3201,3949, # 5430 -4160,2576, 248,1634,3785, 912,7623,2832,3666,3040,3786, 654, 53,7624,2996,7625, # 5446 -1688,4428, 777,3447,1032,3950,1425,7626, 191, 820,2120,2833, 971,4429, 931,3202, # 5462 - 135, 664, 783,3787,1997, 772,2908,1935,3951,3788,4430,2909,3203, 282,2723, 640, # 5478 -1372,3448,1127, 922, 325,3344,7627,7628, 711,2044,7629,7630,3952,2219,2787,1936, # 5494 -3953,3345,2220,2251,3789,2300,7631,4431,3790,1258,3279,3954,3204,2138,2950,3955, # 5510 -3956,7632,2221, 258,3205,4432, 101,1227,7633,3280,1755,7634,1391,3281,7635,2910, # 5526 -2056, 893,7636,7637,7638,1402,4161,2342,7639,7640,3206,3556,7641,7642, 878,1325, # 5542 -1780,2788,4433, 259,1385,2577, 744,1183,2267,4434,7643,3957,2502,7644, 684,1024, # 5558 -4162,7645, 472,3557,3449,1165,3282,3958,3959, 322,2152, 881, 455,1695,1152,1340, # 5574 - 660, 554,2153,4435,1058,4436,4163, 830,1065,3346,3960,4437,1923,7646,1703,1918, # 5590 -7647, 932,2268, 122,7648,4438, 947, 677,7649,3791,2627, 297,1905,1924,2269,4439, # 5606 -2317,3283,7650,7651,4164,7652,4165, 84,4166, 112, 989,7653, 547,1059,3961, 701, # 5622 -3558,1019,7654,4167,7655,3450, 942, 639, 457,2301,2451, 993,2951, 407, 851, 494, # 5638 -4440,3347, 927,7656,1237,7657,2421,3348, 573,4168, 680, 921,2911,1279,1874, 285, # 5654 - 790,1448,1983, 719,2167,7658,7659,4441,3962,3963,1649,7660,1541, 563,7661,1077, # 5670 -7662,3349,3041,3451, 511,2997,3964,3965,3667,3966,1268,2564,3350,3207,4442,4443, # 5686 -7663, 535,1048,1276,1189,2912,2028,3142,1438,1373,2834,2952,1134,2012,7664,4169, # 5702 -1238,2578,3086,1259,7665, 700,7666,2953,3143,3668,4170,7667,4171,1146,1875,1906, # 5718 -4444,2601,3967, 781,2422, 132,1589, 203, 147, 273,2789,2402, 898,1786,2154,3968, # 5734 -3969,7668,3792,2790,7669,7670,4445,4446,7671,3208,7672,1635,3793, 965,7673,1804, # 5750 -2690,1516,3559,1121,1082,1329,3284,3970,1449,3794, 65,1128,2835,2913,2759,1590, # 5766 -3795,7674,7675, 12,2658, 45, 976,2579,3144,4447, 517,2528,1013,1037,3209,7676, # 5782 -3796,2836,7677,3797,7678,3452,7679,2602, 614,1998,2318,3798,3087,2724,2628,7680, # 5798 -2580,4172, 599,1269,7681,1810,3669,7682,2691,3088, 759,1060, 489,1805,3351,3285, # 5814 -1358,7683,7684,2386,1387,1215,2629,2252, 490,7685,7686,4173,1759,2387,2343,7687, # 5830 -4448,3799,1907,3971,2630,1806,3210,4449,3453,3286,2760,2344, 874,7688,7689,3454, # 5846 -3670,1858, 91,2914,3671,3042,3800,4450,7690,3145,3972,2659,7691,3455,1202,1403, # 5862 -3801,2954,2529,1517,2503,4451,3456,2504,7692,4452,7693,2692,1885,1495,1731,3973, # 5878 -2365,4453,7694,2029,7695,7696,3974,2693,1216, 237,2581,4174,2319,3975,3802,4454, # 5894 -4455,2694,3560,3457, 445,4456,7697,7698,7699,7700,2761, 61,3976,3672,1822,3977, # 5910 -7701, 687,2045, 935, 925, 405,2660, 703,1096,1859,2725,4457,3978,1876,1367,2695, # 5926 -3352, 918,2105,1781,2476, 334,3287,1611,1093,4458, 564,3146,3458,3673,3353, 945, # 5942 -2631,2057,4459,7702,1925, 872,4175,7703,3459,2696,3089, 349,4176,3674,3979,4460, # 5958 -3803,4177,3675,2155,3980,4461,4462,4178,4463,2403,2046, 782,3981, 400, 251,4179, # 5974 -1624,7704,7705, 277,3676, 299,1265, 476,1191,3804,2121,4180,4181,1109, 205,7706, # 5990 -2582,1000,2156,3561,1860,7707,7708,7709,4464,7710,4465,2565, 107,2477,2157,3982, # 6006 -3460,3147,7711,1533, 541,1301, 158, 753,4182,2872,3562,7712,1696, 370,1088,4183, # 6022 -4466,3563, 579, 327, 440, 162,2240, 269,1937,1374,3461, 968,3043, 56,1396,3090, # 6038 -2106,3288,3354,7713,1926,2158,4467,2998,7714,3564,7715,7716,3677,4468,2478,7717, # 6054 -2791,7718,1650,4469,7719,2603,7720,7721,3983,2661,3355,1149,3356,3984,3805,3985, # 6070 -7722,1076, 49,7723, 951,3211,3289,3290, 450,2837, 920,7724,1811,2792,2366,4184, # 6086 -1908,1138,2367,3806,3462,7725,3212,4470,1909,1147,1518,2423,4471,3807,7726,4472, # 6102 -2388,2604, 260,1795,3213,7727,7728,3808,3291, 708,7729,3565,1704,7730,3566,1351, # 6118 -1618,3357,2999,1886, 944,4185,3358,4186,3044,3359,4187,7731,3678, 422, 413,1714, # 6134 -3292, 500,2058,2345,4188,2479,7732,1344,1910, 954,7733,1668,7734,7735,3986,2404, # 6150 -4189,3567,3809,4190,7736,2302,1318,2505,3091, 133,3092,2873,4473, 629, 31,2838, # 6166 -2697,3810,4474, 850, 949,4475,3987,2955,1732,2088,4191,1496,1852,7737,3988, 620, # 6182 -3214, 981,1242,3679,3360,1619,3680,1643,3293,2139,2452,1970,1719,3463,2168,7738, # 6198 -3215,7739,7740,3361,1828,7741,1277,4476,1565,2047,7742,1636,3568,3093,7743, 869, # 6214 -2839, 655,3811,3812,3094,3989,3000,3813,1310,3569,4477,7744,7745,7746,1733, 558, # 6230 -4478,3681, 335,1549,3045,1756,4192,3682,1945,3464,1829,1291,1192, 470,2726,2107, # 6246 -2793, 913,1054,3990,7747,1027,7748,3046,3991,4479, 982,2662,3362,3148,3465,3216, # 6262 -3217,1946,2794,7749, 571,4480,7750,1830,7751,3570,2583,1523,2424,7752,2089, 984, # 6278 -4481,3683,1959,7753,3684, 852, 923,2795,3466,3685, 969,1519, 999,2048,2320,1705, # 6294 -7754,3095, 615,1662, 151, 597,3992,2405,2321,1049, 275,4482,3686,4193, 568,3687, # 6310 -3571,2480,4194,3688,7755,2425,2270, 409,3218,7756,1566,2874,3467,1002, 769,2840, # 6326 - 194,2090,3149,3689,2222,3294,4195, 628,1505,7757,7758,1763,2177,3001,3993, 521, # 6342 -1161,2584,1787,2203,2406,4483,3994,1625,4196,4197, 412, 42,3096, 464,7759,2632, # 6358 -4484,3363,1760,1571,2875,3468,2530,1219,2204,3814,2633,2140,2368,4485,4486,3295, # 6374 -1651,3364,3572,7760,7761,3573,2481,3469,7762,3690,7763,7764,2271,2091, 460,7765, # 6390 -4487,7766,3002, 962, 588,3574, 289,3219,2634,1116, 52,7767,3047,1796,7768,7769, # 6406 -7770,1467,7771,1598,1143,3691,4198,1984,1734,1067,4488,1280,3365, 465,4489,1572, # 6422 - 510,7772,1927,2241,1812,1644,3575,7773,4490,3692,7774,7775,2663,1573,1534,7776, # 6438 -7777,4199, 536,1807,1761,3470,3815,3150,2635,7778,7779,7780,4491,3471,2915,1911, # 6454 -2796,7781,3296,1122, 377,3220,7782, 360,7783,7784,4200,1529, 551,7785,2059,3693, # 6470 -1769,2426,7786,2916,4201,3297,3097,2322,2108,2030,4492,1404, 136,1468,1479, 672, # 6486 -1171,3221,2303, 271,3151,7787,2762,7788,2049, 678,2727, 865,1947,4493,7789,2013, # 6502 -3995,2956,7790,2728,2223,1397,3048,3694,4494,4495,1735,2917,3366,3576,7791,3816, # 6518 - 509,2841,2453,2876,3817,7792,7793,3152,3153,4496,4202,2531,4497,2304,1166,1010, # 6534 - 552, 681,1887,7794,7795,2957,2958,3996,1287,1596,1861,3154, 358, 453, 736, 175, # 6550 - 478,1117, 905,1167,1097,7796,1853,1530,7797,1706,7798,2178,3472,2287,3695,3473, # 6566 -3577,4203,2092,4204,7799,3367,1193,2482,4205,1458,2190,2205,1862,1888,1421,3298, # 6582 -2918,3049,2179,3474, 595,2122,7800,3997,7801,7802,4206,1707,2636, 223,3696,1359, # 6598 - 751,3098, 183,3475,7803,2797,3003, 419,2369, 633, 704,3818,2389, 241,7804,7805, # 6614 -7806, 838,3004,3697,2272,2763,2454,3819,1938,2050,3998,1309,3099,2242,1181,7807, # 6630 -1136,2206,3820,2370,1446,4207,2305,4498,7808,7809,4208,1055,2605, 484,3698,7810, # 6646 -3999, 625,4209,2273,3368,1499,4210,4000,7811,4001,4211,3222,2274,2275,3476,7812, # 6662 -7813,2764, 808,2606,3699,3369,4002,4212,3100,2532, 526,3370,3821,4213, 955,7814, # 6678 -1620,4214,2637,2427,7815,1429,3700,1669,1831, 994, 928,7816,3578,1260,7817,7818, # 6694 -7819,1948,2288, 741,2919,1626,4215,2729,2455, 867,1184, 362,3371,1392,7820,7821, # 6710 -4003,4216,1770,1736,3223,2920,4499,4500,1928,2698,1459,1158,7822,3050,3372,2877, # 6726 -1292,1929,2506,2842,3701,1985,1187,2071,2014,2607,4217,7823,2566,2507,2169,3702, # 6742 -2483,3299,7824,3703,4501,7825,7826, 666,1003,3005,1022,3579,4218,7827,4502,1813, # 6758 -2253, 574,3822,1603, 295,1535, 705,3823,4219, 283, 858, 417,7828,7829,3224,4503, # 6774 -4504,3051,1220,1889,1046,2276,2456,4004,1393,1599, 689,2567, 388,4220,7830,2484, # 6790 - 802,7831,2798,3824,2060,1405,2254,7832,4505,3825,2109,1052,1345,3225,1585,7833, # 6806 - 809,7834,7835,7836, 575,2730,3477, 956,1552,1469,1144,2323,7837,2324,1560,2457, # 6822 -3580,3226,4005, 616,2207,3155,2180,2289,7838,1832,7839,3478,4506,7840,1319,3704, # 6838 -3705,1211,3581,1023,3227,1293,2799,7841,7842,7843,3826, 607,2306,3827, 762,2878, # 6854 -1439,4221,1360,7844,1485,3052,7845,4507,1038,4222,1450,2061,2638,4223,1379,4508, # 6870 -2585,7846,7847,4224,1352,1414,2325,2921,1172,7848,7849,3828,3829,7850,1797,1451, # 6886 -7851,7852,7853,7854,2922,4006,4007,2485,2346, 411,4008,4009,3582,3300,3101,4509, # 6902 -1561,2664,1452,4010,1375,7855,7856, 47,2959, 316,7857,1406,1591,2923,3156,7858, # 6918 -1025,2141,3102,3157, 354,2731, 884,2224,4225,2407, 508,3706, 726,3583, 996,2428, # 6934 -3584, 729,7859, 392,2191,1453,4011,4510,3707,7860,7861,2458,3585,2608,1675,2800, # 6950 - 919,2347,2960,2348,1270,4511,4012, 73,7862,7863, 647,7864,3228,2843,2255,1550, # 6966 -1346,3006,7865,1332, 883,3479,7866,7867,7868,7869,3301,2765,7870,1212, 831,1347, # 6982 -4226,4512,2326,3830,1863,3053, 720,3831,4513,4514,3832,7871,4227,7872,7873,4515, # 6998 -7874,7875,1798,4516,3708,2609,4517,3586,1645,2371,7876,7877,2924, 669,2208,2665, # 7014 -2429,7878,2879,7879,7880,1028,3229,7881,4228,2408,7882,2256,1353,7883,7884,4518, # 7030 -3158, 518,7885,4013,7886,4229,1960,7887,2142,4230,7888,7889,3007,2349,2350,3833, # 7046 - 516,1833,1454,4014,2699,4231,4519,2225,2610,1971,1129,3587,7890,2766,7891,2961, # 7062 -1422, 577,1470,3008,1524,3373,7892,7893, 432,4232,3054,3480,7894,2586,1455,2508, # 7078 -2226,1972,1175,7895,1020,2732,4015,3481,4520,7896,2733,7897,1743,1361,3055,3482, # 7094 -2639,4016,4233,4521,2290, 895, 924,4234,2170, 331,2243,3056, 166,1627,3057,1098, # 7110 -7898,1232,2880,2227,3374,4522, 657, 403,1196,2372, 542,3709,3375,1600,4235,3483, # 7126 -7899,4523,2767,3230, 576, 530,1362,7900,4524,2533,2666,3710,4017,7901, 842,3834, # 7142 -7902,2801,2031,1014,4018, 213,2700,3376, 665, 621,4236,7903,3711,2925,2430,7904, # 7158 -2431,3302,3588,3377,7905,4237,2534,4238,4525,3589,1682,4239,3484,1380,7906, 724, # 7174 -2277, 600,1670,7907,1337,1233,4526,3103,2244,7908,1621,4527,7909, 651,4240,7910, # 7190 -1612,4241,2611,7911,2844,7912,2734,2307,3058,7913, 716,2459,3059, 174,1255,2701, # 7206 -4019,3590, 548,1320,1398, 728,4020,1574,7914,1890,1197,3060,4021,7915,3061,3062, # 7222 -3712,3591,3713, 747,7916, 635,4242,4528,7917,7918,7919,4243,7920,7921,4529,7922, # 7238 -3378,4530,2432, 451,7923,3714,2535,2072,4244,2735,4245,4022,7924,1764,4531,7925, # 7254 -4246, 350,7926,2278,2390,2486,7927,4247,4023,2245,1434,4024, 488,4532, 458,4248, # 7270 -4025,3715, 771,1330,2391,3835,2568,3159,2159,2409,1553,2667,3160,4249,7928,2487, # 7286 -2881,2612,1720,2702,4250,3379,4533,7929,2536,4251,7930,3231,4252,2768,7931,2015, # 7302 -2736,7932,1155,1017,3716,3836,7933,3303,2308, 201,1864,4253,1430,7934,4026,7935, # 7318 -7936,7937,7938,7939,4254,1604,7940, 414,1865, 371,2587,4534,4535,3485,2016,3104, # 7334 -4536,1708, 960,4255, 887, 389,2171,1536,1663,1721,7941,2228,4027,2351,2926,1580, # 7350 -7942,7943,7944,1744,7945,2537,4537,4538,7946,4539,7947,2073,7948,7949,3592,3380, # 7366 -2882,4256,7950,4257,2640,3381,2802, 673,2703,2460, 709,3486,4028,3593,4258,7951, # 7382 -1148, 502, 634,7952,7953,1204,4540,3594,1575,4541,2613,3717,7954,3718,3105, 948, # 7398 -3232, 121,1745,3837,1110,7955,4259,3063,2509,3009,4029,3719,1151,1771,3838,1488, # 7414 -4030,1986,7956,2433,3487,7957,7958,2093,7959,4260,3839,1213,1407,2803, 531,2737, # 7430 -2538,3233,1011,1537,7960,2769,4261,3106,1061,7961,3720,3721,1866,2883,7962,2017, # 7446 - 120,4262,4263,2062,3595,3234,2309,3840,2668,3382,1954,4542,7963,7964,3488,1047, # 7462 -2704,1266,7965,1368,4543,2845, 649,3383,3841,2539,2738,1102,2846,2669,7966,7967, # 7478 -1999,7968,1111,3596,2962,7969,2488,3842,3597,2804,1854,3384,3722,7970,7971,3385, # 7494 -2410,2884,3304,3235,3598,7972,2569,7973,3599,2805,4031,1460, 856,7974,3600,7975, # 7510 -2885,2963,7976,2886,3843,7977,4264, 632,2510, 875,3844,1697,3845,2291,7978,7979, # 7526 -4544,3010,1239, 580,4545,4265,7980, 914, 936,2074,1190,4032,1039,2123,7981,7982, # 7542 -7983,3386,1473,7984,1354,4266,3846,7985,2172,3064,4033, 915,3305,4267,4268,3306, # 7558 -1605,1834,7986,2739, 398,3601,4269,3847,4034, 328,1912,2847,4035,3848,1331,4270, # 7574 -3011, 937,4271,7987,3602,4036,4037,3387,2160,4546,3388, 524, 742, 538,3065,1012, # 7590 -7988,7989,3849,2461,7990, 658,1103, 225,3850,7991,7992,4547,7993,4548,7994,3236, # 7606 -1243,7995,4038, 963,2246,4549,7996,2705,3603,3161,7997,7998,2588,2327,7999,4550, # 7622 -8000,8001,8002,3489,3307, 957,3389,2540,2032,1930,2927,2462, 870,2018,3604,1746, # 7638 -2770,2771,2434,2463,8003,3851,8004,3723,3107,3724,3490,3390,3725,8005,1179,3066, # 7654 -8006,3162,2373,4272,3726,2541,3163,3108,2740,4039,8007,3391,1556,2542,2292, 977, # 7670 -2887,2033,4040,1205,3392,8008,1765,3393,3164,2124,1271,1689, 714,4551,3491,8009, # 7686 -2328,3852, 533,4273,3605,2181, 617,8010,2464,3308,3492,2310,8011,8012,3165,8013, # 7702 -8014,3853,1987, 618, 427,2641,3493,3394,8015,8016,1244,1690,8017,2806,4274,4552, # 7718 -8018,3494,8019,8020,2279,1576, 473,3606,4275,3395, 972,8021,3607,8022,3067,8023, # 7734 -8024,4553,4554,8025,3727,4041,4042,8026, 153,4555, 356,8027,1891,2888,4276,2143, # 7750 - 408, 803,2352,8028,3854,8029,4277,1646,2570,2511,4556,4557,3855,8030,3856,4278, # 7766 -8031,2411,3396, 752,8032,8033,1961,2964,8034, 746,3012,2465,8035,4279,3728, 698, # 7782 -4558,1892,4280,3608,2543,4559,3609,3857,8036,3166,3397,8037,1823,1302,4043,2706, # 7798 -3858,1973,4281,8038,4282,3167, 823,1303,1288,1236,2848,3495,4044,3398, 774,3859, # 7814 -8039,1581,4560,1304,2849,3860,4561,8040,2435,2161,1083,3237,4283,4045,4284, 344, # 7830 -1173, 288,2311, 454,1683,8041,8042,1461,4562,4046,2589,8043,8044,4563, 985, 894, # 7846 -8045,3399,3168,8046,1913,2928,3729,1988,8047,2110,1974,8048,4047,8049,2571,1194, # 7862 - 425,8050,4564,3169,1245,3730,4285,8051,8052,2850,8053, 636,4565,1855,3861, 760, # 7878 -1799,8054,4286,2209,1508,4566,4048,1893,1684,2293,8055,8056,8057,4287,4288,2210, # 7894 - 479,8058,8059, 832,8060,4049,2489,8061,2965,2490,3731, 990,3109, 627,1814,2642, # 7910 -4289,1582,4290,2125,2111,3496,4567,8062, 799,4291,3170,8063,4568,2112,1737,3013, # 7926 -1018, 543, 754,4292,3309,1676,4569,4570,4050,8064,1489,8065,3497,8066,2614,2889, # 7942 -4051,8067,8068,2966,8069,8070,8071,8072,3171,4571,4572,2182,1722,8073,3238,3239, # 7958 -1842,3610,1715, 481, 365,1975,1856,8074,8075,1962,2491,4573,8076,2126,3611,3240, # 7974 - 433,1894,2063,2075,8077, 602,2741,8078,8079,8080,8081,8082,3014,1628,3400,8083, # 7990 -3172,4574,4052,2890,4575,2512,8084,2544,2772,8085,8086,8087,3310,4576,2891,8088, # 8006 -4577,8089,2851,4578,4579,1221,2967,4053,2513,8090,8091,8092,1867,1989,8093,8094, # 8022 -8095,1895,8096,8097,4580,1896,4054, 318,8098,2094,4055,4293,8099,8100, 485,8101, # 8038 - 938,3862, 553,2670, 116,8102,3863,3612,8103,3498,2671,2773,3401,3311,2807,8104, # 8054 -3613,2929,4056,1747,2930,2968,8105,8106, 207,8107,8108,2672,4581,2514,8109,3015, # 8070 - 890,3614,3864,8110,1877,3732,3402,8111,2183,2353,3403,1652,8112,8113,8114, 941, # 8086 -2294, 208,3499,4057,2019, 330,4294,3865,2892,2492,3733,4295,8115,8116,8117,8118, # 8102 -) - diff --git a/lib/chardet/euctwprober.py b/lib/chardet/euctwprober.py deleted file mode 100644 index 35669cc..0000000 --- a/lib/chardet/euctwprober.py +++ /dev/null @@ -1,46 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import EUCTWDistributionAnalysis -from .mbcssm import EUCTW_SM_MODEL - -class EUCTWProber(MultiByteCharSetProber): - def __init__(self): - super(EUCTWProber, self).__init__() - self.coding_sm = CodingStateMachine(EUCTW_SM_MODEL) - self.distribution_analyzer = EUCTWDistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "EUC-TW" - - @property - def language(self): - return "Taiwan" diff --git a/lib/chardet/gb2312freq.py b/lib/chardet/gb2312freq.py deleted file mode 100644 index 697837b..0000000 --- a/lib/chardet/gb2312freq.py +++ /dev/null @@ -1,283 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# GB2312 most frequently used character table -# -# Char to FreqOrder table , from hz6763 - -# 512 --> 0.79 -- 0.79 -# 1024 --> 0.92 -- 0.13 -# 2048 --> 0.98 -- 0.06 -# 6768 --> 1.00 -- 0.02 -# -# Ideal Distribution Ratio = 0.79135/(1-0.79135) = 3.79 -# Random Distribution Ration = 512 / (3755 - 512) = 0.157 -# -# Typical Distribution Ratio about 25% of Ideal one, still much higher that RDR - -GB2312_TYPICAL_DISTRIBUTION_RATIO = 0.9 - -GB2312_TABLE_SIZE = 3760 - -GB2312_CHAR_TO_FREQ_ORDER = ( -1671, 749,1443,2364,3924,3807,2330,3921,1704,3463,2691,1511,1515, 572,3191,2205, -2361, 224,2558, 479,1711, 963,3162, 440,4060,1905,2966,2947,3580,2647,3961,3842, -2204, 869,4207, 970,2678,5626,2944,2956,1479,4048, 514,3595, 588,1346,2820,3409, - 249,4088,1746,1873,2047,1774, 581,1813, 358,1174,3590,1014,1561,4844,2245, 670, -1636,3112, 889,1286, 953, 556,2327,3060,1290,3141, 613, 185,3477,1367, 850,3820, -1715,2428,2642,2303,2732,3041,2562,2648,3566,3946,1349, 388,3098,2091,1360,3585, - 152,1687,1539, 738,1559, 59,1232,2925,2267,1388,1249,1741,1679,2960, 151,1566, -1125,1352,4271, 924,4296, 385,3166,4459, 310,1245,2850, 70,3285,2729,3534,3575, -2398,3298,3466,1960,2265, 217,3647, 864,1909,2084,4401,2773,1010,3269,5152, 853, -3051,3121,1244,4251,1895, 364,1499,1540,2313,1180,3655,2268, 562, 715,2417,3061, - 544, 336,3768,2380,1752,4075, 950, 280,2425,4382, 183,2759,3272, 333,4297,2155, -1688,2356,1444,1039,4540, 736,1177,3349,2443,2368,2144,2225, 565, 196,1482,3406, - 927,1335,4147, 692, 878,1311,1653,3911,3622,1378,4200,1840,2969,3149,2126,1816, -2534,1546,2393,2760, 737,2494, 13, 447, 245,2747, 38,2765,2129,2589,1079, 606, - 360, 471,3755,2890, 404, 848, 699,1785,1236, 370,2221,1023,3746,2074,2026,2023, -2388,1581,2119, 812,1141,3091,2536,1519, 804,2053, 406,1596,1090, 784, 548,4414, -1806,2264,2936,1100, 343,4114,5096, 622,3358, 743,3668,1510,1626,5020,3567,2513, -3195,4115,5627,2489,2991, 24,2065,2697,1087,2719, 48,1634, 315, 68, 985,2052, - 198,2239,1347,1107,1439, 597,2366,2172, 871,3307, 919,2487,2790,1867, 236,2570, -1413,3794, 906,3365,3381,1701,1982,1818,1524,2924,1205, 616,2586,2072,2004, 575, - 253,3099, 32,1365,1182, 197,1714,2454,1201, 554,3388,3224,2748, 756,2587, 250, -2567,1507,1517,3529,1922,2761,2337,3416,1961,1677,2452,2238,3153, 615, 911,1506, -1474,2495,1265,1906,2749,3756,3280,2161, 898,2714,1759,3450,2243,2444, 563, 26, -3286,2266,3769,3344,2707,3677, 611,1402, 531,1028,2871,4548,1375, 261,2948, 835, -1190,4134, 353, 840,2684,1900,3082,1435,2109,1207,1674, 329,1872,2781,4055,2686, -2104, 608,3318,2423,2957,2768,1108,3739,3512,3271,3985,2203,1771,3520,1418,2054, -1681,1153, 225,1627,2929, 162,2050,2511,3687,1954, 124,1859,2431,1684,3032,2894, - 585,4805,3969,2869,2704,2088,2032,2095,3656,2635,4362,2209, 256, 518,2042,2105, -3777,3657, 643,2298,1148,1779, 190, 989,3544, 414, 11,2135,2063,2979,1471, 403, -3678, 126, 770,1563, 671,2499,3216,2877, 600,1179, 307,2805,4937,1268,1297,2694, - 252,4032,1448,1494,1331,1394, 127,2256, 222,1647,1035,1481,3056,1915,1048, 873, -3651, 210, 33,1608,2516, 200,1520, 415, 102, 0,3389,1287, 817, 91,3299,2940, - 836,1814, 549,2197,1396,1669,2987,3582,2297,2848,4528,1070, 687, 20,1819, 121, -1552,1364,1461,1968,2617,3540,2824,2083, 177, 948,4938,2291, 110,4549,2066, 648, -3359,1755,2110,2114,4642,4845,1693,3937,3308,1257,1869,2123, 208,1804,3159,2992, -2531,2549,3361,2418,1350,2347,2800,2568,1291,2036,2680, 72, 842,1990, 212,1233, -1154,1586, 75,2027,3410,4900,1823,1337,2710,2676, 728,2810,1522,3026,4995, 157, - 755,1050,4022, 710, 785,1936,2194,2085,1406,2777,2400, 150,1250,4049,1206, 807, -1910, 534, 529,3309,1721,1660, 274, 39,2827, 661,2670,1578, 925,3248,3815,1094, -4278,4901,4252, 41,1150,3747,2572,2227,4501,3658,4902,3813,3357,3617,2884,2258, - 887, 538,4187,3199,1294,2439,3042,2329,2343,2497,1255, 107, 543,1527, 521,3478, -3568, 194,5062, 15, 961,3870,1241,1192,2664, 66,5215,3260,2111,1295,1127,2152, -3805,4135, 901,1164,1976, 398,1278, 530,1460, 748, 904,1054,1966,1426, 53,2909, - 509, 523,2279,1534, 536,1019, 239,1685, 460,2353, 673,1065,2401,3600,4298,2272, -1272,2363, 284,1753,3679,4064,1695, 81, 815,2677,2757,2731,1386, 859, 500,4221, -2190,2566, 757,1006,2519,2068,1166,1455, 337,2654,3203,1863,1682,1914,3025,1252, -1409,1366, 847, 714,2834,2038,3209, 964,2970,1901, 885,2553,1078,1756,3049, 301, -1572,3326, 688,2130,1996,2429,1805,1648,2930,3421,2750,3652,3088, 262,1158,1254, - 389,1641,1812, 526,1719, 923,2073,1073,1902, 468, 489,4625,1140, 857,2375,3070, -3319,2863, 380, 116,1328,2693,1161,2244, 273,1212,1884,2769,3011,1775,1142, 461, -3066,1200,2147,2212, 790, 702,2695,4222,1601,1058, 434,2338,5153,3640, 67,2360, -4099,2502, 618,3472,1329, 416,1132, 830,2782,1807,2653,3211,3510,1662, 192,2124, - 296,3979,1739,1611,3684, 23, 118, 324, 446,1239,1225, 293,2520,3814,3795,2535, -3116, 17,1074, 467,2692,2201, 387,2922, 45,1326,3055,1645,3659,2817, 958, 243, -1903,2320,1339,2825,1784,3289, 356, 576, 865,2315,2381,3377,3916,1088,3122,1713, -1655, 935, 628,4689,1034,1327, 441, 800, 720, 894,1979,2183,1528,5289,2702,1071, -4046,3572,2399,1571,3281, 79, 761,1103, 327, 134, 758,1899,1371,1615, 879, 442, - 215,2605,2579, 173,2048,2485,1057,2975,3317,1097,2253,3801,4263,1403,1650,2946, - 814,4968,3487,1548,2644,1567,1285, 2, 295,2636, 97, 946,3576, 832, 141,4257, -3273, 760,3821,3521,3156,2607, 949,1024,1733,1516,1803,1920,2125,2283,2665,3180, -1501,2064,3560,2171,1592, 803,3518,1416, 732,3897,4258,1363,1362,2458, 119,1427, - 602,1525,2608,1605,1639,3175, 694,3064, 10, 465, 76,2000,4846,4208, 444,3781, -1619,3353,2206,1273,3796, 740,2483, 320,1723,2377,3660,2619,1359,1137,1762,1724, -2345,2842,1850,1862, 912, 821,1866, 612,2625,1735,2573,3369,1093, 844, 89, 937, - 930,1424,3564,2413,2972,1004,3046,3019,2011, 711,3171,1452,4178, 428, 801,1943, - 432, 445,2811, 206,4136,1472, 730, 349, 73, 397,2802,2547, 998,1637,1167, 789, - 396,3217, 154,1218, 716,1120,1780,2819,4826,1931,3334,3762,2139,1215,2627, 552, -3664,3628,3232,1405,2383,3111,1356,2652,3577,3320,3101,1703, 640,1045,1370,1246, -4996, 371,1575,2436,1621,2210, 984,4033,1734,2638, 16,4529, 663,2755,3255,1451, -3917,2257,1253,1955,2234,1263,2951, 214,1229, 617, 485, 359,1831,1969, 473,2310, - 750,2058, 165, 80,2864,2419, 361,4344,2416,2479,1134, 796,3726,1266,2943, 860, -2715, 938, 390,2734,1313,1384, 248, 202, 877,1064,2854, 522,3907, 279,1602, 297, -2357, 395,3740, 137,2075, 944,4089,2584,1267,3802, 62,1533,2285, 178, 176, 780, -2440, 201,3707, 590, 478,1560,4354,2117,1075, 30, 74,4643,4004,1635,1441,2745, - 776,2596, 238,1077,1692,1912,2844, 605, 499,1742,3947, 241,3053, 980,1749, 936, -2640,4511,2582, 515,1543,2162,5322,2892,2993, 890,2148,1924, 665,1827,3581,1032, - 968,3163, 339,1044,1896, 270, 583,1791,1720,4367,1194,3488,3669, 43,2523,1657, - 163,2167, 290,1209,1622,3378, 550, 634,2508,2510, 695,2634,2384,2512,1476,1414, - 220,1469,2341,2138,2852,3183,2900,4939,2865,3502,1211,3680, 854,3227,1299,2976, -3172, 186,2998,1459, 443,1067,3251,1495, 321,1932,3054, 909, 753,1410,1828, 436, -2441,1119,1587,3164,2186,1258, 227, 231,1425,1890,3200,3942, 247, 959, 725,5254, -2741, 577,2158,2079, 929, 120, 174, 838,2813, 591,1115, 417,2024, 40,3240,1536, -1037, 291,4151,2354, 632,1298,2406,2500,3535,1825,1846,3451, 205,1171, 345,4238, - 18,1163, 811, 685,2208,1217, 425,1312,1508,1175,4308,2552,1033, 587,1381,3059, -2984,3482, 340,1316,4023,3972, 792,3176, 519, 777,4690, 918, 933,4130,2981,3741, - 90,3360,2911,2200,5184,4550, 609,3079,2030, 272,3379,2736, 363,3881,1130,1447, - 286, 779, 357,1169,3350,3137,1630,1220,2687,2391, 747,1277,3688,2618,2682,2601, -1156,3196,5290,4034,3102,1689,3596,3128, 874, 219,2783, 798, 508,1843,2461, 269, -1658,1776,1392,1913,2983,3287,2866,2159,2372, 829,4076, 46,4253,2873,1889,1894, - 915,1834,1631,2181,2318, 298, 664,2818,3555,2735, 954,3228,3117, 527,3511,2173, - 681,2712,3033,2247,2346,3467,1652, 155,2164,3382, 113,1994, 450, 899, 494, 994, -1237,2958,1875,2336,1926,3727, 545,1577,1550, 633,3473, 204,1305,3072,2410,1956, -2471, 707,2134, 841,2195,2196,2663,3843,1026,4940, 990,3252,4997, 368,1092, 437, -3212,3258,1933,1829, 675,2977,2893, 412, 943,3723,4644,3294,3283,2230,2373,5154, -2389,2241,2661,2323,1404,2524, 593, 787, 677,3008,1275,2059, 438,2709,2609,2240, -2269,2246,1446, 36,1568,1373,3892,1574,2301,1456,3962, 693,2276,5216,2035,1143, -2720,1919,1797,1811,2763,4137,2597,1830,1699,1488,1198,2090, 424,1694, 312,3634, -3390,4179,3335,2252,1214, 561,1059,3243,2295,2561, 975,5155,2321,2751,3772, 472, -1537,3282,3398,1047,2077,2348,2878,1323,3340,3076, 690,2906, 51, 369, 170,3541, -1060,2187,2688,3670,2541,1083,1683, 928,3918, 459, 109,4427, 599,3744,4286, 143, -2101,2730,2490, 82,1588,3036,2121, 281,1860, 477,4035,1238,2812,3020,2716,3312, -1530,2188,2055,1317, 843, 636,1808,1173,3495, 649, 181,1002, 147,3641,1159,2414, -3750,2289,2795, 813,3123,2610,1136,4368, 5,3391,4541,2174, 420, 429,1728, 754, -1228,2115,2219, 347,2223,2733, 735,1518,3003,2355,3134,1764,3948,3329,1888,2424, -1001,1234,1972,3321,3363,1672,1021,1450,1584, 226, 765, 655,2526,3404,3244,2302, -3665, 731, 594,2184, 319,1576, 621, 658,2656,4299,2099,3864,1279,2071,2598,2739, - 795,3086,3699,3908,1707,2352,2402,1382,3136,2475,1465,4847,3496,3865,1085,3004, -2591,1084, 213,2287,1963,3565,2250, 822, 793,4574,3187,1772,1789,3050, 595,1484, -1959,2770,1080,2650, 456, 422,2996, 940,3322,4328,4345,3092,2742, 965,2784, 739, -4124, 952,1358,2498,2949,2565, 332,2698,2378, 660,2260,2473,4194,3856,2919, 535, -1260,2651,1208,1428,1300,1949,1303,2942, 433,2455,2450,1251,1946, 614,1269, 641, -1306,1810,2737,3078,2912, 564,2365,1419,1415,1497,4460,2367,2185,1379,3005,1307, -3218,2175,1897,3063, 682,1157,4040,4005,1712,1160,1941,1399, 394, 402,2952,1573, -1151,2986,2404, 862, 299,2033,1489,3006, 346, 171,2886,3401,1726,2932, 168,2533, - 47,2507,1030,3735,1145,3370,1395,1318,1579,3609,4560,2857,4116,1457,2529,1965, - 504,1036,2690,2988,2405, 745,5871, 849,2397,2056,3081, 863,2359,3857,2096, 99, -1397,1769,2300,4428,1643,3455,1978,1757,3718,1440, 35,4879,3742,1296,4228,2280, - 160,5063,1599,2013, 166, 520,3479,1646,3345,3012, 490,1937,1545,1264,2182,2505, -1096,1188,1369,1436,2421,1667,2792,2460,1270,2122, 727,3167,2143, 806,1706,1012, -1800,3037, 960,2218,1882, 805, 139,2456,1139,1521, 851,1052,3093,3089, 342,2039, - 744,5097,1468,1502,1585,2087, 223, 939, 326,2140,2577, 892,2481,1623,4077, 982, -3708, 135,2131, 87,2503,3114,2326,1106, 876,1616, 547,2997,2831,2093,3441,4530, -4314, 9,3256,4229,4148, 659,1462,1986,1710,2046,2913,2231,4090,4880,5255,3392, -3274,1368,3689,4645,1477, 705,3384,3635,1068,1529,2941,1458,3782,1509, 100,1656, -2548, 718,2339, 408,1590,2780,3548,1838,4117,3719,1345,3530, 717,3442,2778,3220, -2898,1892,4590,3614,3371,2043,1998,1224,3483, 891, 635, 584,2559,3355, 733,1766, -1729,1172,3789,1891,2307, 781,2982,2271,1957,1580,5773,2633,2005,4195,3097,1535, -3213,1189,1934,5693,3262, 586,3118,1324,1598, 517,1564,2217,1868,1893,4445,3728, -2703,3139,1526,1787,1992,3882,2875,1549,1199,1056,2224,1904,2711,5098,4287, 338, -1993,3129,3489,2689,1809,2815,1997, 957,1855,3898,2550,3275,3057,1105,1319, 627, -1505,1911,1883,3526, 698,3629,3456,1833,1431, 746, 77,1261,2017,2296,1977,1885, - 125,1334,1600, 525,1798,1109,2222,1470,1945, 559,2236,1186,3443,2476,1929,1411, -2411,3135,1777,3372,2621,1841,1613,3229, 668,1430,1839,2643,2916, 195,1989,2671, -2358,1387, 629,3205,2293,5256,4439, 123,1310, 888,1879,4300,3021,3605,1003,1162, -3192,2910,2010, 140,2395,2859, 55,1082,2012,2901, 662, 419,2081,1438, 680,2774, -4654,3912,1620,1731,1625,5035,4065,2328, 512,1344, 802,5443,2163,2311,2537, 524, -3399, 98,1155,2103,1918,2606,3925,2816,1393,2465,1504,3773,2177,3963,1478,4346, - 180,1113,4655,3461,2028,1698, 833,2696,1235,1322,1594,4408,3623,3013,3225,2040, -3022, 541,2881, 607,3632,2029,1665,1219, 639,1385,1686,1099,2803,3231,1938,3188, -2858, 427, 676,2772,1168,2025, 454,3253,2486,3556, 230,1950, 580, 791,1991,1280, -1086,1974,2034, 630, 257,3338,2788,4903,1017, 86,4790, 966,2789,1995,1696,1131, - 259,3095,4188,1308, 179,1463,5257, 289,4107,1248, 42,3413,1725,2288, 896,1947, - 774,4474,4254, 604,3430,4264, 392,2514,2588, 452, 237,1408,3018, 988,4531,1970, -3034,3310, 540,2370,1562,1288,2990, 502,4765,1147, 4,1853,2708, 207, 294,2814, -4078,2902,2509, 684, 34,3105,3532,2551, 644, 709,2801,2344, 573,1727,3573,3557, -2021,1081,3100,4315,2100,3681, 199,2263,1837,2385, 146,3484,1195,2776,3949, 997, -1939,3973,1008,1091,1202,1962,1847,1149,4209,5444,1076, 493, 117,5400,2521, 972, -1490,2934,1796,4542,2374,1512,2933,2657, 413,2888,1135,2762,2314,2156,1355,2369, - 766,2007,2527,2170,3124,2491,2593,2632,4757,2437, 234,3125,3591,1898,1750,1376, -1942,3468,3138, 570,2127,2145,3276,4131, 962, 132,1445,4196, 19, 941,3624,3480, -3366,1973,1374,4461,3431,2629, 283,2415,2275, 808,2887,3620,2112,2563,1353,3610, - 955,1089,3103,1053, 96, 88,4097, 823,3808,1583, 399, 292,4091,3313, 421,1128, - 642,4006, 903,2539,1877,2082, 596, 29,4066,1790, 722,2157, 130, 995,1569, 769, -1485, 464, 513,2213, 288,1923,1101,2453,4316, 133, 486,2445, 50, 625, 487,2207, - 57, 423, 481,2962, 159,3729,1558, 491, 303, 482, 501, 240,2837, 112,3648,2392, -1783, 362, 8,3433,3422, 610,2793,3277,1390,1284,1654, 21,3823, 734, 367, 623, - 193, 287, 374,1009,1483, 816, 476, 313,2255,2340,1262,2150,2899,1146,2581, 782, -2116,1659,2018,1880, 255,3586,3314,1110,2867,2137,2564, 986,2767,5185,2006, 650, - 158, 926, 762, 881,3157,2717,2362,3587, 306,3690,3245,1542,3077,2427,1691,2478, -2118,2985,3490,2438, 539,2305, 983, 129,1754, 355,4201,2386, 827,2923, 104,1773, -2838,2771, 411,2905,3919, 376, 767, 122,1114, 828,2422,1817,3506, 266,3460,1007, -1609,4998, 945,2612,4429,2274, 726,1247,1964,2914,2199,2070,4002,4108, 657,3323, -1422, 579, 455,2764,4737,1222,2895,1670, 824,1223,1487,2525, 558, 861,3080, 598, -2659,2515,1967, 752,2583,2376,2214,4180, 977, 704,2464,4999,2622,4109,1210,2961, - 819,1541, 142,2284, 44, 418, 457,1126,3730,4347,4626,1644,1876,3671,1864, 302, -1063,5694, 624, 723,1984,3745,1314,1676,2488,1610,1449,3558,3569,2166,2098, 409, -1011,2325,3704,2306, 818,1732,1383,1824,1844,3757, 999,2705,3497,1216,1423,2683, -2426,2954,2501,2726,2229,1475,2554,5064,1971,1794,1666,2014,1343, 783, 724, 191, -2434,1354,2220,5065,1763,2752,2472,4152, 131, 175,2885,3434, 92,1466,4920,2616, -3871,3872,3866, 128,1551,1632, 669,1854,3682,4691,4125,1230, 188,2973,3290,1302, -1213, 560,3266, 917, 763,3909,3249,1760, 868,1958, 764,1782,2097, 145,2277,3774, -4462, 64,1491,3062, 971,2132,3606,2442, 221,1226,1617, 218, 323,1185,3207,3147, - 571, 619,1473,1005,1744,2281, 449,1887,2396,3685, 275, 375,3816,1743,3844,3731, - 845,1983,2350,4210,1377, 773, 967,3499,3052,3743,2725,4007,1697,1022,3943,1464, -3264,2855,2722,1952,1029,2839,2467, 84,4383,2215, 820,1391,2015,2448,3672, 377, -1948,2168, 797,2545,3536,2578,2645, 94,2874,1678, 405,1259,3071, 771, 546,1315, - 470,1243,3083, 895,2468, 981, 969,2037, 846,4181, 653,1276,2928, 14,2594, 557, -3007,2474, 156, 902,1338,1740,2574, 537,2518, 973,2282,2216,2433,1928, 138,2903, -1293,2631,1612, 646,3457, 839,2935, 111, 496,2191,2847, 589,3186, 149,3994,2060, -4031,2641,4067,3145,1870, 37,3597,2136,1025,2051,3009,3383,3549,1121,1016,3261, -1301, 251,2446,2599,2153, 872,3246, 637, 334,3705, 831, 884, 921,3065,3140,4092, -2198,1944, 246,2964, 108,2045,1152,1921,2308,1031, 203,3173,4170,1907,3890, 810, -1401,2003,1690, 506, 647,1242,2828,1761,1649,3208,2249,1589,3709,2931,5156,1708, - 498, 666,2613, 834,3817,1231, 184,2851,1124, 883,3197,2261,3710,1765,1553,2658, -1178,2639,2351, 93,1193, 942,2538,2141,4402, 235,1821, 870,1591,2192,1709,1871, -3341,1618,4126,2595,2334, 603, 651, 69, 701, 268,2662,3411,2555,1380,1606, 503, - 448, 254,2371,2646, 574,1187,2309,1770, 322,2235,1292,1801, 305, 566,1133, 229, -2067,2057, 706, 167, 483,2002,2672,3295,1820,3561,3067, 316, 378,2746,3452,1112, - 136,1981, 507,1651,2917,1117, 285,4591, 182,2580,3522,1304, 335,3303,1835,2504, -1795,1792,2248, 674,1018,2106,2449,1857,2292,2845, 976,3047,1781,2600,2727,1389, -1281, 52,3152, 153, 265,3950, 672,3485,3951,4463, 430,1183, 365, 278,2169, 27, -1407,1336,2304, 209,1340,1730,2202,1852,2403,2883, 979,1737,1062, 631,2829,2542, -3876,2592, 825,2086,2226,3048,3625, 352,1417,3724, 542, 991, 431,1351,3938,1861, -2294, 826,1361,2927,3142,3503,1738, 463,2462,2723, 582,1916,1595,2808, 400,3845, -3891,2868,3621,2254, 58,2492,1123, 910,2160,2614,1372,1603,1196,1072,3385,1700, -3267,1980, 696, 480,2430, 920, 799,1570,2920,1951,2041,4047,2540,1321,4223,2469, -3562,2228,1271,2602, 401,2833,3351,2575,5157, 907,2312,1256, 410, 263,3507,1582, - 996, 678,1849,2316,1480, 908,3545,2237, 703,2322, 667,1826,2849,1531,2604,2999, -2407,3146,2151,2630,1786,3711, 469,3542, 497,3899,2409, 858, 837,4446,3393,1274, - 786, 620,1845,2001,3311, 484, 308,3367,1204,1815,3691,2332,1532,2557,1842,2020, -2724,1927,2333,4440, 567, 22,1673,2728,4475,1987,1858,1144,1597, 101,1832,3601, - 12, 974,3783,4391, 951,1412, 1,3720, 453,4608,4041, 528,1041,1027,3230,2628, -1129, 875,1051,3291,1203,2262,1069,2860,2799,2149,2615,3278, 144,1758,3040, 31, - 475,1680, 366,2685,3184, 311,1642,4008,2466,5036,1593,1493,2809, 216,1420,1668, - 233, 304,2128,3284, 232,1429,1768,1040,2008,3407,2740,2967,2543, 242,2133, 778, -1565,2022,2620, 505,2189,2756,1098,2273, 372,1614, 708, 553,2846,2094,2278, 169, -3626,2835,4161, 228,2674,3165, 809,1454,1309, 466,1705,1095, 900,3423, 880,2667, -3751,5258,2317,3109,2571,4317,2766,1503,1342, 866,4447,1118, 63,2076, 314,1881, -1348,1061, 172, 978,3515,1747, 532, 511,3970, 6, 601, 905,2699,3300,1751, 276, -1467,3725,2668, 65,4239,2544,2779,2556,1604, 578,2451,1802, 992,2331,2624,1320, -3446, 713,1513,1013, 103,2786,2447,1661, 886,1702, 916, 654,3574,2031,1556, 751, -2178,2821,2179,1498,1538,2176, 271, 914,2251,2080,1325, 638,1953,2937,3877,2432, -2754, 95,3265,1716, 260,1227,4083, 775, 106,1357,3254, 426,1607, 555,2480, 772, -1985, 244,2546, 474, 495,1046,2611,1851,2061, 71,2089,1675,2590, 742,3758,2843, -3222,1433, 267,2180,2576,2826,2233,2092,3913,2435, 956,1745,3075, 856,2113,1116, - 451, 3,1988,2896,1398, 993,2463,1878,2049,1341,2718,2721,2870,2108, 712,2904, -4363,2753,2324, 277,2872,2349,2649, 384, 987, 435, 691,3000, 922, 164,3939, 652, -1500,1184,4153,2482,3373,2165,4848,2335,3775,3508,3154,2806,2830,1554,2102,1664, -2530,1434,2408, 893,1547,2623,3447,2832,2242,2532,3169,2856,3223,2078, 49,3770, -3469, 462, 318, 656,2259,3250,3069, 679,1629,2758, 344,1138,1104,3120,1836,1283, -3115,2154,1437,4448, 934, 759,1999, 794,2862,1038, 533,2560,1722,2342, 855,2626, -1197,1663,4476,3127, 85,4240,2528, 25,1111,1181,3673, 407,3470,4561,2679,2713, - 768,1925,2841,3986,1544,1165, 932, 373,1240,2146,1930,2673, 721,4766, 354,4333, - 391,2963, 187, 61,3364,1442,1102, 330,1940,1767, 341,3809,4118, 393,2496,2062, -2211, 105, 331, 300, 439, 913,1332, 626, 379,3304,1557, 328, 689,3952, 309,1555, - 931, 317,2517,3027, 325, 569, 686,2107,3084, 60,1042,1333,2794, 264,3177,4014, -1628, 258,3712, 7,4464,1176,1043,1778, 683, 114,1975, 78,1492, 383,1886, 510, - 386, 645,5291,2891,2069,3305,4138,3867,2939,2603,2493,1935,1066,1848,3588,1015, -1282,1289,4609, 697,1453,3044,2666,3611,1856,2412, 54, 719,1330, 568,3778,2459, -1748, 788, 492, 551,1191,1000, 488,3394,3763, 282,1799, 348,2016,1523,3155,2390, -1049, 382,2019,1788,1170, 729,2968,3523, 897,3926,2785,2938,3292, 350,2319,3238, -1718,1717,2655,3453,3143,4465, 161,2889,2980,2009,1421, 56,1908,1640,2387,2232, -1917,1874,2477,4921, 148, 83,3438, 592,4245,2882,1822,1055, 741, 115,1496,1624, - 381,1638,4592,1020, 516,3214, 458, 947,4575,1432, 211,1514,2926,1865,2142, 189, - 852,1221,1400,1486, 882,2299,4036, 351, 28,1122, 700,6479,6480,6481,6482,6483, #last 512 -) - diff --git a/lib/chardet/gb2312prober.py b/lib/chardet/gb2312prober.py deleted file mode 100644 index 8446d2d..0000000 --- a/lib/chardet/gb2312prober.py +++ /dev/null @@ -1,46 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import GB2312DistributionAnalysis -from .mbcssm import GB2312_SM_MODEL - -class GB2312Prober(MultiByteCharSetProber): - def __init__(self): - super(GB2312Prober, self).__init__() - self.coding_sm = CodingStateMachine(GB2312_SM_MODEL) - self.distribution_analyzer = GB2312DistributionAnalysis() - self.reset() - - @property - def charset_name(self): - return "GB2312" - - @property - def language(self): - return "Chinese" diff --git a/lib/chardet/hebrewprober.py b/lib/chardet/hebrewprober.py deleted file mode 100644 index b0e1bf4..0000000 --- a/lib/chardet/hebrewprober.py +++ /dev/null @@ -1,292 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Shy Shalom -# Portions created by the Initial Developer are Copyright (C) 2005 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import ProbingState - -# This prober doesn't actually recognize a language or a charset. -# It is a helper prober for the use of the Hebrew model probers - -### General ideas of the Hebrew charset recognition ### -# -# Four main charsets exist in Hebrew: -# "ISO-8859-8" - Visual Hebrew -# "windows-1255" - Logical Hebrew -# "ISO-8859-8-I" - Logical Hebrew -# "x-mac-hebrew" - ?? Logical Hebrew ?? -# -# Both "ISO" charsets use a completely identical set of code points, whereas -# "windows-1255" and "x-mac-hebrew" are two different proper supersets of -# these code points. windows-1255 defines additional characters in the range -# 0x80-0x9F as some misc punctuation marks as well as some Hebrew-specific -# diacritics and additional 'Yiddish' ligature letters in the range 0xc0-0xd6. -# x-mac-hebrew defines similar additional code points but with a different -# mapping. -# -# As far as an average Hebrew text with no diacritics is concerned, all four -# charsets are identical with respect to code points. Meaning that for the -# main Hebrew alphabet, all four map the same values to all 27 Hebrew letters -# (including final letters). -# -# The dominant difference between these charsets is their directionality. -# "Visual" directionality means that the text is ordered as if the renderer is -# not aware of a BIDI rendering algorithm. The renderer sees the text and -# draws it from left to right. The text itself when ordered naturally is read -# backwards. A buffer of Visual Hebrew generally looks like so: -# "[last word of first line spelled backwards] [whole line ordered backwards -# and spelled backwards] [first word of first line spelled backwards] -# [end of line] [last word of second line] ... etc' " -# adding punctuation marks, numbers and English text to visual text is -# naturally also "visual" and from left to right. -# -# "Logical" directionality means the text is ordered "naturally" according to -# the order it is read. It is the responsibility of the renderer to display -# the text from right to left. A BIDI algorithm is used to place general -# punctuation marks, numbers and English text in the text. -# -# Texts in x-mac-hebrew are almost impossible to find on the Internet. From -# what little evidence I could find, it seems that its general directionality -# is Logical. -# -# To sum up all of the above, the Hebrew probing mechanism knows about two -# charsets: -# Visual Hebrew - "ISO-8859-8" - backwards text - Words and sentences are -# backwards while line order is natural. For charset recognition purposes -# the line order is unimportant (In fact, for this implementation, even -# word order is unimportant). -# Logical Hebrew - "windows-1255" - normal, naturally ordered text. -# -# "ISO-8859-8-I" is a subset of windows-1255 and doesn't need to be -# specifically identified. -# "x-mac-hebrew" is also identified as windows-1255. A text in x-mac-hebrew -# that contain special punctuation marks or diacritics is displayed with -# some unconverted characters showing as question marks. This problem might -# be corrected using another model prober for x-mac-hebrew. Due to the fact -# that x-mac-hebrew texts are so rare, writing another model prober isn't -# worth the effort and performance hit. -# -#### The Prober #### -# -# The prober is divided between two SBCharSetProbers and a HebrewProber, -# all of which are managed, created, fed data, inquired and deleted by the -# SBCSGroupProber. The two SBCharSetProbers identify that the text is in -# fact some kind of Hebrew, Logical or Visual. The final decision about which -# one is it is made by the HebrewProber by combining final-letter scores -# with the scores of the two SBCharSetProbers to produce a final answer. -# -# The SBCSGroupProber is responsible for stripping the original text of HTML -# tags, English characters, numbers, low-ASCII punctuation characters, spaces -# and new lines. It reduces any sequence of such characters to a single space. -# The buffer fed to each prober in the SBCS group prober is pure text in -# high-ASCII. -# The two SBCharSetProbers (model probers) share the same language model: -# Win1255Model. -# The first SBCharSetProber uses the model normally as any other -# SBCharSetProber does, to recognize windows-1255, upon which this model was -# built. The second SBCharSetProber is told to make the pair-of-letter -# lookup in the language model backwards. This in practice exactly simulates -# a visual Hebrew model using the windows-1255 logical Hebrew model. -# -# The HebrewProber is not using any language model. All it does is look for -# final-letter evidence suggesting the text is either logical Hebrew or visual -# Hebrew. Disjointed from the model probers, the results of the HebrewProber -# alone are meaningless. HebrewProber always returns 0.00 as confidence -# since it never identifies a charset by itself. Instead, the pointer to the -# HebrewProber is passed to the model probers as a helper "Name Prober". -# When the Group prober receives a positive identification from any prober, -# it asks for the name of the charset identified. If the prober queried is a -# Hebrew model prober, the model prober forwards the call to the -# HebrewProber to make the final decision. In the HebrewProber, the -# decision is made according to the final-letters scores maintained and Both -# model probers scores. The answer is returned in the form of the name of the -# charset identified, either "windows-1255" or "ISO-8859-8". - -class HebrewProber(CharSetProber): - # windows-1255 / ISO-8859-8 code points of interest - FINAL_KAF = 0xea - NORMAL_KAF = 0xeb - FINAL_MEM = 0xed - NORMAL_MEM = 0xee - FINAL_NUN = 0xef - NORMAL_NUN = 0xf0 - FINAL_PE = 0xf3 - NORMAL_PE = 0xf4 - FINAL_TSADI = 0xf5 - NORMAL_TSADI = 0xf6 - - # Minimum Visual vs Logical final letter score difference. - # If the difference is below this, don't rely solely on the final letter score - # distance. - MIN_FINAL_CHAR_DISTANCE = 5 - - # Minimum Visual vs Logical model score difference. - # If the difference is below this, don't rely at all on the model score - # distance. - MIN_MODEL_DISTANCE = 0.01 - - VISUAL_HEBREW_NAME = "ISO-8859-8" - LOGICAL_HEBREW_NAME = "windows-1255" - - def __init__(self): - super(HebrewProber, self).__init__() - self._final_char_logical_score = None - self._final_char_visual_score = None - self._prev = None - self._before_prev = None - self._logical_prober = None - self._visual_prober = None - self.reset() - - def reset(self): - self._final_char_logical_score = 0 - self._final_char_visual_score = 0 - # The two last characters seen in the previous buffer, - # mPrev and mBeforePrev are initialized to space in order to simulate - # a word delimiter at the beginning of the data - self._prev = ' ' - self._before_prev = ' ' - # These probers are owned by the group prober. - - def set_model_probers(self, logicalProber, visualProber): - self._logical_prober = logicalProber - self._visual_prober = visualProber - - def is_final(self, c): - return c in [self.FINAL_KAF, self.FINAL_MEM, self.FINAL_NUN, - self.FINAL_PE, self.FINAL_TSADI] - - def is_non_final(self, c): - # The normal Tsadi is not a good Non-Final letter due to words like - # 'lechotet' (to chat) containing an apostrophe after the tsadi. This - # apostrophe is converted to a space in FilterWithoutEnglishLetters - # causing the Non-Final tsadi to appear at an end of a word even - # though this is not the case in the original text. - # The letters Pe and Kaf rarely display a related behavior of not being - # a good Non-Final letter. Words like 'Pop', 'Winamp' and 'Mubarak' - # for example legally end with a Non-Final Pe or Kaf. However, the - # benefit of these letters as Non-Final letters outweighs the damage - # since these words are quite rare. - return c in [self.NORMAL_KAF, self.NORMAL_MEM, - self.NORMAL_NUN, self.NORMAL_PE] - - def feed(self, byte_str): - # Final letter analysis for logical-visual decision. - # Look for evidence that the received buffer is either logical Hebrew - # or visual Hebrew. - # The following cases are checked: - # 1) A word longer than 1 letter, ending with a final letter. This is - # an indication that the text is laid out "naturally" since the - # final letter really appears at the end. +1 for logical score. - # 2) A word longer than 1 letter, ending with a Non-Final letter. In - # normal Hebrew, words ending with Kaf, Mem, Nun, Pe or Tsadi, - # should not end with the Non-Final form of that letter. Exceptions - # to this rule are mentioned above in isNonFinal(). This is an - # indication that the text is laid out backwards. +1 for visual - # score - # 3) A word longer than 1 letter, starting with a final letter. Final - # letters should not appear at the beginning of a word. This is an - # indication that the text is laid out backwards. +1 for visual - # score. - # - # The visual score and logical score are accumulated throughout the - # text and are finally checked against each other in GetCharSetName(). - # No checking for final letters in the middle of words is done since - # that case is not an indication for either Logical or Visual text. - # - # We automatically filter out all 7-bit characters (replace them with - # spaces) so the word boundary detection works properly. [MAP] - - if self.state == ProbingState.NOT_ME: - # Both model probers say it's not them. No reason to continue. - return ProbingState.NOT_ME - - byte_str = self.filter_high_byte_only(byte_str) - - for cur in byte_str: - if cur == ' ': - # We stand on a space - a word just ended - if self._before_prev != ' ': - # next-to-last char was not a space so self._prev is not a - # 1 letter word - if self.is_final(self._prev): - # case (1) [-2:not space][-1:final letter][cur:space] - self._final_char_logical_score += 1 - elif self.is_non_final(self._prev): - # case (2) [-2:not space][-1:Non-Final letter][ - # cur:space] - self._final_char_visual_score += 1 - else: - # Not standing on a space - if ((self._before_prev == ' ') and - (self.is_final(self._prev)) and (cur != ' ')): - # case (3) [-2:space][-1:final letter][cur:not space] - self._final_char_visual_score += 1 - self._before_prev = self._prev - self._prev = cur - - # Forever detecting, till the end or until both model probers return - # ProbingState.NOT_ME (handled above) - return ProbingState.DETECTING - - @property - def charset_name(self): - # Make the decision: is it Logical or Visual? - # If the final letter score distance is dominant enough, rely on it. - finalsub = self._final_char_logical_score - self._final_char_visual_score - if finalsub >= self.MIN_FINAL_CHAR_DISTANCE: - return self.LOGICAL_HEBREW_NAME - if finalsub <= -self.MIN_FINAL_CHAR_DISTANCE: - return self.VISUAL_HEBREW_NAME - - # It's not dominant enough, try to rely on the model scores instead. - modelsub = (self._logical_prober.get_confidence() - - self._visual_prober.get_confidence()) - if modelsub > self.MIN_MODEL_DISTANCE: - return self.LOGICAL_HEBREW_NAME - if modelsub < -self.MIN_MODEL_DISTANCE: - return self.VISUAL_HEBREW_NAME - - # Still no good, back to final letter distance, maybe it'll save the - # day. - if finalsub < 0.0: - return self.VISUAL_HEBREW_NAME - - # (finalsub > 0 - Logical) or (don't know what to do) default to - # Logical. - return self.LOGICAL_HEBREW_NAME - - @property - def language(self): - return 'Hebrew' - - @property - def state(self): - # Remain active as long as any of the model probers are active. - if (self._logical_prober.state == ProbingState.NOT_ME) and \ - (self._visual_prober.state == ProbingState.NOT_ME): - return ProbingState.NOT_ME - return ProbingState.DETECTING diff --git a/lib/chardet/jisfreq.py b/lib/chardet/jisfreq.py deleted file mode 100644 index 83fc082..0000000 --- a/lib/chardet/jisfreq.py +++ /dev/null @@ -1,325 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# Sampling from about 20M text materials include literature and computer technology -# -# Japanese frequency table, applied to both S-JIS and EUC-JP -# They are sorted in order. - -# 128 --> 0.77094 -# 256 --> 0.85710 -# 512 --> 0.92635 -# 1024 --> 0.97130 -# 2048 --> 0.99431 -# -# Ideal Distribution Ratio = 0.92635 / (1-0.92635) = 12.58 -# Random Distribution Ration = 512 / (2965+62+83+86-512) = 0.191 -# -# Typical Distribution Ratio, 25% of IDR - -JIS_TYPICAL_DISTRIBUTION_RATIO = 3.0 - -# Char to FreqOrder table , -JIS_TABLE_SIZE = 4368 - -JIS_CHAR_TO_FREQ_ORDER = ( - 40, 1, 6, 182, 152, 180, 295,2127, 285, 381,3295,4304,3068,4606,3165,3510, # 16 -3511,1822,2785,4607,1193,2226,5070,4608, 171,2996,1247, 18, 179,5071, 856,1661, # 32 -1262,5072, 619, 127,3431,3512,3230,1899,1700, 232, 228,1294,1298, 284, 283,2041, # 48 -2042,1061,1062, 48, 49, 44, 45, 433, 434,1040,1041, 996, 787,2997,1255,4305, # 64 -2108,4609,1684,1648,5073,5074,5075,5076,5077,5078,3687,5079,4610,5080,3927,3928, # 80 -5081,3296,3432, 290,2285,1471,2187,5082,2580,2825,1303,2140,1739,1445,2691,3375, # 96 -1691,3297,4306,4307,4611, 452,3376,1182,2713,3688,3069,4308,5083,5084,5085,5086, # 112 -5087,5088,5089,5090,5091,5092,5093,5094,5095,5096,5097,5098,5099,5100,5101,5102, # 128 -5103,5104,5105,5106,5107,5108,5109,5110,5111,5112,4097,5113,5114,5115,5116,5117, # 144 -5118,5119,5120,5121,5122,5123,5124,5125,5126,5127,5128,5129,5130,5131,5132,5133, # 160 -5134,5135,5136,5137,5138,5139,5140,5141,5142,5143,5144,5145,5146,5147,5148,5149, # 176 -5150,5151,5152,4612,5153,5154,5155,5156,5157,5158,5159,5160,5161,5162,5163,5164, # 192 -5165,5166,5167,5168,5169,5170,5171,5172,5173,5174,5175,1472, 598, 618, 820,1205, # 208 -1309,1412,1858,1307,1692,5176,5177,5178,5179,5180,5181,5182,1142,1452,1234,1172, # 224 -1875,2043,2149,1793,1382,2973, 925,2404,1067,1241, 960,1377,2935,1491, 919,1217, # 240 -1865,2030,1406,1499,2749,4098,5183,5184,5185,5186,5187,5188,2561,4099,3117,1804, # 256 -2049,3689,4309,3513,1663,5189,3166,3118,3298,1587,1561,3433,5190,3119,1625,2998, # 272 -3299,4613,1766,3690,2786,4614,5191,5192,5193,5194,2161, 26,3377, 2,3929, 20, # 288 -3691, 47,4100, 50, 17, 16, 35, 268, 27, 243, 42, 155, 24, 154, 29, 184, # 304 - 4, 91, 14, 92, 53, 396, 33, 289, 9, 37, 64, 620, 21, 39, 321, 5, # 320 - 12, 11, 52, 13, 3, 208, 138, 0, 7, 60, 526, 141, 151,1069, 181, 275, # 336 -1591, 83, 132,1475, 126, 331, 829, 15, 69, 160, 59, 22, 157, 55,1079, 312, # 352 - 109, 38, 23, 25, 10, 19, 79,5195, 61, 382,1124, 8, 30,5196,5197,5198, # 368 -5199,5200,5201,5202,5203,5204,5205,5206, 89, 62, 74, 34,2416, 112, 139, 196, # 384 - 271, 149, 84, 607, 131, 765, 46, 88, 153, 683, 76, 874, 101, 258, 57, 80, # 400 - 32, 364, 121,1508, 169,1547, 68, 235, 145,2999, 41, 360,3027, 70, 63, 31, # 416 - 43, 259, 262,1383, 99, 533, 194, 66, 93, 846, 217, 192, 56, 106, 58, 565, # 432 - 280, 272, 311, 256, 146, 82, 308, 71, 100, 128, 214, 655, 110, 261, 104,1140, # 448 - 54, 51, 36, 87, 67,3070, 185,2618,2936,2020, 28,1066,2390,2059,5207,5208, # 464 -5209,5210,5211,5212,5213,5214,5215,5216,4615,5217,5218,5219,5220,5221,5222,5223, # 480 -5224,5225,5226,5227,5228,5229,5230,5231,5232,5233,5234,5235,5236,3514,5237,5238, # 496 -5239,5240,5241,5242,5243,5244,2297,2031,4616,4310,3692,5245,3071,5246,3598,5247, # 512 -4617,3231,3515,5248,4101,4311,4618,3808,4312,4102,5249,4103,4104,3599,5250,5251, # 528 -5252,5253,5254,5255,5256,5257,5258,5259,5260,5261,5262,5263,5264,5265,5266,5267, # 544 -5268,5269,5270,5271,5272,5273,5274,5275,5276,5277,5278,5279,5280,5281,5282,5283, # 560 -5284,5285,5286,5287,5288,5289,5290,5291,5292,5293,5294,5295,5296,5297,5298,5299, # 576 -5300,5301,5302,5303,5304,5305,5306,5307,5308,5309,5310,5311,5312,5313,5314,5315, # 592 -5316,5317,5318,5319,5320,5321,5322,5323,5324,5325,5326,5327,5328,5329,5330,5331, # 608 -5332,5333,5334,5335,5336,5337,5338,5339,5340,5341,5342,5343,5344,5345,5346,5347, # 624 -5348,5349,5350,5351,5352,5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363, # 640 -5364,5365,5366,5367,5368,5369,5370,5371,5372,5373,5374,5375,5376,5377,5378,5379, # 656 -5380,5381, 363, 642,2787,2878,2788,2789,2316,3232,2317,3434,2011, 165,1942,3930, # 672 -3931,3932,3933,5382,4619,5383,4620,5384,5385,5386,5387,5388,5389,5390,5391,5392, # 688 -5393,5394,5395,5396,5397,5398,5399,5400,5401,5402,5403,5404,5405,5406,5407,5408, # 704 -5409,5410,5411,5412,5413,5414,5415,5416,5417,5418,5419,5420,5421,5422,5423,5424, # 720 -5425,5426,5427,5428,5429,5430,5431,5432,5433,5434,5435,5436,5437,5438,5439,5440, # 736 -5441,5442,5443,5444,5445,5446,5447,5448,5449,5450,5451,5452,5453,5454,5455,5456, # 752 -5457,5458,5459,5460,5461,5462,5463,5464,5465,5466,5467,5468,5469,5470,5471,5472, # 768 -5473,5474,5475,5476,5477,5478,5479,5480,5481,5482,5483,5484,5485,5486,5487,5488, # 784 -5489,5490,5491,5492,5493,5494,5495,5496,5497,5498,5499,5500,5501,5502,5503,5504, # 800 -5505,5506,5507,5508,5509,5510,5511,5512,5513,5514,5515,5516,5517,5518,5519,5520, # 816 -5521,5522,5523,5524,5525,5526,5527,5528,5529,5530,5531,5532,5533,5534,5535,5536, # 832 -5537,5538,5539,5540,5541,5542,5543,5544,5545,5546,5547,5548,5549,5550,5551,5552, # 848 -5553,5554,5555,5556,5557,5558,5559,5560,5561,5562,5563,5564,5565,5566,5567,5568, # 864 -5569,5570,5571,5572,5573,5574,5575,5576,5577,5578,5579,5580,5581,5582,5583,5584, # 880 -5585,5586,5587,5588,5589,5590,5591,5592,5593,5594,5595,5596,5597,5598,5599,5600, # 896 -5601,5602,5603,5604,5605,5606,5607,5608,5609,5610,5611,5612,5613,5614,5615,5616, # 912 -5617,5618,5619,5620,5621,5622,5623,5624,5625,5626,5627,5628,5629,5630,5631,5632, # 928 -5633,5634,5635,5636,5637,5638,5639,5640,5641,5642,5643,5644,5645,5646,5647,5648, # 944 -5649,5650,5651,5652,5653,5654,5655,5656,5657,5658,5659,5660,5661,5662,5663,5664, # 960 -5665,5666,5667,5668,5669,5670,5671,5672,5673,5674,5675,5676,5677,5678,5679,5680, # 976 -5681,5682,5683,5684,5685,5686,5687,5688,5689,5690,5691,5692,5693,5694,5695,5696, # 992 -5697,5698,5699,5700,5701,5702,5703,5704,5705,5706,5707,5708,5709,5710,5711,5712, # 1008 -5713,5714,5715,5716,5717,5718,5719,5720,5721,5722,5723,5724,5725,5726,5727,5728, # 1024 -5729,5730,5731,5732,5733,5734,5735,5736,5737,5738,5739,5740,5741,5742,5743,5744, # 1040 -5745,5746,5747,5748,5749,5750,5751,5752,5753,5754,5755,5756,5757,5758,5759,5760, # 1056 -5761,5762,5763,5764,5765,5766,5767,5768,5769,5770,5771,5772,5773,5774,5775,5776, # 1072 -5777,5778,5779,5780,5781,5782,5783,5784,5785,5786,5787,5788,5789,5790,5791,5792, # 1088 -5793,5794,5795,5796,5797,5798,5799,5800,5801,5802,5803,5804,5805,5806,5807,5808, # 1104 -5809,5810,5811,5812,5813,5814,5815,5816,5817,5818,5819,5820,5821,5822,5823,5824, # 1120 -5825,5826,5827,5828,5829,5830,5831,5832,5833,5834,5835,5836,5837,5838,5839,5840, # 1136 -5841,5842,5843,5844,5845,5846,5847,5848,5849,5850,5851,5852,5853,5854,5855,5856, # 1152 -5857,5858,5859,5860,5861,5862,5863,5864,5865,5866,5867,5868,5869,5870,5871,5872, # 1168 -5873,5874,5875,5876,5877,5878,5879,5880,5881,5882,5883,5884,5885,5886,5887,5888, # 1184 -5889,5890,5891,5892,5893,5894,5895,5896,5897,5898,5899,5900,5901,5902,5903,5904, # 1200 -5905,5906,5907,5908,5909,5910,5911,5912,5913,5914,5915,5916,5917,5918,5919,5920, # 1216 -5921,5922,5923,5924,5925,5926,5927,5928,5929,5930,5931,5932,5933,5934,5935,5936, # 1232 -5937,5938,5939,5940,5941,5942,5943,5944,5945,5946,5947,5948,5949,5950,5951,5952, # 1248 -5953,5954,5955,5956,5957,5958,5959,5960,5961,5962,5963,5964,5965,5966,5967,5968, # 1264 -5969,5970,5971,5972,5973,5974,5975,5976,5977,5978,5979,5980,5981,5982,5983,5984, # 1280 -5985,5986,5987,5988,5989,5990,5991,5992,5993,5994,5995,5996,5997,5998,5999,6000, # 1296 -6001,6002,6003,6004,6005,6006,6007,6008,6009,6010,6011,6012,6013,6014,6015,6016, # 1312 -6017,6018,6019,6020,6021,6022,6023,6024,6025,6026,6027,6028,6029,6030,6031,6032, # 1328 -6033,6034,6035,6036,6037,6038,6039,6040,6041,6042,6043,6044,6045,6046,6047,6048, # 1344 -6049,6050,6051,6052,6053,6054,6055,6056,6057,6058,6059,6060,6061,6062,6063,6064, # 1360 -6065,6066,6067,6068,6069,6070,6071,6072,6073,6074,6075,6076,6077,6078,6079,6080, # 1376 -6081,6082,6083,6084,6085,6086,6087,6088,6089,6090,6091,6092,6093,6094,6095,6096, # 1392 -6097,6098,6099,6100,6101,6102,6103,6104,6105,6106,6107,6108,6109,6110,6111,6112, # 1408 -6113,6114,2044,2060,4621, 997,1235, 473,1186,4622, 920,3378,6115,6116, 379,1108, # 1424 -4313,2657,2735,3934,6117,3809, 636,3233, 573,1026,3693,3435,2974,3300,2298,4105, # 1440 - 854,2937,2463, 393,2581,2417, 539, 752,1280,2750,2480, 140,1161, 440, 708,1569, # 1456 - 665,2497,1746,1291,1523,3000, 164,1603, 847,1331, 537,1997, 486, 508,1693,2418, # 1472 -1970,2227, 878,1220, 299,1030, 969, 652,2751, 624,1137,3301,2619, 65,3302,2045, # 1488 -1761,1859,3120,1930,3694,3516, 663,1767, 852, 835,3695, 269, 767,2826,2339,1305, # 1504 - 896,1150, 770,1616,6118, 506,1502,2075,1012,2519, 775,2520,2975,2340,2938,4314, # 1520 -3028,2086,1224,1943,2286,6119,3072,4315,2240,1273,1987,3935,1557, 175, 597, 985, # 1536 -3517,2419,2521,1416,3029, 585, 938,1931,1007,1052,1932,1685,6120,3379,4316,4623, # 1552 - 804, 599,3121,1333,2128,2539,1159,1554,2032,3810, 687,2033,2904, 952, 675,1467, # 1568 -3436,6121,2241,1096,1786,2440,1543,1924, 980,1813,2228, 781,2692,1879, 728,1918, # 1584 -3696,4624, 548,1950,4625,1809,1088,1356,3303,2522,1944, 502, 972, 373, 513,2827, # 1600 - 586,2377,2391,1003,1976,1631,6122,2464,1084, 648,1776,4626,2141, 324, 962,2012, # 1616 -2177,2076,1384, 742,2178,1448,1173,1810, 222, 102, 301, 445, 125,2420, 662,2498, # 1632 - 277, 200,1476,1165,1068, 224,2562,1378,1446, 450,1880, 659, 791, 582,4627,2939, # 1648 -3936,1516,1274, 555,2099,3697,1020,1389,1526,3380,1762,1723,1787,2229, 412,2114, # 1664 -1900,2392,3518, 512,2597, 427,1925,2341,3122,1653,1686,2465,2499, 697, 330, 273, # 1680 - 380,2162, 951, 832, 780, 991,1301,3073, 965,2270,3519, 668,2523,2636,1286, 535, # 1696 -1407, 518, 671, 957,2658,2378, 267, 611,2197,3030,6123, 248,2299, 967,1799,2356, # 1712 - 850,1418,3437,1876,1256,1480,2828,1718,6124,6125,1755,1664,2405,6126,4628,2879, # 1728 -2829, 499,2179, 676,4629, 557,2329,2214,2090, 325,3234, 464, 811,3001, 992,2342, # 1744 -2481,1232,1469, 303,2242, 466,1070,2163, 603,1777,2091,4630,2752,4631,2714, 322, # 1760 -2659,1964,1768, 481,2188,1463,2330,2857,3600,2092,3031,2421,4632,2318,2070,1849, # 1776 -2598,4633,1302,2254,1668,1701,2422,3811,2905,3032,3123,2046,4106,1763,1694,4634, # 1792 -1604, 943,1724,1454, 917, 868,2215,1169,2940, 552,1145,1800,1228,1823,1955, 316, # 1808 -1080,2510, 361,1807,2830,4107,2660,3381,1346,1423,1134,4108,6127, 541,1263,1229, # 1824 -1148,2540, 545, 465,1833,2880,3438,1901,3074,2482, 816,3937, 713,1788,2500, 122, # 1840 -1575, 195,1451,2501,1111,6128, 859, 374,1225,2243,2483,4317, 390,1033,3439,3075, # 1856 -2524,1687, 266, 793,1440,2599, 946, 779, 802, 507, 897,1081, 528,2189,1292, 711, # 1872 -1866,1725,1167,1640, 753, 398,2661,1053, 246, 348,4318, 137,1024,3440,1600,2077, # 1888 -2129, 825,4319, 698, 238, 521, 187,2300,1157,2423,1641,1605,1464,1610,1097,2541, # 1904 -1260,1436, 759,2255,1814,2150, 705,3235, 409,2563,3304, 561,3033,2005,2564, 726, # 1920 -1956,2343,3698,4109, 949,3812,3813,3520,1669, 653,1379,2525, 881,2198, 632,2256, # 1936 -1027, 778,1074, 733,1957, 514,1481,2466, 554,2180, 702,3938,1606,1017,1398,6129, # 1952 -1380,3521, 921, 993,1313, 594, 449,1489,1617,1166, 768,1426,1360, 495,1794,3601, # 1968 -1177,3602,1170,4320,2344, 476, 425,3167,4635,3168,1424, 401,2662,1171,3382,1998, # 1984 -1089,4110, 477,3169, 474,6130,1909, 596,2831,1842, 494, 693,1051,1028,1207,3076, # 2000 - 606,2115, 727,2790,1473,1115, 743,3522, 630, 805,1532,4321,2021, 366,1057, 838, # 2016 - 684,1114,2142,4322,2050,1492,1892,1808,2271,3814,2424,1971,1447,1373,3305,1090, # 2032 -1536,3939,3523,3306,1455,2199, 336, 369,2331,1035, 584,2393, 902, 718,2600,6131, # 2048 -2753, 463,2151,1149,1611,2467, 715,1308,3124,1268, 343,1413,3236,1517,1347,2663, # 2064 -2093,3940,2022,1131,1553,2100,2941,1427,3441,2942,1323,2484,6132,1980, 872,2368, # 2080 -2441,2943, 320,2369,2116,1082, 679,1933,3941,2791,3815, 625,1143,2023, 422,2200, # 2096 -3816,6133, 730,1695, 356,2257,1626,2301,2858,2637,1627,1778, 937, 883,2906,2693, # 2112 -3002,1769,1086, 400,1063,1325,3307,2792,4111,3077, 456,2345,1046, 747,6134,1524, # 2128 - 884,1094,3383,1474,2164,1059, 974,1688,2181,2258,1047, 345,1665,1187, 358, 875, # 2144 -3170, 305, 660,3524,2190,1334,1135,3171,1540,1649,2542,1527, 927, 968,2793, 885, # 2160 -1972,1850, 482, 500,2638,1218,1109,1085,2543,1654,2034, 876, 78,2287,1482,1277, # 2176 - 861,1675,1083,1779, 724,2754, 454, 397,1132,1612,2332, 893, 672,1237, 257,2259, # 2192 -2370, 135,3384, 337,2244, 547, 352, 340, 709,2485,1400, 788,1138,2511, 540, 772, # 2208 -1682,2260,2272,2544,2013,1843,1902,4636,1999,1562,2288,4637,2201,1403,1533, 407, # 2224 - 576,3308,1254,2071, 978,3385, 170, 136,1201,3125,2664,3172,2394, 213, 912, 873, # 2240 -3603,1713,2202, 699,3604,3699, 813,3442, 493, 531,1054, 468,2907,1483, 304, 281, # 2256 -4112,1726,1252,2094, 339,2319,2130,2639, 756,1563,2944, 748, 571,2976,1588,2425, # 2272 -2715,1851,1460,2426,1528,1392,1973,3237, 288,3309, 685,3386, 296, 892,2716,2216, # 2288 -1570,2245, 722,1747,2217, 905,3238,1103,6135,1893,1441,1965, 251,1805,2371,3700, # 2304 -2601,1919,1078, 75,2182,1509,1592,1270,2640,4638,2152,6136,3310,3817, 524, 706, # 2320 -1075, 292,3818,1756,2602, 317, 98,3173,3605,3525,1844,2218,3819,2502, 814, 567, # 2336 - 385,2908,1534,6137, 534,1642,3239, 797,6138,1670,1529, 953,4323, 188,1071, 538, # 2352 - 178, 729,3240,2109,1226,1374,2000,2357,2977, 731,2468,1116,2014,2051,6139,1261, # 2368 -1593, 803,2859,2736,3443, 556, 682, 823,1541,6140,1369,2289,1706,2794, 845, 462, # 2384 -2603,2665,1361, 387, 162,2358,1740, 739,1770,1720,1304,1401,3241,1049, 627,1571, # 2400 -2427,3526,1877,3942,1852,1500, 431,1910,1503, 677, 297,2795, 286,1433,1038,1198, # 2416 -2290,1133,1596,4113,4639,2469,1510,1484,3943,6141,2442, 108, 712,4640,2372, 866, # 2432 -3701,2755,3242,1348, 834,1945,1408,3527,2395,3243,1811, 824, 994,1179,2110,1548, # 2448 -1453, 790,3003, 690,4324,4325,2832,2909,3820,1860,3821, 225,1748, 310, 346,1780, # 2464 -2470, 821,1993,2717,2796, 828, 877,3528,2860,2471,1702,2165,2910,2486,1789, 453, # 2480 - 359,2291,1676, 73,1164,1461,1127,3311, 421, 604, 314,1037, 589, 116,2487, 737, # 2496 - 837,1180, 111, 244, 735,6142,2261,1861,1362, 986, 523, 418, 581,2666,3822, 103, # 2512 - 855, 503,1414,1867,2488,1091, 657,1597, 979, 605,1316,4641,1021,2443,2078,2001, # 2528 -1209, 96, 587,2166,1032, 260,1072,2153, 173, 94, 226,3244, 819,2006,4642,4114, # 2544 -2203, 231,1744, 782, 97,2667, 786,3387, 887, 391, 442,2219,4326,1425,6143,2694, # 2560 - 633,1544,1202, 483,2015, 592,2052,1958,2472,1655, 419, 129,4327,3444,3312,1714, # 2576 -1257,3078,4328,1518,1098, 865,1310,1019,1885,1512,1734, 469,2444, 148, 773, 436, # 2592 -1815,1868,1128,1055,4329,1245,2756,3445,2154,1934,1039,4643, 579,1238, 932,2320, # 2608 - 353, 205, 801, 115,2428, 944,2321,1881, 399,2565,1211, 678, 766,3944, 335,2101, # 2624 -1459,1781,1402,3945,2737,2131,1010, 844, 981,1326,1013, 550,1816,1545,2620,1335, # 2640 -1008, 371,2881, 936,1419,1613,3529,1456,1395,2273,1834,2604,1317,2738,2503, 416, # 2656 -1643,4330, 806,1126, 229, 591,3946,1314,1981,1576,1837,1666, 347,1790, 977,3313, # 2672 - 764,2861,1853, 688,2429,1920,1462, 77, 595, 415,2002,3034, 798,1192,4115,6144, # 2688 -2978,4331,3035,2695,2582,2072,2566, 430,2430,1727, 842,1396,3947,3702, 613, 377, # 2704 - 278, 236,1417,3388,3314,3174, 757,1869, 107,3530,6145,1194, 623,2262, 207,1253, # 2720 -2167,3446,3948, 492,1117,1935, 536,1838,2757,1246,4332, 696,2095,2406,1393,1572, # 2736 -3175,1782, 583, 190, 253,1390,2230, 830,3126,3389, 934,3245,1703,1749,2979,1870, # 2752 -2545,1656,2204, 869,2346,4116,3176,1817, 496,1764,4644, 942,1504, 404,1903,1122, # 2768 -1580,3606,2945,1022, 515, 372,1735, 955,2431,3036,6146,2797,1110,2302,2798, 617, # 2784 -6147, 441, 762,1771,3447,3607,3608,1904, 840,3037, 86, 939,1385, 572,1370,2445, # 2800 -1336, 114,3703, 898, 294, 203,3315, 703,1583,2274, 429, 961,4333,1854,1951,3390, # 2816 -2373,3704,4334,1318,1381, 966,1911,2322,1006,1155, 309, 989, 458,2718,1795,1372, # 2832 -1203, 252,1689,1363,3177, 517,1936, 168,1490, 562, 193,3823,1042,4117,1835, 551, # 2848 - 470,4645, 395, 489,3448,1871,1465,2583,2641, 417,1493, 279,1295, 511,1236,1119, # 2864 - 72,1231,1982,1812,3004, 871,1564, 984,3449,1667,2696,2096,4646,2347,2833,1673, # 2880 -3609, 695,3246,2668, 807,1183,4647, 890, 388,2333,1801,1457,2911,1765,1477,1031, # 2896 -3316,3317,1278,3391,2799,2292,2526, 163,3450,4335,2669,1404,1802,6148,2323,2407, # 2912 -1584,1728,1494,1824,1269, 298, 909,3318,1034,1632, 375, 776,1683,2061, 291, 210, # 2928 -1123, 809,1249,1002,2642,3038, 206,1011,2132, 144, 975, 882,1565, 342, 667, 754, # 2944 -1442,2143,1299,2303,2062, 447, 626,2205,1221,2739,2912,1144,1214,2206,2584, 760, # 2960 -1715, 614, 950,1281,2670,2621, 810, 577,1287,2546,4648, 242,2168, 250,2643, 691, # 2976 - 123,2644, 647, 313,1029, 689,1357,2946,1650, 216, 771,1339,1306, 808,2063, 549, # 2992 - 913,1371,2913,2914,6149,1466,1092,1174,1196,1311,2605,2396,1783,1796,3079, 406, # 3008 -2671,2117,3949,4649, 487,1825,2220,6150,2915, 448,2348,1073,6151,2397,1707, 130, # 3024 - 900,1598, 329, 176,1959,2527,1620,6152,2275,4336,3319,1983,2191,3705,3610,2155, # 3040 -3706,1912,1513,1614,6153,1988, 646, 392,2304,1589,3320,3039,1826,1239,1352,1340, # 3056 -2916, 505,2567,1709,1437,2408,2547, 906,6154,2672, 384,1458,1594,1100,1329, 710, # 3072 - 423,3531,2064,2231,2622,1989,2673,1087,1882, 333, 841,3005,1296,2882,2379, 580, # 3088 -1937,1827,1293,2585, 601, 574, 249,1772,4118,2079,1120, 645, 901,1176,1690, 795, # 3104 -2207, 478,1434, 516,1190,1530, 761,2080, 930,1264, 355, 435,1552, 644,1791, 987, # 3120 - 220,1364,1163,1121,1538, 306,2169,1327,1222, 546,2645, 218, 241, 610,1704,3321, # 3136 -1984,1839,1966,2528, 451,6155,2586,3707,2568, 907,3178, 254,2947, 186,1845,4650, # 3152 - 745, 432,1757, 428,1633, 888,2246,2221,2489,3611,2118,1258,1265, 956,3127,1784, # 3168 -4337,2490, 319, 510, 119, 457,3612, 274,2035,2007,4651,1409,3128, 970,2758, 590, # 3184 -2800, 661,2247,4652,2008,3950,1420,1549,3080,3322,3951,1651,1375,2111, 485,2491, # 3200 -1429,1156,6156,2548,2183,1495, 831,1840,2529,2446, 501,1657, 307,1894,3247,1341, # 3216 - 666, 899,2156,1539,2549,1559, 886, 349,2208,3081,2305,1736,3824,2170,2759,1014, # 3232 -1913,1386, 542,1397,2948, 490, 368, 716, 362, 159, 282,2569,1129,1658,1288,1750, # 3248 -2674, 276, 649,2016, 751,1496, 658,1818,1284,1862,2209,2087,2512,3451, 622,2834, # 3264 - 376, 117,1060,2053,1208,1721,1101,1443, 247,1250,3179,1792,3952,2760,2398,3953, # 3280 -6157,2144,3708, 446,2432,1151,2570,3452,2447,2761,2835,1210,2448,3082, 424,2222, # 3296 -1251,2449,2119,2836, 504,1581,4338, 602, 817, 857,3825,2349,2306, 357,3826,1470, # 3312 -1883,2883, 255, 958, 929,2917,3248, 302,4653,1050,1271,1751,2307,1952,1430,2697, # 3328 -2719,2359, 354,3180, 777, 158,2036,4339,1659,4340,4654,2308,2949,2248,1146,2232, # 3344 -3532,2720,1696,2623,3827,6158,3129,1550,2698,1485,1297,1428, 637, 931,2721,2145, # 3360 - 914,2550,2587, 81,2450, 612, 827,2646,1242,4655,1118,2884, 472,1855,3181,3533, # 3376 -3534, 569,1353,2699,1244,1758,2588,4119,2009,2762,2171,3709,1312,1531,6159,1152, # 3392 -1938, 134,1830, 471,3710,2276,1112,1535,3323,3453,3535, 982,1337,2950, 488, 826, # 3408 - 674,1058,1628,4120,2017, 522,2399, 211, 568,1367,3454, 350, 293,1872,1139,3249, # 3424 -1399,1946,3006,1300,2360,3324, 588, 736,6160,2606, 744, 669,3536,3828,6161,1358, # 3440 - 199, 723, 848, 933, 851,1939,1505,1514,1338,1618,1831,4656,1634,3613, 443,2740, # 3456 -3829, 717,1947, 491,1914,6162,2551,1542,4121,1025,6163,1099,1223, 198,3040,2722, # 3472 - 370, 410,1905,2589, 998,1248,3182,2380, 519,1449,4122,1710, 947, 928,1153,4341, # 3488 -2277, 344,2624,1511, 615, 105, 161,1212,1076,1960,3130,2054,1926,1175,1906,2473, # 3504 - 414,1873,2801,6164,2309, 315,1319,3325, 318,2018,2146,2157, 963, 631, 223,4342, # 3520 -4343,2675, 479,3711,1197,2625,3712,2676,2361,6165,4344,4123,6166,2451,3183,1886, # 3536 -2184,1674,1330,1711,1635,1506, 799, 219,3250,3083,3954,1677,3713,3326,2081,3614, # 3552 -1652,2073,4657,1147,3041,1752, 643,1961, 147,1974,3955,6167,1716,2037, 918,3007, # 3568 -1994, 120,1537, 118, 609,3184,4345, 740,3455,1219, 332,1615,3830,6168,1621,2980, # 3584 -1582, 783, 212, 553,2350,3714,1349,2433,2082,4124, 889,6169,2310,1275,1410, 973, # 3600 - 166,1320,3456,1797,1215,3185,2885,1846,2590,2763,4658, 629, 822,3008, 763, 940, # 3616 -1990,2862, 439,2409,1566,1240,1622, 926,1282,1907,2764, 654,2210,1607, 327,1130, # 3632 -3956,1678,1623,6170,2434,2192, 686, 608,3831,3715, 903,3957,3042,6171,2741,1522, # 3648 -1915,1105,1555,2552,1359, 323,3251,4346,3457, 738,1354,2553,2311,2334,1828,2003, # 3664 -3832,1753,2351,1227,6172,1887,4125,1478,6173,2410,1874,1712,1847, 520,1204,2607, # 3680 - 264,4659, 836,2677,2102, 600,4660,3833,2278,3084,6174,4347,3615,1342, 640, 532, # 3696 - 543,2608,1888,2400,2591,1009,4348,1497, 341,1737,3616,2723,1394, 529,3252,1321, # 3712 - 983,4661,1515,2120, 971,2592, 924, 287,1662,3186,4349,2700,4350,1519, 908,1948, # 3728 -2452, 156, 796,1629,1486,2223,2055, 694,4126,1259,1036,3392,1213,2249,2742,1889, # 3744 -1230,3958,1015, 910, 408, 559,3617,4662, 746, 725, 935,4663,3959,3009,1289, 563, # 3760 - 867,4664,3960,1567,2981,2038,2626, 988,2263,2381,4351, 143,2374, 704,1895,6175, # 3776 -1188,3716,2088, 673,3085,2362,4352, 484,1608,1921,2765,2918, 215, 904,3618,3537, # 3792 - 894, 509, 976,3043,2701,3961,4353,2837,2982, 498,6176,6177,1102,3538,1332,3393, # 3808 -1487,1636,1637, 233, 245,3962, 383, 650, 995,3044, 460,1520,1206,2352, 749,3327, # 3824 - 530, 700, 389,1438,1560,1773,3963,2264, 719,2951,2724,3834, 870,1832,1644,1000, # 3840 - 839,2474,3717, 197,1630,3394, 365,2886,3964,1285,2133, 734, 922, 818,1106, 732, # 3856 - 480,2083,1774,3458, 923,2279,1350, 221,3086, 85,2233,2234,3835,1585,3010,2147, # 3872 -1387,1705,2382,1619,2475, 133, 239,2802,1991,1016,2084,2383, 411,2838,1113, 651, # 3888 -1985,1160,3328, 990,1863,3087,1048,1276,2647, 265,2627,1599,3253,2056, 150, 638, # 3904 -2019, 656, 853, 326,1479, 680,1439,4354,1001,1759, 413,3459,3395,2492,1431, 459, # 3920 -4355,1125,3329,2265,1953,1450,2065,2863, 849, 351,2678,3131,3254,3255,1104,1577, # 3936 - 227,1351,1645,2453,2193,1421,2887, 812,2121, 634, 95,2435, 201,2312,4665,1646, # 3952 -1671,2743,1601,2554,2702,2648,2280,1315,1366,2089,3132,1573,3718,3965,1729,1189, # 3968 - 328,2679,1077,1940,1136, 558,1283, 964,1195, 621,2074,1199,1743,3460,3619,1896, # 3984 -1916,1890,3836,2952,1154,2112,1064, 862, 378,3011,2066,2113,2803,1568,2839,6178, # 4000 -3088,2919,1941,1660,2004,1992,2194, 142, 707,1590,1708,1624,1922,1023,1836,1233, # 4016 -1004,2313, 789, 741,3620,6179,1609,2411,1200,4127,3719,3720,4666,2057,3721, 593, # 4032 -2840, 367,2920,1878,6180,3461,1521, 628,1168, 692,2211,2649, 300, 720,2067,2571, # 4048 -2953,3396, 959,2504,3966,3539,3462,1977, 701,6181, 954,1043, 800, 681, 183,3722, # 4064 -1803,1730,3540,4128,2103, 815,2314, 174, 467, 230,2454,1093,2134, 755,3541,3397, # 4080 -1141,1162,6182,1738,2039, 270,3256,2513,1005,1647,2185,3837, 858,1679,1897,1719, # 4096 -2954,2324,1806, 402, 670, 167,4129,1498,2158,2104, 750,6183, 915, 189,1680,1551, # 4112 - 455,4356,1501,2455, 405,1095,2955, 338,1586,1266,1819, 570, 641,1324, 237,1556, # 4128 -2650,1388,3723,6184,1368,2384,1343,1978,3089,2436, 879,3724, 792,1191, 758,3012, # 4144 -1411,2135,1322,4357, 240,4667,1848,3725,1574,6185, 420,3045,1546,1391, 714,4358, # 4160 -1967, 941,1864, 863, 664, 426, 560,1731,2680,1785,2864,1949,2363, 403,3330,1415, # 4176 -1279,2136,1697,2335, 204, 721,2097,3838, 90,6186,2085,2505, 191,3967, 124,2148, # 4192 -1376,1798,1178,1107,1898,1405, 860,4359,1243,1272,2375,2983,1558,2456,1638, 113, # 4208 -3621, 578,1923,2609, 880, 386,4130, 784,2186,2266,1422,2956,2172,1722, 497, 263, # 4224 -2514,1267,2412,2610, 177,2703,3542, 774,1927,1344, 616,1432,1595,1018, 172,4360, # 4240 -2325, 911,4361, 438,1468,3622, 794,3968,2024,2173,1681,1829,2957, 945, 895,3090, # 4256 - 575,2212,2476, 475,2401,2681, 785,2744,1745,2293,2555,1975,3133,2865, 394,4668, # 4272 -3839, 635,4131, 639, 202,1507,2195,2766,1345,1435,2572,3726,1908,1184,1181,2457, # 4288 -3727,3134,4362, 843,2611, 437, 916,4669, 234, 769,1884,3046,3047,3623, 833,6187, # 4304 -1639,2250,2402,1355,1185,2010,2047, 999, 525,1732,1290,1488,2612, 948,1578,3728, # 4320 -2413,2477,1216,2725,2159, 334,3840,1328,3624,2921,1525,4132, 564,1056, 891,4363, # 4336 -1444,1698,2385,2251,3729,1365,2281,2235,1717,6188, 864,3841,2515, 444, 527,2767, # 4352 -2922,3625, 544, 461,6189, 566, 209,2437,3398,2098,1065,2068,3331,3626,3257,2137, # 4368 #last 512 -) - - diff --git a/lib/chardet/jpcntx.py b/lib/chardet/jpcntx.py deleted file mode 100644 index 20044e4..0000000 --- a/lib/chardet/jpcntx.py +++ /dev/null @@ -1,233 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - - -# This is hiragana 2-char sequence table, the number in each cell represents its frequency category -jp2CharContext = ( -(0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1), -(2,4,0,4,0,3,0,4,0,3,4,4,4,2,4,3,3,4,3,2,3,3,4,2,3,3,3,2,4,1,4,3,3,1,5,4,3,4,3,4,3,5,3,0,3,5,4,2,0,3,1,0,3,3,0,3,3,0,1,1,0,4,3,0,3,3,0,4,0,2,0,3,5,5,5,5,4,0,4,1,0,3,4), -(0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2), -(0,4,0,5,0,5,0,4,0,4,5,4,4,3,5,3,5,1,5,3,4,3,4,4,3,4,3,3,4,3,5,4,4,3,5,5,3,5,5,5,3,5,5,3,4,5,5,3,1,3,2,0,3,4,0,4,2,0,4,2,1,5,3,2,3,5,0,4,0,2,0,5,4,4,5,4,5,0,4,0,0,4,4), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), -(0,3,0,4,0,3,0,3,0,4,5,4,3,3,3,3,4,3,5,4,4,3,5,4,4,3,4,3,4,4,4,4,5,3,4,4,3,4,5,5,4,5,5,1,4,5,4,3,0,3,3,1,3,3,0,4,4,0,3,3,1,5,3,3,3,5,0,4,0,3,0,4,4,3,4,3,3,0,4,1,1,3,4), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), -(0,4,0,3,0,3,0,4,0,3,4,4,3,2,2,1,2,1,3,1,3,3,3,3,3,4,3,1,3,3,5,3,3,0,4,3,0,5,4,3,3,5,4,4,3,4,4,5,0,1,2,0,1,2,0,2,2,0,1,0,0,5,2,2,1,4,0,3,0,1,0,4,4,3,5,4,3,0,2,1,0,4,3), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), -(0,3,0,5,0,4,0,2,1,4,4,2,4,1,4,2,4,2,4,3,3,3,4,3,3,3,3,1,4,2,3,3,3,1,4,4,1,1,1,4,3,3,2,0,2,4,3,2,0,3,3,0,3,1,1,0,0,0,3,3,0,4,2,2,3,4,0,4,0,3,0,4,4,5,3,4,4,0,3,0,0,1,4), -(1,4,0,4,0,4,0,4,0,3,5,4,4,3,4,3,5,4,3,3,4,3,5,4,4,4,4,3,4,2,4,3,3,1,5,4,3,2,4,5,4,5,5,4,4,5,4,4,0,3,2,2,3,3,0,4,3,1,3,2,1,4,3,3,4,5,0,3,0,2,0,4,5,5,4,5,4,0,4,0,0,5,4), -(0,5,0,5,0,4,0,3,0,4,4,3,4,3,3,3,4,0,4,4,4,3,4,3,4,3,3,1,4,2,4,3,4,0,5,4,1,4,5,4,4,5,3,2,4,3,4,3,2,4,1,3,3,3,2,3,2,0,4,3,3,4,3,3,3,4,0,4,0,3,0,4,5,4,4,4,3,0,4,1,0,1,3), -(0,3,1,4,0,3,0,2,0,3,4,4,3,1,4,2,3,3,4,3,4,3,4,3,4,4,3,2,3,1,5,4,4,1,4,4,3,5,4,4,3,5,5,4,3,4,4,3,1,2,3,1,2,2,0,3,2,0,3,1,0,5,3,3,3,4,3,3,3,3,4,4,4,4,5,4,2,0,3,3,2,4,3), -(0,2,0,3,0,1,0,1,0,0,3,2,0,0,2,0,1,0,2,1,3,3,3,1,2,3,1,0,1,0,4,2,1,1,3,3,0,4,3,3,1,4,3,3,0,3,3,2,0,0,0,0,1,0,0,2,0,0,0,0,0,4,1,0,2,3,2,2,2,1,3,3,3,4,4,3,2,0,3,1,0,3,3), -(0,4,0,4,0,3,0,3,0,4,4,4,3,3,3,3,3,3,4,3,4,2,4,3,4,3,3,2,4,3,4,5,4,1,4,5,3,5,4,5,3,5,4,0,3,5,5,3,1,3,3,2,2,3,0,3,4,1,3,3,2,4,3,3,3,4,0,4,0,3,0,4,5,4,4,5,3,0,4,1,0,3,4), -(0,2,0,3,0,3,0,0,0,2,2,2,1,0,1,0,0,0,3,0,3,0,3,0,1,3,1,0,3,1,3,3,3,1,3,3,3,0,1,3,1,3,4,0,0,3,1,1,0,3,2,0,0,0,0,1,3,0,1,0,0,3,3,2,0,3,0,0,0,0,0,3,4,3,4,3,3,0,3,0,0,2,3), -(2,3,0,3,0,2,0,1,0,3,3,4,3,1,3,1,1,1,3,1,4,3,4,3,3,3,0,0,3,1,5,4,3,1,4,3,2,5,5,4,4,4,4,3,3,4,4,4,0,2,1,1,3,2,0,1,2,0,0,1,0,4,1,3,3,3,0,3,0,1,0,4,4,4,5,5,3,0,2,0,0,4,4), -(0,2,0,1,0,3,1,3,0,2,3,3,3,0,3,1,0,0,3,0,3,2,3,1,3,2,1,1,0,0,4,2,1,0,2,3,1,4,3,2,0,4,4,3,1,3,1,3,0,1,0,0,1,0,0,0,1,0,0,0,0,4,1,1,1,2,0,3,0,0,0,3,4,2,4,3,2,0,1,0,0,3,3), -(0,1,0,4,0,5,0,4,0,2,4,4,2,3,3,2,3,3,5,3,3,3,4,3,4,2,3,0,4,3,3,3,4,1,4,3,2,1,5,5,3,4,5,1,3,5,4,2,0,3,3,0,1,3,0,4,2,0,1,3,1,4,3,3,3,3,0,3,0,1,0,3,4,4,4,5,5,0,3,0,1,4,5), -(0,2,0,3,0,3,0,0,0,2,3,1,3,0,4,0,1,1,3,0,3,4,3,2,3,1,0,3,3,2,3,1,3,0,2,3,0,2,1,4,1,2,2,0,0,3,3,0,0,2,0,0,0,1,0,0,0,0,2,2,0,3,2,1,3,3,0,2,0,2,0,0,3,3,1,2,4,0,3,0,2,2,3), -(2,4,0,5,0,4,0,4,0,2,4,4,4,3,4,3,3,3,1,2,4,3,4,3,4,4,5,0,3,3,3,3,2,0,4,3,1,4,3,4,1,4,4,3,3,4,4,3,1,2,3,0,4,2,0,4,1,0,3,3,0,4,3,3,3,4,0,4,0,2,0,3,5,3,4,5,2,0,3,0,0,4,5), -(0,3,0,4,0,1,0,1,0,1,3,2,2,1,3,0,3,0,2,0,2,0,3,0,2,0,0,0,1,0,1,1,0,0,3,1,0,0,0,4,0,3,1,0,2,1,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,4,2,2,3,1,0,3,0,0,0,1,4,4,4,3,0,0,4,0,0,1,4), -(1,4,1,5,0,3,0,3,0,4,5,4,4,3,5,3,3,4,4,3,4,1,3,3,3,3,2,1,4,1,5,4,3,1,4,4,3,5,4,4,3,5,4,3,3,4,4,4,0,3,3,1,2,3,0,3,1,0,3,3,0,5,4,4,4,4,4,4,3,3,5,4,4,3,3,5,4,0,3,2,0,4,4), -(0,2,0,3,0,1,0,0,0,1,3,3,3,2,4,1,3,0,3,1,3,0,2,2,1,1,0,0,2,0,4,3,1,0,4,3,0,4,4,4,1,4,3,1,1,3,3,1,0,2,0,0,1,3,0,0,0,0,2,0,0,4,3,2,4,3,5,4,3,3,3,4,3,3,4,3,3,0,2,1,0,3,3), -(0,2,0,4,0,3,0,2,0,2,5,5,3,4,4,4,4,1,4,3,3,0,4,3,4,3,1,3,3,2,4,3,0,3,4,3,0,3,4,4,2,4,4,0,4,5,3,3,2,2,1,1,1,2,0,1,5,0,3,3,2,4,3,3,3,4,0,3,0,2,0,4,4,3,5,5,0,0,3,0,2,3,3), -(0,3,0,4,0,3,0,1,0,3,4,3,3,1,3,3,3,0,3,1,3,0,4,3,3,1,1,0,3,0,3,3,0,0,4,4,0,1,5,4,3,3,5,0,3,3,4,3,0,2,0,1,1,1,0,1,3,0,1,2,1,3,3,2,3,3,0,3,0,1,0,1,3,3,4,4,1,0,1,2,2,1,3), -(0,1,0,4,0,4,0,3,0,1,3,3,3,2,3,1,1,0,3,0,3,3,4,3,2,4,2,0,1,0,4,3,2,0,4,3,0,5,3,3,2,4,4,4,3,3,3,4,0,1,3,0,0,1,0,0,1,0,0,0,0,4,2,3,3,3,0,3,0,0,0,4,4,4,5,3,2,0,3,3,0,3,5), -(0,2,0,3,0,0,0,3,0,1,3,0,2,0,0,0,1,0,3,1,1,3,3,0,0,3,0,0,3,0,2,3,1,0,3,1,0,3,3,2,0,4,2,2,0,2,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,2,1,2,0,1,0,1,0,0,0,1,3,1,2,0,0,0,1,0,0,1,4), -(0,3,0,3,0,5,0,1,0,2,4,3,1,3,3,2,1,1,5,2,1,0,5,1,2,0,0,0,3,3,2,2,3,2,4,3,0,0,3,3,1,3,3,0,2,5,3,4,0,3,3,0,1,2,0,2,2,0,3,2,0,2,2,3,3,3,0,2,0,1,0,3,4,4,2,5,4,0,3,0,0,3,5), -(0,3,0,3,0,3,0,1,0,3,3,3,3,0,3,0,2,0,2,1,1,0,2,0,1,0,0,0,2,1,0,0,1,0,3,2,0,0,3,3,1,2,3,1,0,3,3,0,0,1,0,0,0,0,0,2,0,0,0,0,0,2,3,1,2,3,0,3,0,1,0,3,2,1,0,4,3,0,1,1,0,3,3), -(0,4,0,5,0,3,0,3,0,4,5,5,4,3,5,3,4,3,5,3,3,2,5,3,4,4,4,3,4,3,4,5,5,3,4,4,3,4,4,5,4,4,4,3,4,5,5,4,2,3,4,2,3,4,0,3,3,1,4,3,2,4,3,3,5,5,0,3,0,3,0,5,5,5,5,4,4,0,4,0,1,4,4), -(0,4,0,4,0,3,0,3,0,3,5,4,4,2,3,2,5,1,3,2,5,1,4,2,3,2,3,3,4,3,3,3,3,2,5,4,1,3,3,5,3,4,4,0,4,4,3,1,1,3,1,0,2,3,0,2,3,0,3,0,0,4,3,1,3,4,0,3,0,2,0,4,4,4,3,4,5,0,4,0,0,3,4), -(0,3,0,3,0,3,1,2,0,3,4,4,3,3,3,0,2,2,4,3,3,1,3,3,3,1,1,0,3,1,4,3,2,3,4,4,2,4,4,4,3,4,4,3,2,4,4,3,1,3,3,1,3,3,0,4,1,0,2,2,1,4,3,2,3,3,5,4,3,3,5,4,4,3,3,0,4,0,3,2,2,4,4), -(0,2,0,1,0,0,0,0,0,1,2,1,3,0,0,0,0,0,2,0,1,2,1,0,0,1,0,0,0,0,3,0,0,1,0,1,1,3,1,0,0,0,1,1,0,1,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,1,2,2,0,3,4,0,0,0,1,1,0,0,1,0,0,0,0,0,1,1), -(0,1,0,0,0,1,0,0,0,0,4,0,4,1,4,0,3,0,4,0,3,0,4,0,3,0,3,0,4,1,5,1,4,0,0,3,0,5,0,5,2,0,1,0,0,0,2,1,4,0,1,3,0,0,3,0,0,3,1,1,4,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0), -(1,4,0,5,0,3,0,2,0,3,5,4,4,3,4,3,5,3,4,3,3,0,4,3,3,3,3,3,3,2,4,4,3,1,3,4,4,5,4,4,3,4,4,1,3,5,4,3,3,3,1,2,2,3,3,1,3,1,3,3,3,5,3,3,4,5,0,3,0,3,0,3,4,3,4,4,3,0,3,0,2,4,3), -(0,1,0,4,0,0,0,0,0,1,4,0,4,1,4,2,4,0,3,0,1,0,1,0,0,0,0,0,2,0,3,1,1,1,0,3,0,0,0,1,2,1,0,0,1,1,1,1,0,1,0,0,0,1,0,0,3,0,0,0,0,3,2,0,2,2,0,1,0,0,0,2,3,2,3,3,0,0,0,0,2,1,0), -(0,5,1,5,0,3,0,3,0,5,4,4,5,1,5,3,3,0,4,3,4,3,5,3,4,3,3,2,4,3,4,3,3,0,3,3,1,4,4,3,4,4,4,3,4,5,5,3,2,3,1,1,3,3,1,3,1,1,3,3,2,4,5,3,3,5,0,4,0,3,0,4,4,3,5,3,3,0,3,4,0,4,3), -(0,5,0,5,0,3,0,2,0,4,4,3,5,2,4,3,3,3,4,4,4,3,5,3,5,3,3,1,4,0,4,3,3,0,3,3,0,4,4,4,4,5,4,3,3,5,5,3,2,3,1,2,3,2,0,1,0,0,3,2,2,4,4,3,1,5,0,4,0,3,0,4,3,1,3,2,1,0,3,3,0,3,3), -(0,4,0,5,0,5,0,4,0,4,5,5,5,3,4,3,3,2,5,4,4,3,5,3,5,3,4,0,4,3,4,4,3,2,4,4,3,4,5,4,4,5,5,0,3,5,5,4,1,3,3,2,3,3,1,3,1,0,4,3,1,4,4,3,4,5,0,4,0,2,0,4,3,4,4,3,3,0,4,0,0,5,5), -(0,4,0,4,0,5,0,1,1,3,3,4,4,3,4,1,3,0,5,1,3,0,3,1,3,1,1,0,3,0,3,3,4,0,4,3,0,4,4,4,3,4,4,0,3,5,4,1,0,3,0,0,2,3,0,3,1,0,3,1,0,3,2,1,3,5,0,3,0,1,0,3,2,3,3,4,4,0,2,2,0,4,4), -(2,4,0,5,0,4,0,3,0,4,5,5,4,3,5,3,5,3,5,3,5,2,5,3,4,3,3,4,3,4,5,3,2,1,5,4,3,2,3,4,5,3,4,1,2,5,4,3,0,3,3,0,3,2,0,2,3,0,4,1,0,3,4,3,3,5,0,3,0,1,0,4,5,5,5,4,3,0,4,2,0,3,5), -(0,5,0,4,0,4,0,2,0,5,4,3,4,3,4,3,3,3,4,3,4,2,5,3,5,3,4,1,4,3,4,4,4,0,3,5,0,4,4,4,4,5,3,1,3,4,5,3,3,3,3,3,3,3,0,2,2,0,3,3,2,4,3,3,3,5,3,4,1,3,3,5,3,2,0,0,0,0,4,3,1,3,3), -(0,1,0,3,0,3,0,1,0,1,3,3,3,2,3,3,3,0,3,0,0,0,3,1,3,0,0,0,2,2,2,3,0,0,3,2,0,1,2,4,1,3,3,0,0,3,3,3,0,1,0,0,2,1,0,0,3,0,3,1,0,3,0,0,1,3,0,2,0,1,0,3,3,1,3,3,0,0,1,1,0,3,3), -(0,2,0,3,0,2,1,4,0,2,2,3,1,1,3,1,1,0,2,0,3,1,2,3,1,3,0,0,1,0,4,3,2,3,3,3,1,4,2,3,3,3,3,1,0,3,1,4,0,1,1,0,1,2,0,1,1,0,1,1,0,3,1,3,2,2,0,1,0,0,0,2,3,3,3,1,0,0,0,0,0,2,3), -(0,5,0,4,0,5,0,2,0,4,5,5,3,3,4,3,3,1,5,4,4,2,4,4,4,3,4,2,4,3,5,5,4,3,3,4,3,3,5,5,4,5,5,1,3,4,5,3,1,4,3,1,3,3,0,3,3,1,4,3,1,4,5,3,3,5,0,4,0,3,0,5,3,3,1,4,3,0,4,0,1,5,3), -(0,5,0,5,0,4,0,2,0,4,4,3,4,3,3,3,3,3,5,4,4,4,4,4,4,5,3,3,5,2,4,4,4,3,4,4,3,3,4,4,5,5,3,3,4,3,4,3,3,4,3,3,3,3,1,2,2,1,4,3,3,5,4,4,3,4,0,4,0,3,0,4,4,4,4,4,1,0,4,2,0,2,4), -(0,4,0,4,0,3,0,1,0,3,5,2,3,0,3,0,2,1,4,2,3,3,4,1,4,3,3,2,4,1,3,3,3,0,3,3,0,0,3,3,3,5,3,3,3,3,3,2,0,2,0,0,2,0,0,2,0,0,1,0,0,3,1,2,2,3,0,3,0,2,0,4,4,3,3,4,1,0,3,0,0,2,4), -(0,0,0,4,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,1,0,2,0,1,0,0,0,0,0,3,1,3,0,3,2,0,0,0,1,0,3,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,0,2,0,0,0,0,0,0,2), -(0,2,1,3,0,2,0,2,0,3,3,3,3,1,3,1,3,3,3,3,3,3,4,2,2,1,2,1,4,0,4,3,1,3,3,3,2,4,3,5,4,3,3,3,3,3,3,3,0,1,3,0,2,0,0,1,0,0,1,0,0,4,2,0,2,3,0,3,3,0,3,3,4,2,3,1,4,0,1,2,0,2,3), -(0,3,0,3,0,1,0,3,0,2,3,3,3,0,3,1,2,0,3,3,2,3,3,2,3,2,3,1,3,0,4,3,2,0,3,3,1,4,3,3,2,3,4,3,1,3,3,1,1,0,1,1,0,1,0,1,0,1,0,0,0,4,1,1,0,3,0,3,1,0,2,3,3,3,3,3,1,0,0,2,0,3,3), -(0,0,0,0,0,0,0,0,0,0,3,0,2,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,3,0,3,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,2,0,2,3,0,0,0,0,0,0,0,0,3), -(0,2,0,3,1,3,0,3,0,2,3,3,3,1,3,1,3,1,3,1,3,3,3,1,3,0,2,3,1,1,4,3,3,2,3,3,1,2,2,4,1,3,3,0,1,4,2,3,0,1,3,0,3,0,0,1,3,0,2,0,0,3,3,2,1,3,0,3,0,2,0,3,4,4,4,3,1,0,3,0,0,3,3), -(0,2,0,1,0,2,0,0,0,1,3,2,2,1,3,0,1,1,3,0,3,2,3,1,2,0,2,0,1,1,3,3,3,0,3,3,1,1,2,3,2,3,3,1,2,3,2,0,0,1,0,0,0,0,0,0,3,0,1,0,0,2,1,2,1,3,0,3,0,0,0,3,4,4,4,3,2,0,2,0,0,2,4), -(0,0,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,3,1,0,0,0,0,0,0,0,3), -(0,3,0,3,0,2,0,3,0,3,3,3,2,3,2,2,2,0,3,1,3,3,3,2,3,3,0,0,3,0,3,2,2,0,2,3,1,4,3,4,3,3,2,3,1,5,4,4,0,3,1,2,1,3,0,3,1,1,2,0,2,3,1,3,1,3,0,3,0,1,0,3,3,4,4,2,1,0,2,1,0,2,4), -(0,1,0,3,0,1,0,2,0,1,4,2,5,1,4,0,2,0,2,1,3,1,4,0,2,1,0,0,2,1,4,1,1,0,3,3,0,5,1,3,2,3,3,1,0,3,2,3,0,1,0,0,0,0,0,0,1,0,0,0,0,4,0,1,0,3,0,2,0,1,0,3,3,3,4,3,3,0,0,0,0,2,3), -(0,0,0,1,0,0,0,0,0,0,2,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,1,0,0,0,0,0,3), -(0,1,0,3,0,4,0,3,0,2,4,3,1,0,3,2,2,1,3,1,2,2,3,1,1,1,2,1,3,0,1,2,0,1,3,2,1,3,0,5,5,1,0,0,1,3,2,1,0,3,0,0,1,0,0,0,0,0,3,4,0,1,1,1,3,2,0,2,0,1,0,2,3,3,1,2,3,0,1,0,1,0,4), -(0,0,0,1,0,3,0,3,0,2,2,1,0,0,4,0,3,0,3,1,3,0,3,0,3,0,1,0,3,0,3,1,3,0,3,3,0,0,1,2,1,1,1,0,1,2,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,2,2,1,2,0,0,2,0,0,0,0,2,3,3,3,3,0,0,0,0,1,4), -(0,0,0,3,0,3,0,0,0,0,3,1,1,0,3,0,1,0,2,0,1,0,0,0,0,0,0,0,1,0,3,0,2,0,2,3,0,0,2,2,3,1,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,2,3), -(2,4,0,5,0,5,0,4,0,3,4,3,3,3,4,3,3,3,4,3,4,4,5,4,5,5,5,2,3,0,5,5,4,1,5,4,3,1,5,4,3,4,4,3,3,4,3,3,0,3,2,0,2,3,0,3,0,0,3,3,0,5,3,2,3,3,0,3,0,3,0,3,4,5,4,5,3,0,4,3,0,3,4), -(0,3,0,3,0,3,0,3,0,3,3,4,3,2,3,2,3,0,4,3,3,3,3,3,3,3,3,0,3,2,4,3,3,1,3,4,3,4,4,4,3,4,4,3,2,4,4,1,0,2,0,0,1,1,0,2,0,0,3,1,0,5,3,2,1,3,0,3,0,1,2,4,3,2,4,3,3,0,3,2,0,4,4), -(0,3,0,3,0,1,0,0,0,1,4,3,3,2,3,1,3,1,4,2,3,2,4,2,3,4,3,0,2,2,3,3,3,0,3,3,3,0,3,4,1,3,3,0,3,4,3,3,0,1,1,0,1,0,0,0,4,0,3,0,0,3,1,2,1,3,0,4,0,1,0,4,3,3,4,3,3,0,2,0,0,3,3), -(0,3,0,4,0,1,0,3,0,3,4,3,3,0,3,3,3,1,3,1,3,3,4,3,3,3,0,0,3,1,5,3,3,1,3,3,2,5,4,3,3,4,5,3,2,5,3,4,0,1,0,0,0,0,0,2,0,0,1,1,0,4,2,2,1,3,0,3,0,2,0,4,4,3,5,3,2,0,1,1,0,3,4), -(0,5,0,4,0,5,0,2,0,4,4,3,3,2,3,3,3,1,4,3,4,1,5,3,4,3,4,0,4,2,4,3,4,1,5,4,0,4,4,4,4,5,4,1,3,5,4,2,1,4,1,1,3,2,0,3,1,0,3,2,1,4,3,3,3,4,0,4,0,3,0,4,4,4,3,3,3,0,4,2,0,3,4), -(1,4,0,4,0,3,0,1,0,3,3,3,1,1,3,3,2,2,3,3,1,0,3,2,2,1,2,0,3,1,2,1,2,0,3,2,0,2,2,3,3,4,3,0,3,3,1,2,0,1,1,3,1,2,0,0,3,0,1,1,0,3,2,2,3,3,0,3,0,0,0,2,3,3,4,3,3,0,1,0,0,1,4), -(0,4,0,4,0,4,0,0,0,3,4,4,3,1,4,2,3,2,3,3,3,1,4,3,4,0,3,0,4,2,3,3,2,2,5,4,2,1,3,4,3,4,3,1,3,3,4,2,0,2,1,0,3,3,0,0,2,0,3,1,0,4,4,3,4,3,0,4,0,1,0,2,4,4,4,4,4,0,3,2,0,3,3), -(0,0,0,1,0,4,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,3,2,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2), -(0,2,0,3,0,4,0,4,0,1,3,3,3,0,4,0,2,1,2,1,1,1,2,0,3,1,1,0,1,0,3,1,0,0,3,3,2,0,1,1,0,0,0,0,0,1,0,2,0,2,2,0,3,1,0,0,1,0,1,1,0,1,2,0,3,0,0,0,0,1,0,0,3,3,4,3,1,0,1,0,3,0,2), -(0,0,0,3,0,5,0,0,0,0,1,0,2,0,3,1,0,1,3,0,0,0,2,0,0,0,1,0,0,0,1,1,0,0,4,0,0,0,2,3,0,1,4,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,1,0,0,0,0,0,0,0,2,0,0,3,0,0,0,0,0,3), -(0,2,0,5,0,5,0,1,0,2,4,3,3,2,5,1,3,2,3,3,3,0,4,1,2,0,3,0,4,0,2,2,1,1,5,3,0,0,1,4,2,3,2,0,3,3,3,2,0,2,4,1,1,2,0,1,1,0,3,1,0,1,3,1,2,3,0,2,0,0,0,1,3,5,4,4,4,0,3,0,0,1,3), -(0,4,0,5,0,4,0,4,0,4,5,4,3,3,4,3,3,3,4,3,4,4,5,3,4,5,4,2,4,2,3,4,3,1,4,4,1,3,5,4,4,5,5,4,4,5,5,5,2,3,3,1,4,3,1,3,3,0,3,3,1,4,3,4,4,4,0,3,0,4,0,3,3,4,4,5,0,0,4,3,0,4,5), -(0,4,0,4,0,3,0,3,0,3,4,4,4,3,3,2,4,3,4,3,4,3,5,3,4,3,2,1,4,2,4,4,3,1,3,4,2,4,5,5,3,4,5,4,1,5,4,3,0,3,2,2,3,2,1,3,1,0,3,3,3,5,3,3,3,5,4,4,2,3,3,4,3,3,3,2,1,0,3,2,1,4,3), -(0,4,0,5,0,4,0,3,0,3,5,5,3,2,4,3,4,0,5,4,4,1,4,4,4,3,3,3,4,3,5,5,2,3,3,4,1,2,5,5,3,5,5,2,3,5,5,4,0,3,2,0,3,3,1,1,5,1,4,1,0,4,3,2,3,5,0,4,0,3,0,5,4,3,4,3,0,0,4,1,0,4,4), -(1,3,0,4,0,2,0,2,0,2,5,5,3,3,3,3,3,0,4,2,3,4,4,4,3,4,0,0,3,4,5,4,3,3,3,3,2,5,5,4,5,5,5,4,3,5,5,5,1,3,1,0,1,0,0,3,2,0,4,2,0,5,2,3,2,4,1,3,0,3,0,4,5,4,5,4,3,0,4,2,0,5,4), -(0,3,0,4,0,5,0,3,0,3,4,4,3,2,3,2,3,3,3,3,3,2,4,3,3,2,2,0,3,3,3,3,3,1,3,3,3,0,4,4,3,4,4,1,1,4,4,2,0,3,1,0,1,1,0,4,1,0,2,3,1,3,3,1,3,4,0,3,0,1,0,3,1,3,0,0,1,0,2,0,0,4,4), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), -(0,3,0,3,0,2,0,3,0,1,5,4,3,3,3,1,4,2,1,2,3,4,4,2,4,4,5,0,3,1,4,3,4,0,4,3,3,3,2,3,2,5,3,4,3,2,2,3,0,0,3,0,2,1,0,1,2,0,0,0,0,2,1,1,3,1,0,2,0,4,0,3,4,4,4,5,2,0,2,0,0,1,3), -(0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,0,1,1,0,0,0,4,2,1,1,0,1,0,3,2,0,0,3,1,1,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,1,0,0,0,2,0,0,0,1,4,0,4,2,1,0,0,0,0,0,1), -(0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,1,0,1,0,0,0,0,3,1,0,0,0,2,0,2,1,0,0,1,2,1,0,1,1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,1,3,1,0,0,0,0,0,1,0,0,2,1,0,0,0,0,0,0,0,0,2), -(0,4,0,4,0,4,0,3,0,4,4,3,4,2,4,3,2,0,4,4,4,3,5,3,5,3,3,2,4,2,4,3,4,3,1,4,0,2,3,4,4,4,3,3,3,4,4,4,3,4,1,3,4,3,2,1,2,1,3,3,3,4,4,3,3,5,0,4,0,3,0,4,3,3,3,2,1,0,3,0,0,3,3), -(0,4,0,3,0,3,0,3,0,3,5,5,3,3,3,3,4,3,4,3,3,3,4,4,4,3,3,3,3,4,3,5,3,3,1,3,2,4,5,5,5,5,4,3,4,5,5,3,2,2,3,3,3,3,2,3,3,1,2,3,2,4,3,3,3,4,0,4,0,2,0,4,3,2,2,1,2,0,3,0,0,4,1), -) - -class JapaneseContextAnalysis(object): - NUM_OF_CATEGORY = 6 - DONT_KNOW = -1 - ENOUGH_REL_THRESHOLD = 100 - MAX_REL_THRESHOLD = 1000 - MINIMUM_DATA_THRESHOLD = 4 - - def __init__(self): - self._total_rel = None - self._rel_sample = None - self._need_to_skip_char_num = None - self._last_char_order = None - self._done = None - self.reset() - - def reset(self): - self._total_rel = 0 # total sequence received - # category counters, each integer counts sequence in its category - self._rel_sample = [0] * self.NUM_OF_CATEGORY - # if last byte in current buffer is not the last byte of a character, - # we need to know how many bytes to skip in next buffer - self._need_to_skip_char_num = 0 - self._last_char_order = -1 # The order of previous char - # If this flag is set to True, detection is done and conclusion has - # been made - self._done = False - - def feed(self, byte_str, num_bytes): - if self._done: - return - - # The buffer we got is byte oriented, and a character may span in more than one - # buffers. In case the last one or two byte in last buffer is not - # complete, we record how many byte needed to complete that character - # and skip these bytes here. We can choose to record those bytes as - # well and analyse the character once it is complete, but since a - # character will not make much difference, by simply skipping - # this character will simply our logic and improve performance. - i = self._need_to_skip_char_num - while i < num_bytes: - order, char_len = self.get_order(byte_str[i:i + 2]) - i += char_len - if i > num_bytes: - self._need_to_skip_char_num = i - num_bytes - self._last_char_order = -1 - else: - if (order != -1) and (self._last_char_order != -1): - self._total_rel += 1 - if self._total_rel > self.MAX_REL_THRESHOLD: - self._done = True - break - self._rel_sample[jp2CharContext[self._last_char_order][order]] += 1 - self._last_char_order = order - - def got_enough_data(self): - return self._total_rel > self.ENOUGH_REL_THRESHOLD - - def get_confidence(self): - # This is just one way to calculate confidence. It works well for me. - if self._total_rel > self.MINIMUM_DATA_THRESHOLD: - return (self._total_rel - self._rel_sample[0]) / self._total_rel - else: - return self.DONT_KNOW - - def get_order(self, byte_str): - return -1, 1 - -class SJISContextAnalysis(JapaneseContextAnalysis): - def __init__(self): - super(SJISContextAnalysis, self).__init__() - self._charset_name = "SHIFT_JIS" - - @property - def charset_name(self): - return self._charset_name - - def get_order(self, byte_str): - if not byte_str: - return -1, 1 - # find out current char's byte length - first_char = byte_str[0] - if (0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC): - char_len = 2 - if (first_char == 0x87) or (0xFA <= first_char <= 0xFC): - self._charset_name = "CP932" - else: - char_len = 1 - - # return its order if it is hiragana - if len(byte_str) > 1: - second_char = byte_str[1] - if (first_char == 202) and (0x9F <= second_char <= 0xF1): - return second_char - 0x9F, char_len - - return -1, char_len - -class EUCJPContextAnalysis(JapaneseContextAnalysis): - def get_order(self, byte_str): - if not byte_str: - return -1, 1 - # find out current char's byte length - first_char = byte_str[0] - if (first_char == 0x8E) or (0xA1 <= first_char <= 0xFE): - char_len = 2 - elif first_char == 0x8F: - char_len = 3 - else: - char_len = 1 - - # return its order if it is hiragana - if len(byte_str) > 1: - second_char = byte_str[1] - if (first_char == 0xA4) and (0xA1 <= second_char <= 0xF3): - return second_char - 0xA1, char_len - - return -1, char_len - - diff --git a/lib/chardet/langbulgarianmodel.py b/lib/chardet/langbulgarianmodel.py deleted file mode 100644 index 2aa4fb2..0000000 --- a/lib/chardet/langbulgarianmodel.py +++ /dev/null @@ -1,228 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Character Mapping Table: -# this table is modified base on win1251BulgarianCharToOrderMap, so -# only number <64 is sure valid - -Latin5_BulgarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 -110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 -253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 -116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 -194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209, # 80 -210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225, # 90 - 81,226,227,228,229,230,105,231,232,233,234,235,236, 45,237,238, # a0 - 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # b0 - 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,239, 67,240, 60, 56, # c0 - 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # d0 - 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,241, 42, 16, # e0 - 62,242,243,244, 58,245, 98,246,247,248,249,250,251, 91,252,253, # f0 -) - -win1251BulgarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 77, 90, 99,100, 72,109,107,101, 79,185, 81,102, 76, 94, 82, # 40 -110,186,108, 91, 74,119, 84, 96,111,187,115,253,253,253,253,253, # 50 -253, 65, 69, 70, 66, 63, 68,112,103, 92,194,104, 95, 86, 87, 71, # 60 -116,195, 85, 93, 97,113,196,197,198,199,200,253,253,253,253,253, # 70 -206,207,208,209,210,211,212,213,120,214,215,216,217,218,219,220, # 80 -221, 78, 64, 83,121, 98,117,105,222,223,224,225,226,227,228,229, # 90 - 88,230,231,232,233,122, 89,106,234,235,236,237,238, 45,239,240, # a0 - 73, 80,118,114,241,242,243,244,245, 62, 58,246,247,248,249,250, # b0 - 31, 32, 35, 43, 37, 44, 55, 47, 40, 59, 33, 46, 38, 36, 41, 30, # c0 - 39, 28, 34, 51, 48, 49, 53, 50, 54, 57, 61,251, 67,252, 60, 56, # d0 - 1, 18, 9, 20, 11, 3, 23, 15, 2, 26, 12, 10, 14, 6, 4, 13, # e0 - 7, 8, 5, 19, 29, 25, 22, 21, 27, 24, 17, 75, 52,253, 42, 16, # f0 -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 96.9392% -# first 1024 sequences:3.0618% -# rest sequences: 0.2992% -# negative sequences: 0.0020% -BulgarianLangModel = ( -0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,3,3,3,3,3, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,2,2,1,2,2, -3,1,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,0,1, -0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,3,3,0,3,1,0, -0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,1,3,2,3,3,3,3,3,3,3,3,0,3,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,2,3,2,2,1,3,3,3,3,2,2,2,1,1,2,0,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,2,3,2,2,3,3,1,1,2,3,3,2,3,3,3,3,2,1,2,0,2,0,3,0,0, -0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,1,3,3,3,3,3,2,3,2,3,3,3,3,3,2,3,3,1,3,0,3,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,1,3,3,2,3,3,3,1,3,3,2,3,2,2,2,0,0,2,0,2,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,0,3,3,3,2,2,3,3,3,1,2,2,3,2,1,1,2,0,2,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,2,3,3,1,2,3,2,2,2,3,3,3,3,3,2,2,3,1,2,0,2,1,2,0,0, -0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,1,3,3,3,3,3,2,3,3,3,2,3,3,2,3,2,2,2,3,1,2,0,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,3,3,1,1,1,2,2,1,3,1,3,2,2,3,0,0,1,0,1,0,1,0,0, -0,0,0,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,2,2,3,2,2,3,1,2,1,1,1,2,3,1,3,1,2,2,0,1,1,1,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,1,3,2,2,3,3,1,2,3,1,1,3,3,3,3,1,2,2,1,1,1,0,2,0,2,0,1, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,2,2,3,3,3,2,2,1,1,2,0,2,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,0,1,2,1,3,3,2,3,3,3,3,3,2,3,2,1,0,3,1,2,1,2,1,2,3,2,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,1,2,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,1,3,3,2,3,3,2,2,2,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,0,3,3,3,3,3,2,1,1,2,1,3,3,0,3,1,1,1,1,3,2,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,2,2,2,3,3,3,3,3,3,3,3,3,3,3,1,1,3,1,3,3,2,3,2,2,2,3,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,2,3,3,2,2,3,2,1,1,1,1,1,3,1,3,1,1,0,0,0,1,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,2,3,2,0,3,2,0,3,0,2,0,0,2,1,3,1,0,0,1,0,0,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,2,1,1,1,1,2,1,1,2,1,1,1,2,2,1,2,1,1,1,0,1,1,0,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,2,1,3,1,1,2,1,3,2,1,1,0,1,2,3,2,1,1,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,2,2,1,0,1,0,0,1,0,0,0,2,1,0,3,0,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,2,3,2,3,3,1,3,2,1,1,1,2,1,1,2,1,3,0,1,0,0,0,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,2,2,3,3,2,3,2,2,2,3,1,2,2,1,1,2,1,1,2,2,0,1,1,0,1,0,2,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,1,3,1,0,2,2,1,3,2,1,0,0,2,0,2,0,1,0,0,0,0,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,3,1,2,0,2,3,1,2,3,2,0,1,3,1,2,1,1,1,0,0,1,0,0,2,2,2,3, -2,2,2,2,1,2,1,1,2,2,1,1,2,0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,1, -3,3,3,3,3,2,1,2,2,1,2,0,2,0,1,0,1,2,1,2,1,1,0,0,0,1,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1, -3,3,2,3,3,1,1,3,1,0,3,2,1,0,0,0,1,2,0,2,0,1,0,0,0,1,0,1,2,1,2,2, -1,1,1,1,1,1,1,2,2,2,1,1,1,1,1,1,1,0,1,2,1,1,1,0,0,0,0,0,1,1,0,0, -3,1,0,1,0,2,3,2,2,2,3,2,2,2,2,2,1,0,2,1,2,1,1,1,0,1,2,1,2,2,2,1, -1,1,2,2,2,2,1,2,1,1,0,1,2,1,2,2,2,1,1,1,0,1,1,1,1,2,0,1,0,0,0,0, -2,3,2,3,3,0,0,2,1,0,2,1,0,0,0,0,2,3,0,2,0,0,0,0,0,1,0,0,2,0,1,2, -2,1,2,1,2,2,1,1,1,2,1,1,1,0,1,2,2,1,1,1,1,1,0,1,1,1,0,0,1,2,0,0, -3,3,2,2,3,0,2,3,1,1,2,0,0,0,1,0,0,2,0,2,0,0,0,1,0,1,0,1,2,0,2,2, -1,1,1,1,2,1,0,1,2,2,2,1,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,1,0,0, -2,3,2,3,3,0,0,3,0,1,1,0,1,0,0,0,2,2,1,2,0,0,0,0,0,0,0,0,2,0,1,2, -2,2,1,1,1,1,1,2,2,2,1,0,2,0,1,0,1,0,0,1,0,1,0,0,1,0,0,0,0,1,0,0, -3,3,3,3,2,2,2,2,2,0,2,1,1,1,1,2,1,2,1,1,0,2,0,1,0,1,0,0,2,0,1,2, -1,1,1,1,1,1,1,2,2,1,1,0,2,0,1,0,2,0,0,1,1,1,0,0,2,0,0,0,1,1,0,0, -2,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0,0,0,0,1,2,0,1,2, -2,2,2,1,1,2,1,1,2,2,2,1,2,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,0,0, -2,3,3,3,3,0,2,2,0,2,1,0,0,0,1,1,1,2,0,2,0,0,0,3,0,0,0,0,2,0,2,2, -1,1,1,2,1,2,1,1,2,2,2,1,2,0,1,1,1,0,1,1,1,1,0,2,1,0,0,0,1,1,0,0, -2,3,3,3,3,0,2,1,0,0,2,0,0,0,0,0,1,2,0,2,0,0,0,0,0,0,0,0,2,0,1,2, -1,1,1,2,1,1,1,1,2,2,2,0,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,0,0,1,0,0, -3,3,2,2,3,0,1,0,1,0,0,0,0,0,0,0,1,1,0,3,0,0,0,0,0,0,0,0,1,0,2,2, -1,1,1,1,1,2,1,1,2,2,1,2,2,1,0,1,1,1,1,1,0,1,0,0,1,0,0,0,1,1,0,0, -3,1,0,1,0,2,2,2,2,3,2,1,1,1,2,3,0,0,1,0,2,1,1,0,1,1,1,1,2,1,1,1, -1,2,2,1,2,1,2,2,1,1,0,1,2,1,2,2,1,1,1,0,0,1,1,1,2,1,0,1,0,0,0,0, -2,1,0,1,0,3,1,2,2,2,2,1,2,2,1,1,1,0,2,1,2,2,1,1,2,1,1,0,2,1,1,1, -1,2,2,2,2,2,2,2,1,2,0,1,1,0,2,1,1,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0, -2,1,1,1,1,2,2,2,2,1,2,2,2,1,2,2,1,1,2,1,2,3,2,2,1,1,1,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,3,2,0,1,2,0,1,2,1,1,0,1,0,1,2,1,2,0,0,0,1,1,0,0,0,1,0,0,2, -1,1,0,0,1,1,0,1,1,1,1,0,2,0,1,1,1,0,0,1,1,0,0,0,0,1,0,0,0,1,0,0, -2,0,0,0,0,1,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,2,1,1,1, -1,2,2,2,2,1,1,2,1,2,1,1,1,0,2,1,2,1,1,1,0,2,1,1,1,1,0,1,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, -1,1,0,1,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,3,2,0,0,0,0,1,0,0,0,0,0,0,1,1,0,2,0,0,0,0,0,0,0,0,1,0,1,2, -1,1,1,1,1,1,0,0,2,2,2,2,2,0,1,1,0,1,1,1,1,1,0,0,1,0,0,0,1,1,0,1, -2,3,1,2,1,0,1,1,0,2,2,2,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,1,0,1,2, -1,1,1,1,2,1,1,1,1,1,1,1,1,0,1,1,0,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0, -2,2,2,2,2,0,0,2,0,0,2,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,0,2,2, -1,1,1,1,1,0,0,1,2,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,0,2,0,1,1,0,0,0,1,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,1,1, -0,0,0,1,1,1,1,1,1,1,1,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,3,2,0,0,1,0,0,1,0,0,0,0,0,0,1,0,2,0,0,0,1,0,0,0,0,0,0,0,2, -1,1,0,0,1,0,0,0,1,1,0,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -2,1,2,2,2,1,2,1,2,2,1,1,2,1,1,1,0,1,1,1,1,2,0,1,0,1,1,1,1,0,1,1, -1,1,2,1,1,1,1,1,1,0,0,1,2,1,1,1,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0, -1,0,0,1,3,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,1,0,0,1,0,2,0,0,0,0,0,1,1,1,0,1,0,0,0,0,0,0,0,0,2,0,0,1, -0,2,0,1,0,0,1,1,2,0,1,0,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,1,1,0,2,1,0,1,1,1,0,0,1,0,2,0,1,0,0,0,0,0,0,0,0,0,1, -0,1,0,0,1,0,0,0,1,1,0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,2,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, -0,1,0,1,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -2,0,1,0,0,1,2,1,1,1,1,1,1,2,2,1,0,0,1,0,1,0,0,0,0,1,1,1,1,0,0,0, -1,1,2,1,1,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,1,2,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1, -0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -0,1,1,0,1,1,1,0,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, -1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,2,0,0,2,0,1,0,0,1,0,0,1, -1,1,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0, -1,1,1,1,1,1,1,2,0,0,0,0,0,0,2,1,0,1,1,0,0,1,1,1,0,1,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,1,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -) - -Latin5BulgarianModel = { - 'char_to_order_map': Latin5_BulgarianCharToOrderMap, - 'precedence_matrix': BulgarianLangModel, - 'typical_positive_ratio': 0.969392, - 'keep_english_letter': False, - 'charset_name': "ISO-8859-5", - 'language': 'Bulgairan', -} - -Win1251BulgarianModel = { - 'char_to_order_map': win1251BulgarianCharToOrderMap, - 'precedence_matrix': BulgarianLangModel, - 'typical_positive_ratio': 0.969392, - 'keep_english_letter': False, - 'charset_name': "windows-1251", - 'language': 'Bulgarian', -} diff --git a/lib/chardet/langcyrillicmodel.py b/lib/chardet/langcyrillicmodel.py deleted file mode 100644 index e5f9a1f..0000000 --- a/lib/chardet/langcyrillicmodel.py +++ /dev/null @@ -1,333 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# KOI8-R language model -# Character Mapping Table: -KOI8R_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, # 80 -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, # 90 -223,224,225, 68,226,227,228,229,230,231,232,233,234,235,236,237, # a0 -238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253, # b0 - 27, 3, 21, 28, 13, 2, 39, 19, 26, 4, 23, 11, 8, 12, 5, 1, # c0 - 15, 16, 9, 7, 6, 14, 24, 10, 17, 18, 20, 25, 30, 29, 22, 54, # d0 - 59, 37, 44, 58, 41, 48, 53, 46, 55, 42, 60, 36, 49, 38, 31, 34, # e0 - 35, 43, 45, 32, 40, 52, 56, 33, 61, 62, 51, 57, 47, 63, 50, 70, # f0 -) - -win1251_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, -239,240,241,242,243,244,245,246, 68,247,248,249,250,251,252,253, - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, -) - -latin5_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, -239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, -) - -macCyrillic_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, -239,240,241,242,243,244,245,246,247,248,249,250,251,252, 68, 16, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27,255, -) - -IBM855_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 -191,192,193,194, 68,195,196,197,198,199,200,201,202,203,204,205, -206,207,208,209,210,211,212,213,214,215,216,217, 27, 59, 54, 70, - 3, 37, 21, 44, 28, 58, 13, 41, 2, 48, 39, 53, 19, 46,218,219, -220,221,222,223,224, 26, 55, 4, 42,225,226,227,228, 23, 60,229, -230,231,232,233,234,235, 11, 36,236,237,238,239,240,241,242,243, - 8, 49, 12, 38, 5, 31, 1, 34, 15,244,245,246,247, 35, 16,248, - 43, 9, 45, 7, 32, 6, 40, 14, 52, 24, 56, 10, 33, 17, 61,249, -250, 18, 62, 20, 51, 25, 57, 30, 47, 29, 63, 22, 50,251,252,255, -) - -IBM866_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,142,143,144,145,146,147,148,149,150,151,152, 74,153, 75,154, # 40 -155,156,157,158,159,160,161,162,163,164,165,253,253,253,253,253, # 50 -253, 71,172, 66,173, 65,174, 76,175, 64,176,177, 77, 72,178, 69, # 60 - 67,179, 78, 73,180,181, 79,182,183,184,185,253,253,253,253,253, # 70 - 37, 44, 33, 46, 41, 48, 56, 51, 42, 60, 36, 49, 38, 31, 34, 35, - 45, 32, 40, 52, 53, 55, 58, 50, 57, 63, 70, 62, 61, 47, 59, 43, - 3, 21, 10, 19, 13, 2, 24, 20, 4, 23, 11, 8, 12, 5, 1, 15, -191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206, -207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222, -223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238, - 9, 7, 6, 14, 39, 26, 28, 22, 25, 29, 54, 18, 17, 30, 27, 16, -239, 68,240,241,242,243,244,245,246,247,248,249,250,251,252,255, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 97.6601% -# first 1024 sequences: 2.3389% -# rest sequences: 0.1237% -# negative sequences: 0.0009% -RussianLangModel = ( -0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,1,3,3,3,3,1,3,3,3,2,3,2,3,3, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,2,2,2,2,2,0,0,2, -3,3,3,2,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,2,3,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,2,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,2,3,3,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, -0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,0,0,3,3,3,3,3,3,3,3,3,3,3,2,1, -0,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,2,2,2,3,1,3,3,1,3,3,3,3,2,2,3,0,2,2,2,3,3,2,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,3,3,3,2,2,3,2,3,3,3,2,1,2,2,0,1,2,2,2,2,2,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,3,0,2,2,3,3,2,1,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,1,2,3,2,2,3,2,3,3,3,3,2,2,3,0,3,2,2,3,1,1,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,3,3,3,3,2,2,2,0,3,3,3,2,2,2,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,2,3,2,3,3,3,3,3,3,2,3,2,2,0,1,3,2,1,2,2,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,2,1,1,3,0,1,1,1,1,2,1,1,0,2,2,2,1,2,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,2,2,2,2,1,3,2,3,2,3,2,1,2,2,0,1,1,2,1,2,1,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,2,3,3,3,2,2,2,2,0,2,2,2,2,3,1,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,2,3,2,2,3,3,3,3,3,3,3,3,3,1,3,2,0,0,3,3,3,3,2,3,3,3,3,2,3,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,3,2,2,3,3,0,2,1,0,3,2,3,2,3,0,0,1,2,0,0,1,0,1,2,1,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,3,0,2,3,3,3,3,2,3,3,3,3,1,2,2,0,0,2,3,2,2,2,3,2,3,2,2,3,0,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,0,2,3,2,3,0,1,2,3,3,2,0,2,3,0,0,2,3,2,2,0,1,3,1,3,2,2,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,3,0,2,3,3,3,3,3,3,3,3,2,1,3,2,0,0,2,2,3,3,3,2,3,3,0,2,2,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,2,3,3,2,2,2,3,3,0,0,1,1,1,1,1,2,0,0,1,1,1,1,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,2,3,3,3,3,3,3,3,0,3,2,3,3,2,3,2,0,2,1,0,1,1,0,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,3,2,2,2,2,3,1,3,2,3,1,1,2,1,0,2,2,2,2,1,3,1,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -2,2,3,3,3,3,3,1,2,2,1,3,1,0,3,0,0,3,0,0,0,1,1,0,1,2,1,0,0,0,0,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,2,1,1,3,3,3,2,2,1,2,2,3,1,1,2,0,0,2,2,1,3,0,0,2,1,1,2,1,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,3,3,3,1,2,2,2,1,2,1,3,3,1,1,2,1,2,1,2,2,0,2,0,0,1,1,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,3,2,1,3,2,2,3,2,0,3,2,0,3,0,1,0,1,1,0,0,1,1,1,1,0,1,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,3,3,3,2,2,2,3,3,1,2,1,2,1,0,1,0,1,1,0,1,0,0,2,1,1,1,0,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -3,1,1,2,1,2,3,3,2,2,1,2,2,3,0,2,1,0,0,2,2,3,2,1,2,2,2,2,2,3,1,0, -0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,1,1,0,1,1,2,2,1,1,3,0,0,1,3,1,1,1,0,0,0,1,0,1,1,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,3,3,3,2,0,0,0,2,1,0,1,0,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,1,0,0,2,3,2,2,2,1,2,2,2,1,2,1,0,0,1,1,1,0,2,0,1,1,1,0,0,1,1, -1,0,0,0,0,0,1,2,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,3,0,0,0,0,1,0,0,0,0,3,0,1,2,1,0,0,0,0,0,0,0,1,1,0,0,1,1, -1,0,1,0,1,2,0,0,1,1,2,1,0,1,1,1,1,0,1,1,1,1,0,1,0,0,1,0,0,1,1,0, -2,2,3,2,2,2,3,1,2,2,2,2,2,2,2,2,1,1,1,1,1,1,1,0,1,0,1,1,1,0,2,1, -1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,0,1,1,1,0,1,1,0, -3,3,3,2,2,2,2,3,2,2,1,1,2,2,2,2,1,1,3,1,2,1,2,0,0,1,1,0,1,0,2,1, -1,1,1,1,1,2,1,0,1,1,1,1,0,1,0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,1,1,0, -2,0,0,1,0,3,2,2,2,2,1,2,1,2,1,2,0,0,0,2,1,2,2,1,1,2,2,0,1,1,0,2, -1,1,1,1,1,0,1,1,1,2,1,1,1,2,1,0,1,2,1,1,1,1,0,1,1,1,0,0,1,0,0,1, -1,3,2,2,2,1,1,1,2,3,0,0,0,0,2,0,2,2,1,0,0,0,0,0,0,1,0,0,0,0,1,1, -1,0,1,1,0,1,0,1,1,0,1,1,0,2,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, -2,3,2,3,2,1,2,2,2,2,1,0,0,0,2,0,0,1,1,0,0,0,0,0,0,0,1,1,0,0,2,1, -1,1,2,1,0,2,0,0,1,0,1,0,0,1,0,0,1,1,0,1,1,0,0,0,0,0,1,0,0,0,0,0, -3,0,0,1,0,2,2,2,3,2,2,2,2,2,2,2,0,0,0,2,1,2,1,1,1,2,2,0,0,0,1,2, -1,1,1,1,1,0,1,2,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,1, -2,3,2,3,3,2,0,1,1,1,0,0,1,0,2,0,1,1,3,1,0,0,0,0,0,0,0,1,0,0,2,1, -1,1,1,1,1,1,1,0,1,0,1,1,1,1,0,1,1,1,0,0,1,1,0,1,0,0,0,0,0,0,1,0, -2,3,3,3,3,1,2,2,2,2,0,1,1,0,2,1,1,1,2,1,0,1,1,0,0,1,0,1,0,0,2,0, -0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,3,3,2,0,0,1,1,2,2,1,0,0,2,0,1,1,3,0,0,1,0,0,0,0,0,1,0,1,2,1, -1,1,2,0,1,1,1,0,1,0,1,1,0,1,0,1,1,1,1,0,1,0,0,0,0,0,0,1,0,1,1,0, -1,3,2,3,2,1,0,0,2,2,2,0,1,0,2,0,1,1,1,0,1,0,0,0,3,0,1,1,0,0,2,1, -1,1,1,0,1,1,0,0,0,0,1,1,0,1,0,0,2,1,1,0,1,0,0,0,1,0,1,0,0,1,1,0, -3,1,2,1,1,2,2,2,2,2,2,1,2,2,1,1,0,0,0,2,2,2,0,0,0,1,2,1,0,1,0,1, -2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,2,1,1,1,0,1,0,1,1,0,1,1,1,0,0,1, -3,0,0,0,0,2,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,1,0,1,0,1,1,0,0,1,0,1, -1,1,0,0,1,0,0,0,1,0,1,1,0,0,1,0,1,0,1,0,0,0,0,1,0,0,0,1,0,0,0,1, -1,3,3,2,2,0,0,0,2,2,0,0,0,1,2,0,1,1,2,0,0,0,0,0,0,0,0,1,0,0,2,1, -0,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0, -2,3,2,3,2,0,0,0,0,1,1,0,0,0,2,0,2,0,2,0,0,0,0,0,1,0,0,1,0,0,1,1, -1,1,2,0,1,2,1,0,1,1,2,1,1,1,1,1,2,1,1,0,1,0,0,1,1,1,1,1,0,1,1,0, -1,3,2,2,2,1,0,0,2,2,1,0,1,2,2,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,1,1, -0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,0,2,3,1,2,2,2,2,2,2,1,1,0,0,0,1,0,1,0,2,1,1,1,0,0,0,0,1, -1,1,0,1,1,0,1,1,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -2,0,2,0,0,1,0,3,2,1,2,1,2,2,0,1,0,0,0,2,1,0,0,2,1,1,1,1,0,2,0,2, -2,1,1,1,1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,0,0,0,1,1,1,1,0,1,0,0,1, -1,2,2,2,2,1,0,0,1,0,0,0,0,0,2,0,1,1,1,1,0,0,0,0,1,0,1,2,0,0,2,0, -1,0,1,1,1,2,1,0,1,0,1,1,0,0,1,0,1,1,1,0,1,0,0,0,1,0,0,1,0,1,1,0, -2,1,2,2,2,0,3,0,1,1,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -0,0,0,1,1,1,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0, -1,2,2,3,2,2,0,0,1,1,2,0,1,2,1,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1, -0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0,1,0,0,0,0,0,0,0,0,1,1,0, -2,2,1,1,2,1,2,2,2,2,2,1,2,2,0,1,0,0,0,1,2,2,2,1,2,1,1,1,1,1,2,1, -1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,0,1, -1,2,2,2,2,0,1,0,2,2,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0, -0,0,1,0,0,1,0,0,0,0,1,0,1,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,0,0,2,2,2,0,1,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1, -0,1,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,2,0,0,0,0,1,0,0,1,1,2,0,0,0,0,1,0,1,0,0,1,0,0,2,0,0,0,1, -0,0,1,0,0,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0, -1,2,2,2,1,1,2,0,2,1,1,1,1,0,2,2,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1, -0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -1,0,2,1,2,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0, -0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, -1,0,0,0,0,2,0,1,2,1,0,1,1,1,0,1,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,1, -0,0,0,0,0,1,0,0,1,1,0,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1, -2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,1,1,0,1,0,1,0,0,1,1,1,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,1,0,1,1,0,1,0,1,0,0,0,0,1,1,0,1,1,0,0,0,0,0,1,0,1,1,0,1,0,0,0, -0,1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, -) - -Koi8rModel = { - 'char_to_order_map': KOI8R_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "KOI8-R", - 'language': 'Russian', -} - -Win1251CyrillicModel = { - 'char_to_order_map': win1251_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "windows-1251", - 'language': 'Russian', -} - -Latin5CyrillicModel = { - 'char_to_order_map': latin5_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "ISO-8859-5", - 'language': 'Russian', -} - -MacCyrillicModel = { - 'char_to_order_map': macCyrillic_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "MacCyrillic", - 'language': 'Russian', -} - -Ibm866Model = { - 'char_to_order_map': IBM866_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "IBM866", - 'language': 'Russian', -} - -Ibm855Model = { - 'char_to_order_map': IBM855_char_to_order_map, - 'precedence_matrix': RussianLangModel, - 'typical_positive_ratio': 0.976601, - 'keep_english_letter': False, - 'charset_name': "IBM855", - 'language': 'Russian', -} diff --git a/lib/chardet/langgreekmodel.py b/lib/chardet/langgreekmodel.py deleted file mode 100644 index 5332221..0000000 --- a/lib/chardet/langgreekmodel.py +++ /dev/null @@ -1,225 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Character Mapping Table: -Latin7_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 - 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 -253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 - 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 -253,233, 90,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 -253,253,253,253,247,248, 61, 36, 46, 71, 73,253, 54,253,108,123, # b0 -110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 - 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 -124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 - 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 -) - -win1253_char_to_order_map = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 82,100,104, 94, 98,101,116,102,111,187,117, 92, 88,113, 85, # 40 - 79,118,105, 83, 67,114,119, 95, 99,109,188,253,253,253,253,253, # 50 -253, 72, 70, 80, 81, 60, 96, 93, 89, 68,120, 97, 77, 86, 69, 55, # 60 - 78,115, 65, 66, 58, 76,106,103, 87,107,112,253,253,253,253,253, # 70 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 80 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 90 -253,233, 61,253,253,253,253,253,253,253,253,253,253, 74,253,253, # a0 -253,253,253,253,247,253,253, 36, 46, 71, 73,253, 54,253,108,123, # b0 -110, 31, 51, 43, 41, 34, 91, 40, 52, 47, 44, 53, 38, 49, 59, 39, # c0 - 35, 48,250, 37, 33, 45, 56, 50, 84, 57,120,121, 17, 18, 22, 15, # d0 -124, 1, 29, 20, 21, 3, 32, 13, 25, 5, 11, 16, 10, 6, 30, 4, # e0 - 9, 8, 14, 7, 2, 12, 28, 23, 42, 24, 64, 75, 19, 26, 27,253, # f0 -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 98.2851% -# first 1024 sequences:1.7001% -# rest sequences: 0.0359% -# negative sequences: 0.0148% -GreekLangModel = ( -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,2,2,3,3,3,3,3,3,3,3,1,3,3,3,0,2,2,3,3,0,3,0,3,2,0,3,3,3,0, -3,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,0,3,3,0,3,2,3,3,0,3,2,3,3,3,0,0,3,0,3,0,3,3,2,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -0,2,3,2,2,3,3,3,3,3,3,3,3,0,3,3,3,3,0,2,3,3,0,3,3,3,3,2,3,3,3,0, -2,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,0,2,1,3,3,3,3,2,3,3,2,3,3,2,0, -0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,3,0,3,2,3,3,0, -2,0,1,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,3,0,0,0,0,3,3,0,3,1,3,3,3,0,3,3,0,3,3,3,3,0,0,0,0, -2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,0,3,0,3,3,3,3,3,0,3,2,2,2,3,0,2,3,3,3,3,3,2,3,3,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,3,2,2,2,3,3,3,3,0,3,1,3,3,3,3,2,3,3,3,3,3,3,3,2,2,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,0,3,0,0,0,3,3,2,3,3,3,3,3,0,0,3,2,3,0,2,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,3,0,0,3,3,0,2,3,0,3,0,3,3,3,0,0,3,0,3,0,2,2,3,3,0,0, -0,0,1,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,0,3,2,3,3,3,3,0,3,3,3,3,3,0,3,3,2,3,2,3,3,2,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,2,3,2,3,3,3,3,3,3,0,2,3,2,3,2,2,2,3,2,3,3,2,3,0,2,2,2,3,0, -2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,0,3,3,3,2,3,3,0,0,3,0,3,0,0,0,3,2,0,3,0,3,0,0,2,0,2,0, -0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,0,3,3,3,3,3,3,0,3,3,0,3,0,0,0,3,3,0,3,3,3,0,0,1,2,3,0, -3,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,2,0,0,3,2,2,3,3,0,3,3,3,3,3,2,1,3,0,3,2,3,3,2,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,3,0,2,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,3,0,3,2,3,0,0,3,3,3,0, -3,0,0,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,0,3,3,3,3,3,3,0,0,3,0,3,0,0,0,3,2,0,3,2,3,0,0,3,2,3,0, -2,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,1,2,2,3,3,3,3,3,3,0,2,3,0,3,0,0,0,3,3,0,3,0,2,0,0,2,3,1,0, -2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,3,0,3,0,3,3,2,3,0,3,3,3,3,3,3,0,3,3,3,0,2,3,0,0,3,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,0,0,3,0,0,0,3,3,0,3,0,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,0,3,3,3,3,3,3,0,0,3,0,2,0,0,0,3,3,0,3,0,3,0,0,2,0,2,0, -0,0,0,0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,3,0,3,0,2,0,3,2,0,3,2,3,2,3,0,0,3,2,3,2,3,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,2,3,3,3,3,3,0,0,0,3,0,2,1,0,0,3,2,2,2,0,3,0,0,2,2,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,2,0,3,0,3,0,3,3,0,2,1,2,3,3,0,0,3,0,3,0,3,3,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,3,3,3,0,3,3,3,3,3,3,0,2,3,0,3,0,0,0,2,1,0,2,2,3,0,0,2,2,2,0, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,3,0,0,2,3,3,3,2,3,0,0,1,3,0,2,0,0,0,0,3,0,1,0,2,0,0,1,1,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,3,1,0,3,0,0,0,3,2,0,3,2,3,3,3,0,0,3,0,3,2,2,2,1,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,3,3,3,0,0,3,0,0,0,0,2,0,2,3,3,2,2,2,2,3,0,2,0,2,2,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,3,3,3,2,0,0,0,0,0,0,2,3,0,2,0,2,3,2,0,0,3,0,3,0,3,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,3,2,3,3,2,2,3,0,2,0,3,0,0,0,2,0,0,0,0,1,2,0,2,0,2,0, -0,2,0,2,0,2,2,0,0,1,0,2,2,2,0,2,2,2,0,2,2,2,0,0,2,0,0,1,0,0,0,0, -0,2,0,3,3,2,0,0,0,0,0,0,1,3,0,2,0,2,2,2,0,0,2,0,3,0,0,2,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,0,2,3,2,0,2,2,0,2,0,2,2,0,2,0,2,2,2,0,0,0,0,0,0,2,3,0,0,0,2, -0,1,2,0,0,0,0,2,2,0,0,0,2,1,0,2,2,0,0,0,0,0,0,1,0,2,0,0,0,0,0,0, -0,0,2,1,0,2,3,2,2,3,2,3,2,0,0,3,3,3,0,0,3,2,0,0,0,1,1,0,2,0,2,2, -0,2,0,2,0,2,2,0,0,2,0,2,2,2,0,2,2,2,2,0,0,2,0,0,0,2,0,1,0,0,0,0, -0,3,0,3,3,2,2,0,3,0,0,0,2,2,0,2,2,2,1,2,0,0,1,2,2,0,0,3,0,0,0,2, -0,1,2,0,0,0,1,2,0,0,0,0,0,0,0,2,2,0,1,0,0,2,0,0,0,2,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,3,3,2,2,0,0,0,2,0,2,3,3,0,2,0,0,0,0,0,0,2,2,2,0,2,2,0,2,0,2, -0,2,2,0,0,2,2,2,2,1,0,0,2,2,0,2,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0, -0,2,0,3,2,3,0,0,0,3,0,0,2,2,0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,0,2, -0,0,2,2,0,0,2,2,2,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,2,0,0,3,2,0,2,2,2,2,2,0,0,0,2,0,0,0,0,2,0,1,0,0,2,0,1,0,0,0, -0,2,2,2,0,2,2,0,1,2,0,2,2,2,0,2,2,2,2,1,2,2,0,0,2,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,2,0,2,0,2,2,0,0,0,0,1,2,1,0,0,2,2,0,0,2,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,3,2,3,0,0,2,0,0,0,2,2,0,2,0,0,0,1,0,0,2,0,2,0,2,2,0,0,0,0, -0,0,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0, -0,2,2,3,2,2,0,0,0,0,0,0,1,3,0,2,0,2,2,0,0,0,1,0,2,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,0,2,0,3,2,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -0,0,2,0,0,0,0,1,1,0,0,2,1,2,0,2,2,0,1,0,0,1,0,0,0,2,0,0,0,0,0,0, -0,3,0,2,2,2,0,0,2,0,0,0,2,0,0,0,2,3,0,2,0,0,0,0,0,0,2,2,0,0,0,2, -0,1,2,0,0,0,1,2,2,1,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,1,2,0,2,2,0,2,0,0,2,0,0,0,0,1,2,1,0,2,1,0,0,0,0,0,0,0,0,0,0, -0,0,2,0,0,0,3,1,2,2,0,2,0,0,0,0,2,0,0,0,2,0,0,3,0,0,0,0,2,2,2,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,1,0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,2, -0,2,2,0,0,2,2,2,2,2,0,1,2,0,0,0,2,2,0,1,0,2,0,0,2,2,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,3,0,0,2,0,0,0,0,0,0,0,0,2,0,2,0,0,0,0,2, -0,1,2,0,0,0,0,2,2,1,0,1,0,1,0,2,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,2,0,1,2,0,0,0,0,0,0,0,0,0,0,2,0,0,2,2,0,0,0,0,1,0,0,0,0,0,0,2, -0,2,2,0,0,0,0,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0, -0,2,2,2,2,0,0,0,3,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,1, -0,0,2,0,0,0,0,1,2,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,2,0,2,2,2,0,0,2,0,0,0,0,0,0,0,2,2,2,0,0,0,2,0,0,0,0,0,0,0,0,2, -0,0,1,0,0,0,0,2,1,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -0,3,0,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,2, -0,0,2,0,0,0,0,2,2,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,2,0,2,2,1,0,0,0,0,0,0,2,0,0,2,0,2,2,2,0,0,0,0,0,0,2,0,0,0,0,2, -0,0,2,0,0,2,0,2,2,0,0,0,0,2,0,2,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0, -0,0,3,0,0,0,2,2,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0, -0,2,2,2,2,2,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1, -0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,2,2,0,0,0,0,0,2,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,2,0,0,0,2,0,0,0,0,0,1,0,0,0,0,2,2,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,2,0,0,0, -0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,2,0,2,0,0,0, -0,0,0,0,0,0,0,0,2,1,0,0,0,0,0,0,2,0,0,0,1,2,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -) - -Latin7GreekModel = { - 'char_to_order_map': Latin7_char_to_order_map, - 'precedence_matrix': GreekLangModel, - 'typical_positive_ratio': 0.982851, - 'keep_english_letter': False, - 'charset_name': "ISO-8859-7", - 'language': 'Greek', -} - -Win1253GreekModel = { - 'char_to_order_map': win1253_char_to_order_map, - 'precedence_matrix': GreekLangModel, - 'typical_positive_ratio': 0.982851, - 'keep_english_letter': False, - 'charset_name': "windows-1253", - 'language': 'Greek', -} diff --git a/lib/chardet/langhebrewmodel.py b/lib/chardet/langhebrewmodel.py deleted file mode 100644 index 58f4c87..0000000 --- a/lib/chardet/langhebrewmodel.py +++ /dev/null @@ -1,200 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Simon Montagu -# Portions created by the Initial Developer are Copyright (C) 2005 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# Shoshannah Forbes - original C code (?) -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Windows-1255 language model -# Character Mapping Table: -WIN1255_CHAR_TO_ORDER_MAP = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 69, 91, 79, 80, 92, 89, 97, 90, 68,111,112, 82, 73, 95, 85, # 40 - 78,121, 86, 71, 67,102,107, 84,114,103,115,253,253,253,253,253, # 50 -253, 50, 74, 60, 61, 42, 76, 70, 64, 53,105, 93, 56, 65, 54, 49, # 60 - 66,110, 51, 43, 44, 63, 81, 77, 98, 75,108,253,253,253,253,253, # 70 -124,202,203,204,205, 40, 58,206,207,208,209,210,211,212,213,214, -215, 83, 52, 47, 46, 72, 32, 94,216,113,217,109,218,219,220,221, - 34,116,222,118,100,223,224,117,119,104,125,225,226, 87, 99,227, -106,122,123,228, 55,229,230,101,231,232,120,233, 48, 39, 57,234, - 30, 59, 41, 88, 33, 37, 36, 31, 29, 35,235, 62, 28,236,126,237, -238, 38, 45,239,240,241,242,243,127,244,245,246,247,248,249,250, - 9, 8, 20, 16, 3, 2, 24, 14, 22, 1, 25, 15, 4, 11, 6, 23, - 12, 19, 13, 26, 18, 27, 21, 17, 7, 10, 5,251,252,128, 96,253, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 98.4004% -# first 1024 sequences: 1.5981% -# rest sequences: 0.087% -# negative sequences: 0.0015% -HEBREW_LANG_MODEL = ( -0,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,3,2,1,2,0,1,0,0, -3,0,3,1,0,0,1,3,2,0,1,1,2,0,2,2,2,1,1,1,1,2,1,1,1,2,0,0,2,2,0,1, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2, -1,2,1,2,1,2,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2, -1,2,1,3,1,1,0,0,2,0,0,0,1,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,1,2,2,1,3, -1,2,1,1,2,2,0,0,2,2,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,2,2,2,3,2, -1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,2,3,2,2,3,2,2,2,1,2,2,2,2, -1,2,1,1,2,2,0,1,2,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,0,2,2,2,2,2, -0,2,0,2,2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,0,2,2,2, -0,2,1,2,2,2,0,0,2,1,0,0,0,0,1,0,1,0,0,0,0,0,0,2,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,2,1,2,3,2,2,2, -1,2,1,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,3,3,1,0,2,0,2, -0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,2,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,2,3,2,2,3,2,1,2,1,1,1, -0,1,1,1,1,1,3,0,1,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,0,1,0,0,1,0,0,0,0, -0,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2, -0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,2,1,2,3,3,2,3,3,3,3,2,3,2,1,2,0,2,1,2, -0,2,0,2,2,2,0,0,1,2,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,1,2,2,3,3,2,3,2,3,2,2,3,1,2,2,0,2,2,2, -0,2,1,2,2,2,0,0,1,2,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,1,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,2,2,3,3,3,3,1,3,2,2,2, -0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,2,3,2,2,2,1,2,2,0,2,2,2,2, -0,2,0,2,2,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,1,3,2,3,3,2,3,3,2,2,1,2,2,2,2,2,2, -0,2,1,2,1,2,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,2,3,3,2,3,3,3,3,2,3,2,3,3,3,3,3,2,2,2,2,2,2,2,1, -0,2,0,1,2,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,2,1,2,3,3,3,3,3,3,3,2,3,2,3,2,1,2,3,0,2,1,2,2, -0,2,1,1,2,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,2,0, -3,3,3,3,3,3,3,3,3,2,3,3,3,3,2,1,3,1,2,2,2,1,2,3,3,1,2,1,2,2,2,2, -0,1,1,1,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,0,2,3,3,3,1,3,3,3,1,2,2,2,2,1,1,2,2,2,2,2,2, -0,2,0,1,1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,3,3,2,2,3,3,3,2,1,2,3,2,3,2,2,2,2,1,2,1,1,1,2,2, -0,2,1,1,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,0,0, -1,0,1,0,0,0,0,0,2,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,2,3,3,2,3,1,2,2,2,2,3,2,3,1,1,2,2,1,2,2,1,1,0,2,2,2,2, -0,1,0,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0, -3,0,0,1,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,0, -0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -3,2,2,1,2,2,2,2,2,2,2,1,2,2,1,2,2,1,1,1,1,1,1,1,1,2,1,1,0,3,3,3, -0,3,0,2,2,2,2,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -2,2,2,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,2,1,2,2,2,1,1,1,2,0,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,2,2,2,2,2,2,0,2,2,0,0,0,0,0,0, -0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1,2,1,0,2,1,0, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -0,3,1,1,2,2,2,2,2,1,2,2,2,1,1,2,2,2,2,2,2,2,1,2,2,1,0,1,1,1,1,0, -0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,2,1,1,1,1,2,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, -0,0,2,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,1,0,0, -2,1,1,2,2,2,2,2,2,2,2,2,2,2,1,2,2,2,2,2,1,2,1,2,1,1,1,1,0,0,0,0, -0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,2,1,2,2,2,2,2,2,2,2,2,2,1,2,1,2,1,1,2,1,1,1,2,1,2,1,2,0,1,0,1, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,3,1,2,2,2,1,2,2,2,2,2,2,2,2,1,2,1,1,1,1,1,1,2,1,2,1,1,0,1,0,1, -0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2, -0,2,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,1,1,1,1,1,1,0,1,1,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,2,0,1,1,1,0,1,0,0,0,1,1,0,1,1,0,0,0,0,0,1,1,0,0, -0,1,1,1,2,1,2,2,2,0,2,0,2,0,1,1,2,1,1,1,1,2,1,0,1,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,1,0,0,0,0,0,1,0,1,2,2,0,1,0,0,1,1,2,2,1,2,0,2,0,0,0,1,2,0,1, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,2,0,2,1,2,0,2,0,0,1,1,1,1,1,1,0,1,0,0,0,1,0,0,1, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,1,2,2,0,0,1,0,0,0,1,0,0,1, -1,1,2,1,0,1,1,1,0,1,0,1,1,1,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,2,2,1, -0,2,0,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,1,0,0,1,0,1,1,1,1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,1,1,1,1,1,1,1,2,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,1,1,0,1,0,0,0,1,1,0,1, -2,0,1,0,1,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,1,0,1,0,0,1,1,2,1,1,2,0,1,0,0,0,1,1,0,1, -1,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,0,0,2,1,1,2,0,2,0,0,0,1,1,0,1, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,2,2,1,2,1,1,0,1,0,0,0,1,1,0,1, -2,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,1, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,2,1,1,1,0,2,1,1,0,0,0,2,1,0,1, -1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,0,2,1,1,0,1,0,0,0,1,1,0,1, -2,2,1,1,1,0,1,1,0,1,1,0,1,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,2,1,1,0,1,0,0,1,1,0,1,2,1,0,2,0,0,0,1,1,0,1, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0, -0,1,0,0,2,0,2,1,1,0,1,0,1,0,0,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,1,0,1,1,2,0,1,0,0,1,1,1,0,1,0,0,1,0,0,0,1,0,0,1, -1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,0,0,0,0,0,1,0,1,1,0,0,1,0,0,2,1,1,1,1,1,0,1,0,0,0,0,1,0,1, -0,1,1,1,2,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,1,1,1,1,1,0,1,0,0,0,1,1,0,0, -) - -Win1255HebrewModel = { - 'char_to_order_map': WIN1255_CHAR_TO_ORDER_MAP, - 'precedence_matrix': HEBREW_LANG_MODEL, - 'typical_positive_ratio': 0.984004, - 'keep_english_letter': False, - 'charset_name': "windows-1255", - 'language': 'Hebrew', -} diff --git a/lib/chardet/langhungarianmodel.py b/lib/chardet/langhungarianmodel.py deleted file mode 100644 index bb7c095..0000000 --- a/lib/chardet/langhungarianmodel.py +++ /dev/null @@ -1,225 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Character Mapping Table: -Latin2_HungarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, - 46, 71, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, -253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, - 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, -159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174, -175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190, -191,192,193,194,195,196,197, 75,198,199,200,201,202,203,204,205, - 79,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, -221, 51, 81,222, 78,223,224,225,226, 44,227,228,229, 61,230,231, -232,233,234, 58,235, 66, 59,236,237,238, 60, 69, 63,239,240,241, - 82, 14, 74,242, 70, 80,243, 72,244, 15, 83, 77, 84, 30, 76, 85, -245,246,247, 25, 73, 42, 24,248,249,250, 31, 56, 29,251,252,253, -) - -win1250HungarianCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253, 28, 40, 54, 45, 32, 50, 49, 38, 39, 53, 36, 41, 34, 35, 47, - 46, 72, 43, 33, 37, 57, 48, 64, 68, 55, 52,253,253,253,253,253, -253, 2, 18, 26, 17, 1, 27, 12, 20, 9, 22, 7, 6, 13, 4, 8, - 23, 67, 10, 5, 3, 21, 19, 65, 62, 16, 11,253,253,253,253,253, -161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176, -177,178,179,180, 78,181, 69,182,183,184,185,186,187,188,189,190, -191,192,193,194,195,196,197, 76,198,199,200,201,202,203,204,205, - 81,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220, -221, 51, 83,222, 80,223,224,225,226, 44,227,228,229, 61,230,231, -232,233,234, 58,235, 66, 59,236,237,238, 60, 70, 63,239,240,241, - 84, 14, 75,242, 71, 82,243, 73,244, 15, 85, 79, 86, 30, 77, 87, -245,246,247, 25, 74, 42, 24,248,249,250, 31, 56, 29,251,252,253, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 94.7368% -# first 1024 sequences:5.2623% -# rest sequences: 0.8894% -# negative sequences: 0.0009% -HungarianLangModel = ( -0,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,1,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, -3,3,3,3,3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,2,2,3,3,1,1,2,2,2,2,2,1,2, -3,2,2,3,3,3,3,3,2,3,3,3,3,3,3,1,2,3,3,3,3,2,3,3,1,1,3,3,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0, -3,2,1,3,3,3,3,3,2,3,3,3,3,3,1,1,2,3,3,3,3,3,3,3,1,1,3,2,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,1,1,2,3,3,3,1,3,3,3,3,3,1,3,3,2,2,0,3,2,3, -0,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,3,3,2,3,3,2,2,3,2,3,2,0,3,2,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,3,3,3,3,2,3,3,3,1,2,3,2,2,3,1,2,3,3,2,2,0,3,3,3, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,3,2,3,3,3,3,2,3,3,3,3,0,2,3,2, -0,0,0,1,1,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,1,1,1,3,3,2,1,3,2,2,3,2,1,3,2,2,1,0,3,3,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,2,2,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,3,2,2,3,1,1,3,2,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,2,2,3,3,3,3,3,2,1,3,3,3,3,3,2,2,1,3,3,3,0,1,1,2, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,2,3,3,3,2,3,3,2,3,3,3,2,0,3,2,3, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,1,0, -3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,1,3,2,2,2,3,1,1,3,3,1,1,0,3,3,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,2,3,3,3,2,3,2,3,3,3,2,3,3,3,3,3,1,2,3,2,2,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,2,2,2,3,1,3,3,2,2,1,3,3,3,1,1,3,1,2,3,2,3,2,2,2,1,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,2,1,3,3,3,2,2,3,2,1,0,3,2,0,1,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,3,3,3,3,3,1,2,3,3,3,3,1,1,0,3,3,3,3,0,2,3,0,0,2,1,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,2,2,3,3,2,2,2,2,3,3,0,1,2,3,2,3,2,2,3,2,1,2,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -3,3,3,3,3,3,1,2,3,3,3,2,1,2,3,3,2,2,2,3,2,3,3,1,3,3,1,1,0,2,3,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,1,2,2,2,2,3,3,3,1,1,1,3,3,1,1,3,1,1,3,2,1,2,3,1,1,0,2,2,2, -0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,2,1,2,1,1,3,3,1,1,1,1,3,3,1,1,2,2,1,2,1,1,2,2,1,1,0,2,2,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,1,1,2,1,1,3,3,1,0,1,1,3,3,2,0,1,1,2,3,1,0,2,2,1,0,0,1,3,2, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,2,1,3,3,3,3,3,1,2,3,2,3,3,2,1,1,3,2,3,2,1,2,2,0,1,2,1,0,0,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,3,3,2,2,2,2,3,1,2,2,1,1,3,3,0,3,2,1,2,3,2,1,3,3,1,1,0,2,1,3, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,3,3,2,2,2,3,2,3,3,3,2,1,1,3,3,1,1,1,2,2,3,2,3,2,2,2,1,0,2,2,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -1,0,0,3,3,3,3,3,0,0,3,3,2,3,0,0,0,2,3,3,1,0,1,2,0,0,1,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,2,3,3,3,3,3,1,2,3,3,2,2,1,1,0,3,3,2,2,1,2,2,1,0,2,2,0,1,1,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,2,1,3,1,2,3,3,2,2,1,1,2,2,1,1,1,1,3,2,1,1,1,1,2,1,0,1,2,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -2,3,3,1,1,1,1,1,3,3,3,0,1,1,3,3,1,1,1,1,1,2,2,0,3,1,1,2,0,2,1,1, -0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, -3,1,0,1,2,1,2,2,0,1,2,3,1,2,0,0,0,2,1,1,1,1,1,2,0,0,1,1,0,0,0,0, -1,2,1,2,2,2,1,2,1,2,0,2,0,2,2,1,1,2,1,1,2,1,1,1,0,1,0,0,0,1,1,0, -1,1,1,2,3,2,3,3,0,1,2,2,3,1,0,1,0,2,1,2,2,0,1,1,0,0,1,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,3,3,2,2,1,0,0,3,2,3,2,0,0,0,1,1,3,0,0,1,1,0,0,2,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,2,2,3,3,1,0,1,3,2,3,1,1,1,0,1,1,1,1,1,3,1,0,0,2,2,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,1,1,2,2,2,1,0,1,2,3,3,2,0,0,0,2,1,1,1,2,1,1,1,0,1,1,1,0,0,0, -1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,2,1,1,1,1,1,1,0,1,1,1,0,0,1,1, -3,2,2,1,0,0,1,1,2,2,0,3,0,1,2,1,1,0,0,1,1,1,0,1,1,1,1,0,2,1,1,1, -2,2,1,1,1,2,1,2,1,1,1,1,1,1,1,2,1,1,1,2,3,1,1,1,1,1,1,1,1,1,0,1, -2,3,3,0,1,0,0,0,3,3,1,0,0,1,2,2,1,0,0,0,0,2,0,0,1,1,1,0,2,1,1,1, -2,1,1,1,1,1,1,2,1,1,0,1,1,0,1,1,1,0,1,2,1,1,0,1,1,1,1,1,1,1,0,1, -2,3,3,0,1,0,0,0,2,2,0,0,0,0,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,1,0, -2,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, -3,2,2,0,1,0,1,0,2,3,2,0,0,1,2,2,1,0,0,1,1,1,0,0,2,1,0,1,2,2,1,1, -2,1,1,1,1,1,1,2,1,1,1,1,1,1,0,2,1,0,1,1,0,1,1,1,0,1,1,2,1,1,0,1, -2,2,2,0,0,1,0,0,2,2,1,1,0,0,2,1,1,0,0,0,1,2,0,0,2,1,0,0,2,1,1,1, -2,1,1,1,1,2,1,2,1,1,1,2,2,1,1,2,1,1,1,2,1,1,1,1,1,1,1,1,1,1,0,1, -1,2,3,0,0,0,1,0,3,2,1,0,0,1,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,2,1, -1,1,0,0,0,1,0,1,1,1,1,1,2,0,0,1,0,0,0,2,0,0,1,1,1,1,1,1,1,1,0,1, -3,0,0,2,1,2,2,1,0,0,2,1,2,2,0,0,0,2,1,1,1,0,1,1,0,0,1,1,2,0,0,0, -1,2,1,2,2,1,1,2,1,2,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,0,0,1, -1,3,2,0,0,0,1,0,2,2,2,0,0,0,2,2,1,0,0,0,0,3,1,1,1,1,0,0,2,1,1,1, -2,1,0,1,1,1,0,1,1,1,1,1,1,1,0,2,1,0,0,1,0,1,1,0,1,1,1,1,1,1,0,1, -2,3,2,0,0,0,1,0,2,2,0,0,0,0,2,1,1,0,0,0,0,2,1,0,1,1,0,0,2,1,1,0, -2,1,1,1,1,2,1,2,1,2,0,1,1,1,0,2,1,1,1,2,1,1,1,1,0,1,1,1,1,1,0,1, -3,1,1,2,2,2,3,2,1,1,2,2,1,1,0,1,0,2,2,1,1,1,1,1,0,0,1,1,0,1,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,0,0,0,0,0,2,2,0,0,0,0,2,2,1,0,0,0,1,1,0,0,1,2,0,0,2,1,1,1, -2,2,1,1,1,2,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,1,1,0,1,2,1,1,1,0,1, -1,0,0,1,2,3,2,1,0,0,2,0,1,1,0,0,0,1,1,1,1,0,1,1,0,0,1,0,0,0,0,0, -1,2,1,2,1,2,1,1,1,2,0,2,1,1,1,0,1,2,0,0,1,1,1,0,0,0,0,0,0,0,0,0, -2,3,2,0,0,0,0,0,1,1,2,1,0,0,1,1,1,0,0,0,0,2,0,0,1,1,0,0,2,1,1,1, -2,1,1,1,1,1,1,2,1,0,1,1,1,1,0,2,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1, -1,2,2,0,1,1,1,0,2,2,2,0,0,0,3,2,1,0,0,0,1,1,0,0,1,1,0,1,1,1,0,0, -1,1,0,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,0,0,1,1,1,0,1,0,1, -2,1,0,2,1,1,2,2,1,1,2,1,1,1,0,0,0,1,1,0,1,1,1,1,0,0,1,1,1,0,0,0, -1,2,2,2,2,2,1,1,1,2,0,2,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,0,0,1,0, -1,2,3,0,0,0,1,0,2,2,0,0,0,0,2,2,0,0,0,0,0,1,0,0,1,0,0,0,2,0,1,0, -2,1,1,1,1,1,0,2,0,0,0,1,2,1,1,1,1,0,1,2,0,1,0,1,0,1,1,1,0,1,0,1, -2,2,2,0,0,0,1,0,2,1,2,0,0,0,1,1,2,0,0,0,0,1,0,0,1,1,0,0,2,1,0,1, -2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,0,1,1,1,1,1,0,1, -1,2,2,0,0,0,1,0,2,2,2,0,0,0,1,1,0,0,0,0,0,1,1,0,2,0,0,1,1,1,0,1, -1,0,1,1,1,1,1,1,0,1,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1,1,1,0,0,0,1, -1,0,0,1,0,1,2,1,0,0,1,1,1,2,0,0,0,1,1,0,1,0,1,1,0,0,1,0,0,0,0,0, -0,2,1,2,1,1,1,1,1,2,0,2,0,1,1,0,1,2,1,0,1,1,1,0,0,0,0,0,0,1,0,0, -2,1,1,0,1,2,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,2,1,0,1, -2,2,1,1,1,1,1,2,1,1,0,1,1,1,1,2,1,1,1,2,1,1,0,1,0,1,1,1,1,1,0,1, -1,2,2,0,0,0,0,0,1,1,0,0,0,0,2,1,0,0,0,0,0,2,0,0,2,2,0,0,2,0,0,1, -2,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,0,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1, -1,1,2,0,0,3,1,0,2,1,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,1,0,0,1,0,1,0, -1,2,1,0,1,1,1,2,1,1,0,1,1,1,1,1,0,0,0,1,1,1,1,1,0,1,0,0,0,1,0,0, -2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,2,0,0,0, -2,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,2,1,1,0,0,1,1,1,1,1,0,1, -2,1,1,1,2,1,1,1,0,1,1,2,1,0,0,0,0,1,1,1,1,0,1,0,0,0,0,1,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,0,1,1,1,1,1,0,0,1,1,2,1,0,0,0,1,1,0,0,0,1,1,0,0,1,0,1,0,0,0, -1,2,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1,1,1,0,0,0,0,0,0,1,0,0, -2,0,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,1,1,1,2,0,0,1,0,0,1,0,1,0,0,0, -0,1,1,1,1,1,1,1,1,2,0,1,1,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,1,1,0,0,2,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,0,0, -0,1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,0,0,0,0,1,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -0,1,1,1,1,1,0,0,1,1,0,1,0,1,0,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, -0,0,0,1,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,1,1,1,0,1,0,0,1,1,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0, -2,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,0,0,1,0,0,1,0,1,0,1,1,1,0,0,1,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,0,1,1,1,1,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, -0,1,1,1,1,1,1,0,1,1,0,1,0,1,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0, -) - -Latin2HungarianModel = { - 'char_to_order_map': Latin2_HungarianCharToOrderMap, - 'precedence_matrix': HungarianLangModel, - 'typical_positive_ratio': 0.947368, - 'keep_english_letter': True, - 'charset_name': "ISO-8859-2", - 'language': 'Hungarian', -} - -Win1250HungarianModel = { - 'char_to_order_map': win1250HungarianCharToOrderMap, - 'precedence_matrix': HungarianLangModel, - 'typical_positive_ratio': 0.947368, - 'keep_english_letter': True, - 'charset_name': "windows-1250", - 'language': 'Hungarian', -} diff --git a/lib/chardet/langthaimodel.py b/lib/chardet/langthaimodel.py deleted file mode 100644 index 15f94c2..0000000 --- a/lib/chardet/langthaimodel.py +++ /dev/null @@ -1,199 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# The following result for thai was collected from a limited sample (1M). - -# Character Mapping Table: -TIS620CharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,254,255,255,254,255,255, # 00 -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, # 10 -253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253, # 20 -252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253, # 30 -253,182,106,107,100,183,184,185,101, 94,186,187,108,109,110,111, # 40 -188,189,190, 89, 95,112,113,191,192,193,194,253,253,253,253,253, # 50 -253, 64, 72, 73,114, 74,115,116,102, 81,201,117, 90,103, 78, 82, # 60 - 96,202, 91, 79, 84,104,105, 97, 98, 92,203,253,253,253,253,253, # 70 -209,210,211,212,213, 88,214,215,216,217,218,219,220,118,221,222, -223,224, 99, 85, 83,225,226,227,228,229,230,231,232,233,234,235, -236, 5, 30,237, 24,238, 75, 8, 26, 52, 34, 51,119, 47, 58, 57, - 49, 53, 55, 43, 20, 19, 44, 14, 48, 3, 17, 25, 39, 62, 31, 54, - 45, 9, 16, 2, 61, 15,239, 12, 42, 46, 18, 21, 76, 4, 66, 63, - 22, 10, 1, 36, 23, 13, 40, 27, 32, 35, 86,240,241,242,243,244, - 11, 28, 41, 29, 33,245, 50, 37, 6, 7, 67, 77, 38, 93,246,247, - 68, 56, 59, 65, 69, 60, 70, 80, 71, 87,248,249,250,251,252,253, -) - -# Model Table: -# total sequences: 100% -# first 512 sequences: 92.6386% -# first 1024 sequences:7.3177% -# rest sequences: 1.0230% -# negative sequences: 0.0436% -ThaiLangModel = ( -0,1,3,3,3,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,0,0,3,3,3,0,3,3,3,3, -0,3,3,0,0,0,1,3,0,3,3,2,3,3,0,1,2,3,3,3,3,0,2,0,2,0,0,3,2,1,2,2, -3,0,3,3,2,3,0,0,3,3,0,3,3,0,3,3,3,3,3,3,3,3,3,0,3,2,3,0,2,2,2,3, -0,2,3,0,0,0,0,1,0,1,2,3,1,1,3,2,2,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1, -3,3,3,2,3,3,3,3,3,3,3,3,3,3,3,2,2,2,2,2,2,2,3,3,2,3,2,3,3,2,2,2, -3,1,2,3,0,3,3,2,2,1,2,3,3,1,2,0,1,3,0,1,0,0,1,0,0,0,0,0,0,0,1,1, -3,3,2,2,3,3,3,3,1,2,3,3,3,3,3,2,2,2,2,3,3,2,2,3,3,2,2,3,2,3,2,2, -3,3,1,2,3,1,2,2,3,3,1,0,2,1,0,0,3,1,2,1,0,0,1,0,0,0,0,0,0,1,0,1, -3,3,3,3,3,3,2,2,3,3,3,3,2,3,2,2,3,3,2,2,3,2,2,2,2,1,1,3,1,2,1,1, -3,2,1,0,2,1,0,1,0,1,1,0,1,1,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0, -3,3,3,2,3,2,3,3,2,2,3,2,3,3,2,3,1,1,2,3,2,2,2,3,2,2,2,2,2,1,2,1, -2,2,1,1,3,3,2,1,0,1,2,2,0,1,3,0,0,0,1,1,0,0,0,0,0,2,3,0,0,2,1,1, -3,3,2,3,3,2,0,0,3,3,0,3,3,0,2,2,3,1,2,2,1,1,1,0,2,2,2,0,2,2,1,1, -0,2,1,0,2,0,0,2,0,1,0,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,0, -3,3,2,3,3,2,0,0,3,3,0,2,3,0,2,1,2,2,2,2,1,2,0,0,2,2,2,0,2,2,1,1, -0,2,1,0,2,0,0,2,0,1,1,0,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0, -3,3,2,3,2,3,2,0,2,2,1,3,2,1,3,2,1,2,3,2,2,3,0,2,3,2,2,1,2,2,2,2, -1,2,2,0,0,0,0,2,0,1,2,0,1,1,1,0,1,0,3,1,1,0,0,0,0,0,0,0,0,0,1,0, -3,3,2,3,3,2,3,2,2,2,3,2,2,3,2,2,1,2,3,2,2,3,1,3,2,2,2,3,2,2,2,3, -3,2,1,3,0,1,1,1,0,2,1,1,1,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,0,2,0,0, -1,0,0,3,0,3,3,3,3,3,0,0,3,0,2,2,3,3,3,3,3,0,0,0,1,1,3,0,0,0,0,2, -0,0,1,0,0,0,0,0,0,0,2,3,0,0,0,3,0,2,0,0,0,0,0,3,0,0,0,0,0,0,0,0, -2,0,3,3,3,3,0,0,2,3,0,0,3,0,3,3,2,3,3,3,3,3,0,0,3,3,3,0,0,0,3,3, -0,0,3,0,0,0,0,2,0,0,2,1,1,3,0,0,1,0,0,2,3,0,1,0,0,0,0,0,0,0,1,0, -3,3,3,3,2,3,3,3,3,3,3,3,1,2,1,3,3,2,2,1,2,2,2,3,1,1,2,0,2,1,2,1, -2,2,1,0,0,0,1,1,0,1,0,1,1,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0, -3,0,2,1,2,3,3,3,0,2,0,2,2,0,2,1,3,2,2,1,2,1,0,0,2,2,1,0,2,1,2,2, -0,1,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,1,3,3,1,1,3,0,2,3,1,1,3,2,1,1,2,0,2,2,3,2,1,1,1,1,1,2, -3,0,0,1,3,1,2,1,2,0,3,0,0,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, -3,3,1,1,3,2,3,3,3,1,3,2,1,3,2,1,3,2,2,2,2,1,3,3,1,2,1,3,1,2,3,0, -2,1,1,3,2,2,2,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2, -3,3,2,3,2,3,3,2,3,2,3,2,3,3,2,1,0,3,2,2,2,1,2,2,2,1,2,2,1,2,1,1, -2,2,2,3,0,1,3,1,1,1,1,0,1,1,0,2,1,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,3,2,2,1,1,3,2,3,2,3,2,0,3,2,2,1,2,0,2,2,2,1,2,2,2,2,1, -3,2,1,2,2,1,0,2,0,1,0,0,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,1, -3,3,3,3,3,2,3,1,2,3,3,2,2,3,0,1,1,2,0,3,3,2,2,3,0,1,1,3,0,0,0,0, -3,1,0,3,3,0,2,0,2,1,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,3,2,3,3,0,1,3,1,1,2,1,2,1,1,3,1,1,0,2,3,1,1,1,1,1,1,1,1, -3,1,1,2,2,2,2,1,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,2,2,1,1,2,1,3,3,2,3,2,2,3,2,2,3,1,2,2,1,2,0,3,2,1,2,2,2,2,2,1, -3,2,1,2,2,2,1,1,1,1,0,0,1,1,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,1,3,3,0,2,1,0,3,2,0,0,3,1,0,1,1,0,1,0,0,0,0,0,1, -1,0,0,1,0,3,2,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,2,2,2,3,0,0,1,3,0,3,2,0,3,2,2,3,3,3,3,3,1,0,2,2,2,0,2,2,1,2, -0,2,3,0,0,0,0,1,0,1,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -3,0,2,3,1,3,3,2,3,3,0,3,3,0,3,2,2,3,2,3,3,3,0,0,2,2,3,0,1,1,1,3, -0,0,3,0,0,0,2,2,0,1,3,0,1,2,2,2,3,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1, -3,2,3,3,2,0,3,3,2,2,3,1,3,2,1,3,2,0,1,2,2,0,2,3,2,1,0,3,0,0,0,0, -3,0,0,2,3,1,3,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,1,3,2,2,2,1,2,0,1,3,1,1,3,1,3,0,0,2,1,1,1,1,2,1,1,1,0,2,1,0,1, -1,2,0,0,0,3,1,1,0,0,0,0,1,0,1,0,0,1,0,1,0,0,0,0,0,3,1,0,0,0,1,0, -3,3,3,3,2,2,2,2,2,1,3,1,1,1,2,0,1,1,2,1,2,1,3,2,0,0,3,1,1,1,1,1, -3,1,0,2,3,0,0,0,3,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,2,3,0,3,3,0,2,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,2,3,1,3,0,0,1,2,0,0,2,0,3,3,2,3,3,3,2,3,0,0,2,2,2,0,0,0,2,2, -0,0,1,0,0,0,0,3,0,0,0,0,2,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -0,0,0,3,0,2,0,0,0,0,0,0,0,0,0,0,1,2,3,1,3,3,0,0,1,0,3,0,0,0,0,0, -0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,1,2,3,1,2,3,1,0,3,0,2,2,1,0,2,1,1,2,0,1,0,0,1,1,1,1,0,1,0,0, -1,0,0,0,0,1,1,0,3,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,2,1,0,1,1,1,3,1,2,2,2,2,2,2,1,1,1,1,0,3,1,0,1,3,1,1,1,1, -1,1,0,2,0,1,3,1,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,1, -3,0,2,2,1,3,3,2,3,3,0,1,1,0,2,2,1,2,1,3,3,1,0,0,3,2,0,0,0,0,2,1, -0,1,0,0,0,0,1,2,0,1,1,3,1,1,2,2,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0, -0,0,3,0,0,1,0,0,0,3,0,0,3,0,3,1,0,1,1,1,3,2,0,0,0,3,0,0,0,0,2,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, -3,3,1,3,2,1,3,3,1,2,2,0,1,2,1,0,1,2,0,0,0,0,0,3,0,0,0,3,0,0,0,0, -3,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,1,2,0,3,3,3,2,2,0,1,1,0,1,3,0,0,0,2,2,0,0,0,0,3,1,0,1,0,0,0, -0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,2,3,1,2,0,0,2,1,0,3,1,0,1,2,0,1,1,1,1,3,0,0,3,1,1,0,2,2,1,1, -0,2,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,3,1,2,0,0,2,2,0,1,2,0,1,0,1,3,1,2,1,0,0,0,2,0,3,0,0,0,1,0, -0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,1,1,2,2,0,0,0,2,0,2,1,0,1,1,0,1,1,1,2,1,0,0,1,1,1,0,2,1,1,1, -0,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1, -0,0,0,2,0,1,3,1,1,1,1,0,0,0,0,3,2,0,1,0,0,0,1,2,0,0,0,1,0,0,0,0, -0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,0,2,3,2,2,0,0,0,1,0,0,0,0,2,3,2,1,2,2,3,0,0,0,2,3,1,0,0,0,1,1, -0,0,1,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0, -3,3,2,2,0,1,0,0,0,0,2,0,2,0,1,0,0,0,1,1,0,0,0,2,1,0,1,0,1,1,0,0, -0,1,0,2,0,0,1,0,3,0,1,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,1,0,0,1,0,0,0,0,0,1,1,2,0,0,0,0,1,0,0,1,3,1,0,0,0,0,1,1,0,0, -0,1,0,0,0,0,3,0,0,0,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0, -3,3,1,1,1,1,2,3,0,0,2,1,1,1,1,1,0,2,1,1,0,0,0,2,1,0,1,2,1,1,0,1, -2,1,0,3,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,3,1,0,0,0,0,0,0,0,3,0,0,0,3,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1, -0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,0,0,0,0,0,0,1,2,1,0,1,1,0,2,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,2,0,0,0,1,3,0,1,0,0,0,2,0,0,0,0,0,0,0,1,2,0,0,0,0,0, -3,3,0,0,1,1,2,0,0,1,2,1,0,1,1,1,0,1,1,0,0,2,1,1,0,1,0,0,1,1,1,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,1,0,0,0,0,1,0,0,0,0,3,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,3,0,0,1,1,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -1,1,0,1,2,0,1,2,0,0,1,1,0,2,0,1,0,0,1,0,0,0,0,1,0,0,0,2,0,0,0,0, -1,0,0,1,0,1,1,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,1,0,0,0,0,0,0,0,1,1,0,1,1,0,2,1,3,0,0,0,0,1,1,0,0,0,0,0,0,0,3, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,1,0,1,0,0,2,0,0,2,0,0,1,1,2,0,0,1,1,0,0,0,1,0,0,0,1,1,0,0,0, -1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0, -1,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,1,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,3,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0, -1,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,1,0,0,2,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -) - -TIS620ThaiModel = { - 'char_to_order_map': TIS620CharToOrderMap, - 'precedence_matrix': ThaiLangModel, - 'typical_positive_ratio': 0.926386, - 'keep_english_letter': False, - 'charset_name': "TIS-620", - 'language': 'Thai', -} diff --git a/lib/chardet/langturkishmodel.py b/lib/chardet/langturkishmodel.py deleted file mode 100644 index a427a45..0000000 --- a/lib/chardet/langturkishmodel.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- coding: utf-8 -*- -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Communicator client code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Özgür Baskın - Turkish Language Model -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -# 255: Control characters that usually does not exist in any text -# 254: Carriage/Return -# 253: symbol (punctuation) that does not belong to word -# 252: 0 - 9 - -# Character Mapping Table: -Latin5_TurkishCharToOrderMap = ( -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -255, 23, 37, 47, 39, 29, 52, 36, 45, 53, 60, 16, 49, 20, 46, 42, - 48, 69, 44, 35, 31, 51, 38, 62, 65, 43, 56,255,255,255,255,255, -255, 1, 21, 28, 12, 2, 18, 27, 25, 3, 24, 10, 5, 13, 4, 15, - 26, 64, 7, 8, 9, 14, 32, 57, 58, 11, 22,255,255,255,255,255, -180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165, -164,163,162,161,160,159,101,158,157,156,155,154,153,152,151,106, -150,149,148,147,146,145,144,100,143,142,141,140,139,138,137,136, - 94, 80, 93,135,105,134,133, 63,132,131,130,129,128,127,126,125, -124,104, 73, 99, 79, 85,123, 54,122, 98, 92,121,120, 91,103,119, - 68,118,117, 97,116,115, 50, 90,114,113,112,111, 55, 41, 40, 86, - 89, 70, 59, 78, 71, 82, 88, 33, 77, 66, 84, 83,110, 75, 61, 96, - 30, 67,109, 74, 87,102, 34, 95, 81,108, 76, 72, 17, 6, 19,107, -) - -TurkishLangModel = ( -3,2,3,3,3,1,3,3,3,3,3,3,3,3,2,1,1,3,3,1,3,3,0,3,3,3,3,3,0,3,1,3, -3,2,1,0,0,1,1,0,0,0,1,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, -3,2,2,3,3,0,3,3,3,3,3,3,3,2,3,1,0,3,3,1,3,3,0,3,3,3,3,3,0,3,0,3, -3,1,1,0,1,0,1,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,2,2,0,0,0,1,0,1, -3,3,2,3,3,0,3,3,3,3,3,3,3,2,3,1,1,3,3,0,3,3,1,2,3,3,3,3,0,3,0,3, -3,1,1,0,0,0,1,0,0,0,0,1,1,0,1,2,1,0,0,0,1,0,0,0,0,2,0,0,0,0,0,1, -3,3,3,3,3,3,2,3,3,3,3,3,3,3,3,1,3,3,2,0,3,2,1,2,2,1,3,3,0,0,0,2, -2,2,0,1,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,0,1,0,0,1, -3,3,3,2,3,3,1,2,3,3,3,3,3,3,3,1,3,2,1,0,3,2,0,1,2,3,3,2,1,0,0,2, -2,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,2,0,2,0,0,0, -1,0,1,3,3,1,3,3,3,3,3,3,3,1,2,0,0,2,3,0,2,3,0,0,2,2,2,3,0,3,0,1, -2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0,3,3,3,0,3,2,0,2,3,2,3,3,1,0,0,2, -3,2,0,0,1,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,2,0,0,1, -3,3,3,2,3,3,2,3,3,3,3,2,3,3,3,0,3,3,0,0,2,1,0,0,2,3,2,2,0,0,0,2, -2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,0,1,0,2,0,0,1, -3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,0,1,3,2,1,1,3,2,3,2,1,0,0,2, -2,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, -3,3,3,2,3,3,3,3,3,3,3,2,3,3,3,0,3,2,2,0,2,3,0,0,2,2,2,2,0,0,0,2, -3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, -3,3,3,3,3,3,3,2,2,2,2,3,2,3,3,0,3,3,1,1,2,2,0,0,2,2,3,2,0,0,1,3, -0,3,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,1, -3,3,3,2,3,3,3,2,1,2,2,3,2,3,3,0,3,2,0,0,1,1,0,1,1,2,1,2,0,0,0,1, -0,3,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0, -3,3,3,2,3,3,2,3,2,2,2,3,3,3,3,1,3,1,1,0,3,2,1,1,3,3,2,3,1,0,0,1, -1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,1, -3,2,2,3,3,0,3,3,3,3,3,3,3,2,2,1,0,3,3,1,3,3,0,1,3,3,2,3,0,3,0,3, -2,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -2,2,2,3,3,0,3,3,3,3,3,3,3,3,3,0,0,3,2,0,3,3,0,3,2,3,3,3,0,3,1,3, -2,0,0,0,0,0,0,0,0,0,0,1,0,1,2,0,1,0,0,0,0,0,0,0,2,2,0,0,1,0,0,1, -3,3,3,1,2,3,3,1,0,0,1,0,0,3,3,2,3,0,0,2,0,0,2,0,2,0,0,0,2,0,2,0, -0,3,1,0,1,0,0,0,2,2,1,0,1,1,2,1,2,2,2,0,2,1,1,0,0,0,2,0,0,0,0,0, -1,2,1,3,3,0,3,3,3,3,3,2,3,0,0,0,0,2,3,0,2,3,1,0,2,3,1,3,0,3,0,2, -3,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,1,3,3,2,2,3,2,2,0,1,2,3,0,1,2,1,0,1,0,0,0,1,0,2,2,0,0,0,1, -1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0, -3,3,3,1,3,3,1,1,3,3,1,1,3,3,1,0,2,1,2,0,2,1,0,0,1,1,2,1,0,0,0,2, -2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,1,0,2,1,3,0,0,2,0,0,3,3,0,3,0,0,1,0,1,2,0,0,1,1,2,2,0,1,0, -0,1,2,1,1,0,1,0,1,1,1,1,1,0,1,1,1,2,2,1,2,0,1,0,0,0,0,0,0,1,0,0, -3,3,3,2,3,2,3,3,0,2,2,2,3,3,3,0,3,0,0,0,2,2,0,1,2,1,1,1,0,0,0,1, -0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -3,3,3,3,3,3,2,1,2,2,3,3,3,3,2,0,2,0,0,0,2,2,0,0,2,1,3,3,0,0,1,1, -1,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0, -1,1,2,3,3,0,3,3,3,3,3,3,2,2,0,2,0,2,3,2,3,2,2,2,2,2,2,2,1,3,2,3, -2,0,2,1,2,2,2,2,1,1,2,2,1,2,2,1,2,0,0,2,1,1,0,2,1,0,0,1,0,0,0,1, -2,3,3,1,1,1,0,1,1,1,2,3,2,1,1,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0, -0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,2,2,3,2,3,2,2,1,3,3,3,0,2,1,2,0,2,1,0,0,1,1,1,1,1,0,0,1, -2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,2,0,1,0,0,0, -3,3,3,2,3,3,3,3,3,2,3,1,2,3,3,1,2,0,0,0,0,0,0,0,3,2,1,1,0,0,0,0, -2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -3,3,3,2,2,3,3,2,1,1,1,1,1,3,3,0,3,1,0,0,1,1,0,0,3,1,2,1,0,0,0,0, -0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0, -3,3,3,2,2,3,2,2,2,3,2,1,1,3,3,0,3,0,0,0,0,1,0,0,3,1,1,2,0,0,0,1, -1,0,0,1,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, -1,1,1,3,3,0,3,3,3,3,3,2,2,2,1,2,0,2,1,2,2,1,1,0,1,2,2,2,2,2,2,2, -0,0,2,1,2,1,2,1,0,1,1,3,1,2,1,1,2,0,0,2,0,1,0,1,0,1,0,0,0,1,0,1, -3,3,3,1,3,3,3,0,1,1,0,2,2,3,1,0,3,0,0,0,1,0,0,0,1,0,0,1,0,1,0,0, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,2,0,0,2,2,1,0,0,1,0,0,3,3,1,3,0,0,1,1,0,2,0,3,0,0,0,2,0,1,1, -0,1,2,0,1,2,2,0,2,2,2,2,1,0,2,1,1,0,2,0,2,1,2,0,0,0,0,0,0,0,0,0, -3,3,3,1,3,2,3,2,0,2,2,2,1,3,2,0,2,1,2,0,1,2,0,0,1,0,2,2,0,0,0,2, -1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,0,0,0, -3,3,3,0,3,3,1,1,2,3,1,0,3,2,3,0,3,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0, -1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,3,3,0,3,3,2,3,3,2,2,0,0,0,0,1,2,0,1,3,0,0,0,3,1,1,0,3,0,2, -2,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,1,2,2,1,0,3,1,1,1,1,3,3,2,3,0,0,1,0,1,2,0,2,2,0,2,2,0,2,1, -0,2,2,1,1,1,1,0,2,1,1,0,1,1,1,1,2,1,2,1,2,0,1,0,1,0,0,0,0,0,0,0, -3,3,3,0,1,1,3,0,0,1,1,0,0,2,2,0,3,0,0,1,1,0,1,0,0,0,0,0,2,0,0,0, -0,3,1,0,1,0,1,0,2,0,0,1,0,1,0,1,1,1,2,1,1,0,2,0,0,0,0,0,0,0,0,0, -3,3,3,0,2,0,2,0,1,1,1,0,0,3,3,0,2,0,0,1,0,0,2,1,1,0,1,0,1,0,1,0, -0,2,0,1,2,0,2,0,2,1,1,0,1,0,2,1,1,0,2,1,1,0,1,0,0,0,1,1,0,0,0,0, -3,2,3,0,1,0,0,0,0,0,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,0,2,0,0,0, -0,0,1,1,0,0,1,0,1,0,0,1,0,0,0,2,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,0,0,2,3,0,0,1,0,1,0,2,3,2,3,0,0,1,3,0,2,1,0,0,0,0,2,0,1,0, -0,2,1,0,0,1,1,0,2,1,0,0,1,0,0,1,1,0,1,1,2,0,1,0,0,0,0,1,0,0,0,0, -3,2,2,0,0,1,1,0,0,0,0,0,0,3,1,1,1,0,0,0,0,0,1,0,0,0,0,0,2,0,1,0, -0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,3,3,0,2,3,2,2,1,2,2,1,1,2,0,1,3,2,2,2,0,0,2,2,0,0,0,1,2,1, -3,0,2,1,1,0,1,1,1,0,1,2,2,2,1,1,2,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0, -0,1,1,2,3,0,3,3,3,2,2,2,2,1,0,1,0,1,0,1,2,2,0,0,2,2,1,3,1,1,2,1, -0,0,1,1,2,0,1,1,0,0,1,2,0,2,1,1,2,0,0,1,0,0,0,1,0,1,0,1,0,0,0,0, -3,3,2,0,0,3,1,0,0,0,0,0,0,3,2,1,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, -0,2,1,1,0,0,1,0,1,2,0,0,1,1,0,0,2,1,1,1,1,0,2,0,0,0,0,0,0,0,0,0, -3,3,2,0,0,1,0,0,0,0,1,0,0,3,3,2,2,0,0,1,0,0,2,0,1,0,0,0,2,0,1,0, -0,0,1,1,0,0,2,0,2,1,0,0,1,1,2,1,2,0,2,1,2,1,1,1,0,0,1,1,0,0,0,0, -3,3,2,0,0,2,2,0,0,0,1,1,0,2,2,1,3,1,0,1,0,1,2,0,0,0,0,0,1,0,1,0, -0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,2,0,0,0,1,0,0,1,0,0,2,3,1,2,0,0,1,0,0,2,0,0,0,1,0,2,0,2,0, -0,1,1,2,2,1,2,0,2,1,1,0,0,1,1,0,1,1,1,1,2,1,1,0,0,0,0,0,0,0,0,0, -3,3,3,0,2,1,2,1,0,0,1,1,0,3,3,1,2,0,0,1,0,0,2,0,2,0,1,1,2,0,0,0, -0,0,1,1,1,1,2,0,1,1,0,1,1,1,1,0,0,0,1,1,1,0,1,0,0,0,1,0,0,0,0,0, -3,3,3,0,2,2,3,2,0,0,1,0,0,2,3,1,0,0,0,0,0,0,2,0,2,0,0,0,2,0,0,0, -0,1,1,0,0,0,1,0,0,1,0,1,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0,0,0, -3,2,3,0,0,0,0,0,0,0,1,0,0,2,2,2,2,0,0,1,0,0,2,0,0,0,0,0,2,0,1,0, -0,0,2,1,1,0,1,0,2,1,1,0,0,1,1,2,1,0,2,0,2,0,1,0,0,0,2,0,0,0,0,0, -0,0,0,2,2,0,2,1,1,1,1,2,2,0,0,1,0,1,0,0,1,3,0,0,0,0,1,0,0,2,1,0, -0,0,1,0,1,0,0,0,0,0,2,1,0,1,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0, -2,0,0,2,3,0,2,3,1,2,2,0,2,0,0,2,0,2,1,1,1,2,1,0,0,1,2,1,1,2,1,0, -1,0,2,0,1,0,1,1,0,0,2,2,1,2,1,1,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0, -3,3,3,0,2,1,2,0,0,0,1,0,0,3,2,0,1,0,0,1,0,0,2,0,0,0,1,2,1,0,1,0, -0,0,0,0,1,0,1,0,0,1,0,0,0,0,1,0,1,0,1,1,1,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,2,2,0,2,2,1,1,0,1,1,1,1,1,0,0,1,2,1,1,1,0,1,0,0,0,1,1,1,1, -0,0,2,1,0,1,1,1,0,1,1,2,1,2,1,1,2,0,1,1,2,1,0,2,0,0,0,0,0,0,0,0, -3,2,2,0,0,2,0,0,0,0,0,0,0,2,2,0,2,0,0,1,0,0,2,0,0,0,0,0,2,0,0,0, -0,2,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,3,2,0,2,2,0,1,1,0,1,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0, -2,0,1,0,1,0,1,1,0,0,1,2,0,1,0,1,1,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0, -2,2,2,0,1,1,0,0,0,1,0,0,0,1,2,0,1,0,0,1,0,0,1,0,0,0,0,1,2,0,1,0, -0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,1,0,2,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,2,1,0,1,1,1,0,0,0,0,1,2,0,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0, -1,1,2,0,1,0,0,0,1,0,1,0,0,0,1,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,2,0,0,0,0,0,1, -0,0,1,2,2,0,2,1,2,1,1,2,2,0,0,0,0,1,0,0,1,1,0,0,2,0,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0, -2,2,2,0,0,0,1,0,0,0,0,0,0,2,2,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, -0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -2,2,2,0,1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,1,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -) - -Latin5TurkishModel = { - 'char_to_order_map': Latin5_TurkishCharToOrderMap, - 'precedence_matrix': TurkishLangModel, - 'typical_positive_ratio': 0.970290, - 'keep_english_letter': True, - 'charset_name': "ISO-8859-9", - 'language': 'Turkish', -} diff --git a/lib/chardet/latin1prober.py b/lib/chardet/latin1prober.py deleted file mode 100644 index 7d1e8c2..0000000 --- a/lib/chardet/latin1prober.py +++ /dev/null @@ -1,145 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import ProbingState - -FREQ_CAT_NUM = 4 - -UDF = 0 # undefined -OTH = 1 # other -ASC = 2 # ascii capital letter -ASS = 3 # ascii small letter -ACV = 4 # accent capital vowel -ACO = 5 # accent capital other -ASV = 6 # accent small vowel -ASO = 7 # accent small other -CLASS_NUM = 8 # total classes - -Latin1_CharToClass = ( - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 00 - 07 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 08 - 0F - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 10 - 17 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 18 - 1F - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 20 - 27 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 28 - 2F - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 30 - 37 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 38 - 3F - OTH, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 40 - 47 - ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 48 - 4F - ASC, ASC, ASC, ASC, ASC, ASC, ASC, ASC, # 50 - 57 - ASC, ASC, ASC, OTH, OTH, OTH, OTH, OTH, # 58 - 5F - OTH, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 60 - 67 - ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 68 - 6F - ASS, ASS, ASS, ASS, ASS, ASS, ASS, ASS, # 70 - 77 - ASS, ASS, ASS, OTH, OTH, OTH, OTH, OTH, # 78 - 7F - OTH, UDF, OTH, ASO, OTH, OTH, OTH, OTH, # 80 - 87 - OTH, OTH, ACO, OTH, ACO, UDF, ACO, UDF, # 88 - 8F - UDF, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # 90 - 97 - OTH, OTH, ASO, OTH, ASO, UDF, ASO, ACO, # 98 - 9F - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A0 - A7 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # A8 - AF - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B0 - B7 - OTH, OTH, OTH, OTH, OTH, OTH, OTH, OTH, # B8 - BF - ACV, ACV, ACV, ACV, ACV, ACV, ACO, ACO, # C0 - C7 - ACV, ACV, ACV, ACV, ACV, ACV, ACV, ACV, # C8 - CF - ACO, ACO, ACV, ACV, ACV, ACV, ACV, OTH, # D0 - D7 - ACV, ACV, ACV, ACV, ACV, ACO, ACO, ACO, # D8 - DF - ASV, ASV, ASV, ASV, ASV, ASV, ASO, ASO, # E0 - E7 - ASV, ASV, ASV, ASV, ASV, ASV, ASV, ASV, # E8 - EF - ASO, ASO, ASV, ASV, ASV, ASV, ASV, OTH, # F0 - F7 - ASV, ASV, ASV, ASV, ASV, ASO, ASO, ASO, # F8 - FF -) - -# 0 : illegal -# 1 : very unlikely -# 2 : normal -# 3 : very likely -Latin1ClassModel = ( -# UDF OTH ASC ASS ACV ACO ASV ASO - 0, 0, 0, 0, 0, 0, 0, 0, # UDF - 0, 3, 3, 3, 3, 3, 3, 3, # OTH - 0, 3, 3, 3, 3, 3, 3, 3, # ASC - 0, 3, 3, 3, 1, 1, 3, 3, # ASS - 0, 3, 3, 3, 1, 2, 1, 2, # ACV - 0, 3, 3, 3, 3, 3, 3, 3, # ACO - 0, 3, 1, 3, 1, 1, 1, 3, # ASV - 0, 3, 1, 3, 1, 1, 3, 3, # ASO -) - - -class Latin1Prober(CharSetProber): - def __init__(self): - super(Latin1Prober, self).__init__() - self._last_char_class = None - self._freq_counter = None - self.reset() - - def reset(self): - self._last_char_class = OTH - self._freq_counter = [0] * FREQ_CAT_NUM - CharSetProber.reset(self) - - @property - def charset_name(self): - return "ISO-8859-1" - - @property - def language(self): - return "" - - def feed(self, byte_str): - byte_str = self.filter_with_english_letters(byte_str) - for c in byte_str: - char_class = Latin1_CharToClass[c] - freq = Latin1ClassModel[(self._last_char_class * CLASS_NUM) - + char_class] - if freq == 0: - self._state = ProbingState.NOT_ME - break - self._freq_counter[freq] += 1 - self._last_char_class = char_class - - return self.state - - def get_confidence(self): - if self.state == ProbingState.NOT_ME: - return 0.01 - - total = sum(self._freq_counter) - if total < 0.01: - confidence = 0.0 - else: - confidence = ((self._freq_counter[3] - self._freq_counter[1] * 20.0) - / total) - if confidence < 0.0: - confidence = 0.0 - # lower the confidence of latin1 so that other more accurate - # detector can take priority. - confidence = confidence * 0.73 - return confidence diff --git a/lib/chardet/mbcharsetprober.py b/lib/chardet/mbcharsetprober.py deleted file mode 100644 index 6256ecf..0000000 --- a/lib/chardet/mbcharsetprober.py +++ /dev/null @@ -1,91 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# Proofpoint, Inc. -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import ProbingState, MachineState - - -class MultiByteCharSetProber(CharSetProber): - """ - MultiByteCharSetProber - """ - - def __init__(self, lang_filter=None): - super(MultiByteCharSetProber, self).__init__(lang_filter=lang_filter) - self.distribution_analyzer = None - self.coding_sm = None - self._last_char = [0, 0] - - def reset(self): - super(MultiByteCharSetProber, self).reset() - if self.coding_sm: - self.coding_sm.reset() - if self.distribution_analyzer: - self.distribution_analyzer.reset() - self._last_char = [0, 0] - - @property - def charset_name(self): - raise NotImplementedError - - @property - def language(self): - raise NotImplementedError - - def feed(self, byte_str): - for i in range(len(byte_str)): - coding_state = self.coding_sm.next_state(byte_str[i]) - if coding_state == MachineState.ERROR: - self.logger.debug('%s %s prober hit error at byte %s', - self.charset_name, self.language, i) - self._state = ProbingState.NOT_ME - break - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - break - elif coding_state == MachineState.START: - char_len = self.coding_sm.get_current_charlen() - if i == 0: - self._last_char[1] = byte_str[0] - self.distribution_analyzer.feed(self._last_char, char_len) - else: - self.distribution_analyzer.feed(byte_str[i - 1:i + 1], - char_len) - - self._last_char[0] = byte_str[-1] - - if self.state == ProbingState.DETECTING: - if (self.distribution_analyzer.got_enough_data() and - (self.get_confidence() > self.SHORTCUT_THRESHOLD)): - self._state = ProbingState.FOUND_IT - - return self.state - - def get_confidence(self): - return self.distribution_analyzer.get_confidence() diff --git a/lib/chardet/mbcsgroupprober.py b/lib/chardet/mbcsgroupprober.py deleted file mode 100644 index 530abe7..0000000 --- a/lib/chardet/mbcsgroupprober.py +++ /dev/null @@ -1,54 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# Proofpoint, Inc. -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetgroupprober import CharSetGroupProber -from .utf8prober import UTF8Prober -from .sjisprober import SJISProber -from .eucjpprober import EUCJPProber -from .gb2312prober import GB2312Prober -from .euckrprober import EUCKRProber -from .cp949prober import CP949Prober -from .big5prober import Big5Prober -from .euctwprober import EUCTWProber - - -class MBCSGroupProber(CharSetGroupProber): - def __init__(self, lang_filter=None): - super(MBCSGroupProber, self).__init__(lang_filter=lang_filter) - self.probers = [ - UTF8Prober(), - SJISProber(), - EUCJPProber(), - GB2312Prober(), - EUCKRProber(), - CP949Prober(), - Big5Prober(), - EUCTWProber() - ] - self.reset() diff --git a/lib/chardet/mbcssm.py b/lib/chardet/mbcssm.py deleted file mode 100644 index 8360d0f..0000000 --- a/lib/chardet/mbcssm.py +++ /dev/null @@ -1,572 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .enums import MachineState - -# BIG5 - -BIG5_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as legal value - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 1,1,1,1,1,1,1,1, # 30 - 37 - 1,1,1,1,1,1,1,1, # 38 - 3f - 2,2,2,2,2,2,2,2, # 40 - 47 - 2,2,2,2,2,2,2,2, # 48 - 4f - 2,2,2,2,2,2,2,2, # 50 - 57 - 2,2,2,2,2,2,2,2, # 58 - 5f - 2,2,2,2,2,2,2,2, # 60 - 67 - 2,2,2,2,2,2,2,2, # 68 - 6f - 2,2,2,2,2,2,2,2, # 70 - 77 - 2,2,2,2,2,2,2,1, # 78 - 7f - 4,4,4,4,4,4,4,4, # 80 - 87 - 4,4,4,4,4,4,4,4, # 88 - 8f - 4,4,4,4,4,4,4,4, # 90 - 97 - 4,4,4,4,4,4,4,4, # 98 - 9f - 4,3,3,3,3,3,3,3, # a0 - a7 - 3,3,3,3,3,3,3,3, # a8 - af - 3,3,3,3,3,3,3,3, # b0 - b7 - 3,3,3,3,3,3,3,3, # b8 - bf - 3,3,3,3,3,3,3,3, # c0 - c7 - 3,3,3,3,3,3,3,3, # c8 - cf - 3,3,3,3,3,3,3,3, # d0 - d7 - 3,3,3,3,3,3,3,3, # d8 - df - 3,3,3,3,3,3,3,3, # e0 - e7 - 3,3,3,3,3,3,3,3, # e8 - ef - 3,3,3,3,3,3,3,3, # f0 - f7 - 3,3,3,3,3,3,3,0 # f8 - ff -) - -BIG5_ST = ( - MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,#08-0f - MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START#10-17 -) - -BIG5_CHAR_LEN_TABLE = (0, 1, 1, 2, 0) - -BIG5_SM_MODEL = {'class_table': BIG5_CLS, - 'class_factor': 5, - 'state_table': BIG5_ST, - 'char_len_table': BIG5_CHAR_LEN_TABLE, - 'name': 'Big5'} - -# CP949 - -CP949_CLS = ( - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,0,0, # 00 - 0f - 1,1,1,1,1,1,1,1, 1,1,1,0,1,1,1,1, # 10 - 1f - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 20 - 2f - 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, # 30 - 3f - 1,4,4,4,4,4,4,4, 4,4,4,4,4,4,4,4, # 40 - 4f - 4,4,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 50 - 5f - 1,5,5,5,5,5,5,5, 5,5,5,5,5,5,5,5, # 60 - 6f - 5,5,5,5,5,5,5,5, 5,5,5,1,1,1,1,1, # 70 - 7f - 0,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 80 - 8f - 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, # 90 - 9f - 6,7,7,7,7,7,7,7, 7,7,7,7,7,8,8,8, # a0 - af - 7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7, # b0 - bf - 7,7,7,7,7,7,9,2, 2,3,2,2,2,2,2,2, # c0 - cf - 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # d0 - df - 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, # e0 - ef - 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,0, # f0 - ff -) - -CP949_ST = ( -#cls= 0 1 2 3 4 5 6 7 8 9 # previous state = - MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.START,MachineState.START, 4, 5,MachineState.ERROR, 6, # MachineState.START - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, # MachineState.ERROR - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME, # MachineState.ITS_ME - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 3 - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 4 - MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, # 5 - MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START, # 6 -) - -CP949_CHAR_LEN_TABLE = (0, 1, 2, 0, 1, 1, 2, 2, 0, 2) - -CP949_SM_MODEL = {'class_table': CP949_CLS, - 'class_factor': 10, - 'state_table': CP949_ST, - 'char_len_table': CP949_CHAR_LEN_TABLE, - 'name': 'CP949'} - -# EUC-JP - -EUCJP_CLS = ( - 4,4,4,4,4,4,4,4, # 00 - 07 - 4,4,4,4,4,4,5,5, # 08 - 0f - 4,4,4,4,4,4,4,4, # 10 - 17 - 4,4,4,5,4,4,4,4, # 18 - 1f - 4,4,4,4,4,4,4,4, # 20 - 27 - 4,4,4,4,4,4,4,4, # 28 - 2f - 4,4,4,4,4,4,4,4, # 30 - 37 - 4,4,4,4,4,4,4,4, # 38 - 3f - 4,4,4,4,4,4,4,4, # 40 - 47 - 4,4,4,4,4,4,4,4, # 48 - 4f - 4,4,4,4,4,4,4,4, # 50 - 57 - 4,4,4,4,4,4,4,4, # 58 - 5f - 4,4,4,4,4,4,4,4, # 60 - 67 - 4,4,4,4,4,4,4,4, # 68 - 6f - 4,4,4,4,4,4,4,4, # 70 - 77 - 4,4,4,4,4,4,4,4, # 78 - 7f - 5,5,5,5,5,5,5,5, # 80 - 87 - 5,5,5,5,5,5,1,3, # 88 - 8f - 5,5,5,5,5,5,5,5, # 90 - 97 - 5,5,5,5,5,5,5,5, # 98 - 9f - 5,2,2,2,2,2,2,2, # a0 - a7 - 2,2,2,2,2,2,2,2, # a8 - af - 2,2,2,2,2,2,2,2, # b0 - b7 - 2,2,2,2,2,2,2,2, # b8 - bf - 2,2,2,2,2,2,2,2, # c0 - c7 - 2,2,2,2,2,2,2,2, # c8 - cf - 2,2,2,2,2,2,2,2, # d0 - d7 - 2,2,2,2,2,2,2,2, # d8 - df - 0,0,0,0,0,0,0,0, # e0 - e7 - 0,0,0,0,0,0,0,0, # e8 - ef - 0,0,0,0,0,0,0,0, # f0 - f7 - 0,0,0,0,0,0,0,5 # f8 - ff -) - -EUCJP_ST = ( - 3, 4, 3, 5,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 3,MachineState.ERROR,#18-1f - 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START#20-27 -) - -EUCJP_CHAR_LEN_TABLE = (2, 2, 2, 3, 1, 0) - -EUCJP_SM_MODEL = {'class_table': EUCJP_CLS, - 'class_factor': 6, - 'state_table': EUCJP_ST, - 'char_len_table': EUCJP_CHAR_LEN_TABLE, - 'name': 'EUC-JP'} - -# EUC-KR - -EUCKR_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 1,1,1,1,1,1,1,1, # 30 - 37 - 1,1,1,1,1,1,1,1, # 38 - 3f - 1,1,1,1,1,1,1,1, # 40 - 47 - 1,1,1,1,1,1,1,1, # 48 - 4f - 1,1,1,1,1,1,1,1, # 50 - 57 - 1,1,1,1,1,1,1,1, # 58 - 5f - 1,1,1,1,1,1,1,1, # 60 - 67 - 1,1,1,1,1,1,1,1, # 68 - 6f - 1,1,1,1,1,1,1,1, # 70 - 77 - 1,1,1,1,1,1,1,1, # 78 - 7f - 0,0,0,0,0,0,0,0, # 80 - 87 - 0,0,0,0,0,0,0,0, # 88 - 8f - 0,0,0,0,0,0,0,0, # 90 - 97 - 0,0,0,0,0,0,0,0, # 98 - 9f - 0,2,2,2,2,2,2,2, # a0 - a7 - 2,2,2,2,2,3,3,3, # a8 - af - 2,2,2,2,2,2,2,2, # b0 - b7 - 2,2,2,2,2,2,2,2, # b8 - bf - 2,2,2,2,2,2,2,2, # c0 - c7 - 2,3,2,2,2,2,2,2, # c8 - cf - 2,2,2,2,2,2,2,2, # d0 - d7 - 2,2,2,2,2,2,2,2, # d8 - df - 2,2,2,2,2,2,2,2, # e0 - e7 - 2,2,2,2,2,2,2,2, # e8 - ef - 2,2,2,2,2,2,2,2, # f0 - f7 - 2,2,2,2,2,2,2,0 # f8 - ff -) - -EUCKR_ST = ( - MachineState.ERROR,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #08-0f -) - -EUCKR_CHAR_LEN_TABLE = (0, 1, 2, 0) - -EUCKR_SM_MODEL = {'class_table': EUCKR_CLS, - 'class_factor': 4, - 'state_table': EUCKR_ST, - 'char_len_table': EUCKR_CHAR_LEN_TABLE, - 'name': 'EUC-KR'} - -# EUC-TW - -EUCTW_CLS = ( - 2,2,2,2,2,2,2,2, # 00 - 07 - 2,2,2,2,2,2,0,0, # 08 - 0f - 2,2,2,2,2,2,2,2, # 10 - 17 - 2,2,2,0,2,2,2,2, # 18 - 1f - 2,2,2,2,2,2,2,2, # 20 - 27 - 2,2,2,2,2,2,2,2, # 28 - 2f - 2,2,2,2,2,2,2,2, # 30 - 37 - 2,2,2,2,2,2,2,2, # 38 - 3f - 2,2,2,2,2,2,2,2, # 40 - 47 - 2,2,2,2,2,2,2,2, # 48 - 4f - 2,2,2,2,2,2,2,2, # 50 - 57 - 2,2,2,2,2,2,2,2, # 58 - 5f - 2,2,2,2,2,2,2,2, # 60 - 67 - 2,2,2,2,2,2,2,2, # 68 - 6f - 2,2,2,2,2,2,2,2, # 70 - 77 - 2,2,2,2,2,2,2,2, # 78 - 7f - 0,0,0,0,0,0,0,0, # 80 - 87 - 0,0,0,0,0,0,6,0, # 88 - 8f - 0,0,0,0,0,0,0,0, # 90 - 97 - 0,0,0,0,0,0,0,0, # 98 - 9f - 0,3,4,4,4,4,4,4, # a0 - a7 - 5,5,1,1,1,1,1,1, # a8 - af - 1,1,1,1,1,1,1,1, # b0 - b7 - 1,1,1,1,1,1,1,1, # b8 - bf - 1,1,3,1,3,3,3,3, # c0 - c7 - 3,3,3,3,3,3,3,3, # c8 - cf - 3,3,3,3,3,3,3,3, # d0 - d7 - 3,3,3,3,3,3,3,3, # d8 - df - 3,3,3,3,3,3,3,3, # e0 - e7 - 3,3,3,3,3,3,3,3, # e8 - ef - 3,3,3,3,3,3,3,3, # f0 - f7 - 3,3,3,3,3,3,3,0 # f8 - ff -) - -EUCTW_ST = ( - MachineState.ERROR,MachineState.ERROR,MachineState.START, 3, 3, 3, 4,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.START,MachineState.ERROR,#10-17 - MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f - 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,#20-27 - MachineState.START,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f -) - -EUCTW_CHAR_LEN_TABLE = (0, 0, 1, 2, 2, 2, 3) - -EUCTW_SM_MODEL = {'class_table': EUCTW_CLS, - 'class_factor': 7, - 'state_table': EUCTW_ST, - 'char_len_table': EUCTW_CHAR_LEN_TABLE, - 'name': 'x-euc-tw'} - -# GB2312 - -GB2312_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 3,3,3,3,3,3,3,3, # 30 - 37 - 3,3,1,1,1,1,1,1, # 38 - 3f - 2,2,2,2,2,2,2,2, # 40 - 47 - 2,2,2,2,2,2,2,2, # 48 - 4f - 2,2,2,2,2,2,2,2, # 50 - 57 - 2,2,2,2,2,2,2,2, # 58 - 5f - 2,2,2,2,2,2,2,2, # 60 - 67 - 2,2,2,2,2,2,2,2, # 68 - 6f - 2,2,2,2,2,2,2,2, # 70 - 77 - 2,2,2,2,2,2,2,4, # 78 - 7f - 5,6,6,6,6,6,6,6, # 80 - 87 - 6,6,6,6,6,6,6,6, # 88 - 8f - 6,6,6,6,6,6,6,6, # 90 - 97 - 6,6,6,6,6,6,6,6, # 98 - 9f - 6,6,6,6,6,6,6,6, # a0 - a7 - 6,6,6,6,6,6,6,6, # a8 - af - 6,6,6,6,6,6,6,6, # b0 - b7 - 6,6,6,6,6,6,6,6, # b8 - bf - 6,6,6,6,6,6,6,6, # c0 - c7 - 6,6,6,6,6,6,6,6, # c8 - cf - 6,6,6,6,6,6,6,6, # d0 - d7 - 6,6,6,6,6,6,6,6, # d8 - df - 6,6,6,6,6,6,6,6, # e0 - e7 - 6,6,6,6,6,6,6,6, # e8 - ef - 6,6,6,6,6,6,6,6, # f0 - f7 - 6,6,6,6,6,6,6,0 # f8 - ff -) - -GB2312_ST = ( - MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START, 3,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,#10-17 - 4,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f - MachineState.ERROR,MachineState.ERROR, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#20-27 - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.START #28-2f -) - -# To be accurate, the length of class 6 can be either 2 or 4. -# But it is not necessary to discriminate between the two since -# it is used for frequency analysis only, and we are validating -# each code range there as well. So it is safe to set it to be -# 2 here. -GB2312_CHAR_LEN_TABLE = (0, 1, 1, 1, 1, 1, 2) - -GB2312_SM_MODEL = {'class_table': GB2312_CLS, - 'class_factor': 7, - 'state_table': GB2312_ST, - 'char_len_table': GB2312_CHAR_LEN_TABLE, - 'name': 'GB2312'} - -# Shift_JIS - -SJIS_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 1,1,1,1,1,1,1,1, # 30 - 37 - 1,1,1,1,1,1,1,1, # 38 - 3f - 2,2,2,2,2,2,2,2, # 40 - 47 - 2,2,2,2,2,2,2,2, # 48 - 4f - 2,2,2,2,2,2,2,2, # 50 - 57 - 2,2,2,2,2,2,2,2, # 58 - 5f - 2,2,2,2,2,2,2,2, # 60 - 67 - 2,2,2,2,2,2,2,2, # 68 - 6f - 2,2,2,2,2,2,2,2, # 70 - 77 - 2,2,2,2,2,2,2,1, # 78 - 7f - 3,3,3,3,3,2,2,3, # 80 - 87 - 3,3,3,3,3,3,3,3, # 88 - 8f - 3,3,3,3,3,3,3,3, # 90 - 97 - 3,3,3,3,3,3,3,3, # 98 - 9f - #0xa0 is illegal in sjis encoding, but some pages does - #contain such byte. We need to be more error forgiven. - 2,2,2,2,2,2,2,2, # a0 - a7 - 2,2,2,2,2,2,2,2, # a8 - af - 2,2,2,2,2,2,2,2, # b0 - b7 - 2,2,2,2,2,2,2,2, # b8 - bf - 2,2,2,2,2,2,2,2, # c0 - c7 - 2,2,2,2,2,2,2,2, # c8 - cf - 2,2,2,2,2,2,2,2, # d0 - d7 - 2,2,2,2,2,2,2,2, # d8 - df - 3,3,3,3,3,3,3,3, # e0 - e7 - 3,3,3,3,3,4,4,4, # e8 - ef - 3,3,3,3,3,3,3,3, # f0 - f7 - 3,3,3,3,3,0,0,0) # f8 - ff - - -SJIS_ST = ( - MachineState.ERROR,MachineState.START,MachineState.START, 3,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START #10-17 -) - -SJIS_CHAR_LEN_TABLE = (0, 1, 1, 2, 0, 0) - -SJIS_SM_MODEL = {'class_table': SJIS_CLS, - 'class_factor': 6, - 'state_table': SJIS_ST, - 'char_len_table': SJIS_CHAR_LEN_TABLE, - 'name': 'Shift_JIS'} - -# UCS2-BE - -UCS2BE_CLS = ( - 0,0,0,0,0,0,0,0, # 00 - 07 - 0,0,1,0,0,2,0,0, # 08 - 0f - 0,0,0,0,0,0,0,0, # 10 - 17 - 0,0,0,3,0,0,0,0, # 18 - 1f - 0,0,0,0,0,0,0,0, # 20 - 27 - 0,3,3,3,3,3,0,0, # 28 - 2f - 0,0,0,0,0,0,0,0, # 30 - 37 - 0,0,0,0,0,0,0,0, # 38 - 3f - 0,0,0,0,0,0,0,0, # 40 - 47 - 0,0,0,0,0,0,0,0, # 48 - 4f - 0,0,0,0,0,0,0,0, # 50 - 57 - 0,0,0,0,0,0,0,0, # 58 - 5f - 0,0,0,0,0,0,0,0, # 60 - 67 - 0,0,0,0,0,0,0,0, # 68 - 6f - 0,0,0,0,0,0,0,0, # 70 - 77 - 0,0,0,0,0,0,0,0, # 78 - 7f - 0,0,0,0,0,0,0,0, # 80 - 87 - 0,0,0,0,0,0,0,0, # 88 - 8f - 0,0,0,0,0,0,0,0, # 90 - 97 - 0,0,0,0,0,0,0,0, # 98 - 9f - 0,0,0,0,0,0,0,0, # a0 - a7 - 0,0,0,0,0,0,0,0, # a8 - af - 0,0,0,0,0,0,0,0, # b0 - b7 - 0,0,0,0,0,0,0,0, # b8 - bf - 0,0,0,0,0,0,0,0, # c0 - c7 - 0,0,0,0,0,0,0,0, # c8 - cf - 0,0,0,0,0,0,0,0, # d0 - d7 - 0,0,0,0,0,0,0,0, # d8 - df - 0,0,0,0,0,0,0,0, # e0 - e7 - 0,0,0,0,0,0,0,0, # e8 - ef - 0,0,0,0,0,0,0,0, # f0 - f7 - 0,0,0,0,0,0,4,5 # f8 - ff -) - -UCS2BE_ST = ( - 5, 7, 7,MachineState.ERROR, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME, 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,#10-17 - 6, 6, 6, 6, 6,MachineState.ITS_ME, 6, 6,#18-1f - 6, 6, 6, 6, 5, 7, 7,MachineState.ERROR,#20-27 - 5, 8, 6, 6,MachineState.ERROR, 6, 6, 6,#28-2f - 6, 6, 6, 6,MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START #30-37 -) - -UCS2BE_CHAR_LEN_TABLE = (2, 2, 2, 0, 2, 2) - -UCS2BE_SM_MODEL = {'class_table': UCS2BE_CLS, - 'class_factor': 6, - 'state_table': UCS2BE_ST, - 'char_len_table': UCS2BE_CHAR_LEN_TABLE, - 'name': 'UTF-16BE'} - -# UCS2-LE - -UCS2LE_CLS = ( - 0,0,0,0,0,0,0,0, # 00 - 07 - 0,0,1,0,0,2,0,0, # 08 - 0f - 0,0,0,0,0,0,0,0, # 10 - 17 - 0,0,0,3,0,0,0,0, # 18 - 1f - 0,0,0,0,0,0,0,0, # 20 - 27 - 0,3,3,3,3,3,0,0, # 28 - 2f - 0,0,0,0,0,0,0,0, # 30 - 37 - 0,0,0,0,0,0,0,0, # 38 - 3f - 0,0,0,0,0,0,0,0, # 40 - 47 - 0,0,0,0,0,0,0,0, # 48 - 4f - 0,0,0,0,0,0,0,0, # 50 - 57 - 0,0,0,0,0,0,0,0, # 58 - 5f - 0,0,0,0,0,0,0,0, # 60 - 67 - 0,0,0,0,0,0,0,0, # 68 - 6f - 0,0,0,0,0,0,0,0, # 70 - 77 - 0,0,0,0,0,0,0,0, # 78 - 7f - 0,0,0,0,0,0,0,0, # 80 - 87 - 0,0,0,0,0,0,0,0, # 88 - 8f - 0,0,0,0,0,0,0,0, # 90 - 97 - 0,0,0,0,0,0,0,0, # 98 - 9f - 0,0,0,0,0,0,0,0, # a0 - a7 - 0,0,0,0,0,0,0,0, # a8 - af - 0,0,0,0,0,0,0,0, # b0 - b7 - 0,0,0,0,0,0,0,0, # b8 - bf - 0,0,0,0,0,0,0,0, # c0 - c7 - 0,0,0,0,0,0,0,0, # c8 - cf - 0,0,0,0,0,0,0,0, # d0 - d7 - 0,0,0,0,0,0,0,0, # d8 - df - 0,0,0,0,0,0,0,0, # e0 - e7 - 0,0,0,0,0,0,0,0, # e8 - ef - 0,0,0,0,0,0,0,0, # f0 - f7 - 0,0,0,0,0,0,4,5 # f8 - ff -) - -UCS2LE_ST = ( - 6, 6, 7, 6, 4, 3,MachineState.ERROR,MachineState.ERROR,#00-07 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#08-0f - MachineState.ITS_ME,MachineState.ITS_ME, 5, 5, 5,MachineState.ERROR,MachineState.ITS_ME,MachineState.ERROR,#10-17 - 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR, 6, 6,#18-1f - 7, 6, 8, 8, 5, 5, 5,MachineState.ERROR,#20-27 - 5, 5, 5,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5,#28-2f - 5, 5, 5,MachineState.ERROR, 5,MachineState.ERROR,MachineState.START,MachineState.START #30-37 -) - -UCS2LE_CHAR_LEN_TABLE = (2, 2, 2, 2, 2, 2) - -UCS2LE_SM_MODEL = {'class_table': UCS2LE_CLS, - 'class_factor': 6, - 'state_table': UCS2LE_ST, - 'char_len_table': UCS2LE_CHAR_LEN_TABLE, - 'name': 'UTF-16LE'} - -# UTF-8 - -UTF8_CLS = ( - 1,1,1,1,1,1,1,1, # 00 - 07 #allow 0x00 as a legal value - 1,1,1,1,1,1,0,0, # 08 - 0f - 1,1,1,1,1,1,1,1, # 10 - 17 - 1,1,1,0,1,1,1,1, # 18 - 1f - 1,1,1,1,1,1,1,1, # 20 - 27 - 1,1,1,1,1,1,1,1, # 28 - 2f - 1,1,1,1,1,1,1,1, # 30 - 37 - 1,1,1,1,1,1,1,1, # 38 - 3f - 1,1,1,1,1,1,1,1, # 40 - 47 - 1,1,1,1,1,1,1,1, # 48 - 4f - 1,1,1,1,1,1,1,1, # 50 - 57 - 1,1,1,1,1,1,1,1, # 58 - 5f - 1,1,1,1,1,1,1,1, # 60 - 67 - 1,1,1,1,1,1,1,1, # 68 - 6f - 1,1,1,1,1,1,1,1, # 70 - 77 - 1,1,1,1,1,1,1,1, # 78 - 7f - 2,2,2,2,3,3,3,3, # 80 - 87 - 4,4,4,4,4,4,4,4, # 88 - 8f - 4,4,4,4,4,4,4,4, # 90 - 97 - 4,4,4,4,4,4,4,4, # 98 - 9f - 5,5,5,5,5,5,5,5, # a0 - a7 - 5,5,5,5,5,5,5,5, # a8 - af - 5,5,5,5,5,5,5,5, # b0 - b7 - 5,5,5,5,5,5,5,5, # b8 - bf - 0,0,6,6,6,6,6,6, # c0 - c7 - 6,6,6,6,6,6,6,6, # c8 - cf - 6,6,6,6,6,6,6,6, # d0 - d7 - 6,6,6,6,6,6,6,6, # d8 - df - 7,8,8,8,8,8,8,8, # e0 - e7 - 8,8,8,8,8,9,8,8, # e8 - ef - 10,11,11,11,11,11,11,11, # f0 - f7 - 12,13,13,13,14,15,0,0 # f8 - ff -) - -UTF8_ST = ( - MachineState.ERROR,MachineState.START,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12, 10,#00-07 - 9, 11, 8, 7, 6, 5, 4, 3,#08-0f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#10-17 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#18-1f - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#20-27 - MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,MachineState.ITS_ME,#28-2f - MachineState.ERROR,MachineState.ERROR, 5, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#30-37 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#38-3f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 5, 5, 5,MachineState.ERROR,MachineState.ERROR,#40-47 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#48-4f - MachineState.ERROR,MachineState.ERROR, 7, 7, 7, 7,MachineState.ERROR,MachineState.ERROR,#50-57 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#58-5f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 7, 7,MachineState.ERROR,MachineState.ERROR,#60-67 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#68-6f - MachineState.ERROR,MachineState.ERROR, 9, 9, 9, 9,MachineState.ERROR,MachineState.ERROR,#70-77 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#78-7f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 9,MachineState.ERROR,MachineState.ERROR,#80-87 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#88-8f - MachineState.ERROR,MachineState.ERROR, 12, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,#90-97 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#98-9f - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR, 12,MachineState.ERROR,MachineState.ERROR,#a0-a7 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#a8-af - MachineState.ERROR,MachineState.ERROR, 12, 12, 12,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b0-b7 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,#b8-bf - MachineState.ERROR,MachineState.ERROR,MachineState.START,MachineState.START,MachineState.START,MachineState.START,MachineState.ERROR,MachineState.ERROR,#c0-c7 - MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR,MachineState.ERROR #c8-cf -) - -UTF8_CHAR_LEN_TABLE = (0, 1, 0, 0, 0, 0, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6) - -UTF8_SM_MODEL = {'class_table': UTF8_CLS, - 'class_factor': 16, - 'state_table': UTF8_ST, - 'char_len_table': UTF8_CHAR_LEN_TABLE, - 'name': 'UTF-8'} diff --git a/lib/chardet/sbcharsetprober.py b/lib/chardet/sbcharsetprober.py deleted file mode 100644 index 0adb51d..0000000 --- a/lib/chardet/sbcharsetprober.py +++ /dev/null @@ -1,132 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import CharacterCategory, ProbingState, SequenceLikelihood - - -class SingleByteCharSetProber(CharSetProber): - SAMPLE_SIZE = 64 - SB_ENOUGH_REL_THRESHOLD = 1024 # 0.25 * SAMPLE_SIZE^2 - POSITIVE_SHORTCUT_THRESHOLD = 0.95 - NEGATIVE_SHORTCUT_THRESHOLD = 0.05 - - def __init__(self, model, reversed=False, name_prober=None): - super(SingleByteCharSetProber, self).__init__() - self._model = model - # TRUE if we need to reverse every pair in the model lookup - self._reversed = reversed - # Optional auxiliary prober for name decision - self._name_prober = name_prober - self._last_order = None - self._seq_counters = None - self._total_seqs = None - self._total_char = None - self._freq_char = None - self.reset() - - def reset(self): - super(SingleByteCharSetProber, self).reset() - # char order of last character - self._last_order = 255 - self._seq_counters = [0] * SequenceLikelihood.get_num_categories() - self._total_seqs = 0 - self._total_char = 0 - # characters that fall in our sampling range - self._freq_char = 0 - - @property - def charset_name(self): - if self._name_prober: - return self._name_prober.charset_name - else: - return self._model['charset_name'] - - @property - def language(self): - if self._name_prober: - return self._name_prober.language - else: - return self._model.get('language') - - def feed(self, byte_str): - if not self._model['keep_english_letter']: - byte_str = self.filter_international_words(byte_str) - if not byte_str: - return self.state - char_to_order_map = self._model['char_to_order_map'] - for i, c in enumerate(byte_str): - # XXX: Order is in range 1-64, so one would think we want 0-63 here, - # but that leads to 27 more test failures than before. - order = char_to_order_map[c] - # XXX: This was SYMBOL_CAT_ORDER before, with a value of 250, but - # CharacterCategory.SYMBOL is actually 253, so we use CONTROL - # to make it closer to the original intent. The only difference - # is whether or not we count digits and control characters for - # _total_char purposes. - if order < CharacterCategory.CONTROL: - self._total_char += 1 - if order < self.SAMPLE_SIZE: - self._freq_char += 1 - if self._last_order < self.SAMPLE_SIZE: - self._total_seqs += 1 - if not self._reversed: - i = (self._last_order * self.SAMPLE_SIZE) + order - model = self._model['precedence_matrix'][i] - else: # reverse the order of the letters in the lookup - i = (order * self.SAMPLE_SIZE) + self._last_order - model = self._model['precedence_matrix'][i] - self._seq_counters[model] += 1 - self._last_order = order - - charset_name = self._model['charset_name'] - if self.state == ProbingState.DETECTING: - if self._total_seqs > self.SB_ENOUGH_REL_THRESHOLD: - confidence = self.get_confidence() - if confidence > self.POSITIVE_SHORTCUT_THRESHOLD: - self.logger.debug('%s confidence = %s, we have a winner', - charset_name, confidence) - self._state = ProbingState.FOUND_IT - elif confidence < self.NEGATIVE_SHORTCUT_THRESHOLD: - self.logger.debug('%s confidence = %s, below negative ' - 'shortcut threshhold %s', charset_name, - confidence, - self.NEGATIVE_SHORTCUT_THRESHOLD) - self._state = ProbingState.NOT_ME - - return self.state - - def get_confidence(self): - r = 0.01 - if self._total_seqs > 0: - r = ((1.0 * self._seq_counters[SequenceLikelihood.POSITIVE]) / - self._total_seqs / self._model['typical_positive_ratio']) - r = r * self._freq_char / self._total_char - if r >= 1.0: - r = 0.99 - return r diff --git a/lib/chardet/sbcsgroupprober.py b/lib/chardet/sbcsgroupprober.py deleted file mode 100644 index 98e95dc..0000000 --- a/lib/chardet/sbcsgroupprober.py +++ /dev/null @@ -1,73 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetgroupprober import CharSetGroupProber -from .sbcharsetprober import SingleByteCharSetProber -from .langcyrillicmodel import (Win1251CyrillicModel, Koi8rModel, - Latin5CyrillicModel, MacCyrillicModel, - Ibm866Model, Ibm855Model) -from .langgreekmodel import Latin7GreekModel, Win1253GreekModel -from .langbulgarianmodel import Latin5BulgarianModel, Win1251BulgarianModel -# from .langhungarianmodel import Latin2HungarianModel, Win1250HungarianModel -from .langthaimodel import TIS620ThaiModel -from .langhebrewmodel import Win1255HebrewModel -from .hebrewprober import HebrewProber -from .langturkishmodel import Latin5TurkishModel - - -class SBCSGroupProber(CharSetGroupProber): - def __init__(self): - super(SBCSGroupProber, self).__init__() - self.probers = [ - SingleByteCharSetProber(Win1251CyrillicModel), - SingleByteCharSetProber(Koi8rModel), - SingleByteCharSetProber(Latin5CyrillicModel), - SingleByteCharSetProber(MacCyrillicModel), - SingleByteCharSetProber(Ibm866Model), - SingleByteCharSetProber(Ibm855Model), - SingleByteCharSetProber(Latin7GreekModel), - SingleByteCharSetProber(Win1253GreekModel), - SingleByteCharSetProber(Latin5BulgarianModel), - SingleByteCharSetProber(Win1251BulgarianModel), - # TODO: Restore Hungarian encodings (iso-8859-2 and windows-1250) - # after we retrain model. - # SingleByteCharSetProber(Latin2HungarianModel), - # SingleByteCharSetProber(Win1250HungarianModel), - SingleByteCharSetProber(TIS620ThaiModel), - SingleByteCharSetProber(Latin5TurkishModel), - ] - hebrew_prober = HebrewProber() - logical_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, - False, hebrew_prober) - visual_hebrew_prober = SingleByteCharSetProber(Win1255HebrewModel, True, - hebrew_prober) - hebrew_prober.set_model_probers(logical_hebrew_prober, visual_hebrew_prober) - self.probers.extend([hebrew_prober, logical_hebrew_prober, - visual_hebrew_prober]) - - self.reset() diff --git a/lib/chardet/sjisprober.py b/lib/chardet/sjisprober.py deleted file mode 100644 index 9e29623..0000000 --- a/lib/chardet/sjisprober.py +++ /dev/null @@ -1,92 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .mbcharsetprober import MultiByteCharSetProber -from .codingstatemachine import CodingStateMachine -from .chardistribution import SJISDistributionAnalysis -from .jpcntx import SJISContextAnalysis -from .mbcssm import SJIS_SM_MODEL -from .enums import ProbingState, MachineState - - -class SJISProber(MultiByteCharSetProber): - def __init__(self): - super(SJISProber, self).__init__() - self.coding_sm = CodingStateMachine(SJIS_SM_MODEL) - self.distribution_analyzer = SJISDistributionAnalysis() - self.context_analyzer = SJISContextAnalysis() - self.reset() - - def reset(self): - super(SJISProber, self).reset() - self.context_analyzer.reset() - - @property - def charset_name(self): - return self.context_analyzer.charset_name - - @property - def language(self): - return "Japanese" - - def feed(self, byte_str): - for i in range(len(byte_str)): - coding_state = self.coding_sm.next_state(byte_str[i]) - if coding_state == MachineState.ERROR: - self.logger.debug('%s %s prober hit error at byte %s', - self.charset_name, self.language, i) - self._state = ProbingState.NOT_ME - break - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - break - elif coding_state == MachineState.START: - char_len = self.coding_sm.get_current_charlen() - if i == 0: - self._last_char[1] = byte_str[0] - self.context_analyzer.feed(self._last_char[2 - char_len:], - char_len) - self.distribution_analyzer.feed(self._last_char, char_len) - else: - self.context_analyzer.feed(byte_str[i + 1 - char_len:i + 3 - - char_len], char_len) - self.distribution_analyzer.feed(byte_str[i - 1:i + 1], - char_len) - - self._last_char[0] = byte_str[-1] - - if self.state == ProbingState.DETECTING: - if (self.context_analyzer.got_enough_data() and - (self.get_confidence() > self.SHORTCUT_THRESHOLD)): - self._state = ProbingState.FOUND_IT - - return self.state - - def get_confidence(self): - context_conf = self.context_analyzer.get_confidence() - distrib_conf = self.distribution_analyzer.get_confidence() - return max(context_conf, distrib_conf) diff --git a/lib/chardet/universaldetector.py b/lib/chardet/universaldetector.py deleted file mode 100644 index 7b4e92d..0000000 --- a/lib/chardet/universaldetector.py +++ /dev/null @@ -1,286 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is Mozilla Universal charset detector code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 2001 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# Shy Shalom - original C code -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### -""" -Module containing the UniversalDetector detector class, which is the primary -class a user of ``chardet`` should use. - -:author: Mark Pilgrim (initial port to Python) -:author: Shy Shalom (original C code) -:author: Dan Blanchard (major refactoring for 3.0) -:author: Ian Cordasco -""" - - -import codecs -import logging -import re - -from .charsetgroupprober import CharSetGroupProber -from .enums import InputState, LanguageFilter, ProbingState -from .escprober import EscCharSetProber -from .latin1prober import Latin1Prober -from .mbcsgroupprober import MBCSGroupProber -from .sbcsgroupprober import SBCSGroupProber - - -class UniversalDetector(object): - """ - The ``UniversalDetector`` class underlies the ``chardet.detect`` function - and coordinates all of the different charset probers. - - To get a ``dict`` containing an encoding and its confidence, you can simply - run: - - .. code:: - - u = UniversalDetector() - u.feed(some_bytes) - u.close() - detected = u.result - - """ - - MINIMUM_THRESHOLD = 0.20 - HIGH_BYTE_DETECTOR = re.compile(b'[\x80-\xFF]') - ESC_DETECTOR = re.compile(b'(\033|~{)') - WIN_BYTE_DETECTOR = re.compile(b'[\x80-\x9F]') - ISO_WIN_MAP = {'iso-8859-1': 'Windows-1252', - 'iso-8859-2': 'Windows-1250', - 'iso-8859-5': 'Windows-1251', - 'iso-8859-6': 'Windows-1256', - 'iso-8859-7': 'Windows-1253', - 'iso-8859-8': 'Windows-1255', - 'iso-8859-9': 'Windows-1254', - 'iso-8859-13': 'Windows-1257'} - - def __init__(self, lang_filter=LanguageFilter.ALL): - self._esc_charset_prober = None - self._charset_probers = [] - self.result = None - self.done = None - self._got_data = None - self._input_state = None - self._last_char = None - self.lang_filter = lang_filter - self.logger = logging.getLogger(__name__) - self._has_win_bytes = None - self.reset() - - def reset(self): - """ - Reset the UniversalDetector and all of its probers back to their - initial states. This is called by ``__init__``, so you only need to - call this directly in between analyses of different documents. - """ - self.result = {'encoding': None, 'confidence': 0.0, 'language': None} - self.done = False - self._got_data = False - self._has_win_bytes = False - self._input_state = InputState.PURE_ASCII - self._last_char = b'' - if self._esc_charset_prober: - self._esc_charset_prober.reset() - for prober in self._charset_probers: - prober.reset() - - def feed(self, byte_str): - """ - Takes a chunk of a document and feeds it through all of the relevant - charset probers. - - After calling ``feed``, you can check the value of the ``done`` - attribute to see if you need to continue feeding the - ``UniversalDetector`` more data, or if it has made a prediction - (in the ``result`` attribute). - - .. note:: - You should always call ``close`` when you're done feeding in your - document if ``done`` is not already ``True``. - """ - if self.done: - return - - if not len(byte_str): - return - - if not isinstance(byte_str, bytearray): - byte_str = bytearray(byte_str) - - # First check for known BOMs, since these are guaranteed to be correct - if not self._got_data: - # If the data starts with BOM, we know it is UTF - if byte_str.startswith(codecs.BOM_UTF8): - # EF BB BF UTF-8 with BOM - self.result = {'encoding': "UTF-8-SIG", - 'confidence': 1.0, - 'language': ''} - elif byte_str.startswith((codecs.BOM_UTF32_LE, - codecs.BOM_UTF32_BE)): - # FF FE 00 00 UTF-32, little-endian BOM - # 00 00 FE FF UTF-32, big-endian BOM - self.result = {'encoding': "UTF-32", - 'confidence': 1.0, - 'language': ''} - elif byte_str.startswith(b'\xFE\xFF\x00\x00'): - # FE FF 00 00 UCS-4, unusual octet order BOM (3412) - self.result = {'encoding': "X-ISO-10646-UCS-4-3412", - 'confidence': 1.0, - 'language': ''} - elif byte_str.startswith(b'\x00\x00\xFF\xFE'): - # 00 00 FF FE UCS-4, unusual octet order BOM (2143) - self.result = {'encoding': "X-ISO-10646-UCS-4-2143", - 'confidence': 1.0, - 'language': ''} - elif byte_str.startswith((codecs.BOM_LE, codecs.BOM_BE)): - # FF FE UTF-16, little endian BOM - # FE FF UTF-16, big endian BOM - self.result = {'encoding': "UTF-16", - 'confidence': 1.0, - 'language': ''} - - self._got_data = True - if self.result['encoding'] is not None: - self.done = True - return - - # If none of those matched and we've only see ASCII so far, check - # for high bytes and escape sequences - if self._input_state == InputState.PURE_ASCII: - if self.HIGH_BYTE_DETECTOR.search(byte_str): - self._input_state = InputState.HIGH_BYTE - elif self._input_state == InputState.PURE_ASCII and \ - self.ESC_DETECTOR.search(self._last_char + byte_str): - self._input_state = InputState.ESC_ASCII - - self._last_char = byte_str[-1:] - - # If we've seen escape sequences, use the EscCharSetProber, which - # uses a simple state machine to check for known escape sequences in - # HZ and ISO-2022 encodings, since those are the only encodings that - # use such sequences. - if self._input_state == InputState.ESC_ASCII: - if not self._esc_charset_prober: - self._esc_charset_prober = EscCharSetProber(self.lang_filter) - if self._esc_charset_prober.feed(byte_str) == ProbingState.FOUND_IT: - self.result = {'encoding': - self._esc_charset_prober.charset_name, - 'confidence': - self._esc_charset_prober.get_confidence(), - 'language': - self._esc_charset_prober.language} - self.done = True - # If we've seen high bytes (i.e., those with values greater than 127), - # we need to do more complicated checks using all our multi-byte and - # single-byte probers that are left. The single-byte probers - # use character bigram distributions to determine the encoding, whereas - # the multi-byte probers use a combination of character unigram and - # bigram distributions. - elif self._input_state == InputState.HIGH_BYTE: - if not self._charset_probers: - self._charset_probers = [MBCSGroupProber(self.lang_filter)] - # If we're checking non-CJK encodings, use single-byte prober - if self.lang_filter & LanguageFilter.NON_CJK: - self._charset_probers.append(SBCSGroupProber()) - self._charset_probers.append(Latin1Prober()) - for prober in self._charset_probers: - if prober.feed(byte_str) == ProbingState.FOUND_IT: - self.result = {'encoding': prober.charset_name, - 'confidence': prober.get_confidence(), - 'language': prober.language} - self.done = True - break - if self.WIN_BYTE_DETECTOR.search(byte_str): - self._has_win_bytes = True - - def close(self): - """ - Stop analyzing the current document and come up with a final - prediction. - - :returns: The ``result`` attribute, a ``dict`` with the keys - `encoding`, `confidence`, and `language`. - """ - # Don't bother with checks if we're already done - if self.done: - return self.result - self.done = True - - if not self._got_data: - self.logger.debug('no data received!') - - # Default to ASCII if it is all we've seen so far - elif self._input_state == InputState.PURE_ASCII: - self.result = {'encoding': 'ascii', - 'confidence': 1.0, - 'language': ''} - - # If we have seen non-ASCII, return the best that met MINIMUM_THRESHOLD - elif self._input_state == InputState.HIGH_BYTE: - prober_confidence = None - max_prober_confidence = 0.0 - max_prober = None - for prober in self._charset_probers: - if not prober: - continue - prober_confidence = prober.get_confidence() - if prober_confidence > max_prober_confidence: - max_prober_confidence = prober_confidence - max_prober = prober - if max_prober and (max_prober_confidence > self.MINIMUM_THRESHOLD): - charset_name = max_prober.charset_name - lower_charset_name = max_prober.charset_name.lower() - confidence = max_prober.get_confidence() - # Use Windows encoding name instead of ISO-8859 if we saw any - # extra Windows-specific bytes - if lower_charset_name.startswith('iso-8859'): - if self._has_win_bytes: - charset_name = self.ISO_WIN_MAP.get(lower_charset_name, - charset_name) - self.result = {'encoding': charset_name, - 'confidence': confidence, - 'language': max_prober.language} - - # Log all prober confidences if none met MINIMUM_THRESHOLD - if self.logger.getEffectiveLevel() == logging.DEBUG: - if self.result['encoding'] is None: - self.logger.debug('no probers hit minimum threshold') - for group_prober in self._charset_probers: - if not group_prober: - continue - if isinstance(group_prober, CharSetGroupProber): - for prober in group_prober.probers: - self.logger.debug('%s %s confidence = %s', - prober.charset_name, - prober.language, - prober.get_confidence()) - else: - self.logger.debug('%s %s confidence = %s', - prober.charset_name, - prober.language, - prober.get_confidence()) - return self.result diff --git a/lib/chardet/utf8prober.py b/lib/chardet/utf8prober.py deleted file mode 100644 index 6c3196c..0000000 --- a/lib/chardet/utf8prober.py +++ /dev/null @@ -1,82 +0,0 @@ -######################## BEGIN LICENSE BLOCK ######################## -# The Original Code is mozilla.org code. -# -# The Initial Developer of the Original Code is -# Netscape Communications Corporation. -# Portions created by the Initial Developer are Copyright (C) 1998 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Mark Pilgrim - port to Python -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -# 02110-1301 USA -######################### END LICENSE BLOCK ######################### - -from .charsetprober import CharSetProber -from .enums import ProbingState, MachineState -from .codingstatemachine import CodingStateMachine -from .mbcssm import UTF8_SM_MODEL - - - -class UTF8Prober(CharSetProber): - ONE_CHAR_PROB = 0.5 - - def __init__(self): - super(UTF8Prober, self).__init__() - self.coding_sm = CodingStateMachine(UTF8_SM_MODEL) - self._num_mb_chars = None - self.reset() - - def reset(self): - super(UTF8Prober, self).reset() - self.coding_sm.reset() - self._num_mb_chars = 0 - - @property - def charset_name(self): - return "utf-8" - - @property - def language(self): - return "" - - def feed(self, byte_str): - for c in byte_str: - coding_state = self.coding_sm.next_state(c) - if coding_state == MachineState.ERROR: - self._state = ProbingState.NOT_ME - break - elif coding_state == MachineState.ITS_ME: - self._state = ProbingState.FOUND_IT - break - elif coding_state == MachineState.START: - if self.coding_sm.get_current_charlen() >= 2: - self._num_mb_chars += 1 - - if self.state == ProbingState.DETECTING: - if self.get_confidence() > self.SHORTCUT_THRESHOLD: - self._state = ProbingState.FOUND_IT - - return self.state - - def get_confidence(self): - unlike = 0.99 - if self._num_mb_chars < 6: - unlike *= self.ONE_CHAR_PROB ** self._num_mb_chars - return 1.0 - unlike - else: - return unlike diff --git a/lib/chardet/version.py b/lib/chardet/version.py deleted file mode 100644 index bb2a34a..0000000 --- a/lib/chardet/version.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -This module exists only to simplify retrieving the version number of chardet -from within setup.py and from chardet subpackages. - -:author: Dan Blanchard (dan.blanchard@gmail.com) -""" - -__version__ = "3.0.4" -VERSION = __version__.split('.') diff --git a/lib/configparser-3.5.0-py3.7-nspkg.pth b/lib/configparser-3.5.0-py3.7-nspkg.pth deleted file mode 100644 index 781f5d2..0000000 --- a/lib/configparser-3.5.0-py3.7-nspkg.pth +++ /dev/null @@ -1 +0,0 @@ -import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('backports',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('backports', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('backports', [os.path.dirname(p)])));m = m or sys.modules.setdefault('backports', types.ModuleType('backports'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p) diff --git a/lib/dateutil/__init__.py b/lib/dateutil/__init__.py deleted file mode 100644 index 0defb82..0000000 --- a/lib/dateutil/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -try: - from ._version import version as __version__ -except ImportError: - __version__ = 'unknown' - -__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', - 'utils', 'zoneinfo'] diff --git a/lib/dateutil/_common.py b/lib/dateutil/_common.py deleted file mode 100644 index 4eb2659..0000000 --- a/lib/dateutil/_common.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Common code used in multiple modules. -""" - - -class weekday(object): - __slots__ = ["weekday", "n"] - - def __init__(self, weekday, n=None): - self.weekday = weekday - self.n = n - - def __call__(self, n): - if n == self.n: - return self - else: - return self.__class__(self.weekday, n) - - def __eq__(self, other): - try: - if self.weekday != other.weekday or self.n != other.n: - return False - except AttributeError: - return False - return True - - def __hash__(self): - return hash(( - self.weekday, - self.n, - )) - - def __ne__(self, other): - return not (self == other) - - def __repr__(self): - s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] - if not self.n: - return s - else: - return "%s(%+d)" % (s, self.n) - -# vim:ts=4:sw=4:et diff --git a/lib/dateutil/_version.py b/lib/dateutil/_version.py deleted file mode 100644 index d3ce856..0000000 --- a/lib/dateutil/_version.py +++ /dev/null @@ -1,4 +0,0 @@ -# coding: utf-8 -# file generated by setuptools_scm -# don't change, don't track in version control -version = '2.7.5' diff --git a/lib/dateutil/easter.py b/lib/dateutil/easter.py deleted file mode 100644 index 53b7c78..0000000 --- a/lib/dateutil/easter.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This module offers a generic easter computing method for any given year, using -Western, Orthodox or Julian algorithms. -""" - -import datetime - -__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] - -EASTER_JULIAN = 1 -EASTER_ORTHODOX = 2 -EASTER_WESTERN = 3 - - -def easter(year, method=EASTER_WESTERN): - """ - This method was ported from the work done by GM Arts, - on top of the algorithm by Claus Tondering, which was - based in part on the algorithm of Ouding (1940), as - quoted in "Explanatory Supplement to the Astronomical - Almanac", P. Kenneth Seidelmann, editor. - - This algorithm implements three different easter - calculation methods: - - 1 - Original calculation in Julian calendar, valid in - dates after 326 AD - 2 - Original method, with date converted to Gregorian - calendar, valid in years 1583 to 4099 - 3 - Revised method, in Gregorian calendar, valid in - years 1583 to 4099 as well - - These methods are represented by the constants: - - * ``EASTER_JULIAN = 1`` - * ``EASTER_ORTHODOX = 2`` - * ``EASTER_WESTERN = 3`` - - The default method is method 3. - - More about the algorithm may be found at: - - `GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_ - - and - - `The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_ - - """ - - if not (1 <= method <= 3): - raise ValueError("invalid method") - - # g - Golden year - 1 - # c - Century - # h - (23 - Epact) mod 30 - # i - Number of days from March 21 to Paschal Full Moon - # j - Weekday for PFM (0=Sunday, etc) - # p - Number of days from March 21 to Sunday on or before PFM - # (-6 to 28 methods 1 & 3, to 56 for method 2) - # e - Extra days to add for method 2 (converting Julian - # date to Gregorian date) - - y = year - g = y % 19 - e = 0 - if method < 3: - # Old method - i = (19*g + 15) % 30 - j = (y + y//4 + i) % 7 - if method == 2: - # Extra dates to convert Julian to Gregorian date - e = 10 - if y > 1600: - e = e + y//100 - 16 - (y//100 - 16)//4 - else: - # New method - c = y//100 - h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30 - i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11)) - j = (y + y//4 + i + 2 - c + c//4) % 7 - - # p can be from -6 to 56 corresponding to dates 22 March to 23 May - # (later dates apply to method 2, although 23 May never actually occurs) - p = i - j + e - d = 1 + (p + 27 + (p + 6)//40) % 31 - m = 3 + (p + 26)//30 - return datetime.date(int(y), int(m), int(d)) diff --git a/lib/dateutil/parser/__init__.py b/lib/dateutil/parser/__init__.py deleted file mode 100644 index 216762c..0000000 --- a/lib/dateutil/parser/__init__.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -from ._parser import parse, parser, parserinfo -from ._parser import DEFAULTPARSER, DEFAULTTZPARSER -from ._parser import UnknownTimezoneWarning - -from ._parser import __doc__ - -from .isoparser import isoparser, isoparse - -__all__ = ['parse', 'parser', 'parserinfo', - 'isoparse', 'isoparser', - 'UnknownTimezoneWarning'] - - -### -# Deprecate portions of the private interface so that downstream code that -# is improperly relying on it is given *some* notice. - - -def __deprecated_private_func(f): - from functools import wraps - import warnings - - msg = ('{name} is a private function and may break without warning, ' - 'it will be moved and or renamed in future versions.') - msg = msg.format(name=f.__name__) - - @wraps(f) - def deprecated_func(*args, **kwargs): - warnings.warn(msg, DeprecationWarning) - return f(*args, **kwargs) - - return deprecated_func - -def __deprecate_private_class(c): - import warnings - - msg = ('{name} is a private class and may break without warning, ' - 'it will be moved and or renamed in future versions.') - msg = msg.format(name=c.__name__) - - class private_class(c): - __doc__ = c.__doc__ - - def __init__(self, *args, **kwargs): - warnings.warn(msg, DeprecationWarning) - super(private_class, self).__init__(*args, **kwargs) - - private_class.__name__ = c.__name__ - - return private_class - - -from ._parser import _timelex, _resultbase -from ._parser import _tzparser, _parsetz - -_timelex = __deprecate_private_class(_timelex) -_tzparser = __deprecate_private_class(_tzparser) -_resultbase = __deprecate_private_class(_resultbase) -_parsetz = __deprecated_private_func(_parsetz) diff --git a/lib/dateutil/parser/_parser.py b/lib/dateutil/parser/_parser.py deleted file mode 100644 index 9d2bb79..0000000 --- a/lib/dateutil/parser/_parser.py +++ /dev/null @@ -1,1578 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This module offers a generic date/time string parser which is able to parse -most known formats to represent a date and/or time. - -This module attempts to be forgiving with regards to unlikely input formats, -returning a datetime object even for dates which are ambiguous. If an element -of a date/time stamp is omitted, the following rules are applied: - -- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour - on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is - specified. -- If a time zone is omitted, a timezone-naive datetime is returned. - -If any other elements are missing, they are taken from the -:class:`datetime.datetime` object passed to the parameter ``default``. If this -results in a day number exceeding the valid number of days per month, the -value falls back to the end of the month. - -Additional resources about date/time string formats can be found below: - -- `A summary of the international standard date and time notation - <http://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_ -- `W3C Date and Time Formats <http://www.w3.org/TR/NOTE-datetime>`_ -- `Time Formats (Planetary Rings Node) <https://pds-rings.seti.org:443/tools/time_formats.html>`_ -- `CPAN ParseDate module - <http://search.cpan.org/~muir/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_ -- `Java SimpleDateFormat Class - <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_ -""" -from __future__ import unicode_literals - -import datetime -import re -import string -import time -import warnings - -from calendar import monthrange -from io import StringIO - -import six -from six import binary_type, integer_types, text_type - -from decimal import Decimal - -from warnings import warn - -from .. import relativedelta -from .. import tz - -__all__ = ["parse", "parserinfo"] - - -# TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth -# making public and/or figuring out if there is something we can -# take off their plate. -class _timelex(object): - # Fractional seconds are sometimes split by a comma - _split_decimal = re.compile("([.,])") - - def __init__(self, instream): - if six.PY2: - # In Python 2, we can't duck type properly because unicode has - # a 'decode' function, and we'd be double-decoding - if isinstance(instream, (binary_type, bytearray)): - instream = instream.decode() - else: - if getattr(instream, 'decode', None) is not None: - instream = instream.decode() - - if isinstance(instream, text_type): - instream = StringIO(instream) - elif getattr(instream, 'read', None) is None: - raise TypeError('Parser must be a string or character stream, not ' - '{itype}'.format(itype=instream.__class__.__name__)) - - self.instream = instream - self.charstack = [] - self.tokenstack = [] - self.eof = False - - def get_token(self): - """ - This function breaks the time string into lexical units (tokens), which - can be parsed by the parser. Lexical units are demarcated by changes in - the character set, so any continuous string of letters is considered - one unit, any continuous string of numbers is considered one unit. - - The main complication arises from the fact that dots ('.') can be used - both as separators (e.g. "Sep.20.2009") or decimal points (e.g. - "4:30:21.447"). As such, it is necessary to read the full context of - any dot-separated strings before breaking it into tokens; as such, this - function maintains a "token stack", for when the ambiguous context - demands that multiple tokens be parsed at once. - """ - if self.tokenstack: - return self.tokenstack.pop(0) - - seenletters = False - token = None - state = None - - while not self.eof: - # We only realize that we've reached the end of a token when we - # find a character that's not part of the current token - since - # that character may be part of the next token, it's stored in the - # charstack. - if self.charstack: - nextchar = self.charstack.pop(0) - else: - nextchar = self.instream.read(1) - while nextchar == '\x00': - nextchar = self.instream.read(1) - - if not nextchar: - self.eof = True - break - elif not state: - # First character of the token - determines if we're starting - # to parse a word, a number or something else. - token = nextchar - if self.isword(nextchar): - state = 'a' - elif self.isnum(nextchar): - state = '0' - elif self.isspace(nextchar): - token = ' ' - break # emit token - else: - break # emit token - elif state == 'a': - # If we've already started reading a word, we keep reading - # letters until we find something that's not part of a word. - seenletters = True - if self.isword(nextchar): - token += nextchar - elif nextchar == '.': - token += nextchar - state = 'a.' - else: - self.charstack.append(nextchar) - break # emit token - elif state == '0': - # If we've already started reading a number, we keep reading - # numbers until we find something that doesn't fit. - if self.isnum(nextchar): - token += nextchar - elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): - token += nextchar - state = '0.' - else: - self.charstack.append(nextchar) - break # emit token - elif state == 'a.': - # If we've seen some letters and a dot separator, continue - # parsing, and the tokens will be broken up later. - seenletters = True - if nextchar == '.' or self.isword(nextchar): - token += nextchar - elif self.isnum(nextchar) and token[-1] == '.': - token += nextchar - state = '0.' - else: - self.charstack.append(nextchar) - break # emit token - elif state == '0.': - # If we've seen at least one dot separator, keep going, we'll - # break up the tokens later. - if nextchar == '.' or self.isnum(nextchar): - token += nextchar - elif self.isword(nextchar) and token[-1] == '.': - token += nextchar - state = 'a.' - else: - self.charstack.append(nextchar) - break # emit token - - if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or - token[-1] in '.,')): - l = self._split_decimal.split(token) - token = l[0] - for tok in l[1:]: - if tok: - self.tokenstack.append(tok) - - if state == '0.' and token.count('.') == 0: - token = token.replace(',', '.') - - return token - - def __iter__(self): - return self - - def __next__(self): - token = self.get_token() - if token is None: - raise StopIteration - - return token - - def next(self): - return self.__next__() # Python 2.x support - - @classmethod - def split(cls, s): - return list(cls(s)) - - @classmethod - def isword(cls, nextchar): - """ Whether or not the next character is part of a word """ - return nextchar.isalpha() - - @classmethod - def isnum(cls, nextchar): - """ Whether the next character is part of a number """ - return nextchar.isdigit() - - @classmethod - def isspace(cls, nextchar): - """ Whether the next character is whitespace """ - return nextchar.isspace() - - -class _resultbase(object): - - def __init__(self): - for attr in self.__slots__: - setattr(self, attr, None) - - def _repr(self, classname): - l = [] - for attr in self.__slots__: - value = getattr(self, attr) - if value is not None: - l.append("%s=%s" % (attr, repr(value))) - return "%s(%s)" % (classname, ", ".join(l)) - - def __len__(self): - return (sum(getattr(self, attr) is not None - for attr in self.__slots__)) - - def __repr__(self): - return self._repr(self.__class__.__name__) - - -class parserinfo(object): - """ - Class which handles what inputs are accepted. Subclass this to customize - the language and acceptable values for each parameter. - - :param dayfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the day (``True``) or month (``False``). If - ``yearfirst`` is set to ``True``, this distinguishes between YDM - and YMD. Default is ``False``. - - :param yearfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the year. If ``True``, the first number is taken - to be the year, otherwise the last number is taken to be the year. - Default is ``False``. - """ - - # m from a.m/p.m, t from ISO T separator - JUMP = [" ", ".", ",", ";", "-", "/", "'", - "at", "on", "and", "ad", "m", "t", "of", - "st", "nd", "rd", "th"] - - WEEKDAYS = [("Mon", "Monday"), - ("Tue", "Tuesday"), # TODO: "Tues" - ("Wed", "Wednesday"), - ("Thu", "Thursday"), # TODO: "Thurs" - ("Fri", "Friday"), - ("Sat", "Saturday"), - ("Sun", "Sunday")] - MONTHS = [("Jan", "January"), - ("Feb", "February"), # TODO: "Febr" - ("Mar", "March"), - ("Apr", "April"), - ("May", "May"), - ("Jun", "June"), - ("Jul", "July"), - ("Aug", "August"), - ("Sep", "Sept", "September"), - ("Oct", "October"), - ("Nov", "November"), - ("Dec", "December")] - HMS = [("h", "hour", "hours"), - ("m", "minute", "minutes"), - ("s", "second", "seconds")] - AMPM = [("am", "a"), - ("pm", "p")] - UTCZONE = ["UTC", "GMT", "Z"] - PERTAIN = ["of"] - TZOFFSET = {} - # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate", - # "Anno Domini", "Year of Our Lord"] - - def __init__(self, dayfirst=False, yearfirst=False): - self._jump = self._convert(self.JUMP) - self._weekdays = self._convert(self.WEEKDAYS) - self._months = self._convert(self.MONTHS) - self._hms = self._convert(self.HMS) - self._ampm = self._convert(self.AMPM) - self._utczone = self._convert(self.UTCZONE) - self._pertain = self._convert(self.PERTAIN) - - self.dayfirst = dayfirst - self.yearfirst = yearfirst - - self._year = time.localtime().tm_year - self._century = self._year // 100 * 100 - - def _convert(self, lst): - dct = {} - for i, v in enumerate(lst): - if isinstance(v, tuple): - for v in v: - dct[v.lower()] = i - else: - dct[v.lower()] = i - return dct - - def jump(self, name): - return name.lower() in self._jump - - def weekday(self, name): - try: - return self._weekdays[name.lower()] - except KeyError: - pass - return None - - def month(self, name): - try: - return self._months[name.lower()] + 1 - except KeyError: - pass - return None - - def hms(self, name): - try: - return self._hms[name.lower()] - except KeyError: - return None - - def ampm(self, name): - try: - return self._ampm[name.lower()] - except KeyError: - return None - - def pertain(self, name): - return name.lower() in self._pertain - - def utczone(self, name): - return name.lower() in self._utczone - - def tzoffset(self, name): - if name in self._utczone: - return 0 - - return self.TZOFFSET.get(name) - - def convertyear(self, year, century_specified=False): - """ - Converts two-digit years to year within [-50, 49] - range of self._year (current local time) - """ - - # Function contract is that the year is always positive - assert year >= 0 - - if year < 100 and not century_specified: - # assume current century to start - year += self._century - - if year >= self._year + 50: # if too far in future - year -= 100 - elif year < self._year - 50: # if too far in past - year += 100 - - return year - - def validate(self, res): - # move to info - if res.year is not None: - res.year = self.convertyear(res.year, res.century_specified) - - if res.tzoffset == 0 and not res.tzname or res.tzname == 'Z': - res.tzname = "UTC" - res.tzoffset = 0 - elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): - res.tzoffset = 0 - return True - - -class _ymd(list): - def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) - self.century_specified = False - self.dstridx = None - self.mstridx = None - self.ystridx = None - - @property - def has_year(self): - return self.ystridx is not None - - @property - def has_month(self): - return self.mstridx is not None - - @property - def has_day(self): - return self.dstridx is not None - - def could_be_day(self, value): - if self.has_day: - return False - elif not self.has_month: - return 1 <= value <= 31 - elif not self.has_year: - # Be permissive, assume leapyear - month = self[self.mstridx] - return 1 <= value <= monthrange(2000, month)[1] - else: - month = self[self.mstridx] - year = self[self.ystridx] - return 1 <= value <= monthrange(year, month)[1] - - def append(self, val, label=None): - if hasattr(val, '__len__'): - if val.isdigit() and len(val) > 2: - self.century_specified = True - if label not in [None, 'Y']: # pragma: no cover - raise ValueError(label) - label = 'Y' - elif val > 100: - self.century_specified = True - if label not in [None, 'Y']: # pragma: no cover - raise ValueError(label) - label = 'Y' - - super(self.__class__, self).append(int(val)) - - if label == 'M': - if self.has_month: - raise ValueError('Month is already set') - self.mstridx = len(self) - 1 - elif label == 'D': - if self.has_day: - raise ValueError('Day is already set') - self.dstridx = len(self) - 1 - elif label == 'Y': - if self.has_year: - raise ValueError('Year is already set') - self.ystridx = len(self) - 1 - - def _resolve_from_stridxs(self, strids): - """ - Try to resolve the identities of year/month/day elements using - ystridx, mstridx, and dstridx, if enough of these are specified. - """ - if len(self) == 3 and len(strids) == 2: - # we can back out the remaining stridx value - missing = [x for x in range(3) if x not in strids.values()] - key = [x for x in ['y', 'm', 'd'] if x not in strids] - assert len(missing) == len(key) == 1 - key = key[0] - val = missing[0] - strids[key] = val - - assert len(self) == len(strids) # otherwise this should not be called - out = {key: self[strids[key]] for key in strids} - return (out.get('y'), out.get('m'), out.get('d')) - - def resolve_ymd(self, yearfirst, dayfirst): - len_ymd = len(self) - year, month, day = (None, None, None) - - strids = (('y', self.ystridx), - ('m', self.mstridx), - ('d', self.dstridx)) - - strids = {key: val for key, val in strids if val is not None} - if (len(self) == len(strids) > 0 or - (len(self) == 3 and len(strids) == 2)): - return self._resolve_from_stridxs(strids) - - mstridx = self.mstridx - - if len_ymd > 3: - raise ValueError("More than three YMD values") - elif len_ymd == 1 or (mstridx is not None and len_ymd == 2): - # One member, or two members with a month string - if mstridx is not None: - month = self[mstridx] - # since mstridx is 0 or 1, self[mstridx-1] always - # looks up the other element - other = self[mstridx - 1] - else: - other = self[0] - - if len_ymd > 1 or mstridx is None: - if other > 31: - year = other - else: - day = other - - elif len_ymd == 2: - # Two members with numbers - if self[0] > 31: - # 99-01 - year, month = self - elif self[1] > 31: - # 01-99 - month, year = self - elif dayfirst and self[1] <= 12: - # 13-01 - day, month = self - else: - # 01-13 - month, day = self - - elif len_ymd == 3: - # Three members - if mstridx == 0: - if self[1] > 31: - # Apr-2003-25 - month, year, day = self - else: - month, day, year = self - elif mstridx == 1: - if self[0] > 31 or (yearfirst and self[2] <= 31): - # 99-Jan-01 - year, month, day = self - else: - # 01-Jan-01 - # Give precendence to day-first, since - # two-digit years is usually hand-written. - day, month, year = self - - elif mstridx == 2: - # WTF!? - if self[1] > 31: - # 01-99-Jan - day, year, month = self - else: - # 99-01-Jan - year, day, month = self - - else: - if (self[0] > 31 or - self.ystridx == 0 or - (yearfirst and self[1] <= 12 and self[2] <= 31)): - # 99-01-01 - if dayfirst and self[2] <= 12: - year, day, month = self - else: - year, month, day = self - elif self[0] > 12 or (dayfirst and self[1] <= 12): - # 13-01-01 - day, month, year = self - else: - # 01-13-01 - month, day, year = self - - return year, month, day - - -class parser(object): - def __init__(self, info=None): - self.info = info or parserinfo() - - def parse(self, timestr, default=None, - ignoretz=False, tzinfos=None, **kwargs): - """ - Parse the date/time string into a :class:`datetime.datetime` object. - - :param timestr: - Any date/time string using the supported formats. - - :param default: - The default datetime object, if this is a datetime object and not - ``None``, elements specified in ``timestr`` replace elements in the - default object. - - :param ignoretz: - If set ``True``, time zones in parsed strings are ignored and a - naive :class:`datetime.datetime` object is returned. - - :param tzinfos: - Additional time zone names / aliases which may be present in the - string. This argument maps time zone names (and optionally offsets - from those time zones) to time zones. This parameter can be a - dictionary with timezone aliases mapping time zone names to time - zones or a function taking two parameters (``tzname`` and - ``tzoffset``) and returning a time zone. - - The timezones to which the names are mapped can be an integer - offset from UTC in seconds or a :class:`tzinfo` object. - - .. doctest:: - :options: +NORMALIZE_WHITESPACE - - >>> from dateutil.parser import parse - >>> from dateutil.tz import gettz - >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} - >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) - datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) - >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) - datetime.datetime(2012, 1, 19, 17, 21, - tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) - - This parameter is ignored if ``ignoretz`` is set. - - :param \\*\\*kwargs: - Keyword arguments as passed to ``_parse()``. - - :return: - Returns a :class:`datetime.datetime` object or, if the - ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the - first element being a :class:`datetime.datetime` object, the second - a tuple containing the fuzzy tokens. - - :raises ValueError: - Raised for invalid or unknown string format, if the provided - :class:`tzinfo` is not in a valid format, or if an invalid date - would be created. - - :raises TypeError: - Raised for non-string or character stream input. - - :raises OverflowError: - Raised if the parsed date exceeds the largest valid C integer on - your system. - """ - - if default is None: - default = datetime.datetime.now().replace(hour=0, minute=0, - second=0, microsecond=0) - - res, skipped_tokens = self._parse(timestr, **kwargs) - - if res is None: - raise ValueError("Unknown string format:", timestr) - - if len(res) == 0: - raise ValueError("String does not contain a date:", timestr) - - ret = self._build_naive(res, default) - - if not ignoretz: - ret = self._build_tzaware(ret, res, tzinfos) - - if kwargs.get('fuzzy_with_tokens', False): - return ret, skipped_tokens - else: - return ret - - class _result(_resultbase): - __slots__ = ["year", "month", "day", "weekday", - "hour", "minute", "second", "microsecond", - "tzname", "tzoffset", "ampm","any_unused_tokens"] - - def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, - fuzzy_with_tokens=False): - """ - Private method which performs the heavy lifting of parsing, called from - ``parse()``, which passes on its ``kwargs`` to this function. - - :param timestr: - The string to parse. - - :param dayfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the day (``True``) or month (``False``). If - ``yearfirst`` is set to ``True``, this distinguishes between YDM - and YMD. If set to ``None``, this value is retrieved from the - current :class:`parserinfo` object (which itself defaults to - ``False``). - - :param yearfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the year. If ``True``, the first number is taken - to be the year, otherwise the last number is taken to be the year. - If this is set to ``None``, the value is retrieved from the current - :class:`parserinfo` object (which itself defaults to ``False``). - - :param fuzzy: - Whether to allow fuzzy parsing, allowing for string like "Today is - January 1, 2047 at 8:21:00AM". - - :param fuzzy_with_tokens: - If ``True``, ``fuzzy`` is automatically set to True, and the parser - will return a tuple where the first element is the parsed - :class:`datetime.datetime` datetimestamp and the second element is - a tuple containing the portions of the string which were ignored: - - .. doctest:: - - >>> from dateutil.parser import parse - >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) - (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) - - """ - if fuzzy_with_tokens: - fuzzy = True - - info = self.info - - if dayfirst is None: - dayfirst = info.dayfirst - - if yearfirst is None: - yearfirst = info.yearfirst - - res = self._result() - l = _timelex.split(timestr) # Splits the timestr into tokens - - skipped_idxs = [] - - # year/month/day list - ymd = _ymd() - - len_l = len(l) - i = 0 - try: - while i < len_l: - - # Check if it's a number - value_repr = l[i] - try: - value = float(value_repr) - except ValueError: - value = None - - if value is not None: - # Numeric token - i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy) - - # Check weekday - elif info.weekday(l[i]) is not None: - value = info.weekday(l[i]) - res.weekday = value - - # Check month name - elif info.month(l[i]) is not None: - value = info.month(l[i]) - ymd.append(value, 'M') - - if i + 1 < len_l: - if l[i + 1] in ('-', '/'): - # Jan-01[-99] - sep = l[i + 1] - ymd.append(l[i + 2]) - - if i + 3 < len_l and l[i + 3] == sep: - # Jan-01-99 - ymd.append(l[i + 4]) - i += 2 - - i += 2 - - elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and - info.pertain(l[i + 2])): - # Jan of 01 - # In this case, 01 is clearly year - if l[i + 4].isdigit(): - # Convert it here to become unambiguous - value = int(l[i + 4]) - year = str(info.convertyear(value)) - ymd.append(year, 'Y') - else: - # Wrong guess - pass - # TODO: not hit in tests - i += 4 - - # Check am/pm - elif info.ampm(l[i]) is not None: - value = info.ampm(l[i]) - val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy) - - if val_is_ampm: - res.hour = self._adjust_ampm(res.hour, value) - res.ampm = value - - elif fuzzy: - skipped_idxs.append(i) - - # Check for a timezone name - elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]): - res.tzname = l[i] - res.tzoffset = info.tzoffset(res.tzname) - - # Check for something like GMT+3, or BRST+3. Notice - # that it doesn't mean "I am 3 hours after GMT", but - # "my time +3 is GMT". If found, we reverse the - # logic so that timezone parsing code will get it - # right. - if i + 1 < len_l and l[i + 1] in ('+', '-'): - l[i + 1] = ('+', '-')[l[i + 1] == '+'] - res.tzoffset = None - if info.utczone(res.tzname): - # With something like GMT+3, the timezone - # is *not* GMT. - res.tzname = None - - # Check for a numbered timezone - elif res.hour is not None and l[i] in ('+', '-'): - signal = (-1, 1)[l[i] == '+'] - len_li = len(l[i + 1]) - - # TODO: check that l[i + 1] is integer? - if len_li == 4: - # -0300 - hour_offset = int(l[i + 1][:2]) - min_offset = int(l[i + 1][2:]) - elif i + 2 < len_l and l[i + 2] == ':': - # -03:00 - hour_offset = int(l[i + 1]) - min_offset = int(l[i + 3]) # TODO: Check that l[i+3] is minute-like? - i += 2 - elif len_li <= 2: - # -[0]3 - hour_offset = int(l[i + 1][:2]) - min_offset = 0 - else: - raise ValueError(timestr) - - res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60) - - # Look for a timezone name between parenthesis - if (i + 5 < len_l and - info.jump(l[i + 2]) and l[i + 3] == '(' and - l[i + 5] == ')' and - 3 <= len(l[i + 4]) and - self._could_be_tzname(res.hour, res.tzname, - None, l[i + 4])): - # -0300 (BRST) - res.tzname = l[i + 4] - i += 4 - - i += 1 - - # Check jumps - elif not (info.jump(l[i]) or fuzzy): - raise ValueError(timestr) - - else: - skipped_idxs.append(i) - i += 1 - - # Process year/month/day - year, month, day = ymd.resolve_ymd(yearfirst, dayfirst) - - res.century_specified = ymd.century_specified - res.year = year - res.month = month - res.day = day - - except (IndexError, ValueError): - return None, None - - if not info.validate(res): - return None, None - - if fuzzy_with_tokens: - skipped_tokens = self._recombine_skipped(l, skipped_idxs) - return res, tuple(skipped_tokens) - else: - return res, None - - def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy): - # Token is a number - value_repr = tokens[idx] - try: - value = self._to_decimal(value_repr) - except Exception as e: - six.raise_from(ValueError('Unknown numeric token'), e) - - len_li = len(value_repr) - - len_l = len(tokens) - - if (len(ymd) == 3 and len_li in (2, 4) and - res.hour is None and - (idx + 1 >= len_l or - (tokens[idx + 1] != ':' and - info.hms(tokens[idx + 1]) is None))): - # 19990101T23[59] - s = tokens[idx] - res.hour = int(s[:2]) - - if len_li == 4: - res.minute = int(s[2:]) - - elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6): - # YYMMDD or HHMMSS[.ss] - s = tokens[idx] - - if not ymd and '.' not in tokens[idx]: - ymd.append(s[:2]) - ymd.append(s[2:4]) - ymd.append(s[4:]) - else: - # 19990101T235959[.59] - - # TODO: Check if res attributes already set. - res.hour = int(s[:2]) - res.minute = int(s[2:4]) - res.second, res.microsecond = self._parsems(s[4:]) - - elif len_li in (8, 12, 14): - # YYYYMMDD - s = tokens[idx] - ymd.append(s[:4], 'Y') - ymd.append(s[4:6]) - ymd.append(s[6:8]) - - if len_li > 8: - res.hour = int(s[8:10]) - res.minute = int(s[10:12]) - - if len_li > 12: - res.second = int(s[12:]) - - elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None: - # HH[ ]h or MM[ ]m or SS[.ss][ ]s - hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True) - (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx) - if hms is not None: - # TODO: checking that hour/minute/second are not - # already set? - self._assign_hms(res, value_repr, hms) - - elif idx + 2 < len_l and tokens[idx + 1] == ':': - # HH:MM[:SS[.ss]] - res.hour = int(value) - value = self._to_decimal(tokens[idx + 2]) # TODO: try/except for this? - (res.minute, res.second) = self._parse_min_sec(value) - - if idx + 4 < len_l and tokens[idx + 3] == ':': - res.second, res.microsecond = self._parsems(tokens[idx + 4]) - - idx += 2 - - idx += 2 - - elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'): - sep = tokens[idx + 1] - ymd.append(value_repr) - - if idx + 2 < len_l and not info.jump(tokens[idx + 2]): - if tokens[idx + 2].isdigit(): - # 01-01[-01] - ymd.append(tokens[idx + 2]) - else: - # 01-Jan[-01] - value = info.month(tokens[idx + 2]) - - if value is not None: - ymd.append(value, 'M') - else: - raise ValueError() - - if idx + 3 < len_l and tokens[idx + 3] == sep: - # We have three members - value = info.month(tokens[idx + 4]) - - if value is not None: - ymd.append(value, 'M') - else: - ymd.append(tokens[idx + 4]) - idx += 2 - - idx += 1 - idx += 1 - - elif idx + 1 >= len_l or info.jump(tokens[idx + 1]): - if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None: - # 12 am - hour = int(value) - res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2])) - idx += 1 - else: - # Year, month or day - ymd.append(value) - idx += 1 - - elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24): - # 12am - hour = int(value) - res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1])) - idx += 1 - - elif ymd.could_be_day(value): - ymd.append(value) - - elif not fuzzy: - raise ValueError() - - return idx - - def _find_hms_idx(self, idx, tokens, info, allow_jump): - len_l = len(tokens) - - if idx+1 < len_l and info.hms(tokens[idx+1]) is not None: - # There is an "h", "m", or "s" label following this token. We take - # assign the upcoming label to the current token. - # e.g. the "12" in 12h" - hms_idx = idx + 1 - - elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and - info.hms(tokens[idx+2]) is not None): - # There is a space and then an "h", "m", or "s" label. - # e.g. the "12" in "12 h" - hms_idx = idx + 2 - - elif idx > 0 and info.hms(tokens[idx-1]) is not None: - # There is a "h", "m", or "s" preceeding this token. Since neither - # of the previous cases was hit, there is no label following this - # token, so we use the previous label. - # e.g. the "04" in "12h04" - hms_idx = idx-1 - - elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and - info.hms(tokens[idx-2]) is not None): - # If we are looking at the final token, we allow for a - # backward-looking check to skip over a space. - # TODO: Are we sure this is the right condition here? - hms_idx = idx - 2 - - else: - hms_idx = None - - return hms_idx - - def _assign_hms(self, res, value_repr, hms): - # See GH issue #427, fixing float rounding - value = self._to_decimal(value_repr) - - if hms == 0: - # Hour - res.hour = int(value) - if value % 1: - res.minute = int(60*(value % 1)) - - elif hms == 1: - (res.minute, res.second) = self._parse_min_sec(value) - - elif hms == 2: - (res.second, res.microsecond) = self._parsems(value_repr) - - def _could_be_tzname(self, hour, tzname, tzoffset, token): - return (hour is not None and - tzname is None and - tzoffset is None and - len(token) <= 5 and - all(x in string.ascii_uppercase for x in token)) - - def _ampm_valid(self, hour, ampm, fuzzy): - """ - For fuzzy parsing, 'a' or 'am' (both valid English words) - may erroneously trigger the AM/PM flag. Deal with that - here. - """ - val_is_ampm = True - - # If there's already an AM/PM flag, this one isn't one. - if fuzzy and ampm is not None: - val_is_ampm = False - - # If AM/PM is found and hour is not, raise a ValueError - if hour is None: - if fuzzy: - val_is_ampm = False - else: - raise ValueError('No hour specified with AM or PM flag.') - elif not 0 <= hour <= 12: - # If AM/PM is found, it's a 12 hour clock, so raise - # an error for invalid range - if fuzzy: - val_is_ampm = False - else: - raise ValueError('Invalid hour specified for 12-hour clock.') - - return val_is_ampm - - def _adjust_ampm(self, hour, ampm): - if hour < 12 and ampm == 1: - hour += 12 - elif hour == 12 and ampm == 0: - hour = 0 - return hour - - def _parse_min_sec(self, value): - # TODO: Every usage of this function sets res.second to the return - # value. Are there any cases where second will be returned as None and - # we *dont* want to set res.second = None? - minute = int(value) - second = None - - sec_remainder = value % 1 - if sec_remainder: - second = int(60 * sec_remainder) - return (minute, second) - - def _parsems(self, value): - """Parse a I[.F] seconds value into (seconds, microseconds).""" - if "." not in value: - return int(value), 0 - else: - i, f = value.split(".") - return int(i), int(f.ljust(6, "0")[:6]) - - def _parse_hms(self, idx, tokens, info, hms_idx): - # TODO: Is this going to admit a lot of false-positives for when we - # just happen to have digits and "h", "m" or "s" characters in non-date - # text? I guess hex hashes won't have that problem, but there's plenty - # of random junk out there. - if hms_idx is None: - hms = None - new_idx = idx - elif hms_idx > idx: - hms = info.hms(tokens[hms_idx]) - new_idx = hms_idx - else: - # Looking backwards, increment one. - hms = info.hms(tokens[hms_idx]) + 1 - new_idx = idx - - return (new_idx, hms) - - def _recombine_skipped(self, tokens, skipped_idxs): - """ - >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"] - >>> skipped_idxs = [0, 1, 2, 5] - >>> _recombine_skipped(tokens, skipped_idxs) - ["foo bar", "baz"] - """ - skipped_tokens = [] - for i, idx in enumerate(sorted(skipped_idxs)): - if i > 0 and idx - 1 == skipped_idxs[i - 1]: - skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx] - else: - skipped_tokens.append(tokens[idx]) - - return skipped_tokens - - def _build_tzinfo(self, tzinfos, tzname, tzoffset): - if callable(tzinfos): - tzdata = tzinfos(tzname, tzoffset) - else: - tzdata = tzinfos.get(tzname) - # handle case where tzinfo is paased an options that returns None - # eg tzinfos = {'BRST' : None} - if isinstance(tzdata, datetime.tzinfo) or tzdata is None: - tzinfo = tzdata - elif isinstance(tzdata, text_type): - tzinfo = tz.tzstr(tzdata) - elif isinstance(tzdata, integer_types): - tzinfo = tz.tzoffset(tzname, tzdata) - return tzinfo - - def _build_tzaware(self, naive, res, tzinfos): - if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)): - tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset) - aware = naive.replace(tzinfo=tzinfo) - aware = self._assign_tzname(aware, res.tzname) - - elif res.tzname and res.tzname in time.tzname: - aware = naive.replace(tzinfo=tz.tzlocal()) - - # Handle ambiguous local datetime - aware = self._assign_tzname(aware, res.tzname) - - # This is mostly relevant for winter GMT zones parsed in the UK - if (aware.tzname() != res.tzname and - res.tzname in self.info.UTCZONE): - aware = aware.replace(tzinfo=tz.tzutc()) - - elif res.tzoffset == 0: - aware = naive.replace(tzinfo=tz.tzutc()) - - elif res.tzoffset: - aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) - - elif not res.tzname and not res.tzoffset: - # i.e. no timezone information was found. - aware = naive - - elif res.tzname: - # tz-like string was parsed but we don't know what to do - # with it - warnings.warn("tzname {tzname} identified but not understood. " - "Pass `tzinfos` argument in order to correctly " - "return a timezone-aware datetime. In a future " - "version, this will raise an " - "exception.".format(tzname=res.tzname), - category=UnknownTimezoneWarning) - aware = naive - - return aware - - def _build_naive(self, res, default): - repl = {} - for attr in ("year", "month", "day", "hour", - "minute", "second", "microsecond"): - value = getattr(res, attr) - if value is not None: - repl[attr] = value - - if 'day' not in repl: - # If the default day exceeds the last day of the month, fall back - # to the end of the month. - cyear = default.year if res.year is None else res.year - cmonth = default.month if res.month is None else res.month - cday = default.day if res.day is None else res.day - - if cday > monthrange(cyear, cmonth)[1]: - repl['day'] = monthrange(cyear, cmonth)[1] - - naive = default.replace(**repl) - - if res.weekday is not None and not res.day: - naive = naive + relativedelta.relativedelta(weekday=res.weekday) - - return naive - - def _assign_tzname(self, dt, tzname): - if dt.tzname() != tzname: - new_dt = tz.enfold(dt, fold=1) - if new_dt.tzname() == tzname: - return new_dt - - return dt - - def _to_decimal(self, val): - try: - decimal_value = Decimal(val) - # See GH 662, edge case, infinite value should not be converted via `_to_decimal` - if not decimal_value.is_finite(): - raise ValueError("Converted decimal value is infinite or NaN") - except Exception as e: - msg = "Could not convert %s to decimal" % val - six.raise_from(ValueError(msg), e) - else: - return decimal_value - - -DEFAULTPARSER = parser() - - -def parse(timestr, parserinfo=None, **kwargs): - """ - - Parse a string in one of the supported formats, using the - ``parserinfo`` parameters. - - :param timestr: - A string containing a date/time stamp. - - :param parserinfo: - A :class:`parserinfo` object containing parameters for the parser. - If ``None``, the default arguments to the :class:`parserinfo` - constructor are used. - - The ``**kwargs`` parameter takes the following keyword arguments: - - :param default: - The default datetime object, if this is a datetime object and not - ``None``, elements specified in ``timestr`` replace elements in the - default object. - - :param ignoretz: - If set ``True``, time zones in parsed strings are ignored and a naive - :class:`datetime` object is returned. - - :param tzinfos: - Additional time zone names / aliases which may be present in the - string. This argument maps time zone names (and optionally offsets - from those time zones) to time zones. This parameter can be a - dictionary with timezone aliases mapping time zone names to time - zones or a function taking two parameters (``tzname`` and - ``tzoffset``) and returning a time zone. - - The timezones to which the names are mapped can be an integer - offset from UTC in seconds or a :class:`tzinfo` object. - - .. doctest:: - :options: +NORMALIZE_WHITESPACE - - >>> from dateutil.parser import parse - >>> from dateutil.tz import gettz - >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} - >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) - datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) - >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) - datetime.datetime(2012, 1, 19, 17, 21, - tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) - - This parameter is ignored if ``ignoretz`` is set. - - :param dayfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the day (``True``) or month (``False``). If - ``yearfirst`` is set to ``True``, this distinguishes between YDM and - YMD. If set to ``None``, this value is retrieved from the current - :class:`parserinfo` object (which itself defaults to ``False``). - - :param yearfirst: - Whether to interpret the first value in an ambiguous 3-integer date - (e.g. 01/05/09) as the year. If ``True``, the first number is taken to - be the year, otherwise the last number is taken to be the year. If - this is set to ``None``, the value is retrieved from the current - :class:`parserinfo` object (which itself defaults to ``False``). - - :param fuzzy: - Whether to allow fuzzy parsing, allowing for string like "Today is - January 1, 2047 at 8:21:00AM". - - :param fuzzy_with_tokens: - If ``True``, ``fuzzy`` is automatically set to True, and the parser - will return a tuple where the first element is the parsed - :class:`datetime.datetime` datetimestamp and the second element is - a tuple containing the portions of the string which were ignored: - - .. doctest:: - - >>> from dateutil.parser import parse - >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) - (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) - - :return: - Returns a :class:`datetime.datetime` object or, if the - ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the - first element being a :class:`datetime.datetime` object, the second - a tuple containing the fuzzy tokens. - - :raises ValueError: - Raised for invalid or unknown string format, if the provided - :class:`tzinfo` is not in a valid format, or if an invalid date - would be created. - - :raises OverflowError: - Raised if the parsed date exceeds the largest valid C integer on - your system. - """ - if parserinfo: - return parser(parserinfo).parse(timestr, **kwargs) - else: - return DEFAULTPARSER.parse(timestr, **kwargs) - - -class _tzparser(object): - - class _result(_resultbase): - - __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", - "start", "end"] - - class _attr(_resultbase): - __slots__ = ["month", "week", "weekday", - "yday", "jyday", "day", "time"] - - def __repr__(self): - return self._repr("") - - def __init__(self): - _resultbase.__init__(self) - self.start = self._attr() - self.end = self._attr() - - def parse(self, tzstr): - res = self._result() - l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x] - used_idxs = list() - try: - - len_l = len(l) - - i = 0 - while i < len_l: - # BRST+3[BRDT[+2]] - j = i - while j < len_l and not [x for x in l[j] - if x in "0123456789:,-+"]: - j += 1 - if j != i: - if not res.stdabbr: - offattr = "stdoffset" - res.stdabbr = "".join(l[i:j]) - else: - offattr = "dstoffset" - res.dstabbr = "".join(l[i:j]) - - for ii in range(j): - used_idxs.append(ii) - i = j - if (i < len_l and (l[i] in ('+', '-') or l[i][0] in - "0123456789")): - if l[i] in ('+', '-'): - # Yes, that's right. See the TZ variable - # documentation. - signal = (1, -1)[l[i] == '+'] - used_idxs.append(i) - i += 1 - else: - signal = -1 - len_li = len(l[i]) - if len_li == 4: - # -0300 - setattr(res, offattr, (int(l[i][:2]) * 3600 + - int(l[i][2:]) * 60) * signal) - elif i + 1 < len_l and l[i + 1] == ':': - # -03:00 - setattr(res, offattr, - (int(l[i]) * 3600 + - int(l[i + 2]) * 60) * signal) - used_idxs.append(i) - i += 2 - elif len_li <= 2: - # -[0]3 - setattr(res, offattr, - int(l[i][:2]) * 3600 * signal) - else: - return None - used_idxs.append(i) - i += 1 - if res.dstabbr: - break - else: - break - - - if i < len_l: - for j in range(i, len_l): - if l[j] == ';': - l[j] = ',' - - assert l[i] == ',' - - i += 1 - - if i >= len_l: - pass - elif (8 <= l.count(',') <= 9 and - not [y for x in l[i:] if x != ',' - for y in x if y not in "0123456789+-"]): - # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] - for x in (res.start, res.end): - x.month = int(l[i]) - used_idxs.append(i) - i += 2 - if l[i] == '-': - value = int(l[i + 1]) * -1 - used_idxs.append(i) - i += 1 - else: - value = int(l[i]) - used_idxs.append(i) - i += 2 - if value: - x.week = value - x.weekday = (int(l[i]) - 1) % 7 - else: - x.day = int(l[i]) - used_idxs.append(i) - i += 2 - x.time = int(l[i]) - used_idxs.append(i) - i += 2 - if i < len_l: - if l[i] in ('-', '+'): - signal = (-1, 1)[l[i] == "+"] - used_idxs.append(i) - i += 1 - else: - signal = 1 - used_idxs.append(i) - res.dstoffset = (res.stdoffset + int(l[i]) * signal) - - # This was a made-up format that is not in normal use - warn(('Parsed time zone "%s"' % tzstr) + - 'is in a non-standard dateutil-specific format, which ' + - 'is now deprecated; support for parsing this format ' + - 'will be removed in future versions. It is recommended ' + - 'that you switch to a standard format like the GNU ' + - 'TZ variable format.', tz.DeprecatedTzFormatWarning) - elif (l.count(',') == 2 and l[i:].count('/') <= 2 and - not [y for x in l[i:] if x not in (',', '/', 'J', 'M', - '.', '-', ':') - for y in x if y not in "0123456789"]): - for x in (res.start, res.end): - if l[i] == 'J': - # non-leap year day (1 based) - used_idxs.append(i) - i += 1 - x.jyday = int(l[i]) - elif l[i] == 'M': - # month[-.]week[-.]weekday - used_idxs.append(i) - i += 1 - x.month = int(l[i]) - used_idxs.append(i) - i += 1 - assert l[i] in ('-', '.') - used_idxs.append(i) - i += 1 - x.week = int(l[i]) - if x.week == 5: - x.week = -1 - used_idxs.append(i) - i += 1 - assert l[i] in ('-', '.') - used_idxs.append(i) - i += 1 - x.weekday = (int(l[i]) - 1) % 7 - else: - # year day (zero based) - x.yday = int(l[i]) + 1 - - used_idxs.append(i) - i += 1 - - if i < len_l and l[i] == '/': - used_idxs.append(i) - i += 1 - # start time - len_li = len(l[i]) - if len_li == 4: - # -0300 - x.time = (int(l[i][:2]) * 3600 + - int(l[i][2:]) * 60) - elif i + 1 < len_l and l[i + 1] == ':': - # -03:00 - x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60 - used_idxs.append(i) - i += 2 - if i + 1 < len_l and l[i + 1] == ':': - used_idxs.append(i) - i += 2 - x.time += int(l[i]) - elif len_li <= 2: - # -[0]3 - x.time = (int(l[i][:2]) * 3600) - else: - return None - used_idxs.append(i) - i += 1 - - assert i == len_l or l[i] == ',' - - i += 1 - - assert i >= len_l - - except (IndexError, ValueError, AssertionError): - return None - - unused_idxs = set(range(len_l)).difference(used_idxs) - res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"}) - return res - - -DEFAULTTZPARSER = _tzparser() - - -def _parsetz(tzstr): - return DEFAULTTZPARSER.parse(tzstr) - -class UnknownTimezoneWarning(RuntimeWarning): - """Raised when the parser finds a timezone it cannot parse into a tzinfo""" -# vim:ts=4:sw=4:et diff --git a/lib/dateutil/parser/isoparser.py b/lib/dateutil/parser/isoparser.py deleted file mode 100644 index cd27f93..0000000 --- a/lib/dateutil/parser/isoparser.py +++ /dev/null @@ -1,406 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This module offers a parser for ISO-8601 strings - -It is intended to support all valid date, time and datetime formats per the -ISO-8601 specification. - -..versionadded:: 2.7.0 -""" -from datetime import datetime, timedelta, time, date -import calendar -from dateutil import tz - -from functools import wraps - -import re -import six - -__all__ = ["isoparse", "isoparser"] - - -def _takes_ascii(f): - @wraps(f) - def func(self, str_in, *args, **kwargs): - # If it's a stream, read the whole thing - str_in = getattr(str_in, 'read', lambda: str_in)() - - # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII - if isinstance(str_in, six.text_type): - # ASCII is the same in UTF-8 - try: - str_in = str_in.encode('ascii') - except UnicodeEncodeError as e: - msg = 'ISO-8601 strings should contain only ASCII characters' - six.raise_from(ValueError(msg), e) - - return f(self, str_in, *args, **kwargs) - - return func - - -class isoparser(object): - def __init__(self, sep=None): - """ - :param sep: - A single character that separates date and time portions. If - ``None``, the parser will accept any single character. - For strict ISO-8601 adherence, pass ``'T'``. - """ - if sep is not None: - if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'): - raise ValueError('Separator must be a single, non-numeric ' + - 'ASCII character') - - sep = sep.encode('ascii') - - self._sep = sep - - @_takes_ascii - def isoparse(self, dt_str): - """ - Parse an ISO-8601 datetime string into a :class:`datetime.datetime`. - - An ISO-8601 datetime string consists of a date portion, followed - optionally by a time portion - the date and time portions are separated - by a single character separator, which is ``T`` in the official - standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be - combined with a time portion. - - Supported date formats are: - - Common: - - - ``YYYY`` - - ``YYYY-MM`` or ``YYYYMM`` - - ``YYYY-MM-DD`` or ``YYYYMMDD`` - - Uncommon: - - - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0) - - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day - - The ISO week and day numbering follows the same logic as - :func:`datetime.date.isocalendar`. - - Supported time formats are: - - - ``hh`` - - ``hh:mm`` or ``hhmm`` - - ``hh:mm:ss`` or ``hhmmss`` - - ``hh:mm:ss.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits) - - Midnight is a special case for `hh`, as the standard supports both - 00:00 and 24:00 as a representation. - - .. caution:: - - Support for fractional components other than seconds is part of the - ISO-8601 standard, but is not currently implemented in this parser. - - Supported time zone offset formats are: - - - `Z` (UTC) - - `±HH:MM` - - `±HHMM` - - `±HH` - - Offsets will be represented as :class:`dateutil.tz.tzoffset` objects, - with the exception of UTC, which will be represented as - :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such - as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`. - - :param dt_str: - A string or stream containing only an ISO-8601 datetime string - - :return: - Returns a :class:`datetime.datetime` representing the string. - Unspecified components default to their lowest value. - - .. warning:: - - As of version 2.7.0, the strictness of the parser should not be - considered a stable part of the contract. Any valid ISO-8601 string - that parses correctly with the default settings will continue to - parse correctly in future versions, but invalid strings that - currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not - guaranteed to continue failing in future versions if they encode - a valid date. - - .. versionadded:: 2.7.0 - """ - components, pos = self._parse_isodate(dt_str) - - if len(dt_str) > pos: - if self._sep is None or dt_str[pos:pos + 1] == self._sep: - components += self._parse_isotime(dt_str[pos + 1:]) - else: - raise ValueError('String contains unknown ISO components') - - return datetime(*components) - - @_takes_ascii - def parse_isodate(self, datestr): - """ - Parse the date portion of an ISO string. - - :param datestr: - The string portion of an ISO string, without a separator - - :return: - Returns a :class:`datetime.date` object - """ - components, pos = self._parse_isodate(datestr) - if pos < len(datestr): - raise ValueError('String contains unknown ISO ' + - 'components: {}'.format(datestr)) - return date(*components) - - @_takes_ascii - def parse_isotime(self, timestr): - """ - Parse the time portion of an ISO string. - - :param timestr: - The time portion of an ISO string, without a separator - - :return: - Returns a :class:`datetime.time` object - """ - return time(*self._parse_isotime(timestr)) - - @_takes_ascii - def parse_tzstr(self, tzstr, zero_as_utc=True): - """ - Parse a valid ISO time zone string. - - See :func:`isoparser.isoparse` for details on supported formats. - - :param tzstr: - A string representing an ISO time zone offset - - :param zero_as_utc: - Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones - - :return: - Returns :class:`dateutil.tz.tzoffset` for offsets and - :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is - specified) offsets equivalent to UTC. - """ - return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc) - - # Constants - _MICROSECOND_END_REGEX = re.compile(b'[-+Z]+') - _DATE_SEP = b'-' - _TIME_SEP = b':' - _MICRO_SEP = b'.' - - def _parse_isodate(self, dt_str): - try: - return self._parse_isodate_common(dt_str) - except ValueError: - return self._parse_isodate_uncommon(dt_str) - - def _parse_isodate_common(self, dt_str): - len_str = len(dt_str) - components = [1, 1, 1] - - if len_str < 4: - raise ValueError('ISO string too short') - - # Year - components[0] = int(dt_str[0:4]) - pos = 4 - if pos >= len_str: - return components, pos - - has_sep = dt_str[pos:pos + 1] == self._DATE_SEP - if has_sep: - pos += 1 - - # Month - if len_str - pos < 2: - raise ValueError('Invalid common month') - - components[1] = int(dt_str[pos:pos + 2]) - pos += 2 - - if pos >= len_str: - if has_sep: - return components, pos - else: - raise ValueError('Invalid ISO format') - - if has_sep: - if dt_str[pos:pos + 1] != self._DATE_SEP: - raise ValueError('Invalid separator in ISO string') - pos += 1 - - # Day - if len_str - pos < 2: - raise ValueError('Invalid common day') - components[2] = int(dt_str[pos:pos + 2]) - return components, pos + 2 - - def _parse_isodate_uncommon(self, dt_str): - if len(dt_str) < 4: - raise ValueError('ISO string too short') - - # All ISO formats start with the year - year = int(dt_str[0:4]) - - has_sep = dt_str[4:5] == self._DATE_SEP - - pos = 4 + has_sep # Skip '-' if it's there - if dt_str[pos:pos + 1] == b'W': - # YYYY-?Www-?D? - pos += 1 - weekno = int(dt_str[pos:pos + 2]) - pos += 2 - - dayno = 1 - if len(dt_str) > pos: - if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep: - raise ValueError('Inconsistent use of dash separator') - - pos += has_sep - - dayno = int(dt_str[pos:pos + 1]) - pos += 1 - - base_date = self._calculate_weekdate(year, weekno, dayno) - else: - # YYYYDDD or YYYY-DDD - if len(dt_str) - pos < 3: - raise ValueError('Invalid ordinal day') - - ordinal_day = int(dt_str[pos:pos + 3]) - pos += 3 - - if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)): - raise ValueError('Invalid ordinal day' + - ' {} for year {}'.format(ordinal_day, year)) - - base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1) - - components = [base_date.year, base_date.month, base_date.day] - return components, pos - - def _calculate_weekdate(self, year, week, day): - """ - Calculate the day of corresponding to the ISO year-week-day calendar. - - This function is effectively the inverse of - :func:`datetime.date.isocalendar`. - - :param year: - The year in the ISO calendar - - :param week: - The week in the ISO calendar - range is [1, 53] - - :param day: - The day in the ISO calendar - range is [1 (MON), 7 (SUN)] - - :return: - Returns a :class:`datetime.date` - """ - if not 0 < week < 54: - raise ValueError('Invalid week: {}'.format(week)) - - if not 0 < day < 8: # Range is 1-7 - raise ValueError('Invalid weekday: {}'.format(day)) - - # Get week 1 for the specific year: - jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it - week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1) - - # Now add the specific number of weeks and days to get what we want - week_offset = (week - 1) * 7 + (day - 1) - return week_1 + timedelta(days=week_offset) - - def _parse_isotime(self, timestr): - len_str = len(timestr) - components = [0, 0, 0, 0, None] - pos = 0 - comp = -1 - - if len(timestr) < 2: - raise ValueError('ISO time too short') - - has_sep = len_str >= 3 and timestr[2:3] == self._TIME_SEP - - while pos < len_str and comp < 5: - comp += 1 - - if timestr[pos:pos + 1] in b'-+Z': - # Detect time zone boundary - components[-1] = self._parse_tzstr(timestr[pos:]) - pos = len_str - break - - if comp < 3: - # Hour, minute, second - components[comp] = int(timestr[pos:pos + 2]) - pos += 2 - if (has_sep and pos < len_str and - timestr[pos:pos + 1] == self._TIME_SEP): - pos += 1 - - if comp == 3: - # Microsecond - if timestr[pos:pos + 1] != self._MICRO_SEP: - continue - - pos += 1 - us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6], - 1)[0] - - components[comp] = int(us_str) * 10**(6 - len(us_str)) - pos += len(us_str) - - if pos < len_str: - raise ValueError('Unused components in ISO string') - - if components[0] == 24: - # Standard supports 00:00 and 24:00 as representations of midnight - if any(component != 0 for component in components[1:4]): - raise ValueError('Hour may only be 24 at 24:00:00.000') - components[0] = 0 - - return components - - def _parse_tzstr(self, tzstr, zero_as_utc=True): - if tzstr == b'Z': - return tz.tzutc() - - if len(tzstr) not in {3, 5, 6}: - raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters') - - if tzstr[0:1] == b'-': - mult = -1 - elif tzstr[0:1] == b'+': - mult = 1 - else: - raise ValueError('Time zone offset requires sign') - - hours = int(tzstr[1:3]) - if len(tzstr) == 3: - minutes = 0 - else: - minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):]) - - if zero_as_utc and hours == 0 and minutes == 0: - return tz.tzutc() - else: - if minutes > 59: - raise ValueError('Invalid minutes in time zone offset') - - if hours > 23: - raise ValueError('Invalid hours in time zone offset') - - return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60) - - -DEFAULT_ISOPARSER = isoparser() -isoparse = DEFAULT_ISOPARSER.isoparse diff --git a/lib/dateutil/relativedelta.py b/lib/dateutil/relativedelta.py deleted file mode 100644 index 1e0d616..0000000 --- a/lib/dateutil/relativedelta.py +++ /dev/null @@ -1,590 +0,0 @@ -# -*- coding: utf-8 -*- -import datetime -import calendar - -import operator -from math import copysign - -from six import integer_types -from warnings import warn - -from ._common import weekday - -MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) - -__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] - - -class relativedelta(object): - """ - The relativedelta type is based on the specification of the excellent - work done by M.-A. Lemburg in his - `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension. - However, notice that this type does *NOT* implement the same algorithm as - his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. - - There are two different ways to build a relativedelta instance. The - first one is passing it two date/datetime classes:: - - relativedelta(datetime1, datetime2) - - The second one is passing it any number of the following keyword arguments:: - - relativedelta(arg1=x,arg2=y,arg3=z...) - - year, month, day, hour, minute, second, microsecond: - Absolute information (argument is singular); adding or subtracting a - relativedelta with absolute information does not perform an arithmetic - operation, but rather REPLACES the corresponding value in the - original datetime with the value(s) in relativedelta. - - years, months, weeks, days, hours, minutes, seconds, microseconds: - Relative information, may be negative (argument is plural); adding - or subtracting a relativedelta with relative information performs - the corresponding aritmetic operation on the original datetime value - with the information in the relativedelta. - - weekday: - One of the weekday instances (MO, TU, etc). These - instances may receive a parameter N, specifying the Nth - weekday, which could be positive or negative (like MO(+1) - or MO(-2). Not specifying it is the same as specifying - +1. You can also use an integer, where 0=MO. Notice that - if the calculated date is already Monday, for example, - using MO(1) or MO(-1) won't change the day. - - leapdays: - Will add given days to the date found, if year is a leap - year, and the date found is post 28 of february. - - yearday, nlyearday: - Set the yearday or the non-leap year day (jump leap days). - These are converted to day/month/leapdays information. - - There are relative and absolute forms of the keyword - arguments. The plural is relative, and the singular is - absolute. For each argument in the order below, the absolute form - is applied first (by setting each attribute to that value) and - then the relative form (by adding the value to the attribute). - - The order of attributes considered when this relativedelta is - added to a datetime is: - - 1. Year - 2. Month - 3. Day - 4. Hours - 5. Minutes - 6. Seconds - 7. Microseconds - - Finally, weekday is applied, using the rule described above. - - For example - - >>> dt = datetime(2018, 4, 9, 13, 37, 0) - >>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) - datetime(2018, 4, 2, 14, 37, 0) - - First, the day is set to 1 (the first of the month), then 25 hours - are added, to get to the 2nd day and 14th hour, finally the - weekday is applied, but since the 2nd is already a Monday there is - no effect. - - """ - - def __init__(self, dt1=None, dt2=None, - years=0, months=0, days=0, leapdays=0, weeks=0, - hours=0, minutes=0, seconds=0, microseconds=0, - year=None, month=None, day=None, weekday=None, - yearday=None, nlyearday=None, - hour=None, minute=None, second=None, microsecond=None): - - if dt1 and dt2: - # datetime is a subclass of date. So both must be date - if not (isinstance(dt1, datetime.date) and - isinstance(dt2, datetime.date)): - raise TypeError("relativedelta only diffs datetime/date") - - # We allow two dates, or two datetimes, so we coerce them to be - # of the same type - if (isinstance(dt1, datetime.datetime) != - isinstance(dt2, datetime.datetime)): - if not isinstance(dt1, datetime.datetime): - dt1 = datetime.datetime.fromordinal(dt1.toordinal()) - elif not isinstance(dt2, datetime.datetime): - dt2 = datetime.datetime.fromordinal(dt2.toordinal()) - - self.years = 0 - self.months = 0 - self.days = 0 - self.leapdays = 0 - self.hours = 0 - self.minutes = 0 - self.seconds = 0 - self.microseconds = 0 - self.year = None - self.month = None - self.day = None - self.weekday = None - self.hour = None - self.minute = None - self.second = None - self.microsecond = None - self._has_time = 0 - - # Get year / month delta between the two - months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month) - self._set_months(months) - - # Remove the year/month delta so the timedelta is just well-defined - # time units (seconds, days and microseconds) - dtm = self.__radd__(dt2) - - # If we've overshot our target, make an adjustment - if dt1 < dt2: - compare = operator.gt - increment = 1 - else: - compare = operator.lt - increment = -1 - - while compare(dt1, dtm): - months += increment - self._set_months(months) - dtm = self.__radd__(dt2) - - # Get the timedelta between the "months-adjusted" date and dt1 - delta = dt1 - dtm - self.seconds = delta.seconds + delta.days * 86400 - self.microseconds = delta.microseconds - else: - # Check for non-integer values in integer-only quantities - if any(x is not None and x != int(x) for x in (years, months)): - raise ValueError("Non-integer years and months are " - "ambiguous and not currently supported.") - - # Relative information - self.years = int(years) - self.months = int(months) - self.days = days + weeks * 7 - self.leapdays = leapdays - self.hours = hours - self.minutes = minutes - self.seconds = seconds - self.microseconds = microseconds - - # Absolute information - self.year = year - self.month = month - self.day = day - self.hour = hour - self.minute = minute - self.second = second - self.microsecond = microsecond - - if any(x is not None and int(x) != x - for x in (year, month, day, hour, - minute, second, microsecond)): - # For now we'll deprecate floats - later it'll be an error. - warn("Non-integer value passed as absolute information. " + - "This is not a well-defined condition and will raise " + - "errors in future versions.", DeprecationWarning) - - if isinstance(weekday, integer_types): - self.weekday = weekdays[weekday] - else: - self.weekday = weekday - - yday = 0 - if nlyearday: - yday = nlyearday - elif yearday: - yday = yearday - if yearday > 59: - self.leapdays = -1 - if yday: - ydayidx = [31, 59, 90, 120, 151, 181, 212, - 243, 273, 304, 334, 366] - for idx, ydays in enumerate(ydayidx): - if yday <= ydays: - self.month = idx+1 - if idx == 0: - self.day = yday - else: - self.day = yday-ydayidx[idx-1] - break - else: - raise ValueError("invalid year day (%d)" % yday) - - self._fix() - - def _fix(self): - if abs(self.microseconds) > 999999: - s = _sign(self.microseconds) - div, mod = divmod(self.microseconds * s, 1000000) - self.microseconds = mod * s - self.seconds += div * s - if abs(self.seconds) > 59: - s = _sign(self.seconds) - div, mod = divmod(self.seconds * s, 60) - self.seconds = mod * s - self.minutes += div * s - if abs(self.minutes) > 59: - s = _sign(self.minutes) - div, mod = divmod(self.minutes * s, 60) - self.minutes = mod * s - self.hours += div * s - if abs(self.hours) > 23: - s = _sign(self.hours) - div, mod = divmod(self.hours * s, 24) - self.hours = mod * s - self.days += div * s - if abs(self.months) > 11: - s = _sign(self.months) - div, mod = divmod(self.months * s, 12) - self.months = mod * s - self.years += div * s - if (self.hours or self.minutes or self.seconds or self.microseconds - or self.hour is not None or self.minute is not None or - self.second is not None or self.microsecond is not None): - self._has_time = 1 - else: - self._has_time = 0 - - @property - def weeks(self): - return int(self.days / 7.0) - - @weeks.setter - def weeks(self, value): - self.days = self.days - (self.weeks * 7) + value * 7 - - def _set_months(self, months): - self.months = months - if abs(self.months) > 11: - s = _sign(self.months) - div, mod = divmod(self.months * s, 12) - self.months = mod * s - self.years = div * s - else: - self.years = 0 - - def normalized(self): - """ - Return a version of this object represented entirely using integer - values for the relative attributes. - - >>> relativedelta(days=1.5, hours=2).normalized() - relativedelta(days=1, hours=14) - - :return: - Returns a :class:`dateutil.relativedelta.relativedelta` object. - """ - # Cascade remainders down (rounding each to roughly nearest microsecond) - days = int(self.days) - - hours_f = round(self.hours + 24 * (self.days - days), 11) - hours = int(hours_f) - - minutes_f = round(self.minutes + 60 * (hours_f - hours), 10) - minutes = int(minutes_f) - - seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8) - seconds = int(seconds_f) - - microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds)) - - # Constructor carries overflow back up with call to _fix() - return self.__class__(years=self.years, months=self.months, - days=days, hours=hours, minutes=minutes, - seconds=seconds, microseconds=microseconds, - leapdays=self.leapdays, year=self.year, - month=self.month, day=self.day, - weekday=self.weekday, hour=self.hour, - minute=self.minute, second=self.second, - microsecond=self.microsecond) - - def __add__(self, other): - if isinstance(other, relativedelta): - return self.__class__(years=other.years + self.years, - months=other.months + self.months, - days=other.days + self.days, - hours=other.hours + self.hours, - minutes=other.minutes + self.minutes, - seconds=other.seconds + self.seconds, - microseconds=(other.microseconds + - self.microseconds), - leapdays=other.leapdays or self.leapdays, - year=(other.year if other.year is not None - else self.year), - month=(other.month if other.month is not None - else self.month), - day=(other.day if other.day is not None - else self.day), - weekday=(other.weekday if other.weekday is not None - else self.weekday), - hour=(other.hour if other.hour is not None - else self.hour), - minute=(other.minute if other.minute is not None - else self.minute), - second=(other.second if other.second is not None - else self.second), - microsecond=(other.microsecond if other.microsecond - is not None else - self.microsecond)) - if isinstance(other, datetime.timedelta): - return self.__class__(years=self.years, - months=self.months, - days=self.days + other.days, - hours=self.hours, - minutes=self.minutes, - seconds=self.seconds + other.seconds, - microseconds=self.microseconds + other.microseconds, - leapdays=self.leapdays, - year=self.year, - month=self.month, - day=self.day, - weekday=self.weekday, - hour=self.hour, - minute=self.minute, - second=self.second, - microsecond=self.microsecond) - if not isinstance(other, datetime.date): - return NotImplemented - elif self._has_time and not isinstance(other, datetime.datetime): - other = datetime.datetime.fromordinal(other.toordinal()) - year = (self.year or other.year)+self.years - month = self.month or other.month - if self.months: - assert 1 <= abs(self.months) <= 12 - month += self.months - if month > 12: - year += 1 - month -= 12 - elif month < 1: - year -= 1 - month += 12 - day = min(calendar.monthrange(year, month)[1], - self.day or other.day) - repl = {"year": year, "month": month, "day": day} - for attr in ["hour", "minute", "second", "microsecond"]: - value = getattr(self, attr) - if value is not None: - repl[attr] = value - days = self.days - if self.leapdays and month > 2 and calendar.isleap(year): - days += self.leapdays - ret = (other.replace(**repl) - + datetime.timedelta(days=days, - hours=self.hours, - minutes=self.minutes, - seconds=self.seconds, - microseconds=self.microseconds)) - if self.weekday: - weekday, nth = self.weekday.weekday, self.weekday.n or 1 - jumpdays = (abs(nth) - 1) * 7 - if nth > 0: - jumpdays += (7 - ret.weekday() + weekday) % 7 - else: - jumpdays += (ret.weekday() - weekday) % 7 - jumpdays *= -1 - ret += datetime.timedelta(days=jumpdays) - return ret - - def __radd__(self, other): - return self.__add__(other) - - def __rsub__(self, other): - return self.__neg__().__radd__(other) - - def __sub__(self, other): - if not isinstance(other, relativedelta): - return NotImplemented # In case the other object defines __rsub__ - return self.__class__(years=self.years - other.years, - months=self.months - other.months, - days=self.days - other.days, - hours=self.hours - other.hours, - minutes=self.minutes - other.minutes, - seconds=self.seconds - other.seconds, - microseconds=self.microseconds - other.microseconds, - leapdays=self.leapdays or other.leapdays, - year=(self.year if self.year is not None - else other.year), - month=(self.month if self.month is not None else - other.month), - day=(self.day if self.day is not None else - other.day), - weekday=(self.weekday if self.weekday is not None else - other.weekday), - hour=(self.hour if self.hour is not None else - other.hour), - minute=(self.minute if self.minute is not None else - other.minute), - second=(self.second if self.second is not None else - other.second), - microsecond=(self.microsecond if self.microsecond - is not None else - other.microsecond)) - - def __abs__(self): - return self.__class__(years=abs(self.years), - months=abs(self.months), - days=abs(self.days), - hours=abs(self.hours), - minutes=abs(self.minutes), - seconds=abs(self.seconds), - microseconds=abs(self.microseconds), - leapdays=self.leapdays, - year=self.year, - month=self.month, - day=self.day, - weekday=self.weekday, - hour=self.hour, - minute=self.minute, - second=self.second, - microsecond=self.microsecond) - - def __neg__(self): - return self.__class__(years=-self.years, - months=-self.months, - days=-self.days, - hours=-self.hours, - minutes=-self.minutes, - seconds=-self.seconds, - microseconds=-self.microseconds, - leapdays=self.leapdays, - year=self.year, - month=self.month, - day=self.day, - weekday=self.weekday, - hour=self.hour, - minute=self.minute, - second=self.second, - microsecond=self.microsecond) - - def __bool__(self): - return not (not self.years and - not self.months and - not self.days and - not self.hours and - not self.minutes and - not self.seconds and - not self.microseconds and - not self.leapdays and - self.year is None and - self.month is None and - self.day is None and - self.weekday is None and - self.hour is None and - self.minute is None and - self.second is None and - self.microsecond is None) - # Compatibility with Python 2.x - __nonzero__ = __bool__ - - def __mul__(self, other): - try: - f = float(other) - except TypeError: - return NotImplemented - - return self.__class__(years=int(self.years * f), - months=int(self.months * f), - days=int(self.days * f), - hours=int(self.hours * f), - minutes=int(self.minutes * f), - seconds=int(self.seconds * f), - microseconds=int(self.microseconds * f), - leapdays=self.leapdays, - year=self.year, - month=self.month, - day=self.day, - weekday=self.weekday, - hour=self.hour, - minute=self.minute, - second=self.second, - microsecond=self.microsecond) - - __rmul__ = __mul__ - - def __eq__(self, other): - if not isinstance(other, relativedelta): - return NotImplemented - if self.weekday or other.weekday: - if not self.weekday or not other.weekday: - return False - if self.weekday.weekday != other.weekday.weekday: - return False - n1, n2 = self.weekday.n, other.weekday.n - if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): - return False - return (self.years == other.years and - self.months == other.months and - self.days == other.days and - self.hours == other.hours and - self.minutes == other.minutes and - self.seconds == other.seconds and - self.microseconds == other.microseconds and - self.leapdays == other.leapdays and - self.year == other.year and - self.month == other.month and - self.day == other.day and - self.hour == other.hour and - self.minute == other.minute and - self.second == other.second and - self.microsecond == other.microsecond) - - def __hash__(self): - return hash(( - self.weekday, - self.years, - self.months, - self.days, - self.hours, - self.minutes, - self.seconds, - self.microseconds, - self.leapdays, - self.year, - self.month, - self.day, - self.hour, - self.minute, - self.second, - self.microsecond, - )) - - def __ne__(self, other): - return not self.__eq__(other) - - def __div__(self, other): - try: - reciprocal = 1 / float(other) - except TypeError: - return NotImplemented - - return self.__mul__(reciprocal) - - __truediv__ = __div__ - - def __repr__(self): - l = [] - for attr in ["years", "months", "days", "leapdays", - "hours", "minutes", "seconds", "microseconds"]: - value = getattr(self, attr) - if value: - l.append("{attr}={value:+g}".format(attr=attr, value=value)) - for attr in ["year", "month", "day", "weekday", - "hour", "minute", "second", "microsecond"]: - value = getattr(self, attr) - if value is not None: - l.append("{attr}={value}".format(attr=attr, value=repr(value))) - return "{classname}({attrs})".format(classname=self.__class__.__name__, - attrs=", ".join(l)) - - -def _sign(x): - return int(copysign(1, x)) - -# vim:ts=4:sw=4:et diff --git a/lib/dateutil/rrule.py b/lib/dateutil/rrule.py deleted file mode 100644 index 8e9c2af..0000000 --- a/lib/dateutil/rrule.py +++ /dev/null @@ -1,1672 +0,0 @@ -# -*- coding: utf-8 -*- -""" -The rrule module offers a small, complete, and very fast, implementation of -the recurrence rules documented in the -`iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_, -including support for caching of results. -""" -import itertools -import datetime -import calendar -import re -import sys - -try: - from math import gcd -except ImportError: - from fractions import gcd - -from six import advance_iterator, integer_types -from six.moves import _thread, range -import heapq - -from ._common import weekday as weekdaybase -from .tz import tzutc, tzlocal - -# For warning about deprecation of until and count -from warnings import warn - -__all__ = ["rrule", "rruleset", "rrulestr", - "YEARLY", "MONTHLY", "WEEKLY", "DAILY", - "HOURLY", "MINUTELY", "SECONDLY", - "MO", "TU", "WE", "TH", "FR", "SA", "SU"] - -# Every mask is 7 days longer to handle cross-year weekly periods. -M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 + - [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7) -M365MASK = list(M366MASK) -M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32)) -MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) -MDAY365MASK = list(MDAY366MASK) -M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0)) -NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) -NMDAY365MASK = list(NMDAY366MASK) -M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366) -M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) -WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55 -del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31] -MDAY365MASK = tuple(MDAY365MASK) -M365MASK = tuple(M365MASK) - -FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY'] - -(YEARLY, - MONTHLY, - WEEKLY, - DAILY, - HOURLY, - MINUTELY, - SECONDLY) = list(range(7)) - -# Imported on demand. -easter = None -parser = None - - -class weekday(weekdaybase): - """ - This version of weekday does not allow n = 0. - """ - def __init__(self, wkday, n=None): - if n == 0: - raise ValueError("Can't create weekday with n==0") - - super(weekday, self).__init__(wkday, n) - - -MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) - - -def _invalidates_cache(f): - """ - Decorator for rruleset methods which may invalidate the - cached length. - """ - def inner_func(self, *args, **kwargs): - rv = f(self, *args, **kwargs) - self._invalidate_cache() - return rv - - return inner_func - - -class rrulebase(object): - def __init__(self, cache=False): - if cache: - self._cache = [] - self._cache_lock = _thread.allocate_lock() - self._invalidate_cache() - else: - self._cache = None - self._cache_complete = False - self._len = None - - def __iter__(self): - if self._cache_complete: - return iter(self._cache) - elif self._cache is None: - return self._iter() - else: - return self._iter_cached() - - def _invalidate_cache(self): - if self._cache is not None: - self._cache = [] - self._cache_complete = False - self._cache_gen = self._iter() - - if self._cache_lock.locked(): - self._cache_lock.release() - - self._len = None - - def _iter_cached(self): - i = 0 - gen = self._cache_gen - cache = self._cache - acquire = self._cache_lock.acquire - release = self._cache_lock.release - while gen: - if i == len(cache): - acquire() - if self._cache_complete: - break - try: - for j in range(10): - cache.append(advance_iterator(gen)) - except StopIteration: - self._cache_gen = gen = None - self._cache_complete = True - break - release() - yield cache[i] - i += 1 - while i < self._len: - yield cache[i] - i += 1 - - def __getitem__(self, item): - if self._cache_complete: - return self._cache[item] - elif isinstance(item, slice): - if item.step and item.step < 0: - return list(iter(self))[item] - else: - return list(itertools.islice(self, - item.start or 0, - item.stop or sys.maxsize, - item.step or 1)) - elif item >= 0: - gen = iter(self) - try: - for i in range(item+1): - res = advance_iterator(gen) - except StopIteration: - raise IndexError - return res - else: - return list(iter(self))[item] - - def __contains__(self, item): - if self._cache_complete: - return item in self._cache - else: - for i in self: - if i == item: - return True - elif i > item: - return False - return False - - # __len__() introduces a large performance penality. - def count(self): - """ Returns the number of recurrences in this set. It will have go - trough the whole recurrence, if this hasn't been done before. """ - if self._len is None: - for x in self: - pass - return self._len - - def before(self, dt, inc=False): - """ Returns the last recurrence before the given datetime instance. The - inc keyword defines what happens if dt is an occurrence. With - inc=True, if dt itself is an occurrence, it will be returned. """ - if self._cache_complete: - gen = self._cache - else: - gen = self - last = None - if inc: - for i in gen: - if i > dt: - break - last = i - else: - for i in gen: - if i >= dt: - break - last = i - return last - - def after(self, dt, inc=False): - """ Returns the first recurrence after the given datetime instance. The - inc keyword defines what happens if dt is an occurrence. With - inc=True, if dt itself is an occurrence, it will be returned. """ - if self._cache_complete: - gen = self._cache - else: - gen = self - if inc: - for i in gen: - if i >= dt: - return i - else: - for i in gen: - if i > dt: - return i - return None - - def xafter(self, dt, count=None, inc=False): - """ - Generator which yields up to `count` recurrences after the given - datetime instance, equivalent to `after`. - - :param dt: - The datetime at which to start generating recurrences. - - :param count: - The maximum number of recurrences to generate. If `None` (default), - dates are generated until the recurrence rule is exhausted. - - :param inc: - If `dt` is an instance of the rule and `inc` is `True`, it is - included in the output. - - :yields: Yields a sequence of `datetime` objects. - """ - - if self._cache_complete: - gen = self._cache - else: - gen = self - - # Select the comparison function - if inc: - comp = lambda dc, dtc: dc >= dtc - else: - comp = lambda dc, dtc: dc > dtc - - # Generate dates - n = 0 - for d in gen: - if comp(d, dt): - if count is not None: - n += 1 - if n > count: - break - - yield d - - def between(self, after, before, inc=False, count=1): - """ Returns all the occurrences of the rrule between after and before. - The inc keyword defines what happens if after and/or before are - themselves occurrences. With inc=True, they will be included in the - list, if they are found in the recurrence set. """ - if self._cache_complete: - gen = self._cache - else: - gen = self - started = False - l = [] - if inc: - for i in gen: - if i > before: - break - elif not started: - if i >= after: - started = True - l.append(i) - else: - l.append(i) - else: - for i in gen: - if i >= before: - break - elif not started: - if i > after: - started = True - l.append(i) - else: - l.append(i) - return l - - -class rrule(rrulebase): - """ - That's the base of the rrule operation. It accepts all the keywords - defined in the RFC as its constructor parameters (except byday, - which was renamed to byweekday) and more. The constructor prototype is:: - - rrule(freq) - - Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, - or SECONDLY. - - .. note:: - Per RFC section 3.3.10, recurrence instances falling on invalid dates - and times are ignored rather than coerced: - - Recurrence rules may generate recurrence instances with an invalid - date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM - on a day where the local time is moved forward by an hour at 1:00 - AM). Such recurrence instances MUST be ignored and MUST NOT be - counted as part of the recurrence set. - - This can lead to possibly surprising behavior when, for example, the - start date occurs at the end of the month: - - >>> from dateutil.rrule import rrule, MONTHLY - >>> from datetime import datetime - >>> start_date = datetime(2014, 12, 31) - >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date)) - ... # doctest: +NORMALIZE_WHITESPACE - [datetime.datetime(2014, 12, 31, 0, 0), - datetime.datetime(2015, 1, 31, 0, 0), - datetime.datetime(2015, 3, 31, 0, 0), - datetime.datetime(2015, 5, 31, 0, 0)] - - Additionally, it supports the following keyword arguments: - - :param dtstart: - The recurrence start. Besides being the base for the recurrence, - missing parameters in the final recurrence instances will also be - extracted from this date. If not given, datetime.now() will be used - instead. - :param interval: - The interval between each freq iteration. For example, when using - YEARLY, an interval of 2 means once every two years, but with HOURLY, - it means once every two hours. The default interval is 1. - :param wkst: - The week start day. Must be one of the MO, TU, WE constants, or an - integer, specifying the first day of the week. This will affect - recurrences based on weekly periods. The default week start is got - from calendar.firstweekday(), and may be modified by - calendar.setfirstweekday(). - :param count: - How many occurrences will be generated. - - .. note:: - As of version 2.5.0, the use of the ``until`` keyword together - with the ``count`` keyword is deprecated per RFC-5545 Sec. 3.3.10. - :param until: - If given, this must be a datetime instance, that will specify the - limit of the recurrence. The last recurrence in the rule is the greatest - datetime that is less than or equal to the value specified in the - ``until`` parameter. - - .. note:: - As of version 2.5.0, the use of the ``until`` keyword together - with the ``count`` keyword is deprecated per RFC-5545 Sec. 3.3.10. - :param bysetpos: - If given, it must be either an integer, or a sequence of integers, - positive or negative. Each given integer will specify an occurrence - number, corresponding to the nth occurrence of the rule inside the - frequency period. For example, a bysetpos of -1 if combined with a - MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will - result in the last work day of every month. - :param bymonth: - If given, it must be either an integer, or a sequence of integers, - meaning the months to apply the recurrence to. - :param bymonthday: - If given, it must be either an integer, or a sequence of integers, - meaning the month days to apply the recurrence to. - :param byyearday: - If given, it must be either an integer, or a sequence of integers, - meaning the year days to apply the recurrence to. - :param byeaster: - If given, it must be either an integer, or a sequence of integers, - positive or negative. Each integer will define an offset from the - Easter Sunday. Passing the offset 0 to byeaster will yield the Easter - Sunday itself. This is an extension to the RFC specification. - :param byweekno: - If given, it must be either an integer, or a sequence of integers, - meaning the week numbers to apply the recurrence to. Week numbers - have the meaning described in ISO8601, that is, the first week of - the year is that containing at least four days of the new year. - :param byweekday: - If given, it must be either an integer (0 == MO), a sequence of - integers, one of the weekday constants (MO, TU, etc), or a sequence - of these constants. When given, these variables will define the - weekdays where the recurrence will be applied. It's also possible to - use an argument n for the weekday instances, which will mean the nth - occurrence of this weekday in the period. For example, with MONTHLY, - or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the - first friday of the month where the recurrence happens. Notice that in - the RFC documentation, this is specified as BYDAY, but was renamed to - avoid the ambiguity of that keyword. - :param byhour: - If given, it must be either an integer, or a sequence of integers, - meaning the hours to apply the recurrence to. - :param byminute: - If given, it must be either an integer, or a sequence of integers, - meaning the minutes to apply the recurrence to. - :param bysecond: - If given, it must be either an integer, or a sequence of integers, - meaning the seconds to apply the recurrence to. - :param cache: - If given, it must be a boolean value specifying to enable or disable - caching of results. If you will use the same rrule instance multiple - times, enabling caching will improve the performance considerably. - """ - def __init__(self, freq, dtstart=None, - interval=1, wkst=None, count=None, until=None, bysetpos=None, - bymonth=None, bymonthday=None, byyearday=None, byeaster=None, - byweekno=None, byweekday=None, - byhour=None, byminute=None, bysecond=None, - cache=False): - super(rrule, self).__init__(cache) - global easter - if not dtstart: - if until and until.tzinfo: - dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0) - else: - dtstart = datetime.datetime.now().replace(microsecond=0) - elif not isinstance(dtstart, datetime.datetime): - dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) - else: - dtstart = dtstart.replace(microsecond=0) - self._dtstart = dtstart - self._tzinfo = dtstart.tzinfo - self._freq = freq - self._interval = interval - self._count = count - - # Cache the original byxxx rules, if they are provided, as the _byxxx - # attributes do not necessarily map to the inputs, and this can be - # a problem in generating the strings. Only store things if they've - # been supplied (the string retrieval will just use .get()) - self._original_rule = {} - - if until and not isinstance(until, datetime.datetime): - until = datetime.datetime.fromordinal(until.toordinal()) - self._until = until - - if self._dtstart and self._until: - if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None): - # According to RFC5545 Section 3.3.10: - # https://tools.ietf.org/html/rfc5545#section-3.3.10 - # - # > If the "DTSTART" property is specified as a date with UTC - # > time or a date with local time and time zone reference, - # > then the UNTIL rule part MUST be specified as a date with - # > UTC time. - raise ValueError( - 'RRULE UNTIL values must be specified in UTC when DTSTART ' - 'is timezone-aware' - ) - - if count is not None and until: - warn("Using both 'count' and 'until' is inconsistent with RFC 5545" - " and has been deprecated in dateutil. Future versions will " - "raise an error.", DeprecationWarning) - - if wkst is None: - self._wkst = calendar.firstweekday() - elif isinstance(wkst, integer_types): - self._wkst = wkst - else: - self._wkst = wkst.weekday - - if bysetpos is None: - self._bysetpos = None - elif isinstance(bysetpos, integer_types): - if bysetpos == 0 or not (-366 <= bysetpos <= 366): - raise ValueError("bysetpos must be between 1 and 366, " - "or between -366 and -1") - self._bysetpos = (bysetpos,) - else: - self._bysetpos = tuple(bysetpos) - for pos in self._bysetpos: - if pos == 0 or not (-366 <= pos <= 366): - raise ValueError("bysetpos must be between 1 and 366, " - "or between -366 and -1") - - if self._bysetpos: - self._original_rule['bysetpos'] = self._bysetpos - - if (byweekno is None and byyearday is None and bymonthday is None and - byweekday is None and byeaster is None): - if freq == YEARLY: - if bymonth is None: - bymonth = dtstart.month - self._original_rule['bymonth'] = None - bymonthday = dtstart.day - self._original_rule['bymonthday'] = None - elif freq == MONTHLY: - bymonthday = dtstart.day - self._original_rule['bymonthday'] = None - elif freq == WEEKLY: - byweekday = dtstart.weekday() - self._original_rule['byweekday'] = None - - # bymonth - if bymonth is None: - self._bymonth = None - else: - if isinstance(bymonth, integer_types): - bymonth = (bymonth,) - - self._bymonth = tuple(sorted(set(bymonth))) - - if 'bymonth' not in self._original_rule: - self._original_rule['bymonth'] = self._bymonth - - # byyearday - if byyearday is None: - self._byyearday = None - else: - if isinstance(byyearday, integer_types): - byyearday = (byyearday,) - - self._byyearday = tuple(sorted(set(byyearday))) - self._original_rule['byyearday'] = self._byyearday - - # byeaster - if byeaster is not None: - if not easter: - from dateutil import easter - if isinstance(byeaster, integer_types): - self._byeaster = (byeaster,) - else: - self._byeaster = tuple(sorted(byeaster)) - - self._original_rule['byeaster'] = self._byeaster - else: - self._byeaster = None - - # bymonthday - if bymonthday is None: - self._bymonthday = () - self._bynmonthday = () - else: - if isinstance(bymonthday, integer_types): - bymonthday = (bymonthday,) - - bymonthday = set(bymonthday) # Ensure it's unique - - self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0)) - self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0)) - - # Storing positive numbers first, then negative numbers - if 'bymonthday' not in self._original_rule: - self._original_rule['bymonthday'] = tuple( - itertools.chain(self._bymonthday, self._bynmonthday)) - - # byweekno - if byweekno is None: - self._byweekno = None - else: - if isinstance(byweekno, integer_types): - byweekno = (byweekno,) - - self._byweekno = tuple(sorted(set(byweekno))) - - self._original_rule['byweekno'] = self._byweekno - - # byweekday / bynweekday - if byweekday is None: - self._byweekday = None - self._bynweekday = None - else: - # If it's one of the valid non-sequence types, convert to a - # single-element sequence before the iterator that builds the - # byweekday set. - if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"): - byweekday = (byweekday,) - - self._byweekday = set() - self._bynweekday = set() - for wday in byweekday: - if isinstance(wday, integer_types): - self._byweekday.add(wday) - elif not wday.n or freq > MONTHLY: - self._byweekday.add(wday.weekday) - else: - self._bynweekday.add((wday.weekday, wday.n)) - - if not self._byweekday: - self._byweekday = None - elif not self._bynweekday: - self._bynweekday = None - - if self._byweekday is not None: - self._byweekday = tuple(sorted(self._byweekday)) - orig_byweekday = [weekday(x) for x in self._byweekday] - else: - orig_byweekday = () - - if self._bynweekday is not None: - self._bynweekday = tuple(sorted(self._bynweekday)) - orig_bynweekday = [weekday(*x) for x in self._bynweekday] - else: - orig_bynweekday = () - - if 'byweekday' not in self._original_rule: - self._original_rule['byweekday'] = tuple(itertools.chain( - orig_byweekday, orig_bynweekday)) - - # byhour - if byhour is None: - if freq < HOURLY: - self._byhour = {dtstart.hour} - else: - self._byhour = None - else: - if isinstance(byhour, integer_types): - byhour = (byhour,) - - if freq == HOURLY: - self._byhour = self.__construct_byset(start=dtstart.hour, - byxxx=byhour, - base=24) - else: - self._byhour = set(byhour) - - self._byhour = tuple(sorted(self._byhour)) - self._original_rule['byhour'] = self._byhour - - # byminute - if byminute is None: - if freq < MINUTELY: - self._byminute = {dtstart.minute} - else: - self._byminute = None - else: - if isinstance(byminute, integer_types): - byminute = (byminute,) - - if freq == MINUTELY: - self._byminute = self.__construct_byset(start=dtstart.minute, - byxxx=byminute, - base=60) - else: - self._byminute = set(byminute) - - self._byminute = tuple(sorted(self._byminute)) - self._original_rule['byminute'] = self._byminute - - # bysecond - if bysecond is None: - if freq < SECONDLY: - self._bysecond = ((dtstart.second,)) - else: - self._bysecond = None - else: - if isinstance(bysecond, integer_types): - bysecond = (bysecond,) - - self._bysecond = set(bysecond) - - if freq == SECONDLY: - self._bysecond = self.__construct_byset(start=dtstart.second, - byxxx=bysecond, - base=60) - else: - self._bysecond = set(bysecond) - - self._bysecond = tuple(sorted(self._bysecond)) - self._original_rule['bysecond'] = self._bysecond - - if self._freq >= HOURLY: - self._timeset = None - else: - self._timeset = [] - for hour in self._byhour: - for minute in self._byminute: - for second in self._bysecond: - self._timeset.append( - datetime.time(hour, minute, second, - tzinfo=self._tzinfo)) - self._timeset.sort() - self._timeset = tuple(self._timeset) - - def __str__(self): - """ - Output a string that would generate this RRULE if passed to rrulestr. - This is mostly compatible with RFC5545, except for the - dateutil-specific extension BYEASTER. - """ - - output = [] - h, m, s = [None] * 3 - if self._dtstart: - output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S')) - h, m, s = self._dtstart.timetuple()[3:6] - - parts = ['FREQ=' + FREQNAMES[self._freq]] - if self._interval != 1: - parts.append('INTERVAL=' + str(self._interval)) - - if self._wkst: - parts.append('WKST=' + repr(weekday(self._wkst))[0:2]) - - if self._count is not None: - parts.append('COUNT=' + str(self._count)) - - if self._until: - parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S')) - - if self._original_rule.get('byweekday') is not None: - # The str() method on weekday objects doesn't generate - # RFC5545-compliant strings, so we should modify that. - original_rule = dict(self._original_rule) - wday_strings = [] - for wday in original_rule['byweekday']: - if wday.n: - wday_strings.append('{n:+d}{wday}'.format( - n=wday.n, - wday=repr(wday)[0:2])) - else: - wday_strings.append(repr(wday)) - - original_rule['byweekday'] = wday_strings - else: - original_rule = self._original_rule - - partfmt = '{name}={vals}' - for name, key in [('BYSETPOS', 'bysetpos'), - ('BYMONTH', 'bymonth'), - ('BYMONTHDAY', 'bymonthday'), - ('BYYEARDAY', 'byyearday'), - ('BYWEEKNO', 'byweekno'), - ('BYDAY', 'byweekday'), - ('BYHOUR', 'byhour'), - ('BYMINUTE', 'byminute'), - ('BYSECOND', 'bysecond'), - ('BYEASTER', 'byeaster')]: - value = original_rule.get(key) - if value: - parts.append(partfmt.format(name=name, vals=(','.join(str(v) - for v in value)))) - - output.append('RRULE:' + ';'.join(parts)) - return '\n'.join(output) - - def replace(self, **kwargs): - """Return new rrule with same attributes except for those attributes given new - values by whichever keyword arguments are specified.""" - new_kwargs = {"interval": self._interval, - "count": self._count, - "dtstart": self._dtstart, - "freq": self._freq, - "until": self._until, - "wkst": self._wkst, - "cache": False if self._cache is None else True } - new_kwargs.update(self._original_rule) - new_kwargs.update(kwargs) - return rrule(**new_kwargs) - - def _iter(self): - year, month, day, hour, minute, second, weekday, yearday, _ = \ - self._dtstart.timetuple() - - # Some local variables to speed things up a bit - freq = self._freq - interval = self._interval - wkst = self._wkst - until = self._until - bymonth = self._bymonth - byweekno = self._byweekno - byyearday = self._byyearday - byweekday = self._byweekday - byeaster = self._byeaster - bymonthday = self._bymonthday - bynmonthday = self._bynmonthday - bysetpos = self._bysetpos - byhour = self._byhour - byminute = self._byminute - bysecond = self._bysecond - - ii = _iterinfo(self) - ii.rebuild(year, month) - - getdayset = {YEARLY: ii.ydayset, - MONTHLY: ii.mdayset, - WEEKLY: ii.wdayset, - DAILY: ii.ddayset, - HOURLY: ii.ddayset, - MINUTELY: ii.ddayset, - SECONDLY: ii.ddayset}[freq] - - if freq < HOURLY: - timeset = self._timeset - else: - gettimeset = {HOURLY: ii.htimeset, - MINUTELY: ii.mtimeset, - SECONDLY: ii.stimeset}[freq] - if ((freq >= HOURLY and - self._byhour and hour not in self._byhour) or - (freq >= MINUTELY and - self._byminute and minute not in self._byminute) or - (freq >= SECONDLY and - self._bysecond and second not in self._bysecond)): - timeset = () - else: - timeset = gettimeset(hour, minute, second) - - total = 0 - count = self._count - while True: - # Get dayset with the right frequency - dayset, start, end = getdayset(year, month, day) - - # Do the "hard" work ;-) - filtered = False - for i in dayset[start:end]: - if ((bymonth and ii.mmask[i] not in bymonth) or - (byweekno and not ii.wnomask[i]) or - (byweekday and ii.wdaymask[i] not in byweekday) or - (ii.nwdaymask and not ii.nwdaymask[i]) or - (byeaster and not ii.eastermask[i]) or - ((bymonthday or bynmonthday) and - ii.mdaymask[i] not in bymonthday and - ii.nmdaymask[i] not in bynmonthday) or - (byyearday and - ((i < ii.yearlen and i+1 not in byyearday and - -ii.yearlen+i not in byyearday) or - (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and - -ii.nextyearlen+i-ii.yearlen not in byyearday)))): - dayset[i] = None - filtered = True - - # Output results - if bysetpos and timeset: - poslist = [] - for pos in bysetpos: - if pos < 0: - daypos, timepos = divmod(pos, len(timeset)) - else: - daypos, timepos = divmod(pos-1, len(timeset)) - try: - i = [x for x in dayset[start:end] - if x is not None][daypos] - time = timeset[timepos] - except IndexError: - pass - else: - date = datetime.date.fromordinal(ii.yearordinal+i) - res = datetime.datetime.combine(date, time) - if res not in poslist: - poslist.append(res) - poslist.sort() - for res in poslist: - if until and res > until: - self._len = total - return - elif res >= self._dtstart: - if count is not None: - count -= 1 - if count < 0: - self._len = total - return - total += 1 - yield res - else: - for i in dayset[start:end]: - if i is not None: - date = datetime.date.fromordinal(ii.yearordinal + i) - for time in timeset: - res = datetime.datetime.combine(date, time) - if until and res > until: - self._len = total - return - elif res >= self._dtstart: - if count is not None: - count -= 1 - if count < 0: - self._len = total - return - - total += 1 - yield res - - # Handle frequency and interval - fixday = False - if freq == YEARLY: - year += interval - if year > datetime.MAXYEAR: - self._len = total - return - ii.rebuild(year, month) - elif freq == MONTHLY: - month += interval - if month > 12: - div, mod = divmod(month, 12) - month = mod - year += div - if month == 0: - month = 12 - year -= 1 - if year > datetime.MAXYEAR: - self._len = total - return - ii.rebuild(year, month) - elif freq == WEEKLY: - if wkst > weekday: - day += -(weekday+1+(6-wkst))+self._interval*7 - else: - day += -(weekday-wkst)+self._interval*7 - weekday = wkst - fixday = True - elif freq == DAILY: - day += interval - fixday = True - elif freq == HOURLY: - if filtered: - # Jump to one iteration before next day - hour += ((23-hour)//interval)*interval - - if byhour: - ndays, hour = self.__mod_distance(value=hour, - byxxx=self._byhour, - base=24) - else: - ndays, hour = divmod(hour+interval, 24) - - if ndays: - day += ndays - fixday = True - - timeset = gettimeset(hour, minute, second) - elif freq == MINUTELY: - if filtered: - # Jump to one iteration before next day - minute += ((1439-(hour*60+minute))//interval)*interval - - valid = False - rep_rate = (24*60) - for j in range(rep_rate // gcd(interval, rep_rate)): - if byminute: - nhours, minute = \ - self.__mod_distance(value=minute, - byxxx=self._byminute, - base=60) - else: - nhours, minute = divmod(minute+interval, 60) - - div, hour = divmod(hour+nhours, 24) - if div: - day += div - fixday = True - filtered = False - - if not byhour or hour in byhour: - valid = True - break - - if not valid: - raise ValueError('Invalid combination of interval and ' + - 'byhour resulting in empty rule.') - - timeset = gettimeset(hour, minute, second) - elif freq == SECONDLY: - if filtered: - # Jump to one iteration before next day - second += (((86399 - (hour * 3600 + minute * 60 + second)) - // interval) * interval) - - rep_rate = (24 * 3600) - valid = False - for j in range(0, rep_rate // gcd(interval, rep_rate)): - if bysecond: - nminutes, second = \ - self.__mod_distance(value=second, - byxxx=self._bysecond, - base=60) - else: - nminutes, second = divmod(second+interval, 60) - - div, minute = divmod(minute+nminutes, 60) - if div: - hour += div - div, hour = divmod(hour, 24) - if div: - day += div - fixday = True - - if ((not byhour or hour in byhour) and - (not byminute or minute in byminute) and - (not bysecond or second in bysecond)): - valid = True - break - - if not valid: - raise ValueError('Invalid combination of interval, ' + - 'byhour and byminute resulting in empty' + - ' rule.') - - timeset = gettimeset(hour, minute, second) - - if fixday and day > 28: - daysinmonth = calendar.monthrange(year, month)[1] - if day > daysinmonth: - while day > daysinmonth: - day -= daysinmonth - month += 1 - if month == 13: - month = 1 - year += 1 - if year > datetime.MAXYEAR: - self._len = total - return - daysinmonth = calendar.monthrange(year, month)[1] - ii.rebuild(year, month) - - def __construct_byset(self, start, byxxx, base): - """ - If a `BYXXX` sequence is passed to the constructor at the same level as - `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some - specifications which cannot be reached given some starting conditions. - - This occurs whenever the interval is not coprime with the base of a - given unit and the difference between the starting position and the - ending position is not coprime with the greatest common denominator - between the interval and the base. For example, with a FREQ of hourly - starting at 17:00 and an interval of 4, the only valid values for - BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not - coprime. - - :param start: - Specifies the starting position. - :param byxxx: - An iterable containing the list of allowed values. - :param base: - The largest allowable value for the specified frequency (e.g. - 24 hours, 60 minutes). - - This does not preserve the type of the iterable, returning a set, since - the values should be unique and the order is irrelevant, this will - speed up later lookups. - - In the event of an empty set, raises a :exception:`ValueError`, as this - results in an empty rrule. - """ - - cset = set() - - # Support a single byxxx value. - if isinstance(byxxx, integer_types): - byxxx = (byxxx, ) - - for num in byxxx: - i_gcd = gcd(self._interval, base) - # Use divmod rather than % because we need to wrap negative nums. - if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0: - cset.add(num) - - if len(cset) == 0: - raise ValueError("Invalid rrule byxxx generates an empty set.") - - return cset - - def __mod_distance(self, value, byxxx, base): - """ - Calculates the next value in a sequence where the `FREQ` parameter is - specified along with a `BYXXX` parameter at the same "level" - (e.g. `HOURLY` specified with `BYHOUR`). - - :param value: - The old value of the component. - :param byxxx: - The `BYXXX` set, which should have been generated by - `rrule._construct_byset`, or something else which checks that a - valid rule is present. - :param base: - The largest allowable value for the specified frequency (e.g. - 24 hours, 60 minutes). - - If a valid value is not found after `base` iterations (the maximum - number before the sequence would start to repeat), this raises a - :exception:`ValueError`, as no valid values were found. - - This returns a tuple of `divmod(n*interval, base)`, where `n` is the - smallest number of `interval` repetitions until the next specified - value in `byxxx` is found. - """ - accumulator = 0 - for ii in range(1, base + 1): - # Using divmod() over % to account for negative intervals - div, value = divmod(value + self._interval, base) - accumulator += div - if value in byxxx: - return (accumulator, value) - - -class _iterinfo(object): - __slots__ = ["rrule", "lastyear", "lastmonth", - "yearlen", "nextyearlen", "yearordinal", "yearweekday", - "mmask", "mrange", "mdaymask", "nmdaymask", - "wdaymask", "wnomask", "nwdaymask", "eastermask"] - - def __init__(self, rrule): - for attr in self.__slots__: - setattr(self, attr, None) - self.rrule = rrule - - def rebuild(self, year, month): - # Every mask is 7 days longer to handle cross-year weekly periods. - rr = self.rrule - if year != self.lastyear: - self.yearlen = 365 + calendar.isleap(year) - self.nextyearlen = 365 + calendar.isleap(year + 1) - firstyday = datetime.date(year, 1, 1) - self.yearordinal = firstyday.toordinal() - self.yearweekday = firstyday.weekday() - - wday = datetime.date(year, 1, 1).weekday() - if self.yearlen == 365: - self.mmask = M365MASK - self.mdaymask = MDAY365MASK - self.nmdaymask = NMDAY365MASK - self.wdaymask = WDAYMASK[wday:] - self.mrange = M365RANGE - else: - self.mmask = M366MASK - self.mdaymask = MDAY366MASK - self.nmdaymask = NMDAY366MASK - self.wdaymask = WDAYMASK[wday:] - self.mrange = M366RANGE - - if not rr._byweekno: - self.wnomask = None - else: - self.wnomask = [0]*(self.yearlen+7) - # no1wkst = firstwkst = self.wdaymask.index(rr._wkst) - no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7 - if no1wkst >= 4: - no1wkst = 0 - # Number of days in the year, plus the days we got - # from last year. - wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7 - else: - # Number of days in the year, minus the days we - # left in last year. - wyearlen = self.yearlen-no1wkst - div, mod = divmod(wyearlen, 7) - numweeks = div+mod//4 - for n in rr._byweekno: - if n < 0: - n += numweeks+1 - if not (0 < n <= numweeks): - continue - if n > 1: - i = no1wkst+(n-1)*7 - if no1wkst != firstwkst: - i -= 7-firstwkst - else: - i = no1wkst - for j in range(7): - self.wnomask[i] = 1 - i += 1 - if self.wdaymask[i] == rr._wkst: - break - if 1 in rr._byweekno: - # Check week number 1 of next year as well - # TODO: Check -numweeks for next year. - i = no1wkst+numweeks*7 - if no1wkst != firstwkst: - i -= 7-firstwkst - if i < self.yearlen: - # If week starts in next year, we - # don't care about it. - for j in range(7): - self.wnomask[i] = 1 - i += 1 - if self.wdaymask[i] == rr._wkst: - break - if no1wkst: - # Check last week number of last year as - # well. If no1wkst is 0, either the year - # started on week start, or week number 1 - # got days from last year, so there are no - # days from last year's last week number in - # this year. - if -1 not in rr._byweekno: - lyearweekday = datetime.date(year-1, 1, 1).weekday() - lno1wkst = (7-lyearweekday+rr._wkst) % 7 - lyearlen = 365+calendar.isleap(year-1) - if lno1wkst >= 4: - lno1wkst = 0 - lnumweeks = 52+(lyearlen + - (lyearweekday-rr._wkst) % 7) % 7//4 - else: - lnumweeks = 52+(self.yearlen-no1wkst) % 7//4 - else: - lnumweeks = -1 - if lnumweeks in rr._byweekno: - for i in range(no1wkst): - self.wnomask[i] = 1 - - if (rr._bynweekday and (month != self.lastmonth or - year != self.lastyear)): - ranges = [] - if rr._freq == YEARLY: - if rr._bymonth: - for month in rr._bymonth: - ranges.append(self.mrange[month-1:month+1]) - else: - ranges = [(0, self.yearlen)] - elif rr._freq == MONTHLY: - ranges = [self.mrange[month-1:month+1]] - if ranges: - # Weekly frequency won't get here, so we may not - # care about cross-year weekly periods. - self.nwdaymask = [0]*self.yearlen - for first, last in ranges: - last -= 1 - for wday, n in rr._bynweekday: - if n < 0: - i = last+(n+1)*7 - i -= (self.wdaymask[i]-wday) % 7 - else: - i = first+(n-1)*7 - i += (7-self.wdaymask[i]+wday) % 7 - if first <= i <= last: - self.nwdaymask[i] = 1 - - if rr._byeaster: - self.eastermask = [0]*(self.yearlen+7) - eyday = easter.easter(year).toordinal()-self.yearordinal - for offset in rr._byeaster: - self.eastermask[eyday+offset] = 1 - - self.lastyear = year - self.lastmonth = month - - def ydayset(self, year, month, day): - return list(range(self.yearlen)), 0, self.yearlen - - def mdayset(self, year, month, day): - dset = [None]*self.yearlen - start, end = self.mrange[month-1:month+1] - for i in range(start, end): - dset[i] = i - return dset, start, end - - def wdayset(self, year, month, day): - # We need to handle cross-year weeks here. - dset = [None]*(self.yearlen+7) - i = datetime.date(year, month, day).toordinal()-self.yearordinal - start = i - for j in range(7): - dset[i] = i - i += 1 - # if (not (0 <= i < self.yearlen) or - # self.wdaymask[i] == self.rrule._wkst): - # This will cross the year boundary, if necessary. - if self.wdaymask[i] == self.rrule._wkst: - break - return dset, start, i - - def ddayset(self, year, month, day): - dset = [None] * self.yearlen - i = datetime.date(year, month, day).toordinal() - self.yearordinal - dset[i] = i - return dset, i, i + 1 - - def htimeset(self, hour, minute, second): - tset = [] - rr = self.rrule - for minute in rr._byminute: - for second in rr._bysecond: - tset.append(datetime.time(hour, minute, second, - tzinfo=rr._tzinfo)) - tset.sort() - return tset - - def mtimeset(self, hour, minute, second): - tset = [] - rr = self.rrule - for second in rr._bysecond: - tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) - tset.sort() - return tset - - def stimeset(self, hour, minute, second): - return (datetime.time(hour, minute, second, - tzinfo=self.rrule._tzinfo),) - - -class rruleset(rrulebase): - """ The rruleset type allows more complex recurrence setups, mixing - multiple rules, dates, exclusion rules, and exclusion dates. The type - constructor takes the following keyword arguments: - - :param cache: If True, caching of results will be enabled, improving - performance of multiple queries considerably. """ - - class _genitem(object): - def __init__(self, genlist, gen): - try: - self.dt = advance_iterator(gen) - genlist.append(self) - except StopIteration: - pass - self.genlist = genlist - self.gen = gen - - def __next__(self): - try: - self.dt = advance_iterator(self.gen) - except StopIteration: - if self.genlist[0] is self: - heapq.heappop(self.genlist) - else: - self.genlist.remove(self) - heapq.heapify(self.genlist) - - next = __next__ - - def __lt__(self, other): - return self.dt < other.dt - - def __gt__(self, other): - return self.dt > other.dt - - def __eq__(self, other): - return self.dt == other.dt - - def __ne__(self, other): - return self.dt != other.dt - - def __init__(self, cache=False): - super(rruleset, self).__init__(cache) - self._rrule = [] - self._rdate = [] - self._exrule = [] - self._exdate = [] - - @_invalidates_cache - def rrule(self, rrule): - """ Include the given :py:class:`rrule` instance in the recurrence set - generation. """ - self._rrule.append(rrule) - - @_invalidates_cache - def rdate(self, rdate): - """ Include the given :py:class:`datetime` instance in the recurrence - set generation. """ - self._rdate.append(rdate) - - @_invalidates_cache - def exrule(self, exrule): - """ Include the given rrule instance in the recurrence set exclusion - list. Dates which are part of the given recurrence rules will not - be generated, even if some inclusive rrule or rdate matches them. - """ - self._exrule.append(exrule) - - @_invalidates_cache - def exdate(self, exdate): - """ Include the given datetime instance in the recurrence set - exclusion list. Dates included that way will not be generated, - even if some inclusive rrule or rdate matches them. """ - self._exdate.append(exdate) - - def _iter(self): - rlist = [] - self._rdate.sort() - self._genitem(rlist, iter(self._rdate)) - for gen in [iter(x) for x in self._rrule]: - self._genitem(rlist, gen) - exlist = [] - self._exdate.sort() - self._genitem(exlist, iter(self._exdate)) - for gen in [iter(x) for x in self._exrule]: - self._genitem(exlist, gen) - lastdt = None - total = 0 - heapq.heapify(rlist) - heapq.heapify(exlist) - while rlist: - ritem = rlist[0] - if not lastdt or lastdt != ritem.dt: - while exlist and exlist[0] < ritem: - exitem = exlist[0] - advance_iterator(exitem) - if exlist and exlist[0] is exitem: - heapq.heapreplace(exlist, exitem) - if not exlist or ritem != exlist[0]: - total += 1 - yield ritem.dt - lastdt = ritem.dt - advance_iterator(ritem) - if rlist and rlist[0] is ritem: - heapq.heapreplace(rlist, ritem) - self._len = total - - -class _rrulestr(object): - - _freq_map = {"YEARLY": YEARLY, - "MONTHLY": MONTHLY, - "WEEKLY": WEEKLY, - "DAILY": DAILY, - "HOURLY": HOURLY, - "MINUTELY": MINUTELY, - "SECONDLY": SECONDLY} - - _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, - "FR": 4, "SA": 5, "SU": 6} - - def _handle_int(self, rrkwargs, name, value, **kwargs): - rrkwargs[name.lower()] = int(value) - - def _handle_int_list(self, rrkwargs, name, value, **kwargs): - rrkwargs[name.lower()] = [int(x) for x in value.split(',')] - - _handle_INTERVAL = _handle_int - _handle_COUNT = _handle_int - _handle_BYSETPOS = _handle_int_list - _handle_BYMONTH = _handle_int_list - _handle_BYMONTHDAY = _handle_int_list - _handle_BYYEARDAY = _handle_int_list - _handle_BYEASTER = _handle_int_list - _handle_BYWEEKNO = _handle_int_list - _handle_BYHOUR = _handle_int_list - _handle_BYMINUTE = _handle_int_list - _handle_BYSECOND = _handle_int_list - - def _handle_FREQ(self, rrkwargs, name, value, **kwargs): - rrkwargs["freq"] = self._freq_map[value] - - def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): - global parser - if not parser: - from dateutil import parser - try: - rrkwargs["until"] = parser.parse(value, - ignoretz=kwargs.get("ignoretz"), - tzinfos=kwargs.get("tzinfos")) - except ValueError: - raise ValueError("invalid until date") - - def _handle_WKST(self, rrkwargs, name, value, **kwargs): - rrkwargs["wkst"] = self._weekday_map[value] - - def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs): - """ - Two ways to specify this: +1MO or MO(+1) - """ - l = [] - for wday in value.split(','): - if '(' in wday: - # If it's of the form TH(+1), etc. - splt = wday.split('(') - w = splt[0] - n = int(splt[1][:-1]) - elif len(wday): - # If it's of the form +1MO - for i in range(len(wday)): - if wday[i] not in '+-0123456789': - break - n = wday[:i] or None - w = wday[i:] - if n: - n = int(n) - else: - raise ValueError("Invalid (empty) BYDAY specification.") - - l.append(weekdays[self._weekday_map[w]](n)) - rrkwargs["byweekday"] = l - - _handle_BYDAY = _handle_BYWEEKDAY - - def _parse_rfc_rrule(self, line, - dtstart=None, - cache=False, - ignoretz=False, - tzinfos=None): - if line.find(':') != -1: - name, value = line.split(':') - if name != "RRULE": - raise ValueError("unknown parameter name") - else: - value = line - rrkwargs = {} - for pair in value.split(';'): - name, value = pair.split('=') - name = name.upper() - value = value.upper() - try: - getattr(self, "_handle_"+name)(rrkwargs, name, value, - ignoretz=ignoretz, - tzinfos=tzinfos) - except AttributeError: - raise ValueError("unknown parameter '%s'" % name) - except (KeyError, ValueError): - raise ValueError("invalid '%s': %s" % (name, value)) - return rrule(dtstart=dtstart, cache=cache, **rrkwargs) - - def _parse_rfc(self, s, - dtstart=None, - cache=False, - unfold=False, - forceset=False, - compatible=False, - ignoretz=False, - tzids=None, - tzinfos=None): - global parser - if compatible: - forceset = True - unfold = True - - TZID_NAMES = dict(map( - lambda x: (x.upper(), x), - re.findall('TZID=(?P<name>[^:]+):', s) - )) - s = s.upper() - if not s.strip(): - raise ValueError("empty string") - if unfold: - lines = s.splitlines() - i = 0 - while i < len(lines): - line = lines[i].rstrip() - if not line: - del lines[i] - elif i > 0 and line[0] == " ": - lines[i-1] += line[1:] - del lines[i] - else: - i += 1 - else: - lines = s.split() - if (not forceset and len(lines) == 1 and (s.find(':') == -1 or - s.startswith('RRULE:'))): - return self._parse_rfc_rrule(lines[0], cache=cache, - dtstart=dtstart, ignoretz=ignoretz, - tzinfos=tzinfos) - else: - rrulevals = [] - rdatevals = [] - exrulevals = [] - exdatevals = [] - for line in lines: - if not line: - continue - if line.find(':') == -1: - name = "RRULE" - value = line - else: - name, value = line.split(':', 1) - parms = name.split(';') - if not parms: - raise ValueError("empty property name") - name = parms[0] - parms = parms[1:] - if name == "RRULE": - for parm in parms: - raise ValueError("unsupported RRULE parm: "+parm) - rrulevals.append(value) - elif name == "RDATE": - for parm in parms: - if parm != "VALUE=DATE-TIME": - raise ValueError("unsupported RDATE parm: "+parm) - rdatevals.append(value) - elif name == "EXRULE": - for parm in parms: - raise ValueError("unsupported EXRULE parm: "+parm) - exrulevals.append(value) - elif name == "EXDATE": - for parm in parms: - if parm != "VALUE=DATE-TIME": - raise ValueError("unsupported EXDATE parm: "+parm) - exdatevals.append(value) - elif name == "DTSTART": - # RFC 5445 3.8.2.4: The VALUE parameter is optional, but - # may be found only once. - value_found = False - TZID = None - valid_values = {"VALUE=DATE-TIME", "VALUE=DATE"} - for parm in parms: - if parm.startswith("TZID="): - try: - tzkey = TZID_NAMES[parm.split('TZID=')[-1]] - except KeyError: - continue - if tzids is None: - from . import tz - tzlookup = tz.gettz - elif callable(tzids): - tzlookup = tzids - else: - tzlookup = getattr(tzids, 'get', None) - if tzlookup is None: - msg = ('tzids must be a callable, ' + - 'mapping, or None, ' + - 'not %s' % tzids) - raise ValueError(msg) - - TZID = tzlookup(tzkey) - continue - if parm not in valid_values: - raise ValueError("unsupported DTSTART parm: "+parm) - else: - if value_found: - msg = ("Duplicate value parameter found in " + - "DTSTART: " + parm) - raise ValueError(msg) - value_found = True - if not parser: - from dateutil import parser - dtstart = parser.parse(value, ignoretz=ignoretz, - tzinfos=tzinfos) - if TZID is not None: - if dtstart.tzinfo is None: - dtstart = dtstart.replace(tzinfo=TZID) - else: - raise ValueError('DTSTART specifies multiple timezones') - else: - raise ValueError("unsupported property: "+name) - if (forceset or len(rrulevals) > 1 or rdatevals - or exrulevals or exdatevals): - if not parser and (rdatevals or exdatevals): - from dateutil import parser - rset = rruleset(cache=cache) - for value in rrulevals: - rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, - ignoretz=ignoretz, - tzinfos=tzinfos)) - for value in rdatevals: - for datestr in value.split(','): - rset.rdate(parser.parse(datestr, - ignoretz=ignoretz, - tzinfos=tzinfos)) - for value in exrulevals: - rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, - ignoretz=ignoretz, - tzinfos=tzinfos)) - for value in exdatevals: - for datestr in value.split(','): - rset.exdate(parser.parse(datestr, - ignoretz=ignoretz, - tzinfos=tzinfos)) - if compatible and dtstart: - rset.rdate(dtstart) - return rset - else: - return self._parse_rfc_rrule(rrulevals[0], - dtstart=dtstart, - cache=cache, - ignoretz=ignoretz, - tzinfos=tzinfos) - - def __call__(self, s, **kwargs): - return self._parse_rfc(s, **kwargs) - - -rrulestr = _rrulestr() - -# vim:ts=4:sw=4:et diff --git a/lib/dateutil/tz/__init__.py b/lib/dateutil/tz/__init__.py deleted file mode 100644 index 5a2d9cd..0000000 --- a/lib/dateutil/tz/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -from .tz import * -from .tz import __doc__ - -#: Convenience constant providing a :class:`tzutc()` instance -#: -#: .. versionadded:: 2.7.0 -UTC = tzutc() - -__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", - "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz", - "enfold", "datetime_ambiguous", "datetime_exists", - "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"] - - -class DeprecatedTzFormatWarning(Warning): - """Warning raised when time zones are parsed from deprecated formats.""" diff --git a/lib/dateutil/tz/_common.py b/lib/dateutil/tz/_common.py deleted file mode 100644 index ccabb7d..0000000 --- a/lib/dateutil/tz/_common.py +++ /dev/null @@ -1,415 +0,0 @@ -from six import PY3 - -from functools import wraps - -from datetime import datetime, timedelta, tzinfo - - -ZERO = timedelta(0) - -__all__ = ['tzname_in_python2', 'enfold'] - - -def tzname_in_python2(namefunc): - """Change unicode output into bytestrings in Python 2 - - tzname() API changed in Python 3. It used to return bytes, but was changed - to unicode strings - """ - def adjust_encoding(*args, **kwargs): - name = namefunc(*args, **kwargs) - if name is not None and not PY3: - name = name.encode() - - return name - - return adjust_encoding - - -# The following is adapted from Alexander Belopolsky's tz library -# https://github.com/abalkin/tz -if hasattr(datetime, 'fold'): - # This is the pre-python 3.6 fold situation - def enfold(dt, fold=1): - """ - Provides a unified interface for assigning the ``fold`` attribute to - datetimes both before and after the implementation of PEP-495. - - :param fold: - The value for the ``fold`` attribute in the returned datetime. This - should be either 0 or 1. - - :return: - Returns an object for which ``getattr(dt, 'fold', 0)`` returns - ``fold`` for all versions of Python. In versions prior to - Python 3.6, this is a ``_DatetimeWithFold`` object, which is a - subclass of :py:class:`datetime.datetime` with the ``fold`` - attribute added, if ``fold`` is 1. - - .. versionadded:: 2.6.0 - """ - return dt.replace(fold=fold) - -else: - class _DatetimeWithFold(datetime): - """ - This is a class designed to provide a PEP 495-compliant interface for - Python versions before 3.6. It is used only for dates in a fold, so - the ``fold`` attribute is fixed at ``1``. - - .. versionadded:: 2.6.0 - """ - __slots__ = () - - def replace(self, *args, **kwargs): - """ - Return a datetime with the same attributes, except for those - attributes given new values by whichever keyword arguments are - specified. Note that tzinfo=None can be specified to create a naive - datetime from an aware datetime with no conversion of date and time - data. - - This is reimplemented in ``_DatetimeWithFold`` because pypy3 will - return a ``datetime.datetime`` even if ``fold`` is unchanged. - """ - argnames = ( - 'year', 'month', 'day', 'hour', 'minute', 'second', - 'microsecond', 'tzinfo' - ) - - for arg, argname in zip(args, argnames): - if argname in kwargs: - raise TypeError('Duplicate argument: {}'.format(argname)) - - kwargs[argname] = arg - - for argname in argnames: - if argname not in kwargs: - kwargs[argname] = getattr(self, argname) - - dt_class = self.__class__ if kwargs.get('fold', 1) else datetime - - return dt_class(**kwargs) - - @property - def fold(self): - return 1 - - def enfold(dt, fold=1): - """ - Provides a unified interface for assigning the ``fold`` attribute to - datetimes both before and after the implementation of PEP-495. - - :param fold: - The value for the ``fold`` attribute in the returned datetime. This - should be either 0 or 1. - - :return: - Returns an object for which ``getattr(dt, 'fold', 0)`` returns - ``fold`` for all versions of Python. In versions prior to - Python 3.6, this is a ``_DatetimeWithFold`` object, which is a - subclass of :py:class:`datetime.datetime` with the ``fold`` - attribute added, if ``fold`` is 1. - - .. versionadded:: 2.6.0 - """ - if getattr(dt, 'fold', 0) == fold: - return dt - - args = dt.timetuple()[:6] - args += (dt.microsecond, dt.tzinfo) - - if fold: - return _DatetimeWithFold(*args) - else: - return datetime(*args) - - -def _validate_fromutc_inputs(f): - """ - The CPython version of ``fromutc`` checks that the input is a ``datetime`` - object and that ``self`` is attached as its ``tzinfo``. - """ - @wraps(f) - def fromutc(self, dt): - if not isinstance(dt, datetime): - raise TypeError("fromutc() requires a datetime argument") - if dt.tzinfo is not self: - raise ValueError("dt.tzinfo is not self") - - return f(self, dt) - - return fromutc - - -class _tzinfo(tzinfo): - """ - Base class for all ``dateutil`` ``tzinfo`` objects. - """ - - def is_ambiguous(self, dt): - """ - Whether or not the "wall time" of a given datetime is ambiguous in this - zone. - - :param dt: - A :py:class:`datetime.datetime`, naive or time zone aware. - - - :return: - Returns ``True`` if ambiguous, ``False`` otherwise. - - .. versionadded:: 2.6.0 - """ - - dt = dt.replace(tzinfo=self) - - wall_0 = enfold(dt, fold=0) - wall_1 = enfold(dt, fold=1) - - same_offset = wall_0.utcoffset() == wall_1.utcoffset() - same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None) - - return same_dt and not same_offset - - def _fold_status(self, dt_utc, dt_wall): - """ - Determine the fold status of a "wall" datetime, given a representation - of the same datetime as a (naive) UTC datetime. This is calculated based - on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all - datetimes, and that this offset is the actual number of hours separating - ``dt_utc`` and ``dt_wall``. - - :param dt_utc: - Representation of the datetime as UTC - - :param dt_wall: - Representation of the datetime as "wall time". This parameter must - either have a `fold` attribute or have a fold-naive - :class:`datetime.tzinfo` attached, otherwise the calculation may - fail. - """ - if self.is_ambiguous(dt_wall): - delta_wall = dt_wall - dt_utc - _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst())) - else: - _fold = 0 - - return _fold - - def _fold(self, dt): - return getattr(dt, 'fold', 0) - - def _fromutc(self, dt): - """ - Given a timezone-aware datetime in a given timezone, calculates a - timezone-aware datetime in a new timezone. - - Since this is the one time that we *know* we have an unambiguous - datetime object, we take this opportunity to determine whether the - datetime is ambiguous and in a "fold" state (e.g. if it's the first - occurence, chronologically, of the ambiguous datetime). - - :param dt: - A timezone-aware :class:`datetime.datetime` object. - """ - - # Re-implement the algorithm from Python's datetime.py - dtoff = dt.utcoffset() - if dtoff is None: - raise ValueError("fromutc() requires a non-None utcoffset() " - "result") - - # The original datetime.py code assumes that `dst()` defaults to - # zero during ambiguous times. PEP 495 inverts this presumption, so - # for pre-PEP 495 versions of python, we need to tweak the algorithm. - dtdst = dt.dst() - if dtdst is None: - raise ValueError("fromutc() requires a non-None dst() result") - delta = dtoff - dtdst - - dt += delta - # Set fold=1 so we can default to being in the fold for - # ambiguous dates. - dtdst = enfold(dt, fold=1).dst() - if dtdst is None: - raise ValueError("fromutc(): dt.dst gave inconsistent " - "results; cannot convert") - return dt + dtdst - - @_validate_fromutc_inputs - def fromutc(self, dt): - """ - Given a timezone-aware datetime in a given timezone, calculates a - timezone-aware datetime in a new timezone. - - Since this is the one time that we *know* we have an unambiguous - datetime object, we take this opportunity to determine whether the - datetime is ambiguous and in a "fold" state (e.g. if it's the first - occurance, chronologically, of the ambiguous datetime). - - :param dt: - A timezone-aware :class:`datetime.datetime` object. - """ - dt_wall = self._fromutc(dt) - - # Calculate the fold status given the two datetimes. - _fold = self._fold_status(dt, dt_wall) - - # Set the default fold value for ambiguous dates - return enfold(dt_wall, fold=_fold) - - -class tzrangebase(_tzinfo): - """ - This is an abstract base class for time zones represented by an annual - transition into and out of DST. Child classes should implement the following - methods: - - * ``__init__(self, *args, **kwargs)`` - * ``transitions(self, year)`` - this is expected to return a tuple of - datetimes representing the DST on and off transitions in standard - time. - - A fully initialized ``tzrangebase`` subclass should also provide the - following attributes: - * ``hasdst``: Boolean whether or not the zone uses DST. - * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects - representing the respective UTC offsets. - * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short - abbreviations in DST and STD, respectively. - * ``_hasdst``: Whether or not the zone has DST. - - .. versionadded:: 2.6.0 - """ - def __init__(self): - raise NotImplementedError('tzrangebase is an abstract base class') - - def utcoffset(self, dt): - isdst = self._isdst(dt) - - if isdst is None: - return None - elif isdst: - return self._dst_offset - else: - return self._std_offset - - def dst(self, dt): - isdst = self._isdst(dt) - - if isdst is None: - return None - elif isdst: - return self._dst_base_offset - else: - return ZERO - - @tzname_in_python2 - def tzname(self, dt): - if self._isdst(dt): - return self._dst_abbr - else: - return self._std_abbr - - def fromutc(self, dt): - """ Given a datetime in UTC, return local time """ - if not isinstance(dt, datetime): - raise TypeError("fromutc() requires a datetime argument") - - if dt.tzinfo is not self: - raise ValueError("dt.tzinfo is not self") - - # Get transitions - if there are none, fixed offset - transitions = self.transitions(dt.year) - if transitions is None: - return dt + self.utcoffset(dt) - - # Get the transition times in UTC - dston, dstoff = transitions - - dston -= self._std_offset - dstoff -= self._std_offset - - utc_transitions = (dston, dstoff) - dt_utc = dt.replace(tzinfo=None) - - isdst = self._naive_isdst(dt_utc, utc_transitions) - - if isdst: - dt_wall = dt + self._dst_offset - else: - dt_wall = dt + self._std_offset - - _fold = int(not isdst and self.is_ambiguous(dt_wall)) - - return enfold(dt_wall, fold=_fold) - - def is_ambiguous(self, dt): - """ - Whether or not the "wall time" of a given datetime is ambiguous in this - zone. - - :param dt: - A :py:class:`datetime.datetime`, naive or time zone aware. - - - :return: - Returns ``True`` if ambiguous, ``False`` otherwise. - - .. versionadded:: 2.6.0 - """ - if not self.hasdst: - return False - - start, end = self.transitions(dt.year) - - dt = dt.replace(tzinfo=None) - return (end <= dt < end + self._dst_base_offset) - - def _isdst(self, dt): - if not self.hasdst: - return False - elif dt is None: - return None - - transitions = self.transitions(dt.year) - - if transitions is None: - return False - - dt = dt.replace(tzinfo=None) - - isdst = self._naive_isdst(dt, transitions) - - # Handle ambiguous dates - if not isdst and self.is_ambiguous(dt): - return not self._fold(dt) - else: - return isdst - - def _naive_isdst(self, dt, transitions): - dston, dstoff = transitions - - dt = dt.replace(tzinfo=None) - - if dston < dstoff: - isdst = dston <= dt < dstoff - else: - isdst = not dstoff <= dt < dston - - return isdst - - @property - def _dst_base_offset(self): - return self._dst_offset - self._std_offset - - __hash__ = None - - def __ne__(self, other): - return not (self == other) - - def __repr__(self): - return "%s(...)" % self.__class__.__name__ - - __reduce__ = object.__reduce__ diff --git a/lib/dateutil/tz/_factories.py b/lib/dateutil/tz/_factories.py deleted file mode 100644 index de2e0c1..0000000 --- a/lib/dateutil/tz/_factories.py +++ /dev/null @@ -1,49 +0,0 @@ -from datetime import timedelta - - -class _TzSingleton(type): - def __init__(cls, *args, **kwargs): - cls.__instance = None - super(_TzSingleton, cls).__init__(*args, **kwargs) - - def __call__(cls): - if cls.__instance is None: - cls.__instance = super(_TzSingleton, cls).__call__() - return cls.__instance - -class _TzFactory(type): - def instance(cls, *args, **kwargs): - """Alternate constructor that returns a fresh instance""" - return type.__call__(cls, *args, **kwargs) - - -class _TzOffsetFactory(_TzFactory): - def __init__(cls, *args, **kwargs): - cls.__instances = {} - - def __call__(cls, name, offset): - if isinstance(offset, timedelta): - key = (name, offset.total_seconds()) - else: - key = (name, offset) - - instance = cls.__instances.get(key, None) - if instance is None: - instance = cls.__instances.setdefault(key, - cls.instance(name, offset)) - return instance - - -class _TzStrFactory(_TzFactory): - def __init__(cls, *args, **kwargs): - cls.__instances = {} - - def __call__(cls, s, posix_offset=False): - key = (s, posix_offset) - instance = cls.__instances.get(key, None) - - if instance is None: - instance = cls.__instances.setdefault(key, - cls.instance(s, posix_offset)) - return instance - diff --git a/lib/dateutil/tz/tz.py b/lib/dateutil/tz/tz.py deleted file mode 100644 index ac82b9c..0000000 --- a/lib/dateutil/tz/tz.py +++ /dev/null @@ -1,1785 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This module offers timezone implementations subclassing the abstract -:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format -files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, -etc), TZ environment string (in all known formats), given ranges (with help -from relative deltas), local machine timezone, fixed offset timezone, and UTC -timezone. -""" -import datetime -import struct -import time -import sys -import os -import bisect - -import six -from six import string_types -from six.moves import _thread -from ._common import tzname_in_python2, _tzinfo -from ._common import tzrangebase, enfold -from ._common import _validate_fromutc_inputs - -from ._factories import _TzSingleton, _TzOffsetFactory -from ._factories import _TzStrFactory -try: - from .win import tzwin, tzwinlocal -except ImportError: - tzwin = tzwinlocal = None - -ZERO = datetime.timedelta(0) -EPOCH = datetime.datetime.utcfromtimestamp(0) -EPOCHORDINAL = EPOCH.toordinal() - - -@six.add_metaclass(_TzSingleton) -class tzutc(datetime.tzinfo): - """ - This is a tzinfo object that represents the UTC time zone. - - **Examples:** - - .. doctest:: - - >>> from datetime import * - >>> from dateutil.tz import * - - >>> datetime.now() - datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) - - >>> datetime.now(tzutc()) - datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) - - >>> datetime.now(tzutc()).tzname() - 'UTC' - - .. versionchanged:: 2.7.0 - ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will - always return the same object. - - .. doctest:: - - >>> from dateutil.tz import tzutc, UTC - >>> tzutc() is tzutc() - True - >>> tzutc() is UTC - True - """ - def utcoffset(self, dt): - return ZERO - - def dst(self, dt): - return ZERO - - @tzname_in_python2 - def tzname(self, dt): - return "UTC" - - def is_ambiguous(self, dt): - """ - Whether or not the "wall time" of a given datetime is ambiguous in this - zone. - - :param dt: - A :py:class:`datetime.datetime`, naive or time zone aware. - - - :return: - Returns ``True`` if ambiguous, ``False`` otherwise. - - .. versionadded:: 2.6.0 - """ - return False - - @_validate_fromutc_inputs - def fromutc(self, dt): - """ - Fast track version of fromutc() returns the original ``dt`` object for - any valid :py:class:`datetime.datetime` object. - """ - return dt - - def __eq__(self, other): - if not isinstance(other, (tzutc, tzoffset)): - return NotImplemented - - return (isinstance(other, tzutc) or - (isinstance(other, tzoffset) and other._offset == ZERO)) - - __hash__ = None - - def __ne__(self, other): - return not (self == other) - - def __repr__(self): - return "%s()" % self.__class__.__name__ - - __reduce__ = object.__reduce__ - - -@six.add_metaclass(_TzOffsetFactory) -class tzoffset(datetime.tzinfo): - """ - A simple class for representing a fixed offset from UTC. - - :param name: - The timezone name, to be returned when ``tzname()`` is called. - :param offset: - The time zone offset in seconds, or (since version 2.6.0, represented - as a :py:class:`datetime.timedelta` object). - """ - def __init__(self, name, offset): - self._name = name - - try: - # Allow a timedelta - offset = offset.total_seconds() - except (TypeError, AttributeError): - pass - self._offset = datetime.timedelta(seconds=offset) - - def utcoffset(self, dt): - return self._offset - - def dst(self, dt): - return ZERO - - @tzname_in_python2 - def tzname(self, dt): - return self._name - - @_validate_fromutc_inputs - def fromutc(self, dt): - return dt + self._offset - - def is_ambiguous(self, dt): - """ - Whether or not the "wall time" of a given datetime is ambiguous in this - zone. - - :param dt: - A :py:class:`datetime.datetime`, naive or time zone aware. - :return: - Returns ``True`` if ambiguous, ``False`` otherwise. - - .. versionadded:: 2.6.0 - """ - return False - - def __eq__(self, other): - if not isinstance(other, tzoffset): - return NotImplemented - - return self._offset == other._offset - - __hash__ = None - - def __ne__(self, other): - return not (self == other) - - def __repr__(self): - return "%s(%s, %s)" % (self.__class__.__name__, - repr(self._name), - int(self._offset.total_seconds())) - - __reduce__ = object.__reduce__ - - -class tzlocal(_tzinfo): - """ - A :class:`tzinfo` subclass built around the ``time`` timezone functions. - """ - def __init__(self): - super(tzlocal, self).__init__() - - self._std_offset = datetime.timedelta(seconds=-time.timezone) - if time.daylight: - self._dst_offset = datetime.timedelta(seconds=-time.altzone) - else: - self._dst_offset = self._std_offset - - self._dst_saved = self._dst_offset - self._std_offset - self._hasdst = bool(self._dst_saved) - self._tznames = tuple(time.tzname) - - def utcoffset(self, dt): - if dt is None and self._hasdst: - return None - - if self._isdst(dt): - return self._dst_offset - else: - return self._std_offset - - def dst(self, dt): - if dt is None and self._hasdst: - return None - - if self._isdst(dt): - return self._dst_offset - self._std_offset - else: - return ZERO - - @tzname_in_python2 - def tzname(self, dt): - return self._tznames[self._isdst(dt)] - - def is_ambiguous(self, dt): - """ - Whether or not the "wall time" of a given datetime is ambiguous in this - zone. - - :param dt: - A :py:class:`datetime.datetime`, naive or time zone aware. - - - :return: - Returns ``True`` if ambiguous, ``False`` otherwise. - - .. versionadded:: 2.6.0 - """ - naive_dst = self._naive_is_dst(dt) - return (not naive_dst and - (naive_dst != self._naive_is_dst(dt - self._dst_saved))) - - def _naive_is_dst(self, dt): - timestamp = _datetime_to_timestamp(dt) - return time.localtime(timestamp + time.timezone).tm_isdst - - def _isdst(self, dt, fold_naive=True): - # We can't use mktime here. It is unstable when deciding if - # the hour near to a change is DST or not. - # - # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, - # dt.minute, dt.second, dt.weekday(), 0, -1)) - # return time.localtime(timestamp).tm_isdst - # - # The code above yields the following result: - # - # >>> import tz, datetime - # >>> t = tz.tzlocal() - # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() - # 'BRDT' - # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() - # 'BRST' - # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() - # 'BRST' - # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() - # 'BRDT' - # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() - # 'BRDT' - # - # Here is a more stable implementation: - # - if not self._hasdst: - return False - - # Check for ambiguous times: - dstval = self._naive_is_dst(dt) - fold = getattr(dt, 'fold', None) - - if self.is_ambiguous(dt): - if fold is not None: - return not self._fold(dt) - else: - return True - - return dstval - - def __eq__(self, other): - if isinstance(other, tzlocal): - return (self._std_offset == other._std_offset and - self._dst_offset == other._dst_offset) - elif isinstance(other, tzutc): - return (not self._hasdst and - self._tznames[0] in {'UTC', 'GMT'} and - self._std_offset == ZERO) - elif isinstance(other, tzoffset): - return (not self._hasdst and - self._tznames[0] == other._name and - self._std_offset == other._offset) - else: - return NotImplemented - - __hash__ = None - - def __ne__(self, other): - return not (self == other) - - def __repr__(self): - return "%s()" % self.__class__.__name__ - - __reduce__ = object.__reduce__ - - -class _ttinfo(object): - __slots__ = ["offset", "delta", "isdst", "abbr", - "isstd", "isgmt", "dstoffset"] - - def __init__(self): - for attr in self.__slots__: - setattr(self, attr, None) - - def __repr__(self): - l = [] - for attr in self.__slots__: - value = getattr(self, attr) - if value is not None: - l.append("%s=%s" % (attr, repr(value))) - return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) - - def __eq__(self, other): - if not isinstance(other, _ttinfo): - return NotImplemented - - return (self.offset == other.offset and - self.delta == other.delta and - self.isdst == other.isdst and - self.abbr == other.abbr and - self.isstd == other.isstd and - self.isgmt == other.isgmt and - self.dstoffset == other.dstoffset) - - __hash__ = None - - def __ne__(self, other): - return not (self == other) - - def __getstate__(self): - state = {} - for name in self.__slots__: - state[name] = getattr(self, name, None) - return state - - def __setstate__(self, state): - for name in self.__slots__: - if name in state: - setattr(self, name, state[name]) - - -class _tzfile(object): - """ - Lightweight class for holding the relevant transition and time zone - information read from binary tzfiles. - """ - attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list', - 'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first'] - - def __init__(self, **kwargs): - for attr in self.attrs: - setattr(self, attr, kwargs.get(attr, None)) - - -class tzfile(_tzinfo): - """ - This is a ``tzinfo`` subclass thant allows one to use the ``tzfile(5)`` - format timezone files to extract current and historical zone information. - - :param fileobj: - This can be an opened file stream or a file name that the time zone - information can be read from. - - :param filename: - This is an optional parameter specifying the source of the time zone - information in the event that ``fileobj`` is a file object. If omitted - and ``fileobj`` is a file stream, this parameter will be set either to - ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``. - - See `Sources for Time Zone and Daylight Saving Time Data - <https://data.iana.org/time-zones/tz-link.html>`_ for more information. - Time zone files can be compiled from the `IANA Time Zone database files - <https://www.iana.org/time-zones>`_ with the `zic time zone compiler - <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_ - - .. note:: - - Only construct a ``tzfile`` directly if you have a specific timezone - file on disk that you want to read into a Python ``tzinfo`` object. - If you want to get a ``tzfile`` representing a specific IANA zone, - (e.g. ``'America/New_York'``), you should call - :func:`dateutil.tz.gettz` with the zone identifier. - - - **Examples:** - - Using the US Eastern time zone as an example, we can see that a ``tzfile`` - provides time zone information for the standard Daylight Saving offsets: - - .. testsetup:: tzfile - - from dateutil.tz import gettz - from datetime import datetime - - .. doctest:: tzfile - - >>> NYC = gettz('America/New_York') - >>> NYC - tzfile('/usr/share/zoneinfo/America/New_York') - - >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST - 2016-01-03 00:00:00-05:00 - - >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT - 2016-07-07 00:00:00-04:00 - - - The ``tzfile`` structure contains a fully history of the time zone, - so historical dates will also have the right offsets. For example, before - the adoption of the UTC standards, New York used local solar mean time: - - .. doctest:: tzfile - - >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT - 1901-04-12 00:00:00-04:56 - - And during World War II, New York was on "Eastern War Time", which was a - state of permanent daylight saving time: - - .. doctest:: tzfile - - >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT - 1944-02-07 00:00:00-04:00 - - """ - - def __init__(self, fileobj, filename=None): - super(tzfile, self).__init__() - - file_opened_here = False - if isinstance(fileobj, string_types): - self._filename = fileobj - fileobj = open(fileobj, 'rb') - file_opened_here = True - elif filename is not None: - self._filename = filename - elif hasattr(fileobj, "name"): - self._filename = fileobj.name - else: - self._filename = repr(fileobj) - - if fileobj is not None: - if not file_opened_here: - fileobj = _ContextWrapper(fileobj) - - with fileobj as file_stream: - tzobj = self._read_tzfile(file_stream) - - self._set_tzdata(tzobj) - - def _set_tzdata(self, tzobj): - """ Set the time zone data of this object from a _tzfile object """ - # Copy the relevant attributes over as private attributes - for attr in _tzfile.attrs: - setattr(self, '_' + attr, getattr(tzobj, attr)) - - def _read_tzfile(self, fileobj): - out = _tzfile() - - # From tzfile(5): - # - # The time zone information files used by tzset(3) - # begin with the magic characters "TZif" to identify - # them as time zone information files, followed by - # sixteen bytes reserved for future use, followed by - # six four-byte values of type long, written in a - # ``standard'' byte order (the high-order byte - # of the value is written first). - if fileobj.read(4).decode() != "TZif": - raise ValueError("magic not found") - - fileobj.read(16) - - ( - # The number of UTC/local indicators stored in the file. - ttisgmtcnt, - - # The number of standard/wall indicators stored in the file. - ttisstdcnt, - - # The number of leap seconds for which data is - # stored in the file. - leapcnt, - - # The number of "transition times" for which data - # is stored in the file. - timecnt, - - # The number of "local time types" for which data - # is stored in the file (must not be zero). - typecnt, - - # The number of characters of "time zone - # abbreviation strings" stored in the file. - charcnt, - - ) = struct.unpack(">6l", fileobj.read(24)) - - # The above header is followed by tzh_timecnt four-byte - # values of type long, sorted in ascending order. - # These values are written in ``standard'' byte order. - # Each is used as a transition time (as returned by - # time(2)) at which the rules for computing local time - # change. - - if timecnt: - out.trans_list_utc = list(struct.unpack(">%dl" % timecnt, - fileobj.read(timecnt*4))) - else: - out.trans_list_utc = [] - - # Next come tzh_timecnt one-byte values of type unsigned - # char; each one tells which of the different types of - # ``local time'' types described in the file is associated - # with the same-indexed transition time. These values - # serve as indices into an array of ttinfo structures that - # appears next in the file. - - if timecnt: - out.trans_idx = struct.unpack(">%dB" % timecnt, - fileobj.read(timecnt)) - else: - out.trans_idx = [] - - # Each ttinfo structure is written as a four-byte value - # for tt_gmtoff of type long, in a standard byte - # order, followed by a one-byte value for tt_isdst - # and a one-byte value for tt_abbrind. In each - # structure, tt_gmtoff gives the number of - # seconds to be added to UTC, tt_isdst tells whether - # tm_isdst should be set by localtime(3), and - # tt_abbrind serves as an index into the array of - # time zone abbreviation characters that follow the - # ttinfo structure(s) in the file. - - ttinfo = [] - - for i in range(typecnt): - ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) - - abbr = fileobj.read(charcnt).decode() - - # Then there are tzh_leapcnt pairs of four-byte - # values, written in standard byte order; the - # first value of each pair gives the time (as - # returned by time(2)) at which a leap second - # occurs; the second gives the total number of - # leap seconds to be applied after the given time. - # The pairs of values are sorted in ascending order - # by time. - - # Not used, for now (but seek for correct file position) - if leapcnt: - fileobj.seek(leapcnt * 8, os.SEEK_CUR) - - # Then there are tzh_ttisstdcnt standard/wall - # indicators, each stored as a one-byte value; - # they tell whether the transition times associated - # with local time types were specified as standard - # time or wall clock time, and are used when - # a time zone file is used in handling POSIX-style - # time zone environment variables. - - if ttisstdcnt: - isstd = struct.unpack(">%db" % ttisstdcnt, - fileobj.read(ttisstdcnt)) - - # Finally, there are tzh_ttisgmtcnt UTC/local - # indicators, each stored as a one-byte value; - # they tell whether the transition times associated - # with local time types were specified as UTC or - # local time, and are used when a time zone file - # is used in handling POSIX-style time zone envi- - # ronment variables. - - if ttisgmtcnt: - isgmt = struct.unpack(">%db" % ttisgmtcnt, - fileobj.read(ttisgmtcnt)) - - # Build ttinfo list - out.ttinfo_list = [] - for i in range(typecnt): - gmtoff, isdst, abbrind = ttinfo[i] - # Round to full-minutes if that's not the case. Python's - # datetime doesn't accept sub-minute timezones. Check - # http://python.org/sf/1447945 for some information. - gmtoff = 60 * ((gmtoff + 30) // 60) - tti = _ttinfo() - tti.offset = gmtoff - tti.dstoffset = datetime.timedelta(0) - tti.delta = datetime.timedelta(seconds=gmtoff) - tti.isdst = isdst - tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)] - tti.isstd = (ttisstdcnt > i and isstd[i] != 0) - tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0) - out.ttinfo_list.append(tti) - - # Replace ttinfo indexes for ttinfo objects. - out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx] - - # Set standard, dst, and before ttinfos. before will be - # used when a given time is before any transitions, - # and will be set to the first non-dst ttinfo, or to - # the first dst, if all of them are dst. - out.ttinfo_std = None - out.ttinfo_dst = None - out.ttinfo_before = None - if out.ttinfo_list: - if not out.trans_list_utc: - out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0] - else: - for i in range(timecnt-1, -1, -1): - tti = out.trans_idx[i] - if not out.ttinfo_std and not tti.isdst: - out.ttinfo_std = tti - elif not out.ttinfo_dst and tti.isdst: - out.ttinfo_dst = tti - - if out.ttinfo_std and out.ttinfo_dst: - break - else: - if out.ttinfo_dst and not out.ttinfo_std: - out.ttinfo_std = out.ttinfo_dst - - for tti in out.ttinfo_list: - if not tti.isdst: - out.ttinfo_before = tti - break - else: - out.ttinfo_before = out.ttinfo_list[0] - - # Now fix transition times to become relative to wall time. - # - # I'm not sure about this. In my tests, the tz source file - # is setup to wall time, and in the binary file isstd and - # isgmt are off, so it should be in wall time. OTOH, it's - # always in gmt time. Let me know if you have comments - # about this. - laststdoffset = None - out.trans_list = [] - for i, tti in enumerate(out.trans_idx): - if not tti.isdst: - offset = tti.offset - laststdoffset = offset - else: - if laststdoffset is not None: - # Store the DST offset as well and update it in the list - tti.dstoffset = tti.offset - laststdoffset - out.trans_idx[i] = tti - - offset = laststdoffset or 0 - - out.trans_list.append(out.trans_list_utc[i] + offset) - - # In case we missed any DST offsets on the way in for some reason, make - # a second pass over the list, looking for the /next/ DST offset. - laststdoffset = None - for i in reversed(range(len(out.trans_idx))): - tti = out.trans_idx[i] - if tti.isdst: - if not (tti.dstoffset or laststdoffset is None): - tti.dstoffset = tti.offset - laststdoffset - else: - laststdoffset = tti.offset - - if not isinstance(tti.dstoffset, datetime.timedelta): - tti.dstoffset = datetime.timedelta(seconds=tti.dstoffset) - - out.trans_idx[i] = tti - - out.trans_idx = tuple(out.trans_idx) - out.trans_list = tuple(out.trans_list) - out.trans_list_utc = tuple(out.trans_list_utc) - - return out - - def _find_last_transition(self, dt, in_utc=False): - # If there's no list, there are no transitions to find - if not self._trans_list: - return None - - timestamp = _datetime_to_timestamp(dt) - - # Find where the timestamp fits in the transition list - if the - # timestamp is a transition time, it's part of the "after" period. - trans_list = self._trans_list_utc if in_utc else self._trans_list - idx = bisect.bisect_right(trans_list, timestamp) - - # We want to know when the previous transition was, so subtract off 1 - return idx - 1 - - def _get_ttinfo(self, idx): - # For no list or after the last transition, default to _ttinfo_std - if idx is None or (idx + 1) >= len(self._trans_list): - return self._ttinfo_std - - # If there is a list and the time is before it, return _ttinfo_before - if idx < 0: - return self._ttinfo_before - - return self._trans_idx[idx] - - def _find_ttinfo(self, dt): - idx = self._resolve_ambiguous_time(dt) - - return self._get_ttinfo(idx) - - def fromutc(self, dt): - """ - The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`. - - :param dt: - A :py:class:`datetime.datetime` object. - - :raises TypeError: - Raised if ``dt`` is not a :py:class:`datetime.datetime` object. - - :raises ValueError: - Raised if this is called with a ``dt`` which does not have this - ``tzinfo`` attached. - - :return: - Returns a :py:class:`datetime.datetime` object representing the - wall time in ``self``'s time zone. - """ - # These isinstance checks are in datetime.tzinfo, so we'll preserve - # them, even if we don't care about duck typing. - if not isinstance(dt, datetime.datetime): - raise TypeError("fromutc() requires a datetime argument") - - if dt.tzinfo is not self: - raise ValueError("dt.tzinfo is not self") - - # First treat UTC as wall time and get the transition we're in. - idx = self._find_last_transition(dt, in_utc=True) - tti = self._get_ttinfo(idx) - - dt_out = dt + datetime.timedelta(seconds=tti.offset) - - fold = self.is_ambiguous(dt_out, idx=idx) - - return enfold(dt_out, fold=int(fold)) - - def is_ambiguous(self, dt, idx=None): - """ - Whether or not the "wall time" of a given datetime is ambiguous in this - zone. - - :param dt: - A :py:class:`datetime.datetime`, naive or time zone aware. - - - :return: - Returns ``True`` if ambiguous, ``False`` otherwise. - - .. versionadded:: 2.6.0 - """ - if idx is None: - idx = self._find_last_transition(dt) - - # Calculate the difference in offsets from current to previous - timestamp = _datetime_to_timestamp(dt) - tti = self._get_ttinfo(idx) - - if idx is None or idx <= 0: - return False - - od = self._get_ttinfo(idx - 1).offset - tti.offset - tt = self._trans_list[idx] # Transition time - - return timestamp < tt + od - - def _resolve_ambiguous_time(self, dt): - idx = self._find_last_transition(dt) - - # If we have no transitions, return the index - _fold = self._fold(dt) - if idx is None or idx == 0: - return idx - - # If it's ambiguous and we're in a fold, shift to a different index. - idx_offset = int(not _fold and self.is_ambiguous(dt, idx)) - - return idx - idx_offset - - def utcoffset(self, dt): - if dt is None: - return None - - if not self._ttinfo_std: - return ZERO - - return self._find_ttinfo(dt).delta - - def dst(self, dt): - if dt is None: - return None - - if not self._ttinfo_dst: - return ZERO - - tti = self._find_ttinfo(dt) - - if not tti.isdst: - return ZERO - - # The documentation says that utcoffset()-dst() must - # be constant for every dt. - return tti.dstoffset - - @tzname_in_python2 - def tzname(self, dt): - if not self._ttinfo_std or dt is None: - return None - return self._find_ttinfo(dt).abbr - - def __eq__(self, other): - if not isinstance(other, tzfile): - return NotImplemented - return (self._trans_list == other._trans_list and - self._trans_idx == other._trans_idx and - self._ttinfo_list == other._ttinfo_list) - - __hash__ = None - - def __ne__(self, other): - return not (self == other) - - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, repr(self._filename)) - - def __reduce__(self): - return self.__reduce_ex__(None) - - def __reduce_ex__(self, protocol): - return (self.__class__, (None, self._filename), self.__dict__) - - -class tzrange(tzrangebase): - """ - The ``tzrange`` object is a time zone specified by a set of offsets and - abbreviations, equivalent to the way the ``TZ`` variable can be specified - in POSIX-like systems, but using Python delta objects to specify DST - start, end and offsets. - - :param stdabbr: - The abbreviation for standard time (e.g. ``'EST'``). - - :param stdoffset: - An integer or :class:`datetime.timedelta` object or equivalent - specifying the base offset from UTC. - - If unspecified, +00:00 is used. - - :param dstabbr: - The abbreviation for DST / "Summer" time (e.g. ``'EDT'``). - - If specified, with no other DST information, DST is assumed to occur - and the default behavior or ``dstoffset``, ``start`` and ``end`` is - used. If unspecified and no other DST information is specified, it - is assumed that this zone has no DST. - - If this is unspecified and other DST information is *is* specified, - DST occurs in the zone but the time zone abbreviation is left - unchanged. - - :param dstoffset: - A an integer or :class:`datetime.timedelta` object or equivalent - specifying the UTC offset during DST. If unspecified and any other DST - information is specified, it is assumed to be the STD offset +1 hour. - - :param start: - A :class:`relativedelta.relativedelta` object or equivalent specifying - the time and time of year that daylight savings time starts. To - specify, for example, that DST starts at 2AM on the 2nd Sunday in - March, pass: - - ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))`` - - If unspecified and any other DST information is specified, the default - value is 2 AM on the first Sunday in April. - - :param end: - A :class:`relativedelta.relativedelta` object or equivalent - representing the time and time of year that daylight savings time - ends, with the same specification method as in ``start``. One note is - that this should point to the first time in the *standard* zone, so if - a transition occurs at 2AM in the DST zone and the clocks are set back - 1 hour to 1AM, set the ``hours`` parameter to +1. - - - **Examples:** - - .. testsetup:: tzrange - - from dateutil.tz import tzrange, tzstr - - .. doctest:: tzrange - - >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") - True - - >>> from dateutil.relativedelta import * - >>> range1 = tzrange("EST", -18000, "EDT") - >>> range2 = tzrange("EST", -18000, "EDT", -14400, - ... relativedelta(hours=+2, month=4, day=1, - ... weekday=SU(+1)), - ... relativedelta(hours=+1, month=10, day=31, - ... weekday=SU(-1))) - >>> tzstr('EST5EDT') == range1 == range2 - True - - """ - def __init__(self, stdabbr, stdoffset=None, - dstabbr=None, dstoffset=None, - start=None, end=None): - - global relativedelta - from dateutil import relativedelta - - self._std_abbr = stdabbr - self._dst_abbr = dstabbr - - try: - stdoffset = stdoffset.total_seconds() - except (TypeError, AttributeError): - pass - - try: - dstoffset = dstoffset.total_seconds() - except (TypeError, AttributeError): - pass - - if stdoffset is not None: - self._std_offset = datetime.timedelta(seconds=stdoffset) - else: - self._std_offset = ZERO - - if dstoffset is not None: - self._dst_offset = datetime.timedelta(seconds=dstoffset) - elif dstabbr and stdoffset is not None: - self._dst_offset = self._std_offset + datetime.timedelta(hours=+1) - else: - self._dst_offset = ZERO - - if dstabbr and start is None: - self._start_delta = relativedelta.relativedelta( - hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) - else: - self._start_delta = start - - if dstabbr and end is None: - self._end_delta = relativedelta.relativedelta( - hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) - else: - self._end_delta = end - - self._dst_base_offset_ = self._dst_offset - self._std_offset - self.hasdst = bool(self._start_delta) - - def transitions(self, year): - """ - For a given year, get the DST on and off transition times, expressed - always on the standard time side. For zones with no transitions, this - function returns ``None``. - - :param year: - The year whose transitions you would like to query. - - :return: - Returns a :class:`tuple` of :class:`datetime.datetime` objects, - ``(dston, dstoff)`` for zones with an annual DST transition, or - ``None`` for fixed offset zones. - """ - if not self.hasdst: - return None - - base_year = datetime.datetime(year, 1, 1) - - start = base_year + self._start_delta - end = base_year + self._end_delta - - return (start, end) - - def __eq__(self, other): - if not isinstance(other, tzrange): - return NotImplemented - - return (self._std_abbr == other._std_abbr and - self._dst_abbr == other._dst_abbr and - self._std_offset == other._std_offset and - self._dst_offset == other._dst_offset and - self._start_delta == other._start_delta and - self._end_delta == other._end_delta) - - @property - def _dst_base_offset(self): - return self._dst_base_offset_ - - -@six.add_metaclass(_TzStrFactory) -class tzstr(tzrange): - """ - ``tzstr`` objects are time zone objects specified by a time-zone string as - it would be passed to a ``TZ`` variable on POSIX-style systems (see - the `GNU C Library: TZ Variable`_ for more details). - - There is one notable exception, which is that POSIX-style time zones use an - inverted offset format, so normally ``GMT+3`` would be parsed as an offset - 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an - offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX - behavior, pass a ``True`` value to ``posix_offset``. - - The :class:`tzrange` object provides the same functionality, but is - specified using :class:`relativedelta.relativedelta` objects. rather than - strings. - - :param s: - A time zone string in ``TZ`` variable format. This can be a - :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: - :class:`unicode`) or a stream emitting unicode characters - (e.g. :class:`StringIO`). - - :param posix_offset: - Optional. If set to ``True``, interpret strings such as ``GMT+3`` or - ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the - POSIX standard. - - .. caution:: - - Prior to version 2.7.0, this function also supported time zones - in the format: - - * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600`` - * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600`` - - This format is non-standard and has been deprecated; this function - will raise a :class:`DeprecatedTZFormatWarning` until - support is removed in a future version. - - .. _`GNU C Library: TZ Variable`: - https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html - """ - def __init__(self, s, posix_offset=False): - global parser - from dateutil.parser import _parser as parser - - self._s = s - - res = parser._parsetz(s) - if res is None or res.any_unused_tokens: - raise ValueError("unknown string format") - - # Here we break the compatibility with the TZ variable handling. - # GMT-3 actually *means* the timezone -3. - if res.stdabbr in ("GMT", "UTC") and not posix_offset: - res.stdoffset *= -1 - - # We must initialize it first, since _delta() needs - # _std_offset and _dst_offset set. Use False in start/end - # to avoid building it two times. - tzrange.__init__(self, res.stdabbr, res.stdoffset, - res.dstabbr, res.dstoffset, - start=False, end=False) - - if not res.dstabbr: - self._start_delta = None - self._end_delta = None - else: - self._start_delta = self._delta(res.start) - if self._start_delta: - self._end_delta = self._delta(res.end, isend=1) - - self.hasdst = bool(self._start_delta) - - def _delta(self, x, isend=0): - from dateutil import relativedelta - kwargs = {} - if x.month is not None: - kwargs["month"] = x.month - if x.weekday is not None: - kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) - if x.week > 0: - kwargs["day"] = 1 - else: - kwargs["day"] = 31 - elif x.day: - kwargs["day"] = x.day - elif x.yday is not None: - kwargs["yearday"] = x.yday - elif x.jyday is not None: - kwargs["nlyearday"] = x.jyday - if not kwargs: - # Default is to start on first sunday of april, and end - # on last sunday of october. - if not isend: - kwargs["month"] = 4 - kwargs["day"] = 1 - kwargs["weekday"] = relativedelta.SU(+1) - else: - kwargs["month"] = 10 - kwargs["day"] = 31 - kwargs["weekday"] = relativedelta.SU(-1) - if x.time is not None: - kwargs["seconds"] = x.time - else: - # Default is 2AM. - kwargs["seconds"] = 7200 - if isend: - # Convert to standard time, to follow the documented way - # of working with the extra hour. See the documentation - # of the tzinfo class. - delta = self._dst_offset - self._std_offset - kwargs["seconds"] -= delta.seconds + delta.days * 86400 - return relativedelta.relativedelta(**kwargs) - - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, repr(self._s)) - - -class _tzicalvtzcomp(object): - def __init__(self, tzoffsetfrom, tzoffsetto, isdst, - tzname=None, rrule=None): - self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) - self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) - self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom - self.isdst = isdst - self.tzname = tzname - self.rrule = rrule - - -class _tzicalvtz(_tzinfo): - def __init__(self, tzid, comps=[]): - super(_tzicalvtz, self).__init__() - - self._tzid = tzid - self._comps = comps - self._cachedate = [] - self._cachecomp = [] - self._cache_lock = _thread.allocate_lock() - - def _find_comp(self, dt): - if len(self._comps) == 1: - return self._comps[0] - - dt = dt.replace(tzinfo=None) - - try: - with self._cache_lock: - return self._cachecomp[self._cachedate.index( - (dt, self._fold(dt)))] - except ValueError: - pass - - lastcompdt = None - lastcomp = None - - for comp in self._comps: - compdt = self._find_compdt(comp, dt) - - if compdt and (not lastcompdt or lastcompdt < compdt): - lastcompdt = compdt - lastcomp = comp - - if not lastcomp: - # RFC says nothing about what to do when a given - # time is before the first onset date. We'll look for the - # first standard component, or the first component, if - # none is found. - for comp in self._comps: - if not comp.isdst: - lastcomp = comp - break - else: - lastcomp = comp[0] - - with self._cache_lock: - self._cachedate.insert(0, (dt, self._fold(dt))) - self._cachecomp.insert(0, lastcomp) - - if len(self._cachedate) > 10: - self._cachedate.pop() - self._cachecomp.pop() - - return lastcomp - - def _find_compdt(self, comp, dt): - if comp.tzoffsetdiff < ZERO and self._fold(dt): - dt -= comp.tzoffsetdiff - - compdt = comp.rrule.before(dt, inc=True) - - return compdt - - def utcoffset(self, dt): - if dt is None: - return None - - return self._find_comp(dt).tzoffsetto - - def dst(self, dt): - comp = self._find_comp(dt) - if comp.isdst: - return comp.tzoffsetdiff - else: - return ZERO - - @tzname_in_python2 - def tzname(self, dt): - return self._find_comp(dt).tzname - - def __repr__(self): - return "<tzicalvtz %s>" % repr(self._tzid) - - __reduce__ = object.__reduce__ - - -class tzical(object): - """ - This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure - as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects. - - :param `fileobj`: - A file or stream in iCalendar format, which should be UTF-8 encoded - with CRLF endings. - - .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545 - """ - def __init__(self, fileobj): - global rrule - from dateutil import rrule - - if isinstance(fileobj, string_types): - self._s = fileobj - # ical should be encoded in UTF-8 with CRLF - fileobj = open(fileobj, 'r') - else: - self._s = getattr(fileobj, 'name', repr(fileobj)) - fileobj = _ContextWrapper(fileobj) - - self._vtz = {} - - with fileobj as fobj: - self._parse_rfc(fobj.read()) - - def keys(self): - """ - Retrieves the available time zones as a list. - """ - return list(self._vtz.keys()) - - def get(self, tzid=None): - """ - Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``. - - :param tzid: - If there is exactly one time zone available, omitting ``tzid`` - or passing :py:const:`None` value returns it. Otherwise a valid - key (which can be retrieved from :func:`keys`) is required. - - :raises ValueError: - Raised if ``tzid`` is not specified but there are either more - or fewer than 1 zone defined. - - :returns: - Returns either a :py:class:`datetime.tzinfo` object representing - the relevant time zone or :py:const:`None` if the ``tzid`` was - not found. - """ - if tzid is None: - if len(self._vtz) == 0: - raise ValueError("no timezones defined") - elif len(self._vtz) > 1: - raise ValueError("more than one timezone available") - tzid = next(iter(self._vtz)) - - return self._vtz.get(tzid) - - def _parse_offset(self, s): - s = s.strip() - if not s: - raise ValueError("empty offset") - if s[0] in ('+', '-'): - signal = (-1, +1)[s[0] == '+'] - s = s[1:] - else: - signal = +1 - if len(s) == 4: - return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal - elif len(s) == 6: - return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal - else: - raise ValueError("invalid offset: " + s) - - def _parse_rfc(self, s): - lines = s.splitlines() - if not lines: - raise ValueError("empty string") - - # Unfold - i = 0 - while i < len(lines): - line = lines[i].rstrip() - if not line: - del lines[i] - elif i > 0 and line[0] == " ": - lines[i-1] += line[1:] - del lines[i] - else: - i += 1 - - tzid = None - comps = [] - invtz = False - comptype = None - for line in lines: - if not line: - continue - name, value = line.split(':', 1) - parms = name.split(';') - if not parms: - raise ValueError("empty property name") - name = parms[0].upper() - parms = parms[1:] - if invtz: - if name == "BEGIN": - if value in ("STANDARD", "DAYLIGHT"): - # Process component - pass - else: - raise ValueError("unknown component: "+value) - comptype = value - founddtstart = False - tzoffsetfrom = None - tzoffsetto = None - rrulelines = [] - tzname = None - elif name == "END": - if value == "VTIMEZONE": - if comptype: - raise ValueError("component not closed: "+comptype) - if not tzid: - raise ValueError("mandatory TZID not found") - if not comps: - raise ValueError( - "at least one component is needed") - # Process vtimezone - self._vtz[tzid] = _tzicalvtz(tzid, comps) - invtz = False - elif value == comptype: - if not founddtstart: - raise ValueError("mandatory DTSTART not found") - if tzoffsetfrom is None: - raise ValueError( - "mandatory TZOFFSETFROM not found") - if tzoffsetto is None: - raise ValueError( - "mandatory TZOFFSETFROM not found") - # Process component - rr = None - if rrulelines: - rr = rrule.rrulestr("\n".join(rrulelines), - compatible=True, - ignoretz=True, - cache=True) - comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, - (comptype == "DAYLIGHT"), - tzname, rr) - comps.append(comp) - comptype = None - else: - raise ValueError("invalid component end: "+value) - elif comptype: - if name == "DTSTART": - # DTSTART in VTIMEZONE takes a subset of valid RRULE - # values under RFC 5545. - for parm in parms: - if parm != 'VALUE=DATE-TIME': - msg = ('Unsupported DTSTART param in ' + - 'VTIMEZONE: ' + parm) - raise ValueError(msg) - rrulelines.append(line) - founddtstart = True - elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): - rrulelines.append(line) - elif name == "TZOFFSETFROM": - if parms: - raise ValueError( - "unsupported %s parm: %s " % (name, parms[0])) - tzoffsetfrom = self._parse_offset(value) - elif name == "TZOFFSETTO": - if parms: - raise ValueError( - "unsupported TZOFFSETTO parm: "+parms[0]) - tzoffsetto = self._parse_offset(value) - elif name == "TZNAME": - if parms: - raise ValueError( - "unsupported TZNAME parm: "+parms[0]) - tzname = value - elif name == "COMMENT": - pass - else: - raise ValueError("unsupported property: "+name) - else: - if name == "TZID": - if parms: - raise ValueError( - "unsupported TZID parm: "+parms[0]) - tzid = value - elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): - pass - else: - raise ValueError("unsupported property: "+name) - elif name == "BEGIN" and value == "VTIMEZONE": - tzid = None - comps = [] - invtz = True - - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, repr(self._s)) - - -if sys.platform != "win32": - TZFILES = ["/etc/localtime", "localtime"] - TZPATHS = ["/usr/share/zoneinfo", - "/usr/lib/zoneinfo", - "/usr/share/lib/zoneinfo", - "/etc/zoneinfo"] -else: - TZFILES = [] - TZPATHS = [] - - -def __get_gettz(): - tzlocal_classes = (tzlocal,) - if tzwinlocal is not None: - tzlocal_classes += (tzwinlocal,) - - class GettzFunc(object): - """ - Retrieve a time zone object from a string representation - - This function is intended to retrieve the :py:class:`tzinfo` subclass - that best represents the time zone that would be used if a POSIX - `TZ variable`_ were set to the same value. - - If no argument or an empty string is passed to ``gettz``, local time - is returned: - - .. code-block:: python3 - - >>> gettz() - tzfile('/etc/localtime') - - This function is also the preferred way to map IANA tz database keys - to :class:`tzfile` objects: - - .. code-block:: python3 - - >>> gettz('Pacific/Kiritimati') - tzfile('/usr/share/zoneinfo/Pacific/Kiritimati') - - On Windows, the standard is extended to include the Windows-specific - zone names provided by the operating system: - - .. code-block:: python3 - - >>> gettz('Egypt Standard Time') - tzwin('Egypt Standard Time') - - Passing a GNU ``TZ`` style string time zone specification returns a - :class:`tzstr` object: - - .. code-block:: python3 - - >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') - tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') - - :param name: - A time zone name (IANA, or, on Windows, Windows keys), location of - a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone - specifier. An empty string, no argument or ``None`` is interpreted - as local time. - - :return: - Returns an instance of one of ``dateutil``'s :py:class:`tzinfo` - subclasses. - - .. versionchanged:: 2.7.0 - - After version 2.7.0, any two calls to ``gettz`` using the same - input strings will return the same object: - - .. code-block:: python3 - - >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago') - True - - In addition to improving performance, this ensures that - `"same zone" semantics`_ are used for datetimes in the same zone. - - - .. _`TZ variable`: - https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html - - .. _`"same zone" semantics`: - https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html - """ - def __init__(self): - - self.__instances = {} - self._cache_lock = _thread.allocate_lock() - - def __call__(self, name=None): - with self._cache_lock: - rv = self.__instances.get(name, None) - - if rv is None: - rv = self.nocache(name=name) - if not (name is None or isinstance(rv, tzlocal_classes)): - # tzlocal is slightly more complicated than the other - # time zone providers because it depends on environment - # at construction time, so don't cache that. - self.__instances[name] = rv - - return rv - - def cache_clear(self): - with self._cache_lock: - self.__instances = {} - - @staticmethod - def nocache(name=None): - """A non-cached version of gettz""" - tz = None - if not name: - try: - name = os.environ["TZ"] - except KeyError: - pass - if name is None or name == ":": - for filepath in TZFILES: - if not os.path.isabs(filepath): - filename = filepath - for path in TZPATHS: - filepath = os.path.join(path, filename) - if os.path.isfile(filepath): - break - else: - continue - if os.path.isfile(filepath): - try: - tz = tzfile(filepath) - break - except (IOError, OSError, ValueError): - pass - else: - tz = tzlocal() - else: - if name.startswith(":"): - name = name[1:] - if os.path.isabs(name): - if os.path.isfile(name): - tz = tzfile(name) - else: - tz = None - else: - for path in TZPATHS: - filepath = os.path.join(path, name) - if not os.path.isfile(filepath): - filepath = filepath.replace(' ', '_') - if not os.path.isfile(filepath): - continue - try: - tz = tzfile(filepath) - break - except (IOError, OSError, ValueError): - pass - else: - tz = None - if tzwin is not None: - try: - tz = tzwin(name) - except WindowsError: - tz = None - - if not tz: - from dateutil.zoneinfo import get_zonefile_instance - tz = get_zonefile_instance().get(name) - - if not tz: - for c in name: - # name is not a tzstr unless it has at least - # one offset. For short values of "name", an - # explicit for loop seems to be the fastest way - # To determine if a string contains a digit - if c in "0123456789": - try: - tz = tzstr(name) - except ValueError: - pass - break - else: - if name in ("GMT", "UTC"): - tz = tzutc() - elif name in time.tzname: - tz = tzlocal() - return tz - - return GettzFunc() - - -gettz = __get_gettz() -del __get_gettz - - -def datetime_exists(dt, tz=None): - """ - Given a datetime and a time zone, determine whether or not a given datetime - would fall in a gap. - - :param dt: - A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` - is provided.) - - :param tz: - A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If - ``None`` or not provided, the datetime's own time zone will be used. - - :return: - Returns a boolean value whether or not the "wall time" exists in - ``tz``. - - .. versionadded:: 2.7.0 - """ - if tz is None: - if dt.tzinfo is None: - raise ValueError('Datetime is naive and no time zone provided.') - tz = dt.tzinfo - - dt = dt.replace(tzinfo=None) - - # This is essentially a test of whether or not the datetime can survive - # a round trip to UTC. - dt_rt = dt.replace(tzinfo=tz).astimezone(tzutc()).astimezone(tz) - dt_rt = dt_rt.replace(tzinfo=None) - - return dt == dt_rt - - -def datetime_ambiguous(dt, tz=None): - """ - Given a datetime and a time zone, determine whether or not a given datetime - is ambiguous (i.e if there are two times differentiated only by their DST - status). - - :param dt: - A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` - is provided.) - - :param tz: - A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If - ``None`` or not provided, the datetime's own time zone will be used. - - :return: - Returns a boolean value whether or not the "wall time" is ambiguous in - ``tz``. - - .. versionadded:: 2.6.0 - """ - if tz is None: - if dt.tzinfo is None: - raise ValueError('Datetime is naive and no time zone provided.') - - tz = dt.tzinfo - - # If a time zone defines its own "is_ambiguous" function, we'll use that. - is_ambiguous_fn = getattr(tz, 'is_ambiguous', None) - if is_ambiguous_fn is not None: - try: - return tz.is_ambiguous(dt) - except Exception: - pass - - # If it doesn't come out and tell us it's ambiguous, we'll just check if - # the fold attribute has any effect on this particular date and time. - dt = dt.replace(tzinfo=tz) - wall_0 = enfold(dt, fold=0) - wall_1 = enfold(dt, fold=1) - - same_offset = wall_0.utcoffset() == wall_1.utcoffset() - same_dst = wall_0.dst() == wall_1.dst() - - return not (same_offset and same_dst) - - -def resolve_imaginary(dt): - """ - Given a datetime that may be imaginary, return an existing datetime. - - This function assumes that an imaginary datetime represents what the - wall time would be in a zone had the offset transition not occurred, so - it will always fall forward by the transition's change in offset. - - .. doctest:: - - >>> from dateutil import tz - >>> from datetime import datetime - >>> NYC = tz.gettz('America/New_York') - >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC))) - 2017-03-12 03:30:00-04:00 - - >>> KIR = tz.gettz('Pacific/Kiritimati') - >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR))) - 1995-01-02 12:30:00+14:00 - - As a note, :func:`datetime.astimezone` is guaranteed to produce a valid, - existing datetime, so a round-trip to and from UTC is sufficient to get - an extant datetime, however, this generally "falls back" to an earlier time - rather than falling forward to the STD side (though no guarantees are made - about this behavior). - - :param dt: - A :class:`datetime.datetime` which may or may not exist. - - :return: - Returns an existing :class:`datetime.datetime`. If ``dt`` was not - imaginary, the datetime returned is guaranteed to be the same object - passed to the function. - - .. versionadded:: 2.7.0 - """ - if dt.tzinfo is not None and not datetime_exists(dt): - - curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset() - old_offset = (dt - datetime.timedelta(hours=24)).utcoffset() - - dt += curr_offset - old_offset - - return dt - - -def _datetime_to_timestamp(dt): - """ - Convert a :class:`datetime.datetime` object to an epoch timestamp in - seconds since January 1, 1970, ignoring the time zone. - """ - return (dt.replace(tzinfo=None) - EPOCH).total_seconds() - - -class _ContextWrapper(object): - """ - Class for wrapping contexts so that they are passed through in a - with statement. - """ - def __init__(self, context): - self.context = context - - def __enter__(self): - return self.context - - def __exit__(*args, **kwargs): - pass - -# vim:ts=4:sw=4:et diff --git a/lib/dateutil/tz/win.py b/lib/dateutil/tz/win.py deleted file mode 100644 index def4353..0000000 --- a/lib/dateutil/tz/win.py +++ /dev/null @@ -1,331 +0,0 @@ -# This code was originally contributed by Jeffrey Harris. -import datetime -import struct - -from six.moves import winreg -from six import text_type - -try: - import ctypes - from ctypes import wintypes -except ValueError: - # ValueError is raised on non-Windows systems for some horrible reason. - raise ImportError("Running tzwin on non-Windows system") - -from ._common import tzrangebase - -__all__ = ["tzwin", "tzwinlocal", "tzres"] - -ONEWEEK = datetime.timedelta(7) - -TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" -TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" -TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" - - -def _settzkeyname(): - handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) - try: - winreg.OpenKey(handle, TZKEYNAMENT).Close() - TZKEYNAME = TZKEYNAMENT - except WindowsError: - TZKEYNAME = TZKEYNAME9X - handle.Close() - return TZKEYNAME - - -TZKEYNAME = _settzkeyname() - - -class tzres(object): - """ - Class for accessing `tzres.dll`, which contains timezone name related - resources. - - .. versionadded:: 2.5.0 - """ - p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char - - def __init__(self, tzres_loc='tzres.dll'): - # Load the user32 DLL so we can load strings from tzres - user32 = ctypes.WinDLL('user32') - - # Specify the LoadStringW function - user32.LoadStringW.argtypes = (wintypes.HINSTANCE, - wintypes.UINT, - wintypes.LPWSTR, - ctypes.c_int) - - self.LoadStringW = user32.LoadStringW - self._tzres = ctypes.WinDLL(tzres_loc) - self.tzres_loc = tzres_loc - - def load_name(self, offset): - """ - Load a timezone name from a DLL offset (integer). - - >>> from dateutil.tzwin import tzres - >>> tzr = tzres() - >>> print(tzr.load_name(112)) - 'Eastern Standard Time' - - :param offset: - A positive integer value referring to a string from the tzres dll. - - ..note: - Offsets found in the registry are generally of the form - `@tzres.dll,-114`. The offset in this case if 114, not -114. - - """ - resource = self.p_wchar() - lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR) - nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0) - return resource[:nchar] - - def name_from_string(self, tzname_str): - """ - Parse strings as returned from the Windows registry into the time zone - name as defined in the registry. - - >>> from dateutil.tzwin import tzres - >>> tzr = tzres() - >>> print(tzr.name_from_string('@tzres.dll,-251')) - 'Dateline Daylight Time' - >>> print(tzr.name_from_string('Eastern Standard Time')) - 'Eastern Standard Time' - - :param tzname_str: - A timezone name string as returned from a Windows registry key. - - :return: - Returns the localized timezone string from tzres.dll if the string - is of the form `@tzres.dll,-offset`, else returns the input string. - """ - if not tzname_str.startswith('@'): - return tzname_str - - name_splt = tzname_str.split(',-') - try: - offset = int(name_splt[1]) - except: - raise ValueError("Malformed timezone string.") - - return self.load_name(offset) - - -class tzwinbase(tzrangebase): - """tzinfo class based on win32's timezones available in the registry.""" - def __init__(self): - raise NotImplementedError('tzwinbase is an abstract base class') - - def __eq__(self, other): - # Compare on all relevant dimensions, including name. - if not isinstance(other, tzwinbase): - return NotImplemented - - return (self._std_offset == other._std_offset and - self._dst_offset == other._dst_offset and - self._stddayofweek == other._stddayofweek and - self._dstdayofweek == other._dstdayofweek and - self._stdweeknumber == other._stdweeknumber and - self._dstweeknumber == other._dstweeknumber and - self._stdhour == other._stdhour and - self._dsthour == other._dsthour and - self._stdminute == other._stdminute and - self._dstminute == other._dstminute and - self._std_abbr == other._std_abbr and - self._dst_abbr == other._dst_abbr) - - @staticmethod - def list(): - """Return a list of all time zones known to the system.""" - with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: - with winreg.OpenKey(handle, TZKEYNAME) as tzkey: - result = [winreg.EnumKey(tzkey, i) - for i in range(winreg.QueryInfoKey(tzkey)[0])] - return result - - def display(self): - return self._display - - def transitions(self, year): - """ - For a given year, get the DST on and off transition times, expressed - always on the standard time side. For zones with no transitions, this - function returns ``None``. - - :param year: - The year whose transitions you would like to query. - - :return: - Returns a :class:`tuple` of :class:`datetime.datetime` objects, - ``(dston, dstoff)`` for zones with an annual DST transition, or - ``None`` for fixed offset zones. - """ - - if not self.hasdst: - return None - - dston = picknthweekday(year, self._dstmonth, self._dstdayofweek, - self._dsthour, self._dstminute, - self._dstweeknumber) - - dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek, - self._stdhour, self._stdminute, - self._stdweeknumber) - - # Ambiguous dates default to the STD side - dstoff -= self._dst_base_offset - - return dston, dstoff - - def _get_hasdst(self): - return self._dstmonth != 0 - - @property - def _dst_base_offset(self): - return self._dst_base_offset_ - - -class tzwin(tzwinbase): - - def __init__(self, name): - self._name = name - - with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: - tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name) - with winreg.OpenKey(handle, tzkeyname) as tzkey: - keydict = valuestodict(tzkey) - - self._std_abbr = keydict["Std"] - self._dst_abbr = keydict["Dlt"] - - self._display = keydict["Display"] - - # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm - tup = struct.unpack("=3l16h", keydict["TZI"]) - stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 - dstoffset = stdoffset-tup[2] # + DaylightBias * -1 - self._std_offset = datetime.timedelta(minutes=stdoffset) - self._dst_offset = datetime.timedelta(minutes=dstoffset) - - # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs - # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx - (self._stdmonth, - self._stddayofweek, # Sunday = 0 - self._stdweeknumber, # Last = 5 - self._stdhour, - self._stdminute) = tup[4:9] - - (self._dstmonth, - self._dstdayofweek, # Sunday = 0 - self._dstweeknumber, # Last = 5 - self._dsthour, - self._dstminute) = tup[12:17] - - self._dst_base_offset_ = self._dst_offset - self._std_offset - self.hasdst = self._get_hasdst() - - def __repr__(self): - return "tzwin(%s)" % repr(self._name) - - def __reduce__(self): - return (self.__class__, (self._name,)) - - -class tzwinlocal(tzwinbase): - def __init__(self): - with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: - with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: - keydict = valuestodict(tzlocalkey) - - self._std_abbr = keydict["StandardName"] - self._dst_abbr = keydict["DaylightName"] - - try: - tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME, - sn=self._std_abbr) - with winreg.OpenKey(handle, tzkeyname) as tzkey: - _keydict = valuestodict(tzkey) - self._display = _keydict["Display"] - except OSError: - self._display = None - - stdoffset = -keydict["Bias"]-keydict["StandardBias"] - dstoffset = stdoffset-keydict["DaylightBias"] - - self._std_offset = datetime.timedelta(minutes=stdoffset) - self._dst_offset = datetime.timedelta(minutes=dstoffset) - - # For reasons unclear, in this particular key, the day of week has been - # moved to the END of the SYSTEMTIME structure. - tup = struct.unpack("=8h", keydict["StandardStart"]) - - (self._stdmonth, - self._stdweeknumber, # Last = 5 - self._stdhour, - self._stdminute) = tup[1:5] - - self._stddayofweek = tup[7] - - tup = struct.unpack("=8h", keydict["DaylightStart"]) - - (self._dstmonth, - self._dstweeknumber, # Last = 5 - self._dsthour, - self._dstminute) = tup[1:5] - - self._dstdayofweek = tup[7] - - self._dst_base_offset_ = self._dst_offset - self._std_offset - self.hasdst = self._get_hasdst() - - def __repr__(self): - return "tzwinlocal()" - - def __str__(self): - # str will return the standard name, not the daylight name. - return "tzwinlocal(%s)" % repr(self._std_abbr) - - def __reduce__(self): - return (self.__class__, ()) - - -def picknthweekday(year, month, dayofweek, hour, minute, whichweek): - """ dayofweek == 0 means Sunday, whichweek 5 means last instance """ - first = datetime.datetime(year, month, 1, hour, minute) - - # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6), - # Because 7 % 7 = 0 - weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1) - wd = weekdayone + ((whichweek - 1) * ONEWEEK) - if (wd.month != month): - wd -= ONEWEEK - - return wd - - -def valuestodict(key): - """Convert a registry key's values to a dictionary.""" - dout = {} - size = winreg.QueryInfoKey(key)[1] - tz_res = None - - for i in range(size): - key_name, value, dtype = winreg.EnumValue(key, i) - if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN: - # If it's a DWORD (32-bit integer), it's stored as unsigned - convert - # that to a proper signed integer - if value & (1 << 31): - value = value - (1 << 32) - elif dtype == winreg.REG_SZ: - # If it's a reference to the tzres DLL, load the actual string - if value.startswith('@tzres'): - tz_res = tz_res or tzres() - value = tz_res.name_from_string(value) - - value = value.rstrip('\x00') # Remove trailing nulls - - dout[key_name] = value - - return dout diff --git a/lib/dateutil/tzwin.py b/lib/dateutil/tzwin.py deleted file mode 100644 index cebc673..0000000 --- a/lib/dateutil/tzwin.py +++ /dev/null @@ -1,2 +0,0 @@ -# tzwin has moved to dateutil.tz.win -from .tz.win import * diff --git a/lib/dateutil/utils.py b/lib/dateutil/utils.py deleted file mode 100644 index ebcce6a..0000000 --- a/lib/dateutil/utils.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This module offers general convenience and utility functions for dealing with -datetimes. - -.. versionadded:: 2.7.0 -""" -from __future__ import unicode_literals - -from datetime import datetime, time - - -def today(tzinfo=None): - """ - Returns a :py:class:`datetime` representing the current day at midnight - - :param tzinfo: - The time zone to attach (also used to determine the current day). - - :return: - A :py:class:`datetime.datetime` object representing the current day - at midnight. - """ - - dt = datetime.now(tzinfo) - return datetime.combine(dt.date(), time(0, tzinfo=tzinfo)) - - -def default_tzinfo(dt, tzinfo): - """ - Sets the the ``tzinfo`` parameter on naive datetimes only - - This is useful for example when you are provided a datetime that may have - either an implicit or explicit time zone, such as when parsing a time zone - string. - - .. doctest:: - - >>> from dateutil.tz import tzoffset - >>> from dateutil.parser import parse - >>> from dateutil.utils import default_tzinfo - >>> dflt_tz = tzoffset("EST", -18000) - >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz)) - 2014-01-01 12:30:00+00:00 - >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz)) - 2014-01-01 12:30:00-05:00 - - :param dt: - The datetime on which to replace the time zone - - :param tzinfo: - The :py:class:`datetime.tzinfo` subclass instance to assign to - ``dt`` if (and only if) it is naive. - - :return: - Returns an aware :py:class:`datetime.datetime`. - """ - if dt.tzinfo is not None: - return dt - else: - return dt.replace(tzinfo=tzinfo) - - -def within_delta(dt1, dt2, delta): - """ - Useful for comparing two datetimes that may a negilible difference - to be considered equal. - """ - delta = abs(delta) - difference = dt1 - dt2 - return -delta <= difference <= delta diff --git a/lib/dateutil/zoneinfo/__init__.py b/lib/dateutil/zoneinfo/__init__.py deleted file mode 100644 index 34f11ad..0000000 --- a/lib/dateutil/zoneinfo/__init__.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- coding: utf-8 -*- -import warnings -import json - -from tarfile import TarFile -from pkgutil import get_data -from io import BytesIO - -from dateutil.tz import tzfile as _tzfile - -__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] - -ZONEFILENAME = "dateutil-zoneinfo.tar.gz" -METADATA_FN = 'METADATA' - - -class tzfile(_tzfile): - def __reduce__(self): - return (gettz, (self._filename,)) - - -def getzoneinfofile_stream(): - try: - return BytesIO(get_data(__name__, ZONEFILENAME)) - except IOError as e: # TODO switch to FileNotFoundError? - warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) - return None - - -class ZoneInfoFile(object): - def __init__(self, zonefile_stream=None): - if zonefile_stream is not None: - with TarFile.open(fileobj=zonefile_stream) as tf: - self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name) - for zf in tf.getmembers() - if zf.isfile() and zf.name != METADATA_FN} - # deal with links: They'll point to their parent object. Less - # waste of memory - links = {zl.name: self.zones[zl.linkname] - for zl in tf.getmembers() if - zl.islnk() or zl.issym()} - self.zones.update(links) - try: - metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) - metadata_str = metadata_json.read().decode('UTF-8') - self.metadata = json.loads(metadata_str) - except KeyError: - # no metadata in tar file - self.metadata = None - else: - self.zones = {} - self.metadata = None - - def get(self, name, default=None): - """ - Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method - for retrieving zones from the zone dictionary. - - :param name: - The name of the zone to retrieve. (Generally IANA zone names) - - :param default: - The value to return in the event of a missing key. - - .. versionadded:: 2.6.0 - - """ - return self.zones.get(name, default) - - -# The current API has gettz as a module function, although in fact it taps into -# a stateful class. So as a workaround for now, without changing the API, we -# will create a new "global" class instance the first time a user requests a -# timezone. Ugly, but adheres to the api. -# -# TODO: Remove after deprecation period. -_CLASS_ZONE_INSTANCE = [] - - -def get_zonefile_instance(new_instance=False): - """ - This is a convenience function which provides a :class:`ZoneInfoFile` - instance using the data provided by the ``dateutil`` package. By default, it - caches a single instance of the ZoneInfoFile object and returns that. - - :param new_instance: - If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and - used as the cached instance for the next call. Otherwise, new instances - are created only as necessary. - - :return: - Returns a :class:`ZoneInfoFile` object. - - .. versionadded:: 2.6 - """ - if new_instance: - zif = None - else: - zif = getattr(get_zonefile_instance, '_cached_instance', None) - - if zif is None: - zif = ZoneInfoFile(getzoneinfofile_stream()) - - get_zonefile_instance._cached_instance = zif - - return zif - - -def gettz(name): - """ - This retrieves a time zone from the local zoneinfo tarball that is packaged - with dateutil. - - :param name: - An IANA-style time zone name, as found in the zoneinfo file. - - :return: - Returns a :class:`dateutil.tz.tzfile` time zone object. - - .. warning:: - It is generally inadvisable to use this function, and it is only - provided for API compatibility with earlier versions. This is *not* - equivalent to ``dateutil.tz.gettz()``, which selects an appropriate - time zone based on the inputs, favoring system zoneinfo. This is ONLY - for accessing the dateutil-specific zoneinfo (which may be out of - date compared to the system zoneinfo). - - .. deprecated:: 2.6 - If you need to use a specific zoneinfofile over the system zoneinfo, - instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call - :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead. - - Use :func:`get_zonefile_instance` to retrieve an instance of the - dateutil-provided zoneinfo. - """ - warnings.warn("zoneinfo.gettz() will be removed in future versions, " - "to use the dateutil-provided zoneinfo files, instantiate a " - "ZoneInfoFile object and use ZoneInfoFile.zones.get() " - "instead. See the documentation for details.", - DeprecationWarning) - - if len(_CLASS_ZONE_INSTANCE) == 0: - _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) - return _CLASS_ZONE_INSTANCE[0].zones.get(name) - - -def gettz_db_metadata(): - """ Get the zonefile metadata - - See `zonefile_metadata`_ - - :returns: - A dictionary with the database metadata - - .. deprecated:: 2.6 - See deprecation warning in :func:`zoneinfo.gettz`. To get metadata, - query the attribute ``zoneinfo.ZoneInfoFile.metadata``. - """ - warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future " - "versions, to use the dateutil-provided zoneinfo files, " - "ZoneInfoFile object and query the 'metadata' attribute " - "instead. See the documentation for details.", - DeprecationWarning) - - if len(_CLASS_ZONE_INSTANCE) == 0: - _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) - return _CLASS_ZONE_INSTANCE[0].metadata diff --git a/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz b/lib/dateutil/zoneinfo/dateutil-zoneinfo.tar.gz deleted file mode 100644 index 6e8c05efd4f06dcb3b18cd919438255cd71ace47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154226 zcmX6^bzBth*A)>^@=JH<(%oGOEFIE~(v5U?cXx>_AX3uJN;gPLOLuqd?mPb8KMwcK zIrrTAJo91qv$H!x9sByVU+S#EON9AnHw$++8+$e%Cr1k#M@uJmx6dvwj*ZYhX}I6{ zmyFd>`SzM+F1nSCNisQPBaIl_9DI$tWGqReM0!uKQS_Rb1v6QBQ6|Fqg^czea?tBE zN=frx#M+fzBOy#$q82WZo0UgPV6)b~x8iyR>%7xP3*)t!r-ejclQ{{uJ$QL@wLSS9 zz4B?>at%1yIACe@^?CGaZ*TW~nx1U+_44wTi&1$bca)0k3m;&PA>g6uev&{KN*^b! zKcu)ly+IVNfZG_|DtI~HvGa=ZvNw2&`j76v=Sn|hqH=d{?57?8V@AEE#N&e8{r-b= zjkbEh1cx{o<_SXza>-$HQAN&E$cJE=!5d04Z2_MogO%5CM9KS-B?&~ykYG9-U2Gv9 zJN`|T2qS+?zzEw3wR1&S;c@n(Q!I2{gxg2*6>O&5NzCo^@q1gt`XM;}`}HNxE!DKZ z?DA@8;Mydphl7=MWl(H-S&a1q^}!Uww7|E-!py?<#6sUN&oEg?VU6+L!QLq$6%pC# z5&14TX(`Ut#ni#O9{=lPf6?COYvZ-HA#tC4an>==N=2e7b!VBU7sL}{BAw-=3(0Jb zG-;Vw=2+nvTb<D%KENg0of}*Q^$1Lh@rrU^@2#KiVHv!E1}qiEkr3eH5{g)V7pC1R z#fp!MKgG`pXb&IwEpUo|H>w&bZRMwmL`g3EH<MZxGoBIxh9GDy)$9$|e+T12pRFP= z$sQ{TD~kNTfr_8{q%lX&5%EX(xWu@*7YI&2Im5nR_n1$j4XP_&|7;vcpktiO6hfal zB*=-#$P37j*^5gmmybH#$%Q}dAHf9zWDy7BijOr{=VQ|d=oZm5oAC73PGY_~^})Io zF^5RRJ#=YZ(KjFVd7uy%flT}|Uy%rj=V888B{Cs<EKoJS{gn9G^KQ!9;z9!d!G!Uq zyP3bdrXu^m#^da=eUHCQ#Lsx#AaPghJhnih=n6DCjHE*>7qE+hl|e!@id+>-?n4`( zUg@;SfR$fWn1RFTXlOX1S@~Gjv`IHfrm!t;#(L56wQ;-gWs|zqGgir6rxQ#4_$Fh! zOIz_uo^!&bMuZ@99J(hXvam4ot@Y{m*^z~ht-%$=3Mc9bKP-$W<}SsZH77qBRRusH zhd$+Dt>B)$T{2nf3O;C8_59Ko?W#^MsJ_(zcH?On-w%U4nYnOPomF2)SFgSa7cc(2 zy>nZMl?|hb<MWIC({pd4-QSt=YBzg1LeB+m8b=N_yWay#gO@%<LzlkTc{fh={jl9y zOtFm`E8)*po1fzS`{QtZueDfjuG2OMx;iB~$jtZiGBe?qfiFe(L@Z@Dnzo?6Vcx!V z>!Xoug@5eqyat-k2mN#&At5W*i2^p?guGUly^(AND|Tnk;oQ>#j=+d!s6U5fir!+P zCx%$+yLMHV5#^f5i=%=1yi56%wZGE76{RZ78Mke%Q{`#2=~@sMQi+;!*B@E8t!qhe z*K*y8`M2tJ0d8Mqc>)f9$5FAB=nFeB(a?lZU2muga|lH*Pf~aI_%$lW)-;G-ggEB& z>v{6Hba1(hak;D@G2M~~!AXQrNrkY331Z_Y5+!5g!3l~q{q&$g2GAfQVws{Wj-n!t zqDC@C51e2CPA~!|n1B;LgA>fa2^Qc4D{z7hIKd8_-~di=I#!Nx1}C_J6WqZGp5O#; zntn0Rpg3qyB1!Pqkmx6Kc1~_;l5G6AF%0l7s(I{v_YlQr^8!w89g=L0xG_@jE>==Y z@6a<6UL;AjY}^<fco!e!8k6kZKeS?M9>B#tNRn+8H^v9vB?q}CBzq4It(cj^k)lK6 z#-wPRlan8YhVsnKjk&o6a0~lX57w2`h#y7VMeF1KK(FVy=yOEyi;%f%;b!e=QwZ<J z`$xH$r}d9{@KIPH;{bOvJkT5A>-E@Tb%KCeAf6)4AK`(mh^^C;(_AeCOzKG@DhpAH z2t@>Ayw^rBT-_ml|AFK7@}wh-PsPPK4xr7xL<pW_gtrE@A9>+p8v$|7lE|GzUVcrO z&224ZQ#h{waV$82huzVP-+$UG)Iomk@K&`Vv60QwosSK1n(feEJbasO(n_8>xW-Kf zz3*u%>-V1#KpPwA^wr6|HG1svvOafz+JJk<$r3$gCp=p5P}!xg2XqX`El2A&pD<iq zH{_4-kGdV&nk^qJD=J%I-*Fa*+4AHYNQ`^zMY5H)Zxk+`-i_Nl)`qOn>xtKk+?Y6x zS{9gjIz9xm`Cb!gVAr@0<Z+!<v_5C#xQdZ7?=Us?rR*t*wa0=o*$VxBrJZRKn!)=6 zN?$6Ll4Gbanx!yca}gyx{?>;yCTjA232W5daoydV^VL-p40L4Au1Id2-6kDSx3bb5 zJry=(5opi4NW3Y({LXRneU0?&Uc^TeiQ4B+?%tnb6m7URc89wjwg>zJws;b2b4!by zW2;Z=itYNI{*ldI13wF~wS$?d`BvJ6e*1)z-3gksHRZL)c9M3m{ve)1gG&6aO+M9r z!vxw9*<hE~MTp|Ag!5zp=%ip=sA>WZYj-^r<ydvyPJ(b(TX$aX%t|-9IL?kd@M!-z zkeIyJpZ(okrJ-n1oo#o}vA>a>@oW0f;$l~;h5Kzh3#qT(Fav3}$i5A3U{K#%@GdUM z^-U-9Ccckm9}{?&&_}C}4ZKU7Bsef6YGz)|#l0TXCj{Q51i2<AKMW30n442^a}$$h zC&rD*gLmmbuBpin!$TAn<^?aKE8@m<z`LxVtBmB<(V=Gxb8H^&MAB}HF5JS};pmyT zF+1=sKj<nqS!iNN)XKaXxiIQSun-RITJhZb6)ST%vOF@09vX=r28kXHi5@<Q9x;g? z`Tf9SknvlaS3cno{C!;ESYwJK6rV5%spK@)HLASGZ*p3gS7&Fp=poC^EBR1(rcX$v z`!|6WS^Um!CyOca4%XPUzr-))5z<ayl39I1M*;4cA~5mhzfhcMY2NSh#H4!Uq<Yk( zdLU9g1{n^n|IYL{NcDJvA-P$N$sXKw0SX8(lDmy$;5=i+@;y$QdsIR+x)xeL*xVF$ zwgpp1sFscXK=8~U_Eh|7{7bvE1zVLV5WC+dzb#p7XPzmqS3=E~L^?fBd*35d_Lr?s zVwq%(3jyz*LM}TgklzK*%?H_ibV@r2nY5m(L`Io!d;71-f6~TJK~hp6IVE+hbd>t_ zA@`TPQiGEP&&RSju)>Faqv?Q)YfJk$)kvARXBm=*QO--^5czK$D5=sWn59u&6e*(q z-G%*6XO9_`S&v}%>l$UQ<z|QMMVN%A@$t<lyl{dc7Lh%K$ScQ@4mh;Sen3pTk?rx# zjFk{QHd%Epa=;qAg9%7<XhO`_M?X)z(LrxikayO=SFe8leW%}j`)0v|`pg&Bq|+0x zn)<jVsw=uN4@rCy?m%N;j8)R_&tj~azAx3^;qNA!8D{crYj?$NQ=I1f&+(6e65BU5 zN70!h0SWUE<yyfVGvP+sqp1)ow%qXN1E&mQntO8Y%jU2bzXdF|okoY&vy!2Fpg>nJ z{5GxwC8&Xr#9Q%&jUDGD(dX@|o*{#X<YsJ;(_2mChIBQo-x(kHBn>d;e%URXvR|XQ zKi7>dLJ#J<97-n<E1jZ##`A$^tguOG*ktmfB>CGLzH3B9LicX(TcdH=HBuY#CAah~ zT}iqFuZl@Z1~NbP{4vt<0%p$zzrzvPgc*(>@>VH#6;>xk<9iv330DmlU8^M3OR}9z z{sOKUTOTHAulhgx&Yh93Cb1t7+_?02Pp9`3erH-F%GEf{cQfqtQg8EME%foXGQNse zj;9o$7EO2bzi-^SWfG`2yO-iym{Czb&2_5qo>1m*r}%Tq=w=z*`ejIHjwv_wie;6~ z<_mhDh>OKRUy4PF#OLg5)pU6TCieq6d8OqNs$!w`Cbpmg_a&;deo=gsCAotf@3OVo zw?!~H!8rfYck8C92$}kLe)b&uhT!0a<SF}NtazZxo_G0p6ERAjpDZ8>a`FSOA;opz zC(T>Sa$ZhI`V>=!T!TeW4&ps9r^!z!Abd;jC70ymI*OCltgYU5PGL*=6z4|(CD=d- z73fS7BD553JwKHY=3JH^Z5!&rn1Xwv30|wU?wOiMH-1`kFG@5iUWV_j#)X=0MSwf! zcO;-?2yyY#HE15<;oiIoF`)#T%zXU2{4l#<krjDnRA5`M>he|i>A~?TPe@>{)pwxc zYGB~URi=}Ru21B5jJ4KMc6%2EBLW^dBSWFWt<)wA%2C%9?MwvcT&?Y7S;AbQwnt@c zgBCu+&X>ykmGxScDoAyrzvAkrv1-R#>k@iGCGAvW`;W~l#?_9BYsY^I8fI(F0-SEH zaMnF^B|HSX<VgPB*Ey*U>KH8vb~)xrt(GqcdCcV={8_aqX?vP&KBQt0aG#`<f$e_C zGCLc%P3ROLTD;3PSh~wM+W&(H&NM^luqmUN5-;H#KO2e(HneR09bs&*g1u@#Nkus< zN{UR7(g-<;6nl3PxlU0-b@14@Wql4F<bq0#T#%VCGbmK)sO;)h=J{O*yBr=$?;K}T zkF>huv+faNH~KvpL;A(5>v!7!(2Rz2N{sg3^0BQ8u$i@PCr`SxZt{$#bMvL8b(Ey1 zF?gp}EEF&=sH|&N2f7-!UM#-f*>$OKIofI*IcX+lPQX25@qAQPJH6F2YhCCzedrEs z+v(a998FA*)TJVL(=6(CYVGY+(@M7<lcNq_w1B)k+fGGSIp-U7uHOByQNoCpF&lF) z!9{%IS4mjmXT)N>U8&vU4b|RU!>-!yKgm#+Wz|2ZDYdg`a>yL5He}1p<uPHGkV$2U z^O4d<?HeKVy-sFnL{DZBc%4)+dz)O5QN^w^s-s?A&}&i=u&(IZsi{@rT-l=SYQ<hP z9}%g4(y!Af%%fuG&Tu@pve{(QcCMA<JE>78dyKkRzoETY0aIFRUol)91iioY=GUIj z%q}{Z3?B(l-E8z(hV49BwOM0jT+fp^R+aJP{E@a3ZLhS9s;uP8{>vcXP#|@M!vPYg z@R7kd52Qz&Qn(%xJm~CYbs;wt?q)UGeM(p?(RFPdjqcH3c&c`3R+g|{O>7+!V-4Rf zAa@+<99T>BofM?W^`TCck~Nv>A#=)jx?U*@*SVKVux`<h9qarU%Uovjk*U6+YtpoF ze|tk8>iSk6QL@^I)Q&oeyl(rwpY?zS=g{E227}LN=rUiD8Ax$RYGriP87XnQD4bSk zttyM?tc8Yw!0RjBpRSC^b>WhDIm35}qCd6BZ>BPr_UBw1xq6B1%z)PcByYvZr{6ch zMAZ18;OGvHDCa@W%nO9U`eV`jIsH@f>&kA(XQ1pAf;)%@SI4qwtH%zn$HHyMTK3+5 z`N;R(OyWV*ri8p;T!~3g-;u<-fP+qoSQ^w8+Av8xDKL>VMWy7os89kq66d6s(I9Mf zn&7_fH?%1v&narA=ZdD$-5hV|QK7UsB<Cq#UdoG->PeF7Nt5cyk?JXt>Zy_HX_D&c zlIj_f>U|>BGY_V1h+}PvV{M6JZHr^=h-2;APfmHkQ}>#u?!Vrd6cU~~8lE~vo;psR zIzeRluUtv=$*60%RH^-BVmf&CG|~L1v$QL}>P>xr1|iG$k?IWv)9%Kx?n~migNZz7 zqD4?wY0o9`yuH5Q`TWf_4Xpj!8=JZh;5@ebE>lwFh3R>xE)CsFuKR-5+hH%xl;x4u za`BKm1nAJboD}{$UrVF?+sX9tm#;=$&|PU55>O!Jb!H{0LUq29CK;3_4XDY0${h@- zF98(=uwwvrV!+M`*v&bk%A`Zzi_(#URRrVm+&Ht!`a-)!>8Qafl5u%qL1|HvS%iGu zSSoT~FGWJc^RhVQ%C=u|P#ZJPjb=)-#Nrd89;Q|HNy3DaQZq=tpApLqV}!aZd?NX- zfoHZY9f3qo$AkHYsp#`B6?tXMKYV(af7X-wgT%CnkZ;B7NPb2Rnf{`piA8EpWJNum z3(mXm3tBb7NA}<ViYs0U;ePvDY|e;!jKjzE35vem2c*70x?UQM^u!F5s1BR{>Ut0K zssegVru7H;R|9?CykrWFL3+Xj3ZyK!J|!;vYYG&x=aBroo9_$iETBg{R_5jU#8DBA z<ZA(x!OrglH%x&w>R|q<u{Qk$SAr{|9;JOn{Qi`WcUOZw&lFuoUFph=rvAZd8!|Bz zou8a40%Gt2t6awA?UM>E^0@R2@<yhJgBSw9D)2b_7bJ_XIO}jfne)7$mHIVQ8k8K5 z1j<DQ^S_R>$0J!}<gC;CWZoO}BV7p@M2-gL$BDD2Az76EO8*wrJ!JV(E+QmZ86_zW z1(b^c<|m1>=O$US=B!%;Ji(+o83$herM=++nuCF59(I#$Ywqrjr;P^fNB8N9rLzd+ z4`w|)jfkU@jScwpL!e*%A!4i&F;in>nDf}iyLXtFG2ak<11+O;RjHcaWncR~zWmMR zieYS;mD(wVNZLL#BaX^KAwh!UtbJO24O$uI@u>*vcP_@UXlZO&r+lv@eBJGPxY694 zlMvRame^ETNqN+$AZ~0H>i5mV=E1xm`?B2L#Ja4Seu(L9WJ#I``Irv$sn74^x6)Po zH=Yvm!m3P1b`#ds$9v?1+bPEsvp&v)i3rr`RY#)|U!D;~iP;K{RcfQjXR`wrb$HI? z>~pM0j!AX0(U|Vuk#}#LdzTD$miLKqv~Too3Ndk(BBl4F;bX<cF8A_$QpwMx{45o1 zQ;Nl?x8`qqhQ-~|m<z;Dhkm3Y^xV0zx5ksjJSPk%7QLbk`7hu)XV43@32#GVM1Gvp zyJM$!0%c4k+hCvJ!V7n0%9?JLit{w#6QL8;WDor^dNM?i8^-x`NxmD)nQ`#W=(KKu z^G+QWi&ypysSbM~IJgfzC?r$!1qugKupo0FQ4%2%3P<@%ZSGX=PZr$e$mk;KuL;~B zc#PX}OlrT9^}l`r^}k551;UPGH~26(B%a_E3VHc!bbfWbk3St=)P0KzaVTZgW)3Wh z4q-z&)B79*;b#0UXQQT;y!18cT|X|$oR2(#W^6xa=qwrvAyX3L_Xs4tXh0?_`ua}h z!+V(@VIeVTZ%_!!@zLqk8EMLAD+pECe-&!KOAU$cVREPAX3A#F>r*h-=KUxb{T7`i zmoaZ3S7xIDj2Di7$jq1z9Lj(Dg2$AorAnZ(Y0gy{pogNVjg+n>35$Hm%t8s%7IrBr zEZ;gsxMHPgBehzvVIkU&ptspjcqH?$%msuwB_ccfrqJ~enf~pKyLc1ku#s=}-L^O6 zRH)8qDR;|ZaP1@N!50`f0ntMBeV9@9VRDIu@SBz0NY<N-nC{|6g^w6%BYZw~S}zA# zv@jA=Dzp;SeypC*weO;98sd;|+p9pm8T2HOYUf%oigQX<bSI~jDi6GNYP9%I>?{l^ zPfd_f)4S;D39hw*Wy6AZYA_)+SRe%x7*xMsDi<{(mLCU`*x?-s`;ro;ena`E25Z-D z>$(3T!UV|Uh#Z7T*p~bxm%=!88%Tp2m`V^X2JoZ#zUN#=hYwp)Pgo<Gu)E6(FB@2* z)%*iBg6l|PucV88<Wa&&Ud#bLZ)iE%SGN?BSd&8x7F{4x9%5p{{%IFm#>RD)pQkEf z_<(lv#ajL#*r4O~w|m6(_fNzp&Zk}n7rjN7scG!fGpYGIR>L=tJ^6sk*b+!Dbw+YN zy;x=)mM^^s4!gqymA=C0XFuBs8?SFW)A<5>J_`n;AcJvl?W%Mm3XE~@6O0DGGV#ny zgF!o#Ce|c$_WGeq*{zZfse{Aek+O0!-+Fhx86lULc8~9D2Jk`f_zZVa(qE31T(%6A z)N2W3b{QzMxV}wnJ>FQ+Z{?`Y9j)ms)zbaBUfGwK%F<OkU|7C|(dq*AS0_9>+y23( ztvDCsPp4B;E<aZoSTXk{I^8x8@u9+-P5MWpS+e$1lx;Q7nR3<X<E>>g*T<Ywe~x<J z*;k8Za?bTupCHF`@S??bpQPh0=cVJ^iqcAsE6Hh-VTLC$A-cI`cg5;J%Zgc=DH*#K z_v%^eC9JB8<Jy$Zzmckj!xnc-9q{=O=Tp0uj%PMV<372_rzdI1M{y}H6aARc_Ri=~ zY6^ibL2Q!rCmy8j+&dy$Z@$ax=PJjKIrvK34P?i(8ii))Ki%8!Vp|+pzomo<M2fO~ zcfVY-ICU#;S}A#!DIcA9>9VjVQsNj(>2h~fs31m9k>Puq#d?FjPIFhVV8Y$lWK)z% zpWS;{IrAtjliPbW`TN~VgB+0KfZfNrqpadv&#7;&guJpfKgw>}{RUS8Ev>wRs)Beq z*HjbqwvtfYnL!mZ*ovW@g~PR>{Blw;{w#xy`Aq#{f#3R%7P7Y{3uo{YCO8?6?+wY} zoNJ<{YcCzPhuL>Onhn?$rygbxUdQ|%2+*B>)@X8>fK#jMS+1INWQl~1_oByYc=%Mw zw_XTS7g*$#*61DDJPObBX6X}b91#8ykcjs`a8Rf@e)vM9XbF0B=##r%j!NHe#^veH zMk&}5;~~IMiYk#bWwytxCFGAF!JwTt{jECIA5{2fbO_VZoCrC3IBv`mY{?6%&%qtr zC9ltQqK*l8U7zPP=5}A(O~NcU=H=&XDhA@Ajtz~5NvgjXTb4r!g1q`k4?(pyUy_js z(?WvrR_sUq6#iNWMh^~HlHIx}NPh`y*w}2*(Z;-<k>mTVia`YXPaEfY$~G$Cb;avP zYj0_Zu(?-qF)u5We!O&&9|;K{E5|rq)t|!ooF3c>Dgn?4pdY{-fE@rg!BrrX4~R;7 zxPaQb>q)uzAc)+4u@U}M|45iPSvmfrHAHQ{I9Z`6ksnN<(PtYjPR2w8koo}W5-q+Y z)nSE#@82-vHK{=VXpM7zR{ITLnL4a20Jh3&5}o1E+7(#2%JtO1rWi}Xe)0GKqA*?c zdJ4QH#zF&h3XxR*01U<ohD7ZbCNsa3C!LZ7xl2>W6X5yts>@$b$uVsN$gu%rzcimH zK3c0nWgalx=UXBeJ;q+18J(6+1`#()AQuw@CoN}AN+SepI)LpnU`dyuj!z>32!rdX z9vMW67BP?;1EN15qJXL6HA(y%p1%bS+hTSwJ!V#p{DJ`ohnR+Rz`-;yM!h%@Y#T4G z-?48}XIq>&FMpmsNAAz-)UBiKd_5Ht4X3LRI&b~UQXzcaAsG6yLgY&%P&rbZ$gc`i zQu#M<h166&GRpe=tAt)dSrF{kwHz=m&u-D*D6Ta}@Vjbc>zS&AK(*8)r5l8BO=dOX z?`iqyB?9puLW2E<PrY|zhNpRl>0zwEYKOribGb0<a>Cbk((@9-7tXbJf|5eCh;duD zw1czWKy2Ia+gR}Jh%w~(mG2aL_wBby&`JMI<8gmYoIrXdi8*ttNRQ@Hd1?7|S#T-$ z`VLQezGkoHgwLp^ICD*TNWZ0e&N{CaZw-&u^iHi#g7~Kz_4Bq)TO$UIMtkLxxhoT1 zEvQ=msgG+}`9P-73Ey3y?T^N9b>+7|hD(LdwmIkgo1w#1>x8L}pX^e7W5wtbRi->9 zb6Av&#o12Q*w1qOViszjRtzlfV6|V30=FsK*K0*?eU8wUjslH;&HjZo-9}tdl!B#J zPT~hC_(^>lxcCklyJs7MC$`tEOUv~ZdrE^Y-;@S1NMz+moLQp#otKL3`yp$bM>G1y zQtl|T37{<-jDwI0wMN%0uTeCXKR=ud4DH=|l;)J)j-T9ONBET{*_tegXt!7#rn%O3 z&S2!0U+qNSvaeV3xUbGFtesqumY&+J@!sg>Sf2^8jm#cZ-5qzLq2wz0Zygso*k$*G z2roXh><EOnAD-Z%IOe{?aOaf~AG1qr+p;_K-7FP*RAbVBj-#Ex@C&n0$Mo%}zvuJx zWBRU(%fVJ4Ie5yU?FF@iWNSQF7t4-`8k<T|isQ|Liup<xiEBS`>W`G)ls*25G*HY1 z+rRmlXt--e-N-q{>*FH-l;u3GWcDQ9v_*llSTAICD3(iG<uy{&T1T1)T!w8(r?hqv zPP*J@%QO?&L<xxb`drjUW(uI<)KFY)&6tXJ9y!*vF1~6p-+W=nc=1BcFo>K^{8v04 z!(VVT@t5L0td@_PXe*4)uN>37UJ57}29wiu0kRb!3rKEauP|Pq$x(!m(@`NsD}E{J zW1w+H2P#nn6jlH-pgSIK5U8{Q8u0;*$bm-KfQ$ynq2zRMKt|j65FIJG`7S5EPZp@4 z1sdWxrnS5gP<Rd_r%ULG$5Y(^M^_}NZL$DnBEXCRn8N`xAVmVwpyVdEA=3q^W11kA zfI>$EIh|u~JRTHKwt%UwKu=+yCnX@`0CFTb-6<d=Ho)@8e?Q2h1wl}<PE)9U#f$&< zl<tn?cpdhqrV{5%ac+NrMSc)@KHV+L5y}dq4eAQx0qV+k`D2}VoR*KFKoKbGP#f^v zyt@AYkc2H$PJqQ9I6d(I@&Qx<=manhU=_d#02lx);KWb@U<<*Z6YJ5;<b5O8_DVou zDXg6R0Doowt#cL1JX6cZX=VT{09XOA0bmEf0e}+#7Xa=SwP_xR?KCeC`Dkp&K}W#p zwV?nO3c!XEhyZM;fC#{b8i>^Yl?6QnvWKu^oy!*spHkg)bddjzB%5<QiR5Ap)NF4g z&iyE$>XSeBj3_)oc>2N+1827gvCaS?#Er|OFha`m5n))4(={&dZxjX#+>CH%vRhU@ ztYhC$U%!E8Dv`GxpSIUtI?m9;>bi3CGqY0^x@H6p22`FhB9Wg0n$9$y=2IP>9NV)@ z*Sj7E(<n{+%@BTv!vsg)(r-CT#T6f1o;9Cz9CjgotFnz}l_K;OIu)ME?HF0=DRwyA zbP^=1cjpI*SbB{E(B}ES8Iw+{uJCz<AJ7AK`B)Whp?TV?zkcuWs2S^KM`{EX^U%-j zR7Rf1qFa=kv7f_q#;3JqU`Xf_%Eg}sTp)Am6fpU;zNtcKMIf0468NT)htfyG%^2&E zA<h1&uM?)uY#Kyz4L~Ywlt-GlKRQG(FjXXNMnS2d`7f~olC*z`9gxiaOPqj&U=T=L z|JD3UJShkj(gt%dzKJD$=NPF37H^@got>^_3Bwqz42-^QKQB~&GZ2`j?+4TLgt5u! z97Togi-)clw<GRuZsBGNfz$Fao*4*4`)%ON?|>o))cygPj%pa9@KA!I&}2Rk_#sg5 zst~wzoi3zvw>&&bF+}*+9mrnQZ8*&FdCCg$%S2S<jt;4(j2aED*`4Loo*orX&f;6N zDKL~deK91NQEw+%F@EaOp*&H?;y7{ngz=!T;o+iiv*a>jr23#={qnN0w}1O6lkzgY z5obHM9ecF7v@qprX+ifge~DwSS*4laPaT-O#lG=fAg>WSubu`v4)r_Y=mf18j}t~> z4D63FPY)w>w)c#5EbhM*c9_c3CU&dRyN)cFT#LfAZ@)MzwsKI}>@0{kXzG3eYi!yv z5w8EK);dfxDPK8jFzkP7Qg7_%Z`H4rQn<;v+CH3g(zk8N+9BJ+(YH0mNVPlBwJY~G zkJGkVd&+1#QqEb<Dk55aQs%q+en*h_R*QG?mtcQ|l*)L!4@3VTNakw1MCaPVb?A|$ zgb>Q0v+dJfz<e`#oJ>xe(ZIXX_Q8?&q_*VZ*wdo-YkdYieuj<m^6Ag}NN6#iYrXEv z9IV+KVhXD_mDAc7eu5@%CQhqS3jL&PF8pz;cJgjAv<IJTs$~5?+0>V(&o>Qn#yVRX zRT+g=XWU-Y%@^7GE6NSF>krMiWT>pkHmYPEG8v7mC$}H7R=D{GNS`*{w@$;J!L5U@ z+NWIySzC4PGZoIfdo47Mt}=ehQ(J#_ugnnt*xPXajKPK*Y}6Q)i_Z#tYPS3M1Y(<N z9kH3J2w{b>Lw`R`#;KwH85u#|LQc=(o7{PyZ_9Yd+b;Ttp$1Q6U(s8xHdn!Lx!+|a zRy4z_Q!7>?a5Y8Er6}X48Pwvw_svq2w;d};6>M8Ppwt|vf<8TPO=e2^^jTRvw+f%# zA!(R$F7A$)W6iEVeBMxb1isnN%Y$>fXDj-r%!+hU#-PMFXl@8c;dEf)nzIyoeCSkt z@u(G1!f}>m6F0`6cN|F)dC8`pH<X+mSr`8&vJtr51k<iHDq7da7)%j66xSzj6_qY{ zfXRi=kpI?W6T;J{mgg3s%dB$$M}-0;Z;yg!=4?+}OC&ee4^(yvf>TK1%xI(g1;H+D zg8Ye#tn-#9nf*(Z2Sl~}cJaVbm3AL+RHvI;!er_&|DRsvc#IanBmfNIx2Q(FE+E+{ z1`<l%TK)>iyLroKKC?<xQwQxg%9Z2Hj<|O9R5pN9Dpohw9n9Lvkp7=teN$}79fSyA zZJgsk<q^8Mjmm&>11P)EeEN`grVbf;M0WMq`oJVkV5Ghs&}JaYj6&Y1Z;92p%SxNH zmR~XNEPD=IIsW5YbfccL*W?Ij-qKwia1a24r-4ty$-22(`hc-|2$-ZHQ#rl_ifz=R zV*`%Rfu=t8fVP$nY_|U2X0z#mt^Wiza%K&T?*}%5)#t0tm1YGomsIBgg0-YNUz!cX z9<ciWo0(Ik*pg#rn73@i8x%}g0@mQ(&y@i20nDrk&a^l9cJ-$*z-B^#`j55X*jcs8 z@ix%Bbux2Iqn>OZP?`WJ;D09oqMOU72z0Y$0)`R;-E4sp>VMl616KEWlSEeiBXDvn zbc@|JC`P(tnCC5J{+${Z(|Drt3f<hB4-{L1{}crl;Pgug={f^8vKZZ5i+{}?fo3K# zK(law?f++~1r$QN2DyKG5C2Eh0}FT$^Z-6%r|Vaa<M~<i1E)Vi7np%?-|;Atm?h_N z_nu!rK!{+*kEcB(mYtRWe-g`Nfw$MldxUX$O)NU1@WYzKKo?V8s!cuOvxd4jwZ?RP z^fm%5_s{3^_vSTz&%Ll7eVB5j@xy)OF3MKJFJ|SjV4Ski?nRc|P*v<?1HZ!F74Gul z7280%@|(ymmt3vIlBPE$z&!+`>g{SyRj?rQa;a22eYuJqb=jTd`SM$W@_IM$k-B|( zYpj3!{0)40*Kz(q|IQ3~Of178)z{J9?3B;DVC4xpf04i;Lu#-gqbH<yTfB1xa!xkB z9R&`D4E4~n{a6uWI&{>iI;%HRzusU5exArJfgicpB70DB;p7#3>t|iGrsBOPSI!q4 z6>V826ykSj@s{fpb1}R+-7PB78M<(MqyD_*xf2$$3JIGD-QH*>mjj90F(iuW8k03z zf93bVVmi5RTKZE?Ov~h6Wiy~C{;UPO4eavX>#^zR>axl6Xb}I5nVgxOmeArdKwQ{4 zJQK)}ia9F$XpXPNa9Vx?dXd2DWe~$oBa?OB5~^%tto>}#_jn8SgFX5?Q@u)1*+_;j z-5gO2_kj4gFcN7yIAj~o<$9i6+<HNDZ_Y+PV$Z?Vyi5{2yl7**0Qh)8)XrwFBx!-G z?-x(ex<T4amy9=}|7n~TkRmy0DjB{{&PGa<J`R$77x4>(tN1rL1L$JkJ(17a!BLM& z#iZ|LVu>TF{`0(R?41{_DP9L|?Hjh^o>HLaO0Taak^W*IyBDNaOMMc#mh>#!=3K6i z-wSfURK6GdC+coYR2r=ohpK!z9jM0~^tF5k_;ELlePj;3X}%l~a^kOj|Ef#XjTtWc zE~5v8tFe(BRdp^G8+J#@rc(BDw+94+ema{R{XOM9F*Y-0vC2J&Kmqqj_jOfQym&4^ zxZa(64I{j*j1Vk5Fq6l|-~JG{K?FPlvFt5A)nJ#A>8v^^ax1nlL5gF!Y$4IontE|9 z+kb4eIk>!1yvZc8r!9uuuBP2p>Z4O5yXaMTm|MYWu|W_CzjPhxS{FQAoph{*9w^F& z@~MtvHLJMu=6I~Fcr_hzV%Fv4DtYNREO`_yO86x$jX8t}ebp0b^zN2({Y0qBcfR2@ zQqVv9Hx*YsHWp_#Lq0f-EmlxJP*=*f{<5ps;e+u=0t3a#hl<=~oIjiUziAVNAanMz zvO8_98Yfc;6Vm~%nWoegM#pwzMrVoY?RsUo?bJslx4KO^b$Rbk`1Fd8wdMI_2GzCg zwqIjl7ulBz_SoI45smIfv97F9Xr21X&KhyJu3NQJOhv}#KII!6?n7^jynj{aAO1Qh z8|P9K*FwWuv(#o#(MIzWky-o>6^5*7k9??_D=OJjzm4|jlb}-6D=PtQxG`Gf3Mb$e zK4@ex?UOWqZ}z@E6iMLP%FP;75!;j-XKtZAwqq$K-g&aHT}&CW;1wG^NJXC@o)X$t zjhG8Gdo6VQGb8K+&fdY-b7b|`o6;@>5t8UKhHQ~NxH_XnNdyC2#&`o<&CGFT#PTGA zc0V{1ZfHMK+?DqT#vtnpnFnX~BUMqf^$1!DU~Jvft+|q5YzfZ2jVl)Xnl7bJBI_*4 z64}I=uo<L{?fjX7AuHV3z6^bfgb5QA-tqsm|7VK+SBXqC|Fe|;e_;C8070!G^}hxh z(*GZ1{y%{KKgj-nkRus9u>4Hn`~M5dR{!6+vh{&L^-4pDL>5;|i4T|%eU|0&|B}!D zhY%oy1eL$uYSAWmr6m8ELgxLiS%LxQTs@M(VPKcvXg>Ww-(tkJwip2d`peSl|8eLT zTO`bHwsZ&ot#B1j1K8rn|2P}Noe5C75y&xDHlO{T>kYRMX9gAfKR1eM9@g(WuL8YW zpnlI~vh&XpzzgAa77g`tc@0J_H(Se-OuDPV!pI&=4Yz;h7hU+NLKrN?!0cwa&j0qv z!9yqpx-hHx?hd)JZ%h9+Wx-ZhA^*UExzj1Wc<lB}*+L-hOIof;k1-t9VH@t;tQ52K zp^8L!z_=XtzR`up%9pUlKH-O!zw#VrE+v)2X_jd2!*}B~*zrIswfxBkmF#6%r~3oz zFMFBTeWj}R67nMp$Y$c<#O8!a{fjI8Lg+L3g`C@}4#C&j;mxnuZ{Hgr6F+@r3yJlm zje1~kA|2DRIeKA(@!5C@4543B&obF1D$i*q+0AO0;~a#rMLz6^JFAI1z4zl<ipesW z&ggqiX`<iF-0HiOUi)!hVl!$QAa6i?rMT3SW#SV(J8VD(<V!y#8~Q&7TUYeHoXUvg z`&xx#C+YiQ{%_+k3bBvIAc)2Ld9^vC_rp^-c2rctuqbT7`yjv@=7eKXSa~G@*SMBA z;j2bGnwpZDowPV;Q=k=$XGe*nF<&^p6B(L7?fSi|UXi9s5wOob=Y0293I{O+%><pS zk9wI7q_k+_BA#@`-b0b&xD)!H@u9C10=J8)5vw^f?=-REy~lwo%FFeIJvxMJ24^e+ zfeGJ}_m97V7Fee#i#gX<Y;E@r9+tyyD6L%N6L=~{geHu;{t620g!ew|@x&$!W^0$} z=1rT#QSA{sOeq=q>fkA|<r!~eevyBon~wd^ml@lSC-7JjbRGLyR1QipQLO-_X%lPh zYfO3|?ss3>&kT1jHqm|5fYxGkmT&D{LVqEGi>CZP05?>2b#d$ja(GU%!VyyUCZhiE zK<M9yz1B6hlHFwD+{?<GjW5nhygQB1@?DX}-v#SrB}9L?yN$mrdPP2egmnJiel5GP z@&5MRRB*yKBfF(ol!Y$@ccWGuA3-(6<|SmM>a9+~3Ra_bE|2kRn(F+eLSj~X0R(rY z9tQbIzYV65%d(tqBM)2fpu4Q!7__K*ht}gHww%xwi=AFWoZg^(a~c_1&q#)HfdcKo z@ZGrQ6H;#s&P(l2+icxK2BFE#uR%_P_LfWB_jEQ#U$HA`u^G0j!i1BW(ykZmnTa21 zZt}3ZrA(zjsB+oEfgGxnCarKCZ8&22{_Np$@IdEONzvaZ?m<G8_*~m%!Tr`hvy1c8 z<iSw*=Mma+w@tFga@|Ig6u#1JuhZGbL}}Wb#M|a&6*m@J;R^?pBl+g|!}81Vld>Ix z!Goit6Dxi%d8=l|Ti099k}GoTEU3|Q>mppG<JzcfYC4>fO^MjQ!+w3zo5qOQc*R5L zGzz9r)VrH@$N@=9Yie34UaQ>;$gm#0+Tu$c#Zxs{q{KPdp<k~}U%%_o?7mL^NZLHF z$>mjCrC#HGJEEX!mhLF5p;ca6tG||eYdgrX{KG`T+OtuZ+>WC2^N-fbkgECl*S;si zDow)m$S3Z;96W9Dm7f<-P8wR##v6Qjqquz9Y6QH~101i|uY-r5Ch&@#ZdWYVLsfpj z2iGj2^@+zf_5yQ{745lXtaab9*p8Iq2NE^N6LXvdFfTslk`U+SH097gpfzgc&QY<V zq#>rJL`~4jAL7Lmevz-K|4x6hJ@3L(kEL{_VAByz(N`YcvWC6qa?O>mpKEf)85=NG zrrh_%daW(u<-ix&@T0_oRd`{g+#{fI2FGmNC}!@9n-H1psgFcW@iJ<l>s_$`rPyu` zq<|b-*eBkR`KF+D{_~-p3_7L1B$i#4#^DUlM=XxqcQU+ws_|pls}x%7D)DV@iz|0} z417LTb*<|{olA(+on55%4;0Ajd8P?^n_DCVxMtc68E%-4R|y2<7k{`Qr;<$Ue=E(b zgPYWWFJx?=**uwwev$HRNaEVuSDhS2Y2B41qwQ7{TM}|78p9%l>EQ+5$Ft%;G=w`x z3Lfmj=*r*q7<do8DJo5<F;~~#U=cgQQmMOt)s;xq4aw`KBnh;bTR!_DtGJ~yP+{x; zKw@=9#~vMvRf(S|M6a@8tJ}w7h!xuszOlpWoNc;GQXL{Aim%hX1Y$Q(^6TwKi*o(h z-6ulhjN_4Yk0zo-(f41P*K1n#9wL0|{+(FD4lCnt#qukCJF(G)Eq)gwo5#>G4GQ@W zhW*L3eL6srCsUT{6$_G&Y8OliyJtzD^?qC3BqO@(&~1o{SD0)QJtL>uXUB^Zy}a*+ z2c0^gO(>p`7ag1cRRhsawk%aJQH9}xDu2RpGstCELO9>MKX|GVIj#~Vt`aS-@=aVN zMqDM<K3|eJXC0WcPKC2hTk=t!gHwrqT#_N(4LM(P8rw}>UIHMnkWvypWhv>uq!sJt ze30eBG&kzz%x*yGHJ>KNOqDm|RBDPs9@n$uRHCB8?z0^4=3HdV!?44N7=}oY(5Iq) z&QcN%`^;^Fn{@rUcbohjavgg&`7z5|+gH;J*XZ)aoILOHdpIAEA+Mru(1E6`ZgKL> z+`u2`zv1}3+xlLU`t%XQ4;KY|%9yF)AX5kYUh|D8-7QWfab^l!J4#@ZCopLjnA8>t zOri#eKoWWZf(CQSK47W;0bQklrQ-K<3Spn=k@s`9;;+?%V?NhWo$0Xv<SPV~T`39u z(_b;WG7|dCKHqsYpj?(qiJq;WGy9r=D3q5_%6wXj#)c(g2vQ?CtqaIjfL!%YChFbR z3s;XEb^`1*B=mP5OAs<h9!080Uaw+7yd8%=t={+M@kNL~Y=XCq&4>U`SYhNO$5Y_7 znI2q%=Lm7lKpmsxZ<Nu$y@gbqic`pxXZY!;=WB?wJLaWks7jaKq}q2gO_z-zB6%4< zCtBhv4c1|;XjKm}9)a?KHKAcXPfdj%jnhP$z5xvW`Q;PC?&GPcE2s~s2OpY`R^wqQ zVmZPoqGFB=zFL$y+eQs8NvabTK7~*DDgHHi%G1<=B12Azl{L*JpVVN4)cfRqE4-~6 zY1<GmXe*Qq$M1*0wQj}=q{={jX23lawkkl2V*Cf6z^$@M+{-HWD^YIZp#i%$sC7=Z zHAP=_iDcGN+TVY<i)nb^luQ0ZA6v<B{oGhRatR~k1GDCW=U5<bYw9dp`OL}6`+R8b zk29D3)vwhi#8aLIR?4Sv`6~$38REZL*Ui`dHAQo&zOq#c-0sHN6iYoICM74FLk$*n zYU4IJ7wN9Vly?QMP-WIuY7&K;Ilk9ab&C58#%>4Ltt%ZmU0{-(uMxq13A{@+i&&8( zBe-==TKYJJgZ=$Kf}^MC0J+B7VP`s3daQ9iF6;d*zsLJDw&C6hUKNEu%Z%Rlw>7EY z$~Y`Cp|hfZ+4e%(ME(T@GJIENYkO-a|HEgHh>cIeg3l!QZME#zBfZ+yn-wYt$L*{9 zeJnql>w))vHg51dcag9`c41XjN-^J=-dO5_HpIKfX%Xv$4}za!q_2$`$)<dZqNLBl zzv0OxM^j<SF!*p16zzAbrF}z_N=8SMn?i>~h2ulM0~i9Z58!nKK7<i~902QqrlKey zeg`lFU?0HiNMs13)wUd(dPDCU^*#Wz0JZ^K_rAeH{`)E#<x5E4E5Y}s$ayqeFIAFs zkr?F0g79fQe!any`g<Kk_r<4=z#PdGBaey;RV7guxlDE}cm{O)>i{<tkYxawR#40o zACS=jIUJ@q7BU0!=-$BX4eH|tTax8b)nch6lAx8zR))@icDoO7p8#Jl;QIjh-T^W$ zAin|Ruo;kL&jGH;#&uMYq$yKKY!-T%tbO<lXaHdC0Jaq{83QIxz(k^wn1u<*5i=lg z?*VQFATI*4BOnW=ys4#-Q;M7cHSut70m2O+_yK}M9u?DzJgRa4Oh`Zk!1ST5d>^5q z^~ENr&_;8xzM!lcDd5}36C$7d+-eUD9BPhcR@Xb?N2oaN|8ByXL3jgC1$OcxhV>Dd z<!}yv92S}zzFmp4tO0{vB@T{pvFi+ej;4HXIG5Gp^oFucH$An3bLio2zrp6VQI(=% zJGr)wM<IK1OIZ%oVp3-=ty8E%|F+uRcG1%VS{JB5I2!C=mllfbqHph7$`Ywxs*4S6 z9dUuo3uF}I&ae(XV>1mRqZ+8B>DS~{E_J(HPN9t)y!_$vILo;+R)UvNZ_viPhwa7m zzgu|Q+gePi7Zxv9+i}t{J_lsPBCE3e?-t%!pWOf5!b@0~n(^-z-im&Sdc)3J&DPs3 ziqRXV2^N8b#M%`@M=eJOZaqh6r<(Jay>07^Q;3VFz0-~{TI^2bnsVAx!4|6oe|4oO zBc7wT?%T-Ab7R-h;ys<TL0LmKRrNeJ)n*h%QugYDJecaLYloM!ksCK{SHu`4+N9IH zo?nQ>e@D8E)rb2lCBNoP@n9mvy!UhYS!>{n3G;2rks%Snc=@H;mP(QnLqqL256%qH zX_bMUUTaxo8>_8-jHM01RE85bniDUI@b@Cra!5hFo=mTU2wq!zsdIkakVguRU_e13 zD<B{Nh%f($m+i9eLjV#TAX!_Mm{BmRn-t!M4ltl#lNAz(N}|*7iZVq75y)BlsdGjT zSm|`^ef_YN`Vu2v6`h?|oaxu^dkBpbKwtp`KS2Bih_C+$9Doo9%r8iVUI!CI35H{% zfHK0=B{vC#3DW)@(W?|mbq^0AjSRgX844a5>K++F8XbB+IutxQ)Ezt8`tik<iz7jw zB)Ylbe0&ILrfYE!QKqXaD%|Yla=7@=$ZgFP5icYC4+$}D0R+?yrXx=0rwyO%crJuX zcJ7wfOc@67v@Iw4R=cY_?z(lB_!`h>;WVRbBwjC9N<0R{mJH_5A0`HNSR6!yl~Yva ztLm<|K~TsHiVM4#{|}|<>|7?rNqR9w;buf`$sTdWd|-QBdj%}M?D5R<)VcFFzkVQB zK={#}Bil`;&zZ4L>nBQB`oL(0u{TMcTVs!iVogD1y4y+ruabq-ONQ0^rjzO1dPjy; z1**S3zJH2n2dDX~J%wvj+nzEeZVI|f5k*d)7oJ9hVTr*KE#lvW!HA!_-;atZ=wZcA z@ajYkzBvh@b>D*-<j$0W6YIL`2V1G>Snu>q1q~KDq4?oiUIV3D+Pwmu3`-|}Atu>R z9t(M06OV>-?CrZ`>uQAzR&{m-)SgF1>>QrszJA%_>(xxv;g(Ma7&v4<H6Q2F&jYVj zwCK}L%M_jWx-aXWH~A{e)kd+OuV)k*Bx*5ctOD24H+6?6fM*gElk;tw-z`s9%QTg& z#@Z{klPqR<@YJ!L*w#K)zk!RNxg<NlQly*4TQ$v>xxFUe2H7iq22ey(=V0@U5Ts3V zU}6Qp3qTZrGyo+4ng9#|m<JK?57iL}OQKVo8=$`h00KY<fB^s#02Tmj05||}0pJ0^ z2S5OT5C9Q#J#;Yu5;Wm7b!_2p0lWhMVlPm58y#EbqT3Vk(fZQihxG^NC~lW5ZkO+- z;rQV!9)jVl_SE5QZPo;BlISLJI!s~xS?&UBu8d~yXPl?PHj}{haXn<?bz?XV#E*<e z(^tShAqhDyE)Q#)>iXCy;(E7a3*lEkA826<_e`0d2)Vm@n$=3{DTem^?-AM!jt|&~ zP*yv{<X?%CaHRqSeB@zWtaD|Rt{-;xUs<3K!UnN01w+1rCaTWE5xKX4Re{^^yOAYC z;3(WlpMgf0{~gaAwB~7a0dadiB8;%Je1xt5KkBY3`p3g3k8LYh>l63Xb}44{cG=-0 z+)fjQqtALJY#9O)8bo~(jjVB5#jIr6_IfMy!<X_~3?nH&=yzrg+tW(;>{t|QBpO&V z6Ld4ESxPHg4%M_zslHY&x$5e{Y{)I*En4WDQ&jYx3dZYH94z#x+b8O}br<TmT5{{; z84UP+`wUXh*Bb4aR{HC9Za<D%>Cjmm4U3F4@#LqU$jhct+7%jzBfb=y<{N<@-hIxv z+=;Rp(J_i(F=;liUo1bXKK^kRUe`YAJbF6g&ALudQ|ixCQPFg#=IyZl^Kl8@nBwtk zR!020(QkXM&IYO4{v9b*(e2);$#jsvt!@J9a=O_Li+%#1e<|hDJUg{hy}A&5WznVX zRCA|narTDXl)v#V$mR4%HsgxCI(l(7&OhVKG?bP2agd34d7;6N`kNlR_jbLZV~awo zdTB(8I&WXfC2ft%&Elt>rGwm&8&}FS@<K{Y$Lc`))|(6com=vcM1#*$Bl*8SVOVOg zy(1heW!@#OXF6!gQeMP~UQBcE%u<dw{OsN2TX9%<s$RFS|81L-UCU*+yli{Avue9d zUwasYqjpIvp<_tlkk)aeU~swUrT{-sWkbQkU_n7EY#ySXYb3C>Y9ydyH;8HBXdpOo zSx~RMbgya&$o${a9(CL5z|$Ul`sI;Mb|ui9>hy<Gjn?ClIhR%r`q7;-YnN~on1zL8 zrQt>Vyi1ApTbCU*fmQ_v>Q>v>4E;twx}8J5BkQ>{UmHF{bX&dz!UbF7&f>YHbG3@j zy(n#~0h_ARy7(M}6@Fy_?~KxRbs3j4Z-v{aRT^LZ6=vVYLMGqLT$U#<+9C1DfssdU z9$r|h+TxjfhqhBY)DWRWT-BYjrr%q2(*VZLwT`#SoWo2`Z=}b6-Yy+?wT5q4Y+Gv8 zSR=tFw|9kZt%K8{>Bvq-8KL>mq);5%Q8sq=6oYDG_U5I#)_I>pg4Xd76#eBal0`v_ zj``}VW&I~4Tz#~4B;dCg$ttsGe*Ll~z6KQ>H~mc0@f~&sbUUN7_K{V7*jIOn-FJ6C z2~vC=Ti)K0`Kq3ZZ3WpM`YT~m`YU7Db?BYWxE2x_Z-9ipZP>DL-+|eF<dw>lj7>j^ z$Ig9AjkJSJgj>ThJr+=p$yjmxDvF5eA)%t*olunX_t|h97bFwnS>NI~b8rlzh^9mm zrEGgOo9cILh2*LI4bJ%Vq)!!al*a$gIa>7cm$t*Ktch*0&g9$xrb%UdzOeSwmJ!8X z;pygT1!DYI$OO(QJArVUyeUN7Pp&@TMO9j%Y|l>NXHbaj^ghVfSv}_!wsOohNWDIs zD;;m)=yxq|m?GF<vEIY6S<3FZ&~LLU=!~l@`L$)rI+zo~#d^T}7zn8%=}qY}#Z)AV z1qfD<9UJI~ZJ-v?Di}?5zz(wG03C6F>^PI$vXUD~<b1}4a>j<*#)g*0h92WmFO$Jr zA*qm=mcO(%eC&eqKJgeTDVG1J<$sib%BJjNeCmC2#MZVyWUJ*jt&LF9H3e}Vtuirj zUI((d!ypQS{JL|u{Ir+hmlQWDK&kpio&BTy{!!EbbuuUqJUYtte@tCfR2*HiMH4*1 z-7SRR?iMTrhoFPIySo$IAvnPj+}+&??yiHoGq?Hvm-|q4_Nm&nPo3_yCQMIvr?8O7 z?}B|7nx$|YcE<}UKf~`G_bz;75gnHvzw86_w)rqRp1g8ar`s<F`2OicCci|sN$#Rn z|9+lbH1=^i-yC~6p0Dha*_&tfj3Shomx$+0cpVNp53^MqyQwIKmctxo`O59+14r5; z-1UVkffBsCr0i34?{nki+!3OjhQ)!jpeSXg0|voCVhS@rfy;jm0$85_mOQ{}LIPN7 z0BamzUE&ZNCbmpU6+H(7l*%`KWBmTP#A(*ZGP(w1JT2wu5kNybMc+!6=L1maX%?$| zDi)Y=NlLJj2>Xw0g0dW;yXm8wsy2F%BL%a@+dv7VNdJ))5e*HxV~C12#m8MyQ|^Cf zpIi>753oBm+Ib!t<VlRX`b4>}Br#qTnW5~TsD{te!0O;*yhAiH=$;(s$UwQTB{AL< znc<4hbI0oN3M57c-BaTnSt$1nCB}z@heska()|;&@p&ZK9I{Myw8sV?e#JR*Qtn%; z8w{^@(r<ASjc-O~4B+#musH~t?qrM)x)Vz^WyW3c#X9g)?z>Bj-$Z7d;q&aXIjouP ztV|3(WXB0WQ9eOQ+`>d2zsFm|)GI@IfcZW)NSGr*;R{6-(>W*zEv4fh_X0!d`A*^% zHS(AkZ&8T3I#kbyv};f&AnpZ@(i6$e;SxLYm;rB57i!3mxq1RB>r;kDvWuEMHQ^;u z<S`%Kq9=1TM9=6$_n=@<TpI%A6S~ALRphY@-eNj)wUEA143L0NWu?FQa#FSRjdMo> zZ?S>7+Ew2O(mhBL66cLV>4`6K%NBWTj<-0^T-~5=<Qs?)$&W_)L`3FJEOE;xe90eq z?1{Jd1gxWP^r3f95Mle%rD)`FX#N+m>vckk>+3_>d%G`Z&AeQ9rN66QA6~NOA-2yK zui2p~w2u&9F-NfO8pQo%`R)$*XS||ie`Ek+d;?*vf$YZvPg@qu%!`XdY+tSy2d;2D zW?$E7*$8AHy(D^?@-dbbn026}Qk}K+>5&oe_Dh{NKhaK-%fv&9?vbE;`3l63FX>UW z(fPKKY<X%0``!rJNbX7`%cylc^Y&NdC&4&}2vg3oG0g}}g#0u$y!q_RBVUWX0^IHq zbJ5iHwE2hF5siPNT0$W9t4kaY;xT@lsnWu=60RGcMJE}{>Z%1(j>YO<RuD_s`qDkk zAC4!a%i1FjbiQ#iUHCC8O=HP(noob`Z}&Z{pcQ*&8%N6^0^VLaysyivv`6=6Zx7&P zbGTZ8kDj$5v>hE?S<i0y))Ujqx3(@t3%92r>!mwu)aBQt^+jDl+=I`CKjlf4hd($+ z&R^QFfcQ+RK^+$h_J-TShkIRB(ONa9-x@IIOQJVb$6O0_T~<!!RGYc-Yahj4==Pq7 z_e+G5nT&ik92YJ&#Qm;|jpT0rxvopakUsEac|D9iUyq^<)X4XzW!tutw5K*o$M2~+ zbBlvGObM4&ozWO+jXwwgFYc&Q7(Ub;jH6#?aXkGDZE>pQPL)E0h?WwFI_h4*@3^D& z6wCu5ajon0yekM8=^=K=C~2DHBmEo9cN_b&H&Hp_(T5h>($WcwNp(iM8~E|Gcbn`_ zV)LLPcvQ;qkd%OX_&9ddIOdN<(}RxEgKPv5_NbzX@f>2E!GYDEsW1|YU6Y?G)TPau zbr$a(>)z@9nRS$l*B*H}uxq<aYbs=)QoKs<-_u+#Qv@!XQOrJE2K+uvq&DG_L2oJd z6-<9xcyfusX|T#_lV51FSh8A+YFO~ro?G++#iV<k5wkz;%CSBWYgm@1Tvq*geY}f8 zjh9R#XcwAUFf(UT-X_nC3OS@ami0iX;vin>IbF~BU8d0@aqHJT5jnPs?dj+#WT=p9 zb(=F5Y}FNgcEYf>YGCEQG&J+ssikaohw=7D1SugO%Iq><X@=F2oku!d{xJ8puQ$r1 z?W*tDcYAZmk4Z-euKC+>a6Ci&TO3CwP;$0J=hH)ry3~bZ<}8`*iG*!!H%J*V2?j(` z_R+9ApXRCr{kDjYU_b4>0iBGBE54_a%nC>B@w1@}C=4dOn{Yzmt@mGq?BSBL-C#!K zwf%Q#+dD5YP4ynaxhkJ~iPe3!J^*P1APonk19oWzyDl+{{Certy|-{;@8L(}i2`?N zvw+|{P-Dw29K{oAX>Wx=7}FRUCI4(_gqFunMq<!|iK|HPkrXgmMU0P3Eb0_CFt)qs z99;HTQvBlP!rFj&0hS?dexAh*A$P=ePr8qo^5c%2%AqV5fn)s5P9_ML+;8?z!O4Za zEAIsRrJXwM$u5WrpzVnZy3l%~dNU-=g<B=fE2W?LuSg4^&7r=*PC+NW%ZLAj`YSiy zpC*T$3?ytWyyO?}ckORmiY`2b2q$0;AA1c0kl%d^#HJvxasfy$0Er+V@hfk@lwU1e z0rVjvAcO@7(SJgf(o(>PZD+3Thmtl0&>WPstpY*p9#FL^0!Rl=`CY#U)II_g<UoZu zP!R(VXmWwIMF9dFljvj6lpi~i6mQ?4COlOe5d0N91w17NRN(?uOl0PGu}&y+*|jKA z9`C4R1Lb1Nr6JitCmh;TBfe$}NA1t+XIYR{$^Dn9=K;2($BDE<;C@G5ljy|LXCeBY zcJ>MZ)}BVj@*?sk&wPdlDZU=An?S2|D?+nXHR|q0TRE+x+=?rL5Az+i0@VkF7iMXg zbMFg@>fbX-5R!0MnRq%?1>RhwJ=W7^%dzvXHjUm|De3lHQ4QU0O-KANpMy`I&d;2) z>S?d%DZxcqlwHSnbi=LNWA`#1RplRy)q3bned>}biC`Z(*g7yBw2a~0<Ky`EYkg@- zT~9{WvdbAc>hfSQMD(P55lPaf9>;#&lz!=%(z5Dw+p4c5sy4Zp)P_E-rl?LOZLeM_ zg-}aI$i6O#XQ4;oHZr1Y<(EXrB_%H&{dZ-QZ(O#9eIkn}NzcoRpBo<56bh<~$}Cnv zAr^gW^{PzvU79ZsMH2JxL#;W+<vHj0tGa$zO8Od}jImie@6~b6WhUu45^kH!pdOhx z##oqN9jtWj;j0hq86Q|&GrEo*Y{!{hyVoxkGu2EKc6jyf-5J%Dt(8aBKbo>@1#*N& zp*Hj{5y&p<S@HArnuHzOAe-%3qcn2$;iwk`v78^X8dhFW8lHRnF6--K>rZxkN1%1$ zsb~4JXQY)rvAnQ^R<j&Q`fO%3pA%N~GMUhiW_(I0#_MB(;_lGBFyt9sHGOwy1sbMZ zZ1(aVox6D$sJDsWT5irJH9lh5q|}-qZnJ9ZmR$0fU|aZJCd3{h2R273Ve4L?{aeMt z@B6{<tDd-F@0dh?F?hVc_~j=H?>@ER^<S%gpA=WZr-?7Ngm&k7)_Sc*5gIkqoW<Ml z>AWY(%X()O)>VNt%OO}=1`(Fa>o|AM^oFgk42I7S@{x$tPxzQF-&xUo7lyeFbZYH) zhMM~o4K)cQX@~k*+Q4`N<IR>%k7pBX83Hda6~{cSZdHtFplx1yT?fO4Swyw*q{{+< zD_ZW#HfM01qjHQT1h&ZP7yZjHU3wmE)7B2us95b$c!b$JCh4X|c!Ka2H9x&Qrqpwg zDRneVc+q)bud18!7X4f)hUq^++O=*<4A)>aq9_QdBc2q0h#D1R4?RZqCU*Gr`OjWU zH}O5rJ?vgv;7`;l;;?-n<or*fUC&RAY8;@QRf&W>0Wn{<uTxNDhkmIO-&uchItV@U zSi-bY90Fzz3ygr7!vSNE8KrJd$Aky@(%{p1mNTqQTrI3l)vx<Ib+Ji78e)G*DhBBI ztWNs)Lcq!as|TzduztYC0h<GC4X{1HPCMy;;OuvszWA$Ap$4c?5yQmebkWC?<VJn} z1t%oZ3|Aqw1lS{B1n?D7ynty2UyFMH<1fIP0b2s>k$P!xATlp1<5O(y_u9BV7eiT0 z>z!HQInI9)bAF}vc~L|WcffmPqKFKD_^I%m0GT=IQcu2bXU*2iQ)!^^l;}SiLCRJ# z^E@+5q0ar%C^O6i!93aT1mT=4hg3_w`AI-Pr<*X|Bp^FKU3f~aHdI>3KR?&(Lqar+ zw9G#m8UgY2RCa7@gDKV8aA_?d;QbcB0RkF8!15nW7FD@M=dTD(`@}{QXiFwQvqN}F zuQs$K!V;$v=eNLj4-_g{iS$$_e4xezsL2LIq9oGYqTcu%0Ds;L(-T>++UB?H_oXPH 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 Rs}CAN=Sk9x^OX$ee*qyL&5r;8 diff --git a/lib/dateutil/zoneinfo/rebuild.py b/lib/dateutil/zoneinfo/rebuild.py deleted file mode 100644 index 78f0d1a..0000000 --- a/lib/dateutil/zoneinfo/rebuild.py +++ /dev/null @@ -1,53 +0,0 @@ -import logging -import os -import tempfile -import shutil -import json -from subprocess import check_call -from tarfile import TarFile - -from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME - - -def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): - """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* - - filename is the timezone tarball from ``ftp.iana.org/tz``. - - """ - tmpdir = tempfile.mkdtemp() - zonedir = os.path.join(tmpdir, "zoneinfo") - moduledir = os.path.dirname(__file__) - try: - with TarFile.open(filename) as tf: - for name in zonegroups: - tf.extract(name, tmpdir) - filepaths = [os.path.join(tmpdir, n) for n in zonegroups] - try: - check_call(["zic", "-d", zonedir] + filepaths) - except OSError as e: - _print_on_nosuchfile(e) - raise - # write metadata file - with open(os.path.join(zonedir, METADATA_FN), 'w') as f: - json.dump(metadata, f, indent=4, sort_keys=True) - target = os.path.join(moduledir, ZONEFILENAME) - with TarFile.open(target, "w:%s" % format) as tf: - for entry in os.listdir(zonedir): - entrypath = os.path.join(zonedir, entry) - tf.add(entrypath, entry) - finally: - shutil.rmtree(tmpdir) - - -def _print_on_nosuchfile(e): - """Print helpful troubleshooting message - - e is an exception raised by subprocess.check_call() - - """ - if e.errno == 2: - logging.error( - "Could not find zic. Perhaps you need to install " - "libc-bin or some other package that provides it, " - "or it's not in your PATH?") diff --git a/lib/easy_install.py b/lib/easy_install.py deleted file mode 100644 index d87e984..0000000 --- a/lib/easy_install.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Run the EasyInstall command""" - -if __name__ == '__main__': - from setuptools.command.easy_install import main - main() diff --git a/lib/geoip2/__init__.py b/lib/geoip2/__init__.py deleted file mode 100644 index 81776ec..0000000 --- a/lib/geoip2/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# pylint:disable=C0111 - -__title__ = 'geoip2' -__version__ = '2.9.0' -__author__ = 'Gregory Oschwald' -__license__ = 'Apache License, Version 2.0' -__copyright__ = 'Copyright (c) 2013-2018 Maxmind, Inc.' diff --git a/lib/geoip2/compat.py b/lib/geoip2/compat.py deleted file mode 100644 index 228198c..0000000 --- a/lib/geoip2/compat.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Intended for internal use only.""" -import sys - -import ipaddress - -# pylint: skip-file - -if sys.version_info[0] == 2: - - def compat_ip_address(address): - """Intended for internal use only.""" - if isinstance(address, bytes): - address = address.decode() - return ipaddress.ip_address(address) -else: - - def compat_ip_address(address): - """Intended for internal use only.""" - return ipaddress.ip_address(address) diff --git a/lib/geoip2/database.py b/lib/geoip2/database.py deleted file mode 100644 index f6c68b8..0000000 --- a/lib/geoip2/database.py +++ /dev/null @@ -1,214 +0,0 @@ -""" -====================== -GeoIP2 Database Reader -====================== - -""" -import inspect - -import maxminddb -# pylint: disable=unused-import -from maxminddb import (MODE_AUTO, MODE_MMAP, MODE_MMAP_EXT, MODE_FILE, - MODE_MEMORY, MODE_FD) - -import geoip2 -import geoip2.models -import geoip2.errors - - -class Reader(object): - """GeoIP2 database Reader object. - - Instances of this class provide a reader for the GeoIP2 database format. - IP addresses can be looked up using the ``country`` and ``city`` methods. - - The basic API for this class is the same for every database. First, you - create a reader object, specifying a file name or file descriptor. - You then call the method corresponding to the specific database, passing - it the IP address you want to look up. - - If the request succeeds, the method call will return a model class for the - method you called. This model in turn contains multiple record classes, - each of which represents part of the data returned by the database. If the - database does not contain the requested information, the attributes on the - record class will have a ``None`` value. - - If the address is not in the database, an - ``geoip2.errors.AddressNotFoundError`` exception will be thrown. If the - database is corrupt or invalid, a ``maxminddb.InvalidDatabaseError`` will - be thrown. - -""" - - def __init__(self, fileish, locales=None, mode=MODE_AUTO): - """Create GeoIP2 Reader. - - :param fileish: The string path to the GeoIP2 database, or an existing - file descriptor pointing to the database. Note that this latter - usage is only valid when mode is MODE_FD. - :param locales: This is list of locale codes. This argument will be - passed on to record classes to use when their name properties are - called. The default value is ['en']. - - The order of the locales is significant. When a record class has - multiple names (country, city, etc.), its name property will return - the name in the first locale that has one. - - Note that the only locale which is always present in the GeoIP2 - data is "en". If you do not include this locale, the name property - may end up returning None even when the record has an English name. - - Currently, the valid locale codes are: - - * de -- German - * en -- English names may still include accented characters if that - is the accepted spelling in English. In other words, English does - not mean ASCII. - * es -- Spanish - * fr -- French - * ja -- Japanese - * pt-BR -- Brazilian Portuguese - * ru -- Russian - * zh-CN -- Simplified Chinese. - :param mode: The mode to open the database with. Valid mode are: - * MODE_MMAP_EXT - use the C extension with memory map. - * MODE_MMAP - read from memory map. Pure Python. - * MODE_FILE - read database as standard file. Pure Python. - * MODE_MEMORY - load database into memory. Pure Python. - * MODE_FD - the param passed via fileish is a file descriptor, not a - path. This mode implies MODE_MEMORY. Pure Python. - * MODE_AUTO - try MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that order. - Default. - - """ - if locales is None: - locales = ['en'] - self._db_reader = maxminddb.open_database(fileish, mode) - self._locales = locales - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - self.close() - - def country(self, ip_address): - """Get the Country object for the IP address. - - :param ip_address: IPv4 or IPv6 address as a string. - - :returns: :py:class:`geoip2.models.Country` object - - """ - - return self._model_for(geoip2.models.Country, 'Country', ip_address) - - def city(self, ip_address): - """Get the City object for the IP address. - - :param ip_address: IPv4 or IPv6 address as a string. - - :returns: :py:class:`geoip2.models.City` object - - """ - return self._model_for(geoip2.models.City, 'City', ip_address) - - def anonymous_ip(self, ip_address): - """Get the AnonymousIP object for the IP address. - - :param ip_address: IPv4 or IPv6 address as a string. - - :returns: :py:class:`geoip2.models.AnonymousIP` object - - """ - return self._flat_model_for(geoip2.models.AnonymousIP, - 'GeoIP2-Anonymous-IP', ip_address) - - def asn(self, ip_address): - """Get the ASN object for the IP address. - - :param ip_address: IPv4 or IPv6 address as a string. - - :returns: :py:class:`geoip2.models.ASN` object - - """ - return self._flat_model_for(geoip2.models.ASN, 'GeoLite2-ASN', - ip_address) - - def connection_type(self, ip_address): - """Get the ConnectionType object for the IP address. - - :param ip_address: IPv4 or IPv6 address as a string. - - :returns: :py:class:`geoip2.models.ConnectionType` object - - """ - return self._flat_model_for(geoip2.models.ConnectionType, - 'GeoIP2-Connection-Type', ip_address) - - def domain(self, ip_address): - """Get the Domain object for the IP address. - - :param ip_address: IPv4 or IPv6 address as a string. - - :returns: :py:class:`geoip2.models.Domain` object - - """ - return self._flat_model_for(geoip2.models.Domain, 'GeoIP2-Domain', - ip_address) - - def enterprise(self, ip_address): - """Get the Enterprise object for the IP address. - - :param ip_address: IPv4 or IPv6 address as a string. - - :returns: :py:class:`geoip2.models.Enterprise` object - - """ - return self._model_for(geoip2.models.Enterprise, 'Enterprise', - ip_address) - - def isp(self, ip_address): - """Get the ISP object for the IP address. - - :param ip_address: IPv4 or IPv6 address as a string. - - :returns: :py:class:`geoip2.models.ISP` object - - """ - return self._flat_model_for(geoip2.models.ISP, 'GeoIP2-ISP', - ip_address) - - def _get(self, database_type, ip_address): - if database_type not in self.metadata().database_type: - caller = inspect.stack()[2][3] - raise TypeError("The %s method cannot be used with the " - "%s database" % (caller, - self.metadata().database_type)) - record = self._db_reader.get(ip_address) - if record is None: - raise geoip2.errors.AddressNotFoundError( - "The address %s is not in the database." % ip_address) - return record - - def _model_for(self, model_class, types, ip_address): - record = self._get(types, ip_address) - record.setdefault('traits', {})['ip_address'] = ip_address - return model_class(record, locales=self._locales) - - def _flat_model_for(self, model_class, types, ip_address): - record = self._get(types, ip_address) - record['ip_address'] = ip_address - return model_class(record) - - def metadata(self): - """The metadata for the open database. - - :returns: :py:class:`maxminddb.reader.Metadata` object - """ - return self._db_reader.metadata() - - def close(self): - """Closes the GeoIP2 database.""" - - self._db_reader.close() diff --git a/lib/geoip2/errors.py b/lib/geoip2/errors.py deleted file mode 100644 index 468b585..0000000 --- a/lib/geoip2/errors.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -Errors -====== - -""" - - -class GeoIP2Error(RuntimeError): - """There was a generic error in GeoIP2. - - This class represents a generic error. It extends :py:exc:`RuntimeError` - and does not add any additional attributes. - - """ - - -class AddressNotFoundError(GeoIP2Error): - """The address you were looking up was not found.""" - - -class AuthenticationError(GeoIP2Error): - """There was a problem authenticating the request.""" - - -class HTTPError(GeoIP2Error): - """There was an error when making your HTTP request. - - This class represents an HTTP transport error. It extends - :py:exc:`GeoIP2Error` and adds attributes of its own. - - :ivar http_status: The HTTP status code returned - :ivar uri: The URI queried - - """ - - def __init__(self, message, http_status=None, uri=None): - super(HTTPError, self).__init__(message) - self.http_status = http_status - self.uri = uri - - -class InvalidRequestError(GeoIP2Error): - """The request was invalid.""" - - -class OutOfQueriesError(GeoIP2Error): - """Your account is out of funds for the service queried.""" - - -class PermissionRequiredError(GeoIP2Error): - """Your account does not have permission to access this service.""" diff --git a/lib/geoip2/mixins.py b/lib/geoip2/mixins.py deleted file mode 100644 index 4afc5a0..0000000 --- a/lib/geoip2/mixins.py +++ /dev/null @@ -1,16 +0,0 @@ -"""This package contains utility mixins""" -# pylint: disable=too-few-public-methods -from abc import ABCMeta - - -class SimpleEquality(object): - """Naive __dict__ equality mixin""" - - __metaclass__ = ABCMeta - - def __eq__(self, other): - return (isinstance(other, self.__class__) - and self.__dict__ == other.__dict__) - - def __ne__(self, other): - return not self.__eq__(other) diff --git a/lib/geoip2/models.py b/lib/geoip2/models.py deleted file mode 100644 index d50fb91..0000000 --- a/lib/geoip2/models.py +++ /dev/null @@ -1,502 +0,0 @@ -""" -Models -====== - -These classes provide models for the data returned by the GeoIP2 -web service and databases. - -The only difference between the City and Insights model classes is which -fields in each record may be populated. See -http://dev.maxmind.com/geoip/geoip2/web-services for more details. - -""" -# pylint: disable=too-many-instance-attributes,too-few-public-methods -from abc import ABCMeta - -import geoip2.records -from geoip2.mixins import SimpleEquality - - -class Country(SimpleEquality): - """Model for the GeoIP2 Precision: Country and the GeoIP2 Country database. - - This class provides the following attributes: - - .. attribute:: continent - - Continent object for the requested IP address. - - :type: :py:class:`geoip2.records.Continent` - - .. attribute:: country - - Country object for the requested IP address. This record represents the - country where MaxMind believes the IP is located. - - :type: :py:class:`geoip2.records.Country` - - .. attribute:: maxmind - - Information related to your MaxMind account. - - :type: :py:class:`geoip2.records.MaxMind` - - .. attribute:: registered_country - - The registered country object for the requested IP address. This record - represents the country where the ISP has registered a given IP block in - and may differ from the user's country. - - :type: :py:class:`geoip2.records.Country` - - .. attribute:: represented_country - - Object for the country represented by the users of the IP address - when that country is different than the country in ``country``. For - instance, the country represented by an overseas military base. - - :type: :py:class:`geoip2.records.RepresentedCountry` - - .. attribute:: traits - - Object with the traits of the requested IP address. - - :type: :py:class:`geoip2.records.Traits` - - """ - - def __init__(self, raw_response, locales=None): - if locales is None: - locales = ['en'] - self._locales = locales - self.continent = \ - geoip2.records.Continent(locales, - **raw_response.get('continent', {})) - self.country = \ - geoip2.records.Country(locales, - **raw_response.get('country', {})) - self.registered_country = \ - geoip2.records.Country(locales, - **raw_response.get('registered_country', - {})) - self.represented_country \ - = geoip2.records.RepresentedCountry(locales, - **raw_response.get( - 'represented_country', {})) - - self.maxmind = \ - geoip2.records.MaxMind(**raw_response.get('maxmind', {})) - - self.traits = geoip2.records.Traits(**raw_response.get('traits', {})) - self.raw = raw_response - - def __repr__(self): - return '{module}.{class_name}({data}, {locales})'.format( - module=self.__module__, - class_name=self.__class__.__name__, - data=self.raw, - locales=self._locales) - - -class City(Country): - """Model for the GeoIP2 Precision: City and the GeoIP2 City database. - - .. attribute:: city - - City object for the requested IP address. - - :type: :py:class:`geoip2.records.City` - - .. attribute:: continent - - Continent object for the requested IP address. - - :type: :py:class:`geoip2.records.Continent` - - .. attribute:: country - - Country object for the requested IP address. This record represents the - country where MaxMind believes the IP is located. - - :type: :py:class:`geoip2.records.Country` - - .. attribute:: location - - Location object for the requested IP address. - - .. attribute:: maxmind - - Information related to your MaxMind account. - - :type: :py:class:`geoip2.records.MaxMind` - - .. attribute:: registered_country - - The registered country object for the requested IP address. This record - represents the country where the ISP has registered a given IP block in - and may differ from the user's country. - - :type: :py:class:`geoip2.records.Country` - - .. attribute:: represented_country - - Object for the country represented by the users of the IP address - when that country is different than the country in ``country``. For - instance, the country represented by an overseas military base. - - :type: :py:class:`geoip2.records.RepresentedCountry` - - .. attribute:: subdivisions - - Object (tuple) representing the subdivisions of the country to which - the location of the requested IP address belongs. - - :type: :py:class:`geoip2.records.Subdivisions` - - .. attribute:: traits - - Object with the traits of the requested IP address. - - :type: :py:class:`geoip2.records.Traits` - - """ - - def __init__(self, raw_response, locales=None): - super(City, self).__init__(raw_response, locales) - self.city = \ - geoip2.records.City(locales, **raw_response.get('city', {})) - self.location = \ - geoip2.records.Location(**raw_response.get('location', {})) - self.postal = \ - geoip2.records.Postal(**raw_response.get('postal', {})) - self.subdivisions = \ - geoip2.records.Subdivisions(locales, - *raw_response.get('subdivisions', [])) - - -class Insights(City): - """Model for the GeoIP2 Precision: Insights web service endpoint. - - .. attribute:: city - - City object for the requested IP address. - - :type: :py:class:`geoip2.records.City` - - .. attribute:: continent - - Continent object for the requested IP address. - - :type: :py:class:`geoip2.records.Continent` - - .. attribute:: country - - Country object for the requested IP address. This record represents the - country where MaxMind believes the IP is located. - - :type: :py:class:`geoip2.records.Country` - - .. attribute:: location - - Location object for the requested IP address. - - .. attribute:: maxmind - - Information related to your MaxMind account. - - :type: :py:class:`geoip2.records.MaxMind` - - .. attribute:: registered_country - - The registered country object for the requested IP address. This record - represents the country where the ISP has registered a given IP block in - and may differ from the user's country. - - :type: :py:class:`geoip2.records.Country` - - .. attribute:: represented_country - - Object for the country represented by the users of the IP address - when that country is different than the country in ``country``. For - instance, the country represented by an overseas military base. - - :type: :py:class:`geoip2.records.RepresentedCountry` - - .. attribute:: subdivisions - - Object (tuple) representing the subdivisions of the country to which - the location of the requested IP address belongs. - - :type: :py:class:`geoip2.records.Subdivisions` - - .. attribute:: traits - - Object with the traits of the requested IP address. - - :type: :py:class:`geoip2.records.Traits` - - """ - - -class Enterprise(City): - """Model for the GeoIP2 Enterprise database. - - .. attribute:: city - - City object for the requested IP address. - - :type: :py:class:`geoip2.records.City` - - .. attribute:: continent - - Continent object for the requested IP address. - - :type: :py:class:`geoip2.records.Continent` - - .. attribute:: country - - Country object for the requested IP address. This record represents the - country where MaxMind believes the IP is located. - - :type: :py:class:`geoip2.records.Country` - - .. attribute:: location - - Location object for the requested IP address. - - .. attribute:: maxmind - - Information related to your MaxMind account. - - :type: :py:class:`geoip2.records.MaxMind` - - .. attribute:: registered_country - - The registered country object for the requested IP address. This record - represents the country where the ISP has registered a given IP block in - and may differ from the user's country. - - :type: :py:class:`geoip2.records.Country` - - .. attribute:: represented_country - - Object for the country represented by the users of the IP address - when that country is different than the country in ``country``. For - instance, the country represented by an overseas military base. - - :type: :py:class:`geoip2.records.RepresentedCountry` - - .. attribute:: subdivisions - - Object (tuple) representing the subdivisions of the country to which - the location of the requested IP address belongs. - - :type: :py:class:`geoip2.records.Subdivisions` - - .. attribute:: traits - - Object with the traits of the requested IP address. - - :type: :py:class:`geoip2.records.Traits` - - """ - - -class SimpleModel(SimpleEquality): - """Provides basic methods for non-location models""" - - __metaclass__ = ABCMeta - - def __repr__(self): - # pylint: disable=no-member - return '{module}.{class_name}({data})'.format( - module=self.__module__, - class_name=self.__class__.__name__, - data=str(self.raw)) - - -class AnonymousIP(SimpleModel): - """Model class for the GeoIP2 Anonymous IP. - - This class provides the following attribute: - - .. attribute:: is_anonymous - - This is true if the IP address belongs to any sort of anonymous network. - - :type: bool - - .. attribute:: is_anonymous_vpn - - This is true if the IP address belongs to an anonymous VPN system. - - :type: bool - - .. attribute:: is_hosting_provider - - This is true if the IP address belongs to a hosting provider. - - :type: bool - - .. attribute:: is_public_proxy - - This is true if the IP address belongs to a public proxy. - - :type: bool - - .. attribute:: is_tor_exit_node - - This is true if the IP address is a Tor exit node. - - :type: bool - - .. attribute:: ip_address - - The IP address used in the lookup. - - :type: unicode - """ - - def __init__(self, raw): - self.is_anonymous = raw.get('is_anonymous', False) - self.is_anonymous_vpn = raw.get('is_anonymous_vpn', False) - self.is_hosting_provider = raw.get('is_hosting_provider', False) - self.is_public_proxy = raw.get('is_public_proxy', False) - self.is_tor_exit_node = raw.get('is_tor_exit_node', False) - - self.ip_address = raw.get('ip_address') - self.raw = raw - - -class ASN(SimpleModel): - """Model class for the GeoLite2 ASN. - - This class provides the following attribute: - - .. attribute:: autonomous_system_number - - The autonomous system number associated with the IP address. - - :type: int - - .. attribute:: autonomous_system_organization - - The organization associated with the registered autonomous system number - for the IP address. - - :type: unicode - - .. attribute:: ip_address - - The IP address used in the lookup. - - :type: unicode - """ - - # pylint:disable=too-many-arguments - def __init__(self, raw): - self.autonomous_system_number = raw.get('autonomous_system_number') - self.autonomous_system_organization = raw.get( - 'autonomous_system_organization') - self.ip_address = raw.get('ip_address') - self.raw = raw - - -class ConnectionType(SimpleModel): - """Model class for the GeoIP2 Connection-Type. - - This class provides the following attribute: - - .. attribute:: connection_type - - The connection type may take the following values: - - - Dialup - - Cable/DSL - - Corporate - - Cellular - - Additional values may be added in the future. - - :type: unicode - - .. attribute:: ip_address - - The IP address used in the lookup. - - :type: unicode - """ - - def __init__(self, raw): - self.connection_type = raw.get('connection_type') - self.ip_address = raw.get('ip_address') - self.raw = raw - - -class Domain(SimpleModel): - """Model class for the GeoIP2 Domain. - - This class provides the following attribute: - - .. attribute:: domain - - The domain associated with the IP address. - - :type: unicode - - .. attribute:: ip_address - - The IP address used in the lookup. - - :type: unicode - - """ - - def __init__(self, raw): - self.domain = raw.get('domain') - self.ip_address = raw.get('ip_address') - self.raw = raw - - -class ISP(ASN): - """Model class for the GeoIP2 ISP. - - This class provides the following attribute: - - .. attribute:: autonomous_system_number - - The autonomous system number associated with the IP address. - - :type: int - - .. attribute:: autonomous_system_organization - - The organization associated with the registered autonomous system number - for the IP address. - - :type: unicode - - .. attribute:: isp - - The name of the ISP associated with the IP address. - - :type: unicode - - .. attribute:: organization - - The name of the organization associated with the IP address. - - :type: unicode - - .. attribute:: ip_address - - The IP address used in the lookup. - - :type: unicode - """ - - # pylint:disable=too-many-arguments - def __init__(self, raw): - super(ISP, self).__init__(raw) - self.isp = raw.get('isp') - self.organization = raw.get('organization') diff --git a/lib/geoip2/records.py b/lib/geoip2/records.py deleted file mode 100644 index 9e04960..0000000 --- a/lib/geoip2/records.py +++ /dev/null @@ -1,675 +0,0 @@ -""" - -Records -======= - -""" - -# pylint:disable=R0903 -from abc import ABCMeta - -from geoip2.mixins import SimpleEquality - - -class Record(SimpleEquality): - """All records are subclasses of the abstract class ``Record``.""" - - __metaclass__ = ABCMeta - - _valid_attributes = set() - - def __init__(self, **kwargs): - valid_args = dict((k, kwargs.get(k)) for k in self._valid_attributes) - self.__dict__.update(valid_args) - - def __setattr__(self, name, value): - raise AttributeError("can't set attribute") - - def __repr__(self): - args = ', '.join('%s=%r' % x for x in self.__dict__.items()) - return '{module}.{class_name}({data})'.format( - module=self.__module__, - class_name=self.__class__.__name__, - data=args) - - -class PlaceRecord(Record): - """All records with :py:attr:`names` subclass :py:class:`PlaceRecord`.""" - - __metaclass__ = ABCMeta - - def __init__(self, locales=None, **kwargs): - if locales is None: - locales = ['en'] - if kwargs.get('names') is None: - kwargs['names'] = {} - object.__setattr__(self, '_locales', locales) - super(PlaceRecord, self).__init__(**kwargs) - - @property - def name(self): - """Dict with locale codes as keys and localized name as value.""" - # pylint:disable=E1101 - return next((self.names.get(x) for x in self._locales - if x in self.names), None) - - -class City(PlaceRecord): - """Contains data for the city record associated with an IP address. - - This class contains the city-level data associated with an IP address. - - This record is returned by ``city``, ``enterprise``, and ``insights``. - - Attributes: - - .. attribute:: confidence - - A value from 0-100 indicating MaxMind's - confidence that the city is correct. This attribute is only available - from the Insights end point and the GeoIP2 Enterprise database. - - :type: int - - .. attribute:: geoname_id - - The GeoName ID for the city. - - :type: int - - .. attribute:: name - - The name of the city based on the locales list passed to the - constructor. - - :type: unicode - - .. attribute:: names - - A dictionary where the keys are locale codes - and the values are names. - - :type: dict - - """ - - _valid_attributes = set(['confidence', 'geoname_id', 'names']) - - -class Continent(PlaceRecord): - """Contains data for the continent record associated with an IP address. - - This class contains the continent-level data associated with an IP - address. - - Attributes: - - - .. attribute:: code - - A two character continent code like "NA" (North America) - or "OC" (Oceania). - - :type: unicode - - .. attribute:: geoname_id - - The GeoName ID for the continent. - - :type: int - - .. attribute:: name - - Returns the name of the continent based on the locales list passed to - the constructor. - - :type: unicode - - .. attribute:: names - - A dictionary where the keys are locale codes - and the values are names. - - :type: dict - - """ - - _valid_attributes = set(['code', 'geoname_id', 'names']) - - -class Country(PlaceRecord): - """Contains data for the country record associated with an IP address. - - This class contains the country-level data associated with an IP address. - - Attributes: - - - .. attribute:: confidence - - A value from 0-100 indicating MaxMind's confidence that - the country is correct. This attribute is only available from the - Insights end point and the GeoIP2 Enterprise database. - - :type: int - - .. attribute:: geoname_id - - The GeoName ID for the country. - - :type: int - - .. attribute:: is_in_european_union - - This is true if the country is a member state of the European Union. - - :type: bool - - .. attribute:: iso_code - - The two-character `ISO 3166-1 - <http://en.wikipedia.org/wiki/ISO_3166-1>`_ alpha code for the - country. - - :type: unicode - - .. attribute:: name - - The name of the country based on the locales list passed to the - constructor. - - :type: unicode - - .. attribute:: names - - A dictionary where the keys are locale codes and the values - are names. - - :type: dict - - """ - - _valid_attributes = set([ - 'confidence', 'geoname_id', 'is_in_european_union', 'iso_code', 'names' - ]) - - def __init__(self, locales=None, **kwargs): - if 'is_in_european_union' not in kwargs: - kwargs['is_in_european_union'] = False - super(Country, self).__init__(locales, **kwargs) - - -class RepresentedCountry(Country): - """Contains data for the represented country associated with an IP address. - - This class contains the country-level data associated with an IP address - for the IP's represented country. The represented country is the country - represented by something like a military base. - - Attributes: - - - .. attribute:: confidence - - A value from 0-100 indicating MaxMind's confidence that - the country is correct. This attribute is only available from the - Insights end point and the GeoIP2 Enterprise database. - - :type: int - - .. attribute:: geoname_id - - The GeoName ID for the country. - - :type: int - - .. attribute:: is_in_european_union - - This is true if the country is a member state of the European Union. - - :type: bool - - .. attribute:: iso_code - - The two-character `ISO 3166-1 - <http://en.wikipedia.org/wiki/ISO_3166-1>`_ alpha code for the country. - - :type: unicode - - .. attribute:: name - - The name of the country based on the locales list passed to the - constructor. - - :type: unicode - - .. attribute:: names - - A dictionary where the keys are locale codes and the values - are names. - - :type: dict - - - .. attribute:: type - - A string indicating the type of entity that is representing the - country. Currently we only return ``military`` but this could expand to - include other types in the future. - - :type: unicode - - """ - - _valid_attributes = set([ - 'confidence', 'geoname_id', 'is_in_european_union', 'iso_code', - 'names', 'type' - ]) - - -class Location(Record): - """Contains data for the location record associated with an IP address. - - This class contains the location data associated with an IP address. - - This record is returned by ``city``, ``enterprise``, and ``insights``. - - Attributes: - - .. attribute:: average_income - - The average income in US dollars associated with the requested IP - address. This attribute is only available from the Insights end point. - - :type: int - - .. attribute:: accuracy_radius - - The approximate accuracy radius in kilometers around the latitude and - longitude for the IP address. This is the radius where we have a 67% - confidence that the device using the IP address resides within the - circle centered at the latitude and longitude with the provided radius. - - :type: int - - .. attribute:: latitude - - The approximate latitude of the location associated with the IP - address. This value is not precise and should not be used to identify a - particular address or household. - - :type: float - - .. attribute:: longitude - - The approximate longitude of the location associated with the IP - address. This value is not precise and should not be used to identify a - particular address or household. - - :type: float - - .. attribute:: metro_code - - The metro code of the location if the - location is in the US. MaxMind returns the same metro codes as the - `Google AdWords API - <https://developers.google.com/adwords/api/docs/appendix/cities-DMAregions>`_. - - :type: int - - .. attribute:: population_density - - The estimated population per square kilometer associated with the IP - address. This attribute is only available from the Insights end point. - - :type: int - - .. attribute:: time_zone - - The time zone associated with location, as specified by the `IANA Time - Zone Database <http://www.iana.org/time-zones>`_, e.g., - "America/New_York". - - :type: unicode - - """ - - _valid_attributes = set([ - 'average_income', 'accuracy_radius', 'latitude', 'longitude', - 'metro_code', 'population_density', 'postal_code', 'postal_confidence', - 'time_zone' - ]) - - -class MaxMind(Record): - """Contains data related to your MaxMind account. - - Attributes: - - .. attribute:: queries_remaining - - The number of remaining queries you have - for the end point you are calling. - - :type: int - - """ - - _valid_attributes = set(['queries_remaining']) - - -class Postal(Record): - """Contains data for the postal record associated with an IP address. - - This class contains the postal data associated with an IP address. - - This attribute is returned by ``city``, ``enterprise``, and ``insights``. - - Attributes: - - .. attribute:: code - - The postal code of the location. Postal - codes are not available for all countries. In some countries, this will - only contain part of the postal code. - - :type: unicode - - .. attribute:: confidence - - A value from 0-100 indicating - MaxMind's confidence that the postal code is correct. This attribute is - only available from the Insights end point and the GeoIP2 Enterprise - database. - - :type: int - - """ - - _valid_attributes = set(['code', 'confidence']) - - -class Subdivision(PlaceRecord): - """Contains data for the subdivisions associated with an IP address. - - This class contains the subdivision data associated with an IP address. - - This attribute is returned by ``city``, ``enterprise``, and ``insights``. - - Attributes: - - .. attribute:: confidence - - This is a value from 0-100 indicating MaxMind's - confidence that the subdivision is correct. This attribute is only - available from the Insights end point and the GeoIP2 Enterprise - database. - - :type: int - - .. attribute:: geoname_id - - This is a GeoName ID for the subdivision. - - :type: int - - .. attribute:: iso_code - - This is a string up to three characters long - contain the subdivision portion of the `ISO 3166-2 code - <http://en.wikipedia.org/wiki/ISO_3166-2>`_. - - :type: unicode - - .. attribute:: name - - The name of the subdivision based on the locales list passed to the - constructor. - - :type: unicode - - .. attribute:: names - - A dictionary where the keys are locale codes and the - values are names - - :type: dict - - """ - - _valid_attributes = set(['confidence', 'geoname_id', 'iso_code', 'names']) - - -class Subdivisions(tuple): - """A tuple-like collection of subdivisions associated with an IP address. - - This class contains the subdivisions of the country associated with the - IP address from largest to smallest. - - For instance, the response for Oxford in the United Kingdom would have - England as the first element and Oxfordshire as the second element. - - This attribute is returned by ``city``, ``enterprise``, and ``insights``. - """ - - def __new__(cls, locales, *subdivisions): - subdivisions = [Subdivision(locales, **x) for x in subdivisions] - obj = super(cls, Subdivisions).__new__(cls, subdivisions) - return obj - - def __init__(self, locales, *subdivisions): # pylint:disable=W0613 - self._locales = locales - super(Subdivisions, self).__init__() - - @property - def most_specific(self): - """The most specific (smallest) subdivision available. - - If there are no :py:class:`Subdivision` objects for the response, - this returns an empty :py:class:`Subdivision`. - - :type: :py:class:`Subdivision` - """ - try: - return self[-1] - except IndexError: - return Subdivision(self._locales) - - -class Traits(Record): - """Contains data for the traits record associated with an IP address. - - This class contains the traits data associated with an IP address. - - This class has the following attributes: - - - .. attribute:: autonomous_system_number - - The `autonomous system - number <http://en.wikipedia.org/wiki/Autonomous_system_(Internet)>`_ - associated with the IP address. This attribute is only available from - the City and Insights web service end points and the GeoIP2 Enterprise - database. - - :type: int - - .. attribute:: autonomous_system_organization - - The organization associated with the registered `autonomous system - number <http://en.wikipedia.org/wiki/Autonomous_system_(Internet)>`_ for - the IP address. This attribute is only available from the City and - Insights web service end points and the GeoIP2 Enterprise database. - - :type: unicode - - .. attribute:: connection_type - - The connection type may take the following values: - - - Dialup - - Cable/DSL - - Corporate - - Cellular - - Additional values may be added in the future. - - This attribute is only available in the GeoIP2 Enterprise database. - - :type: unicode - - .. attribute:: domain - - The second level domain associated with the - IP address. This will be something like "example.com" or - "example.co.uk", not "foo.example.com". This attribute is only available - from the City and Insights web service end points and the GeoIP2 - Enterprise database. - - :type: unicode - - .. attribute:: ip_address - - The IP address that the data in the model - is for. If you performed a "me" lookup against the web service, this - will be the externally routable IP address for the system the code is - running on. If the system is behind a NAT, this may differ from the IP - address locally assigned to it. - - :type: unicode - - .. attribute:: is_anonymous - - This is true if the IP address belongs to any sort of anonymous network. - This attribute is only available from GeoIP2 Precision Insights. - - :type: bool - - .. attribute:: is_anonymous_proxy - - This is true if the IP is an anonymous - proxy. See http://dev.maxmind.com/faq/geoip#anonproxy for further - details. - - :type: bool - - .. deprecated:: 2.2.0 - Use our our `GeoIP2 Anonymous IP database - <https://www.maxmind.com/en/geoip2-anonymous-ip-database GeoIP2>`_ - instead. - - .. attribute:: is_anonymous_vpn - - This is true if the IP address belongs to an anonymous VPN system. - This attribute is only available from GeoIP2 Precision Insights. - - :type: bool - - .. attribute:: is_hosting_provider - - This is true if the IP address belongs to a hosting provider. - This attribute is only available from GeoIP2 Precision Insights. - - :type: bool - - .. attribute:: is_legitimate_proxy - - This attribute is true if MaxMind believes this IP address to be a - legitimate proxy, such as an internal VPN used by a corporation. This - attribute is only available in the GeoIP2 Enterprise database. - - :type: bool - - .. attribute:: is_public_proxy - - This is true if the IP address belongs to a public proxy. This attribute - is only available from GeoIP2 Precision Insights. - - :type: bool - - .. attribute:: is_satellite_provider - - This is true if the IP address is from a satellite provider that - provides service to multiple countries. - - :type: bool - - .. deprecated:: 2.2.0 - Due to the increased coverage by mobile carriers, very few - satellite providers now serve multiple countries. As a result, the - output does not provide sufficiently relevant data for us to maintain - it. - - .. attribute:: is_tor_exit_node - - This is true if the IP address is a Tor exit node. This attribute is - only available from GeoIP2 Precision Insights. - - :type: bool - - .. attribute:: isp - - The name of the ISP associated with the IP address. This attribute is - only available from the City and Insights web service end points and the - GeoIP2 Enterprise database. - - :type: unicode - - .. attribute:: organization - - The name of the organization associated with the IP address. This - attribute is only available from the City and Insights web service end - points and the GeoIP2 Enterprise database. - - :type: unicode - - .. attribute:: user_type - - The user type associated with the IP - address. This can be one of the following values: - - * business - * cafe - * cellular - * college - * content_delivery_network - * dialup - * government - * hosting - * library - * military - * residential - * router - * school - * search_engine_spider - * traveler - - This attribute is only available from the Insights end point and the - GeoIP2 Enterprise database. - - :type: unicode - - """ - - _valid_attributes = set([ - 'autonomous_system_number', 'autonomous_system_organization', - 'connection_type', 'domain', 'is_anonymous', 'is_anonymous_proxy', - 'is_anonymous_vpn', 'is_hosting_provider', 'is_legitimate_proxy', - 'is_public_proxy', 'is_satellite_provider', 'is_tor_exit_node', - 'is_satellite_provider', 'isp', 'ip_address', 'organization', - 'user_type' - ]) - - def __init__(self, **kwargs): - for k in [ - 'is_anonymous', - 'is_anonymous_proxy', - 'is_anonymous_vpn', - 'is_hosting_provider', - 'is_legitimate_proxy', - 'is_public_proxy', - 'is_satellite_provider', - 'is_tor_exit_node', - ]: - kwargs[k] = bool(kwargs.get(k, False)) - super(Traits, self).__init__(**kwargs) diff --git a/lib/geoip2/webservice.py b/lib/geoip2/webservice.py deleted file mode 100644 index 3e238a3..0000000 --- a/lib/geoip2/webservice.py +++ /dev/null @@ -1,235 +0,0 @@ -""" -============================ -WebServices Client API -============================ - -This class provides a client API for all the GeoIP2 Precision web service end -points. The end points are Country, City, and Insights. Each end point returns -a different set of data about an IP address, with Country returning the least -data and Insights the most. - -Each web service end point is represented by a different model class, and -these model classes in turn contain multiple record classes. The record -classes have attributes which contain data about the IP address. - -If the web service does not return a particular piece of data for an IP -address, the associated attribute is not populated. - -The web service may not return any information for an entire record, in which -case all of the attributes for that record class will be empty. - -SSL ---- - -Requests to the GeoIP2 Precision web service are always made with SSL. - -""" - -import requests - -from requests.utils import default_user_agent - -import geoip2 -import geoip2.models - -from .compat import compat_ip_address - -from .errors import (AddressNotFoundError, AuthenticationError, GeoIP2Error, - HTTPError, InvalidRequestError, OutOfQueriesError, - PermissionRequiredError) - - -class Client(object): - """Creates a new client object. - - It accepts the following required arguments: - - :param account_id: Your MaxMind account ID. - :param license_key: Your MaxMind license key. - - Go to https://www.maxmind.com/en/my_license_key to see your MaxMind - account ID and license key. - - The following keyword arguments are also accepted: - - :param host: The hostname to make a request against. This defaults to - "geoip.maxmind.com". In most cases, you should not need to set this - explicitly. - :param locales: This is list of locale codes. This argument will be - passed on to record classes to use when their name properties are - called. The default value is ['en']. - - The order of the locales is significant. When a record class has - multiple names (country, city, etc.), its name property will return - the name in the first locale that has one. - - Note that the only locale which is always present in the GeoIP2 - data is "en". If you do not include this locale, the name property - may end up returning None even when the record has an English name. - - Currently, the valid locale codes are: - - * de -- German - * en -- English names may still include accented characters if that is - the accepted spelling in English. In other words, English does not - mean ASCII. - * es -- Spanish - * fr -- French - * ja -- Japanese - * pt-BR -- Brazilian Portuguese - * ru -- Russian - * zh-CN -- Simplified Chinese. - - """ - - def __init__( - self, - account_id=None, - license_key=None, - host='geoip.maxmind.com', - locales=None, - timeout=None, - - # This is deprecated and not documented for that reason. - # It can be removed if we do a major release in the future. - user_id=None): - """Construct a Client.""" - # pylint: disable=too-many-arguments - if locales is None: - locales = ['en'] - if account_id is None: - account_id = user_id - - if account_id is None: - raise TypeError('The account_id is a required parameter') - if license_key is None: - raise TypeError('The license_key is a required parameter') - - self._locales = locales - # requests 2.12.2 requires that the username passed to auth be bytes - # or a string, with the former being preferred. - self._account_id = account_id if isinstance(account_id, - bytes) else str(account_id) - self._license_key = license_key - self._base_uri = 'https://%s/geoip/v2.1' % host - self._timeout = timeout - - def city(self, ip_address='me'): - """Call GeoIP2 Precision City endpoint with the specified IP. - - :param ip_address: IPv4 or IPv6 address as a string. If no - address is provided, the address that the web service is - called from will be used. - - :returns: :py:class:`geoip2.models.City` object - - """ - return self._response_for('city', geoip2.models.City, ip_address) - - def country(self, ip_address='me'): - """Call the GeoIP2 Country endpoint with the specified IP. - - :param ip_address: IPv4 or IPv6 address as a string. If no address - is provided, the address that the web service is called from will - be used. - - :returns: :py:class:`geoip2.models.Country` object - - """ - return self._response_for('country', geoip2.models.Country, ip_address) - - def insights(self, ip_address='me'): - """Call the GeoIP2 Precision: Insights endpoint with the specified IP. - - :param ip_address: IPv4 or IPv6 address as a string. If no address - is provided, the address that the web service is called from will - be used. - - :returns: :py:class:`geoip2.models.Insights` object - - """ - return self._response_for('insights', geoip2.models.Insights, - ip_address) - - def _response_for(self, path, model_class, ip_address): - if ip_address != 'me': - ip_address = str(compat_ip_address(ip_address)) - uri = '/'.join([self._base_uri, path, ip_address]) - response = requests.get( - uri, - auth=(self._account_id, self._license_key), - headers={ - 'Accept': 'application/json', - 'User-Agent': self._user_agent() - }, - timeout=self._timeout) - if response.status_code != 200: - raise self._exception_for_error(response, uri) - body = self._handle_success(response, uri) - return model_class(body, locales=self._locales) - - def _user_agent(self): - return 'GeoIP2 Python Client v%s (%s)' % (geoip2.__version__, - default_user_agent()) - - def _handle_success(self, response, uri): - try: - return response.json() - except ValueError as ex: - raise GeoIP2Error('Received a 200 response for %(uri)s' - ' but could not decode the response as ' - 'JSON: ' % locals() + ', '.join(ex.args), 200, - uri) - - def _exception_for_error(self, response, uri): - status = response.status_code - - if 400 <= status < 500: - return self._exception_for_4xx_status(response, status, uri) - elif 500 <= status < 600: - return self._exception_for_5xx_status(status, uri) - return self._exception_for_non_200_status(status, uri) - - def _exception_for_4xx_status(self, response, status, uri): - if not response.content: - return HTTPError('Received a %(status)i error for %(uri)s ' - 'with no body.' % locals(), status, uri) - elif response.headers['Content-Type'].find('json') == -1: - return HTTPError('Received a %i for %s with the following ' - 'body: %s' % (status, uri, response.content), - status, uri) - try: - body = response.json() - except ValueError as ex: - return HTTPError( - 'Received a %(status)i error for %(uri)s but it did' - ' not include the expected JSON body: ' % locals() + - ', '.join(ex.args), status, uri) - else: - if 'code' in body and 'error' in body: - return self._exception_for_web_service_error( - body.get('error'), body.get('code'), status, uri) - return HTTPError('Response contains JSON but it does not specify ' - 'code or error keys', status, uri) - - def _exception_for_web_service_error(self, message, code, status, uri): - if code in ('IP_ADDRESS_NOT_FOUND', 'IP_ADDRESS_RESERVED'): - return AddressNotFoundError(message) - elif code in ('ACCOUNT_ID_REQUIRED', 'ACCOUNT_ID_UNKNOWN', - 'AUTHORIZATION_INVALID', 'LICENSE_KEY_REQUIRED', - 'USER_ID_REQUIRED', 'USER_ID_UNKNOWN'): - return AuthenticationError(message) - elif code in ('INSUFFICIENT_FUNDS', 'OUT_OF_QUERIES'): - return OutOfQueriesError(message) - elif code == 'PERMISSION_REQUIRED': - return PermissionRequiredError(message) - - return InvalidRequestError(message, code, status, uri) - - def _exception_for_5xx_status(self, status, uri): - return HTTPError('Received a server error (%(status)i) for ' - '%(uri)s' % locals(), status, uri) - - def _exception_for_non_200_status(self, status, uri): - return HTTPError('Received a very surprising HTTP status ' - '(%(status)i) for %(uri)s' % locals(), status, uri) diff --git a/lib/idna/__init__.py b/lib/idna/__init__.py deleted file mode 100644 index 847bf93..0000000 --- a/lib/idna/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .package_data import __version__ -from .core import * diff --git a/lib/idna/codec.py b/lib/idna/codec.py deleted file mode 100644 index 98c65ea..0000000 --- a/lib/idna/codec.py +++ /dev/null @@ -1,118 +0,0 @@ -from .core import encode, decode, alabel, ulabel, IDNAError -import codecs -import re - -_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') - -class Codec(codecs.Codec): - - def encode(self, data, errors='strict'): - - if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) - - if not data: - return "", 0 - - return encode(data), len(data) - - def decode(self, data, errors='strict'): - - if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) - - if not data: - return u"", 0 - - return decode(data), len(data) - -class IncrementalEncoder(codecs.BufferedIncrementalEncoder): - def _buffer_encode(self, data, errors, final): - if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) - - if not data: - return ("", 0) - - labels = _unicode_dots_re.split(data) - trailing_dot = u'' - if labels: - if not labels[-1]: - trailing_dot = '.' - del labels[-1] - elif not final: - # Keep potentially unfinished label until the next call - del labels[-1] - if labels: - trailing_dot = '.' - - result = [] - size = 0 - for label in labels: - result.append(alabel(label)) - if size: - size += 1 - size += len(label) - - # Join with U+002E - result = ".".join(result) + trailing_dot - size += len(trailing_dot) - return (result, size) - -class IncrementalDecoder(codecs.BufferedIncrementalDecoder): - def _buffer_decode(self, data, errors, final): - if errors != 'strict': - raise IDNAError("Unsupported error handling \"{0}\"".format(errors)) - - if not data: - return (u"", 0) - - # IDNA allows decoding to operate on Unicode strings, too. - if isinstance(data, unicode): - labels = _unicode_dots_re.split(data) - else: - # Must be ASCII string - data = str(data) - unicode(data, "ascii") - labels = data.split(".") - - trailing_dot = u'' - if labels: - if not labels[-1]: - trailing_dot = u'.' - del labels[-1] - elif not final: - # Keep potentially unfinished label until the next call - del labels[-1] - if labels: - trailing_dot = u'.' - - result = [] - size = 0 - for label in labels: - result.append(ulabel(label)) - if size: - size += 1 - size += len(label) - - result = u".".join(result) + trailing_dot - size += len(trailing_dot) - return (result, size) - - -class StreamWriter(Codec, codecs.StreamWriter): - pass - -class StreamReader(Codec, codecs.StreamReader): - pass - -def getregentry(): - return codecs.CodecInfo( - name='idna', - encode=Codec().encode, - decode=Codec().decode, - incrementalencoder=IncrementalEncoder, - incrementaldecoder=IncrementalDecoder, - streamwriter=StreamWriter, - streamreader=StreamReader, - ) diff --git a/lib/idna/compat.py b/lib/idna/compat.py deleted file mode 100644 index 4d47f33..0000000 --- a/lib/idna/compat.py +++ /dev/null @@ -1,12 +0,0 @@ -from .core import * -from .codec import * - -def ToASCII(label): - return encode(label) - -def ToUnicode(label): - return decode(label) - -def nameprep(s): - raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") - diff --git a/lib/idna/core.py b/lib/idna/core.py deleted file mode 100644 index 090c2c1..0000000 --- a/lib/idna/core.py +++ /dev/null @@ -1,399 +0,0 @@ -from . import idnadata -import bisect -import unicodedata -import re -import sys -from .intranges import intranges_contain - -_virama_combining_class = 9 -_alabel_prefix = b'xn--' -_unicode_dots_re = re.compile(u'[\u002e\u3002\uff0e\uff61]') - -if sys.version_info[0] == 3: - unicode = str - unichr = chr - -class IDNAError(UnicodeError): - """ Base exception for all IDNA-encoding related problems """ - pass - - -class IDNABidiError(IDNAError): - """ Exception when bidirectional requirements are not satisfied """ - pass - - -class InvalidCodepoint(IDNAError): - """ Exception when a disallowed or unallocated codepoint is used """ - pass - - -class InvalidCodepointContext(IDNAError): - """ Exception when the codepoint is not valid in the context it is used """ - pass - - -def _combining_class(cp): - v = unicodedata.combining(unichr(cp)) - if v == 0: - if not unicodedata.name(unichr(cp)): - raise ValueError("Unknown character in unicodedata") - return v - -def _is_script(cp, script): - return intranges_contain(ord(cp), idnadata.scripts[script]) - -def _punycode(s): - return s.encode('punycode') - -def _unot(s): - return 'U+{0:04X}'.format(s) - - -def valid_label_length(label): - - if len(label) > 63: - return False - return True - - -def valid_string_length(label, trailing_dot): - - if len(label) > (254 if trailing_dot else 253): - return False - return True - - -def check_bidi(label, check_ltr=False): - - # Bidi rules should only be applied if string contains RTL characters - bidi_label = False - for (idx, cp) in enumerate(label, 1): - direction = unicodedata.bidirectional(cp) - if direction == '': - # String likely comes from a newer version of Unicode - raise IDNABidiError('Unknown directionality in label {0} at position {1}'.format(repr(label), idx)) - if direction in ['R', 'AL', 'AN']: - bidi_label = True - if not bidi_label and not check_ltr: - return True - - # Bidi rule 1 - direction = unicodedata.bidirectional(label[0]) - if direction in ['R', 'AL']: - rtl = True - elif direction == 'L': - rtl = False - else: - raise IDNABidiError('First codepoint in label {0} must be directionality L, R or AL'.format(repr(label))) - - valid_ending = False - number_type = False - for (idx, cp) in enumerate(label, 1): - direction = unicodedata.bidirectional(cp) - - if rtl: - # Bidi rule 2 - if not direction in ['R', 'AL', 'AN', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: - raise IDNABidiError('Invalid direction for codepoint at position {0} in a right-to-left label'.format(idx)) - # Bidi rule 3 - if direction in ['R', 'AL', 'EN', 'AN']: - valid_ending = True - elif direction != 'NSM': - valid_ending = False - # Bidi rule 4 - if direction in ['AN', 'EN']: - if not number_type: - number_type = direction - else: - if number_type != direction: - raise IDNABidiError('Can not mix numeral types in a right-to-left label') - else: - # Bidi rule 5 - if not direction in ['L', 'EN', 'ES', 'CS', 'ET', 'ON', 'BN', 'NSM']: - raise IDNABidiError('Invalid direction for codepoint at position {0} in a left-to-right label'.format(idx)) - # Bidi rule 6 - if direction in ['L', 'EN']: - valid_ending = True - elif direction != 'NSM': - valid_ending = False - - if not valid_ending: - raise IDNABidiError('Label ends with illegal codepoint directionality') - - return True - - -def check_initial_combiner(label): - - if unicodedata.category(label[0])[0] == 'M': - raise IDNAError('Label begins with an illegal combining character') - return True - - -def check_hyphen_ok(label): - - if label[2:4] == '--': - raise IDNAError('Label has disallowed hyphens in 3rd and 4th position') - if label[0] == '-' or label[-1] == '-': - raise IDNAError('Label must not start or end with a hyphen') - return True - - -def check_nfc(label): - - if unicodedata.normalize('NFC', label) != label: - raise IDNAError('Label must be in Normalization Form C') - - -def valid_contextj(label, pos): - - cp_value = ord(label[pos]) - - if cp_value == 0x200c: - - if pos > 0: - if _combining_class(ord(label[pos - 1])) == _virama_combining_class: - return True - - ok = False - for i in range(pos-1, -1, -1): - joining_type = idnadata.joining_types.get(ord(label[i])) - if joining_type == ord('T'): - continue - if joining_type in [ord('L'), ord('D')]: - ok = True - break - - if not ok: - return False - - ok = False - for i in range(pos+1, len(label)): - joining_type = idnadata.joining_types.get(ord(label[i])) - if joining_type == ord('T'): - continue - if joining_type in [ord('R'), ord('D')]: - ok = True - break - return ok - - if cp_value == 0x200d: - - if pos > 0: - if _combining_class(ord(label[pos - 1])) == _virama_combining_class: - return True - return False - - else: - - return False - - -def valid_contexto(label, pos, exception=False): - - cp_value = ord(label[pos]) - - if cp_value == 0x00b7: - if 0 < pos < len(label)-1: - if ord(label[pos - 1]) == 0x006c and ord(label[pos + 1]) == 0x006c: - return True - return False - - elif cp_value == 0x0375: - if pos < len(label)-1 and len(label) > 1: - return _is_script(label[pos + 1], 'Greek') - return False - - elif cp_value == 0x05f3 or cp_value == 0x05f4: - if pos > 0: - return _is_script(label[pos - 1], 'Hebrew') - return False - - elif cp_value == 0x30fb: - for cp in label: - if cp == u'\u30fb': - continue - if _is_script(cp, 'Hiragana') or _is_script(cp, 'Katakana') or _is_script(cp, 'Han'): - return True - return False - - elif 0x660 <= cp_value <= 0x669: - for cp in label: - if 0x6f0 <= ord(cp) <= 0x06f9: - return False - return True - - elif 0x6f0 <= cp_value <= 0x6f9: - for cp in label: - if 0x660 <= ord(cp) <= 0x0669: - return False - return True - - -def check_label(label): - - if isinstance(label, (bytes, bytearray)): - label = label.decode('utf-8') - if len(label) == 0: - raise IDNAError('Empty Label') - - check_nfc(label) - check_hyphen_ok(label) - check_initial_combiner(label) - - for (pos, cp) in enumerate(label): - cp_value = ord(cp) - if intranges_contain(cp_value, idnadata.codepoint_classes['PVALID']): - continue - elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTJ']): - try: - if not valid_contextj(label, pos): - raise InvalidCodepointContext('Joiner {0} not allowed at position {1} in {2}'.format( - _unot(cp_value), pos+1, repr(label))) - except ValueError: - raise IDNAError('Unknown codepoint adjacent to joiner {0} at position {1} in {2}'.format( - _unot(cp_value), pos+1, repr(label))) - elif intranges_contain(cp_value, idnadata.codepoint_classes['CONTEXTO']): - if not valid_contexto(label, pos): - raise InvalidCodepointContext('Codepoint {0} not allowed at position {1} in {2}'.format(_unot(cp_value), pos+1, repr(label))) - else: - raise InvalidCodepoint('Codepoint {0} at position {1} of {2} not allowed'.format(_unot(cp_value), pos+1, repr(label))) - - check_bidi(label) - - -def alabel(label): - - try: - label = label.encode('ascii') - try: - ulabel(label) - except IDNAError: - raise IDNAError('The label {0} is not a valid A-label'.format(label)) - if not valid_label_length(label): - raise IDNAError('Label too long') - return label - except UnicodeEncodeError: - pass - - if not label: - raise IDNAError('No Input') - - label = unicode(label) - check_label(label) - label = _punycode(label) - label = _alabel_prefix + label - - if not valid_label_length(label): - raise IDNAError('Label too long') - - return label - - -def ulabel(label): - - if not isinstance(label, (bytes, bytearray)): - try: - label = label.encode('ascii') - except UnicodeEncodeError: - check_label(label) - return label - - label = label.lower() - if label.startswith(_alabel_prefix): - label = label[len(_alabel_prefix):] - else: - check_label(label) - return label.decode('ascii') - - label = label.decode('punycode') - check_label(label) - return label - - -def uts46_remap(domain, std3_rules=True, transitional=False): - """Re-map the characters in the string according to UTS46 processing.""" - from .uts46data import uts46data - output = u"" - try: - for pos, char in enumerate(domain): - code_point = ord(char) - uts46row = uts46data[code_point if code_point < 256 else - bisect.bisect_left(uts46data, (code_point, "Z")) - 1] - status = uts46row[1] - replacement = uts46row[2] if len(uts46row) == 3 else None - if (status == "V" or - (status == "D" and not transitional) or - (status == "3" and not std3_rules and replacement is None)): - output += char - elif replacement is not None and (status == "M" or - (status == "3" and not std3_rules) or - (status == "D" and transitional)): - output += replacement - elif status != "I": - raise IndexError() - return unicodedata.normalize("NFC", output) - except IndexError: - raise InvalidCodepoint( - "Codepoint {0} not allowed at position {1} in {2}".format( - _unot(code_point), pos + 1, repr(domain))) - - -def encode(s, strict=False, uts46=False, std3_rules=False, transitional=False): - - if isinstance(s, (bytes, bytearray)): - s = s.decode("ascii") - if uts46: - s = uts46_remap(s, std3_rules, transitional) - trailing_dot = False - result = [] - if strict: - labels = s.split('.') - else: - labels = _unicode_dots_re.split(s) - if not labels or labels == ['']: - raise IDNAError('Empty domain') - if labels[-1] == '': - del labels[-1] - trailing_dot = True - for label in labels: - s = alabel(label) - if s: - result.append(s) - else: - raise IDNAError('Empty label') - if trailing_dot: - result.append(b'') - s = b'.'.join(result) - if not valid_string_length(s, trailing_dot): - raise IDNAError('Domain too long') - return s - - -def decode(s, strict=False, uts46=False, std3_rules=False): - - if isinstance(s, (bytes, bytearray)): - s = s.decode("ascii") - if uts46: - s = uts46_remap(s, std3_rules, False) - trailing_dot = False - result = [] - if not strict: - labels = _unicode_dots_re.split(s) - else: - labels = s.split(u'.') - if not labels or labels == ['']: - raise IDNAError('Empty domain') - if not labels[-1]: - del labels[-1] - trailing_dot = True - for label in labels: - s = ulabel(label) - if s: - result.append(s) - else: - raise IDNAError('Empty label') - if trailing_dot: - result.append(u'') - return u'.'.join(result) diff --git a/lib/idna/idnadata.py b/lib/idna/idnadata.py deleted file mode 100644 index 17974e2..0000000 --- a/lib/idna/idnadata.py +++ /dev/null @@ -1,1893 +0,0 @@ -# This file is automatically generated by tools/idna-data - -__version__ = "10.0.0" -scripts = { - 'Greek': ( - 0x37000000374, - 0x37500000378, - 0x37a0000037e, - 0x37f00000380, - 0x38400000385, - 0x38600000387, - 0x3880000038b, - 0x38c0000038d, - 0x38e000003a2, - 0x3a3000003e2, - 0x3f000000400, - 0x1d2600001d2b, - 0x1d5d00001d62, - 0x1d6600001d6b, - 0x1dbf00001dc0, - 0x1f0000001f16, - 0x1f1800001f1e, - 0x1f2000001f46, - 0x1f4800001f4e, - 0x1f5000001f58, - 0x1f5900001f5a, - 0x1f5b00001f5c, - 0x1f5d00001f5e, - 0x1f5f00001f7e, - 0x1f8000001fb5, - 0x1fb600001fc5, - 0x1fc600001fd4, - 0x1fd600001fdc, - 0x1fdd00001ff0, - 0x1ff200001ff5, - 0x1ff600001fff, - 0x212600002127, - 0xab650000ab66, - 0x101400001018f, - 0x101a0000101a1, - 0x1d2000001d246, - ), - 'Han': ( - 0x2e8000002e9a, - 0x2e9b00002ef4, - 0x2f0000002fd6, - 0x300500003006, - 0x300700003008, - 0x30210000302a, - 0x30380000303c, - 0x340000004db6, - 0x4e0000009feb, - 0xf9000000fa6e, - 0xfa700000fada, - 0x200000002a6d7, - 0x2a7000002b735, - 0x2b7400002b81e, - 0x2b8200002cea2, - 0x2ceb00002ebe1, - 0x2f8000002fa1e, - ), - 'Hebrew': ( - 0x591000005c8, - 0x5d0000005eb, - 0x5f0000005f5, - 0xfb1d0000fb37, - 0xfb380000fb3d, - 0xfb3e0000fb3f, - 0xfb400000fb42, - 0xfb430000fb45, - 0xfb460000fb50, - ), - 'Hiragana': ( - 0x304100003097, - 0x309d000030a0, - 0x1b0010001b11f, - 0x1f2000001f201, - ), - 'Katakana': ( - 0x30a1000030fb, - 0x30fd00003100, - 0x31f000003200, - 0x32d0000032ff, - 0x330000003358, - 0xff660000ff70, - 0xff710000ff9e, - 0x1b0000001b001, - ), -} -joining_types = { - 0x600: 85, - 0x601: 85, - 0x602: 85, - 0x603: 85, - 0x604: 85, - 0x605: 85, - 0x608: 85, - 0x60b: 85, - 0x620: 68, - 0x621: 85, - 0x622: 82, - 0x623: 82, - 0x624: 82, - 0x625: 82, - 0x626: 68, - 0x627: 82, - 0x628: 68, - 0x629: 82, - 0x62a: 68, - 0x62b: 68, - 0x62c: 68, - 0x62d: 68, - 0x62e: 68, - 0x62f: 82, - 0x630: 82, - 0x631: 82, - 0x632: 82, - 0x633: 68, - 0x634: 68, - 0x635: 68, - 0x636: 68, - 0x637: 68, - 0x638: 68, - 0x639: 68, - 0x63a: 68, - 0x63b: 68, - 0x63c: 68, - 0x63d: 68, - 0x63e: 68, - 0x63f: 68, - 0x640: 67, - 0x641: 68, - 0x642: 68, - 0x643: 68, - 0x644: 68, - 0x645: 68, - 0x646: 68, - 0x647: 68, - 0x648: 82, - 0x649: 68, - 0x64a: 68, - 0x66e: 68, - 0x66f: 68, - 0x671: 82, - 0x672: 82, - 0x673: 82, - 0x674: 85, - 0x675: 82, - 0x676: 82, - 0x677: 82, - 0x678: 68, - 0x679: 68, - 0x67a: 68, - 0x67b: 68, - 0x67c: 68, - 0x67d: 68, - 0x67e: 68, - 0x67f: 68, - 0x680: 68, - 0x681: 68, - 0x682: 68, - 0x683: 68, - 0x684: 68, - 0x685: 68, - 0x686: 68, - 0x687: 68, - 0x688: 82, - 0x689: 82, - 0x68a: 82, - 0x68b: 82, - 0x68c: 82, - 0x68d: 82, - 0x68e: 82, - 0x68f: 82, - 0x690: 82, - 0x691: 82, - 0x692: 82, - 0x693: 82, - 0x694: 82, - 0x695: 82, - 0x696: 82, - 0x697: 82, - 0x698: 82, - 0x699: 82, - 0x69a: 68, - 0x69b: 68, - 0x69c: 68, - 0x69d: 68, - 0x69e: 68, - 0x69f: 68, - 0x6a0: 68, - 0x6a1: 68, - 0x6a2: 68, - 0x6a3: 68, - 0x6a4: 68, - 0x6a5: 68, - 0x6a6: 68, - 0x6a7: 68, - 0x6a8: 68, - 0x6a9: 68, - 0x6aa: 68, - 0x6ab: 68, - 0x6ac: 68, - 0x6ad: 68, - 0x6ae: 68, - 0x6af: 68, - 0x6b0: 68, - 0x6b1: 68, - 0x6b2: 68, - 0x6b3: 68, - 0x6b4: 68, - 0x6b5: 68, - 0x6b6: 68, - 0x6b7: 68, - 0x6b8: 68, - 0x6b9: 68, - 0x6ba: 68, - 0x6bb: 68, - 0x6bc: 68, - 0x6bd: 68, - 0x6be: 68, - 0x6bf: 68, - 0x6c0: 82, - 0x6c1: 68, - 0x6c2: 68, - 0x6c3: 82, - 0x6c4: 82, - 0x6c5: 82, - 0x6c6: 82, - 0x6c7: 82, - 0x6c8: 82, - 0x6c9: 82, - 0x6ca: 82, - 0x6cb: 82, - 0x6cc: 68, - 0x6cd: 82, - 0x6ce: 68, - 0x6cf: 82, - 0x6d0: 68, - 0x6d1: 68, - 0x6d2: 82, - 0x6d3: 82, - 0x6d5: 82, - 0x6dd: 85, - 0x6ee: 82, - 0x6ef: 82, - 0x6fa: 68, - 0x6fb: 68, - 0x6fc: 68, - 0x6ff: 68, - 0x710: 82, - 0x712: 68, - 0x713: 68, - 0x714: 68, - 0x715: 82, - 0x716: 82, - 0x717: 82, - 0x718: 82, - 0x719: 82, - 0x71a: 68, - 0x71b: 68, - 0x71c: 68, - 0x71d: 68, - 0x71e: 82, - 0x71f: 68, - 0x720: 68, - 0x721: 68, - 0x722: 68, - 0x723: 68, - 0x724: 68, - 0x725: 68, - 0x726: 68, - 0x727: 68, - 0x728: 82, - 0x729: 68, - 0x72a: 82, - 0x72b: 68, - 0x72c: 82, - 0x72d: 68, - 0x72e: 68, - 0x72f: 82, - 0x74d: 82, - 0x74e: 68, - 0x74f: 68, - 0x750: 68, - 0x751: 68, - 0x752: 68, - 0x753: 68, - 0x754: 68, - 0x755: 68, - 0x756: 68, - 0x757: 68, - 0x758: 68, - 0x759: 82, - 0x75a: 82, - 0x75b: 82, - 0x75c: 68, - 0x75d: 68, - 0x75e: 68, - 0x75f: 68, - 0x760: 68, - 0x761: 68, - 0x762: 68, - 0x763: 68, - 0x764: 68, - 0x765: 68, - 0x766: 68, - 0x767: 68, - 0x768: 68, - 0x769: 68, - 0x76a: 68, - 0x76b: 82, - 0x76c: 82, - 0x76d: 68, - 0x76e: 68, - 0x76f: 68, - 0x770: 68, - 0x771: 82, - 0x772: 68, - 0x773: 82, - 0x774: 82, - 0x775: 68, - 0x776: 68, - 0x777: 68, - 0x778: 82, - 0x779: 82, - 0x77a: 68, - 0x77b: 68, - 0x77c: 68, - 0x77d: 68, - 0x77e: 68, - 0x77f: 68, - 0x7ca: 68, - 0x7cb: 68, - 0x7cc: 68, - 0x7cd: 68, - 0x7ce: 68, - 0x7cf: 68, - 0x7d0: 68, - 0x7d1: 68, - 0x7d2: 68, - 0x7d3: 68, - 0x7d4: 68, - 0x7d5: 68, - 0x7d6: 68, - 0x7d7: 68, - 0x7d8: 68, - 0x7d9: 68, - 0x7da: 68, - 0x7db: 68, - 0x7dc: 68, - 0x7dd: 68, - 0x7de: 68, - 0x7df: 68, - 0x7e0: 68, - 0x7e1: 68, - 0x7e2: 68, - 0x7e3: 68, - 0x7e4: 68, - 0x7e5: 68, - 0x7e6: 68, - 0x7e7: 68, - 0x7e8: 68, - 0x7e9: 68, - 0x7ea: 68, - 0x7fa: 67, - 0x840: 82, - 0x841: 68, - 0x842: 68, - 0x843: 68, - 0x844: 68, - 0x845: 68, - 0x846: 82, - 0x847: 82, - 0x848: 68, - 0x849: 82, - 0x84a: 68, - 0x84b: 68, - 0x84c: 68, - 0x84d: 68, - 0x84e: 68, - 0x84f: 68, - 0x850: 68, - 0x851: 68, - 0x852: 68, - 0x853: 68, - 0x854: 82, - 0x855: 68, - 0x856: 85, - 0x857: 85, - 0x858: 85, - 0x860: 68, - 0x861: 85, - 0x862: 68, - 0x863: 68, - 0x864: 68, - 0x865: 68, - 0x866: 85, - 0x867: 82, - 0x868: 68, - 0x869: 82, - 0x86a: 82, - 0x8a0: 68, - 0x8a1: 68, - 0x8a2: 68, - 0x8a3: 68, - 0x8a4: 68, - 0x8a5: 68, - 0x8a6: 68, - 0x8a7: 68, - 0x8a8: 68, - 0x8a9: 68, - 0x8aa: 82, - 0x8ab: 82, - 0x8ac: 82, - 0x8ad: 85, - 0x8ae: 82, - 0x8af: 68, - 0x8b0: 68, - 0x8b1: 82, - 0x8b2: 82, - 0x8b3: 68, - 0x8b4: 68, - 0x8b6: 68, - 0x8b7: 68, - 0x8b8: 68, - 0x8b9: 82, - 0x8ba: 68, - 0x8bb: 68, - 0x8bc: 68, - 0x8bd: 68, - 0x8e2: 85, - 0x1806: 85, - 0x1807: 68, - 0x180a: 67, - 0x180e: 85, - 0x1820: 68, - 0x1821: 68, - 0x1822: 68, - 0x1823: 68, - 0x1824: 68, - 0x1825: 68, - 0x1826: 68, - 0x1827: 68, - 0x1828: 68, - 0x1829: 68, - 0x182a: 68, - 0x182b: 68, - 0x182c: 68, - 0x182d: 68, - 0x182e: 68, - 0x182f: 68, - 0x1830: 68, - 0x1831: 68, - 0x1832: 68, - 0x1833: 68, - 0x1834: 68, - 0x1835: 68, - 0x1836: 68, - 0x1837: 68, - 0x1838: 68, - 0x1839: 68, - 0x183a: 68, - 0x183b: 68, - 0x183c: 68, - 0x183d: 68, - 0x183e: 68, - 0x183f: 68, - 0x1840: 68, - 0x1841: 68, - 0x1842: 68, - 0x1843: 68, - 0x1844: 68, - 0x1845: 68, - 0x1846: 68, - 0x1847: 68, - 0x1848: 68, - 0x1849: 68, - 0x184a: 68, - 0x184b: 68, - 0x184c: 68, - 0x184d: 68, - 0x184e: 68, - 0x184f: 68, - 0x1850: 68, - 0x1851: 68, - 0x1852: 68, - 0x1853: 68, - 0x1854: 68, - 0x1855: 68, - 0x1856: 68, - 0x1857: 68, - 0x1858: 68, - 0x1859: 68, - 0x185a: 68, - 0x185b: 68, - 0x185c: 68, - 0x185d: 68, - 0x185e: 68, - 0x185f: 68, - 0x1860: 68, - 0x1861: 68, - 0x1862: 68, - 0x1863: 68, - 0x1864: 68, - 0x1865: 68, - 0x1866: 68, - 0x1867: 68, - 0x1868: 68, - 0x1869: 68, - 0x186a: 68, - 0x186b: 68, - 0x186c: 68, - 0x186d: 68, - 0x186e: 68, - 0x186f: 68, - 0x1870: 68, - 0x1871: 68, - 0x1872: 68, - 0x1873: 68, - 0x1874: 68, - 0x1875: 68, - 0x1876: 68, - 0x1877: 68, - 0x1880: 85, - 0x1881: 85, - 0x1882: 85, - 0x1883: 85, - 0x1884: 85, - 0x1885: 84, - 0x1886: 84, - 0x1887: 68, - 0x1888: 68, - 0x1889: 68, - 0x188a: 68, - 0x188b: 68, - 0x188c: 68, - 0x188d: 68, - 0x188e: 68, - 0x188f: 68, - 0x1890: 68, - 0x1891: 68, - 0x1892: 68, - 0x1893: 68, - 0x1894: 68, - 0x1895: 68, - 0x1896: 68, - 0x1897: 68, - 0x1898: 68, - 0x1899: 68, - 0x189a: 68, - 0x189b: 68, - 0x189c: 68, - 0x189d: 68, - 0x189e: 68, - 0x189f: 68, - 0x18a0: 68, - 0x18a1: 68, - 0x18a2: 68, - 0x18a3: 68, - 0x18a4: 68, - 0x18a5: 68, - 0x18a6: 68, - 0x18a7: 68, - 0x18a8: 68, - 0x18aa: 68, - 0x200c: 85, - 0x200d: 67, - 0x202f: 85, - 0x2066: 85, - 0x2067: 85, - 0x2068: 85, - 0x2069: 85, - 0xa840: 68, - 0xa841: 68, - 0xa842: 68, - 0xa843: 68, - 0xa844: 68, - 0xa845: 68, - 0xa846: 68, - 0xa847: 68, - 0xa848: 68, - 0xa849: 68, - 0xa84a: 68, - 0xa84b: 68, - 0xa84c: 68, - 0xa84d: 68, - 0xa84e: 68, - 0xa84f: 68, - 0xa850: 68, - 0xa851: 68, - 0xa852: 68, - 0xa853: 68, - 0xa854: 68, - 0xa855: 68, - 0xa856: 68, - 0xa857: 68, - 0xa858: 68, - 0xa859: 68, - 0xa85a: 68, - 0xa85b: 68, - 0xa85c: 68, - 0xa85d: 68, - 0xa85e: 68, - 0xa85f: 68, - 0xa860: 68, - 0xa861: 68, - 0xa862: 68, - 0xa863: 68, - 0xa864: 68, - 0xa865: 68, - 0xa866: 68, - 0xa867: 68, - 0xa868: 68, - 0xa869: 68, - 0xa86a: 68, - 0xa86b: 68, - 0xa86c: 68, - 0xa86d: 68, - 0xa86e: 68, - 0xa86f: 68, - 0xa870: 68, - 0xa871: 68, - 0xa872: 76, - 0xa873: 85, - 0x10ac0: 68, - 0x10ac1: 68, - 0x10ac2: 68, - 0x10ac3: 68, - 0x10ac4: 68, - 0x10ac5: 82, - 0x10ac6: 85, - 0x10ac7: 82, - 0x10ac8: 85, - 0x10ac9: 82, - 0x10aca: 82, - 0x10acb: 85, - 0x10acc: 85, - 0x10acd: 76, - 0x10ace: 82, - 0x10acf: 82, - 0x10ad0: 82, - 0x10ad1: 82, - 0x10ad2: 82, - 0x10ad3: 68, - 0x10ad4: 68, - 0x10ad5: 68, - 0x10ad6: 68, - 0x10ad7: 76, - 0x10ad8: 68, - 0x10ad9: 68, - 0x10ada: 68, - 0x10adb: 68, - 0x10adc: 68, - 0x10add: 82, - 0x10ade: 68, - 0x10adf: 68, - 0x10ae0: 68, - 0x10ae1: 82, - 0x10ae2: 85, - 0x10ae3: 85, - 0x10ae4: 82, - 0x10aeb: 68, - 0x10aec: 68, - 0x10aed: 68, - 0x10aee: 68, - 0x10aef: 82, - 0x10b80: 68, - 0x10b81: 82, - 0x10b82: 68, - 0x10b83: 82, - 0x10b84: 82, - 0x10b85: 82, - 0x10b86: 68, - 0x10b87: 68, - 0x10b88: 68, - 0x10b89: 82, - 0x10b8a: 68, - 0x10b8b: 68, - 0x10b8c: 82, - 0x10b8d: 68, - 0x10b8e: 82, - 0x10b8f: 82, - 0x10b90: 68, - 0x10b91: 82, - 0x10ba9: 82, - 0x10baa: 82, - 0x10bab: 82, - 0x10bac: 82, - 0x10bad: 68, - 0x10bae: 68, - 0x10baf: 85, - 0x1e900: 68, - 0x1e901: 68, - 0x1e902: 68, - 0x1e903: 68, - 0x1e904: 68, - 0x1e905: 68, - 0x1e906: 68, - 0x1e907: 68, - 0x1e908: 68, - 0x1e909: 68, - 0x1e90a: 68, - 0x1e90b: 68, - 0x1e90c: 68, - 0x1e90d: 68, - 0x1e90e: 68, - 0x1e90f: 68, - 0x1e910: 68, - 0x1e911: 68, - 0x1e912: 68, - 0x1e913: 68, - 0x1e914: 68, - 0x1e915: 68, - 0x1e916: 68, - 0x1e917: 68, - 0x1e918: 68, - 0x1e919: 68, - 0x1e91a: 68, - 0x1e91b: 68, - 0x1e91c: 68, - 0x1e91d: 68, - 0x1e91e: 68, - 0x1e91f: 68, - 0x1e920: 68, - 0x1e921: 68, - 0x1e922: 68, - 0x1e923: 68, - 0x1e924: 68, - 0x1e925: 68, - 0x1e926: 68, - 0x1e927: 68, - 0x1e928: 68, - 0x1e929: 68, - 0x1e92a: 68, - 0x1e92b: 68, - 0x1e92c: 68, - 0x1e92d: 68, - 0x1e92e: 68, - 0x1e92f: 68, - 0x1e930: 68, - 0x1e931: 68, - 0x1e932: 68, - 0x1e933: 68, - 0x1e934: 68, - 0x1e935: 68, - 0x1e936: 68, - 0x1e937: 68, - 0x1e938: 68, - 0x1e939: 68, - 0x1e93a: 68, - 0x1e93b: 68, - 0x1e93c: 68, - 0x1e93d: 68, - 0x1e93e: 68, - 0x1e93f: 68, - 0x1e940: 68, - 0x1e941: 68, - 0x1e942: 68, - 0x1e943: 68, -} -codepoint_classes = { - 'PVALID': ( - 0x2d0000002e, - 0x300000003a, - 0x610000007b, - 0xdf000000f7, - 0xf800000100, - 0x10100000102, - 0x10300000104, - 0x10500000106, - 0x10700000108, - 0x1090000010a, - 0x10b0000010c, - 0x10d0000010e, - 0x10f00000110, - 0x11100000112, - 0x11300000114, - 0x11500000116, - 0x11700000118, - 0x1190000011a, - 0x11b0000011c, - 0x11d0000011e, - 0x11f00000120, - 0x12100000122, - 0x12300000124, - 0x12500000126, - 0x12700000128, - 0x1290000012a, - 0x12b0000012c, - 0x12d0000012e, - 0x12f00000130, - 0x13100000132, - 0x13500000136, - 0x13700000139, - 0x13a0000013b, - 0x13c0000013d, - 0x13e0000013f, - 0x14200000143, - 0x14400000145, - 0x14600000147, - 0x14800000149, - 0x14b0000014c, - 0x14d0000014e, - 0x14f00000150, - 0x15100000152, - 0x15300000154, - 0x15500000156, - 0x15700000158, - 0x1590000015a, - 0x15b0000015c, - 0x15d0000015e, - 0x15f00000160, - 0x16100000162, - 0x16300000164, - 0x16500000166, - 0x16700000168, - 0x1690000016a, - 0x16b0000016c, - 0x16d0000016e, - 0x16f00000170, - 0x17100000172, - 0x17300000174, - 0x17500000176, - 0x17700000178, - 0x17a0000017b, - 0x17c0000017d, - 0x17e0000017f, - 0x18000000181, - 0x18300000184, - 0x18500000186, - 0x18800000189, - 0x18c0000018e, - 0x19200000193, - 0x19500000196, - 0x1990000019c, - 0x19e0000019f, - 0x1a1000001a2, - 0x1a3000001a4, - 0x1a5000001a6, - 0x1a8000001a9, - 0x1aa000001ac, - 0x1ad000001ae, - 0x1b0000001b1, - 0x1b4000001b5, - 0x1b6000001b7, - 0x1b9000001bc, - 0x1bd000001c4, - 0x1ce000001cf, - 0x1d0000001d1, - 0x1d2000001d3, - 0x1d4000001d5, - 0x1d6000001d7, - 0x1d8000001d9, - 0x1da000001db, - 0x1dc000001de, - 0x1df000001e0, - 0x1e1000001e2, - 0x1e3000001e4, - 0x1e5000001e6, - 0x1e7000001e8, - 0x1e9000001ea, - 0x1eb000001ec, - 0x1ed000001ee, - 0x1ef000001f1, - 0x1f5000001f6, - 0x1f9000001fa, - 0x1fb000001fc, - 0x1fd000001fe, - 0x1ff00000200, - 0x20100000202, - 0x20300000204, - 0x20500000206, - 0x20700000208, - 0x2090000020a, - 0x20b0000020c, - 0x20d0000020e, - 0x20f00000210, - 0x21100000212, - 0x21300000214, - 0x21500000216, - 0x21700000218, - 0x2190000021a, - 0x21b0000021c, - 0x21d0000021e, - 0x21f00000220, - 0x22100000222, - 0x22300000224, - 0x22500000226, - 0x22700000228, - 0x2290000022a, - 0x22b0000022c, - 0x22d0000022e, - 0x22f00000230, - 0x23100000232, - 0x2330000023a, - 0x23c0000023d, - 0x23f00000241, - 0x24200000243, - 0x24700000248, - 0x2490000024a, - 0x24b0000024c, - 0x24d0000024e, - 0x24f000002b0, - 0x2b9000002c2, - 0x2c6000002d2, - 0x2ec000002ed, - 0x2ee000002ef, - 0x30000000340, - 0x34200000343, - 0x3460000034f, - 0x35000000370, - 0x37100000372, - 0x37300000374, - 0x37700000378, - 0x37b0000037e, - 0x39000000391, - 0x3ac000003cf, - 0x3d7000003d8, - 0x3d9000003da, - 0x3db000003dc, - 0x3dd000003de, - 0x3df000003e0, - 0x3e1000003e2, - 0x3e3000003e4, - 0x3e5000003e6, - 0x3e7000003e8, - 0x3e9000003ea, - 0x3eb000003ec, - 0x3ed000003ee, - 0x3ef000003f0, - 0x3f3000003f4, - 0x3f8000003f9, - 0x3fb000003fd, - 0x43000000460, - 0x46100000462, - 0x46300000464, - 0x46500000466, - 0x46700000468, - 0x4690000046a, - 0x46b0000046c, - 0x46d0000046e, - 0x46f00000470, - 0x47100000472, - 0x47300000474, - 0x47500000476, - 0x47700000478, - 0x4790000047a, - 0x47b0000047c, - 0x47d0000047e, - 0x47f00000480, - 0x48100000482, - 0x48300000488, - 0x48b0000048c, - 0x48d0000048e, - 0x48f00000490, - 0x49100000492, - 0x49300000494, - 0x49500000496, - 0x49700000498, - 0x4990000049a, - 0x49b0000049c, - 0x49d0000049e, - 0x49f000004a0, - 0x4a1000004a2, - 0x4a3000004a4, - 0x4a5000004a6, - 0x4a7000004a8, - 0x4a9000004aa, - 0x4ab000004ac, - 0x4ad000004ae, - 0x4af000004b0, - 0x4b1000004b2, - 0x4b3000004b4, - 0x4b5000004b6, - 0x4b7000004b8, - 0x4b9000004ba, - 0x4bb000004bc, - 0x4bd000004be, - 0x4bf000004c0, - 0x4c2000004c3, - 0x4c4000004c5, - 0x4c6000004c7, - 0x4c8000004c9, - 0x4ca000004cb, - 0x4cc000004cd, - 0x4ce000004d0, - 0x4d1000004d2, - 0x4d3000004d4, - 0x4d5000004d6, - 0x4d7000004d8, - 0x4d9000004da, - 0x4db000004dc, - 0x4dd000004de, - 0x4df000004e0, - 0x4e1000004e2, - 0x4e3000004e4, - 0x4e5000004e6, - 0x4e7000004e8, - 0x4e9000004ea, - 0x4eb000004ec, - 0x4ed000004ee, - 0x4ef000004f0, - 0x4f1000004f2, - 0x4f3000004f4, - 0x4f5000004f6, - 0x4f7000004f8, - 0x4f9000004fa, - 0x4fb000004fc, - 0x4fd000004fe, - 0x4ff00000500, - 0x50100000502, - 0x50300000504, - 0x50500000506, - 0x50700000508, - 0x5090000050a, - 0x50b0000050c, - 0x50d0000050e, - 0x50f00000510, - 0x51100000512, - 0x51300000514, - 0x51500000516, - 0x51700000518, - 0x5190000051a, - 0x51b0000051c, - 0x51d0000051e, - 0x51f00000520, - 0x52100000522, - 0x52300000524, - 0x52500000526, - 0x52700000528, - 0x5290000052a, - 0x52b0000052c, - 0x52d0000052e, - 0x52f00000530, - 0x5590000055a, - 0x56100000587, - 0x591000005be, - 0x5bf000005c0, - 0x5c1000005c3, - 0x5c4000005c6, - 0x5c7000005c8, - 0x5d0000005eb, - 0x5f0000005f3, - 0x6100000061b, - 0x62000000640, - 0x64100000660, - 0x66e00000675, - 0x679000006d4, - 0x6d5000006dd, - 0x6df000006e9, - 0x6ea000006f0, - 0x6fa00000700, - 0x7100000074b, - 0x74d000007b2, - 0x7c0000007f6, - 0x8000000082e, - 0x8400000085c, - 0x8600000086b, - 0x8a0000008b5, - 0x8b6000008be, - 0x8d4000008e2, - 0x8e300000958, - 0x96000000964, - 0x96600000970, - 0x97100000984, - 0x9850000098d, - 0x98f00000991, - 0x993000009a9, - 0x9aa000009b1, - 0x9b2000009b3, - 0x9b6000009ba, - 0x9bc000009c5, - 0x9c7000009c9, - 0x9cb000009cf, - 0x9d7000009d8, - 0x9e0000009e4, - 0x9e6000009f2, - 0x9fc000009fd, - 0xa0100000a04, - 0xa0500000a0b, - 0xa0f00000a11, - 0xa1300000a29, - 0xa2a00000a31, - 0xa3200000a33, - 0xa3500000a36, - 0xa3800000a3a, - 0xa3c00000a3d, - 0xa3e00000a43, - 0xa4700000a49, - 0xa4b00000a4e, - 0xa5100000a52, - 0xa5c00000a5d, - 0xa6600000a76, - 0xa8100000a84, - 0xa8500000a8e, - 0xa8f00000a92, - 0xa9300000aa9, - 0xaaa00000ab1, - 0xab200000ab4, - 0xab500000aba, - 0xabc00000ac6, - 0xac700000aca, - 0xacb00000ace, - 0xad000000ad1, - 0xae000000ae4, - 0xae600000af0, - 0xaf900000b00, - 0xb0100000b04, - 0xb0500000b0d, - 0xb0f00000b11, - 0xb1300000b29, - 0xb2a00000b31, - 0xb3200000b34, - 0xb3500000b3a, - 0xb3c00000b45, - 0xb4700000b49, - 0xb4b00000b4e, - 0xb5600000b58, - 0xb5f00000b64, - 0xb6600000b70, - 0xb7100000b72, - 0xb8200000b84, - 0xb8500000b8b, - 0xb8e00000b91, - 0xb9200000b96, - 0xb9900000b9b, - 0xb9c00000b9d, - 0xb9e00000ba0, - 0xba300000ba5, - 0xba800000bab, - 0xbae00000bba, - 0xbbe00000bc3, - 0xbc600000bc9, - 0xbca00000bce, - 0xbd000000bd1, - 0xbd700000bd8, - 0xbe600000bf0, - 0xc0000000c04, - 0xc0500000c0d, - 0xc0e00000c11, - 0xc1200000c29, - 0xc2a00000c3a, - 0xc3d00000c45, - 0xc4600000c49, - 0xc4a00000c4e, - 0xc5500000c57, - 0xc5800000c5b, - 0xc6000000c64, - 0xc6600000c70, - 0xc8000000c84, - 0xc8500000c8d, - 0xc8e00000c91, - 0xc9200000ca9, - 0xcaa00000cb4, - 0xcb500000cba, - 0xcbc00000cc5, - 0xcc600000cc9, - 0xcca00000cce, - 0xcd500000cd7, - 0xcde00000cdf, - 0xce000000ce4, - 0xce600000cf0, - 0xcf100000cf3, - 0xd0000000d04, - 0xd0500000d0d, - 0xd0e00000d11, - 0xd1200000d45, - 0xd4600000d49, - 0xd4a00000d4f, - 0xd5400000d58, - 0xd5f00000d64, - 0xd6600000d70, - 0xd7a00000d80, - 0xd8200000d84, - 0xd8500000d97, - 0xd9a00000db2, - 0xdb300000dbc, - 0xdbd00000dbe, - 0xdc000000dc7, - 0xdca00000dcb, - 0xdcf00000dd5, - 0xdd600000dd7, - 0xdd800000de0, - 0xde600000df0, - 0xdf200000df4, - 0xe0100000e33, - 0xe3400000e3b, - 0xe4000000e4f, - 0xe5000000e5a, - 0xe8100000e83, - 0xe8400000e85, - 0xe8700000e89, - 0xe8a00000e8b, - 0xe8d00000e8e, - 0xe9400000e98, - 0xe9900000ea0, - 0xea100000ea4, - 0xea500000ea6, - 0xea700000ea8, - 0xeaa00000eac, - 0xead00000eb3, - 0xeb400000eba, - 0xebb00000ebe, - 0xec000000ec5, - 0xec600000ec7, - 0xec800000ece, - 0xed000000eda, - 0xede00000ee0, - 0xf0000000f01, - 0xf0b00000f0c, - 0xf1800000f1a, - 0xf2000000f2a, - 0xf3500000f36, - 0xf3700000f38, - 0xf3900000f3a, - 0xf3e00000f43, - 0xf4400000f48, - 0xf4900000f4d, - 0xf4e00000f52, - 0xf5300000f57, - 0xf5800000f5c, - 0xf5d00000f69, - 0xf6a00000f6d, - 0xf7100000f73, - 0xf7400000f75, - 0xf7a00000f81, - 0xf8200000f85, - 0xf8600000f93, - 0xf9400000f98, - 0xf9900000f9d, - 0xf9e00000fa2, - 0xfa300000fa7, - 0xfa800000fac, - 0xfad00000fb9, - 0xfba00000fbd, - 0xfc600000fc7, - 0x10000000104a, - 0x10500000109e, - 0x10d0000010fb, - 0x10fd00001100, - 0x120000001249, - 0x124a0000124e, - 0x125000001257, - 0x125800001259, - 0x125a0000125e, - 0x126000001289, - 0x128a0000128e, - 0x1290000012b1, - 0x12b2000012b6, - 0x12b8000012bf, - 0x12c0000012c1, - 0x12c2000012c6, - 0x12c8000012d7, - 0x12d800001311, - 0x131200001316, - 0x13180000135b, - 0x135d00001360, - 0x138000001390, - 0x13a0000013f6, - 0x14010000166d, - 0x166f00001680, - 0x16810000169b, - 0x16a0000016eb, - 0x16f1000016f9, - 0x17000000170d, - 0x170e00001715, - 0x172000001735, - 0x174000001754, - 0x17600000176d, - 0x176e00001771, - 0x177200001774, - 0x1780000017b4, - 0x17b6000017d4, - 0x17d7000017d8, - 0x17dc000017de, - 0x17e0000017ea, - 0x18100000181a, - 0x182000001878, - 0x1880000018ab, - 0x18b0000018f6, - 0x19000000191f, - 0x19200000192c, - 0x19300000193c, - 0x19460000196e, - 0x197000001975, - 0x1980000019ac, - 0x19b0000019ca, - 0x19d0000019da, - 0x1a0000001a1c, - 0x1a2000001a5f, - 0x1a6000001a7d, - 0x1a7f00001a8a, - 0x1a9000001a9a, - 0x1aa700001aa8, - 0x1ab000001abe, - 0x1b0000001b4c, - 0x1b5000001b5a, - 0x1b6b00001b74, - 0x1b8000001bf4, - 0x1c0000001c38, - 0x1c4000001c4a, - 0x1c4d00001c7e, - 0x1cd000001cd3, - 0x1cd400001cfa, - 0x1d0000001d2c, - 0x1d2f00001d30, - 0x1d3b00001d3c, - 0x1d4e00001d4f, - 0x1d6b00001d78, - 0x1d7900001d9b, - 0x1dc000001dfa, - 0x1dfb00001e00, - 0x1e0100001e02, - 0x1e0300001e04, - 0x1e0500001e06, - 0x1e0700001e08, - 0x1e0900001e0a, - 0x1e0b00001e0c, - 0x1e0d00001e0e, - 0x1e0f00001e10, - 0x1e1100001e12, - 0x1e1300001e14, - 0x1e1500001e16, - 0x1e1700001e18, - 0x1e1900001e1a, - 0x1e1b00001e1c, - 0x1e1d00001e1e, - 0x1e1f00001e20, - 0x1e2100001e22, - 0x1e2300001e24, - 0x1e2500001e26, - 0x1e2700001e28, - 0x1e2900001e2a, - 0x1e2b00001e2c, - 0x1e2d00001e2e, - 0x1e2f00001e30, - 0x1e3100001e32, - 0x1e3300001e34, - 0x1e3500001e36, - 0x1e3700001e38, - 0x1e3900001e3a, - 0x1e3b00001e3c, - 0x1e3d00001e3e, - 0x1e3f00001e40, - 0x1e4100001e42, - 0x1e4300001e44, - 0x1e4500001e46, - 0x1e4700001e48, - 0x1e4900001e4a, - 0x1e4b00001e4c, - 0x1e4d00001e4e, - 0x1e4f00001e50, - 0x1e5100001e52, - 0x1e5300001e54, - 0x1e5500001e56, - 0x1e5700001e58, - 0x1e5900001e5a, - 0x1e5b00001e5c, - 0x1e5d00001e5e, - 0x1e5f00001e60, - 0x1e6100001e62, - 0x1e6300001e64, - 0x1e6500001e66, - 0x1e6700001e68, - 0x1e6900001e6a, - 0x1e6b00001e6c, - 0x1e6d00001e6e, - 0x1e6f00001e70, - 0x1e7100001e72, - 0x1e7300001e74, - 0x1e7500001e76, - 0x1e7700001e78, - 0x1e7900001e7a, - 0x1e7b00001e7c, - 0x1e7d00001e7e, - 0x1e7f00001e80, - 0x1e8100001e82, - 0x1e8300001e84, - 0x1e8500001e86, - 0x1e8700001e88, - 0x1e8900001e8a, - 0x1e8b00001e8c, - 0x1e8d00001e8e, - 0x1e8f00001e90, - 0x1e9100001e92, - 0x1e9300001e94, - 0x1e9500001e9a, - 0x1e9c00001e9e, - 0x1e9f00001ea0, - 0x1ea100001ea2, - 0x1ea300001ea4, - 0x1ea500001ea6, - 0x1ea700001ea8, - 0x1ea900001eaa, - 0x1eab00001eac, - 0x1ead00001eae, - 0x1eaf00001eb0, - 0x1eb100001eb2, - 0x1eb300001eb4, - 0x1eb500001eb6, - 0x1eb700001eb8, - 0x1eb900001eba, - 0x1ebb00001ebc, - 0x1ebd00001ebe, - 0x1ebf00001ec0, - 0x1ec100001ec2, - 0x1ec300001ec4, - 0x1ec500001ec6, - 0x1ec700001ec8, - 0x1ec900001eca, - 0x1ecb00001ecc, - 0x1ecd00001ece, - 0x1ecf00001ed0, - 0x1ed100001ed2, - 0x1ed300001ed4, - 0x1ed500001ed6, - 0x1ed700001ed8, - 0x1ed900001eda, - 0x1edb00001edc, - 0x1edd00001ede, - 0x1edf00001ee0, - 0x1ee100001ee2, - 0x1ee300001ee4, - 0x1ee500001ee6, - 0x1ee700001ee8, - 0x1ee900001eea, - 0x1eeb00001eec, - 0x1eed00001eee, - 0x1eef00001ef0, - 0x1ef100001ef2, - 0x1ef300001ef4, - 0x1ef500001ef6, - 0x1ef700001ef8, - 0x1ef900001efa, - 0x1efb00001efc, - 0x1efd00001efe, - 0x1eff00001f08, - 0x1f1000001f16, - 0x1f2000001f28, - 0x1f3000001f38, - 0x1f4000001f46, - 0x1f5000001f58, - 0x1f6000001f68, - 0x1f7000001f71, - 0x1f7200001f73, - 0x1f7400001f75, - 0x1f7600001f77, - 0x1f7800001f79, - 0x1f7a00001f7b, - 0x1f7c00001f7d, - 0x1fb000001fb2, - 0x1fb600001fb7, - 0x1fc600001fc7, - 0x1fd000001fd3, - 0x1fd600001fd8, - 0x1fe000001fe3, - 0x1fe400001fe8, - 0x1ff600001ff7, - 0x214e0000214f, - 0x218400002185, - 0x2c3000002c5f, - 0x2c6100002c62, - 0x2c6500002c67, - 0x2c6800002c69, - 0x2c6a00002c6b, - 0x2c6c00002c6d, - 0x2c7100002c72, - 0x2c7300002c75, - 0x2c7600002c7c, - 0x2c8100002c82, - 0x2c8300002c84, - 0x2c8500002c86, - 0x2c8700002c88, - 0x2c8900002c8a, - 0x2c8b00002c8c, - 0x2c8d00002c8e, - 0x2c8f00002c90, - 0x2c9100002c92, - 0x2c9300002c94, - 0x2c9500002c96, - 0x2c9700002c98, - 0x2c9900002c9a, - 0x2c9b00002c9c, - 0x2c9d00002c9e, - 0x2c9f00002ca0, - 0x2ca100002ca2, - 0x2ca300002ca4, - 0x2ca500002ca6, - 0x2ca700002ca8, - 0x2ca900002caa, - 0x2cab00002cac, - 0x2cad00002cae, - 0x2caf00002cb0, - 0x2cb100002cb2, - 0x2cb300002cb4, - 0x2cb500002cb6, - 0x2cb700002cb8, - 0x2cb900002cba, - 0x2cbb00002cbc, - 0x2cbd00002cbe, - 0x2cbf00002cc0, - 0x2cc100002cc2, - 0x2cc300002cc4, - 0x2cc500002cc6, - 0x2cc700002cc8, - 0x2cc900002cca, - 0x2ccb00002ccc, - 0x2ccd00002cce, - 0x2ccf00002cd0, - 0x2cd100002cd2, - 0x2cd300002cd4, - 0x2cd500002cd6, - 0x2cd700002cd8, - 0x2cd900002cda, - 0x2cdb00002cdc, - 0x2cdd00002cde, - 0x2cdf00002ce0, - 0x2ce100002ce2, - 0x2ce300002ce5, - 0x2cec00002ced, - 0x2cee00002cf2, - 0x2cf300002cf4, - 0x2d0000002d26, - 0x2d2700002d28, - 0x2d2d00002d2e, - 0x2d3000002d68, - 0x2d7f00002d97, - 0x2da000002da7, - 0x2da800002daf, - 0x2db000002db7, - 0x2db800002dbf, - 0x2dc000002dc7, - 0x2dc800002dcf, - 0x2dd000002dd7, - 0x2dd800002ddf, - 0x2de000002e00, - 0x2e2f00002e30, - 0x300500003008, - 0x302a0000302e, - 0x303c0000303d, - 0x304100003097, - 0x30990000309b, - 0x309d0000309f, - 0x30a1000030fb, - 0x30fc000030ff, - 0x31050000312f, - 0x31a0000031bb, - 0x31f000003200, - 0x340000004db6, - 0x4e0000009feb, - 0xa0000000a48d, - 0xa4d00000a4fe, - 0xa5000000a60d, - 0xa6100000a62c, - 0xa6410000a642, - 0xa6430000a644, - 0xa6450000a646, - 0xa6470000a648, - 0xa6490000a64a, - 0xa64b0000a64c, - 0xa64d0000a64e, - 0xa64f0000a650, - 0xa6510000a652, - 0xa6530000a654, - 0xa6550000a656, - 0xa6570000a658, - 0xa6590000a65a, - 0xa65b0000a65c, - 0xa65d0000a65e, - 0xa65f0000a660, - 0xa6610000a662, - 0xa6630000a664, - 0xa6650000a666, - 0xa6670000a668, - 0xa6690000a66a, - 0xa66b0000a66c, - 0xa66d0000a670, - 0xa6740000a67e, - 0xa67f0000a680, - 0xa6810000a682, - 0xa6830000a684, - 0xa6850000a686, - 0xa6870000a688, - 0xa6890000a68a, - 0xa68b0000a68c, - 0xa68d0000a68e, - 0xa68f0000a690, - 0xa6910000a692, - 0xa6930000a694, - 0xa6950000a696, - 0xa6970000a698, - 0xa6990000a69a, - 0xa69b0000a69c, - 0xa69e0000a6e6, - 0xa6f00000a6f2, - 0xa7170000a720, - 0xa7230000a724, - 0xa7250000a726, - 0xa7270000a728, - 0xa7290000a72a, - 0xa72b0000a72c, - 0xa72d0000a72e, - 0xa72f0000a732, - 0xa7330000a734, - 0xa7350000a736, - 0xa7370000a738, - 0xa7390000a73a, - 0xa73b0000a73c, - 0xa73d0000a73e, - 0xa73f0000a740, - 0xa7410000a742, - 0xa7430000a744, - 0xa7450000a746, - 0xa7470000a748, - 0xa7490000a74a, - 0xa74b0000a74c, - 0xa74d0000a74e, - 0xa74f0000a750, - 0xa7510000a752, - 0xa7530000a754, - 0xa7550000a756, - 0xa7570000a758, - 0xa7590000a75a, - 0xa75b0000a75c, - 0xa75d0000a75e, - 0xa75f0000a760, - 0xa7610000a762, - 0xa7630000a764, - 0xa7650000a766, - 0xa7670000a768, - 0xa7690000a76a, - 0xa76b0000a76c, - 0xa76d0000a76e, - 0xa76f0000a770, - 0xa7710000a779, - 0xa77a0000a77b, - 0xa77c0000a77d, - 0xa77f0000a780, - 0xa7810000a782, - 0xa7830000a784, - 0xa7850000a786, - 0xa7870000a789, - 0xa78c0000a78d, - 0xa78e0000a790, - 0xa7910000a792, - 0xa7930000a796, - 0xa7970000a798, - 0xa7990000a79a, - 0xa79b0000a79c, - 0xa79d0000a79e, - 0xa79f0000a7a0, - 0xa7a10000a7a2, - 0xa7a30000a7a4, - 0xa7a50000a7a6, - 0xa7a70000a7a8, - 0xa7a90000a7aa, - 0xa7b50000a7b6, - 0xa7b70000a7b8, - 0xa7f70000a7f8, - 0xa7fa0000a828, - 0xa8400000a874, - 0xa8800000a8c6, - 0xa8d00000a8da, - 0xa8e00000a8f8, - 0xa8fb0000a8fc, - 0xa8fd0000a8fe, - 0xa9000000a92e, - 0xa9300000a954, - 0xa9800000a9c1, - 0xa9cf0000a9da, - 0xa9e00000a9ff, - 0xaa000000aa37, - 0xaa400000aa4e, - 0xaa500000aa5a, - 0xaa600000aa77, - 0xaa7a0000aac3, - 0xaadb0000aade, - 0xaae00000aaf0, - 0xaaf20000aaf7, - 0xab010000ab07, - 0xab090000ab0f, - 0xab110000ab17, - 0xab200000ab27, - 0xab280000ab2f, - 0xab300000ab5b, - 0xab600000ab66, - 0xabc00000abeb, - 0xabec0000abee, - 0xabf00000abfa, - 0xac000000d7a4, - 0xfa0e0000fa10, - 0xfa110000fa12, - 0xfa130000fa15, - 0xfa1f0000fa20, - 0xfa210000fa22, - 0xfa230000fa25, - 0xfa270000fa2a, - 0xfb1e0000fb1f, - 0xfe200000fe30, - 0xfe730000fe74, - 0x100000001000c, - 0x1000d00010027, - 0x100280001003b, - 0x1003c0001003e, - 0x1003f0001004e, - 0x100500001005e, - 0x10080000100fb, - 0x101fd000101fe, - 0x102800001029d, - 0x102a0000102d1, - 0x102e0000102e1, - 0x1030000010320, - 0x1032d00010341, - 0x103420001034a, - 0x103500001037b, - 0x103800001039e, - 0x103a0000103c4, - 0x103c8000103d0, - 0x104280001049e, - 0x104a0000104aa, - 0x104d8000104fc, - 0x1050000010528, - 0x1053000010564, - 0x1060000010737, - 0x1074000010756, - 0x1076000010768, - 0x1080000010806, - 0x1080800010809, - 0x1080a00010836, - 0x1083700010839, - 0x1083c0001083d, - 0x1083f00010856, - 0x1086000010877, - 0x108800001089f, - 0x108e0000108f3, - 0x108f4000108f6, - 0x1090000010916, - 0x109200001093a, - 0x10980000109b8, - 0x109be000109c0, - 0x10a0000010a04, - 0x10a0500010a07, - 0x10a0c00010a14, - 0x10a1500010a18, - 0x10a1900010a34, - 0x10a3800010a3b, - 0x10a3f00010a40, - 0x10a6000010a7d, - 0x10a8000010a9d, - 0x10ac000010ac8, - 0x10ac900010ae7, - 0x10b0000010b36, - 0x10b4000010b56, - 0x10b6000010b73, - 0x10b8000010b92, - 0x10c0000010c49, - 0x10cc000010cf3, - 0x1100000011047, - 0x1106600011070, - 0x1107f000110bb, - 0x110d0000110e9, - 0x110f0000110fa, - 0x1110000011135, - 0x1113600011140, - 0x1115000011174, - 0x1117600011177, - 0x11180000111c5, - 0x111ca000111cd, - 0x111d0000111db, - 0x111dc000111dd, - 0x1120000011212, - 0x1121300011238, - 0x1123e0001123f, - 0x1128000011287, - 0x1128800011289, - 0x1128a0001128e, - 0x1128f0001129e, - 0x1129f000112a9, - 0x112b0000112eb, - 0x112f0000112fa, - 0x1130000011304, - 0x113050001130d, - 0x1130f00011311, - 0x1131300011329, - 0x1132a00011331, - 0x1133200011334, - 0x113350001133a, - 0x1133c00011345, - 0x1134700011349, - 0x1134b0001134e, - 0x1135000011351, - 0x1135700011358, - 0x1135d00011364, - 0x113660001136d, - 0x1137000011375, - 0x114000001144b, - 0x114500001145a, - 0x11480000114c6, - 0x114c7000114c8, - 0x114d0000114da, - 0x11580000115b6, - 0x115b8000115c1, - 0x115d8000115de, - 0x1160000011641, - 0x1164400011645, - 0x116500001165a, - 0x11680000116b8, - 0x116c0000116ca, - 0x117000001171a, - 0x1171d0001172c, - 0x117300001173a, - 0x118c0000118ea, - 0x118ff00011900, - 0x11a0000011a3f, - 0x11a4700011a48, - 0x11a5000011a84, - 0x11a8600011a9a, - 0x11ac000011af9, - 0x11c0000011c09, - 0x11c0a00011c37, - 0x11c3800011c41, - 0x11c5000011c5a, - 0x11c7200011c90, - 0x11c9200011ca8, - 0x11ca900011cb7, - 0x11d0000011d07, - 0x11d0800011d0a, - 0x11d0b00011d37, - 0x11d3a00011d3b, - 0x11d3c00011d3e, - 0x11d3f00011d48, - 0x11d5000011d5a, - 0x120000001239a, - 0x1248000012544, - 0x130000001342f, - 0x1440000014647, - 0x1680000016a39, - 0x16a4000016a5f, - 0x16a6000016a6a, - 0x16ad000016aee, - 0x16af000016af5, - 0x16b0000016b37, - 0x16b4000016b44, - 0x16b5000016b5a, - 0x16b6300016b78, - 0x16b7d00016b90, - 0x16f0000016f45, - 0x16f5000016f7f, - 0x16f8f00016fa0, - 0x16fe000016fe2, - 0x17000000187ed, - 0x1880000018af3, - 0x1b0000001b11f, - 0x1b1700001b2fc, - 0x1bc000001bc6b, - 0x1bc700001bc7d, - 0x1bc800001bc89, - 0x1bc900001bc9a, - 0x1bc9d0001bc9f, - 0x1da000001da37, - 0x1da3b0001da6d, - 0x1da750001da76, - 0x1da840001da85, - 0x1da9b0001daa0, - 0x1daa10001dab0, - 0x1e0000001e007, - 0x1e0080001e019, - 0x1e01b0001e022, - 0x1e0230001e025, - 0x1e0260001e02b, - 0x1e8000001e8c5, - 0x1e8d00001e8d7, - 0x1e9220001e94b, - 0x1e9500001e95a, - 0x200000002a6d7, - 0x2a7000002b735, - 0x2b7400002b81e, - 0x2b8200002cea2, - 0x2ceb00002ebe1, - ), - 'CONTEXTJ': ( - 0x200c0000200e, - ), - 'CONTEXTO': ( - 0xb7000000b8, - 0x37500000376, - 0x5f3000005f5, - 0x6600000066a, - 0x6f0000006fa, - 0x30fb000030fc, - ), -} diff --git a/lib/idna/intranges.py b/lib/idna/intranges.py deleted file mode 100644 index fa8a735..0000000 --- a/lib/idna/intranges.py +++ /dev/null @@ -1,53 +0,0 @@ -""" -Given a list of integers, made up of (hopefully) a small number of long runs -of consecutive integers, compute a representation of the form -((start1, end1), (start2, end2) ...). Then answer the question "was x present -in the original list?" in time O(log(# runs)). -""" - -import bisect - -def intranges_from_list(list_): - """Represent a list of integers as a sequence of ranges: - ((start_0, end_0), (start_1, end_1), ...), such that the original - integers are exactly those x such that start_i <= x < end_i for some i. - - Ranges are encoded as single integers (start << 32 | end), not as tuples. - """ - - sorted_list = sorted(list_) - ranges = [] - last_write = -1 - for i in range(len(sorted_list)): - if i+1 < len(sorted_list): - if sorted_list[i] == sorted_list[i+1]-1: - continue - current_range = sorted_list[last_write+1:i+1] - ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) - last_write = i - - return tuple(ranges) - -def _encode_range(start, end): - return (start << 32) | end - -def _decode_range(r): - return (r >> 32), (r & ((1 << 32) - 1)) - - -def intranges_contain(int_, ranges): - """Determine if `int_` falls into one of the ranges in `ranges`.""" - tuple_ = _encode_range(int_, 0) - pos = bisect.bisect_left(ranges, tuple_) - # we could be immediately ahead of a tuple (start, end) - # with start < int_ <= end - if pos > 0: - left, right = _decode_range(ranges[pos-1]) - if left <= int_ < right: - return True - # or we could be immediately behind a tuple (int_, end) - if pos < len(ranges): - left, _ = _decode_range(ranges[pos]) - if left == int_: - return True - return False diff --git a/lib/idna/package_data.py b/lib/idna/package_data.py deleted file mode 100644 index 39c192b..0000000 --- a/lib/idna/package_data.py +++ /dev/null @@ -1,2 +0,0 @@ -__version__ = '2.7' - diff --git a/lib/idna/uts46data.py b/lib/idna/uts46data.py deleted file mode 100644 index 79731cb..0000000 --- a/lib/idna/uts46data.py +++ /dev/null @@ -1,8179 +0,0 @@ -# This file is automatically generated by tools/idna-data -# vim: set fileencoding=utf-8 : - -"""IDNA Mapping Table from UTS46.""" - - -__version__ = "10.0.0" -def _seg_0(): - return [ - (0x0, '3'), - (0x1, '3'), - (0x2, '3'), - (0x3, '3'), - (0x4, '3'), - (0x5, '3'), - (0x6, '3'), - (0x7, '3'), - (0x8, '3'), - (0x9, '3'), - (0xA, '3'), - (0xB, '3'), - (0xC, '3'), - (0xD, '3'), - (0xE, '3'), - (0xF, '3'), - (0x10, '3'), - (0x11, '3'), - (0x12, '3'), - (0x13, '3'), - (0x14, '3'), - (0x15, '3'), - (0x16, '3'), - (0x17, '3'), - (0x18, '3'), - (0x19, '3'), - (0x1A, '3'), - (0x1B, '3'), - (0x1C, '3'), - (0x1D, '3'), - (0x1E, '3'), - (0x1F, '3'), - (0x20, '3'), - (0x21, '3'), - (0x22, '3'), - (0x23, '3'), - (0x24, '3'), - (0x25, '3'), - (0x26, '3'), - (0x27, '3'), - (0x28, '3'), - (0x29, '3'), - (0x2A, '3'), - (0x2B, '3'), - (0x2C, '3'), - (0x2D, 'V'), - (0x2E, 'V'), - (0x2F, '3'), - (0x30, 'V'), - (0x31, 'V'), - (0x32, 'V'), - (0x33, 'V'), - (0x34, 'V'), - (0x35, 'V'), - (0x36, 'V'), - (0x37, 'V'), - (0x38, 'V'), - (0x39, 'V'), - (0x3A, '3'), - (0x3B, '3'), - (0x3C, '3'), - (0x3D, '3'), - (0x3E, '3'), - (0x3F, '3'), - (0x40, '3'), - (0x41, 'M', u'a'), - (0x42, 'M', u'b'), - (0x43, 'M', u'c'), - (0x44, 'M', u'd'), - (0x45, 'M', u'e'), - (0x46, 'M', u'f'), - (0x47, 'M', u'g'), - (0x48, 'M', u'h'), - (0x49, 'M', u'i'), - (0x4A, 'M', u'j'), - (0x4B, 'M', u'k'), - (0x4C, 'M', u'l'), - (0x4D, 'M', u'm'), - (0x4E, 'M', u'n'), - (0x4F, 'M', u'o'), - (0x50, 'M', u'p'), - (0x51, 'M', u'q'), - (0x52, 'M', u'r'), - (0x53, 'M', u's'), - (0x54, 'M', u't'), - (0x55, 'M', u'u'), - (0x56, 'M', u'v'), - (0x57, 'M', u'w'), - (0x58, 'M', u'x'), - (0x59, 'M', u'y'), - (0x5A, 'M', u'z'), - (0x5B, '3'), - (0x5C, '3'), - (0x5D, '3'), - (0x5E, '3'), - (0x5F, '3'), - (0x60, '3'), - (0x61, 'V'), - (0x62, 'V'), - (0x63, 'V'), - ] - -def _seg_1(): - return [ - (0x64, 'V'), - (0x65, 'V'), - (0x66, 'V'), - (0x67, 'V'), - (0x68, 'V'), - (0x69, 'V'), - (0x6A, 'V'), - (0x6B, 'V'), - (0x6C, 'V'), - (0x6D, 'V'), - (0x6E, 'V'), - (0x6F, 'V'), - (0x70, 'V'), - (0x71, 'V'), - (0x72, 'V'), - (0x73, 'V'), - (0x74, 'V'), - (0x75, 'V'), - (0x76, 'V'), - (0x77, 'V'), - (0x78, 'V'), - (0x79, 'V'), - (0x7A, 'V'), - (0x7B, '3'), - (0x7C, '3'), - (0x7D, '3'), - (0x7E, '3'), - (0x7F, '3'), - (0x80, 'X'), - (0x81, 'X'), - (0x82, 'X'), - (0x83, 'X'), - (0x84, 'X'), - (0x85, 'X'), - (0x86, 'X'), - (0x87, 'X'), - (0x88, 'X'), - (0x89, 'X'), - (0x8A, 'X'), - (0x8B, 'X'), - (0x8C, 'X'), - (0x8D, 'X'), - (0x8E, 'X'), - (0x8F, 'X'), - (0x90, 'X'), - (0x91, 'X'), - (0x92, 'X'), - (0x93, 'X'), - (0x94, 'X'), - (0x95, 'X'), - (0x96, 'X'), - (0x97, 'X'), - (0x98, 'X'), - (0x99, 'X'), - (0x9A, 'X'), - (0x9B, 'X'), - (0x9C, 'X'), - (0x9D, 'X'), - (0x9E, 'X'), - (0x9F, 'X'), - (0xA0, '3', u' '), - (0xA1, 'V'), - (0xA2, 'V'), - (0xA3, 'V'), - (0xA4, 'V'), - (0xA5, 'V'), - (0xA6, 'V'), - (0xA7, 'V'), - (0xA8, '3', u' ̈'), - (0xA9, 'V'), - (0xAA, 'M', u'a'), - (0xAB, 'V'), - (0xAC, 'V'), - (0xAD, 'I'), - (0xAE, 'V'), - (0xAF, '3', u' ̄'), - (0xB0, 'V'), - (0xB1, 'V'), - (0xB2, 'M', u'2'), - (0xB3, 'M', u'3'), - (0xB4, '3', u' ́'), - (0xB5, 'M', u'μ'), - (0xB6, 'V'), - (0xB7, 'V'), - (0xB8, '3', u' ̧'), - (0xB9, 'M', u'1'), - (0xBA, 'M', u'o'), - (0xBB, 'V'), - (0xBC, 'M', u'1⁄4'), - (0xBD, 'M', u'1⁄2'), - (0xBE, 'M', u'3⁄4'), - (0xBF, 'V'), - (0xC0, 'M', u'à'), - (0xC1, 'M', u'á'), - (0xC2, 'M', u'â'), - (0xC3, 'M', u'ã'), - (0xC4, 'M', u'ä'), - (0xC5, 'M', u'å'), - (0xC6, 'M', u'æ'), - (0xC7, 'M', u'ç'), - ] - -def _seg_2(): - return [ - (0xC8, 'M', u'è'), - (0xC9, 'M', u'é'), - (0xCA, 'M', u'ê'), - (0xCB, 'M', u'ë'), - (0xCC, 'M', u'ì'), - (0xCD, 'M', u'í'), - (0xCE, 'M', u'î'), - (0xCF, 'M', u'ï'), - (0xD0, 'M', u'ð'), - (0xD1, 'M', u'ñ'), - (0xD2, 'M', u'ò'), - (0xD3, 'M', u'ó'), - (0xD4, 'M', u'ô'), - (0xD5, 'M', u'õ'), - (0xD6, 'M', u'ö'), - (0xD7, 'V'), - (0xD8, 'M', u'ø'), - (0xD9, 'M', u'ù'), - (0xDA, 'M', u'ú'), - (0xDB, 'M', u'û'), - (0xDC, 'M', u'ü'), - (0xDD, 'M', u'ý'), - (0xDE, 'M', u'þ'), - (0xDF, 'D', u'ss'), - (0xE0, 'V'), - (0xE1, 'V'), - (0xE2, 'V'), - (0xE3, 'V'), - (0xE4, 'V'), - (0xE5, 'V'), - (0xE6, 'V'), - (0xE7, 'V'), - (0xE8, 'V'), - (0xE9, 'V'), - (0xEA, 'V'), - (0xEB, 'V'), - (0xEC, 'V'), - (0xED, 'V'), - (0xEE, 'V'), - (0xEF, 'V'), - (0xF0, 'V'), - (0xF1, 'V'), - (0xF2, 'V'), - (0xF3, 'V'), - (0xF4, 'V'), - (0xF5, 'V'), - (0xF6, 'V'), - (0xF7, 'V'), - (0xF8, 'V'), - (0xF9, 'V'), - (0xFA, 'V'), - (0xFB, 'V'), - (0xFC, 'V'), - (0xFD, 'V'), - (0xFE, 'V'), - (0xFF, 'V'), - (0x100, 'M', u'ā'), - (0x101, 'V'), - (0x102, 'M', u'ă'), - (0x103, 'V'), - (0x104, 'M', u'ą'), - (0x105, 'V'), - (0x106, 'M', u'ć'), - (0x107, 'V'), - (0x108, 'M', u'ĉ'), - (0x109, 'V'), - (0x10A, 'M', u'ċ'), - (0x10B, 'V'), - (0x10C, 'M', u'č'), - (0x10D, 'V'), - (0x10E, 'M', u'ď'), - (0x10F, 'V'), - (0x110, 'M', u'đ'), - (0x111, 'V'), - (0x112, 'M', u'ē'), - (0x113, 'V'), - (0x114, 'M', u'ĕ'), - (0x115, 'V'), - (0x116, 'M', u'ė'), - (0x117, 'V'), - (0x118, 'M', u'ę'), - (0x119, 'V'), - (0x11A, 'M', u'ě'), - (0x11B, 'V'), - (0x11C, 'M', u'ĝ'), - (0x11D, 'V'), - (0x11E, 'M', u'ğ'), - (0x11F, 'V'), - (0x120, 'M', u'ġ'), - (0x121, 'V'), - (0x122, 'M', u'ģ'), - (0x123, 'V'), - (0x124, 'M', u'ĥ'), - (0x125, 'V'), - (0x126, 'M', u'ħ'), - (0x127, 'V'), - (0x128, 'M', u'ĩ'), - (0x129, 'V'), - (0x12A, 'M', u'ī'), - (0x12B, 'V'), - ] - -def _seg_3(): - return [ - (0x12C, 'M', u'ĭ'), - (0x12D, 'V'), - (0x12E, 'M', u'į'), - (0x12F, 'V'), - (0x130, 'M', u'i̇'), - (0x131, 'V'), - (0x132, 'M', u'ij'), - (0x134, 'M', u'ĵ'), - (0x135, 'V'), - (0x136, 'M', u'ķ'), - (0x137, 'V'), - (0x139, 'M', u'ĺ'), - (0x13A, 'V'), - (0x13B, 'M', u'ļ'), - (0x13C, 'V'), - (0x13D, 'M', u'ľ'), - (0x13E, 'V'), - (0x13F, 'M', u'l·'), - (0x141, 'M', u'ł'), - (0x142, 'V'), - (0x143, 'M', u'ń'), - (0x144, 'V'), - (0x145, 'M', u'ņ'), - (0x146, 'V'), - (0x147, 'M', u'ň'), - (0x148, 'V'), - (0x149, 'M', u'ʼn'), - (0x14A, 'M', u'ŋ'), - (0x14B, 'V'), - (0x14C, 'M', u'ō'), - (0x14D, 'V'), - (0x14E, 'M', u'ŏ'), - (0x14F, 'V'), - (0x150, 'M', u'ő'), - (0x151, 'V'), - (0x152, 'M', u'œ'), - (0x153, 'V'), - (0x154, 'M', u'ŕ'), - (0x155, 'V'), - (0x156, 'M', u'ŗ'), - (0x157, 'V'), - (0x158, 'M', u'ř'), - (0x159, 'V'), - (0x15A, 'M', u'ś'), - (0x15B, 'V'), - (0x15C, 'M', u'ŝ'), - (0x15D, 'V'), - (0x15E, 'M', u'ş'), - (0x15F, 'V'), - (0x160, 'M', u'š'), - (0x161, 'V'), - (0x162, 'M', u'ţ'), - (0x163, 'V'), - (0x164, 'M', u'ť'), - (0x165, 'V'), - (0x166, 'M', u'ŧ'), - (0x167, 'V'), - (0x168, 'M', u'ũ'), - (0x169, 'V'), - (0x16A, 'M', u'ū'), - (0x16B, 'V'), - (0x16C, 'M', u'ŭ'), - (0x16D, 'V'), - (0x16E, 'M', u'ů'), - (0x16F, 'V'), - (0x170, 'M', u'ű'), - (0x171, 'V'), - (0x172, 'M', u'ų'), - (0x173, 'V'), - (0x174, 'M', u'ŵ'), - (0x175, 'V'), - (0x176, 'M', u'ŷ'), - (0x177, 'V'), - (0x178, 'M', u'ÿ'), - (0x179, 'M', u'ź'), - (0x17A, 'V'), - (0x17B, 'M', u'ż'), - (0x17C, 'V'), - (0x17D, 'M', u'ž'), - (0x17E, 'V'), - (0x17F, 'M', u's'), - (0x180, 'V'), - (0x181, 'M', u'ɓ'), - (0x182, 'M', u'ƃ'), - (0x183, 'V'), - (0x184, 'M', u'ƅ'), - (0x185, 'V'), - (0x186, 'M', u'ɔ'), - (0x187, 'M', u'ƈ'), - (0x188, 'V'), - (0x189, 'M', u'ɖ'), - (0x18A, 'M', u'ɗ'), - (0x18B, 'M', u'ƌ'), - (0x18C, 'V'), - (0x18E, 'M', u'ǝ'), - (0x18F, 'M', u'ə'), - (0x190, 'M', u'ɛ'), - (0x191, 'M', u'ƒ'), - (0x192, 'V'), - (0x193, 'M', u'ɠ'), - ] - -def _seg_4(): - return [ - (0x194, 'M', u'ɣ'), - (0x195, 'V'), - (0x196, 'M', u'ɩ'), - (0x197, 'M', u'ɨ'), - (0x198, 'M', u'ƙ'), - (0x199, 'V'), - (0x19C, 'M', u'ɯ'), - (0x19D, 'M', u'ɲ'), - (0x19E, 'V'), - (0x19F, 'M', u'ɵ'), - (0x1A0, 'M', u'ơ'), - (0x1A1, 'V'), - (0x1A2, 'M', u'ƣ'), - (0x1A3, 'V'), - (0x1A4, 'M', u'ƥ'), - (0x1A5, 'V'), - (0x1A6, 'M', u'ʀ'), - (0x1A7, 'M', u'ƨ'), - (0x1A8, 'V'), - (0x1A9, 'M', u'ʃ'), - (0x1AA, 'V'), - (0x1AC, 'M', u'ƭ'), - (0x1AD, 'V'), - (0x1AE, 'M', u'ʈ'), - (0x1AF, 'M', u'ư'), - (0x1B0, 'V'), - (0x1B1, 'M', u'ʊ'), - (0x1B2, 'M', u'ʋ'), - (0x1B3, 'M', u'ƴ'), - (0x1B4, 'V'), - (0x1B5, 'M', u'ƶ'), - (0x1B6, 'V'), - (0x1B7, 'M', u'ʒ'), - (0x1B8, 'M', u'ƹ'), - (0x1B9, 'V'), - (0x1BC, 'M', u'ƽ'), - (0x1BD, 'V'), - (0x1C4, 'M', u'dž'), - (0x1C7, 'M', u'lj'), - (0x1CA, 'M', u'nj'), - (0x1CD, 'M', u'ǎ'), - (0x1CE, 'V'), - (0x1CF, 'M', u'ǐ'), - (0x1D0, 'V'), - (0x1D1, 'M', u'ǒ'), - (0x1D2, 'V'), - (0x1D3, 'M', u'ǔ'), - (0x1D4, 'V'), - (0x1D5, 'M', u'ǖ'), - (0x1D6, 'V'), - (0x1D7, 'M', u'ǘ'), - (0x1D8, 'V'), - (0x1D9, 'M', u'ǚ'), - (0x1DA, 'V'), - (0x1DB, 'M', u'ǜ'), - (0x1DC, 'V'), - (0x1DE, 'M', u'ǟ'), - (0x1DF, 'V'), - (0x1E0, 'M', u'ǡ'), - (0x1E1, 'V'), - (0x1E2, 'M', u'ǣ'), - (0x1E3, 'V'), - (0x1E4, 'M', u'ǥ'), - (0x1E5, 'V'), - (0x1E6, 'M', u'ǧ'), - (0x1E7, 'V'), - (0x1E8, 'M', u'ǩ'), - (0x1E9, 'V'), - (0x1EA, 'M', u'ǫ'), - (0x1EB, 'V'), - (0x1EC, 'M', u'ǭ'), - (0x1ED, 'V'), - (0x1EE, 'M', u'ǯ'), - (0x1EF, 'V'), - (0x1F1, 'M', u'dz'), - (0x1F4, 'M', u'ǵ'), - (0x1F5, 'V'), - (0x1F6, 'M', u'ƕ'), - (0x1F7, 'M', u'ƿ'), - (0x1F8, 'M', u'ǹ'), - (0x1F9, 'V'), - (0x1FA, 'M', u'ǻ'), - (0x1FB, 'V'), - (0x1FC, 'M', u'ǽ'), - (0x1FD, 'V'), - (0x1FE, 'M', u'ǿ'), - (0x1FF, 'V'), - (0x200, 'M', u'ȁ'), - (0x201, 'V'), - (0x202, 'M', u'ȃ'), - (0x203, 'V'), - (0x204, 'M', u'ȅ'), - (0x205, 'V'), - (0x206, 'M', u'ȇ'), - (0x207, 'V'), - (0x208, 'M', u'ȉ'), - (0x209, 'V'), - (0x20A, 'M', u'ȋ'), - (0x20B, 'V'), - (0x20C, 'M', u'ȍ'), - ] - -def _seg_5(): - return [ - (0x20D, 'V'), - (0x20E, 'M', u'ȏ'), - (0x20F, 'V'), - (0x210, 'M', u'ȑ'), - (0x211, 'V'), - (0x212, 'M', u'ȓ'), - (0x213, 'V'), - (0x214, 'M', u'ȕ'), - (0x215, 'V'), - (0x216, 'M', u'ȗ'), - (0x217, 'V'), - (0x218, 'M', u'ș'), - (0x219, 'V'), - (0x21A, 'M', u'ț'), - (0x21B, 'V'), - (0x21C, 'M', u'ȝ'), - (0x21D, 'V'), - (0x21E, 'M', u'ȟ'), - (0x21F, 'V'), - (0x220, 'M', u'ƞ'), - (0x221, 'V'), - (0x222, 'M', u'ȣ'), - (0x223, 'V'), - (0x224, 'M', u'ȥ'), - (0x225, 'V'), - (0x226, 'M', u'ȧ'), - (0x227, 'V'), - (0x228, 'M', u'ȩ'), - (0x229, 'V'), - (0x22A, 'M', u'ȫ'), - (0x22B, 'V'), - (0x22C, 'M', u'ȭ'), - (0x22D, 'V'), - (0x22E, 'M', u'ȯ'), - (0x22F, 'V'), - (0x230, 'M', u'ȱ'), - (0x231, 'V'), - (0x232, 'M', u'ȳ'), - (0x233, 'V'), - (0x23A, 'M', u'ⱥ'), - (0x23B, 'M', u'ȼ'), - (0x23C, 'V'), - (0x23D, 'M', u'ƚ'), - (0x23E, 'M', u'ⱦ'), - (0x23F, 'V'), - (0x241, 'M', u'ɂ'), - (0x242, 'V'), - (0x243, 'M', u'ƀ'), - (0x244, 'M', u'ʉ'), - (0x245, 'M', u'ʌ'), - (0x246, 'M', u'ɇ'), - (0x247, 'V'), - (0x248, 'M', u'ɉ'), - (0x249, 'V'), - (0x24A, 'M', u'ɋ'), - (0x24B, 'V'), - (0x24C, 'M', u'ɍ'), - (0x24D, 'V'), - (0x24E, 'M', u'ɏ'), - (0x24F, 'V'), - (0x2B0, 'M', u'h'), - (0x2B1, 'M', u'ɦ'), - (0x2B2, 'M', u'j'), - (0x2B3, 'M', u'r'), - (0x2B4, 'M', u'ɹ'), - (0x2B5, 'M', u'ɻ'), - (0x2B6, 'M', u'ʁ'), - (0x2B7, 'M', u'w'), - (0x2B8, 'M', u'y'), - (0x2B9, 'V'), - (0x2D8, '3', u' ̆'), - (0x2D9, '3', u' ̇'), - (0x2DA, '3', u' ̊'), - (0x2DB, '3', u' ̨'), - (0x2DC, '3', u' ̃'), - (0x2DD, '3', u' ̋'), - (0x2DE, 'V'), - (0x2E0, 'M', u'ɣ'), - (0x2E1, 'M', u'l'), - (0x2E2, 'M', u's'), - (0x2E3, 'M', u'x'), - (0x2E4, 'M', u'ʕ'), - (0x2E5, 'V'), - (0x340, 'M', u'̀'), - (0x341, 'M', u'́'), - (0x342, 'V'), - (0x343, 'M', u'̓'), - (0x344, 'M', u'̈́'), - (0x345, 'M', u'ι'), - (0x346, 'V'), - (0x34F, 'I'), - (0x350, 'V'), - (0x370, 'M', u'ͱ'), - (0x371, 'V'), - (0x372, 'M', u'ͳ'), - (0x373, 'V'), - (0x374, 'M', u'ʹ'), - (0x375, 'V'), - (0x376, 'M', u'ͷ'), - (0x377, 'V'), - ] - -def _seg_6(): - return [ - (0x378, 'X'), - (0x37A, '3', u' ι'), - (0x37B, 'V'), - (0x37E, '3', u';'), - (0x37F, 'M', u'ϳ'), - (0x380, 'X'), - (0x384, '3', u' ́'), - (0x385, '3', u' ̈́'), - (0x386, 'M', u'ά'), - (0x387, 'M', u'·'), - (0x388, 'M', u'έ'), - (0x389, 'M', u'ή'), - (0x38A, 'M', u'ί'), - (0x38B, 'X'), - (0x38C, 'M', u'ό'), - (0x38D, 'X'), - (0x38E, 'M', u'ύ'), - (0x38F, 'M', u'ώ'), - (0x390, 'V'), - (0x391, 'M', u'α'), - (0x392, 'M', u'β'), - (0x393, 'M', u'γ'), - (0x394, 'M', u'δ'), - (0x395, 'M', u'ε'), - (0x396, 'M', u'ζ'), - (0x397, 'M', u'η'), - (0x398, 'M', u'θ'), - (0x399, 'M', u'ι'), - (0x39A, 'M', u'κ'), - (0x39B, 'M', u'λ'), - (0x39C, 'M', u'μ'), - (0x39D, 'M', u'ν'), - (0x39E, 'M', u'ξ'), - (0x39F, 'M', u'ο'), - (0x3A0, 'M', u'π'), - (0x3A1, 'M', u'ρ'), - (0x3A2, 'X'), - (0x3A3, 'M', u'σ'), - (0x3A4, 'M', u'τ'), - (0x3A5, 'M', u'υ'), - (0x3A6, 'M', u'φ'), - (0x3A7, 'M', u'χ'), - (0x3A8, 'M', u'ψ'), - (0x3A9, 'M', u'ω'), - (0x3AA, 'M', u'ϊ'), - (0x3AB, 'M', u'ϋ'), - (0x3AC, 'V'), - (0x3C2, 'D', u'σ'), - (0x3C3, 'V'), - (0x3CF, 'M', u'ϗ'), - (0x3D0, 'M', u'β'), - (0x3D1, 'M', u'θ'), - (0x3D2, 'M', u'υ'), - (0x3D3, 'M', u'ύ'), - (0x3D4, 'M', u'ϋ'), - (0x3D5, 'M', u'φ'), - (0x3D6, 'M', u'π'), - (0x3D7, 'V'), - (0x3D8, 'M', u'ϙ'), - (0x3D9, 'V'), - (0x3DA, 'M', u'ϛ'), - (0x3DB, 'V'), - (0x3DC, 'M', u'ϝ'), - (0x3DD, 'V'), - (0x3DE, 'M', u'ϟ'), - (0x3DF, 'V'), - (0x3E0, 'M', u'ϡ'), - (0x3E1, 'V'), - (0x3E2, 'M', u'ϣ'), - (0x3E3, 'V'), - (0x3E4, 'M', u'ϥ'), - (0x3E5, 'V'), - (0x3E6, 'M', u'ϧ'), - (0x3E7, 'V'), - (0x3E8, 'M', u'ϩ'), - (0x3E9, 'V'), - (0x3EA, 'M', u'ϫ'), - (0x3EB, 'V'), - (0x3EC, 'M', u'ϭ'), - (0x3ED, 'V'), - (0x3EE, 'M', u'ϯ'), - (0x3EF, 'V'), - (0x3F0, 'M', u'κ'), - (0x3F1, 'M', u'ρ'), - (0x3F2, 'M', u'σ'), - (0x3F3, 'V'), - (0x3F4, 'M', u'θ'), - (0x3F5, 'M', u'ε'), - (0x3F6, 'V'), - (0x3F7, 'M', u'ϸ'), - (0x3F8, 'V'), - (0x3F9, 'M', u'σ'), - (0x3FA, 'M', u'ϻ'), - (0x3FB, 'V'), - (0x3FD, 'M', u'ͻ'), - (0x3FE, 'M', u'ͼ'), - (0x3FF, 'M', u'ͽ'), - (0x400, 'M', u'ѐ'), - (0x401, 'M', u'ё'), - (0x402, 'M', u'ђ'), - ] - -def _seg_7(): - return [ - (0x403, 'M', u'ѓ'), - (0x404, 'M', u'є'), - (0x405, 'M', u'ѕ'), - (0x406, 'M', u'і'), - (0x407, 'M', u'ї'), - (0x408, 'M', u'ј'), - (0x409, 'M', u'љ'), - (0x40A, 'M', u'њ'), - (0x40B, 'M', u'ћ'), - (0x40C, 'M', u'ќ'), - (0x40D, 'M', u'ѝ'), - (0x40E, 'M', u'ў'), - (0x40F, 'M', u'џ'), - (0x410, 'M', u'а'), - (0x411, 'M', u'б'), - (0x412, 'M', u'в'), - (0x413, 'M', u'г'), - (0x414, 'M', u'д'), - (0x415, 'M', u'е'), - (0x416, 'M', u'ж'), - (0x417, 'M', u'з'), - (0x418, 'M', u'и'), - (0x419, 'M', u'й'), - (0x41A, 'M', u'к'), - (0x41B, 'M', u'л'), - (0x41C, 'M', u'м'), - (0x41D, 'M', u'н'), - (0x41E, 'M', u'о'), - (0x41F, 'M', u'п'), - (0x420, 'M', u'р'), - (0x421, 'M', u'с'), - (0x422, 'M', u'т'), - (0x423, 'M', u'у'), - (0x424, 'M', u'ф'), - (0x425, 'M', u'х'), - (0x426, 'M', u'ц'), - (0x427, 'M', u'ч'), - (0x428, 'M', u'ш'), - (0x429, 'M', u'щ'), - (0x42A, 'M', u'ъ'), - (0x42B, 'M', u'ы'), - (0x42C, 'M', u'ь'), - (0x42D, 'M', u'э'), - (0x42E, 'M', u'ю'), - (0x42F, 'M', u'я'), - (0x430, 'V'), - (0x460, 'M', u'ѡ'), - (0x461, 'V'), - (0x462, 'M', u'ѣ'), - (0x463, 'V'), - (0x464, 'M', u'ѥ'), - (0x465, 'V'), - (0x466, 'M', u'ѧ'), - (0x467, 'V'), - (0x468, 'M', u'ѩ'), - (0x469, 'V'), - (0x46A, 'M', u'ѫ'), - (0x46B, 'V'), - (0x46C, 'M', u'ѭ'), - (0x46D, 'V'), - (0x46E, 'M', u'ѯ'), - (0x46F, 'V'), - (0x470, 'M', u'ѱ'), - (0x471, 'V'), - (0x472, 'M', u'ѳ'), - (0x473, 'V'), - (0x474, 'M', u'ѵ'), - (0x475, 'V'), - (0x476, 'M', u'ѷ'), - (0x477, 'V'), - (0x478, 'M', u'ѹ'), - (0x479, 'V'), - (0x47A, 'M', u'ѻ'), - (0x47B, 'V'), - (0x47C, 'M', u'ѽ'), - (0x47D, 'V'), - (0x47E, 'M', u'ѿ'), - (0x47F, 'V'), - (0x480, 'M', u'ҁ'), - (0x481, 'V'), - (0x48A, 'M', u'ҋ'), - (0x48B, 'V'), - (0x48C, 'M', u'ҍ'), - (0x48D, 'V'), - (0x48E, 'M', u'ҏ'), - (0x48F, 'V'), - (0x490, 'M', u'ґ'), - (0x491, 'V'), - (0x492, 'M', u'ғ'), - (0x493, 'V'), - (0x494, 'M', u'ҕ'), - (0x495, 'V'), - (0x496, 'M', u'җ'), - (0x497, 'V'), - (0x498, 'M', u'ҙ'), - (0x499, 'V'), - (0x49A, 'M', u'қ'), - (0x49B, 'V'), - (0x49C, 'M', u'ҝ'), - (0x49D, 'V'), - ] - -def _seg_8(): - return [ - (0x49E, 'M', u'ҟ'), - (0x49F, 'V'), - (0x4A0, 'M', u'ҡ'), - (0x4A1, 'V'), - (0x4A2, 'M', u'ң'), - (0x4A3, 'V'), - (0x4A4, 'M', u'ҥ'), - (0x4A5, 'V'), - (0x4A6, 'M', u'ҧ'), - (0x4A7, 'V'), - (0x4A8, 'M', u'ҩ'), - (0x4A9, 'V'), - (0x4AA, 'M', u'ҫ'), - (0x4AB, 'V'), - (0x4AC, 'M', u'ҭ'), - (0x4AD, 'V'), - (0x4AE, 'M', u'ү'), - (0x4AF, 'V'), - (0x4B0, 'M', u'ұ'), - (0x4B1, 'V'), - (0x4B2, 'M', u'ҳ'), - (0x4B3, 'V'), - (0x4B4, 'M', u'ҵ'), - (0x4B5, 'V'), - (0x4B6, 'M', u'ҷ'), - (0x4B7, 'V'), - (0x4B8, 'M', u'ҹ'), - (0x4B9, 'V'), - (0x4BA, 'M', u'һ'), - (0x4BB, 'V'), - (0x4BC, 'M', u'ҽ'), - (0x4BD, 'V'), - (0x4BE, 'M', u'ҿ'), - (0x4BF, 'V'), - (0x4C0, 'X'), - (0x4C1, 'M', u'ӂ'), - (0x4C2, 'V'), - (0x4C3, 'M', u'ӄ'), - (0x4C4, 'V'), - (0x4C5, 'M', u'ӆ'), - (0x4C6, 'V'), - (0x4C7, 'M', u'ӈ'), - (0x4C8, 'V'), - (0x4C9, 'M', u'ӊ'), - (0x4CA, 'V'), - (0x4CB, 'M', u'ӌ'), - (0x4CC, 'V'), - (0x4CD, 'M', u'ӎ'), - (0x4CE, 'V'), - (0x4D0, 'M', u'ӑ'), - (0x4D1, 'V'), - (0x4D2, 'M', u'ӓ'), - (0x4D3, 'V'), - (0x4D4, 'M', u'ӕ'), - (0x4D5, 'V'), - (0x4D6, 'M', u'ӗ'), - (0x4D7, 'V'), - (0x4D8, 'M', u'ә'), - (0x4D9, 'V'), - (0x4DA, 'M', u'ӛ'), - (0x4DB, 'V'), - (0x4DC, 'M', u'ӝ'), - (0x4DD, 'V'), - (0x4DE, 'M', u'ӟ'), - (0x4DF, 'V'), - (0x4E0, 'M', u'ӡ'), - (0x4E1, 'V'), - (0x4E2, 'M', u'ӣ'), - (0x4E3, 'V'), - (0x4E4, 'M', u'ӥ'), - (0x4E5, 'V'), - (0x4E6, 'M', u'ӧ'), - (0x4E7, 'V'), - (0x4E8, 'M', u'ө'), - (0x4E9, 'V'), - (0x4EA, 'M', u'ӫ'), - (0x4EB, 'V'), - (0x4EC, 'M', u'ӭ'), - (0x4ED, 'V'), - (0x4EE, 'M', u'ӯ'), - (0x4EF, 'V'), - (0x4F0, 'M', u'ӱ'), - (0x4F1, 'V'), - (0x4F2, 'M', u'ӳ'), - (0x4F3, 'V'), - (0x4F4, 'M', u'ӵ'), - (0x4F5, 'V'), - (0x4F6, 'M', u'ӷ'), - (0x4F7, 'V'), - (0x4F8, 'M', u'ӹ'), - (0x4F9, 'V'), - (0x4FA, 'M', u'ӻ'), - (0x4FB, 'V'), - (0x4FC, 'M', u'ӽ'), - (0x4FD, 'V'), - (0x4FE, 'M', u'ӿ'), - (0x4FF, 'V'), - (0x500, 'M', u'ԁ'), - (0x501, 'V'), - (0x502, 'M', u'ԃ'), - ] - -def _seg_9(): - return [ - (0x503, 'V'), - (0x504, 'M', u'ԅ'), - (0x505, 'V'), - (0x506, 'M', u'ԇ'), - (0x507, 'V'), - (0x508, 'M', u'ԉ'), - (0x509, 'V'), - (0x50A, 'M', u'ԋ'), - (0x50B, 'V'), - (0x50C, 'M', u'ԍ'), - (0x50D, 'V'), - (0x50E, 'M', u'ԏ'), - (0x50F, 'V'), - (0x510, 'M', u'ԑ'), - (0x511, 'V'), - (0x512, 'M', u'ԓ'), - (0x513, 'V'), - (0x514, 'M', u'ԕ'), - (0x515, 'V'), - (0x516, 'M', u'ԗ'), - (0x517, 'V'), - (0x518, 'M', u'ԙ'), - (0x519, 'V'), - (0x51A, 'M', u'ԛ'), - (0x51B, 'V'), - (0x51C, 'M', u'ԝ'), - (0x51D, 'V'), - (0x51E, 'M', u'ԟ'), - (0x51F, 'V'), - (0x520, 'M', u'ԡ'), - (0x521, 'V'), - (0x522, 'M', u'ԣ'), - (0x523, 'V'), - (0x524, 'M', u'ԥ'), - (0x525, 'V'), - (0x526, 'M', u'ԧ'), - (0x527, 'V'), - (0x528, 'M', u'ԩ'), - (0x529, 'V'), - (0x52A, 'M', u'ԫ'), - (0x52B, 'V'), - (0x52C, 'M', u'ԭ'), - (0x52D, 'V'), - (0x52E, 'M', u'ԯ'), - (0x52F, 'V'), - (0x530, 'X'), - (0x531, 'M', u'ա'), - (0x532, 'M', u'բ'), - (0x533, 'M', u'գ'), - (0x534, 'M', u'դ'), - (0x535, 'M', u'ե'), - (0x536, 'M', u'զ'), - (0x537, 'M', u'է'), - (0x538, 'M', u'ը'), - (0x539, 'M', u'թ'), - (0x53A, 'M', u'ժ'), - (0x53B, 'M', u'ի'), - (0x53C, 'M', u'լ'), - (0x53D, 'M', u'խ'), - (0x53E, 'M', u'ծ'), - (0x53F, 'M', u'կ'), - (0x540, 'M', u'հ'), - (0x541, 'M', u'ձ'), - (0x542, 'M', u'ղ'), - (0x543, 'M', u'ճ'), - (0x544, 'M', u'մ'), - (0x545, 'M', u'յ'), - (0x546, 'M', u'ն'), - (0x547, 'M', u'շ'), - (0x548, 'M', u'ո'), - (0x549, 'M', u'չ'), - (0x54A, 'M', u'պ'), - (0x54B, 'M', u'ջ'), - (0x54C, 'M', u'ռ'), - (0x54D, 'M', u'ս'), - (0x54E, 'M', u'վ'), - (0x54F, 'M', u'տ'), - (0x550, 'M', u'ր'), - (0x551, 'M', u'ց'), - (0x552, 'M', u'ւ'), - (0x553, 'M', u'փ'), - (0x554, 'M', u'ք'), - (0x555, 'M', u'օ'), - (0x556, 'M', u'ֆ'), - (0x557, 'X'), - (0x559, 'V'), - (0x560, 'X'), - (0x561, 'V'), - (0x587, 'M', u'եւ'), - (0x588, 'X'), - (0x589, 'V'), - (0x58B, 'X'), - (0x58D, 'V'), - (0x590, 'X'), - (0x591, 'V'), - (0x5C8, 'X'), - (0x5D0, 'V'), - (0x5EB, 'X'), - (0x5F0, 'V'), - (0x5F5, 'X'), - ] - -def _seg_10(): - return [ - (0x606, 'V'), - (0x61C, 'X'), - (0x61E, 'V'), - (0x675, 'M', u'اٴ'), - (0x676, 'M', u'وٴ'), - (0x677, 'M', u'ۇٴ'), - (0x678, 'M', u'يٴ'), - (0x679, 'V'), - (0x6DD, 'X'), - (0x6DE, 'V'), - (0x70E, 'X'), - (0x710, 'V'), - (0x74B, 'X'), - (0x74D, 'V'), - (0x7B2, 'X'), - (0x7C0, 'V'), - (0x7FB, 'X'), - (0x800, 'V'), - (0x82E, 'X'), - (0x830, 'V'), - (0x83F, 'X'), - (0x840, 'V'), - (0x85C, 'X'), - (0x85E, 'V'), - (0x85F, 'X'), - (0x860, 'V'), - (0x86B, 'X'), - (0x8A0, 'V'), - (0x8B5, 'X'), - (0x8B6, 'V'), - (0x8BE, 'X'), - (0x8D4, 'V'), - (0x8E2, 'X'), - (0x8E3, 'V'), - (0x958, 'M', u'क़'), - (0x959, 'M', u'ख़'), - (0x95A, 'M', u'ग़'), - (0x95B, 'M', u'ज़'), - (0x95C, 'M', u'ड़'), - (0x95D, 'M', u'ढ़'), - (0x95E, 'M', u'फ़'), - (0x95F, 'M', u'य़'), - (0x960, 'V'), - (0x984, 'X'), - (0x985, 'V'), - (0x98D, 'X'), - (0x98F, 'V'), - (0x991, 'X'), - (0x993, 'V'), - (0x9A9, 'X'), - (0x9AA, 'V'), - (0x9B1, 'X'), - (0x9B2, 'V'), - (0x9B3, 'X'), - (0x9B6, 'V'), - (0x9BA, 'X'), - (0x9BC, 'V'), - (0x9C5, 'X'), - (0x9C7, 'V'), - (0x9C9, 'X'), - (0x9CB, 'V'), - (0x9CF, 'X'), - (0x9D7, 'V'), - (0x9D8, 'X'), - (0x9DC, 'M', u'ড়'), - (0x9DD, 'M', u'ঢ়'), - (0x9DE, 'X'), - (0x9DF, 'M', u'য়'), - (0x9E0, 'V'), - (0x9E4, 'X'), - (0x9E6, 'V'), - (0x9FE, 'X'), - (0xA01, 'V'), - (0xA04, 'X'), - (0xA05, 'V'), - (0xA0B, 'X'), - (0xA0F, 'V'), - (0xA11, 'X'), - (0xA13, 'V'), - (0xA29, 'X'), - (0xA2A, 'V'), - (0xA31, 'X'), - (0xA32, 'V'), - (0xA33, 'M', u'ਲ਼'), - (0xA34, 'X'), - (0xA35, 'V'), - (0xA36, 'M', u'ਸ਼'), - (0xA37, 'X'), - (0xA38, 'V'), - (0xA3A, 'X'), - (0xA3C, 'V'), - (0xA3D, 'X'), - (0xA3E, 'V'), - (0xA43, 'X'), - (0xA47, 'V'), - (0xA49, 'X'), - (0xA4B, 'V'), - (0xA4E, 'X'), - (0xA51, 'V'), - (0xA52, 'X'), - ] - -def _seg_11(): - return [ - (0xA59, 'M', u'ਖ਼'), - (0xA5A, 'M', u'ਗ਼'), - (0xA5B, 'M', u'ਜ਼'), - (0xA5C, 'V'), - (0xA5D, 'X'), - (0xA5E, 'M', u'ਫ਼'), - (0xA5F, 'X'), - (0xA66, 'V'), - (0xA76, 'X'), - (0xA81, 'V'), - (0xA84, 'X'), - (0xA85, 'V'), - (0xA8E, 'X'), - (0xA8F, 'V'), - (0xA92, 'X'), - (0xA93, 'V'), - (0xAA9, 'X'), - (0xAAA, 'V'), - (0xAB1, 'X'), - (0xAB2, 'V'), - (0xAB4, 'X'), - (0xAB5, 'V'), - (0xABA, 'X'), - (0xABC, 'V'), - (0xAC6, 'X'), - (0xAC7, 'V'), - (0xACA, 'X'), - (0xACB, 'V'), - (0xACE, 'X'), - (0xAD0, 'V'), - (0xAD1, 'X'), - (0xAE0, 'V'), - (0xAE4, 'X'), - (0xAE6, 'V'), - (0xAF2, 'X'), - (0xAF9, 'V'), - (0xB00, 'X'), - (0xB01, 'V'), - (0xB04, 'X'), - (0xB05, 'V'), - (0xB0D, 'X'), - (0xB0F, 'V'), - (0xB11, 'X'), - (0xB13, 'V'), - (0xB29, 'X'), - (0xB2A, 'V'), - (0xB31, 'X'), - (0xB32, 'V'), - (0xB34, 'X'), - (0xB35, 'V'), - (0xB3A, 'X'), - (0xB3C, 'V'), - (0xB45, 'X'), - (0xB47, 'V'), - (0xB49, 'X'), - (0xB4B, 'V'), - (0xB4E, 'X'), - (0xB56, 'V'), - (0xB58, 'X'), - (0xB5C, 'M', u'ଡ଼'), - (0xB5D, 'M', u'ଢ଼'), - (0xB5E, 'X'), - (0xB5F, 'V'), - (0xB64, 'X'), - (0xB66, 'V'), - (0xB78, 'X'), - (0xB82, 'V'), - (0xB84, 'X'), - (0xB85, 'V'), - (0xB8B, 'X'), - (0xB8E, 'V'), - (0xB91, 'X'), - (0xB92, 'V'), - (0xB96, 'X'), - (0xB99, 'V'), - (0xB9B, 'X'), - (0xB9C, 'V'), - (0xB9D, 'X'), - (0xB9E, 'V'), - (0xBA0, 'X'), - (0xBA3, 'V'), - (0xBA5, 'X'), - (0xBA8, 'V'), - (0xBAB, 'X'), - (0xBAE, 'V'), - (0xBBA, 'X'), - (0xBBE, 'V'), - (0xBC3, 'X'), - (0xBC6, 'V'), - (0xBC9, 'X'), - (0xBCA, 'V'), - (0xBCE, 'X'), - (0xBD0, 'V'), - (0xBD1, 'X'), - (0xBD7, 'V'), - (0xBD8, 'X'), - (0xBE6, 'V'), - (0xBFB, 'X'), - (0xC00, 'V'), - (0xC04, 'X'), - ] - -def _seg_12(): - return [ - (0xC05, 'V'), - (0xC0D, 'X'), - (0xC0E, 'V'), - (0xC11, 'X'), - (0xC12, 'V'), - (0xC29, 'X'), - (0xC2A, 'V'), - (0xC3A, 'X'), - (0xC3D, 'V'), - (0xC45, 'X'), - (0xC46, 'V'), - (0xC49, 'X'), - (0xC4A, 'V'), - (0xC4E, 'X'), - (0xC55, 'V'), - (0xC57, 'X'), - (0xC58, 'V'), - (0xC5B, 'X'), - (0xC60, 'V'), - (0xC64, 'X'), - (0xC66, 'V'), - (0xC70, 'X'), - (0xC78, 'V'), - (0xC84, 'X'), - (0xC85, 'V'), - (0xC8D, 'X'), - (0xC8E, 'V'), - (0xC91, 'X'), - (0xC92, 'V'), - (0xCA9, 'X'), - (0xCAA, 'V'), - (0xCB4, 'X'), - (0xCB5, 'V'), - (0xCBA, 'X'), - (0xCBC, 'V'), - (0xCC5, 'X'), - (0xCC6, 'V'), - (0xCC9, 'X'), - (0xCCA, 'V'), - (0xCCE, 'X'), - (0xCD5, 'V'), - (0xCD7, 'X'), - (0xCDE, 'V'), - (0xCDF, 'X'), - (0xCE0, 'V'), - (0xCE4, 'X'), - (0xCE6, 'V'), - (0xCF0, 'X'), - (0xCF1, 'V'), - (0xCF3, 'X'), - (0xD00, 'V'), - (0xD04, 'X'), - (0xD05, 'V'), - (0xD0D, 'X'), - (0xD0E, 'V'), - (0xD11, 'X'), - (0xD12, 'V'), - (0xD45, 'X'), - (0xD46, 'V'), - (0xD49, 'X'), - (0xD4A, 'V'), - (0xD50, 'X'), - (0xD54, 'V'), - (0xD64, 'X'), - (0xD66, 'V'), - (0xD80, 'X'), - (0xD82, 'V'), - (0xD84, 'X'), - (0xD85, 'V'), - (0xD97, 'X'), - (0xD9A, 'V'), - (0xDB2, 'X'), - (0xDB3, 'V'), - (0xDBC, 'X'), - (0xDBD, 'V'), - (0xDBE, 'X'), - (0xDC0, 'V'), - (0xDC7, 'X'), - (0xDCA, 'V'), - (0xDCB, 'X'), - (0xDCF, 'V'), - (0xDD5, 'X'), - (0xDD6, 'V'), - (0xDD7, 'X'), - (0xDD8, 'V'), - (0xDE0, 'X'), - (0xDE6, 'V'), - (0xDF0, 'X'), - (0xDF2, 'V'), - (0xDF5, 'X'), - (0xE01, 'V'), - (0xE33, 'M', u'ํา'), - (0xE34, 'V'), - (0xE3B, 'X'), - (0xE3F, 'V'), - (0xE5C, 'X'), - (0xE81, 'V'), - (0xE83, 'X'), - (0xE84, 'V'), - (0xE85, 'X'), - ] - -def _seg_13(): - return [ - (0xE87, 'V'), - (0xE89, 'X'), - (0xE8A, 'V'), - (0xE8B, 'X'), - (0xE8D, 'V'), - (0xE8E, 'X'), - (0xE94, 'V'), - (0xE98, 'X'), - (0xE99, 'V'), - (0xEA0, 'X'), - (0xEA1, 'V'), - (0xEA4, 'X'), - (0xEA5, 'V'), - (0xEA6, 'X'), - (0xEA7, 'V'), - (0xEA8, 'X'), - (0xEAA, 'V'), - (0xEAC, 'X'), - (0xEAD, 'V'), - (0xEB3, 'M', u'ໍາ'), - (0xEB4, 'V'), - (0xEBA, 'X'), - (0xEBB, 'V'), - (0xEBE, 'X'), - (0xEC0, 'V'), - (0xEC5, 'X'), - (0xEC6, 'V'), - (0xEC7, 'X'), - (0xEC8, 'V'), - (0xECE, 'X'), - (0xED0, 'V'), - (0xEDA, 'X'), - (0xEDC, 'M', u'ຫນ'), - (0xEDD, 'M', u'ຫມ'), - (0xEDE, 'V'), - (0xEE0, 'X'), - (0xF00, 'V'), - (0xF0C, 'M', u'་'), - (0xF0D, 'V'), - (0xF43, 'M', u'གྷ'), - (0xF44, 'V'), - (0xF48, 'X'), - (0xF49, 'V'), - (0xF4D, 'M', u'ཌྷ'), - (0xF4E, 'V'), - (0xF52, 'M', u'དྷ'), - (0xF53, 'V'), - (0xF57, 'M', u'བྷ'), - (0xF58, 'V'), - (0xF5C, 'M', u'ཛྷ'), - (0xF5D, 'V'), - (0xF69, 'M', u'ཀྵ'), - (0xF6A, 'V'), - (0xF6D, 'X'), - (0xF71, 'V'), - (0xF73, 'M', u'ཱི'), - (0xF74, 'V'), - (0xF75, 'M', u'ཱུ'), - (0xF76, 'M', u'ྲྀ'), - (0xF77, 'M', u'ྲཱྀ'), - (0xF78, 'M', u'ླྀ'), - (0xF79, 'M', u'ླཱྀ'), - (0xF7A, 'V'), - (0xF81, 'M', u'ཱྀ'), - (0xF82, 'V'), - (0xF93, 'M', u'ྒྷ'), - (0xF94, 'V'), - (0xF98, 'X'), - (0xF99, 'V'), - (0xF9D, 'M', u'ྜྷ'), - (0xF9E, 'V'), - (0xFA2, 'M', u'ྡྷ'), - (0xFA3, 'V'), - (0xFA7, 'M', u'ྦྷ'), - (0xFA8, 'V'), - (0xFAC, 'M', u'ྫྷ'), - (0xFAD, 'V'), - (0xFB9, 'M', u'ྐྵ'), - (0xFBA, 'V'), - (0xFBD, 'X'), - (0xFBE, 'V'), - (0xFCD, 'X'), - (0xFCE, 'V'), - (0xFDB, 'X'), - (0x1000, 'V'), - (0x10A0, 'X'), - (0x10C7, 'M', u'ⴧ'), - (0x10C8, 'X'), - (0x10CD, 'M', u'ⴭ'), - (0x10CE, 'X'), - (0x10D0, 'V'), - (0x10FC, 'M', u'ნ'), - (0x10FD, 'V'), - (0x115F, 'X'), - (0x1161, 'V'), - (0x1249, 'X'), - (0x124A, 'V'), - (0x124E, 'X'), - (0x1250, 'V'), - (0x1257, 'X'), - ] - -def _seg_14(): - return [ - (0x1258, 'V'), - (0x1259, 'X'), - (0x125A, 'V'), - (0x125E, 'X'), - (0x1260, 'V'), - (0x1289, 'X'), - (0x128A, 'V'), - (0x128E, 'X'), - (0x1290, 'V'), - (0x12B1, 'X'), - (0x12B2, 'V'), - (0x12B6, 'X'), - (0x12B8, 'V'), - (0x12BF, 'X'), - (0x12C0, 'V'), - (0x12C1, 'X'), - (0x12C2, 'V'), - (0x12C6, 'X'), - (0x12C8, 'V'), - (0x12D7, 'X'), - (0x12D8, 'V'), - (0x1311, 'X'), - (0x1312, 'V'), - (0x1316, 'X'), - (0x1318, 'V'), - (0x135B, 'X'), - (0x135D, 'V'), - (0x137D, 'X'), - (0x1380, 'V'), - (0x139A, 'X'), - (0x13A0, 'V'), - (0x13F6, 'X'), - (0x13F8, 'M', u'Ᏸ'), - (0x13F9, 'M', u'Ᏹ'), - (0x13FA, 'M', u'Ᏺ'), - (0x13FB, 'M', u'Ᏻ'), - (0x13FC, 'M', u'Ᏼ'), - (0x13FD, 'M', u'Ᏽ'), - (0x13FE, 'X'), - (0x1400, 'V'), - (0x1680, 'X'), - (0x1681, 'V'), - (0x169D, 'X'), - (0x16A0, 'V'), - (0x16F9, 'X'), - (0x1700, 'V'), - (0x170D, 'X'), - (0x170E, 'V'), - (0x1715, 'X'), - (0x1720, 'V'), - (0x1737, 'X'), - (0x1740, 'V'), - (0x1754, 'X'), - (0x1760, 'V'), - (0x176D, 'X'), - (0x176E, 'V'), - (0x1771, 'X'), - (0x1772, 'V'), - (0x1774, 'X'), - (0x1780, 'V'), - (0x17B4, 'X'), - (0x17B6, 'V'), - (0x17DE, 'X'), - (0x17E0, 'V'), - (0x17EA, 'X'), - (0x17F0, 'V'), - (0x17FA, 'X'), - (0x1800, 'V'), - (0x1806, 'X'), - (0x1807, 'V'), - (0x180B, 'I'), - (0x180E, 'X'), - (0x1810, 'V'), - (0x181A, 'X'), - (0x1820, 'V'), - (0x1878, 'X'), - (0x1880, 'V'), - (0x18AB, 'X'), - (0x18B0, 'V'), - (0x18F6, 'X'), - (0x1900, 'V'), - (0x191F, 'X'), - (0x1920, 'V'), - (0x192C, 'X'), - (0x1930, 'V'), - (0x193C, 'X'), - (0x1940, 'V'), - (0x1941, 'X'), - (0x1944, 'V'), - (0x196E, 'X'), - (0x1970, 'V'), - (0x1975, 'X'), - (0x1980, 'V'), - (0x19AC, 'X'), - (0x19B0, 'V'), - (0x19CA, 'X'), - (0x19D0, 'V'), - (0x19DB, 'X'), - (0x19DE, 'V'), - (0x1A1C, 'X'), - ] - -def _seg_15(): - return [ - (0x1A1E, 'V'), - (0x1A5F, 'X'), - (0x1A60, 'V'), - (0x1A7D, 'X'), - (0x1A7F, 'V'), - (0x1A8A, 'X'), - (0x1A90, 'V'), - (0x1A9A, 'X'), - (0x1AA0, 'V'), - (0x1AAE, 'X'), - (0x1AB0, 'V'), - (0x1ABF, 'X'), - (0x1B00, 'V'), - (0x1B4C, 'X'), - (0x1B50, 'V'), - (0x1B7D, 'X'), - (0x1B80, 'V'), - (0x1BF4, 'X'), - (0x1BFC, 'V'), - (0x1C38, 'X'), - (0x1C3B, 'V'), - (0x1C4A, 'X'), - (0x1C4D, 'V'), - (0x1C80, 'M', u'в'), - (0x1C81, 'M', u'д'), - (0x1C82, 'M', u'о'), - (0x1C83, 'M', u'с'), - (0x1C84, 'M', u'т'), - (0x1C86, 'M', u'ъ'), - (0x1C87, 'M', u'ѣ'), - (0x1C88, 'M', u'ꙋ'), - (0x1C89, 'X'), - (0x1CC0, 'V'), - (0x1CC8, 'X'), - (0x1CD0, 'V'), - (0x1CFA, 'X'), - (0x1D00, 'V'), - (0x1D2C, 'M', u'a'), - (0x1D2D, 'M', u'æ'), - (0x1D2E, 'M', u'b'), - (0x1D2F, 'V'), - (0x1D30, 'M', u'd'), - (0x1D31, 'M', u'e'), - (0x1D32, 'M', u'ǝ'), - (0x1D33, 'M', u'g'), - (0x1D34, 'M', u'h'), - (0x1D35, 'M', u'i'), - (0x1D36, 'M', u'j'), - (0x1D37, 'M', u'k'), - (0x1D38, 'M', u'l'), - (0x1D39, 'M', u'm'), - (0x1D3A, 'M', u'n'), - (0x1D3B, 'V'), - (0x1D3C, 'M', u'o'), - (0x1D3D, 'M', u'ȣ'), - (0x1D3E, 'M', u'p'), - (0x1D3F, 'M', u'r'), - (0x1D40, 'M', u't'), - (0x1D41, 'M', u'u'), - (0x1D42, 'M', u'w'), - (0x1D43, 'M', u'a'), - (0x1D44, 'M', u'ɐ'), - (0x1D45, 'M', u'ɑ'), - (0x1D46, 'M', u'ᴂ'), - (0x1D47, 'M', u'b'), - (0x1D48, 'M', u'd'), - (0x1D49, 'M', u'e'), - (0x1D4A, 'M', u'ə'), - (0x1D4B, 'M', u'ɛ'), - (0x1D4C, 'M', u'ɜ'), - (0x1D4D, 'M', u'g'), - (0x1D4E, 'V'), - (0x1D4F, 'M', u'k'), - (0x1D50, 'M', u'm'), - (0x1D51, 'M', u'ŋ'), - (0x1D52, 'M', u'o'), - (0x1D53, 'M', u'ɔ'), - (0x1D54, 'M', u'ᴖ'), - (0x1D55, 'M', u'ᴗ'), - (0x1D56, 'M', u'p'), - (0x1D57, 'M', u't'), - (0x1D58, 'M', u'u'), - (0x1D59, 'M', u'ᴝ'), - (0x1D5A, 'M', u'ɯ'), - (0x1D5B, 'M', u'v'), - (0x1D5C, 'M', u'ᴥ'), - (0x1D5D, 'M', u'β'), - (0x1D5E, 'M', u'γ'), - (0x1D5F, 'M', u'δ'), - (0x1D60, 'M', u'φ'), - (0x1D61, 'M', u'χ'), - (0x1D62, 'M', u'i'), - (0x1D63, 'M', u'r'), - (0x1D64, 'M', u'u'), - (0x1D65, 'M', u'v'), - (0x1D66, 'M', u'β'), - (0x1D67, 'M', u'γ'), - (0x1D68, 'M', u'ρ'), - (0x1D69, 'M', u'φ'), - (0x1D6A, 'M', u'χ'), - ] - -def _seg_16(): - return [ - (0x1D6B, 'V'), - (0x1D78, 'M', u'н'), - (0x1D79, 'V'), - (0x1D9B, 'M', u'ɒ'), - (0x1D9C, 'M', u'c'), - (0x1D9D, 'M', u'ɕ'), - (0x1D9E, 'M', u'ð'), - (0x1D9F, 'M', u'ɜ'), - (0x1DA0, 'M', u'f'), - (0x1DA1, 'M', u'ɟ'), - (0x1DA2, 'M', u'ɡ'), - (0x1DA3, 'M', u'ɥ'), - (0x1DA4, 'M', u'ɨ'), - (0x1DA5, 'M', u'ɩ'), - (0x1DA6, 'M', u'ɪ'), - (0x1DA7, 'M', u'ᵻ'), - (0x1DA8, 'M', u'ʝ'), - (0x1DA9, 'M', u'ɭ'), - (0x1DAA, 'M', u'ᶅ'), - (0x1DAB, 'M', u'ʟ'), - (0x1DAC, 'M', u'ɱ'), - (0x1DAD, 'M', u'ɰ'), - (0x1DAE, 'M', u'ɲ'), - (0x1DAF, 'M', u'ɳ'), - (0x1DB0, 'M', u'ɴ'), - (0x1DB1, 'M', u'ɵ'), - (0x1DB2, 'M', u'ɸ'), - (0x1DB3, 'M', u'ʂ'), - (0x1DB4, 'M', u'ʃ'), - (0x1DB5, 'M', u'ƫ'), - (0x1DB6, 'M', u'ʉ'), - (0x1DB7, 'M', u'ʊ'), - (0x1DB8, 'M', u'ᴜ'), - (0x1DB9, 'M', u'ʋ'), - (0x1DBA, 'M', u'ʌ'), - (0x1DBB, 'M', u'z'), - (0x1DBC, 'M', u'ʐ'), - (0x1DBD, 'M', u'ʑ'), - (0x1DBE, 'M', u'ʒ'), - (0x1DBF, 'M', u'θ'), - (0x1DC0, 'V'), - (0x1DFA, 'X'), - (0x1DFB, 'V'), - (0x1E00, 'M', u'ḁ'), - (0x1E01, 'V'), - (0x1E02, 'M', u'ḃ'), - (0x1E03, 'V'), - (0x1E04, 'M', u'ḅ'), - (0x1E05, 'V'), - (0x1E06, 'M', u'ḇ'), - (0x1E07, 'V'), - (0x1E08, 'M', u'ḉ'), - (0x1E09, 'V'), - (0x1E0A, 'M', u'ḋ'), - (0x1E0B, 'V'), - (0x1E0C, 'M', u'ḍ'), - (0x1E0D, 'V'), - (0x1E0E, 'M', u'ḏ'), - (0x1E0F, 'V'), - (0x1E10, 'M', u'ḑ'), - (0x1E11, 'V'), - (0x1E12, 'M', u'ḓ'), - (0x1E13, 'V'), - (0x1E14, 'M', u'ḕ'), - (0x1E15, 'V'), - (0x1E16, 'M', u'ḗ'), - (0x1E17, 'V'), - (0x1E18, 'M', u'ḙ'), - (0x1E19, 'V'), - (0x1E1A, 'M', u'ḛ'), - (0x1E1B, 'V'), - (0x1E1C, 'M', u'ḝ'), - (0x1E1D, 'V'), - (0x1E1E, 'M', u'ḟ'), - (0x1E1F, 'V'), - (0x1E20, 'M', u'ḡ'), - (0x1E21, 'V'), - (0x1E22, 'M', u'ḣ'), - (0x1E23, 'V'), - (0x1E24, 'M', u'ḥ'), - (0x1E25, 'V'), - (0x1E26, 'M', u'ḧ'), - (0x1E27, 'V'), - (0x1E28, 'M', u'ḩ'), - (0x1E29, 'V'), - (0x1E2A, 'M', u'ḫ'), - (0x1E2B, 'V'), - (0x1E2C, 'M', u'ḭ'), - (0x1E2D, 'V'), - (0x1E2E, 'M', u'ḯ'), - (0x1E2F, 'V'), - (0x1E30, 'M', u'ḱ'), - (0x1E31, 'V'), - (0x1E32, 'M', u'ḳ'), - (0x1E33, 'V'), - (0x1E34, 'M', u'ḵ'), - (0x1E35, 'V'), - (0x1E36, 'M', u'ḷ'), - (0x1E37, 'V'), - (0x1E38, 'M', u'ḹ'), - ] - -def _seg_17(): - return [ - (0x1E39, 'V'), - (0x1E3A, 'M', u'ḻ'), - (0x1E3B, 'V'), - (0x1E3C, 'M', u'ḽ'), - (0x1E3D, 'V'), - (0x1E3E, 'M', u'ḿ'), - (0x1E3F, 'V'), - (0x1E40, 'M', u'ṁ'), - (0x1E41, 'V'), - (0x1E42, 'M', u'ṃ'), - (0x1E43, 'V'), - (0x1E44, 'M', u'ṅ'), - (0x1E45, 'V'), - (0x1E46, 'M', u'ṇ'), - (0x1E47, 'V'), - (0x1E48, 'M', u'ṉ'), - (0x1E49, 'V'), - (0x1E4A, 'M', u'ṋ'), - (0x1E4B, 'V'), - (0x1E4C, 'M', u'ṍ'), - (0x1E4D, 'V'), - (0x1E4E, 'M', u'ṏ'), - (0x1E4F, 'V'), - (0x1E50, 'M', u'ṑ'), - (0x1E51, 'V'), - (0x1E52, 'M', u'ṓ'), - (0x1E53, 'V'), - (0x1E54, 'M', u'ṕ'), - (0x1E55, 'V'), - (0x1E56, 'M', u'ṗ'), - (0x1E57, 'V'), - (0x1E58, 'M', u'ṙ'), - (0x1E59, 'V'), - (0x1E5A, 'M', u'ṛ'), - (0x1E5B, 'V'), - (0x1E5C, 'M', u'ṝ'), - (0x1E5D, 'V'), - (0x1E5E, 'M', u'ṟ'), - (0x1E5F, 'V'), - (0x1E60, 'M', u'ṡ'), - (0x1E61, 'V'), - (0x1E62, 'M', u'ṣ'), - (0x1E63, 'V'), - (0x1E64, 'M', u'ṥ'), - (0x1E65, 'V'), - (0x1E66, 'M', u'ṧ'), - (0x1E67, 'V'), - (0x1E68, 'M', u'ṩ'), - (0x1E69, 'V'), - (0x1E6A, 'M', u'ṫ'), - (0x1E6B, 'V'), - (0x1E6C, 'M', u'ṭ'), - (0x1E6D, 'V'), - (0x1E6E, 'M', u'ṯ'), - (0x1E6F, 'V'), - (0x1E70, 'M', u'ṱ'), - (0x1E71, 'V'), - (0x1E72, 'M', u'ṳ'), - (0x1E73, 'V'), - (0x1E74, 'M', u'ṵ'), - (0x1E75, 'V'), - (0x1E76, 'M', u'ṷ'), - (0x1E77, 'V'), - (0x1E78, 'M', u'ṹ'), - (0x1E79, 'V'), - (0x1E7A, 'M', u'ṻ'), - (0x1E7B, 'V'), - (0x1E7C, 'M', u'ṽ'), - (0x1E7D, 'V'), - (0x1E7E, 'M', u'ṿ'), - (0x1E7F, 'V'), - (0x1E80, 'M', u'ẁ'), - (0x1E81, 'V'), - (0x1E82, 'M', u'ẃ'), - (0x1E83, 'V'), - (0x1E84, 'M', u'ẅ'), - (0x1E85, 'V'), - (0x1E86, 'M', u'ẇ'), - (0x1E87, 'V'), - (0x1E88, 'M', u'ẉ'), - (0x1E89, 'V'), - (0x1E8A, 'M', u'ẋ'), - (0x1E8B, 'V'), - (0x1E8C, 'M', u'ẍ'), - (0x1E8D, 'V'), - (0x1E8E, 'M', u'ẏ'), - (0x1E8F, 'V'), - (0x1E90, 'M', u'ẑ'), - (0x1E91, 'V'), - (0x1E92, 'M', u'ẓ'), - (0x1E93, 'V'), - (0x1E94, 'M', u'ẕ'), - (0x1E95, 'V'), - (0x1E9A, 'M', u'aʾ'), - (0x1E9B, 'M', u'ṡ'), - (0x1E9C, 'V'), - (0x1E9E, 'M', u'ss'), - (0x1E9F, 'V'), - (0x1EA0, 'M', u'ạ'), - (0x1EA1, 'V'), - ] - -def _seg_18(): - return [ - (0x1EA2, 'M', u'ả'), - (0x1EA3, 'V'), - (0x1EA4, 'M', u'ấ'), - (0x1EA5, 'V'), - (0x1EA6, 'M', u'ầ'), - (0x1EA7, 'V'), - (0x1EA8, 'M', u'ẩ'), - (0x1EA9, 'V'), - (0x1EAA, 'M', u'ẫ'), - (0x1EAB, 'V'), - (0x1EAC, 'M', u'ậ'), - (0x1EAD, 'V'), - (0x1EAE, 'M', u'ắ'), - (0x1EAF, 'V'), - (0x1EB0, 'M', u'ằ'), - (0x1EB1, 'V'), - (0x1EB2, 'M', u'ẳ'), - (0x1EB3, 'V'), - (0x1EB4, 'M', u'ẵ'), - (0x1EB5, 'V'), - (0x1EB6, 'M', u'ặ'), - (0x1EB7, 'V'), - (0x1EB8, 'M', u'ẹ'), - (0x1EB9, 'V'), - (0x1EBA, 'M', u'ẻ'), - (0x1EBB, 'V'), - (0x1EBC, 'M', u'ẽ'), - (0x1EBD, 'V'), - (0x1EBE, 'M', u'ế'), - (0x1EBF, 'V'), - (0x1EC0, 'M', u'ề'), - (0x1EC1, 'V'), - (0x1EC2, 'M', u'ể'), - (0x1EC3, 'V'), - (0x1EC4, 'M', u'ễ'), - (0x1EC5, 'V'), - (0x1EC6, 'M', u'ệ'), - (0x1EC7, 'V'), - (0x1EC8, 'M', u'ỉ'), - (0x1EC9, 'V'), - (0x1ECA, 'M', u'ị'), - (0x1ECB, 'V'), - (0x1ECC, 'M', u'ọ'), - (0x1ECD, 'V'), - (0x1ECE, 'M', u'ỏ'), - (0x1ECF, 'V'), - (0x1ED0, 'M', u'ố'), - (0x1ED1, 'V'), - (0x1ED2, 'M', u'ồ'), - (0x1ED3, 'V'), - (0x1ED4, 'M', u'ổ'), - (0x1ED5, 'V'), - (0x1ED6, 'M', u'ỗ'), - (0x1ED7, 'V'), - (0x1ED8, 'M', u'ộ'), - (0x1ED9, 'V'), - (0x1EDA, 'M', u'ớ'), - (0x1EDB, 'V'), - (0x1EDC, 'M', u'ờ'), - (0x1EDD, 'V'), - (0x1EDE, 'M', u'ở'), - (0x1EDF, 'V'), - (0x1EE0, 'M', u'ỡ'), - (0x1EE1, 'V'), - (0x1EE2, 'M', u'ợ'), - (0x1EE3, 'V'), - (0x1EE4, 'M', u'ụ'), - (0x1EE5, 'V'), - (0x1EE6, 'M', u'ủ'), - (0x1EE7, 'V'), - (0x1EE8, 'M', u'ứ'), - (0x1EE9, 'V'), - (0x1EEA, 'M', u'ừ'), - (0x1EEB, 'V'), - (0x1EEC, 'M', u'ử'), - (0x1EED, 'V'), - (0x1EEE, 'M', u'ữ'), - (0x1EEF, 'V'), - (0x1EF0, 'M', u'ự'), - (0x1EF1, 'V'), - (0x1EF2, 'M', u'ỳ'), - (0x1EF3, 'V'), - (0x1EF4, 'M', u'ỵ'), - (0x1EF5, 'V'), - (0x1EF6, 'M', u'ỷ'), - (0x1EF7, 'V'), - (0x1EF8, 'M', u'ỹ'), - (0x1EF9, 'V'), - (0x1EFA, 'M', u'ỻ'), - (0x1EFB, 'V'), - (0x1EFC, 'M', u'ỽ'), - (0x1EFD, 'V'), - (0x1EFE, 'M', u'ỿ'), - (0x1EFF, 'V'), - (0x1F08, 'M', u'ἀ'), - (0x1F09, 'M', u'ἁ'), - (0x1F0A, 'M', u'ἂ'), - (0x1F0B, 'M', u'ἃ'), - (0x1F0C, 'M', u'ἄ'), - (0x1F0D, 'M', u'ἅ'), - ] - -def _seg_19(): - return [ - (0x1F0E, 'M', u'ἆ'), - (0x1F0F, 'M', u'ἇ'), - (0x1F10, 'V'), - (0x1F16, 'X'), - (0x1F18, 'M', u'ἐ'), - (0x1F19, 'M', u'ἑ'), - (0x1F1A, 'M', u'ἒ'), - (0x1F1B, 'M', u'ἓ'), - (0x1F1C, 'M', u'ἔ'), - (0x1F1D, 'M', u'ἕ'), - (0x1F1E, 'X'), - (0x1F20, 'V'), - (0x1F28, 'M', u'ἠ'), - (0x1F29, 'M', u'ἡ'), - (0x1F2A, 'M', u'ἢ'), - (0x1F2B, 'M', u'ἣ'), - (0x1F2C, 'M', u'ἤ'), - (0x1F2D, 'M', u'ἥ'), - (0x1F2E, 'M', u'ἦ'), - (0x1F2F, 'M', u'ἧ'), - (0x1F30, 'V'), - (0x1F38, 'M', u'ἰ'), - (0x1F39, 'M', u'ἱ'), - (0x1F3A, 'M', u'ἲ'), - (0x1F3B, 'M', u'ἳ'), - (0x1F3C, 'M', u'ἴ'), - (0x1F3D, 'M', u'ἵ'), - (0x1F3E, 'M', u'ἶ'), - (0x1F3F, 'M', u'ἷ'), - (0x1F40, 'V'), - (0x1F46, 'X'), - (0x1F48, 'M', u'ὀ'), - (0x1F49, 'M', u'ὁ'), - (0x1F4A, 'M', u'ὂ'), - (0x1F4B, 'M', u'ὃ'), - (0x1F4C, 'M', u'ὄ'), - (0x1F4D, 'M', u'ὅ'), - (0x1F4E, 'X'), - (0x1F50, 'V'), - (0x1F58, 'X'), - (0x1F59, 'M', u'ὑ'), - (0x1F5A, 'X'), - (0x1F5B, 'M', u'ὓ'), - (0x1F5C, 'X'), - (0x1F5D, 'M', u'ὕ'), - (0x1F5E, 'X'), - (0x1F5F, 'M', u'ὗ'), - (0x1F60, 'V'), - (0x1F68, 'M', u'ὠ'), - (0x1F69, 'M', u'ὡ'), - (0x1F6A, 'M', u'ὢ'), - (0x1F6B, 'M', u'ὣ'), - (0x1F6C, 'M', u'ὤ'), - (0x1F6D, 'M', u'ὥ'), - (0x1F6E, 'M', u'ὦ'), - (0x1F6F, 'M', u'ὧ'), - (0x1F70, 'V'), - (0x1F71, 'M', u'ά'), - (0x1F72, 'V'), - (0x1F73, 'M', u'έ'), - (0x1F74, 'V'), - (0x1F75, 'M', u'ή'), - (0x1F76, 'V'), - (0x1F77, 'M', u'ί'), - (0x1F78, 'V'), - (0x1F79, 'M', u'ό'), - (0x1F7A, 'V'), - (0x1F7B, 'M', u'ύ'), - (0x1F7C, 'V'), - (0x1F7D, 'M', u'ώ'), - (0x1F7E, 'X'), - (0x1F80, 'M', u'ἀι'), - (0x1F81, 'M', u'ἁι'), - (0x1F82, 'M', u'ἂι'), - (0x1F83, 'M', u'ἃι'), - (0x1F84, 'M', u'ἄι'), - (0x1F85, 'M', u'ἅι'), - (0x1F86, 'M', u'ἆι'), - (0x1F87, 'M', u'ἇι'), - (0x1F88, 'M', u'ἀι'), - (0x1F89, 'M', u'ἁι'), - (0x1F8A, 'M', u'ἂι'), - (0x1F8B, 'M', u'ἃι'), - (0x1F8C, 'M', u'ἄι'), - (0x1F8D, 'M', u'ἅι'), - (0x1F8E, 'M', u'ἆι'), - (0x1F8F, 'M', u'ἇι'), - (0x1F90, 'M', u'ἠι'), - (0x1F91, 'M', u'ἡι'), - (0x1F92, 'M', u'ἢι'), - (0x1F93, 'M', u'ἣι'), - (0x1F94, 'M', u'ἤι'), - (0x1F95, 'M', u'ἥι'), - (0x1F96, 'M', u'ἦι'), - (0x1F97, 'M', u'ἧι'), - (0x1F98, 'M', u'ἠι'), - (0x1F99, 'M', u'ἡι'), - (0x1F9A, 'M', u'ἢι'), - (0x1F9B, 'M', u'ἣι'), - (0x1F9C, 'M', u'ἤι'), - ] - -def _seg_20(): - return [ - (0x1F9D, 'M', u'ἥι'), - (0x1F9E, 'M', u'ἦι'), - (0x1F9F, 'M', u'ἧι'), - (0x1FA0, 'M', u'ὠι'), - (0x1FA1, 'M', u'ὡι'), - (0x1FA2, 'M', u'ὢι'), - (0x1FA3, 'M', u'ὣι'), - (0x1FA4, 'M', u'ὤι'), - (0x1FA5, 'M', u'ὥι'), - (0x1FA6, 'M', u'ὦι'), - (0x1FA7, 'M', u'ὧι'), - (0x1FA8, 'M', u'ὠι'), - (0x1FA9, 'M', u'ὡι'), - (0x1FAA, 'M', u'ὢι'), - (0x1FAB, 'M', u'ὣι'), - (0x1FAC, 'M', u'ὤι'), - (0x1FAD, 'M', u'ὥι'), - (0x1FAE, 'M', u'ὦι'), - (0x1FAF, 'M', u'ὧι'), - (0x1FB0, 'V'), - (0x1FB2, 'M', u'ὰι'), - (0x1FB3, 'M', u'αι'), - (0x1FB4, 'M', u'άι'), - (0x1FB5, 'X'), - (0x1FB6, 'V'), - (0x1FB7, 'M', u'ᾶι'), - (0x1FB8, 'M', u'ᾰ'), - (0x1FB9, 'M', u'ᾱ'), - (0x1FBA, 'M', u'ὰ'), - (0x1FBB, 'M', u'ά'), - (0x1FBC, 'M', u'αι'), - (0x1FBD, '3', u' ̓'), - (0x1FBE, 'M', u'ι'), - (0x1FBF, '3', u' ̓'), - (0x1FC0, '3', u' ͂'), - (0x1FC1, '3', u' ̈͂'), - (0x1FC2, 'M', u'ὴι'), - (0x1FC3, 'M', u'ηι'), - (0x1FC4, 'M', u'ήι'), - (0x1FC5, 'X'), - (0x1FC6, 'V'), - (0x1FC7, 'M', u'ῆι'), - (0x1FC8, 'M', u'ὲ'), - (0x1FC9, 'M', u'έ'), - (0x1FCA, 'M', u'ὴ'), - (0x1FCB, 'M', u'ή'), - (0x1FCC, 'M', u'ηι'), - (0x1FCD, '3', u' ̓̀'), - (0x1FCE, '3', u' ̓́'), - (0x1FCF, '3', u' ̓͂'), - (0x1FD0, 'V'), - (0x1FD3, 'M', u'ΐ'), - (0x1FD4, 'X'), - (0x1FD6, 'V'), - (0x1FD8, 'M', u'ῐ'), - (0x1FD9, 'M', u'ῑ'), - (0x1FDA, 'M', u'ὶ'), - (0x1FDB, 'M', u'ί'), - (0x1FDC, 'X'), - (0x1FDD, '3', u' ̔̀'), - (0x1FDE, '3', u' ̔́'), - (0x1FDF, '3', u' ̔͂'), - (0x1FE0, 'V'), - (0x1FE3, 'M', u'ΰ'), - (0x1FE4, 'V'), - (0x1FE8, 'M', u'ῠ'), - (0x1FE9, 'M', u'ῡ'), - (0x1FEA, 'M', u'ὺ'), - (0x1FEB, 'M', u'ύ'), - (0x1FEC, 'M', u'ῥ'), - (0x1FED, '3', u' ̈̀'), - (0x1FEE, '3', u' ̈́'), - (0x1FEF, '3', u'`'), - (0x1FF0, 'X'), - (0x1FF2, 'M', u'ὼι'), - (0x1FF3, 'M', u'ωι'), - (0x1FF4, 'M', u'ώι'), - (0x1FF5, 'X'), - (0x1FF6, 'V'), - (0x1FF7, 'M', u'ῶι'), - (0x1FF8, 'M', u'ὸ'), - (0x1FF9, 'M', u'ό'), - (0x1FFA, 'M', u'ὼ'), - (0x1FFB, 'M', u'ώ'), - (0x1FFC, 'M', u'ωι'), - (0x1FFD, '3', u' ́'), - (0x1FFE, '3', u' ̔'), - (0x1FFF, 'X'), - (0x2000, '3', u' '), - (0x200B, 'I'), - (0x200C, 'D', u''), - (0x200E, 'X'), - (0x2010, 'V'), - (0x2011, 'M', u'‐'), - (0x2012, 'V'), - (0x2017, '3', u' ̳'), - (0x2018, 'V'), - (0x2024, 'X'), - (0x2027, 'V'), - (0x2028, 'X'), - ] - -def _seg_21(): - return [ - (0x202F, '3', u' '), - (0x2030, 'V'), - (0x2033, 'M', u'′′'), - (0x2034, 'M', u'′′′'), - (0x2035, 'V'), - (0x2036, 'M', u'‵‵'), - (0x2037, 'M', u'‵‵‵'), - (0x2038, 'V'), - (0x203C, '3', u'!!'), - (0x203D, 'V'), - (0x203E, '3', u' ̅'), - (0x203F, 'V'), - (0x2047, '3', u'??'), - (0x2048, '3', u'?!'), - (0x2049, '3', u'!?'), - (0x204A, 'V'), - (0x2057, 'M', u'′′′′'), - (0x2058, 'V'), - (0x205F, '3', u' '), - (0x2060, 'I'), - (0x2061, 'X'), - (0x2064, 'I'), - (0x2065, 'X'), - (0x2070, 'M', u'0'), - (0x2071, 'M', u'i'), - (0x2072, 'X'), - (0x2074, 'M', u'4'), - (0x2075, 'M', u'5'), - (0x2076, 'M', u'6'), - (0x2077, 'M', u'7'), - (0x2078, 'M', u'8'), - (0x2079, 'M', u'9'), - (0x207A, '3', u'+'), - (0x207B, 'M', u'−'), - (0x207C, '3', u'='), - (0x207D, '3', u'('), - (0x207E, '3', u')'), - (0x207F, 'M', u'n'), - (0x2080, 'M', u'0'), - (0x2081, 'M', u'1'), - (0x2082, 'M', u'2'), - (0x2083, 'M', u'3'), - (0x2084, 'M', u'4'), - (0x2085, 'M', u'5'), - (0x2086, 'M', u'6'), - (0x2087, 'M', u'7'), - (0x2088, 'M', u'8'), - (0x2089, 'M', u'9'), - (0x208A, '3', u'+'), - (0x208B, 'M', u'−'), - (0x208C, '3', u'='), - (0x208D, '3', u'('), - (0x208E, '3', u')'), - (0x208F, 'X'), - (0x2090, 'M', u'a'), - (0x2091, 'M', u'e'), - (0x2092, 'M', u'o'), - (0x2093, 'M', u'x'), - (0x2094, 'M', u'ə'), - (0x2095, 'M', u'h'), - (0x2096, 'M', u'k'), - (0x2097, 'M', u'l'), - (0x2098, 'M', u'm'), - (0x2099, 'M', u'n'), - (0x209A, 'M', u'p'), - (0x209B, 'M', u's'), - (0x209C, 'M', u't'), - (0x209D, 'X'), - (0x20A0, 'V'), - (0x20A8, 'M', u'rs'), - (0x20A9, 'V'), - (0x20C0, 'X'), - (0x20D0, 'V'), - (0x20F1, 'X'), - (0x2100, '3', u'a/c'), - (0x2101, '3', u'a/s'), - (0x2102, 'M', u'c'), - (0x2103, 'M', u'°c'), - (0x2104, 'V'), - (0x2105, '3', u'c/o'), - (0x2106, '3', u'c/u'), - (0x2107, 'M', u'ɛ'), - (0x2108, 'V'), - (0x2109, 'M', u'°f'), - (0x210A, 'M', u'g'), - (0x210B, 'M', u'h'), - (0x210F, 'M', u'ħ'), - (0x2110, 'M', u'i'), - (0x2112, 'M', u'l'), - (0x2114, 'V'), - (0x2115, 'M', u'n'), - (0x2116, 'M', u'no'), - (0x2117, 'V'), - (0x2119, 'M', u'p'), - (0x211A, 'M', u'q'), - (0x211B, 'M', u'r'), - (0x211E, 'V'), - (0x2120, 'M', u'sm'), - (0x2121, 'M', u'tel'), - (0x2122, 'M', u'tm'), - ] - -def _seg_22(): - return [ - (0x2123, 'V'), - (0x2124, 'M', u'z'), - (0x2125, 'V'), - (0x2126, 'M', u'ω'), - (0x2127, 'V'), - (0x2128, 'M', u'z'), - (0x2129, 'V'), - (0x212A, 'M', u'k'), - (0x212B, 'M', u'å'), - (0x212C, 'M', u'b'), - (0x212D, 'M', u'c'), - (0x212E, 'V'), - (0x212F, 'M', u'e'), - (0x2131, 'M', u'f'), - (0x2132, 'X'), - (0x2133, 'M', u'm'), - (0x2134, 'M', u'o'), - (0x2135, 'M', u'א'), - (0x2136, 'M', u'ב'), - (0x2137, 'M', u'ג'), - (0x2138, 'M', u'ד'), - (0x2139, 'M', u'i'), - (0x213A, 'V'), - (0x213B, 'M', u'fax'), - (0x213C, 'M', u'π'), - (0x213D, 'M', u'γ'), - (0x213F, 'M', u'π'), - (0x2140, 'M', u'∑'), - (0x2141, 'V'), - (0x2145, 'M', u'd'), - (0x2147, 'M', u'e'), - (0x2148, 'M', u'i'), - (0x2149, 'M', u'j'), - (0x214A, 'V'), - (0x2150, 'M', u'1⁄7'), - (0x2151, 'M', u'1⁄9'), - (0x2152, 'M', u'1⁄10'), - (0x2153, 'M', u'1⁄3'), - (0x2154, 'M', u'2⁄3'), - (0x2155, 'M', u'1⁄5'), - (0x2156, 'M', u'2⁄5'), - (0x2157, 'M', u'3⁄5'), - (0x2158, 'M', u'4⁄5'), - (0x2159, 'M', u'1⁄6'), - (0x215A, 'M', u'5⁄6'), - (0x215B, 'M', u'1⁄8'), - (0x215C, 'M', u'3⁄8'), - (0x215D, 'M', u'5⁄8'), - (0x215E, 'M', u'7⁄8'), - (0x215F, 'M', u'1⁄'), - (0x2160, 'M', u'i'), - (0x2161, 'M', u'ii'), - (0x2162, 'M', u'iii'), - (0x2163, 'M', u'iv'), - (0x2164, 'M', u'v'), - (0x2165, 'M', u'vi'), - (0x2166, 'M', u'vii'), - (0x2167, 'M', u'viii'), - (0x2168, 'M', u'ix'), - (0x2169, 'M', u'x'), - (0x216A, 'M', u'xi'), - (0x216B, 'M', u'xii'), - (0x216C, 'M', u'l'), - (0x216D, 'M', u'c'), - (0x216E, 'M', u'd'), - (0x216F, 'M', u'm'), - (0x2170, 'M', u'i'), - (0x2171, 'M', u'ii'), - (0x2172, 'M', u'iii'), - (0x2173, 'M', u'iv'), - (0x2174, 'M', u'v'), - (0x2175, 'M', u'vi'), - (0x2176, 'M', u'vii'), - (0x2177, 'M', u'viii'), - (0x2178, 'M', u'ix'), - (0x2179, 'M', u'x'), - (0x217A, 'M', u'xi'), - (0x217B, 'M', u'xii'), - (0x217C, 'M', u'l'), - (0x217D, 'M', u'c'), - (0x217E, 'M', u'd'), - (0x217F, 'M', u'm'), - (0x2180, 'V'), - (0x2183, 'X'), - (0x2184, 'V'), - (0x2189, 'M', u'0⁄3'), - (0x218A, 'V'), - (0x218C, 'X'), - (0x2190, 'V'), - (0x222C, 'M', u'∫∫'), - (0x222D, 'M', u'∫∫∫'), - (0x222E, 'V'), - (0x222F, 'M', u'∮∮'), - (0x2230, 'M', u'∮∮∮'), - (0x2231, 'V'), - (0x2260, '3'), - (0x2261, 'V'), - (0x226E, '3'), - (0x2270, 'V'), - (0x2329, 'M', u'〈'), - ] - -def _seg_23(): - return [ - (0x232A, 'M', u'〉'), - (0x232B, 'V'), - (0x2427, 'X'), - (0x2440, 'V'), - (0x244B, 'X'), - (0x2460, 'M', u'1'), - (0x2461, 'M', u'2'), - (0x2462, 'M', u'3'), - (0x2463, 'M', u'4'), - (0x2464, 'M', u'5'), - (0x2465, 'M', u'6'), - (0x2466, 'M', u'7'), - (0x2467, 'M', u'8'), - (0x2468, 'M', u'9'), - (0x2469, 'M', u'10'), - (0x246A, 'M', u'11'), - (0x246B, 'M', u'12'), - (0x246C, 'M', u'13'), - (0x246D, 'M', u'14'), - (0x246E, 'M', u'15'), - (0x246F, 'M', u'16'), - (0x2470, 'M', u'17'), - (0x2471, 'M', u'18'), - (0x2472, 'M', u'19'), - (0x2473, 'M', u'20'), - (0x2474, '3', u'(1)'), - (0x2475, '3', u'(2)'), - (0x2476, '3', u'(3)'), - (0x2477, '3', u'(4)'), - (0x2478, '3', u'(5)'), - (0x2479, '3', u'(6)'), - (0x247A, '3', u'(7)'), - (0x247B, '3', u'(8)'), - (0x247C, '3', u'(9)'), - (0x247D, '3', u'(10)'), - (0x247E, '3', u'(11)'), - (0x247F, '3', u'(12)'), - (0x2480, '3', u'(13)'), - (0x2481, '3', u'(14)'), - (0x2482, '3', u'(15)'), - (0x2483, '3', u'(16)'), - (0x2484, '3', u'(17)'), - (0x2485, '3', u'(18)'), - (0x2486, '3', u'(19)'), - (0x2487, '3', u'(20)'), - (0x2488, 'X'), - (0x249C, '3', u'(a)'), - (0x249D, '3', u'(b)'), - (0x249E, '3', u'(c)'), - (0x249F, '3', u'(d)'), - (0x24A0, '3', u'(e)'), - (0x24A1, '3', u'(f)'), - (0x24A2, '3', u'(g)'), - (0x24A3, '3', u'(h)'), - (0x24A4, '3', u'(i)'), - (0x24A5, '3', u'(j)'), - (0x24A6, '3', u'(k)'), - (0x24A7, '3', u'(l)'), - (0x24A8, '3', u'(m)'), - (0x24A9, '3', u'(n)'), - (0x24AA, '3', u'(o)'), - (0x24AB, '3', u'(p)'), - (0x24AC, '3', u'(q)'), - (0x24AD, '3', u'(r)'), - (0x24AE, '3', u'(s)'), - (0x24AF, '3', u'(t)'), - (0x24B0, '3', u'(u)'), - (0x24B1, '3', u'(v)'), - (0x24B2, '3', u'(w)'), - (0x24B3, '3', u'(x)'), - (0x24B4, '3', u'(y)'), - (0x24B5, '3', u'(z)'), - (0x24B6, 'M', u'a'), - (0x24B7, 'M', u'b'), - (0x24B8, 'M', u'c'), - (0x24B9, 'M', u'd'), - (0x24BA, 'M', u'e'), - (0x24BB, 'M', u'f'), - (0x24BC, 'M', u'g'), - (0x24BD, 'M', u'h'), - (0x24BE, 'M', u'i'), - (0x24BF, 'M', u'j'), - (0x24C0, 'M', u'k'), - (0x24C1, 'M', u'l'), - (0x24C2, 'M', u'm'), - (0x24C3, 'M', u'n'), - (0x24C4, 'M', u'o'), - (0x24C5, 'M', u'p'), - (0x24C6, 'M', u'q'), - (0x24C7, 'M', u'r'), - (0x24C8, 'M', u's'), - (0x24C9, 'M', u't'), - (0x24CA, 'M', u'u'), - (0x24CB, 'M', u'v'), - (0x24CC, 'M', u'w'), - (0x24CD, 'M', u'x'), - (0x24CE, 'M', u'y'), - (0x24CF, 'M', u'z'), - (0x24D0, 'M', u'a'), - (0x24D1, 'M', u'b'), - ] - -def _seg_24(): - return [ - (0x24D2, 'M', u'c'), - (0x24D3, 'M', u'd'), - (0x24D4, 'M', u'e'), - (0x24D5, 'M', u'f'), - (0x24D6, 'M', u'g'), - (0x24D7, 'M', u'h'), - (0x24D8, 'M', u'i'), - (0x24D9, 'M', u'j'), - (0x24DA, 'M', u'k'), - (0x24DB, 'M', u'l'), - (0x24DC, 'M', u'm'), - (0x24DD, 'M', u'n'), - (0x24DE, 'M', u'o'), - (0x24DF, 'M', u'p'), - (0x24E0, 'M', u'q'), - (0x24E1, 'M', u'r'), - (0x24E2, 'M', u's'), - (0x24E3, 'M', u't'), - (0x24E4, 'M', u'u'), - (0x24E5, 'M', u'v'), - (0x24E6, 'M', u'w'), - (0x24E7, 'M', u'x'), - (0x24E8, 'M', u'y'), - (0x24E9, 'M', u'z'), - (0x24EA, 'M', u'0'), - (0x24EB, 'V'), - (0x2A0C, 'M', u'∫∫∫∫'), - (0x2A0D, 'V'), - (0x2A74, '3', u'::='), - (0x2A75, '3', u'=='), - (0x2A76, '3', u'==='), - (0x2A77, 'V'), - (0x2ADC, 'M', u'⫝̸'), - (0x2ADD, 'V'), - (0x2B74, 'X'), - (0x2B76, 'V'), - (0x2B96, 'X'), - (0x2B98, 'V'), - (0x2BBA, 'X'), - (0x2BBD, 'V'), - (0x2BC9, 'X'), - (0x2BCA, 'V'), - (0x2BD3, 'X'), - (0x2BEC, 'V'), - (0x2BF0, 'X'), - (0x2C00, 'M', u'ⰰ'), - (0x2C01, 'M', u'ⰱ'), - (0x2C02, 'M', u'ⰲ'), - (0x2C03, 'M', u'ⰳ'), - (0x2C04, 'M', u'ⰴ'), - (0x2C05, 'M', u'ⰵ'), - (0x2C06, 'M', u'ⰶ'), - (0x2C07, 'M', u'ⰷ'), - (0x2C08, 'M', u'ⰸ'), - (0x2C09, 'M', u'ⰹ'), - (0x2C0A, 'M', u'ⰺ'), - (0x2C0B, 'M', u'ⰻ'), - (0x2C0C, 'M', u'ⰼ'), - (0x2C0D, 'M', u'ⰽ'), - (0x2C0E, 'M', u'ⰾ'), - (0x2C0F, 'M', u'ⰿ'), - (0x2C10, 'M', u'ⱀ'), - (0x2C11, 'M', u'ⱁ'), - (0x2C12, 'M', u'ⱂ'), - (0x2C13, 'M', u'ⱃ'), - (0x2C14, 'M', u'ⱄ'), - (0x2C15, 'M', u'ⱅ'), - (0x2C16, 'M', u'ⱆ'), - (0x2C17, 'M', u'ⱇ'), - (0x2C18, 'M', u'ⱈ'), - (0x2C19, 'M', u'ⱉ'), - (0x2C1A, 'M', u'ⱊ'), - (0x2C1B, 'M', u'ⱋ'), - (0x2C1C, 'M', u'ⱌ'), - (0x2C1D, 'M', u'ⱍ'), - (0x2C1E, 'M', u'ⱎ'), - (0x2C1F, 'M', u'ⱏ'), - (0x2C20, 'M', u'ⱐ'), - (0x2C21, 'M', u'ⱑ'), - (0x2C22, 'M', u'ⱒ'), - (0x2C23, 'M', u'ⱓ'), - (0x2C24, 'M', u'ⱔ'), - (0x2C25, 'M', u'ⱕ'), - (0x2C26, 'M', u'ⱖ'), - (0x2C27, 'M', u'ⱗ'), - (0x2C28, 'M', u'ⱘ'), - (0x2C29, 'M', u'ⱙ'), - (0x2C2A, 'M', u'ⱚ'), - (0x2C2B, 'M', u'ⱛ'), - (0x2C2C, 'M', u'ⱜ'), - (0x2C2D, 'M', u'ⱝ'), - (0x2C2E, 'M', u'ⱞ'), - (0x2C2F, 'X'), - (0x2C30, 'V'), - (0x2C5F, 'X'), - (0x2C60, 'M', u'ⱡ'), - (0x2C61, 'V'), - (0x2C62, 'M', u'ɫ'), - (0x2C63, 'M', u'ᵽ'), - (0x2C64, 'M', u'ɽ'), - ] - -def _seg_25(): - return [ - (0x2C65, 'V'), - (0x2C67, 'M', u'ⱨ'), - (0x2C68, 'V'), - (0x2C69, 'M', u'ⱪ'), - (0x2C6A, 'V'), - (0x2C6B, 'M', u'ⱬ'), - (0x2C6C, 'V'), - (0x2C6D, 'M', u'ɑ'), - (0x2C6E, 'M', u'ɱ'), - (0x2C6F, 'M', u'ɐ'), - (0x2C70, 'M', u'ɒ'), - (0x2C71, 'V'), - (0x2C72, 'M', u'ⱳ'), - (0x2C73, 'V'), - (0x2C75, 'M', u'ⱶ'), - (0x2C76, 'V'), - (0x2C7C, 'M', u'j'), - (0x2C7D, 'M', u'v'), - (0x2C7E, 'M', u'ȿ'), - (0x2C7F, 'M', u'ɀ'), - (0x2C80, 'M', u'ⲁ'), - (0x2C81, 'V'), - (0x2C82, 'M', u'ⲃ'), - (0x2C83, 'V'), - (0x2C84, 'M', u'ⲅ'), - (0x2C85, 'V'), - (0x2C86, 'M', u'ⲇ'), - (0x2C87, 'V'), - (0x2C88, 'M', u'ⲉ'), - (0x2C89, 'V'), - (0x2C8A, 'M', u'ⲋ'), - (0x2C8B, 'V'), - (0x2C8C, 'M', u'ⲍ'), - (0x2C8D, 'V'), - (0x2C8E, 'M', u'ⲏ'), - (0x2C8F, 'V'), - (0x2C90, 'M', u'ⲑ'), - (0x2C91, 'V'), - (0x2C92, 'M', u'ⲓ'), - (0x2C93, 'V'), - (0x2C94, 'M', u'ⲕ'), - (0x2C95, 'V'), - (0x2C96, 'M', u'ⲗ'), - (0x2C97, 'V'), - (0x2C98, 'M', u'ⲙ'), - (0x2C99, 'V'), - (0x2C9A, 'M', u'ⲛ'), - (0x2C9B, 'V'), - (0x2C9C, 'M', u'ⲝ'), - (0x2C9D, 'V'), - (0x2C9E, 'M', u'ⲟ'), - (0x2C9F, 'V'), - (0x2CA0, 'M', u'ⲡ'), - (0x2CA1, 'V'), - (0x2CA2, 'M', u'ⲣ'), - (0x2CA3, 'V'), - (0x2CA4, 'M', u'ⲥ'), - (0x2CA5, 'V'), - (0x2CA6, 'M', u'ⲧ'), - (0x2CA7, 'V'), - (0x2CA8, 'M', u'ⲩ'), - (0x2CA9, 'V'), - (0x2CAA, 'M', u'ⲫ'), - (0x2CAB, 'V'), - (0x2CAC, 'M', u'ⲭ'), - (0x2CAD, 'V'), - (0x2CAE, 'M', u'ⲯ'), - (0x2CAF, 'V'), - (0x2CB0, 'M', u'ⲱ'), - (0x2CB1, 'V'), - (0x2CB2, 'M', u'ⲳ'), - (0x2CB3, 'V'), - (0x2CB4, 'M', u'ⲵ'), - (0x2CB5, 'V'), - (0x2CB6, 'M', u'ⲷ'), - (0x2CB7, 'V'), - (0x2CB8, 'M', u'ⲹ'), - (0x2CB9, 'V'), - (0x2CBA, 'M', u'ⲻ'), - (0x2CBB, 'V'), - (0x2CBC, 'M', u'ⲽ'), - (0x2CBD, 'V'), - (0x2CBE, 'M', u'ⲿ'), - (0x2CBF, 'V'), - (0x2CC0, 'M', u'ⳁ'), - (0x2CC1, 'V'), - (0x2CC2, 'M', u'ⳃ'), - (0x2CC3, 'V'), - (0x2CC4, 'M', u'ⳅ'), - (0x2CC5, 'V'), - (0x2CC6, 'M', u'ⳇ'), - (0x2CC7, 'V'), - (0x2CC8, 'M', u'ⳉ'), - (0x2CC9, 'V'), - (0x2CCA, 'M', u'ⳋ'), - (0x2CCB, 'V'), - (0x2CCC, 'M', u'ⳍ'), - (0x2CCD, 'V'), - (0x2CCE, 'M', u'ⳏ'), - (0x2CCF, 'V'), - ] - -def _seg_26(): - return [ - (0x2CD0, 'M', u'ⳑ'), - (0x2CD1, 'V'), - (0x2CD2, 'M', u'ⳓ'), - (0x2CD3, 'V'), - (0x2CD4, 'M', u'ⳕ'), - (0x2CD5, 'V'), - (0x2CD6, 'M', u'ⳗ'), - (0x2CD7, 'V'), - (0x2CD8, 'M', u'ⳙ'), - (0x2CD9, 'V'), - (0x2CDA, 'M', u'ⳛ'), - (0x2CDB, 'V'), - (0x2CDC, 'M', u'ⳝ'), - (0x2CDD, 'V'), - (0x2CDE, 'M', u'ⳟ'), - (0x2CDF, 'V'), - (0x2CE0, 'M', u'ⳡ'), - (0x2CE1, 'V'), - (0x2CE2, 'M', u'ⳣ'), - (0x2CE3, 'V'), - (0x2CEB, 'M', u'ⳬ'), - (0x2CEC, 'V'), - (0x2CED, 'M', u'ⳮ'), - (0x2CEE, 'V'), - (0x2CF2, 'M', u'ⳳ'), - (0x2CF3, 'V'), - (0x2CF4, 'X'), - (0x2CF9, 'V'), - (0x2D26, 'X'), - (0x2D27, 'V'), - (0x2D28, 'X'), - (0x2D2D, 'V'), - (0x2D2E, 'X'), - (0x2D30, 'V'), - (0x2D68, 'X'), - (0x2D6F, 'M', u'ⵡ'), - (0x2D70, 'V'), - (0x2D71, 'X'), - (0x2D7F, 'V'), - (0x2D97, 'X'), - (0x2DA0, 'V'), - (0x2DA7, 'X'), - (0x2DA8, 'V'), - (0x2DAF, 'X'), - (0x2DB0, 'V'), - (0x2DB7, 'X'), - (0x2DB8, 'V'), - (0x2DBF, 'X'), - (0x2DC0, 'V'), - (0x2DC7, 'X'), - (0x2DC8, 'V'), - (0x2DCF, 'X'), - (0x2DD0, 'V'), - (0x2DD7, 'X'), - (0x2DD8, 'V'), - (0x2DDF, 'X'), - (0x2DE0, 'V'), - (0x2E4A, 'X'), - (0x2E80, 'V'), - (0x2E9A, 'X'), - (0x2E9B, 'V'), - (0x2E9F, 'M', u'母'), - (0x2EA0, 'V'), - (0x2EF3, 'M', u'龟'), - (0x2EF4, 'X'), - (0x2F00, 'M', u'一'), - (0x2F01, 'M', u'丨'), - (0x2F02, 'M', u'丶'), - (0x2F03, 'M', u'丿'), - (0x2F04, 'M', u'乙'), - (0x2F05, 'M', u'亅'), - (0x2F06, 'M', u'二'), - (0x2F07, 'M', u'亠'), - (0x2F08, 'M', u'人'), - (0x2F09, 'M', u'儿'), - (0x2F0A, 'M', u'入'), - (0x2F0B, 'M', u'八'), - (0x2F0C, 'M', u'冂'), - (0x2F0D, 'M', u'冖'), - (0x2F0E, 'M', u'冫'), - (0x2F0F, 'M', u'几'), - (0x2F10, 'M', u'凵'), - (0x2F11, 'M', u'刀'), - (0x2F12, 'M', u'力'), - (0x2F13, 'M', u'勹'), - (0x2F14, 'M', u'匕'), - (0x2F15, 'M', u'匚'), - (0x2F16, 'M', u'匸'), - (0x2F17, 'M', u'十'), - (0x2F18, 'M', u'卜'), - (0x2F19, 'M', u'卩'), - (0x2F1A, 'M', u'厂'), - (0x2F1B, 'M', u'厶'), - (0x2F1C, 'M', u'又'), - (0x2F1D, 'M', u'口'), - (0x2F1E, 'M', u'囗'), - (0x2F1F, 'M', u'土'), - (0x2F20, 'M', u'士'), - (0x2F21, 'M', u'夂'), - (0x2F22, 'M', u'夊'), - ] - -def _seg_27(): - return [ - (0x2F23, 'M', u'夕'), - (0x2F24, 'M', u'大'), - (0x2F25, 'M', u'女'), - (0x2F26, 'M', u'子'), - (0x2F27, 'M', u'宀'), - (0x2F28, 'M', u'寸'), - (0x2F29, 'M', u'小'), - (0x2F2A, 'M', u'尢'), - (0x2F2B, 'M', u'尸'), - (0x2F2C, 'M', u'屮'), - (0x2F2D, 'M', u'山'), - (0x2F2E, 'M', u'巛'), - (0x2F2F, 'M', u'工'), - (0x2F30, 'M', u'己'), - (0x2F31, 'M', u'巾'), - (0x2F32, 'M', u'干'), - (0x2F33, 'M', u'幺'), - (0x2F34, 'M', u'广'), - (0x2F35, 'M', u'廴'), - (0x2F36, 'M', u'廾'), - (0x2F37, 'M', u'弋'), - (0x2F38, 'M', u'弓'), - (0x2F39, 'M', u'彐'), - (0x2F3A, 'M', u'彡'), - (0x2F3B, 'M', u'彳'), - (0x2F3C, 'M', u'心'), - (0x2F3D, 'M', u'戈'), - (0x2F3E, 'M', u'戶'), - (0x2F3F, 'M', u'手'), - (0x2F40, 'M', u'支'), - (0x2F41, 'M', u'攴'), - (0x2F42, 'M', u'文'), - (0x2F43, 'M', u'斗'), - (0x2F44, 'M', u'斤'), - (0x2F45, 'M', u'方'), - (0x2F46, 'M', u'无'), - (0x2F47, 'M', u'日'), - (0x2F48, 'M', u'曰'), - (0x2F49, 'M', u'月'), - (0x2F4A, 'M', u'木'), - (0x2F4B, 'M', u'欠'), - (0x2F4C, 'M', u'止'), - (0x2F4D, 'M', u'歹'), - (0x2F4E, 'M', u'殳'), - (0x2F4F, 'M', u'毋'), - (0x2F50, 'M', u'比'), - (0x2F51, 'M', u'毛'), - (0x2F52, 'M', u'氏'), - (0x2F53, 'M', u'气'), - (0x2F54, 'M', u'水'), - (0x2F55, 'M', u'火'), - (0x2F56, 'M', u'爪'), - (0x2F57, 'M', u'父'), - (0x2F58, 'M', u'爻'), - (0x2F59, 'M', u'爿'), - (0x2F5A, 'M', u'片'), - (0x2F5B, 'M', u'牙'), - (0x2F5C, 'M', u'牛'), - (0x2F5D, 'M', u'犬'), - (0x2F5E, 'M', u'玄'), - (0x2F5F, 'M', u'玉'), - (0x2F60, 'M', u'瓜'), - (0x2F61, 'M', u'瓦'), - (0x2F62, 'M', u'甘'), - (0x2F63, 'M', u'生'), - (0x2F64, 'M', u'用'), - (0x2F65, 'M', u'田'), - (0x2F66, 'M', u'疋'), - (0x2F67, 'M', u'疒'), - (0x2F68, 'M', u'癶'), - (0x2F69, 'M', u'白'), - (0x2F6A, 'M', u'皮'), - (0x2F6B, 'M', u'皿'), - (0x2F6C, 'M', u'目'), - (0x2F6D, 'M', u'矛'), - (0x2F6E, 'M', u'矢'), - (0x2F6F, 'M', u'石'), - (0x2F70, 'M', u'示'), - (0x2F71, 'M', u'禸'), - (0x2F72, 'M', u'禾'), - (0x2F73, 'M', u'穴'), - (0x2F74, 'M', u'立'), - (0x2F75, 'M', u'竹'), - (0x2F76, 'M', u'米'), - (0x2F77, 'M', u'糸'), - (0x2F78, 'M', u'缶'), - (0x2F79, 'M', u'网'), - (0x2F7A, 'M', u'羊'), - (0x2F7B, 'M', u'羽'), - (0x2F7C, 'M', u'老'), - (0x2F7D, 'M', u'而'), - (0x2F7E, 'M', u'耒'), - (0x2F7F, 'M', u'耳'), - (0x2F80, 'M', u'聿'), - (0x2F81, 'M', u'肉'), - (0x2F82, 'M', u'臣'), - (0x2F83, 'M', u'自'), - (0x2F84, 'M', u'至'), - (0x2F85, 'M', u'臼'), - (0x2F86, 'M', u'舌'), - ] - -def _seg_28(): - return [ - (0x2F87, 'M', u'舛'), - (0x2F88, 'M', u'舟'), - (0x2F89, 'M', u'艮'), - (0x2F8A, 'M', u'色'), - (0x2F8B, 'M', u'艸'), - (0x2F8C, 'M', u'虍'), - (0x2F8D, 'M', u'虫'), - (0x2F8E, 'M', u'血'), - (0x2F8F, 'M', u'行'), - (0x2F90, 'M', u'衣'), - (0x2F91, 'M', u'襾'), - (0x2F92, 'M', u'見'), - (0x2F93, 'M', u'角'), - (0x2F94, 'M', u'言'), - (0x2F95, 'M', u'谷'), - (0x2F96, 'M', u'豆'), - (0x2F97, 'M', u'豕'), - (0x2F98, 'M', u'豸'), - (0x2F99, 'M', u'貝'), - (0x2F9A, 'M', u'赤'), - (0x2F9B, 'M', u'走'), - (0x2F9C, 'M', u'足'), - (0x2F9D, 'M', u'身'), - (0x2F9E, 'M', u'車'), - (0x2F9F, 'M', u'辛'), - (0x2FA0, 'M', u'辰'), - (0x2FA1, 'M', u'辵'), - (0x2FA2, 'M', u'邑'), - (0x2FA3, 'M', u'酉'), - (0x2FA4, 'M', u'釆'), - (0x2FA5, 'M', u'里'), - (0x2FA6, 'M', u'金'), - (0x2FA7, 'M', u'長'), - (0x2FA8, 'M', u'門'), - (0x2FA9, 'M', u'阜'), - (0x2FAA, 'M', u'隶'), - (0x2FAB, 'M', u'隹'), - (0x2FAC, 'M', u'雨'), - (0x2FAD, 'M', u'靑'), - (0x2FAE, 'M', u'非'), - (0x2FAF, 'M', u'面'), - (0x2FB0, 'M', u'革'), - (0x2FB1, 'M', u'韋'), - (0x2FB2, 'M', u'韭'), - (0x2FB3, 'M', u'音'), - (0x2FB4, 'M', u'頁'), - (0x2FB5, 'M', u'風'), - (0x2FB6, 'M', u'飛'), - (0x2FB7, 'M', u'食'), - (0x2FB8, 'M', u'首'), - (0x2FB9, 'M', u'香'), - (0x2FBA, 'M', u'馬'), - (0x2FBB, 'M', u'骨'), - (0x2FBC, 'M', u'高'), - (0x2FBD, 'M', u'髟'), - (0x2FBE, 'M', u'鬥'), - (0x2FBF, 'M', u'鬯'), - (0x2FC0, 'M', u'鬲'), - (0x2FC1, 'M', u'鬼'), - (0x2FC2, 'M', u'魚'), - (0x2FC3, 'M', u'鳥'), - (0x2FC4, 'M', u'鹵'), - (0x2FC5, 'M', u'鹿'), - (0x2FC6, 'M', u'麥'), - (0x2FC7, 'M', u'麻'), - (0x2FC8, 'M', u'黃'), - (0x2FC9, 'M', u'黍'), - (0x2FCA, 'M', u'黑'), - (0x2FCB, 'M', u'黹'), - (0x2FCC, 'M', u'黽'), - (0x2FCD, 'M', u'鼎'), - (0x2FCE, 'M', u'鼓'), - (0x2FCF, 'M', u'鼠'), - (0x2FD0, 'M', u'鼻'), - (0x2FD1, 'M', u'齊'), - (0x2FD2, 'M', u'齒'), - (0x2FD3, 'M', u'龍'), - (0x2FD4, 'M', u'龜'), - (0x2FD5, 'M', u'龠'), - (0x2FD6, 'X'), - (0x3000, '3', u' '), - (0x3001, 'V'), - (0x3002, 'M', u'.'), - (0x3003, 'V'), - (0x3036, 'M', u'〒'), - (0x3037, 'V'), - (0x3038, 'M', u'十'), - (0x3039, 'M', u'卄'), - (0x303A, 'M', u'卅'), - (0x303B, 'V'), - (0x3040, 'X'), - (0x3041, 'V'), - (0x3097, 'X'), - (0x3099, 'V'), - (0x309B, '3', u' ゙'), - (0x309C, '3', u' ゚'), - (0x309D, 'V'), - (0x309F, 'M', u'より'), - (0x30A0, 'V'), - (0x30FF, 'M', u'コト'), - ] - -def _seg_29(): - return [ - (0x3100, 'X'), - (0x3105, 'V'), - (0x312F, 'X'), - (0x3131, 'M', u'ᄀ'), - (0x3132, 'M', u'ᄁ'), - (0x3133, 'M', u'ᆪ'), - (0x3134, 'M', u'ᄂ'), - (0x3135, 'M', u'ᆬ'), - (0x3136, 'M', u'ᆭ'), - (0x3137, 'M', u'ᄃ'), - (0x3138, 'M', u'ᄄ'), - (0x3139, 'M', u'ᄅ'), - (0x313A, 'M', u'ᆰ'), - (0x313B, 'M', u'ᆱ'), - (0x313C, 'M', u'ᆲ'), - (0x313D, 'M', u'ᆳ'), - (0x313E, 'M', u'ᆴ'), - (0x313F, 'M', u'ᆵ'), - (0x3140, 'M', u'ᄚ'), - (0x3141, 'M', u'ᄆ'), - (0x3142, 'M', u'ᄇ'), - (0x3143, 'M', u'ᄈ'), - (0x3144, 'M', u'ᄡ'), - (0x3145, 'M', u'ᄉ'), - (0x3146, 'M', u'ᄊ'), - (0x3147, 'M', u'ᄋ'), - (0x3148, 'M', u'ᄌ'), - (0x3149, 'M', u'ᄍ'), - (0x314A, 'M', u'ᄎ'), - (0x314B, 'M', u'ᄏ'), - (0x314C, 'M', u'ᄐ'), - (0x314D, 'M', u'ᄑ'), - (0x314E, 'M', u'ᄒ'), - (0x314F, 'M', u'ᅡ'), - (0x3150, 'M', u'ᅢ'), - (0x3151, 'M', u'ᅣ'), - (0x3152, 'M', u'ᅤ'), - (0x3153, 'M', u'ᅥ'), - (0x3154, 'M', u'ᅦ'), - (0x3155, 'M', u'ᅧ'), - (0x3156, 'M', u'ᅨ'), - (0x3157, 'M', u'ᅩ'), - (0x3158, 'M', u'ᅪ'), - (0x3159, 'M', u'ᅫ'), - (0x315A, 'M', u'ᅬ'), - (0x315B, 'M', u'ᅭ'), - (0x315C, 'M', u'ᅮ'), - (0x315D, 'M', u'ᅯ'), - (0x315E, 'M', u'ᅰ'), - (0x315F, 'M', u'ᅱ'), - (0x3160, 'M', u'ᅲ'), - (0x3161, 'M', u'ᅳ'), - (0x3162, 'M', u'ᅴ'), - (0x3163, 'M', u'ᅵ'), - (0x3164, 'X'), - (0x3165, 'M', u'ᄔ'), - (0x3166, 'M', u'ᄕ'), - (0x3167, 'M', u'ᇇ'), - (0x3168, 'M', u'ᇈ'), - (0x3169, 'M', u'ᇌ'), - (0x316A, 'M', u'ᇎ'), - (0x316B, 'M', u'ᇓ'), - (0x316C, 'M', u'ᇗ'), - (0x316D, 'M', u'ᇙ'), - (0x316E, 'M', u'ᄜ'), - (0x316F, 'M', u'ᇝ'), - (0x3170, 'M', u'ᇟ'), - (0x3171, 'M', u'ᄝ'), - (0x3172, 'M', u'ᄞ'), - (0x3173, 'M', u'ᄠ'), - (0x3174, 'M', u'ᄢ'), - (0x3175, 'M', u'ᄣ'), - (0x3176, 'M', u'ᄧ'), - (0x3177, 'M', u'ᄩ'), - (0x3178, 'M', u'ᄫ'), - (0x3179, 'M', u'ᄬ'), - (0x317A, 'M', u'ᄭ'), - (0x317B, 'M', u'ᄮ'), - (0x317C, 'M', u'ᄯ'), - (0x317D, 'M', u'ᄲ'), - (0x317E, 'M', u'ᄶ'), - (0x317F, 'M', u'ᅀ'), - (0x3180, 'M', u'ᅇ'), - (0x3181, 'M', u'ᅌ'), - (0x3182, 'M', u'ᇱ'), - (0x3183, 'M', u'ᇲ'), - (0x3184, 'M', u'ᅗ'), - (0x3185, 'M', u'ᅘ'), - (0x3186, 'M', u'ᅙ'), - (0x3187, 'M', u'ᆄ'), - (0x3188, 'M', u'ᆅ'), - (0x3189, 'M', u'ᆈ'), - (0x318A, 'M', u'ᆑ'), - (0x318B, 'M', u'ᆒ'), - (0x318C, 'M', u'ᆔ'), - (0x318D, 'M', u'ᆞ'), - (0x318E, 'M', u'ᆡ'), - (0x318F, 'X'), - (0x3190, 'V'), - (0x3192, 'M', u'一'), - ] - -def _seg_30(): - return [ - (0x3193, 'M', u'二'), - (0x3194, 'M', u'三'), - (0x3195, 'M', u'四'), - (0x3196, 'M', u'上'), - (0x3197, 'M', u'中'), - (0x3198, 'M', u'下'), - (0x3199, 'M', u'甲'), - (0x319A, 'M', u'乙'), - (0x319B, 'M', u'丙'), - (0x319C, 'M', u'丁'), - (0x319D, 'M', u'天'), - (0x319E, 'M', u'地'), - (0x319F, 'M', u'人'), - (0x31A0, 'V'), - (0x31BB, 'X'), - (0x31C0, 'V'), - (0x31E4, 'X'), - (0x31F0, 'V'), - (0x3200, '3', u'(ᄀ)'), - (0x3201, '3', u'(ᄂ)'), - (0x3202, '3', u'(ᄃ)'), - (0x3203, '3', u'(ᄅ)'), - (0x3204, '3', u'(ᄆ)'), - (0x3205, '3', u'(ᄇ)'), - (0x3206, '3', u'(ᄉ)'), - (0x3207, '3', u'(ᄋ)'), - (0x3208, '3', u'(ᄌ)'), - (0x3209, '3', u'(ᄎ)'), - (0x320A, '3', u'(ᄏ)'), - (0x320B, '3', u'(ᄐ)'), - (0x320C, '3', u'(ᄑ)'), - (0x320D, '3', u'(ᄒ)'), - (0x320E, '3', u'(가)'), - (0x320F, '3', u'(나)'), - (0x3210, '3', u'(다)'), - (0x3211, '3', u'(라)'), - (0x3212, '3', u'(마)'), - (0x3213, '3', u'(바)'), - (0x3214, '3', u'(사)'), - (0x3215, '3', u'(아)'), - (0x3216, '3', u'(자)'), - (0x3217, '3', u'(차)'), - (0x3218, '3', u'(카)'), - (0x3219, '3', u'(타)'), - (0x321A, '3', u'(파)'), - (0x321B, '3', u'(하)'), - (0x321C, '3', u'(주)'), - (0x321D, '3', u'(오전)'), - (0x321E, '3', u'(오후)'), - (0x321F, 'X'), - (0x3220, '3', u'(一)'), - (0x3221, '3', u'(二)'), - (0x3222, '3', u'(三)'), - (0x3223, '3', u'(四)'), - (0x3224, '3', u'(五)'), - (0x3225, '3', u'(六)'), - (0x3226, '3', u'(七)'), - (0x3227, '3', u'(八)'), - (0x3228, '3', u'(九)'), - (0x3229, '3', u'(十)'), - (0x322A, '3', u'(月)'), - (0x322B, '3', u'(火)'), - (0x322C, '3', u'(水)'), - (0x322D, '3', u'(木)'), - (0x322E, '3', u'(金)'), - (0x322F, '3', u'(土)'), - (0x3230, '3', u'(日)'), - (0x3231, '3', u'(株)'), - (0x3232, '3', u'(有)'), - (0x3233, '3', u'(社)'), - (0x3234, '3', u'(名)'), - (0x3235, '3', u'(特)'), - (0x3236, '3', u'(財)'), - (0x3237, '3', u'(祝)'), - (0x3238, '3', u'(労)'), - (0x3239, '3', u'(代)'), - (0x323A, '3', u'(呼)'), - (0x323B, '3', u'(学)'), - (0x323C, '3', u'(監)'), - (0x323D, '3', u'(企)'), - (0x323E, '3', u'(資)'), - (0x323F, '3', u'(協)'), - (0x3240, '3', u'(祭)'), - (0x3241, '3', u'(休)'), - (0x3242, '3', u'(自)'), - (0x3243, '3', u'(至)'), - (0x3244, 'M', u'問'), - (0x3245, 'M', u'幼'), - (0x3246, 'M', u'文'), - (0x3247, 'M', u'箏'), - (0x3248, 'V'), - (0x3250, 'M', u'pte'), - (0x3251, 'M', u'21'), - (0x3252, 'M', u'22'), - (0x3253, 'M', u'23'), - (0x3254, 'M', u'24'), - (0x3255, 'M', u'25'), - (0x3256, 'M', u'26'), - (0x3257, 'M', u'27'), - (0x3258, 'M', u'28'), - ] - -def _seg_31(): - return [ - (0x3259, 'M', u'29'), - (0x325A, 'M', u'30'), - (0x325B, 'M', u'31'), - (0x325C, 'M', u'32'), - (0x325D, 'M', u'33'), - (0x325E, 'M', u'34'), - (0x325F, 'M', u'35'), - (0x3260, 'M', u'ᄀ'), - (0x3261, 'M', u'ᄂ'), - (0x3262, 'M', u'ᄃ'), - (0x3263, 'M', u'ᄅ'), - (0x3264, 'M', u'ᄆ'), - (0x3265, 'M', u'ᄇ'), - (0x3266, 'M', u'ᄉ'), - (0x3267, 'M', u'ᄋ'), - (0x3268, 'M', u'ᄌ'), - (0x3269, 'M', u'ᄎ'), - (0x326A, 'M', u'ᄏ'), - (0x326B, 'M', u'ᄐ'), - (0x326C, 'M', u'ᄑ'), - (0x326D, 'M', u'ᄒ'), - (0x326E, 'M', u'가'), - (0x326F, 'M', u'나'), - (0x3270, 'M', u'다'), - (0x3271, 'M', u'라'), - (0x3272, 'M', u'마'), - (0x3273, 'M', u'바'), - (0x3274, 'M', u'사'), - (0x3275, 'M', u'아'), - (0x3276, 'M', u'자'), - (0x3277, 'M', u'차'), - (0x3278, 'M', u'카'), - (0x3279, 'M', u'타'), - (0x327A, 'M', u'파'), - (0x327B, 'M', u'하'), - (0x327C, 'M', u'참고'), - (0x327D, 'M', u'주의'), - (0x327E, 'M', u'우'), - (0x327F, 'V'), - (0x3280, 'M', u'一'), - (0x3281, 'M', u'二'), - (0x3282, 'M', u'三'), - (0x3283, 'M', u'四'), - (0x3284, 'M', u'五'), - (0x3285, 'M', u'六'), - (0x3286, 'M', u'七'), - (0x3287, 'M', u'八'), - (0x3288, 'M', u'九'), - (0x3289, 'M', u'十'), - (0x328A, 'M', u'月'), - (0x328B, 'M', u'火'), - (0x328C, 'M', u'水'), - (0x328D, 'M', u'木'), - (0x328E, 'M', u'金'), - (0x328F, 'M', u'土'), - (0x3290, 'M', u'日'), - (0x3291, 'M', u'株'), - (0x3292, 'M', u'有'), - (0x3293, 'M', u'社'), - (0x3294, 'M', u'名'), - (0x3295, 'M', u'特'), - (0x3296, 'M', u'財'), - (0x3297, 'M', u'祝'), - (0x3298, 'M', u'労'), - (0x3299, 'M', u'秘'), - (0x329A, 'M', u'男'), - (0x329B, 'M', u'女'), - (0x329C, 'M', u'適'), - (0x329D, 'M', u'優'), - (0x329E, 'M', u'印'), - (0x329F, 'M', u'注'), - (0x32A0, 'M', u'項'), - (0x32A1, 'M', u'休'), - (0x32A2, 'M', u'写'), - (0x32A3, 'M', u'正'), - (0x32A4, 'M', u'上'), - (0x32A5, 'M', u'中'), - (0x32A6, 'M', u'下'), - (0x32A7, 'M', u'左'), - (0x32A8, 'M', u'右'), - (0x32A9, 'M', u'医'), - (0x32AA, 'M', u'宗'), - (0x32AB, 'M', u'学'), - (0x32AC, 'M', u'監'), - (0x32AD, 'M', u'企'), - (0x32AE, 'M', u'資'), - (0x32AF, 'M', u'協'), - (0x32B0, 'M', u'夜'), - (0x32B1, 'M', u'36'), - (0x32B2, 'M', u'37'), - (0x32B3, 'M', u'38'), - (0x32B4, 'M', u'39'), - (0x32B5, 'M', u'40'), - (0x32B6, 'M', u'41'), - (0x32B7, 'M', u'42'), - (0x32B8, 'M', u'43'), - (0x32B9, 'M', u'44'), - (0x32BA, 'M', u'45'), - (0x32BB, 'M', u'46'), - (0x32BC, 'M', u'47'), - ] - -def _seg_32(): - return [ - (0x32BD, 'M', u'48'), - (0x32BE, 'M', u'49'), - (0x32BF, 'M', u'50'), - (0x32C0, 'M', u'1月'), - (0x32C1, 'M', u'2月'), - (0x32C2, 'M', u'3月'), - (0x32C3, 'M', u'4月'), - (0x32C4, 'M', u'5月'), - (0x32C5, 'M', u'6月'), - (0x32C6, 'M', u'7月'), - (0x32C7, 'M', u'8月'), - (0x32C8, 'M', u'9月'), - (0x32C9, 'M', u'10月'), - (0x32CA, 'M', u'11月'), - (0x32CB, 'M', u'12月'), - (0x32CC, 'M', u'hg'), - (0x32CD, 'M', u'erg'), - (0x32CE, 'M', u'ev'), - (0x32CF, 'M', u'ltd'), - (0x32D0, 'M', u'ア'), - (0x32D1, 'M', u'イ'), - (0x32D2, 'M', u'ウ'), - (0x32D3, 'M', u'エ'), - (0x32D4, 'M', u'オ'), - (0x32D5, 'M', u'カ'), - (0x32D6, 'M', u'キ'), - (0x32D7, 'M', u'ク'), - (0x32D8, 'M', u'ケ'), - (0x32D9, 'M', u'コ'), - (0x32DA, 'M', u'サ'), - (0x32DB, 'M', u'シ'), - (0x32DC, 'M', u'ス'), - (0x32DD, 'M', u'セ'), - (0x32DE, 'M', u'ソ'), - (0x32DF, 'M', u'タ'), - (0x32E0, 'M', u'チ'), - (0x32E1, 'M', u'ツ'), - (0x32E2, 'M', u'テ'), - (0x32E3, 'M', u'ト'), - (0x32E4, 'M', u'ナ'), - (0x32E5, 'M', u'ニ'), - (0x32E6, 'M', u'ヌ'), - (0x32E7, 'M', u'ネ'), - (0x32E8, 'M', u'ノ'), - (0x32E9, 'M', u'ハ'), - (0x32EA, 'M', u'ヒ'), - (0x32EB, 'M', u'フ'), - (0x32EC, 'M', u'ヘ'), - (0x32ED, 'M', u'ホ'), - (0x32EE, 'M', u'マ'), - (0x32EF, 'M', u'ミ'), - (0x32F0, 'M', u'ム'), - (0x32F1, 'M', u'メ'), - (0x32F2, 'M', u'モ'), - (0x32F3, 'M', u'ヤ'), - (0x32F4, 'M', u'ユ'), - (0x32F5, 'M', u'ヨ'), - (0x32F6, 'M', u'ラ'), - (0x32F7, 'M', u'リ'), - (0x32F8, 'M', u'ル'), - (0x32F9, 'M', u'レ'), - (0x32FA, 'M', u'ロ'), - (0x32FB, 'M', u'ワ'), - (0x32FC, 'M', u'ヰ'), - (0x32FD, 'M', u'ヱ'), - (0x32FE, 'M', u'ヲ'), - (0x32FF, 'X'), - (0x3300, 'M', u'アパート'), - (0x3301, 'M', u'アルファ'), - (0x3302, 'M', u'アンペア'), - (0x3303, 'M', u'アール'), - (0x3304, 'M', u'イニング'), - (0x3305, 'M', u'インチ'), - (0x3306, 'M', u'ウォン'), - (0x3307, 'M', u'エスクード'), - (0x3308, 'M', u'エーカー'), - (0x3309, 'M', u'オンス'), - (0x330A, 'M', u'オーム'), - (0x330B, 'M', u'カイリ'), - (0x330C, 'M', u'カラット'), - (0x330D, 'M', u'カロリー'), - (0x330E, 'M', u'ガロン'), - (0x330F, 'M', u'ガンマ'), - (0x3310, 'M', u'ギガ'), - (0x3311, 'M', u'ギニー'), - (0x3312, 'M', u'キュリー'), - (0x3313, 'M', u'ギルダー'), - (0x3314, 'M', u'キロ'), - (0x3315, 'M', u'キログラム'), - (0x3316, 'M', u'キロメートル'), - (0x3317, 'M', u'キロワット'), - (0x3318, 'M', u'グラム'), - (0x3319, 'M', u'グラムトン'), - (0x331A, 'M', u'クルゼイロ'), - (0x331B, 'M', u'クローネ'), - (0x331C, 'M', u'ケース'), - (0x331D, 'M', u'コルナ'), - (0x331E, 'M', u'コーポ'), - (0x331F, 'M', u'サイクル'), - (0x3320, 'M', u'サンチーム'), - ] - -def _seg_33(): - return [ - (0x3321, 'M', u'シリング'), - (0x3322, 'M', u'センチ'), - (0x3323, 'M', u'セント'), - (0x3324, 'M', u'ダース'), - (0x3325, 'M', u'デシ'), - (0x3326, 'M', u'ドル'), - (0x3327, 'M', u'トン'), - (0x3328, 'M', u'ナノ'), - (0x3329, 'M', u'ノット'), - (0x332A, 'M', u'ハイツ'), - (0x332B, 'M', u'パーセント'), - (0x332C, 'M', u'パーツ'), - (0x332D, 'M', u'バーレル'), - (0x332E, 'M', u'ピアストル'), - (0x332F, 'M', u'ピクル'), - (0x3330, 'M', u'ピコ'), - (0x3331, 'M', u'ビル'), - (0x3332, 'M', u'ファラッド'), - (0x3333, 'M', u'フィート'), - (0x3334, 'M', u'ブッシェル'), - (0x3335, 'M', u'フラン'), - (0x3336, 'M', u'ヘクタール'), - (0x3337, 'M', u'ペソ'), - (0x3338, 'M', u'ペニヒ'), - (0x3339, 'M', u'ヘルツ'), - (0x333A, 'M', u'ペンス'), - (0x333B, 'M', u'ページ'), - (0x333C, 'M', u'ベータ'), - (0x333D, 'M', u'ポイント'), - (0x333E, 'M', u'ボルト'), - (0x333F, 'M', u'ホン'), - (0x3340, 'M', u'ポンド'), - (0x3341, 'M', u'ホール'), - (0x3342, 'M', u'ホーン'), - (0x3343, 'M', u'マイクロ'), - (0x3344, 'M', u'マイル'), - (0x3345, 'M', u'マッハ'), - (0x3346, 'M', u'マルク'), - (0x3347, 'M', u'マンション'), - (0x3348, 'M', u'ミクロン'), - (0x3349, 'M', u'ミリ'), - (0x334A, 'M', u'ミリバール'), - (0x334B, 'M', u'メガ'), - (0x334C, 'M', u'メガトン'), - (0x334D, 'M', u'メートル'), - (0x334E, 'M', u'ヤード'), - (0x334F, 'M', u'ヤール'), - (0x3350, 'M', u'ユアン'), - (0x3351, 'M', u'リットル'), - (0x3352, 'M', u'リラ'), - (0x3353, 'M', u'ルピー'), - (0x3354, 'M', u'ルーブル'), - (0x3355, 'M', u'レム'), - (0x3356, 'M', u'レントゲン'), - (0x3357, 'M', u'ワット'), - (0x3358, 'M', u'0点'), - (0x3359, 'M', u'1点'), - (0x335A, 'M', u'2点'), - (0x335B, 'M', u'3点'), - (0x335C, 'M', u'4点'), - (0x335D, 'M', u'5点'), - (0x335E, 'M', u'6点'), - (0x335F, 'M', u'7点'), - (0x3360, 'M', u'8点'), - (0x3361, 'M', u'9点'), - (0x3362, 'M', u'10点'), - (0x3363, 'M', u'11点'), - (0x3364, 'M', u'12点'), - (0x3365, 'M', u'13点'), - (0x3366, 'M', u'14点'), - (0x3367, 'M', u'15点'), - (0x3368, 'M', u'16点'), - (0x3369, 'M', u'17点'), - (0x336A, 'M', u'18点'), - (0x336B, 'M', u'19点'), - (0x336C, 'M', u'20点'), - (0x336D, 'M', u'21点'), - (0x336E, 'M', u'22点'), - (0x336F, 'M', u'23点'), - (0x3370, 'M', u'24点'), - (0x3371, 'M', u'hpa'), - (0x3372, 'M', u'da'), - (0x3373, 'M', u'au'), - (0x3374, 'M', u'bar'), - (0x3375, 'M', u'ov'), - (0x3376, 'M', u'pc'), - (0x3377, 'M', u'dm'), - (0x3378, 'M', u'dm2'), - (0x3379, 'M', u'dm3'), - (0x337A, 'M', u'iu'), - (0x337B, 'M', u'平成'), - (0x337C, 'M', u'昭和'), - (0x337D, 'M', u'大正'), - (0x337E, 'M', u'明治'), - (0x337F, 'M', u'株式会社'), - (0x3380, 'M', u'pa'), - (0x3381, 'M', u'na'), - (0x3382, 'M', u'μa'), - (0x3383, 'M', u'ma'), - (0x3384, 'M', u'ka'), - ] - -def _seg_34(): - return [ - (0x3385, 'M', u'kb'), - (0x3386, 'M', u'mb'), - (0x3387, 'M', u'gb'), - (0x3388, 'M', u'cal'), - (0x3389, 'M', u'kcal'), - (0x338A, 'M', u'pf'), - (0x338B, 'M', u'nf'), - (0x338C, 'M', u'μf'), - (0x338D, 'M', u'μg'), - (0x338E, 'M', u'mg'), - (0x338F, 'M', u'kg'), - (0x3390, 'M', u'hz'), - (0x3391, 'M', u'khz'), - (0x3392, 'M', u'mhz'), - (0x3393, 'M', u'ghz'), - (0x3394, 'M', u'thz'), - (0x3395, 'M', u'μl'), - (0x3396, 'M', u'ml'), - (0x3397, 'M', u'dl'), - (0x3398, 'M', u'kl'), - (0x3399, 'M', u'fm'), - (0x339A, 'M', u'nm'), - (0x339B, 'M', u'μm'), - (0x339C, 'M', u'mm'), - (0x339D, 'M', u'cm'), - (0x339E, 'M', u'km'), - (0x339F, 'M', u'mm2'), - (0x33A0, 'M', u'cm2'), - (0x33A1, 'M', u'm2'), - (0x33A2, 'M', u'km2'), - (0x33A3, 'M', u'mm3'), - (0x33A4, 'M', u'cm3'), - (0x33A5, 'M', u'm3'), - (0x33A6, 'M', u'km3'), - (0x33A7, 'M', u'm∕s'), - (0x33A8, 'M', u'm∕s2'), - (0x33A9, 'M', u'pa'), - (0x33AA, 'M', u'kpa'), - (0x33AB, 'M', u'mpa'), - (0x33AC, 'M', u'gpa'), - (0x33AD, 'M', u'rad'), - (0x33AE, 'M', u'rad∕s'), - (0x33AF, 'M', u'rad∕s2'), - (0x33B0, 'M', u'ps'), - (0x33B1, 'M', u'ns'), - (0x33B2, 'M', u'μs'), - (0x33B3, 'M', u'ms'), - (0x33B4, 'M', u'pv'), - (0x33B5, 'M', u'nv'), - (0x33B6, 'M', u'μv'), - (0x33B7, 'M', u'mv'), - (0x33B8, 'M', u'kv'), - (0x33B9, 'M', u'mv'), - (0x33BA, 'M', u'pw'), - (0x33BB, 'M', u'nw'), - (0x33BC, 'M', u'μw'), - (0x33BD, 'M', u'mw'), - (0x33BE, 'M', u'kw'), - (0x33BF, 'M', u'mw'), - (0x33C0, 'M', u'kω'), - (0x33C1, 'M', u'mω'), - (0x33C2, 'X'), - (0x33C3, 'M', u'bq'), - (0x33C4, 'M', u'cc'), - (0x33C5, 'M', u'cd'), - (0x33C6, 'M', u'c∕kg'), - (0x33C7, 'X'), - (0x33C8, 'M', u'db'), - (0x33C9, 'M', u'gy'), - (0x33CA, 'M', u'ha'), - (0x33CB, 'M', u'hp'), - (0x33CC, 'M', u'in'), - (0x33CD, 'M', u'kk'), - (0x33CE, 'M', u'km'), - (0x33CF, 'M', u'kt'), - (0x33D0, 'M', u'lm'), - (0x33D1, 'M', u'ln'), - (0x33D2, 'M', u'log'), - (0x33D3, 'M', u'lx'), - (0x33D4, 'M', u'mb'), - (0x33D5, 'M', u'mil'), - (0x33D6, 'M', u'mol'), - (0x33D7, 'M', u'ph'), - (0x33D8, 'X'), - (0x33D9, 'M', u'ppm'), - (0x33DA, 'M', u'pr'), - (0x33DB, 'M', u'sr'), - (0x33DC, 'M', u'sv'), - (0x33DD, 'M', u'wb'), - (0x33DE, 'M', u'v∕m'), - (0x33DF, 'M', u'a∕m'), - (0x33E0, 'M', u'1日'), - (0x33E1, 'M', u'2日'), - (0x33E2, 'M', u'3日'), - (0x33E3, 'M', u'4日'), - (0x33E4, 'M', u'5日'), - (0x33E5, 'M', u'6日'), - (0x33E6, 'M', u'7日'), - (0x33E7, 'M', u'8日'), - (0x33E8, 'M', u'9日'), - ] - -def _seg_35(): - return [ - (0x33E9, 'M', u'10日'), - (0x33EA, 'M', u'11日'), - (0x33EB, 'M', u'12日'), - (0x33EC, 'M', u'13日'), - (0x33ED, 'M', u'14日'), - (0x33EE, 'M', u'15日'), - (0x33EF, 'M', u'16日'), - (0x33F0, 'M', u'17日'), - (0x33F1, 'M', u'18日'), - (0x33F2, 'M', u'19日'), - (0x33F3, 'M', u'20日'), - (0x33F4, 'M', u'21日'), - (0x33F5, 'M', u'22日'), - (0x33F6, 'M', u'23日'), - (0x33F7, 'M', u'24日'), - (0x33F8, 'M', u'25日'), - (0x33F9, 'M', u'26日'), - (0x33FA, 'M', u'27日'), - (0x33FB, 'M', u'28日'), - (0x33FC, 'M', u'29日'), - (0x33FD, 'M', u'30日'), - (0x33FE, 'M', u'31日'), - (0x33FF, 'M', u'gal'), - (0x3400, 'V'), - (0x4DB6, 'X'), - (0x4DC0, 'V'), - (0x9FEB, 'X'), - (0xA000, 'V'), - (0xA48D, 'X'), - (0xA490, 'V'), - (0xA4C7, 'X'), - (0xA4D0, 'V'), - (0xA62C, 'X'), - (0xA640, 'M', u'ꙁ'), - (0xA641, 'V'), - (0xA642, 'M', u'ꙃ'), - (0xA643, 'V'), - (0xA644, 'M', u'ꙅ'), - (0xA645, 'V'), - (0xA646, 'M', u'ꙇ'), - (0xA647, 'V'), - (0xA648, 'M', u'ꙉ'), - (0xA649, 'V'), - (0xA64A, 'M', u'ꙋ'), - (0xA64B, 'V'), - (0xA64C, 'M', u'ꙍ'), - (0xA64D, 'V'), - (0xA64E, 'M', u'ꙏ'), - (0xA64F, 'V'), - (0xA650, 'M', u'ꙑ'), - (0xA651, 'V'), - (0xA652, 'M', u'ꙓ'), - (0xA653, 'V'), - (0xA654, 'M', u'ꙕ'), - (0xA655, 'V'), - (0xA656, 'M', u'ꙗ'), - (0xA657, 'V'), - (0xA658, 'M', u'ꙙ'), - (0xA659, 'V'), - (0xA65A, 'M', u'ꙛ'), - (0xA65B, 'V'), - (0xA65C, 'M', u'ꙝ'), - (0xA65D, 'V'), - (0xA65E, 'M', u'ꙟ'), - (0xA65F, 'V'), - (0xA660, 'M', u'ꙡ'), - (0xA661, 'V'), - (0xA662, 'M', u'ꙣ'), - (0xA663, 'V'), - (0xA664, 'M', u'ꙥ'), - (0xA665, 'V'), - (0xA666, 'M', u'ꙧ'), - (0xA667, 'V'), - (0xA668, 'M', u'ꙩ'), - (0xA669, 'V'), - (0xA66A, 'M', u'ꙫ'), - (0xA66B, 'V'), - (0xA66C, 'M', u'ꙭ'), - (0xA66D, 'V'), - (0xA680, 'M', u'ꚁ'), - (0xA681, 'V'), - (0xA682, 'M', u'ꚃ'), - (0xA683, 'V'), - (0xA684, 'M', u'ꚅ'), - (0xA685, 'V'), - (0xA686, 'M', u'ꚇ'), - (0xA687, 'V'), - (0xA688, 'M', u'ꚉ'), - (0xA689, 'V'), - (0xA68A, 'M', u'ꚋ'), - (0xA68B, 'V'), - (0xA68C, 'M', u'ꚍ'), - (0xA68D, 'V'), - (0xA68E, 'M', u'ꚏ'), - (0xA68F, 'V'), - (0xA690, 'M', u'ꚑ'), - (0xA691, 'V'), - (0xA692, 'M', u'ꚓ'), - (0xA693, 'V'), - (0xA694, 'M', u'ꚕ'), - ] - -def _seg_36(): - return [ - (0xA695, 'V'), - (0xA696, 'M', u'ꚗ'), - (0xA697, 'V'), - (0xA698, 'M', u'ꚙ'), - (0xA699, 'V'), - (0xA69A, 'M', u'ꚛ'), - (0xA69B, 'V'), - (0xA69C, 'M', u'ъ'), - (0xA69D, 'M', u'ь'), - (0xA69E, 'V'), - (0xA6F8, 'X'), - (0xA700, 'V'), - (0xA722, 'M', u'ꜣ'), - (0xA723, 'V'), - (0xA724, 'M', u'ꜥ'), - (0xA725, 'V'), - (0xA726, 'M', u'ꜧ'), - (0xA727, 'V'), - (0xA728, 'M', u'ꜩ'), - (0xA729, 'V'), - (0xA72A, 'M', u'ꜫ'), - (0xA72B, 'V'), - (0xA72C, 'M', u'ꜭ'), - (0xA72D, 'V'), - (0xA72E, 'M', u'ꜯ'), - (0xA72F, 'V'), - (0xA732, 'M', u'ꜳ'), - (0xA733, 'V'), - (0xA734, 'M', u'ꜵ'), - (0xA735, 'V'), - (0xA736, 'M', u'ꜷ'), - (0xA737, 'V'), - (0xA738, 'M', u'ꜹ'), - (0xA739, 'V'), - (0xA73A, 'M', u'ꜻ'), - (0xA73B, 'V'), - (0xA73C, 'M', u'ꜽ'), - (0xA73D, 'V'), - (0xA73E, 'M', u'ꜿ'), - (0xA73F, 'V'), - (0xA740, 'M', u'ꝁ'), - (0xA741, 'V'), - (0xA742, 'M', u'ꝃ'), - (0xA743, 'V'), - (0xA744, 'M', u'ꝅ'), - (0xA745, 'V'), - (0xA746, 'M', u'ꝇ'), - (0xA747, 'V'), - (0xA748, 'M', u'ꝉ'), - (0xA749, 'V'), - (0xA74A, 'M', u'ꝋ'), - (0xA74B, 'V'), - (0xA74C, 'M', u'ꝍ'), - (0xA74D, 'V'), - (0xA74E, 'M', u'ꝏ'), - (0xA74F, 'V'), - (0xA750, 'M', u'ꝑ'), - (0xA751, 'V'), - (0xA752, 'M', u'ꝓ'), - (0xA753, 'V'), - (0xA754, 'M', u'ꝕ'), - (0xA755, 'V'), - (0xA756, 'M', u'ꝗ'), - (0xA757, 'V'), - (0xA758, 'M', u'ꝙ'), - (0xA759, 'V'), - (0xA75A, 'M', u'ꝛ'), - (0xA75B, 'V'), - (0xA75C, 'M', u'ꝝ'), - (0xA75D, 'V'), - (0xA75E, 'M', u'ꝟ'), - (0xA75F, 'V'), - (0xA760, 'M', u'ꝡ'), - (0xA761, 'V'), - (0xA762, 'M', u'ꝣ'), - (0xA763, 'V'), - (0xA764, 'M', u'ꝥ'), - (0xA765, 'V'), - (0xA766, 'M', u'ꝧ'), - (0xA767, 'V'), - (0xA768, 'M', u'ꝩ'), - (0xA769, 'V'), - (0xA76A, 'M', u'ꝫ'), - (0xA76B, 'V'), - (0xA76C, 'M', u'ꝭ'), - (0xA76D, 'V'), - (0xA76E, 'M', u'ꝯ'), - (0xA76F, 'V'), - (0xA770, 'M', u'ꝯ'), - (0xA771, 'V'), - (0xA779, 'M', u'ꝺ'), - (0xA77A, 'V'), - (0xA77B, 'M', u'ꝼ'), - (0xA77C, 'V'), - (0xA77D, 'M', u'ᵹ'), - (0xA77E, 'M', u'ꝿ'), - (0xA77F, 'V'), - (0xA780, 'M', u'ꞁ'), - (0xA781, 'V'), - (0xA782, 'M', u'ꞃ'), - ] - -def _seg_37(): - return [ - (0xA783, 'V'), - (0xA784, 'M', u'ꞅ'), - (0xA785, 'V'), - (0xA786, 'M', u'ꞇ'), - (0xA787, 'V'), - (0xA78B, 'M', u'ꞌ'), - (0xA78C, 'V'), - (0xA78D, 'M', u'ɥ'), - (0xA78E, 'V'), - (0xA790, 'M', u'ꞑ'), - (0xA791, 'V'), - (0xA792, 'M', u'ꞓ'), - (0xA793, 'V'), - (0xA796, 'M', u'ꞗ'), - (0xA797, 'V'), - (0xA798, 'M', u'ꞙ'), - (0xA799, 'V'), - (0xA79A, 'M', u'ꞛ'), - (0xA79B, 'V'), - (0xA79C, 'M', u'ꞝ'), - (0xA79D, 'V'), - (0xA79E, 'M', u'ꞟ'), - (0xA79F, 'V'), - (0xA7A0, 'M', u'ꞡ'), - (0xA7A1, 'V'), - (0xA7A2, 'M', u'ꞣ'), - (0xA7A3, 'V'), - (0xA7A4, 'M', u'ꞥ'), - (0xA7A5, 'V'), - (0xA7A6, 'M', u'ꞧ'), - (0xA7A7, 'V'), - (0xA7A8, 'M', u'ꞩ'), - (0xA7A9, 'V'), - (0xA7AA, 'M', u'ɦ'), - (0xA7AB, 'M', u'ɜ'), - (0xA7AC, 'M', u'ɡ'), - (0xA7AD, 'M', u'ɬ'), - (0xA7AE, 'M', u'ɪ'), - (0xA7AF, 'X'), - (0xA7B0, 'M', u'ʞ'), - (0xA7B1, 'M', u'ʇ'), - (0xA7B2, 'M', u'ʝ'), - (0xA7B3, 'M', u'ꭓ'), - (0xA7B4, 'M', u'ꞵ'), - (0xA7B5, 'V'), - (0xA7B6, 'M', u'ꞷ'), - (0xA7B7, 'V'), - (0xA7B8, 'X'), - (0xA7F7, 'V'), - (0xA7F8, 'M', u'ħ'), - (0xA7F9, 'M', u'œ'), - (0xA7FA, 'V'), - (0xA82C, 'X'), - (0xA830, 'V'), - (0xA83A, 'X'), - (0xA840, 'V'), - (0xA878, 'X'), - (0xA880, 'V'), - (0xA8C6, 'X'), - (0xA8CE, 'V'), - (0xA8DA, 'X'), - (0xA8E0, 'V'), - (0xA8FE, 'X'), - (0xA900, 'V'), - (0xA954, 'X'), - (0xA95F, 'V'), - (0xA97D, 'X'), - (0xA980, 'V'), - (0xA9CE, 'X'), - (0xA9CF, 'V'), - (0xA9DA, 'X'), - (0xA9DE, 'V'), - (0xA9FF, 'X'), - (0xAA00, 'V'), - (0xAA37, 'X'), - (0xAA40, 'V'), - (0xAA4E, 'X'), - (0xAA50, 'V'), - (0xAA5A, 'X'), - (0xAA5C, 'V'), - (0xAAC3, 'X'), - (0xAADB, 'V'), - (0xAAF7, 'X'), - (0xAB01, 'V'), - (0xAB07, 'X'), - (0xAB09, 'V'), - (0xAB0F, 'X'), - (0xAB11, 'V'), - (0xAB17, 'X'), - (0xAB20, 'V'), - (0xAB27, 'X'), - (0xAB28, 'V'), - (0xAB2F, 'X'), - (0xAB30, 'V'), - (0xAB5C, 'M', u'ꜧ'), - (0xAB5D, 'M', u'ꬷ'), - (0xAB5E, 'M', u'ɫ'), - (0xAB5F, 'M', u'ꭒ'), - (0xAB60, 'V'), - (0xAB66, 'X'), - ] - -def _seg_38(): - return [ - (0xAB70, 'M', u'Ꭰ'), - (0xAB71, 'M', u'Ꭱ'), - (0xAB72, 'M', u'Ꭲ'), - (0xAB73, 'M', u'Ꭳ'), - (0xAB74, 'M', u'Ꭴ'), - (0xAB75, 'M', u'Ꭵ'), - (0xAB76, 'M', u'Ꭶ'), - (0xAB77, 'M', u'Ꭷ'), - (0xAB78, 'M', u'Ꭸ'), - (0xAB79, 'M', u'Ꭹ'), - (0xAB7A, 'M', u'Ꭺ'), - (0xAB7B, 'M', u'Ꭻ'), - (0xAB7C, 'M', u'Ꭼ'), - (0xAB7D, 'M', u'Ꭽ'), - (0xAB7E, 'M', u'Ꭾ'), - (0xAB7F, 'M', u'Ꭿ'), - (0xAB80, 'M', u'Ꮀ'), - (0xAB81, 'M', u'Ꮁ'), - (0xAB82, 'M', u'Ꮂ'), - (0xAB83, 'M', u'Ꮃ'), - (0xAB84, 'M', u'Ꮄ'), - (0xAB85, 'M', u'Ꮅ'), - (0xAB86, 'M', u'Ꮆ'), - (0xAB87, 'M', u'Ꮇ'), - (0xAB88, 'M', u'Ꮈ'), - (0xAB89, 'M', u'Ꮉ'), - (0xAB8A, 'M', u'Ꮊ'), - (0xAB8B, 'M', u'Ꮋ'), - (0xAB8C, 'M', u'Ꮌ'), - (0xAB8D, 'M', u'Ꮍ'), - (0xAB8E, 'M', u'Ꮎ'), - (0xAB8F, 'M', u'Ꮏ'), - (0xAB90, 'M', u'Ꮐ'), - (0xAB91, 'M', u'Ꮑ'), - (0xAB92, 'M', u'Ꮒ'), - (0xAB93, 'M', u'Ꮓ'), - (0xAB94, 'M', u'Ꮔ'), - (0xAB95, 'M', u'Ꮕ'), - (0xAB96, 'M', u'Ꮖ'), - (0xAB97, 'M', u'Ꮗ'), - (0xAB98, 'M', u'Ꮘ'), - (0xAB99, 'M', u'Ꮙ'), - (0xAB9A, 'M', u'Ꮚ'), - (0xAB9B, 'M', u'Ꮛ'), - (0xAB9C, 'M', u'Ꮜ'), - (0xAB9D, 'M', u'Ꮝ'), - (0xAB9E, 'M', u'Ꮞ'), - (0xAB9F, 'M', u'Ꮟ'), - (0xABA0, 'M', u'Ꮠ'), - (0xABA1, 'M', u'Ꮡ'), - (0xABA2, 'M', u'Ꮢ'), - (0xABA3, 'M', u'Ꮣ'), - (0xABA4, 'M', u'Ꮤ'), - (0xABA5, 'M', u'Ꮥ'), - (0xABA6, 'M', u'Ꮦ'), - (0xABA7, 'M', u'Ꮧ'), - (0xABA8, 'M', u'Ꮨ'), - (0xABA9, 'M', u'Ꮩ'), - (0xABAA, 'M', u'Ꮪ'), - (0xABAB, 'M', u'Ꮫ'), - (0xABAC, 'M', u'Ꮬ'), - (0xABAD, 'M', u'Ꮭ'), - (0xABAE, 'M', u'Ꮮ'), - (0xABAF, 'M', u'Ꮯ'), - (0xABB0, 'M', u'Ꮰ'), - (0xABB1, 'M', u'Ꮱ'), - (0xABB2, 'M', u'Ꮲ'), - (0xABB3, 'M', u'Ꮳ'), - (0xABB4, 'M', u'Ꮴ'), - (0xABB5, 'M', u'Ꮵ'), - (0xABB6, 'M', u'Ꮶ'), - (0xABB7, 'M', u'Ꮷ'), - (0xABB8, 'M', u'Ꮸ'), - (0xABB9, 'M', u'Ꮹ'), - (0xABBA, 'M', u'Ꮺ'), - (0xABBB, 'M', u'Ꮻ'), - (0xABBC, 'M', u'Ꮼ'), - (0xABBD, 'M', u'Ꮽ'), - (0xABBE, 'M', u'Ꮾ'), - (0xABBF, 'M', u'Ꮿ'), - (0xABC0, 'V'), - (0xABEE, 'X'), - (0xABF0, 'V'), - (0xABFA, 'X'), - (0xAC00, 'V'), - (0xD7A4, 'X'), - (0xD7B0, 'V'), - (0xD7C7, 'X'), - (0xD7CB, 'V'), - (0xD7FC, 'X'), - (0xF900, 'M', u'豈'), - (0xF901, 'M', u'更'), - (0xF902, 'M', u'車'), - (0xF903, 'M', u'賈'), - (0xF904, 'M', u'滑'), - (0xF905, 'M', u'串'), - (0xF906, 'M', u'句'), - (0xF907, 'M', u'龜'), - (0xF909, 'M', u'契'), - (0xF90A, 'M', u'金'), - ] - -def _seg_39(): - return [ - (0xF90B, 'M', u'喇'), - (0xF90C, 'M', u'奈'), - (0xF90D, 'M', u'懶'), - (0xF90E, 'M', u'癩'), - (0xF90F, 'M', u'羅'), - (0xF910, 'M', u'蘿'), - (0xF911, 'M', u'螺'), - (0xF912, 'M', u'裸'), - (0xF913, 'M', u'邏'), - (0xF914, 'M', u'樂'), - (0xF915, 'M', u'洛'), - (0xF916, 'M', u'烙'), - (0xF917, 'M', u'珞'), - (0xF918, 'M', u'落'), - (0xF919, 'M', u'酪'), - (0xF91A, 'M', u'駱'), - (0xF91B, 'M', u'亂'), - (0xF91C, 'M', u'卵'), - (0xF91D, 'M', u'欄'), - (0xF91E, 'M', u'爛'), - (0xF91F, 'M', u'蘭'), - (0xF920, 'M', u'鸞'), - (0xF921, 'M', u'嵐'), - (0xF922, 'M', u'濫'), - (0xF923, 'M', u'藍'), - (0xF924, 'M', u'襤'), - (0xF925, 'M', u'拉'), - (0xF926, 'M', u'臘'), - (0xF927, 'M', u'蠟'), - (0xF928, 'M', u'廊'), - (0xF929, 'M', u'朗'), - (0xF92A, 'M', u'浪'), - (0xF92B, 'M', u'狼'), - (0xF92C, 'M', u'郎'), - (0xF92D, 'M', u'來'), - (0xF92E, 'M', u'冷'), - (0xF92F, 'M', u'勞'), - (0xF930, 'M', u'擄'), - (0xF931, 'M', u'櫓'), - (0xF932, 'M', u'爐'), - (0xF933, 'M', u'盧'), - (0xF934, 'M', u'老'), - (0xF935, 'M', u'蘆'), - (0xF936, 'M', u'虜'), - (0xF937, 'M', u'路'), - (0xF938, 'M', u'露'), - (0xF939, 'M', u'魯'), - (0xF93A, 'M', u'鷺'), - (0xF93B, 'M', u'碌'), - (0xF93C, 'M', u'祿'), - (0xF93D, 'M', u'綠'), - (0xF93E, 'M', u'菉'), - (0xF93F, 'M', u'錄'), - (0xF940, 'M', u'鹿'), - (0xF941, 'M', u'論'), - (0xF942, 'M', u'壟'), - (0xF943, 'M', u'弄'), - (0xF944, 'M', u'籠'), - (0xF945, 'M', u'聾'), - (0xF946, 'M', u'牢'), - (0xF947, 'M', u'磊'), - (0xF948, 'M', u'賂'), - (0xF949, 'M', u'雷'), - (0xF94A, 'M', u'壘'), - (0xF94B, 'M', u'屢'), - (0xF94C, 'M', u'樓'), - (0xF94D, 'M', u'淚'), - (0xF94E, 'M', u'漏'), - (0xF94F, 'M', u'累'), - (0xF950, 'M', u'縷'), - (0xF951, 'M', u'陋'), - (0xF952, 'M', u'勒'), - (0xF953, 'M', u'肋'), - (0xF954, 'M', u'凜'), - (0xF955, 'M', u'凌'), - (0xF956, 'M', u'稜'), - (0xF957, 'M', u'綾'), - (0xF958, 'M', u'菱'), - (0xF959, 'M', u'陵'), - (0xF95A, 'M', u'讀'), - (0xF95B, 'M', u'拏'), - (0xF95C, 'M', u'樂'), - (0xF95D, 'M', u'諾'), - (0xF95E, 'M', u'丹'), - (0xF95F, 'M', u'寧'), - (0xF960, 'M', u'怒'), - (0xF961, 'M', u'率'), - (0xF962, 'M', u'異'), - (0xF963, 'M', u'北'), - (0xF964, 'M', u'磻'), - (0xF965, 'M', u'便'), - (0xF966, 'M', u'復'), - (0xF967, 'M', u'不'), - (0xF968, 'M', u'泌'), - (0xF969, 'M', u'數'), - (0xF96A, 'M', u'索'), - (0xF96B, 'M', u'參'), - (0xF96C, 'M', u'塞'), - (0xF96D, 'M', u'省'), - (0xF96E, 'M', u'葉'), - ] - -def _seg_40(): - return [ - (0xF96F, 'M', u'說'), - (0xF970, 'M', u'殺'), - (0xF971, 'M', u'辰'), - (0xF972, 'M', u'沈'), - (0xF973, 'M', u'拾'), - (0xF974, 'M', u'若'), - (0xF975, 'M', u'掠'), - (0xF976, 'M', u'略'), - (0xF977, 'M', u'亮'), - (0xF978, 'M', u'兩'), - (0xF979, 'M', u'凉'), - (0xF97A, 'M', u'梁'), - (0xF97B, 'M', u'糧'), - (0xF97C, 'M', u'良'), - (0xF97D, 'M', u'諒'), - (0xF97E, 'M', u'量'), - (0xF97F, 'M', u'勵'), - (0xF980, 'M', u'呂'), - (0xF981, 'M', u'女'), - (0xF982, 'M', u'廬'), - (0xF983, 'M', u'旅'), - (0xF984, 'M', u'濾'), - (0xF985, 'M', u'礪'), - (0xF986, 'M', u'閭'), - (0xF987, 'M', u'驪'), - (0xF988, 'M', u'麗'), - (0xF989, 'M', u'黎'), - (0xF98A, 'M', u'力'), - (0xF98B, 'M', u'曆'), - (0xF98C, 'M', u'歷'), - (0xF98D, 'M', u'轢'), - (0xF98E, 'M', u'年'), - (0xF98F, 'M', u'憐'), - (0xF990, 'M', u'戀'), - (0xF991, 'M', u'撚'), - (0xF992, 'M', u'漣'), - (0xF993, 'M', u'煉'), - (0xF994, 'M', u'璉'), - (0xF995, 'M', u'秊'), - (0xF996, 'M', u'練'), - (0xF997, 'M', u'聯'), - (0xF998, 'M', u'輦'), - (0xF999, 'M', u'蓮'), - (0xF99A, 'M', u'連'), - (0xF99B, 'M', u'鍊'), - (0xF99C, 'M', u'列'), - (0xF99D, 'M', u'劣'), - (0xF99E, 'M', u'咽'), - (0xF99F, 'M', u'烈'), - (0xF9A0, 'M', u'裂'), - (0xF9A1, 'M', u'說'), - (0xF9A2, 'M', u'廉'), - (0xF9A3, 'M', u'念'), - (0xF9A4, 'M', u'捻'), - (0xF9A5, 'M', u'殮'), - (0xF9A6, 'M', u'簾'), - (0xF9A7, 'M', u'獵'), - (0xF9A8, 'M', u'令'), - (0xF9A9, 'M', u'囹'), - (0xF9AA, 'M', u'寧'), - (0xF9AB, 'M', u'嶺'), - (0xF9AC, 'M', u'怜'), - (0xF9AD, 'M', u'玲'), - (0xF9AE, 'M', u'瑩'), - (0xF9AF, 'M', u'羚'), - (0xF9B0, 'M', u'聆'), - (0xF9B1, 'M', u'鈴'), - (0xF9B2, 'M', u'零'), - (0xF9B3, 'M', u'靈'), - (0xF9B4, 'M', u'領'), - (0xF9B5, 'M', u'例'), - (0xF9B6, 'M', u'禮'), - (0xF9B7, 'M', u'醴'), - (0xF9B8, 'M', u'隸'), - (0xF9B9, 'M', u'惡'), - (0xF9BA, 'M', u'了'), - (0xF9BB, 'M', u'僚'), - (0xF9BC, 'M', u'寮'), - (0xF9BD, 'M', u'尿'), - (0xF9BE, 'M', u'料'), - (0xF9BF, 'M', u'樂'), - (0xF9C0, 'M', u'燎'), - (0xF9C1, 'M', u'療'), - (0xF9C2, 'M', u'蓼'), - (0xF9C3, 'M', u'遼'), - (0xF9C4, 'M', u'龍'), - (0xF9C5, 'M', u'暈'), - (0xF9C6, 'M', u'阮'), - (0xF9C7, 'M', u'劉'), - (0xF9C8, 'M', u'杻'), - (0xF9C9, 'M', u'柳'), - (0xF9CA, 'M', u'流'), - (0xF9CB, 'M', u'溜'), - (0xF9CC, 'M', u'琉'), - (0xF9CD, 'M', u'留'), - (0xF9CE, 'M', u'硫'), - (0xF9CF, 'M', u'紐'), - (0xF9D0, 'M', u'類'), - (0xF9D1, 'M', u'六'), - (0xF9D2, 'M', u'戮'), - ] - -def _seg_41(): - return [ - (0xF9D3, 'M', u'陸'), - (0xF9D4, 'M', u'倫'), - (0xF9D5, 'M', u'崙'), - (0xF9D6, 'M', u'淪'), - (0xF9D7, 'M', u'輪'), - (0xF9D8, 'M', u'律'), - (0xF9D9, 'M', u'慄'), - (0xF9DA, 'M', u'栗'), - (0xF9DB, 'M', u'率'), - (0xF9DC, 'M', u'隆'), - (0xF9DD, 'M', u'利'), - (0xF9DE, 'M', u'吏'), - (0xF9DF, 'M', u'履'), - (0xF9E0, 'M', u'易'), - (0xF9E1, 'M', u'李'), - (0xF9E2, 'M', u'梨'), - (0xF9E3, 'M', u'泥'), - (0xF9E4, 'M', u'理'), - (0xF9E5, 'M', u'痢'), - (0xF9E6, 'M', u'罹'), - (0xF9E7, 'M', u'裏'), - (0xF9E8, 'M', u'裡'), - (0xF9E9, 'M', u'里'), - (0xF9EA, 'M', u'離'), - (0xF9EB, 'M', u'匿'), - (0xF9EC, 'M', u'溺'), - (0xF9ED, 'M', u'吝'), - (0xF9EE, 'M', u'燐'), - (0xF9EF, 'M', u'璘'), - (0xF9F0, 'M', u'藺'), - (0xF9F1, 'M', u'隣'), - (0xF9F2, 'M', u'鱗'), - (0xF9F3, 'M', u'麟'), - (0xF9F4, 'M', u'林'), - (0xF9F5, 'M', u'淋'), - (0xF9F6, 'M', u'臨'), - (0xF9F7, 'M', u'立'), - (0xF9F8, 'M', u'笠'), - (0xF9F9, 'M', u'粒'), - (0xF9FA, 'M', u'狀'), - (0xF9FB, 'M', u'炙'), - (0xF9FC, 'M', u'識'), - (0xF9FD, 'M', u'什'), - (0xF9FE, 'M', u'茶'), - (0xF9FF, 'M', u'刺'), - (0xFA00, 'M', u'切'), - (0xFA01, 'M', u'度'), - (0xFA02, 'M', u'拓'), - (0xFA03, 'M', u'糖'), - (0xFA04, 'M', u'宅'), - (0xFA05, 'M', u'洞'), - (0xFA06, 'M', u'暴'), - (0xFA07, 'M', u'輻'), - (0xFA08, 'M', u'行'), - (0xFA09, 'M', u'降'), - (0xFA0A, 'M', u'見'), - (0xFA0B, 'M', u'廓'), - (0xFA0C, 'M', u'兀'), - (0xFA0D, 'M', u'嗀'), - (0xFA0E, 'V'), - (0xFA10, 'M', u'塚'), - (0xFA11, 'V'), - (0xFA12, 'M', u'晴'), - (0xFA13, 'V'), - (0xFA15, 'M', u'凞'), - (0xFA16, 'M', u'猪'), - (0xFA17, 'M', u'益'), - (0xFA18, 'M', u'礼'), - (0xFA19, 'M', u'神'), - (0xFA1A, 'M', u'祥'), - (0xFA1B, 'M', u'福'), - (0xFA1C, 'M', u'靖'), - (0xFA1D, 'M', u'精'), - (0xFA1E, 'M', u'羽'), - (0xFA1F, 'V'), - (0xFA20, 'M', u'蘒'), - (0xFA21, 'V'), - (0xFA22, 'M', u'諸'), - (0xFA23, 'V'), - (0xFA25, 'M', u'逸'), - (0xFA26, 'M', u'都'), - (0xFA27, 'V'), - (0xFA2A, 'M', u'飯'), - (0xFA2B, 'M', u'飼'), - (0xFA2C, 'M', u'館'), - (0xFA2D, 'M', u'鶴'), - (0xFA2E, 'M', u'郞'), - (0xFA2F, 'M', u'隷'), - (0xFA30, 'M', u'侮'), - (0xFA31, 'M', u'僧'), - (0xFA32, 'M', u'免'), - (0xFA33, 'M', u'勉'), - (0xFA34, 'M', u'勤'), - (0xFA35, 'M', u'卑'), - (0xFA36, 'M', u'喝'), - (0xFA37, 'M', u'嘆'), - (0xFA38, 'M', u'器'), - (0xFA39, 'M', u'塀'), - (0xFA3A, 'M', u'墨'), - (0xFA3B, 'M', u'層'), - ] - -def _seg_42(): - return [ - (0xFA3C, 'M', u'屮'), - (0xFA3D, 'M', u'悔'), - (0xFA3E, 'M', u'慨'), - (0xFA3F, 'M', u'憎'), - (0xFA40, 'M', u'懲'), - (0xFA41, 'M', u'敏'), - (0xFA42, 'M', u'既'), - (0xFA43, 'M', u'暑'), - (0xFA44, 'M', u'梅'), - (0xFA45, 'M', u'海'), - (0xFA46, 'M', u'渚'), - (0xFA47, 'M', u'漢'), - (0xFA48, 'M', u'煮'), - (0xFA49, 'M', u'爫'), - (0xFA4A, 'M', u'琢'), - (0xFA4B, 'M', u'碑'), - (0xFA4C, 'M', u'社'), - (0xFA4D, 'M', u'祉'), - (0xFA4E, 'M', u'祈'), - (0xFA4F, 'M', u'祐'), - (0xFA50, 'M', u'祖'), - (0xFA51, 'M', u'祝'), - (0xFA52, 'M', u'禍'), - (0xFA53, 'M', u'禎'), - (0xFA54, 'M', u'穀'), - (0xFA55, 'M', u'突'), - (0xFA56, 'M', u'節'), - (0xFA57, 'M', u'練'), - (0xFA58, 'M', u'縉'), - (0xFA59, 'M', u'繁'), - (0xFA5A, 'M', u'署'), - (0xFA5B, 'M', u'者'), - (0xFA5C, 'M', u'臭'), - (0xFA5D, 'M', u'艹'), - (0xFA5F, 'M', u'著'), - (0xFA60, 'M', u'褐'), - (0xFA61, 'M', u'視'), - (0xFA62, 'M', u'謁'), - (0xFA63, 'M', u'謹'), - (0xFA64, 'M', u'賓'), - (0xFA65, 'M', u'贈'), - (0xFA66, 'M', u'辶'), - (0xFA67, 'M', u'逸'), - (0xFA68, 'M', u'難'), - (0xFA69, 'M', u'響'), - (0xFA6A, 'M', u'頻'), - (0xFA6B, 'M', u'恵'), - (0xFA6C, 'M', u'𤋮'), - (0xFA6D, 'M', u'舘'), - (0xFA6E, 'X'), - (0xFA70, 'M', u'並'), - (0xFA71, 'M', u'况'), - (0xFA72, 'M', u'全'), - (0xFA73, 'M', u'侀'), - (0xFA74, 'M', u'充'), - (0xFA75, 'M', u'冀'), - (0xFA76, 'M', u'勇'), - (0xFA77, 'M', u'勺'), - (0xFA78, 'M', u'喝'), - (0xFA79, 'M', u'啕'), - (0xFA7A, 'M', u'喙'), - (0xFA7B, 'M', u'嗢'), - (0xFA7C, 'M', u'塚'), - (0xFA7D, 'M', u'墳'), - (0xFA7E, 'M', u'奄'), - (0xFA7F, 'M', u'奔'), - (0xFA80, 'M', u'婢'), - (0xFA81, 'M', u'嬨'), - (0xFA82, 'M', u'廒'), - (0xFA83, 'M', u'廙'), - (0xFA84, 'M', u'彩'), - (0xFA85, 'M', u'徭'), - (0xFA86, 'M', u'惘'), - (0xFA87, 'M', u'慎'), - (0xFA88, 'M', u'愈'), - (0xFA89, 'M', u'憎'), - (0xFA8A, 'M', u'慠'), - (0xFA8B, 'M', u'懲'), - (0xFA8C, 'M', u'戴'), - (0xFA8D, 'M', u'揄'), - (0xFA8E, 'M', u'搜'), - (0xFA8F, 'M', u'摒'), - (0xFA90, 'M', u'敖'), - (0xFA91, 'M', u'晴'), - (0xFA92, 'M', u'朗'), - (0xFA93, 'M', u'望'), - (0xFA94, 'M', u'杖'), - (0xFA95, 'M', u'歹'), - (0xFA96, 'M', u'殺'), - (0xFA97, 'M', u'流'), - (0xFA98, 'M', u'滛'), - (0xFA99, 'M', u'滋'), - (0xFA9A, 'M', u'漢'), - (0xFA9B, 'M', u'瀞'), - (0xFA9C, 'M', u'煮'), - (0xFA9D, 'M', u'瞧'), - (0xFA9E, 'M', u'爵'), - (0xFA9F, 'M', u'犯'), - (0xFAA0, 'M', u'猪'), - (0xFAA1, 'M', u'瑱'), - ] - -def _seg_43(): - return [ - (0xFAA2, 'M', u'甆'), - (0xFAA3, 'M', u'画'), - (0xFAA4, 'M', u'瘝'), - (0xFAA5, 'M', u'瘟'), - (0xFAA6, 'M', u'益'), - (0xFAA7, 'M', u'盛'), - (0xFAA8, 'M', u'直'), - (0xFAA9, 'M', u'睊'), - (0xFAAA, 'M', u'着'), - (0xFAAB, 'M', u'磌'), - (0xFAAC, 'M', u'窱'), - (0xFAAD, 'M', u'節'), - (0xFAAE, 'M', u'类'), - (0xFAAF, 'M', u'絛'), - (0xFAB0, 'M', u'練'), - (0xFAB1, 'M', u'缾'), - (0xFAB2, 'M', u'者'), - (0xFAB3, 'M', u'荒'), - (0xFAB4, 'M', u'華'), - (0xFAB5, 'M', u'蝹'), - (0xFAB6, 'M', u'襁'), - (0xFAB7, 'M', u'覆'), - (0xFAB8, 'M', u'視'), - (0xFAB9, 'M', u'調'), - (0xFABA, 'M', u'諸'), - (0xFABB, 'M', u'請'), - (0xFABC, 'M', u'謁'), - (0xFABD, 'M', u'諾'), - (0xFABE, 'M', u'諭'), - (0xFABF, 'M', u'謹'), - (0xFAC0, 'M', u'變'), - (0xFAC1, 'M', u'贈'), - (0xFAC2, 'M', u'輸'), - (0xFAC3, 'M', u'遲'), - (0xFAC4, 'M', u'醙'), - (0xFAC5, 'M', u'鉶'), - (0xFAC6, 'M', u'陼'), - (0xFAC7, 'M', u'難'), - (0xFAC8, 'M', u'靖'), - (0xFAC9, 'M', u'韛'), - (0xFACA, 'M', u'響'), - (0xFACB, 'M', u'頋'), - (0xFACC, 'M', u'頻'), - (0xFACD, 'M', u'鬒'), - (0xFACE, 'M', u'龜'), - (0xFACF, 'M', u'𢡊'), - (0xFAD0, 'M', u'𢡄'), - (0xFAD1, 'M', u'𣏕'), - (0xFAD2, 'M', u'㮝'), - (0xFAD3, 'M', u'䀘'), - (0xFAD4, 'M', u'䀹'), - (0xFAD5, 'M', u'𥉉'), - (0xFAD6, 'M', u'𥳐'), - (0xFAD7, 'M', u'𧻓'), - (0xFAD8, 'M', u'齃'), - (0xFAD9, 'M', u'龎'), - (0xFADA, 'X'), - (0xFB00, 'M', u'ff'), - (0xFB01, 'M', u'fi'), - (0xFB02, 'M', u'fl'), - (0xFB03, 'M', u'ffi'), - (0xFB04, 'M', u'ffl'), - (0xFB05, 'M', u'st'), - (0xFB07, 'X'), - (0xFB13, 'M', u'մն'), - (0xFB14, 'M', u'մե'), - (0xFB15, 'M', u'մի'), - (0xFB16, 'M', u'վն'), - (0xFB17, 'M', u'մխ'), - (0xFB18, 'X'), - (0xFB1D, 'M', u'יִ'), - (0xFB1E, 'V'), - (0xFB1F, 'M', u'ײַ'), - (0xFB20, 'M', u'ע'), - (0xFB21, 'M', u'א'), - (0xFB22, 'M', u'ד'), - (0xFB23, 'M', u'ה'), - (0xFB24, 'M', u'כ'), - (0xFB25, 'M', u'ל'), - (0xFB26, 'M', u'ם'), - (0xFB27, 'M', u'ר'), - (0xFB28, 'M', u'ת'), - (0xFB29, '3', u'+'), - (0xFB2A, 'M', u'שׁ'), - (0xFB2B, 'M', u'שׂ'), - (0xFB2C, 'M', u'שּׁ'), - (0xFB2D, 'M', u'שּׂ'), - (0xFB2E, 'M', u'אַ'), - (0xFB2F, 'M', u'אָ'), - (0xFB30, 'M', u'אּ'), - (0xFB31, 'M', u'בּ'), - (0xFB32, 'M', u'גּ'), - (0xFB33, 'M', u'דּ'), - (0xFB34, 'M', u'הּ'), - (0xFB35, 'M', u'וּ'), - (0xFB36, 'M', u'זּ'), - (0xFB37, 'X'), - (0xFB38, 'M', u'טּ'), - (0xFB39, 'M', u'יּ'), - (0xFB3A, 'M', u'ךּ'), - ] - -def _seg_44(): - return [ - (0xFB3B, 'M', u'כּ'), - (0xFB3C, 'M', u'לּ'), - (0xFB3D, 'X'), - (0xFB3E, 'M', u'מּ'), - (0xFB3F, 'X'), - (0xFB40, 'M', u'נּ'), - (0xFB41, 'M', u'סּ'), - (0xFB42, 'X'), - (0xFB43, 'M', u'ףּ'), - (0xFB44, 'M', u'פּ'), - (0xFB45, 'X'), - (0xFB46, 'M', u'צּ'), - (0xFB47, 'M', u'קּ'), - (0xFB48, 'M', u'רּ'), - (0xFB49, 'M', u'שּ'), - (0xFB4A, 'M', u'תּ'), - (0xFB4B, 'M', u'וֹ'), - (0xFB4C, 'M', u'בֿ'), - (0xFB4D, 'M', u'כֿ'), - (0xFB4E, 'M', u'פֿ'), - (0xFB4F, 'M', u'אל'), - (0xFB50, 'M', u'ٱ'), - (0xFB52, 'M', u'ٻ'), - (0xFB56, 'M', u'پ'), - (0xFB5A, 'M', u'ڀ'), - (0xFB5E, 'M', u'ٺ'), - (0xFB62, 'M', u'ٿ'), - (0xFB66, 'M', u'ٹ'), - (0xFB6A, 'M', u'ڤ'), - (0xFB6E, 'M', u'ڦ'), - (0xFB72, 'M', u'ڄ'), - (0xFB76, 'M', u'ڃ'), - (0xFB7A, 'M', u'چ'), - (0xFB7E, 'M', u'ڇ'), - (0xFB82, 'M', u'ڍ'), - (0xFB84, 'M', u'ڌ'), - (0xFB86, 'M', u'ڎ'), - (0xFB88, 'M', u'ڈ'), - (0xFB8A, 'M', u'ژ'), - (0xFB8C, 'M', u'ڑ'), - (0xFB8E, 'M', u'ک'), - (0xFB92, 'M', u'گ'), - (0xFB96, 'M', u'ڳ'), - (0xFB9A, 'M', u'ڱ'), - (0xFB9E, 'M', u'ں'), - (0xFBA0, 'M', u'ڻ'), - (0xFBA4, 'M', u'ۀ'), - (0xFBA6, 'M', u'ہ'), - (0xFBAA, 'M', u'ھ'), - (0xFBAE, 'M', u'ے'), - (0xFBB0, 'M', u'ۓ'), - (0xFBB2, 'V'), - (0xFBC2, 'X'), - (0xFBD3, 'M', u'ڭ'), - (0xFBD7, 'M', u'ۇ'), - (0xFBD9, 'M', u'ۆ'), - (0xFBDB, 'M', u'ۈ'), - (0xFBDD, 'M', u'ۇٴ'), - (0xFBDE, 'M', u'ۋ'), - (0xFBE0, 'M', u'ۅ'), - (0xFBE2, 'M', u'ۉ'), - (0xFBE4, 'M', u'ې'), - (0xFBE8, 'M', u'ى'), - (0xFBEA, 'M', u'ئا'), - (0xFBEC, 'M', u'ئە'), - (0xFBEE, 'M', u'ئو'), - (0xFBF0, 'M', u'ئۇ'), - (0xFBF2, 'M', u'ئۆ'), - (0xFBF4, 'M', u'ئۈ'), - (0xFBF6, 'M', u'ئې'), - (0xFBF9, 'M', u'ئى'), - (0xFBFC, 'M', u'ی'), - (0xFC00, 'M', u'ئج'), - (0xFC01, 'M', u'ئح'), - (0xFC02, 'M', u'ئم'), - (0xFC03, 'M', u'ئى'), - (0xFC04, 'M', u'ئي'), - (0xFC05, 'M', u'بج'), - (0xFC06, 'M', u'بح'), - (0xFC07, 'M', u'بخ'), - (0xFC08, 'M', u'بم'), - (0xFC09, 'M', u'بى'), - (0xFC0A, 'M', u'بي'), - (0xFC0B, 'M', u'تج'), - (0xFC0C, 'M', u'تح'), - (0xFC0D, 'M', u'تخ'), - (0xFC0E, 'M', u'تم'), - (0xFC0F, 'M', u'تى'), - (0xFC10, 'M', u'تي'), - (0xFC11, 'M', u'ثج'), - (0xFC12, 'M', u'ثم'), - (0xFC13, 'M', u'ثى'), - (0xFC14, 'M', u'ثي'), - (0xFC15, 'M', u'جح'), - (0xFC16, 'M', u'جم'), - (0xFC17, 'M', u'حج'), - (0xFC18, 'M', u'حم'), - (0xFC19, 'M', u'خج'), - (0xFC1A, 'M', u'خح'), - (0xFC1B, 'M', u'خم'), - ] - -def _seg_45(): - return [ - (0xFC1C, 'M', u'سج'), - (0xFC1D, 'M', u'سح'), - (0xFC1E, 'M', u'سخ'), - (0xFC1F, 'M', u'سم'), - (0xFC20, 'M', u'صح'), - (0xFC21, 'M', u'صم'), - (0xFC22, 'M', u'ضج'), - (0xFC23, 'M', u'ضح'), - (0xFC24, 'M', u'ضخ'), - (0xFC25, 'M', u'ضم'), - (0xFC26, 'M', u'طح'), - (0xFC27, 'M', u'طم'), - (0xFC28, 'M', u'ظم'), - (0xFC29, 'M', u'عج'), - (0xFC2A, 'M', u'عم'), - (0xFC2B, 'M', u'غج'), - (0xFC2C, 'M', u'غم'), - (0xFC2D, 'M', u'فج'), - (0xFC2E, 'M', u'فح'), - (0xFC2F, 'M', u'فخ'), - (0xFC30, 'M', u'فم'), - (0xFC31, 'M', u'فى'), - (0xFC32, 'M', u'في'), - (0xFC33, 'M', u'قح'), - (0xFC34, 'M', u'قم'), - (0xFC35, 'M', u'قى'), - (0xFC36, 'M', u'قي'), - (0xFC37, 'M', u'كا'), - (0xFC38, 'M', u'كج'), - (0xFC39, 'M', u'كح'), - (0xFC3A, 'M', u'كخ'), - (0xFC3B, 'M', u'كل'), - (0xFC3C, 'M', u'كم'), - (0xFC3D, 'M', u'كى'), - (0xFC3E, 'M', u'كي'), - (0xFC3F, 'M', u'لج'), - (0xFC40, 'M', u'لح'), - (0xFC41, 'M', u'لخ'), - (0xFC42, 'M', u'لم'), - (0xFC43, 'M', u'لى'), - (0xFC44, 'M', u'لي'), - (0xFC45, 'M', u'مج'), - (0xFC46, 'M', u'مح'), - (0xFC47, 'M', u'مخ'), - (0xFC48, 'M', u'مم'), - (0xFC49, 'M', u'مى'), - (0xFC4A, 'M', u'مي'), - (0xFC4B, 'M', u'نج'), - (0xFC4C, 'M', u'نح'), - (0xFC4D, 'M', u'نخ'), - (0xFC4E, 'M', u'نم'), - (0xFC4F, 'M', u'نى'), - (0xFC50, 'M', u'ني'), - (0xFC51, 'M', u'هج'), - (0xFC52, 'M', u'هم'), - (0xFC53, 'M', u'هى'), - (0xFC54, 'M', u'هي'), - (0xFC55, 'M', u'يج'), - (0xFC56, 'M', u'يح'), - (0xFC57, 'M', u'يخ'), - (0xFC58, 'M', u'يم'), - (0xFC59, 'M', u'يى'), - (0xFC5A, 'M', u'يي'), - (0xFC5B, 'M', u'ذٰ'), - (0xFC5C, 'M', u'رٰ'), - (0xFC5D, 'M', u'ىٰ'), - (0xFC5E, '3', u' ٌّ'), - (0xFC5F, '3', u' ٍّ'), - (0xFC60, '3', u' َّ'), - (0xFC61, '3', u' ُّ'), - (0xFC62, '3', u' ِّ'), - (0xFC63, '3', u' ّٰ'), - (0xFC64, 'M', u'ئر'), - (0xFC65, 'M', u'ئز'), - (0xFC66, 'M', u'ئم'), - (0xFC67, 'M', u'ئن'), - (0xFC68, 'M', u'ئى'), - (0xFC69, 'M', u'ئي'), - (0xFC6A, 'M', u'بر'), - (0xFC6B, 'M', u'بز'), - (0xFC6C, 'M', u'بم'), - (0xFC6D, 'M', u'بن'), - (0xFC6E, 'M', u'بى'), - (0xFC6F, 'M', u'بي'), - (0xFC70, 'M', u'تر'), - (0xFC71, 'M', u'تز'), - (0xFC72, 'M', u'تم'), - (0xFC73, 'M', u'تن'), - (0xFC74, 'M', u'تى'), - (0xFC75, 'M', u'تي'), - (0xFC76, 'M', u'ثر'), - (0xFC77, 'M', u'ثز'), - (0xFC78, 'M', u'ثم'), - (0xFC79, 'M', u'ثن'), - (0xFC7A, 'M', u'ثى'), - (0xFC7B, 'M', u'ثي'), - (0xFC7C, 'M', u'فى'), - (0xFC7D, 'M', u'في'), - (0xFC7E, 'M', u'قى'), - (0xFC7F, 'M', u'قي'), - ] - -def _seg_46(): - return [ - (0xFC80, 'M', u'كا'), - (0xFC81, 'M', u'كل'), - (0xFC82, 'M', u'كم'), - (0xFC83, 'M', u'كى'), - (0xFC84, 'M', u'كي'), - (0xFC85, 'M', u'لم'), - (0xFC86, 'M', u'لى'), - (0xFC87, 'M', u'لي'), - (0xFC88, 'M', u'ما'), - (0xFC89, 'M', u'مم'), - (0xFC8A, 'M', u'نر'), - (0xFC8B, 'M', u'نز'), - (0xFC8C, 'M', u'نم'), - (0xFC8D, 'M', u'نن'), - (0xFC8E, 'M', u'نى'), - (0xFC8F, 'M', u'ني'), - (0xFC90, 'M', u'ىٰ'), - (0xFC91, 'M', u'ير'), - (0xFC92, 'M', u'يز'), - (0xFC93, 'M', u'يم'), - (0xFC94, 'M', u'ين'), - (0xFC95, 'M', u'يى'), - (0xFC96, 'M', u'يي'), - (0xFC97, 'M', u'ئج'), - (0xFC98, 'M', u'ئح'), - (0xFC99, 'M', u'ئخ'), - (0xFC9A, 'M', u'ئم'), - (0xFC9B, 'M', u'ئه'), - (0xFC9C, 'M', u'بج'), - (0xFC9D, 'M', u'بح'), - (0xFC9E, 'M', u'بخ'), - (0xFC9F, 'M', u'بم'), - (0xFCA0, 'M', u'به'), - (0xFCA1, 'M', u'تج'), - (0xFCA2, 'M', u'تح'), - (0xFCA3, 'M', u'تخ'), - (0xFCA4, 'M', u'تم'), - (0xFCA5, 'M', u'ته'), - (0xFCA6, 'M', u'ثم'), - (0xFCA7, 'M', u'جح'), - (0xFCA8, 'M', u'جم'), - (0xFCA9, 'M', u'حج'), - (0xFCAA, 'M', u'حم'), - (0xFCAB, 'M', u'خج'), - (0xFCAC, 'M', u'خم'), - (0xFCAD, 'M', u'سج'), - (0xFCAE, 'M', u'سح'), - (0xFCAF, 'M', u'سخ'), - (0xFCB0, 'M', u'سم'), - (0xFCB1, 'M', u'صح'), - (0xFCB2, 'M', u'صخ'), - (0xFCB3, 'M', u'صم'), - (0xFCB4, 'M', u'ضج'), - (0xFCB5, 'M', u'ضح'), - (0xFCB6, 'M', u'ضخ'), - (0xFCB7, 'M', u'ضم'), - (0xFCB8, 'M', u'طح'), - (0xFCB9, 'M', u'ظم'), - (0xFCBA, 'M', u'عج'), - (0xFCBB, 'M', u'عم'), - (0xFCBC, 'M', u'غج'), - (0xFCBD, 'M', u'غم'), - (0xFCBE, 'M', u'فج'), - (0xFCBF, 'M', u'فح'), - (0xFCC0, 'M', u'فخ'), - (0xFCC1, 'M', u'فم'), - (0xFCC2, 'M', u'قح'), - (0xFCC3, 'M', u'قم'), - (0xFCC4, 'M', u'كج'), - (0xFCC5, 'M', u'كح'), - (0xFCC6, 'M', u'كخ'), - (0xFCC7, 'M', u'كل'), - (0xFCC8, 'M', u'كم'), - (0xFCC9, 'M', u'لج'), - (0xFCCA, 'M', u'لح'), - (0xFCCB, 'M', u'لخ'), - (0xFCCC, 'M', u'لم'), - (0xFCCD, 'M', u'له'), - (0xFCCE, 'M', u'مج'), - (0xFCCF, 'M', u'مح'), - (0xFCD0, 'M', u'مخ'), - (0xFCD1, 'M', u'مم'), - (0xFCD2, 'M', u'نج'), - (0xFCD3, 'M', u'نح'), - (0xFCD4, 'M', u'نخ'), - (0xFCD5, 'M', u'نم'), - (0xFCD6, 'M', u'نه'), - (0xFCD7, 'M', u'هج'), - (0xFCD8, 'M', u'هم'), - (0xFCD9, 'M', u'هٰ'), - (0xFCDA, 'M', u'يج'), - (0xFCDB, 'M', u'يح'), - (0xFCDC, 'M', u'يخ'), - (0xFCDD, 'M', u'يم'), - (0xFCDE, 'M', u'يه'), - (0xFCDF, 'M', u'ئم'), - (0xFCE0, 'M', u'ئه'), - (0xFCE1, 'M', u'بم'), - (0xFCE2, 'M', u'به'), - (0xFCE3, 'M', u'تم'), - ] - -def _seg_47(): - return [ - (0xFCE4, 'M', u'ته'), - (0xFCE5, 'M', u'ثم'), - (0xFCE6, 'M', u'ثه'), - (0xFCE7, 'M', u'سم'), - (0xFCE8, 'M', u'سه'), - (0xFCE9, 'M', u'شم'), - (0xFCEA, 'M', u'شه'), - (0xFCEB, 'M', u'كل'), - (0xFCEC, 'M', u'كم'), - (0xFCED, 'M', u'لم'), - (0xFCEE, 'M', u'نم'), - (0xFCEF, 'M', u'نه'), - (0xFCF0, 'M', u'يم'), - (0xFCF1, 'M', u'يه'), - (0xFCF2, 'M', u'ـَّ'), - (0xFCF3, 'M', u'ـُّ'), - (0xFCF4, 'M', u'ـِّ'), - (0xFCF5, 'M', u'طى'), - (0xFCF6, 'M', u'طي'), - (0xFCF7, 'M', u'عى'), - (0xFCF8, 'M', u'عي'), - (0xFCF9, 'M', u'غى'), - (0xFCFA, 'M', u'غي'), - (0xFCFB, 'M', u'سى'), - (0xFCFC, 'M', u'سي'), - (0xFCFD, 'M', u'شى'), - (0xFCFE, 'M', u'شي'), - (0xFCFF, 'M', u'حى'), - (0xFD00, 'M', u'حي'), - (0xFD01, 'M', u'جى'), - (0xFD02, 'M', u'جي'), - (0xFD03, 'M', u'خى'), - (0xFD04, 'M', u'خي'), - (0xFD05, 'M', u'صى'), - (0xFD06, 'M', u'صي'), - (0xFD07, 'M', u'ضى'), - (0xFD08, 'M', u'ضي'), - (0xFD09, 'M', u'شج'), - (0xFD0A, 'M', u'شح'), - (0xFD0B, 'M', u'شخ'), - (0xFD0C, 'M', u'شم'), - (0xFD0D, 'M', u'شر'), - (0xFD0E, 'M', u'سر'), - (0xFD0F, 'M', u'صر'), - (0xFD10, 'M', u'ضر'), - (0xFD11, 'M', u'طى'), - (0xFD12, 'M', u'طي'), - (0xFD13, 'M', u'عى'), - (0xFD14, 'M', u'عي'), - (0xFD15, 'M', u'غى'), - (0xFD16, 'M', u'غي'), - (0xFD17, 'M', u'سى'), - (0xFD18, 'M', u'سي'), - (0xFD19, 'M', u'شى'), - (0xFD1A, 'M', u'شي'), - (0xFD1B, 'M', u'حى'), - (0xFD1C, 'M', u'حي'), - (0xFD1D, 'M', u'جى'), - (0xFD1E, 'M', u'جي'), - (0xFD1F, 'M', u'خى'), - (0xFD20, 'M', u'خي'), - (0xFD21, 'M', u'صى'), - (0xFD22, 'M', u'صي'), - (0xFD23, 'M', u'ضى'), - (0xFD24, 'M', u'ضي'), - (0xFD25, 'M', u'شج'), - (0xFD26, 'M', u'شح'), - (0xFD27, 'M', u'شخ'), - (0xFD28, 'M', u'شم'), - (0xFD29, 'M', u'شر'), - (0xFD2A, 'M', u'سر'), - (0xFD2B, 'M', u'صر'), - (0xFD2C, 'M', u'ضر'), - (0xFD2D, 'M', u'شج'), - (0xFD2E, 'M', u'شح'), - (0xFD2F, 'M', u'شخ'), - (0xFD30, 'M', u'شم'), - (0xFD31, 'M', u'سه'), - (0xFD32, 'M', u'شه'), - (0xFD33, 'M', u'طم'), - (0xFD34, 'M', u'سج'), - (0xFD35, 'M', u'سح'), - (0xFD36, 'M', u'سخ'), - (0xFD37, 'M', u'شج'), - (0xFD38, 'M', u'شح'), - (0xFD39, 'M', u'شخ'), - (0xFD3A, 'M', u'طم'), - (0xFD3B, 'M', u'ظم'), - (0xFD3C, 'M', u'اً'), - (0xFD3E, 'V'), - (0xFD40, 'X'), - (0xFD50, 'M', u'تجم'), - (0xFD51, 'M', u'تحج'), - (0xFD53, 'M', u'تحم'), - (0xFD54, 'M', u'تخم'), - (0xFD55, 'M', u'تمج'), - (0xFD56, 'M', u'تمح'), - (0xFD57, 'M', u'تمخ'), - (0xFD58, 'M', u'جمح'), - (0xFD5A, 'M', u'حمي'), - ] - -def _seg_48(): - return [ - (0xFD5B, 'M', u'حمى'), - (0xFD5C, 'M', u'سحج'), - (0xFD5D, 'M', u'سجح'), - (0xFD5E, 'M', u'سجى'), - (0xFD5F, 'M', u'سمح'), - (0xFD61, 'M', u'سمج'), - (0xFD62, 'M', u'سمم'), - (0xFD64, 'M', u'صحح'), - (0xFD66, 'M', u'صمم'), - (0xFD67, 'M', u'شحم'), - (0xFD69, 'M', u'شجي'), - (0xFD6A, 'M', u'شمخ'), - (0xFD6C, 'M', u'شمم'), - (0xFD6E, 'M', u'ضحى'), - (0xFD6F, 'M', u'ضخم'), - (0xFD71, 'M', u'طمح'), - (0xFD73, 'M', u'طمم'), - (0xFD74, 'M', u'طمي'), - (0xFD75, 'M', u'عجم'), - (0xFD76, 'M', u'عمم'), - (0xFD78, 'M', u'عمى'), - (0xFD79, 'M', u'غمم'), - (0xFD7A, 'M', u'غمي'), - (0xFD7B, 'M', u'غمى'), - (0xFD7C, 'M', u'فخم'), - (0xFD7E, 'M', u'قمح'), - (0xFD7F, 'M', u'قمم'), - (0xFD80, 'M', u'لحم'), - (0xFD81, 'M', u'لحي'), - (0xFD82, 'M', u'لحى'), - (0xFD83, 'M', u'لجج'), - (0xFD85, 'M', u'لخم'), - (0xFD87, 'M', u'لمح'), - (0xFD89, 'M', u'محج'), - (0xFD8A, 'M', u'محم'), - (0xFD8B, 'M', u'محي'), - (0xFD8C, 'M', u'مجح'), - (0xFD8D, 'M', u'مجم'), - (0xFD8E, 'M', u'مخج'), - (0xFD8F, 'M', u'مخم'), - (0xFD90, 'X'), - (0xFD92, 'M', u'مجخ'), - (0xFD93, 'M', u'همج'), - (0xFD94, 'M', u'همم'), - (0xFD95, 'M', u'نحم'), - (0xFD96, 'M', u'نحى'), - (0xFD97, 'M', u'نجم'), - (0xFD99, 'M', u'نجى'), - (0xFD9A, 'M', u'نمي'), - (0xFD9B, 'M', u'نمى'), - (0xFD9C, 'M', u'يمم'), - (0xFD9E, 'M', u'بخي'), - (0xFD9F, 'M', u'تجي'), - (0xFDA0, 'M', u'تجى'), - (0xFDA1, 'M', u'تخي'), - (0xFDA2, 'M', u'تخى'), - (0xFDA3, 'M', u'تمي'), - (0xFDA4, 'M', u'تمى'), - (0xFDA5, 'M', u'جمي'), - (0xFDA6, 'M', u'جحى'), - (0xFDA7, 'M', u'جمى'), - (0xFDA8, 'M', u'سخى'), - (0xFDA9, 'M', u'صحي'), - (0xFDAA, 'M', u'شحي'), - (0xFDAB, 'M', u'ضحي'), - (0xFDAC, 'M', u'لجي'), - (0xFDAD, 'M', u'لمي'), - (0xFDAE, 'M', u'يحي'), - (0xFDAF, 'M', u'يجي'), - (0xFDB0, 'M', u'يمي'), - (0xFDB1, 'M', u'ممي'), - (0xFDB2, 'M', u'قمي'), - (0xFDB3, 'M', u'نحي'), - (0xFDB4, 'M', u'قمح'), - (0xFDB5, 'M', u'لحم'), - (0xFDB6, 'M', u'عمي'), - (0xFDB7, 'M', u'كمي'), - (0xFDB8, 'M', u'نجح'), - (0xFDB9, 'M', u'مخي'), - (0xFDBA, 'M', u'لجم'), - (0xFDBB, 'M', u'كمم'), - (0xFDBC, 'M', u'لجم'), - (0xFDBD, 'M', u'نجح'), - (0xFDBE, 'M', u'جحي'), - (0xFDBF, 'M', u'حجي'), - (0xFDC0, 'M', u'مجي'), - (0xFDC1, 'M', u'فمي'), - (0xFDC2, 'M', u'بحي'), - (0xFDC3, 'M', u'كمم'), - (0xFDC4, 'M', u'عجم'), - (0xFDC5, 'M', u'صمم'), - (0xFDC6, 'M', u'سخي'), - (0xFDC7, 'M', u'نجي'), - (0xFDC8, 'X'), - (0xFDF0, 'M', u'صلے'), - (0xFDF1, 'M', u'قلے'), - (0xFDF2, 'M', u'الله'), - (0xFDF3, 'M', u'اكبر'), - (0xFDF4, 'M', u'محمد'), - (0xFDF5, 'M', u'صلعم'), - ] - -def _seg_49(): - return [ - (0xFDF6, 'M', u'رسول'), - (0xFDF7, 'M', u'عليه'), - (0xFDF8, 'M', u'وسلم'), - (0xFDF9, 'M', u'صلى'), - (0xFDFA, '3', u'صلى الله عليه وسلم'), - (0xFDFB, '3', u'جل جلاله'), - (0xFDFC, 'M', u'ریال'), - (0xFDFD, 'V'), - (0xFDFE, 'X'), - (0xFE00, 'I'), - (0xFE10, '3', u','), - (0xFE11, 'M', u'、'), - (0xFE12, 'X'), - (0xFE13, '3', u':'), - (0xFE14, '3', u';'), - (0xFE15, '3', u'!'), - (0xFE16, '3', u'?'), - (0xFE17, 'M', u'〖'), - (0xFE18, 'M', u'〗'), - (0xFE19, 'X'), - (0xFE20, 'V'), - (0xFE30, 'X'), - (0xFE31, 'M', u'—'), - (0xFE32, 'M', u'–'), - (0xFE33, '3', u'_'), - (0xFE35, '3', u'('), - (0xFE36, '3', u')'), - (0xFE37, '3', u'{'), - (0xFE38, '3', u'}'), - (0xFE39, 'M', u'〔'), - (0xFE3A, 'M', u'〕'), - (0xFE3B, 'M', u'【'), - (0xFE3C, 'M', u'】'), - (0xFE3D, 'M', u'《'), - (0xFE3E, 'M', u'》'), - (0xFE3F, 'M', u'〈'), - (0xFE40, 'M', u'〉'), - (0xFE41, 'M', u'「'), - (0xFE42, 'M', u'」'), - (0xFE43, 'M', u'『'), - (0xFE44, 'M', u'』'), - (0xFE45, 'V'), - (0xFE47, '3', u'['), - (0xFE48, '3', u']'), - (0xFE49, '3', u' ̅'), - (0xFE4D, '3', u'_'), - (0xFE50, '3', u','), - (0xFE51, 'M', u'、'), - (0xFE52, 'X'), - (0xFE54, '3', u';'), - (0xFE55, '3', u':'), - (0xFE56, '3', u'?'), - (0xFE57, '3', u'!'), - (0xFE58, 'M', u'—'), - (0xFE59, '3', u'('), - (0xFE5A, '3', u')'), - (0xFE5B, '3', u'{'), - (0xFE5C, '3', u'}'), - (0xFE5D, 'M', u'〔'), - (0xFE5E, 'M', u'〕'), - (0xFE5F, '3', u'#'), - (0xFE60, '3', u'&'), - (0xFE61, '3', u'*'), - (0xFE62, '3', u'+'), - (0xFE63, 'M', u'-'), - (0xFE64, '3', u'<'), - (0xFE65, '3', u'>'), - (0xFE66, '3', u'='), - (0xFE67, 'X'), - (0xFE68, '3', u'\\'), - (0xFE69, '3', u'$'), - (0xFE6A, '3', u'%'), - (0xFE6B, '3', u'@'), - (0xFE6C, 'X'), - (0xFE70, '3', u' ً'), - (0xFE71, 'M', u'ـً'), - (0xFE72, '3', u' ٌ'), - (0xFE73, 'V'), - (0xFE74, '3', u' ٍ'), - (0xFE75, 'X'), - (0xFE76, '3', u' َ'), - (0xFE77, 'M', u'ـَ'), - (0xFE78, '3', u' ُ'), - (0xFE79, 'M', u'ـُ'), - (0xFE7A, '3', u' ِ'), - (0xFE7B, 'M', u'ـِ'), - (0xFE7C, '3', u' ّ'), - (0xFE7D, 'M', u'ـّ'), - (0xFE7E, '3', u' ْ'), - (0xFE7F, 'M', u'ـْ'), - (0xFE80, 'M', u'ء'), - (0xFE81, 'M', u'آ'), - (0xFE83, 'M', u'أ'), - (0xFE85, 'M', u'ؤ'), - (0xFE87, 'M', u'إ'), - (0xFE89, 'M', u'ئ'), - (0xFE8D, 'M', u'ا'), - (0xFE8F, 'M', u'ب'), - (0xFE93, 'M', u'ة'), - (0xFE95, 'M', u'ت'), - ] - -def _seg_50(): - return [ - (0xFE99, 'M', u'ث'), - (0xFE9D, 'M', u'ج'), - (0xFEA1, 'M', u'ح'), - (0xFEA5, 'M', u'خ'), - (0xFEA9, 'M', u'د'), - (0xFEAB, 'M', u'ذ'), - (0xFEAD, 'M', u'ر'), - (0xFEAF, 'M', u'ز'), - (0xFEB1, 'M', u'س'), - (0xFEB5, 'M', u'ش'), - (0xFEB9, 'M', u'ص'), - (0xFEBD, 'M', u'ض'), - (0xFEC1, 'M', u'ط'), - (0xFEC5, 'M', u'ظ'), - (0xFEC9, 'M', u'ع'), - (0xFECD, 'M', u'غ'), - (0xFED1, 'M', u'ف'), - (0xFED5, 'M', u'ق'), - (0xFED9, 'M', u'ك'), - (0xFEDD, 'M', u'ل'), - (0xFEE1, 'M', u'م'), - (0xFEE5, 'M', u'ن'), - (0xFEE9, 'M', u'ه'), - (0xFEED, 'M', u'و'), - (0xFEEF, 'M', u'ى'), - (0xFEF1, 'M', u'ي'), - (0xFEF5, 'M', u'لآ'), - (0xFEF7, 'M', u'لأ'), - (0xFEF9, 'M', u'لإ'), - (0xFEFB, 'M', u'لا'), - (0xFEFD, 'X'), - (0xFEFF, 'I'), - (0xFF00, 'X'), - (0xFF01, '3', u'!'), - (0xFF02, '3', u'"'), - (0xFF03, '3', u'#'), - (0xFF04, '3', u'$'), - (0xFF05, '3', u'%'), - (0xFF06, '3', u'&'), - (0xFF07, '3', u'\''), - (0xFF08, '3', u'('), - (0xFF09, '3', u')'), - (0xFF0A, '3', u'*'), - (0xFF0B, '3', u'+'), - (0xFF0C, '3', u','), - (0xFF0D, 'M', u'-'), - (0xFF0E, 'M', u'.'), - (0xFF0F, '3', u'/'), - (0xFF10, 'M', u'0'), - (0xFF11, 'M', u'1'), - (0xFF12, 'M', u'2'), - (0xFF13, 'M', u'3'), - (0xFF14, 'M', u'4'), - (0xFF15, 'M', u'5'), - (0xFF16, 'M', u'6'), - (0xFF17, 'M', u'7'), - (0xFF18, 'M', u'8'), - (0xFF19, 'M', u'9'), - (0xFF1A, '3', u':'), - (0xFF1B, '3', u';'), - (0xFF1C, '3', u'<'), - (0xFF1D, '3', u'='), - (0xFF1E, '3', u'>'), - (0xFF1F, '3', u'?'), - (0xFF20, '3', u'@'), - (0xFF21, 'M', u'a'), - (0xFF22, 'M', u'b'), - (0xFF23, 'M', u'c'), - (0xFF24, 'M', u'd'), - (0xFF25, 'M', u'e'), - (0xFF26, 'M', u'f'), - (0xFF27, 'M', u'g'), - (0xFF28, 'M', u'h'), - (0xFF29, 'M', u'i'), - (0xFF2A, 'M', u'j'), - (0xFF2B, 'M', u'k'), - (0xFF2C, 'M', u'l'), - (0xFF2D, 'M', u'm'), - (0xFF2E, 'M', u'n'), - (0xFF2F, 'M', u'o'), - (0xFF30, 'M', u'p'), - (0xFF31, 'M', u'q'), - (0xFF32, 'M', u'r'), - (0xFF33, 'M', u's'), - (0xFF34, 'M', u't'), - (0xFF35, 'M', u'u'), - (0xFF36, 'M', u'v'), - (0xFF37, 'M', u'w'), - (0xFF38, 'M', u'x'), - (0xFF39, 'M', u'y'), - (0xFF3A, 'M', u'z'), - (0xFF3B, '3', u'['), - (0xFF3C, '3', u'\\'), - (0xFF3D, '3', u']'), - (0xFF3E, '3', u'^'), - (0xFF3F, '3', u'_'), - (0xFF40, '3', u'`'), - (0xFF41, 'M', u'a'), - (0xFF42, 'M', u'b'), - (0xFF43, 'M', u'c'), - ] - -def _seg_51(): - return [ - (0xFF44, 'M', u'd'), - (0xFF45, 'M', u'e'), - (0xFF46, 'M', u'f'), - (0xFF47, 'M', u'g'), - (0xFF48, 'M', u'h'), - (0xFF49, 'M', u'i'), - (0xFF4A, 'M', u'j'), - (0xFF4B, 'M', u'k'), - (0xFF4C, 'M', u'l'), - (0xFF4D, 'M', u'm'), - (0xFF4E, 'M', u'n'), - (0xFF4F, 'M', u'o'), - (0xFF50, 'M', u'p'), - (0xFF51, 'M', u'q'), - (0xFF52, 'M', u'r'), - (0xFF53, 'M', u's'), - (0xFF54, 'M', u't'), - (0xFF55, 'M', u'u'), - (0xFF56, 'M', u'v'), - (0xFF57, 'M', u'w'), - (0xFF58, 'M', u'x'), - (0xFF59, 'M', u'y'), - (0xFF5A, 'M', u'z'), - (0xFF5B, '3', u'{'), - (0xFF5C, '3', u'|'), - (0xFF5D, '3', u'}'), - (0xFF5E, '3', u'~'), - (0xFF5F, 'M', u'⦅'), - (0xFF60, 'M', u'⦆'), - (0xFF61, 'M', u'.'), - (0xFF62, 'M', u'「'), - (0xFF63, 'M', u'」'), - (0xFF64, 'M', u'、'), - (0xFF65, 'M', u'・'), - (0xFF66, 'M', u'ヲ'), - (0xFF67, 'M', u'ァ'), - (0xFF68, 'M', u'ィ'), - (0xFF69, 'M', u'ゥ'), - (0xFF6A, 'M', u'ェ'), - (0xFF6B, 'M', u'ォ'), - (0xFF6C, 'M', u'ャ'), - (0xFF6D, 'M', u'ュ'), - (0xFF6E, 'M', u'ョ'), - (0xFF6F, 'M', u'ッ'), - (0xFF70, 'M', u'ー'), - (0xFF71, 'M', u'ア'), - (0xFF72, 'M', u'イ'), - (0xFF73, 'M', u'ウ'), - (0xFF74, 'M', u'エ'), - (0xFF75, 'M', u'オ'), - (0xFF76, 'M', u'カ'), - (0xFF77, 'M', u'キ'), - (0xFF78, 'M', u'ク'), - (0xFF79, 'M', u'ケ'), - (0xFF7A, 'M', u'コ'), - (0xFF7B, 'M', u'サ'), - (0xFF7C, 'M', u'シ'), - (0xFF7D, 'M', u'ス'), - (0xFF7E, 'M', u'セ'), - (0xFF7F, 'M', u'ソ'), - (0xFF80, 'M', u'タ'), - (0xFF81, 'M', u'チ'), - (0xFF82, 'M', u'ツ'), - (0xFF83, 'M', u'テ'), - (0xFF84, 'M', u'ト'), - (0xFF85, 'M', u'ナ'), - (0xFF86, 'M', u'ニ'), - (0xFF87, 'M', u'ヌ'), - (0xFF88, 'M', u'ネ'), - (0xFF89, 'M', u'ノ'), - (0xFF8A, 'M', u'ハ'), - (0xFF8B, 'M', u'ヒ'), - (0xFF8C, 'M', u'フ'), - (0xFF8D, 'M', u'ヘ'), - (0xFF8E, 'M', u'ホ'), - (0xFF8F, 'M', u'マ'), - (0xFF90, 'M', u'ミ'), - (0xFF91, 'M', u'ム'), - (0xFF92, 'M', u'メ'), - (0xFF93, 'M', u'モ'), - (0xFF94, 'M', u'ヤ'), - (0xFF95, 'M', u'ユ'), - (0xFF96, 'M', u'ヨ'), - (0xFF97, 'M', u'ラ'), - (0xFF98, 'M', u'リ'), - (0xFF99, 'M', u'ル'), - (0xFF9A, 'M', u'レ'), - (0xFF9B, 'M', u'ロ'), - (0xFF9C, 'M', u'ワ'), - (0xFF9D, 'M', u'ン'), - (0xFF9E, 'M', u'゙'), - (0xFF9F, 'M', u'゚'), - (0xFFA0, 'X'), - (0xFFA1, 'M', u'ᄀ'), - (0xFFA2, 'M', u'ᄁ'), - (0xFFA3, 'M', u'ᆪ'), - (0xFFA4, 'M', u'ᄂ'), - (0xFFA5, 'M', u'ᆬ'), - (0xFFA6, 'M', u'ᆭ'), - (0xFFA7, 'M', u'ᄃ'), - ] - -def _seg_52(): - return [ - (0xFFA8, 'M', u'ᄄ'), - (0xFFA9, 'M', u'ᄅ'), - (0xFFAA, 'M', u'ᆰ'), - (0xFFAB, 'M', u'ᆱ'), - (0xFFAC, 'M', u'ᆲ'), - (0xFFAD, 'M', u'ᆳ'), - (0xFFAE, 'M', u'ᆴ'), - (0xFFAF, 'M', u'ᆵ'), - (0xFFB0, 'M', u'ᄚ'), - (0xFFB1, 'M', u'ᄆ'), - (0xFFB2, 'M', u'ᄇ'), - (0xFFB3, 'M', u'ᄈ'), - (0xFFB4, 'M', u'ᄡ'), - (0xFFB5, 'M', u'ᄉ'), - (0xFFB6, 'M', u'ᄊ'), - (0xFFB7, 'M', u'ᄋ'), - (0xFFB8, 'M', u'ᄌ'), - (0xFFB9, 'M', u'ᄍ'), - (0xFFBA, 'M', u'ᄎ'), - (0xFFBB, 'M', u'ᄏ'), - (0xFFBC, 'M', u'ᄐ'), - (0xFFBD, 'M', u'ᄑ'), - (0xFFBE, 'M', u'ᄒ'), - (0xFFBF, 'X'), - (0xFFC2, 'M', u'ᅡ'), - (0xFFC3, 'M', u'ᅢ'), - (0xFFC4, 'M', u'ᅣ'), - (0xFFC5, 'M', u'ᅤ'), - (0xFFC6, 'M', u'ᅥ'), - (0xFFC7, 'M', u'ᅦ'), - (0xFFC8, 'X'), - (0xFFCA, 'M', u'ᅧ'), - (0xFFCB, 'M', u'ᅨ'), - (0xFFCC, 'M', u'ᅩ'), - (0xFFCD, 'M', u'ᅪ'), - (0xFFCE, 'M', u'ᅫ'), - (0xFFCF, 'M', u'ᅬ'), - (0xFFD0, 'X'), - (0xFFD2, 'M', u'ᅭ'), - (0xFFD3, 'M', u'ᅮ'), - (0xFFD4, 'M', u'ᅯ'), - (0xFFD5, 'M', u'ᅰ'), - (0xFFD6, 'M', u'ᅱ'), - (0xFFD7, 'M', u'ᅲ'), - (0xFFD8, 'X'), - (0xFFDA, 'M', u'ᅳ'), - (0xFFDB, 'M', u'ᅴ'), - (0xFFDC, 'M', u'ᅵ'), - (0xFFDD, 'X'), - (0xFFE0, 'M', u'¢'), - (0xFFE1, 'M', u'£'), - (0xFFE2, 'M', u'¬'), - (0xFFE3, '3', u' ̄'), - (0xFFE4, 'M', u'¦'), - (0xFFE5, 'M', u'¥'), - (0xFFE6, 'M', u'₩'), - (0xFFE7, 'X'), - (0xFFE8, 'M', u'│'), - (0xFFE9, 'M', u'←'), - (0xFFEA, 'M', u'↑'), - (0xFFEB, 'M', u'→'), - (0xFFEC, 'M', u'↓'), - (0xFFED, 'M', u'■'), - (0xFFEE, 'M', u'○'), - (0xFFEF, 'X'), - (0x10000, 'V'), - (0x1000C, 'X'), - (0x1000D, 'V'), - (0x10027, 'X'), - (0x10028, 'V'), - (0x1003B, 'X'), - (0x1003C, 'V'), - (0x1003E, 'X'), - (0x1003F, 'V'), - (0x1004E, 'X'), - (0x10050, 'V'), - (0x1005E, 'X'), - (0x10080, 'V'), - (0x100FB, 'X'), - (0x10100, 'V'), - (0x10103, 'X'), - (0x10107, 'V'), - (0x10134, 'X'), - (0x10137, 'V'), - (0x1018F, 'X'), - (0x10190, 'V'), - (0x1019C, 'X'), - (0x101A0, 'V'), - (0x101A1, 'X'), - (0x101D0, 'V'), - (0x101FE, 'X'), - (0x10280, 'V'), - (0x1029D, 'X'), - (0x102A0, 'V'), - (0x102D1, 'X'), - (0x102E0, 'V'), - (0x102FC, 'X'), - (0x10300, 'V'), - (0x10324, 'X'), - (0x1032D, 'V'), - ] - -def _seg_53(): - return [ - (0x1034B, 'X'), - (0x10350, 'V'), - (0x1037B, 'X'), - (0x10380, 'V'), - (0x1039E, 'X'), - (0x1039F, 'V'), - (0x103C4, 'X'), - (0x103C8, 'V'), - (0x103D6, 'X'), - (0x10400, 'M', u'𐐨'), - (0x10401, 'M', u'𐐩'), - (0x10402, 'M', u'𐐪'), - (0x10403, 'M', u'𐐫'), - (0x10404, 'M', u'𐐬'), - (0x10405, 'M', u'𐐭'), - (0x10406, 'M', u'𐐮'), - (0x10407, 'M', u'𐐯'), - (0x10408, 'M', u'𐐰'), - (0x10409, 'M', u'𐐱'), - (0x1040A, 'M', u'𐐲'), - (0x1040B, 'M', u'𐐳'), - (0x1040C, 'M', u'𐐴'), - (0x1040D, 'M', u'𐐵'), - (0x1040E, 'M', u'𐐶'), - (0x1040F, 'M', u'𐐷'), - (0x10410, 'M', u'𐐸'), - (0x10411, 'M', u'𐐹'), - (0x10412, 'M', u'𐐺'), - (0x10413, 'M', u'𐐻'), - (0x10414, 'M', u'𐐼'), - (0x10415, 'M', u'𐐽'), - (0x10416, 'M', u'𐐾'), - (0x10417, 'M', u'𐐿'), - (0x10418, 'M', u'𐑀'), - (0x10419, 'M', u'𐑁'), - (0x1041A, 'M', u'𐑂'), - (0x1041B, 'M', u'𐑃'), - (0x1041C, 'M', u'𐑄'), - (0x1041D, 'M', u'𐑅'), - (0x1041E, 'M', u'𐑆'), - (0x1041F, 'M', u'𐑇'), - (0x10420, 'M', u'𐑈'), - (0x10421, 'M', u'𐑉'), - (0x10422, 'M', u'𐑊'), - (0x10423, 'M', u'𐑋'), - (0x10424, 'M', u'𐑌'), - (0x10425, 'M', u'𐑍'), - (0x10426, 'M', u'𐑎'), - (0x10427, 'M', u'𐑏'), - (0x10428, 'V'), - (0x1049E, 'X'), - (0x104A0, 'V'), - (0x104AA, 'X'), - (0x104B0, 'M', u'𐓘'), - (0x104B1, 'M', u'𐓙'), - (0x104B2, 'M', u'𐓚'), - (0x104B3, 'M', u'𐓛'), - (0x104B4, 'M', u'𐓜'), - (0x104B5, 'M', u'𐓝'), - (0x104B6, 'M', u'𐓞'), - (0x104B7, 'M', u'𐓟'), - (0x104B8, 'M', u'𐓠'), - (0x104B9, 'M', u'𐓡'), - (0x104BA, 'M', u'𐓢'), - (0x104BB, 'M', u'𐓣'), - (0x104BC, 'M', u'𐓤'), - (0x104BD, 'M', u'𐓥'), - (0x104BE, 'M', u'𐓦'), - (0x104BF, 'M', u'𐓧'), - (0x104C0, 'M', u'𐓨'), - (0x104C1, 'M', u'𐓩'), - (0x104C2, 'M', u'𐓪'), - (0x104C3, 'M', u'𐓫'), - (0x104C4, 'M', u'𐓬'), - (0x104C5, 'M', u'𐓭'), - (0x104C6, 'M', u'𐓮'), - (0x104C7, 'M', u'𐓯'), - (0x104C8, 'M', u'𐓰'), - (0x104C9, 'M', u'𐓱'), - (0x104CA, 'M', u'𐓲'), - (0x104CB, 'M', u'𐓳'), - (0x104CC, 'M', u'𐓴'), - (0x104CD, 'M', u'𐓵'), - (0x104CE, 'M', u'𐓶'), - (0x104CF, 'M', u'𐓷'), - (0x104D0, 'M', u'𐓸'), - (0x104D1, 'M', u'𐓹'), - (0x104D2, 'M', u'𐓺'), - (0x104D3, 'M', u'𐓻'), - (0x104D4, 'X'), - (0x104D8, 'V'), - (0x104FC, 'X'), - (0x10500, 'V'), - (0x10528, 'X'), - (0x10530, 'V'), - (0x10564, 'X'), - (0x1056F, 'V'), - (0x10570, 'X'), - (0x10600, 'V'), - (0x10737, 'X'), - ] - -def _seg_54(): - return [ - (0x10740, 'V'), - (0x10756, 'X'), - (0x10760, 'V'), - (0x10768, 'X'), - (0x10800, 'V'), - (0x10806, 'X'), - (0x10808, 'V'), - (0x10809, 'X'), - (0x1080A, 'V'), - (0x10836, 'X'), - (0x10837, 'V'), - (0x10839, 'X'), - (0x1083C, 'V'), - (0x1083D, 'X'), - (0x1083F, 'V'), - (0x10856, 'X'), - (0x10857, 'V'), - (0x1089F, 'X'), - (0x108A7, 'V'), - (0x108B0, 'X'), - (0x108E0, 'V'), - (0x108F3, 'X'), - (0x108F4, 'V'), - (0x108F6, 'X'), - (0x108FB, 'V'), - (0x1091C, 'X'), - (0x1091F, 'V'), - (0x1093A, 'X'), - (0x1093F, 'V'), - (0x10940, 'X'), - (0x10980, 'V'), - (0x109B8, 'X'), - (0x109BC, 'V'), - (0x109D0, 'X'), - (0x109D2, 'V'), - (0x10A04, 'X'), - (0x10A05, 'V'), - (0x10A07, 'X'), - (0x10A0C, 'V'), - (0x10A14, 'X'), - (0x10A15, 'V'), - (0x10A18, 'X'), - (0x10A19, 'V'), - (0x10A34, 'X'), - (0x10A38, 'V'), - (0x10A3B, 'X'), - (0x10A3F, 'V'), - (0x10A48, 'X'), - (0x10A50, 'V'), - (0x10A59, 'X'), - (0x10A60, 'V'), - (0x10AA0, 'X'), - (0x10AC0, 'V'), - (0x10AE7, 'X'), - (0x10AEB, 'V'), - (0x10AF7, 'X'), - (0x10B00, 'V'), - (0x10B36, 'X'), - (0x10B39, 'V'), - (0x10B56, 'X'), - (0x10B58, 'V'), - (0x10B73, 'X'), - (0x10B78, 'V'), - (0x10B92, 'X'), - (0x10B99, 'V'), - (0x10B9D, 'X'), - (0x10BA9, 'V'), - (0x10BB0, 'X'), - (0x10C00, 'V'), - (0x10C49, 'X'), - (0x10C80, 'M', u'𐳀'), - (0x10C81, 'M', u'𐳁'), - (0x10C82, 'M', u'𐳂'), - (0x10C83, 'M', u'𐳃'), - (0x10C84, 'M', u'𐳄'), - (0x10C85, 'M', u'𐳅'), - (0x10C86, 'M', u'𐳆'), - (0x10C87, 'M', u'𐳇'), - (0x10C88, 'M', u'𐳈'), - (0x10C89, 'M', u'𐳉'), - (0x10C8A, 'M', u'𐳊'), - (0x10C8B, 'M', u'𐳋'), - (0x10C8C, 'M', u'𐳌'), - (0x10C8D, 'M', u'𐳍'), - (0x10C8E, 'M', u'𐳎'), - (0x10C8F, 'M', u'𐳏'), - (0x10C90, 'M', u'𐳐'), - (0x10C91, 'M', u'𐳑'), - (0x10C92, 'M', u'𐳒'), - (0x10C93, 'M', u'𐳓'), - (0x10C94, 'M', u'𐳔'), - (0x10C95, 'M', u'𐳕'), - (0x10C96, 'M', u'𐳖'), - (0x10C97, 'M', u'𐳗'), - (0x10C98, 'M', u'𐳘'), - (0x10C99, 'M', u'𐳙'), - (0x10C9A, 'M', u'𐳚'), - (0x10C9B, 'M', u'𐳛'), - (0x10C9C, 'M', u'𐳜'), - (0x10C9D, 'M', u'𐳝'), - ] - -def _seg_55(): - return [ - (0x10C9E, 'M', u'𐳞'), - (0x10C9F, 'M', u'𐳟'), - (0x10CA0, 'M', u'𐳠'), - (0x10CA1, 'M', u'𐳡'), - (0x10CA2, 'M', u'𐳢'), - (0x10CA3, 'M', u'𐳣'), - (0x10CA4, 'M', u'𐳤'), - (0x10CA5, 'M', u'𐳥'), - (0x10CA6, 'M', u'𐳦'), - (0x10CA7, 'M', u'𐳧'), - (0x10CA8, 'M', u'𐳨'), - (0x10CA9, 'M', u'𐳩'), - (0x10CAA, 'M', u'𐳪'), - (0x10CAB, 'M', u'𐳫'), - (0x10CAC, 'M', u'𐳬'), - (0x10CAD, 'M', u'𐳭'), - (0x10CAE, 'M', u'𐳮'), - (0x10CAF, 'M', u'𐳯'), - (0x10CB0, 'M', u'𐳰'), - (0x10CB1, 'M', u'𐳱'), - (0x10CB2, 'M', u'𐳲'), - (0x10CB3, 'X'), - (0x10CC0, 'V'), - (0x10CF3, 'X'), - (0x10CFA, 'V'), - (0x10D00, 'X'), - (0x10E60, 'V'), - (0x10E7F, 'X'), - (0x11000, 'V'), - (0x1104E, 'X'), - (0x11052, 'V'), - (0x11070, 'X'), - (0x1107F, 'V'), - (0x110BD, 'X'), - (0x110BE, 'V'), - (0x110C2, 'X'), - (0x110D0, 'V'), - (0x110E9, 'X'), - (0x110F0, 'V'), - (0x110FA, 'X'), - (0x11100, 'V'), - (0x11135, 'X'), - (0x11136, 'V'), - (0x11144, 'X'), - (0x11150, 'V'), - (0x11177, 'X'), - (0x11180, 'V'), - (0x111CE, 'X'), - (0x111D0, 'V'), - (0x111E0, 'X'), - (0x111E1, 'V'), - (0x111F5, 'X'), - (0x11200, 'V'), - (0x11212, 'X'), - (0x11213, 'V'), - (0x1123F, 'X'), - (0x11280, 'V'), - (0x11287, 'X'), - (0x11288, 'V'), - (0x11289, 'X'), - (0x1128A, 'V'), - (0x1128E, 'X'), - (0x1128F, 'V'), - (0x1129E, 'X'), - (0x1129F, 'V'), - (0x112AA, 'X'), - (0x112B0, 'V'), - (0x112EB, 'X'), - (0x112F0, 'V'), - (0x112FA, 'X'), - (0x11300, 'V'), - (0x11304, 'X'), - (0x11305, 'V'), - (0x1130D, 'X'), - (0x1130F, 'V'), - (0x11311, 'X'), - (0x11313, 'V'), - (0x11329, 'X'), - (0x1132A, 'V'), - (0x11331, 'X'), - (0x11332, 'V'), - (0x11334, 'X'), - (0x11335, 'V'), - (0x1133A, 'X'), - (0x1133C, 'V'), - (0x11345, 'X'), - (0x11347, 'V'), - (0x11349, 'X'), - (0x1134B, 'V'), - (0x1134E, 'X'), - (0x11350, 'V'), - (0x11351, 'X'), - (0x11357, 'V'), - (0x11358, 'X'), - (0x1135D, 'V'), - (0x11364, 'X'), - (0x11366, 'V'), - (0x1136D, 'X'), - (0x11370, 'V'), - (0x11375, 'X'), - ] - -def _seg_56(): - return [ - (0x11400, 'V'), - (0x1145A, 'X'), - (0x1145B, 'V'), - (0x1145C, 'X'), - (0x1145D, 'V'), - (0x1145E, 'X'), - (0x11480, 'V'), - (0x114C8, 'X'), - (0x114D0, 'V'), - (0x114DA, 'X'), - (0x11580, 'V'), - (0x115B6, 'X'), - (0x115B8, 'V'), - (0x115DE, 'X'), - (0x11600, 'V'), - (0x11645, 'X'), - (0x11650, 'V'), - (0x1165A, 'X'), - (0x11660, 'V'), - (0x1166D, 'X'), - (0x11680, 'V'), - (0x116B8, 'X'), - (0x116C0, 'V'), - (0x116CA, 'X'), - (0x11700, 'V'), - (0x1171A, 'X'), - (0x1171D, 'V'), - (0x1172C, 'X'), - (0x11730, 'V'), - (0x11740, 'X'), - (0x118A0, 'M', u'𑣀'), - (0x118A1, 'M', u'𑣁'), - (0x118A2, 'M', u'𑣂'), - (0x118A3, 'M', u'𑣃'), - (0x118A4, 'M', u'𑣄'), - (0x118A5, 'M', u'𑣅'), - (0x118A6, 'M', u'𑣆'), - (0x118A7, 'M', u'𑣇'), - (0x118A8, 'M', u'𑣈'), - (0x118A9, 'M', u'𑣉'), - (0x118AA, 'M', u'𑣊'), - (0x118AB, 'M', u'𑣋'), - (0x118AC, 'M', u'𑣌'), - (0x118AD, 'M', u'𑣍'), - (0x118AE, 'M', u'𑣎'), - (0x118AF, 'M', u'𑣏'), - (0x118B0, 'M', u'𑣐'), - (0x118B1, 'M', u'𑣑'), - (0x118B2, 'M', u'𑣒'), - (0x118B3, 'M', u'𑣓'), - (0x118B4, 'M', u'𑣔'), - (0x118B5, 'M', u'𑣕'), - (0x118B6, 'M', u'𑣖'), - (0x118B7, 'M', u'𑣗'), - (0x118B8, 'M', u'𑣘'), - (0x118B9, 'M', u'𑣙'), - (0x118BA, 'M', u'𑣚'), - (0x118BB, 'M', u'𑣛'), - (0x118BC, 'M', u'𑣜'), - (0x118BD, 'M', u'𑣝'), - (0x118BE, 'M', u'𑣞'), - (0x118BF, 'M', u'𑣟'), - (0x118C0, 'V'), - (0x118F3, 'X'), - (0x118FF, 'V'), - (0x11900, 'X'), - (0x11A00, 'V'), - (0x11A48, 'X'), - (0x11A50, 'V'), - (0x11A84, 'X'), - (0x11A86, 'V'), - (0x11A9D, 'X'), - (0x11A9E, 'V'), - (0x11AA3, 'X'), - (0x11AC0, 'V'), - (0x11AF9, 'X'), - (0x11C00, 'V'), - (0x11C09, 'X'), - (0x11C0A, 'V'), - (0x11C37, 'X'), - (0x11C38, 'V'), - (0x11C46, 'X'), - (0x11C50, 'V'), - (0x11C6D, 'X'), - (0x11C70, 'V'), - (0x11C90, 'X'), - (0x11C92, 'V'), - (0x11CA8, 'X'), - (0x11CA9, 'V'), - (0x11CB7, 'X'), - (0x11D00, 'V'), - (0x11D07, 'X'), - (0x11D08, 'V'), - (0x11D0A, 'X'), - (0x11D0B, 'V'), - (0x11D37, 'X'), - (0x11D3A, 'V'), - (0x11D3B, 'X'), - (0x11D3C, 'V'), - (0x11D3E, 'X'), - ] - -def _seg_57(): - return [ - (0x11D3F, 'V'), - (0x11D48, 'X'), - (0x11D50, 'V'), - (0x11D5A, 'X'), - (0x12000, 'V'), - (0x1239A, 'X'), - (0x12400, 'V'), - (0x1246F, 'X'), - (0x12470, 'V'), - (0x12475, 'X'), - (0x12480, 'V'), - (0x12544, 'X'), - (0x13000, 'V'), - (0x1342F, 'X'), - (0x14400, 'V'), - (0x14647, 'X'), - (0x16800, 'V'), - (0x16A39, 'X'), - (0x16A40, 'V'), - (0x16A5F, 'X'), - (0x16A60, 'V'), - (0x16A6A, 'X'), - (0x16A6E, 'V'), - (0x16A70, 'X'), - (0x16AD0, 'V'), - (0x16AEE, 'X'), - (0x16AF0, 'V'), - (0x16AF6, 'X'), - (0x16B00, 'V'), - (0x16B46, 'X'), - (0x16B50, 'V'), - (0x16B5A, 'X'), - (0x16B5B, 'V'), - (0x16B62, 'X'), - (0x16B63, 'V'), - (0x16B78, 'X'), - (0x16B7D, 'V'), - (0x16B90, 'X'), - (0x16F00, 'V'), - (0x16F45, 'X'), - (0x16F50, 'V'), - (0x16F7F, 'X'), - (0x16F8F, 'V'), - (0x16FA0, 'X'), - (0x16FE0, 'V'), - (0x16FE2, 'X'), - (0x17000, 'V'), - (0x187ED, 'X'), - (0x18800, 'V'), - (0x18AF3, 'X'), - (0x1B000, 'V'), - (0x1B11F, 'X'), - (0x1B170, 'V'), - (0x1B2FC, 'X'), - (0x1BC00, 'V'), - (0x1BC6B, 'X'), - (0x1BC70, 'V'), - (0x1BC7D, 'X'), - (0x1BC80, 'V'), - (0x1BC89, 'X'), - (0x1BC90, 'V'), - (0x1BC9A, 'X'), - (0x1BC9C, 'V'), - (0x1BCA0, 'I'), - (0x1BCA4, 'X'), - (0x1D000, 'V'), - (0x1D0F6, 'X'), - (0x1D100, 'V'), - (0x1D127, 'X'), - (0x1D129, 'V'), - (0x1D15E, 'M', u'𝅗𝅥'), - (0x1D15F, 'M', u'𝅘𝅥'), - (0x1D160, 'M', u'𝅘𝅥𝅮'), - (0x1D161, 'M', u'𝅘𝅥𝅯'), - (0x1D162, 'M', u'𝅘𝅥𝅰'), - (0x1D163, 'M', u'𝅘𝅥𝅱'), - (0x1D164, 'M', u'𝅘𝅥𝅲'), - (0x1D165, 'V'), - (0x1D173, 'X'), - (0x1D17B, 'V'), - (0x1D1BB, 'M', u'𝆹𝅥'), - (0x1D1BC, 'M', u'𝆺𝅥'), - (0x1D1BD, 'M', u'𝆹𝅥𝅮'), - (0x1D1BE, 'M', u'𝆺𝅥𝅮'), - (0x1D1BF, 'M', u'𝆹𝅥𝅯'), - (0x1D1C0, 'M', u'𝆺𝅥𝅯'), - (0x1D1C1, 'V'), - (0x1D1E9, 'X'), - (0x1D200, 'V'), - (0x1D246, 'X'), - (0x1D300, 'V'), - (0x1D357, 'X'), - (0x1D360, 'V'), - (0x1D372, 'X'), - (0x1D400, 'M', u'a'), - (0x1D401, 'M', u'b'), - (0x1D402, 'M', u'c'), - (0x1D403, 'M', u'd'), - (0x1D404, 'M', u'e'), - (0x1D405, 'M', u'f'), - ] - -def _seg_58(): - return [ - (0x1D406, 'M', u'g'), - (0x1D407, 'M', u'h'), - (0x1D408, 'M', u'i'), - (0x1D409, 'M', u'j'), - (0x1D40A, 'M', u'k'), - (0x1D40B, 'M', u'l'), - (0x1D40C, 'M', u'm'), - (0x1D40D, 'M', u'n'), - (0x1D40E, 'M', u'o'), - (0x1D40F, 'M', u'p'), - (0x1D410, 'M', u'q'), - (0x1D411, 'M', u'r'), - (0x1D412, 'M', u's'), - (0x1D413, 'M', u't'), - (0x1D414, 'M', u'u'), - (0x1D415, 'M', u'v'), - (0x1D416, 'M', u'w'), - (0x1D417, 'M', u'x'), - (0x1D418, 'M', u'y'), - (0x1D419, 'M', u'z'), - (0x1D41A, 'M', u'a'), - (0x1D41B, 'M', u'b'), - (0x1D41C, 'M', u'c'), - (0x1D41D, 'M', u'd'), - (0x1D41E, 'M', u'e'), - (0x1D41F, 'M', u'f'), - (0x1D420, 'M', u'g'), - (0x1D421, 'M', u'h'), - (0x1D422, 'M', u'i'), - (0x1D423, 'M', u'j'), - (0x1D424, 'M', u'k'), - (0x1D425, 'M', u'l'), - (0x1D426, 'M', u'm'), - (0x1D427, 'M', u'n'), - (0x1D428, 'M', u'o'), - (0x1D429, 'M', u'p'), - (0x1D42A, 'M', u'q'), - (0x1D42B, 'M', u'r'), - (0x1D42C, 'M', u's'), - (0x1D42D, 'M', u't'), - (0x1D42E, 'M', u'u'), - (0x1D42F, 'M', u'v'), - (0x1D430, 'M', u'w'), - (0x1D431, 'M', u'x'), - (0x1D432, 'M', u'y'), - (0x1D433, 'M', u'z'), - (0x1D434, 'M', u'a'), - (0x1D435, 'M', u'b'), - (0x1D436, 'M', u'c'), - (0x1D437, 'M', u'd'), - (0x1D438, 'M', u'e'), - (0x1D439, 'M', u'f'), - (0x1D43A, 'M', u'g'), - (0x1D43B, 'M', u'h'), - (0x1D43C, 'M', u'i'), - (0x1D43D, 'M', u'j'), - (0x1D43E, 'M', u'k'), - (0x1D43F, 'M', u'l'), - (0x1D440, 'M', u'm'), - (0x1D441, 'M', u'n'), - (0x1D442, 'M', u'o'), - (0x1D443, 'M', u'p'), - (0x1D444, 'M', u'q'), - (0x1D445, 'M', u'r'), - (0x1D446, 'M', u's'), - (0x1D447, 'M', u't'), - (0x1D448, 'M', u'u'), - (0x1D449, 'M', u'v'), - (0x1D44A, 'M', u'w'), - (0x1D44B, 'M', u'x'), - (0x1D44C, 'M', u'y'), - (0x1D44D, 'M', u'z'), - (0x1D44E, 'M', u'a'), - (0x1D44F, 'M', u'b'), - (0x1D450, 'M', u'c'), - (0x1D451, 'M', u'd'), - (0x1D452, 'M', u'e'), - (0x1D453, 'M', u'f'), - (0x1D454, 'M', u'g'), - (0x1D455, 'X'), - (0x1D456, 'M', u'i'), - (0x1D457, 'M', u'j'), - (0x1D458, 'M', u'k'), - (0x1D459, 'M', u'l'), - (0x1D45A, 'M', u'm'), - (0x1D45B, 'M', u'n'), - (0x1D45C, 'M', u'o'), - (0x1D45D, 'M', u'p'), - (0x1D45E, 'M', u'q'), - (0x1D45F, 'M', u'r'), - (0x1D460, 'M', u's'), - (0x1D461, 'M', u't'), - (0x1D462, 'M', u'u'), - (0x1D463, 'M', u'v'), - (0x1D464, 'M', u'w'), - (0x1D465, 'M', u'x'), - (0x1D466, 'M', u'y'), - (0x1D467, 'M', u'z'), - (0x1D468, 'M', u'a'), - (0x1D469, 'M', u'b'), - ] - -def _seg_59(): - return [ - (0x1D46A, 'M', u'c'), - (0x1D46B, 'M', u'd'), - (0x1D46C, 'M', u'e'), - (0x1D46D, 'M', u'f'), - (0x1D46E, 'M', u'g'), - (0x1D46F, 'M', u'h'), - (0x1D470, 'M', u'i'), - (0x1D471, 'M', u'j'), - (0x1D472, 'M', u'k'), - (0x1D473, 'M', u'l'), - (0x1D474, 'M', u'm'), - (0x1D475, 'M', u'n'), - (0x1D476, 'M', u'o'), - (0x1D477, 'M', u'p'), - (0x1D478, 'M', u'q'), - (0x1D479, 'M', u'r'), - (0x1D47A, 'M', u's'), - (0x1D47B, 'M', u't'), - (0x1D47C, 'M', u'u'), - (0x1D47D, 'M', u'v'), - (0x1D47E, 'M', u'w'), - (0x1D47F, 'M', u'x'), - (0x1D480, 'M', u'y'), - (0x1D481, 'M', u'z'), - (0x1D482, 'M', u'a'), - (0x1D483, 'M', u'b'), - (0x1D484, 'M', u'c'), - (0x1D485, 'M', u'd'), - (0x1D486, 'M', u'e'), - (0x1D487, 'M', u'f'), - (0x1D488, 'M', u'g'), - (0x1D489, 'M', u'h'), - (0x1D48A, 'M', u'i'), - (0x1D48B, 'M', u'j'), - (0x1D48C, 'M', u'k'), - (0x1D48D, 'M', u'l'), - (0x1D48E, 'M', u'm'), - (0x1D48F, 'M', u'n'), - (0x1D490, 'M', u'o'), - (0x1D491, 'M', u'p'), - (0x1D492, 'M', u'q'), - (0x1D493, 'M', u'r'), - (0x1D494, 'M', u's'), - (0x1D495, 'M', u't'), - (0x1D496, 'M', u'u'), - (0x1D497, 'M', u'v'), - (0x1D498, 'M', u'w'), - (0x1D499, 'M', u'x'), - (0x1D49A, 'M', u'y'), - (0x1D49B, 'M', u'z'), - (0x1D49C, 'M', u'a'), - (0x1D49D, 'X'), - (0x1D49E, 'M', u'c'), - (0x1D49F, 'M', u'd'), - (0x1D4A0, 'X'), - (0x1D4A2, 'M', u'g'), - (0x1D4A3, 'X'), - (0x1D4A5, 'M', u'j'), - (0x1D4A6, 'M', u'k'), - (0x1D4A7, 'X'), - (0x1D4A9, 'M', u'n'), - (0x1D4AA, 'M', u'o'), - (0x1D4AB, 'M', u'p'), - (0x1D4AC, 'M', u'q'), - (0x1D4AD, 'X'), - (0x1D4AE, 'M', u's'), - (0x1D4AF, 'M', u't'), - (0x1D4B0, 'M', u'u'), - (0x1D4B1, 'M', u'v'), - (0x1D4B2, 'M', u'w'), - (0x1D4B3, 'M', u'x'), - (0x1D4B4, 'M', u'y'), - (0x1D4B5, 'M', u'z'), - (0x1D4B6, 'M', u'a'), - (0x1D4B7, 'M', u'b'), - (0x1D4B8, 'M', u'c'), - (0x1D4B9, 'M', u'd'), - (0x1D4BA, 'X'), - (0x1D4BB, 'M', u'f'), - (0x1D4BC, 'X'), - (0x1D4BD, 'M', u'h'), - (0x1D4BE, 'M', u'i'), - (0x1D4BF, 'M', u'j'), - (0x1D4C0, 'M', u'k'), - (0x1D4C1, 'M', u'l'), - (0x1D4C2, 'M', u'm'), - (0x1D4C3, 'M', u'n'), - (0x1D4C4, 'X'), - (0x1D4C5, 'M', u'p'), - (0x1D4C6, 'M', u'q'), - (0x1D4C7, 'M', u'r'), - (0x1D4C8, 'M', u's'), - (0x1D4C9, 'M', u't'), - (0x1D4CA, 'M', u'u'), - (0x1D4CB, 'M', u'v'), - (0x1D4CC, 'M', u'w'), - (0x1D4CD, 'M', u'x'), - (0x1D4CE, 'M', u'y'), - (0x1D4CF, 'M', u'z'), - (0x1D4D0, 'M', u'a'), - ] - -def _seg_60(): - return [ - (0x1D4D1, 'M', u'b'), - (0x1D4D2, 'M', u'c'), - (0x1D4D3, 'M', u'd'), - (0x1D4D4, 'M', u'e'), - (0x1D4D5, 'M', u'f'), - (0x1D4D6, 'M', u'g'), - (0x1D4D7, 'M', u'h'), - (0x1D4D8, 'M', u'i'), - (0x1D4D9, 'M', u'j'), - (0x1D4DA, 'M', u'k'), - (0x1D4DB, 'M', u'l'), - (0x1D4DC, 'M', u'm'), - (0x1D4DD, 'M', u'n'), - (0x1D4DE, 'M', u'o'), - (0x1D4DF, 'M', u'p'), - (0x1D4E0, 'M', u'q'), - (0x1D4E1, 'M', u'r'), - (0x1D4E2, 'M', u's'), - (0x1D4E3, 'M', u't'), - (0x1D4E4, 'M', u'u'), - (0x1D4E5, 'M', u'v'), - (0x1D4E6, 'M', u'w'), - (0x1D4E7, 'M', u'x'), - (0x1D4E8, 'M', u'y'), - (0x1D4E9, 'M', u'z'), - (0x1D4EA, 'M', u'a'), - (0x1D4EB, 'M', u'b'), - (0x1D4EC, 'M', u'c'), - (0x1D4ED, 'M', u'd'), - (0x1D4EE, 'M', u'e'), - (0x1D4EF, 'M', u'f'), - (0x1D4F0, 'M', u'g'), - (0x1D4F1, 'M', u'h'), - (0x1D4F2, 'M', u'i'), - (0x1D4F3, 'M', u'j'), - (0x1D4F4, 'M', u'k'), - (0x1D4F5, 'M', u'l'), - (0x1D4F6, 'M', u'm'), - (0x1D4F7, 'M', u'n'), - (0x1D4F8, 'M', u'o'), - (0x1D4F9, 'M', u'p'), - (0x1D4FA, 'M', u'q'), - (0x1D4FB, 'M', u'r'), - (0x1D4FC, 'M', u's'), - (0x1D4FD, 'M', u't'), - (0x1D4FE, 'M', u'u'), - (0x1D4FF, 'M', u'v'), - (0x1D500, 'M', u'w'), - (0x1D501, 'M', u'x'), - (0x1D502, 'M', u'y'), - (0x1D503, 'M', u'z'), - (0x1D504, 'M', u'a'), - (0x1D505, 'M', u'b'), - (0x1D506, 'X'), - (0x1D507, 'M', u'd'), - (0x1D508, 'M', u'e'), - (0x1D509, 'M', u'f'), - (0x1D50A, 'M', u'g'), - (0x1D50B, 'X'), - (0x1D50D, 'M', u'j'), - (0x1D50E, 'M', u'k'), - (0x1D50F, 'M', u'l'), - (0x1D510, 'M', u'm'), - (0x1D511, 'M', u'n'), - (0x1D512, 'M', u'o'), - (0x1D513, 'M', u'p'), - (0x1D514, 'M', u'q'), - (0x1D515, 'X'), - (0x1D516, 'M', u's'), - (0x1D517, 'M', u't'), - (0x1D518, 'M', u'u'), - (0x1D519, 'M', u'v'), - (0x1D51A, 'M', u'w'), - (0x1D51B, 'M', u'x'), - (0x1D51C, 'M', u'y'), - (0x1D51D, 'X'), - (0x1D51E, 'M', u'a'), - (0x1D51F, 'M', u'b'), - (0x1D520, 'M', u'c'), - (0x1D521, 'M', u'd'), - (0x1D522, 'M', u'e'), - (0x1D523, 'M', u'f'), - (0x1D524, 'M', u'g'), - (0x1D525, 'M', u'h'), - (0x1D526, 'M', u'i'), - (0x1D527, 'M', u'j'), - (0x1D528, 'M', u'k'), - (0x1D529, 'M', u'l'), - (0x1D52A, 'M', u'm'), - (0x1D52B, 'M', u'n'), - (0x1D52C, 'M', u'o'), - (0x1D52D, 'M', u'p'), - (0x1D52E, 'M', u'q'), - (0x1D52F, 'M', u'r'), - (0x1D530, 'M', u's'), - (0x1D531, 'M', u't'), - (0x1D532, 'M', u'u'), - (0x1D533, 'M', u'v'), - (0x1D534, 'M', u'w'), - (0x1D535, 'M', u'x'), - ] - -def _seg_61(): - return [ - (0x1D536, 'M', u'y'), - (0x1D537, 'M', u'z'), - (0x1D538, 'M', u'a'), - (0x1D539, 'M', u'b'), - (0x1D53A, 'X'), - (0x1D53B, 'M', u'd'), - (0x1D53C, 'M', u'e'), - (0x1D53D, 'M', u'f'), - (0x1D53E, 'M', u'g'), - (0x1D53F, 'X'), - (0x1D540, 'M', u'i'), - (0x1D541, 'M', u'j'), - (0x1D542, 'M', u'k'), - (0x1D543, 'M', u'l'), - (0x1D544, 'M', u'm'), - (0x1D545, 'X'), - (0x1D546, 'M', u'o'), - (0x1D547, 'X'), - (0x1D54A, 'M', u's'), - (0x1D54B, 'M', u't'), - (0x1D54C, 'M', u'u'), - (0x1D54D, 'M', u'v'), - (0x1D54E, 'M', u'w'), - (0x1D54F, 'M', u'x'), - (0x1D550, 'M', u'y'), - (0x1D551, 'X'), - (0x1D552, 'M', u'a'), - (0x1D553, 'M', u'b'), - (0x1D554, 'M', u'c'), - (0x1D555, 'M', u'd'), - (0x1D556, 'M', u'e'), - (0x1D557, 'M', u'f'), - (0x1D558, 'M', u'g'), - (0x1D559, 'M', u'h'), - (0x1D55A, 'M', u'i'), - (0x1D55B, 'M', u'j'), - (0x1D55C, 'M', u'k'), - (0x1D55D, 'M', u'l'), - (0x1D55E, 'M', u'm'), - (0x1D55F, 'M', u'n'), - (0x1D560, 'M', u'o'), - (0x1D561, 'M', u'p'), - (0x1D562, 'M', u'q'), - (0x1D563, 'M', u'r'), - (0x1D564, 'M', u's'), - (0x1D565, 'M', u't'), - (0x1D566, 'M', u'u'), - (0x1D567, 'M', u'v'), - (0x1D568, 'M', u'w'), - (0x1D569, 'M', u'x'), - (0x1D56A, 'M', u'y'), - (0x1D56B, 'M', u'z'), - (0x1D56C, 'M', u'a'), - (0x1D56D, 'M', u'b'), - (0x1D56E, 'M', u'c'), - (0x1D56F, 'M', u'd'), - (0x1D570, 'M', u'e'), - (0x1D571, 'M', u'f'), - (0x1D572, 'M', u'g'), - (0x1D573, 'M', u'h'), - (0x1D574, 'M', u'i'), - (0x1D575, 'M', u'j'), - (0x1D576, 'M', u'k'), - (0x1D577, 'M', u'l'), - (0x1D578, 'M', u'm'), - (0x1D579, 'M', u'n'), - (0x1D57A, 'M', u'o'), - (0x1D57B, 'M', u'p'), - (0x1D57C, 'M', u'q'), - (0x1D57D, 'M', u'r'), - (0x1D57E, 'M', u's'), - (0x1D57F, 'M', u't'), - (0x1D580, 'M', u'u'), - (0x1D581, 'M', u'v'), - (0x1D582, 'M', u'w'), - (0x1D583, 'M', u'x'), - (0x1D584, 'M', u'y'), - (0x1D585, 'M', u'z'), - (0x1D586, 'M', u'a'), - (0x1D587, 'M', u'b'), - (0x1D588, 'M', u'c'), - (0x1D589, 'M', u'd'), - (0x1D58A, 'M', u'e'), - (0x1D58B, 'M', u'f'), - (0x1D58C, 'M', u'g'), - (0x1D58D, 'M', u'h'), - (0x1D58E, 'M', u'i'), - (0x1D58F, 'M', u'j'), - (0x1D590, 'M', u'k'), - (0x1D591, 'M', u'l'), - (0x1D592, 'M', u'm'), - (0x1D593, 'M', u'n'), - (0x1D594, 'M', u'o'), - (0x1D595, 'M', u'p'), - (0x1D596, 'M', u'q'), - (0x1D597, 'M', u'r'), - (0x1D598, 'M', u's'), - (0x1D599, 'M', u't'), - (0x1D59A, 'M', u'u'), - (0x1D59B, 'M', u'v'), - ] - -def _seg_62(): - return [ - (0x1D59C, 'M', u'w'), - (0x1D59D, 'M', u'x'), - (0x1D59E, 'M', u'y'), - (0x1D59F, 'M', u'z'), - (0x1D5A0, 'M', u'a'), - (0x1D5A1, 'M', u'b'), - (0x1D5A2, 'M', u'c'), - (0x1D5A3, 'M', u'd'), - (0x1D5A4, 'M', u'e'), - (0x1D5A5, 'M', u'f'), - (0x1D5A6, 'M', u'g'), - (0x1D5A7, 'M', u'h'), - (0x1D5A8, 'M', u'i'), - (0x1D5A9, 'M', u'j'), - (0x1D5AA, 'M', u'k'), - (0x1D5AB, 'M', u'l'), - (0x1D5AC, 'M', u'm'), - (0x1D5AD, 'M', u'n'), - (0x1D5AE, 'M', u'o'), - (0x1D5AF, 'M', u'p'), - (0x1D5B0, 'M', u'q'), - (0x1D5B1, 'M', u'r'), - (0x1D5B2, 'M', u's'), - (0x1D5B3, 'M', u't'), - (0x1D5B4, 'M', u'u'), - (0x1D5B5, 'M', u'v'), - (0x1D5B6, 'M', u'w'), - (0x1D5B7, 'M', u'x'), - (0x1D5B8, 'M', u'y'), - (0x1D5B9, 'M', u'z'), - (0x1D5BA, 'M', u'a'), - (0x1D5BB, 'M', u'b'), - (0x1D5BC, 'M', u'c'), - (0x1D5BD, 'M', u'd'), - (0x1D5BE, 'M', u'e'), - (0x1D5BF, 'M', u'f'), - (0x1D5C0, 'M', u'g'), - (0x1D5C1, 'M', u'h'), - (0x1D5C2, 'M', u'i'), - (0x1D5C3, 'M', u'j'), - (0x1D5C4, 'M', u'k'), - (0x1D5C5, 'M', u'l'), - (0x1D5C6, 'M', u'm'), - (0x1D5C7, 'M', u'n'), - (0x1D5C8, 'M', u'o'), - (0x1D5C9, 'M', u'p'), - (0x1D5CA, 'M', u'q'), - (0x1D5CB, 'M', u'r'), - (0x1D5CC, 'M', u's'), - (0x1D5CD, 'M', u't'), - (0x1D5CE, 'M', u'u'), - (0x1D5CF, 'M', u'v'), - (0x1D5D0, 'M', u'w'), - (0x1D5D1, 'M', u'x'), - (0x1D5D2, 'M', u'y'), - (0x1D5D3, 'M', u'z'), - (0x1D5D4, 'M', u'a'), - (0x1D5D5, 'M', u'b'), - (0x1D5D6, 'M', u'c'), - (0x1D5D7, 'M', u'd'), - (0x1D5D8, 'M', u'e'), - (0x1D5D9, 'M', u'f'), - (0x1D5DA, 'M', u'g'), - (0x1D5DB, 'M', u'h'), - (0x1D5DC, 'M', u'i'), - (0x1D5DD, 'M', u'j'), - (0x1D5DE, 'M', u'k'), - (0x1D5DF, 'M', u'l'), - (0x1D5E0, 'M', u'm'), - (0x1D5E1, 'M', u'n'), - (0x1D5E2, 'M', u'o'), - (0x1D5E3, 'M', u'p'), - (0x1D5E4, 'M', u'q'), - (0x1D5E5, 'M', u'r'), - (0x1D5E6, 'M', u's'), - (0x1D5E7, 'M', u't'), - (0x1D5E8, 'M', u'u'), - (0x1D5E9, 'M', u'v'), - (0x1D5EA, 'M', u'w'), - (0x1D5EB, 'M', u'x'), - (0x1D5EC, 'M', u'y'), - (0x1D5ED, 'M', u'z'), - (0x1D5EE, 'M', u'a'), - (0x1D5EF, 'M', u'b'), - (0x1D5F0, 'M', u'c'), - (0x1D5F1, 'M', u'd'), - (0x1D5F2, 'M', u'e'), - (0x1D5F3, 'M', u'f'), - (0x1D5F4, 'M', u'g'), - (0x1D5F5, 'M', u'h'), - (0x1D5F6, 'M', u'i'), - (0x1D5F7, 'M', u'j'), - (0x1D5F8, 'M', u'k'), - (0x1D5F9, 'M', u'l'), - (0x1D5FA, 'M', u'm'), - (0x1D5FB, 'M', u'n'), - (0x1D5FC, 'M', u'o'), - (0x1D5FD, 'M', u'p'), - (0x1D5FE, 'M', u'q'), - (0x1D5FF, 'M', u'r'), - ] - -def _seg_63(): - return [ - (0x1D600, 'M', u's'), - (0x1D601, 'M', u't'), - (0x1D602, 'M', u'u'), - (0x1D603, 'M', u'v'), - (0x1D604, 'M', u'w'), - (0x1D605, 'M', u'x'), - (0x1D606, 'M', u'y'), - (0x1D607, 'M', u'z'), - (0x1D608, 'M', u'a'), - (0x1D609, 'M', u'b'), - (0x1D60A, 'M', u'c'), - (0x1D60B, 'M', u'd'), - (0x1D60C, 'M', u'e'), - (0x1D60D, 'M', u'f'), - (0x1D60E, 'M', u'g'), - (0x1D60F, 'M', u'h'), - (0x1D610, 'M', u'i'), - (0x1D611, 'M', u'j'), - (0x1D612, 'M', u'k'), - (0x1D613, 'M', u'l'), - (0x1D614, 'M', u'm'), - (0x1D615, 'M', u'n'), - (0x1D616, 'M', u'o'), - (0x1D617, 'M', u'p'), - (0x1D618, 'M', u'q'), - (0x1D619, 'M', u'r'), - (0x1D61A, 'M', u's'), - (0x1D61B, 'M', u't'), - (0x1D61C, 'M', u'u'), - (0x1D61D, 'M', u'v'), - (0x1D61E, 'M', u'w'), - (0x1D61F, 'M', u'x'), - (0x1D620, 'M', u'y'), - (0x1D621, 'M', u'z'), - (0x1D622, 'M', u'a'), - (0x1D623, 'M', u'b'), - (0x1D624, 'M', u'c'), - (0x1D625, 'M', u'd'), - (0x1D626, 'M', u'e'), - (0x1D627, 'M', u'f'), - (0x1D628, 'M', u'g'), - (0x1D629, 'M', u'h'), - (0x1D62A, 'M', u'i'), - (0x1D62B, 'M', u'j'), - (0x1D62C, 'M', u'k'), - (0x1D62D, 'M', u'l'), - (0x1D62E, 'M', u'm'), - (0x1D62F, 'M', u'n'), - (0x1D630, 'M', u'o'), - (0x1D631, 'M', u'p'), - (0x1D632, 'M', u'q'), - (0x1D633, 'M', u'r'), - (0x1D634, 'M', u's'), - (0x1D635, 'M', u't'), - (0x1D636, 'M', u'u'), - (0x1D637, 'M', u'v'), - (0x1D638, 'M', u'w'), - (0x1D639, 'M', u'x'), - (0x1D63A, 'M', u'y'), - (0x1D63B, 'M', u'z'), - (0x1D63C, 'M', u'a'), - (0x1D63D, 'M', u'b'), - (0x1D63E, 'M', u'c'), - (0x1D63F, 'M', u'd'), - (0x1D640, 'M', u'e'), - (0x1D641, 'M', u'f'), - (0x1D642, 'M', u'g'), - (0x1D643, 'M', u'h'), - (0x1D644, 'M', u'i'), - (0x1D645, 'M', u'j'), - (0x1D646, 'M', u'k'), - (0x1D647, 'M', u'l'), - (0x1D648, 'M', u'm'), - (0x1D649, 'M', u'n'), - (0x1D64A, 'M', u'o'), - (0x1D64B, 'M', u'p'), - (0x1D64C, 'M', u'q'), - (0x1D64D, 'M', u'r'), - (0x1D64E, 'M', u's'), - (0x1D64F, 'M', u't'), - (0x1D650, 'M', u'u'), - (0x1D651, 'M', u'v'), - (0x1D652, 'M', u'w'), - (0x1D653, 'M', u'x'), - (0x1D654, 'M', u'y'), - (0x1D655, 'M', u'z'), - (0x1D656, 'M', u'a'), - (0x1D657, 'M', u'b'), - (0x1D658, 'M', u'c'), - (0x1D659, 'M', u'd'), - (0x1D65A, 'M', u'e'), - (0x1D65B, 'M', u'f'), - (0x1D65C, 'M', u'g'), - (0x1D65D, 'M', u'h'), - (0x1D65E, 'M', u'i'), - (0x1D65F, 'M', u'j'), - (0x1D660, 'M', u'k'), - (0x1D661, 'M', u'l'), - (0x1D662, 'M', u'm'), - (0x1D663, 'M', u'n'), - ] - -def _seg_64(): - return [ - (0x1D664, 'M', u'o'), - (0x1D665, 'M', u'p'), - (0x1D666, 'M', u'q'), - (0x1D667, 'M', u'r'), - (0x1D668, 'M', u's'), - (0x1D669, 'M', u't'), - (0x1D66A, 'M', u'u'), - (0x1D66B, 'M', u'v'), - (0x1D66C, 'M', u'w'), - (0x1D66D, 'M', u'x'), - (0x1D66E, 'M', u'y'), - (0x1D66F, 'M', u'z'), - (0x1D670, 'M', u'a'), - (0x1D671, 'M', u'b'), - (0x1D672, 'M', u'c'), - (0x1D673, 'M', u'd'), - (0x1D674, 'M', u'e'), - (0x1D675, 'M', u'f'), - (0x1D676, 'M', u'g'), - (0x1D677, 'M', u'h'), - (0x1D678, 'M', u'i'), - (0x1D679, 'M', u'j'), - (0x1D67A, 'M', u'k'), - (0x1D67B, 'M', u'l'), - (0x1D67C, 'M', u'm'), - (0x1D67D, 'M', u'n'), - (0x1D67E, 'M', u'o'), - (0x1D67F, 'M', u'p'), - (0x1D680, 'M', u'q'), - (0x1D681, 'M', u'r'), - (0x1D682, 'M', u's'), - (0x1D683, 'M', u't'), - (0x1D684, 'M', u'u'), - (0x1D685, 'M', u'v'), - (0x1D686, 'M', u'w'), - (0x1D687, 'M', u'x'), - (0x1D688, 'M', u'y'), - (0x1D689, 'M', u'z'), - (0x1D68A, 'M', u'a'), - (0x1D68B, 'M', u'b'), - (0x1D68C, 'M', u'c'), - (0x1D68D, 'M', u'd'), - (0x1D68E, 'M', u'e'), - (0x1D68F, 'M', u'f'), - (0x1D690, 'M', u'g'), - (0x1D691, 'M', u'h'), - (0x1D692, 'M', u'i'), - (0x1D693, 'M', u'j'), - (0x1D694, 'M', u'k'), - (0x1D695, 'M', u'l'), - (0x1D696, 'M', u'm'), - (0x1D697, 'M', u'n'), - (0x1D698, 'M', u'o'), - (0x1D699, 'M', u'p'), - (0x1D69A, 'M', u'q'), - (0x1D69B, 'M', u'r'), - (0x1D69C, 'M', u's'), - (0x1D69D, 'M', u't'), - (0x1D69E, 'M', u'u'), - (0x1D69F, 'M', u'v'), - (0x1D6A0, 'M', u'w'), - (0x1D6A1, 'M', u'x'), - (0x1D6A2, 'M', u'y'), - (0x1D6A3, 'M', u'z'), - (0x1D6A4, 'M', u'ı'), - (0x1D6A5, 'M', u'ȷ'), - (0x1D6A6, 'X'), - (0x1D6A8, 'M', u'α'), - (0x1D6A9, 'M', u'β'), - (0x1D6AA, 'M', u'γ'), - (0x1D6AB, 'M', u'δ'), - (0x1D6AC, 'M', u'ε'), - (0x1D6AD, 'M', u'ζ'), - (0x1D6AE, 'M', u'η'), - (0x1D6AF, 'M', u'θ'), - (0x1D6B0, 'M', u'ι'), - (0x1D6B1, 'M', u'κ'), - (0x1D6B2, 'M', u'λ'), - (0x1D6B3, 'M', u'μ'), - (0x1D6B4, 'M', u'ν'), - (0x1D6B5, 'M', u'ξ'), - (0x1D6B6, 'M', u'ο'), - (0x1D6B7, 'M', u'π'), - (0x1D6B8, 'M', u'ρ'), - (0x1D6B9, 'M', u'θ'), - (0x1D6BA, 'M', u'σ'), - (0x1D6BB, 'M', u'τ'), - (0x1D6BC, 'M', u'υ'), - (0x1D6BD, 'M', u'φ'), - (0x1D6BE, 'M', u'χ'), - (0x1D6BF, 'M', u'ψ'), - (0x1D6C0, 'M', u'ω'), - (0x1D6C1, 'M', u'∇'), - (0x1D6C2, 'M', u'α'), - (0x1D6C3, 'M', u'β'), - (0x1D6C4, 'M', u'γ'), - (0x1D6C5, 'M', u'δ'), - (0x1D6C6, 'M', u'ε'), - (0x1D6C7, 'M', u'ζ'), - (0x1D6C8, 'M', u'η'), - ] - -def _seg_65(): - return [ - (0x1D6C9, 'M', u'θ'), - (0x1D6CA, 'M', u'ι'), - (0x1D6CB, 'M', u'κ'), - (0x1D6CC, 'M', u'λ'), - (0x1D6CD, 'M', u'μ'), - (0x1D6CE, 'M', u'ν'), - (0x1D6CF, 'M', u'ξ'), - (0x1D6D0, 'M', u'ο'), - (0x1D6D1, 'M', u'π'), - (0x1D6D2, 'M', u'ρ'), - (0x1D6D3, 'M', u'σ'), - (0x1D6D5, 'M', u'τ'), - (0x1D6D6, 'M', u'υ'), - (0x1D6D7, 'M', u'φ'), - (0x1D6D8, 'M', u'χ'), - (0x1D6D9, 'M', u'ψ'), - (0x1D6DA, 'M', u'ω'), - (0x1D6DB, 'M', u'∂'), - (0x1D6DC, 'M', u'ε'), - (0x1D6DD, 'M', u'θ'), - (0x1D6DE, 'M', u'κ'), - (0x1D6DF, 'M', u'φ'), - (0x1D6E0, 'M', u'ρ'), - (0x1D6E1, 'M', u'π'), - (0x1D6E2, 'M', u'α'), - (0x1D6E3, 'M', u'β'), - (0x1D6E4, 'M', u'γ'), - (0x1D6E5, 'M', u'δ'), - (0x1D6E6, 'M', u'ε'), - (0x1D6E7, 'M', u'ζ'), - (0x1D6E8, 'M', u'η'), - (0x1D6E9, 'M', u'θ'), - (0x1D6EA, 'M', u'ι'), - (0x1D6EB, 'M', u'κ'), - (0x1D6EC, 'M', u'λ'), - (0x1D6ED, 'M', u'μ'), - (0x1D6EE, 'M', u'ν'), - (0x1D6EF, 'M', u'ξ'), - (0x1D6F0, 'M', u'ο'), - (0x1D6F1, 'M', u'π'), - (0x1D6F2, 'M', u'ρ'), - (0x1D6F3, 'M', u'θ'), - (0x1D6F4, 'M', u'σ'), - (0x1D6F5, 'M', u'τ'), - (0x1D6F6, 'M', u'υ'), - (0x1D6F7, 'M', u'φ'), - (0x1D6F8, 'M', u'χ'), - (0x1D6F9, 'M', u'ψ'), - (0x1D6FA, 'M', u'ω'), - (0x1D6FB, 'M', u'∇'), - (0x1D6FC, 'M', u'α'), - (0x1D6FD, 'M', u'β'), - (0x1D6FE, 'M', u'γ'), - (0x1D6FF, 'M', u'δ'), - (0x1D700, 'M', u'ε'), - (0x1D701, 'M', u'ζ'), - (0x1D702, 'M', u'η'), - (0x1D703, 'M', u'θ'), - (0x1D704, 'M', u'ι'), - (0x1D705, 'M', u'κ'), - (0x1D706, 'M', u'λ'), - (0x1D707, 'M', u'μ'), - (0x1D708, 'M', u'ν'), - (0x1D709, 'M', u'ξ'), - (0x1D70A, 'M', u'ο'), - (0x1D70B, 'M', u'π'), - (0x1D70C, 'M', u'ρ'), - (0x1D70D, 'M', u'σ'), - (0x1D70F, 'M', u'τ'), - (0x1D710, 'M', u'υ'), - (0x1D711, 'M', u'φ'), - (0x1D712, 'M', u'χ'), - (0x1D713, 'M', u'ψ'), - (0x1D714, 'M', u'ω'), - (0x1D715, 'M', u'∂'), - (0x1D716, 'M', u'ε'), - (0x1D717, 'M', u'θ'), - (0x1D718, 'M', u'κ'), - (0x1D719, 'M', u'φ'), - (0x1D71A, 'M', u'ρ'), - (0x1D71B, 'M', u'π'), - (0x1D71C, 'M', u'α'), - (0x1D71D, 'M', u'β'), - (0x1D71E, 'M', u'γ'), - (0x1D71F, 'M', u'δ'), - (0x1D720, 'M', u'ε'), - (0x1D721, 'M', u'ζ'), - (0x1D722, 'M', u'η'), - (0x1D723, 'M', u'θ'), - (0x1D724, 'M', u'ι'), - (0x1D725, 'M', u'κ'), - (0x1D726, 'M', u'λ'), - (0x1D727, 'M', u'μ'), - (0x1D728, 'M', u'ν'), - (0x1D729, 'M', u'ξ'), - (0x1D72A, 'M', u'ο'), - (0x1D72B, 'M', u'π'), - (0x1D72C, 'M', u'ρ'), - (0x1D72D, 'M', u'θ'), - (0x1D72E, 'M', u'σ'), - ] - -def _seg_66(): - return [ - (0x1D72F, 'M', u'τ'), - (0x1D730, 'M', u'υ'), - (0x1D731, 'M', u'φ'), - (0x1D732, 'M', u'χ'), - (0x1D733, 'M', u'ψ'), - (0x1D734, 'M', u'ω'), - (0x1D735, 'M', u'∇'), - (0x1D736, 'M', u'α'), - (0x1D737, 'M', u'β'), - (0x1D738, 'M', u'γ'), - (0x1D739, 'M', u'δ'), - (0x1D73A, 'M', u'ε'), - (0x1D73B, 'M', u'ζ'), - (0x1D73C, 'M', u'η'), - (0x1D73D, 'M', u'θ'), - (0x1D73E, 'M', u'ι'), - (0x1D73F, 'M', u'κ'), - (0x1D740, 'M', u'λ'), - (0x1D741, 'M', u'μ'), - (0x1D742, 'M', u'ν'), - (0x1D743, 'M', u'ξ'), - (0x1D744, 'M', u'ο'), - (0x1D745, 'M', u'π'), - (0x1D746, 'M', u'ρ'), - (0x1D747, 'M', u'σ'), - (0x1D749, 'M', u'τ'), - (0x1D74A, 'M', u'υ'), - (0x1D74B, 'M', u'φ'), - (0x1D74C, 'M', u'χ'), - (0x1D74D, 'M', u'ψ'), - (0x1D74E, 'M', u'ω'), - (0x1D74F, 'M', u'∂'), - (0x1D750, 'M', u'ε'), - (0x1D751, 'M', u'θ'), - (0x1D752, 'M', u'κ'), - (0x1D753, 'M', u'φ'), - (0x1D754, 'M', u'ρ'), - (0x1D755, 'M', u'π'), - (0x1D756, 'M', u'α'), - (0x1D757, 'M', u'β'), - (0x1D758, 'M', u'γ'), - (0x1D759, 'M', u'δ'), - (0x1D75A, 'M', u'ε'), - (0x1D75B, 'M', u'ζ'), - (0x1D75C, 'M', u'η'), - (0x1D75D, 'M', u'θ'), - (0x1D75E, 'M', u'ι'), - (0x1D75F, 'M', u'κ'), - (0x1D760, 'M', u'λ'), - (0x1D761, 'M', u'μ'), - (0x1D762, 'M', u'ν'), - (0x1D763, 'M', u'ξ'), - (0x1D764, 'M', u'ο'), - (0x1D765, 'M', u'π'), - (0x1D766, 'M', u'ρ'), - (0x1D767, 'M', u'θ'), - (0x1D768, 'M', u'σ'), - (0x1D769, 'M', u'τ'), - (0x1D76A, 'M', u'υ'), - (0x1D76B, 'M', u'φ'), - (0x1D76C, 'M', u'χ'), - (0x1D76D, 'M', u'ψ'), - (0x1D76E, 'M', u'ω'), - (0x1D76F, 'M', u'∇'), - (0x1D770, 'M', u'α'), - (0x1D771, 'M', u'β'), - (0x1D772, 'M', u'γ'), - (0x1D773, 'M', u'δ'), - (0x1D774, 'M', u'ε'), - (0x1D775, 'M', u'ζ'), - (0x1D776, 'M', u'η'), - (0x1D777, 'M', u'θ'), - (0x1D778, 'M', u'ι'), - (0x1D779, 'M', u'κ'), - (0x1D77A, 'M', u'λ'), - (0x1D77B, 'M', u'μ'), - (0x1D77C, 'M', u'ν'), - (0x1D77D, 'M', u'ξ'), - (0x1D77E, 'M', u'ο'), - (0x1D77F, 'M', u'π'), - (0x1D780, 'M', u'ρ'), - (0x1D781, 'M', u'σ'), - (0x1D783, 'M', u'τ'), - (0x1D784, 'M', u'υ'), - (0x1D785, 'M', u'φ'), - (0x1D786, 'M', u'χ'), - (0x1D787, 'M', u'ψ'), - (0x1D788, 'M', u'ω'), - (0x1D789, 'M', u'∂'), - (0x1D78A, 'M', u'ε'), - (0x1D78B, 'M', u'θ'), - (0x1D78C, 'M', u'κ'), - (0x1D78D, 'M', u'φ'), - (0x1D78E, 'M', u'ρ'), - (0x1D78F, 'M', u'π'), - (0x1D790, 'M', u'α'), - (0x1D791, 'M', u'β'), - (0x1D792, 'M', u'γ'), - (0x1D793, 'M', u'δ'), - (0x1D794, 'M', u'ε'), - ] - -def _seg_67(): - return [ - (0x1D795, 'M', u'ζ'), - (0x1D796, 'M', u'η'), - (0x1D797, 'M', u'θ'), - (0x1D798, 'M', u'ι'), - (0x1D799, 'M', u'κ'), - (0x1D79A, 'M', u'λ'), - (0x1D79B, 'M', u'μ'), - (0x1D79C, 'M', u'ν'), - (0x1D79D, 'M', u'ξ'), - (0x1D79E, 'M', u'ο'), - (0x1D79F, 'M', u'π'), - (0x1D7A0, 'M', u'ρ'), - (0x1D7A1, 'M', u'θ'), - (0x1D7A2, 'M', u'σ'), - (0x1D7A3, 'M', u'τ'), - (0x1D7A4, 'M', u'υ'), - (0x1D7A5, 'M', u'φ'), - (0x1D7A6, 'M', u'χ'), - (0x1D7A7, 'M', u'ψ'), - (0x1D7A8, 'M', u'ω'), - (0x1D7A9, 'M', u'∇'), - (0x1D7AA, 'M', u'α'), - (0x1D7AB, 'M', u'β'), - (0x1D7AC, 'M', u'γ'), - (0x1D7AD, 'M', u'δ'), - (0x1D7AE, 'M', u'ε'), - (0x1D7AF, 'M', u'ζ'), - (0x1D7B0, 'M', u'η'), - (0x1D7B1, 'M', u'θ'), - (0x1D7B2, 'M', u'ι'), - (0x1D7B3, 'M', u'κ'), - (0x1D7B4, 'M', u'λ'), - (0x1D7B5, 'M', u'μ'), - (0x1D7B6, 'M', u'ν'), - (0x1D7B7, 'M', u'ξ'), - (0x1D7B8, 'M', u'ο'), - (0x1D7B9, 'M', u'π'), - (0x1D7BA, 'M', u'ρ'), - (0x1D7BB, 'M', u'σ'), - (0x1D7BD, 'M', u'τ'), - (0x1D7BE, 'M', u'υ'), - (0x1D7BF, 'M', u'φ'), - (0x1D7C0, 'M', u'χ'), - (0x1D7C1, 'M', u'ψ'), - (0x1D7C2, 'M', u'ω'), - (0x1D7C3, 'M', u'∂'), - (0x1D7C4, 'M', u'ε'), - (0x1D7C5, 'M', u'θ'), - (0x1D7C6, 'M', u'κ'), - (0x1D7C7, 'M', u'φ'), - (0x1D7C8, 'M', u'ρ'), - (0x1D7C9, 'M', u'π'), - (0x1D7CA, 'M', u'ϝ'), - (0x1D7CC, 'X'), - (0x1D7CE, 'M', u'0'), - (0x1D7CF, 'M', u'1'), - (0x1D7D0, 'M', u'2'), - (0x1D7D1, 'M', u'3'), - (0x1D7D2, 'M', u'4'), - (0x1D7D3, 'M', u'5'), - (0x1D7D4, 'M', u'6'), - (0x1D7D5, 'M', u'7'), - (0x1D7D6, 'M', u'8'), - (0x1D7D7, 'M', u'9'), - (0x1D7D8, 'M', u'0'), - (0x1D7D9, 'M', u'1'), - (0x1D7DA, 'M', u'2'), - (0x1D7DB, 'M', u'3'), - (0x1D7DC, 'M', u'4'), - (0x1D7DD, 'M', u'5'), - (0x1D7DE, 'M', u'6'), - (0x1D7DF, 'M', u'7'), - (0x1D7E0, 'M', u'8'), - (0x1D7E1, 'M', u'9'), - (0x1D7E2, 'M', u'0'), - (0x1D7E3, 'M', u'1'), - (0x1D7E4, 'M', u'2'), - (0x1D7E5, 'M', u'3'), - (0x1D7E6, 'M', u'4'), - (0x1D7E7, 'M', u'5'), - (0x1D7E8, 'M', u'6'), - (0x1D7E9, 'M', u'7'), - (0x1D7EA, 'M', u'8'), - (0x1D7EB, 'M', u'9'), - (0x1D7EC, 'M', u'0'), - (0x1D7ED, 'M', u'1'), - (0x1D7EE, 'M', u'2'), - (0x1D7EF, 'M', u'3'), - (0x1D7F0, 'M', u'4'), - (0x1D7F1, 'M', u'5'), - (0x1D7F2, 'M', u'6'), - (0x1D7F3, 'M', u'7'), - (0x1D7F4, 'M', u'8'), - (0x1D7F5, 'M', u'9'), - (0x1D7F6, 'M', u'0'), - (0x1D7F7, 'M', u'1'), - (0x1D7F8, 'M', u'2'), - (0x1D7F9, 'M', u'3'), - (0x1D7FA, 'M', u'4'), - (0x1D7FB, 'M', u'5'), - ] - -def _seg_68(): - return [ - (0x1D7FC, 'M', u'6'), - (0x1D7FD, 'M', u'7'), - (0x1D7FE, 'M', u'8'), - (0x1D7FF, 'M', u'9'), - (0x1D800, 'V'), - (0x1DA8C, 'X'), - (0x1DA9B, 'V'), - (0x1DAA0, 'X'), - (0x1DAA1, 'V'), - (0x1DAB0, 'X'), - (0x1E000, 'V'), - (0x1E007, 'X'), - (0x1E008, 'V'), - (0x1E019, 'X'), - (0x1E01B, 'V'), - (0x1E022, 'X'), - (0x1E023, 'V'), - (0x1E025, 'X'), - (0x1E026, 'V'), - (0x1E02B, 'X'), - (0x1E800, 'V'), - (0x1E8C5, 'X'), - (0x1E8C7, 'V'), - (0x1E8D7, 'X'), - (0x1E900, 'M', u'𞤢'), - (0x1E901, 'M', u'𞤣'), - (0x1E902, 'M', u'𞤤'), - (0x1E903, 'M', u'𞤥'), - (0x1E904, 'M', u'𞤦'), - (0x1E905, 'M', u'𞤧'), - (0x1E906, 'M', u'𞤨'), - (0x1E907, 'M', u'𞤩'), - (0x1E908, 'M', u'𞤪'), - (0x1E909, 'M', u'𞤫'), - (0x1E90A, 'M', u'𞤬'), - (0x1E90B, 'M', u'𞤭'), - (0x1E90C, 'M', u'𞤮'), - (0x1E90D, 'M', u'𞤯'), - (0x1E90E, 'M', u'𞤰'), - (0x1E90F, 'M', u'𞤱'), - (0x1E910, 'M', u'𞤲'), - (0x1E911, 'M', u'𞤳'), - (0x1E912, 'M', u'𞤴'), - (0x1E913, 'M', u'𞤵'), - (0x1E914, 'M', u'𞤶'), - (0x1E915, 'M', u'𞤷'), - (0x1E916, 'M', u'𞤸'), - (0x1E917, 'M', u'𞤹'), - (0x1E918, 'M', u'𞤺'), - (0x1E919, 'M', u'𞤻'), - (0x1E91A, 'M', u'𞤼'), - (0x1E91B, 'M', u'𞤽'), - (0x1E91C, 'M', u'𞤾'), - (0x1E91D, 'M', u'𞤿'), - (0x1E91E, 'M', u'𞥀'), - (0x1E91F, 'M', u'𞥁'), - (0x1E920, 'M', u'𞥂'), - (0x1E921, 'M', u'𞥃'), - (0x1E922, 'V'), - (0x1E94B, 'X'), - (0x1E950, 'V'), - (0x1E95A, 'X'), - (0x1E95E, 'V'), - (0x1E960, 'X'), - (0x1EE00, 'M', u'ا'), - (0x1EE01, 'M', u'ب'), - (0x1EE02, 'M', u'ج'), - (0x1EE03, 'M', u'د'), - (0x1EE04, 'X'), - (0x1EE05, 'M', u'و'), - (0x1EE06, 'M', u'ز'), - (0x1EE07, 'M', u'ح'), - (0x1EE08, 'M', u'ط'), - (0x1EE09, 'M', u'ي'), - (0x1EE0A, 'M', u'ك'), - (0x1EE0B, 'M', u'ل'), - (0x1EE0C, 'M', u'م'), - (0x1EE0D, 'M', u'ن'), - (0x1EE0E, 'M', u'س'), - (0x1EE0F, 'M', u'ع'), - (0x1EE10, 'M', u'ف'), - (0x1EE11, 'M', u'ص'), - (0x1EE12, 'M', u'ق'), - (0x1EE13, 'M', u'ر'), - (0x1EE14, 'M', u'ش'), - (0x1EE15, 'M', u'ت'), - (0x1EE16, 'M', u'ث'), - (0x1EE17, 'M', u'خ'), - (0x1EE18, 'M', u'ذ'), - (0x1EE19, 'M', u'ض'), - (0x1EE1A, 'M', u'ظ'), - (0x1EE1B, 'M', u'غ'), - (0x1EE1C, 'M', u'ٮ'), - (0x1EE1D, 'M', u'ں'), - (0x1EE1E, 'M', u'ڡ'), - (0x1EE1F, 'M', u'ٯ'), - (0x1EE20, 'X'), - (0x1EE21, 'M', u'ب'), - (0x1EE22, 'M', u'ج'), - (0x1EE23, 'X'), - ] - -def _seg_69(): - return [ - (0x1EE24, 'M', u'ه'), - (0x1EE25, 'X'), - (0x1EE27, 'M', u'ح'), - (0x1EE28, 'X'), - (0x1EE29, 'M', u'ي'), - (0x1EE2A, 'M', u'ك'), - (0x1EE2B, 'M', u'ل'), - (0x1EE2C, 'M', u'م'), - (0x1EE2D, 'M', u'ن'), - (0x1EE2E, 'M', u'س'), - (0x1EE2F, 'M', u'ع'), - (0x1EE30, 'M', u'ف'), - (0x1EE31, 'M', u'ص'), - (0x1EE32, 'M', u'ق'), - (0x1EE33, 'X'), - (0x1EE34, 'M', u'ش'), - (0x1EE35, 'M', u'ت'), - (0x1EE36, 'M', u'ث'), - (0x1EE37, 'M', u'خ'), - (0x1EE38, 'X'), - (0x1EE39, 'M', u'ض'), - (0x1EE3A, 'X'), - (0x1EE3B, 'M', u'غ'), - (0x1EE3C, 'X'), - (0x1EE42, 'M', u'ج'), - (0x1EE43, 'X'), - (0x1EE47, 'M', u'ح'), - (0x1EE48, 'X'), - (0x1EE49, 'M', u'ي'), - (0x1EE4A, 'X'), - (0x1EE4B, 'M', u'ل'), - (0x1EE4C, 'X'), - (0x1EE4D, 'M', u'ن'), - (0x1EE4E, 'M', u'س'), - (0x1EE4F, 'M', u'ع'), - (0x1EE50, 'X'), - (0x1EE51, 'M', u'ص'), - (0x1EE52, 'M', u'ق'), - (0x1EE53, 'X'), - (0x1EE54, 'M', u'ش'), - (0x1EE55, 'X'), - (0x1EE57, 'M', u'خ'), - (0x1EE58, 'X'), - (0x1EE59, 'M', u'ض'), - (0x1EE5A, 'X'), - (0x1EE5B, 'M', u'غ'), - (0x1EE5C, 'X'), - (0x1EE5D, 'M', u'ں'), - (0x1EE5E, 'X'), - (0x1EE5F, 'M', u'ٯ'), - (0x1EE60, 'X'), - (0x1EE61, 'M', u'ب'), - (0x1EE62, 'M', u'ج'), - (0x1EE63, 'X'), - (0x1EE64, 'M', u'ه'), - (0x1EE65, 'X'), - (0x1EE67, 'M', u'ح'), - (0x1EE68, 'M', u'ط'), - (0x1EE69, 'M', u'ي'), - (0x1EE6A, 'M', u'ك'), - (0x1EE6B, 'X'), - (0x1EE6C, 'M', u'م'), - (0x1EE6D, 'M', u'ن'), - (0x1EE6E, 'M', u'س'), - (0x1EE6F, 'M', u'ع'), - (0x1EE70, 'M', u'ف'), - (0x1EE71, 'M', u'ص'), - (0x1EE72, 'M', u'ق'), - (0x1EE73, 'X'), - (0x1EE74, 'M', u'ش'), - (0x1EE75, 'M', u'ت'), - (0x1EE76, 'M', u'ث'), - (0x1EE77, 'M', u'خ'), - (0x1EE78, 'X'), - (0x1EE79, 'M', u'ض'), - (0x1EE7A, 'M', u'ظ'), - (0x1EE7B, 'M', u'غ'), - (0x1EE7C, 'M', u'ٮ'), - (0x1EE7D, 'X'), - (0x1EE7E, 'M', u'ڡ'), - (0x1EE7F, 'X'), - (0x1EE80, 'M', u'ا'), - (0x1EE81, 'M', u'ب'), - (0x1EE82, 'M', u'ج'), - (0x1EE83, 'M', u'د'), - (0x1EE84, 'M', u'ه'), - (0x1EE85, 'M', u'و'), - (0x1EE86, 'M', u'ز'), - (0x1EE87, 'M', u'ح'), - (0x1EE88, 'M', u'ط'), - (0x1EE89, 'M', u'ي'), - (0x1EE8A, 'X'), - (0x1EE8B, 'M', u'ل'), - (0x1EE8C, 'M', u'م'), - (0x1EE8D, 'M', u'ن'), - (0x1EE8E, 'M', u'س'), - (0x1EE8F, 'M', u'ع'), - (0x1EE90, 'M', u'ف'), - (0x1EE91, 'M', u'ص'), - (0x1EE92, 'M', u'ق'), - ] - -def _seg_70(): - return [ - (0x1EE93, 'M', u'ر'), - (0x1EE94, 'M', u'ش'), - (0x1EE95, 'M', u'ت'), - (0x1EE96, 'M', u'ث'), - (0x1EE97, 'M', u'خ'), - (0x1EE98, 'M', u'ذ'), - (0x1EE99, 'M', u'ض'), - (0x1EE9A, 'M', u'ظ'), - (0x1EE9B, 'M', u'غ'), - (0x1EE9C, 'X'), - (0x1EEA1, 'M', u'ب'), - (0x1EEA2, 'M', u'ج'), - (0x1EEA3, 'M', u'د'), - (0x1EEA4, 'X'), - (0x1EEA5, 'M', u'و'), - (0x1EEA6, 'M', u'ز'), - (0x1EEA7, 'M', u'ح'), - (0x1EEA8, 'M', u'ط'), - (0x1EEA9, 'M', u'ي'), - (0x1EEAA, 'X'), - (0x1EEAB, 'M', u'ل'), - (0x1EEAC, 'M', u'م'), - (0x1EEAD, 'M', u'ن'), - (0x1EEAE, 'M', u'س'), - (0x1EEAF, 'M', u'ع'), - (0x1EEB0, 'M', u'ف'), - (0x1EEB1, 'M', u'ص'), - (0x1EEB2, 'M', u'ق'), - (0x1EEB3, 'M', u'ر'), - (0x1EEB4, 'M', u'ش'), - (0x1EEB5, 'M', u'ت'), - (0x1EEB6, 'M', u'ث'), - (0x1EEB7, 'M', u'خ'), - (0x1EEB8, 'M', u'ذ'), - (0x1EEB9, 'M', u'ض'), - (0x1EEBA, 'M', u'ظ'), - (0x1EEBB, 'M', u'غ'), - (0x1EEBC, 'X'), - (0x1EEF0, 'V'), - (0x1EEF2, 'X'), - (0x1F000, 'V'), - (0x1F02C, 'X'), - (0x1F030, 'V'), - (0x1F094, 'X'), - (0x1F0A0, 'V'), - (0x1F0AF, 'X'), - (0x1F0B1, 'V'), - (0x1F0C0, 'X'), - (0x1F0C1, 'V'), - (0x1F0D0, 'X'), - (0x1F0D1, 'V'), - (0x1F0F6, 'X'), - (0x1F101, '3', u'0,'), - (0x1F102, '3', u'1,'), - (0x1F103, '3', u'2,'), - (0x1F104, '3', u'3,'), - (0x1F105, '3', u'4,'), - (0x1F106, '3', u'5,'), - (0x1F107, '3', u'6,'), - (0x1F108, '3', u'7,'), - (0x1F109, '3', u'8,'), - (0x1F10A, '3', u'9,'), - (0x1F10B, 'V'), - (0x1F10D, 'X'), - (0x1F110, '3', u'(a)'), - (0x1F111, '3', u'(b)'), - (0x1F112, '3', u'(c)'), - (0x1F113, '3', u'(d)'), - (0x1F114, '3', u'(e)'), - (0x1F115, '3', u'(f)'), - (0x1F116, '3', u'(g)'), - (0x1F117, '3', u'(h)'), - (0x1F118, '3', u'(i)'), - (0x1F119, '3', u'(j)'), - (0x1F11A, '3', u'(k)'), - (0x1F11B, '3', u'(l)'), - (0x1F11C, '3', u'(m)'), - (0x1F11D, '3', u'(n)'), - (0x1F11E, '3', u'(o)'), - (0x1F11F, '3', u'(p)'), - (0x1F120, '3', u'(q)'), - (0x1F121, '3', u'(r)'), - (0x1F122, '3', u'(s)'), - (0x1F123, '3', u'(t)'), - (0x1F124, '3', u'(u)'), - (0x1F125, '3', u'(v)'), - (0x1F126, '3', u'(w)'), - (0x1F127, '3', u'(x)'), - (0x1F128, '3', u'(y)'), - (0x1F129, '3', u'(z)'), - (0x1F12A, 'M', u'〔s〕'), - (0x1F12B, 'M', u'c'), - (0x1F12C, 'M', u'r'), - (0x1F12D, 'M', u'cd'), - (0x1F12E, 'M', u'wz'), - (0x1F12F, 'X'), - (0x1F130, 'M', u'a'), - (0x1F131, 'M', u'b'), - (0x1F132, 'M', u'c'), - (0x1F133, 'M', u'd'), - ] - -def _seg_71(): - return [ - (0x1F134, 'M', u'e'), - (0x1F135, 'M', u'f'), - (0x1F136, 'M', u'g'), - (0x1F137, 'M', u'h'), - (0x1F138, 'M', u'i'), - (0x1F139, 'M', u'j'), - (0x1F13A, 'M', u'k'), - (0x1F13B, 'M', u'l'), - (0x1F13C, 'M', u'm'), - (0x1F13D, 'M', u'n'), - (0x1F13E, 'M', u'o'), - (0x1F13F, 'M', u'p'), - (0x1F140, 'M', u'q'), - (0x1F141, 'M', u'r'), - (0x1F142, 'M', u's'), - (0x1F143, 'M', u't'), - (0x1F144, 'M', u'u'), - (0x1F145, 'M', u'v'), - (0x1F146, 'M', u'w'), - (0x1F147, 'M', u'x'), - (0x1F148, 'M', u'y'), - (0x1F149, 'M', u'z'), - (0x1F14A, 'M', u'hv'), - (0x1F14B, 'M', u'mv'), - (0x1F14C, 'M', u'sd'), - (0x1F14D, 'M', u'ss'), - (0x1F14E, 'M', u'ppv'), - (0x1F14F, 'M', u'wc'), - (0x1F150, 'V'), - (0x1F16A, 'M', u'mc'), - (0x1F16B, 'M', u'md'), - (0x1F16C, 'X'), - (0x1F170, 'V'), - (0x1F190, 'M', u'dj'), - (0x1F191, 'V'), - (0x1F1AD, 'X'), - (0x1F1E6, 'V'), - (0x1F200, 'M', u'ほか'), - (0x1F201, 'M', u'ココ'), - (0x1F202, 'M', u'サ'), - (0x1F203, 'X'), - (0x1F210, 'M', u'手'), - (0x1F211, 'M', u'字'), - (0x1F212, 'M', u'双'), - (0x1F213, 'M', u'デ'), - (0x1F214, 'M', u'二'), - (0x1F215, 'M', u'多'), - (0x1F216, 'M', u'解'), - (0x1F217, 'M', u'天'), - (0x1F218, 'M', u'交'), - (0x1F219, 'M', u'映'), - (0x1F21A, 'M', u'無'), - (0x1F21B, 'M', u'料'), - (0x1F21C, 'M', u'前'), - (0x1F21D, 'M', u'後'), - (0x1F21E, 'M', u'再'), - (0x1F21F, 'M', u'新'), - (0x1F220, 'M', u'初'), - (0x1F221, 'M', u'終'), - (0x1F222, 'M', u'生'), - (0x1F223, 'M', u'販'), - (0x1F224, 'M', u'声'), - (0x1F225, 'M', u'吹'), - (0x1F226, 'M', u'演'), - (0x1F227, 'M', u'投'), - (0x1F228, 'M', u'捕'), - (0x1F229, 'M', u'一'), - (0x1F22A, 'M', u'三'), - (0x1F22B, 'M', u'遊'), - (0x1F22C, 'M', u'左'), - (0x1F22D, 'M', u'中'), - (0x1F22E, 'M', u'右'), - (0x1F22F, 'M', u'指'), - (0x1F230, 'M', u'走'), - (0x1F231, 'M', u'打'), - (0x1F232, 'M', u'禁'), - (0x1F233, 'M', u'空'), - (0x1F234, 'M', u'合'), - (0x1F235, 'M', u'満'), - (0x1F236, 'M', u'有'), - (0x1F237, 'M', u'月'), - (0x1F238, 'M', u'申'), - (0x1F239, 'M', u'割'), - (0x1F23A, 'M', u'営'), - (0x1F23B, 'M', u'配'), - (0x1F23C, 'X'), - (0x1F240, 'M', u'〔本〕'), - (0x1F241, 'M', u'〔三〕'), - (0x1F242, 'M', u'〔二〕'), - (0x1F243, 'M', u'〔安〕'), - (0x1F244, 'M', u'〔点〕'), - (0x1F245, 'M', u'〔打〕'), - (0x1F246, 'M', u'〔盗〕'), - (0x1F247, 'M', u'〔勝〕'), - (0x1F248, 'M', u'〔敗〕'), - (0x1F249, 'X'), - (0x1F250, 'M', u'得'), - (0x1F251, 'M', u'可'), - (0x1F252, 'X'), - (0x1F260, 'V'), - ] - -def _seg_72(): - return [ - (0x1F266, 'X'), - (0x1F300, 'V'), - (0x1F6D5, 'X'), - (0x1F6E0, 'V'), - (0x1F6ED, 'X'), - (0x1F6F0, 'V'), - (0x1F6F9, 'X'), - (0x1F700, 'V'), - (0x1F774, 'X'), - (0x1F780, 'V'), - (0x1F7D5, 'X'), - (0x1F800, 'V'), - (0x1F80C, 'X'), - (0x1F810, 'V'), - (0x1F848, 'X'), - (0x1F850, 'V'), - (0x1F85A, 'X'), - (0x1F860, 'V'), - (0x1F888, 'X'), - (0x1F890, 'V'), - (0x1F8AE, 'X'), - (0x1F900, 'V'), - (0x1F90C, 'X'), - (0x1F910, 'V'), - (0x1F93F, 'X'), - (0x1F940, 'V'), - (0x1F94D, 'X'), - (0x1F950, 'V'), - (0x1F96C, 'X'), - (0x1F980, 'V'), - (0x1F998, 'X'), - (0x1F9C0, 'V'), - (0x1F9C1, 'X'), - (0x1F9D0, 'V'), - (0x1F9E7, 'X'), - (0x20000, 'V'), - (0x2A6D7, 'X'), - (0x2A700, 'V'), - (0x2B735, 'X'), - (0x2B740, 'V'), - (0x2B81E, 'X'), - (0x2B820, 'V'), - (0x2CEA2, 'X'), - (0x2CEB0, 'V'), - (0x2EBE1, 'X'), - (0x2F800, 'M', u'丽'), - (0x2F801, 'M', u'丸'), - (0x2F802, 'M', u'乁'), - (0x2F803, 'M', u'𠄢'), - (0x2F804, 'M', u'你'), - (0x2F805, 'M', u'侮'), - (0x2F806, 'M', u'侻'), - (0x2F807, 'M', u'倂'), - (0x2F808, 'M', u'偺'), - (0x2F809, 'M', u'備'), - (0x2F80A, 'M', u'僧'), - (0x2F80B, 'M', u'像'), - (0x2F80C, 'M', u'㒞'), - (0x2F80D, 'M', u'𠘺'), - (0x2F80E, 'M', u'免'), - (0x2F80F, 'M', u'兔'), - (0x2F810, 'M', u'兤'), - (0x2F811, 'M', u'具'), - (0x2F812, 'M', u'𠔜'), - (0x2F813, 'M', u'㒹'), - (0x2F814, 'M', u'內'), - (0x2F815, 'M', u'再'), - (0x2F816, 'M', u'𠕋'), - (0x2F817, 'M', u'冗'), - (0x2F818, 'M', u'冤'), - (0x2F819, 'M', u'仌'), - (0x2F81A, 'M', u'冬'), - (0x2F81B, 'M', u'况'), - (0x2F81C, 'M', u'𩇟'), - (0x2F81D, 'M', u'凵'), - (0x2F81E, 'M', u'刃'), - (0x2F81F, 'M', u'㓟'), - (0x2F820, 'M', u'刻'), - (0x2F821, 'M', u'剆'), - (0x2F822, 'M', u'割'), - (0x2F823, 'M', u'剷'), - (0x2F824, 'M', u'㔕'), - (0x2F825, 'M', u'勇'), - (0x2F826, 'M', u'勉'), - (0x2F827, 'M', u'勤'), - (0x2F828, 'M', u'勺'), - (0x2F829, 'M', u'包'), - (0x2F82A, 'M', u'匆'), - (0x2F82B, 'M', u'北'), - (0x2F82C, 'M', u'卉'), - (0x2F82D, 'M', u'卑'), - (0x2F82E, 'M', u'博'), - (0x2F82F, 'M', u'即'), - (0x2F830, 'M', u'卽'), - (0x2F831, 'M', u'卿'), - (0x2F834, 'M', u'𠨬'), - (0x2F835, 'M', u'灰'), - (0x2F836, 'M', u'及'), - (0x2F837, 'M', u'叟'), - (0x2F838, 'M', u'𠭣'), - ] - -def _seg_73(): - return [ - (0x2F839, 'M', u'叫'), - (0x2F83A, 'M', u'叱'), - (0x2F83B, 'M', u'吆'), - (0x2F83C, 'M', u'咞'), - (0x2F83D, 'M', u'吸'), - (0x2F83E, 'M', u'呈'), - (0x2F83F, 'M', u'周'), - (0x2F840, 'M', u'咢'), - (0x2F841, 'M', u'哶'), - (0x2F842, 'M', u'唐'), - (0x2F843, 'M', u'啓'), - (0x2F844, 'M', u'啣'), - (0x2F845, 'M', u'善'), - (0x2F847, 'M', u'喙'), - (0x2F848, 'M', u'喫'), - (0x2F849, 'M', u'喳'), - (0x2F84A, 'M', u'嗂'), - (0x2F84B, 'M', u'圖'), - (0x2F84C, 'M', u'嘆'), - (0x2F84D, 'M', u'圗'), - (0x2F84E, 'M', u'噑'), - (0x2F84F, 'M', u'噴'), - (0x2F850, 'M', u'切'), - (0x2F851, 'M', u'壮'), - (0x2F852, 'M', u'城'), - (0x2F853, 'M', u'埴'), - (0x2F854, 'M', u'堍'), - (0x2F855, 'M', u'型'), - (0x2F856, 'M', u'堲'), - (0x2F857, 'M', u'報'), - (0x2F858, 'M', u'墬'), - (0x2F859, 'M', u'𡓤'), - (0x2F85A, 'M', u'売'), - (0x2F85B, 'M', u'壷'), - (0x2F85C, 'M', u'夆'), - (0x2F85D, 'M', u'多'), - (0x2F85E, 'M', u'夢'), - (0x2F85F, 'M', u'奢'), - (0x2F860, 'M', u'𡚨'), - (0x2F861, 'M', u'𡛪'), - (0x2F862, 'M', u'姬'), - (0x2F863, 'M', u'娛'), - (0x2F864, 'M', u'娧'), - (0x2F865, 'M', u'姘'), - (0x2F866, 'M', u'婦'), - (0x2F867, 'M', u'㛮'), - (0x2F868, 'X'), - (0x2F869, 'M', u'嬈'), - (0x2F86A, 'M', u'嬾'), - (0x2F86C, 'M', u'𡧈'), - (0x2F86D, 'M', u'寃'), - (0x2F86E, 'M', u'寘'), - (0x2F86F, 'M', u'寧'), - (0x2F870, 'M', u'寳'), - (0x2F871, 'M', u'𡬘'), - (0x2F872, 'M', u'寿'), - (0x2F873, 'M', u'将'), - (0x2F874, 'X'), - (0x2F875, 'M', u'尢'), - (0x2F876, 'M', u'㞁'), - (0x2F877, 'M', u'屠'), - (0x2F878, 'M', u'屮'), - (0x2F879, 'M', u'峀'), - (0x2F87A, 'M', u'岍'), - (0x2F87B, 'M', u'𡷤'), - (0x2F87C, 'M', u'嵃'), - (0x2F87D, 'M', u'𡷦'), - (0x2F87E, 'M', u'嵮'), - (0x2F87F, 'M', u'嵫'), - (0x2F880, 'M', u'嵼'), - (0x2F881, 'M', u'巡'), - (0x2F882, 'M', u'巢'), - (0x2F883, 'M', u'㠯'), - (0x2F884, 'M', u'巽'), - (0x2F885, 'M', u'帨'), - (0x2F886, 'M', u'帽'), - (0x2F887, 'M', u'幩'), - (0x2F888, 'M', u'㡢'), - (0x2F889, 'M', u'𢆃'), - (0x2F88A, 'M', u'㡼'), - (0x2F88B, 'M', u'庰'), - (0x2F88C, 'M', u'庳'), - (0x2F88D, 'M', u'庶'), - (0x2F88E, 'M', u'廊'), - (0x2F88F, 'M', u'𪎒'), - (0x2F890, 'M', u'廾'), - (0x2F891, 'M', u'𢌱'), - (0x2F893, 'M', u'舁'), - (0x2F894, 'M', u'弢'), - (0x2F896, 'M', u'㣇'), - (0x2F897, 'M', u'𣊸'), - (0x2F898, 'M', u'𦇚'), - (0x2F899, 'M', u'形'), - (0x2F89A, 'M', u'彫'), - (0x2F89B, 'M', u'㣣'), - (0x2F89C, 'M', u'徚'), - (0x2F89D, 'M', u'忍'), - (0x2F89E, 'M', u'志'), - (0x2F89F, 'M', u'忹'), - (0x2F8A0, 'M', u'悁'), - ] - -def _seg_74(): - return [ - (0x2F8A1, 'M', u'㤺'), - (0x2F8A2, 'M', u'㤜'), - (0x2F8A3, 'M', u'悔'), - (0x2F8A4, 'M', u'𢛔'), - (0x2F8A5, 'M', u'惇'), - (0x2F8A6, 'M', u'慈'), - (0x2F8A7, 'M', u'慌'), - (0x2F8A8, 'M', u'慎'), - (0x2F8A9, 'M', u'慌'), - (0x2F8AA, 'M', u'慺'), - (0x2F8AB, 'M', u'憎'), - (0x2F8AC, 'M', u'憲'), - (0x2F8AD, 'M', u'憤'), - (0x2F8AE, 'M', u'憯'), - (0x2F8AF, 'M', u'懞'), - (0x2F8B0, 'M', u'懲'), - (0x2F8B1, 'M', u'懶'), - (0x2F8B2, 'M', u'成'), - (0x2F8B3, 'M', u'戛'), - (0x2F8B4, 'M', u'扝'), - (0x2F8B5, 'M', u'抱'), - (0x2F8B6, 'M', u'拔'), - (0x2F8B7, 'M', u'捐'), - (0x2F8B8, 'M', u'𢬌'), - (0x2F8B9, 'M', u'挽'), - (0x2F8BA, 'M', u'拼'), - (0x2F8BB, 'M', u'捨'), - (0x2F8BC, 'M', u'掃'), - (0x2F8BD, 'M', u'揤'), - (0x2F8BE, 'M', u'𢯱'), - (0x2F8BF, 'M', u'搢'), - (0x2F8C0, 'M', u'揅'), - (0x2F8C1, 'M', u'掩'), - (0x2F8C2, 'M', u'㨮'), - (0x2F8C3, 'M', u'摩'), - (0x2F8C4, 'M', u'摾'), - (0x2F8C5, 'M', u'撝'), - (0x2F8C6, 'M', u'摷'), - (0x2F8C7, 'M', u'㩬'), - (0x2F8C8, 'M', u'敏'), - (0x2F8C9, 'M', u'敬'), - (0x2F8CA, 'M', u'𣀊'), - (0x2F8CB, 'M', u'旣'), - (0x2F8CC, 'M', u'書'), - (0x2F8CD, 'M', u'晉'), - (0x2F8CE, 'M', u'㬙'), - (0x2F8CF, 'M', u'暑'), - (0x2F8D0, 'M', u'㬈'), - (0x2F8D1, 'M', u'㫤'), - (0x2F8D2, 'M', u'冒'), - (0x2F8D3, 'M', u'冕'), - (0x2F8D4, 'M', u'最'), - (0x2F8D5, 'M', u'暜'), - (0x2F8D6, 'M', u'肭'), - (0x2F8D7, 'M', u'䏙'), - (0x2F8D8, 'M', u'朗'), - (0x2F8D9, 'M', u'望'), - (0x2F8DA, 'M', u'朡'), - (0x2F8DB, 'M', u'杞'), - (0x2F8DC, 'M', u'杓'), - (0x2F8DD, 'M', u'𣏃'), - (0x2F8DE, 'M', u'㭉'), - (0x2F8DF, 'M', u'柺'), - (0x2F8E0, 'M', u'枅'), - (0x2F8E1, 'M', u'桒'), - (0x2F8E2, 'M', u'梅'), - (0x2F8E3, 'M', u'𣑭'), - (0x2F8E4, 'M', u'梎'), - (0x2F8E5, 'M', u'栟'), - (0x2F8E6, 'M', u'椔'), - (0x2F8E7, 'M', u'㮝'), - (0x2F8E8, 'M', u'楂'), - (0x2F8E9, 'M', u'榣'), - (0x2F8EA, 'M', u'槪'), - (0x2F8EB, 'M', u'檨'), - (0x2F8EC, 'M', u'𣚣'), - (0x2F8ED, 'M', u'櫛'), - (0x2F8EE, 'M', u'㰘'), - (0x2F8EF, 'M', u'次'), - (0x2F8F0, 'M', u'𣢧'), - (0x2F8F1, 'M', u'歔'), - (0x2F8F2, 'M', u'㱎'), - (0x2F8F3, 'M', u'歲'), - (0x2F8F4, 'M', u'殟'), - (0x2F8F5, 'M', u'殺'), - (0x2F8F6, 'M', u'殻'), - (0x2F8F7, 'M', u'𣪍'), - (0x2F8F8, 'M', u'𡴋'), - (0x2F8F9, 'M', u'𣫺'), - (0x2F8FA, 'M', u'汎'), - (0x2F8FB, 'M', u'𣲼'), - (0x2F8FC, 'M', u'沿'), - (0x2F8FD, 'M', u'泍'), - (0x2F8FE, 'M', u'汧'), - (0x2F8FF, 'M', u'洖'), - (0x2F900, 'M', u'派'), - (0x2F901, 'M', u'海'), - (0x2F902, 'M', u'流'), - (0x2F903, 'M', u'浩'), - (0x2F904, 'M', u'浸'), - ] - -def _seg_75(): - return [ - (0x2F905, 'M', u'涅'), - (0x2F906, 'M', u'𣴞'), - (0x2F907, 'M', u'洴'), - (0x2F908, 'M', u'港'), - (0x2F909, 'M', u'湮'), - (0x2F90A, 'M', u'㴳'), - (0x2F90B, 'M', u'滋'), - (0x2F90C, 'M', u'滇'), - (0x2F90D, 'M', u'𣻑'), - (0x2F90E, 'M', u'淹'), - (0x2F90F, 'M', u'潮'), - (0x2F910, 'M', u'𣽞'), - (0x2F911, 'M', u'𣾎'), - (0x2F912, 'M', u'濆'), - (0x2F913, 'M', u'瀹'), - (0x2F914, 'M', u'瀞'), - (0x2F915, 'M', u'瀛'), - (0x2F916, 'M', u'㶖'), - (0x2F917, 'M', u'灊'), - (0x2F918, 'M', u'災'), - (0x2F919, 'M', u'灷'), - (0x2F91A, 'M', u'炭'), - (0x2F91B, 'M', u'𠔥'), - (0x2F91C, 'M', u'煅'), - (0x2F91D, 'M', u'𤉣'), - (0x2F91E, 'M', u'熜'), - (0x2F91F, 'X'), - (0x2F920, 'M', u'爨'), - (0x2F921, 'M', u'爵'), - (0x2F922, 'M', u'牐'), - (0x2F923, 'M', u'𤘈'), - (0x2F924, 'M', u'犀'), - (0x2F925, 'M', u'犕'), - (0x2F926, 'M', u'𤜵'), - (0x2F927, 'M', u'𤠔'), - (0x2F928, 'M', u'獺'), - (0x2F929, 'M', u'王'), - (0x2F92A, 'M', u'㺬'), - (0x2F92B, 'M', u'玥'), - (0x2F92C, 'M', u'㺸'), - (0x2F92E, 'M', u'瑇'), - (0x2F92F, 'M', u'瑜'), - (0x2F930, 'M', u'瑱'), - (0x2F931, 'M', u'璅'), - (0x2F932, 'M', u'瓊'), - (0x2F933, 'M', u'㼛'), - (0x2F934, 'M', u'甤'), - (0x2F935, 'M', u'𤰶'), - (0x2F936, 'M', u'甾'), - (0x2F937, 'M', u'𤲒'), - (0x2F938, 'M', u'異'), - (0x2F939, 'M', u'𢆟'), - (0x2F93A, 'M', u'瘐'), - (0x2F93B, 'M', u'𤾡'), - (0x2F93C, 'M', u'𤾸'), - (0x2F93D, 'M', u'𥁄'), - (0x2F93E, 'M', u'㿼'), - (0x2F93F, 'M', u'䀈'), - (0x2F940, 'M', u'直'), - (0x2F941, 'M', u'𥃳'), - (0x2F942, 'M', u'𥃲'), - (0x2F943, 'M', u'𥄙'), - (0x2F944, 'M', u'𥄳'), - (0x2F945, 'M', u'眞'), - (0x2F946, 'M', u'真'), - (0x2F948, 'M', u'睊'), - (0x2F949, 'M', u'䀹'), - (0x2F94A, 'M', u'瞋'), - (0x2F94B, 'M', u'䁆'), - (0x2F94C, 'M', u'䂖'), - (0x2F94D, 'M', u'𥐝'), - (0x2F94E, 'M', u'硎'), - (0x2F94F, 'M', u'碌'), - (0x2F950, 'M', u'磌'), - (0x2F951, 'M', u'䃣'), - (0x2F952, 'M', u'𥘦'), - (0x2F953, 'M', u'祖'), - (0x2F954, 'M', u'𥚚'), - (0x2F955, 'M', u'𥛅'), - (0x2F956, 'M', u'福'), - (0x2F957, 'M', u'秫'), - (0x2F958, 'M', u'䄯'), - (0x2F959, 'M', u'穀'), - (0x2F95A, 'M', u'穊'), - (0x2F95B, 'M', u'穏'), - (0x2F95C, 'M', u'𥥼'), - (0x2F95D, 'M', u'𥪧'), - (0x2F95F, 'X'), - (0x2F960, 'M', u'䈂'), - (0x2F961, 'M', u'𥮫'), - (0x2F962, 'M', u'篆'), - (0x2F963, 'M', u'築'), - (0x2F964, 'M', u'䈧'), - (0x2F965, 'M', u'𥲀'), - (0x2F966, 'M', u'糒'), - (0x2F967, 'M', u'䊠'), - (0x2F968, 'M', u'糨'), - (0x2F969, 'M', u'糣'), - (0x2F96A, 'M', u'紀'), - (0x2F96B, 'M', u'𥾆'), - ] - -def _seg_76(): - return [ - (0x2F96C, 'M', u'絣'), - (0x2F96D, 'M', u'䌁'), - (0x2F96E, 'M', u'緇'), - (0x2F96F, 'M', u'縂'), - (0x2F970, 'M', u'繅'), - (0x2F971, 'M', u'䌴'), - (0x2F972, 'M', u'𦈨'), - (0x2F973, 'M', u'𦉇'), - (0x2F974, 'M', u'䍙'), - (0x2F975, 'M', u'𦋙'), - (0x2F976, 'M', u'罺'), - (0x2F977, 'M', u'𦌾'), - (0x2F978, 'M', u'羕'), - (0x2F979, 'M', u'翺'), - (0x2F97A, 'M', u'者'), - (0x2F97B, 'M', u'𦓚'), - (0x2F97C, 'M', u'𦔣'), - (0x2F97D, 'M', u'聠'), - (0x2F97E, 'M', u'𦖨'), - (0x2F97F, 'M', u'聰'), - (0x2F980, 'M', u'𣍟'), - (0x2F981, 'M', u'䏕'), - (0x2F982, 'M', u'育'), - (0x2F983, 'M', u'脃'), - (0x2F984, 'M', u'䐋'), - (0x2F985, 'M', u'脾'), - (0x2F986, 'M', u'媵'), - (0x2F987, 'M', u'𦞧'), - (0x2F988, 'M', u'𦞵'), - (0x2F989, 'M', u'𣎓'), - (0x2F98A, 'M', u'𣎜'), - (0x2F98B, 'M', u'舁'), - (0x2F98C, 'M', u'舄'), - (0x2F98D, 'M', u'辞'), - (0x2F98E, 'M', u'䑫'), - (0x2F98F, 'M', u'芑'), - (0x2F990, 'M', u'芋'), - (0x2F991, 'M', u'芝'), - (0x2F992, 'M', u'劳'), - (0x2F993, 'M', u'花'), - (0x2F994, 'M', u'芳'), - (0x2F995, 'M', u'芽'), - (0x2F996, 'M', u'苦'), - (0x2F997, 'M', u'𦬼'), - (0x2F998, 'M', u'若'), - (0x2F999, 'M', u'茝'), - (0x2F99A, 'M', u'荣'), - (0x2F99B, 'M', u'莭'), - (0x2F99C, 'M', u'茣'), - (0x2F99D, 'M', u'莽'), - (0x2F99E, 'M', u'菧'), - (0x2F99F, 'M', u'著'), - (0x2F9A0, 'M', u'荓'), - (0x2F9A1, 'M', u'菊'), - (0x2F9A2, 'M', u'菌'), - (0x2F9A3, 'M', u'菜'), - (0x2F9A4, 'M', u'𦰶'), - (0x2F9A5, 'M', u'𦵫'), - (0x2F9A6, 'M', u'𦳕'), - (0x2F9A7, 'M', u'䔫'), - (0x2F9A8, 'M', u'蓱'), - (0x2F9A9, 'M', u'蓳'), - (0x2F9AA, 'M', u'蔖'), - (0x2F9AB, 'M', u'𧏊'), - (0x2F9AC, 'M', u'蕤'), - (0x2F9AD, 'M', u'𦼬'), - (0x2F9AE, 'M', u'䕝'), - (0x2F9AF, 'M', u'䕡'), - (0x2F9B0, 'M', u'𦾱'), - (0x2F9B1, 'M', u'𧃒'), - (0x2F9B2, 'M', u'䕫'), - (0x2F9B3, 'M', u'虐'), - (0x2F9B4, 'M', u'虜'), - (0x2F9B5, 'M', u'虧'), - (0x2F9B6, 'M', u'虩'), - (0x2F9B7, 'M', u'蚩'), - (0x2F9B8, 'M', u'蚈'), - (0x2F9B9, 'M', u'蜎'), - (0x2F9BA, 'M', u'蛢'), - (0x2F9BB, 'M', u'蝹'), - (0x2F9BC, 'M', u'蜨'), - (0x2F9BD, 'M', u'蝫'), - (0x2F9BE, 'M', u'螆'), - (0x2F9BF, 'X'), - (0x2F9C0, 'M', u'蟡'), - (0x2F9C1, 'M', u'蠁'), - (0x2F9C2, 'M', u'䗹'), - (0x2F9C3, 'M', u'衠'), - (0x2F9C4, 'M', u'衣'), - (0x2F9C5, 'M', u'𧙧'), - (0x2F9C6, 'M', u'裗'), - (0x2F9C7, 'M', u'裞'), - (0x2F9C8, 'M', u'䘵'), - (0x2F9C9, 'M', u'裺'), - (0x2F9CA, 'M', u'㒻'), - (0x2F9CB, 'M', u'𧢮'), - (0x2F9CC, 'M', u'𧥦'), - (0x2F9CD, 'M', u'䚾'), - (0x2F9CE, 'M', u'䛇'), - (0x2F9CF, 'M', u'誠'), - ] - -def _seg_77(): - return [ - (0x2F9D0, 'M', u'諭'), - (0x2F9D1, 'M', u'變'), - (0x2F9D2, 'M', u'豕'), - (0x2F9D3, 'M', u'𧲨'), - (0x2F9D4, 'M', u'貫'), - (0x2F9D5, 'M', u'賁'), - (0x2F9D6, 'M', u'贛'), - (0x2F9D7, 'M', u'起'), - (0x2F9D8, 'M', u'𧼯'), - (0x2F9D9, 'M', u'𠠄'), - (0x2F9DA, 'M', u'跋'), - (0x2F9DB, 'M', u'趼'), - (0x2F9DC, 'M', u'跰'), - (0x2F9DD, 'M', u'𠣞'), - (0x2F9DE, 'M', u'軔'), - (0x2F9DF, 'M', u'輸'), - (0x2F9E0, 'M', u'𨗒'), - (0x2F9E1, 'M', u'𨗭'), - (0x2F9E2, 'M', u'邔'), - (0x2F9E3, 'M', u'郱'), - (0x2F9E4, 'M', u'鄑'), - (0x2F9E5, 'M', u'𨜮'), - (0x2F9E6, 'M', u'鄛'), - (0x2F9E7, 'M', u'鈸'), - (0x2F9E8, 'M', u'鋗'), - (0x2F9E9, 'M', u'鋘'), - (0x2F9EA, 'M', u'鉼'), - (0x2F9EB, 'M', u'鏹'), - (0x2F9EC, 'M', u'鐕'), - (0x2F9ED, 'M', u'𨯺'), - (0x2F9EE, 'M', u'開'), - (0x2F9EF, 'M', u'䦕'), - (0x2F9F0, 'M', u'閷'), - (0x2F9F1, 'M', u'𨵷'), - (0x2F9F2, 'M', u'䧦'), - (0x2F9F3, 'M', u'雃'), - (0x2F9F4, 'M', u'嶲'), - (0x2F9F5, 'M', u'霣'), - (0x2F9F6, 'M', u'𩅅'), - (0x2F9F7, 'M', u'𩈚'), - (0x2F9F8, 'M', u'䩮'), - (0x2F9F9, 'M', u'䩶'), - (0x2F9FA, 'M', u'韠'), - (0x2F9FB, 'M', u'𩐊'), - (0x2F9FC, 'M', u'䪲'), - (0x2F9FD, 'M', u'𩒖'), - (0x2F9FE, 'M', u'頋'), - (0x2FA00, 'M', u'頩'), - (0x2FA01, 'M', u'𩖶'), - (0x2FA02, 'M', u'飢'), - (0x2FA03, 'M', u'䬳'), - (0x2FA04, 'M', u'餩'), - (0x2FA05, 'M', u'馧'), - (0x2FA06, 'M', u'駂'), - (0x2FA07, 'M', u'駾'), - (0x2FA08, 'M', u'䯎'), - (0x2FA09, 'M', u'𩬰'), - (0x2FA0A, 'M', u'鬒'), - (0x2FA0B, 'M', u'鱀'), - (0x2FA0C, 'M', u'鳽'), - (0x2FA0D, 'M', u'䳎'), - (0x2FA0E, 'M', u'䳭'), - (0x2FA0F, 'M', u'鵧'), - (0x2FA10, 'M', u'𪃎'), - (0x2FA11, 'M', u'䳸'), - (0x2FA12, 'M', u'𪄅'), - (0x2FA13, 'M', u'𪈎'), - (0x2FA14, 'M', u'𪊑'), - (0x2FA15, 'M', u'麻'), - (0x2FA16, 'M', u'䵖'), - (0x2FA17, 'M', u'黹'), - (0x2FA18, 'M', u'黾'), - (0x2FA19, 'M', u'鼅'), - (0x2FA1A, 'M', u'鼏'), - (0x2FA1B, 'M', u'鼖'), - (0x2FA1C, 'M', u'鼻'), - (0x2FA1D, 'M', u'𪘀'), - (0x2FA1E, 'X'), - (0xE0100, 'I'), - (0xE01F0, 'X'), - ] - -uts46data = tuple( - _seg_0() - + _seg_1() - + _seg_2() - + _seg_3() - + _seg_4() - + _seg_5() - + _seg_6() - + _seg_7() - + _seg_8() - + _seg_9() - + _seg_10() - + _seg_11() - + _seg_12() - + _seg_13() - + _seg_14() - + _seg_15() - + _seg_16() - + _seg_17() - + _seg_18() - + _seg_19() - + _seg_20() - + _seg_21() - + _seg_22() - + _seg_23() - + _seg_24() - + _seg_25() - + _seg_26() - + _seg_27() - + _seg_28() - + _seg_29() - + _seg_30() - + _seg_31() - + _seg_32() - + _seg_33() - + _seg_34() - + _seg_35() - + _seg_36() - + _seg_37() - + _seg_38() - + _seg_39() - + _seg_40() - + _seg_41() - + _seg_42() - + _seg_43() - + _seg_44() - + _seg_45() - + _seg_46() - + _seg_47() - + _seg_48() - + _seg_49() - + _seg_50() - + _seg_51() - + _seg_52() - + _seg_53() - + _seg_54() - + _seg_55() - + _seg_56() - + _seg_57() - + _seg_58() - + _seg_59() - + _seg_60() - + _seg_61() - + _seg_62() - + _seg_63() - + _seg_64() - + _seg_65() - + _seg_66() - + _seg_67() - + _seg_68() - + _seg_69() - + _seg_70() - + _seg_71() - + _seg_72() - + _seg_73() - + _seg_74() - + _seg_75() - + _seg_76() - + _seg_77() -) diff --git a/lib/influxdb/__init__.py b/lib/influxdb/__init__.py deleted file mode 100644 index 03f7458..0000000 --- a/lib/influxdb/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -"""Initialize the influxdb package.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from .client import InfluxDBClient -from .dataframe_client import DataFrameClient -from .helper import SeriesHelper - - -__all__ = [ - 'InfluxDBClient', - 'DataFrameClient', - 'SeriesHelper', -] - - -__version__ = '5.2.0' diff --git a/lib/influxdb/_dataframe_client.py b/lib/influxdb/_dataframe_client.py deleted file mode 100644 index 646f298..0000000 --- a/lib/influxdb/_dataframe_client.py +++ /dev/null @@ -1,452 +0,0 @@ -# -*- coding: utf-8 -*- -"""DataFrame client for InfluxDB.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import math -from collections import defaultdict - -import pandas as pd -import numpy as np - -from .client import InfluxDBClient -from .line_protocol import _escape_tag - - -def _pandas_time_unit(time_precision): - unit = time_precision - if time_precision == 'm': - unit = 'ms' - elif time_precision == 'u': - unit = 'us' - elif time_precision == 'n': - unit = 'ns' - assert unit in ('s', 'ms', 'us', 'ns') - return unit - - -def _escape_pandas_series(s): - return s.apply(lambda v: _escape_tag(v)) - - -class DataFrameClient(InfluxDBClient): - """DataFrameClient instantiates InfluxDBClient to connect to the backend. - - The ``DataFrameClient`` object holds information necessary to connect - to InfluxDB. Requests can be made to InfluxDB directly through the client. - The client reads and writes from pandas DataFrames. - """ - - EPOCH = pd.Timestamp('1970-01-01 00:00:00.000+00:00') - - def write_points(self, - dataframe, - measurement, - tags=None, - tag_columns=None, - field_columns=None, - time_precision=None, - database=None, - retention_policy=None, - batch_size=None, - protocol='line', - numeric_precision=None): - """Write to multiple time series names. - - :param dataframe: data points in a DataFrame - :param measurement: name of measurement - :param tags: dictionary of tags, with string key-values - :param time_precision: [Optional, default None] Either 's', 'ms', 'u' - or 'n'. - :param batch_size: [Optional] Value to write the points in batches - instead of all at one time. Useful for when doing data dumps from - one database to another or when doing a massive write operation - :type batch_size: int - :param protocol: Protocol for writing data. Either 'line' or 'json'. - :param numeric_precision: Precision for floating point values. - Either None, 'full' or some int, where int is the desired decimal - precision. 'full' preserves full precision for int and float - datatypes. Defaults to None, which preserves 14-15 significant - figures for float and all significant figures for int datatypes. - """ - if tag_columns is None: - tag_columns = [] - - if field_columns is None: - field_columns = [] - - if batch_size: - number_batches = int(math.ceil(len(dataframe) / float(batch_size))) - - for batch in range(number_batches): - start_index = batch * batch_size - end_index = (batch + 1) * batch_size - - if protocol == 'line': - points = self._convert_dataframe_to_lines( - dataframe.iloc[start_index:end_index].copy(), - measurement=measurement, - global_tags=tags, - time_precision=time_precision, - tag_columns=tag_columns, - field_columns=field_columns, - numeric_precision=numeric_precision) - else: - points = self._convert_dataframe_to_json( - dataframe.iloc[start_index:end_index].copy(), - measurement=measurement, - tags=tags, - time_precision=time_precision, - tag_columns=tag_columns, - field_columns=field_columns) - - super(DataFrameClient, self).write_points( - points, - time_precision, - database, - retention_policy, - protocol=protocol) - - return True - - if protocol == 'line': - points = self._convert_dataframe_to_lines( - dataframe, - measurement=measurement, - global_tags=tags, - tag_columns=tag_columns, - field_columns=field_columns, - time_precision=time_precision, - numeric_precision=numeric_precision) - else: - points = self._convert_dataframe_to_json( - dataframe, - measurement=measurement, - tags=tags, - time_precision=time_precision, - tag_columns=tag_columns, - field_columns=field_columns) - - super(DataFrameClient, self).write_points( - points, - time_precision, - database, - retention_policy, - protocol=protocol) - - return True - - def query(self, - query, - params=None, - epoch=None, - expected_response_code=200, - database=None, - raise_errors=True, - chunked=False, - chunk_size=0, - dropna=True): - """ - Quering data into a DataFrame. - - :param query: the actual query string - :param params: additional parameters for the request, defaults to {} - :param epoch: response timestamps to be in epoch format either 'h', - 'm', 's', 'ms', 'u', or 'ns',defaults to `None` which is - RFC3339 UTC format with nanosecond precision - :param expected_response_code: the expected status code of response, - defaults to 200 - :param database: database to query, defaults to None - :param raise_errors: Whether or not to raise exceptions when InfluxDB - returns errors, defaults to True - :param chunked: Enable to use chunked responses from InfluxDB. - With ``chunked`` enabled, one ResultSet is returned per chunk - containing all results within that chunk - :param chunk_size: Size of each chunk to tell InfluxDB to use. - :param dropna: drop columns where all values are missing - :returns: the queried data - :rtype: :class:`~.ResultSet` - """ - query_args = dict(params=params, - epoch=epoch, - expected_response_code=expected_response_code, - raise_errors=raise_errors, - chunked=chunked, - database=database, - chunk_size=chunk_size) - results = super(DataFrameClient, self).query(query, **query_args) - if query.strip().upper().startswith("SELECT"): - if len(results) > 0: - return self._to_dataframe(results, dropna) - else: - return {} - else: - return results - - def _to_dataframe(self, rs, dropna=True): - result = defaultdict(list) - if isinstance(rs, list): - return map(self._to_dataframe, rs) - - for key, data in rs.items(): - name, tags = key - if tags is None: - key = name - else: - key = (name, tuple(sorted(tags.items()))) - df = pd.DataFrame(data) - df.time = pd.to_datetime(df.time) - df.set_index('time', inplace=True) - df.index = df.index.tz_localize('UTC') - df.index.name = None - result[key].append(df) - for key, data in result.items(): - df = pd.concat(data).sort_index() - if dropna: - df.dropna(how='all', axis=1, inplace=True) - result[key] = df - - return result - - @staticmethod - def _convert_dataframe_to_json(dataframe, - measurement, - tags=None, - tag_columns=None, - field_columns=None, - time_precision=None): - - if not isinstance(dataframe, pd.DataFrame): - raise TypeError('Must be DataFrame, but type was: {0}.' - .format(type(dataframe))) - if not (isinstance(dataframe.index, pd.PeriodIndex) or - isinstance(dataframe.index, pd.DatetimeIndex)): - raise TypeError('Must be DataFrame with DatetimeIndex or ' - 'PeriodIndex.') - - # Make sure tags and tag columns are correctly typed - tag_columns = tag_columns if tag_columns is not None else [] - field_columns = field_columns if field_columns is not None else [] - tags = tags if tags is not None else {} - # Assume field columns are all columns not included in tag columns - if not field_columns: - field_columns = list( - set(dataframe.columns).difference(set(tag_columns))) - - dataframe.index = pd.to_datetime(dataframe.index) - if dataframe.index.tzinfo is None: - dataframe.index = dataframe.index.tz_localize('UTC') - - # Convert column to strings - dataframe.columns = dataframe.columns.astype('str') - - # Convert dtype for json serialization - dataframe = dataframe.astype('object') - - precision_factor = { - "n": 1, - "u": 1e3, - "ms": 1e6, - "s": 1e9, - "m": 1e9 * 60, - "h": 1e9 * 3600, - }.get(time_precision, 1) - - points = [ - {'measurement': measurement, - 'tags': dict(list(tag.items()) + list(tags.items())), - 'fields': rec, - 'time': np.int64(ts.value / precision_factor)} - for ts, tag, rec in zip(dataframe.index, - dataframe[tag_columns].to_dict('record'), - dataframe[field_columns].to_dict('record')) - ] - - return points - - def _convert_dataframe_to_lines(self, - dataframe, - measurement, - field_columns=None, - tag_columns=None, - global_tags=None, - time_precision=None, - numeric_precision=None): - - dataframe = dataframe.dropna(how='all').copy() - if len(dataframe) == 0: - return [] - - if not isinstance(dataframe, pd.DataFrame): - raise TypeError('Must be DataFrame, but type was: {0}.' - .format(type(dataframe))) - if not (isinstance(dataframe.index, pd.PeriodIndex) or - isinstance(dataframe.index, pd.DatetimeIndex)): - raise TypeError('Must be DataFrame with DatetimeIndex or ' - 'PeriodIndex.') - - dataframe = dataframe.rename( - columns={item: _escape_tag(item) for item in dataframe.columns}) - # Create a Series of columns for easier indexing - column_series = pd.Series(dataframe.columns) - - if field_columns is None: - field_columns = [] - - if tag_columns is None: - tag_columns = [] - - if global_tags is None: - global_tags = {} - - # Make sure field_columns and tag_columns are lists - field_columns = list(field_columns) if list(field_columns) else [] - tag_columns = list(tag_columns) if list(tag_columns) else [] - - # If field columns but no tag columns, assume rest of columns are tags - if field_columns and (not tag_columns): - tag_columns = list(column_series[~column_series.isin( - field_columns)]) - - # If no field columns, assume non-tag columns are fields - if not field_columns: - field_columns = list(column_series[~column_series.isin( - tag_columns)]) - - precision_factor = { - "n": 1, - "u": 1e3, - "ms": 1e6, - "s": 1e9, - "m": 1e9 * 60, - "h": 1e9 * 3600, - }.get(time_precision, 1) - - # Make array of timestamp ints - if isinstance(dataframe.index, pd.PeriodIndex): - time = ((dataframe.index.to_timestamp().values.astype(np.int64) / - precision_factor).astype(np.int64).astype(str)) - else: - time = ((pd.to_datetime(dataframe.index).values.astype(np.int64) / - precision_factor).astype(np.int64).astype(str)) - - # If tag columns exist, make an array of formatted tag keys and values - if tag_columns: - - # Make global_tags as tag_columns - if global_tags: - for tag in global_tags: - dataframe[tag] = global_tags[tag] - tag_columns.append(tag) - - tag_df = dataframe[tag_columns] - tag_df = tag_df.fillna('') # replace NA with empty string - tag_df = tag_df.sort_index(axis=1) - tag_df = self._stringify_dataframe( - tag_df, numeric_precision, datatype='tag') - - # join preprendded tags, leaving None values out - tags = tag_df.apply( - lambda s: [',' + s.name + '=' + v if v else '' for v in s]) - tags = tags.sum(axis=1) - - del tag_df - elif global_tags: - tag_string = ''.join( - [",{}={}".format(k, _escape_tag(v)) if v else '' - for k, v in sorted(global_tags.items())] - ) - tags = pd.Series(tag_string, index=dataframe.index) - else: - tags = '' - - # Make an array of formatted field keys and values - field_df = dataframe[field_columns] - # Keep the positions where Null values are found - mask_null = field_df.isnull().values - - field_df = self._stringify_dataframe(field_df, - numeric_precision, - datatype='field') - - field_df = (field_df.columns.values + '=').tolist() + field_df - field_df[field_df.columns[1:]] = ',' + field_df[ - field_df.columns[1:]] - field_df = field_df.where(~mask_null, '') # drop Null entries - fields = field_df.sum(axis=1) - del field_df - - # Generate line protocol string - measurement = _escape_tag(measurement) - points = (measurement + tags + ' ' + fields + ' ' + time).tolist() - return points - - @staticmethod - def _stringify_dataframe(dframe, numeric_precision, datatype='field'): - - # Prevent modification of input dataframe - dframe = dframe.copy() - - # Find int and string columns for field-type data - int_columns = dframe.select_dtypes(include=['integer']).columns - string_columns = dframe.select_dtypes(include=['object']).columns - - # Convert dframe to string - if numeric_precision is None: - # If no precision specified, convert directly to string (fast) - dframe = dframe.astype(str) - elif numeric_precision == 'full': - # If full precision, use repr to get full float precision - float_columns = (dframe.select_dtypes( - include=['floating']).columns) - nonfloat_columns = dframe.columns[~dframe.columns.isin( - float_columns)] - dframe[float_columns] = dframe[float_columns].applymap(repr) - dframe[nonfloat_columns] = (dframe[nonfloat_columns].astype(str)) - elif isinstance(numeric_precision, int): - # If precision is specified, round to appropriate precision - float_columns = (dframe.select_dtypes( - include=['floating']).columns) - nonfloat_columns = dframe.columns[~dframe.columns.isin( - float_columns)] - dframe[float_columns] = (dframe[float_columns].round( - numeric_precision)) - - # If desired precision is > 10 decimal places, need to use repr - if numeric_precision > 10: - dframe[float_columns] = (dframe[float_columns].applymap(repr)) - dframe[nonfloat_columns] = (dframe[nonfloat_columns] - .astype(str)) - else: - dframe = dframe.astype(str) - else: - raise ValueError('Invalid numeric precision.') - - if datatype == 'field': - # If dealing with fields, format ints and strings correctly - dframe[int_columns] += 'i' - dframe[string_columns] = '"' + dframe[string_columns] + '"' - elif datatype == 'tag': - dframe = dframe.apply(_escape_pandas_series) - - dframe.columns = dframe.columns.astype(str) - - return dframe - - def _datetime_to_epoch(self, datetime, time_precision='s'): - seconds = (datetime - self.EPOCH).total_seconds() - if time_precision == 'h': - return seconds / 3600 - elif time_precision == 'm': - return seconds / 60 - elif time_precision == 's': - return seconds - elif time_precision == 'ms': - return seconds * 1e3 - elif time_precision == 'u': - return seconds * 1e6 - elif time_precision == 'n': - return seconds * 1e9 diff --git a/lib/influxdb/chunked_json.py b/lib/influxdb/chunked_json.py deleted file mode 100644 index 4e40f01..0000000 --- a/lib/influxdb/chunked_json.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -"""Module to generate chunked JSON replies.""" - -# -# Author: Adrian Sampson <adrian@radbox.org> -# Source: https://gist.github.com/sampsyo/920215 -# - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import json - - -def loads(s): - """Generate a sequence of JSON values from a string.""" - _decoder = json.JSONDecoder() - - while s: - s = s.strip() - obj, pos = _decoder.raw_decode(s) - if not pos: - raise ValueError('no JSON object found at %i' % pos) - yield obj - s = s[pos:] diff --git a/lib/influxdb/client.py b/lib/influxdb/client.py deleted file mode 100644 index 8f8b14a..0000000 --- a/lib/influxdb/client.py +++ /dev/null @@ -1,980 +0,0 @@ -# -*- coding: utf-8 -*- -"""Python client for InfluxDB.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import time -import random - -import json -import socket -import requests -import requests.exceptions -from six.moves import xrange -from six.moves.urllib.parse import urlparse - -from influxdb.line_protocol import make_lines, quote_ident, quote_literal -from influxdb.resultset import ResultSet -from .exceptions import InfluxDBClientError -from .exceptions import InfluxDBServerError - - -class InfluxDBClient(object): - """InfluxDBClient primary client object to connect InfluxDB. - - The :class:`~.InfluxDBClient` object holds information necessary to - connect to InfluxDB. Requests can be made to InfluxDB directly through - the client. - - :param host: hostname to connect to InfluxDB, defaults to 'localhost' - :type host: str - :param port: port to connect to InfluxDB, defaults to 8086 - :type port: int - :param username: user to connect, defaults to 'root' - :type username: str - :param password: password of the user, defaults to 'root' - :type password: str - :param pool_size: urllib3 connection pool size, defaults to 10. - :type pool_size: int - :param database: database name to connect to, defaults to None - :type database: str - :param ssl: use https instead of http to connect to InfluxDB, defaults to - False - :type ssl: bool - :param verify_ssl: verify SSL certificates for HTTPS requests, defaults to - False - :type verify_ssl: bool - :param timeout: number of seconds Requests will wait for your client to - establish a connection, defaults to None - :type timeout: int - :param retries: number of retries your client will try before aborting, - defaults to 3. 0 indicates try until success - :type retries: int - :param use_udp: use UDP to connect to InfluxDB, defaults to False - :type use_udp: bool - :param udp_port: UDP port to connect to InfluxDB, defaults to 4444 - :type udp_port: int - :param proxies: HTTP(S) proxy to use for Requests, defaults to {} - :type proxies: dict - :param path: path of InfluxDB on the server to connect, defaults to '' - :type path: str - """ - - def __init__(self, - host='localhost', - port=8086, - username='root', - password='root', - database=None, - ssl=False, - verify_ssl=False, - timeout=None, - retries=3, - use_udp=False, - udp_port=4444, - proxies=None, - pool_size=10, - path='', - ): - """Construct a new InfluxDBClient object.""" - self.__host = host - self.__port = int(port) - self._username = username - self._password = password - self._database = database - self._timeout = timeout - self._retries = retries - - self._verify_ssl = verify_ssl - - self.__use_udp = use_udp - self.__udp_port = udp_port - self._session = requests.Session() - adapter = requests.adapters.HTTPAdapter( - pool_connections=int(pool_size), - pool_maxsize=int(pool_size) - ) - - if use_udp: - self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - if not path: - self.__path = '' - elif path[0] == '/': - self.__path = path - else: - self.__path = '/' + path - - self._scheme = "http" - - if ssl is True: - self._scheme = "https" - - self._session.mount(self._scheme + '://', adapter) - - if proxies is None: - self._proxies = {} - else: - self._proxies = proxies - - self.__baseurl = "{0}://{1}:{2}{3}".format( - self._scheme, - self._host, - self._port, - self._path) - - self._headers = { - 'Content-Type': 'application/json', - 'Accept': 'text/plain' - } - - @property - def _baseurl(self): - return self.__baseurl - - @property - def _host(self): - return self.__host - - @property - def _port(self): - return self.__port - - @property - def _path(self): - return self.__path - - @property - def _udp_port(self): - return self.__udp_port - - @property - def _use_udp(self): - return self.__use_udp - - @classmethod - def from_dsn(cls, dsn, **kwargs): - r"""Generate an instance of InfluxDBClient from given data source name. - - Return an instance of :class:`~.InfluxDBClient` from the provided - data source name. Supported schemes are "influxdb", "https+influxdb" - and "udp+influxdb". Parameters for the :class:`~.InfluxDBClient` - constructor may also be passed to this method. - - :param dsn: data source name - :type dsn: string - :param kwargs: additional parameters for `InfluxDBClient` - :type kwargs: dict - :raises ValueError: if the provided DSN has any unexpected values - - :Example: - - :: - - >> cli = InfluxDBClient.from_dsn('influxdb://username:password@\ - localhost:8086/databasename', timeout=5) - >> type(cli) - <class 'influxdb.client.InfluxDBClient'> - >> cli = InfluxDBClient.from_dsn('udp+influxdb://username:pass@\ - localhost:8086/databasename', timeout=5, udp_port=159) - >> print('{0._baseurl} - {0.use_udp} {0.udp_port}'.format(cli)) - http://localhost:8086 - True 159 - - .. note:: parameters provided in `**kwargs` may override dsn parameters - .. note:: when using "udp+influxdb" the specified port (if any) will - be used for the TCP connection; specify the UDP port with the - additional `udp_port` parameter (cf. examples). - """ - init_args = _parse_dsn(dsn) - host, port = init_args.pop('hosts')[0] - init_args['host'] = host - init_args['port'] = port - init_args.update(kwargs) - - return cls(**init_args) - - def switch_database(self, database): - """Change the client's database. - - :param database: the name of the database to switch to - :type database: str - """ - self._database = database - - def switch_user(self, username, password): - """Change the client's username. - - :param username: the username to switch to - :type username: str - :param password: the password for the username - :type password: str - """ - self._username = username - self._password = password - - def request(self, url, method='GET', params=None, data=None, - expected_response_code=200, headers=None): - """Make a HTTP request to the InfluxDB API. - - :param url: the path of the HTTP request, e.g. write, query, etc. - :type url: str - :param method: the HTTP method for the request, defaults to GET - :type method: str - :param params: additional parameters for the request, defaults to None - :type params: dict - :param data: the data of the request, defaults to None - :type data: str - :param expected_response_code: the expected response code of - the request, defaults to 200 - :type expected_response_code: int - :param headers: headers to add to the request - :type headers: dict - :returns: the response from the request - :rtype: :class:`requests.Response` - :raises InfluxDBServerError: if the response code is any server error - code (5xx) - :raises InfluxDBClientError: if the response code is not the - same as `expected_response_code` and is not a server error code - """ - url = "{0}/{1}".format(self._baseurl, url) - - if headers is None: - headers = self._headers - - if params is None: - params = {} - - if isinstance(data, (dict, list)): - data = json.dumps(data) - - # Try to send the request more than once by default (see #103) - retry = True - _try = 0 - while retry: - try: - response = self._session.request( - method=method, - url=url, - auth=(self._username, self._password), - params=params, - data=data, - headers=headers, - proxies=self._proxies, - verify=self._verify_ssl, - timeout=self._timeout - ) - break - except (requests.exceptions.ConnectionError, - requests.exceptions.HTTPError, - requests.exceptions.Timeout): - _try += 1 - if self._retries != 0: - retry = _try < self._retries - if method == "POST": - time.sleep((2 ** _try) * random.random() / 100.0) - if not retry: - raise - # if there's not an error, there must have been a successful response - if 500 <= response.status_code < 600: - raise InfluxDBServerError(response.content) - elif response.status_code == expected_response_code: - return response - else: - raise InfluxDBClientError(response.content, response.status_code) - - def write(self, data, params=None, expected_response_code=204, - protocol='json'): - """Write data to InfluxDB. - - :param data: the data to be written - :type data: (if protocol is 'json') dict - (if protocol is 'line') sequence of line protocol strings - or single string - :param params: additional parameters for the request, defaults to None - :type params: dict - :param expected_response_code: the expected response code of the write - operation, defaults to 204 - :type expected_response_code: int - :param protocol: protocol of input data, either 'json' or 'line' - :type protocol: str - :returns: True, if the write operation is successful - :rtype: bool - """ - headers = self._headers - headers['Content-Type'] = 'application/octet-stream' - - if params: - precision = params.get('precision') - else: - precision = None - - if protocol == 'json': - data = make_lines(data, precision).encode('utf-8') - elif protocol == 'line': - if isinstance(data, str): - data = [data] - data = ('\n'.join(data) + '\n').encode('utf-8') - - self.request( - url="write", - method='POST', - params=params, - data=data, - expected_response_code=expected_response_code, - headers=headers - ) - return True - - @staticmethod - def _read_chunked_response(response, raise_errors=True): - result_set = {} - for line in response.iter_lines(): - if isinstance(line, bytes): - line = line.decode('utf-8') - data = json.loads(line) - for result in data.get('results', []): - for _key in result: - if isinstance(result[_key], list): - result_set.setdefault( - _key, []).extend(result[_key]) - return ResultSet(result_set, raise_errors=raise_errors) - - def query(self, - query, - params=None, - epoch=None, - expected_response_code=200, - database=None, - raise_errors=True, - chunked=False, - chunk_size=0, - method="GET"): - """Send a query to InfluxDB. - - :param query: the actual query string - :type query: str - - :param params: additional parameters for the request, - defaults to {} - :type params: dict - - :param epoch: response timestamps to be in epoch format either 'h', - 'm', 's', 'ms', 'u', or 'ns',defaults to `None` which is - RFC3339 UTC format with nanosecond precision - :type epoch: str - - :param expected_response_code: the expected status code of response, - defaults to 200 - :type expected_response_code: int - - :param database: database to query, defaults to None - :type database: str - - :param raise_errors: Whether or not to raise exceptions when InfluxDB - returns errors, defaults to True - :type raise_errors: bool - - :param chunked: Enable to use chunked responses from InfluxDB. - With ``chunked`` enabled, one ResultSet is returned per chunk - containing all results within that chunk - :type chunked: bool - - :param chunk_size: Size of each chunk to tell InfluxDB to use. - :type chunk_size: int - - :param method: the HTTP method for the request, defaults to GET - :type method: str - - :returns: the queried data - :rtype: :class:`~.ResultSet` - """ - if params is None: - params = {} - - params['q'] = query - params['db'] = database or self._database - - if epoch is not None: - params['epoch'] = epoch - - if chunked: - params['chunked'] = 'true' - if chunk_size > 0: - params['chunk_size'] = chunk_size - - if query.lower().startswith("select ") and " into " in query.lower(): - method = "POST" - - response = self.request( - url="query", - method=method, - params=params, - data=None, - expected_response_code=expected_response_code - ) - - if chunked: - return self._read_chunked_response(response) - - data = response.json() - - results = [ - ResultSet(result, raise_errors=raise_errors) - for result - in data.get('results', []) - ] - - # TODO(aviau): Always return a list. (This would be a breaking change) - if len(results) == 1: - return results[0] - - return results - - def write_points(self, - points, - time_precision=None, - database=None, - retention_policy=None, - tags=None, - batch_size=None, - protocol='json' - ): - """Write to multiple time series names. - - :param points: the list of points to be written in the database - :type points: list of dictionaries, each dictionary represents a point - :type points: (if protocol is 'json') list of dicts, where each dict - represents a point. - (if protocol is 'line') sequence of line protocol strings. - :param time_precision: Either 's', 'm', 'ms' or 'u', defaults to None - :type time_precision: str - :param database: the database to write the points to. Defaults to - the client's current database - :type database: str - :param tags: a set of key-value pairs associated with each point. Both - keys and values must be strings. These are shared tags and will be - merged with point-specific tags, defaults to None - :type tags: dict - :param retention_policy: the retention policy for the points. Defaults - to None - :type retention_policy: str - :param batch_size: value to write the points in batches - instead of all at one time. Useful for when doing data dumps from - one database to another or when doing a massive write operation, - defaults to None - :type batch_size: int - :param protocol: Protocol for writing data. Either 'line' or 'json'. - :type protocol: str - :returns: True, if the operation is successful - :rtype: bool - - .. note:: if no retention policy is specified, the default retention - policy for the database is used - """ - if batch_size and batch_size > 0: - for batch in self._batches(points, batch_size): - self._write_points(points=batch, - time_precision=time_precision, - database=database, - retention_policy=retention_policy, - tags=tags, protocol=protocol) - return True - - return self._write_points(points=points, - time_precision=time_precision, - database=database, - retention_policy=retention_policy, - tags=tags, protocol=protocol) - - def ping(self): - """Check connectivity to InfluxDB. - - :returns: The version of the InfluxDB the client is connected to - """ - response = self.request( - url="ping", - method='GET', - expected_response_code=204 - ) - - return response.headers['X-Influxdb-Version'] - - @staticmethod - def _batches(iterable, size): - for i in xrange(0, len(iterable), size): - yield iterable[i:i + size] - - def _write_points(self, - points, - time_precision, - database, - retention_policy, - tags, - protocol='json'): - if time_precision not in ['n', 'u', 'ms', 's', 'm', 'h', None]: - raise ValueError( - "Invalid time precision is given. " - "(use 'n', 'u', 'ms', 's', 'm' or 'h')") - - if protocol == 'json': - data = { - 'points': points - } - - if tags is not None: - data['tags'] = tags - else: - data = points - - params = { - 'db': database or self._database - } - - if time_precision is not None: - params['precision'] = time_precision - - if retention_policy is not None: - params['rp'] = retention_policy - - if self._use_udp: - self.send_packet( - data, protocol=protocol, time_precision=time_precision - ) - else: - self.write( - data=data, - params=params, - expected_response_code=204, - protocol=protocol - ) - - return True - - def get_list_database(self): - """Get the list of databases in InfluxDB. - - :returns: all databases in InfluxDB - :rtype: list of dictionaries - - :Example: - - :: - - >> dbs = client.get_list_database() - >> dbs - [{u'name': u'db1'}, {u'name': u'db2'}, {u'name': u'db3'}] - """ - return list(self.query("SHOW DATABASES").get_points()) - - def create_database(self, dbname): - """Create a new database in InfluxDB. - - :param dbname: the name of the database to create - :type dbname: str - """ - self.query("CREATE DATABASE {0}".format(quote_ident(dbname)), - method="POST") - - def drop_database(self, dbname): - """Drop a database from InfluxDB. - - :param dbname: the name of the database to drop - :type dbname: str - """ - self.query("DROP DATABASE {0}".format(quote_ident(dbname)), - method="POST") - - def get_list_measurements(self): - """Get the list of measurements in InfluxDB. - - :returns: all measurements in InfluxDB - :rtype: list of dictionaries - - :Example: - - :: - - >> dbs = client.get_list_measurements() - >> dbs - [{u'name': u'measurements1'}, - {u'name': u'measurements2'}, - {u'name': u'measurements3'}] - """ - return list(self.query("SHOW MEASUREMENTS").get_points()) - - def drop_measurement(self, measurement): - """Drop a measurement from InfluxDB. - - :param measurement: the name of the measurement to drop - :type measurement: str - """ - self.query("DROP MEASUREMENT {0}".format(quote_ident(measurement)), - method="POST") - - def create_retention_policy(self, name, duration, replication, - database=None, - default=False, shard_duration="0s"): - """Create a retention policy for a database. - - :param name: the name of the new retention policy - :type name: str - :param duration: the duration of the new retention policy. - Durations such as 1h, 90m, 12h, 7d, and 4w, are all supported - and mean 1 hour, 90 minutes, 12 hours, 7 day, and 4 weeks, - respectively. For infinite retention - meaning the data will - never be deleted - use 'INF' for duration. - The minimum retention period is 1 hour. - :type duration: str - :param replication: the replication of the retention policy - :type replication: str - :param database: the database for which the retention policy is - created. Defaults to current client's database - :type database: str - :param default: whether or not to set the policy as default - :type default: bool - :param shard_duration: the shard duration of the retention policy. - Durations such as 1h, 90m, 12h, 7d, and 4w, are all supported and - mean 1 hour, 90 minutes, 12 hours, 7 day, and 4 weeks, - respectively. Infinite retention is not supported. As a workaround, - specify a "1000w" duration to achieve an extremely long shard group - duration. Defaults to "0s", which is interpreted by the database - to mean the default value given the duration. - The minimum shard group duration is 1 hour. - :type shard_duration: str - """ - query_string = \ - "CREATE RETENTION POLICY {0} ON {1} " \ - "DURATION {2} REPLICATION {3} SHARD DURATION {4}".format( - quote_ident(name), quote_ident(database or self._database), - duration, replication, shard_duration) - - if default is True: - query_string += " DEFAULT" - - self.query(query_string, method="POST") - - def alter_retention_policy(self, name, database=None, - duration=None, replication=None, - default=None, shard_duration=None): - """Modify an existing retention policy for a database. - - :param name: the name of the retention policy to modify - :type name: str - :param database: the database for which the retention policy is - modified. Defaults to current client's database - :type database: str - :param duration: the new duration of the existing retention policy. - Durations such as 1h, 90m, 12h, 7d, and 4w, are all supported - and mean 1 hour, 90 minutes, 12 hours, 7 day, and 4 weeks, - respectively. For infinite retention, meaning the data will - never be deleted, use 'INF' for duration. - The minimum retention period is 1 hour. - :type duration: str - :param replication: the new replication of the existing - retention policy - :type replication: int - :param default: whether or not to set the modified policy as default - :type default: bool - :param shard_duration: the shard duration of the retention policy. - Durations such as 1h, 90m, 12h, 7d, and 4w, are all supported and - mean 1 hour, 90 minutes, 12 hours, 7 day, and 4 weeks, - respectively. Infinite retention is not supported. As a workaround, - specify a "1000w" duration to achieve an extremely long shard group - duration. - The minimum shard group duration is 1 hour. - :type shard_duration: str - - .. note:: at least one of duration, replication, or default flag - should be set. Otherwise the operation will fail. - """ - query_string = ( - "ALTER RETENTION POLICY {0} ON {1}" - ).format(quote_ident(name), - quote_ident(database or self._database), shard_duration) - if duration: - query_string += " DURATION {0}".format(duration) - if shard_duration: - query_string += " SHARD DURATION {0}".format(shard_duration) - if replication: - query_string += " REPLICATION {0}".format(replication) - if default is True: - query_string += " DEFAULT" - - self.query(query_string, method="POST") - - def drop_retention_policy(self, name, database=None): - """Drop an existing retention policy for a database. - - :param name: the name of the retention policy to drop - :type name: str - :param database: the database for which the retention policy is - dropped. Defaults to current client's database - :type database: str - """ - query_string = ( - "DROP RETENTION POLICY {0} ON {1}" - ).format(quote_ident(name), quote_ident(database or self._database)) - self.query(query_string, method="POST") - - def get_list_retention_policies(self, database=None): - """Get the list of retention policies for a database. - - :param database: the name of the database, defaults to the client's - current database - :type database: str - :returns: all retention policies for the database - :rtype: list of dictionaries - - :Example: - - :: - - >> ret_policies = client.get_list_retention_policies('my_db') - >> ret_policies - [{u'default': True, - u'duration': u'0', - u'name': u'default', - u'replicaN': 1}] - """ - if not (database or self._database): - raise InfluxDBClientError( - "get_list_retention_policies() requires a database as a " - "parameter or the client to be using a database") - - rsp = self.query( - "SHOW RETENTION POLICIES ON {0}".format( - quote_ident(database or self._database)) - ) - return list(rsp.get_points()) - - def get_list_users(self): - """Get the list of all users in InfluxDB. - - :returns: all users in InfluxDB - :rtype: list of dictionaries - - :Example: - - :: - - >> users = client.get_list_users() - >> users - [{u'admin': True, u'user': u'user1'}, - {u'admin': False, u'user': u'user2'}, - {u'admin': False, u'user': u'user3'}] - """ - return list(self.query("SHOW USERS").get_points()) - - def create_user(self, username, password, admin=False): - """Create a new user in InfluxDB. - - :param username: the new username to create - :type username: str - :param password: the password for the new user - :type password: str - :param admin: whether the user should have cluster administration - privileges or not - :type admin: boolean - """ - text = "CREATE USER {0} WITH PASSWORD {1}".format( - quote_ident(username), quote_literal(password)) - if admin: - text += ' WITH ALL PRIVILEGES' - self.query(text, method="POST") - - def drop_user(self, username): - """Drop a user from InfluxDB. - - :param username: the username to drop - :type username: str - """ - text = "DROP USER {0}".format(quote_ident(username), method="POST") - self.query(text, method="POST") - - def set_user_password(self, username, password): - """Change the password of an existing user. - - :param username: the username who's password is being changed - :type username: str - :param password: the new password for the user - :type password: str - """ - text = "SET PASSWORD FOR {0} = {1}".format( - quote_ident(username), quote_literal(password)) - self.query(text) - - def delete_series(self, database=None, measurement=None, tags=None): - """Delete series from a database. - - Series can be filtered by measurement and tags. - - :param database: the database from which the series should be - deleted, defaults to client's current database - :type database: str - :param measurement: Delete all series from a measurement - :type measurement: str - :param tags: Delete all series that match given tags - :type tags: dict - """ - database = database or self._database - query_str = 'DROP SERIES' - if measurement: - query_str += ' FROM {0}'.format(quote_ident(measurement)) - - if tags: - tag_eq_list = ["{0}={1}".format(quote_ident(k), quote_literal(v)) - for k, v in tags.items()] - query_str += ' WHERE ' + ' AND '.join(tag_eq_list) - self.query(query_str, database=database, method="POST") - - def grant_admin_privileges(self, username): - """Grant cluster administration privileges to a user. - - :param username: the username to grant privileges to - :type username: str - - .. note:: Only a cluster administrator can create/drop databases - and manage users. - """ - text = "GRANT ALL PRIVILEGES TO {0}".format(quote_ident(username)) - self.query(text, method="POST") - - def revoke_admin_privileges(self, username): - """Revoke cluster administration privileges from a user. - - :param username: the username to revoke privileges from - :type username: str - - .. note:: Only a cluster administrator can create/ drop databases - and manage users. - """ - text = "REVOKE ALL PRIVILEGES FROM {0}".format(quote_ident(username)) - self.query(text, method="POST") - - def grant_privilege(self, privilege, database, username): - """Grant a privilege on a database to a user. - - :param privilege: the privilege to grant, one of 'read', 'write' - or 'all'. The string is case-insensitive - :type privilege: str - :param database: the database to grant the privilege on - :type database: str - :param username: the username to grant the privilege to - :type username: str - """ - text = "GRANT {0} ON {1} TO {2}".format(privilege, - quote_ident(database), - quote_ident(username)) - self.query(text, method="POST") - - def revoke_privilege(self, privilege, database, username): - """Revoke a privilege on a database from a user. - - :param privilege: the privilege to revoke, one of 'read', 'write' - or 'all'. The string is case-insensitive - :type privilege: str - :param database: the database to revoke the privilege on - :type database: str - :param username: the username to revoke the privilege from - :type username: str - """ - text = "REVOKE {0} ON {1} FROM {2}".format(privilege, - quote_ident(database), - quote_ident(username)) - self.query(text, method="POST") - - def get_list_privileges(self, username): - """Get the list of all privileges granted to given user. - - :param username: the username to get privileges of - :type username: str - - :returns: all privileges granted to given user - :rtype: list of dictionaries - - :Example: - - :: - - >> privileges = client.get_list_privileges('user1') - >> privileges - [{u'privilege': u'WRITE', u'database': u'db1'}, - {u'privilege': u'ALL PRIVILEGES', u'database': u'db2'}, - {u'privilege': u'NO PRIVILEGES', u'database': u'db3'}] - """ - text = "SHOW GRANTS FOR {0}".format(quote_ident(username)) - return list(self.query(text).get_points()) - - def send_packet(self, packet, protocol='json', time_precision=None): - """Send an UDP packet. - - :param packet: the packet to be sent - :type packet: (if protocol is 'json') dict - (if protocol is 'line') list of line protocol strings - :param protocol: protocol of input data, either 'json' or 'line' - :type protocol: str - :param time_precision: Either 's', 'm', 'ms' or 'u', defaults to None - :type time_precision: str - """ - if protocol == 'json': - data = make_lines(packet, time_precision).encode('utf-8') - elif protocol == 'line': - data = ('\n'.join(packet) + '\n').encode('utf-8') - self.udp_socket.sendto(data, (self._host, self._udp_port)) - - def close(self): - """Close http session.""" - if isinstance(self._session, requests.Session): - self._session.close() - - -def _parse_dsn(dsn): - """Parse data source name. - - This is a helper function to split the data source name provided in - the from_dsn classmethod - """ - conn_params = urlparse(dsn) - init_args = {} - scheme_info = conn_params.scheme.split('+') - if len(scheme_info) == 1: - scheme = scheme_info[0] - modifier = None - else: - modifier, scheme = scheme_info - - if scheme != 'influxdb': - raise ValueError('Unknown scheme "{0}".'.format(scheme)) - - if modifier: - if modifier == 'udp': - init_args['use_udp'] = True - elif modifier == 'https': - init_args['ssl'] = True - else: - raise ValueError('Unknown modifier "{0}".'.format(modifier)) - - netlocs = conn_params.netloc.split(',') - - init_args['hosts'] = [] - for netloc in netlocs: - parsed = _parse_netloc(netloc) - init_args['hosts'].append((parsed['host'], int(parsed['port']))) - init_args['username'] = parsed['username'] - init_args['password'] = parsed['password'] - - if conn_params.path and len(conn_params.path) > 1: - init_args['database'] = conn_params.path[1:] - - return init_args - - -def _parse_netloc(netloc): - info = urlparse("http://{0}".format(netloc)) - return {'username': info.username or None, - 'password': info.password or None, - 'host': info.hostname or 'localhost', - 'port': info.port or 8086} diff --git a/lib/influxdb/dataframe_client.py b/lib/influxdb/dataframe_client.py deleted file mode 100644 index 9725864..0000000 --- a/lib/influxdb/dataframe_client.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -"""DataFrame client for InfluxDB.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -__all__ = ['DataFrameClient'] - -try: - import pandas - del pandas -except ImportError as err: - from .client import InfluxDBClient - - class DataFrameClient(InfluxDBClient): - """DataFrameClient default class instantiation.""" - - err = err - - def __init__(self, *a, **kw): - """Initialize the default DataFrameClient.""" - super(DataFrameClient, self).__init__() - raise ImportError("DataFrameClient requires Pandas " - "which couldn't be imported: %s" % self.err) -else: - from ._dataframe_client import DataFrameClient diff --git a/lib/influxdb/exceptions.py b/lib/influxdb/exceptions.py deleted file mode 100644 index bd71d30..0000000 --- a/lib/influxdb/exceptions.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -"""Exception handler for InfluxDBClient.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - - -class InfluxDBClientError(Exception): - """Raised when an error occurs in the request.""" - - def __init__(self, content, code=None): - """Initialize the InfluxDBClientError handler.""" - if isinstance(content, type(b'')): - content = content.decode('UTF-8', 'replace') - - if code is not None: - message = "%s: %s" % (code, content) - else: - message = content - - super(InfluxDBClientError, self).__init__( - message - ) - self.content = content - self.code = code - - -class InfluxDBServerError(Exception): - """Raised when a server error occurs.""" - - def __init__(self, content): - """Initialize the InfluxDBServerError handler.""" - super(InfluxDBServerError, self).__init__(content) diff --git a/lib/influxdb/helper.py b/lib/influxdb/helper.py deleted file mode 100644 index e622526..0000000 --- a/lib/influxdb/helper.py +++ /dev/null @@ -1,184 +0,0 @@ -# -*- coding: utf-8 -*- -"""Helper class for InfluxDB.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from collections import namedtuple, defaultdict -from datetime import datetime -from warnings import warn - -import six - - -class SeriesHelper(object): - """Subclass this helper eases writing data points in bulk. - - All data points are immutable, ensuring they do not get overwritten. - Each subclass can write to its own database. - The time series names can also be based on one or more defined fields. - The field "time" can be specified when creating a point, and may be any of - the time types supported by the client (i.e. str, datetime, int). - If the time is not specified, the current system time (utc) will be used. - - Annotated example:: - - class MySeriesHelper(SeriesHelper): - class Meta: - # Meta class stores time series helper configuration. - series_name = 'events.stats.{server_name}' - # Series name must be a string, curly brackets for dynamic use. - fields = ['time', 'server_name'] - # Defines all the fields in this time series. - ### Following attributes are optional. ### - client = TestSeriesHelper.client - # Client should be an instance of InfluxDBClient. - :warning: Only used if autocommit is True. - bulk_size = 5 - # Defines the number of data points to write simultaneously. - # Only applicable if autocommit is True. - autocommit = True - # If True and no bulk_size, then will set bulk_size to 1. - - """ - - __initialized__ = False - - def __new__(cls, *args, **kwargs): - """Initialize class attributes for subsequent constructor calls. - - :note: *args and **kwargs are not explicitly used in this function, - but needed for Python 2 compatibility. - """ - if not cls.__initialized__: - cls.__initialized__ = True - try: - _meta = getattr(cls, 'Meta') - except AttributeError: - raise AttributeError( - 'Missing Meta class in {0}.'.format( - cls.__name__)) - - for attr in ['series_name', 'fields', 'tags']: - try: - setattr(cls, '_' + attr, getattr(_meta, attr)) - except AttributeError: - raise AttributeError( - 'Missing {0} in {1} Meta class.'.format( - attr, - cls.__name__)) - - cls._autocommit = getattr(_meta, 'autocommit', False) - - cls._client = getattr(_meta, 'client', None) - if cls._autocommit and not cls._client: - raise AttributeError( - 'In {0}, autocommit is set to True, but no client is set.' - .format(cls.__name__)) - - try: - cls._bulk_size = getattr(_meta, 'bulk_size') - if cls._bulk_size < 1 and cls._autocommit: - warn( - 'Definition of bulk_size in {0} forced to 1, ' - 'was less than 1.'.format(cls.__name__)) - cls._bulk_size = 1 - except AttributeError: - cls._bulk_size = -1 - else: - if not cls._autocommit: - warn( - 'Definition of bulk_size in {0} has no affect because' - ' autocommit is false.'.format(cls.__name__)) - - cls._datapoints = defaultdict(list) - - if 'time' in cls._fields: - cls._fields.remove('time') - cls._type = namedtuple(cls.__name__, - ['time'] + cls._tags + cls._fields) - cls._type.__new__.__defaults__ = (None,) * len(cls._fields) - - return super(SeriesHelper, cls).__new__(cls) - - def __init__(self, **kw): - """Call to constructor creates a new data point. - - :note: Data points written when `bulk_size` is reached per Helper. - :warning: Data points are *immutable* (`namedtuples`). - """ - cls = self.__class__ - timestamp = kw.pop('time', self._current_timestamp()) - tags = set(cls._tags) - fields = set(cls._fields) - keys = set(kw.keys()) - - # all tags should be passed, and keys - tags should be a subset of keys - if not(tags <= keys): - raise NameError( - 'Expected arguments to contain all tags {0}, instead got {1}.' - .format(cls._tags, kw.keys())) - if not(keys - tags <= fields): - raise NameError('Got arguments not in tags or fields: {0}' - .format(keys - tags - fields)) - - cls._datapoints[cls._series_name.format(**kw)].append( - cls._type(time=timestamp, **kw) - ) - - if cls._autocommit and \ - sum(len(series) for series in cls._datapoints.values()) \ - >= cls._bulk_size: - cls.commit() - - @classmethod - def commit(cls, client=None): - """Commit everything from datapoints via the client. - - :param client: InfluxDBClient instance for writing points to InfluxDB. - :attention: any provided client will supersede the class client. - :return: result of client.write_points. - """ - if not client: - client = cls._client - rtn = client.write_points(cls._json_body_()) - cls._reset_() - return rtn - - @classmethod - def _json_body_(cls): - """Return the JSON body of given datapoints. - - :return: JSON body of these datapoints. - """ - json = [] - for series_name, data in six.iteritems(cls._datapoints): - for point in data: - json_point = { - "measurement": series_name, - "fields": {}, - "tags": {}, - "time": getattr(point, "time") - } - - for field in cls._fields: - value = getattr(point, field) - if value is not None: - json_point['fields'][field] = value - - for tag in cls._tags: - json_point['tags'][tag] = getattr(point, tag) - - json.append(json_point) - return json - - @classmethod - def _reset_(cls): - """Reset data storage.""" - cls._datapoints = defaultdict(list) - - @staticmethod - def _current_timestamp(): - return datetime.utcnow() diff --git a/lib/influxdb/influxdb08/__init__.py b/lib/influxdb/influxdb08/__init__.py deleted file mode 100644 index f4e6c08..0000000 --- a/lib/influxdb/influxdb08/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define the influxdb08 package.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from .client import InfluxDBClient -from .dataframe_client import DataFrameClient -from .helper import SeriesHelper - - -__all__ = [ - 'InfluxDBClient', - 'DataFrameClient', - 'SeriesHelper', -] diff --git a/lib/influxdb/influxdb08/chunked_json.py b/lib/influxdb/influxdb08/chunked_json.py deleted file mode 100644 index d6847de..0000000 --- a/lib/influxdb/influxdb08/chunked_json.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -"""Module to generate chunked JSON replies for influxdb08.""" - -# -# Author: Adrian Sampson <adrian@radbox.org> -# Source: https://gist.github.com/sampsyo/920215 -# - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import json - - -def loads(s): - """Generate a sequence of JSON values from a string.""" - _decoder = json.JSONDecoder() - - while s: - s = s.strip() - obj, pos = _decoder.raw_decode(s) - if not pos: - raise ValueError('no JSON object found at %i' % pos) - yield obj - s = s[pos:] diff --git a/lib/influxdb/influxdb08/client.py b/lib/influxdb/influxdb08/client.py deleted file mode 100644 index 965a91d..0000000 --- a/lib/influxdb/influxdb08/client.py +++ /dev/null @@ -1,843 +0,0 @@ -# -*- coding: utf-8 -*- -"""Python client for InfluxDB v0.8.""" - -import warnings - -import json -import socket -import requests -import requests.exceptions -from six.moves import xrange -from six.moves.urllib.parse import urlparse - -from influxdb import chunked_json - -session = requests.Session() - - -class InfluxDBClientError(Exception): - """Raised when an error occurs in the request.""" - - def __init__(self, content, code=-1): - """Initialize an InfluxDBClientError handler.""" - super(InfluxDBClientError, self).__init__( - "{0}: {1}".format(code, content)) - self.content = content - self.code = code - - -class InfluxDBClient(object): - """Define the standard InfluxDBClient for influxdb v0.8. - - The ``InfluxDBClient`` object holds information necessary to connect - to InfluxDB. Requests can be made to InfluxDB directly through the client. - - :param host: hostname to connect to InfluxDB, defaults to 'localhost' - :type host: string - :param port: port to connect to InfluxDB, defaults to 'localhost' - :type port: int - :param username: user to connect, defaults to 'root' - :type username: string - :param password: password of the user, defaults to 'root' - :type password: string - :param database: database name to connect to, defaults is None - :type database: string - :param ssl: use https instead of http to connect to InfluxDB, defaults is - False - :type ssl: boolean - :param verify_ssl: verify SSL certificates for HTTPS requests, defaults is - False - :type verify_ssl: boolean - :param retries: number of retries your client will try before aborting, - defaults to 3. 0 indicates try until success - :type retries: int - :param timeout: number of seconds Requests will wait for your client to - establish a connection, defaults to None - :type timeout: int - :param use_udp: use UDP to connect to InfluxDB, defaults is False - :type use_udp: int - :param udp_port: UDP port to connect to InfluxDB, defaults is 4444 - :type udp_port: int - """ - - def __init__(self, - host='localhost', - port=8086, - username='root', - password='root', - database=None, - ssl=False, - verify_ssl=False, - timeout=None, - retries=3, - use_udp=False, - udp_port=4444): - """Construct a new InfluxDBClient object.""" - self._host = host - self._port = port - self._username = username - self._password = password - self._database = database - self._timeout = timeout - self._retries = retries - - self._verify_ssl = verify_ssl - - self._use_udp = use_udp - self._udp_port = udp_port - if use_udp: - self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - self._scheme = "http" - - if ssl is True: - self._scheme = "https" - - self._baseurl = "{0}://{1}:{2}".format( - self._scheme, - self._host, - self._port) - - self._headers = { - 'Content-type': 'application/json', - 'Accept': 'text/plain'} - - @staticmethod - def from_dsn(dsn, **kwargs): - r"""Return an instaance of InfluxDBClient from given data source name. - - Returns an instance of InfluxDBClient from the provided data source - name. Supported schemes are "influxdb", "https+influxdb", - "udp+influxdb". Parameters for the InfluxDBClient constructor may be - also be passed to this function. - - Examples: - >> cli = InfluxDBClient.from_dsn('influxdb://username:password@\ - ... localhost:8086/databasename', timeout=5) - >> type(cli) - <class 'influxdb.client.InfluxDBClient'> - >> cli = InfluxDBClient.from_dsn('udp+influxdb://username:pass@\ - ... localhost:8086/databasename', timeout=5, udp_port=159) - >> print('{0._baseurl} - {0.use_udp} {0.udp_port}'.format(cli)) - http://localhost:8086 - True 159 - - :param dsn: data source name - :type dsn: string - :param **kwargs: additional parameters for InfluxDBClient. - :type **kwargs: dict - :note: parameters provided in **kwargs may override dsn parameters. - :note: when using "udp+influxdb" the specified port (if any) will be - used for the TCP connection; specify the udp port with the additional - udp_port parameter (cf. examples). - :raise ValueError: if the provided DSN has any unexpected value. - - """ - init_args = {} - conn_params = urlparse(dsn) - scheme_info = conn_params.scheme.split('+') - - if len(scheme_info) == 1: - scheme = scheme_info[0] - modifier = None - else: - modifier, scheme = scheme_info - - if scheme != 'influxdb': - raise ValueError('Unknown scheme "{0}".'.format(scheme)) - - if modifier: - if modifier == 'udp': - init_args['use_udp'] = True - elif modifier == 'https': - init_args['ssl'] = True - else: - raise ValueError('Unknown modifier "{0}".'.format(modifier)) - - if conn_params.hostname: - init_args['host'] = conn_params.hostname - if conn_params.port: - init_args['port'] = conn_params.port - if conn_params.username: - init_args['username'] = conn_params.username - if conn_params.password: - init_args['password'] = conn_params.password - if conn_params.path and len(conn_params.path) > 1: - init_args['database'] = conn_params.path[1:] - - init_args.update(kwargs) - - return InfluxDBClient(**init_args) - - # Change member variables - - def switch_database(self, database): - """Change client database. - - :param database: the new database name to switch to - :type database: string - """ - self._database = database - - def switch_db(self, database): - """Change client database. - - DEPRECATED. - """ - warnings.warn( - "switch_db is deprecated, and will be removed " - "in future versions. Please use " - "``InfluxDBClient.switch_database(database)`` instead.", - FutureWarning) - return self.switch_database(database) - - def switch_user(self, username, password): - """Change client username. - - :param username: the new username to switch to - :type username: string - :param password: the new password to switch to - :type password: string - """ - self._username = username - self._password = password - - def request(self, url, method='GET', params=None, data=None, - expected_response_code=200): - """Make a http request to API.""" - url = "{0}/{1}".format(self._baseurl, url) - - if params is None: - params = {} - - auth = { - 'u': self._username, - 'p': self._password - } - - params.update(auth) - - if data is not None and not isinstance(data, str): - data = json.dumps(data) - - retry = True - _try = 0 - # Try to send the request more than once by default (see #103) - while retry: - try: - response = session.request( - method=method, - url=url, - params=params, - data=data, - headers=self._headers, - verify=self._verify_ssl, - timeout=self._timeout - ) - break - except (requests.exceptions.ConnectionError, - requests.exceptions.Timeout): - _try += 1 - if self._retries != 0: - retry = _try < self._retries - else: - raise requests.exceptions.ConnectionError - - if response.status_code == expected_response_code: - return response - else: - raise InfluxDBClientError(response.content, response.status_code) - - def write(self, data): - """Provide as convenience for influxdb v0.9.0, this may change.""" - self.request( - url="write", - method='POST', - params=None, - data=data, - expected_response_code=200 - ) - return True - - # Writing Data - # - # Assuming you have a database named foo_production you can write data - # by doing a POST to /db/foo_production/series?u=some_user&p=some_password - # with a JSON body of points. - - def write_points(self, data, time_precision='s', *args, **kwargs): - """Write to multiple time series names. - - An example data blob is: - - data = [ - { - "points": [ - [ - 12 - ] - ], - "name": "cpu_load_short", - "columns": [ - "value" - ] - } - ] - - :param data: A list of dicts in InfluxDB 0.8.x data format. - :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms' - or 'u'. - :param batch_size: [Optional] Value to write the points in batches - instead of all at one time. Useful for when doing data dumps from - one database to another or when doing a massive write operation - :type batch_size: int - - """ - def list_chunks(l, n): - """Yield successive n-sized chunks from l.""" - for i in xrange(0, len(l), n): - yield l[i:i + n] - - batch_size = kwargs.get('batch_size') - if batch_size and batch_size > 0: - for item in data: - name = item.get('name') - columns = item.get('columns') - point_list = item.get('points', []) - - for batch in list_chunks(point_list, batch_size): - item = [{ - "points": batch, - "name": name, - "columns": columns - }] - self._write_points( - data=item, - time_precision=time_precision) - return True - - return self._write_points(data=data, - time_precision=time_precision) - - def write_points_with_precision(self, data, time_precision='s'): - """Write to multiple time series names. - - DEPRECATED. - """ - warnings.warn( - "write_points_with_precision is deprecated, and will be removed " - "in future versions. Please use " - "``InfluxDBClient.write_points(time_precision='..')`` instead.", - FutureWarning) - return self._write_points(data=data, time_precision=time_precision) - - def _write_points(self, data, time_precision): - if time_precision not in ['s', 'm', 'ms', 'u']: - raise Exception( - "Invalid time precision is given. (use 's', 'm', 'ms' or 'u')") - - if self._use_udp and time_precision != 's': - raise Exception( - "InfluxDB only supports seconds precision for udp writes" - ) - - url = "db/{0}/series".format(self._database) - - params = { - 'time_precision': time_precision - } - - if self._use_udp: - self.send_packet(data) - else: - self.request( - url=url, - method='POST', - params=params, - data=data, - expected_response_code=200 - ) - - return True - - # One Time Deletes - - def delete_points(self, name): - """Delete an entire series.""" - url = "db/{0}/series/{1}".format(self._database, name) - - self.request( - url=url, - method='DELETE', - expected_response_code=204 - ) - - return True - - # Regularly Scheduled Deletes - - def create_scheduled_delete(self, json_body): - """Create schedule delete from database. - - 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, - but it is documented in http://influxdb.org/docs/api/http.html. - See also: src/api/http/api.go:l57 - - """ - raise NotImplementedError() - - # get list of deletes - # curl http://localhost:8086/db/site_dev/scheduled_deletes - # - # remove a regularly scheduled delete - # curl -X DELETE http://localhost:8086/db/site_dev/scheduled_deletes/:id - - def get_list_scheduled_delete(self): - """Get list of scheduled deletes. - - 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, - but it is documented in http://influxdb.org/docs/api/http.html. - See also: src/api/http/api.go:l57 - - """ - raise NotImplementedError() - - def remove_scheduled_delete(self, delete_id): - """Remove scheduled delete. - - 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, - but it is documented in http://influxdb.org/docs/api/http.html. - See also: src/api/http/api.go:l57 - - """ - raise NotImplementedError() - - def query(self, query, time_precision='s', chunked=False): - """Query data from the influxdb v0.8 database. - - :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms' - or 'u'. - :param chunked: [Optional, default=False] True if the data shall be - retrieved in chunks, False otherwise. - """ - return self._query(query, time_precision=time_precision, - chunked=chunked) - - # Querying Data - # - # GET db/:name/series. It takes five parameters - def _query(self, query, time_precision='s', chunked=False): - if time_precision not in ['s', 'm', 'ms', 'u']: - raise Exception( - "Invalid time precision is given. (use 's', 'm', 'ms' or 'u')") - - if chunked is True: - chunked_param = 'true' - else: - chunked_param = 'false' - - # Build the URL of the series to query - url = "db/{0}/series".format(self._database) - - params = { - 'q': query, - 'time_precision': time_precision, - 'chunked': chunked_param - } - - response = self.request( - url=url, - method='GET', - params=params, - expected_response_code=200 - ) - - if chunked: - try: - decoded = chunked_json.loads(response.content.decode()) - except UnicodeDecodeError: - decoded = chunked_json.loads(response.content.decode('utf-8')) - - return list(decoded) - - return response.json() - - # Creating and Dropping Databases - # - # ### create a database - # curl -X POST http://localhost:8086/db -d '{"name": "site_development"}' - # - # ### drop a database - # curl -X DELETE http://localhost:8086/db/site_development - - def create_database(self, database): - """Create a database on the InfluxDB server. - - :param database: the name of the database to create - :type database: string - :rtype: boolean - """ - url = "db" - - data = {'name': database} - - self.request( - url=url, - method='POST', - data=data, - expected_response_code=201 - ) - - return True - - def delete_database(self, database): - """Drop a database on the InfluxDB server. - - :param database: the name of the database to delete - :type database: string - :rtype: boolean - """ - url = "db/{0}".format(database) - - self.request( - url=url, - method='DELETE', - expected_response_code=204 - ) - - return True - - # ### get list of databases - # curl -X GET http://localhost:8086/db - - def get_list_database(self): - """Get the list of databases.""" - url = "db" - - response = self.request( - url=url, - method='GET', - expected_response_code=200 - ) - - return response.json() - - def get_database_list(self): - """Get the list of databases. - - DEPRECATED. - """ - warnings.warn( - "get_database_list is deprecated, and will be removed " - "in future versions. Please use " - "``InfluxDBClient.get_list_database`` instead.", - FutureWarning) - return self.get_list_database() - - def delete_series(self, series): - """Drop a series on the InfluxDB server. - - :param series: the name of the series to delete - :type series: string - :rtype: boolean - """ - url = "db/{0}/series/{1}".format( - self._database, - series - ) - - self.request( - url=url, - method='DELETE', - expected_response_code=204 - ) - - return True - - def get_list_series(self): - """Get a list of all time series in a database.""" - response = self._query('list series') - return [series[1] for series in response[0]['points']] - - def get_list_continuous_queries(self): - """Get a list of continuous queries.""" - response = self._query('list continuous queries') - return [query[2] for query in response[0]['points']] - - # Security - # get list of cluster admins - # curl http://localhost:8086/cluster_admins?u=root&p=root - - # add cluster admin - # curl -X POST http://localhost:8086/cluster_admins?u=root&p=root \ - # -d '{"name": "paul", "password": "i write teh docz"}' - - # update cluster admin password - # curl -X POST http://localhost:8086/cluster_admins/paul?u=root&p=root \ - # -d '{"password": "new pass"}' - - # delete cluster admin - # curl -X DELETE http://localhost:8086/cluster_admins/paul?u=root&p=root - - # Database admins, with a database name of site_dev - # get list of database admins - # curl http://localhost:8086/db/site_dev/admins?u=root&p=root - - # add database admin - # curl -X POST http://localhost:8086/db/site_dev/admins?u=root&p=root \ - # -d '{"name": "paul", "password": "i write teh docz"}' - - # update database admin password - # curl -X POST http://localhost:8086/db/site_dev/admins/paul?u=root&p=root\ - # -d '{"password": "new pass"}' - - # delete database admin - # curl -X DELETE \ - # http://localhost:8086/db/site_dev/admins/paul?u=root&p=root - - def get_list_cluster_admins(self): - """Get list of cluster admins.""" - response = self.request( - url="cluster_admins", - method='GET', - expected_response_code=200 - ) - - return response.json() - - def add_cluster_admin(self, new_username, new_password): - """Add cluster admin.""" - data = { - 'name': new_username, - 'password': new_password - } - - self.request( - url="cluster_admins", - method='POST', - data=data, - expected_response_code=200 - ) - - return True - - def update_cluster_admin_password(self, username, new_password): - """Update cluster admin password.""" - url = "cluster_admins/{0}".format(username) - - data = { - 'password': new_password - } - - self.request( - url=url, - method='POST', - data=data, - expected_response_code=200 - ) - - return True - - def delete_cluster_admin(self, username): - """Delete cluster admin.""" - url = "cluster_admins/{0}".format(username) - - self.request( - url=url, - method='DELETE', - expected_response_code=200 - ) - - return True - - def set_database_admin(self, username): - """Set user as database admin.""" - return self.alter_database_admin(username, True) - - def unset_database_admin(self, username): - """Unset user as database admin.""" - return self.alter_database_admin(username, False) - - def alter_database_admin(self, username, is_admin): - """Alter the database admin.""" - url = "db/{0}/users/{1}".format(self._database, username) - - data = {'admin': is_admin} - - self.request( - url=url, - method='POST', - data=data, - expected_response_code=200 - ) - - return True - - def get_list_database_admins(self): - """Get list of database admins. - - 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, - but it is documented in http://influxdb.org/docs/api/http.html. - See also: src/api/http/api.go:l57 - - """ - raise NotImplementedError() - - def add_database_admin(self, new_username, new_password): - """Add cluster admin. - - 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, - but it is documented in http://influxdb.org/docs/api/http.html. - See also: src/api/http/api.go:l57 - - """ - raise NotImplementedError() - - def update_database_admin_password(self, username, new_password): - """Update database admin password. - - 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, - but it is documented in http://influxdb.org/docs/api/http.html. - See also: src/api/http/api.go:l57 - - """ - raise NotImplementedError() - - def delete_database_admin(self, username): - """Delete database admin. - - 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, - but it is documented in http://influxdb.org/docs/api/http.html. - See also: src/api/http/api.go:l57 - - """ - raise NotImplementedError() - - ### - # Limiting User Access - - # Database users - # get list of database users - # curl http://localhost:8086/db/site_dev/users?u=root&p=root - - # add database user - # curl -X POST http://localhost:8086/db/site_dev/users?u=root&p=root \ - # -d '{"name": "paul", "password": "i write teh docz"}' - - # update database user password - # curl -X POST http://localhost:8086/db/site_dev/users/paul?u=root&p=root \ - # -d '{"password": "new pass"}' - - # delete database user - # curl -X DELETE http://localhost:8086/db/site_dev/users/paul?u=root&p=root - - def get_database_users(self): - """Get list of database users.""" - url = "db/{0}/users".format(self._database) - - response = self.request( - url=url, - method='GET', - expected_response_code=200 - ) - - return response.json() - - def add_database_user(self, new_username, new_password, permissions=None): - """Add database user. - - :param permissions: A ``(readFrom, writeTo)`` tuple - """ - url = "db/{0}/users".format(self._database) - - data = { - 'name': new_username, - 'password': new_password - } - - if permissions: - try: - data['readFrom'], data['writeTo'] = permissions - except (ValueError, TypeError): - raise TypeError( - "'permissions' must be (readFrom, writeTo) tuple" - ) - - self.request( - url=url, - method='POST', - data=data, - expected_response_code=200 - ) - - return True - - def update_database_user_password(self, username, new_password): - """Update password.""" - return self.alter_database_user(username, new_password) - - def alter_database_user(self, username, password=None, permissions=None): - """Alter a database user and/or their permissions. - - :param permissions: A ``(readFrom, writeTo)`` tuple - :raise TypeError: if permissions cannot be read. - :raise ValueError: if neither password nor permissions provided. - """ - url = "db/{0}/users/{1}".format(self._database, username) - - if not password and not permissions: - raise ValueError("Nothing to alter for user {0}.".format(username)) - - data = {} - - if password: - data['password'] = password - - if permissions: - try: - data['readFrom'], data['writeTo'] = permissions - except (ValueError, TypeError): - raise TypeError( - "'permissions' must be (readFrom, writeTo) tuple" - ) - - self.request( - url=url, - method='POST', - data=data, - expected_response_code=200 - ) - - if username == self._username: - self._password = password - - return True - - def delete_database_user(self, username): - """Delete database user.""" - url = "db/{0}/users/{1}".format(self._database, username) - - self.request( - url=url, - method='DELETE', - expected_response_code=200 - ) - - return True - - # update the user by POSTing to db/site_dev/users/paul - - def update_permission(self, username, json_body): - """Update read/write permission. - - 2013-11-08: This endpoint has not been implemented yet in ver0.0.8, - but it is documented in http://influxdb.org/docs/api/http.html. - See also: src/api/http/api.go:l57 - - """ - raise NotImplementedError() - - def send_packet(self, packet): - """Send a UDP packet along the wire.""" - data = json.dumps(packet) - byte = data.encode('utf-8') - self.udp_socket.sendto(byte, (self._host, self._udp_port)) diff --git a/lib/influxdb/influxdb08/dataframe_client.py b/lib/influxdb/influxdb08/dataframe_client.py deleted file mode 100644 index 2867125..0000000 --- a/lib/influxdb/influxdb08/dataframe_client.py +++ /dev/null @@ -1,177 +0,0 @@ -# -*- coding: utf-8 -*- -"""DataFrame client for InfluxDB v0.8.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import math -import warnings - -from .client import InfluxDBClient - - -class DataFrameClient(InfluxDBClient): - """Primary defintion of the DataFrameClient for v0.8. - - The ``DataFrameClient`` object holds information necessary to connect - to InfluxDB. Requests can be made to InfluxDB directly through the client. - The client reads and writes from pandas DataFrames. - """ - - def __init__(self, ignore_nan=True, *args, **kwargs): - """Initialize an instance of the DataFrameClient.""" - super(DataFrameClient, self).__init__(*args, **kwargs) - - try: - global pd - import pandas as pd - except ImportError as ex: - raise ImportError('DataFrameClient requires Pandas, ' - '"{ex}" problem importing'.format(ex=str(ex))) - - self.EPOCH = pd.Timestamp('1970-01-01 00:00:00.000+00:00') - self.ignore_nan = ignore_nan - - def write_points(self, data, *args, **kwargs): - """Write to multiple time series names. - - :param data: A dictionary mapping series names to pandas DataFrames - :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms' - or 'u'. - :param batch_size: [Optional] Value to write the points in batches - instead of all at one time. Useful for when doing data dumps from - one database to another or when doing a massive write operation - :type batch_size: int - """ - batch_size = kwargs.get('batch_size') - time_precision = kwargs.get('time_precision', 's') - if batch_size: - kwargs.pop('batch_size') # don't hand over to InfluxDBClient - for key, data_frame in data.items(): - number_batches = int(math.ceil( - len(data_frame) / float(batch_size))) - for batch in range(number_batches): - start_index = batch * batch_size - end_index = (batch + 1) * batch_size - outdata = [ - self._convert_dataframe_to_json( - name=key, - dataframe=data_frame - .iloc[start_index:end_index].copy(), - time_precision=time_precision)] - InfluxDBClient.write_points(self, outdata, *args, **kwargs) - return True - - outdata = [ - self._convert_dataframe_to_json(name=key, dataframe=dataframe, - time_precision=time_precision) - for key, dataframe in data.items()] - return InfluxDBClient.write_points(self, outdata, *args, **kwargs) - - def write_points_with_precision(self, data, time_precision='s'): - """Write to multiple time series names. - - DEPRECATED - """ - warnings.warn( - "write_points_with_precision is deprecated, and will be removed " - "in future versions. Please use " - "``DataFrameClient.write_points(time_precision='..')`` instead.", - FutureWarning) - return self.write_points(data, time_precision='s') - - def query(self, query, time_precision='s', chunked=False): - """Query data into DataFrames. - - Returns a DataFrame for a single time series and a map for multiple - time series with the time series as value and its name as key. - - :param time_precision: [Optional, default 's'] Either 's', 'm', 'ms' - or 'u'. - :param chunked: [Optional, default=False] True if the data shall be - retrieved in chunks, False otherwise. - """ - result = InfluxDBClient.query(self, query=query, - time_precision=time_precision, - chunked=chunked) - if len(result) == 0: - return result - elif len(result) == 1: - return self._to_dataframe(result[0], time_precision) - else: - ret = {} - for time_series in result: - ret[time_series['name']] = self._to_dataframe(time_series, - time_precision) - return ret - - @staticmethod - def _to_dataframe(json_result, time_precision): - dataframe = pd.DataFrame(data=json_result['points'], - columns=json_result['columns']) - if 'sequence_number' in dataframe.keys(): - dataframe.sort_values(['time', 'sequence_number'], inplace=True) - else: - dataframe.sort_values(['time'], inplace=True) - - pandas_time_unit = time_precision - if time_precision == 'm': - pandas_time_unit = 'ms' - elif time_precision == 'u': - pandas_time_unit = 'us' - - dataframe.index = pd.to_datetime(list(dataframe['time']), - unit=pandas_time_unit, - utc=True) - del dataframe['time'] - return dataframe - - def _convert_dataframe_to_json(self, dataframe, name, time_precision='s'): - if not isinstance(dataframe, pd.DataFrame): - raise TypeError('Must be DataFrame, but type was: {0}.' - .format(type(dataframe))) - if not (isinstance(dataframe.index, pd.PeriodIndex) or - isinstance(dataframe.index, pd.DatetimeIndex)): - raise TypeError('Must be DataFrame with DatetimeIndex or \ - PeriodIndex.') - - if isinstance(dataframe.index, pd.PeriodIndex): - dataframe.index = dataframe.index.to_timestamp() - else: - dataframe.index = pd.to_datetime(dataframe.index) - - if dataframe.index.tzinfo is None: - dataframe.index = dataframe.index.tz_localize('UTC') - dataframe['time'] = [self._datetime_to_epoch(dt, time_precision) - for dt in dataframe.index] - data = {'name': name, - 'columns': [str(column) for column in dataframe.columns], - 'points': [self._convert_array(x) for x in dataframe.values]} - return data - - def _convert_array(self, array): - try: - global np - import numpy as np - except ImportError as ex: - raise ImportError('DataFrameClient requires Numpy, ' - '"{ex}" problem importing'.format(ex=str(ex))) - - if self.ignore_nan: - number_types = (int, float, np.number) - condition = (all(isinstance(el, number_types) for el in array) and - np.isnan(array)) - return list(np.where(condition, None, array)) - - return list(array) - - def _datetime_to_epoch(self, datetime, time_precision='s'): - seconds = (datetime - self.EPOCH).total_seconds() - if time_precision == 's': - return seconds - elif time_precision == 'm' or time_precision == 'ms': - return seconds * 1000 - elif time_precision == 'u': - return seconds * 1000000 diff --git a/lib/influxdb/influxdb08/helper.py b/lib/influxdb/influxdb08/helper.py deleted file mode 100644 index f3dec33..0000000 --- a/lib/influxdb/influxdb08/helper.py +++ /dev/null @@ -1,153 +0,0 @@ -# -*- coding: utf-8 -*- -"""Helper class for InfluxDB for v0.8.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from collections import namedtuple, defaultdict -from warnings import warn - -import six - - -class SeriesHelper(object): - """Define the SeriesHelper object for InfluxDB v0.8. - - Subclassing this helper eases writing data points in bulk. - All data points are immutable, ensuring they do not get overwritten. - Each subclass can write to its own database. - The time series names can also be based on one or more defined fields. - - Annotated example:: - - class MySeriesHelper(SeriesHelper): - class Meta: - # Meta class stores time series helper configuration. - series_name = 'events.stats.{server_name}' - # Series name must be a string, curly brackets for dynamic use. - fields = ['time', 'server_name'] - # Defines all the fields in this time series. - ### Following attributes are optional. ### - client = TestSeriesHelper.client - # Client should be an instance of InfluxDBClient. - :warning: Only used if autocommit is True. - bulk_size = 5 - # Defines the number of data points to write simultaneously. - # Only applicable if autocommit is True. - autocommit = True - # If True and no bulk_size, then will set bulk_size to 1. - - """ - - __initialized__ = False - - def __new__(cls, *args, **kwargs): - """Initialize class attributes for subsequent constructor calls. - - :note: *args and **kwargs are not explicitly used in this function, - but needed for Python 2 compatibility. - """ - if not cls.__initialized__: - cls.__initialized__ = True - try: - _meta = getattr(cls, 'Meta') - except AttributeError: - raise AttributeError( - 'Missing Meta class in {0}.'.format( - cls.__name__)) - - for attr in ['series_name', 'fields']: - try: - setattr(cls, '_' + attr, getattr(_meta, attr)) - except AttributeError: - raise AttributeError( - 'Missing {0} in {1} Meta class.'.format( - attr, - cls.__name__)) - - cls._autocommit = getattr(_meta, 'autocommit', False) - - cls._client = getattr(_meta, 'client', None) - if cls._autocommit and not cls._client: - raise AttributeError( - 'In {0}, autocommit is set to True, but no client is set.' - .format(cls.__name__)) - - try: - cls._bulk_size = getattr(_meta, 'bulk_size') - if cls._bulk_size < 1 and cls._autocommit: - warn( - 'Definition of bulk_size in {0} forced to 1, ' - 'was less than 1.'.format(cls.__name__)) - cls._bulk_size = 1 - except AttributeError: - cls._bulk_size = -1 - else: - if not cls._autocommit: - warn( - 'Definition of bulk_size in {0} has no affect because' - ' autocommit is false.'.format(cls.__name__)) - - cls._datapoints = defaultdict(list) - cls._type = namedtuple(cls.__name__, cls._fields) - - return super(SeriesHelper, cls).__new__(cls) - - def __init__(self, **kw): - """Create a new data point. - - All fields must be present. - - :note: Data points written when `bulk_size` is reached per Helper. - :warning: Data points are *immutable* (`namedtuples`). - """ - cls = self.__class__ - - if sorted(cls._fields) != sorted(kw.keys()): - raise NameError( - 'Expected {0}, got {1}.'.format( - cls._fields, - kw.keys())) - - cls._datapoints[cls._series_name.format(**kw)].append(cls._type(**kw)) - - if cls._autocommit and \ - sum(len(series) for series in cls._datapoints.values()) \ - >= cls._bulk_size: - cls.commit() - - @classmethod - def commit(cls, client=None): - """Commit everything from datapoints via the client. - - :param client: InfluxDBClient instance for writing points to InfluxDB. - :attention: any provided client will supersede the class client. - :return: result of client.write_points. - """ - if not client: - client = cls._client - rtn = client.write_points(cls._json_body_()) - cls._reset_() - return rtn - - @classmethod - def _json_body_(cls): - """Return JSON body of the datapoints. - - :return: JSON body of the datapoints. - """ - json = [] - for series_name, data in six.iteritems(cls._datapoints): - json.append({'name': series_name, - 'columns': cls._fields, - 'points': [[getattr(point, k) for k in cls._fields] - for point in data] - }) - return json - - @classmethod - def _reset_(cls): - """Reset data storage.""" - cls._datapoints = defaultdict(list) diff --git a/lib/influxdb/line_protocol.py b/lib/influxdb/line_protocol.py deleted file mode 100644 index e8816fc..0000000 --- a/lib/influxdb/line_protocol.py +++ /dev/null @@ -1,172 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define the line_protocol handler.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from datetime import datetime -from numbers import Integral - -from pytz import UTC -from dateutil.parser import parse -from six import iteritems, binary_type, text_type, integer_types, PY2 - -EPOCH = UTC.localize(datetime.utcfromtimestamp(0)) - - -def _convert_timestamp(timestamp, precision=None): - if isinstance(timestamp, Integral): - return timestamp # assume precision is correct if timestamp is int - - if isinstance(_get_unicode(timestamp), text_type): - timestamp = parse(timestamp) - - if isinstance(timestamp, datetime): - if not timestamp.tzinfo: - timestamp = UTC.localize(timestamp) - - ns = (timestamp - EPOCH).total_seconds() * 1e9 - if precision is None or precision == 'n': - return ns - elif precision == 'u': - return ns / 1e3 - elif precision == 'ms': - return ns / 1e6 - elif precision == 's': - return ns / 1e9 - elif precision == 'm': - return ns / 1e9 / 60 - elif precision == 'h': - return ns / 1e9 / 3600 - - raise ValueError(timestamp) - - -def _escape_tag(tag): - tag = _get_unicode(tag, force=True) - return tag.replace( - "\\", "\\\\" - ).replace( - " ", "\\ " - ).replace( - ",", "\\," - ).replace( - "=", "\\=" - ) - - -def _escape_tag_value(value): - ret = _escape_tag(value) - if ret.endswith('\\'): - ret += ' ' - return ret - - -def quote_ident(value): - """Indent the quotes.""" - return "\"{}\"".format(value - .replace("\\", "\\\\") - .replace("\"", "\\\"") - .replace("\n", "\\n")) - - -def quote_literal(value): - """Quote provided literal.""" - return "'{}'".format(value - .replace("\\", "\\\\") - .replace("'", "\\'")) - - -def _is_float(value): - try: - float(value) - except (TypeError, ValueError): - return False - - return True - - -def _escape_value(value): - value = _get_unicode(value) - - if isinstance(value, text_type) and value != '': - return quote_ident(value) - elif isinstance(value, integer_types) and not isinstance(value, bool): - return str(value) + 'i' - elif _is_float(value): - return repr(value) - - return str(value) - - -def _get_unicode(data, force=False): - """Try to return a text aka unicode object from the given data.""" - if isinstance(data, binary_type): - return data.decode('utf-8') - elif data is None: - return '' - elif force: - if PY2: - return unicode(data) - else: - return str(data) - else: - return data - - -def make_lines(data, precision=None): - """Extract points from given dict. - - Extracts the points from the given dict and returns a Unicode string - matching the line protocol introduced in InfluxDB 0.9.0. - """ - lines = [] - static_tags = data.get('tags') - for point in data['points']: - elements = [] - - # add measurement name - measurement = _escape_tag(_get_unicode( - point.get('measurement', data.get('measurement')))) - key_values = [measurement] - - # add tags - if static_tags: - tags = dict(static_tags) # make a copy, since we'll modify - tags.update(point.get('tags') or {}) - else: - tags = point.get('tags') or {} - - # tags should be sorted client-side to take load off server - for tag_key, tag_value in sorted(iteritems(tags)): - key = _escape_tag(tag_key) - value = _escape_tag_value(tag_value) - - if key != '' and value != '': - key_values.append(key + "=" + value) - - elements.append(','.join(key_values)) - - # add fields - field_values = [] - for field_key, field_value in sorted(iteritems(point['fields'])): - key = _escape_tag(field_key) - value = _escape_value(field_value) - - if key != '' and value != '': - field_values.append(key + "=" + value) - - elements.append(','.join(field_values)) - - # add timestamp - if 'time' in point: - timestamp = _get_unicode(str(int( - _convert_timestamp(point['time'], precision)))) - elements.append(timestamp) - - line = ' '.join(elements) - lines.append(line) - - return '\n'.join(lines) + '\n' diff --git a/lib/influxdb/resultset.py b/lib/influxdb/resultset.py deleted file mode 100644 index ba4f3c1..0000000 --- a/lib/influxdb/resultset.py +++ /dev/null @@ -1,206 +0,0 @@ -# -*- coding: utf-8 -*- -"""Module to prepare the resultset.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import warnings - -from influxdb.exceptions import InfluxDBClientError - -_sentinel = object() - - -class ResultSet(object): - """A wrapper around a single InfluxDB query result.""" - - def __init__(self, series, raise_errors=True): - """Initialize the ResultSet.""" - self._raw = series - self._error = self._raw.get('error', None) - - if self.error is not None and raise_errors is True: - raise InfluxDBClientError(self.error) - - @property - def raw(self): - """Raw JSON from InfluxDB.""" - return self._raw - - @raw.setter - def raw(self, value): - self._raw = value - - @property - def error(self): - """Error returned by InfluxDB.""" - return self._error - - def __getitem__(self, key): - """Retrieve the series name or specific set based on key. - - :param key: Either a series name, or a tags_dict, or - a 2-tuple(series_name, tags_dict). - If the series name is None (or not given) then any serie - matching the eventual given tags will be given its points - one after the other. - To get the points of every series in this resultset then - you have to provide None as key. - :return: A generator yielding `Point`s matching the given key. - NB: - The order in which the points are yielded is actually undefined but - it might change.. - """ - warnings.warn( - ("ResultSet's ``__getitem__`` method will be deprecated. Use" - "``get_points`` instead."), - DeprecationWarning - ) - - if isinstance(key, tuple): - if len(key) != 2: - raise TypeError('only 2-tuples allowed') - - name = key[0] - tags = key[1] - - if not isinstance(tags, dict) and tags is not None: - raise TypeError('tags should be a dict') - elif isinstance(key, dict): - name = None - tags = key - else: - name = key - tags = None - - return self.get_points(name, tags) - - def get_points(self, measurement=None, tags=None): - """Return a generator for all the points that match the given filters. - - :param measurement: The measurement name - :type measurement: str - - :param tags: Tags to look for - :type tags: dict - - :return: Points generator - """ - # Raise error if measurement is not str or bytes - if not isinstance(measurement, - (bytes, type(b''.decode()), type(None))): - raise TypeError('measurement must be an str or None') - - for series in self._get_series(): - series_name = series.get('measurement', - series.get('name', 'results')) - if series_name is None: - # this is a "system" query or a query which - # doesn't return a name attribute. - # like 'show retention policies' .. - if tags is None: - for item in self._get_points_for_series(series): - yield item - - elif measurement in (None, series_name): - # by default if no tags was provided then - # we will matches every returned series - series_tags = series.get('tags', {}) - for item in self._get_points_for_series(series): - if tags is None or \ - self._tag_matches(item, tags) or \ - self._tag_matches(series_tags, tags): - yield item - - def __repr__(self): - """Representation of ResultSet object.""" - items = [] - - for item in self.items(): - items.append("'%s': %s" % (item[0], list(item[1]))) - - return "ResultSet({%s})" % ", ".join(items) - - def __iter__(self): - """Yield one dict instance per series result.""" - for key in self.keys(): - yield list(self.__getitem__(key)) - - @staticmethod - def _tag_matches(tags, filter): - """Check if all key/values in filter match in tags.""" - for tag_name, tag_value in filter.items(): - # using _sentinel as I'm not sure that "None" - # could be used, because it could be a valid - # series_tags value : when a series has no such tag - # then I think it's set to /null/None/.. TBC.. - series_tag_value = tags.get(tag_name, _sentinel) - if series_tag_value != tag_value: - return False - - return True - - def _get_series(self): - """Return all series.""" - return self.raw.get('series', []) - - def __len__(self): - """Return the len of the keys in the ResultSet.""" - return len(self.keys()) - - def keys(self): - """Return the list of keys in the ResultSet. - - :return: List of keys. Keys are tuples (series_name, tags) - """ - keys = [] - for series in self._get_series(): - keys.append( - (series.get('measurement', - series.get('name', 'results')), - series.get('tags', None)) - ) - return keys - - def items(self): - """Return the set of items from the ResultSet. - - :return: List of tuples, (key, generator) - """ - items = [] - for series in self._get_series(): - series_key = (series.get('measurement', - series.get('name', 'results')), - series.get('tags', None)) - items.append( - (series_key, self._get_points_for_series(series)) - ) - return items - - def _get_points_for_series(self, series): - """Return generator of dict from columns and values of a series. - - :param series: One series - :return: Generator of dicts - """ - for point in series.get('values', []): - yield self.point_from_cols_vals( - series['columns'], - point - ) - - @staticmethod - def point_from_cols_vals(cols, vals): - """Create a dict from columns and values lists. - - :param cols: List of columns - :param vals: List of values - :return: Dict where keys are columns. - """ - point = {} - for col_index, col_name in enumerate(cols): - point[col_name] = vals[col_index] - - return point diff --git a/lib/influxdb/tests/__init__.py b/lib/influxdb/tests/__init__.py deleted file mode 100644 index adf2f20..0000000 --- a/lib/influxdb/tests/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -"""Configure the tests package for InfluxDBClient.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import sys -import os - -import unittest - -using_pypy = hasattr(sys, "pypy_version_info") -skipIfPYpy = unittest.skipIf(using_pypy, "Skipping this test on pypy.") - -_skip_server_tests = os.environ.get( - 'INFLUXDB_PYTHON_SKIP_SERVER_TESTS', - None) == 'True' -skipServerTests = unittest.skipIf(_skip_server_tests, - "Skipping server tests...") diff --git a/lib/influxdb/tests/chunked_json_test.py b/lib/influxdb/tests/chunked_json_test.py deleted file mode 100644 index f633bcb..0000000 --- a/lib/influxdb/tests/chunked_json_test.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -"""Chunked JSON test.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import unittest - -from influxdb import chunked_json - - -class TestChunkJson(unittest.TestCase): - """Set up the TestChunkJson object.""" - - @classmethod - def setUpClass(cls): - """Initialize the TestChunkJson object.""" - super(TestChunkJson, cls).setUpClass() - - def test_load(self): - """Test reading a sequence of JSON values from a string.""" - example_response = \ - '{"results": [{"series": [{"measurement": "sdfsdfsdf", ' \ - '"columns": ["time", "value"], "values": ' \ - '[["2009-11-10T23:00:00Z", 0.64]]}]}, {"series": ' \ - '[{"measurement": "cpu_load_short", "columns": ["time", "value"],'\ - '"values": [["2009-11-10T23:00:00Z", 0.64]]}]}]}' - - res = list(chunked_json.loads(example_response)) - # import ipdb; ipdb.set_trace() - - self.assertListEqual( - [ - { - 'results': [ - {'series': [{ - 'values': [['2009-11-10T23:00:00Z', 0.64]], - 'measurement': 'sdfsdfsdf', - 'columns': - ['time', 'value']}]}, - {'series': [{ - 'values': [['2009-11-10T23:00:00Z', 0.64]], - 'measurement': 'cpu_load_short', - 'columns': ['time', 'value']}]} - ] - } - ], - res - ) diff --git a/lib/influxdb/tests/client_test.py b/lib/influxdb/tests/client_test.py deleted file mode 100644 index e27eef1..0000000 --- a/lib/influxdb/tests/client_test.py +++ /dev/null @@ -1,1094 +0,0 @@ -# -*- coding: utf-8 -*- -"""Unit tests for the InfluxDBClient. - -NB/WARNING: -This module implements tests for the InfluxDBClient class -but does so - + without any server instance running - + by mocking all the expected responses. - -So any change of (response format from) the server will **NOT** be -detected by this module. - -See client_test_with_server.py for tests against a running server instance. - -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import random -import socket -import unittest -import warnings - -import json -import mock -import requests -import requests.exceptions -import requests_mock - -from nose.tools import raises - -from influxdb import InfluxDBClient -from influxdb.resultset import ResultSet - - -def _build_response_object(status_code=200, content=""): - resp = requests.Response() - resp.status_code = status_code - resp._content = content.encode("utf8") - return resp - - -def _mocked_session(cli, method="GET", status_code=200, content=""): - method = method.upper() - - def request(*args, **kwargs): - """Request content from the mocked session.""" - c = content - - # Check method - assert method == kwargs.get('method', 'GET') - - if method == 'POST': - data = kwargs.get('data', None) - - if data is not None: - # Data must be a string - assert isinstance(data, str) - - # Data must be a JSON string - assert c == json.loads(data, strict=True) - - c = data - - # Anyway, Content must be a JSON string (or empty string) - if not isinstance(c, str): - c = json.dumps(c) - - return _build_response_object(status_code=status_code, content=c) - - return mock.patch.object(cli._session, 'request', side_effect=request) - - -class TestInfluxDBClient(unittest.TestCase): - """Set up the TestInfluxDBClient object.""" - - def setUp(self): - """Initialize an instance of TestInfluxDBClient object.""" - # By default, raise exceptions on warnings - warnings.simplefilter('error', FutureWarning) - - self.cli = InfluxDBClient('localhost', 8086, 'username', 'password') - self.dummy_points = [ - { - "measurement": "cpu_load_short", - "tags": { - "host": "server01", - "region": "us-west" - }, - "time": "2009-11-10T23:00:00.123456Z", - "fields": { - "value": 0.64 - } - } - ] - - self.dsn_string = 'influxdb://uSr:pWd@my.host.fr:1886/db' - - def test_scheme(self): - """Set up the test schema for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') - self.assertEqual('http://host:8086', cli._baseurl) - - cli = InfluxDBClient( - 'host', 8086, 'username', 'password', 'database', ssl=True - ) - self.assertEqual('https://host:8086', cli._baseurl) - - cli = InfluxDBClient( - 'host', 8086, 'username', 'password', 'database', ssl=True, - path="somepath" - ) - self.assertEqual('https://host:8086/somepath', cli._baseurl) - - cli = InfluxDBClient( - 'host', 8086, 'username', 'password', 'database', ssl=True, - path=None - ) - self.assertEqual('https://host:8086', cli._baseurl) - - cli = InfluxDBClient( - 'host', 8086, 'username', 'password', 'database', ssl=True, - path="/somepath" - ) - self.assertEqual('https://host:8086/somepath', cli._baseurl) - - def test_dsn(self): - """Set up the test datasource name for TestInfluxDBClient object.""" - cli = InfluxDBClient.from_dsn('influxdb://192.168.0.1:1886') - self.assertEqual('http://192.168.0.1:1886', cli._baseurl) - - cli = InfluxDBClient.from_dsn(self.dsn_string) - self.assertEqual('http://my.host.fr:1886', cli._baseurl) - self.assertEqual('uSr', cli._username) - self.assertEqual('pWd', cli._password) - self.assertEqual('db', cli._database) - self.assertFalse(cli._use_udp) - - cli = InfluxDBClient.from_dsn('udp+' + self.dsn_string) - self.assertTrue(cli._use_udp) - - cli = InfluxDBClient.from_dsn('https+' + self.dsn_string) - self.assertEqual('https://my.host.fr:1886', cli._baseurl) - - cli = InfluxDBClient.from_dsn('https+' + self.dsn_string, - **{'ssl': False}) - self.assertEqual('http://my.host.fr:1886', cli._baseurl) - - def test_switch_database(self): - """Test switch database in TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') - cli.switch_database('another_database') - self.assertEqual('another_database', cli._database) - - def test_switch_user(self): - """Test switch user in TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') - cli.switch_user('another_username', 'another_password') - self.assertEqual('another_username', cli._username) - self.assertEqual('another_password', cli._password) - - def test_write(self): - """Test write in TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/write", - status_code=204 - ) - cli = InfluxDBClient(database='db') - cli.write( - {"database": "mydb", - "retentionPolicy": "mypolicy", - "points": [{"measurement": "cpu_load_short", - "tags": {"host": "server01", - "region": "us-west"}, - "time": "2009-11-10T23:00:00Z", - "fields": {"value": 0.64}}]} - ) - - self.assertEqual( - m.last_request.body, - b"cpu_load_short,host=server01,region=us-west " - b"value=0.64 1257894000000000000\n", - ) - - def test_write_points(self): - """Test write points for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/write", - status_code=204 - ) - - cli = InfluxDBClient(database='db') - cli.write_points( - self.dummy_points, - ) - self.assertEqual( - 'cpu_load_short,host=server01,region=us-west ' - 'value=0.64 1257894000123456000\n', - m.last_request.body.decode('utf-8'), - ) - - def test_write_points_toplevel_attributes(self): - """Test write points attrs for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/write", - status_code=204 - ) - - cli = InfluxDBClient(database='db') - cli.write_points( - self.dummy_points, - database='testdb', - tags={"tag": "hello"}, - retention_policy="somepolicy" - ) - self.assertEqual( - 'cpu_load_short,host=server01,region=us-west,tag=hello ' - 'value=0.64 1257894000123456000\n', - m.last_request.body.decode('utf-8'), - ) - - def test_write_points_batch(self): - """Test write points batch for TestInfluxDBClient object.""" - dummy_points = [ - {"measurement": "cpu_usage", "tags": {"unit": "percent"}, - "time": "2009-11-10T23:00:00Z", "fields": {"value": 12.34}}, - {"measurement": "network", "tags": {"direction": "in"}, - "time": "2009-11-10T23:00:00Z", "fields": {"value": 123.00}}, - {"measurement": "network", "tags": {"direction": "out"}, - "time": "2009-11-10T23:00:00Z", "fields": {"value": 12.00}} - ] - expected_last_body = ( - "network,direction=out,host=server01,region=us-west " - "value=12.0 1257894000000000000\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - cli = InfluxDBClient(database='db') - cli.write_points(points=dummy_points, - database='db', - tags={"host": "server01", - "region": "us-west"}, - batch_size=2) - self.assertEqual(m.call_count, 2) - self.assertEqual(expected_last_body, - m.last_request.body.decode('utf-8')) - - def test_write_points_udp(self): - """Test write points UDP for TestInfluxDBClient object.""" - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - port = random.randint(4000, 8000) - s.bind(('0.0.0.0', port)) - - cli = InfluxDBClient( - 'localhost', 8086, 'root', 'root', - 'test', use_udp=True, udp_port=port - ) - cli.write_points(self.dummy_points) - - received_data, addr = s.recvfrom(1024) - - self.assertEqual( - 'cpu_load_short,host=server01,region=us-west ' - 'value=0.64 1257894000123456000\n', - received_data.decode() - ) - - @raises(Exception) - def test_write_points_fails(self): - """Test write points fail for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - with _mocked_session(cli, 'post', 500): - cli.write_points([]) - - def test_write_points_with_precision(self): - """Test write points with precision for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/write", - status_code=204 - ) - - cli = InfluxDBClient(database='db') - - cli.write_points(self.dummy_points, time_precision='n') - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 1257894000123456000\n', - m.last_request.body, - ) - - cli.write_points(self.dummy_points, time_precision='u') - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 1257894000123456\n', - m.last_request.body, - ) - - cli.write_points(self.dummy_points, time_precision='ms') - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 1257894000123\n', - m.last_request.body, - ) - - cli.write_points(self.dummy_points, time_precision='s') - self.assertEqual( - b"cpu_load_short,host=server01,region=us-west " - b"value=0.64 1257894000\n", - m.last_request.body, - ) - - cli.write_points(self.dummy_points, time_precision='m') - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 20964900\n', - m.last_request.body, - ) - - cli.write_points(self.dummy_points, time_precision='h') - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 349415\n', - m.last_request.body, - ) - - def test_write_points_with_precision_udp(self): - """Test write points with precision for TestInfluxDBClient object.""" - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - port = random.randint(4000, 8000) - s.bind(('0.0.0.0', port)) - - cli = InfluxDBClient( - 'localhost', 8086, 'root', 'root', - 'test', use_udp=True, udp_port=port - ) - - cli.write_points(self.dummy_points, time_precision='n') - received_data, addr = s.recvfrom(1024) - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 1257894000123456000\n', - received_data, - ) - - cli.write_points(self.dummy_points, time_precision='u') - received_data, addr = s.recvfrom(1024) - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 1257894000123456\n', - received_data, - ) - - cli.write_points(self.dummy_points, time_precision='ms') - received_data, addr = s.recvfrom(1024) - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 1257894000123\n', - received_data, - ) - - cli.write_points(self.dummy_points, time_precision='s') - received_data, addr = s.recvfrom(1024) - self.assertEqual( - b"cpu_load_short,host=server01,region=us-west " - b"value=0.64 1257894000\n", - received_data, - ) - - cli.write_points(self.dummy_points, time_precision='m') - received_data, addr = s.recvfrom(1024) - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 20964900\n', - received_data, - ) - - cli.write_points(self.dummy_points, time_precision='h') - received_data, addr = s.recvfrom(1024) - self.assertEqual( - b'cpu_load_short,host=server01,region=us-west ' - b'value=0.64 349415\n', - received_data, - ) - - def test_write_points_bad_precision(self): - """Test write points w/bad precision TestInfluxDBClient object.""" - cli = InfluxDBClient() - with self.assertRaisesRegexp( - Exception, - "Invalid time precision is given. " - "\(use 'n', 'u', 'ms', 's', 'm' or 'h'\)" - ): - cli.write_points( - self.dummy_points, - time_precision='g' - ) - - @raises(Exception) - def test_write_points_with_precision_fails(self): - """Test write points w/precision fail for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - with _mocked_session(cli, 'post', 500): - cli.write_points_with_precision([]) - - def test_query(self): - """Test query method for TestInfluxDBClient object.""" - example_response = ( - '{"results": [{"series": [{"measurement": "sdfsdfsdf", ' - '"columns": ["time", "value"], "values": ' - '[["2009-11-10T23:00:00Z", 0.64]]}]}, {"series": ' - '[{"measurement": "cpu_load_short", "columns": ["time", "value"], ' - '"values": [["2009-11-10T23:00:00Z", 0.64]]}]}]}' - ) - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/query", - text=example_response - ) - rs = self.cli.query('select * from foo') - - self.assertListEqual( - list(rs[0].get_points()), - [{'value': 0.64, 'time': '2009-11-10T23:00:00Z'}] - ) - - def test_select_into_post(self): - """Test SELECT.*INTO is POSTed.""" - example_response = ( - '{"results": [{"series": [{"measurement": "sdfsdfsdf", ' - '"columns": ["time", "value"], "values": ' - '[["2009-11-10T23:00:00Z", 0.64]]}]}, {"series": ' - '[{"measurement": "cpu_load_short", "columns": ["time", "value"], ' - '"values": [["2009-11-10T23:00:00Z", 0.64]]}]}]}' - ) - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text=example_response - ) - rs = self.cli.query('select * INTO newmeas from foo') - - self.assertListEqual( - list(rs[0].get_points()), - [{'value': 0.64, 'time': '2009-11-10T23:00:00Z'}] - ) - - @unittest.skip('Not implemented for 0.9') - def test_query_chunked(self): - """Test chunked query for TestInfluxDBClient object.""" - cli = InfluxDBClient(database='db') - example_object = { - 'points': [ - [1415206250119, 40001, 667], - [1415206244555, 30001, 7], - [1415206228241, 20001, 788], - [1415206212980, 10001, 555], - [1415197271586, 10001, 23] - ], - 'measurement': 'foo', - 'columns': [ - 'time', - 'sequence_number', - 'val' - ] - } - example_response = \ - json.dumps(example_object) + json.dumps(example_object) - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/db/db/series", - text=example_response - ) - - self.assertListEqual( - cli.query('select * from foo', chunked=True), - [example_object, example_object] - ) - - @raises(Exception) - def test_query_fail(self): - """Test query failed for TestInfluxDBClient object.""" - with _mocked_session(self.cli, 'get', 401): - self.cli.query('select column_one from foo;') - - def test_ping(self): - """Test ping querying InfluxDB version.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/ping", - status_code=204, - headers={'X-Influxdb-Version': '1.2.3'} - ) - version = self.cli.ping() - self.assertEqual(version, '1.2.3') - - def test_create_database(self): - """Test create database for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text='{"results":[{}]}' - ) - self.cli.create_database('new_db') - self.assertEqual( - m.last_request.qs['q'][0], - 'create database "new_db"' - ) - - def test_create_numeric_named_database(self): - """Test create db w/numeric name for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text='{"results":[{}]}' - ) - self.cli.create_database('123') - self.assertEqual( - m.last_request.qs['q'][0], - 'create database "123"' - ) - - @raises(Exception) - def test_create_database_fails(self): - """Test create database fail for TestInfluxDBClient object.""" - with _mocked_session(self.cli, 'post', 401): - self.cli.create_database('new_db') - - def test_drop_database(self): - """Test drop database for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text='{"results":[{}]}' - ) - self.cli.drop_database('new_db') - self.assertEqual( - m.last_request.qs['q'][0], - 'drop database "new_db"' - ) - - def test_drop_measurement(self): - """Test drop measurement for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text='{"results":[{}]}' - ) - self.cli.drop_measurement('new_measurement') - self.assertEqual( - m.last_request.qs['q'][0], - 'drop measurement "new_measurement"' - ) - - def test_drop_numeric_named_database(self): - """Test drop numeric db for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text='{"results":[{}]}' - ) - self.cli.drop_database('123') - self.assertEqual( - m.last_request.qs['q'][0], - 'drop database "123"' - ) - - def test_get_list_database(self): - """Test get list of databases for TestInfluxDBClient object.""" - data = {'results': [ - {'series': [ - {'name': 'databases', - 'values': [ - ['new_db_1'], - ['new_db_2']], - 'columns': ['name']}]} - ]} - - with _mocked_session(self.cli, 'get', 200, json.dumps(data)): - self.assertListEqual( - self.cli.get_list_database(), - [{'name': 'new_db_1'}, {'name': 'new_db_2'}] - ) - - @raises(Exception) - def test_get_list_database_fails(self): - """Test get list of dbs fail for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password') - with _mocked_session(cli, 'get', 401): - cli.get_list_database() - - def test_get_list_measurements(self): - """Test get list of measurements for TestInfluxDBClient object.""" - data = { - "results": [{ - "series": [ - {"name": "measurements", - "columns": ["name"], - "values": [["cpu"], ["disk"] - ]}]} - ] - } - - with _mocked_session(self.cli, 'get', 200, json.dumps(data)): - self.assertListEqual( - self.cli.get_list_measurements(), - [{'name': 'cpu'}, {'name': 'disk'}] - ) - - def test_create_retention_policy_default(self): - """Test create default ret policy for TestInfluxDBClient object.""" - example_response = '{"results":[{}]}' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text=example_response - ) - self.cli.create_retention_policy( - 'somename', '1d', 4, default=True, database='db' - ) - - self.assertEqual( - m.last_request.qs['q'][0], - 'create retention policy "somename" on ' - '"db" duration 1d replication 4 shard duration 0s default' - ) - - def test_create_retention_policy(self): - """Test create retention policy for TestInfluxDBClient object.""" - example_response = '{"results":[{}]}' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text=example_response - ) - self.cli.create_retention_policy( - 'somename', '1d', 4, database='db' - ) - - self.assertEqual( - m.last_request.qs['q'][0], - 'create retention policy "somename" on ' - '"db" duration 1d replication 4 shard duration 0s' - ) - - def test_alter_retention_policy(self): - """Test alter retention policy for TestInfluxDBClient object.""" - example_response = '{"results":[{}]}' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text=example_response - ) - # Test alter duration - self.cli.alter_retention_policy('somename', 'db', - duration='4d') - self.assertEqual( - m.last_request.qs['q'][0], - 'alter retention policy "somename" on "db" duration 4d' - ) - # Test alter replication - self.cli.alter_retention_policy('somename', 'db', - replication=4) - self.assertEqual( - m.last_request.qs['q'][0], - 'alter retention policy "somename" on "db" replication 4' - ) - - # Test alter shard duration - self.cli.alter_retention_policy('somename', 'db', - shard_duration='1h') - self.assertEqual( - m.last_request.qs['q'][0], - 'alter retention policy "somename" on "db" shard duration 1h' - ) - - # Test alter default - self.cli.alter_retention_policy('somename', 'db', - default=True) - self.assertEqual( - m.last_request.qs['q'][0], - 'alter retention policy "somename" on "db" default' - ) - - @raises(Exception) - def test_alter_retention_policy_invalid(self): - """Test invalid alter ret policy for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password') - with _mocked_session(cli, 'get', 400): - self.cli.alter_retention_policy('somename', 'db') - - def test_drop_retention_policy(self): - """Test drop retention policy for TestInfluxDBClient object.""" - example_response = '{"results":[{}]}' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text=example_response - ) - self.cli.drop_retention_policy('somename', 'db') - self.assertEqual( - m.last_request.qs['q'][0], - 'drop retention policy "somename" on "db"' - ) - - @raises(Exception) - def test_drop_retention_policy_fails(self): - """Test failed drop ret policy for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password') - with _mocked_session(cli, 'delete', 401): - cli.drop_retention_policy('default', 'db') - - def test_get_list_retention_policies(self): - """Test get retention policies for TestInfluxDBClient object.""" - example_response = \ - '{"results": [{"series": [{"values": [["fsfdsdf", "24h0m0s", 2]],'\ - ' "columns": ["name", "duration", "replicaN"]}]}]}' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/query", - text=example_response - ) - self.assertListEqual( - self.cli.get_list_retention_policies("db"), - [{'duration': '24h0m0s', - 'name': 'fsfdsdf', 'replicaN': 2}] - ) - - @mock.patch('requests.Session.request') - def test_request_retry(self, mock_request): - """Test that two connection errors will be handled.""" - class CustomMock(object): - """Create custom mock object for test.""" - - def __init__(self): - self.i = 0 - - def connection_error(self, *args, **kwargs): - """Handle a connection error for the CustomMock object.""" - self.i += 1 - - if self.i < 3: - raise requests.exceptions.ConnectionError - - r = requests.Response() - r.status_code = 204 - return r - - mock_request.side_effect = CustomMock().connection_error - - cli = InfluxDBClient(database='db') - cli.write_points( - self.dummy_points - ) - - @mock.patch('requests.Session.request') - def test_request_retry_raises(self, mock_request): - """Test that three requests errors will not be handled.""" - class CustomMock(object): - """Create custom mock object for test.""" - - def __init__(self): - self.i = 0 - - def connection_error(self, *args, **kwargs): - """Handle a connection error for the CustomMock object.""" - self.i += 1 - - if self.i < 4: - raise requests.exceptions.HTTPError - else: - r = requests.Response() - r.status_code = 200 - return r - - mock_request.side_effect = CustomMock().connection_error - - cli = InfluxDBClient(database='db') - - with self.assertRaises(requests.exceptions.HTTPError): - cli.write_points(self.dummy_points) - - @mock.patch('requests.Session.request') - def test_random_request_retry(self, mock_request): - """Test that a random number of connection errors will be handled.""" - class CustomMock(object): - """Create custom mock object for test.""" - - def __init__(self, retries): - self.i = 0 - self.retries = retries - - def connection_error(self, *args, **kwargs): - """Handle a connection error for the CustomMock object.""" - self.i += 1 - - if self.i < self.retries: - raise requests.exceptions.ConnectionError - else: - r = requests.Response() - r.status_code = 204 - return r - - retries = random.randint(1, 5) - mock_request.side_effect = CustomMock(retries).connection_error - - cli = InfluxDBClient(database='db', retries=retries) - cli.write_points(self.dummy_points) - - @mock.patch('requests.Session.request') - def test_random_request_retry_raises(self, mock_request): - """Test a random number of conn errors plus one will not be handled.""" - class CustomMock(object): - """Create custom mock object for test.""" - - def __init__(self, retries): - self.i = 0 - self.retries = retries - - def connection_error(self, *args, **kwargs): - """Handle a connection error for the CustomMock object.""" - self.i += 1 - - if self.i < self.retries + 1: - raise requests.exceptions.ConnectionError - else: - r = requests.Response() - r.status_code = 200 - return r - - retries = random.randint(1, 5) - mock_request.side_effect = CustomMock(retries).connection_error - - cli = InfluxDBClient(database='db', retries=retries) - - with self.assertRaises(requests.exceptions.ConnectionError): - cli.write_points(self.dummy_points) - - def test_get_list_users(self): - """Test get users for TestInfluxDBClient object.""" - example_response = ( - '{"results":[{"series":[{"columns":["user","admin"],' - '"values":[["test",false]]}]}]}' - ) - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/query", - text=example_response - ) - - self.assertListEqual( - self.cli.get_list_users(), - [{'user': 'test', 'admin': False}] - ) - - def test_get_list_users_empty(self): - """Test get empty userlist for TestInfluxDBClient object.""" - example_response = ( - '{"results":[{"series":[{"columns":["user","admin"]}]}]}' - ) - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/query", - text=example_response - ) - - self.assertListEqual(self.cli.get_list_users(), []) - - def test_grant_admin_privileges(self): - """Test grant admin privs for TestInfluxDBClient object.""" - example_response = '{"results":[{}]}' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text=example_response - ) - self.cli.grant_admin_privileges('test') - - self.assertEqual( - m.last_request.qs['q'][0], - 'grant all privileges to "test"' - ) - - @raises(Exception) - def test_grant_admin_privileges_invalid(self): - """Test grant invalid admin privs for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password') - with _mocked_session(cli, 'get', 400): - self.cli.grant_admin_privileges('') - - def test_revoke_admin_privileges(self): - """Test revoke admin privs for TestInfluxDBClient object.""" - example_response = '{"results":[{}]}' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text=example_response - ) - self.cli.revoke_admin_privileges('test') - - self.assertEqual( - m.last_request.qs['q'][0], - 'revoke all privileges from "test"' - ) - - @raises(Exception) - def test_revoke_admin_privileges_invalid(self): - """Test revoke invalid admin privs for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password') - with _mocked_session(cli, 'get', 400): - self.cli.revoke_admin_privileges('') - - def test_grant_privilege(self): - """Test grant privs for TestInfluxDBClient object.""" - example_response = '{"results":[{}]}' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text=example_response - ) - self.cli.grant_privilege('read', 'testdb', 'test') - - self.assertEqual( - m.last_request.qs['q'][0], - 'grant read on "testdb" to "test"' - ) - - @raises(Exception) - def test_grant_privilege_invalid(self): - """Test grant invalid privs for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password') - with _mocked_session(cli, 'get', 400): - self.cli.grant_privilege('', 'testdb', 'test') - - def test_revoke_privilege(self): - """Test revoke privs for TestInfluxDBClient object.""" - example_response = '{"results":[{}]}' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/query", - text=example_response - ) - self.cli.revoke_privilege('read', 'testdb', 'test') - - self.assertEqual( - m.last_request.qs['q'][0], - 'revoke read on "testdb" from "test"' - ) - - @raises(Exception) - def test_revoke_privilege_invalid(self): - """Test revoke invalid privs for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password') - with _mocked_session(cli, 'get', 400): - self.cli.revoke_privilege('', 'testdb', 'test') - - def test_get_list_privileges(self): - """Tst get list of privs for TestInfluxDBClient object.""" - data = {'results': [ - {'series': [ - {'columns': ['database', 'privilege'], - 'values': [ - ['db1', 'READ'], - ['db2', 'ALL PRIVILEGES'], - ['db3', 'NO PRIVILEGES']]} - ]} - ]} - - with _mocked_session(self.cli, 'get', 200, json.dumps(data)): - self.assertListEqual( - self.cli.get_list_privileges('test'), - [{'database': 'db1', 'privilege': 'READ'}, - {'database': 'db2', 'privilege': 'ALL PRIVILEGES'}, - {'database': 'db3', 'privilege': 'NO PRIVILEGES'}] - ) - - @raises(Exception) - def test_get_list_privileges_fails(self): - """Test failed get list of privs for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password') - with _mocked_session(cli, 'get', 401): - cli.get_list_privileges('test') - - def test_invalid_port_fails(self): - """Test invalid port fail for TestInfluxDBClient object.""" - with self.assertRaises(ValueError): - InfluxDBClient('host', '80/redir', 'username', 'password') - - def test_chunked_response(self): - """Test chunked reponse for TestInfluxDBClient object.""" - example_response = \ - u'{"results":[{"statement_id":0,"series":' \ - '[{"name":"cpu","columns":["fieldKey","fieldType"],"values":' \ - '[["value","integer"]]}],"partial":true}]}\n{"results":' \ - '[{"statement_id":0,"series":[{"name":"iops","columns":' \ - '["fieldKey","fieldType"],"values":[["value","integer"]]}],' \ - '"partial":true}]}\n{"results":[{"statement_id":0,"series":' \ - '[{"name":"load","columns":["fieldKey","fieldType"],"values":' \ - '[["value","integer"]]}],"partial":true}]}\n{"results":' \ - '[{"statement_id":0,"series":[{"name":"memory","columns":' \ - '["fieldKey","fieldType"],"values":[["value","integer"]]}]}]}\n' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/query", - text=example_response - ) - response = self.cli.query('show series limit 4 offset 0', - chunked=True, chunk_size=4) - self.assertTrue(len(response) == 4) - self.assertEqual(response.__repr__(), ResultSet( - {'series': [{'values': [['value', 'integer']], - 'name': 'cpu', - 'columns': ['fieldKey', 'fieldType']}, - {'values': [['value', 'integer']], - 'name': 'iops', - 'columns': ['fieldKey', 'fieldType']}, - {'values': [['value', 'integer']], - 'name': 'load', - 'columns': ['fieldKey', 'fieldType']}, - {'values': [['value', 'integer']], - 'name': 'memory', - 'columns': ['fieldKey', 'fieldType']}]} - ).__repr__()) - - -class FakeClient(InfluxDBClient): - """Set up a fake client instance of InfluxDBClient.""" - - def __init__(self, *args, **kwargs): - """Initialize an instance of the FakeClient object.""" - super(FakeClient, self).__init__(*args, **kwargs) - - def query(self, - query, - params=None, - expected_response_code=200, - database=None): - """Query data from the FakeClient object.""" - if query == 'Fail': - raise Exception("Fail") - elif query == 'Fail once' and self._host == 'host1': - raise Exception("Fail Once") - elif query == 'Fail twice' and self._host in 'host1 host2': - raise Exception("Fail Twice") - else: - return "Success" diff --git a/lib/influxdb/tests/dataframe_client_test.py b/lib/influxdb/tests/dataframe_client_test.py deleted file mode 100644 index 78f5437..0000000 --- a/lib/influxdb/tests/dataframe_client_test.py +++ /dev/null @@ -1,711 +0,0 @@ -# -*- coding: utf-8 -*- -"""Unit tests for misc module.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from datetime import timedelta - -import json -import unittest -import warnings -import requests_mock - -from influxdb.tests import skipIfPYpy, using_pypy -from nose.tools import raises - -from .client_test import _mocked_session - -if not using_pypy: - import pandas as pd - from pandas.util.testing import assert_frame_equal - from influxdb import DataFrameClient - - -@skipIfPYpy -class TestDataFrameClient(unittest.TestCase): - """Set up a test DataFrameClient object.""" - - def setUp(self): - """Instantiate a TestDataFrameClient object.""" - # By default, raise exceptions on warnings - warnings.simplefilter('error', FutureWarning) - - def test_write_points_from_dataframe(self): - """Test write points from df in TestDataFrameClient object.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - expected = ( - b"foo column_one=\"1\",column_two=1i,column_three=1.0 0\n" - b"foo column_one=\"2\",column_two=2i,column_three=2.0 " - b"3600000000000\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - - cli.write_points(dataframe, 'foo') - self.assertEqual(m.last_request.body, expected) - - cli.write_points(dataframe, 'foo', tags=None) - self.assertEqual(m.last_request.body, expected) - - def test_dataframe_write_points_with_whitespace_measurement(self): - """write_points should escape white space in measurements.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - expected = ( - b"meas\\ with\\ space " - b"column_one=\"1\",column_two=1i,column_three=1.0 0\n" - b"meas\\ with\\ space " - b"column_one=\"2\",column_two=2i,column_three=2.0 " - b"3600000000000\n" - ) - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - cli = DataFrameClient(database='db') - cli.write_points(dataframe, 'meas with space') - self.assertEqual(m.last_request.body, expected) - - def test_dataframe_write_points_with_whitespace_in_column_names(self): - """write_points should escape white space in column names.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column one", "column two", - "column three"]) - expected = ( - b"foo column\\ one=\"1\",column\\ two=1i,column\\ three=1.0 0\n" - b"foo column\\ one=\"2\",column\\ two=2i,column\\ three=2.0 " - b"3600000000000\n" - ) - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - cli = DataFrameClient(database='db') - cli.write_points(dataframe, 'foo') - self.assertEqual(m.last_request.body, expected) - - def test_write_points_from_dataframe_with_none(self): - """Test write points from df in TestDataFrameClient object.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[["1", None, 1.0], ["2", 2.0, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - expected = ( - b"foo column_one=\"1\",column_three=1.0 0\n" - b"foo column_one=\"2\",column_two=2.0,column_three=2.0 " - b"3600000000000\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - - cli.write_points(dataframe, 'foo') - self.assertEqual(m.last_request.body, expected) - - cli.write_points(dataframe, 'foo', tags=None) - self.assertEqual(m.last_request.body, expected) - - def test_write_points_from_dataframe_with_line_of_none(self): - """Test write points from df in TestDataFrameClient object.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[[None, None, None], ["2", 2.0, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - expected = ( - b"foo column_one=\"2\",column_two=2.0,column_three=2.0 " - b"3600000000000\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - - cli.write_points(dataframe, 'foo') - self.assertEqual(m.last_request.body, expected) - - cli.write_points(dataframe, 'foo', tags=None) - self.assertEqual(m.last_request.body, expected) - - def test_write_points_from_dataframe_with_all_none(self): - """Test write points from df in TestDataFrameClient object.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[[None, None, None], [None, None, None]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - expected = ( - b"\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - - cli.write_points(dataframe, 'foo') - self.assertEqual(m.last_request.body, expected) - - cli.write_points(dataframe, 'foo', tags=None) - self.assertEqual(m.last_request.body, expected) - - def test_write_points_from_dataframe_in_batches(self): - """Test write points in batch from df in TestDataFrameClient object.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - self.assertTrue(cli.write_points(dataframe, "foo", batch_size=1)) - - def test_write_points_from_dataframe_with_tag_columns(self): - """Test write points from df w/tag in TestDataFrameClient object.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[['blue', 1, "1", 1, 1.0], - ['red', 0, "2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["tag_one", "tag_two", "column_one", - "column_two", "column_three"]) - expected = ( - b"foo,tag_one=blue,tag_two=1 " - b"column_one=\"1\",column_two=1i,column_three=1.0 " - b"0\n" - b"foo,tag_one=red,tag_two=0 " - b"column_one=\"2\",column_two=2i,column_three=2.0 " - b"3600000000000\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - - cli.write_points(dataframe, 'foo', - tag_columns=['tag_one', 'tag_two']) - self.assertEqual(m.last_request.body, expected) - - cli.write_points(dataframe, 'foo', - tag_columns=['tag_one', 'tag_two'], tags=None) - self.assertEqual(m.last_request.body, expected) - - def test_write_points_from_dataframe_with_tag_cols_and_global_tags(self): - """Test write points from df w/tag + cols in TestDataFrameClient.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[['blue', 1, "1", 1, 1.0], - ['red', 0, "2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["tag_one", "tag_two", "column_one", - "column_two", "column_three"]) - expected = ( - b"foo,global_tag=value,tag_one=blue,tag_two=1 " - b"column_one=\"1\",column_two=1i,column_three=1.0 " - b"0\n" - b"foo,global_tag=value,tag_one=red,tag_two=0 " - b"column_one=\"2\",column_two=2i,column_three=2.0 " - b"3600000000000\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - - cli.write_points(dataframe, 'foo', - tag_columns=['tag_one', 'tag_two'], - tags={'global_tag': 'value'}) - self.assertEqual(m.last_request.body, expected) - - def test_write_points_from_dataframe_with_tag_cols_and_defaults(self): - """Test default write points from df w/tag in TestDataFrameClient.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[['blue', 1, "1", 1, 1.0, 'hot'], - ['red', 0, "2", 2, 2.0, 'cold']], - index=[now, now + timedelta(hours=1)], - columns=["tag_one", "tag_two", "column_one", - "column_two", "column_three", - "tag_three"]) - expected_tags_and_fields = ( - b"foo,tag_one=blue " - b"column_one=\"1\",column_two=1i " - b"0\n" - b"foo,tag_one=red " - b"column_one=\"2\",column_two=2i " - b"3600000000000\n" - ) - - expected_tags_no_fields = ( - b"foo,tag_one=blue,tag_two=1 " - b"column_one=\"1\",column_two=1i,column_three=1.0," - b"tag_three=\"hot\" 0\n" - b"foo,tag_one=red,tag_two=0 " - b"column_one=\"2\",column_two=2i,column_three=2.0," - b"tag_three=\"cold\" 3600000000000\n" - ) - - expected_fields_no_tags = ( - b"foo,tag_one=blue,tag_three=hot,tag_two=1 " - b"column_one=\"1\",column_two=1i,column_three=1.0 " - b"0\n" - b"foo,tag_one=red,tag_three=cold,tag_two=0 " - b"column_one=\"2\",column_two=2i,column_three=2.0 " - b"3600000000000\n" - ) - - expected_no_tags_no_fields = ( - b"foo " - b"tag_one=\"blue\",tag_two=1i,column_one=\"1\"," - b"column_two=1i,column_three=1.0,tag_three=\"hot\" " - b"0\n" - b"foo " - b"tag_one=\"red\",tag_two=0i,column_one=\"2\"," - b"column_two=2i,column_three=2.0,tag_three=\"cold\" " - b"3600000000000\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - - cli.write_points(dataframe, 'foo', - field_columns=['column_one', 'column_two'], - tag_columns=['tag_one']) - self.assertEqual(m.last_request.body, expected_tags_and_fields) - - cli.write_points(dataframe, 'foo', - tag_columns=['tag_one', 'tag_two']) - self.assertEqual(m.last_request.body, expected_tags_no_fields) - - cli.write_points(dataframe, 'foo', - field_columns=['column_one', 'column_two', - 'column_three']) - self.assertEqual(m.last_request.body, expected_fields_no_tags) - - cli.write_points(dataframe, 'foo') - self.assertEqual(m.last_request.body, expected_no_tags_no_fields) - - def test_write_points_from_dataframe_with_tag_escaped(self): - """Test write points from df w/escaped tag in TestDataFrameClient.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame( - data=[ - ['blue orange', "1", 1, 'hot=cold'], # space, equal - ['red,green', "2", 2, r'cold\fire'], # comma, backslash - ['some', "2", 2, ''], # skip empty - ['some', "2", 2, None], # skip None - ['', "2", 2, None], # all tags empty - ], - index=pd.period_range(now, freq='H', periods=5), - columns=["tag_one", "column_one", "column_two", "tag_three"] - ) - - expected_escaped_tags = ( - b"foo,tag_one=blue\\ orange,tag_three=hot\\=cold " - b"column_one=\"1\",column_two=1i " - b"0\n" - b"foo,tag_one=red\\,green,tag_three=cold\\\\fire " - b"column_one=\"2\",column_two=2i " - b"3600000000000\n" - b"foo,tag_one=some " - b"column_one=\"2\",column_two=2i " - b"7200000000000\n" - b"foo,tag_one=some " - b"column_one=\"2\",column_two=2i " - b"10800000000000\n" - b"foo " - b"column_one=\"2\",column_two=2i " - b"14400000000000\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - cli = DataFrameClient(database='db') - cli.write_points(dataframe, 'foo', - field_columns=['column_one', 'column_two'], - tag_columns=['tag_one', 'tag_three']) - self.assertEqual(m.last_request.body, expected_escaped_tags) - - def test_write_points_from_dataframe_with_numeric_column_names(self): - """Test write points from df with numeric cols.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - # df with numeric column names - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)]) - - expected = ( - b'foo,hello=there 0=\"1\",1=1i,2=1.0 0\n' - b'foo,hello=there 0=\"2\",1=2i,2=2.0 3600000000000\n' - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - cli.write_points(dataframe, "foo", {"hello": "there"}) - - self.assertEqual(m.last_request.body, expected) - - def test_write_points_from_dataframe_with_numeric_precision(self): - """Test write points from df with numeric precision.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - # df with numeric column names - dataframe = pd.DataFrame(data=[["1", 1, 1.1111111111111], - ["2", 2, 2.2222222222222]], - index=[now, now + timedelta(hours=1)]) - - expected_default_precision = ( - b'foo,hello=there 0=\"1\",1=1i,2=1.11111111111 0\n' - b'foo,hello=there 0=\"2\",1=2i,2=2.22222222222 3600000000000\n' - ) - - expected_specified_precision = ( - b'foo,hello=there 0=\"1\",1=1i,2=1.1111 0\n' - b'foo,hello=there 0=\"2\",1=2i,2=2.2222 3600000000000\n' - ) - - expected_full_precision = ( - b'foo,hello=there 0=\"1\",1=1i,2=1.1111111111111 0\n' - b'foo,hello=there 0=\"2\",1=2i,2=2.2222222222222 3600000000000\n' - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - cli.write_points(dataframe, "foo", {"hello": "there"}) - - self.assertEqual(m.last_request.body, expected_default_precision) - - cli = DataFrameClient(database='db') - cli.write_points(dataframe, "foo", {"hello": "there"}, - numeric_precision=4) - - self.assertEqual(m.last_request.body, expected_specified_precision) - - cli = DataFrameClient(database='db') - cli.write_points(dataframe, "foo", {"hello": "there"}, - numeric_precision='full') - - self.assertEqual(m.last_request.body, expected_full_precision) - - def test_write_points_from_dataframe_with_period_index(self): - """Test write points from df with period index.""" - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[pd.Period('1970-01-01'), - pd.Period('1970-01-02')], - columns=["column_one", "column_two", - "column_three"]) - - expected = ( - b"foo column_one=\"1\",column_two=1i,column_three=1.0 0\n" - b"foo column_one=\"2\",column_two=2i,column_three=2.0 " - b"86400000000000\n" - ) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - cli.write_points(dataframe, "foo") - - self.assertEqual(m.last_request.body, expected) - - def test_write_points_from_dataframe_with_time_precision(self): - """Test write points from df with time precision.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/write", - status_code=204) - - cli = DataFrameClient(database='db') - measurement = "foo" - - cli.write_points(dataframe, measurement, time_precision='h') - self.assertEqual(m.last_request.qs['precision'], ['h']) - self.assertEqual( - b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' - b'column_one="2",column_two=2i,column_three=2.0 1\n', - m.last_request.body, - ) - - cli.write_points(dataframe, measurement, time_precision='m') - self.assertEqual(m.last_request.qs['precision'], ['m']) - self.assertEqual( - b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' - b'column_one="2",column_two=2i,column_three=2.0 60\n', - m.last_request.body, - ) - - cli.write_points(dataframe, measurement, time_precision='s') - self.assertEqual(m.last_request.qs['precision'], ['s']) - self.assertEqual( - b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' - b'column_one="2",column_two=2i,column_three=2.0 3600\n', - m.last_request.body, - ) - - cli.write_points(dataframe, measurement, time_precision='ms') - self.assertEqual(m.last_request.qs['precision'], ['ms']) - self.assertEqual( - b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' - b'column_one="2",column_two=2i,column_three=2.0 3600000\n', - m.last_request.body, - ) - - cli.write_points(dataframe, measurement, time_precision='u') - self.assertEqual(m.last_request.qs['precision'], ['u']) - self.assertEqual( - b'foo column_one="1",column_two=1i,column_three=1.0 0\nfoo ' - b'column_one="2",column_two=2i,column_three=2.0 3600000000\n', - m.last_request.body, - ) - - cli.write_points(dataframe, measurement, time_precision='n') - self.assertEqual(m.last_request.qs['precision'], ['n']) - self.assertEqual( - b'foo column_one="1",column_two=1i,column_three=1.0 0\n' - b'foo column_one="2",column_two=2i,column_three=2.0 ' - b'3600000000000\n', - m.last_request.body, - ) - - @raises(TypeError) - def test_write_points_from_dataframe_fails_without_time_index(self): - """Test failed write points from df without time index.""" - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - columns=["column_one", "column_two", - "column_three"]) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series", - status_code=204) - - cli = DataFrameClient(database='db') - cli.write_points(dataframe, "foo") - - @raises(TypeError) - def test_write_points_from_dataframe_fails_with_series(self): - """Test failed write points from df with series.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.Series(data=[1.0, 2.0], - index=[now, now + timedelta(hours=1)]) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series", - status_code=204) - - cli = DataFrameClient(database='db') - cli.write_points(dataframe, "foo") - - def test_query_into_dataframe(self): - """Test query into df for TestDataFrameClient object.""" - data = { - "results": [{ - "series": [ - {"measurement": "network", - "tags": {"direction": ""}, - "columns": ["time", "value"], - "values":[["2009-11-10T23:00:00Z", 23422]] - }, - {"measurement": "network", - "tags": {"direction": "in"}, - "columns": ["time", "value"], - "values": [["2009-11-10T23:00:00Z", 23422], - ["2009-11-10T23:00:00Z", 23422], - ["2009-11-10T23:00:00Z", 23422]] - } - ] - }] - } - - pd1 = pd.DataFrame( - [[23422]], columns=['value'], - index=pd.to_datetime(["2009-11-10T23:00:00Z"])) - pd1.index = pd1.index.tz_localize('UTC') - pd2 = pd.DataFrame( - [[23422], [23422], [23422]], columns=['value'], - index=pd.to_datetime(["2009-11-10T23:00:00Z", - "2009-11-10T23:00:00Z", - "2009-11-10T23:00:00Z"])) - pd2.index = pd2.index.tz_localize('UTC') - expected = { - ('network', (('direction', ''),)): pd1, - ('network', (('direction', 'in'),)): pd2 - } - - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - with _mocked_session(cli, 'GET', 200, data): - result = cli.query('select value from network group by direction;') - for k in expected: - assert_frame_equal(expected[k], result[k]) - - def test_multiquery_into_dataframe(self): - """Test multiquyer into df for TestDataFrameClient object.""" - data = { - "results": [ - { - "series": [ - { - "name": "cpu_load_short", - "columns": ["time", "value"], - "values": [ - ["2015-01-29T21:55:43.702900257Z", 0.55], - ["2015-01-29T21:55:43.702900257Z", 23422], - ["2015-06-11T20:46:02Z", 0.64] - ] - } - ] - }, { - "series": [ - { - "name": "cpu_load_short", - "columns": ["time", "count"], - "values": [ - ["1970-01-01T00:00:00Z", 3] - ] - } - ] - } - ] - } - - pd1 = pd.DataFrame( - [[0.55], [23422.0], [0.64]], columns=['value'], - index=pd.to_datetime([ - "2015-01-29 21:55:43.702900257+0000", - "2015-01-29 21:55:43.702900257+0000", - "2015-06-11 20:46:02+0000"])).tz_localize('UTC') - pd2 = pd.DataFrame( - [[3]], columns=['count'], - index=pd.to_datetime(["1970-01-01 00:00:00+00:00"]))\ - .tz_localize('UTC') - expected = [{'cpu_load_short': pd1}, {'cpu_load_short': pd2}] - - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - iql = "SELECT value FROM cpu_load_short WHERE region='us-west';"\ - "SELECT count(value) FROM cpu_load_short WHERE region='us-west'" - with _mocked_session(cli, 'GET', 200, data): - result = cli.query(iql) - for r, e in zip(result, expected): - for k in e: - assert_frame_equal(e[k], r[k]) - - def test_query_with_empty_result(self): - """Test query with empty results in TestDataFrameClient object.""" - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - with _mocked_session(cli, 'GET', 200, {"results": [{}]}): - result = cli.query('select column_one from foo;') - self.assertEqual(result, {}) - - def test_get_list_database(self): - """Test get list of databases in TestDataFrameClient object.""" - data = {'results': [ - {'series': [ - {'measurement': 'databases', - 'values': [ - ['new_db_1'], - ['new_db_2']], - 'columns': ['name']}]} - ]} - - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - with _mocked_session(cli, 'get', 200, json.dumps(data)): - self.assertListEqual( - cli.get_list_database(), - [{'name': 'new_db_1'}, {'name': 'new_db_2'}] - ) - - def test_datetime_to_epoch(self): - """Test convert datetime to epoch in TestDataFrameClient object.""" - timestamp = pd.Timestamp('2013-01-01 00:00:00.000+00:00') - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - - self.assertEqual( - cli._datetime_to_epoch(timestamp), - 1356998400.0 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='h'), - 1356998400.0 / 3600 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='m'), - 1356998400.0 / 60 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='s'), - 1356998400.0 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='ms'), - 1356998400000.0 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='u'), - 1356998400000000.0 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='n'), - 1356998400000000000.0 - ) - - def test_dsn_constructor(self): - """Test data source name deconstructor in TestDataFrameClient.""" - client = DataFrameClient.from_dsn('influxdb://localhost:8086') - self.assertIsInstance(client, DataFrameClient) - self.assertEqual('http://localhost:8086', client._baseurl) diff --git a/lib/influxdb/tests/helper_test.py b/lib/influxdb/tests/helper_test.py deleted file mode 100644 index 6f24e85..0000000 --- a/lib/influxdb/tests/helper_test.py +++ /dev/null @@ -1,367 +0,0 @@ -# -*- coding: utf-8 -*- -"""Set of series helper functions for test.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from datetime import datetime, timedelta - -import unittest -import warnings - -import mock -from influxdb import SeriesHelper, InfluxDBClient -from requests.exceptions import ConnectionError - - -class TestSeriesHelper(unittest.TestCase): - """Define the SeriesHelper test object.""" - - @classmethod - def setUpClass(cls): - """Set up the TestSeriesHelper object.""" - super(TestSeriesHelper, cls).setUpClass() - - TestSeriesHelper.client = InfluxDBClient( - 'host', - 8086, - 'username', - 'password', - 'database' - ) - - class MySeriesHelper(SeriesHelper): - """Define a SeriesHelper object.""" - - class Meta: - """Define metadata for the SeriesHelper object.""" - - client = TestSeriesHelper.client - series_name = 'events.stats.{server_name}' - fields = ['some_stat'] - tags = ['server_name', 'other_tag'] - bulk_size = 5 - autocommit = True - - TestSeriesHelper.MySeriesHelper = MySeriesHelper - - def tearDown(self): - """Deconstruct the TestSeriesHelper object.""" - super(TestSeriesHelper, self).tearDown() - TestSeriesHelper.MySeriesHelper._reset_() - self.assertEqual( - TestSeriesHelper.MySeriesHelper._json_body_(), - [], - 'Resetting helper did not empty datapoints.') - - def test_auto_commit(self): - """Test write_points called after valid number of events.""" - class AutoCommitTest(SeriesHelper): - """Define a SeriesHelper instance to test autocommit.""" - - class Meta: - """Define metadata for AutoCommitTest.""" - - series_name = 'events.stats.{server_name}' - fields = ['some_stat'] - tags = ['server_name', 'other_tag'] - bulk_size = 5 - client = InfluxDBClient() - autocommit = True - - fake_write_points = mock.MagicMock() - AutoCommitTest(server_name='us.east-1', some_stat=159, other_tag='gg') - AutoCommitTest._client.write_points = fake_write_points - AutoCommitTest(server_name='us.east-1', some_stat=158, other_tag='gg') - AutoCommitTest(server_name='us.east-1', some_stat=157, other_tag='gg') - AutoCommitTest(server_name='us.east-1', some_stat=156, other_tag='gg') - self.assertFalse(fake_write_points.called) - AutoCommitTest(server_name='us.east-1', some_stat=3443, other_tag='gg') - self.assertTrue(fake_write_points.called) - - @mock.patch('influxdb.helper.SeriesHelper._current_timestamp') - def testSingleSeriesName(self, current_timestamp): - """Test JSON conversion when there is only one series name.""" - current_timestamp.return_value = current_date = datetime.today() - TestSeriesHelper.MySeriesHelper( - server_name='us.east-1', other_tag='ello', some_stat=159) - TestSeriesHelper.MySeriesHelper( - server_name='us.east-1', other_tag='ello', some_stat=158) - TestSeriesHelper.MySeriesHelper( - server_name='us.east-1', other_tag='ello', some_stat=157) - TestSeriesHelper.MySeriesHelper( - server_name='us.east-1', other_tag='ello', some_stat=156) - expectation = [ - { - "measurement": "events.stats.us.east-1", - "tags": { - "other_tag": "ello", - "server_name": "us.east-1" - }, - "fields": { - "some_stat": 159 - }, - "time": current_date, - }, - { - "measurement": "events.stats.us.east-1", - "tags": { - "other_tag": "ello", - "server_name": "us.east-1" - }, - "fields": { - "some_stat": 158 - }, - "time": current_date, - }, - { - "measurement": "events.stats.us.east-1", - "tags": { - "other_tag": "ello", - "server_name": "us.east-1" - }, - "fields": { - "some_stat": 157 - }, - "time": current_date, - }, - { - "measurement": "events.stats.us.east-1", - "tags": { - "other_tag": "ello", - "server_name": "us.east-1" - }, - "fields": { - "some_stat": 156 - }, - "time": current_date, - } - ] - - rcvd = TestSeriesHelper.MySeriesHelper._json_body_() - self.assertTrue(all([el in expectation for el in rcvd]) and - all([el in rcvd for el in expectation]), - 'Invalid JSON body of time series returned from ' - '_json_body_ for one series name: {0}.'.format(rcvd)) - - @mock.patch('influxdb.helper.SeriesHelper._current_timestamp') - def testSeveralSeriesNames(self, current_timestamp): - """Test JSON conversion when there are multiple series names.""" - current_timestamp.return_value = current_date = datetime.today() - TestSeriesHelper.MySeriesHelper( - server_name='us.east-1', some_stat=159, other_tag='ello') - TestSeriesHelper.MySeriesHelper( - server_name='fr.paris-10', some_stat=158, other_tag='ello') - TestSeriesHelper.MySeriesHelper( - server_name='lu.lux', some_stat=157, other_tag='ello') - TestSeriesHelper.MySeriesHelper( - server_name='uk.london', some_stat=156, other_tag='ello') - expectation = [ - { - 'fields': { - 'some_stat': 157 - }, - 'measurement': 'events.stats.lu.lux', - 'tags': { - 'other_tag': 'ello', - 'server_name': 'lu.lux' - }, - "time": current_date, - }, - { - 'fields': { - 'some_stat': 156 - }, - 'measurement': 'events.stats.uk.london', - 'tags': { - 'other_tag': 'ello', - 'server_name': 'uk.london' - }, - "time": current_date, - }, - { - 'fields': { - 'some_stat': 158 - }, - 'measurement': 'events.stats.fr.paris-10', - 'tags': { - 'other_tag': 'ello', - 'server_name': 'fr.paris-10' - }, - "time": current_date, - }, - { - 'fields': { - 'some_stat': 159 - }, - 'measurement': 'events.stats.us.east-1', - 'tags': { - 'other_tag': 'ello', - 'server_name': 'us.east-1' - }, - "time": current_date, - } - ] - - rcvd = TestSeriesHelper.MySeriesHelper._json_body_() - self.assertTrue(all([el in expectation for el in rcvd]) and - all([el in rcvd for el in expectation]), - 'Invalid JSON body of time series returned from ' - '_json_body_ for several series names: {0}.' - .format(rcvd)) - - @mock.patch('influxdb.helper.SeriesHelper._current_timestamp') - def testSeriesWithoutTimeField(self, current_timestamp): - """Test that time is optional on a series without a time field.""" - current_date = datetime.today() - yesterday = current_date - timedelta(days=1) - current_timestamp.return_value = yesterday - TestSeriesHelper.MySeriesHelper( - server_name='us.east-1', other_tag='ello', - some_stat=159, time=current_date - ) - TestSeriesHelper.MySeriesHelper( - server_name='us.east-1', other_tag='ello', - some_stat=158, - ) - point1, point2 = TestSeriesHelper.MySeriesHelper._json_body_() - self.assertTrue('time' in point1 and 'time' in point2) - self.assertEqual(point1['time'], current_date) - self.assertEqual(point2['time'], yesterday) - - def testSeriesWithoutAllTags(self): - """Test that creating a data point without a tag throws an error.""" - class MyTimeFieldSeriesHelper(SeriesHelper): - - class Meta: - client = TestSeriesHelper.client - series_name = 'events.stats.{server_name}' - fields = ['some_stat', 'time'] - tags = ['server_name', 'other_tag'] - bulk_size = 5 - autocommit = True - - self.assertRaises(NameError, MyTimeFieldSeriesHelper, - **{"server_name": 'us.east-1', - "some_stat": 158}) - - @mock.patch('influxdb.helper.SeriesHelper._current_timestamp') - def testSeriesWithTimeField(self, current_timestamp): - """Test that time is optional on a series with a time field.""" - current_date = datetime.today() - yesterday = current_date - timedelta(days=1) - current_timestamp.return_value = yesterday - - class MyTimeFieldSeriesHelper(SeriesHelper): - - class Meta: - client = TestSeriesHelper.client - series_name = 'events.stats.{server_name}' - fields = ['some_stat', 'time'] - tags = ['server_name', 'other_tag'] - bulk_size = 5 - autocommit = True - - MyTimeFieldSeriesHelper( - server_name='us.east-1', other_tag='ello', - some_stat=159, time=current_date - ) - MyTimeFieldSeriesHelper( - server_name='us.east-1', other_tag='ello', - some_stat=158, - ) - point1, point2 = MyTimeFieldSeriesHelper._json_body_() - self.assertTrue('time' in point1 and 'time' in point2) - self.assertEqual(point1['time'], current_date) - self.assertEqual(point2['time'], yesterday) - - def testInvalidHelpers(self): - """Test errors in invalid helpers.""" - class MissingMeta(SeriesHelper): - """Define instance of SeriesHelper for missing meta.""" - - pass - - class MissingClient(SeriesHelper): - """Define SeriesHelper for missing client data.""" - - class Meta: - """Define metadat for MissingClient.""" - - series_name = 'events.stats.{server_name}' - fields = ['time', 'server_name'] - autocommit = True - - class MissingSeriesName(SeriesHelper): - """Define instance of SeriesHelper for missing series.""" - - class Meta: - """Define metadata for MissingSeriesName.""" - - fields = ['time', 'server_name'] - - class MissingFields(SeriesHelper): - """Define instance of SeriesHelper for missing fields.""" - - class Meta: - """Define metadata for MissingFields.""" - - series_name = 'events.stats.{server_name}' - - for cls in [MissingMeta, MissingClient, MissingFields, - MissingSeriesName]: - self.assertRaises( - AttributeError, cls, **{'time': 159, - 'server_name': 'us.east-1'}) - - @unittest.skip("Fails on py32") - def testWarnBulkSizeZero(self): - """Test warning for an invalid bulk size.""" - class WarnBulkSizeZero(SeriesHelper): - - class Meta: - client = TestSeriesHelper.client - series_name = 'events.stats.{server_name}' - fields = ['time', 'server_name'] - tags = [] - bulk_size = 0 - autocommit = True - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - try: - WarnBulkSizeZero(time=159, server_name='us.east-1') - except ConnectionError: - # Server defined in the client is invalid, we're testing - # the warning only. - pass - self.assertEqual(len(w), 1, - '{0} call should have generated one warning.' - .format(WarnBulkSizeZero)) - self.assertIn('forced to 1', str(w[-1].message), - 'Warning message did not contain "forced to 1".') - - def testWarnBulkSizeNoEffect(self): - """Test warning for a set bulk size but autocommit False.""" - class WarnBulkSizeNoEffect(SeriesHelper): - """Define SeriesHelper for warning on bulk size.""" - - class Meta: - """Define metadat for WarnBulkSizeNoEffect.""" - - series_name = 'events.stats.{server_name}' - fields = ['time', 'server_name'] - bulk_size = 5 - tags = [] - autocommit = False - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - WarnBulkSizeNoEffect(time=159, server_name='us.east-1') - self.assertEqual(len(w), 1, - '{0} call should have generated one warning.' - .format(WarnBulkSizeNoEffect)) - self.assertIn('has no affect', str(w[-1].message), - 'Warning message did not contain "has not affect".') diff --git a/lib/influxdb/tests/influxdb08/__init__.py b/lib/influxdb/tests/influxdb08/__init__.py deleted file mode 100644 index 0e79ed1..0000000 --- a/lib/influxdb/tests/influxdb08/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define the influxdb08 test package.""" diff --git a/lib/influxdb/tests/influxdb08/client_test.py b/lib/influxdb/tests/influxdb08/client_test.py deleted file mode 100644 index 39ab52d..0000000 --- a/lib/influxdb/tests/influxdb08/client_test.py +++ /dev/null @@ -1,904 +0,0 @@ -# -*- coding: utf-8 -*- -"""Client unit tests.""" - -import json -import socket -import sys -import unittest -import random -import warnings - -import mock -import requests -import requests.exceptions -import requests_mock - -from nose.tools import raises -from mock import patch - -from influxdb.influxdb08 import InfluxDBClient -from influxdb.influxdb08.client import session - -if sys.version < '3': - import codecs - - def u(x): - """Test codec.""" - return codecs.unicode_escape_decode(x)[0] -else: - def u(x): - """Test codec.""" - return x - - -def _build_response_object(status_code=200, content=""): - resp = requests.Response() - resp.status_code = status_code - resp._content = content.encode("utf8") - return resp - - -def _mocked_session(method="GET", status_code=200, content=""): - method = method.upper() - - def request(*args, **kwargs): - """Define a request for the _mocked_session.""" - c = content - - # Check method - assert method == kwargs.get('method', 'GET') - - if method == 'POST': - data = kwargs.get('data', None) - - if data is not None: - # Data must be a string - assert isinstance(data, str) - - # Data must be a JSON string - assert c == json.loads(data, strict=True) - - c = data - - # Anyway, Content must be a JSON string (or empty string) - if not isinstance(c, str): - c = json.dumps(c) - - return _build_response_object(status_code=status_code, content=c) - - mocked = patch.object( - session, - 'request', - side_effect=request - ) - - return mocked - - -class TestInfluxDBClient(unittest.TestCase): - """Define a TestInfluxDBClient object.""" - - def setUp(self): - """Set up a TestInfluxDBClient object.""" - # By default, raise exceptions on warnings - warnings.simplefilter('error', FutureWarning) - - self.dummy_points = [ - { - "points": [ - ["1", 1, 1.0], - ["2", 2, 2.0] - ], - "name": "foo", - "columns": ["column_one", "column_two", "column_three"] - } - ] - - self.dsn_string = 'influxdb://uSr:pWd@host:1886/db' - - def test_scheme(self): - """Test database scheme for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') - self.assertEqual(cli._baseurl, 'http://host:8086') - - cli = InfluxDBClient( - 'host', 8086, 'username', 'password', 'database', ssl=True - ) - self.assertEqual(cli._baseurl, 'https://host:8086') - - def test_dsn(self): - """Test datasource name for TestInfluxDBClient object.""" - cli = InfluxDBClient.from_dsn(self.dsn_string) - self.assertEqual('http://host:1886', cli._baseurl) - self.assertEqual('uSr', cli._username) - self.assertEqual('pWd', cli._password) - self.assertEqual('db', cli._database) - self.assertFalse(cli._use_udp) - - cli = InfluxDBClient.from_dsn('udp+' + self.dsn_string) - self.assertTrue(cli._use_udp) - - cli = InfluxDBClient.from_dsn('https+' + self.dsn_string) - self.assertEqual('https://host:1886', cli._baseurl) - - cli = InfluxDBClient.from_dsn('https+' + self.dsn_string, - **{'ssl': False}) - self.assertEqual('http://host:1886', cli._baseurl) - - def test_switch_database(self): - """Test switch database for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') - cli.switch_database('another_database') - self.assertEqual(cli._database, 'another_database') - - @raises(FutureWarning) - def test_switch_db_deprecated(self): - """Test deprecated switch database for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') - cli.switch_db('another_database') - self.assertEqual(cli._database, 'another_database') - - def test_switch_user(self): - """Test switch user for TestInfluxDBClient object.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'database') - cli.switch_user('another_username', 'another_password') - self.assertEqual(cli._username, 'another_username') - self.assertEqual(cli._password, 'another_password') - - def test_write(self): - """Test write to database for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/write" - ) - cli = InfluxDBClient(database='db') - cli.write( - {"database": "mydb", - "retentionPolicy": "mypolicy", - "points": [{"name": "cpu_load_short", - "tags": {"host": "server01", - "region": "us-west"}, - "timestamp": "2009-11-10T23:00:00Z", - "values": {"value": 0.64}}]} - ) - - self.assertEqual( - json.loads(m.last_request.body), - {"database": "mydb", - "retentionPolicy": "mypolicy", - "points": [{"name": "cpu_load_short", - "tags": {"host": "server01", - "region": "us-west"}, - "timestamp": "2009-11-10T23:00:00Z", - "values": {"value": 0.64}}]} - ) - - def test_write_points(self): - """Test write points for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/db/db/series" - ) - - cli = InfluxDBClient(database='db') - cli.write_points( - self.dummy_points - ) - - self.assertListEqual( - json.loads(m.last_request.body), - self.dummy_points - ) - - def test_write_points_string(self): - """Test write string points for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/db/db/series" - ) - - cli = InfluxDBClient(database='db') - cli.write_points( - str(json.dumps(self.dummy_points)) - ) - - self.assertListEqual( - json.loads(m.last_request.body), - self.dummy_points - ) - - def test_write_points_batch(self): - """Test write batch points for TestInfluxDBClient object.""" - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - cli = InfluxDBClient('localhost', 8086, - 'username', 'password', 'db') - cli.write_points(data=self.dummy_points, batch_size=2) - self.assertEqual(1, m.call_count) - - def test_write_points_batch_invalid_size(self): - """Test write batch points invalid size for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - cli = InfluxDBClient('localhost', 8086, - 'username', 'password', 'db') - cli.write_points(data=self.dummy_points, batch_size=-2) - self.assertEqual(1, m.call_count) - - def test_write_points_batch_multiple_series(self): - """Test write points batch multiple series.""" - dummy_points = [ - {"points": [["1", 1, 1.0], ["2", 2, 2.0], ["3", 3, 3.0], - ["4", 4, 4.0], ["5", 5, 5.0]], - "name": "foo", - "columns": ["val1", "val2", "val3"]}, - {"points": [["1", 1, 1.0], ["2", 2, 2.0], ["3", 3, 3.0], - ["4", 4, 4.0], ["5", 5, 5.0], ["6", 6, 6.0], - ["7", 7, 7.0], ["8", 8, 8.0]], - "name": "bar", - "columns": ["val1", "val2", "val3"]}, - ] - expected_last_body = [{'points': [['7', 7, 7.0], ['8', 8, 8.0]], - 'name': 'bar', - 'columns': ['val1', 'val2', 'val3']}] - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - cli = InfluxDBClient('localhost', 8086, - 'username', 'password', 'db') - cli.write_points(data=dummy_points, batch_size=3) - self.assertEqual(m.call_count, 5) - self.assertEqual(expected_last_body, m.request_history[4].json()) - - def test_write_points_udp(self): - """Test write points UDP for TestInfluxDBClient object.""" - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - port = random.randint(4000, 8000) - s.bind(('0.0.0.0', port)) - - cli = InfluxDBClient( - 'localhost', 8086, 'root', 'root', - 'test', use_udp=True, udp_port=port - ) - cli.write_points(self.dummy_points) - - received_data, addr = s.recvfrom(1024) - - self.assertEqual(self.dummy_points, - json.loads(received_data.decode(), strict=True)) - - def test_write_bad_precision_udp(self): - """Test write UDP w/bad precision.""" - cli = InfluxDBClient( - 'localhost', 8086, 'root', 'root', - 'test', use_udp=True, udp_port=4444 - ) - - with self.assertRaisesRegexp( - Exception, - "InfluxDB only supports seconds precision for udp writes" - ): - cli.write_points( - self.dummy_points, - time_precision='ms' - ) - - @raises(Exception) - def test_write_points_fails(self): - """Test failed write points for TestInfluxDBClient object.""" - with _mocked_session('post', 500): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.write_points([]) - - def test_write_points_with_precision(self): - """Test write points with precision.""" - with _mocked_session('post', 200, self.dummy_points): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - self.assertTrue(cli.write_points(self.dummy_points)) - - def test_write_points_bad_precision(self): - """Test write points with bad precision.""" - cli = InfluxDBClient() - with self.assertRaisesRegexp( - Exception, - "Invalid time precision is given. \(use 's', 'm', 'ms' or 'u'\)" - ): - cli.write_points( - self.dummy_points, - time_precision='g' - ) - - @raises(Exception) - def test_write_points_with_precision_fails(self): - """Test write points where precision fails.""" - with _mocked_session('post', 500): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.write_points_with_precision([]) - - def test_delete_points(self): - """Test delete points for TestInfluxDBClient object.""" - with _mocked_session('delete', 204) as mocked: - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - self.assertTrue(cli.delete_points("foo")) - - self.assertEqual(len(mocked.call_args_list), 1) - args, kwds = mocked.call_args_list[0] - - self.assertEqual(kwds['params'], - {'u': 'username', 'p': 'password'}) - self.assertEqual(kwds['url'], 'http://host:8086/db/db/series/foo') - - @raises(Exception) - def test_delete_points_with_wrong_name(self): - """Test delete points with wrong name.""" - with _mocked_session('delete', 400): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.delete_points("nonexist") - - @raises(NotImplementedError) - def test_create_scheduled_delete(self): - """Test create scheduled deletes.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.create_scheduled_delete([]) - - @raises(NotImplementedError) - def test_get_list_scheduled_delete(self): - """Test get schedule list of deletes TestInfluxDBClient.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.get_list_scheduled_delete() - - @raises(NotImplementedError) - def test_remove_scheduled_delete(self): - """Test remove scheduled delete TestInfluxDBClient.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.remove_scheduled_delete(1) - - def test_query(self): - """Test query for TestInfluxDBClient object.""" - data = [ - { - "name": "foo", - "columns": ["time", "sequence_number", "column_one"], - "points": [ - [1383876043, 16, "2"], [1383876043, 15, "1"], - [1383876035, 14, "2"], [1383876035, 13, "1"] - ] - } - ] - with _mocked_session('get', 200, data): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - result = cli.query('select column_one from foo;') - self.assertEqual(len(result[0]['points']), 4) - - def test_query_chunked(self): - """Test chunked query for TestInfluxDBClient object.""" - cli = InfluxDBClient(database='db') - example_object = { - 'points': [ - [1415206250119, 40001, 667], - [1415206244555, 30001, 7], - [1415206228241, 20001, 788], - [1415206212980, 10001, 555], - [1415197271586, 10001, 23] - ], - 'name': 'foo', - 'columns': [ - 'time', - 'sequence_number', - 'val' - ] - } - example_response = \ - json.dumps(example_object) + json.dumps(example_object) - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/db/db/series", - text=example_response - ) - - self.assertListEqual( - cli.query('select * from foo', chunked=True), - [example_object, example_object] - ) - - def test_query_chunked_unicode(self): - """Test unicode chunked query for TestInfluxDBClient object.""" - cli = InfluxDBClient(database='db') - example_object = { - 'points': [ - [1415206212980, 10001, u('unicode-\xcf\x89')], - [1415197271586, 10001, u('more-unicode-\xcf\x90')] - ], - 'name': 'foo', - 'columns': [ - 'time', - 'sequence_number', - 'val' - ] - } - example_response = \ - json.dumps(example_object) + json.dumps(example_object) - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/db/db/series", - text=example_response - ) - - self.assertListEqual( - cli.query('select * from foo', chunked=True), - [example_object, example_object] - ) - - @raises(Exception) - def test_query_fail(self): - """Test failed query for TestInfluxDBClient.""" - with _mocked_session('get', 401): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.query('select column_one from foo;') - - def test_query_bad_precision(self): - """Test query with bad precision for TestInfluxDBClient.""" - cli = InfluxDBClient() - with self.assertRaisesRegexp( - Exception, - "Invalid time precision is given. \(use 's', 'm', 'ms' or 'u'\)" - ): - cli.query('select column_one from foo', time_precision='g') - - def test_create_database(self): - """Test create database for TestInfluxDBClient.""" - with _mocked_session('post', 201, {"name": "new_db"}): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - self.assertTrue(cli.create_database('new_db')) - - @raises(Exception) - def test_create_database_fails(self): - """Test failed create database for TestInfluxDBClient.""" - with _mocked_session('post', 401): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.create_database('new_db') - - def test_delete_database(self): - """Test delete database for TestInfluxDBClient.""" - with _mocked_session('delete', 204): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - self.assertTrue(cli.delete_database('old_db')) - - @raises(Exception) - def test_delete_database_fails(self): - """Test failed delete database for TestInfluxDBClient.""" - with _mocked_session('delete', 401): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.delete_database('old_db') - - def test_get_list_database(self): - """Test get list of databases for TestInfluxDBClient.""" - data = [ - {"name": "a_db"} - ] - with _mocked_session('get', 200, data): - cli = InfluxDBClient('host', 8086, 'username', 'password') - self.assertEqual(len(cli.get_list_database()), 1) - self.assertEqual(cli.get_list_database()[0]['name'], 'a_db') - - @raises(Exception) - def test_get_list_database_fails(self): - """Test failed get list of databases for TestInfluxDBClient.""" - with _mocked_session('get', 401): - cli = InfluxDBClient('host', 8086, 'username', 'password') - cli.get_list_database() - - @raises(FutureWarning) - def test_get_database_list_deprecated(self): - """Test deprecated get database list for TestInfluxDBClient.""" - data = [ - {"name": "a_db"} - ] - with _mocked_session('get', 200, data): - cli = InfluxDBClient('host', 8086, 'username', 'password') - self.assertEqual(len(cli.get_database_list()), 1) - self.assertEqual(cli.get_database_list()[0]['name'], 'a_db') - - def test_delete_series(self): - """Test delete series for TestInfluxDBClient.""" - with _mocked_session('delete', 204): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.delete_series('old_series') - - @raises(Exception) - def test_delete_series_fails(self): - """Test failed delete series for TestInfluxDBClient.""" - with _mocked_session('delete', 401): - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.delete_series('old_series') - - def test_get_series_list(self): - """Test get list of series for TestInfluxDBClient.""" - cli = InfluxDBClient(database='db') - - with requests_mock.Mocker() as m: - example_response = \ - '[{"name":"list_series_result","columns":' \ - '["time","name"],"points":[[0,"foo"],[0,"bar"]]}]' - - m.register_uri( - requests_mock.GET, - "http://localhost:8086/db/db/series", - text=example_response - ) - - self.assertListEqual( - cli.get_list_series(), - ['foo', 'bar'] - ) - - def test_get_continuous_queries(self): - """Test get continuous queries for TestInfluxDBClient.""" - cli = InfluxDBClient(database='db') - - with requests_mock.Mocker() as m: - - # Tip: put this in a json linter! - example_response = '[ { "name": "continuous queries", "columns"' \ - ': [ "time", "id", "query" ], "points": [ [ ' \ - '0, 1, "select foo(bar,95) from \\"foo_bar' \ - 's\\" group by time(5m) into response_times.' \ - 'percentiles.5m.95" ], [ 0, 2, "select perce' \ - 'ntile(value,95) from \\"response_times\\" g' \ - 'roup by time(5m) into response_times.percen' \ - 'tiles.5m.95" ] ] } ]' - - m.register_uri( - requests_mock.GET, - "http://localhost:8086/db/db/series", - text=example_response - ) - - self.assertListEqual( - cli.get_list_continuous_queries(), - [ - 'select foo(bar,95) from "foo_bars" group ' - 'by time(5m) into response_times.percentiles.5m.95', - - 'select percentile(value,95) from "response_times" group ' - 'by time(5m) into response_times.percentiles.5m.95' - ] - ) - - def test_get_list_cluster_admins(self): - """Test get list of cluster admins, not implemented.""" - pass - - def test_add_cluster_admin(self): - """Test add cluster admin for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/cluster_admins" - ) - - cli = InfluxDBClient(database='db') - cli.add_cluster_admin( - new_username='paul', - new_password='laup' - ) - - self.assertDictEqual( - json.loads(m.last_request.body), - { - 'name': 'paul', - 'password': 'laup' - } - ) - - def test_update_cluster_admin_password(self): - """Test update cluster admin pass for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/cluster_admins/paul" - ) - - cli = InfluxDBClient(database='db') - cli.update_cluster_admin_password( - username='paul', - new_password='laup' - ) - - self.assertDictEqual( - json.loads(m.last_request.body), - {'password': 'laup'} - ) - - def test_delete_cluster_admin(self): - """Test delete cluster admin for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.DELETE, - "http://localhost:8086/cluster_admins/paul", - status_code=200, - ) - - cli = InfluxDBClient(database='db') - cli.delete_cluster_admin(username='paul') - - self.assertIsNone(m.last_request.body) - - def test_set_database_admin(self): - """Test set database admin for TestInfluxDBClient.""" - pass - - def test_unset_database_admin(self): - """Test unset database admin for TestInfluxDBClient.""" - pass - - def test_alter_database_admin(self): - """Test alter database admin for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/db/db/users/paul" - ) - - cli = InfluxDBClient(database='db') - cli.alter_database_admin( - username='paul', - is_admin=False - ) - - self.assertDictEqual( - json.loads(m.last_request.body), - { - 'admin': False - } - ) - - @raises(NotImplementedError) - def test_get_list_database_admins(self): - """Test get list of database admins for TestInfluxDBClient.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.get_list_database_admins() - - @raises(NotImplementedError) - def test_add_database_admin(self): - """Test add database admins for TestInfluxDBClient.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.add_database_admin('admin', 'admin_secret_password') - - @raises(NotImplementedError) - def test_update_database_admin_password(self): - """Test update database admin pass for TestInfluxDBClient.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.update_database_admin_password('admin', 'admin_secret_password') - - @raises(NotImplementedError) - def test_delete_database_admin(self): - """Test delete database admin for TestInfluxDBClient.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.delete_database_admin('admin') - - def test_get_database_users(self): - """Test get database users for TestInfluxDBClient.""" - cli = InfluxDBClient('localhost', 8086, 'username', 'password', 'db') - - example_response = \ - '[{"name":"paul","isAdmin":false,"writeTo":".*","readFrom":".*"},'\ - '{"name":"bobby","isAdmin":false,"writeTo":".*","readFrom":".*"}]' - - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.GET, - "http://localhost:8086/db/db/users", - text=example_response - ) - users = cli.get_database_users() - - self.assertEqual(json.loads(example_response), users) - - def test_add_database_user(self): - """Test add database user for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/db/db/users" - ) - cli = InfluxDBClient(database='db') - cli.add_database_user( - new_username='paul', - new_password='laup', - permissions=('.*', '.*') - ) - - self.assertDictEqual( - json.loads(m.last_request.body), - { - 'writeTo': '.*', - 'password': 'laup', - 'readFrom': '.*', - 'name': 'paul' - } - ) - - def test_add_database_user_bad_permissions(self): - """Test add database user with bad perms for TestInfluxDBClient.""" - cli = InfluxDBClient() - - with self.assertRaisesRegexp( - Exception, - "'permissions' must be \(readFrom, writeTo\) tuple" - ): - cli.add_database_user( - new_password='paul', - new_username='paul', - permissions=('hello', 'hello', 'hello') - ) - - def test_alter_database_user_password(self): - """Test alter database user pass for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/db/db/users/paul" - ) - - cli = InfluxDBClient(database='db') - cli.alter_database_user( - username='paul', - password='n3wp4ss!' - ) - - self.assertDictEqual( - json.loads(m.last_request.body), - { - 'password': 'n3wp4ss!' - } - ) - - def test_alter_database_user_permissions(self): - """Test alter database user perms for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/db/db/users/paul" - ) - - cli = InfluxDBClient(database='db') - cli.alter_database_user( - username='paul', - permissions=('^$', '.*') - ) - - self.assertDictEqual( - json.loads(m.last_request.body), - { - 'readFrom': '^$', - 'writeTo': '.*' - } - ) - - def test_alter_database_user_password_and_permissions(self): - """Test alter database user pass and perms for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/db/db/users/paul" - ) - - cli = InfluxDBClient(database='db') - cli.alter_database_user( - username='paul', - password='n3wp4ss!', - permissions=('^$', '.*') - ) - - self.assertDictEqual( - json.loads(m.last_request.body), - { - 'password': 'n3wp4ss!', - 'readFrom': '^$', - 'writeTo': '.*' - } - ) - - def test_update_database_user_password_current_user(self): - """Test update database user pass for TestInfluxDBClient.""" - cli = InfluxDBClient( - username='root', - password='hello', - database='database' - ) - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.POST, - "http://localhost:8086/db/database/users/root" - ) - - cli.update_database_user_password( - username='root', - new_password='bye' - ) - - self.assertEqual(cli._password, 'bye') - - def test_delete_database_user(self): - """Test delete database user for TestInfluxDBClient.""" - with requests_mock.Mocker() as m: - m.register_uri( - requests_mock.DELETE, - "http://localhost:8086/db/db/users/paul" - ) - - cli = InfluxDBClient(database='db') - cli.delete_database_user(username='paul') - - self.assertIsNone(m.last_request.body) - - @raises(NotImplementedError) - def test_update_permission(self): - """Test update permission for TestInfluxDBClient.""" - cli = InfluxDBClient('host', 8086, 'username', 'password', 'db') - cli.update_permission('admin', []) - - @mock.patch('requests.Session.request') - def test_request_retry(self, mock_request): - """Test that two connection errors will be handled.""" - class CustomMock(object): - """Define CustomMock object.""" - - def __init__(self): - self.i = 0 - - def connection_error(self, *args, **kwargs): - """Test connection error in CustomMock.""" - self.i += 1 - - if self.i < 3: - raise requests.exceptions.ConnectionError - else: - r = requests.Response() - r.status_code = 200 - return r - - mock_request.side_effect = CustomMock().connection_error - - cli = InfluxDBClient(database='db') - cli.write_points( - self.dummy_points - ) - - @mock.patch('requests.Session.request') - def test_request_retry_raises(self, mock_request): - """Test that three connection errors will not be handled.""" - class CustomMock(object): - """Define CustomMock object.""" - - def __init__(self): - """Initialize the object.""" - self.i = 0 - - def connection_error(self, *args, **kwargs): - """Test the connection error for CustomMock.""" - self.i += 1 - - if self.i < 4: - raise requests.exceptions.ConnectionError - else: - r = requests.Response() - r.status_code = 200 - return r - - mock_request.side_effect = CustomMock().connection_error - - cli = InfluxDBClient(database='db') - - with self.assertRaises(requests.exceptions.ConnectionError): - cli.write_points(self.dummy_points) diff --git a/lib/influxdb/tests/influxdb08/dataframe_client_test.py b/lib/influxdb/tests/influxdb08/dataframe_client_test.py deleted file mode 100644 index 6e6fa2c..0000000 --- a/lib/influxdb/tests/influxdb08/dataframe_client_test.py +++ /dev/null @@ -1,331 +0,0 @@ -# -*- coding: utf-8 -*- -"""Unit tests for misc module.""" - -from datetime import timedelta - -import copy -import json -import unittest -import warnings - -import requests_mock - -from nose.tools import raises - -from influxdb.tests import skipIfPYpy, using_pypy - -from .client_test import _mocked_session - -if not using_pypy: - import pandas as pd - from pandas.util.testing import assert_frame_equal - from influxdb.influxdb08 import DataFrameClient - - -@skipIfPYpy -class TestDataFrameClient(unittest.TestCase): - """Define the DataFramClient test object.""" - - def setUp(self): - """Set up an instance of TestDataFrameClient object.""" - # By default, raise exceptions on warnings - warnings.simplefilter('error', FutureWarning) - - def test_write_points_from_dataframe(self): - """Test write points from dataframe.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - points = [ - { - "points": [ - ["1", 1, 1.0, 0], - ["2", 2, 2.0, 3600] - ], - "name": "foo", - "columns": ["column_one", "column_two", "column_three", "time"] - } - ] - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - - cli = DataFrameClient(database='db') - cli.write_points({"foo": dataframe}) - - self.assertListEqual(json.loads(m.last_request.body), points) - - def test_write_points_from_dataframe_with_float_nan(self): - """Test write points from dataframe with NaN float.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[[1, float("NaN"), 1.0], [2, 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - points = [ - { - "points": [ - [1, None, 1.0, 0], - [2, 2, 2.0, 3600] - ], - "name": "foo", - "columns": ["column_one", "column_two", "column_three", "time"] - } - ] - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - - cli = DataFrameClient(database='db') - cli.write_points({"foo": dataframe}) - - self.assertListEqual(json.loads(m.last_request.body), points) - - def test_write_points_from_dataframe_in_batches(self): - """Test write points from dataframe in batches.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - - cli = DataFrameClient(database='db') - self.assertTrue(cli.write_points({"foo": dataframe}, batch_size=1)) - - def test_write_points_from_dataframe_with_numeric_column_names(self): - """Test write points from dataframe with numeric columns.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - # df with numeric column names - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)]) - points = [ - { - "points": [ - ["1", 1, 1.0, 0], - ["2", 2, 2.0, 3600] - ], - "name": "foo", - "columns": ['0', '1', '2', "time"] - } - ] - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - - cli = DataFrameClient(database='db') - cli.write_points({"foo": dataframe}) - - self.assertListEqual(json.loads(m.last_request.body), points) - - def test_write_points_from_dataframe_with_period_index(self): - """Test write points from dataframe with period index.""" - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[pd.Period('1970-01-01'), - pd.Period('1970-01-02')], - columns=["column_one", "column_two", - "column_three"]) - points = [ - { - "points": [ - ["1", 1, 1.0, 0], - ["2", 2, 2.0, 86400] - ], - "name": "foo", - "columns": ["column_one", "column_two", "column_three", "time"] - } - ] - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - - cli = DataFrameClient(database='db') - cli.write_points({"foo": dataframe}) - - self.assertListEqual(json.loads(m.last_request.body), points) - - def test_write_points_from_dataframe_with_time_precision(self): - """Test write points from dataframe with time precision.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - index=[now, now + timedelta(hours=1)], - columns=["column_one", "column_two", - "column_three"]) - points = [ - { - "points": [ - ["1", 1, 1.0, 0], - ["2", 2, 2.0, 3600] - ], - "name": "foo", - "columns": ["column_one", "column_two", "column_three", "time"] - } - ] - - points_ms = copy.deepcopy(points) - points_ms[0]["points"][1][-1] = 3600 * 1000 - - points_us = copy.deepcopy(points) - points_us[0]["points"][1][-1] = 3600 * 1000000 - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - - cli = DataFrameClient(database='db') - - cli.write_points({"foo": dataframe}, time_precision='s') - self.assertListEqual(json.loads(m.last_request.body), points) - - cli.write_points({"foo": dataframe}, time_precision='m') - self.assertListEqual(json.loads(m.last_request.body), points_ms) - - cli.write_points({"foo": dataframe}, time_precision='u') - self.assertListEqual(json.loads(m.last_request.body), points_us) - - @raises(TypeError) - def test_write_points_from_dataframe_fails_without_time_index(self): - """Test write points from dataframe that fails without time index.""" - dataframe = pd.DataFrame(data=[["1", 1, 1.0], ["2", 2, 2.0]], - columns=["column_one", "column_two", - "column_three"]) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - - cli = DataFrameClient(database='db') - cli.write_points({"foo": dataframe}) - - @raises(TypeError) - def test_write_points_from_dataframe_fails_with_series(self): - """Test failed write points from dataframe with series.""" - now = pd.Timestamp('1970-01-01 00:00+00:00') - dataframe = pd.Series(data=[1.0, 2.0], - index=[now, now + timedelta(hours=1)]) - - with requests_mock.Mocker() as m: - m.register_uri(requests_mock.POST, - "http://localhost:8086/db/db/series") - - cli = DataFrameClient(database='db') - cli.write_points({"foo": dataframe}) - - def test_query_into_dataframe(self): - """Test query into a dataframe.""" - data = [ - { - "name": "foo", - "columns": ["time", "sequence_number", "column_one"], - "points": [ - [3600, 16, 2], [3600, 15, 1], - [0, 14, 2], [0, 13, 1] - ] - } - ] - # dataframe sorted ascending by time first, then sequence_number - dataframe = pd.DataFrame(data=[[13, 1], [14, 2], [15, 1], [16, 2]], - index=pd.to_datetime([0, 0, - 3600, 3600], - unit='s', utc=True), - columns=['sequence_number', 'column_one']) - with _mocked_session('get', 200, data): - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - result = cli.query('select column_one from foo;') - assert_frame_equal(dataframe, result) - - def test_query_multiple_time_series(self): - """Test query for multiple time series.""" - data = [ - { - "name": "series1", - "columns": ["time", "mean", "min", "max", "stddev"], - "points": [[0, 323048, 323048, 323048, 0]] - }, - { - "name": "series2", - "columns": ["time", "mean", "min", "max", "stddev"], - "points": [[0, -2.8233, -2.8503, -2.7832, 0.0173]] - }, - { - "name": "series3", - "columns": ["time", "mean", "min", "max", "stddev"], - "points": [[0, -0.01220, -0.01220, -0.01220, 0]] - } - ] - dataframes = { - 'series1': pd.DataFrame(data=[[323048, 323048, 323048, 0]], - index=pd.to_datetime([0], unit='s', - utc=True), - columns=['mean', 'min', 'max', 'stddev']), - 'series2': pd.DataFrame(data=[[-2.8233, -2.8503, -2.7832, 0.0173]], - index=pd.to_datetime([0], unit='s', - utc=True), - columns=['mean', 'min', 'max', 'stddev']), - 'series3': pd.DataFrame(data=[[-0.01220, -0.01220, -0.01220, 0]], - index=pd.to_datetime([0], unit='s', - utc=True), - columns=['mean', 'min', 'max', 'stddev']) - } - with _mocked_session('get', 200, data): - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - result = cli.query("""select mean(value), min(value), max(value), - stddev(value) from series1, series2, series3""") - self.assertEqual(dataframes.keys(), result.keys()) - for key in dataframes.keys(): - assert_frame_equal(dataframes[key], result[key]) - - def test_query_with_empty_result(self): - """Test query with empty results.""" - with _mocked_session('get', 200, []): - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - result = cli.query('select column_one from foo;') - self.assertEqual(result, []) - - def test_list_series(self): - """Test list of series for dataframe object.""" - response = [ - { - 'columns': ['time', 'name'], - 'name': 'list_series_result', - 'points': [[0, 'seriesA'], [0, 'seriesB']] - } - ] - with _mocked_session('get', 200, response): - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - series_list = cli.get_list_series() - self.assertEqual(series_list, ['seriesA', 'seriesB']) - - def test_datetime_to_epoch(self): - """Test convert datetime to epoch.""" - timestamp = pd.Timestamp('2013-01-01 00:00:00.000+00:00') - cli = DataFrameClient('host', 8086, 'username', 'password', 'db') - - self.assertEqual( - cli._datetime_to_epoch(timestamp), - 1356998400.0 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='s'), - 1356998400.0 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='m'), - 1356998400000.0 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='ms'), - 1356998400000.0 - ) - self.assertEqual( - cli._datetime_to_epoch(timestamp, time_precision='u'), - 1356998400000000.0 - ) diff --git a/lib/influxdb/tests/influxdb08/helper_test.py b/lib/influxdb/tests/influxdb08/helper_test.py deleted file mode 100644 index 2e305f3..0000000 --- a/lib/influxdb/tests/influxdb08/helper_test.py +++ /dev/null @@ -1,228 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define set of helper functions for the dataframe.""" - -import unittest -import warnings - -import mock -from influxdb.influxdb08 import SeriesHelper, InfluxDBClient -from requests.exceptions import ConnectionError - - -class TestSeriesHelper(unittest.TestCase): - """Define the SeriesHelper for test.""" - - @classmethod - def setUpClass(cls): - """Set up an instance of the TestSerisHelper object.""" - super(TestSeriesHelper, cls).setUpClass() - - TestSeriesHelper.client = InfluxDBClient( - 'host', - 8086, - 'username', - 'password', - 'database' - ) - - class MySeriesHelper(SeriesHelper): - """Define a subset SeriesHelper instance.""" - - class Meta: - """Define metadata for the TestSeriesHelper object.""" - - client = TestSeriesHelper.client - series_name = 'events.stats.{server_name}' - fields = ['time', 'server_name'] - bulk_size = 5 - autocommit = True - - TestSeriesHelper.MySeriesHelper = MySeriesHelper - - def test_auto_commit(self): - """Test that write_points called after the right number of events.""" - class AutoCommitTest(SeriesHelper): - """Define an instance of SeriesHelper for AutoCommit test.""" - - class Meta: - """Define metadata AutoCommitTest object.""" - - series_name = 'events.stats.{server_name}' - fields = ['time', 'server_name'] - bulk_size = 5 - client = InfluxDBClient() - autocommit = True - - fake_write_points = mock.MagicMock() - AutoCommitTest(server_name='us.east-1', time=159) - AutoCommitTest._client.write_points = fake_write_points - AutoCommitTest(server_name='us.east-1', time=158) - AutoCommitTest(server_name='us.east-1', time=157) - AutoCommitTest(server_name='us.east-1', time=156) - self.assertFalse(fake_write_points.called) - AutoCommitTest(server_name='us.east-1', time=3443) - self.assertTrue(fake_write_points.called) - - def testSingleSeriesName(self): - """Test JSON conversion when there is only one series name.""" - TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=159) - TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=158) - TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=157) - TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=156) - expectation = [{'points': [[159, 'us.east-1'], - [158, 'us.east-1'], - [157, 'us.east-1'], - [156, 'us.east-1']], - 'name': 'events.stats.us.east-1', - 'columns': ['time', 'server_name']}] - - rcvd = TestSeriesHelper.MySeriesHelper._json_body_() - self.assertTrue(all([el in expectation for el in rcvd]) and - all([el in rcvd for el in expectation]), - 'Invalid JSON body of time series returned from ' - '_json_body_ for one series name: {0}.'.format(rcvd)) - TestSeriesHelper.MySeriesHelper._reset_() - self.assertEqual( - TestSeriesHelper.MySeriesHelper._json_body_(), - [], - 'Resetting helper did not empty datapoints.') - - def testSeveralSeriesNames(self): - """Test JSON conversion when there is only one series name.""" - TestSeriesHelper.MySeriesHelper(server_name='us.east-1', time=159) - TestSeriesHelper.MySeriesHelper(server_name='fr.paris-10', time=158) - TestSeriesHelper.MySeriesHelper(server_name='lu.lux', time=157) - TestSeriesHelper.MySeriesHelper(server_name='uk.london', time=156) - expectation = [{'points': [[157, 'lu.lux']], - 'name': 'events.stats.lu.lux', - 'columns': ['time', 'server_name']}, - {'points': [[156, 'uk.london']], - 'name': 'events.stats.uk.london', - 'columns': ['time', 'server_name']}, - {'points': [[158, 'fr.paris-10']], - 'name': 'events.stats.fr.paris-10', - 'columns': ['time', 'server_name']}, - {'points': [[159, 'us.east-1']], - 'name': 'events.stats.us.east-1', - 'columns': ['time', 'server_name']}] - - rcvd = TestSeriesHelper.MySeriesHelper._json_body_() - self.assertTrue(all([el in expectation for el in rcvd]) and - all([el in rcvd for el in expectation]), - 'Invalid JSON body of time series returned from ' - '_json_body_ for several series names: {0}.' - .format(rcvd)) - TestSeriesHelper.MySeriesHelper._reset_() - self.assertEqual( - TestSeriesHelper.MySeriesHelper._json_body_(), - [], - 'Resetting helper did not empty datapoints.') - - def testInvalidHelpers(self): - """Test errors in invalid helpers.""" - class MissingMeta(SeriesHelper): - """Define SeriesHelper object for MissingMeta test.""" - - pass - - class MissingClient(SeriesHelper): - """Define SeriesHelper object for MissingClient test.""" - - class Meta: - """Define metadata for MissingClient object.""" - - series_name = 'events.stats.{server_name}' - fields = ['time', 'server_name'] - autocommit = True - - class MissingSeriesName(SeriesHelper): - """Define SeriesHelper object for MissingSeries test.""" - - class Meta: - """Define metadata for MissingSeriesName object.""" - - fields = ['time', 'server_name'] - - class MissingFields(SeriesHelper): - """Define SeriesHelper for MissingFields test.""" - - class Meta: - """Define metadata for MissingFields object.""" - - series_name = 'events.stats.{server_name}' - - for cls in [MissingMeta, MissingClient, MissingFields, - MissingSeriesName]: - self.assertRaises( - AttributeError, cls, **{'time': 159, - 'server_name': 'us.east-1'}) - - def testWarnBulkSizeZero(self): - """Test warning for an invalid bulk size.""" - class WarnBulkSizeZero(SeriesHelper): - """Define SeriesHelper for WarnBulkSizeZero test.""" - - class Meta: - """Define metadata for WarnBulkSizeZero object.""" - - client = TestSeriesHelper.client - series_name = 'events.stats.{server_name}' - fields = ['time', 'server_name'] - bulk_size = 0 - autocommit = True - - with warnings.catch_warnings(record=True) as rec_warnings: - warnings.simplefilter("always") - # Server defined in the client is invalid, we're testing - # the warning only. - with self.assertRaises(ConnectionError): - WarnBulkSizeZero(time=159, server_name='us.east-1') - - self.assertGreaterEqual( - len(rec_warnings), 1, - '{0} call should have generated one warning.' - 'Actual generated warnings: {1}'.format( - WarnBulkSizeZero, '\n'.join(map(str, rec_warnings)))) - - expected_msg = ( - 'Definition of bulk_size in WarnBulkSizeZero forced to 1, ' - 'was less than 1.') - - self.assertIn(expected_msg, list(w.message.args[0] - for w in rec_warnings), - 'Warning message did not contain "forced to 1".') - - def testWarnBulkSizeNoEffect(self): - """Test warning for a set bulk size but autocommit False.""" - class WarnBulkSizeNoEffect(SeriesHelper): - """Define SeriesHelper for WarnBulkSizeNoEffect object.""" - - class Meta: - """Define metadata for WarnBulkSizeNoEffect object.""" - - series_name = 'events.stats.{server_name}' - fields = ['time', 'server_name'] - bulk_size = 5 - autocommit = False - - with warnings.catch_warnings(record=True) as rec_warnings: - warnings.simplefilter("always") - WarnBulkSizeNoEffect(time=159, server_name='us.east-1') - - self.assertGreaterEqual( - len(rec_warnings), 1, - '{0} call should have generated one warning.' - 'Actual generated warnings: {1}'.format( - WarnBulkSizeNoEffect, '\n'.join(map(str, rec_warnings)))) - - expected_msg = ( - 'Definition of bulk_size in WarnBulkSizeNoEffect has no affect ' - 'because autocommit is false.') - - self.assertIn(expected_msg, list(w.message.args[0] - for w in rec_warnings), - 'Warning message did not contain the expected_msg.') - - -if __name__ == '__main__': - unittest.main() diff --git a/lib/influxdb/tests/misc.py b/lib/influxdb/tests/misc.py deleted file mode 100644 index 324d13c..0000000 --- a/lib/influxdb/tests/misc.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define the misc handler for InfluxDBClient test.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import socket - - -def get_free_ports(num_ports, ip='127.0.0.1'): - """Determine free ports on provided interface. - - Get `num_ports` free/available ports on the interface linked to the `ip` - :param int num_ports: The number of free ports to get - :param str ip: The ip on which the ports have to be taken - :return: a set of ports number - """ - sock_ports = [] - ports = set() - try: - for _ in range(num_ports): - sock = socket.socket() - cur = [sock, -1] - # append the socket directly, - # so that it'll be also closed (no leaked resource) - # in the finally here after. - sock_ports.append(cur) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind((ip, 0)) - cur[1] = sock.getsockname()[1] - finally: - for sock, port in sock_ports: - sock.close() - ports.add(port) - assert num_ports == len(ports) - return ports - - -def is_port_open(port, ip='127.0.0.1'): - """Check if given TCP port is open for connection.""" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - result = sock.connect_ex((ip, port)) - if not result: - sock.shutdown(socket.SHUT_RDWR) - return result == 0 - finally: - sock.close() diff --git a/lib/influxdb/tests/resultset_test.py b/lib/influxdb/tests/resultset_test.py deleted file mode 100644 index 83faa4d..0000000 --- a/lib/influxdb/tests/resultset_test.py +++ /dev/null @@ -1,202 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define the resultset test package.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import unittest - -from influxdb.exceptions import InfluxDBClientError -from influxdb.resultset import ResultSet - - -class TestResultSet(unittest.TestCase): - """Define the ResultSet test object.""" - - def setUp(self): - """Set up an instance of TestResultSet.""" - self.query_response = { - "results": [ - {"series": [{"name": "cpu_load_short", - "columns": ["time", "value", "host", "region"], - "values": [ - ["2015-01-29T21:51:28.968422294Z", - 0.64, - "server01", - "us-west"], - ["2015-01-29T21:51:28.968422294Z", - 0.65, - "server02", - "us-west"], - ]}, - {"name": "other_series", - "columns": ["time", "value", "host", "region"], - "values": [ - ["2015-01-29T21:51:28.968422294Z", - 0.66, - "server01", - "us-west"], - ]}]} - ] - } - - self.rs = ResultSet(self.query_response['results'][0]) - - def test_filter_by_name(self): - """Test filtering by name in TestResultSet object.""" - expected = [ - {'value': 0.64, - 'time': '2015-01-29T21:51:28.968422294Z', - 'host': 'server01', - 'region': 'us-west'}, - {'value': 0.65, - 'time': '2015-01-29T21:51:28.968422294Z', - 'host': 'server02', - 'region': 'us-west'}, - ] - - self.assertEqual(expected, list(self.rs['cpu_load_short'])) - self.assertEqual(expected, - list(self.rs.get_points( - measurement='cpu_load_short'))) - - def test_filter_by_tags(self): - """Test filter by tags in TestResultSet object.""" - expected = [ - {'value': 0.64, - 'time': '2015-01-29T21:51:28.968422294Z', - 'host': 'server01', - 'region': 'us-west'}, - {'value': 0.66, - 'time': '2015-01-29T21:51:28.968422294Z', - 'host': 'server01', - 'region': 'us-west'}, - ] - - self.assertEqual( - expected, - list(self.rs[{"host": "server01"}]) - ) - - self.assertEqual( - expected, - list(self.rs.get_points(tags={'host': 'server01'})) - ) - - def test_filter_by_name_and_tags(self): - """Test filter by name and tags in TestResultSet object.""" - self.assertEqual( - list(self.rs[('cpu_load_short', {"host": "server01"})]), - [{'value': 0.64, - 'time': '2015-01-29T21:51:28.968422294Z', - 'host': 'server01', - 'region': 'us-west'}] - ) - - self.assertEqual( - list(self.rs[('cpu_load_short', {"region": "us-west"})]), - [ - {'value': 0.64, - 'time': '2015-01-29T21:51:28.968422294Z', - 'host': 'server01', - 'region': 'us-west'}, - {'value': 0.65, - 'time': '2015-01-29T21:51:28.968422294Z', - 'host': 'server02', - 'region': 'us-west'}, - ] - ) - - def test_keys(self): - """Test keys in TestResultSet object.""" - self.assertEqual( - self.rs.keys(), - [ - ('cpu_load_short', None), - ('other_series', None), - ] - ) - - def test_len(self): - """Test length in TestResultSet object.""" - self.assertEqual( - len(self.rs), - 2 - ) - - def test_items(self): - """Test items in TestResultSet object.""" - items = list(self.rs.items()) - items_lists = [(item[0], list(item[1])) for item in items] - - self.assertEqual( - items_lists, - [ - ( - ('cpu_load_short', None), - [ - {'time': '2015-01-29T21:51:28.968422294Z', - 'value': 0.64, - 'host': 'server01', - 'region': 'us-west'}, - {'time': '2015-01-29T21:51:28.968422294Z', - 'value': 0.65, - 'host': 'server02', - 'region': 'us-west'}]), - ( - ('other_series', None), - [ - {'time': '2015-01-29T21:51:28.968422294Z', - 'value': 0.66, - 'host': 'server01', - 'region': 'us-west'}])] - ) - - def test_point_from_cols_vals(self): - """Test points from columns in TestResultSet object.""" - cols = ['col1', 'col2'] - vals = [1, '2'] - - point = ResultSet.point_from_cols_vals(cols, vals) - self.assertDictEqual( - point, - {'col1': 1, 'col2': '2'} - ) - - def test_system_query(self): - """Test system query capabilities in TestResultSet object.""" - rs = ResultSet( - {'series': [ - {'values': [['another', '48h0m0s', 3, False], - ['default', '0', 1, False], - ['somename', '24h0m0s', 4, True]], - 'columns': ['name', 'duration', - 'replicaN', 'default']}]} - ) - - self.assertEqual( - rs.keys(), - [('results', None)] - ) - - self.assertEqual( - list(rs['results']), - [ - {'duration': '48h0m0s', 'default': False, 'replicaN': 3, - 'name': 'another'}, - {'duration': '0', 'default': False, 'replicaN': 1, - 'name': 'default'}, - {'duration': '24h0m0s', 'default': True, 'replicaN': 4, - 'name': 'somename'} - ] - ) - - def test_resultset_error(self): - """Test returning error in TestResultSet object.""" - with self.assertRaises(InfluxDBClientError): - ResultSet({ - "series": [], - "error": "Big error, many problems." - }) diff --git a/lib/influxdb/tests/server_tests/__init__.py b/lib/influxdb/tests/server_tests/__init__.py deleted file mode 100644 index ce149ab..0000000 --- a/lib/influxdb/tests/server_tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Define the server tests package.""" diff --git a/lib/influxdb/tests/server_tests/base.py b/lib/influxdb/tests/server_tests/base.py deleted file mode 100644 index f4bd3ff..0000000 --- a/lib/influxdb/tests/server_tests/base.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define the base module for server test.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import sys - -from influxdb.tests import using_pypy -from influxdb.tests.server_tests.influxdb_instance import InfluxDbInstance - -from influxdb.client import InfluxDBClient - -if not using_pypy: - from influxdb.dataframe_client import DataFrameClient - - -def _setup_influxdb_server(inst): - inst.influxd_inst = InfluxDbInstance( - inst.influxdb_template_conf, - udp_enabled=getattr(inst, 'influxdb_udp_enabled', False), - ) - - inst.cli = InfluxDBClient('localhost', - inst.influxd_inst.http_port, - 'root', - '', - database='db') - if not using_pypy: - inst.cliDF = DataFrameClient('localhost', - inst.influxd_inst.http_port, - 'root', - '', - database='db') - - -def _teardown_influxdb_server(inst): - remove_tree = sys.exc_info() == (None, None, None) - inst.influxd_inst.close(remove_tree=remove_tree) - - -class SingleTestCaseWithServerMixin(object): - """Define the single testcase with server mixin. - - A mixin for unittest.TestCase to start an influxdb server instance - in a temporary directory **for each test function/case** - """ - - # 'influxdb_template_conf' attribute must be set - # on the TestCase class or instance. - - setUp = _setup_influxdb_server - tearDown = _teardown_influxdb_server - - -class ManyTestCasesWithServerMixin(object): - """Define the many testcase with server mixin. - - Same as the SingleTestCaseWithServerMixin but this module creates - a single instance for the whole class. Also pre-creates a fresh - database: 'db'. - """ - - # 'influxdb_template_conf' attribute must be set on the class itself ! - - @classmethod - def setUpClass(cls): - """Set up an instance of the ManyTestCasesWithServerMixin.""" - _setup_influxdb_server(cls) - - def setUp(self): - """Set up an instance of the ManyTestCasesWithServerMixin.""" - self.cli.create_database('db') - - @classmethod - def tearDownClass(cls): - """Deconstruct an instance of ManyTestCasesWithServerMixin.""" - _teardown_influxdb_server(cls) - - def tearDown(self): - """Deconstruct an instance of ManyTestCasesWithServerMixin.""" - self.cli.drop_database('db') diff --git a/lib/influxdb/tests/server_tests/client_test_with_server.py b/lib/influxdb/tests/server_tests/client_test_with_server.py deleted file mode 100644 index 2f8a209..0000000 --- a/lib/influxdb/tests/server_tests/client_test_with_server.py +++ /dev/null @@ -1,825 +0,0 @@ -# -*- coding: utf-8 -*- -"""Unit tests for checking the InfluxDB server. - -The good/expected interaction between: - -+ the python client.. (obviously) -+ and a *_real_* server instance running. - -This basically duplicates what's in client_test.py -but without mocking around every call. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from functools import partial -import os -import time -import unittest -import warnings - -from influxdb import InfluxDBClient -from influxdb.exceptions import InfluxDBClientError - -from influxdb.tests import skipIfPYpy, using_pypy, skipServerTests -from influxdb.tests.server_tests.base import ManyTestCasesWithServerMixin -from influxdb.tests.server_tests.base import SingleTestCaseWithServerMixin - -# By default, raise exceptions on warnings -warnings.simplefilter('error', FutureWarning) - -if not using_pypy: - import pandas as pd - from pandas.util.testing import assert_frame_equal - - -THIS_DIR = os.path.abspath(os.path.dirname(__file__)) - - -def point(series_name, timestamp=None, tags=None, **fields): - """Define what a point looks like.""" - res = {'measurement': series_name} - - if timestamp: - res['time'] = timestamp - - if tags: - res['tags'] = tags - - res['fields'] = fields - return res - - -dummy_point = [ # some dummy points - { - "measurement": "cpu_load_short", - "tags": { - "host": "server01", - "region": "us-west" - }, - "time": "2009-11-10T23:00:00Z", - "fields": { - "value": 0.64 - } - } -] - -dummy_points = [ # some dummy points - dummy_point[0], - { - "measurement": "memory", - "tags": { - "host": "server01", - "region": "us-west" - }, - "time": "2009-11-10T23:01:35Z", - "fields": { - "value": 33.0 - } - } -] - -if not using_pypy: - dummy_pointDF = { - "measurement": "cpu_load_short", - "tags": {"host": "server01", - "region": "us-west"}, - "dataframe": pd.DataFrame( - [[0.64]], columns=['value'], - index=pd.to_datetime(["2009-11-10T23:00:00Z"])) - } - dummy_pointsDF = [{ - "measurement": "cpu_load_short", - "tags": {"host": "server01", "region": "us-west"}, - "dataframe": pd.DataFrame( - [[0.64]], columns=['value'], - index=pd.to_datetime(["2009-11-10T23:00:00Z"])), - }, { - "measurement": "memory", - "tags": {"host": "server01", "region": "us-west"}, - "dataframe": pd.DataFrame( - [[33]], columns=['value'], - index=pd.to_datetime(["2009-11-10T23:01:35Z"]) - ) - }] - - -dummy_point_without_timestamp = [ - { - "measurement": "cpu_load_short", - "tags": { - "host": "server02", - "region": "us-west" - }, - "fields": { - "value": 0.64 - } - } -] - - -@skipServerTests -class SimpleTests(SingleTestCaseWithServerMixin, unittest.TestCase): - """Define the class of simple tests.""" - - influxdb_template_conf = os.path.join(THIS_DIR, 'influxdb.conf.template') - - def test_fresh_server_no_db(self): - """Test a fresh server without database.""" - self.assertEqual([], self.cli.get_list_database()) - - def test_create_database(self): - """Test create a database.""" - self.assertIsNone(self.cli.create_database('new_db_1')) - self.assertIsNone(self.cli.create_database('new_db_2')) - self.assertEqual( - self.cli.get_list_database(), - [{'name': 'new_db_1'}, {'name': 'new_db_2'}] - ) - - def test_drop_database(self): - """Test drop a database.""" - self.test_create_database() - self.assertIsNone(self.cli.drop_database('new_db_1')) - self.assertEqual([{'name': 'new_db_2'}], self.cli.get_list_database()) - - def test_query_fail(self): - """Test that a query failed.""" - with self.assertRaises(InfluxDBClientError) as ctx: - self.cli.query('select column_one from foo') - self.assertIn('database not found: db', - ctx.exception.content) - - def test_query_fail_ignore_errors(self): - """Test query failed but ignore errors.""" - result = self.cli.query('select column_one from foo', - raise_errors=False) - self.assertEqual(result.error, 'database not found: db') - - def test_create_user(self): - """Test create user.""" - self.cli.create_user('test_user', 'secret_password') - rsp = list(self.cli.query("SHOW USERS")['results']) - self.assertIn({'user': 'test_user', 'admin': False}, - rsp) - - def test_create_user_admin(self): - """Test create admin user.""" - self.cli.create_user('test_user', 'secret_password', True) - rsp = list(self.cli.query("SHOW USERS")['results']) - self.assertIn({'user': 'test_user', 'admin': True}, - rsp) - - def test_create_user_blank_password(self): - """Test create user with a blank pass.""" - self.cli.create_user('test_user', '') - rsp = list(self.cli.query("SHOW USERS")['results']) - self.assertIn({'user': 'test_user', 'admin': False}, - rsp) - - def test_get_list_users_empty(self): - """Test get list of users, but empty.""" - rsp = self.cli.get_list_users() - self.assertEqual([], rsp) - - def test_get_list_users(self): - """Test get list of users.""" - self.cli.query("CREATE USER test WITH PASSWORD 'test'") - rsp = self.cli.get_list_users() - - self.assertEqual( - [{'user': 'test', 'admin': False}], - rsp - ) - - def test_create_user_blank_username(self): - """Test create blank username.""" - with self.assertRaises(InfluxDBClientError) as ctx: - self.cli.create_user('', 'secret_password') - self.assertIn('username required', - ctx.exception.content) - rsp = list(self.cli.query("SHOW USERS")['results']) - self.assertEqual(rsp, []) - - def test_drop_user(self): - """Test drop a user.""" - self.cli.query("CREATE USER test WITH PASSWORD 'test'") - self.cli.drop_user('test') - users = list(self.cli.query("SHOW USERS")['results']) - self.assertEqual(users, []) - - def test_drop_user_nonexisting(self): - """Test dropping a nonexistent user.""" - with self.assertRaises(InfluxDBClientError) as ctx: - self.cli.drop_user('test') - self.assertIn('user not found', - ctx.exception.content) - - @unittest.skip("Broken as of 0.9.0") - def test_revoke_admin_privileges(self): - """Test revoking admin privs, deprecated as of v0.9.0.""" - self.cli.create_user('test', 'test', admin=True) - self.assertEqual([{'user': 'test', 'admin': True}], - self.cli.get_list_users()) - self.cli.revoke_admin_privileges('test') - self.assertEqual([{'user': 'test', 'admin': False}], - self.cli.get_list_users()) - - def test_grant_privilege(self): - """Test grant privs to user.""" - self.cli.create_user('test', 'test') - self.cli.create_database('testdb') - self.cli.grant_privilege('all', 'testdb', 'test') - # TODO: when supported by InfluxDB, check if privileges are granted - - def test_grant_privilege_invalid(self): - """Test grant invalid privs to user.""" - self.cli.create_user('test', 'test') - self.cli.create_database('testdb') - with self.assertRaises(InfluxDBClientError) as ctx: - self.cli.grant_privilege('', 'testdb', 'test') - self.assertEqual(400, ctx.exception.code) - self.assertIn('{"error":"error parsing query: ', - ctx.exception.content) - - def test_revoke_privilege(self): - """Test revoke privs from user.""" - self.cli.create_user('test', 'test') - self.cli.create_database('testdb') - self.cli.revoke_privilege('all', 'testdb', 'test') - # TODO: when supported by InfluxDB, check if privileges are revoked - - def test_revoke_privilege_invalid(self): - """Test revoke invalid privs from user.""" - self.cli.create_user('test', 'test') - self.cli.create_database('testdb') - with self.assertRaises(InfluxDBClientError) as ctx: - self.cli.revoke_privilege('', 'testdb', 'test') - self.assertEqual(400, ctx.exception.code) - self.assertIn('{"error":"error parsing query: ', - ctx.exception.content) - - def test_invalid_port_fails(self): - """Test invalid port access fails.""" - with self.assertRaises(ValueError): - InfluxDBClient('host', '80/redir', 'username', 'password') - - -@skipServerTests -class CommonTests(ManyTestCasesWithServerMixin, unittest.TestCase): - """Define a class to handle common tests for the server.""" - - influxdb_template_conf = os.path.join(THIS_DIR, 'influxdb.conf.template') - - def test_write(self): - """Test write to the server.""" - self.assertIs(True, self.cli.write( - {'points': dummy_point}, - params={'db': 'db'}, - )) - - def test_write_check_read(self): - """Test write and check read of data to server.""" - self.test_write() - time.sleep(1) - rsp = self.cli.query('SELECT * FROM cpu_load_short', database='db') - self.assertListEqual([{'value': 0.64, 'time': '2009-11-10T23:00:00Z', - "host": "server01", "region": "us-west"}], - list(rsp.get_points())) - - def test_write_points(self): - """Test writing points to the server.""" - self.assertIs(True, self.cli.write_points(dummy_point)) - - @skipIfPYpy - def test_write_points_DF(self): - """Test writing points with dataframe.""" - self.assertIs( - True, - self.cliDF.write_points( - dummy_pointDF['dataframe'], - dummy_pointDF['measurement'], - dummy_pointDF['tags'] - ) - ) - - def test_write_points_check_read(self): - """Test writing points and check read back.""" - self.test_write_points() - time.sleep(1) # same as test_write_check_read() - rsp = self.cli.query('SELECT * FROM cpu_load_short') - - self.assertEqual( - list(rsp), - [[ - {'value': 0.64, - 'time': '2009-11-10T23:00:00Z', - "host": "server01", - "region": "us-west"} - ]] - ) - - rsp2 = list(rsp.get_points()) - self.assertEqual(len(rsp2), 1) - pt = rsp2[0] - - self.assertEqual( - pt, - {'time': '2009-11-10T23:00:00Z', - 'value': 0.64, - "host": "server01", - "region": "us-west"} - ) - - @unittest.skip("Broken as of 0.9.0") - def test_write_points_check_read_DF(self): - """Test write points and check back with dataframe.""" - self.test_write_points_DF() - time.sleep(1) # same as test_write_check_read() - - rsp = self.cliDF.query('SELECT * FROM cpu_load_short') - assert_frame_equal( - rsp['cpu_load_short'], - dummy_pointDF['dataframe'] - ) - - # Query with Tags - rsp = self.cliDF.query( - "SELECT * FROM cpu_load_short GROUP BY *") - assert_frame_equal( - rsp[('cpu_load_short', - (('host', 'server01'), ('region', 'us-west')))], - dummy_pointDF['dataframe'] - ) - - def test_write_multiple_points_different_series(self): - """Test write multiple points to different series.""" - self.assertIs(True, self.cli.write_points(dummy_points)) - time.sleep(1) - rsp = self.cli.query('SELECT * FROM cpu_load_short') - lrsp = list(rsp) - - self.assertEqual( - [[ - {'value': 0.64, - 'time': '2009-11-10T23:00:00Z', - "host": "server01", - "region": "us-west"} - ]], - lrsp - ) - - rsp = list(self.cli.query('SELECT * FROM memory')) - - self.assertEqual( - rsp, - [[ - {'value': 33, - 'time': '2009-11-10T23:01:35Z', - "host": "server01", - "region": "us-west"} - ]] - ) - - def test_select_into_as_post(self): - """Test SELECT INTO is POSTed.""" - self.assertIs(True, self.cli.write_points(dummy_points)) - time.sleep(1) - rsp = self.cli.query('SELECT * INTO "newmeas" FROM "memory"') - rsp = self.cli.query('SELECT * FROM "newmeas"') - lrsp = list(rsp) - - self.assertEqual( - lrsp, - [[ - {'value': 33, - 'time': '2009-11-10T23:01:35Z', - "host": "server01", - "region": "us-west"} - ]] - ) - - @unittest.skip("Broken as of 0.9.0") - def test_write_multiple_points_different_series_DF(self): - """Test write multiple points using dataframe to different series.""" - for i in range(2): - self.assertIs( - True, self.cliDF.write_points( - dummy_pointsDF[i]['dataframe'], - dummy_pointsDF[i]['measurement'], - dummy_pointsDF[i]['tags'])) - time.sleep(1) - rsp = self.cliDF.query('SELECT * FROM cpu_load_short') - - assert_frame_equal( - rsp['cpu_load_short'], - dummy_pointsDF[0]['dataframe'] - ) - - rsp = self.cliDF.query('SELECT * FROM memory') - assert_frame_equal( - rsp['memory'], - dummy_pointsDF[1]['dataframe'] - ) - - def test_write_points_batch(self): - """Test writing points in a batch.""" - dummy_points = [ - {"measurement": "cpu_usage", "tags": {"unit": "percent"}, - "time": "2009-11-10T23:00:00Z", "fields": {"value": 12.34}}, - {"measurement": "network", "tags": {"direction": "in"}, - "time": "2009-11-10T23:00:00Z", "fields": {"value": 123.00}}, - {"measurement": "network", "tags": {"direction": "out"}, - "time": "2009-11-10T23:00:00Z", "fields": {"value": 12.00}} - ] - self.cli.write_points(points=dummy_points, - tags={"host": "server01", - "region": "us-west"}, - batch_size=2) - time.sleep(5) - net_in = self.cli.query("SELECT value FROM network " - "WHERE direction='in'").raw - net_out = self.cli.query("SELECT value FROM network " - "WHERE direction='out'").raw - cpu = self.cli.query("SELECT value FROM cpu_usage").raw - self.assertIn(123, net_in['series'][0]['values'][0]) - self.assertIn(12, net_out['series'][0]['values'][0]) - self.assertIn(12.34, cpu['series'][0]['values'][0]) - - def test_query(self): - """Test querying data back from server.""" - self.assertIs(True, self.cli.write_points(dummy_point)) - - @unittest.skip('Not implemented for 0.9') - def test_query_chunked(self): - """Test query for chunked response from server.""" - cli = InfluxDBClient(database='db') - example_object = { - 'points': [ - [1415206250119, 40001, 667], - [1415206244555, 30001, 7], - [1415206228241, 20001, 788], - [1415206212980, 10001, 555], - [1415197271586, 10001, 23] - ], - 'name': 'foo', - 'columns': [ - 'time', - 'sequence_number', - 'val' - ] - } - del cli - del example_object - # TODO ? - - def test_delete_series_invalid(self): - """Test delete invalid series.""" - with self.assertRaises(InfluxDBClientError): - self.cli.delete_series() - - def test_default_retention_policy(self): - """Test add default retention policy.""" - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'name': 'autogen', - 'duration': '0s', - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'default': True} - ], - rsp - ) - - def test_create_retention_policy_default(self): - """Test create a new default retention policy.""" - self.cli.create_retention_policy('somename', '1d', 1, default=True) - self.cli.create_retention_policy('another', '2d', 1, default=False) - rsp = self.cli.get_list_retention_policies() - - self.assertEqual( - [ - {'duration': '0s', - 'default': False, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'}, - {'duration': '24h0m0s', - 'default': True, - 'replicaN': 1, - 'shardGroupDuration': u'1h0m0s', - 'name': 'somename'}, - {'duration': '48h0m0s', - 'default': False, - 'replicaN': 1, - 'shardGroupDuration': u'24h0m0s', - 'name': 'another'} - ], - rsp - ) - - def test_create_retention_policy(self): - """Test creating a new retention policy, not default.""" - self.cli.create_retention_policy('somename', '1d', 1) - # NB: creating a retention policy without specifying - # shard group duration - # leads to a shard group duration of 1 hour - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'duration': '0s', - 'default': True, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'}, - {'duration': '24h0m0s', - 'default': False, - 'replicaN': 1, - 'shardGroupDuration': u'1h0m0s', - 'name': 'somename'} - ], - rsp - ) - - self.cli.drop_retention_policy('somename', 'db') - # recreate the RP - self.cli.create_retention_policy('somename', '1w', 1, - shard_duration='1h') - - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'duration': '0s', - 'default': True, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'}, - {'duration': '168h0m0s', - 'default': False, - 'replicaN': 1, - 'shardGroupDuration': u'1h0m0s', - 'name': 'somename'} - ], - rsp - ) - - self.cli.drop_retention_policy('somename', 'db') - # recreate the RP - self.cli.create_retention_policy('somename', '1w', 1) - - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'duration': '0s', - 'default': True, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'}, - {'duration': '168h0m0s', - 'default': False, - 'replicaN': 1, - 'shardGroupDuration': u'24h0m0s', - 'name': 'somename'} - ], - rsp - ) - - def test_alter_retention_policy(self): - """Test alter a retention policy, not default.""" - self.cli.create_retention_policy('somename', '1d', 1) - - # Test alter duration - self.cli.alter_retention_policy('somename', 'db', - duration='4d', - shard_duration='2h') - # NB: altering retention policy doesn't change shard group duration - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'duration': '0s', - 'default': True, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'}, - {'duration': '96h0m0s', - 'default': False, - 'replicaN': 1, - 'shardGroupDuration': u'2h0m0s', - 'name': 'somename'} - ], - rsp - ) - - # Test alter replication - self.cli.alter_retention_policy('somename', 'db', - replication=4) - - # NB: altering retention policy doesn't change shard group duration - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'duration': '0s', - 'default': True, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'}, - {'duration': '96h0m0s', - 'default': False, - 'replicaN': 4, - 'shardGroupDuration': u'2h0m0s', - 'name': 'somename'} - ], - rsp - ) - - # Test alter default - self.cli.alter_retention_policy('somename', 'db', - default=True) - # NB: altering retention policy doesn't change shard group duration - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'duration': '0s', - 'default': False, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'}, - {'duration': '96h0m0s', - 'default': True, - 'replicaN': 4, - 'shardGroupDuration': u'2h0m0s', - 'name': 'somename'} - ], - rsp - ) - - # Test alter shard_duration - self.cli.alter_retention_policy('somename', 'db', - shard_duration='4h') - - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'duration': '0s', - 'default': False, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'}, - {'duration': '96h0m0s', - 'default': True, - 'replicaN': 4, - 'shardGroupDuration': u'4h0m0s', - 'name': 'somename'} - ], - rsp - ) - - def test_alter_retention_policy_invalid(self): - """Test invalid alter retention policy.""" - self.cli.create_retention_policy('somename', '1d', 1) - with self.assertRaises(InfluxDBClientError) as ctx: - self.cli.alter_retention_policy('somename', 'db') - self.assertEqual(400, ctx.exception.code) - self.assertIn('{"error":"error parsing query: ', - ctx.exception.content) - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'duration': '0s', - 'default': True, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'}, - {'duration': '24h0m0s', - 'default': False, - 'replicaN': 1, - 'shardGroupDuration': u'1h0m0s', - 'name': 'somename'} - ], - rsp - ) - - def test_drop_retention_policy(self): - """Test drop a retention policy.""" - self.cli.create_retention_policy('somename', '1d', 1) - - # Test drop retention - self.cli.drop_retention_policy('somename', 'db') - rsp = self.cli.get_list_retention_policies() - self.assertEqual( - [ - {'duration': '0s', - 'default': True, - 'replicaN': 1, - 'shardGroupDuration': u'168h0m0s', - 'name': 'autogen'} - ], - rsp - ) - - def test_issue_143(self): - """Test for PR#143 from repo.""" - pt = partial(point, 'a_series_name', timestamp='2015-03-30T16:16:37Z') - pts = [ - pt(value=15), - pt(tags={'tag_1': 'value1'}, value=5), - pt(tags={'tag_1': 'value2'}, value=10), - ] - self.cli.write_points(pts) - time.sleep(1) - rsp = list(self.cli.query('SELECT * FROM a_series_name \ -GROUP BY tag_1').get_points()) - - self.assertEqual( - [ - {'time': '2015-03-30T16:16:37Z', 'value': 15}, - {'time': '2015-03-30T16:16:37Z', 'value': 5}, - {'time': '2015-03-30T16:16:37Z', 'value': 10} - ], - rsp - ) - - # a slightly more complex one with 2 tags values: - pt = partial(point, 'series2', timestamp='2015-03-30T16:16:37Z') - pts = [ - pt(tags={'tag1': 'value1', 'tag2': 'v1'}, value=0), - pt(tags={'tag1': 'value1', 'tag2': 'v2'}, value=5), - pt(tags={'tag1': 'value2', 'tag2': 'v1'}, value=10), - ] - self.cli.write_points(pts) - time.sleep(1) - rsp = self.cli.query('SELECT * FROM series2 GROUP BY tag1,tag2') - - self.assertEqual( - [ - {'value': 0, 'time': '2015-03-30T16:16:37Z'}, - {'value': 5, 'time': '2015-03-30T16:16:37Z'}, - {'value': 10, 'time': '2015-03-30T16:16:37Z'} - ], - list(rsp['series2']) - ) - - all_tag2_equal_v1 = list(rsp.get_points(tags={'tag2': 'v1'})) - - self.assertEqual( - [{'value': 0, 'time': '2015-03-30T16:16:37Z'}, - {'value': 10, 'time': '2015-03-30T16:16:37Z'}], - all_tag2_equal_v1, - ) - - def test_query_multiple_series(self): - """Test query for multiple series.""" - pt = partial(point, 'series1', timestamp='2015-03-30T16:16:37Z') - pts = [ - pt(tags={'tag1': 'value1', 'tag2': 'v1'}, value=0), - ] - self.cli.write_points(pts) - - pt = partial(point, 'series2', timestamp='1970-03-30T16:16:37Z') - pts = [ - pt(tags={'tag1': 'value1', 'tag2': 'v1'}, - value=0, data1=33, data2="bla"), - ] - self.cli.write_points(pts) - - -@skipServerTests -class UdpTests(ManyTestCasesWithServerMixin, unittest.TestCase): - """Define a class to test UDP series.""" - - influxdb_udp_enabled = True - influxdb_template_conf = os.path.join(THIS_DIR, - 'influxdb.conf.template') - - def test_write_points_udp(self): - """Test write points UDP.""" - cli = InfluxDBClient( - 'localhost', - self.influxd_inst.http_port, - 'root', - '', - database='db', - use_udp=True, - udp_port=self.influxd_inst.udp_port - ) - cli.write_points(dummy_point) - - # The points are not immediately available after write_points. - # This is to be expected because we are using udp (no response !). - # So we have to wait some time, - time.sleep(3) # 3 sec seems to be a good choice. - rsp = self.cli.query('SELECT * FROM cpu_load_short') - - self.assertEqual( - # this is dummy_points : - [ - {'value': 0.64, - 'time': '2009-11-10T23:00:00Z', - "host": "server01", - "region": "us-west"} - ], - list(rsp['cpu_load_short']) - ) diff --git a/lib/influxdb/tests/server_tests/influxdb_instance.py b/lib/influxdb/tests/server_tests/influxdb_instance.py deleted file mode 100644 index 1dcd756..0000000 --- a/lib/influxdb/tests/server_tests/influxdb_instance.py +++ /dev/null @@ -1,198 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define the test module for an influxdb instance.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import datetime -import distutils -import os -import tempfile -import shutil -import subprocess -import sys -import time -import unittest - -from influxdb.tests.misc import is_port_open, get_free_ports - -# hack in check_output if it's not defined, like for python 2.6 -if "check_output" not in dir(subprocess): - def f(*popenargs, **kwargs): - """Check for output.""" - if 'stdout' in kwargs: - raise ValueError( - 'stdout argument not allowed, it will be overridden.' - ) - process = subprocess.Popen(stdout=subprocess.PIPE, - *popenargs, - **kwargs) - output, unused_err = process.communicate() - retcode = process.poll() - if retcode: - cmd = kwargs.get("args") - if cmd is None: - cmd = popenargs[0] - raise subprocess.CalledProcessError(retcode, cmd) - return output - subprocess.check_output = f - - -class InfluxDbInstance(object): - """Define an instance of InfluxDB. - - A class to launch of fresh influxdb server instance - in a temporary place, using a config file template. - """ - - def __init__(self, conf_template, udp_enabled=False): - """Initialize an instance of InfluxDbInstance.""" - if os.environ.get("INFLUXDB_PYTHON_SKIP_SERVER_TESTS", None) == 'True': - raise unittest.SkipTest( - "Skipping server test (INFLUXDB_PYTHON_SKIP_SERVER_TESTS)" - ) - - self.influxd_path = self.find_influxd_path() - - errors = 0 - while True: - try: - self._start_server(conf_template, udp_enabled) - break - # Happens when the ports are already in use. - except RuntimeError as e: - errors += 1 - if errors > 2: - raise e - - def _start_server(self, conf_template, udp_enabled): - # create a temporary dir to store all needed files - # for the influxdb server instance : - self.temp_dir_base = tempfile.mkdtemp() - - # "temp_dir_base" will be used for conf file and logs, - # while "temp_dir_influxdb" is for the databases files/dirs : - tempdir = self.temp_dir_influxdb = tempfile.mkdtemp( - dir=self.temp_dir_base) - - # find a couple free ports : - free_ports = get_free_ports(4) - ports = {} - for service in 'http', 'global', 'meta', 'udp': - ports[service + '_port'] = free_ports.pop() - if not udp_enabled: - ports['udp_port'] = -1 - - conf_data = dict( - meta_dir=os.path.join(tempdir, 'meta'), - data_dir=os.path.join(tempdir, 'data'), - wal_dir=os.path.join(tempdir, 'wal'), - cluster_dir=os.path.join(tempdir, 'state'), - handoff_dir=os.path.join(tempdir, 'handoff'), - logs_file=os.path.join(self.temp_dir_base, 'logs.txt'), - udp_enabled='true' if udp_enabled else 'false', - ) - conf_data.update(ports) - self.__dict__.update(conf_data) - - conf_file = os.path.join(self.temp_dir_base, 'influxdb.conf') - with open(conf_file, "w") as fh: - with open(conf_template) as fh_template: - fh.write(fh_template.read().format(**conf_data)) - - # now start the server instance: - self.proc = subprocess.Popen( - [self.influxd_path, '-config', conf_file], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - - print( - "%s > Started influxdb bin in %r with ports %s and %s.." % ( - datetime.datetime.now(), - self.temp_dir_base, - self.global_port, - self.http_port - ) - ) - - # wait for it to listen on the broker and admin ports: - # usually a fresh instance is ready in less than 1 sec .. - timeout = time.time() + 10 # so 10 secs should be enough, - # otherwise either your system load is high, - # or you run a 286 @ 1Mhz ? - try: - while time.time() < timeout: - if (is_port_open(self.http_port) and - is_port_open(self.global_port)): - # it's hard to check if a UDP port is open.. - if udp_enabled: - # so let's just sleep 0.5 sec in this case - # to be sure that the server has open the port - time.sleep(0.5) - break - time.sleep(0.5) - if self.proc.poll() is not None: - raise RuntimeError('influxdb prematurely exited') - else: - self.proc.terminate() - self.proc.wait() - raise RuntimeError('Timeout waiting for influxdb to listen' - ' on its ports (%s)' % ports) - except RuntimeError as err: - data = self.get_logs_and_output() - data['reason'] = str(err) - data['now'] = datetime.datetime.now() - raise RuntimeError("%(now)s > %(reason)s. RC=%(rc)s\n" - "stdout=%(out)s\nstderr=%(err)s\nlogs=%(logs)r" - % data) - - def find_influxd_path(self): - """Find the path for InfluxDB.""" - influxdb_bin_path = os.environ.get( - 'INFLUXDB_PYTHON_INFLUXD_PATH', - None - ) - - if influxdb_bin_path is None: - influxdb_bin_path = distutils.spawn.find_executable('influxd') - if not influxdb_bin_path: - try: - influxdb_bin_path = subprocess.check_output( - ['which', 'influxd'] - ).strip() - except subprocess.CalledProcessError: - # fallback on : - influxdb_bin_path = '/opt/influxdb/influxd' - - if not os.path.isfile(influxdb_bin_path): - raise unittest.SkipTest("Could not find influxd binary") - - version = subprocess.check_output([influxdb_bin_path, 'version']) - print("InfluxDB version: %s" % version, file=sys.stderr) - - return influxdb_bin_path - - def get_logs_and_output(self): - """Query for logs and output.""" - proc = self.proc - try: - with open(self.logs_file) as fh: - logs = fh.read() - except IOError as err: - logs = "Couldn't read logs: %s" % err - return { - 'rc': proc.returncode, - 'out': proc.stdout.read(), - 'err': proc.stderr.read(), - 'logs': logs - } - - def close(self, remove_tree=True): - """Close an instance of InfluxDB.""" - self.proc.terminate() - self.proc.wait() - if remove_tree: - shutil.rmtree(self.temp_dir_base) diff --git a/lib/influxdb/tests/test_line_protocol.py b/lib/influxdb/tests/test_line_protocol.py deleted file mode 100644 index a3d8479..0000000 --- a/lib/influxdb/tests/test_line_protocol.py +++ /dev/null @@ -1,147 +0,0 @@ -# -*- coding: utf-8 -*- -"""Define the line protocol test module.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from datetime import datetime -import unittest -from pytz import UTC, timezone - -from influxdb import line_protocol - - -class TestLineProtocol(unittest.TestCase): - """Define the LineProtocol test object.""" - - def test_make_lines(self): - """Test make new lines in TestLineProtocol object.""" - data = { - "tags": { - "empty_tag": "", - "none_tag": None, - "backslash_tag": "C:\\", - "integer_tag": 2, - "string_tag": "hello" - }, - "points": [ - { - "measurement": "test", - "fields": { - "string_val": "hello!", - "int_val": 1, - "float_val": 1.1, - "none_field": None, - "bool_val": True, - } - } - ] - } - - self.assertEqual( - line_protocol.make_lines(data), - 'test,backslash_tag=C:\\\\ ,integer_tag=2,string_tag=hello ' - 'bool_val=True,float_val=1.1,int_val=1i,string_val="hello!"\n' - ) - - def test_timezone(self): - """Test timezone in TestLineProtocol object.""" - dt = datetime(2009, 11, 10, 23, 0, 0, 123456) - utc = UTC.localize(dt) - berlin = timezone('Europe/Berlin').localize(dt) - eastern = berlin.astimezone(timezone('US/Eastern')) - data = { - "points": [ - {"measurement": "A", "fields": {"val": 1}, - "time": 0}, - {"measurement": "A", "fields": {"val": 1}, - "time": "2009-11-10T23:00:00.123456Z"}, - {"measurement": "A", "fields": {"val": 1}, "time": dt}, - {"measurement": "A", "fields": {"val": 1}, "time": utc}, - {"measurement": "A", "fields": {"val": 1}, "time": berlin}, - {"measurement": "A", "fields": {"val": 1}, "time": eastern}, - ] - } - self.assertEqual( - line_protocol.make_lines(data), - '\n'.join([ - 'A val=1i 0', - 'A val=1i 1257894000123456000', - 'A val=1i 1257894000123456000', - 'A val=1i 1257894000123456000', - 'A val=1i 1257890400123456000', - 'A val=1i 1257890400123456000', - ]) + '\n' - ) - - def test_string_val_newline(self): - """Test string value with newline in TestLineProtocol object.""" - data = { - "points": [ - { - "measurement": "m1", - "fields": { - "multi_line": "line1\nline1\nline3" - } - } - ] - } - - self.assertEqual( - line_protocol.make_lines(data), - 'm1 multi_line="line1\\nline1\\nline3"\n' - ) - - def test_make_lines_unicode(self): - """Test make unicode lines in TestLineProtocol object.""" - data = { - "tags": { - "unicode_tag": "\'Привет!\'" # Hello! in Russian - }, - "points": [ - { - "measurement": "test", - "fields": { - "unicode_val": "Привет!", # Hello! in Russian - } - } - ] - } - - self.assertEqual( - line_protocol.make_lines(data), - 'test,unicode_tag=\'Привет!\' unicode_val="Привет!"\n' - ) - - def test_quote_ident(self): - """Test quote indentation in TestLineProtocol object.""" - self.assertEqual( - line_protocol.quote_ident(r"""\foo ' bar " Örf"""), - r'''"\\foo ' bar \" Örf"''' - ) - - def test_quote_literal(self): - """Test quote literal in TestLineProtocol object.""" - self.assertEqual( - line_protocol.quote_literal(r"""\foo ' bar " Örf"""), - r"""'\\foo \' bar " Örf'""" - ) - - def test_float_with_long_decimal_fraction(self): - """Ensure precision is preserved when casting floats into strings.""" - data = { - "points": [ - { - "measurement": "test", - "fields": { - "float_val": 1.0000000000000009, - } - } - ] - } - self.assertEqual( - line_protocol.make_lines(data), - 'test float_val=1.0000000000000009\n' - ) diff --git a/lib/maxminddb/__init__.py b/lib/maxminddb/__init__.py deleted file mode 100644 index cb89c0f..0000000 --- a/lib/maxminddb/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -# pylint:disable=C0111 -import os - -import maxminddb.reader - -try: - import maxminddb.extension -except ImportError: - maxminddb.extension = None - -from maxminddb.const import (MODE_AUTO, MODE_MMAP, MODE_MMAP_EXT, MODE_FILE, - MODE_MEMORY, MODE_FD) -from maxminddb.decoder import InvalidDatabaseError - - -def open_database(database, mode=MODE_AUTO): - """Open a Maxmind DB database - - Arguments: - database -- A path to a valid MaxMind DB file such as a GeoIP2 database - file, or a file descriptor in the case of MODE_FD. - mode -- mode to open the database with. Valid mode are: - * MODE_MMAP_EXT - use the C extension with memory map. - * MODE_MMAP - read from memory map. Pure Python. - * MODE_FILE - read database as standard file. Pure Python. - * MODE_MEMORY - load database into memory. Pure Python. - * MODE_FD - the param passed via database is a file descriptor, not - a path. This mode implies MODE_MEMORY. - * MODE_AUTO - tries MODE_MMAP_EXT, MODE_MMAP, MODE_FILE in that - order. Default mode. - """ - has_extension = maxminddb.extension and hasattr(maxminddb.extension, - 'Reader') - if (mode == MODE_AUTO and has_extension) or mode == MODE_MMAP_EXT: - if not has_extension: - raise ValueError( - "MODE_MMAP_EXT requires the maxminddb.extension module to be available" - ) - return maxminddb.extension.Reader(database) - elif mode in (MODE_AUTO, MODE_MMAP, MODE_FILE, MODE_MEMORY, MODE_FD): - return maxminddb.reader.Reader(database, mode) - raise ValueError('Unsupported open mode: {0}'.format(mode)) - - -def Reader(database): # pylint: disable=invalid-name - """This exists for backwards compatibility. Use open_database instead""" - return open_database(database) - - -__title__ = 'maxminddb' -__version__ = '1.4.1' -__author__ = 'Gregory Oschwald' -__license__ = 'Apache License, Version 2.0' -__copyright__ = 'Copyright 2013-2018 Maxmind, Inc.' diff --git a/lib/maxminddb/compat.py b/lib/maxminddb/compat.py deleted file mode 100644 index 4542e75..0000000 --- a/lib/maxminddb/compat.py +++ /dev/null @@ -1,43 +0,0 @@ -import sys - -import ipaddress - -# pylint: skip-file - -if sys.version_info[0] == 2: - - def compat_ip_address(address): - if isinstance(address, bytes): - address = address.decode() - return ipaddress.ip_address(address) - - int_from_byte = ord - - FileNotFoundError = IOError - - def int_from_bytes(b): - if b: - return int(b.encode("hex"), 16) - return 0 - - byte_from_int = chr - - string_type = basestring - - string_type_name = 'string' -else: - - def compat_ip_address(address): - return ipaddress.ip_address(address) - - int_from_byte = lambda x: x - - FileNotFoundError = FileNotFoundError - - int_from_bytes = lambda x: int.from_bytes(x, 'big') - - byte_from_int = lambda x: bytes([x]) - - string_type = str - - string_type_name = string_type.__name__ diff --git a/lib/maxminddb/const.py b/lib/maxminddb/const.py deleted file mode 100644 index 45222c2..0000000 --- a/lib/maxminddb/const.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Constants used in the API""" - -MODE_AUTO = 0 -MODE_MMAP_EXT = 1 -MODE_MMAP = 2 -MODE_FILE = 4 -MODE_MEMORY = 8 -MODE_FD = 16 diff --git a/lib/maxminddb/decoder.py b/lib/maxminddb/decoder.py deleted file mode 100644 index 00f8bb1..0000000 --- a/lib/maxminddb/decoder.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -maxminddb.decoder -~~~~~~~~~~~~~~~~~ - -This package contains code for decoding the MaxMind DB data section. - -""" -from __future__ import unicode_literals - -import struct - -from maxminddb.compat import byte_from_int, int_from_bytes -from maxminddb.errors import InvalidDatabaseError - - -class Decoder(object): # pylint: disable=too-few-public-methods - """Decoder for the data section of the MaxMind DB""" - - def __init__(self, database_buffer, pointer_base=0, pointer_test=False): - """Created a Decoder for a MaxMind DB - - Arguments: - database_buffer -- an mmap'd MaxMind DB file. - pointer_base -- the base number to use when decoding a pointer - pointer_test -- used for internal unit testing of pointer code - """ - self._pointer_test = pointer_test - self._buffer = database_buffer - self._pointer_base = pointer_base - - def _decode_array(self, size, offset): - array = [] - for _ in range(size): - (value, offset) = self.decode(offset) - array.append(value) - return array, offset - - def _decode_boolean(self, size, offset): - return size != 0, offset - - def _decode_bytes(self, size, offset): - new_offset = offset + size - return self._buffer[offset:new_offset], new_offset - - # pylint: disable=no-self-argument - # |-> I am open to better ways of doing this as long as it doesn't involve - # lots of code duplication. - def _decode_packed_type(type_code, type_size, pad=False): - # pylint: disable=protected-access, missing-docstring - def unpack_type(self, size, offset): - if not pad: - self._verify_size(size, type_size) - new_offset = offset + size - packed_bytes = self._buffer[offset:new_offset] - if pad: - packed_bytes = packed_bytes.rjust(type_size, b'\x00') - (value, ) = struct.unpack(type_code, packed_bytes) - return value, new_offset - - return unpack_type - - def _decode_map(self, size, offset): - container = {} - for _ in range(size): - (key, offset) = self.decode(offset) - (value, offset) = self.decode(offset) - container[key] = value - return container, offset - - _pointer_value_offset = { - 1: 0, - 2: 2048, - 3: 526336, - 4: 0, - } - - def _decode_pointer(self, size, offset): - pointer_size = ((size >> 3) & 0x3) + 1 - new_offset = offset + pointer_size - pointer_bytes = self._buffer[offset:new_offset] - packed = pointer_bytes if pointer_size == 4 else struct.pack( - b'!c', byte_from_int(size & 0x7)) + pointer_bytes - unpacked = int_from_bytes(packed) - pointer = unpacked + self._pointer_base + \ - self._pointer_value_offset[pointer_size] - if self._pointer_test: - return pointer, new_offset - (value, _) = self.decode(pointer) - return value, new_offset - - def _decode_uint(self, size, offset): - new_offset = offset + size - uint_bytes = self._buffer[offset:new_offset] - return int_from_bytes(uint_bytes), new_offset - - def _decode_utf8_string(self, size, offset): - new_offset = offset + size - return self._buffer[offset:new_offset].decode('utf-8'), new_offset - - _type_decoder = { - 1: _decode_pointer, - 2: _decode_utf8_string, - 3: _decode_packed_type(b'!d', 8), # double, - 4: _decode_bytes, - 5: _decode_uint, # uint16 - 6: _decode_uint, # uint32 - 7: _decode_map, - 8: _decode_packed_type(b'!i', 4, pad=True), # int32 - 9: _decode_uint, # uint64 - 10: _decode_uint, # uint128 - 11: _decode_array, - 14: _decode_boolean, - 15: _decode_packed_type(b'!f', 4), # float, - } - - def decode(self, offset): - """Decode a section of the data section starting at offset - - Arguments: - offset -- the location of the data structure to decode - """ - new_offset = offset + 1 - (ctrl_byte, ) = struct.unpack(b'!B', self._buffer[offset:new_offset]) - type_num = ctrl_byte >> 5 - # Extended type - if not type_num: - (type_num, new_offset) = self._read_extended(new_offset) - - if type_num not in self._type_decoder: - raise InvalidDatabaseError('Unexpected type number ({type}) ' - 'encountered'.format(type=type_num)) - - (size, new_offset) = self._size_from_ctrl_byte(ctrl_byte, new_offset, - type_num) - return self._type_decoder[type_num](self, size, new_offset) - - def _read_extended(self, offset): - (next_byte, ) = struct.unpack(b'!B', self._buffer[offset:offset + 1]) - type_num = next_byte + 7 - if type_num < 7: - raise InvalidDatabaseError( - 'Something went horribly wrong in the decoder. An ' - 'extended type resolved to a type number < 8 ' - '({type})'.format(type=type_num)) - return type_num, offset + 1 - - def _verify_size(self, expected, actual): - if expected != actual: - raise InvalidDatabaseError( - 'The MaxMind DB file\'s data section contains bad data ' - '(unknown data type or corrupt data)') - - def _size_from_ctrl_byte(self, ctrl_byte, offset, type_num): - size = ctrl_byte & 0x1f - if type_num == 1: - return size, offset - bytes_to_read = 0 if size < 29 else size - 28 - - new_offset = offset + bytes_to_read - size_bytes = self._buffer[offset:new_offset] - - # Using unpack rather than int_from_bytes as it is about 200 lookups - # per second faster here. - if size == 29: - size = 29 + struct.unpack(b'!B', size_bytes)[0] - elif size == 30: - size = 285 + struct.unpack(b'!H', size_bytes)[0] - elif size > 30: - size = struct.unpack(b'!I', size_bytes.rjust(4, - b'\x00'))[0] + 65821 - - return size, new_offset diff --git a/lib/maxminddb/errors.py b/lib/maxminddb/errors.py deleted file mode 100644 index c996b96..0000000 --- a/lib/maxminddb/errors.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -maxminddb.errors -~~~~~~~~~~~~~~~~ - -This module contains custom errors for the MaxMind DB reader -""" - - -class InvalidDatabaseError(RuntimeError): - """This error is thrown when unexpected data is found in the database.""" diff --git a/lib/maxminddb/extension/maxminddb.c b/lib/maxminddb/extension/maxminddb.c deleted file mode 100644 index ac193ee..0000000 --- a/lib/maxminddb/extension/maxminddb.c +++ /dev/null @@ -1,602 +0,0 @@ -#include <Python.h> -#include <maxminddb.h> -#include "structmember.h" - -#define __STDC_FORMAT_MACROS -#include <inttypes.h> - -static PyTypeObject Reader_Type; -static PyTypeObject Metadata_Type; -static PyObject *MaxMindDB_error; - -typedef struct { - PyObject_HEAD /* no semicolon */ - MMDB_s *mmdb; - PyObject *closed; -} Reader_obj; - -typedef struct { - PyObject_HEAD /* no semicolon */ - PyObject *binary_format_major_version; - PyObject *binary_format_minor_version; - PyObject *build_epoch; - PyObject *database_type; - PyObject *description; - PyObject *ip_version; - PyObject *languages; - PyObject *node_count; - PyObject *record_size; -} Metadata_obj; - -static PyObject *from_entry_data_list(MMDB_entry_data_list_s **entry_data_list); -static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list); -static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list); -static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list); - -#if PY_MAJOR_VERSION >= 3 - #define MOD_INIT(name) PyMODINIT_FUNC PyInit_ ## name(void) - #define RETURN_MOD_INIT(m) return (m) - #define FILE_NOT_FOUND_ERROR PyExc_FileNotFoundError -#else - #define MOD_INIT(name) PyMODINIT_FUNC init ## name(void) - #define RETURN_MOD_INIT(m) return - #define PyInt_FromLong PyLong_FromLong - #define FILE_NOT_FOUND_ERROR PyExc_IOError -#endif - -#ifdef __GNUC__ - # define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) -#else - # define UNUSED(x) UNUSED_ ## x -#endif - -static int Reader_init(PyObject *self, PyObject *args, PyObject *kwds) -{ - char *filename; - int mode = 0; - - static char *kwlist[] = {"database", "mode", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|i", kwlist, &filename, &mode)) { - return -1; - } - - if (mode != 0 && mode != 1) { - PyErr_Format(PyExc_ValueError, "Unsupported open mode (%i). Only " - "MODE_AUTO and MODE_MMAP_EXT are supported by this extension.", - mode); - return -1; - } - - if (0 != access(filename, R_OK)) { - PyErr_Format(FILE_NOT_FOUND_ERROR, - "No such file or directory: '%s'", - filename); - return -1; - } - - MMDB_s *mmdb = (MMDB_s *)malloc(sizeof(MMDB_s)); - if (NULL == mmdb) { - PyErr_NoMemory(); - return -1; - } - - Reader_obj *mmdb_obj = (Reader_obj *)self; - if (!mmdb_obj) { - free(mmdb); - PyErr_NoMemory(); - return -1; - } - - uint16_t status = MMDB_open(filename, MMDB_MODE_MMAP, mmdb); - - if (MMDB_SUCCESS != status) { - free(mmdb); - PyErr_Format( - MaxMindDB_error, - "Error opening database file (%s). Is this a valid MaxMind DB file?", - filename - ); - return -1; - } - - mmdb_obj->mmdb = mmdb; - mmdb_obj->closed = Py_False; - return 0; -} - -static PyObject *Reader_get(PyObject *self, PyObject *args) -{ - char *ip_address = NULL; - - Reader_obj *mmdb_obj = (Reader_obj *)self; - if (!PyArg_ParseTuple(args, "s", &ip_address)) { - return NULL; - } - - MMDB_s *mmdb = mmdb_obj->mmdb; - - if (NULL == mmdb) { - PyErr_SetString(PyExc_ValueError, - "Attempt to read from a closed MaxMind DB."); - return NULL; - } - - int gai_error = 0; - int mmdb_error = MMDB_SUCCESS; - MMDB_lookup_result_s result = - MMDB_lookup_string(mmdb, ip_address, &gai_error, - &mmdb_error); - - if (0 != gai_error) { - PyErr_Format(PyExc_ValueError, - "'%s' does not appear to be an IPv4 or IPv6 address.", - ip_address); - return NULL; - } - - if (MMDB_SUCCESS != mmdb_error) { - PyObject *exception; - if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) { - exception = PyExc_ValueError; - } else { - exception = MaxMindDB_error; - } - PyErr_Format(exception, "Error looking up %s. %s", - ip_address, MMDB_strerror(mmdb_error)); - return NULL; - } - - if (!result.found_entry) { - Py_RETURN_NONE; - } - - MMDB_entry_data_list_s *entry_data_list = NULL; - int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list); - if (MMDB_SUCCESS != status) { - PyErr_Format(MaxMindDB_error, - "Error while looking up data for %s. %s", - ip_address, MMDB_strerror(status)); - MMDB_free_entry_data_list(entry_data_list); - return NULL; - } - - MMDB_entry_data_list_s *original_entry_data_list = entry_data_list; - PyObject *py_obj = from_entry_data_list(&entry_data_list); - MMDB_free_entry_data_list(original_entry_data_list); - return py_obj; -} - -static PyObject *Reader_metadata(PyObject *self, PyObject *UNUSED(args)) -{ - Reader_obj *mmdb_obj = (Reader_obj *)self; - - if (NULL == mmdb_obj->mmdb) { - PyErr_SetString(PyExc_IOError, - "Attempt to read from a closed MaxMind DB."); - return NULL; - } - - MMDB_entry_data_list_s *entry_data_list; - MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list); - MMDB_entry_data_list_s *original_entry_data_list = entry_data_list; - - PyObject *metadata_dict = from_entry_data_list(&entry_data_list); - MMDB_free_entry_data_list(original_entry_data_list); - if (NULL == metadata_dict || !PyDict_Check(metadata_dict)) { - PyErr_SetString(MaxMindDB_error, - "Error decoding metadata."); - return NULL; - } - - PyObject *args = PyTuple_New(0); - if (NULL == args) { - Py_DECREF(metadata_dict); - return NULL; - } - - PyObject *metadata = PyObject_Call((PyObject *)&Metadata_Type, args, - metadata_dict); - - Py_DECREF(metadata_dict); - return metadata; -} - -static PyObject *Reader_close(PyObject *self, PyObject *UNUSED(args)) -{ - Reader_obj *mmdb_obj = (Reader_obj *)self; - - if (NULL != mmdb_obj->mmdb) { - MMDB_close(mmdb_obj->mmdb); - free(mmdb_obj->mmdb); - mmdb_obj->mmdb = NULL; - } - - mmdb_obj->closed = Py_True; - - Py_RETURN_NONE; -} - -static PyObject *Reader__enter__(PyObject *self, PyObject *UNUSED(args)) -{ - Reader_obj *mmdb_obj = (Reader_obj *)self; - - if(mmdb_obj->closed == Py_True) { - PyErr_SetString(PyExc_ValueError, - "Attempt to reopen a closed MaxMind DB."); - return NULL; - } - - Py_INCREF(self); - return (PyObject *)self; -} - -static PyObject *Reader__exit__(PyObject *self, PyObject *UNUSED(args)) -{ - Reader_close(self, NULL); - Py_RETURN_NONE; -} - -static void Reader_dealloc(PyObject *self) -{ - Reader_obj *obj = (Reader_obj *)self; - if (NULL != obj->mmdb) { - Reader_close(self, NULL); - } - - PyObject_Del(self); -} - -static int Metadata_init(PyObject *self, PyObject *args, PyObject *kwds) -{ - - PyObject - *binary_format_major_version, - *binary_format_minor_version, - *build_epoch, - *database_type, - *description, - *ip_version, - *languages, - *node_count, - *record_size; - - static char *kwlist[] = { - "binary_format_major_version", - "binary_format_minor_version", - "build_epoch", - "database_type", - "description", - "ip_version", - "languages", - "node_count", - "record_size", - NULL - }; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOOOOO", kwlist, - &binary_format_major_version, - &binary_format_minor_version, - &build_epoch, - &database_type, - &description, - &ip_version, - &languages, - &node_count, - &record_size)) { - return -1; - } - - Metadata_obj *obj = (Metadata_obj *)self; - - obj->binary_format_major_version = binary_format_major_version; - obj->binary_format_minor_version = binary_format_minor_version; - obj->build_epoch = build_epoch; - obj->database_type = database_type; - obj->description = description; - obj->ip_version = ip_version; - obj->languages = languages; - obj->node_count = node_count; - obj->record_size = record_size; - - Py_INCREF(obj->binary_format_major_version); - Py_INCREF(obj->binary_format_minor_version); - Py_INCREF(obj->build_epoch); - Py_INCREF(obj->database_type); - Py_INCREF(obj->description); - Py_INCREF(obj->ip_version); - Py_INCREF(obj->languages); - Py_INCREF(obj->node_count); - Py_INCREF(obj->record_size); - - return 0; -} - -static void Metadata_dealloc(PyObject *self) -{ - Metadata_obj *obj = (Metadata_obj *)self; - Py_DECREF(obj->binary_format_major_version); - Py_DECREF(obj->binary_format_minor_version); - Py_DECREF(obj->build_epoch); - Py_DECREF(obj->database_type); - Py_DECREF(obj->description); - Py_DECREF(obj->ip_version); - Py_DECREF(obj->languages); - Py_DECREF(obj->node_count); - Py_DECREF(obj->record_size); - PyObject_Del(self); -} - -static PyObject *from_entry_data_list(MMDB_entry_data_list_s **entry_data_list) -{ - if (NULL == entry_data_list || NULL == *entry_data_list) { - PyErr_SetString( - MaxMindDB_error, - "Error while looking up data. Your database may be corrupt or you have found a bug in libmaxminddb." - ); - return NULL; - } - - switch ((*entry_data_list)->entry_data.type) { - case MMDB_DATA_TYPE_MAP: - return from_map(entry_data_list); - case MMDB_DATA_TYPE_ARRAY: - return from_array(entry_data_list); - case MMDB_DATA_TYPE_UTF8_STRING: - return PyUnicode_FromStringAndSize( - (*entry_data_list)->entry_data.utf8_string, - (*entry_data_list)->entry_data.data_size - ); - case MMDB_DATA_TYPE_BYTES: - return PyByteArray_FromStringAndSize( - (const char *)(*entry_data_list)->entry_data.bytes, - (Py_ssize_t)(*entry_data_list)->entry_data.data_size); - case MMDB_DATA_TYPE_DOUBLE: - return PyFloat_FromDouble((*entry_data_list)->entry_data.double_value); - case MMDB_DATA_TYPE_FLOAT: - return PyFloat_FromDouble((*entry_data_list)->entry_data.float_value); - case MMDB_DATA_TYPE_UINT16: - return PyLong_FromLong( (*entry_data_list)->entry_data.uint16); - case MMDB_DATA_TYPE_UINT32: - return PyLong_FromLong((*entry_data_list)->entry_data.uint32); - case MMDB_DATA_TYPE_BOOLEAN: - return PyBool_FromLong((*entry_data_list)->entry_data.boolean); - case MMDB_DATA_TYPE_UINT64: - return PyLong_FromUnsignedLongLong( - (*entry_data_list)->entry_data.uint64); - case MMDB_DATA_TYPE_UINT128: - return from_uint128(*entry_data_list); - case MMDB_DATA_TYPE_INT32: - return PyLong_FromLong((*entry_data_list)->entry_data.int32); - default: - PyErr_Format(MaxMindDB_error, - "Invalid data type arguments: %d", - (*entry_data_list)->entry_data.type); - return NULL; - } - return NULL; -} - -static PyObject *from_map(MMDB_entry_data_list_s **entry_data_list) -{ - PyObject *py_obj = PyDict_New(); - if (NULL == py_obj) { - PyErr_NoMemory(); - return NULL; - } - - const uint32_t map_size = (*entry_data_list)->entry_data.data_size; - - uint i; - // entry_data_list cannot start out NULL (see from_entry_data_list). We - // check it in the loop because it may become NULL. - // coverity[check_after_deref] - for (i = 0; i < map_size && entry_data_list; i++) { - *entry_data_list = (*entry_data_list)->next; - - PyObject *key = PyUnicode_FromStringAndSize( - (char *)(*entry_data_list)->entry_data.utf8_string, - (*entry_data_list)->entry_data.data_size - ); - - *entry_data_list = (*entry_data_list)->next; - - PyObject *value = from_entry_data_list(entry_data_list); - if (NULL == value) { - Py_DECREF(key); - Py_DECREF(py_obj); - return NULL; - } - PyDict_SetItem(py_obj, key, value); - Py_DECREF(value); - Py_DECREF(key); - } - - return py_obj; -} - -static PyObject *from_array(MMDB_entry_data_list_s **entry_data_list) -{ - const uint32_t size = (*entry_data_list)->entry_data.data_size; - - PyObject *py_obj = PyList_New(size); - if (NULL == py_obj) { - PyErr_NoMemory(); - return NULL; - } - - uint i; - // entry_data_list cannot start out NULL (see from_entry_data_list). We - // check it in the loop because it may become NULL. - // coverity[check_after_deref] - for (i = 0; i < size && entry_data_list; i++) { - *entry_data_list = (*entry_data_list)->next; - PyObject *value = from_entry_data_list(entry_data_list); - if (NULL == value) { - Py_DECREF(py_obj); - return NULL; - } - // PyList_SetItem 'steals' the reference - PyList_SetItem(py_obj, i, value); - } - return py_obj; -} - -static PyObject *from_uint128(const MMDB_entry_data_list_s *entry_data_list) -{ - uint64_t high = 0; - uint64_t low = 0; -#if MMDB_UINT128_IS_BYTE_ARRAY - int i; - for (i = 0; i < 8; i++) { - high = (high << 8) | entry_data_list->entry_data.uint128[i]; - } - - for (i = 8; i < 16; i++) { - low = (low << 8) | entry_data_list->entry_data.uint128[i]; - } -#else - high = entry_data_list->entry_data.uint128 >> 64; - low = (uint64_t)entry_data_list->entry_data.uint128; -#endif - - char *num_str = malloc(33); - if (NULL == num_str) { - PyErr_NoMemory(); - return NULL; - } - - snprintf(num_str, 33, "%016" PRIX64 "%016" PRIX64, high, low); - - PyObject *py_obj = PyLong_FromString(num_str, NULL, 16); - - free(num_str); - return py_obj; -} - -static PyMethodDef Reader_methods[] = { - { "get", Reader_get, METH_VARARGS, - "Get record for IP address" }, - { "metadata", Reader_metadata, METH_NOARGS, - "Returns metadata object for database" }, - { "close", Reader_close, METH_NOARGS, "Closes database"}, - { "__exit__", Reader__exit__, METH_VARARGS, "Called when exiting a with-context. Calls close"}, - { "__enter__", Reader__enter__, METH_NOARGS, "Called when entering a with-context."}, - { NULL, NULL, 0, NULL } -}; - -static PyMemberDef Reader_members[] = { - { "closed", T_OBJECT, offsetof(Reader_obj, closed), READONLY, NULL }, - { NULL, 0, 0, 0, NULL } -}; - -static PyTypeObject Reader_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_basicsize = sizeof(Reader_obj), - .tp_dealloc = Reader_dealloc, - .tp_doc = "Reader object", - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_methods = Reader_methods, - .tp_members = Reader_members, - .tp_name = "Reader", - .tp_init = Reader_init, -}; - -static PyMethodDef Metadata_methods[] = { - { NULL, NULL, 0, NULL } -}; - -/* *INDENT-OFF* */ -static PyMemberDef Metadata_members[] = { - { "binary_format_major_version", T_OBJECT, offsetof( - Metadata_obj, binary_format_major_version), READONLY, NULL }, - { "binary_format_minor_version", T_OBJECT, offsetof( - Metadata_obj, binary_format_minor_version), READONLY, NULL }, - { "build_epoch", T_OBJECT, offsetof(Metadata_obj, build_epoch), - READONLY, NULL }, - { "database_type", T_OBJECT, offsetof(Metadata_obj, database_type), - READONLY, NULL }, - { "description", T_OBJECT, offsetof(Metadata_obj, description), - READONLY, NULL }, - { "ip_version", T_OBJECT, offsetof(Metadata_obj, ip_version), - READONLY, NULL }, - { "languages", T_OBJECT, offsetof(Metadata_obj, languages), READONLY, - NULL }, - { "node_count", T_OBJECT, offsetof(Metadata_obj, node_count), - READONLY, NULL }, - { "record_size", T_OBJECT, offsetof(Metadata_obj, record_size), - READONLY, NULL }, - { NULL, 0, 0, 0, NULL } -}; -/* *INDENT-ON* */ - -static PyTypeObject Metadata_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_basicsize = sizeof(Metadata_obj), - .tp_dealloc = Metadata_dealloc, - .tp_doc = "Metadata object", - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_members = Metadata_members, - .tp_methods = Metadata_methods, - .tp_name = "Metadata", - .tp_init = Metadata_init -}; - -static PyMethodDef MaxMindDB_methods[] = { - { NULL, NULL, 0, NULL } -}; - - -#if PY_MAJOR_VERSION >= 3 -static struct PyModuleDef MaxMindDB_module = { - PyModuleDef_HEAD_INIT, - .m_name = "extension", - .m_doc = "This is a C extension to read MaxMind DB file format", - .m_methods = MaxMindDB_methods, -}; -#endif - -MOD_INIT(extension){ - PyObject *m; - -#if PY_MAJOR_VERSION >= 3 - m = PyModule_Create(&MaxMindDB_module); -#else - m = Py_InitModule("extension", MaxMindDB_methods); -#endif - - if (!m) { - RETURN_MOD_INIT(NULL); - } - - Reader_Type.tp_new = PyType_GenericNew; - if (PyType_Ready(&Reader_Type)) { - RETURN_MOD_INIT(NULL); - } - Py_INCREF(&Reader_Type); - PyModule_AddObject(m, "Reader", (PyObject *)&Reader_Type); - - Metadata_Type.tp_new = PyType_GenericNew; - if (PyType_Ready(&Metadata_Type)) { - RETURN_MOD_INIT(NULL); - } - PyModule_AddObject(m, "extension", (PyObject *)&Metadata_Type); - - PyObject* error_mod = PyImport_ImportModule("maxminddb.errors"); - if (error_mod == NULL) { - RETURN_MOD_INIT(NULL); - } - - MaxMindDB_error = PyObject_GetAttrString(error_mod, "InvalidDatabaseError"); - Py_DECREF(error_mod); - - if (MaxMindDB_error == NULL) { - RETURN_MOD_INIT(NULL); - } - - Py_INCREF(MaxMindDB_error); - - /* We primarily add it to the module for backwards compatibility */ - PyModule_AddObject(m, "InvalidDatabaseError", MaxMindDB_error); - - RETURN_MOD_INIT(m); -} diff --git a/lib/maxminddb/file.py b/lib/maxminddb/file.py deleted file mode 100644 index 92b180b..0000000 --- a/lib/maxminddb/file.py +++ /dev/null @@ -1,65 +0,0 @@ -"""For internal use only. It provides a slice-like file reader.""" - -import os - -try: - # pylint: disable=no-name-in-module - from multiprocessing import Lock -except ImportError: - from threading import Lock - - -class FileBuffer(object): - """A slice-able file reader""" - - def __init__(self, database): - self._handle = open(database, 'rb') - self._size = os.fstat(self._handle.fileno()).st_size - if not hasattr(os, 'pread'): - self._lock = Lock() - - def __getitem__(self, key): - if isinstance(key, slice): - return self._read(key.stop - key.start, key.start) - elif isinstance(key, int): - return self._read(1, key) - else: - raise TypeError("Invalid argument type.") - - def rfind(self, needle, start): - """Reverse find needle from start""" - pos = self._read(self._size - start - 1, start).rfind(needle) - if pos == -1: - return pos - return start + pos - - def size(self): - """Size of file""" - return self._size - - def close(self): - """Close file""" - self._handle.close() - - if hasattr(os, 'pread'): - - def _read(self, buffersize, offset): - """read that uses pread""" - # pylint: disable=no-member - return os.pread(self._handle.fileno(), buffersize, offset) - - else: - - def _read(self, buffersize, offset): - """read with a lock - - This lock is necessary as after a fork, the different processes - will share the same file table entry, even if we dup the fd, and - as such the same offsets. There does not appear to be a way to - duplicate the file table entry and we cannot re-open based on the - original path as that file may have replaced with another or - unlinked. - """ - with self._lock: - self._handle.seek(offset) - return self._handle.read(buffersize) diff --git a/lib/maxminddb/reader.py b/lib/maxminddb/reader.py deleted file mode 100644 index 3761b7d..0000000 --- a/lib/maxminddb/reader.py +++ /dev/null @@ -1,309 +0,0 @@ -""" -maxminddb.reader -~~~~~~~~~~~~~~~~ - -This module contains the pure Python database reader and related classes. - -""" -from __future__ import unicode_literals - -try: - import mmap -except ImportError: - # pylint: disable=invalid-name - mmap = None - -import struct - -from maxminddb.compat import (byte_from_int, compat_ip_address, string_type, - string_type_name) -from maxminddb.const import MODE_AUTO, MODE_MMAP, MODE_FILE, MODE_MEMORY, MODE_FD -from maxminddb.decoder import Decoder -from maxminddb.errors import InvalidDatabaseError -from maxminddb.file import FileBuffer - - -class Reader(object): - """ - Instances of this class provide a reader for the MaxMind DB format. IP - addresses can be looked up using the ``get`` method. - """ - - _DATA_SECTION_SEPARATOR_SIZE = 16 - _METADATA_START_MARKER = b"\xAB\xCD\xEFMaxMind.com" - - _ipv4_start = None - - def __init__(self, database, mode=MODE_AUTO): - """Reader for the MaxMind DB file format - - Arguments: - database -- A path to a valid MaxMind DB file such as a GeoIP2 database - file, or a file descriptor in the case of MODE_FD. - mode -- mode to open the database with. Valid mode are: - * MODE_MMAP - read from memory map. - * MODE_FILE - read database as standard file. - * MODE_MEMORY - load database into memory. - * MODE_AUTO - tries MODE_MMAP and then MODE_FILE. Default. - * MODE_FD - the param passed via database is a file descriptor, not - a path. This mode implies MODE_MEMORY. - """ - if (mode == MODE_AUTO and mmap) or mode == MODE_MMAP: - with open(database, 'rb') as db_file: - self._buffer = mmap.mmap( - db_file.fileno(), 0, access=mmap.ACCESS_READ) - self._buffer_size = self._buffer.size() - filename = database - elif mode in (MODE_AUTO, MODE_FILE): - self._buffer = FileBuffer(database) - self._buffer_size = self._buffer.size() - filename = database - elif mode == MODE_MEMORY: - with open(database, 'rb') as db_file: - self._buffer = db_file.read() - self._buffer_size = len(self._buffer) - filename = database - elif mode == MODE_FD: - self._buffer = database.read() - self._buffer_size = len(self._buffer) - filename = database.name - else: - raise ValueError( - 'Unsupported open mode ({0}). Only MODE_AUTO, MODE_FILE, ' - 'MODE_MEMORY and MODE_FD are supported by the pure Python ' - 'Reader'.format(mode)) - - metadata_start = self._buffer.rfind( - self._METADATA_START_MARKER, max(0, - self._buffer_size - 128 * 1024)) - - if metadata_start == -1: - self.close() - raise InvalidDatabaseError('Error opening database file ({0}). ' - 'Is this a valid MaxMind DB file?' - ''.format(filename)) - - metadata_start += len(self._METADATA_START_MARKER) - metadata_decoder = Decoder(self._buffer, metadata_start) - (metadata, _) = metadata_decoder.decode(metadata_start) - self._metadata = Metadata(**metadata) # pylint: disable=bad-option-value - - self._decoder = Decoder(self._buffer, self._metadata.search_tree_size + - self._DATA_SECTION_SEPARATOR_SIZE) - self.closed = False - - def metadata(self): - """Return the metadata associated with the MaxMind DB file""" - return self._metadata - - def get(self, ip_address): - """Return the record for the ip_address in the MaxMind DB - - - Arguments: - ip_address -- an IP address in the standard string notation - """ - if not isinstance(ip_address, string_type): - raise TypeError('argument 1 must be %s, not %s' % - (string_type_name, type(ip_address).__name__)) - - address = compat_ip_address(ip_address) - - if address.version == 6 and self._metadata.ip_version == 4: - raise ValueError( - 'Error looking up {0}. You attempted to look up ' - 'an IPv6 address in an IPv4-only database.'.format(ip_address)) - pointer = self._find_address_in_tree(address) - - return self._resolve_data_pointer(pointer) if pointer else None - - def _find_address_in_tree(self, ip_address): - packed = bytearray(ip_address.packed) - - bit_count = len(packed) * 8 - node = self._start_node(bit_count) - - for i in range(bit_count): - if node >= self._metadata.node_count: - break - bit = 1 & (packed[i >> 3] >> 7 - (i % 8)) - node = self._read_node(node, bit) - if node == self._metadata.node_count: - # Record is empty - return 0 - elif node > self._metadata.node_count: - return node - - raise InvalidDatabaseError('Invalid node in search tree') - - def _start_node(self, length): - if self._metadata.ip_version != 6 or length == 128: - return 0 - - # We are looking up an IPv4 address in an IPv6 tree. Skip over the - # first 96 nodes. - if self._ipv4_start: - return self._ipv4_start - - node = 0 - for _ in range(96): - if node >= self._metadata.node_count: - break - node = self._read_node(node, 0) - self._ipv4_start = node - return node - - def _read_node(self, node_number, index): - base_offset = node_number * self._metadata.node_byte_size - - record_size = self._metadata.record_size - if record_size == 24: - offset = base_offset + index * 3 - node_bytes = b'\x00' + self._buffer[offset:offset + 3] - elif record_size == 28: - (middle, ) = struct.unpack( - b'!B', self._buffer[base_offset + 3:base_offset + 4]) - if index: - middle &= 0x0F - else: - middle = (0xF0 & middle) >> 4 - offset = base_offset + index * 4 - node_bytes = byte_from_int(middle) + self._buffer[offset:offset + 3] - elif record_size == 32: - offset = base_offset + index * 4 - node_bytes = self._buffer[offset:offset + 4] - else: - raise InvalidDatabaseError( - 'Unknown record size: {0}'.format(record_size)) - return struct.unpack(b'!I', node_bytes)[0] - - def _resolve_data_pointer(self, pointer): - resolved = pointer - self._metadata.node_count + \ - self._metadata.search_tree_size - - if resolved > self._buffer_size: - raise InvalidDatabaseError( - "The MaxMind DB file's search tree is corrupt") - - (data, _) = self._decoder.decode(resolved) - return data - - def close(self): - """Closes the MaxMind DB file and returns the resources to the system""" - # pylint: disable=unidiomatic-typecheck - if type(self._buffer) not in (str, bytes): - self._buffer.close() - self.closed = True - - def __exit__(self, *args): - self.close() - - def __enter__(self): - if self.closed: - raise ValueError('Attempt to reopen a closed MaxMind DB') - return self - - -class Metadata(object): - """Metadata for the MaxMind DB reader - - - .. attribute:: binary_format_major_version - - The major version number of the binary format used when creating the - database. - - :type: int - - .. attribute:: binary_format_minor_version - - The minor version number of the binary format used when creating the - database. - - :type: int - - .. attribute:: build_epoch - - The Unix epoch for the build time of the database. - - :type: int - - .. attribute:: database_type - - A string identifying the database type, e.g., "GeoIP2-City". - - :type: str - - .. attribute:: description - - A map from locales to text descriptions of the database. - - :type: dict(str, str) - - .. attribute:: ip_version - - The IP version of the data in a database. A value of "4" means the - database only supports IPv4. A database with a value of "6" may support - both IPv4 and IPv6 lookups. - - :type: int - - .. attribute:: languages - - A list of locale codes supported by the databse. - - :type: list(str) - - .. attribute:: node_count - - The number of nodes in the database. - - :type: int - - .. attribute:: record_size - - The bit size of a record in the search tree. - - :type: int - - """ - - # pylint: disable=too-many-instance-attributes - def __init__(self, **kwargs): - """Creates new Metadata object. kwargs are key/value pairs from spec""" - # Although I could just update __dict__, that is less obvious and it - # doesn't work well with static analysis tools and some IDEs - self.node_count = kwargs['node_count'] - self.record_size = kwargs['record_size'] - self.ip_version = kwargs['ip_version'] - self.database_type = kwargs['database_type'] - self.languages = kwargs['languages'] - self.binary_format_major_version = kwargs[ - 'binary_format_major_version'] - self.binary_format_minor_version = kwargs[ - 'binary_format_minor_version'] - self.build_epoch = kwargs['build_epoch'] - self.description = kwargs['description'] - - @property - def node_byte_size(self): - """The size of a node in bytes - - :type: int - """ - return self.record_size // 4 - - @property - def search_tree_size(self): - """The size of the search tree - - :type: int - """ - return self.node_count * self.node_byte_size - - def __repr__(self): - args = ', '.join('%s=%r' % x for x in self.__dict__.items()) - return '{module}.{class_name}({data})'.format( - module=self.__module__, - class_name=self.__class__.__name__, - data=args) diff --git a/lib/pkg_resources/__init__.py b/lib/pkg_resources/__init__.py deleted file mode 100644 index 6ca68da..0000000 --- a/lib/pkg_resources/__init__.py +++ /dev/null @@ -1,3171 +0,0 @@ -# coding: utf-8 -""" -Package resource API --------------------- - -A resource is a logical file contained within a package, or a logical -subdirectory thereof. The package resource API expects resource names -to have their path parts separated with ``/``, *not* whatever the local -path separator is. Do not use os.path operations to manipulate resource -names being passed into the API. - -The package resource API is designed to work with normal filesystem packages, -.egg files, and unpacked .egg files. It can also work in a limited way with -.zip files and with custom PEP 302 loaders that support the ``get_data()`` -method. -""" - -from __future__ import absolute_import - -import sys -import os -import io -import time -import re -import types -import zipfile -import zipimport -import warnings -import stat -import functools -import pkgutil -import operator -import platform -import collections -import plistlib -import email.parser -import errno -import tempfile -import textwrap -import itertools -import inspect -from pkgutil import get_importer - -try: - import _imp -except ImportError: - # Python 3.2 compatibility - import imp as _imp - -try: - FileExistsError -except NameError: - FileExistsError = OSError - -from pkg_resources.extern import six -from pkg_resources.extern.six.moves import urllib, map, filter - -# capture these to bypass sandboxing -from os import utime -try: - from os import mkdir, rename, unlink - WRITE_SUPPORT = True -except ImportError: - # no write support, probably under GAE - WRITE_SUPPORT = False - -from os import open as os_open -from os.path import isdir, split - -try: - import importlib.machinery as importlib_machinery - # access attribute to force import under delayed import mechanisms. - importlib_machinery.__name__ -except ImportError: - importlib_machinery = None - -from . import py31compat -from pkg_resources.extern import appdirs -from pkg_resources.extern import packaging -__import__('pkg_resources.extern.packaging.version') -__import__('pkg_resources.extern.packaging.specifiers') -__import__('pkg_resources.extern.packaging.requirements') -__import__('pkg_resources.extern.packaging.markers') - - -__metaclass__ = type - - -if (3, 0) < sys.version_info < (3, 4): - raise RuntimeError("Python 3.4 or later is required") - -if six.PY2: - # Those builtin exceptions are only defined in Python 3 - PermissionError = None - NotADirectoryError = None - -# declare some globals that will be defined later to -# satisfy the linters. -require = None -working_set = None -add_activation_listener = None -resources_stream = None -cleanup_resources = None -resource_dir = None -resource_stream = None -set_extraction_path = None -resource_isdir = None -resource_string = None -iter_entry_points = None -resource_listdir = None -resource_filename = None -resource_exists = None -_distribution_finders = None -_namespace_handlers = None -_namespace_packages = None - - -class PEP440Warning(RuntimeWarning): - """ - Used when there is an issue with a version or specifier not complying with - PEP 440. - """ - - -def parse_version(v): - try: - return packaging.version.Version(v) - except packaging.version.InvalidVersion: - return packaging.version.LegacyVersion(v) - - -_state_vars = {} - - -def _declare_state(vartype, **kw): - globals().update(kw) - _state_vars.update(dict.fromkeys(kw, vartype)) - - -def __getstate__(): - state = {} - g = globals() - for k, v in _state_vars.items(): - state[k] = g['_sget_' + v](g[k]) - return state - - -def __setstate__(state): - g = globals() - for k, v in state.items(): - g['_sset_' + _state_vars[k]](k, g[k], v) - return state - - -def _sget_dict(val): - return val.copy() - - -def _sset_dict(key, ob, state): - ob.clear() - ob.update(state) - - -def _sget_object(val): - return val.__getstate__() - - -def _sset_object(key, ob, state): - ob.__setstate__(state) - - -_sget_none = _sset_none = lambda *args: None - - -def get_supported_platform(): - """Return this platform's maximum compatible version. - - distutils.util.get_platform() normally reports the minimum version - of Mac OS X that would be required to *use* extensions produced by - distutils. But what we want when checking compatibility is to know the - version of Mac OS X that we are *running*. To allow usage of packages that - explicitly require a newer version of Mac OS X, we must also know the - current version of the OS. - - If this condition occurs for any other platform with a version in its - platform strings, this function should be extended accordingly. - """ - plat = get_build_platform() - m = macosVersionString.match(plat) - if m is not None and sys.platform == "darwin": - try: - plat = 'macosx-%s-%s' % ('.'.join(_macosx_vers()[:2]), m.group(3)) - except ValueError: - # not Mac OS X - pass - return plat - - -__all__ = [ - # Basic resource access and distribution/entry point discovery - 'require', 'run_script', 'get_provider', 'get_distribution', - 'load_entry_point', 'get_entry_map', 'get_entry_info', - 'iter_entry_points', - 'resource_string', 'resource_stream', 'resource_filename', - 'resource_listdir', 'resource_exists', 'resource_isdir', - - # Environmental control - 'declare_namespace', 'working_set', 'add_activation_listener', - 'find_distributions', 'set_extraction_path', 'cleanup_resources', - 'get_default_cache', - - # Primary implementation classes - 'Environment', 'WorkingSet', 'ResourceManager', - 'Distribution', 'Requirement', 'EntryPoint', - - # Exceptions - 'ResolutionError', 'VersionConflict', 'DistributionNotFound', - 'UnknownExtra', 'ExtractionError', - - # Warnings - 'PEP440Warning', - - # Parsing functions and string utilities - 'parse_requirements', 'parse_version', 'safe_name', 'safe_version', - 'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections', - 'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker', - - # filesystem utilities - 'ensure_directory', 'normalize_path', - - # Distribution "precedence" constants - 'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST', - - # "Provider" interfaces, implementations, and registration/lookup APIs - 'IMetadataProvider', 'IResourceProvider', 'FileMetadata', - 'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider', - 'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider', - 'register_finder', 'register_namespace_handler', 'register_loader_type', - 'fixup_namespace_packages', 'get_importer', - - # Warnings - 'PkgResourcesDeprecationWarning', - - # Deprecated/backward compatibility only - 'run_main', 'AvailableDistributions', -] - - -class ResolutionError(Exception): - """Abstract base for dependency resolution errors""" - - def __repr__(self): - return self.__class__.__name__ + repr(self.args) - - -class VersionConflict(ResolutionError): - """ - An already-installed version conflicts with the requested version. - - Should be initialized with the installed Distribution and the requested - Requirement. - """ - - _template = "{self.dist} is installed but {self.req} is required" - - @property - def dist(self): - return self.args[0] - - @property - def req(self): - return self.args[1] - - def report(self): - return self._template.format(**locals()) - - def with_context(self, required_by): - """ - If required_by is non-empty, return a version of self that is a - ContextualVersionConflict. - """ - if not required_by: - return self - args = self.args + (required_by,) - return ContextualVersionConflict(*args) - - -class ContextualVersionConflict(VersionConflict): - """ - A VersionConflict that accepts a third parameter, the set of the - requirements that required the installed Distribution. - """ - - _template = VersionConflict._template + ' by {self.required_by}' - - @property - def required_by(self): - return self.args[2] - - -class DistributionNotFound(ResolutionError): - """A requested distribution was not found""" - - _template = ("The '{self.req}' distribution was not found " - "and is required by {self.requirers_str}") - - @property - def req(self): - return self.args[0] - - @property - def requirers(self): - return self.args[1] - - @property - def requirers_str(self): - if not self.requirers: - return 'the application' - return ', '.join(self.requirers) - - def report(self): - return self._template.format(**locals()) - - def __str__(self): - return self.report() - - -class UnknownExtra(ResolutionError): - """Distribution doesn't have an "extra feature" of the given name""" - - -_provider_factories = {} - -PY_MAJOR = sys.version[:3] -EGG_DIST = 3 -BINARY_DIST = 2 -SOURCE_DIST = 1 -CHECKOUT_DIST = 0 -DEVELOP_DIST = -1 - - -def register_loader_type(loader_type, provider_factory): - """Register `provider_factory` to make providers for `loader_type` - - `loader_type` is the type or class of a PEP 302 ``module.__loader__``, - and `provider_factory` is a function that, passed a *module* object, - returns an ``IResourceProvider`` for that module. - """ - _provider_factories[loader_type] = provider_factory - - -def get_provider(moduleOrReq): - """Return an IResourceProvider for the named module or requirement""" - if isinstance(moduleOrReq, Requirement): - return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0] - try: - module = sys.modules[moduleOrReq] - except KeyError: - __import__(moduleOrReq) - module = sys.modules[moduleOrReq] - loader = getattr(module, '__loader__', None) - return _find_adapter(_provider_factories, loader)(module) - - -def _macosx_vers(_cache=[]): - if not _cache: - version = platform.mac_ver()[0] - # fallback for MacPorts - if version == '': - plist = '/System/Library/CoreServices/SystemVersion.plist' - if os.path.exists(plist): - if hasattr(plistlib, 'readPlist'): - plist_content = plistlib.readPlist(plist) - if 'ProductVersion' in plist_content: - version = plist_content['ProductVersion'] - - _cache.append(version.split('.')) - return _cache[0] - - -def _macosx_arch(machine): - return {'PowerPC': 'ppc', 'Power_Macintosh': 'ppc'}.get(machine, machine) - - -def get_build_platform(): - """Return this platform's string for platform-specific distributions - - XXX Currently this is the same as ``distutils.util.get_platform()``, but it - needs some hacks for Linux and Mac OS X. - """ - from sysconfig import get_platform - - plat = get_platform() - if sys.platform == "darwin" and not plat.startswith('macosx-'): - try: - version = _macosx_vers() - machine = os.uname()[4].replace(" ", "_") - return "macosx-%d.%d-%s" % ( - int(version[0]), int(version[1]), - _macosx_arch(machine), - ) - except ValueError: - # if someone is running a non-Mac darwin system, this will fall - # through to the default implementation - pass - return plat - - -macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)") -darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)") -# XXX backward compat -get_platform = get_build_platform - - -def compatible_platforms(provided, required): - """Can code for the `provided` platform run on the `required` platform? - - Returns true if either platform is ``None``, or the platforms are equal. - - XXX Needs compatibility checks for Linux and other unixy OSes. - """ - if provided is None or required is None or provided == required: - # easy case - return True - - # Mac OS X special cases - reqMac = macosVersionString.match(required) - if reqMac: - provMac = macosVersionString.match(provided) - - # is this a Mac package? - if not provMac: - # this is backwards compatibility for packages built before - # setuptools 0.6. All packages built after this point will - # use the new macosx designation. - provDarwin = darwinVersionString.match(provided) - if provDarwin: - dversion = int(provDarwin.group(1)) - macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2)) - if dversion == 7 and macosversion >= "10.3" or \ - dversion == 8 and macosversion >= "10.4": - return True - # egg isn't macosx or legacy darwin - return False - - # are they the same major version and machine type? - if provMac.group(1) != reqMac.group(1) or \ - provMac.group(3) != reqMac.group(3): - return False - - # is the required OS major update >= the provided one? - if int(provMac.group(2)) > int(reqMac.group(2)): - return False - - return True - - # XXX Linux and other platforms' special cases should go here - return False - - -def run_script(dist_spec, script_name): - """Locate distribution `dist_spec` and run its `script_name` script""" - ns = sys._getframe(1).f_globals - name = ns['__name__'] - ns.clear() - ns['__name__'] = name - require(dist_spec)[0].run_script(script_name, ns) - - -# backward compatibility -run_main = run_script - - -def get_distribution(dist): - """Return a current distribution object for a Requirement or string""" - if isinstance(dist, six.string_types): - dist = Requirement.parse(dist) - if isinstance(dist, Requirement): - dist = get_provider(dist) - if not isinstance(dist, Distribution): - raise TypeError("Expected string, Requirement, or Distribution", dist) - return dist - - -def load_entry_point(dist, group, name): - """Return `name` entry point of `group` for `dist` or raise ImportError""" - return get_distribution(dist).load_entry_point(group, name) - - -def get_entry_map(dist, group=None): - """Return the entry point map for `group`, or the full entry map""" - return get_distribution(dist).get_entry_map(group) - - -def get_entry_info(dist, group, name): - """Return the EntryPoint object for `group`+`name`, or ``None``""" - return get_distribution(dist).get_entry_info(group, name) - - -class IMetadataProvider: - def has_metadata(name): - """Does the package's distribution contain the named metadata?""" - - def get_metadata(name): - """The named metadata resource as a string""" - - def get_metadata_lines(name): - """Yield named metadata resource as list of non-blank non-comment lines - - Leading and trailing whitespace is stripped from each line, and lines - with ``#`` as the first non-blank character are omitted.""" - - def metadata_isdir(name): - """Is the named metadata a directory? (like ``os.path.isdir()``)""" - - def metadata_listdir(name): - """List of metadata names in the directory (like ``os.listdir()``)""" - - def run_script(script_name, namespace): - """Execute the named script in the supplied namespace dictionary""" - - -class IResourceProvider(IMetadataProvider): - """An object that provides access to package resources""" - - def get_resource_filename(manager, resource_name): - """Return a true filesystem path for `resource_name` - - `manager` must be an ``IResourceManager``""" - - def get_resource_stream(manager, resource_name): - """Return a readable file-like object for `resource_name` - - `manager` must be an ``IResourceManager``""" - - def get_resource_string(manager, resource_name): - """Return a string containing the contents of `resource_name` - - `manager` must be an ``IResourceManager``""" - - def has_resource(resource_name): - """Does the package contain the named resource?""" - - def resource_isdir(resource_name): - """Is the named resource a directory? (like ``os.path.isdir()``)""" - - def resource_listdir(resource_name): - """List of resource names in the directory (like ``os.listdir()``)""" - - -class WorkingSet: - """A collection of active distributions on sys.path (or a similar list)""" - - def __init__(self, entries=None): - """Create working set from list of path entries (default=sys.path)""" - self.entries = [] - self.entry_keys = {} - self.by_key = {} - self.callbacks = [] - - if entries is None: - entries = sys.path - - for entry in entries: - self.add_entry(entry) - - @classmethod - def _build_master(cls): - """ - Prepare the master working set. - """ - ws = cls() - try: - from __main__ import __requires__ - except ImportError: - # The main program does not list any requirements - return ws - - # ensure the requirements are met - try: - ws.require(__requires__) - except VersionConflict: - return cls._build_from_requirements(__requires__) - - return ws - - @classmethod - def _build_from_requirements(cls, req_spec): - """ - Build a working set from a requirement spec. Rewrites sys.path. - """ - # try it without defaults already on sys.path - # by starting with an empty path - ws = cls([]) - reqs = parse_requirements(req_spec) - dists = ws.resolve(reqs, Environment()) - for dist in dists: - ws.add(dist) - - # add any missing entries from sys.path - for entry in sys.path: - if entry not in ws.entries: - ws.add_entry(entry) - - # then copy back to sys.path - sys.path[:] = ws.entries - return ws - - def add_entry(self, entry): - """Add a path item to ``.entries``, finding any distributions on it - - ``find_distributions(entry, True)`` is used to find distributions - corresponding to the path entry, and they are added. `entry` is - always appended to ``.entries``, even if it is already present. - (This is because ``sys.path`` can contain the same value more than - once, and the ``.entries`` of the ``sys.path`` WorkingSet should always - equal ``sys.path``.) - """ - self.entry_keys.setdefault(entry, []) - self.entries.append(entry) - for dist in find_distributions(entry, True): - self.add(dist, entry, False) - - def __contains__(self, dist): - """True if `dist` is the active distribution for its project""" - return self.by_key.get(dist.key) == dist - - def find(self, req): - """Find a distribution matching requirement `req` - - If there is an active distribution for the requested project, this - returns it as long as it meets the version requirement specified by - `req`. But, if there is an active distribution for the project and it - does *not* meet the `req` requirement, ``VersionConflict`` is raised. - If there is no active distribution for the requested project, ``None`` - is returned. - """ - dist = self.by_key.get(req.key) - if dist is not None and dist not in req: - # XXX add more info - raise VersionConflict(dist, req) - return dist - - def iter_entry_points(self, group, name=None): - """Yield entry point objects from `group` matching `name` - - If `name` is None, yields all entry points in `group` from all - distributions in the working set, otherwise only ones matching - both `group` and `name` are yielded (in distribution order). - """ - return ( - entry - for dist in self - for entry in dist.get_entry_map(group).values() - if name is None or name == entry.name - ) - - def run_script(self, requires, script_name): - """Locate distribution for `requires` and run `script_name` script""" - ns = sys._getframe(1).f_globals - name = ns['__name__'] - ns.clear() - ns['__name__'] = name - self.require(requires)[0].run_script(script_name, ns) - - def __iter__(self): - """Yield distributions for non-duplicate projects in the working set - - The yield order is the order in which the items' path entries were - added to the working set. - """ - seen = {} - for item in self.entries: - if item not in self.entry_keys: - # workaround a cache issue - continue - - for key in self.entry_keys[item]: - if key not in seen: - seen[key] = 1 - yield self.by_key[key] - - def add(self, dist, entry=None, insert=True, replace=False): - """Add `dist` to working set, associated with `entry` - - If `entry` is unspecified, it defaults to the ``.location`` of `dist`. - On exit from this routine, `entry` is added to the end of the working - set's ``.entries`` (if it wasn't already present). - - `dist` is only added to the working set if it's for a project that - doesn't already have a distribution in the set, unless `replace=True`. - If it's added, any callbacks registered with the ``subscribe()`` method - will be called. - """ - if insert: - dist.insert_on(self.entries, entry, replace=replace) - - if entry is None: - entry = dist.location - keys = self.entry_keys.setdefault(entry, []) - keys2 = self.entry_keys.setdefault(dist.location, []) - if not replace and dist.key in self.by_key: - # ignore hidden distros - return - - self.by_key[dist.key] = dist - if dist.key not in keys: - keys.append(dist.key) - if dist.key not in keys2: - keys2.append(dist.key) - self._added_new(dist) - - def resolve(self, requirements, env=None, installer=None, - replace_conflicting=False, extras=None): - """List all distributions needed to (recursively) meet `requirements` - - `requirements` must be a sequence of ``Requirement`` objects. `env`, - if supplied, should be an ``Environment`` instance. If - not supplied, it defaults to all distributions available within any - entry or distribution in the working set. `installer`, if supplied, - will be invoked with each requirement that cannot be met by an - already-installed distribution; it should return a ``Distribution`` or - ``None``. - - Unless `replace_conflicting=True`, raises a VersionConflict exception - if - any requirements are found on the path that have the correct name but - the wrong version. Otherwise, if an `installer` is supplied it will be - invoked to obtain the correct version of the requirement and activate - it. - - `extras` is a list of the extras to be used with these requirements. - This is important because extra requirements may look like `my_req; - extra = "my_extra"`, which would otherwise be interpreted as a purely - optional requirement. Instead, we want to be able to assert that these - requirements are truly required. - """ - - # set up the stack - requirements = list(requirements)[::-1] - # set of processed requirements - processed = {} - # key -> dist - best = {} - to_activate = [] - - req_extras = _ReqExtras() - - # Mapping of requirement to set of distributions that required it; - # useful for reporting info about conflicts. - required_by = collections.defaultdict(set) - - while requirements: - # process dependencies breadth-first - req = requirements.pop(0) - if req in processed: - # Ignore cyclic or redundant dependencies - continue - - if not req_extras.markers_pass(req, extras): - continue - - dist = best.get(req.key) - if dist is None: - # Find the best distribution and add it to the map - dist = self.by_key.get(req.key) - if dist is None or (dist not in req and replace_conflicting): - ws = self - if env is None: - if dist is None: - env = Environment(self.entries) - else: - # Use an empty environment and workingset to avoid - # any further conflicts with the conflicting - # distribution - env = Environment([]) - ws = WorkingSet([]) - dist = best[req.key] = env.best_match( - req, ws, installer, - replace_conflicting=replace_conflicting - ) - if dist is None: - requirers = required_by.get(req, None) - raise DistributionNotFound(req, requirers) - to_activate.append(dist) - if dist not in req: - # Oops, the "best" so far conflicts with a dependency - dependent_req = required_by[req] - raise VersionConflict(dist, req).with_context(dependent_req) - - # push the new requirements onto the stack - new_requirements = dist.requires(req.extras)[::-1] - requirements.extend(new_requirements) - - # Register the new requirements needed by req - for new_requirement in new_requirements: - required_by[new_requirement].add(req.project_name) - req_extras[new_requirement] = req.extras - - processed[req] = True - - # return list of distros to activate - return to_activate - - def find_plugins( - self, plugin_env, full_env=None, installer=None, fallback=True): - """Find all activatable distributions in `plugin_env` - - Example usage:: - - distributions, errors = working_set.find_plugins( - Environment(plugin_dirlist) - ) - # add plugins+libs to sys.path - map(working_set.add, distributions) - # display errors - print('Could not load', errors) - - The `plugin_env` should be an ``Environment`` instance that contains - only distributions that are in the project's "plugin directory" or - directories. The `full_env`, if supplied, should be an ``Environment`` - contains all currently-available distributions. If `full_env` is not - supplied, one is created automatically from the ``WorkingSet`` this - method is called on, which will typically mean that every directory on - ``sys.path`` will be scanned for distributions. - - `installer` is a standard installer callback as used by the - ``resolve()`` method. The `fallback` flag indicates whether we should - attempt to resolve older versions of a plugin if the newest version - cannot be resolved. - - This method returns a 2-tuple: (`distributions`, `error_info`), where - `distributions` is a list of the distributions found in `plugin_env` - that were loadable, along with any other distributions that are needed - to resolve their dependencies. `error_info` is a dictionary mapping - unloadable plugin distributions to an exception instance describing the - error that occurred. Usually this will be a ``DistributionNotFound`` or - ``VersionConflict`` instance. - """ - - plugin_projects = list(plugin_env) - # scan project names in alphabetic order - plugin_projects.sort() - - error_info = {} - distributions = {} - - if full_env is None: - env = Environment(self.entries) - env += plugin_env - else: - env = full_env + plugin_env - - shadow_set = self.__class__([]) - # put all our entries in shadow_set - list(map(shadow_set.add, self)) - - for project_name in plugin_projects: - - for dist in plugin_env[project_name]: - - req = [dist.as_requirement()] - - try: - resolvees = shadow_set.resolve(req, env, installer) - - except ResolutionError as v: - # save error info - error_info[dist] = v - if fallback: - # try the next older version of project - continue - else: - # give up on this project, keep going - break - - else: - list(map(shadow_set.add, resolvees)) - distributions.update(dict.fromkeys(resolvees)) - - # success, no need to try any more versions of this project - break - - distributions = list(distributions) - distributions.sort() - - return distributions, error_info - - def require(self, *requirements): - """Ensure that distributions matching `requirements` are activated - - `requirements` must be a string or a (possibly-nested) sequence - thereof, specifying the distributions and versions required. The - return value is a sequence of the distributions that needed to be - activated to fulfill the requirements; all relevant distributions are - included, even if they were already activated in this working set. - """ - needed = self.resolve(parse_requirements(requirements)) - - for dist in needed: - self.add(dist) - - return needed - - def subscribe(self, callback, existing=True): - """Invoke `callback` for all distributions - - If `existing=True` (default), - call on all existing ones, as well. - """ - if callback in self.callbacks: - return - self.callbacks.append(callback) - if not existing: - return - for dist in self: - callback(dist) - - def _added_new(self, dist): - for callback in self.callbacks: - callback(dist) - - def __getstate__(self): - return ( - self.entries[:], self.entry_keys.copy(), self.by_key.copy(), - self.callbacks[:] - ) - - def __setstate__(self, e_k_b_c): - entries, keys, by_key, callbacks = e_k_b_c - self.entries = entries[:] - self.entry_keys = keys.copy() - self.by_key = by_key.copy() - self.callbacks = callbacks[:] - - -class _ReqExtras(dict): - """ - Map each requirement to the extras that demanded it. - """ - - def markers_pass(self, req, extras=None): - """ - Evaluate markers for req against each extra that - demanded it. - - Return False if the req has a marker and fails - evaluation. Otherwise, return True. - """ - extra_evals = ( - req.marker.evaluate({'extra': extra}) - for extra in self.get(req, ()) + (extras or (None,)) - ) - return not req.marker or any(extra_evals) - - -class Environment: - """Searchable snapshot of distributions on a search path""" - - def __init__( - self, search_path=None, platform=get_supported_platform(), - python=PY_MAJOR): - """Snapshot distributions available on a search path - - Any distributions found on `search_path` are added to the environment. - `search_path` should be a sequence of ``sys.path`` items. If not - supplied, ``sys.path`` is used. - - `platform` is an optional string specifying the name of the platform - that platform-specific distributions must be compatible with. If - unspecified, it defaults to the current platform. `python` is an - optional string naming the desired version of Python (e.g. ``'3.6'``); - it defaults to the current version. - - You may explicitly set `platform` (and/or `python`) to ``None`` if you - wish to map *all* distributions, not just those compatible with the - running platform or Python version. - """ - self._distmap = {} - self.platform = platform - self.python = python - self.scan(search_path) - - def can_add(self, dist): - """Is distribution `dist` acceptable for this environment? - - The distribution must match the platform and python version - requirements specified when this environment was created, or False - is returned. - """ - py_compat = ( - self.python is None - or dist.py_version is None - or dist.py_version == self.python - ) - return py_compat and compatible_platforms(dist.platform, self.platform) - - def remove(self, dist): - """Remove `dist` from the environment""" - self._distmap[dist.key].remove(dist) - - def scan(self, search_path=None): - """Scan `search_path` for distributions usable in this environment - - Any distributions found are added to the environment. - `search_path` should be a sequence of ``sys.path`` items. If not - supplied, ``sys.path`` is used. Only distributions conforming to - the platform/python version defined at initialization are added. - """ - if search_path is None: - search_path = sys.path - - for item in search_path: - for dist in find_distributions(item): - self.add(dist) - - def __getitem__(self, project_name): - """Return a newest-to-oldest list of distributions for `project_name` - - Uses case-insensitive `project_name` comparison, assuming all the - project's distributions use their project's name converted to all - lowercase as their key. - - """ - distribution_key = project_name.lower() - return self._distmap.get(distribution_key, []) - - def add(self, dist): - """Add `dist` if we ``can_add()`` it and it has not already been added - """ - if self.can_add(dist) and dist.has_version(): - dists = self._distmap.setdefault(dist.key, []) - if dist not in dists: - dists.append(dist) - dists.sort(key=operator.attrgetter('hashcmp'), reverse=True) - - def best_match( - self, req, working_set, installer=None, replace_conflicting=False): - """Find distribution best matching `req` and usable on `working_set` - - This calls the ``find(req)`` method of the `working_set` to see if a - suitable distribution is already active. (This may raise - ``VersionConflict`` if an unsuitable version of the project is already - active in the specified `working_set`.) If a suitable distribution - isn't active, this method returns the newest distribution in the - environment that meets the ``Requirement`` in `req`. If no suitable - distribution is found, and `installer` is supplied, then the result of - calling the environment's ``obtain(req, installer)`` method will be - returned. - """ - try: - dist = working_set.find(req) - except VersionConflict: - if not replace_conflicting: - raise - dist = None - if dist is not None: - return dist - for dist in self[req.key]: - if dist in req: - return dist - # try to download/install - return self.obtain(req, installer) - - def obtain(self, requirement, installer=None): - """Obtain a distribution matching `requirement` (e.g. via download) - - Obtain a distro that matches requirement (e.g. via download). In the - base ``Environment`` class, this routine just returns - ``installer(requirement)``, unless `installer` is None, in which case - None is returned instead. This method is a hook that allows subclasses - to attempt other ways of obtaining a distribution before falling back - to the `installer` argument.""" - if installer is not None: - return installer(requirement) - - def __iter__(self): - """Yield the unique project names of the available distributions""" - for key in self._distmap.keys(): - if self[key]: - yield key - - def __iadd__(self, other): - """In-place addition of a distribution or environment""" - if isinstance(other, Distribution): - self.add(other) - elif isinstance(other, Environment): - for project in other: - for dist in other[project]: - self.add(dist) - else: - raise TypeError("Can't add %r to environment" % (other,)) - return self - - def __add__(self, other): - """Add an environment or distribution to an environment""" - new = self.__class__([], platform=None, python=None) - for env in self, other: - new += env - return new - - -# XXX backward compatibility -AvailableDistributions = Environment - - -class ExtractionError(RuntimeError): - """An error occurred extracting a resource - - The following attributes are available from instances of this exception: - - manager - The resource manager that raised this exception - - cache_path - The base directory for resource extraction - - original_error - The exception instance that caused extraction to fail - """ - - -class ResourceManager: - """Manage resource extraction and packages""" - extraction_path = None - - def __init__(self): - self.cached_files = {} - - def resource_exists(self, package_or_requirement, resource_name): - """Does the named resource exist?""" - return get_provider(package_or_requirement).has_resource(resource_name) - - def resource_isdir(self, package_or_requirement, resource_name): - """Is the named resource an existing directory?""" - return get_provider(package_or_requirement).resource_isdir( - resource_name - ) - - def resource_filename(self, package_or_requirement, resource_name): - """Return a true filesystem path for specified resource""" - return get_provider(package_or_requirement).get_resource_filename( - self, resource_name - ) - - def resource_stream(self, package_or_requirement, resource_name): - """Return a readable file-like object for specified resource""" - return get_provider(package_or_requirement).get_resource_stream( - self, resource_name - ) - - def resource_string(self, package_or_requirement, resource_name): - """Return specified resource as a string""" - return get_provider(package_or_requirement).get_resource_string( - self, resource_name - ) - - def resource_listdir(self, package_or_requirement, resource_name): - """List the contents of the named resource directory""" - return get_provider(package_or_requirement).resource_listdir( - resource_name - ) - - def extraction_error(self): - """Give an error message for problems extracting file(s)""" - - old_exc = sys.exc_info()[1] - cache_path = self.extraction_path or get_default_cache() - - tmpl = textwrap.dedent(""" - Can't extract file(s) to egg cache - - The following error occurred while trying to extract file(s) - to the Python egg cache: - - {old_exc} - - The Python egg cache directory is currently set to: - - {cache_path} - - Perhaps your account does not have write access to this directory? - You can change the cache directory by setting the PYTHON_EGG_CACHE - environment variable to point to an accessible directory. - """).lstrip() - err = ExtractionError(tmpl.format(**locals())) - err.manager = self - err.cache_path = cache_path - err.original_error = old_exc - raise err - - def get_cache_path(self, archive_name, names=()): - """Return absolute location in cache for `archive_name` and `names` - - The parent directory of the resulting path will be created if it does - not already exist. `archive_name` should be the base filename of the - enclosing egg (which may not be the name of the enclosing zipfile!), - including its ".egg" extension. `names`, if provided, should be a - sequence of path name parts "under" the egg's extraction location. - - This method should only be called by resource providers that need to - obtain an extraction location, and only for names they intend to - extract, as it tracks the generated names for possible cleanup later. - """ - extract_path = self.extraction_path or get_default_cache() - target_path = os.path.join(extract_path, archive_name + '-tmp', *names) - try: - _bypass_ensure_directory(target_path) - except Exception: - self.extraction_error() - - self._warn_unsafe_extraction_path(extract_path) - - self.cached_files[target_path] = 1 - return target_path - - @staticmethod - def _warn_unsafe_extraction_path(path): - """ - If the default extraction path is overridden and set to an insecure - location, such as /tmp, it opens up an opportunity for an attacker to - replace an extracted file with an unauthorized payload. Warn the user - if a known insecure location is used. - - See Distribute #375 for more details. - """ - if os.name == 'nt' and not path.startswith(os.environ['windir']): - # On Windows, permissions are generally restrictive by default - # and temp directories are not writable by other users, so - # bypass the warning. - return - mode = os.stat(path).st_mode - if mode & stat.S_IWOTH or mode & stat.S_IWGRP: - msg = ( - "%s is writable by group/others and vulnerable to attack " - "when " - "used with get_resource_filename. Consider a more secure " - "location (set with .set_extraction_path or the " - "PYTHON_EGG_CACHE environment variable)." % path - ) - warnings.warn(msg, UserWarning) - - def postprocess(self, tempname, filename): - """Perform any platform-specific postprocessing of `tempname` - - This is where Mac header rewrites should be done; other platforms don't - have anything special they should do. - - Resource providers should call this method ONLY after successfully - extracting a compressed resource. They must NOT call it on resources - that are already in the filesystem. - - `tempname` is the current (temporary) name of the file, and `filename` - is the name it will be renamed to by the caller after this routine - returns. - """ - - if os.name == 'posix': - # Make the resource executable - mode = ((os.stat(tempname).st_mode) | 0o555) & 0o7777 - os.chmod(tempname, mode) - - def set_extraction_path(self, path): - """Set the base path where resources will be extracted to, if needed. - - If you do not call this routine before any extractions take place, the - path defaults to the return value of ``get_default_cache()``. (Which - is based on the ``PYTHON_EGG_CACHE`` environment variable, with various - platform-specific fallbacks. See that routine's documentation for more - details.) - - Resources are extracted to subdirectories of this path based upon - information given by the ``IResourceProvider``. You may set this to a - temporary directory, but then you must call ``cleanup_resources()`` to - delete the extracted files when done. There is no guarantee that - ``cleanup_resources()`` will be able to remove all extracted files. - - (Note: you may not change the extraction path for a given resource - manager once resources have been extracted, unless you first call - ``cleanup_resources()``.) - """ - if self.cached_files: - raise ValueError( - "Can't change extraction path, files already extracted" - ) - - self.extraction_path = path - - def cleanup_resources(self, force=False): - """ - Delete all extracted resource files and directories, returning a list - of the file and directory names that could not be successfully removed. - This function does not have any concurrency protection, so it should - generally only be called when the extraction path is a temporary - directory exclusive to a single process. This method is not - automatically called; you must call it explicitly or register it as an - ``atexit`` function if you wish to ensure cleanup of a temporary - directory used for extractions. - """ - # XXX - - -def get_default_cache(): - """ - Return the ``PYTHON_EGG_CACHE`` environment variable - or a platform-relevant user cache dir for an app - named "Python-Eggs". - """ - return ( - os.environ.get('PYTHON_EGG_CACHE') - or appdirs.user_cache_dir(appname='Python-Eggs') - ) - - -def safe_name(name): - """Convert an arbitrary string to a standard distribution name - - Any runs of non-alphanumeric/. characters are replaced with a single '-'. - """ - return re.sub('[^A-Za-z0-9.]+', '-', name) - - -def safe_version(version): - """ - Convert an arbitrary string to a standard version string - """ - try: - # normalize the version - return str(packaging.version.Version(version)) - except packaging.version.InvalidVersion: - version = version.replace(' ', '.') - return re.sub('[^A-Za-z0-9.]+', '-', version) - - -def safe_extra(extra): - """Convert an arbitrary string to a standard 'extra' name - - Any runs of non-alphanumeric characters are replaced with a single '_', - and the result is always lowercased. - """ - return re.sub('[^A-Za-z0-9.-]+', '_', extra).lower() - - -def to_filename(name): - """Convert a project or version name to its filename-escaped form - - Any '-' characters are currently replaced with '_'. - """ - return name.replace('-', '_') - - -def invalid_marker(text): - """ - Validate text as a PEP 508 environment marker; return an exception - if invalid or False otherwise. - """ - try: - evaluate_marker(text) - except SyntaxError as e: - e.filename = None - e.lineno = None - return e - return False - - -def evaluate_marker(text, extra=None): - """ - Evaluate a PEP 508 environment marker. - Return a boolean indicating the marker result in this environment. - Raise SyntaxError if marker is invalid. - - This implementation uses the 'pyparsing' module. - """ - try: - marker = packaging.markers.Marker(text) - return marker.evaluate() - except packaging.markers.InvalidMarker as e: - raise SyntaxError(e) - - -class NullProvider: - """Try to implement resources and metadata for arbitrary PEP 302 loaders""" - - egg_name = None - egg_info = None - loader = None - - def __init__(self, module): - self.loader = getattr(module, '__loader__', None) - self.module_path = os.path.dirname(getattr(module, '__file__', '')) - - def get_resource_filename(self, manager, resource_name): - return self._fn(self.module_path, resource_name) - - def get_resource_stream(self, manager, resource_name): - return io.BytesIO(self.get_resource_string(manager, resource_name)) - - def get_resource_string(self, manager, resource_name): - return self._get(self._fn(self.module_path, resource_name)) - - def has_resource(self, resource_name): - return self._has(self._fn(self.module_path, resource_name)) - - def has_metadata(self, name): - return self.egg_info and self._has(self._fn(self.egg_info, name)) - - def get_metadata(self, name): - if not self.egg_info: - return "" - value = self._get(self._fn(self.egg_info, name)) - return value.decode('utf-8') if six.PY3 else value - - def get_metadata_lines(self, name): - return yield_lines(self.get_metadata(name)) - - def resource_isdir(self, resource_name): - return self._isdir(self._fn(self.module_path, resource_name)) - - def metadata_isdir(self, name): - return self.egg_info and self._isdir(self._fn(self.egg_info, name)) - - def resource_listdir(self, resource_name): - return self._listdir(self._fn(self.module_path, resource_name)) - - def metadata_listdir(self, name): - if self.egg_info: - return self._listdir(self._fn(self.egg_info, name)) - return [] - - def run_script(self, script_name, namespace): - script = 'scripts/' + script_name - if not self.has_metadata(script): - raise ResolutionError( - "Script {script!r} not found in metadata at {self.egg_info!r}" - .format(**locals()), - ) - script_text = self.get_metadata(script).replace('\r\n', '\n') - script_text = script_text.replace('\r', '\n') - script_filename = self._fn(self.egg_info, script) - namespace['__file__'] = script_filename - if os.path.exists(script_filename): - source = open(script_filename).read() - code = compile(source, script_filename, 'exec') - exec(code, namespace, namespace) - else: - from linecache import cache - cache[script_filename] = ( - len(script_text), 0, script_text.split('\n'), script_filename - ) - script_code = compile(script_text, script_filename, 'exec') - exec(script_code, namespace, namespace) - - def _has(self, path): - raise NotImplementedError( - "Can't perform this operation for unregistered loader type" - ) - - def _isdir(self, path): - raise NotImplementedError( - "Can't perform this operation for unregistered loader type" - ) - - def _listdir(self, path): - raise NotImplementedError( - "Can't perform this operation for unregistered loader type" - ) - - def _fn(self, base, resource_name): - if resource_name: - return os.path.join(base, *resource_name.split('/')) - return base - - def _get(self, path): - if hasattr(self.loader, 'get_data'): - return self.loader.get_data(path) - raise NotImplementedError( - "Can't perform this operation for loaders without 'get_data()'" - ) - - -register_loader_type(object, NullProvider) - - -class EggProvider(NullProvider): - """Provider based on a virtual filesystem""" - - def __init__(self, module): - NullProvider.__init__(self, module) - self._setup_prefix() - - def _setup_prefix(self): - # we assume here that our metadata may be nested inside a "basket" - # of multiple eggs; that's why we use module_path instead of .archive - path = self.module_path - old = None - while path != old: - if _is_egg_path(path): - self.egg_name = os.path.basename(path) - self.egg_info = os.path.join(path, 'EGG-INFO') - self.egg_root = path - break - old = path - path, base = os.path.split(path) - - -class DefaultProvider(EggProvider): - """Provides access to package resources in the filesystem""" - - def _has(self, path): - return os.path.exists(path) - - def _isdir(self, path): - return os.path.isdir(path) - - def _listdir(self, path): - return os.listdir(path) - - def get_resource_stream(self, manager, resource_name): - return open(self._fn(self.module_path, resource_name), 'rb') - - def _get(self, path): - with open(path, 'rb') as stream: - return stream.read() - - @classmethod - def _register(cls): - loader_names = 'SourceFileLoader', 'SourcelessFileLoader', - for name in loader_names: - loader_cls = getattr(importlib_machinery, name, type(None)) - register_loader_type(loader_cls, cls) - - -DefaultProvider._register() - - -class EmptyProvider(NullProvider): - """Provider that returns nothing for all requests""" - - module_path = None - - _isdir = _has = lambda self, path: False - - def _get(self, path): - return '' - - def _listdir(self, path): - return [] - - def __init__(self): - pass - - -empty_provider = EmptyProvider() - - -class ZipManifests(dict): - """ - zip manifest builder - """ - - @classmethod - def build(cls, path): - """ - Build a dictionary similar to the zipimport directory - caches, except instead of tuples, store ZipInfo objects. - - Use a platform-specific path separator (os.sep) for the path keys - for compatibility with pypy on Windows. - """ - with zipfile.ZipFile(path) as zfile: - items = ( - ( - name.replace('/', os.sep), - zfile.getinfo(name), - ) - for name in zfile.namelist() - ) - return dict(items) - - load = build - - -class MemoizedZipManifests(ZipManifests): - """ - Memoized zipfile manifests. - """ - manifest_mod = collections.namedtuple('manifest_mod', 'manifest mtime') - - def load(self, path): - """ - Load a manifest at path or return a suitable manifest already loaded. - """ - path = os.path.normpath(path) - mtime = os.stat(path).st_mtime - - if path not in self or self[path].mtime != mtime: - manifest = self.build(path) - self[path] = self.manifest_mod(manifest, mtime) - - return self[path].manifest - - -class ZipProvider(EggProvider): - """Resource support for zips and eggs""" - - eagers = None - _zip_manifests = MemoizedZipManifests() - - def __init__(self, module): - EggProvider.__init__(self, module) - self.zip_pre = self.loader.archive + os.sep - - def _zipinfo_name(self, fspath): - # Convert a virtual filename (full path to file) into a zipfile subpath - # usable with the zipimport directory cache for our target archive - fspath = fspath.rstrip(os.sep) - if fspath == self.loader.archive: - return '' - if fspath.startswith(self.zip_pre): - return fspath[len(self.zip_pre):] - raise AssertionError( - "%s is not a subpath of %s" % (fspath, self.zip_pre) - ) - - def _parts(self, zip_path): - # Convert a zipfile subpath into an egg-relative path part list. - # pseudo-fs path - fspath = self.zip_pre + zip_path - if fspath.startswith(self.egg_root + os.sep): - return fspath[len(self.egg_root) + 1:].split(os.sep) - raise AssertionError( - "%s is not a subpath of %s" % (fspath, self.egg_root) - ) - - @property - def zipinfo(self): - return self._zip_manifests.load(self.loader.archive) - - def get_resource_filename(self, manager, resource_name): - if not self.egg_name: - raise NotImplementedError( - "resource_filename() only supported for .egg, not .zip" - ) - # no need to lock for extraction, since we use temp names - zip_path = self._resource_to_zip(resource_name) - eagers = self._get_eager_resources() - if '/'.join(self._parts(zip_path)) in eagers: - for name in eagers: - self._extract_resource(manager, self._eager_to_zip(name)) - return self._extract_resource(manager, zip_path) - - @staticmethod - def _get_date_and_size(zip_stat): - size = zip_stat.file_size - # ymdhms+wday, yday, dst - date_time = zip_stat.date_time + (0, 0, -1) - # 1980 offset already done - timestamp = time.mktime(date_time) - return timestamp, size - - def _extract_resource(self, manager, zip_path): - - if zip_path in self._index(): - for name in self._index()[zip_path]: - last = self._extract_resource( - manager, os.path.join(zip_path, name) - ) - # return the extracted directory name - return os.path.dirname(last) - - timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) - - if not WRITE_SUPPORT: - raise IOError('"os.rename" and "os.unlink" are not supported ' - 'on this platform') - try: - - real_path = manager.get_cache_path( - self.egg_name, self._parts(zip_path) - ) - - if self._is_current(real_path, zip_path): - return real_path - - outf, tmpnam = _mkstemp( - ".$extract", - dir=os.path.dirname(real_path), - ) - os.write(outf, self.loader.get_data(zip_path)) - os.close(outf) - utime(tmpnam, (timestamp, timestamp)) - manager.postprocess(tmpnam, real_path) - - try: - rename(tmpnam, real_path) - - except os.error: - if os.path.isfile(real_path): - if self._is_current(real_path, zip_path): - # the file became current since it was checked above, - # so proceed. - return real_path - # Windows, del old file and retry - elif os.name == 'nt': - unlink(real_path) - rename(tmpnam, real_path) - return real_path - raise - - except os.error: - # report a user-friendly error - manager.extraction_error() - - return real_path - - def _is_current(self, file_path, zip_path): - """ - Return True if the file_path is current for this zip_path - """ - timestamp, size = self._get_date_and_size(self.zipinfo[zip_path]) - if not os.path.isfile(file_path): - return False - stat = os.stat(file_path) - if stat.st_size != size or stat.st_mtime != timestamp: - return False - # check that the contents match - zip_contents = self.loader.get_data(zip_path) - with open(file_path, 'rb') as f: - file_contents = f.read() - return zip_contents == file_contents - - def _get_eager_resources(self): - if self.eagers is None: - eagers = [] - for name in ('native_libs.txt', 'eager_resources.txt'): - if self.has_metadata(name): - eagers.extend(self.get_metadata_lines(name)) - self.eagers = eagers - return self.eagers - - def _index(self): - try: - return self._dirindex - except AttributeError: - ind = {} - for path in self.zipinfo: - parts = path.split(os.sep) - while parts: - parent = os.sep.join(parts[:-1]) - if parent in ind: - ind[parent].append(parts[-1]) - break - else: - ind[parent] = [parts.pop()] - self._dirindex = ind - return ind - - def _has(self, fspath): - zip_path = self._zipinfo_name(fspath) - return zip_path in self.zipinfo or zip_path in self._index() - - def _isdir(self, fspath): - return self._zipinfo_name(fspath) in self._index() - - def _listdir(self, fspath): - return list(self._index().get(self._zipinfo_name(fspath), ())) - - def _eager_to_zip(self, resource_name): - return self._zipinfo_name(self._fn(self.egg_root, resource_name)) - - def _resource_to_zip(self, resource_name): - return self._zipinfo_name(self._fn(self.module_path, resource_name)) - - -register_loader_type(zipimport.zipimporter, ZipProvider) - - -class FileMetadata(EmptyProvider): - """Metadata handler for standalone PKG-INFO files - - Usage:: - - metadata = FileMetadata("/path/to/PKG-INFO") - - This provider rejects all data and metadata requests except for PKG-INFO, - which is treated as existing, and will be the contents of the file at - the provided location. - """ - - def __init__(self, path): - self.path = path - - def has_metadata(self, name): - return name == 'PKG-INFO' and os.path.isfile(self.path) - - def get_metadata(self, name): - if name != 'PKG-INFO': - raise KeyError("No metadata except PKG-INFO is available") - - with io.open(self.path, encoding='utf-8', errors="replace") as f: - metadata = f.read() - self._warn_on_replacement(metadata) - return metadata - - def _warn_on_replacement(self, metadata): - # Python 2.7 compat for: replacement_char = '�' - replacement_char = b'\xef\xbf\xbd'.decode('utf-8') - if replacement_char in metadata: - tmpl = "{self.path} could not be properly decoded in UTF-8" - msg = tmpl.format(**locals()) - warnings.warn(msg) - - def get_metadata_lines(self, name): - return yield_lines(self.get_metadata(name)) - - -class PathMetadata(DefaultProvider): - """Metadata provider for egg directories - - Usage:: - - # Development eggs: - - egg_info = "/path/to/PackageName.egg-info" - base_dir = os.path.dirname(egg_info) - metadata = PathMetadata(base_dir, egg_info) - dist_name = os.path.splitext(os.path.basename(egg_info))[0] - dist = Distribution(basedir, project_name=dist_name, metadata=metadata) - - # Unpacked egg directories: - - egg_path = "/path/to/PackageName-ver-pyver-etc.egg" - metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO')) - dist = Distribution.from_filename(egg_path, metadata=metadata) - """ - - def __init__(self, path, egg_info): - self.module_path = path - self.egg_info = egg_info - - -class EggMetadata(ZipProvider): - """Metadata provider for .egg files""" - - def __init__(self, importer): - """Create a metadata provider from a zipimporter""" - - self.zip_pre = importer.archive + os.sep - self.loader = importer - if importer.prefix: - self.module_path = os.path.join(importer.archive, importer.prefix) - else: - self.module_path = importer.archive - self._setup_prefix() - - -_declare_state('dict', _distribution_finders={}) - - -def register_finder(importer_type, distribution_finder): - """Register `distribution_finder` to find distributions in sys.path items - - `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item - handler), and `distribution_finder` is a callable that, passed a path - item and the importer instance, yields ``Distribution`` instances found on - that path item. See ``pkg_resources.find_on_path`` for an example.""" - _distribution_finders[importer_type] = distribution_finder - - -def find_distributions(path_item, only=False): - """Yield distributions accessible via `path_item`""" - importer = get_importer(path_item) - finder = _find_adapter(_distribution_finders, importer) - return finder(importer, path_item, only) - - -def find_eggs_in_zip(importer, path_item, only=False): - """ - Find eggs in zip files; possibly multiple nested eggs. - """ - if importer.archive.endswith('.whl'): - # wheels are not supported with this finder - # they don't have PKG-INFO metadata, and won't ever contain eggs - return - metadata = EggMetadata(importer) - if metadata.has_metadata('PKG-INFO'): - yield Distribution.from_filename(path_item, metadata=metadata) - if only: - # don't yield nested distros - return - for subitem in metadata.resource_listdir('/'): - if _is_egg_path(subitem): - subpath = os.path.join(path_item, subitem) - dists = find_eggs_in_zip(zipimport.zipimporter(subpath), subpath) - for dist in dists: - yield dist - elif subitem.lower().endswith('.dist-info'): - subpath = os.path.join(path_item, subitem) - submeta = EggMetadata(zipimport.zipimporter(subpath)) - submeta.egg_info = subpath - yield Distribution.from_location(path_item, subitem, submeta) - - -register_finder(zipimport.zipimporter, find_eggs_in_zip) - - -def find_nothing(importer, path_item, only=False): - return () - - -register_finder(object, find_nothing) - - -def _by_version_descending(names): - """ - Given a list of filenames, return them in descending order - by version number. - - >>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg' - >>> _by_version_descending(names) - ['Python-2.7.10.egg', 'Python-2.7.2.egg', 'foo', 'bar'] - >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg' - >>> _by_version_descending(names) - ['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg'] - >>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg' - >>> _by_version_descending(names) - ['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg'] - """ - def _by_version(name): - """ - Parse each component of the filename - """ - name, ext = os.path.splitext(name) - parts = itertools.chain(name.split('-'), [ext]) - return [packaging.version.parse(part) for part in parts] - - return sorted(names, key=_by_version, reverse=True) - - -def find_on_path(importer, path_item, only=False): - """Yield distributions accessible on a sys.path directory""" - path_item = _normalize_cached(path_item) - - if _is_unpacked_egg(path_item): - yield Distribution.from_filename( - path_item, metadata=PathMetadata( - path_item, os.path.join(path_item, 'EGG-INFO') - ) - ) - return - - entries = safe_listdir(path_item) - - # for performance, before sorting by version, - # screen entries for only those that will yield - # distributions - filtered = ( - entry - for entry in entries - if dist_factory(path_item, entry, only) - ) - - # scan for .egg and .egg-info in directory - path_item_entries = _by_version_descending(filtered) - for entry in path_item_entries: - fullpath = os.path.join(path_item, entry) - factory = dist_factory(path_item, entry, only) - for dist in factory(fullpath): - yield dist - - -def dist_factory(path_item, entry, only): - """ - Return a dist_factory for a path_item and entry - """ - lower = entry.lower() - is_meta = any(map(lower.endswith, ('.egg-info', '.dist-info'))) - return ( - distributions_from_metadata - if is_meta else - find_distributions - if not only and _is_egg_path(entry) else - resolve_egg_link - if not only and lower.endswith('.egg-link') else - NoDists() - ) - - -class NoDists: - """ - >>> bool(NoDists()) - False - - >>> list(NoDists()('anything')) - [] - """ - def __bool__(self): - return False - if six.PY2: - __nonzero__ = __bool__ - - def __call__(self, fullpath): - return iter(()) - - -def safe_listdir(path): - """ - Attempt to list contents of path, but suppress some exceptions. - """ - try: - return os.listdir(path) - except (PermissionError, NotADirectoryError): - pass - except OSError as e: - # Ignore the directory if does not exist, not a directory or - # permission denied - ignorable = ( - e.errno in (errno.ENOTDIR, errno.EACCES, errno.ENOENT) - # Python 2 on Windows needs to be handled this way :( - or getattr(e, "winerror", None) == 267 - ) - if not ignorable: - raise - return () - - -def distributions_from_metadata(path): - root = os.path.dirname(path) - if os.path.isdir(path): - if len(os.listdir(path)) == 0: - # empty metadata dir; skip - return - metadata = PathMetadata(root, path) - else: - metadata = FileMetadata(path) - entry = os.path.basename(path) - yield Distribution.from_location( - root, entry, metadata, precedence=DEVELOP_DIST, - ) - - -def non_empty_lines(path): - """ - Yield non-empty lines from file at path - """ - with open(path) as f: - for line in f: - line = line.strip() - if line: - yield line - - -def resolve_egg_link(path): - """ - Given a path to an .egg-link, resolve distributions - present in the referenced path. - """ - referenced_paths = non_empty_lines(path) - resolved_paths = ( - os.path.join(os.path.dirname(path), ref) - for ref in referenced_paths - ) - dist_groups = map(find_distributions, resolved_paths) - return next(dist_groups, ()) - - -register_finder(pkgutil.ImpImporter, find_on_path) - -if hasattr(importlib_machinery, 'FileFinder'): - register_finder(importlib_machinery.FileFinder, find_on_path) - -_declare_state('dict', _namespace_handlers={}) -_declare_state('dict', _namespace_packages={}) - - -def register_namespace_handler(importer_type, namespace_handler): - """Register `namespace_handler` to declare namespace packages - - `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item - handler), and `namespace_handler` is a callable like this:: - - def namespace_handler(importer, path_entry, moduleName, module): - # return a path_entry to use for child packages - - Namespace handlers are only called if the importer object has already - agreed that it can handle the relevant path item, and they should only - return a subpath if the module __path__ does not already contain an - equivalent subpath. For an example namespace handler, see - ``pkg_resources.file_ns_handler``. - """ - _namespace_handlers[importer_type] = namespace_handler - - -def _handle_ns(packageName, path_item): - """Ensure that named package includes a subpath of path_item (if needed)""" - - importer = get_importer(path_item) - if importer is None: - return None - - # capture warnings due to #1111 - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - loader = importer.find_module(packageName) - - if loader is None: - return None - module = sys.modules.get(packageName) - if module is None: - module = sys.modules[packageName] = types.ModuleType(packageName) - module.__path__ = [] - _set_parent_ns(packageName) - elif not hasattr(module, '__path__'): - raise TypeError("Not a package:", packageName) - handler = _find_adapter(_namespace_handlers, importer) - subpath = handler(importer, path_item, packageName, module) - if subpath is not None: - path = module.__path__ - path.append(subpath) - loader.load_module(packageName) - _rebuild_mod_path(path, packageName, module) - return subpath - - -def _rebuild_mod_path(orig_path, package_name, module): - """ - Rebuild module.__path__ ensuring that all entries are ordered - corresponding to their sys.path order - """ - sys_path = [_normalize_cached(p) for p in sys.path] - - def safe_sys_path_index(entry): - """ - Workaround for #520 and #513. - """ - try: - return sys_path.index(entry) - except ValueError: - return float('inf') - - def position_in_sys_path(path): - """ - Return the ordinal of the path based on its position in sys.path - """ - path_parts = path.split(os.sep) - module_parts = package_name.count('.') + 1 - parts = path_parts[:-module_parts] - return safe_sys_path_index(_normalize_cached(os.sep.join(parts))) - - new_path = sorted(orig_path, key=position_in_sys_path) - new_path = [_normalize_cached(p) for p in new_path] - - if isinstance(module.__path__, list): - module.__path__[:] = new_path - else: - module.__path__ = new_path - - -def declare_namespace(packageName): - """Declare that package 'packageName' is a namespace package""" - - _imp.acquire_lock() - try: - if packageName in _namespace_packages: - return - - path = sys.path - parent, _, _ = packageName.rpartition('.') - - if parent: - declare_namespace(parent) - if parent not in _namespace_packages: - __import__(parent) - try: - path = sys.modules[parent].__path__ - except AttributeError: - raise TypeError("Not a package:", parent) - - # Track what packages are namespaces, so when new path items are added, - # they can be updated - _namespace_packages.setdefault(parent or None, []).append(packageName) - _namespace_packages.setdefault(packageName, []) - - for path_item in path: - # Ensure all the parent's path items are reflected in the child, - # if they apply - _handle_ns(packageName, path_item) - - finally: - _imp.release_lock() - - -def fixup_namespace_packages(path_item, parent=None): - """Ensure that previously-declared namespace packages include path_item""" - _imp.acquire_lock() - try: - for package in _namespace_packages.get(parent, ()): - subpath = _handle_ns(package, path_item) - if subpath: - fixup_namespace_packages(subpath, package) - finally: - _imp.release_lock() - - -def file_ns_handler(importer, path_item, packageName, module): - """Compute an ns-package subpath for a filesystem or zipfile importer""" - - subpath = os.path.join(path_item, packageName.split('.')[-1]) - normalized = _normalize_cached(subpath) - for item in module.__path__: - if _normalize_cached(item) == normalized: - break - else: - # Only return the path if it's not already there - return subpath - - -register_namespace_handler(pkgutil.ImpImporter, file_ns_handler) -register_namespace_handler(zipimport.zipimporter, file_ns_handler) - -if hasattr(importlib_machinery, 'FileFinder'): - register_namespace_handler(importlib_machinery.FileFinder, file_ns_handler) - - -def null_ns_handler(importer, path_item, packageName, module): - return None - - -register_namespace_handler(object, null_ns_handler) - - -def normalize_path(filename): - """Normalize a file/dir name for comparison purposes""" - return os.path.normcase(os.path.realpath(os.path.normpath(_cygwin_patch(filename)))) - - -def _cygwin_patch(filename): # pragma: nocover - """ - Contrary to POSIX 2008, on Cygwin, getcwd (3) contains - symlink components. Using - os.path.abspath() works around this limitation. A fix in os.getcwd() - would probably better, in Cygwin even more so, except - that this seems to be by design... - """ - return os.path.abspath(filename) if sys.platform == 'cygwin' else filename - - -def _normalize_cached(filename, _cache={}): - try: - return _cache[filename] - except KeyError: - _cache[filename] = result = normalize_path(filename) - return result - - -def _is_egg_path(path): - """ - Determine if given path appears to be an egg. - """ - return path.lower().endswith('.egg') - - -def _is_unpacked_egg(path): - """ - Determine if given path appears to be an unpacked egg. - """ - return ( - _is_egg_path(path) and - os.path.isfile(os.path.join(path, 'EGG-INFO', 'PKG-INFO')) - ) - - -def _set_parent_ns(packageName): - parts = packageName.split('.') - name = parts.pop() - if parts: - parent = '.'.join(parts) - setattr(sys.modules[parent], name, sys.modules[packageName]) - - -def yield_lines(strs): - """Yield non-empty/non-comment lines of a string or sequence""" - if isinstance(strs, six.string_types): - for s in strs.splitlines(): - s = s.strip() - # skip blank lines/comments - if s and not s.startswith('#'): - yield s - else: - for ss in strs: - for s in yield_lines(ss): - yield s - - -MODULE = re.compile(r"\w+(\.\w+)*$").match -EGG_NAME = re.compile( - r""" - (?P<name>[^-]+) ( - -(?P<ver>[^-]+) ( - -py(?P<pyver>[^-]+) ( - -(?P<plat>.+) - )? - )? - )? - """, - re.VERBOSE | re.IGNORECASE, -).match - - -class EntryPoint: - """Object representing an advertised importable object""" - - def __init__(self, name, module_name, attrs=(), extras=(), dist=None): - if not MODULE(module_name): - raise ValueError("Invalid module name", module_name) - self.name = name - self.module_name = module_name - self.attrs = tuple(attrs) - self.extras = tuple(extras) - self.dist = dist - - def __str__(self): - s = "%s = %s" % (self.name, self.module_name) - if self.attrs: - s += ':' + '.'.join(self.attrs) - if self.extras: - s += ' [%s]' % ','.join(self.extras) - return s - - def __repr__(self): - return "EntryPoint.parse(%r)" % str(self) - - def load(self, require=True, *args, **kwargs): - """ - Require packages for this EntryPoint, then resolve it. - """ - if not require or args or kwargs: - warnings.warn( - "Parameters to load are deprecated. Call .resolve and " - ".require separately.", - PkgResourcesDeprecationWarning, - stacklevel=2, - ) - if require: - self.require(*args, **kwargs) - return self.resolve() - - def resolve(self): - """ - Resolve the entry point from its module and attrs. - """ - module = __import__(self.module_name, fromlist=['__name__'], level=0) - try: - return functools.reduce(getattr, self.attrs, module) - except AttributeError as exc: - raise ImportError(str(exc)) - - def require(self, env=None, installer=None): - if self.extras and not self.dist: - raise UnknownExtra("Can't require() without a distribution", self) - - # Get the requirements for this entry point with all its extras and - # then resolve them. We have to pass `extras` along when resolving so - # that the working set knows what extras we want. Otherwise, for - # dist-info distributions, the working set will assume that the - # requirements for that extra are purely optional and skip over them. - reqs = self.dist.requires(self.extras) - items = working_set.resolve(reqs, env, installer, extras=self.extras) - list(map(working_set.add, items)) - - pattern = re.compile( - r'\s*' - r'(?P<name>.+?)\s*' - r'=\s*' - r'(?P<module>[\w.]+)\s*' - r'(:\s*(?P<attr>[\w.]+))?\s*' - r'(?P<extras>\[.*\])?\s*$' - ) - - @classmethod - def parse(cls, src, dist=None): - """Parse a single entry point from string `src` - - Entry point syntax follows the form:: - - name = some.module:some.attr [extra1, extra2] - - The entry name and module name are required, but the ``:attrs`` and - ``[extras]`` parts are optional - """ - m = cls.pattern.match(src) - if not m: - msg = "EntryPoint must be in 'name=module:attrs [extras]' format" - raise ValueError(msg, src) - res = m.groupdict() - extras = cls._parse_extras(res['extras']) - attrs = res['attr'].split('.') if res['attr'] else () - return cls(res['name'], res['module'], attrs, extras, dist) - - @classmethod - def _parse_extras(cls, extras_spec): - if not extras_spec: - return () - req = Requirement.parse('x' + extras_spec) - if req.specs: - raise ValueError() - return req.extras - - @classmethod - def parse_group(cls, group, lines, dist=None): - """Parse an entry point group""" - if not MODULE(group): - raise ValueError("Invalid group name", group) - this = {} - for line in yield_lines(lines): - ep = cls.parse(line, dist) - if ep.name in this: - raise ValueError("Duplicate entry point", group, ep.name) - this[ep.name] = ep - return this - - @classmethod - def parse_map(cls, data, dist=None): - """Parse a map of entry point groups""" - if isinstance(data, dict): - data = data.items() - else: - data = split_sections(data) - maps = {} - for group, lines in data: - if group is None: - if not lines: - continue - raise ValueError("Entry points must be listed in groups") - group = group.strip() - if group in maps: - raise ValueError("Duplicate group name", group) - maps[group] = cls.parse_group(group, lines, dist) - return maps - - -def _remove_md5_fragment(location): - if not location: - return '' - parsed = urllib.parse.urlparse(location) - if parsed[-1].startswith('md5='): - return urllib.parse.urlunparse(parsed[:-1] + ('',)) - return location - - -def _version_from_file(lines): - """ - Given an iterable of lines from a Metadata file, return - the value of the Version field, if present, or None otherwise. - """ - def is_version_line(line): - return line.lower().startswith('version:') - version_lines = filter(is_version_line, lines) - line = next(iter(version_lines), '') - _, _, value = line.partition(':') - return safe_version(value.strip()) or None - - -class Distribution: - """Wrap an actual or potential sys.path entry w/metadata""" - PKG_INFO = 'PKG-INFO' - - def __init__( - self, location=None, metadata=None, project_name=None, - version=None, py_version=PY_MAJOR, platform=None, - precedence=EGG_DIST): - self.project_name = safe_name(project_name or 'Unknown') - if version is not None: - self._version = safe_version(version) - self.py_version = py_version - self.platform = platform - self.location = location - self.precedence = precedence - self._provider = metadata or empty_provider - - @classmethod - def from_location(cls, location, basename, metadata=None, **kw): - project_name, version, py_version, platform = [None] * 4 - basename, ext = os.path.splitext(basename) - if ext.lower() in _distributionImpl: - cls = _distributionImpl[ext.lower()] - - match = EGG_NAME(basename) - if match: - project_name, version, py_version, platform = match.group( - 'name', 'ver', 'pyver', 'plat' - ) - return cls( - location, metadata, project_name=project_name, version=version, - py_version=py_version, platform=platform, **kw - )._reload_version() - - def _reload_version(self): - return self - - @property - def hashcmp(self): - return ( - self.parsed_version, - self.precedence, - self.key, - _remove_md5_fragment(self.location), - self.py_version or '', - self.platform or '', - ) - - def __hash__(self): - return hash(self.hashcmp) - - def __lt__(self, other): - return self.hashcmp < other.hashcmp - - def __le__(self, other): - return self.hashcmp <= other.hashcmp - - def __gt__(self, other): - return self.hashcmp > other.hashcmp - - def __ge__(self, other): - return self.hashcmp >= other.hashcmp - - def __eq__(self, other): - if not isinstance(other, self.__class__): - # It's not a Distribution, so they are not equal - return False - return self.hashcmp == other.hashcmp - - def __ne__(self, other): - return not self == other - - # These properties have to be lazy so that we don't have to load any - # metadata until/unless it's actually needed. (i.e., some distributions - # may not know their name or version without loading PKG-INFO) - - @property - def key(self): - try: - return self._key - except AttributeError: - self._key = key = self.project_name.lower() - return key - - @property - def parsed_version(self): - if not hasattr(self, "_parsed_version"): - self._parsed_version = parse_version(self.version) - - return self._parsed_version - - def _warn_legacy_version(self): - LV = packaging.version.LegacyVersion - is_legacy = isinstance(self._parsed_version, LV) - if not is_legacy: - return - - # While an empty version is technically a legacy version and - # is not a valid PEP 440 version, it's also unlikely to - # actually come from someone and instead it is more likely that - # it comes from setuptools attempting to parse a filename and - # including it in the list. So for that we'll gate this warning - # on if the version is anything at all or not. - if not self.version: - return - - tmpl = textwrap.dedent(""" - '{project_name} ({version})' is being parsed as a legacy, - non PEP 440, - version. You may find odd behavior and sort order. - In particular it will be sorted as less than 0.0. It - is recommended to migrate to PEP 440 compatible - versions. - """).strip().replace('\n', ' ') - - warnings.warn(tmpl.format(**vars(self)), PEP440Warning) - - @property - def version(self): - try: - return self._version - except AttributeError: - version = _version_from_file(self._get_metadata(self.PKG_INFO)) - if version is None: - tmpl = "Missing 'Version:' header and/or %s file" - raise ValueError(tmpl % self.PKG_INFO, self) - return version - - @property - def _dep_map(self): - """ - A map of extra to its list of (direct) requirements - for this distribution, including the null extra. - """ - try: - return self.__dep_map - except AttributeError: - self.__dep_map = self._filter_extras(self._build_dep_map()) - return self.__dep_map - - @staticmethod - def _filter_extras(dm): - """ - Given a mapping of extras to dependencies, strip off - environment markers and filter out any dependencies - not matching the markers. - """ - for extra in list(filter(None, dm)): - new_extra = extra - reqs = dm.pop(extra) - new_extra, _, marker = extra.partition(':') - fails_marker = marker and ( - invalid_marker(marker) - or not evaluate_marker(marker) - ) - if fails_marker: - reqs = [] - new_extra = safe_extra(new_extra) or None - - dm.setdefault(new_extra, []).extend(reqs) - return dm - - def _build_dep_map(self): - dm = {} - for name in 'requires.txt', 'depends.txt': - for extra, reqs in split_sections(self._get_metadata(name)): - dm.setdefault(extra, []).extend(parse_requirements(reqs)) - return dm - - def requires(self, extras=()): - """List of Requirements needed for this distro if `extras` are used""" - dm = self._dep_map - deps = [] - deps.extend(dm.get(None, ())) - for ext in extras: - try: - deps.extend(dm[safe_extra(ext)]) - except KeyError: - raise UnknownExtra( - "%s has no such extra feature %r" % (self, ext) - ) - return deps - - def _get_metadata(self, name): - if self.has_metadata(name): - for line in self.get_metadata_lines(name): - yield line - - def activate(self, path=None, replace=False): - """Ensure distribution is importable on `path` (default=sys.path)""" - if path is None: - path = sys.path - self.insert_on(path, replace=replace) - if path is sys.path: - fixup_namespace_packages(self.location) - for pkg in self._get_metadata('namespace_packages.txt'): - if pkg in sys.modules: - declare_namespace(pkg) - - def egg_name(self): - """Return what this distribution's standard .egg filename should be""" - filename = "%s-%s-py%s" % ( - to_filename(self.project_name), to_filename(self.version), - self.py_version or PY_MAJOR - ) - - if self.platform: - filename += '-' + self.platform - return filename - - def __repr__(self): - if self.location: - return "%s (%s)" % (self, self.location) - else: - return str(self) - - def __str__(self): - try: - version = getattr(self, 'version', None) - except ValueError: - version = None - version = version or "[unknown version]" - return "%s %s" % (self.project_name, version) - - def __getattr__(self, attr): - """Delegate all unrecognized public attributes to .metadata provider""" - if attr.startswith('_'): - raise AttributeError(attr) - return getattr(self._provider, attr) - - def __dir__(self): - return list( - set(super(Distribution, self).__dir__()) - | set( - attr for attr in self._provider.__dir__() - if not attr.startswith('_') - ) - ) - - if not hasattr(object, '__dir__'): - # python 2.7 not supported - del __dir__ - - @classmethod - def from_filename(cls, filename, metadata=None, **kw): - return cls.from_location( - _normalize_cached(filename), os.path.basename(filename), metadata, - **kw - ) - - def as_requirement(self): - """Return a ``Requirement`` that matches this distribution exactly""" - if isinstance(self.parsed_version, packaging.version.Version): - spec = "%s==%s" % (self.project_name, self.parsed_version) - else: - spec = "%s===%s" % (self.project_name, self.parsed_version) - - return Requirement.parse(spec) - - def load_entry_point(self, group, name): - """Return the `name` entry point of `group` or raise ImportError""" - ep = self.get_entry_info(group, name) - if ep is None: - raise ImportError("Entry point %r not found" % ((group, name),)) - return ep.load() - - def get_entry_map(self, group=None): - """Return the entry point map for `group`, or the full entry map""" - try: - ep_map = self._ep_map - except AttributeError: - ep_map = self._ep_map = EntryPoint.parse_map( - self._get_metadata('entry_points.txt'), self - ) - if group is not None: - return ep_map.get(group, {}) - return ep_map - - def get_entry_info(self, group, name): - """Return the EntryPoint object for `group`+`name`, or ``None``""" - return self.get_entry_map(group).get(name) - - def insert_on(self, path, loc=None, replace=False): - """Ensure self.location is on path - - If replace=False (default): - - If location is already in path anywhere, do nothing. - - Else: - - If it's an egg and its parent directory is on path, - insert just ahead of the parent. - - Else: add to the end of path. - If replace=True: - - If location is already on path anywhere (not eggs) - or higher priority than its parent (eggs) - do nothing. - - Else: - - If it's an egg and its parent directory is on path, - insert just ahead of the parent, - removing any lower-priority entries. - - Else: add it to the front of path. - """ - - loc = loc or self.location - if not loc: - return - - nloc = _normalize_cached(loc) - bdir = os.path.dirname(nloc) - npath = [(p and _normalize_cached(p) or p) for p in path] - - for p, item in enumerate(npath): - if item == nloc: - if replace: - break - else: - # don't modify path (even removing duplicates) if - # found and not replace - return - elif item == bdir and self.precedence == EGG_DIST: - # if it's an .egg, give it precedence over its directory - # UNLESS it's already been added to sys.path and replace=False - if (not replace) and nloc in npath[p:]: - return - if path is sys.path: - self.check_version_conflict() - path.insert(p, loc) - npath.insert(p, nloc) - break - else: - if path is sys.path: - self.check_version_conflict() - if replace: - path.insert(0, loc) - else: - path.append(loc) - return - - # p is the spot where we found or inserted loc; now remove duplicates - while True: - try: - np = npath.index(nloc, p + 1) - except ValueError: - break - else: - del npath[np], path[np] - # ha! - p = np - - return - - def check_version_conflict(self): - if self.key == 'setuptools': - # ignore the inevitable setuptools self-conflicts :( - return - - nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt')) - loc = normalize_path(self.location) - for modname in self._get_metadata('top_level.txt'): - if (modname not in sys.modules or modname in nsp - or modname in _namespace_packages): - continue - if modname in ('pkg_resources', 'setuptools', 'site'): - continue - fn = getattr(sys.modules[modname], '__file__', None) - if fn and (normalize_path(fn).startswith(loc) or - fn.startswith(self.location)): - continue - issue_warning( - "Module %s was already imported from %s, but %s is being added" - " to sys.path" % (modname, fn, self.location), - ) - - def has_version(self): - try: - self.version - except ValueError: - issue_warning("Unbuilt egg for " + repr(self)) - return False - return True - - def clone(self, **kw): - """Copy this distribution, substituting in any changed keyword args""" - names = 'project_name version py_version platform location precedence' - for attr in names.split(): - kw.setdefault(attr, getattr(self, attr, None)) - kw.setdefault('metadata', self._provider) - return self.__class__(**kw) - - @property - def extras(self): - return [dep for dep in self._dep_map if dep] - - -class EggInfoDistribution(Distribution): - def _reload_version(self): - """ - Packages installed by distutils (e.g. numpy or scipy), - which uses an old safe_version, and so - their version numbers can get mangled when - converted to filenames (e.g., 1.11.0.dev0+2329eae to - 1.11.0.dev0_2329eae). These distributions will not be - parsed properly - downstream by Distribution and safe_version, so - take an extra step and try to get the version number from - the metadata file itself instead of the filename. - """ - md_version = _version_from_file(self._get_metadata(self.PKG_INFO)) - if md_version: - self._version = md_version - return self - - -class DistInfoDistribution(Distribution): - """ - Wrap an actual or potential sys.path entry - w/metadata, .dist-info style. - """ - PKG_INFO = 'METADATA' - EQEQ = re.compile(r"([\(,])\s*(\d.*?)\s*([,\)])") - - @property - def _parsed_pkg_info(self): - """Parse and cache metadata""" - try: - return self._pkg_info - except AttributeError: - metadata = self.get_metadata(self.PKG_INFO) - self._pkg_info = email.parser.Parser().parsestr(metadata) - return self._pkg_info - - @property - def _dep_map(self): - try: - return self.__dep_map - except AttributeError: - self.__dep_map = self._compute_dependencies() - return self.__dep_map - - def _compute_dependencies(self): - """Recompute this distribution's dependencies.""" - dm = self.__dep_map = {None: []} - - reqs = [] - # Including any condition expressions - for req in self._parsed_pkg_info.get_all('Requires-Dist') or []: - reqs.extend(parse_requirements(req)) - - def reqs_for_extra(extra): - for req in reqs: - if not req.marker or req.marker.evaluate({'extra': extra}): - yield req - - common = frozenset(reqs_for_extra(None)) - dm[None].extend(common) - - for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []: - s_extra = safe_extra(extra.strip()) - dm[s_extra] = list(frozenset(reqs_for_extra(extra)) - common) - - return dm - - -_distributionImpl = { - '.egg': Distribution, - '.egg-info': EggInfoDistribution, - '.dist-info': DistInfoDistribution, -} - - -def issue_warning(*args, **kw): - level = 1 - g = globals() - try: - # find the first stack frame that is *not* code in - # the pkg_resources module, to use for the warning - while sys._getframe(level).f_globals is g: - level += 1 - except ValueError: - pass - warnings.warn(stacklevel=level + 1, *args, **kw) - - -class RequirementParseError(ValueError): - def __str__(self): - return ' '.join(self.args) - - -def parse_requirements(strs): - """Yield ``Requirement`` objects for each specification in `strs` - - `strs` must be a string, or a (possibly-nested) iterable thereof. - """ - # create a steppable iterator, so we can handle \-continuations - lines = iter(yield_lines(strs)) - - for line in lines: - # Drop comments -- a hash without a space may be in a URL. - if ' #' in line: - line = line[:line.find(' #')] - # If there is a line continuation, drop it, and append the next line. - if line.endswith('\\'): - line = line[:-2].strip() - try: - line += next(lines) - except StopIteration: - return - yield Requirement(line) - - -class Requirement(packaging.requirements.Requirement): - def __init__(self, requirement_string): - """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!""" - try: - super(Requirement, self).__init__(requirement_string) - except packaging.requirements.InvalidRequirement as e: - raise RequirementParseError(str(e)) - self.unsafe_name = self.name - project_name = safe_name(self.name) - self.project_name, self.key = project_name, project_name.lower() - self.specs = [ - (spec.operator, spec.version) for spec in self.specifier] - self.extras = tuple(map(safe_extra, self.extras)) - self.hashCmp = ( - self.key, - self.specifier, - frozenset(self.extras), - str(self.marker) if self.marker else None, - ) - self.__hash = hash(self.hashCmp) - - def __eq__(self, other): - return ( - isinstance(other, Requirement) and - self.hashCmp == other.hashCmp - ) - - def __ne__(self, other): - return not self == other - - def __contains__(self, item): - if isinstance(item, Distribution): - if item.key != self.key: - return False - - item = item.version - - # Allow prereleases always in order to match the previous behavior of - # this method. In the future this should be smarter and follow PEP 440 - # more accurately. - return self.specifier.contains(item, prereleases=True) - - def __hash__(self): - return self.__hash - - def __repr__(self): - return "Requirement.parse(%r)" % str(self) - - @staticmethod - def parse(s): - req, = parse_requirements(s) - return req - - -def _always_object(classes): - """ - Ensure object appears in the mro even - for old-style classes. - """ - if object not in classes: - return classes + (object,) - return classes - - -def _find_adapter(registry, ob): - """Return an adapter factory for `ob` from `registry`""" - types = _always_object(inspect.getmro(getattr(ob, '__class__', type(ob)))) - for t in types: - if t in registry: - return registry[t] - - -def ensure_directory(path): - """Ensure that the parent directory of `path` exists""" - dirname = os.path.dirname(path) - py31compat.makedirs(dirname, exist_ok=True) - - -def _bypass_ensure_directory(path): - """Sandbox-bypassing version of ensure_directory()""" - if not WRITE_SUPPORT: - raise IOError('"os.mkdir" not supported on this platform.') - dirname, filename = split(path) - if dirname and filename and not isdir(dirname): - _bypass_ensure_directory(dirname) - try: - mkdir(dirname, 0o755) - except FileExistsError: - pass - - -def split_sections(s): - """Split a string or iterable thereof into (section, content) pairs - - Each ``section`` is a stripped version of the section header ("[section]") - and each ``content`` is a list of stripped lines excluding blank lines and - comment-only lines. If there are any such lines before the first section - header, they're returned in a first ``section`` of ``None``. - """ - section = None - content = [] - for line in yield_lines(s): - if line.startswith("["): - if line.endswith("]"): - if section or content: - yield section, content - section = line[1:-1].strip() - content = [] - else: - raise ValueError("Invalid section heading", line) - else: - content.append(line) - - # wrap up last segment - yield section, content - - -def _mkstemp(*args, **kw): - old_open = os.open - try: - # temporarily bypass sandboxing - os.open = os_open - return tempfile.mkstemp(*args, **kw) - finally: - # and then put it back - os.open = old_open - - -# Silence the PEP440Warning by default, so that end users don't get hit by it -# randomly just because they use pkg_resources. We want to append the rule -# because we want earlier uses of filterwarnings to take precedence over this -# one. -warnings.filterwarnings("ignore", category=PEP440Warning, append=True) - - -# from jaraco.functools 1.3 -def _call_aside(f, *args, **kwargs): - f(*args, **kwargs) - return f - - -@_call_aside -def _initialize(g=globals()): - "Set up global resource manager (deliberately not state-saved)" - manager = ResourceManager() - g['_manager'] = manager - g.update( - (name, getattr(manager, name)) - for name in dir(manager) - if not name.startswith('_') - ) - - -@_call_aside -def _initialize_master_working_set(): - """ - Prepare the master working set and make the ``require()`` - API available. - - This function has explicit effects on the global state - of pkg_resources. It is intended to be invoked once at - the initialization of this module. - - Invocation by other packages is unsupported and done - at their own risk. - """ - working_set = WorkingSet._build_master() - _declare_state('object', working_set=working_set) - - require = working_set.require - iter_entry_points = working_set.iter_entry_points - add_activation_listener = working_set.subscribe - run_script = working_set.run_script - # backward compatibility - run_main = run_script - # Activate all distributions already on sys.path with replace=False and - # ensure that all distributions added to the working set in the future - # (e.g. by calling ``require()``) will get activated as well, - # with higher priority (replace=True). - tuple( - dist.activate(replace=False) - for dist in working_set - ) - add_activation_listener( - lambda dist: dist.activate(replace=True), - existing=False, - ) - working_set.entries = [] - # match order - list(map(working_set.add_entry, sys.path)) - globals().update(locals()) - -class PkgResourcesDeprecationWarning(Warning): - """ - Base class for warning about deprecations in ``pkg_resources`` - - This class is not derived from ``DeprecationWarning``, and as such is - visible by default. - """ diff --git a/lib/pkg_resources/_vendor/__init__.py b/lib/pkg_resources/_vendor/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/pkg_resources/_vendor/appdirs.py b/lib/pkg_resources/_vendor/appdirs.py deleted file mode 100644 index ae67001..0000000 --- a/lib/pkg_resources/_vendor/appdirs.py +++ /dev/null @@ -1,608 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# Copyright (c) 2005-2010 ActiveState Software Inc. -# Copyright (c) 2013 Eddy Petrișor - -"""Utilities for determining application-specific dirs. - -See <http://github.com/ActiveState/appdirs> for details and usage. -""" -# Dev Notes: -# - MSDN on where to store app data files: -# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 -# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html -# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html - -__version_info__ = (1, 4, 3) -__version__ = '.'.join(map(str, __version_info__)) - - -import sys -import os - -PY3 = sys.version_info[0] == 3 - -if PY3: - unicode = str - -if sys.platform.startswith('java'): - import platform - os_name = platform.java_ver()[3][0] - if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. - system = 'win32' - elif os_name.startswith('Mac'): # "Mac OS X", etc. - system = 'darwin' - else: # "Linux", "SunOS", "FreeBSD", etc. - # Setting this to "linux2" is not ideal, but only Windows or Mac - # are actually checked for and the rest of the module expects - # *sys.platform* style strings. - system = 'linux2' -else: - system = sys.platform - - - -def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): - r"""Return full path to the user-specific data dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be "<major>.<minor>". - Only applied when appname is present. - "roaming" (boolean, default False) can be set True to use the Windows - roaming appdata directory. That means that for users on a Windows - network setup for roaming profiles, this user data will be - sync'd on login. See - <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> - for a discussion of issues. - - Typical user data directories are: - Mac OS X: ~/Library/Application Support/<AppName> - Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined - Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName> - Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName> - Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName> - Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName> - - For Unix, we follow the XDG spec and support $XDG_DATA_HOME. - That means, by default "~/.local/share/<AppName>". - """ - if system == "win32": - if appauthor is None: - appauthor = appname - const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" - path = os.path.normpath(_get_win_folder(const)) - if appname: - if appauthor is not False: - path = os.path.join(path, appauthor, appname) - else: - path = os.path.join(path, appname) - elif system == 'darwin': - path = os.path.expanduser('~/Library/Application Support/') - if appname: - path = os.path.join(path, appname) - else: - path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) - if appname: - path = os.path.join(path, appname) - if appname and version: - path = os.path.join(path, version) - return path - - -def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): - r"""Return full path to the user-shared data dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be "<major>.<minor>". - Only applied when appname is present. - "multipath" is an optional parameter only applicable to *nix - which indicates that the entire list of data dirs should be - returned. By default, the first item from XDG_DATA_DIRS is - returned, or '/usr/local/share/<AppName>', - if XDG_DATA_DIRS is not set - - Typical site data directories are: - Mac OS X: /Library/Application Support/<AppName> - Unix: /usr/local/share/<AppName> or /usr/share/<AppName> - Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName> - Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) - Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7. - - For Unix, this is using the $XDG_DATA_DIRS[0] default. - - WARNING: Do not use this on Windows. See the Vista-Fail note above for why. - """ - if system == "win32": - if appauthor is None: - appauthor = appname - path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) - if appname: - if appauthor is not False: - path = os.path.join(path, appauthor, appname) - else: - path = os.path.join(path, appname) - elif system == 'darwin': - path = os.path.expanduser('/Library/Application Support') - if appname: - path = os.path.join(path, appname) - else: - # XDG default for $XDG_DATA_DIRS - # only first, if multipath is False - path = os.getenv('XDG_DATA_DIRS', - os.pathsep.join(['/usr/local/share', '/usr/share'])) - pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] - if appname: - if version: - appname = os.path.join(appname, version) - pathlist = [os.sep.join([x, appname]) for x in pathlist] - - if multipath: - path = os.pathsep.join(pathlist) - else: - path = pathlist[0] - return path - - if appname and version: - path = os.path.join(path, version) - return path - - -def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): - r"""Return full path to the user-specific config dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be "<major>.<minor>". - Only applied when appname is present. - "roaming" (boolean, default False) can be set True to use the Windows - roaming appdata directory. That means that for users on a Windows - network setup for roaming profiles, this user data will be - sync'd on login. See - <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> - for a discussion of issues. - - Typical user config directories are: - Mac OS X: same as user_data_dir - Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined - Win *: same as user_data_dir - - For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. - That means, by default "~/.config/<AppName>". - """ - if system in ["win32", "darwin"]: - path = user_data_dir(appname, appauthor, None, roaming) - else: - path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) - if appname: - path = os.path.join(path, appname) - if appname and version: - path = os.path.join(path, version) - return path - - -def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): - r"""Return full path to the user-shared data dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be "<major>.<minor>". - Only applied when appname is present. - "multipath" is an optional parameter only applicable to *nix - which indicates that the entire list of config dirs should be - returned. By default, the first item from XDG_CONFIG_DIRS is - returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set - - Typical site config directories are: - Mac OS X: same as site_data_dir - Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in - $XDG_CONFIG_DIRS - Win *: same as site_data_dir - Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) - - For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False - - WARNING: Do not use this on Windows. See the Vista-Fail note above for why. - """ - if system in ["win32", "darwin"]: - path = site_data_dir(appname, appauthor) - if appname and version: - path = os.path.join(path, version) - else: - # XDG default for $XDG_CONFIG_DIRS - # only first, if multipath is False - path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') - pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] - if appname: - if version: - appname = os.path.join(appname, version) - pathlist = [os.sep.join([x, appname]) for x in pathlist] - - if multipath: - path = os.pathsep.join(pathlist) - else: - path = pathlist[0] - return path - - -def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): - r"""Return full path to the user-specific cache dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be "<major>.<minor>". - Only applied when appname is present. - "opinion" (boolean) can be False to disable the appending of - "Cache" to the base app data dir for Windows. See - discussion below. - - Typical user cache directories are: - Mac OS X: ~/Library/Caches/<AppName> - Unix: ~/.cache/<AppName> (XDG default) - Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache - Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache - - On Windows the only suggestion in the MSDN docs is that local settings go in - the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming - app data dir (the default returned by `user_data_dir` above). Apps typically - put cache data somewhere *under* the given dir here. Some examples: - ...\Mozilla\Firefox\Profiles\<ProfileName>\Cache - ...\Acme\SuperApp\Cache\1.0 - OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. - This can be disabled with the `opinion=False` option. - """ - if system == "win32": - if appauthor is None: - appauthor = appname - path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) - if appname: - if appauthor is not False: - path = os.path.join(path, appauthor, appname) - else: - path = os.path.join(path, appname) - if opinion: - path = os.path.join(path, "Cache") - elif system == 'darwin': - path = os.path.expanduser('~/Library/Caches') - if appname: - path = os.path.join(path, appname) - else: - path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) - if appname: - path = os.path.join(path, appname) - if appname and version: - path = os.path.join(path, version) - return path - - -def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): - r"""Return full path to the user-specific state dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be "<major>.<minor>". - Only applied when appname is present. - "roaming" (boolean, default False) can be set True to use the Windows - roaming appdata directory. That means that for users on a Windows - network setup for roaming profiles, this user data will be - sync'd on login. See - <http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx> - for a discussion of issues. - - Typical user state directories are: - Mac OS X: same as user_data_dir - Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined - Win *: same as user_data_dir - - For Unix, we follow this Debian proposal <https://wiki.debian.org/XDGBaseDirectorySpecification#state> - to extend the XDG spec and support $XDG_STATE_HOME. - - That means, by default "~/.local/state/<AppName>". - """ - if system in ["win32", "darwin"]: - path = user_data_dir(appname, appauthor, None, roaming) - else: - path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) - if appname: - path = os.path.join(path, appname) - if appname and version: - path = os.path.join(path, version) - return path - - -def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): - r"""Return full path to the user-specific log dir for this application. - - "appname" is the name of application. - If None, just the system directory is returned. - "appauthor" (only used on Windows) is the name of the - appauthor or distributing body for this application. Typically - it is the owning company name. This falls back to appname. You may - pass False to disable it. - "version" is an optional version path element to append to the - path. You might want to use this if you want multiple versions - of your app to be able to run independently. If used, this - would typically be "<major>.<minor>". - Only applied when appname is present. - "opinion" (boolean) can be False to disable the appending of - "Logs" to the base app data dir for Windows, and "log" to the - base cache dir for Unix. See discussion below. - - Typical user log directories are: - Mac OS X: ~/Library/Logs/<AppName> - Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined - Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs - Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs - - On Windows the only suggestion in the MSDN docs is that local settings - go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in - examples of what some windows apps use for a logs dir.) - - OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` - value for Windows and appends "log" to the user cache dir for Unix. - This can be disabled with the `opinion=False` option. - """ - if system == "darwin": - path = os.path.join( - os.path.expanduser('~/Library/Logs'), - appname) - elif system == "win32": - path = user_data_dir(appname, appauthor, version) - version = False - if opinion: - path = os.path.join(path, "Logs") - else: - path = user_cache_dir(appname, appauthor, version) - version = False - if opinion: - path = os.path.join(path, "log") - if appname and version: - path = os.path.join(path, version) - return path - - -class AppDirs(object): - """Convenience wrapper for getting application dirs.""" - def __init__(self, appname=None, appauthor=None, version=None, - roaming=False, multipath=False): - self.appname = appname - self.appauthor = appauthor - self.version = version - self.roaming = roaming - self.multipath = multipath - - @property - def user_data_dir(self): - return user_data_dir(self.appname, self.appauthor, - version=self.version, roaming=self.roaming) - - @property - def site_data_dir(self): - return site_data_dir(self.appname, self.appauthor, - version=self.version, multipath=self.multipath) - - @property - def user_config_dir(self): - return user_config_dir(self.appname, self.appauthor, - version=self.version, roaming=self.roaming) - - @property - def site_config_dir(self): - return site_config_dir(self.appname, self.appauthor, - version=self.version, multipath=self.multipath) - - @property - def user_cache_dir(self): - return user_cache_dir(self.appname, self.appauthor, - version=self.version) - - @property - def user_state_dir(self): - return user_state_dir(self.appname, self.appauthor, - version=self.version) - - @property - def user_log_dir(self): - return user_log_dir(self.appname, self.appauthor, - version=self.version) - - -#---- internal support stuff - -def _get_win_folder_from_registry(csidl_name): - """This is a fallback technique at best. I'm not sure if using the - registry for this guarantees us the correct answer for all CSIDL_* - names. - """ - if PY3: - import winreg as _winreg - else: - import _winreg - - shell_folder_name = { - "CSIDL_APPDATA": "AppData", - "CSIDL_COMMON_APPDATA": "Common AppData", - "CSIDL_LOCAL_APPDATA": "Local AppData", - }[csidl_name] - - key = _winreg.OpenKey( - _winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" - ) - dir, type = _winreg.QueryValueEx(key, shell_folder_name) - return dir - - -def _get_win_folder_with_pywin32(csidl_name): - from win32com.shell import shellcon, shell - dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0) - # Try to make this a unicode path because SHGetFolderPath does - # not return unicode strings when there is unicode data in the - # path. - try: - dir = unicode(dir) - - # Downgrade to short path name if have highbit chars. See - # <http://bugs.activestate.com/show_bug.cgi?id=85099>. - has_high_char = False - for c in dir: - if ord(c) > 255: - has_high_char = True - break - if has_high_char: - try: - import win32api - dir = win32api.GetShortPathName(dir) - except ImportError: - pass - except UnicodeError: - pass - return dir - - -def _get_win_folder_with_ctypes(csidl_name): - import ctypes - - csidl_const = { - "CSIDL_APPDATA": 26, - "CSIDL_COMMON_APPDATA": 35, - "CSIDL_LOCAL_APPDATA": 28, - }[csidl_name] - - buf = ctypes.create_unicode_buffer(1024) - ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) - - # Downgrade to short path name if have highbit chars. See - # <http://bugs.activestate.com/show_bug.cgi?id=85099>. - has_high_char = False - for c in buf: - if ord(c) > 255: - has_high_char = True - break - if has_high_char: - buf2 = ctypes.create_unicode_buffer(1024) - if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): - buf = buf2 - - return buf.value - -def _get_win_folder_with_jna(csidl_name): - import array - from com.sun import jna - from com.sun.jna.platform import win32 - - buf_size = win32.WinDef.MAX_PATH * 2 - buf = array.zeros('c', buf_size) - shell = win32.Shell32.INSTANCE - shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) - dir = jna.Native.toString(buf.tostring()).rstrip("\0") - - # Downgrade to short path name if have highbit chars. See - # <http://bugs.activestate.com/show_bug.cgi?id=85099>. - has_high_char = False - for c in dir: - if ord(c) > 255: - has_high_char = True - break - if has_high_char: - buf = array.zeros('c', buf_size) - kernel = win32.Kernel32.INSTANCE - if kernel.GetShortPathName(dir, buf, buf_size): - dir = jna.Native.toString(buf.tostring()).rstrip("\0") - - return dir - -if system == "win32": - try: - import win32com.shell - _get_win_folder = _get_win_folder_with_pywin32 - except ImportError: - try: - from ctypes import windll - _get_win_folder = _get_win_folder_with_ctypes - except ImportError: - try: - import com.sun.jna - _get_win_folder = _get_win_folder_with_jna - except ImportError: - _get_win_folder = _get_win_folder_from_registry - - -#---- self test code - -if __name__ == "__main__": - appname = "MyApp" - appauthor = "MyCompany" - - props = ("user_data_dir", - "user_config_dir", - "user_cache_dir", - "user_state_dir", - "user_log_dir", - "site_data_dir", - "site_config_dir") - - print("-- app dirs %s --" % __version__) - - print("-- app dirs (with optional 'version')") - dirs = AppDirs(appname, appauthor, version="1.0") - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) - - print("\n-- app dirs (without optional 'version')") - dirs = AppDirs(appname, appauthor) - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) - - print("\n-- app dirs (without optional 'appauthor')") - dirs = AppDirs(appname) - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) - - print("\n-- app dirs (with disabled 'appauthor')") - dirs = AppDirs(appname, appauthor=False) - for prop in props: - print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/lib/pkg_resources/_vendor/packaging/__about__.py b/lib/pkg_resources/_vendor/packaging/__about__.py deleted file mode 100644 index 95d330e..0000000 --- a/lib/pkg_resources/_vendor/packaging/__about__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -__all__ = [ - "__title__", "__summary__", "__uri__", "__version__", "__author__", - "__email__", "__license__", "__copyright__", -] - -__title__ = "packaging" -__summary__ = "Core utilities for Python packages" -__uri__ = "https://github.com/pypa/packaging" - -__version__ = "16.8" - -__author__ = "Donald Stufft and individual contributors" -__email__ = "donald@stufft.io" - -__license__ = "BSD or Apache License, Version 2.0" -__copyright__ = "Copyright 2014-2016 %s" % __author__ diff --git a/lib/pkg_resources/_vendor/packaging/__init__.py b/lib/pkg_resources/_vendor/packaging/__init__.py deleted file mode 100644 index 5ee6220..0000000 --- a/lib/pkg_resources/_vendor/packaging/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -from .__about__ import ( - __author__, __copyright__, __email__, __license__, __summary__, __title__, - __uri__, __version__ -) - -__all__ = [ - "__title__", "__summary__", "__uri__", "__version__", "__author__", - "__email__", "__license__", "__copyright__", -] diff --git a/lib/pkg_resources/_vendor/packaging/_compat.py b/lib/pkg_resources/_vendor/packaging/_compat.py deleted file mode 100644 index 210bb80..0000000 --- a/lib/pkg_resources/_vendor/packaging/_compat.py +++ /dev/null @@ -1,30 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import sys - - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -# flake8: noqa - -if PY3: - string_types = str, -else: - string_types = basestring, - - -def with_metaclass(meta, *bases): - """ - Create a base class with a metaclass. - """ - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(meta): - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) diff --git a/lib/pkg_resources/_vendor/packaging/_structures.py b/lib/pkg_resources/_vendor/packaging/_structures.py deleted file mode 100644 index ccc2786..0000000 --- a/lib/pkg_resources/_vendor/packaging/_structures.py +++ /dev/null @@ -1,68 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - - -class Infinity(object): - - def __repr__(self): - return "Infinity" - - def __hash__(self): - return hash(repr(self)) - - def __lt__(self, other): - return False - - def __le__(self, other): - return False - - def __eq__(self, other): - return isinstance(other, self.__class__) - - def __ne__(self, other): - return not isinstance(other, self.__class__) - - def __gt__(self, other): - return True - - def __ge__(self, other): - return True - - def __neg__(self): - return NegativeInfinity - -Infinity = Infinity() - - -class NegativeInfinity(object): - - def __repr__(self): - return "-Infinity" - - def __hash__(self): - return hash(repr(self)) - - def __lt__(self, other): - return True - - def __le__(self, other): - return True - - def __eq__(self, other): - return isinstance(other, self.__class__) - - def __ne__(self, other): - return not isinstance(other, self.__class__) - - def __gt__(self, other): - return False - - def __ge__(self, other): - return False - - def __neg__(self): - return Infinity - -NegativeInfinity = NegativeInfinity() diff --git a/lib/pkg_resources/_vendor/packaging/markers.py b/lib/pkg_resources/_vendor/packaging/markers.py deleted file mode 100644 index 892e578..0000000 --- a/lib/pkg_resources/_vendor/packaging/markers.py +++ /dev/null @@ -1,301 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import operator -import os -import platform -import sys - -from pkg_resources.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd -from pkg_resources.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString -from pkg_resources.extern.pyparsing import Literal as L # noqa - -from ._compat import string_types -from .specifiers import Specifier, InvalidSpecifier - - -__all__ = [ - "InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName", - "Marker", "default_environment", -] - - -class InvalidMarker(ValueError): - """ - An invalid marker was found, users should refer to PEP 508. - """ - - -class UndefinedComparison(ValueError): - """ - An invalid operation was attempted on a value that doesn't support it. - """ - - -class UndefinedEnvironmentName(ValueError): - """ - A name was attempted to be used that does not exist inside of the - environment. - """ - - -class Node(object): - - def __init__(self, value): - self.value = value - - def __str__(self): - return str(self.value) - - def __repr__(self): - return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) - - def serialize(self): - raise NotImplementedError - - -class Variable(Node): - - def serialize(self): - return str(self) - - -class Value(Node): - - def serialize(self): - return '"{0}"'.format(self) - - -class Op(Node): - - def serialize(self): - return str(self) - - -VARIABLE = ( - L("implementation_version") | - L("platform_python_implementation") | - L("implementation_name") | - L("python_full_version") | - L("platform_release") | - L("platform_version") | - L("platform_machine") | - L("platform_system") | - L("python_version") | - L("sys_platform") | - L("os_name") | - L("os.name") | # PEP-345 - L("sys.platform") | # PEP-345 - L("platform.version") | # PEP-345 - L("platform.machine") | # PEP-345 - L("platform.python_implementation") | # PEP-345 - L("python_implementation") | # undocumented setuptools legacy - L("extra") -) -ALIASES = { - 'os.name': 'os_name', - 'sys.platform': 'sys_platform', - 'platform.version': 'platform_version', - 'platform.machine': 'platform_machine', - 'platform.python_implementation': 'platform_python_implementation', - 'python_implementation': 'platform_python_implementation' -} -VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) - -VERSION_CMP = ( - L("===") | - L("==") | - L(">=") | - L("<=") | - L("!=") | - L("~=") | - L(">") | - L("<") -) - -MARKER_OP = VERSION_CMP | L("not in") | L("in") -MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) - -MARKER_VALUE = QuotedString("'") | QuotedString('"') -MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) - -BOOLOP = L("and") | L("or") - -MARKER_VAR = VARIABLE | MARKER_VALUE - -MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) -MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) - -LPAREN = L("(").suppress() -RPAREN = L(")").suppress() - -MARKER_EXPR = Forward() -MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) -MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) - -MARKER = stringStart + MARKER_EXPR + stringEnd - - -def _coerce_parse_result(results): - if isinstance(results, ParseResults): - return [_coerce_parse_result(i) for i in results] - else: - return results - - -def _format_marker(marker, first=True): - assert isinstance(marker, (list, tuple, string_types)) - - # Sometimes we have a structure like [[...]] which is a single item list - # where the single item is itself it's own list. In that case we want skip - # the rest of this function so that we don't get extraneous () on the - # outside. - if (isinstance(marker, list) and len(marker) == 1 and - isinstance(marker[0], (list, tuple))): - return _format_marker(marker[0]) - - if isinstance(marker, list): - inner = (_format_marker(m, first=False) for m in marker) - if first: - return " ".join(inner) - else: - return "(" + " ".join(inner) + ")" - elif isinstance(marker, tuple): - return " ".join([m.serialize() for m in marker]) - else: - return marker - - -_operators = { - "in": lambda lhs, rhs: lhs in rhs, - "not in": lambda lhs, rhs: lhs not in rhs, - "<": operator.lt, - "<=": operator.le, - "==": operator.eq, - "!=": operator.ne, - ">=": operator.ge, - ">": operator.gt, -} - - -def _eval_op(lhs, op, rhs): - try: - spec = Specifier("".join([op.serialize(), rhs])) - except InvalidSpecifier: - pass - else: - return spec.contains(lhs) - - oper = _operators.get(op.serialize()) - if oper is None: - raise UndefinedComparison( - "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) - ) - - return oper(lhs, rhs) - - -_undefined = object() - - -def _get_env(environment, name): - value = environment.get(name, _undefined) - - if value is _undefined: - raise UndefinedEnvironmentName( - "{0!r} does not exist in evaluation environment.".format(name) - ) - - return value - - -def _evaluate_markers(markers, environment): - groups = [[]] - - for marker in markers: - assert isinstance(marker, (list, tuple, string_types)) - - if isinstance(marker, list): - groups[-1].append(_evaluate_markers(marker, environment)) - elif isinstance(marker, tuple): - lhs, op, rhs = marker - - if isinstance(lhs, Variable): - lhs_value = _get_env(environment, lhs.value) - rhs_value = rhs.value - else: - lhs_value = lhs.value - rhs_value = _get_env(environment, rhs.value) - - groups[-1].append(_eval_op(lhs_value, op, rhs_value)) - else: - assert marker in ["and", "or"] - if marker == "or": - groups.append([]) - - return any(all(item) for item in groups) - - -def format_full_version(info): - version = '{0.major}.{0.minor}.{0.micro}'.format(info) - kind = info.releaselevel - if kind != 'final': - version += kind[0] + str(info.serial) - return version - - -def default_environment(): - if hasattr(sys, 'implementation'): - iver = format_full_version(sys.implementation.version) - implementation_name = sys.implementation.name - else: - iver = '0' - implementation_name = '' - - return { - "implementation_name": implementation_name, - "implementation_version": iver, - "os_name": os.name, - "platform_machine": platform.machine(), - "platform_release": platform.release(), - "platform_system": platform.system(), - "platform_version": platform.version(), - "python_full_version": platform.python_version(), - "platform_python_implementation": platform.python_implementation(), - "python_version": platform.python_version()[:3], - "sys_platform": sys.platform, - } - - -class Marker(object): - - def __init__(self, marker): - try: - self._markers = _coerce_parse_result(MARKER.parseString(marker)) - except ParseException as e: - err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( - marker, marker[e.loc:e.loc + 8]) - raise InvalidMarker(err_str) - - def __str__(self): - return _format_marker(self._markers) - - def __repr__(self): - return "<Marker({0!r})>".format(str(self)) - - def evaluate(self, environment=None): - """Evaluate a marker. - - Return the boolean from evaluating the given marker against the - environment. environment is an optional argument to override all or - part of the determined environment. - - The environment is determined from the current Python process. - """ - current_environment = default_environment() - if environment is not None: - current_environment.update(environment) - - return _evaluate_markers(self._markers, current_environment) diff --git a/lib/pkg_resources/_vendor/packaging/requirements.py b/lib/pkg_resources/_vendor/packaging/requirements.py deleted file mode 100644 index 0c8c4a3..0000000 --- a/lib/pkg_resources/_vendor/packaging/requirements.py +++ /dev/null @@ -1,127 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import string -import re - -from pkg_resources.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException -from pkg_resources.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine -from pkg_resources.extern.pyparsing import Literal as L # noqa -from pkg_resources.extern.six.moves.urllib import parse as urlparse - -from .markers import MARKER_EXPR, Marker -from .specifiers import LegacySpecifier, Specifier, SpecifierSet - - -class InvalidRequirement(ValueError): - """ - An invalid requirement was found, users should refer to PEP 508. - """ - - -ALPHANUM = Word(string.ascii_letters + string.digits) - -LBRACKET = L("[").suppress() -RBRACKET = L("]").suppress() -LPAREN = L("(").suppress() -RPAREN = L(")").suppress() -COMMA = L(",").suppress() -SEMICOLON = L(";").suppress() -AT = L("@").suppress() - -PUNCTUATION = Word("-_.") -IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) -IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) - -NAME = IDENTIFIER("name") -EXTRA = IDENTIFIER - -URI = Regex(r'[^ ]+')("url") -URL = (AT + URI) - -EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) -EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") - -VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) -VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) - -VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY -VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), - joinString=",", adjacent=False)("_raw_spec") -_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) -_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '') - -VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") -VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) - -MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") -MARKER_EXPR.setParseAction( - lambda s, l, t: Marker(s[t._original_start:t._original_end]) -) -MARKER_SEPERATOR = SEMICOLON -MARKER = MARKER_SEPERATOR + MARKER_EXPR - -VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) -URL_AND_MARKER = URL + Optional(MARKER) - -NAMED_REQUIREMENT = \ - NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) - -REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd - - -class Requirement(object): - """Parse a requirement. - - Parse a given requirement string into its parts, such as name, specifier, - URL, and extras. Raises InvalidRequirement on a badly-formed requirement - string. - """ - - # TODO: Can we test whether something is contained within a requirement? - # If so how do we do that? Do we need to test against the _name_ of - # the thing as well as the version? What about the markers? - # TODO: Can we normalize the name and extra name? - - def __init__(self, requirement_string): - try: - req = REQUIREMENT.parseString(requirement_string) - except ParseException as e: - raise InvalidRequirement( - "Invalid requirement, parse error at \"{0!r}\"".format( - requirement_string[e.loc:e.loc + 8])) - - self.name = req.name - if req.url: - parsed_url = urlparse.urlparse(req.url) - if not (parsed_url.scheme and parsed_url.netloc) or ( - not parsed_url.scheme and not parsed_url.netloc): - raise InvalidRequirement("Invalid URL given") - self.url = req.url - else: - self.url = None - self.extras = set(req.extras.asList() if req.extras else []) - self.specifier = SpecifierSet(req.specifier) - self.marker = req.marker if req.marker else None - - def __str__(self): - parts = [self.name] - - if self.extras: - parts.append("[{0}]".format(",".join(sorted(self.extras)))) - - if self.specifier: - parts.append(str(self.specifier)) - - if self.url: - parts.append("@ {0}".format(self.url)) - - if self.marker: - parts.append("; {0}".format(self.marker)) - - return "".join(parts) - - def __repr__(self): - return "<Requirement({0!r})>".format(str(self)) diff --git a/lib/pkg_resources/_vendor/packaging/specifiers.py b/lib/pkg_resources/_vendor/packaging/specifiers.py deleted file mode 100644 index 7f5a76c..0000000 --- a/lib/pkg_resources/_vendor/packaging/specifiers.py +++ /dev/null @@ -1,774 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import abc -import functools -import itertools -import re - -from ._compat import string_types, with_metaclass -from .version import Version, LegacyVersion, parse - - -class InvalidSpecifier(ValueError): - """ - An invalid specifier was found, users should refer to PEP 440. - """ - - -class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): - - @abc.abstractmethod - def __str__(self): - """ - Returns the str representation of this Specifier like object. This - should be representative of the Specifier itself. - """ - - @abc.abstractmethod - def __hash__(self): - """ - Returns a hash value for this Specifier like object. - """ - - @abc.abstractmethod - def __eq__(self, other): - """ - Returns a boolean representing whether or not the two Specifier like - objects are equal. - """ - - @abc.abstractmethod - def __ne__(self, other): - """ - Returns a boolean representing whether or not the two Specifier like - objects are not equal. - """ - - @abc.abstractproperty - def prereleases(self): - """ - Returns whether or not pre-releases as a whole are allowed by this - specifier. - """ - - @prereleases.setter - def prereleases(self, value): - """ - Sets whether or not pre-releases as a whole are allowed by this - specifier. - """ - - @abc.abstractmethod - def contains(self, item, prereleases=None): - """ - Determines if the given item is contained within this specifier. - """ - - @abc.abstractmethod - def filter(self, iterable, prereleases=None): - """ - Takes an iterable of items and filters them so that only items which - are contained within this specifier are allowed in it. - """ - - -class _IndividualSpecifier(BaseSpecifier): - - _operators = {} - - def __init__(self, spec="", prereleases=None): - match = self._regex.search(spec) - if not match: - raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) - - self._spec = ( - match.group("operator").strip(), - match.group("version").strip(), - ) - - # Store whether or not this Specifier should accept prereleases - self._prereleases = prereleases - - def __repr__(self): - pre = ( - ", prereleases={0!r}".format(self.prereleases) - if self._prereleases is not None - else "" - ) - - return "<{0}({1!r}{2})>".format( - self.__class__.__name__, - str(self), - pre, - ) - - def __str__(self): - return "{0}{1}".format(*self._spec) - - def __hash__(self): - return hash(self._spec) - - def __eq__(self, other): - if isinstance(other, string_types): - try: - other = self.__class__(other) - except InvalidSpecifier: - return NotImplemented - elif not isinstance(other, self.__class__): - return NotImplemented - - return self._spec == other._spec - - def __ne__(self, other): - if isinstance(other, string_types): - try: - other = self.__class__(other) - except InvalidSpecifier: - return NotImplemented - elif not isinstance(other, self.__class__): - return NotImplemented - - return self._spec != other._spec - - def _get_operator(self, op): - return getattr(self, "_compare_{0}".format(self._operators[op])) - - def _coerce_version(self, version): - if not isinstance(version, (LegacyVersion, Version)): - version = parse(version) - return version - - @property - def operator(self): - return self._spec[0] - - @property - def version(self): - return self._spec[1] - - @property - def prereleases(self): - return self._prereleases - - @prereleases.setter - def prereleases(self, value): - self._prereleases = value - - def __contains__(self, item): - return self.contains(item) - - def contains(self, item, prereleases=None): - # Determine if prereleases are to be allowed or not. - if prereleases is None: - prereleases = self.prereleases - - # Normalize item to a Version or LegacyVersion, this allows us to have - # a shortcut for ``"2.0" in Specifier(">=2") - item = self._coerce_version(item) - - # Determine if we should be supporting prereleases in this specifier - # or not, if we do not support prereleases than we can short circuit - # logic if this version is a prereleases. - if item.is_prerelease and not prereleases: - return False - - # Actually do the comparison to determine if this item is contained - # within this Specifier or not. - return self._get_operator(self.operator)(item, self.version) - - def filter(self, iterable, prereleases=None): - yielded = False - found_prereleases = [] - - kw = {"prereleases": prereleases if prereleases is not None else True} - - # Attempt to iterate over all the values in the iterable and if any of - # them match, yield them. - for version in iterable: - parsed_version = self._coerce_version(version) - - if self.contains(parsed_version, **kw): - # If our version is a prerelease, and we were not set to allow - # prereleases, then we'll store it for later incase nothing - # else matches this specifier. - if (parsed_version.is_prerelease and not - (prereleases or self.prereleases)): - found_prereleases.append(version) - # Either this is not a prerelease, or we should have been - # accepting prereleases from the begining. - else: - yielded = True - yield version - - # Now that we've iterated over everything, determine if we've yielded - # any values, and if we have not and we have any prereleases stored up - # then we will go ahead and yield the prereleases. - if not yielded and found_prereleases: - for version in found_prereleases: - yield version - - -class LegacySpecifier(_IndividualSpecifier): - - _regex_str = ( - r""" - (?P<operator>(==|!=|<=|>=|<|>)) - \s* - (?P<version> - [^,;\s)]* # Since this is a "legacy" specifier, and the version - # string can be just about anything, we match everything - # except for whitespace, a semi-colon for marker support, - # a closing paren since versions can be enclosed in - # them, and a comma since it's a version separator. - ) - """ - ) - - _regex = re.compile( - r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) - - _operators = { - "==": "equal", - "!=": "not_equal", - "<=": "less_than_equal", - ">=": "greater_than_equal", - "<": "less_than", - ">": "greater_than", - } - - def _coerce_version(self, version): - if not isinstance(version, LegacyVersion): - version = LegacyVersion(str(version)) - return version - - def _compare_equal(self, prospective, spec): - return prospective == self._coerce_version(spec) - - def _compare_not_equal(self, prospective, spec): - return prospective != self._coerce_version(spec) - - def _compare_less_than_equal(self, prospective, spec): - return prospective <= self._coerce_version(spec) - - def _compare_greater_than_equal(self, prospective, spec): - return prospective >= self._coerce_version(spec) - - def _compare_less_than(self, prospective, spec): - return prospective < self._coerce_version(spec) - - def _compare_greater_than(self, prospective, spec): - return prospective > self._coerce_version(spec) - - -def _require_version_compare(fn): - @functools.wraps(fn) - def wrapped(self, prospective, spec): - if not isinstance(prospective, Version): - return False - return fn(self, prospective, spec) - return wrapped - - -class Specifier(_IndividualSpecifier): - - _regex_str = ( - r""" - (?P<operator>(~=|==|!=|<=|>=|<|>|===)) - (?P<version> - (?: - # The identity operators allow for an escape hatch that will - # do an exact string match of the version you wish to install. - # This will not be parsed by PEP 440 and we cannot determine - # any semantic meaning from it. This operator is discouraged - # but included entirely as an escape hatch. - (?<====) # Only match for the identity operator - \s* - [^\s]* # We just match everything, except for whitespace - # since we are only testing for strict identity. - ) - | - (?: - # The (non)equality operators allow for wild card and local - # versions to be specified so we have to define these two - # operators separately to enable that. - (?<===|!=) # Only match for equals and not equals - - \s* - v? - (?:[0-9]+!)? # epoch - [0-9]+(?:\.[0-9]+)* # release - (?: # pre release - [-_\.]? - (a|b|c|rc|alpha|beta|pre|preview) - [-_\.]? - [0-9]* - )? - (?: # post release - (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) - )? - - # You cannot use a wild card and a dev or local version - # together so group them with a | and make them optional. - (?: - (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release - (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local - | - \.\* # Wild card syntax of .* - )? - ) - | - (?: - # The compatible operator requires at least two digits in the - # release segment. - (?<=~=) # Only match for the compatible operator - - \s* - v? - (?:[0-9]+!)? # epoch - [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) - (?: # pre release - [-_\.]? - (a|b|c|rc|alpha|beta|pre|preview) - [-_\.]? - [0-9]* - )? - (?: # post release - (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) - )? - (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release - ) - | - (?: - # All other operators only allow a sub set of what the - # (non)equality operators do. Specifically they do not allow - # local versions to be specified nor do they allow the prefix - # matching wild cards. - (?<!==|!=|~=) # We have special cases for these - # operators so we want to make sure they - # don't match here. - - \s* - v? - (?:[0-9]+!)? # epoch - [0-9]+(?:\.[0-9]+)* # release - (?: # pre release - [-_\.]? - (a|b|c|rc|alpha|beta|pre|preview) - [-_\.]? - [0-9]* - )? - (?: # post release - (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) - )? - (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release - ) - ) - """ - ) - - _regex = re.compile( - r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) - - _operators = { - "~=": "compatible", - "==": "equal", - "!=": "not_equal", - "<=": "less_than_equal", - ">=": "greater_than_equal", - "<": "less_than", - ">": "greater_than", - "===": "arbitrary", - } - - @_require_version_compare - def _compare_compatible(self, prospective, spec): - # Compatible releases have an equivalent combination of >= and ==. That - # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to - # implement this in terms of the other specifiers instead of - # implementing it ourselves. The only thing we need to do is construct - # the other specifiers. - - # We want everything but the last item in the version, but we want to - # ignore post and dev releases and we want to treat the pre-release as - # it's own separate segment. - prefix = ".".join( - list( - itertools.takewhile( - lambda x: (not x.startswith("post") and not - x.startswith("dev")), - _version_split(spec), - ) - )[:-1] - ) - - # Add the prefix notation to the end of our string - prefix += ".*" - - return (self._get_operator(">=")(prospective, spec) and - self._get_operator("==")(prospective, prefix)) - - @_require_version_compare - def _compare_equal(self, prospective, spec): - # We need special logic to handle prefix matching - if spec.endswith(".*"): - # In the case of prefix matching we want to ignore local segment. - prospective = Version(prospective.public) - # Split the spec out by dots, and pretend that there is an implicit - # dot in between a release segment and a pre-release segment. - spec = _version_split(spec[:-2]) # Remove the trailing .* - - # Split the prospective version out by dots, and pretend that there - # is an implicit dot in between a release segment and a pre-release - # segment. - prospective = _version_split(str(prospective)) - - # Shorten the prospective version to be the same length as the spec - # so that we can determine if the specifier is a prefix of the - # prospective version or not. - prospective = prospective[:len(spec)] - - # Pad out our two sides with zeros so that they both equal the same - # length. - spec, prospective = _pad_version(spec, prospective) - else: - # Convert our spec string into a Version - spec = Version(spec) - - # If the specifier does not have a local segment, then we want to - # act as if the prospective version also does not have a local - # segment. - if not spec.local: - prospective = Version(prospective.public) - - return prospective == spec - - @_require_version_compare - def _compare_not_equal(self, prospective, spec): - return not self._compare_equal(prospective, spec) - - @_require_version_compare - def _compare_less_than_equal(self, prospective, spec): - return prospective <= Version(spec) - - @_require_version_compare - def _compare_greater_than_equal(self, prospective, spec): - return prospective >= Version(spec) - - @_require_version_compare - def _compare_less_than(self, prospective, spec): - # Convert our spec to a Version instance, since we'll want to work with - # it as a version. - spec = Version(spec) - - # Check to see if the prospective version is less than the spec - # version. If it's not we can short circuit and just return False now - # instead of doing extra unneeded work. - if not prospective < spec: - return False - - # This special case is here so that, unless the specifier itself - # includes is a pre-release version, that we do not accept pre-release - # versions for the version mentioned in the specifier (e.g. <3.1 should - # not match 3.1.dev0, but should match 3.0.dev0). - if not spec.is_prerelease and prospective.is_prerelease: - if Version(prospective.base_version) == Version(spec.base_version): - return False - - # If we've gotten to here, it means that prospective version is both - # less than the spec version *and* it's not a pre-release of the same - # version in the spec. - return True - - @_require_version_compare - def _compare_greater_than(self, prospective, spec): - # Convert our spec to a Version instance, since we'll want to work with - # it as a version. - spec = Version(spec) - - # Check to see if the prospective version is greater than the spec - # version. If it's not we can short circuit and just return False now - # instead of doing extra unneeded work. - if not prospective > spec: - return False - - # This special case is here so that, unless the specifier itself - # includes is a post-release version, that we do not accept - # post-release versions for the version mentioned in the specifier - # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). - if not spec.is_postrelease and prospective.is_postrelease: - if Version(prospective.base_version) == Version(spec.base_version): - return False - - # Ensure that we do not allow a local version of the version mentioned - # in the specifier, which is techincally greater than, to match. - if prospective.local is not None: - if Version(prospective.base_version) == Version(spec.base_version): - return False - - # If we've gotten to here, it means that prospective version is both - # greater than the spec version *and* it's not a pre-release of the - # same version in the spec. - return True - - def _compare_arbitrary(self, prospective, spec): - return str(prospective).lower() == str(spec).lower() - - @property - def prereleases(self): - # If there is an explicit prereleases set for this, then we'll just - # blindly use that. - if self._prereleases is not None: - return self._prereleases - - # Look at all of our specifiers and determine if they are inclusive - # operators, and if they are if they are including an explicit - # prerelease. - operator, version = self._spec - if operator in ["==", ">=", "<=", "~=", "==="]: - # The == specifier can include a trailing .*, if it does we - # want to remove before parsing. - if operator == "==" and version.endswith(".*"): - version = version[:-2] - - # Parse the version, and if it is a pre-release than this - # specifier allows pre-releases. - if parse(version).is_prerelease: - return True - - return False - - @prereleases.setter - def prereleases(self, value): - self._prereleases = value - - -_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") - - -def _version_split(version): - result = [] - for item in version.split("."): - match = _prefix_regex.search(item) - if match: - result.extend(match.groups()) - else: - result.append(item) - return result - - -def _pad_version(left, right): - left_split, right_split = [], [] - - # Get the release segment of our versions - left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) - right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) - - # Get the rest of our versions - left_split.append(left[len(left_split[0]):]) - right_split.append(right[len(right_split[0]):]) - - # Insert our padding - left_split.insert( - 1, - ["0"] * max(0, len(right_split[0]) - len(left_split[0])), - ) - right_split.insert( - 1, - ["0"] * max(0, len(left_split[0]) - len(right_split[0])), - ) - - return ( - list(itertools.chain(*left_split)), - list(itertools.chain(*right_split)), - ) - - -class SpecifierSet(BaseSpecifier): - - def __init__(self, specifiers="", prereleases=None): - # Split on , to break each indidivual specifier into it's own item, and - # strip each item to remove leading/trailing whitespace. - specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] - - # Parsed each individual specifier, attempting first to make it a - # Specifier and falling back to a LegacySpecifier. - parsed = set() - for specifier in specifiers: - try: - parsed.add(Specifier(specifier)) - except InvalidSpecifier: - parsed.add(LegacySpecifier(specifier)) - - # Turn our parsed specifiers into a frozen set and save them for later. - self._specs = frozenset(parsed) - - # Store our prereleases value so we can use it later to determine if - # we accept prereleases or not. - self._prereleases = prereleases - - def __repr__(self): - pre = ( - ", prereleases={0!r}".format(self.prereleases) - if self._prereleases is not None - else "" - ) - - return "<SpecifierSet({0!r}{1})>".format(str(self), pre) - - def __str__(self): - return ",".join(sorted(str(s) for s in self._specs)) - - def __hash__(self): - return hash(self._specs) - - def __and__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - specifier = SpecifierSet() - specifier._specs = frozenset(self._specs | other._specs) - - if self._prereleases is None and other._prereleases is not None: - specifier._prereleases = other._prereleases - elif self._prereleases is not None and other._prereleases is None: - specifier._prereleases = self._prereleases - elif self._prereleases == other._prereleases: - specifier._prereleases = self._prereleases - else: - raise ValueError( - "Cannot combine SpecifierSets with True and False prerelease " - "overrides." - ) - - return specifier - - def __eq__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif isinstance(other, _IndividualSpecifier): - other = SpecifierSet(str(other)) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - return self._specs == other._specs - - def __ne__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif isinstance(other, _IndividualSpecifier): - other = SpecifierSet(str(other)) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - return self._specs != other._specs - - def __len__(self): - return len(self._specs) - - def __iter__(self): - return iter(self._specs) - - @property - def prereleases(self): - # If we have been given an explicit prerelease modifier, then we'll - # pass that through here. - if self._prereleases is not None: - return self._prereleases - - # If we don't have any specifiers, and we don't have a forced value, - # then we'll just return None since we don't know if this should have - # pre-releases or not. - if not self._specs: - return None - - # Otherwise we'll see if any of the given specifiers accept - # prereleases, if any of them do we'll return True, otherwise False. - return any(s.prereleases for s in self._specs) - - @prereleases.setter - def prereleases(self, value): - self._prereleases = value - - def __contains__(self, item): - return self.contains(item) - - def contains(self, item, prereleases=None): - # Ensure that our item is a Version or LegacyVersion instance. - if not isinstance(item, (LegacyVersion, Version)): - item = parse(item) - - # Determine if we're forcing a prerelease or not, if we're not forcing - # one for this particular filter call, then we'll use whatever the - # SpecifierSet thinks for whether or not we should support prereleases. - if prereleases is None: - prereleases = self.prereleases - - # We can determine if we're going to allow pre-releases by looking to - # see if any of the underlying items supports them. If none of them do - # and this item is a pre-release then we do not allow it and we can - # short circuit that here. - # Note: This means that 1.0.dev1 would not be contained in something - # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 - if not prereleases and item.is_prerelease: - return False - - # We simply dispatch to the underlying specs here to make sure that the - # given version is contained within all of them. - # Note: This use of all() here means that an empty set of specifiers - # will always return True, this is an explicit design decision. - return all( - s.contains(item, prereleases=prereleases) - for s in self._specs - ) - - def filter(self, iterable, prereleases=None): - # Determine if we're forcing a prerelease or not, if we're not forcing - # one for this particular filter call, then we'll use whatever the - # SpecifierSet thinks for whether or not we should support prereleases. - if prereleases is None: - prereleases = self.prereleases - - # If we have any specifiers, then we want to wrap our iterable in the - # filter method for each one, this will act as a logical AND amongst - # each specifier. - if self._specs: - for spec in self._specs: - iterable = spec.filter(iterable, prereleases=bool(prereleases)) - return iterable - # If we do not have any specifiers, then we need to have a rough filter - # which will filter out any pre-releases, unless there are no final - # releases, and which will filter out LegacyVersion in general. - else: - filtered = [] - found_prereleases = [] - - for item in iterable: - # Ensure that we some kind of Version class for this item. - if not isinstance(item, (LegacyVersion, Version)): - parsed_version = parse(item) - else: - parsed_version = item - - # Filter out any item which is parsed as a LegacyVersion - if isinstance(parsed_version, LegacyVersion): - continue - - # Store any item which is a pre-release for later unless we've - # already found a final version or we are accepting prereleases - if parsed_version.is_prerelease and not prereleases: - if not filtered: - found_prereleases.append(item) - else: - filtered.append(item) - - # If we've found no items except for pre-releases, then we'll go - # ahead and use the pre-releases - if not filtered and found_prereleases and prereleases is None: - return found_prereleases - - return filtered diff --git a/lib/pkg_resources/_vendor/packaging/utils.py b/lib/pkg_resources/_vendor/packaging/utils.py deleted file mode 100644 index 942387c..0000000 --- a/lib/pkg_resources/_vendor/packaging/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import re - - -_canonicalize_regex = re.compile(r"[-_.]+") - - -def canonicalize_name(name): - # This is taken from PEP 503. - return _canonicalize_regex.sub("-", name).lower() diff --git a/lib/pkg_resources/_vendor/packaging/version.py b/lib/pkg_resources/_vendor/packaging/version.py deleted file mode 100644 index 83b5ee8..0000000 --- a/lib/pkg_resources/_vendor/packaging/version.py +++ /dev/null @@ -1,393 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import collections -import itertools -import re - -from ._structures import Infinity - - -__all__ = [ - "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN" -] - - -_Version = collections.namedtuple( - "_Version", - ["epoch", "release", "dev", "pre", "post", "local"], -) - - -def parse(version): - """ - Parse the given version string and return either a :class:`Version` object - or a :class:`LegacyVersion` object depending on if the given version is - a valid PEP 440 version or a legacy version. - """ - try: - return Version(version) - except InvalidVersion: - return LegacyVersion(version) - - -class InvalidVersion(ValueError): - """ - An invalid version was found, users should refer to PEP 440. - """ - - -class _BaseVersion(object): - - def __hash__(self): - return hash(self._key) - - def __lt__(self, other): - return self._compare(other, lambda s, o: s < o) - - def __le__(self, other): - return self._compare(other, lambda s, o: s <= o) - - def __eq__(self, other): - return self._compare(other, lambda s, o: s == o) - - def __ge__(self, other): - return self._compare(other, lambda s, o: s >= o) - - def __gt__(self, other): - return self._compare(other, lambda s, o: s > o) - - def __ne__(self, other): - return self._compare(other, lambda s, o: s != o) - - def _compare(self, other, method): - if not isinstance(other, _BaseVersion): - return NotImplemented - - return method(self._key, other._key) - - -class LegacyVersion(_BaseVersion): - - def __init__(self, version): - self._version = str(version) - self._key = _legacy_cmpkey(self._version) - - def __str__(self): - return self._version - - def __repr__(self): - return "<LegacyVersion({0})>".format(repr(str(self))) - - @property - def public(self): - return self._version - - @property - def base_version(self): - return self._version - - @property - def local(self): - return None - - @property - def is_prerelease(self): - return False - - @property - def is_postrelease(self): - return False - - -_legacy_version_component_re = re.compile( - r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, -) - -_legacy_version_replacement_map = { - "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@", -} - - -def _parse_version_parts(s): - for part in _legacy_version_component_re.split(s): - part = _legacy_version_replacement_map.get(part, part) - - if not part or part == ".": - continue - - if part[:1] in "0123456789": - # pad for numeric comparison - yield part.zfill(8) - else: - yield "*" + part - - # ensure that alpha/beta/candidate are before final - yield "*final" - - -def _legacy_cmpkey(version): - # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch - # greater than or equal to 0. This will effectively put the LegacyVersion, - # which uses the defacto standard originally implemented by setuptools, - # as before all PEP 440 versions. - epoch = -1 - - # This scheme is taken from pkg_resources.parse_version setuptools prior to - # it's adoption of the packaging library. - parts = [] - for part in _parse_version_parts(version.lower()): - if part.startswith("*"): - # remove "-" before a prerelease tag - if part < "*final": - while parts and parts[-1] == "*final-": - parts.pop() - - # remove trailing zeros from each series of numeric parts - while parts and parts[-1] == "00000000": - parts.pop() - - parts.append(part) - parts = tuple(parts) - - return epoch, parts - -# Deliberately not anchored to the start and end of the string, to make it -# easier for 3rd party code to reuse -VERSION_PATTERN = r""" - v? - (?: - (?:(?P<epoch>[0-9]+)!)? # epoch - (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment - (?P<pre> # pre-release - [-_\.]? - (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview)) - [-_\.]? - (?P<pre_n>[0-9]+)? - )? - (?P<post> # post release - (?:-(?P<post_n1>[0-9]+)) - | - (?: - [-_\.]? - (?P<post_l>post|rev|r) - [-_\.]? - (?P<post_n2>[0-9]+)? - ) - )? - (?P<dev> # dev release - [-_\.]? - (?P<dev_l>dev) - [-_\.]? - (?P<dev_n>[0-9]+)? - )? - ) - (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version -""" - - -class Version(_BaseVersion): - - _regex = re.compile( - r"^\s*" + VERSION_PATTERN + r"\s*$", - re.VERBOSE | re.IGNORECASE, - ) - - def __init__(self, version): - # Validate the version and parse it into pieces - match = self._regex.search(version) - if not match: - raise InvalidVersion("Invalid version: '{0}'".format(version)) - - # Store the parsed out pieces of the version - self._version = _Version( - epoch=int(match.group("epoch")) if match.group("epoch") else 0, - release=tuple(int(i) for i in match.group("release").split(".")), - pre=_parse_letter_version( - match.group("pre_l"), - match.group("pre_n"), - ), - post=_parse_letter_version( - match.group("post_l"), - match.group("post_n1") or match.group("post_n2"), - ), - dev=_parse_letter_version( - match.group("dev_l"), - match.group("dev_n"), - ), - local=_parse_local_version(match.group("local")), - ) - - # Generate a key which will be used for sorting - self._key = _cmpkey( - self._version.epoch, - self._version.release, - self._version.pre, - self._version.post, - self._version.dev, - self._version.local, - ) - - def __repr__(self): - return "<Version({0})>".format(repr(str(self))) - - def __str__(self): - parts = [] - - # Epoch - if self._version.epoch != 0: - parts.append("{0}!".format(self._version.epoch)) - - # Release segment - parts.append(".".join(str(x) for x in self._version.release)) - - # Pre-release - if self._version.pre is not None: - parts.append("".join(str(x) for x in self._version.pre)) - - # Post-release - if self._version.post is not None: - parts.append(".post{0}".format(self._version.post[1])) - - # Development release - if self._version.dev is not None: - parts.append(".dev{0}".format(self._version.dev[1])) - - # Local version segment - if self._version.local is not None: - parts.append( - "+{0}".format(".".join(str(x) for x in self._version.local)) - ) - - return "".join(parts) - - @property - def public(self): - return str(self).split("+", 1)[0] - - @property - def base_version(self): - parts = [] - - # Epoch - if self._version.epoch != 0: - parts.append("{0}!".format(self._version.epoch)) - - # Release segment - parts.append(".".join(str(x) for x in self._version.release)) - - return "".join(parts) - - @property - def local(self): - version_string = str(self) - if "+" in version_string: - return version_string.split("+", 1)[1] - - @property - def is_prerelease(self): - return bool(self._version.dev or self._version.pre) - - @property - def is_postrelease(self): - return bool(self._version.post) - - -def _parse_letter_version(letter, number): - if letter: - # We consider there to be an implicit 0 in a pre-release if there is - # not a numeral associated with it. - if number is None: - number = 0 - - # We normalize any letters to their lower case form - letter = letter.lower() - - # We consider some words to be alternate spellings of other words and - # in those cases we want to normalize the spellings to our preferred - # spelling. - if letter == "alpha": - letter = "a" - elif letter == "beta": - letter = "b" - elif letter in ["c", "pre", "preview"]: - letter = "rc" - elif letter in ["rev", "r"]: - letter = "post" - - return letter, int(number) - if not letter and number: - # We assume if we are given a number, but we are not given a letter - # then this is using the implicit post release syntax (e.g. 1.0-1) - letter = "post" - - return letter, int(number) - - -_local_version_seperators = re.compile(r"[\._-]") - - -def _parse_local_version(local): - """ - Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). - """ - if local is not None: - return tuple( - part.lower() if not part.isdigit() else int(part) - for part in _local_version_seperators.split(local) - ) - - -def _cmpkey(epoch, release, pre, post, dev, local): - # When we compare a release version, we want to compare it with all of the - # trailing zeros removed. So we'll use a reverse the list, drop all the now - # leading zeros until we come to something non zero, then take the rest - # re-reverse it back into the correct order and make it a tuple and use - # that for our sorting key. - release = tuple( - reversed(list( - itertools.dropwhile( - lambda x: x == 0, - reversed(release), - ) - )) - ) - - # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. - # We'll do this by abusing the pre segment, but we _only_ want to do this - # if there is not a pre or a post segment. If we have one of those then - # the normal sorting rules will handle this case correctly. - if pre is None and post is None and dev is not None: - pre = -Infinity - # Versions without a pre-release (except as noted above) should sort after - # those with one. - elif pre is None: - pre = Infinity - - # Versions without a post segment should sort before those with one. - if post is None: - post = -Infinity - - # Versions without a development segment should sort after those with one. - if dev is None: - dev = Infinity - - if local is None: - # Versions without a local segment should sort before those with one. - local = -Infinity - else: - # Versions with a local segment need that segment parsed to implement - # the sorting rules in PEP440. - # - Alpha numeric segments sort before numeric segments - # - Alpha numeric segments sort lexicographically - # - Numeric segments sort numerically - # - Shorter versions sort before longer versions when the prefixes - # match exactly - local = tuple( - (i, "") if isinstance(i, int) else (-Infinity, i) - for i in local - ) - - return epoch, release, pre, post, dev, local diff --git a/lib/pkg_resources/_vendor/pyparsing.py b/lib/pkg_resources/_vendor/pyparsing.py deleted file mode 100644 index 4aa30ee..0000000 --- a/lib/pkg_resources/_vendor/pyparsing.py +++ /dev/null @@ -1,5742 +0,0 @@ -# module pyparsing.py -# -# Copyright (c) 2003-2018 Paul T. McGuire -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# - -__doc__ = \ -""" -pyparsing module - Classes and methods to define and execute parsing grammars -============================================================================= - -The pyparsing module is an alternative approach to creating and executing simple grammars, -vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you -don't need to learn a new syntax for defining grammars or matching expressions - the parsing module -provides a library of classes that you use to construct the grammar directly in Python. - -Here is a program to parse "Hello, World!" (or any greeting of the form -C{"<salutation>, <addressee>!"}), built up using L{Word}, L{Literal}, and L{And} elements -(L{'+'<ParserElement.__add__>} operator gives L{And} expressions, strings are auto-converted to -L{Literal} expressions):: - - from pyparsing import Word, alphas - - # define grammar of a greeting - greet = Word(alphas) + "," + Word(alphas) + "!" - - hello = "Hello, World!" - print (hello, "->", greet.parseString(hello)) - -The program outputs the following:: - - Hello, World! -> ['Hello', ',', 'World', '!'] - -The Python representation of the grammar is quite readable, owing to the self-explanatory -class names, and the use of '+', '|' and '^' operators. - -The L{ParseResults} object returned from L{ParserElement.parseString<ParserElement.parseString>} can be accessed as a nested list, a dictionary, or an -object with named attributes. - -The pyparsing module handles some of the problems that are typically vexing when writing text parsers: - - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) - - quoted strings - - embedded comments - - -Getting Started - ------------------ -Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing -classes inherit from. Use the docstrings for examples of how to: - - construct literal match expressions from L{Literal} and L{CaselessLiteral} classes - - construct character word-group expressions using the L{Word} class - - see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes - - use L{'+'<And>}, L{'|'<MatchFirst>}, L{'^'<Or>}, and L{'&'<Each>} operators to combine simple expressions into more complex ones - - associate names with your parsed results using L{ParserElement.setResultsName} - - find some helpful expression short-cuts like L{delimitedList} and L{oneOf} - - find more useful common expressions in the L{pyparsing_common} namespace class -""" - -__version__ = "2.2.1" -__versionTime__ = "18 Sep 2018 00:49 UTC" -__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" - -import string -from weakref import ref as wkref -import copy -import sys -import warnings -import re -import sre_constants -import collections -import pprint -import traceback -import types -from datetime import datetime - -try: - from _thread import RLock -except ImportError: - from threading import RLock - -try: - # Python 3 - from collections.abc import Iterable - from collections.abc import MutableMapping -except ImportError: - # Python 2.7 - from collections import Iterable - from collections import MutableMapping - -try: - from collections import OrderedDict as _OrderedDict -except ImportError: - try: - from ordereddict import OrderedDict as _OrderedDict - except ImportError: - _OrderedDict = None - -#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) - -__all__ = [ -'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', -'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', -'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', -'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', -'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', -'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', -'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', -'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', -'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', -'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', -'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno', -'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', -'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', -'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', -'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', -'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', -'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass', -'CloseMatch', 'tokenMap', 'pyparsing_common', -] - -system_version = tuple(sys.version_info)[:3] -PY_3 = system_version[0] == 3 -if PY_3: - _MAX_INT = sys.maxsize - basestring = str - unichr = chr - _ustr = str - - # build list of single arg builtins, that can be used as parse actions - singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max] - -else: - _MAX_INT = sys.maxint - range = xrange - - def _ustr(obj): - """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries - str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It - then < returns the unicode object | encodes it with the default encoding | ... >. - """ - if isinstance(obj,unicode): - return obj - - try: - # If this works, then _ustr(obj) has the same behaviour as str(obj), so - # it won't break any existing code. - return str(obj) - - except UnicodeEncodeError: - # Else encode it - ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace') - xmlcharref = Regex(r'&#\d+;') - xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:]) - return xmlcharref.transformString(ret) - - # build list of single arg builtins, tolerant of Python version, that can be used as parse actions - singleArgBuiltins = [] - import __builtin__ - for fname in "sum len sorted reversed list tuple set any all min max".split(): - try: - singleArgBuiltins.append(getattr(__builtin__,fname)) - except AttributeError: - continue - -_generatorType = type((y for y in range(1))) - -def _xml_escape(data): - """Escape &, <, >, ", ', etc. in a string of data.""" - - # ampersand must be replaced first - from_symbols = '&><"\'' - to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split()) - for from_,to_ in zip(from_symbols, to_symbols): - data = data.replace(from_, to_) - return data - -class _Constants(object): - pass - -alphas = string.ascii_uppercase + string.ascii_lowercase -nums = "0123456789" -hexnums = nums + "ABCDEFabcdef" -alphanums = alphas + nums -_bslash = chr(92) -printables = "".join(c for c in string.printable if c not in string.whitespace) - -class ParseBaseException(Exception): - """base exception class for all parsing runtime exceptions""" - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, pstr, loc=0, msg=None, elem=None ): - self.loc = loc - if msg is None: - self.msg = pstr - self.pstr = "" - else: - self.msg = msg - self.pstr = pstr - self.parserElement = elem - self.args = (pstr, loc, msg) - - @classmethod - def _from_exception(cls, pe): - """ - internal factory method to simplify creating one type of ParseException - from another - avoids having __init__ signature conflicts among subclasses - """ - return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement) - - def __getattr__( self, aname ): - """supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - """ - if( aname == "lineno" ): - return lineno( self.loc, self.pstr ) - elif( aname in ("col", "column") ): - return col( self.loc, self.pstr ) - elif( aname == "line" ): - return line( self.loc, self.pstr ) - else: - raise AttributeError(aname) - - def __str__( self ): - return "%s (at char %d), (line:%d, col:%d)" % \ - ( self.msg, self.loc, self.lineno, self.column ) - def __repr__( self ): - return _ustr(self) - def markInputline( self, markerString = ">!<" ): - """Extracts the exception line from the input string, and marks - the location of the exception with a special symbol. - """ - line_str = self.line - line_column = self.column - 1 - if markerString: - line_str = "".join((line_str[:line_column], - markerString, line_str[line_column:])) - return line_str.strip() - def __dir__(self): - return "lineno col line".split() + dir(type(self)) - -class ParseException(ParseBaseException): - """ - Exception thrown when parse expressions don't match class; - supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - - Example:: - try: - Word(nums).setName("integer").parseString("ABC") - except ParseException as pe: - print(pe) - print("column: {}".format(pe.col)) - - prints:: - Expected integer (at char 0), (line:1, col:1) - column: 1 - """ - pass - -class ParseFatalException(ParseBaseException): - """user-throwable exception thrown when inconsistent parse content - is found; stops all parsing immediately""" - pass - -class ParseSyntaxException(ParseFatalException): - """just like L{ParseFatalException}, but thrown internally when an - L{ErrorStop<And._ErrorStop>} ('-' operator) indicates that parsing is to stop - immediately because an unbacktrackable syntax error has been found""" - pass - -#~ class ReparseException(ParseBaseException): - #~ """Experimental class - parse actions can raise this exception to cause - #~ pyparsing to reparse the input string: - #~ - with a modified input string, and/or - #~ - with a modified start location - #~ Set the values of the ReparseException in the constructor, and raise the - #~ exception in a parse action to cause pyparsing to use the new string/location. - #~ Setting the values as None causes no change to be made. - #~ """ - #~ def __init_( self, newstring, restartLoc ): - #~ self.newParseText = newstring - #~ self.reparseLoc = restartLoc - -class RecursiveGrammarException(Exception): - """exception thrown by L{ParserElement.validate} if the grammar could be improperly recursive""" - def __init__( self, parseElementList ): - self.parseElementTrace = parseElementList - - def __str__( self ): - return "RecursiveGrammarException: %s" % self.parseElementTrace - -class _ParseResultsWithOffset(object): - def __init__(self,p1,p2): - self.tup = (p1,p2) - def __getitem__(self,i): - return self.tup[i] - def __repr__(self): - return repr(self.tup[0]) - def setOffset(self,i): - self.tup = (self.tup[0],i) - -class ParseResults(object): - """ - Structured parse results, to provide multiple means of access to the parsed data: - - as a list (C{len(results)}) - - by list index (C{results[0], results[1]}, etc.) - - by attribute (C{results.<resultsName>} - see L{ParserElement.setResultsName}) - - Example:: - integer = Word(nums) - date_str = (integer.setResultsName("year") + '/' - + integer.setResultsName("month") + '/' - + integer.setResultsName("day")) - # equivalent form: - # date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - # parseString returns a ParseResults object - result = date_str.parseString("1999/12/31") - - def test(s, fn=repr): - print("%s -> %s" % (s, fn(eval(s)))) - test("list(result)") - test("result[0]") - test("result['month']") - test("result.day") - test("'month' in result") - test("'minutes' in result") - test("result.dump()", str) - prints:: - list(result) -> ['1999', '/', '12', '/', '31'] - result[0] -> '1999' - result['month'] -> '12' - result.day -> '31' - 'month' in result -> True - 'minutes' in result -> False - result.dump() -> ['1999', '/', '12', '/', '31'] - - day: 31 - - month: 12 - - year: 1999 - """ - def __new__(cls, toklist=None, name=None, asList=True, modal=True ): - if isinstance(toklist, cls): - return toklist - retobj = object.__new__(cls) - retobj.__doinit = True - return retobj - - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ): - if self.__doinit: - self.__doinit = False - self.__name = None - self.__parent = None - self.__accumNames = {} - self.__asList = asList - self.__modal = modal - if toklist is None: - toklist = [] - if isinstance(toklist, list): - self.__toklist = toklist[:] - elif isinstance(toklist, _generatorType): - self.__toklist = list(toklist) - else: - self.__toklist = [toklist] - self.__tokdict = dict() - - if name is not None and name: - if not modal: - self.__accumNames[name] = 0 - if isinstance(name,int): - name = _ustr(name) # will always return a str, but use _ustr for consistency - self.__name = name - if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])): - if isinstance(toklist,basestring): - toklist = [ toklist ] - if asList: - if isinstance(toklist,ParseResults): - self[name] = _ParseResultsWithOffset(toklist.copy(),0) - else: - self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) - self[name].__name = name - else: - try: - self[name] = toklist[0] - except (KeyError,TypeError,IndexError): - self[name] = toklist - - def __getitem__( self, i ): - if isinstance( i, (int,slice) ): - return self.__toklist[i] - else: - if i not in self.__accumNames: - return self.__tokdict[i][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[i] ]) - - def __setitem__( self, k, v, isinstance=isinstance ): - if isinstance(v,_ParseResultsWithOffset): - self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] - sub = v[0] - elif isinstance(k,(int,slice)): - self.__toklist[k] = v - sub = v - else: - self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] - sub = v - if isinstance(sub,ParseResults): - sub.__parent = wkref(self) - - def __delitem__( self, i ): - if isinstance(i,(int,slice)): - mylen = len( self.__toklist ) - del self.__toklist[i] - - # convert int to slice - if isinstance(i, int): - if i < 0: - i += mylen - i = slice(i, i+1) - # get removed indices - removed = list(range(*i.indices(mylen))) - removed.reverse() - # fixup indices in token dictionary - for name,occurrences in self.__tokdict.items(): - for j in removed: - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) - else: - del self.__tokdict[i] - - def __contains__( self, k ): - return k in self.__tokdict - - def __len__( self ): return len( self.__toklist ) - def __bool__(self): return ( not not self.__toklist ) - __nonzero__ = __bool__ - def __iter__( self ): return iter( self.__toklist ) - def __reversed__( self ): return iter( self.__toklist[::-1] ) - def _iterkeys( self ): - if hasattr(self.__tokdict, "iterkeys"): - return self.__tokdict.iterkeys() - else: - return iter(self.__tokdict) - - def _itervalues( self ): - return (self[k] for k in self._iterkeys()) - - def _iteritems( self ): - return ((k, self[k]) for k in self._iterkeys()) - - if PY_3: - keys = _iterkeys - """Returns an iterator of all named result keys (Python 3.x only).""" - - values = _itervalues - """Returns an iterator of all named result values (Python 3.x only).""" - - items = _iteritems - """Returns an iterator of all named result key-value tuples (Python 3.x only).""" - - else: - iterkeys = _iterkeys - """Returns an iterator of all named result keys (Python 2.x only).""" - - itervalues = _itervalues - """Returns an iterator of all named result values (Python 2.x only).""" - - iteritems = _iteritems - """Returns an iterator of all named result key-value tuples (Python 2.x only).""" - - def keys( self ): - """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x).""" - return list(self.iterkeys()) - - def values( self ): - """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x).""" - return list(self.itervalues()) - - def items( self ): - """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x).""" - return list(self.iteritems()) - - def haskeys( self ): - """Since keys() returns an iterator, this method is helpful in bypassing - code that looks for the existence of any defined results names.""" - return bool(self.__tokdict) - - def pop( self, *args, **kwargs): - """ - Removes and returns item at specified index (default=C{last}). - Supports both C{list} and C{dict} semantics for C{pop()}. If passed no - argument or an integer argument, it will use C{list} semantics - and pop tokens from the list of parsed tokens. If passed a - non-integer argument (most likely a string), it will use C{dict} - semantics and pop the corresponding value from any defined - results names. A second default return value argument is - supported, just as in C{dict.pop()}. - - Example:: - def remove_first(tokens): - tokens.pop(0) - print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] - print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321'] - - label = Word(alphas) - patt = label("LABEL") + OneOrMore(Word(nums)) - print(patt.parseString("AAB 123 321").dump()) - - # Use pop() in a parse action to remove named result (note that corresponding value is not - # removed from list form of results) - def remove_LABEL(tokens): - tokens.pop("LABEL") - return tokens - patt.addParseAction(remove_LABEL) - print(patt.parseString("AAB 123 321").dump()) - prints:: - ['AAB', '123', '321'] - - LABEL: AAB - - ['AAB', '123', '321'] - """ - if not args: - args = [-1] - for k,v in kwargs.items(): - if k == 'default': - args = (args[0], v) - else: - raise TypeError("pop() got an unexpected keyword argument '%s'" % k) - if (isinstance(args[0], int) or - len(args) == 1 or - args[0] in self): - index = args[0] - ret = self[index] - del self[index] - return ret - else: - defaultvalue = args[1] - return defaultvalue - - def get(self, key, defaultValue=None): - """ - Returns named result matching the given key, or if there is no - such name, then returns the given C{defaultValue} or C{None} if no - C{defaultValue} is specified. - - Similar to C{dict.get()}. - - Example:: - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString("1999/12/31") - print(result.get("year")) # -> '1999' - print(result.get("hour", "not specified")) # -> 'not specified' - print(result.get("hour")) # -> None - """ - if key in self: - return self[key] - else: - return defaultValue - - def insert( self, index, insStr ): - """ - Inserts new element at location index in the list of parsed tokens. - - Similar to C{list.insert()}. - - Example:: - print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] - - # use a parse action to insert the parse location in the front of the parsed results - def insert_locn(locn, tokens): - tokens.insert(0, locn) - print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321'] - """ - self.__toklist.insert(index, insStr) - # fixup indices in token dictionary - for name,occurrences in self.__tokdict.items(): - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) - - def append( self, item ): - """ - Add single element to end of ParseResults list of elements. - - Example:: - print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] - - # use a parse action to compute the sum of the parsed integers, and add it to the end - def append_sum(tokens): - tokens.append(sum(map(int, tokens))) - print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444] - """ - self.__toklist.append(item) - - def extend( self, itemseq ): - """ - Add sequence of elements to end of ParseResults list of elements. - - Example:: - patt = OneOrMore(Word(alphas)) - - # use a parse action to append the reverse of the matched strings, to make a palindrome - def make_palindrome(tokens): - tokens.extend(reversed([t[::-1] for t in tokens])) - return ''.join(tokens) - print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' - """ - if isinstance(itemseq, ParseResults): - self += itemseq - else: - self.__toklist.extend(itemseq) - - def clear( self ): - """ - Clear all elements and results names. - """ - del self.__toklist[:] - self.__tokdict.clear() - - def __getattr__( self, name ): - try: - return self[name] - except KeyError: - return "" - - if name in self.__tokdict: - if name not in self.__accumNames: - return self.__tokdict[name][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[name] ]) - else: - return "" - - def __add__( self, other ): - ret = self.copy() - ret += other - return ret - - def __iadd__( self, other ): - if other.__tokdict: - offset = len(self.__toklist) - addoffset = lambda a: offset if a<0 else a+offset - otheritems = other.__tokdict.items() - otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) - for (k,vlist) in otheritems for v in vlist] - for k,v in otherdictitems: - self[k] = v - if isinstance(v[0],ParseResults): - v[0].__parent = wkref(self) - - self.__toklist += other.__toklist - self.__accumNames.update( other.__accumNames ) - return self - - def __radd__(self, other): - if isinstance(other,int) and other == 0: - # useful for merging many ParseResults using sum() builtin - return self.copy() - else: - # this may raise a TypeError - so be it - return other + self - - def __repr__( self ): - return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) - - def __str__( self ): - return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']' - - def _asStringList( self, sep='' ): - out = [] - for item in self.__toklist: - if out and sep: - out.append(sep) - if isinstance( item, ParseResults ): - out += item._asStringList() - else: - out.append( _ustr(item) ) - return out - - def asList( self ): - """ - Returns the parse results as a nested list of matching tokens, all converted to strings. - - Example:: - patt = OneOrMore(Word(alphas)) - result = patt.parseString("sldkj lsdkj sldkj") - # even though the result prints in string-like form, it is actually a pyparsing ParseResults - print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj'] - - # Use asList() to create an actual list - result_list = result.asList() - print(type(result_list), result_list) # -> <class 'list'> ['sldkj', 'lsdkj', 'sldkj'] - """ - return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist] - - def asDict( self ): - """ - Returns the named parse results as a nested dictionary. - - Example:: - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString('12/31/1999') - print(type(result), repr(result)) # -> <class 'pyparsing.ParseResults'> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]}) - - result_dict = result.asDict() - print(type(result_dict), repr(result_dict)) # -> <class 'dict'> {'day': '1999', 'year': '12', 'month': '31'} - - # even though a ParseResults supports dict-like access, sometime you just need to have a dict - import json - print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable - print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"} - """ - if PY_3: - item_fn = self.items - else: - item_fn = self.iteritems - - def toItem(obj): - if isinstance(obj, ParseResults): - if obj.haskeys(): - return obj.asDict() - else: - return [toItem(v) for v in obj] - else: - return obj - - return dict((k,toItem(v)) for k,v in item_fn()) - - def copy( self ): - """ - Returns a new copy of a C{ParseResults} object. - """ - ret = ParseResults( self.__toklist ) - ret.__tokdict = self.__tokdict.copy() - ret.__parent = self.__parent - ret.__accumNames.update( self.__accumNames ) - ret.__name = self.__name - return ret - - def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): - """ - (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names. - """ - nl = "\n" - out = [] - namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items() - for v in vlist) - nextLevelIndent = indent + " " - - # collapse out indents if formatting is not desired - if not formatted: - indent = "" - nextLevelIndent = "" - nl = "" - - selfTag = None - if doctag is not None: - selfTag = doctag - else: - if self.__name: - selfTag = self.__name - - if not selfTag: - if namedItemsOnly: - return "" - else: - selfTag = "ITEM" - - out += [ nl, indent, "<", selfTag, ">" ] - - for i,res in enumerate(self.__toklist): - if isinstance(res,ParseResults): - if i in namedItems: - out += [ res.asXML(namedItems[i], - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - out += [ res.asXML(None, - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - # individual token, see if there is a name for it - resTag = None - if i in namedItems: - resTag = namedItems[i] - if not resTag: - if namedItemsOnly: - continue - else: - resTag = "ITEM" - xmlBodyText = _xml_escape(_ustr(res)) - out += [ nl, nextLevelIndent, "<", resTag, ">", - xmlBodyText, - "</", resTag, ">" ] - - out += [ nl, indent, "</", selfTag, ">" ] - return "".join(out) - - def __lookup(self,sub): - for k,vlist in self.__tokdict.items(): - for v,loc in vlist: - if sub is v: - return k - return None - - def getName(self): - r""" - Returns the results name for this token expression. Useful when several - different expressions might match at a particular location. - - Example:: - integer = Word(nums) - ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") - house_number_expr = Suppress('#') + Word(nums, alphanums) - user_data = (Group(house_number_expr)("house_number") - | Group(ssn_expr)("ssn") - | Group(integer)("age")) - user_info = OneOrMore(user_data) - - result = user_info.parseString("22 111-22-3333 #221B") - for item in result: - print(item.getName(), ':', item[0]) - prints:: - age : 22 - ssn : 111-22-3333 - house_number : 221B - """ - if self.__name: - return self.__name - elif self.__parent: - par = self.__parent() - if par: - return par.__lookup(self) - else: - return None - elif (len(self) == 1 and - len(self.__tokdict) == 1 and - next(iter(self.__tokdict.values()))[0][1] in (0,-1)): - return next(iter(self.__tokdict.keys())) - else: - return None - - def dump(self, indent='', depth=0, full=True): - """ - Diagnostic method for listing out the contents of a C{ParseResults}. - Accepts an optional C{indent} argument so that this string can be embedded - in a nested display of other data. - - Example:: - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString('12/31/1999') - print(result.dump()) - prints:: - ['12', '/', '31', '/', '1999'] - - day: 1999 - - month: 31 - - year: 12 - """ - out = [] - NL = '\n' - out.append( indent+_ustr(self.asList()) ) - if full: - if self.haskeys(): - items = sorted((str(k), v) for k,v in self.items()) - for k,v in items: - if out: - out.append(NL) - out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) - if isinstance(v,ParseResults): - if v: - out.append( v.dump(indent,depth+1) ) - else: - out.append(_ustr(v)) - else: - out.append(repr(v)) - elif any(isinstance(vv,ParseResults) for vv in self): - v = self - for i,vv in enumerate(v): - if isinstance(vv,ParseResults): - out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),vv.dump(indent,depth+1) )) - else: - out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),_ustr(vv))) - - return "".join(out) - - def pprint(self, *args, **kwargs): - """ - Pretty-printer for parsed results as a list, using the C{pprint} module. - Accepts additional positional or keyword args as defined for the - C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint}) - - Example:: - ident = Word(alphas, alphanums) - num = Word(nums) - func = Forward() - term = ident | num | Group('(' + func + ')') - func <<= ident + Group(Optional(delimitedList(term))) - result = func.parseString("fna a,b,(fnb c,d,200),100") - result.pprint(width=40) - prints:: - ['fna', - ['a', - 'b', - ['(', 'fnb', ['c', 'd', '200'], ')'], - '100']] - """ - pprint.pprint(self.asList(), *args, **kwargs) - - # add support for pickle protocol - def __getstate__(self): - return ( self.__toklist, - ( self.__tokdict.copy(), - self.__parent is not None and self.__parent() or None, - self.__accumNames, - self.__name ) ) - - def __setstate__(self,state): - self.__toklist = state[0] - (self.__tokdict, - par, - inAccumNames, - self.__name) = state[1] - self.__accumNames = {} - self.__accumNames.update(inAccumNames) - if par is not None: - self.__parent = wkref(par) - else: - self.__parent = None - - def __getnewargs__(self): - return self.__toklist, self.__name, self.__asList, self.__modal - - def __dir__(self): - return (dir(type(self)) + list(self.keys())) - -MutableMapping.register(ParseResults) - -def col (loc,strg): - """Returns current column within a string, counting newlines as line separators. - The first column is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - s = strg - return 1 if 0<loc<len(s) and s[loc-1] == '\n' else loc - s.rfind("\n", 0, loc) - -def lineno(loc,strg): - """Returns current line number within a string, counting newlines as line separators. - The first line is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - return strg.count("\n",0,loc) + 1 - -def line( loc, strg ): - """Returns the line of text containing loc within a string, counting newlines as line separators. - """ - lastCR = strg.rfind("\n", 0, loc) - nextCR = strg.find("\n", loc) - if nextCR >= 0: - return strg[lastCR+1:nextCR] - else: - return strg[lastCR+1:] - -def _defaultStartDebugAction( instring, loc, expr ): - print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))) - -def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): - print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) - -def _defaultExceptionDebugAction( instring, loc, expr, exc ): - print ("Exception raised:" + _ustr(exc)) - -def nullDebugAction(*args): - """'Do-nothing' debug action, to suppress debugging output during parsing.""" - pass - -# Only works on Python 3.x - nonlocal is toxic to Python 2 installs -#~ 'decorator to trim function calls to match the arity of the target' -#~ def _trim_arity(func, maxargs=3): - #~ if func in singleArgBuiltins: - #~ return lambda s,l,t: func(t) - #~ limit = 0 - #~ foundArity = False - #~ def wrapper(*args): - #~ nonlocal limit,foundArity - #~ while 1: - #~ try: - #~ ret = func(*args[limit:]) - #~ foundArity = True - #~ return ret - #~ except TypeError: - #~ if limit == maxargs or foundArity: - #~ raise - #~ limit += 1 - #~ continue - #~ return wrapper - -# this version is Python 2.x-3.x cross-compatible -'decorator to trim function calls to match the arity of the target' -def _trim_arity(func, maxargs=2): - if func in singleArgBuiltins: - return lambda s,l,t: func(t) - limit = [0] - foundArity = [False] - - # traceback return data structure changed in Py3.5 - normalize back to plain tuples - if system_version[:2] >= (3,5): - def extract_stack(limit=0): - # special handling for Python 3.5.0 - extra deep call stack by 1 - offset = -3 if system_version == (3,5,0) else -2 - frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset] - return [frame_summary[:2]] - def extract_tb(tb, limit=0): - frames = traceback.extract_tb(tb, limit=limit) - frame_summary = frames[-1] - return [frame_summary[:2]] - else: - extract_stack = traceback.extract_stack - extract_tb = traceback.extract_tb - - # synthesize what would be returned by traceback.extract_stack at the call to - # user's parse action 'func', so that we don't incur call penalty at parse time - - LINE_DIFF = 6 - # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND - # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!! - this_line = extract_stack(limit=2)[-1] - pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF) - - def wrapper(*args): - while 1: - try: - ret = func(*args[limit[0]:]) - foundArity[0] = True - return ret - except TypeError: - # re-raise TypeErrors if they did not come from our arity testing - if foundArity[0]: - raise - else: - try: - tb = sys.exc_info()[-1] - if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth: - raise - finally: - del tb - - if limit[0] <= maxargs: - limit[0] += 1 - continue - raise - - # copy func name to wrapper for sensible debug output - func_name = "<parse action>" - try: - func_name = getattr(func, '__name__', - getattr(func, '__class__').__name__) - except Exception: - func_name = str(func) - wrapper.__name__ = func_name - - return wrapper - -class ParserElement(object): - """Abstract base level parser element class.""" - DEFAULT_WHITE_CHARS = " \n\t\r" - verbose_stacktrace = False - - @staticmethod - def setDefaultWhitespaceChars( chars ): - r""" - Overrides the default whitespace chars - - Example:: - # default whitespace chars are space, <TAB> and newline - OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl'] - - # change to just treat newline as significant - ParserElement.setDefaultWhitespaceChars(" \t") - OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def'] - """ - ParserElement.DEFAULT_WHITE_CHARS = chars - - @staticmethod - def inlineLiteralsUsing(cls): - """ - Set class to be used for inclusion of string literals into a parser. - - Example:: - # default literal class used is Literal - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31'] - - - # change to Suppress - ParserElement.inlineLiteralsUsing(Suppress) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - date_str.parseString("1999/12/31") # -> ['1999', '12', '31'] - """ - ParserElement._literalStringClass = cls - - def __init__( self, savelist=False ): - self.parseAction = list() - self.failAction = None - #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall - self.strRepr = None - self.resultsName = None - self.saveAsList = savelist - self.skipWhitespace = True - self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - self.copyDefaultWhiteChars = True - self.mayReturnEmpty = False # used when checking for left-recursion - self.keepTabs = False - self.ignoreExprs = list() - self.debug = False - self.streamlined = False - self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index - self.errmsg = "" - self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) - self.debugActions = ( None, None, None ) #custom debug actions - self.re = None - self.callPreparse = True # used to avoid redundant calls to preParse - self.callDuringTry = False - - def copy( self ): - """ - Make a copy of this C{ParserElement}. Useful for defining different parse actions - for the same parsing pattern, using copies of the original parse element. - - Example:: - integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K") - integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") - - print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M")) - prints:: - [5120, 100, 655360, 268435456] - Equivalent form of C{expr.copy()} is just C{expr()}:: - integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") - """ - cpy = copy.copy( self ) - cpy.parseAction = self.parseAction[:] - cpy.ignoreExprs = self.ignoreExprs[:] - if self.copyDefaultWhiteChars: - cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - return cpy - - def setName( self, name ): - """ - Define name for this expression, makes debugging and exception messages clearer. - - Example:: - Word(nums).parseString("ABC") # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1) - Word(nums).setName("integer").parseString("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1) - """ - self.name = name - self.errmsg = "Expected " + self.name - if hasattr(self,"exception"): - self.exception.msg = self.errmsg - return self - - def setResultsName( self, name, listAllMatches=False ): - """ - Define name for referencing matching tokens as a nested attribute - of the returned parse results. - NOTE: this returns a *copy* of the original C{ParserElement} object; - this is so that the client can define a basic element, such as an - integer, and reference it in multiple places with different names. - - You can also set results names using the abbreviated syntax, - C{expr("name")} in place of C{expr.setResultsName("name")} - - see L{I{__call__}<__call__>}. - - Example:: - date_str = (integer.setResultsName("year") + '/' - + integer.setResultsName("month") + '/' - + integer.setResultsName("day")) - - # equivalent form: - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - """ - newself = self.copy() - if name.endswith("*"): - name = name[:-1] - listAllMatches=True - newself.resultsName = name - newself.modalResults = not listAllMatches - return newself - - def setBreak(self,breakFlag = True): - """Method to invoke the Python pdb debugger when this element is - about to be parsed. Set C{breakFlag} to True to enable, False to - disable. - """ - if breakFlag: - _parseMethod = self._parse - def breaker(instring, loc, doActions=True, callPreParse=True): - import pdb - pdb.set_trace() - return _parseMethod( instring, loc, doActions, callPreParse ) - breaker._originalParseMethod = _parseMethod - self._parse = breaker - else: - if hasattr(self._parse,"_originalParseMethod"): - self._parse = self._parse._originalParseMethod - return self - - def setParseAction( self, *fns, **kwargs ): - """ - Define one or more actions to perform when successfully matching parse element definition. - Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)}, - C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where: - - s = the original string being parsed (see note below) - - loc = the location of the matching substring - - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object - If the functions in fns modify the tokens, they can return them as the return - value from fn, and the modified list of tokens will replace the original. - Otherwise, fn does not need to return any value. - - Optional keyword arguments: - - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{parseString}<parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - - Example:: - integer = Word(nums) - date_str = integer + '/' + integer + '/' + integer - - date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31'] - - # use parse action to convert to ints at parse time - integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - date_str = integer + '/' + integer + '/' + integer - - # note that integer fields are now ints, not strings - date_str.parseString("1999/12/31") # -> [1999, '/', 12, '/', 31] - """ - self.parseAction = list(map(_trim_arity, list(fns))) - self.callDuringTry = kwargs.get("callDuringTry", False) - return self - - def addParseAction( self, *fns, **kwargs ): - """ - Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}<setParseAction>}. - - See examples in L{I{copy}<copy>}. - """ - self.parseAction += list(map(_trim_arity, list(fns))) - self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) - return self - - def addCondition(self, *fns, **kwargs): - """Add a boolean predicate function to expression's list of parse actions. See - L{I{setParseAction}<setParseAction>} for function call signatures. Unlike C{setParseAction}, - functions passed to C{addCondition} need to return boolean success/fail of the condition. - - Optional keyword arguments: - - message = define a custom message to be used in the raised exception - - fatal = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException - - Example:: - integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - year_int = integer.copy() - year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later") - date_str = year_int + '/' + integer + '/' + integer - - result = date_str.parseString("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1) - """ - msg = kwargs.get("message", "failed user-defined condition") - exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException - for fn in fns: - def pa(s,l,t): - if not bool(_trim_arity(fn)(s,l,t)): - raise exc_type(s,l,msg) - self.parseAction.append(pa) - self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) - return self - - def setFailAction( self, fn ): - """Define action to perform if parsing fails at this expression. - Fail acton fn is a callable function that takes the arguments - C{fn(s,loc,expr,err)} where: - - s = string being parsed - - loc = location where expression match was attempted and failed - - expr = the parse expression that failed - - err = the exception thrown - The function returns no value. It may throw C{L{ParseFatalException}} - if it is desired to stop parsing immediately.""" - self.failAction = fn - return self - - def _skipIgnorables( self, instring, loc ): - exprsFound = True - while exprsFound: - exprsFound = False - for e in self.ignoreExprs: - try: - while 1: - loc,dummy = e._parse( instring, loc ) - exprsFound = True - except ParseException: - pass - return loc - - def preParse( self, instring, loc ): - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - - if self.skipWhitespace: - wt = self.whiteChars - instrlen = len(instring) - while loc < instrlen and instring[loc] in wt: - loc += 1 - - return loc - - def parseImpl( self, instring, loc, doActions=True ): - return loc, [] - - def postParse( self, instring, loc, tokenlist ): - return tokenlist - - #~ @profile - def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): - debugging = ( self.debug ) #and doActions ) - - if debugging or self.failAction: - #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - if (self.debugActions[0] ): - self.debugActions[0]( instring, loc, self ) - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - try: - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - except ParseBaseException as err: - #~ print ("Exception raised:", err) - if self.debugActions[2]: - self.debugActions[2]( instring, tokensStart, self, err ) - if self.failAction: - self.failAction( instring, tokensStart, self, err ) - raise - else: - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - if self.mayIndexError or preloc >= len(instring): - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - else: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - - tokens = self.postParse( instring, loc, tokens ) - - retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) - if self.parseAction and (doActions or self.callDuringTry): - if debugging: - try: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - except ParseBaseException as err: - #~ print "Exception raised in user parse action:", err - if (self.debugActions[2] ): - self.debugActions[2]( instring, tokensStart, self, err ) - raise - else: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - if debugging: - #~ print ("Matched",self,"->",retTokens.asList()) - if (self.debugActions[1] ): - self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) - - return loc, retTokens - - def tryParse( self, instring, loc ): - try: - return self._parse( instring, loc, doActions=False )[0] - except ParseFatalException: - raise ParseException( instring, loc, self.errmsg, self) - - def canParseNext(self, instring, loc): - try: - self.tryParse(instring, loc) - except (ParseException, IndexError): - return False - else: - return True - - class _UnboundedCache(object): - def __init__(self): - cache = {} - self.not_in_cache = not_in_cache = object() - - def get(self, key): - return cache.get(key, not_in_cache) - - def set(self, key, value): - cache[key] = value - - def clear(self): - cache.clear() - - def cache_len(self): - return len(cache) - - self.get = types.MethodType(get, self) - self.set = types.MethodType(set, self) - self.clear = types.MethodType(clear, self) - self.__len__ = types.MethodType(cache_len, self) - - if _OrderedDict is not None: - class _FifoCache(object): - def __init__(self, size): - self.not_in_cache = not_in_cache = object() - - cache = _OrderedDict() - - def get(self, key): - return cache.get(key, not_in_cache) - - def set(self, key, value): - cache[key] = value - while len(cache) > size: - try: - cache.popitem(False) - except KeyError: - pass - - def clear(self): - cache.clear() - - def cache_len(self): - return len(cache) - - self.get = types.MethodType(get, self) - self.set = types.MethodType(set, self) - self.clear = types.MethodType(clear, self) - self.__len__ = types.MethodType(cache_len, self) - - else: - class _FifoCache(object): - def __init__(self, size): - self.not_in_cache = not_in_cache = object() - - cache = {} - key_fifo = collections.deque([], size) - - def get(self, key): - return cache.get(key, not_in_cache) - - def set(self, key, value): - cache[key] = value - while len(key_fifo) > size: - cache.pop(key_fifo.popleft(), None) - key_fifo.append(key) - - def clear(self): - cache.clear() - key_fifo.clear() - - def cache_len(self): - return len(cache) - - self.get = types.MethodType(get, self) - self.set = types.MethodType(set, self) - self.clear = types.MethodType(clear, self) - self.__len__ = types.MethodType(cache_len, self) - - # argument cache for optimizing repeated calls when backtracking through recursive expressions - packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail - packrat_cache_lock = RLock() - packrat_cache_stats = [0, 0] - - # this method gets repeatedly called during backtracking with the same arguments - - # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression - def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): - HIT, MISS = 0, 1 - lookup = (self, instring, loc, callPreParse, doActions) - with ParserElement.packrat_cache_lock: - cache = ParserElement.packrat_cache - value = cache.get(lookup) - if value is cache.not_in_cache: - ParserElement.packrat_cache_stats[MISS] += 1 - try: - value = self._parseNoCache(instring, loc, doActions, callPreParse) - except ParseBaseException as pe: - # cache a copy of the exception, without the traceback - cache.set(lookup, pe.__class__(*pe.args)) - raise - else: - cache.set(lookup, (value[0], value[1].copy())) - return value - else: - ParserElement.packrat_cache_stats[HIT] += 1 - if isinstance(value, Exception): - raise value - return (value[0], value[1].copy()) - - _parse = _parseNoCache - - @staticmethod - def resetCache(): - ParserElement.packrat_cache.clear() - ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats) - - _packratEnabled = False - @staticmethod - def enablePackrat(cache_size_limit=128): - """Enables "packrat" parsing, which adds memoizing to the parsing logic. - Repeated parse attempts at the same string location (which happens - often in many complex grammars) can immediately return a cached value, - instead of re-executing parsing/validating code. Memoizing is done of - both valid results and parsing exceptions. - - Parameters: - - cache_size_limit - (default=C{128}) - if an integer value is provided - will limit the size of the packrat cache; if None is passed, then - the cache size will be unbounded; if 0 is passed, the cache will - be effectively disabled. - - This speedup may break existing programs that use parse actions that - have side-effects. For this reason, packrat parsing is disabled when - you first import pyparsing. To activate the packrat feature, your - program must call the class method C{ParserElement.enablePackrat()}. If - your program uses C{psyco} to "compile as you go", you must call - C{enablePackrat} before calling C{psyco.full()}. If you do not do this, - Python will crash. For best results, call C{enablePackrat()} immediately - after importing pyparsing. - - Example:: - import pyparsing - pyparsing.ParserElement.enablePackrat() - """ - if not ParserElement._packratEnabled: - ParserElement._packratEnabled = True - if cache_size_limit is None: - ParserElement.packrat_cache = ParserElement._UnboundedCache() - else: - ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit) - ParserElement._parse = ParserElement._parseCache - - def parseString( self, instring, parseAll=False ): - """ - Execute the parse expression with the given string. - This is the main interface to the client code, once the complete - expression has been built. - - If you want the grammar to require that the entire input string be - successfully parsed, then set C{parseAll} to True (equivalent to ending - the grammar with C{L{StringEnd()}}). - - Note: C{parseString} implicitly calls C{expandtabs()} on the input string, - in order to report proper column numbers in parse actions. - If the input string contains tabs and - the grammar uses parse actions that use the C{loc} argument to index into the - string being parsed, you can ensure you have a consistent view of the input - string by: - - calling C{parseWithTabs} on your grammar before calling C{parseString} - (see L{I{parseWithTabs}<parseWithTabs>}) - - define your parse action using the full C{(s,loc,toks)} signature, and - reference the input string using the parse action's C{s} argument - - explictly expand the tabs in your input string before calling - C{parseString} - - Example:: - Word('a').parseString('aaaaabaaa') # -> ['aaaaa'] - Word('a').parseString('aaaaabaaa', parseAll=True) # -> Exception: Expected end of text - """ - ParserElement.resetCache() - if not self.streamlined: - self.streamline() - #~ self.saveAsList = True - for e in self.ignoreExprs: - e.streamline() - if not self.keepTabs: - instring = instring.expandtabs() - try: - loc, tokens = self._parse( instring, 0 ) - if parseAll: - loc = self.preParse( instring, loc ) - se = Empty() + StringEnd() - se._parse( instring, loc ) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - else: - return tokens - - def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ): - """ - Scan the input string for expression matches. Each match will return the - matching tokens, start location, and end location. May be called with optional - C{maxMatches} argument, to clip scanning after 'n' matches are found. If - C{overlap} is specified, then overlapping matches will be reported. - - Note that the start and end locations are reported relative to the string - being parsed. See L{I{parseString}<parseString>} for more information on parsing - strings with embedded tabs. - - Example:: - source = "sldjf123lsdjjkf345sldkjf879lkjsfd987" - print(source) - for tokens,start,end in Word(alphas).scanString(source): - print(' '*start + '^'*(end-start)) - print(' '*start + tokens[0]) - - prints:: - - sldjf123lsdjjkf345sldkjf879lkjsfd987 - ^^^^^ - sldjf - ^^^^^^^ - lsdjjkf - ^^^^^^ - sldkjf - ^^^^^^ - lkjsfd - """ - if not self.streamlined: - self.streamline() - for e in self.ignoreExprs: - e.streamline() - - if not self.keepTabs: - instring = _ustr(instring).expandtabs() - instrlen = len(instring) - loc = 0 - preparseFn = self.preParse - parseFn = self._parse - ParserElement.resetCache() - matches = 0 - try: - while loc <= instrlen and matches < maxMatches: - try: - preloc = preparseFn( instring, loc ) - nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) - except ParseException: - loc = preloc+1 - else: - if nextLoc > loc: - matches += 1 - yield tokens, preloc, nextLoc - if overlap: - nextloc = preparseFn( instring, loc ) - if nextloc > loc: - loc = nextLoc - else: - loc += 1 - else: - loc = nextLoc - else: - loc = preloc+1 - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def transformString( self, instring ): - """ - Extension to C{L{scanString}}, to modify matching text with modified tokens that may - be returned from a parse action. To use C{transformString}, define a grammar and - attach a parse action to it that modifies the returned token list. - Invoking C{transformString()} on a target string will then scan for matches, - and replace the matched text patterns according to the logic in the parse - action. C{transformString()} returns the resulting transformed string. - - Example:: - wd = Word(alphas) - wd.setParseAction(lambda toks: toks[0].title()) - - print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york.")) - Prints:: - Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York. - """ - out = [] - lastE = 0 - # force preservation of <TAB>s, to minimize unwanted transformation of string, and to - # keep string locs straight between transformString and scanString - self.keepTabs = True - try: - for t,s,e in self.scanString( instring ): - out.append( instring[lastE:s] ) - if t: - if isinstance(t,ParseResults): - out += t.asList() - elif isinstance(t,list): - out += t - else: - out.append(t) - lastE = e - out.append(instring[lastE:]) - out = [o for o in out if o] - return "".join(map(_ustr,_flatten(out))) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def searchString( self, instring, maxMatches=_MAX_INT ): - """ - Another extension to C{L{scanString}}, simplifying the access to the tokens found - to match the given parse expression. May be called with optional - C{maxMatches} argument, to clip searching after 'n' matches are found. - - Example:: - # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters - cap_word = Word(alphas.upper(), alphas.lower()) - - print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")) - - # the sum() builtin can be used to merge results into a single ParseResults object - print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))) - prints:: - [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']] - ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] - """ - try: - return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False): - """ - Generator method to split a string using the given expression as a separator. - May be called with optional C{maxsplit} argument, to limit the number of splits; - and the optional C{includeSeparators} argument (default=C{False}), if the separating - matching text should be included in the split results. - - Example:: - punc = oneOf(list(".,;:/-!?")) - print(list(punc.split("This, this?, this sentence, is badly punctuated!"))) - prints:: - ['This', ' this', '', ' this sentence', ' is badly punctuated', ''] - """ - splits = 0 - last = 0 - for t,s,e in self.scanString(instring, maxMatches=maxsplit): - yield instring[last:s] - if includeSeparators: - yield t[0] - last = e - yield instring[last:] - - def __add__(self, other ): - """ - Implementation of + operator - returns C{L{And}}. Adding strings to a ParserElement - converts them to L{Literal}s by default. - - Example:: - greet = Word(alphas) + "," + Word(alphas) + "!" - hello = "Hello, World!" - print (hello, "->", greet.parseString(hello)) - Prints:: - Hello, World! -> ['Hello', ',', 'World', '!'] - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, other ] ) - - def __radd__(self, other ): - """ - Implementation of + operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other + self - - def __sub__(self, other): - """ - Implementation of - operator, returns C{L{And}} with error stop - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return self + And._ErrorStop() + other - - def __rsub__(self, other ): - """ - Implementation of - operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other - self - - def __mul__(self,other): - """ - Implementation of * operator, allows use of C{expr * 3} in place of - C{expr + expr + expr}. Expressions may also me multiplied by a 2-integer - tuple, similar to C{{min,max}} multipliers in regular expressions. Tuples - may also include C{None} as in: - - C{expr*(n,None)} or C{expr*(n,)} is equivalent - to C{expr*n + L{ZeroOrMore}(expr)} - (read as "at least n instances of C{expr}") - - C{expr*(None,n)} is equivalent to C{expr*(0,n)} - (read as "0 to n instances of C{expr}") - - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)} - - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)} - - Note that C{expr*(None,n)} does not raise an exception if - more than n exprs exist in the input stream; that is, - C{expr*(None,n)} does not enforce a maximum number of expr - occurrences. If this behavior is desired, then write - C{expr*(None,n) + ~expr} - """ - if isinstance(other,int): - minElements, optElements = other,0 - elif isinstance(other,tuple): - other = (other + (None, None))[:2] - if other[0] is None: - other = (0, other[1]) - if isinstance(other[0],int) and other[1] is None: - if other[0] == 0: - return ZeroOrMore(self) - if other[0] == 1: - return OneOrMore(self) - else: - return self*other[0] + ZeroOrMore(self) - elif isinstance(other[0],int) and isinstance(other[1],int): - minElements, optElements = other - optElements -= minElements - else: - raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) - else: - raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) - - if minElements < 0: - raise ValueError("cannot multiply ParserElement by negative value") - if optElements < 0: - raise ValueError("second tuple value must be greater or equal to first tuple value") - if minElements == optElements == 0: - raise ValueError("cannot multiply ParserElement by 0 or (0,0)") - - if (optElements): - def makeOptionalList(n): - if n>1: - return Optional(self + makeOptionalList(n-1)) - else: - return Optional(self) - if minElements: - if minElements == 1: - ret = self + makeOptionalList(optElements) - else: - ret = And([self]*minElements) + makeOptionalList(optElements) - else: - ret = makeOptionalList(optElements) - else: - if minElements == 1: - ret = self - else: - ret = And([self]*minElements) - return ret - - def __rmul__(self, other): - return self.__mul__(other) - - def __or__(self, other ): - """ - Implementation of | operator - returns C{L{MatchFirst}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return MatchFirst( [ self, other ] ) - - def __ror__(self, other ): - """ - Implementation of | operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other | self - - def __xor__(self, other ): - """ - Implementation of ^ operator - returns C{L{Or}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Or( [ self, other ] ) - - def __rxor__(self, other ): - """ - Implementation of ^ operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other ^ self - - def __and__(self, other ): - """ - Implementation of & operator - returns C{L{Each}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Each( [ self, other ] ) - - def __rand__(self, other ): - """ - Implementation of & operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other & self - - def __invert__( self ): - """ - Implementation of ~ operator - returns C{L{NotAny}} - """ - return NotAny( self ) - - def __call__(self, name=None): - """ - Shortcut for C{L{setResultsName}}, with C{listAllMatches=False}. - - If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be - passed as C{True}. - - If C{name} is omitted, same as calling C{L{copy}}. - - Example:: - # these are equivalent - userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") - userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") - """ - if name is not None: - return self.setResultsName(name) - else: - return self.copy() - - def suppress( self ): - """ - Suppresses the output of this C{ParserElement}; useful to keep punctuation from - cluttering up returned output. - """ - return Suppress( self ) - - def leaveWhitespace( self ): - """ - Disables the skipping of whitespace before matching the characters in the - C{ParserElement}'s defined pattern. This is normally only used internally by - the pyparsing module, but may be needed in some whitespace-sensitive grammars. - """ - self.skipWhitespace = False - return self - - def setWhitespaceChars( self, chars ): - """ - Overrides the default whitespace chars - """ - self.skipWhitespace = True - self.whiteChars = chars - self.copyDefaultWhiteChars = False - return self - - def parseWithTabs( self ): - """ - Overrides default behavior to expand C{<TAB>}s to spaces before parsing the input string. - Must be called before C{parseString} when the input grammar contains elements that - match C{<TAB>} characters. - """ - self.keepTabs = True - return self - - def ignore( self, other ): - """ - Define expression to be ignored (e.g., comments) while doing pattern - matching; may be called repeatedly, to define multiple comment or other - ignorable patterns. - - Example:: - patt = OneOrMore(Word(alphas)) - patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj'] - - patt.ignore(cStyleComment) - patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd'] - """ - if isinstance(other, basestring): - other = Suppress(other) - - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - self.ignoreExprs.append(other) - else: - self.ignoreExprs.append( Suppress( other.copy() ) ) - return self - - def setDebugActions( self, startAction, successAction, exceptionAction ): - """ - Enable display of debugging messages while doing pattern matching. - """ - self.debugActions = (startAction or _defaultStartDebugAction, - successAction or _defaultSuccessDebugAction, - exceptionAction or _defaultExceptionDebugAction) - self.debug = True - return self - - def setDebug( self, flag=True ): - """ - Enable display of debugging messages while doing pattern matching. - Set C{flag} to True to enable, False to disable. - - Example:: - wd = Word(alphas).setName("alphaword") - integer = Word(nums).setName("numword") - term = wd | integer - - # turn on debugging for wd - wd.setDebug() - - OneOrMore(term).parseString("abc 123 xyz 890") - - prints:: - Match alphaword at loc 0(1,1) - Matched alphaword -> ['abc'] - Match alphaword at loc 3(1,4) - Exception raised:Expected alphaword (at char 4), (line:1, col:5) - Match alphaword at loc 7(1,8) - Matched alphaword -> ['xyz'] - Match alphaword at loc 11(1,12) - Exception raised:Expected alphaword (at char 12), (line:1, col:13) - Match alphaword at loc 15(1,16) - Exception raised:Expected alphaword (at char 15), (line:1, col:16) - - The output shown is that produced by the default debug actions - custom debug actions can be - specified using L{setDebugActions}. Prior to attempting - to match the C{wd} expression, the debugging message C{"Match <exprname> at loc <n>(<line>,<col>)"} - is shown. Then if the parse succeeds, a C{"Matched"} message is shown, or an C{"Exception raised"} - message is shown. Also note the use of L{setName} to assign a human-readable name to the expression, - which makes debugging and exception messages easier to understand - for instance, the default - name created for the C{Word} expression without calling C{setName} is C{"W:(ABCD...)"}. - """ - if flag: - self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) - else: - self.debug = False - return self - - def __str__( self ): - return self.name - - def __repr__( self ): - return _ustr(self) - - def streamline( self ): - self.streamlined = True - self.strRepr = None - return self - - def checkRecursion( self, parseElementList ): - pass - - def validate( self, validateTrace=[] ): - """ - Check defined expressions for valid structure, check for infinite recursive definitions. - """ - self.checkRecursion( [] ) - - def parseFile( self, file_or_filename, parseAll=False ): - """ - Execute the parse expression on the given file or filename. - If a filename is specified (instead of a file object), - the entire file is opened, read, and closed before parsing. - """ - try: - file_contents = file_or_filename.read() - except AttributeError: - with open(file_or_filename, "r") as f: - file_contents = f.read() - try: - return self.parseString(file_contents, parseAll) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def __eq__(self,other): - if isinstance(other, ParserElement): - return self is other or vars(self) == vars(other) - elif isinstance(other, basestring): - return self.matches(other) - else: - return super(ParserElement,self)==other - - def __ne__(self,other): - return not (self == other) - - def __hash__(self): - return hash(id(self)) - - def __req__(self,other): - return self == other - - def __rne__(self,other): - return not (self == other) - - def matches(self, testString, parseAll=True): - """ - Method for quick testing of a parser against a test string. Good for simple - inline microtests of sub expressions while building up larger parser. - - Parameters: - - testString - to test against this expression for a match - - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests - - Example:: - expr = Word(nums) - assert expr.matches("100") - """ - try: - self.parseString(_ustr(testString), parseAll=parseAll) - return True - except ParseBaseException: - return False - - def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False): - """ - Execute the parse expression on a series of test strings, showing each - test, the parsed results or where the parse failed. Quick and easy way to - run a parse expression against a list of sample strings. - - Parameters: - - tests - a list of separate test strings, or a multiline string of test strings - - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests - - comment - (default=C{'#'}) - expression for indicating embedded comments in the test - string; pass None to disable comment filtering - - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline; - if False, only dump nested list - - printResults - (default=C{True}) prints test output to stdout - - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing - - Returns: a (success, results) tuple, where success indicates that all tests succeeded - (or failed if C{failureTests} is True), and the results contain a list of lines of each - test's output - - Example:: - number_expr = pyparsing_common.number.copy() - - result = number_expr.runTests(''' - # unsigned integer - 100 - # negative integer - -100 - # float with scientific notation - 6.02e23 - # integer with scientific notation - 1e-12 - ''') - print("Success" if result[0] else "Failed!") - - result = number_expr.runTests(''' - # stray character - 100Z - # missing leading digit before '.' - -.100 - # too many '.' - 3.14.159 - ''', failureTests=True) - print("Success" if result[0] else "Failed!") - prints:: - # unsigned integer - 100 - [100] - - # negative integer - -100 - [-100] - - # float with scientific notation - 6.02e23 - [6.02e+23] - - # integer with scientific notation - 1e-12 - [1e-12] - - Success - - # stray character - 100Z - ^ - FAIL: Expected end of text (at char 3), (line:1, col:4) - - # missing leading digit before '.' - -.100 - ^ - FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1) - - # too many '.' - 3.14.159 - ^ - FAIL: Expected end of text (at char 4), (line:1, col:5) - - Success - - Each test string must be on a single line. If you want to test a string that spans multiple - lines, create a test like this:: - - expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines") - - (Note that this is a raw string literal, you must include the leading 'r'.) - """ - if isinstance(tests, basestring): - tests = list(map(str.strip, tests.rstrip().splitlines())) - if isinstance(comment, basestring): - comment = Literal(comment) - allResults = [] - comments = [] - success = True - for t in tests: - if comment is not None and comment.matches(t, False) or comments and not t: - comments.append(t) - continue - if not t: - continue - out = ['\n'.join(comments), t] - comments = [] - try: - t = t.replace(r'\n','\n') - result = self.parseString(t, parseAll=parseAll) - out.append(result.dump(full=fullDump)) - success = success and not failureTests - except ParseBaseException as pe: - fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else "" - if '\n' in t: - out.append(line(pe.loc, t)) - out.append(' '*(col(pe.loc,t)-1) + '^' + fatal) - else: - out.append(' '*pe.loc + '^' + fatal) - out.append("FAIL: " + str(pe)) - success = success and failureTests - result = pe - except Exception as exc: - out.append("FAIL-EXCEPTION: " + str(exc)) - success = success and failureTests - result = exc - - if printResults: - if fullDump: - out.append('') - print('\n'.join(out)) - - allResults.append((t, result)) - - return success, allResults - - -class Token(ParserElement): - """ - Abstract C{ParserElement} subclass, for defining atomic matching patterns. - """ - def __init__( self ): - super(Token,self).__init__( savelist=False ) - - -class Empty(Token): - """ - An empty token, will always match. - """ - def __init__( self ): - super(Empty,self).__init__() - self.name = "Empty" - self.mayReturnEmpty = True - self.mayIndexError = False - - -class NoMatch(Token): - """ - A token that will never match. - """ - def __init__( self ): - super(NoMatch,self).__init__() - self.name = "NoMatch" - self.mayReturnEmpty = True - self.mayIndexError = False - self.errmsg = "Unmatchable token" - - def parseImpl( self, instring, loc, doActions=True ): - raise ParseException(instring, loc, self.errmsg, self) - - -class Literal(Token): - """ - Token to exactly match a specified string. - - Example:: - Literal('blah').parseString('blah') # -> ['blah'] - Literal('blah').parseString('blahfooblah') # -> ['blah'] - Literal('blah').parseString('bla') # -> Exception: Expected "blah" - - For case-insensitive matching, use L{CaselessLiteral}. - - For keyword matching (force word break before and after the matched string), - use L{Keyword} or L{CaselessKeyword}. - """ - def __init__( self, matchString ): - super(Literal,self).__init__() - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Literal; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.__class__ = Empty - self.name = '"%s"' % _ustr(self.match) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - - # Performance tuning: this routine gets called a *lot* - # if this is a single character match string and the first character matches, - # short-circuit as quickly as possible, and avoid calling startswith - #~ @profile - def parseImpl( self, instring, loc, doActions=True ): - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) ): - return loc+self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) -_L = Literal -ParserElement._literalStringClass = Literal - -class Keyword(Token): - """ - Token to exactly match a specified string as a keyword, that is, it must be - immediately followed by a non-keyword character. Compare with C{L{Literal}}: - - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}. - - C{Keyword("if")} will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'} - Accepts two optional constructor arguments in addition to the keyword string: - - C{identChars} is a string of characters that would be valid identifier characters, - defaulting to all alphanumerics + "_" and "$" - - C{caseless} allows case-insensitive matching, default is C{False}. - - Example:: - Keyword("start").parseString("start") # -> ['start'] - Keyword("start").parseString("starting") # -> Exception - - For case-insensitive matching, use L{CaselessKeyword}. - """ - DEFAULT_KEYWORD_CHARS = alphanums+"_$" - - def __init__( self, matchString, identChars=None, caseless=False ): - super(Keyword,self).__init__() - if identChars is None: - identChars = Keyword.DEFAULT_KEYWORD_CHARS - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Keyword; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.name = '"%s"' % self.match - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - self.caseless = caseless - if caseless: - self.caselessmatch = matchString.upper() - identChars = identChars.upper() - self.identChars = set(identChars) - - def parseImpl( self, instring, loc, doActions=True ): - if self.caseless: - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and - (loc == 0 or instring[loc-1].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - else: - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and - (loc == 0 or instring[loc-1] not in self.identChars) ): - return loc+self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) - - def copy(self): - c = super(Keyword,self).copy() - c.identChars = Keyword.DEFAULT_KEYWORD_CHARS - return c - - @staticmethod - def setDefaultKeywordChars( chars ): - """Overrides the default Keyword chars - """ - Keyword.DEFAULT_KEYWORD_CHARS = chars - -class CaselessLiteral(Literal): - """ - Token to match a specified string, ignoring case of letters. - Note: the matched results will always be in the case of the given - match string, NOT the case of the input text. - - Example:: - OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD'] - - (Contrast with example for L{CaselessKeyword}.) - """ - def __init__( self, matchString ): - super(CaselessLiteral,self).__init__( matchString.upper() ) - # Preserve the defining literal. - self.returnString = matchString - self.name = "'%s'" % self.returnString - self.errmsg = "Expected " + self.name - - def parseImpl( self, instring, loc, doActions=True ): - if instring[ loc:loc+self.matchLen ].upper() == self.match: - return loc+self.matchLen, self.returnString - raise ParseException(instring, loc, self.errmsg, self) - -class CaselessKeyword(Keyword): - """ - Caseless version of L{Keyword}. - - Example:: - OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD'] - - (Contrast with example for L{CaselessLiteral}.) - """ - def __init__( self, matchString, identChars=None ): - super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) - - def parseImpl( self, instring, loc, doActions=True ): - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) - -class CloseMatch(Token): - """ - A variation on L{Literal} which matches "close" matches, that is, - strings with at most 'n' mismatching characters. C{CloseMatch} takes parameters: - - C{match_string} - string to be matched - - C{maxMismatches} - (C{default=1}) maximum number of mismatches allowed to count as a match - - The results from a successful parse will contain the matched text from the input string and the following named results: - - C{mismatches} - a list of the positions within the match_string where mismatches were found - - C{original} - the original match_string used to compare against the input string - - If C{mismatches} is an empty list, then the match was an exact match. - - Example:: - patt = CloseMatch("ATCATCGAATGGA") - patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']}) - patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1) - - # exact match - patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']}) - - # close match allowing up to 2 mismatches - patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2) - patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']}) - """ - def __init__(self, match_string, maxMismatches=1): - super(CloseMatch,self).__init__() - self.name = match_string - self.match_string = match_string - self.maxMismatches = maxMismatches - self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches) - self.mayIndexError = False - self.mayReturnEmpty = False - - def parseImpl( self, instring, loc, doActions=True ): - start = loc - instrlen = len(instring) - maxloc = start + len(self.match_string) - - if maxloc <= instrlen: - match_string = self.match_string - match_stringloc = 0 - mismatches = [] - maxMismatches = self.maxMismatches - - for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)): - src,mat = s_m - if src != mat: - mismatches.append(match_stringloc) - if len(mismatches) > maxMismatches: - break - else: - loc = match_stringloc + 1 - results = ParseResults([instring[start:loc]]) - results['original'] = self.match_string - results['mismatches'] = mismatches - return loc, results - - raise ParseException(instring, loc, self.errmsg, self) - - -class Word(Token): - """ - Token for matching words composed of allowed character sets. - Defined with string containing all allowed initial characters, - an optional string containing allowed body characters (if omitted, - defaults to the initial character set), and an optional minimum, - maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. An optional - C{excludeChars} parameter can list characters that might be found in - the input C{bodyChars} string; useful to define a word of all printables - except for one or two characters, for instance. - - L{srange} is useful for defining custom character set strings for defining - C{Word} expressions, using range notation from regular expression character sets. - - A common mistake is to use C{Word} to match a specific literal string, as in - C{Word("Address")}. Remember that C{Word} uses the string argument to define - I{sets} of matchable characters. This expression would match "Add", "AAA", - "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'. - To match an exact literal string, use L{Literal} or L{Keyword}. - - pyparsing includes helper strings for building Words: - - L{alphas} - - L{nums} - - L{alphanums} - - L{hexnums} - - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.) - - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.) - - L{printables} (any non-whitespace character) - - Example:: - # a word composed of digits - integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9")) - - # a word with a leading capital, and zero or more lowercase - capital_word = Word(alphas.upper(), alphas.lower()) - - # hostnames are alphanumeric, with leading alpha, and '-' - hostname = Word(alphas, alphanums+'-') - - # roman numeral (not a strict parser, accepts invalid mix of characters) - roman = Word("IVXLCDM") - - # any string of non-whitespace characters, except for ',' - csv_value = Word(printables, excludeChars=",") - """ - def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ): - super(Word,self).__init__() - if excludeChars: - initChars = ''.join(c for c in initChars if c not in excludeChars) - if bodyChars: - bodyChars = ''.join(c for c in bodyChars if c not in excludeChars) - self.initCharsOrig = initChars - self.initChars = set(initChars) - if bodyChars : - self.bodyCharsOrig = bodyChars - self.bodyChars = set(bodyChars) - else: - self.bodyCharsOrig = initChars - self.bodyChars = set(initChars) - - self.maxSpecified = max > 0 - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.asKeyword = asKeyword - - if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): - if self.bodyCharsOrig == self.initCharsOrig: - self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) - elif len(self.initCharsOrig) == 1: - self.reString = "%s[%s]*" % \ - (re.escape(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - else: - self.reString = "[%s][%s]*" % \ - (_escapeRegexRangeChars(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - if self.asKeyword: - self.reString = r"\b"+self.reString+r"\b" - try: - self.re = re.compile( self.reString ) - except Exception: - self.re = None - - def parseImpl( self, instring, loc, doActions=True ): - if self.re: - result = self.re.match(instring,loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - return loc, result.group() - - if not(instring[ loc ] in self.initChars): - raise ParseException(instring, loc, self.errmsg, self) - - start = loc - loc += 1 - instrlen = len(instring) - bodychars = self.bodyChars - maxloc = start + self.maxLen - maxloc = min( maxloc, instrlen ) - while loc < maxloc and instring[loc] in bodychars: - loc += 1 - - throwException = False - if loc - start < self.minLen: - throwException = True - if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: - throwException = True - if self.asKeyword: - if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): - throwException = True - - if throwException: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(Word,self).__str__() - except Exception: - pass - - - if self.strRepr is None: - - def charsAsStr(s): - if len(s)>4: - return s[:4]+"..." - else: - return s - - if ( self.initCharsOrig != self.bodyCharsOrig ): - self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) - else: - self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) - - return self.strRepr - - -class Regex(Token): - r""" - Token for matching strings that match a given regular expression. - Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. - If the given regex contains named groups (defined using C{(?P<name>...)}), these will be preserved as - named parse results. - - Example:: - realnum = Regex(r"[+-]?\d+\.\d*") - date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\d\d?)') - # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression - roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})") - """ - compiledREtype = type(re.compile("[A-Z]")) - def __init__( self, pattern, flags=0): - """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags.""" - super(Regex,self).__init__() - - if isinstance(pattern, basestring): - if not pattern: - warnings.warn("null string passed to Regex; use Empty() instead", - SyntaxWarning, stacklevel=2) - - self.pattern = pattern - self.flags = flags - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % pattern, - SyntaxWarning, stacklevel=2) - raise - - elif isinstance(pattern, Regex.compiledREtype): - self.re = pattern - self.pattern = \ - self.reString = str(pattern) - self.flags = flags - - else: - raise ValueError("Regex may only be constructed with a string or a compiled RE object") - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = self.re.match(instring,loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - d = result.groupdict() - ret = ParseResults(result.group()) - if d: - for k in d: - ret[k] = d[k] - return loc,ret - - def __str__( self ): - try: - return super(Regex,self).__str__() - except Exception: - pass - - if self.strRepr is None: - self.strRepr = "Re:(%s)" % repr(self.pattern) - - return self.strRepr - - -class QuotedString(Token): - r""" - Token for matching strings that are delimited by quoting characters. - - Defined with the following parameters: - - quoteChar - string of one or more characters defining the quote delimiting string - - escChar - character to escape quotes, typically backslash (default=C{None}) - - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{None}) - - multiline - boolean indicating whether quotes can span multiple lines (default=C{False}) - - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True}) - - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar) - - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True}) - - Example:: - qs = QuotedString('"') - print(qs.searchString('lsjdf "This is the quote" sldjf')) - complex_qs = QuotedString('{{', endQuoteChar='}}') - print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf')) - sql_qs = QuotedString('"', escQuote='""') - print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf')) - prints:: - [['This is the quote']] - [['This is the "quote"']] - [['This is the quote with "embedded" quotes']] - """ - def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True): - super(QuotedString,self).__init__() - - # remove white space from quote chars - wont work anyway - quoteChar = quoteChar.strip() - if not quoteChar: - warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - if endQuoteChar is None: - endQuoteChar = quoteChar - else: - endQuoteChar = endQuoteChar.strip() - if not endQuoteChar: - warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - self.quoteChar = quoteChar - self.quoteCharLen = len(quoteChar) - self.firstQuoteChar = quoteChar[0] - self.endQuoteChar = endQuoteChar - self.endQuoteCharLen = len(endQuoteChar) - self.escChar = escChar - self.escQuote = escQuote - self.unquoteResults = unquoteResults - self.convertWhitespaceEscapes = convertWhitespaceEscapes - - if multiline: - self.flags = re.MULTILINE | re.DOTALL - self.pattern = r'%s(?:[^%s%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - else: - self.flags = 0 - self.pattern = r'%s(?:[^%s\n\r%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - if len(self.endQuoteChar) > 1: - self.pattern += ( - '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]), - _escapeRegexRangeChars(self.endQuoteChar[i])) - for i in range(len(self.endQuoteChar)-1,0,-1)) + ')' - ) - if escQuote: - self.pattern += (r'|(?:%s)' % re.escape(escQuote)) - if escChar: - self.pattern += (r'|(?:%s.)' % re.escape(escChar)) - self.escCharReplacePattern = re.escape(self.escChar)+"(.)" - self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, - SyntaxWarning, stacklevel=2) - raise - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - ret = result.group() - - if self.unquoteResults: - - # strip off quotes - ret = ret[self.quoteCharLen:-self.endQuoteCharLen] - - if isinstance(ret,basestring): - # replace escaped whitespace - if '\\' in ret and self.convertWhitespaceEscapes: - ws_map = { - r'\t' : '\t', - r'\n' : '\n', - r'\f' : '\f', - r'\r' : '\r', - } - for wslit,wschar in ws_map.items(): - ret = ret.replace(wslit, wschar) - - # replace escaped characters - if self.escChar: - ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret) - - # replace escaped quotes - if self.escQuote: - ret = ret.replace(self.escQuote, self.endQuoteChar) - - return loc, ret - - def __str__( self ): - try: - return super(QuotedString,self).__str__() - except Exception: - pass - - if self.strRepr is None: - self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) - - return self.strRepr - - -class CharsNotIn(Token): - """ - Token for matching words composed of characters I{not} in a given set (will - include whitespace in matched characters if not listed in the provided exclusion set - see example). - Defined with string containing all disallowed characters, and an optional - minimum, maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. - - Example:: - # define a comma-separated-value as anything that is not a ',' - csv_value = CharsNotIn(',') - print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213")) - prints:: - ['dkls', 'lsdkjf', 's12 34', '@!#', '213'] - """ - def __init__( self, notChars, min=1, max=0, exact=0 ): - super(CharsNotIn,self).__init__() - self.skipWhitespace = False - self.notChars = notChars - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = ( self.minLen == 0 ) - self.mayIndexError = False - - def parseImpl( self, instring, loc, doActions=True ): - if instring[loc] in self.notChars: - raise ParseException(instring, loc, self.errmsg, self) - - start = loc - loc += 1 - notchars = self.notChars - maxlen = min( start+self.maxLen, len(instring) ) - while loc < maxlen and \ - (instring[loc] not in notchars): - loc += 1 - - if loc - start < self.minLen: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(CharsNotIn, self).__str__() - except Exception: - pass - - if self.strRepr is None: - if len(self.notChars) > 4: - self.strRepr = "!W:(%s...)" % self.notChars[:4] - else: - self.strRepr = "!W:(%s)" % self.notChars - - return self.strRepr - -class White(Token): - """ - Special matching class for matching whitespace. Normally, whitespace is ignored - by pyparsing grammars. This class is included when some whitespace structures - are significant. Define with a string containing the whitespace characters to be - matched; default is C{" \\t\\r\\n"}. Also takes optional C{min}, C{max}, and C{exact} arguments, - as defined for the C{L{Word}} class. - """ - whiteStrs = { - " " : "<SPC>", - "\t": "<TAB>", - "\n": "<LF>", - "\r": "<CR>", - "\f": "<FF>", - } - def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): - super(White,self).__init__() - self.matchWhite = ws - self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) ) - #~ self.leaveWhitespace() - self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite)) - self.mayReturnEmpty = True - self.errmsg = "Expected " + self.name - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - def parseImpl( self, instring, loc, doActions=True ): - if not(instring[ loc ] in self.matchWhite): - raise ParseException(instring, loc, self.errmsg, self) - start = loc - loc += 1 - maxloc = start + self.maxLen - maxloc = min( maxloc, len(instring) ) - while loc < maxloc and instring[loc] in self.matchWhite: - loc += 1 - - if loc - start < self.minLen: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - -class _PositionToken(Token): - def __init__( self ): - super(_PositionToken,self).__init__() - self.name=self.__class__.__name__ - self.mayReturnEmpty = True - self.mayIndexError = False - -class GoToColumn(_PositionToken): - """ - Token to advance to a specific column of input text; useful for tabular report scraping. - """ - def __init__( self, colno ): - super(GoToColumn,self).__init__() - self.col = colno - - def preParse( self, instring, loc ): - if col(loc,instring) != self.col: - instrlen = len(instring) - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : - loc += 1 - return loc - - def parseImpl( self, instring, loc, doActions=True ): - thiscol = col( loc, instring ) - if thiscol > self.col: - raise ParseException( instring, loc, "Text not in expected column", self ) - newloc = loc + self.col - thiscol - ret = instring[ loc: newloc ] - return newloc, ret - - -class LineStart(_PositionToken): - """ - Matches if current position is at the beginning of a line within the parse string - - Example:: - - test = '''\ - AAA this line - AAA and this line - AAA but not this one - B AAA and definitely not this one - ''' - - for t in (LineStart() + 'AAA' + restOfLine).searchString(test): - print(t) - - Prints:: - ['AAA', ' this line'] - ['AAA', ' and this line'] - - """ - def __init__( self ): - super(LineStart,self).__init__() - self.errmsg = "Expected start of line" - - def parseImpl( self, instring, loc, doActions=True ): - if col(loc, instring) == 1: - return loc, [] - raise ParseException(instring, loc, self.errmsg, self) - -class LineEnd(_PositionToken): - """ - Matches if current position is at the end of a line within the parse string - """ - def __init__( self ): - super(LineEnd,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) - self.errmsg = "Expected end of line" - - def parseImpl( self, instring, loc, doActions=True ): - if loc<len(instring): - if instring[loc] == "\n": - return loc+1, "\n" - else: - raise ParseException(instring, loc, self.errmsg, self) - elif loc == len(instring): - return loc+1, [] - else: - raise ParseException(instring, loc, self.errmsg, self) - -class StringStart(_PositionToken): - """ - Matches if current position is at the beginning of the parse string - """ - def __init__( self ): - super(StringStart,self).__init__() - self.errmsg = "Expected start of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc != 0: - # see if entire string up to here is just whitespace and ignoreables - if loc != self.preParse( instring, 0 ): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - -class StringEnd(_PositionToken): - """ - Matches if current position is at the end of the parse string - """ - def __init__( self ): - super(StringEnd,self).__init__() - self.errmsg = "Expected end of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc < len(instring): - raise ParseException(instring, loc, self.errmsg, self) - elif loc == len(instring): - return loc+1, [] - elif loc > len(instring): - return loc, [] - else: - raise ParseException(instring, loc, self.errmsg, self) - -class WordStart(_PositionToken): - """ - Matches if the current position is at the beginning of a Word, and - is not preceded by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of - the string being parsed, or at the beginning of a line. - """ - def __init__(self, wordChars = printables): - super(WordStart,self).__init__() - self.wordChars = set(wordChars) - self.errmsg = "Not at the start of a word" - - def parseImpl(self, instring, loc, doActions=True ): - if loc != 0: - if (instring[loc-1] in self.wordChars or - instring[loc] not in self.wordChars): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - -class WordEnd(_PositionToken): - """ - Matches if the current position is at the end of a Word, and - is not followed by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of - the string being parsed, or at the end of a line. - """ - def __init__(self, wordChars = printables): - super(WordEnd,self).__init__() - self.wordChars = set(wordChars) - self.skipWhitespace = False - self.errmsg = "Not at the end of a word" - - def parseImpl(self, instring, loc, doActions=True ): - instrlen = len(instring) - if instrlen>0 and loc<instrlen: - if (instring[loc] in self.wordChars or - instring[loc-1] not in self.wordChars): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - - -class ParseExpression(ParserElement): - """ - Abstract subclass of ParserElement, for combining and post-processing parsed tokens. - """ - def __init__( self, exprs, savelist = False ): - super(ParseExpression,self).__init__(savelist) - if isinstance( exprs, _generatorType ): - exprs = list(exprs) - - if isinstance( exprs, basestring ): - self.exprs = [ ParserElement._literalStringClass( exprs ) ] - elif isinstance( exprs, Iterable ): - exprs = list(exprs) - # if sequence of strings provided, wrap with Literal - if all(isinstance(expr, basestring) for expr in exprs): - exprs = map(ParserElement._literalStringClass, exprs) - self.exprs = list(exprs) - else: - try: - self.exprs = list( exprs ) - except TypeError: - self.exprs = [ exprs ] - self.callPreparse = False - - def __getitem__( self, i ): - return self.exprs[i] - - def append( self, other ): - self.exprs.append( other ) - self.strRepr = None - return self - - def leaveWhitespace( self ): - """Extends C{leaveWhitespace} defined in base class, and also invokes C{leaveWhitespace} on - all contained expressions.""" - self.skipWhitespace = False - self.exprs = [ e.copy() for e in self.exprs ] - for e in self.exprs: - e.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - else: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - return self - - def __str__( self ): - try: - return super(ParseExpression,self).__str__() - except Exception: - pass - - if self.strRepr is None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) - return self.strRepr - - def streamline( self ): - super(ParseExpression,self).streamline() - - for e in self.exprs: - e.streamline() - - # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) - # but only if there are no parse actions or resultsNames on the nested And's - # (likewise for Or's and MatchFirst's) - if ( len(self.exprs) == 2 ): - other = self.exprs[0] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = other.exprs[:] + [ self.exprs[1] ] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - other = self.exprs[-1] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = self.exprs[:-1] + other.exprs[:] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - self.errmsg = "Expected " + _ustr(self) - - return self - - def setResultsName( self, name, listAllMatches=False ): - ret = super(ParseExpression,self).setResultsName(name,listAllMatches) - return ret - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - for e in self.exprs: - e.validate(tmp) - self.checkRecursion( [] ) - - def copy(self): - ret = super(ParseExpression,self).copy() - ret.exprs = [e.copy() for e in self.exprs] - return ret - -class And(ParseExpression): - """ - Requires all given C{ParseExpression}s to be found in the given order. - Expressions may be separated by whitespace. - May be constructed using the C{'+'} operator. - May also be constructed using the C{'-'} operator, which will suppress backtracking. - - Example:: - integer = Word(nums) - name_expr = OneOrMore(Word(alphas)) - - expr = And([integer("id"),name_expr("name"),integer("age")]) - # more easily written as: - expr = integer("id") + name_expr("name") + integer("age") - """ - - class _ErrorStop(Empty): - def __init__(self, *args, **kwargs): - super(And._ErrorStop,self).__init__(*args, **kwargs) - self.name = '-' - self.leaveWhitespace() - - def __init__( self, exprs, savelist = True ): - super(And,self).__init__(exprs, savelist) - self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - self.setWhitespaceChars( self.exprs[0].whiteChars ) - self.skipWhitespace = self.exprs[0].skipWhitespace - self.callPreparse = True - - def parseImpl( self, instring, loc, doActions=True ): - # pass False as last arg to _parse for first element, since we already - # pre-parsed the string as part of our And pre-parsing - loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) - errorStop = False - for e in self.exprs[1:]: - if isinstance(e, And._ErrorStop): - errorStop = True - continue - if errorStop: - try: - loc, exprtokens = e._parse( instring, loc, doActions ) - except ParseSyntaxException: - raise - except ParseBaseException as pe: - pe.__traceback__ = None - raise ParseSyntaxException._from_exception(pe) - except IndexError: - raise ParseSyntaxException(instring, len(instring), self.errmsg, self) - else: - loc, exprtokens = e._parse( instring, loc, doActions ) - if exprtokens or exprtokens.haskeys(): - resultlist += exprtokens - return loc, resultlist - - def __iadd__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #And( [ self, other ] ) - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - if not e.mayReturnEmpty: - break - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - -class Or(ParseExpression): - """ - Requires that at least one C{ParseExpression} is found. - If two expressions match, the expression that matches the longest string will be used. - May be constructed using the C{'^'} operator. - - Example:: - # construct Or using '^' operator - - number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums)) - print(number.searchString("123 3.1416 789")) - prints:: - [['123'], ['3.1416'], ['789']] - """ - def __init__( self, exprs, savelist = False ): - super(Or,self).__init__(exprs, savelist) - if self.exprs: - self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) - else: - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxException = None - matches = [] - for e in self.exprs: - try: - loc2 = e.tryParse( instring, loc ) - except ParseException as err: - err.__traceback__ = None - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - else: - # save match among all matches, to retry longest to shortest - matches.append((loc2, e)) - - if matches: - matches.sort(key=lambda x: -x[0]) - for _,e in matches: - try: - return e._parse( instring, loc, doActions ) - except ParseException as err: - err.__traceback__ = None - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - - if maxException is not None: - maxException.msg = self.errmsg - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - - def __ixor__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #Or( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class MatchFirst(ParseExpression): - """ - Requires that at least one C{ParseExpression} is found. - If two expressions match, the first one listed is the one that will match. - May be constructed using the C{'|'} operator. - - Example:: - # construct MatchFirst using '|' operator - - # watch the order of expressions to match - number = Word(nums) | Combine(Word(nums) + '.' + Word(nums)) - print(number.searchString("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']] - - # put more selective expression first - number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) - print(number.searchString("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] - """ - def __init__( self, exprs, savelist = False ): - super(MatchFirst,self).__init__(exprs, savelist) - if self.exprs: - self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) - else: - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxException = None - for e in self.exprs: - try: - ret = e._parse( instring, loc, doActions ) - return ret - except ParseException as err: - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - - # only got here if no expression matched, raise exception for match that made it the furthest - else: - if maxException is not None: - maxException.msg = self.errmsg - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - def __ior__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #MatchFirst( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class Each(ParseExpression): - """ - Requires all given C{ParseExpression}s to be found, but in any order. - Expressions may be separated by whitespace. - May be constructed using the C{'&'} operator. - - Example:: - color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN") - shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON") - integer = Word(nums) - shape_attr = "shape:" + shape_type("shape") - posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn") - color_attr = "color:" + color("color") - size_attr = "size:" + integer("size") - - # use Each (using operator '&') to accept attributes in any order - # (shape and posn are required, color and size are optional) - shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr) - - shape_spec.runTests(''' - shape: SQUARE color: BLACK posn: 100, 120 - shape: CIRCLE size: 50 color: BLUE posn: 50,80 - color:GREEN size:20 shape:TRIANGLE posn:20,40 - ''' - ) - prints:: - shape: SQUARE color: BLACK posn: 100, 120 - ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']] - - color: BLACK - - posn: ['100', ',', '120'] - - x: 100 - - y: 120 - - shape: SQUARE - - - shape: CIRCLE size: 50 color: BLUE posn: 50,80 - ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']] - - color: BLUE - - posn: ['50', ',', '80'] - - x: 50 - - y: 80 - - shape: CIRCLE - - size: 50 - - - color: GREEN size: 20 shape: TRIANGLE posn: 20,40 - ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']] - - color: GREEN - - posn: ['20', ',', '40'] - - x: 20 - - y: 40 - - shape: TRIANGLE - - size: 20 - """ - def __init__( self, exprs, savelist = True ): - super(Each,self).__init__(exprs, savelist) - self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - self.skipWhitespace = True - self.initExprGroups = True - - def parseImpl( self, instring, loc, doActions=True ): - if self.initExprGroups: - self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional)) - opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] - opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)] - self.optionals = opt1 + opt2 - self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] - self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] - self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] - self.required += self.multirequired - self.initExprGroups = False - tmpLoc = loc - tmpReqd = self.required[:] - tmpOpt = self.optionals[:] - matchOrder = [] - - keepMatching = True - while keepMatching: - tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired - failed = [] - for e in tmpExprs: - try: - tmpLoc = e.tryParse( instring, tmpLoc ) - except ParseException: - failed.append(e) - else: - matchOrder.append(self.opt1map.get(id(e),e)) - if e in tmpReqd: - tmpReqd.remove(e) - elif e in tmpOpt: - tmpOpt.remove(e) - if len(failed) == len(tmpExprs): - keepMatching = False - - if tmpReqd: - missing = ", ".join(_ustr(e) for e in tmpReqd) - raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) - - # add any unmatched Optionals, in case they have default values defined - matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt] - - resultlist = [] - for e in matchOrder: - loc,results = e._parse(instring,loc,doActions) - resultlist.append(results) - - finalResults = sum(resultlist, ParseResults([])) - return loc, finalResults - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class ParseElementEnhance(ParserElement): - """ - Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens. - """ - def __init__( self, expr, savelist=False ): - super(ParseElementEnhance,self).__init__(savelist) - if isinstance( expr, basestring ): - if issubclass(ParserElement._literalStringClass, Token): - expr = ParserElement._literalStringClass(expr) - else: - expr = ParserElement._literalStringClass(Literal(expr)) - self.expr = expr - self.strRepr = None - if expr is not None: - self.mayIndexError = expr.mayIndexError - self.mayReturnEmpty = expr.mayReturnEmpty - self.setWhitespaceChars( expr.whiteChars ) - self.skipWhitespace = expr.skipWhitespace - self.saveAsList = expr.saveAsList - self.callPreparse = expr.callPreparse - self.ignoreExprs.extend(expr.ignoreExprs) - - def parseImpl( self, instring, loc, doActions=True ): - if self.expr is not None: - return self.expr._parse( instring, loc, doActions, callPreParse=False ) - else: - raise ParseException("",loc,self.errmsg,self) - - def leaveWhitespace( self ): - self.skipWhitespace = False - self.expr = self.expr.copy() - if self.expr is not None: - self.expr.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - else: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - return self - - def streamline( self ): - super(ParseElementEnhance,self).streamline() - if self.expr is not None: - self.expr.streamline() - return self - - def checkRecursion( self, parseElementList ): - if self in parseElementList: - raise RecursiveGrammarException( parseElementList+[self] ) - subRecCheckList = parseElementList[:] + [ self ] - if self.expr is not None: - self.expr.checkRecursion( subRecCheckList ) - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion( [] ) - - def __str__( self ): - try: - return super(ParseElementEnhance,self).__str__() - except Exception: - pass - - if self.strRepr is None and self.expr is not None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) - return self.strRepr - - -class FollowedBy(ParseElementEnhance): - """ - Lookahead matching of the given parse expression. C{FollowedBy} - does I{not} advance the parsing position within the input string, it only - verifies that the specified parse expression matches at the current - position. C{FollowedBy} always returns a null token list. - - Example:: - # use FollowedBy to match a label only if it is followed by a ':' - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - - OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint() - prints:: - [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']] - """ - def __init__( self, expr ): - super(FollowedBy,self).__init__(expr) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - self.expr.tryParse( instring, loc ) - return loc, [] - - -class NotAny(ParseElementEnhance): - """ - Lookahead to disallow matching with the given parse expression. C{NotAny} - does I{not} advance the parsing position within the input string, it only - verifies that the specified parse expression does I{not} match at the current - position. Also, C{NotAny} does I{not} skip over leading whitespace. C{NotAny} - always returns a null token list. May be constructed using the '~' operator. - - Example:: - - """ - def __init__( self, expr ): - super(NotAny,self).__init__(expr) - #~ self.leaveWhitespace() - self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs - self.mayReturnEmpty = True - self.errmsg = "Found unwanted token, "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - if self.expr.canParseNext(instring, loc): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "~{" + _ustr(self.expr) + "}" - - return self.strRepr - -class _MultipleMatch(ParseElementEnhance): - def __init__( self, expr, stopOn=None): - super(_MultipleMatch, self).__init__(expr) - self.saveAsList = True - ender = stopOn - if isinstance(ender, basestring): - ender = ParserElement._literalStringClass(ender) - self.not_ender = ~ender if ender is not None else None - - def parseImpl( self, instring, loc, doActions=True ): - self_expr_parse = self.expr._parse - self_skip_ignorables = self._skipIgnorables - check_ender = self.not_ender is not None - if check_ender: - try_not_ender = self.not_ender.tryParse - - # must be at least one (but first see if we are the stopOn sentinel; - # if so, fail) - if check_ender: - try_not_ender(instring, loc) - loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False ) - try: - hasIgnoreExprs = (not not self.ignoreExprs) - while 1: - if check_ender: - try_not_ender(instring, loc) - if hasIgnoreExprs: - preloc = self_skip_ignorables( instring, loc ) - else: - preloc = loc - loc, tmptokens = self_expr_parse( instring, preloc, doActions ) - if tmptokens or tmptokens.haskeys(): - tokens += tmptokens - except (ParseException,IndexError): - pass - - return loc, tokens - -class OneOrMore(_MultipleMatch): - """ - Repetition of one or more of the given expression. - - Parameters: - - expr - expression that must match one or more times - - stopOn - (default=C{None}) - expression for a terminating sentinel - (only required if the sentinel would ordinarily match the repetition - expression) - - Example:: - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join)) - - text = "shape: SQUARE posn: upper left color: BLACK" - OneOrMore(attr_expr).parseString(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']] - - # use stopOn attribute for OneOrMore to avoid reading label string as part of the data - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']] - - # could also be written as - (attr_expr * (1,)).parseString(text).pprint() - """ - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + _ustr(self.expr) + "}..." - - return self.strRepr - -class ZeroOrMore(_MultipleMatch): - """ - Optional repetition of zero or more of the given expression. - - Parameters: - - expr - expression that must match zero or more times - - stopOn - (default=C{None}) - expression for a terminating sentinel - (only required if the sentinel would ordinarily match the repetition - expression) - - Example: similar to L{OneOrMore} - """ - def __init__( self, expr, stopOn=None): - super(ZeroOrMore,self).__init__(expr, stopOn=stopOn) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - try: - return super(ZeroOrMore, self).parseImpl(instring, loc, doActions) - except (ParseException,IndexError): - return loc, [] - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]..." - - return self.strRepr - -class _NullToken(object): - def __bool__(self): - return False - __nonzero__ = __bool__ - def __str__(self): - return "" - -_optionalNotMatched = _NullToken() -class Optional(ParseElementEnhance): - """ - Optional matching of the given expression. - - Parameters: - - expr - expression that must match zero or more times - - default (optional) - value to be returned if the optional expression is not found. - - Example:: - # US postal code can be a 5-digit zip, plus optional 4-digit qualifier - zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4))) - zip.runTests(''' - # traditional ZIP code - 12345 - - # ZIP+4 form - 12101-0001 - - # invalid ZIP - 98765- - ''') - prints:: - # traditional ZIP code - 12345 - ['12345'] - - # ZIP+4 form - 12101-0001 - ['12101-0001'] - - # invalid ZIP - 98765- - ^ - FAIL: Expected end of text (at char 5), (line:1, col:6) - """ - def __init__( self, expr, default=_optionalNotMatched ): - super(Optional,self).__init__( expr, savelist=False ) - self.saveAsList = self.expr.saveAsList - self.defaultValue = default - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - except (ParseException,IndexError): - if self.defaultValue is not _optionalNotMatched: - if self.expr.resultsName: - tokens = ParseResults([ self.defaultValue ]) - tokens[self.expr.resultsName] = self.defaultValue - else: - tokens = [ self.defaultValue ] - else: - tokens = [] - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]" - - return self.strRepr - -class SkipTo(ParseElementEnhance): - """ - Token for skipping over all undefined text until the matched expression is found. - - Parameters: - - expr - target expression marking the end of the data to be skipped - - include - (default=C{False}) if True, the target expression is also parsed - (the skipped text and target expression are returned as a 2-element list). - - ignore - (default=C{None}) used to define grammars (typically quoted strings and - comments) that might contain false matches to the target expression - - failOn - (default=C{None}) define expressions that are not allowed to be - included in the skipped test; if found before the target expression is found, - the SkipTo is not a match - - Example:: - report = ''' - Outstanding Issues Report - 1 Jan 2000 - - # | Severity | Description | Days Open - -----+----------+-------------------------------------------+----------- - 101 | Critical | Intermittent system crash | 6 - 94 | Cosmetic | Spelling error on Login ('log|n') | 14 - 79 | Minor | System slow when running too many reports | 47 - ''' - integer = Word(nums) - SEP = Suppress('|') - # use SkipTo to simply match everything up until the next SEP - # - ignore quoted strings, so that a '|' character inside a quoted string does not match - # - parse action will call token.strip() for each matched token, i.e., the description body - string_data = SkipTo(SEP, ignore=quotedString) - string_data.setParseAction(tokenMap(str.strip)) - ticket_expr = (integer("issue_num") + SEP - + string_data("sev") + SEP - + string_data("desc") + SEP - + integer("days_open")) - - for tkt in ticket_expr.searchString(report): - print tkt.dump() - prints:: - ['101', 'Critical', 'Intermittent system crash', '6'] - - days_open: 6 - - desc: Intermittent system crash - - issue_num: 101 - - sev: Critical - ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14'] - - days_open: 14 - - desc: Spelling error on Login ('log|n') - - issue_num: 94 - - sev: Cosmetic - ['79', 'Minor', 'System slow when running too many reports', '47'] - - days_open: 47 - - desc: System slow when running too many reports - - issue_num: 79 - - sev: Minor - """ - def __init__( self, other, include=False, ignore=None, failOn=None ): - super( SkipTo, self ).__init__( other ) - self.ignoreExpr = ignore - self.mayReturnEmpty = True - self.mayIndexError = False - self.includeMatch = include - self.asList = False - if isinstance(failOn, basestring): - self.failOn = ParserElement._literalStringClass(failOn) - else: - self.failOn = failOn - self.errmsg = "No match found for "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - startloc = loc - instrlen = len(instring) - expr = self.expr - expr_parse = self.expr._parse - self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None - self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None - - tmploc = loc - while tmploc <= instrlen: - if self_failOn_canParseNext is not None: - # break if failOn expression matches - if self_failOn_canParseNext(instring, tmploc): - break - - if self_ignoreExpr_tryParse is not None: - # advance past ignore expressions - while 1: - try: - tmploc = self_ignoreExpr_tryParse(instring, tmploc) - except ParseBaseException: - break - - try: - expr_parse(instring, tmploc, doActions=False, callPreParse=False) - except (ParseException, IndexError): - # no match, advance loc in string - tmploc += 1 - else: - # matched skipto expr, done - break - - else: - # ran off the end of the input string without matching skipto expr, fail - raise ParseException(instring, loc, self.errmsg, self) - - # build up return values - loc = tmploc - skiptext = instring[startloc:loc] - skipresult = ParseResults(skiptext) - - if self.includeMatch: - loc, mat = expr_parse(instring,loc,doActions,callPreParse=False) - skipresult += mat - - return loc, skipresult - -class Forward(ParseElementEnhance): - """ - Forward declaration of an expression to be defined later - - used for recursive grammars, such as algebraic infix notation. - When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator. - - Note: take care when assigning to C{Forward} not to overlook precedence of operators. - Specifically, '|' has a lower precedence than '<<', so that:: - fwdExpr << a | b | c - will actually be evaluated as:: - (fwdExpr << a) | b | c - thereby leaving b and c out as parseable alternatives. It is recommended that you - explicitly group the values inserted into the C{Forward}:: - fwdExpr << (a | b | c) - Converting to use the '<<=' operator instead will avoid this problem. - - See L{ParseResults.pprint} for an example of a recursive parser created using - C{Forward}. - """ - def __init__( self, other=None ): - super(Forward,self).__init__( other, savelist=False ) - - def __lshift__( self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass(other) - self.expr = other - self.strRepr = None - self.mayIndexError = self.expr.mayIndexError - self.mayReturnEmpty = self.expr.mayReturnEmpty - self.setWhitespaceChars( self.expr.whiteChars ) - self.skipWhitespace = self.expr.skipWhitespace - self.saveAsList = self.expr.saveAsList - self.ignoreExprs.extend(self.expr.ignoreExprs) - return self - - def __ilshift__(self, other): - return self << other - - def leaveWhitespace( self ): - self.skipWhitespace = False - return self - - def streamline( self ): - if not self.streamlined: - self.streamlined = True - if self.expr is not None: - self.expr.streamline() - return self - - def validate( self, validateTrace=[] ): - if self not in validateTrace: - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion([]) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - return self.__class__.__name__ + ": ..." - - # stubbed out for now - creates awful memory and perf issues - self._revertClass = self.__class__ - self.__class__ = _ForwardNoRecurse - try: - if self.expr is not None: - retString = _ustr(self.expr) - else: - retString = "None" - finally: - self.__class__ = self._revertClass - return self.__class__.__name__ + ": " + retString - - def copy(self): - if self.expr is not None: - return super(Forward,self).copy() - else: - ret = Forward() - ret <<= self - return ret - -class _ForwardNoRecurse(Forward): - def __str__( self ): - return "..." - -class TokenConverter(ParseElementEnhance): - """ - Abstract subclass of C{ParseExpression}, for converting parsed results. - """ - def __init__( self, expr, savelist=False ): - super(TokenConverter,self).__init__( expr )#, savelist ) - self.saveAsList = False - -class Combine(TokenConverter): - """ - Converter to concatenate all matching tokens to a single string. - By default, the matching patterns must also be contiguous in the input string; - this can be disabled by specifying C{'adjacent=False'} in the constructor. - - Example:: - real = Word(nums) + '.' + Word(nums) - print(real.parseString('3.1416')) # -> ['3', '.', '1416'] - # will also erroneously match the following - print(real.parseString('3. 1416')) # -> ['3', '.', '1416'] - - real = Combine(Word(nums) + '.' + Word(nums)) - print(real.parseString('3.1416')) # -> ['3.1416'] - # no match when there are internal spaces - print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...) - """ - def __init__( self, expr, joinString="", adjacent=True ): - super(Combine,self).__init__( expr ) - # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself - if adjacent: - self.leaveWhitespace() - self.adjacent = adjacent - self.skipWhitespace = True - self.joinString = joinString - self.callPreparse = True - - def ignore( self, other ): - if self.adjacent: - ParserElement.ignore(self, other) - else: - super( Combine, self).ignore( other ) - return self - - def postParse( self, instring, loc, tokenlist ): - retToks = tokenlist.copy() - del retToks[:] - retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) - - if self.resultsName and retToks.haskeys(): - return [ retToks ] - else: - return retToks - -class Group(TokenConverter): - """ - Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions. - - Example:: - ident = Word(alphas) - num = Word(nums) - term = ident | num - func = ident + Optional(delimitedList(term)) - print(func.parseString("fn a,b,100")) # -> ['fn', 'a', 'b', '100'] - - func = ident + Group(Optional(delimitedList(term))) - print(func.parseString("fn a,b,100")) # -> ['fn', ['a', 'b', '100']] - """ - def __init__( self, expr ): - super(Group,self).__init__( expr ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - return [ tokenlist ] - -class Dict(TokenConverter): - """ - Converter to return a repetitive expression as a list, but also as a dictionary. - Each element can also be referenced using the first token in the expression as its key. - Useful for tabular report scraping when the first column can be used as a item key. - - Example:: - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join)) - - text = "shape: SQUARE posn: upper left color: light blue texture: burlap" - attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - - # print attributes as plain groups - print(OneOrMore(attr_expr).parseString(text).dump()) - - # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names - result = Dict(OneOrMore(Group(attr_expr))).parseString(text) - print(result.dump()) - - # access named fields as dict entries, or output as dict - print(result['shape']) - print(result.asDict()) - prints:: - ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap'] - - [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] - - color: light blue - - posn: upper left - - shape: SQUARE - - texture: burlap - SQUARE - {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'} - See more examples at L{ParseResults} of accessing fields by results name. - """ - def __init__( self, expr ): - super(Dict,self).__init__( expr ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - for i,tok in enumerate(tokenlist): - if len(tok) == 0: - continue - ikey = tok[0] - if isinstance(ikey,int): - ikey = _ustr(tok[0]).strip() - if len(tok)==1: - tokenlist[ikey] = _ParseResultsWithOffset("",i) - elif len(tok)==2 and not isinstance(tok[1],ParseResults): - tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) - else: - dictvalue = tok.copy() #ParseResults(i) - del dictvalue[0] - if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()): - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) - else: - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) - - if self.resultsName: - return [ tokenlist ] - else: - return tokenlist - - -class Suppress(TokenConverter): - """ - Converter for ignoring the results of a parsed expression. - - Example:: - source = "a, b, c,d" - wd = Word(alphas) - wd_list1 = wd + ZeroOrMore(',' + wd) - print(wd_list1.parseString(source)) - - # often, delimiters that are useful during parsing are just in the - # way afterward - use Suppress to keep them out of the parsed output - wd_list2 = wd + ZeroOrMore(Suppress(',') + wd) - print(wd_list2.parseString(source)) - prints:: - ['a', ',', 'b', ',', 'c', ',', 'd'] - ['a', 'b', 'c', 'd'] - (See also L{delimitedList}.) - """ - def postParse( self, instring, loc, tokenlist ): - return [] - - def suppress( self ): - return self - - -class OnlyOnce(object): - """ - Wrapper for parse actions, to ensure they are only called once. - """ - def __init__(self, methodCall): - self.callable = _trim_arity(methodCall) - self.called = False - def __call__(self,s,l,t): - if not self.called: - results = self.callable(s,l,t) - self.called = True - return results - raise ParseException(s,l,"") - def reset(self): - self.called = False - -def traceParseAction(f): - """ - Decorator for debugging parse actions. - - When the parse action is called, this decorator will print C{">> entering I{method-name}(line:I{current_source_line}, I{parse_location}, I{matched_tokens})".} - When the parse action completes, the decorator will print C{"<<"} followed by the returned value, or any exception that the parse action raised. - - Example:: - wd = Word(alphas) - - @traceParseAction - def remove_duplicate_chars(tokens): - return ''.join(sorted(set(''.join(tokens)))) - - wds = OneOrMore(wd).setParseAction(remove_duplicate_chars) - print(wds.parseString("slkdjs sld sldd sdlf sdljf")) - prints:: - >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {})) - <<leaving remove_duplicate_chars (ret: 'dfjkls') - ['dfjkls'] - """ - f = _trim_arity(f) - def z(*paArgs): - thisFunc = f.__name__ - s,l,t = paArgs[-3:] - if len(paArgs)>3: - thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc - sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) ) - try: - ret = f(*paArgs) - except Exception as exc: - sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) - raise - sys.stderr.write( "<<leaving %s (ret: %r)\n" % (thisFunc,ret) ) - return ret - try: - z.__name__ = f.__name__ - except AttributeError: - pass - return z - -# -# global helpers -# -def delimitedList( expr, delim=",", combine=False ): - """ - Helper to define a delimited list of expressions - the delimiter defaults to ','. - By default, the list elements and delimiters can have intervening whitespace, and - comments, but this can be overridden by passing C{combine=True} in the constructor. - If C{combine} is set to C{True}, the matching tokens are returned as a single token - string, with the delimiters included; otherwise, the matching tokens are returned - as a list of tokens, with the delimiters suppressed. - - Example:: - delimitedList(Word(alphas)).parseString("aa,bb,cc") # -> ['aa', 'bb', 'cc'] - delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE'] - """ - dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." - if combine: - return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) - else: - return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) - -def countedArray( expr, intExpr=None ): - """ - Helper to define a counted list of expressions. - This helper defines a pattern of the form:: - integer expr expr expr... - where the leading integer tells how many expr expressions follow. - The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed. - - If C{intExpr} is specified, it should be a pyparsing expression that produces an integer value. - - Example:: - countedArray(Word(alphas)).parseString('2 ab cd ef') # -> ['ab', 'cd'] - - # in this parser, the leading integer value is given in binary, - # '10' indicating that 2 values are in the array - binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2)) - countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef') # -> ['ab', 'cd'] - """ - arrayExpr = Forward() - def countFieldParseAction(s,l,t): - n = t[0] - arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) - return [] - if intExpr is None: - intExpr = Word(nums).setParseAction(lambda t:int(t[0])) - else: - intExpr = intExpr.copy() - intExpr.setName("arrayLen") - intExpr.addParseAction(countFieldParseAction, callDuringTry=True) - return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...') - -def _flatten(L): - ret = [] - for i in L: - if isinstance(i,list): - ret.extend(_flatten(i)) - else: - ret.append(i) - return ret - -def matchPreviousLiteral(expr): - """ - Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousLiteral(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches a - previous literal, will also match the leading C{"1:1"} in C{"1:10"}. - If this is not desired, use C{matchPreviousExpr}. - Do I{not} use with packrat parsing enabled. - """ - rep = Forward() - def copyTokenToRepeater(s,l,t): - if t: - if len(t) == 1: - rep << t[0] - else: - # flatten t tokens - tflat = _flatten(t.asList()) - rep << And(Literal(tt) for tt in tflat) - else: - rep << Empty() - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - rep.setName('(prev) ' + _ustr(expr)) - return rep - -def matchPreviousExpr(expr): - """ - Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousExpr(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches by - expressions, will I{not} match the leading C{"1:1"} in C{"1:10"}; - the expressions are evaluated first, and then compared, so - C{"1"} is compared with C{"10"}. - Do I{not} use with packrat parsing enabled. - """ - rep = Forward() - e2 = expr.copy() - rep <<= e2 - def copyTokenToRepeater(s,l,t): - matchTokens = _flatten(t.asList()) - def mustMatchTheseTokens(s,l,t): - theseTokens = _flatten(t.asList()) - if theseTokens != matchTokens: - raise ParseException("",0,"") - rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - rep.setName('(prev) ' + _ustr(expr)) - return rep - -def _escapeRegexRangeChars(s): - #~ escape these chars: ^-] - for c in r"\^-]": - s = s.replace(c,_bslash+c) - s = s.replace("\n",r"\n") - s = s.replace("\t",r"\t") - return _ustr(s) - -def oneOf( strs, caseless=False, useRegex=True ): - """ - Helper to quickly define a set of alternative Literals, and makes sure to do - longest-first testing when there is a conflict, regardless of the input order, - but returns a C{L{MatchFirst}} for best performance. - - Parameters: - - strs - a string of space-delimited literals, or a collection of string literals - - caseless - (default=C{False}) - treat all literals as caseless - - useRegex - (default=C{True}) - as an optimization, will generate a Regex - object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or - if creating a C{Regex} raises an exception) - - Example:: - comp_oper = oneOf("< = > <= >= !=") - var = Word(alphas) - number = Word(nums) - term = var | number - comparison_expr = term + comp_oper + term - print(comparison_expr.searchString("B = 12 AA=23 B<=AA AA>12")) - prints:: - [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] - """ - if caseless: - isequal = ( lambda a,b: a.upper() == b.upper() ) - masks = ( lambda a,b: b.upper().startswith(a.upper()) ) - parseElementClass = CaselessLiteral - else: - isequal = ( lambda a,b: a == b ) - masks = ( lambda a,b: b.startswith(a) ) - parseElementClass = Literal - - symbols = [] - if isinstance(strs,basestring): - symbols = strs.split() - elif isinstance(strs, Iterable): - symbols = list(strs) - else: - warnings.warn("Invalid argument to oneOf, expected string or iterable", - SyntaxWarning, stacklevel=2) - if not symbols: - return NoMatch() - - i = 0 - while i < len(symbols)-1: - cur = symbols[i] - for j,other in enumerate(symbols[i+1:]): - if ( isequal(other, cur) ): - del symbols[i+j+1] - break - elif ( masks(cur, other) ): - del symbols[i+j+1] - symbols.insert(i,other) - cur = other - break - else: - i += 1 - - if not caseless and useRegex: - #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) - try: - if len(symbols)==len("".join(symbols)): - return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols)) - else: - return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols)) - except Exception: - warnings.warn("Exception creating Regex for oneOf, building MatchFirst", - SyntaxWarning, stacklevel=2) - - - # last resort, just use MatchFirst - return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols)) - -def dictOf( key, value ): - """ - Helper to easily and clearly define a dictionary by specifying the respective patterns - for the key and value. Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens - in the proper order. The key pattern can include delimiting markers or punctuation, - as long as they are suppressed, thereby leaving the significant key text. The value - pattern can include named results, so that the C{Dict} results can include named token - fields. - - Example:: - text = "shape: SQUARE posn: upper left color: light blue texture: burlap" - attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - print(OneOrMore(attr_expr).parseString(text).dump()) - - attr_label = label - attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join) - - # similar to Dict, but simpler call format - result = dictOf(attr_label, attr_value).parseString(text) - print(result.dump()) - print(result['shape']) - print(result.shape) # object attribute access works too - print(result.asDict()) - prints:: - [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] - - color: light blue - - posn: upper left - - shape: SQUARE - - texture: burlap - SQUARE - SQUARE - {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'} - """ - return Dict( ZeroOrMore( Group ( key + value ) ) ) - -def originalTextFor(expr, asString=True): - """ - Helper to return the original, untokenized text for a given expression. Useful to - restore the parsed fields of an HTML start tag into the raw tag text itself, or to - revert separate tokens with intervening whitespace back to the original matching - input text. By default, returns astring containing the original parsed text. - - If the optional C{asString} argument is passed as C{False}, then the return value is a - C{L{ParseResults}} containing any results names that were originally matched, and a - single token containing the original matched text from the input string. So if - the expression passed to C{L{originalTextFor}} contains expressions with defined - results names, you must set C{asString} to C{False} if you want to preserve those - results name values. - - Example:: - src = "this is test <b> bold <i>text</i> </b> normal text " - for tag in ("b","i"): - opener,closer = makeHTMLTags(tag) - patt = originalTextFor(opener + SkipTo(closer) + closer) - print(patt.searchString(src)[0]) - prints:: - ['<b> bold <i>text</i> </b>'] - ['<i>text</i>'] - """ - locMarker = Empty().setParseAction(lambda s,loc,t: loc) - endlocMarker = locMarker.copy() - endlocMarker.callPreparse = False - matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") - if asString: - extractText = lambda s,l,t: s[t._original_start:t._original_end] - else: - def extractText(s,l,t): - t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]] - matchExpr.setParseAction(extractText) - matchExpr.ignoreExprs = expr.ignoreExprs - return matchExpr - -def ungroup(expr): - """ - Helper to undo pyparsing's default grouping of And expressions, even - if all but one are non-empty. - """ - return TokenConverter(expr).setParseAction(lambda t:t[0]) - -def locatedExpr(expr): - """ - Helper to decorate a returned token with its starting and ending locations in the input string. - This helper adds the following results names: - - locn_start = location where matched expression begins - - locn_end = location where matched expression ends - - value = the actual parsed results - - Be careful if the input text contains C{<TAB>} characters, you may want to call - C{L{ParserElement.parseWithTabs}} - - Example:: - wd = Word(alphas) - for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"): - print(match) - prints:: - [[0, 'ljsdf', 5]] - [[8, 'lksdjjf', 15]] - [[18, 'lkkjj', 23]] - """ - locator = Empty().setParseAction(lambda s,l,t: l) - return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end")) - - -# convenience constants for positional expressions -empty = Empty().setName("empty") -lineStart = LineStart().setName("lineStart") -lineEnd = LineEnd().setName("lineEnd") -stringStart = StringStart().setName("stringStart") -stringEnd = StringEnd().setName("stringEnd") - -_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) -_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16))) -_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8))) -_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1) -_charRange = Group(_singleChar + Suppress("-") + _singleChar) -_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" - -def srange(s): - r""" - Helper to easily define string ranges for use in Word construction. Borrows - syntax from regexp '[]' string range definitions:: - srange("[0-9]") -> "0123456789" - srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" - srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" - The input string must be enclosed in []'s, and the returned string is the expanded - character set joined into a single string. - The values enclosed in the []'s may be: - - a single character - - an escaped character with a leading backslash (such as C{\-} or C{\]}) - - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character) - (C{\0x##} is also supported for backwards compatibility) - - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character) - - a range of any of the above, separated by a dash (C{'a-z'}, etc.) - - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.) - """ - _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1)) - try: - return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body) - except Exception: - return "" - -def matchOnlyAtCol(n): - """ - Helper method for defining parse actions that require matching at a specific - column in the input text. - """ - def verifyCol(strg,locn,toks): - if col(locn,strg) != n: - raise ParseException(strg,locn,"matched token not at column %d" % n) - return verifyCol - -def replaceWith(replStr): - """ - Helper method for common parse actions that simply return a literal value. Especially - useful when used with C{L{transformString<ParserElement.transformString>}()}. - - Example:: - num = Word(nums).setParseAction(lambda toks: int(toks[0])) - na = oneOf("N/A NA").setParseAction(replaceWith(math.nan)) - term = na | num - - OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234] - """ - return lambda s,l,t: [replStr] - -def removeQuotes(s,l,t): - """ - Helper parse action for removing quotation marks from parsed quoted strings. - - Example:: - # by default, quotation marks are included in parsed results - quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"] - - # use removeQuotes to strip quotation marks from parsed results - quotedString.setParseAction(removeQuotes) - quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"] - """ - return t[0][1:-1] - -def tokenMap(func, *args): - """ - Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional - args are passed, they are forwarded to the given function as additional arguments after - the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the - parsed data to an integer using base 16. - - Example (compare the last to example in L{ParserElement.transformString}:: - hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16)) - hex_ints.runTests(''' - 00 11 22 aa FF 0a 0d 1a - ''') - - upperword = Word(alphas).setParseAction(tokenMap(str.upper)) - OneOrMore(upperword).runTests(''' - my kingdom for a horse - ''') - - wd = Word(alphas).setParseAction(tokenMap(str.title)) - OneOrMore(wd).setParseAction(' '.join).runTests(''' - now is the winter of our discontent made glorious summer by this sun of york - ''') - prints:: - 00 11 22 aa FF 0a 0d 1a - [0, 17, 34, 170, 255, 10, 13, 26] - - my kingdom for a horse - ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE'] - - now is the winter of our discontent made glorious summer by this sun of york - ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York'] - """ - def pa(s,l,t): - return [func(tokn, *args) for tokn in t] - - try: - func_name = getattr(func, '__name__', - getattr(func, '__class__').__name__) - except Exception: - func_name = str(func) - pa.__name__ = func_name - - return pa - -upcaseTokens = tokenMap(lambda t: _ustr(t).upper()) -"""(Deprecated) Helper parse action to convert tokens to upper case. Deprecated in favor of L{pyparsing_common.upcaseTokens}""" - -downcaseTokens = tokenMap(lambda t: _ustr(t).lower()) -"""(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of L{pyparsing_common.downcaseTokens}""" - -def _makeTags(tagStr, xml): - """Internal helper to construct opening and closing tag expressions, given a tag name""" - if isinstance(tagStr,basestring): - resname = tagStr - tagStr = Keyword(tagStr, caseless=not xml) - else: - resname = tagStr.name - - tagAttrName = Word(alphas,alphanums+"_-:") - if (xml): - tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - else: - printablesLessRAbrack = "".join(c for c in printables if c not in ">") - tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ - Optional( Suppress("=") + tagAttrValue ) ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - closeTag = Combine(_L("</") + tagStr + ">") - - openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname) - closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % resname) - openTag.tag = resname - closeTag.tag = resname - return openTag, closeTag - -def makeHTMLTags(tagStr): - """ - Helper to construct opening and closing tag expressions for HTML, given a tag name. Matches - tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values. - - Example:: - text = '<td>More info at the <a href="http://pyparsing.wikispaces.com">pyparsing</a> wiki page</td>' - # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple - a,a_end = makeHTMLTags("A") - link_expr = a + SkipTo(a_end)("link_text") + a_end - - for link in link_expr.searchString(text): - # attributes in the <A> tag (like "href" shown here) are also accessible as named results - print(link.link_text, '->', link.href) - prints:: - pyparsing -> http://pyparsing.wikispaces.com - """ - return _makeTags( tagStr, False ) - -def makeXMLTags(tagStr): - """ - Helper to construct opening and closing tag expressions for XML, given a tag name. Matches - tags only in the given upper/lower case. - - Example: similar to L{makeHTMLTags} - """ - return _makeTags( tagStr, True ) - -def withAttribute(*args,**attrDict): - """ - Helper to create a validating parse action to be used with start tags created - with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag - with a required attribute value, to avoid false matches on common tags such as - C{<TD>} or C{<DIV>}. - - Call C{withAttribute} with a series of attribute names and values. Specify the list - of filter attributes names and values as: - - keyword arguments, as in C{(align="right")}, or - - as an explicit dict with C{**} operator, when an attribute name is also a Python - reserved word, as in C{**{"class":"Customer", "align":"right"}} - - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) - For attribute names with a namespace prefix, you must use the second form. Attribute - names are matched insensitive to upper/lower case. - - If just testing for C{class} (with or without a namespace), use C{L{withClass}}. - - To verify that the attribute exists, but without specifying a value, pass - C{withAttribute.ANY_VALUE} as the value. - - Example:: - html = ''' - <div> - Some text - <div type="grid">1 4 0 1 0</div> - <div type="graph">1,3 2,3 1,1</div> - <div>this has no type</div> - </div> - - ''' - div,div_end = makeHTMLTags("div") - - # only match div tag having a type attribute with value "grid" - div_grid = div().setParseAction(withAttribute(type="grid")) - grid_expr = div_grid + SkipTo(div | div_end)("body") - for grid_header in grid_expr.searchString(html): - print(grid_header.body) - - # construct a match with any div tag having a type attribute, regardless of the value - div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) - div_expr = div_any_type + SkipTo(div | div_end)("body") - for div_header in div_expr.searchString(html): - print(div_header.body) - prints:: - 1 4 0 1 0 - - 1 4 0 1 0 - 1,3 2,3 1,1 - """ - if args: - attrs = args[:] - else: - attrs = attrDict.items() - attrs = [(k,v) for k,v in attrs] - def pa(s,l,tokens): - for attrName,attrValue in attrs: - if attrName not in tokens: - raise ParseException(s,l,"no matching attribute " + attrName) - if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: - raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % - (attrName, tokens[attrName], attrValue)) - return pa -withAttribute.ANY_VALUE = object() - -def withClass(classname, namespace=''): - """ - Simplified version of C{L{withAttribute}} when matching on a div class - made - difficult because C{class} is a reserved word in Python. - - Example:: - html = ''' - <div> - Some text - <div class="grid">1 4 0 1 0</div> - <div class="graph">1,3 2,3 1,1</div> - <div>this &lt;div&gt; has no class</div> - </div> - - ''' - div,div_end = makeHTMLTags("div") - div_grid = div().setParseAction(withClass("grid")) - - grid_expr = div_grid + SkipTo(div | div_end)("body") - for grid_header in grid_expr.searchString(html): - print(grid_header.body) - - div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) - div_expr = div_any_type + SkipTo(div | div_end)("body") - for div_header in div_expr.searchString(html): - print(div_header.body) - prints:: - 1 4 0 1 0 - - 1 4 0 1 0 - 1,3 2,3 1,1 - """ - classattr = "%s:class" % namespace if namespace else "class" - return withAttribute(**{classattr : classname}) - -opAssoc = _Constants() -opAssoc.LEFT = object() -opAssoc.RIGHT = object() - -def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): - """ - Helper method for constructing grammars of expressions made up of - operators working in a precedence hierarchy. Operators may be unary or - binary, left- or right-associative. Parse actions can also be attached - to operator expressions. The generated parser will also recognize the use - of parentheses to override operator precedences (see example below). - - Note: if you define a deep operator list, you may see performance issues - when using infixNotation. See L{ParserElement.enablePackrat} for a - mechanism to potentially improve your parser performance. - - Parameters: - - baseExpr - expression representing the most basic element for the nested - - opList - list of tuples, one for each operator precedence level in the - expression grammar; each tuple is of the form - (opExpr, numTerms, rightLeftAssoc, parseAction), where: - - opExpr is the pyparsing expression for the operator; - may also be a string, which will be converted to a Literal; - if numTerms is 3, opExpr is a tuple of two expressions, for the - two operators separating the 3 terms - - numTerms is the number of terms for this operator (must - be 1, 2, or 3) - - rightLeftAssoc is the indicator whether the operator is - right or left associative, using the pyparsing-defined - constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. - - parseAction is the parse action to be associated with - expressions matching this operator expression (the - parse action tuple member may be omitted); if the parse action - is passed a tuple or list of functions, this is equivalent to - calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) - - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) - - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) - - Example:: - # simple example of four-function arithmetic with ints and variable names - integer = pyparsing_common.signed_integer - varname = pyparsing_common.identifier - - arith_expr = infixNotation(integer | varname, - [ - ('-', 1, opAssoc.RIGHT), - (oneOf('* /'), 2, opAssoc.LEFT), - (oneOf('+ -'), 2, opAssoc.LEFT), - ]) - - arith_expr.runTests(''' - 5+3*6 - (5+3)*6 - -2--11 - ''', fullDump=False) - prints:: - 5+3*6 - [[5, '+', [3, '*', 6]]] - - (5+3)*6 - [[[5, '+', 3], '*', 6]] - - -2--11 - [[['-', 2], '-', ['-', 11]]] - """ - ret = Forward() - lastExpr = baseExpr | ( lpar + ret + rpar ) - for i,operDef in enumerate(opList): - opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] - termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr - if arity == 3: - if opExpr is None or len(opExpr) != 2: - raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") - opExpr1, opExpr2 = opExpr - thisExpr = Forward().setName(termName) - if rightLeftAssoc == opAssoc.LEFT: - if arity == 1: - matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) - else: - matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ - Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - elif rightLeftAssoc == opAssoc.RIGHT: - if arity == 1: - # try to avoid LR with this extra test - if not isinstance(opExpr, Optional): - opExpr = Optional(opExpr) - matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) - else: - matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ - Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - else: - raise ValueError("operator must indicate right or left associativity") - if pa: - if isinstance(pa, (tuple, list)): - matchExpr.setParseAction(*pa) - else: - matchExpr.setParseAction(pa) - thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) - lastExpr = thisExpr - ret <<= lastExpr - return ret - -operatorPrecedence = infixNotation -"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release.""" - -dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") -sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") -quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| - Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") -unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") - -def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): - """ - Helper method for defining nested lists enclosed in opening and closing - delimiters ("(" and ")" are the default). - - Parameters: - - opener - opening character for a nested list (default=C{"("}); can also be a pyparsing expression - - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression - - content - expression for items within the nested lists (default=C{None}) - - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{quotedString}) - - If an expression is not provided for the content argument, the nested - expression will capture all whitespace-delimited content between delimiters - as a list of separate values. - - Use the C{ignoreExpr} argument to define expressions that may contain - opening or closing characters that should not be treated as opening - or closing characters for nesting, such as quotedString or a comment - expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. - The default is L{quotedString}, but if no expressions are to be ignored, - then pass C{None} for this argument. - - Example:: - data_type = oneOf("void int short long char float double") - decl_data_type = Combine(data_type + Optional(Word('*'))) - ident = Word(alphas+'_', alphanums+'_') - number = pyparsing_common.number - arg = Group(decl_data_type + ident) - LPAR,RPAR = map(Suppress, "()") - - code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) - - c_function = (decl_data_type("type") - + ident("name") - + LPAR + Optional(delimitedList(arg), [])("args") + RPAR - + code_body("body")) - c_function.ignore(cStyleComment) - - source_code = ''' - int is_odd(int x) { - return (x%2); - } - - int dec_to_hex(char hchar) { - if (hchar >= '0' && hchar <= '9') { - return (ord(hchar)-ord('0')); - } else { - return (10+ord(hchar)-ord('A')); - } - } - ''' - for func in c_function.searchString(source_code): - print("%(name)s (%(type)s) args: %(args)s" % func) - - prints:: - is_odd (int) args: [['int', 'x']] - dec_to_hex (int) args: [['char', 'hchar']] - """ - if opener == closer: - raise ValueError("opening and closing strings cannot be the same") - if content is None: - if isinstance(opener,basestring) and isinstance(closer,basestring): - if len(opener) == 1 and len(closer)==1: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS - ).setParseAction(lambda t:t[0].strip())) - else: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - ~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - raise ValueError("opening and closing arguments must be strings if no content expression is given") - ret = Forward() - if ignoreExpr is not None: - ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) - else: - ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) - ret.setName('nested %s%s expression' % (opener,closer)) - return ret - -def indentedBlock(blockStatementExpr, indentStack, indent=True): - """ - Helper method for defining space-delimited indentation blocks, such as - those used to define block statements in Python source code. - - Parameters: - - blockStatementExpr - expression defining syntax of statement that - is repeated within the indented block - - indentStack - list created by caller to manage indentation stack - (multiple statementWithIndentedBlock expressions within a single grammar - should share a common indentStack) - - indent - boolean indicating whether block must be indented beyond the - the current level; set to False for block of left-most statements - (default=C{True}) - - A valid block must contain at least one C{blockStatement}. - - Example:: - data = ''' - def A(z): - A1 - B = 100 - G = A2 - A2 - A3 - B - def BB(a,b,c): - BB1 - def BBA(): - bba1 - bba2 - bba3 - C - D - def spam(x,y): - def eggs(z): - pass - ''' - - - indentStack = [1] - stmt = Forward() - - identifier = Word(alphas, alphanums) - funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") - func_body = indentedBlock(stmt, indentStack) - funcDef = Group( funcDecl + func_body ) - - rvalue = Forward() - funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") - rvalue << (funcCall | identifier | Word(nums)) - assignment = Group(identifier + "=" + rvalue) - stmt << ( funcDef | assignment | identifier ) - - module_body = OneOrMore(stmt) - - parseTree = module_body.parseString(data) - parseTree.pprint() - prints:: - [['def', - 'A', - ['(', 'z', ')'], - ':', - [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], - 'B', - ['def', - 'BB', - ['(', 'a', 'b', 'c', ')'], - ':', - [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], - 'C', - 'D', - ['def', - 'spam', - ['(', 'x', 'y', ')'], - ':', - [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] - """ - def checkPeerIndent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if curCol != indentStack[-1]: - if curCol > indentStack[-1]: - raise ParseFatalException(s,l,"illegal nesting") - raise ParseException(s,l,"not a peer entry") - - def checkSubIndent(s,l,t): - curCol = col(l,s) - if curCol > indentStack[-1]: - indentStack.append( curCol ) - else: - raise ParseException(s,l,"not a subentry") - - def checkUnindent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): - raise ParseException(s,l,"not an unindent") - indentStack.pop() - - NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) - INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') - PEER = Empty().setParseAction(checkPeerIndent).setName('') - UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') - if indent: - smExpr = Group( Optional(NL) + - #~ FollowedBy(blockStatementExpr) + - INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) - else: - smExpr = Group( Optional(NL) + - (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) - blockStatementExpr.ignore(_bslash + LineEnd()) - return smExpr.setName('indented block') - -alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") -punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") - -anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) -_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) -commonHTMLEntity = Regex('&(?P<entity>' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") -def replaceHTMLEntity(t): - """Helper parser action to replace common HTML entities with their special characters""" - return _htmlEntityMap.get(t.entity) - -# it's easy to get these comment structures wrong - they're very common, so may as well make them available -cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") -"Comment of the form C{/* ... */}" - -htmlComment = Regex(r"<!--[\s\S]*?-->").setName("HTML comment") -"Comment of the form C{<!-- ... -->}" - -restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") -dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") -"Comment of the form C{// ... (to end of line)}" - -cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") -"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}" - -javaStyleComment = cppStyleComment -"Same as C{L{cppStyleComment}}" - -pythonStyleComment = Regex(r"#.*").setName("Python style comment") -"Comment of the form C{# ... (to end of line)}" - -_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + - Optional( Word(" \t") + - ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") -commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") -"""(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. - This expression is deprecated in favor of L{pyparsing_common.comma_separated_list}.""" - -# some other useful expressions - using lower-case class name since we are really using this as a namespace -class pyparsing_common: - """ - Here are some common low-level expressions that may be useful in jump-starting parser development: - - numeric forms (L{integers<integer>}, L{reals<real>}, L{scientific notation<sci_real>}) - - common L{programming identifiers<identifier>} - - network addresses (L{MAC<mac_address>}, L{IPv4<ipv4_address>}, L{IPv6<ipv6_address>}) - - ISO8601 L{dates<iso8601_date>} and L{datetime<iso8601_datetime>} - - L{UUID<uuid>} - - L{comma-separated list<comma_separated_list>} - Parse actions: - - C{L{convertToInteger}} - - C{L{convertToFloat}} - - C{L{convertToDate}} - - C{L{convertToDatetime}} - - C{L{stripHTMLTags}} - - C{L{upcaseTokens}} - - C{L{downcaseTokens}} - - Example:: - pyparsing_common.number.runTests(''' - # any int or real number, returned as the appropriate type - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - ''') - - pyparsing_common.fnumber.runTests(''' - # any int or real number, returned as float - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - ''') - - pyparsing_common.hex_integer.runTests(''' - # hex numbers - 100 - FF - ''') - - pyparsing_common.fraction.runTests(''' - # fractions - 1/2 - -3/4 - ''') - - pyparsing_common.mixed_integer.runTests(''' - # mixed fractions - 1 - 1/2 - -3/4 - 1-3/4 - ''') - - import uuid - pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) - pyparsing_common.uuid.runTests(''' - # uuid - 12345678-1234-5678-1234-567812345678 - ''') - prints:: - # any int or real number, returned as the appropriate type - 100 - [100] - - -100 - [-100] - - +100 - [100] - - 3.14159 - [3.14159] - - 6.02e23 - [6.02e+23] - - 1e-12 - [1e-12] - - # any int or real number, returned as float - 100 - [100.0] - - -100 - [-100.0] - - +100 - [100.0] - - 3.14159 - [3.14159] - - 6.02e23 - [6.02e+23] - - 1e-12 - [1e-12] - - # hex numbers - 100 - [256] - - FF - [255] - - # fractions - 1/2 - [0.5] - - -3/4 - [-0.75] - - # mixed fractions - 1 - [1] - - 1/2 - [0.5] - - -3/4 - [-0.75] - - 1-3/4 - [1.75] - - # uuid - 12345678-1234-5678-1234-567812345678 - [UUID('12345678-1234-5678-1234-567812345678')] - """ - - convertToInteger = tokenMap(int) - """ - Parse action for converting parsed integers to Python int - """ - - convertToFloat = tokenMap(float) - """ - Parse action for converting parsed numbers to Python float - """ - - integer = Word(nums).setName("integer").setParseAction(convertToInteger) - """expression that parses an unsigned integer, returns an int""" - - hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) - """expression that parses a hexadecimal integer, returns an int""" - - signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) - """expression that parses an integer with optional leading sign, returns an int""" - - fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") - """fractional expression of an integer divided by an integer, returns a float""" - fraction.addParseAction(lambda t: t[0]/t[-1]) - - mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") - """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" - mixed_integer.addParseAction(sum) - - real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) - """expression that parses a floating point number and returns a float""" - - sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) - """expression that parses a floating point number with optional scientific notation and returns a float""" - - # streamlining this expression makes the docs nicer-looking - number = (sci_real | real | signed_integer).streamline() - """any numeric expression, returns the corresponding Python type""" - - fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) - """any int or real number, returned as float""" - - identifier = Word(alphas+'_', alphanums+'_').setName("identifier") - """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" - - ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") - "IPv4 address (C{0.0.0.0 - 255.255.255.255})" - - _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") - _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") - _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") - _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) - _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") - ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") - "IPv6 address (long, short, or mixed form)" - - mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") - "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" - - @staticmethod - def convertToDate(fmt="%Y-%m-%d"): - """ - Helper to create a parse action for converting parsed date string to Python datetime.date - - Params - - - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"}) - - Example:: - date_expr = pyparsing_common.iso8601_date.copy() - date_expr.setParseAction(pyparsing_common.convertToDate()) - print(date_expr.parseString("1999-12-31")) - prints:: - [datetime.date(1999, 12, 31)] - """ - def cvt_fn(s,l,t): - try: - return datetime.strptime(t[0], fmt).date() - except ValueError as ve: - raise ParseException(s, l, str(ve)) - return cvt_fn - - @staticmethod - def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): - """ - Helper to create a parse action for converting parsed datetime string to Python datetime.datetime - - Params - - - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"}) - - Example:: - dt_expr = pyparsing_common.iso8601_datetime.copy() - dt_expr.setParseAction(pyparsing_common.convertToDatetime()) - print(dt_expr.parseString("1999-12-31T23:59:59.999")) - prints:: - [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] - """ - def cvt_fn(s,l,t): - try: - return datetime.strptime(t[0], fmt) - except ValueError as ve: - raise ParseException(s, l, str(ve)) - return cvt_fn - - iso8601_date = Regex(r'(?P<year>\d{4})(?:-(?P<month>\d\d)(?:-(?P<day>\d\d))?)?').setName("ISO8601 date") - "ISO8601 date (C{yyyy-mm-dd})" - - iso8601_datetime = Regex(r'(?P<year>\d{4})-(?P<month>\d\d)-(?P<day>\d\d)[T ](?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d(\.\d*)?)?)?(?P<tz>Z|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") - "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}" - - uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") - "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})" - - _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() - @staticmethod - def stripHTMLTags(s, l, tokens): - """ - Parse action to remove HTML tags from web page HTML source - - Example:: - # strip HTML links from normal text - text = '<td>More info at the <a href="http://pyparsing.wikispaces.com">pyparsing</a> wiki page</td>' - td,td_end = makeHTMLTags("TD") - table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end - - print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page' - """ - return pyparsing_common._html_stripper.transformString(tokens[0]) - - _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') - + Optional( White(" \t") ) ) ).streamline().setName("commaItem") - comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") - """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" - - upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) - """Parse action to convert tokens to upper case.""" - - downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) - """Parse action to convert tokens to lower case.""" - - -if __name__ == "__main__": - - selectToken = CaselessLiteral("select") - fromToken = CaselessLiteral("from") - - ident = Word(alphas, alphanums + "_$") - - columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) - columnNameList = Group(delimitedList(columnName)).setName("columns") - columnSpec = ('*' | columnNameList) - - tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) - tableNameList = Group(delimitedList(tableName)).setName("tables") - - simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") - - # demo runTests method, including embedded comments in test string - simpleSQL.runTests(""" - # '*' as column list and dotted table name - select * from SYS.XYZZY - - # caseless match on "SELECT", and casts back to "select" - SELECT * from XYZZY, ABC - - # list of column names, and mixed case SELECT keyword - Select AA,BB,CC from Sys.dual - - # multiple tables - Select A, B, C from Sys.dual, Table2 - - # invalid SELECT keyword - should fail - Xelect A, B, C from Sys.dual - - # incomplete command - should fail - Select - - # invalid column name - should fail - Select ^^^ frox Sys.dual - - """) - - pyparsing_common.number.runTests(""" - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - """) - - # any int or real number, returned as float - pyparsing_common.fnumber.runTests(""" - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - """) - - pyparsing_common.hex_integer.runTests(""" - 100 - FF - """) - - import uuid - pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) - pyparsing_common.uuid.runTests(""" - 12345678-1234-5678-1234-567812345678 - """) diff --git a/lib/pkg_resources/_vendor/six.py b/lib/pkg_resources/_vendor/six.py deleted file mode 100644 index 190c023..0000000 --- a/lib/pkg_resources/_vendor/six.py +++ /dev/null @@ -1,868 +0,0 @@ -"""Utilities for writing code that runs on Python 2 and 3""" - -# Copyright (c) 2010-2015 Benjamin Peterson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import absolute_import - -import functools -import itertools -import operator -import sys -import types - -__author__ = "Benjamin Peterson <benjamin@python.org>" -__version__ = "1.10.0" - - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -PY34 = sys.version_info[0:2] >= (3, 4) - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) # Invokes __set__. - try: - # This is a bit ugly, but it avoids running this again by - # removing this descriptor. - delattr(obj.__class__, self.name) - except AttributeError: - pass - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - def __getattr__(self, attr): - _module = self._resolve() - value = getattr(_module, attr) - setattr(self, attr, value) - return value - - -class _LazyModule(types.ModuleType): - - def __init__(self, name): - super(_LazyModule, self).__init__(name) - self.__doc__ = self.__class__.__doc__ - - def __dir__(self): - attrs = ["__doc__", "__name__"] - attrs += [attr.name for attr in self._moved_attributes] - return attrs - - # Subclasses should override this - _moved_attributes = [] - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _SixMetaPathImporter(object): - - """ - A meta path importer to import six.moves and its submodules. - - This class implements a PEP302 finder and loader. It should be compatible - with Python 2.5 and all existing versions of Python3 - """ - - def __init__(self, six_module_name): - self.name = six_module_name - self.known_modules = {} - - def _add_module(self, mod, *fullnames): - for fullname in fullnames: - self.known_modules[self.name + "." + fullname] = mod - - def _get_module(self, fullname): - return self.known_modules[self.name + "." + fullname] - - def find_module(self, fullname, path=None): - if fullname in self.known_modules: - return self - return None - - def __get_module(self, fullname): - try: - return self.known_modules[fullname] - except KeyError: - raise ImportError("This loader does not know module " + fullname) - - def load_module(self, fullname): - try: - # in case of a reload - return sys.modules[fullname] - except KeyError: - pass - mod = self.__get_module(fullname) - if isinstance(mod, MovedModule): - mod = mod._resolve() - else: - mod.__loader__ = self - sys.modules[fullname] = mod - return mod - - def is_package(self, fullname): - """ - Return true, if the named module is a package. - - We need this method to get correct spec objects with - Python 3.4 (see PEP451) - """ - return hasattr(self.__get_module(fullname), "__path__") - - def get_code(self, fullname): - """Return None - - Required, if is_package is implemented""" - self.__get_module(fullname) # eventually raises ImportError - return None - get_source = get_code # same as get_code - -_importer = _SixMetaPathImporter(__name__) - - -class _MovedItems(_LazyModule): - - """Lazy loading of moved objects""" - __path__ = [] # mark as package - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("intern", "__builtin__", "sys"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), - MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), - MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("UserDict", "UserDict", "collections"), - MovedAttribute("UserList", "UserList", "collections"), - MovedAttribute("UserString", "UserString", "collections"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("copyreg", "copy_reg"), - MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("_thread", "thread", "_thread"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), - MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), - MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), - MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), -] -# Add windows specific modules. -if sys.platform == "win32": - _moved_attributes += [ - MovedModule("winreg", "_winreg"), - ] - -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) - if isinstance(attr, MovedModule): - _importer._add_module(attr, "moves." + attr.name) -del attr - -_MovedItems._moved_attributes = _moved_attributes - -moves = _MovedItems(__name__ + ".moves") -_importer._add_module(moves, "moves") - - -class Module_six_moves_urllib_parse(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_parse""" - - -_urllib_parse_moved_attributes = [ - MovedAttribute("ParseResult", "urlparse", "urllib.parse"), - MovedAttribute("SplitResult", "urlparse", "urllib.parse"), - MovedAttribute("parse_qs", "urlparse", "urllib.parse"), - MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), - MovedAttribute("urldefrag", "urlparse", "urllib.parse"), - MovedAttribute("urljoin", "urlparse", "urllib.parse"), - MovedAttribute("urlparse", "urlparse", "urllib.parse"), - MovedAttribute("urlsplit", "urlparse", "urllib.parse"), - MovedAttribute("urlunparse", "urlparse", "urllib.parse"), - MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), - MovedAttribute("quote", "urllib", "urllib.parse"), - MovedAttribute("quote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote", "urllib", "urllib.parse"), - MovedAttribute("unquote_plus", "urllib", "urllib.parse"), - MovedAttribute("urlencode", "urllib", "urllib.parse"), - MovedAttribute("splitquery", "urllib", "urllib.parse"), - MovedAttribute("splittag", "urllib", "urllib.parse"), - MovedAttribute("splituser", "urllib", "urllib.parse"), - MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), - MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), - MovedAttribute("uses_params", "urlparse", "urllib.parse"), - MovedAttribute("uses_query", "urlparse", "urllib.parse"), - MovedAttribute("uses_relative", "urlparse", "urllib.parse"), -] -for attr in _urllib_parse_moved_attributes: - setattr(Module_six_moves_urllib_parse, attr.name, attr) -del attr - -Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes - -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") - - -class Module_six_moves_urllib_error(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_error""" - - -_urllib_error_moved_attributes = [ - MovedAttribute("URLError", "urllib2", "urllib.error"), - MovedAttribute("HTTPError", "urllib2", "urllib.error"), - MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), -] -for attr in _urllib_error_moved_attributes: - setattr(Module_six_moves_urllib_error, attr.name, attr) -del attr - -Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes - -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") - - -class Module_six_moves_urllib_request(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_request""" - - -_urllib_request_moved_attributes = [ - MovedAttribute("urlopen", "urllib2", "urllib.request"), - MovedAttribute("install_opener", "urllib2", "urllib.request"), - MovedAttribute("build_opener", "urllib2", "urllib.request"), - MovedAttribute("pathname2url", "urllib", "urllib.request"), - MovedAttribute("url2pathname", "urllib", "urllib.request"), - MovedAttribute("getproxies", "urllib", "urllib.request"), - MovedAttribute("Request", "urllib2", "urllib.request"), - MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), - MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), - MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), - MovedAttribute("BaseHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), - MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), - MovedAttribute("FileHandler", "urllib2", "urllib.request"), - MovedAttribute("FTPHandler", "urllib2", "urllib.request"), - MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), - MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), - MovedAttribute("urlretrieve", "urllib", "urllib.request"), - MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), - MovedAttribute("proxy_bypass", "urllib", "urllib.request"), -] -for attr in _urllib_request_moved_attributes: - setattr(Module_six_moves_urllib_request, attr.name, attr) -del attr - -Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes - -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") - - -class Module_six_moves_urllib_response(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_response""" - - -_urllib_response_moved_attributes = [ - MovedAttribute("addbase", "urllib", "urllib.response"), - MovedAttribute("addclosehook", "urllib", "urllib.response"), - MovedAttribute("addinfo", "urllib", "urllib.response"), - MovedAttribute("addinfourl", "urllib", "urllib.response"), -] -for attr in _urllib_response_moved_attributes: - setattr(Module_six_moves_urllib_response, attr.name, attr) -del attr - -Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes - -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") - - -class Module_six_moves_urllib_robotparser(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_robotparser""" - - -_urllib_robotparser_moved_attributes = [ - MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), -] -for attr in _urllib_robotparser_moved_attributes: - setattr(Module_six_moves_urllib_robotparser, attr.name, attr) -del attr - -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes - -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") - - -class Module_six_moves_urllib(types.ModuleType): - - """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" - __path__ = [] # mark as package - parse = _importer._get_module("moves.urllib_parse") - error = _importer._get_module("moves.urllib_error") - request = _importer._get_module("moves.urllib_request") - response = _importer._get_module("moves.urllib_response") - robotparser = _importer._get_module("moves.urllib_robotparser") - - def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] - -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - - -if PY3: - def get_unbound_function(unbound): - return unbound - - create_bound_method = types.MethodType - - def create_unbound_method(func, cls): - return func - - Iterator = object -else: - def get_unbound_function(unbound): - return unbound.im_func - - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - - def create_unbound_method(func, cls): - return types.MethodType(func, None, cls) - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -if PY3: - def iterkeys(d, **kw): - return iter(d.keys(**kw)) - - def itervalues(d, **kw): - return iter(d.values(**kw)) - - def iteritems(d, **kw): - return iter(d.items(**kw)) - - def iterlists(d, **kw): - return iter(d.lists(**kw)) - - viewkeys = operator.methodcaller("keys") - - viewvalues = operator.methodcaller("values") - - viewitems = operator.methodcaller("items") -else: - def iterkeys(d, **kw): - return d.iterkeys(**kw) - - def itervalues(d, **kw): - return d.itervalues(**kw) - - def iteritems(d, **kw): - return d.iteritems(**kw) - - def iterlists(d, **kw): - return d.iterlists(**kw) - - viewkeys = operator.methodcaller("viewkeys") - - viewvalues = operator.methodcaller("viewvalues") - - viewitems = operator.methodcaller("viewitems") - -_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") -_add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - unichr = chr - import struct - int2byte = struct.Struct(">B").pack - del struct - byte2int = operator.itemgetter(0) - indexbytes = operator.getitem - iterbytes = iter - import io - StringIO = io.StringIO - BytesIO = io.BytesIO - _assertCountEqual = "assertCountEqual" - if sys.version_info[1] <= 1: - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - else: - _assertRaisesRegex = "assertRaisesRegex" - _assertRegex = "assertRegex" -else: - def b(s): - return s - # Workaround for standalone backslash - - def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") - unichr = unichr - int2byte = chr - - def byte2int(bs): - return ord(bs[0]) - - def indexbytes(buf, i): - return ord(buf[i]) - iterbytes = functools.partial(itertools.imap, ord) - import StringIO - StringIO = BytesIO = StringIO.StringIO - _assertCountEqual = "assertItemsEqual" - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -def assertCountEqual(self, *args, **kwargs): - return getattr(self, _assertCountEqual)(*args, **kwargs) - - -def assertRaisesRegex(self, *args, **kwargs): - return getattr(self, _assertRaisesRegex)(*args, **kwargs) - - -def assertRegex(self, *args, **kwargs): - return getattr(self, _assertRegex)(*args, **kwargs) - - -if PY3: - exec_ = getattr(moves.builtins, "exec") - - def reraise(tp, value, tb=None): - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") - - -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - if from_value is None: - raise value - raise value from from_value -""") -elif sys.version_info[:2] > (3, 2): - exec_("""def raise_from(value, from_value): - raise value from from_value -""") -else: - def raise_from(value, from_value): - raise value - - -print_ = getattr(moves.builtins, "print", None) -if print_ is None: - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(fp.encoding, errors) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) -if sys.version_info[:2] < (3, 3): - _print = print_ - - def print_(*args, **kwargs): - fp = kwargs.get("file", sys.stdout) - flush = kwargs.pop("flush", False) - _print(*args, **kwargs) - if flush and fp is not None: - fp.flush() - -_add_doc(reraise, """Reraise an exception.""") - -if sys.version_info[0:2] < (3, 4): - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f - return wrapper -else: - wraps = functools.wraps - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(meta): - - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -def python_2_unicode_compatible(klass): - """ - A decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - """ - if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass - - -# Complete the moves implementation. -# This code is at the end of this module to speed up module loading. -# Turn this module into a package. -__path__ = [] # required for PEP 302 and PEP 451 -__package__ = __name__ # see PEP 366 @ReservedAssignment -if globals().get("__spec__") is not None: - __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable -# Remove other six meta path importers, since they cause problems. This can -# happen if six is removed from sys.modules and then reloaded. (Setuptools does -# this for some reason.) -if sys.meta_path: - for i, importer in enumerate(sys.meta_path): - # Here's some real nastiness: Another "instance" of the six module might - # be floating around. Therefore, we can't use isinstance() to check for - # the six meta path importer, since the other six instance will have - # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): - del sys.meta_path[i] - break - del i, importer -# Finally, add the importer to the meta path import hook. -sys.meta_path.append(_importer) diff --git a/lib/pkg_resources/extern/__init__.py b/lib/pkg_resources/extern/__init__.py deleted file mode 100644 index c1eb9e9..0000000 --- a/lib/pkg_resources/extern/__init__.py +++ /dev/null @@ -1,73 +0,0 @@ -import sys - - -class VendorImporter: - """ - A PEP 302 meta path importer for finding optionally-vendored - or otherwise naturally-installed packages from root_name. - """ - - def __init__(self, root_name, vendored_names=(), vendor_pkg=None): - self.root_name = root_name - self.vendored_names = set(vendored_names) - self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') - - @property - def search_path(self): - """ - Search first the vendor package then as a natural package. - """ - yield self.vendor_pkg + '.' - yield '' - - def find_module(self, fullname, path=None): - """ - Return self when fullname starts with root_name and the - target module is one vendored through this importer. - """ - root, base, target = fullname.partition(self.root_name + '.') - if root: - return - if not any(map(target.startswith, self.vendored_names)): - return - return self - - def load_module(self, fullname): - """ - Iterate over the search path to locate and load fullname. - """ - root, base, target = fullname.partition(self.root_name + '.') - for prefix in self.search_path: - try: - extant = prefix + target - __import__(extant) - mod = sys.modules[extant] - sys.modules[fullname] = mod - # mysterious hack: - # Remove the reference to the extant package/module - # on later Python versions to cause relative imports - # in the vendor package to resolve the same modules - # as those going through this importer. - if prefix and sys.version_info > (3, 3): - del sys.modules[extant] - return mod - except ImportError: - pass - else: - raise ImportError( - "The '{target}' package is required; " - "normally this is bundled with this package so if you get " - "this warning, consult the packager of your " - "distribution.".format(**locals()) - ) - - def install(self): - """ - Install this importer into sys.meta_path if not already present. - """ - if self not in sys.meta_path: - sys.meta_path.append(self) - - -names = 'packaging', 'pyparsing', 'six', 'appdirs' -VendorImporter(__name__, names).install() diff --git a/lib/pkg_resources/py31compat.py b/lib/pkg_resources/py31compat.py deleted file mode 100644 index a381c42..0000000 --- a/lib/pkg_resources/py31compat.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -import errno -import sys - -from .extern import six - - -def _makedirs_31(path, exist_ok=False): - try: - os.makedirs(path) - except OSError as exc: - if not exist_ok or exc.errno != errno.EEXIST: - raise - - -# rely on compatibility behavior until mode considerations -# and exists_ok considerations are disentangled. -# See https://github.com/pypa/setuptools/pull/1083#issuecomment-315168663 -needs_makedirs = ( - six.PY2 or - (3, 4) <= sys.version_info < (3, 4, 1) -) -makedirs = _makedirs_31 if needs_makedirs else os.makedirs diff --git a/lib/pytz/__init__.py b/lib/pytz/__init__.py deleted file mode 100644 index 6e92317..0000000 --- a/lib/pytz/__init__.py +++ /dev/null @@ -1,1527 +0,0 @@ -''' -datetime.tzinfo timezone definitions generated from the -Olson timezone database: - - ftp://elsie.nci.nih.gov/pub/tz*.tar.gz - -See the datetime section of the Python Library Reference for information -on how to use these modules. -''' - -import sys -import datetime -import os.path - -from pytz.exceptions import AmbiguousTimeError -from pytz.exceptions import InvalidTimeError -from pytz.exceptions import NonExistentTimeError -from pytz.exceptions import UnknownTimeZoneError -from pytz.lazy import LazyDict, LazyList, LazySet -from pytz.tzinfo import unpickler, BaseTzInfo -from pytz.tzfile import build_tzinfo - - -# The IANA (nee Olson) database is updated several times a year. -OLSON_VERSION = '2018g' -VERSION = '2018.7' # pip compatible version number. -__version__ = VERSION - -OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling - -__all__ = [ - 'timezone', 'utc', 'country_timezones', 'country_names', - 'AmbiguousTimeError', 'InvalidTimeError', - 'NonExistentTimeError', 'UnknownTimeZoneError', - 'all_timezones', 'all_timezones_set', - 'common_timezones', 'common_timezones_set', - 'BaseTzInfo', -] - - -if sys.version_info[0] > 2: # Python 3.x - - # Python 3.x doesn't have unicode(), making writing code - # for Python 2.3 and Python 3.x a pain. - unicode = str - - def ascii(s): - r""" - >>> ascii('Hello') - 'Hello' - >>> ascii('\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - UnicodeEncodeError: ... - """ - if type(s) == bytes: - s = s.decode('ASCII') - else: - s.encode('ASCII') # Raise an exception if not ASCII - return s # But the string - not a byte string. - -else: # Python 2.x - - def ascii(s): - r""" - >>> ascii('Hello') - 'Hello' - >>> ascii(u'Hello') - 'Hello' - >>> ascii(u'\N{TRADE MARK SIGN}') #doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - UnicodeEncodeError: ... - """ - return s.encode('ASCII') - - -def open_resource(name): - """Open a resource from the zoneinfo subdir for reading. - - Uses the pkg_resources module if available and no standard file - found at the calculated location. - - It is possible to specify different location for zoneinfo - subdir by using the PYTZ_TZDATADIR environment variable. - """ - name_parts = name.lstrip('/').split('/') - for part in name_parts: - if part == os.path.pardir or os.path.sep in part: - raise ValueError('Bad path segment: %r' % part) - zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None) - if zoneinfo_dir is not None: - filename = os.path.join(zoneinfo_dir, *name_parts) - else: - filename = os.path.join(os.path.dirname(__file__), - 'zoneinfo', *name_parts) - if not os.path.exists(filename): - # http://bugs.launchpad.net/bugs/383171 - we avoid using this - # unless absolutely necessary to help when a broken version of - # pkg_resources is installed. - try: - from pkg_resources import resource_stream - except ImportError: - resource_stream = None - - if resource_stream is not None: - return resource_stream(__name__, 'zoneinfo/' + name) - return open(filename, 'rb') - - -def resource_exists(name): - """Return true if the given resource exists""" - try: - open_resource(name).close() - return True - except IOError: - return False - - -_tzinfo_cache = {} - - -def timezone(zone): - r''' Return a datetime.tzinfo implementation for the given timezone - - >>> from datetime import datetime, timedelta - >>> utc = timezone('UTC') - >>> eastern = timezone('US/Eastern') - >>> eastern.zone - 'US/Eastern' - >>> timezone(unicode('US/Eastern')) is eastern - True - >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) - >>> loc_dt = utc_dt.astimezone(eastern) - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - >>> loc_dt.strftime(fmt) - '2002-10-27 01:00:00 EST (-0500)' - >>> (loc_dt - timedelta(minutes=10)).strftime(fmt) - '2002-10-27 00:50:00 EST (-0500)' - >>> eastern.normalize(loc_dt - timedelta(minutes=10)).strftime(fmt) - '2002-10-27 01:50:00 EDT (-0400)' - >>> (loc_dt + timedelta(minutes=10)).strftime(fmt) - '2002-10-27 01:10:00 EST (-0500)' - - Raises UnknownTimeZoneError if passed an unknown zone. - - >>> try: - ... timezone('Asia/Shangri-La') - ... except UnknownTimeZoneError: - ... print('Unknown') - Unknown - - >>> try: - ... timezone(unicode('\N{TRADE MARK SIGN}')) - ... except UnknownTimeZoneError: - ... print('Unknown') - Unknown - - ''' - if zone.upper() == 'UTC': - return utc - - try: - zone = ascii(zone) - except UnicodeEncodeError: - # All valid timezones are ASCII - raise UnknownTimeZoneError(zone) - - zone = _unmunge_zone(zone) - if zone not in _tzinfo_cache: - if zone in all_timezones_set: - fp = open_resource(zone) - try: - _tzinfo_cache[zone] = build_tzinfo(zone, fp) - finally: - fp.close() - else: - raise UnknownTimeZoneError(zone) - - return _tzinfo_cache[zone] - - -def _unmunge_zone(zone): - """Undo the time zone name munging done by older versions of pytz.""" - return zone.replace('_plus_', '+').replace('_minus_', '-') - - -ZERO = datetime.timedelta(0) -HOUR = datetime.timedelta(hours=1) - - -class UTC(BaseTzInfo): - """UTC - - Optimized UTC implementation. It unpickles using the single module global - instance defined beneath this class declaration. - """ - zone = "UTC" - - _utcoffset = ZERO - _dst = ZERO - _tzname = zone - - def fromutc(self, dt): - if dt.tzinfo is None: - return self.localize(dt) - return super(utc.__class__, self).fromutc(dt) - - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return ZERO - - def __reduce__(self): - return _UTC, () - - def localize(self, dt, is_dst=False): - '''Convert naive time to local time''' - if dt.tzinfo is not None: - raise ValueError('Not naive datetime (tzinfo is already set)') - return dt.replace(tzinfo=self) - - def normalize(self, dt, is_dst=False): - '''Correct the timezone information on the given datetime''' - if dt.tzinfo is self: - return dt - if dt.tzinfo is None: - raise ValueError('Naive time - no tzinfo set') - return dt.astimezone(self) - - def __repr__(self): - return "<UTC>" - - def __str__(self): - return "UTC" - - -UTC = utc = UTC() # UTC is a singleton - - -def _UTC(): - """Factory function for utc unpickling. - - Makes sure that unpickling a utc instance always returns the same - module global. - - These examples belong in the UTC class above, but it is obscured; or in - the README.txt, but we are not depending on Python 2.4 so integrating - the README.txt examples with the unit tests is not trivial. - - >>> import datetime, pickle - >>> dt = datetime.datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc) - >>> naive = dt.replace(tzinfo=None) - >>> p = pickle.dumps(dt, 1) - >>> naive_p = pickle.dumps(naive, 1) - >>> len(p) - len(naive_p) - 17 - >>> new = pickle.loads(p) - >>> new == dt - True - >>> new is dt - False - >>> new.tzinfo is dt.tzinfo - True - >>> utc is UTC is timezone('UTC') - True - >>> utc is timezone('GMT') - False - """ - return utc -_UTC.__safe_for_unpickling__ = True - - -def _p(*args): - """Factory function for unpickling pytz tzinfo instances. - - Just a wrapper around tzinfo.unpickler to save a few bytes in each pickle - by shortening the path. - """ - return unpickler(*args) -_p.__safe_for_unpickling__ = True - - -class _CountryTimezoneDict(LazyDict): - """Map ISO 3166 country code to a list of timezone names commonly used - in that country. - - iso3166_code is the two letter code used to identify the country. - - >>> def print_list(list_of_strings): - ... 'We use a helper so doctests work under Python 2.3 -> 3.x' - ... for s in list_of_strings: - ... print(s) - - >>> print_list(country_timezones['nz']) - Pacific/Auckland - Pacific/Chatham - >>> print_list(country_timezones['ch']) - Europe/Zurich - >>> print_list(country_timezones['CH']) - Europe/Zurich - >>> print_list(country_timezones[unicode('ch')]) - Europe/Zurich - >>> print_list(country_timezones['XXX']) - Traceback (most recent call last): - ... - KeyError: 'XXX' - - Previously, this information was exposed as a function rather than a - dictionary. This is still supported:: - - >>> print_list(country_timezones('nz')) - Pacific/Auckland - Pacific/Chatham - """ - def __call__(self, iso3166_code): - """Backwards compatibility.""" - return self[iso3166_code] - - def _fill(self): - data = {} - zone_tab = open_resource('zone.tab') - try: - for line in zone_tab: - line = line.decode('UTF-8') - if line.startswith('#'): - continue - code, coordinates, zone = line.split(None, 4)[:3] - if zone not in all_timezones_set: - continue - try: - data[code].append(zone) - except KeyError: - data[code] = [zone] - self.data = data - finally: - zone_tab.close() - -country_timezones = _CountryTimezoneDict() - - -class _CountryNameDict(LazyDict): - '''Dictionary proving ISO3166 code -> English name. - - >>> print(country_names['au']) - Australia - ''' - def _fill(self): - data = {} - zone_tab = open_resource('iso3166.tab') - try: - for line in zone_tab.readlines(): - line = line.decode('UTF-8') - if line.startswith('#'): - continue - code, name = line.split(None, 1) - data[code] = name.strip() - self.data = data - finally: - zone_tab.close() - -country_names = _CountryNameDict() - - -# Time-zone info based solely on fixed offsets - -class _FixedOffset(datetime.tzinfo): - - zone = None # to match the standard pytz API - - def __init__(self, minutes): - if abs(minutes) >= 1440: - raise ValueError("absolute offset is too large", minutes) - self._minutes = minutes - self._offset = datetime.timedelta(minutes=minutes) - - def utcoffset(self, dt): - return self._offset - - def __reduce__(self): - return FixedOffset, (self._minutes, ) - - def dst(self, dt): - return ZERO - - def tzname(self, dt): - return None - - def __repr__(self): - return 'pytz.FixedOffset(%d)' % self._minutes - - def localize(self, dt, is_dst=False): - '''Convert naive time to local time''' - if dt.tzinfo is not None: - raise ValueError('Not naive datetime (tzinfo is already set)') - return dt.replace(tzinfo=self) - - def normalize(self, dt, is_dst=False): - '''Correct the timezone information on the given datetime''' - if dt.tzinfo is self: - return dt - if dt.tzinfo is None: - raise ValueError('Naive time - no tzinfo set') - return dt.astimezone(self) - - -def FixedOffset(offset, _tzinfos={}): - """return a fixed-offset timezone based off a number of minutes. - - >>> one = FixedOffset(-330) - >>> one - pytz.FixedOffset(-330) - >>> str(one.utcoffset(datetime.datetime.now())) - '-1 day, 18:30:00' - >>> str(one.dst(datetime.datetime.now())) - '0:00:00' - - >>> two = FixedOffset(1380) - >>> two - pytz.FixedOffset(1380) - >>> str(two.utcoffset(datetime.datetime.now())) - '23:00:00' - >>> str(two.dst(datetime.datetime.now())) - '0:00:00' - - The datetime.timedelta must be between the range of -1 and 1 day, - non-inclusive. - - >>> FixedOffset(1440) - Traceback (most recent call last): - ... - ValueError: ('absolute offset is too large', 1440) - - >>> FixedOffset(-1440) - Traceback (most recent call last): - ... - ValueError: ('absolute offset is too large', -1440) - - An offset of 0 is special-cased to return UTC. - - >>> FixedOffset(0) is UTC - True - - There should always be only one instance of a FixedOffset per timedelta. - This should be true for multiple creation calls. - - >>> FixedOffset(-330) is one - True - >>> FixedOffset(1380) is two - True - - It should also be true for pickling. - - >>> import pickle - >>> pickle.loads(pickle.dumps(one)) is one - True - >>> pickle.loads(pickle.dumps(two)) is two - True - """ - if offset == 0: - return UTC - - info = _tzinfos.get(offset) - if info is None: - # We haven't seen this one before. we need to save it. - - # Use setdefault to avoid a race condition and make sure we have - # only one - info = _tzinfos.setdefault(offset, _FixedOffset(offset)) - - return info - -FixedOffset.__safe_for_unpickling__ = True - - -def _test(): - import doctest - sys.path.insert(0, os.pardir) - import pytz - return doctest.testmod(pytz) - -if __name__ == '__main__': - _test() -all_timezones = \ -['Africa/Abidjan', - 'Africa/Accra', - 'Africa/Addis_Ababa', - 'Africa/Algiers', - 'Africa/Asmara', - 'Africa/Asmera', - 'Africa/Bamako', - 'Africa/Bangui', - 'Africa/Banjul', - 'Africa/Bissau', - 'Africa/Blantyre', - 'Africa/Brazzaville', - 'Africa/Bujumbura', - 'Africa/Cairo', - 'Africa/Casablanca', - 'Africa/Ceuta', - 'Africa/Conakry', - 'Africa/Dakar', - 'Africa/Dar_es_Salaam', - 'Africa/Djibouti', - 'Africa/Douala', - 'Africa/El_Aaiun', - 'Africa/Freetown', - 'Africa/Gaborone', - 'Africa/Harare', - 'Africa/Johannesburg', - 'Africa/Juba', - 'Africa/Kampala', - 'Africa/Khartoum', - 'Africa/Kigali', - 'Africa/Kinshasa', - 'Africa/Lagos', - 'Africa/Libreville', - 'Africa/Lome', - 'Africa/Luanda', - 'Africa/Lubumbashi', - 'Africa/Lusaka', - 'Africa/Malabo', - 'Africa/Maputo', - 'Africa/Maseru', - 'Africa/Mbabane', - 'Africa/Mogadishu', - 'Africa/Monrovia', - 'Africa/Nairobi', - 'Africa/Ndjamena', - 'Africa/Niamey', - 'Africa/Nouakchott', - 'Africa/Ouagadougou', - 'Africa/Porto-Novo', - 'Africa/Sao_Tome', - 'Africa/Timbuktu', - 'Africa/Tripoli', - 'Africa/Tunis', - 'Africa/Windhoek', - 'America/Adak', - 'America/Anchorage', - 'America/Anguilla', - 'America/Antigua', - 'America/Araguaina', - 'America/Argentina/Buenos_Aires', - 'America/Argentina/Catamarca', - 'America/Argentina/ComodRivadavia', - 'America/Argentina/Cordoba', - 'America/Argentina/Jujuy', - 'America/Argentina/La_Rioja', - 'America/Argentina/Mendoza', - 'America/Argentina/Rio_Gallegos', - 'America/Argentina/Salta', - 'America/Argentina/San_Juan', - 'America/Argentina/San_Luis', - 'America/Argentina/Tucuman', - 'America/Argentina/Ushuaia', - 'America/Aruba', - 'America/Asuncion', - 'America/Atikokan', - 'America/Atka', - 'America/Bahia', - 'America/Bahia_Banderas', - 'America/Barbados', - 'America/Belem', - 'America/Belize', - 'America/Blanc-Sablon', - 'America/Boa_Vista', - 'America/Bogota', - 'America/Boise', - 'America/Buenos_Aires', - 'America/Cambridge_Bay', - 'America/Campo_Grande', - 'America/Cancun', - 'America/Caracas', - 'America/Catamarca', - 'America/Cayenne', - 'America/Cayman', - 'America/Chicago', - 'America/Chihuahua', - 'America/Coral_Harbour', - 'America/Cordoba', - 'America/Costa_Rica', - 'America/Creston', - 'America/Cuiaba', - 'America/Curacao', - 'America/Danmarkshavn', - 'America/Dawson', - 'America/Dawson_Creek', - 'America/Denver', - 'America/Detroit', - 'America/Dominica', - 'America/Edmonton', - 'America/Eirunepe', - 'America/El_Salvador', - 'America/Ensenada', - 'America/Fort_Nelson', - 'America/Fort_Wayne', - 'America/Fortaleza', - 'America/Glace_Bay', - 'America/Godthab', - 'America/Goose_Bay', - 'America/Grand_Turk', - 'America/Grenada', - 'America/Guadeloupe', - 'America/Guatemala', - 'America/Guayaquil', - 'America/Guyana', - 'America/Halifax', - 'America/Havana', - 'America/Hermosillo', - 'America/Indiana/Indianapolis', - 'America/Indiana/Knox', - 'America/Indiana/Marengo', - 'America/Indiana/Petersburg', - 'America/Indiana/Tell_City', - 'America/Indiana/Vevay', - 'America/Indiana/Vincennes', - 'America/Indiana/Winamac', - 'America/Indianapolis', - 'America/Inuvik', - 'America/Iqaluit', - 'America/Jamaica', - 'America/Jujuy', - 'America/Juneau', - 'America/Kentucky/Louisville', - 'America/Kentucky/Monticello', - 'America/Knox_IN', - 'America/Kralendijk', - 'America/La_Paz', - 'America/Lima', - 'America/Los_Angeles', - 'America/Louisville', - 'America/Lower_Princes', - 'America/Maceio', - 'America/Managua', - 'America/Manaus', - 'America/Marigot', - 'America/Martinique', - 'America/Matamoros', - 'America/Mazatlan', - 'America/Mendoza', - 'America/Menominee', - 'America/Merida', - 'America/Metlakatla', - 'America/Mexico_City', - 'America/Miquelon', - 'America/Moncton', - 'America/Monterrey', - 'America/Montevideo', - 'America/Montreal', - 'America/Montserrat', - 'America/Nassau', - 'America/New_York', - 'America/Nipigon', - 'America/Nome', - 'America/Noronha', - 'America/North_Dakota/Beulah', - 'America/North_Dakota/Center', - 'America/North_Dakota/New_Salem', - 'America/Ojinaga', - 'America/Panama', - 'America/Pangnirtung', - 'America/Paramaribo', - 'America/Phoenix', - 'America/Port-au-Prince', - 'America/Port_of_Spain', - 'America/Porto_Acre', - 'America/Porto_Velho', - 'America/Puerto_Rico', - 'America/Punta_Arenas', - 'America/Rainy_River', - 'America/Rankin_Inlet', - 'America/Recife', - 'America/Regina', - 'America/Resolute', - 'America/Rio_Branco', - 'America/Rosario', - 'America/Santa_Isabel', - 'America/Santarem', - 'America/Santiago', - 'America/Santo_Domingo', - 'America/Sao_Paulo', - 'America/Scoresbysund', - 'America/Shiprock', - 'America/Sitka', - 'America/St_Barthelemy', - 'America/St_Johns', - 'America/St_Kitts', - 'America/St_Lucia', - 'America/St_Thomas', - 'America/St_Vincent', - 'America/Swift_Current', - 'America/Tegucigalpa', - 'America/Thule', - 'America/Thunder_Bay', - 'America/Tijuana', - 'America/Toronto', - 'America/Tortola', - 'America/Vancouver', - 'America/Virgin', - 'America/Whitehorse', - 'America/Winnipeg', - 'America/Yakutat', - 'America/Yellowknife', - 'Antarctica/Casey', - 'Antarctica/Davis', - 'Antarctica/DumontDUrville', - 'Antarctica/Macquarie', - 'Antarctica/Mawson', - 'Antarctica/McMurdo', - 'Antarctica/Palmer', - 'Antarctica/Rothera', - 'Antarctica/South_Pole', - 'Antarctica/Syowa', - 'Antarctica/Troll', - 'Antarctica/Vostok', - 'Arctic/Longyearbyen', - 'Asia/Aden', - 'Asia/Almaty', - 'Asia/Amman', - 'Asia/Anadyr', - 'Asia/Aqtau', - 'Asia/Aqtobe', - 'Asia/Ashgabat', - 'Asia/Ashkhabad', - 'Asia/Atyrau', - 'Asia/Baghdad', - 'Asia/Bahrain', - 'Asia/Baku', - 'Asia/Bangkok', - 'Asia/Barnaul', - 'Asia/Beirut', - 'Asia/Bishkek', - 'Asia/Brunei', - 'Asia/Calcutta', - 'Asia/Chita', - 'Asia/Choibalsan', - 'Asia/Chongqing', - 'Asia/Chungking', - 'Asia/Colombo', - 'Asia/Dacca', - 'Asia/Damascus', - 'Asia/Dhaka', - 'Asia/Dili', - 'Asia/Dubai', - 'Asia/Dushanbe', - 'Asia/Famagusta', - 'Asia/Gaza', - 'Asia/Harbin', - 'Asia/Hebron', - 'Asia/Ho_Chi_Minh', - 'Asia/Hong_Kong', - 'Asia/Hovd', - 'Asia/Irkutsk', - 'Asia/Istanbul', - 'Asia/Jakarta', - 'Asia/Jayapura', - 'Asia/Jerusalem', - 'Asia/Kabul', - 'Asia/Kamchatka', - 'Asia/Karachi', - 'Asia/Kashgar', - 'Asia/Kathmandu', - 'Asia/Katmandu', - 'Asia/Khandyga', - 'Asia/Kolkata', - 'Asia/Krasnoyarsk', - 'Asia/Kuala_Lumpur', - 'Asia/Kuching', - 'Asia/Kuwait', - 'Asia/Macao', - 'Asia/Macau', - 'Asia/Magadan', - 'Asia/Makassar', - 'Asia/Manila', - 'Asia/Muscat', - 'Asia/Nicosia', - 'Asia/Novokuznetsk', - 'Asia/Novosibirsk', - 'Asia/Omsk', - 'Asia/Oral', - 'Asia/Phnom_Penh', - 'Asia/Pontianak', - 'Asia/Pyongyang', - 'Asia/Qatar', - 'Asia/Qyzylorda', - 'Asia/Rangoon', - 'Asia/Riyadh', - 'Asia/Saigon', - 'Asia/Sakhalin', - 'Asia/Samarkand', - 'Asia/Seoul', - 'Asia/Shanghai', - 'Asia/Singapore', - 'Asia/Srednekolymsk', - 'Asia/Taipei', - 'Asia/Tashkent', - 'Asia/Tbilisi', - 'Asia/Tehran', - 'Asia/Tel_Aviv', - 'Asia/Thimbu', - 'Asia/Thimphu', - 'Asia/Tokyo', - 'Asia/Tomsk', - 'Asia/Ujung_Pandang', - 'Asia/Ulaanbaatar', - 'Asia/Ulan_Bator', - 'Asia/Urumqi', - 'Asia/Ust-Nera', - 'Asia/Vientiane', - 'Asia/Vladivostok', - 'Asia/Yakutsk', - 'Asia/Yangon', - 'Asia/Yekaterinburg', - 'Asia/Yerevan', - 'Atlantic/Azores', - 'Atlantic/Bermuda', - 'Atlantic/Canary', - 'Atlantic/Cape_Verde', - 'Atlantic/Faeroe', - 'Atlantic/Faroe', - 'Atlantic/Jan_Mayen', - 'Atlantic/Madeira', - 'Atlantic/Reykjavik', - 'Atlantic/South_Georgia', - 'Atlantic/St_Helena', - 'Atlantic/Stanley', - 'Australia/ACT', - 'Australia/Adelaide', - 'Australia/Brisbane', - 'Australia/Broken_Hill', - 'Australia/Canberra', - 'Australia/Currie', - 'Australia/Darwin', - 'Australia/Eucla', - 'Australia/Hobart', - 'Australia/LHI', - 'Australia/Lindeman', - 'Australia/Lord_Howe', - 'Australia/Melbourne', - 'Australia/NSW', - 'Australia/North', - 'Australia/Perth', - 'Australia/Queensland', - 'Australia/South', - 'Australia/Sydney', - 'Australia/Tasmania', - 'Australia/Victoria', - 'Australia/West', - 'Australia/Yancowinna', - 'Brazil/Acre', - 'Brazil/DeNoronha', - 'Brazil/East', - 'Brazil/West', - 'CET', - 'CST6CDT', - 'Canada/Atlantic', - 'Canada/Central', - 'Canada/Eastern', - 'Canada/Mountain', - 'Canada/Newfoundland', - 'Canada/Pacific', - 'Canada/Saskatchewan', - 'Canada/Yukon', - 'Chile/Continental', - 'Chile/EasterIsland', - 'Cuba', - 'EET', - 'EST', - 'EST5EDT', - 'Egypt', - 'Eire', - 'Etc/GMT', - 'Etc/GMT+0', - 'Etc/GMT+1', - 'Etc/GMT+10', - 'Etc/GMT+11', - 'Etc/GMT+12', - 'Etc/GMT+2', - 'Etc/GMT+3', - 'Etc/GMT+4', - 'Etc/GMT+5', - 'Etc/GMT+6', - 'Etc/GMT+7', - 'Etc/GMT+8', - 'Etc/GMT+9', - 'Etc/GMT-0', - 'Etc/GMT-1', - 'Etc/GMT-10', - 'Etc/GMT-11', - 'Etc/GMT-12', - 'Etc/GMT-13', - 'Etc/GMT-14', - 'Etc/GMT-2', - 'Etc/GMT-3', - 'Etc/GMT-4', - 'Etc/GMT-5', - 'Etc/GMT-6', - 'Etc/GMT-7', - 'Etc/GMT-8', - 'Etc/GMT-9', - 'Etc/GMT0', - 'Etc/Greenwich', - 'Etc/UCT', - 'Etc/UTC', - 'Etc/Universal', - 'Etc/Zulu', - 'Europe/Amsterdam', - 'Europe/Andorra', - 'Europe/Astrakhan', - 'Europe/Athens', - 'Europe/Belfast', - 'Europe/Belgrade', - 'Europe/Berlin', - 'Europe/Bratislava', - 'Europe/Brussels', - 'Europe/Bucharest', - 'Europe/Budapest', - 'Europe/Busingen', - 'Europe/Chisinau', - 'Europe/Copenhagen', - 'Europe/Dublin', - 'Europe/Gibraltar', - 'Europe/Guernsey', - 'Europe/Helsinki', - 'Europe/Isle_of_Man', - 'Europe/Istanbul', - 'Europe/Jersey', - 'Europe/Kaliningrad', - 'Europe/Kiev', - 'Europe/Kirov', - 'Europe/Lisbon', - 'Europe/Ljubljana', - 'Europe/London', - 'Europe/Luxembourg', - 'Europe/Madrid', - 'Europe/Malta', - 'Europe/Mariehamn', - 'Europe/Minsk', - 'Europe/Monaco', - 'Europe/Moscow', - 'Europe/Nicosia', - 'Europe/Oslo', - 'Europe/Paris', - 'Europe/Podgorica', - 'Europe/Prague', - 'Europe/Riga', - 'Europe/Rome', - 'Europe/Samara', - 'Europe/San_Marino', - 'Europe/Sarajevo', - 'Europe/Saratov', - 'Europe/Simferopol', - 'Europe/Skopje', - 'Europe/Sofia', - 'Europe/Stockholm', - 'Europe/Tallinn', - 'Europe/Tirane', - 'Europe/Tiraspol', - 'Europe/Ulyanovsk', - 'Europe/Uzhgorod', - 'Europe/Vaduz', - 'Europe/Vatican', - 'Europe/Vienna', - 'Europe/Vilnius', - 'Europe/Volgograd', - 'Europe/Warsaw', - 'Europe/Zagreb', - 'Europe/Zaporozhye', - 'Europe/Zurich', - 'GB', - 'GB-Eire', - 'GMT', - 'GMT+0', - 'GMT-0', - 'GMT0', - 'Greenwich', - 'HST', - 'Hongkong', - 'Iceland', - 'Indian/Antananarivo', - 'Indian/Chagos', - 'Indian/Christmas', - 'Indian/Cocos', - 'Indian/Comoro', - 'Indian/Kerguelen', - 'Indian/Mahe', - 'Indian/Maldives', - 'Indian/Mauritius', - 'Indian/Mayotte', - 'Indian/Reunion', - 'Iran', - 'Israel', - 'Jamaica', - 'Japan', - 'Kwajalein', - 'Libya', - 'MET', - 'MST', - 'MST7MDT', - 'Mexico/BajaNorte', - 'Mexico/BajaSur', - 'Mexico/General', - 'NZ', - 'NZ-CHAT', - 'Navajo', - 'PRC', - 'PST8PDT', - 'Pacific/Apia', - 'Pacific/Auckland', - 'Pacific/Bougainville', - 'Pacific/Chatham', - 'Pacific/Chuuk', - 'Pacific/Easter', - 'Pacific/Efate', - 'Pacific/Enderbury', - 'Pacific/Fakaofo', - 'Pacific/Fiji', - 'Pacific/Funafuti', - 'Pacific/Galapagos', - 'Pacific/Gambier', - 'Pacific/Guadalcanal', - 'Pacific/Guam', - 'Pacific/Honolulu', - 'Pacific/Johnston', - 'Pacific/Kiritimati', - 'Pacific/Kosrae', - 'Pacific/Kwajalein', - 'Pacific/Majuro', - 'Pacific/Marquesas', - 'Pacific/Midway', - 'Pacific/Nauru', - 'Pacific/Niue', - 'Pacific/Norfolk', - 'Pacific/Noumea', - 'Pacific/Pago_Pago', - 'Pacific/Palau', - 'Pacific/Pitcairn', - 'Pacific/Pohnpei', - 'Pacific/Ponape', - 'Pacific/Port_Moresby', - 'Pacific/Rarotonga', - 'Pacific/Saipan', - 'Pacific/Samoa', - 'Pacific/Tahiti', - 'Pacific/Tarawa', - 'Pacific/Tongatapu', - 'Pacific/Truk', - 'Pacific/Wake', - 'Pacific/Wallis', - 'Pacific/Yap', - 'Poland', - 'Portugal', - 'ROC', - 'ROK', - 'Singapore', - 'Turkey', - 'UCT', - 'US/Alaska', - 'US/Aleutian', - 'US/Arizona', - 'US/Central', - 'US/East-Indiana', - 'US/Eastern', - 'US/Hawaii', - 'US/Indiana-Starke', - 'US/Michigan', - 'US/Mountain', - 'US/Pacific', - 'US/Samoa', - 'UTC', - 'Universal', - 'W-SU', - 'WET', - 'Zulu'] -all_timezones = LazyList( - tz for tz in all_timezones if resource_exists(tz)) - -all_timezones_set = LazySet(all_timezones) -common_timezones = \ -['Africa/Abidjan', - 'Africa/Accra', - 'Africa/Addis_Ababa', - 'Africa/Algiers', - 'Africa/Asmara', - 'Africa/Bamako', - 'Africa/Bangui', - 'Africa/Banjul', - 'Africa/Bissau', - 'Africa/Blantyre', - 'Africa/Brazzaville', - 'Africa/Bujumbura', - 'Africa/Cairo', - 'Africa/Casablanca', - 'Africa/Ceuta', - 'Africa/Conakry', - 'Africa/Dakar', - 'Africa/Dar_es_Salaam', - 'Africa/Djibouti', - 'Africa/Douala', - 'Africa/El_Aaiun', - 'Africa/Freetown', - 'Africa/Gaborone', - 'Africa/Harare', - 'Africa/Johannesburg', - 'Africa/Juba', - 'Africa/Kampala', - 'Africa/Khartoum', - 'Africa/Kigali', - 'Africa/Kinshasa', - 'Africa/Lagos', - 'Africa/Libreville', - 'Africa/Lome', - 'Africa/Luanda', - 'Africa/Lubumbashi', - 'Africa/Lusaka', - 'Africa/Malabo', - 'Africa/Maputo', - 'Africa/Maseru', - 'Africa/Mbabane', - 'Africa/Mogadishu', - 'Africa/Monrovia', - 'Africa/Nairobi', - 'Africa/Ndjamena', - 'Africa/Niamey', - 'Africa/Nouakchott', - 'Africa/Ouagadougou', - 'Africa/Porto-Novo', - 'Africa/Sao_Tome', - 'Africa/Tripoli', - 'Africa/Tunis', - 'Africa/Windhoek', - 'America/Adak', - 'America/Anchorage', - 'America/Anguilla', - 'America/Antigua', - 'America/Araguaina', - 'America/Argentina/Buenos_Aires', - 'America/Argentina/Catamarca', - 'America/Argentina/Cordoba', - 'America/Argentina/Jujuy', - 'America/Argentina/La_Rioja', - 'America/Argentina/Mendoza', - 'America/Argentina/Rio_Gallegos', - 'America/Argentina/Salta', - 'America/Argentina/San_Juan', - 'America/Argentina/San_Luis', - 'America/Argentina/Tucuman', - 'America/Argentina/Ushuaia', - 'America/Aruba', - 'America/Asuncion', - 'America/Atikokan', - 'America/Bahia', - 'America/Bahia_Banderas', - 'America/Barbados', - 'America/Belem', - 'America/Belize', - 'America/Blanc-Sablon', - 'America/Boa_Vista', - 'America/Bogota', - 'America/Boise', - 'America/Cambridge_Bay', - 'America/Campo_Grande', - 'America/Cancun', - 'America/Caracas', - 'America/Cayenne', - 'America/Cayman', - 'America/Chicago', - 'America/Chihuahua', - 'America/Costa_Rica', - 'America/Creston', - 'America/Cuiaba', - 'America/Curacao', - 'America/Danmarkshavn', - 'America/Dawson', - 'America/Dawson_Creek', - 'America/Denver', - 'America/Detroit', - 'America/Dominica', - 'America/Edmonton', - 'America/Eirunepe', - 'America/El_Salvador', - 'America/Fort_Nelson', - 'America/Fortaleza', - 'America/Glace_Bay', - 'America/Godthab', - 'America/Goose_Bay', - 'America/Grand_Turk', - 'America/Grenada', - 'America/Guadeloupe', - 'America/Guatemala', - 'America/Guayaquil', - 'America/Guyana', - 'America/Halifax', - 'America/Havana', - 'America/Hermosillo', - 'America/Indiana/Indianapolis', - 'America/Indiana/Knox', - 'America/Indiana/Marengo', - 'America/Indiana/Petersburg', - 'America/Indiana/Tell_City', - 'America/Indiana/Vevay', - 'America/Indiana/Vincennes', - 'America/Indiana/Winamac', - 'America/Inuvik', - 'America/Iqaluit', - 'America/Jamaica', - 'America/Juneau', - 'America/Kentucky/Louisville', - 'America/Kentucky/Monticello', - 'America/Kralendijk', - 'America/La_Paz', - 'America/Lima', - 'America/Los_Angeles', - 'America/Lower_Princes', - 'America/Maceio', - 'America/Managua', - 'America/Manaus', - 'America/Marigot', - 'America/Martinique', - 'America/Matamoros', - 'America/Mazatlan', - 'America/Menominee', - 'America/Merida', - 'America/Metlakatla', - 'America/Mexico_City', - 'America/Miquelon', - 'America/Moncton', - 'America/Monterrey', - 'America/Montevideo', - 'America/Montserrat', - 'America/Nassau', - 'America/New_York', - 'America/Nipigon', - 'America/Nome', - 'America/Noronha', - 'America/North_Dakota/Beulah', - 'America/North_Dakota/Center', - 'America/North_Dakota/New_Salem', - 'America/Ojinaga', - 'America/Panama', - 'America/Pangnirtung', - 'America/Paramaribo', - 'America/Phoenix', - 'America/Port-au-Prince', - 'America/Port_of_Spain', - 'America/Porto_Velho', - 'America/Puerto_Rico', - 'America/Punta_Arenas', - 'America/Rainy_River', - 'America/Rankin_Inlet', - 'America/Recife', - 'America/Regina', - 'America/Resolute', - 'America/Rio_Branco', - 'America/Santarem', - 'America/Santiago', - 'America/Santo_Domingo', - 'America/Sao_Paulo', - 'America/Scoresbysund', - 'America/Sitka', - 'America/St_Barthelemy', - 'America/St_Johns', - 'America/St_Kitts', - 'America/St_Lucia', - 'America/St_Thomas', - 'America/St_Vincent', - 'America/Swift_Current', - 'America/Tegucigalpa', - 'America/Thule', - 'America/Thunder_Bay', - 'America/Tijuana', - 'America/Toronto', - 'America/Tortola', - 'America/Vancouver', - 'America/Whitehorse', - 'America/Winnipeg', - 'America/Yakutat', - 'America/Yellowknife', - 'Antarctica/Casey', - 'Antarctica/Davis', - 'Antarctica/DumontDUrville', - 'Antarctica/Macquarie', - 'Antarctica/Mawson', - 'Antarctica/McMurdo', - 'Antarctica/Palmer', - 'Antarctica/Rothera', - 'Antarctica/Syowa', - 'Antarctica/Troll', - 'Antarctica/Vostok', - 'Arctic/Longyearbyen', - 'Asia/Aden', - 'Asia/Almaty', - 'Asia/Amman', - 'Asia/Anadyr', - 'Asia/Aqtau', - 'Asia/Aqtobe', - 'Asia/Ashgabat', - 'Asia/Atyrau', - 'Asia/Baghdad', - 'Asia/Bahrain', - 'Asia/Baku', - 'Asia/Bangkok', - 'Asia/Barnaul', - 'Asia/Beirut', - 'Asia/Bishkek', - 'Asia/Brunei', - 'Asia/Chita', - 'Asia/Choibalsan', - 'Asia/Colombo', - 'Asia/Damascus', - 'Asia/Dhaka', - 'Asia/Dili', - 'Asia/Dubai', - 'Asia/Dushanbe', - 'Asia/Famagusta', - 'Asia/Gaza', - 'Asia/Hebron', - 'Asia/Ho_Chi_Minh', - 'Asia/Hong_Kong', - 'Asia/Hovd', - 'Asia/Irkutsk', - 'Asia/Jakarta', - 'Asia/Jayapura', - 'Asia/Jerusalem', - 'Asia/Kabul', - 'Asia/Kamchatka', - 'Asia/Karachi', - 'Asia/Kathmandu', - 'Asia/Khandyga', - 'Asia/Kolkata', - 'Asia/Krasnoyarsk', - 'Asia/Kuala_Lumpur', - 'Asia/Kuching', - 'Asia/Kuwait', - 'Asia/Macau', - 'Asia/Magadan', - 'Asia/Makassar', - 'Asia/Manila', - 'Asia/Muscat', - 'Asia/Nicosia', - 'Asia/Novokuznetsk', - 'Asia/Novosibirsk', - 'Asia/Omsk', - 'Asia/Oral', - 'Asia/Phnom_Penh', - 'Asia/Pontianak', - 'Asia/Pyongyang', - 'Asia/Qatar', - 'Asia/Qyzylorda', - 'Asia/Riyadh', - 'Asia/Sakhalin', - 'Asia/Samarkand', - 'Asia/Seoul', - 'Asia/Shanghai', - 'Asia/Singapore', - 'Asia/Srednekolymsk', - 'Asia/Taipei', - 'Asia/Tashkent', - 'Asia/Tbilisi', - 'Asia/Tehran', - 'Asia/Thimphu', - 'Asia/Tokyo', - 'Asia/Tomsk', - 'Asia/Ulaanbaatar', - 'Asia/Urumqi', - 'Asia/Ust-Nera', - 'Asia/Vientiane', - 'Asia/Vladivostok', - 'Asia/Yakutsk', - 'Asia/Yangon', - 'Asia/Yekaterinburg', - 'Asia/Yerevan', - 'Atlantic/Azores', - 'Atlantic/Bermuda', - 'Atlantic/Canary', - 'Atlantic/Cape_Verde', - 'Atlantic/Faroe', - 'Atlantic/Madeira', - 'Atlantic/Reykjavik', - 'Atlantic/South_Georgia', - 'Atlantic/St_Helena', - 'Atlantic/Stanley', - 'Australia/Adelaide', - 'Australia/Brisbane', - 'Australia/Broken_Hill', - 'Australia/Currie', - 'Australia/Darwin', - 'Australia/Eucla', - 'Australia/Hobart', - 'Australia/Lindeman', - 'Australia/Lord_Howe', - 'Australia/Melbourne', - 'Australia/Perth', - 'Australia/Sydney', - 'Canada/Atlantic', - 'Canada/Central', - 'Canada/Eastern', - 'Canada/Mountain', - 'Canada/Newfoundland', - 'Canada/Pacific', - 'Europe/Amsterdam', - 'Europe/Andorra', - 'Europe/Astrakhan', - 'Europe/Athens', - 'Europe/Belgrade', - 'Europe/Berlin', - 'Europe/Bratislava', - 'Europe/Brussels', - 'Europe/Bucharest', - 'Europe/Budapest', - 'Europe/Busingen', - 'Europe/Chisinau', - 'Europe/Copenhagen', - 'Europe/Dublin', - 'Europe/Gibraltar', - 'Europe/Guernsey', - 'Europe/Helsinki', - 'Europe/Isle_of_Man', - 'Europe/Istanbul', - 'Europe/Jersey', - 'Europe/Kaliningrad', - 'Europe/Kiev', - 'Europe/Kirov', - 'Europe/Lisbon', - 'Europe/Ljubljana', - 'Europe/London', - 'Europe/Luxembourg', - 'Europe/Madrid', - 'Europe/Malta', - 'Europe/Mariehamn', - 'Europe/Minsk', - 'Europe/Monaco', - 'Europe/Moscow', - 'Europe/Oslo', - 'Europe/Paris', - 'Europe/Podgorica', - 'Europe/Prague', - 'Europe/Riga', - 'Europe/Rome', - 'Europe/Samara', - 'Europe/San_Marino', - 'Europe/Sarajevo', - 'Europe/Saratov', - 'Europe/Simferopol', - 'Europe/Skopje', - 'Europe/Sofia', - 'Europe/Stockholm', - 'Europe/Tallinn', - 'Europe/Tirane', - 'Europe/Ulyanovsk', - 'Europe/Uzhgorod', - 'Europe/Vaduz', - 'Europe/Vatican', - 'Europe/Vienna', - 'Europe/Vilnius', - 'Europe/Volgograd', - 'Europe/Warsaw', - 'Europe/Zagreb', - 'Europe/Zaporozhye', - 'Europe/Zurich', - 'GMT', - 'Indian/Antananarivo', - 'Indian/Chagos', - 'Indian/Christmas', - 'Indian/Cocos', - 'Indian/Comoro', - 'Indian/Kerguelen', - 'Indian/Mahe', - 'Indian/Maldives', - 'Indian/Mauritius', - 'Indian/Mayotte', - 'Indian/Reunion', - 'Pacific/Apia', - 'Pacific/Auckland', - 'Pacific/Bougainville', - 'Pacific/Chatham', - 'Pacific/Chuuk', - 'Pacific/Easter', - 'Pacific/Efate', - 'Pacific/Enderbury', - 'Pacific/Fakaofo', - 'Pacific/Fiji', - 'Pacific/Funafuti', - 'Pacific/Galapagos', - 'Pacific/Gambier', - 'Pacific/Guadalcanal', - 'Pacific/Guam', - 'Pacific/Honolulu', - 'Pacific/Kiritimati', - 'Pacific/Kosrae', - 'Pacific/Kwajalein', - 'Pacific/Majuro', - 'Pacific/Marquesas', - 'Pacific/Midway', - 'Pacific/Nauru', - 'Pacific/Niue', - 'Pacific/Norfolk', - 'Pacific/Noumea', - 'Pacific/Pago_Pago', - 'Pacific/Palau', - 'Pacific/Pitcairn', - 'Pacific/Pohnpei', - 'Pacific/Port_Moresby', - 'Pacific/Rarotonga', - 'Pacific/Saipan', - 'Pacific/Tahiti', - 'Pacific/Tarawa', - 'Pacific/Tongatapu', - 'Pacific/Wake', - 'Pacific/Wallis', - 'US/Alaska', - 'US/Arizona', - 'US/Central', - 'US/Eastern', - 'US/Hawaii', - 'US/Mountain', - 'US/Pacific', - 'UTC'] -common_timezones = LazyList( - tz for tz in common_timezones if tz in all_timezones) - -common_timezones_set = LazySet(common_timezones) diff --git a/lib/pytz/exceptions.py b/lib/pytz/exceptions.py deleted file mode 100644 index 18df33e..0000000 --- a/lib/pytz/exceptions.py +++ /dev/null @@ -1,48 +0,0 @@ -''' -Custom exceptions raised by pytz. -''' - -__all__ = [ - 'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError', - 'NonExistentTimeError', -] - - -class UnknownTimeZoneError(KeyError): - '''Exception raised when pytz is passed an unknown timezone. - - >>> isinstance(UnknownTimeZoneError(), LookupError) - True - - This class is actually a subclass of KeyError to provide backwards - compatibility with code relying on the undocumented behavior of earlier - pytz releases. - - >>> isinstance(UnknownTimeZoneError(), KeyError) - True - ''' - pass - - -class InvalidTimeError(Exception): - '''Base class for invalid time exceptions.''' - - -class AmbiguousTimeError(InvalidTimeError): - '''Exception raised when attempting to create an ambiguous wallclock time. - - At the end of a DST transition period, a particular wallclock time will - occur twice (once before the clocks are set back, once after). Both - possibilities may be correct, unless further information is supplied. - - See DstTzInfo.normalize() for more info - ''' - - -class NonExistentTimeError(InvalidTimeError): - '''Exception raised when attempting to create a wallclock time that - cannot exist. - - At the start of a DST transition period, the wallclock time jumps forward. - The instants jumped over never occur. - ''' diff --git a/lib/pytz/lazy.py b/lib/pytz/lazy.py deleted file mode 100644 index 39344fc..0000000 --- a/lib/pytz/lazy.py +++ /dev/null @@ -1,172 +0,0 @@ -from threading import RLock -try: - from collections.abc import Mapping as DictMixin -except ImportError: # Python < 3.3 - try: - from UserDict import DictMixin # Python 2 - except ImportError: # Python 3.0-3.3 - from collections import Mapping as DictMixin - - -# With lazy loading, we might end up with multiple threads triggering -# it at the same time. We need a lock. -_fill_lock = RLock() - - -class LazyDict(DictMixin): - """Dictionary populated on first use.""" - data = None - - def __getitem__(self, key): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return self.data[key.upper()] - - def __contains__(self, key): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return key in self.data - - def __iter__(self): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return iter(self.data) - - def __len__(self): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return len(self.data) - - def keys(self): - if self.data is None: - _fill_lock.acquire() - try: - if self.data is None: - self._fill() - finally: - _fill_lock.release() - return self.data.keys() - - -class LazyList(list): - """List populated on first use.""" - - _props = [ - '__str__', '__repr__', '__unicode__', - '__hash__', '__sizeof__', '__cmp__', - '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', - 'append', 'count', 'index', 'extend', 'insert', 'pop', 'remove', - 'reverse', 'sort', '__add__', '__radd__', '__iadd__', '__mul__', - '__rmul__', '__imul__', '__contains__', '__len__', '__nonzero__', - '__getitem__', '__setitem__', '__delitem__', '__iter__', - '__reversed__', '__getslice__', '__setslice__', '__delslice__'] - - def __new__(cls, fill_iter=None): - - if fill_iter is None: - return list() - - # We need a new class as we will be dynamically messing with its - # methods. - class LazyList(list): - pass - - fill_iter = [fill_iter] - - def lazy(name): - def _lazy(self, *args, **kw): - _fill_lock.acquire() - try: - if len(fill_iter) > 0: - list.extend(self, fill_iter.pop()) - for method_name in cls._props: - delattr(LazyList, method_name) - finally: - _fill_lock.release() - return getattr(list, name)(self, *args, **kw) - return _lazy - - for name in cls._props: - setattr(LazyList, name, lazy(name)) - - new_list = LazyList() - return new_list - -# Not all versions of Python declare the same magic methods. -# Filter out properties that don't exist in this version of Python -# from the list. -LazyList._props = [prop for prop in LazyList._props if hasattr(list, prop)] - - -class LazySet(set): - """Set populated on first use.""" - - _props = ( - '__str__', '__repr__', '__unicode__', - '__hash__', '__sizeof__', '__cmp__', - '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', - '__contains__', '__len__', '__nonzero__', - '__getitem__', '__setitem__', '__delitem__', '__iter__', - '__sub__', '__and__', '__xor__', '__or__', - '__rsub__', '__rand__', '__rxor__', '__ror__', - '__isub__', '__iand__', '__ixor__', '__ior__', - 'add', 'clear', 'copy', 'difference', 'difference_update', - 'discard', 'intersection', 'intersection_update', 'isdisjoint', - 'issubset', 'issuperset', 'pop', 'remove', - 'symmetric_difference', 'symmetric_difference_update', - 'union', 'update') - - def __new__(cls, fill_iter=None): - - if fill_iter is None: - return set() - - class LazySet(set): - pass - - fill_iter = [fill_iter] - - def lazy(name): - def _lazy(self, *args, **kw): - _fill_lock.acquire() - try: - if len(fill_iter) > 0: - for i in fill_iter.pop(): - set.add(self, i) - for method_name in cls._props: - delattr(LazySet, method_name) - finally: - _fill_lock.release() - return getattr(set, name)(self, *args, **kw) - return _lazy - - for name in cls._props: - setattr(LazySet, name, lazy(name)) - - new_set = LazySet() - return new_set - -# Not all versions of Python declare the same magic methods. -# Filter out properties that don't exist in this version of Python -# from the list. -LazySet._props = [prop for prop in LazySet._props if hasattr(set, prop)] diff --git a/lib/pytz/reference.py b/lib/pytz/reference.py deleted file mode 100644 index f765ca0..0000000 --- a/lib/pytz/reference.py +++ /dev/null @@ -1,140 +0,0 @@ -''' -Reference tzinfo implementations from the Python docs. -Used for testing against as they are only correct for the years -1987 to 2006. Do not use these for real code. -''' - -from datetime import tzinfo, timedelta, datetime -from pytz import HOUR, ZERO, UTC - -__all__ = [ - 'FixedOffset', - 'LocalTimezone', - 'USTimeZone', - 'Eastern', - 'Central', - 'Mountain', - 'Pacific', - 'UTC' -] - - -# A class building tzinfo objects for fixed-offset time zones. -# Note that FixedOffset(0, "UTC") is a different way to build a -# UTC tzinfo object. -class FixedOffset(tzinfo): - """Fixed offset in minutes east from UTC.""" - - def __init__(self, offset, name): - self.__offset = timedelta(minutes=offset) - self.__name = name - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return ZERO - - -import time as _time - -STDOFFSET = timedelta(seconds=-_time.timezone) -if _time.daylight: - DSTOFFSET = timedelta(seconds=-_time.altzone) -else: - DSTOFFSET = STDOFFSET - -DSTDIFF = DSTOFFSET - STDOFFSET - - -# A class capturing the platform's idea of local time. -class LocalTimezone(tzinfo): - - def utcoffset(self, dt): - if self._isdst(dt): - return DSTOFFSET - else: - return STDOFFSET - - def dst(self, dt): - if self._isdst(dt): - return DSTDIFF - else: - return ZERO - - def tzname(self, dt): - return _time.tzname[self._isdst(dt)] - - def _isdst(self, dt): - tt = (dt.year, dt.month, dt.day, - dt.hour, dt.minute, dt.second, - dt.weekday(), 0, -1) - stamp = _time.mktime(tt) - tt = _time.localtime(stamp) - return tt.tm_isdst > 0 - -Local = LocalTimezone() - - -def first_sunday_on_or_after(dt): - days_to_go = 6 - dt.weekday() - if days_to_go: - dt += timedelta(days_to_go) - return dt - - -# In the US, DST starts at 2am (standard time) on the first Sunday in April. -DSTSTART = datetime(1, 4, 1, 2) -# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct. -# which is the first Sunday on or after Oct 25. -DSTEND = datetime(1, 10, 25, 1) - - -# A complete implementation of current DST rules for major US time zones. -class USTimeZone(tzinfo): - - def __init__(self, hours, reprname, stdname, dstname): - self.stdoffset = timedelta(hours=hours) - self.reprname = reprname - self.stdname = stdname - self.dstname = dstname - - def __repr__(self): - return self.reprname - - def tzname(self, dt): - if self.dst(dt): - return self.dstname - else: - return self.stdname - - def utcoffset(self, dt): - return self.stdoffset + self.dst(dt) - - def dst(self, dt): - if dt is None or dt.tzinfo is None: - # An exception may be sensible here, in one or both cases. - # It depends on how you want to treat them. The default - # fromutc() implementation (called by the default astimezone() - # implementation) passes a datetime with dt.tzinfo is self. - return ZERO - assert dt.tzinfo is self - - # Find first Sunday in April & the last in October. - start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year)) - end = first_sunday_on_or_after(DSTEND.replace(year=dt.year)) - - # Can't compare naive to aware objects, so strip the timezone from - # dt first. - if start <= dt.replace(tzinfo=None) < end: - return HOUR - else: - return ZERO - -Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") -Central = USTimeZone(-6, "Central", "CST", "CDT") -Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") -Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") diff --git a/lib/pytz/tzfile.py b/lib/pytz/tzfile.py deleted file mode 100644 index 25117f3..0000000 --- a/lib/pytz/tzfile.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python -''' -$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $ -''' - -from datetime import datetime -from struct import unpack, calcsize - -from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo -from pytz.tzinfo import memorized_datetime, memorized_timedelta - - -def _byte_string(s): - """Cast a string or byte string to an ASCII byte string.""" - return s.encode('ASCII') - -_NULL = _byte_string('\0') - - -def _std_string(s): - """Cast a string or byte string to an ASCII string.""" - return str(s.decode('ASCII')) - - -def build_tzinfo(zone, fp): - head_fmt = '>4s c 15x 6l' - head_size = calcsize(head_fmt) - (magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, - typecnt, charcnt) = unpack(head_fmt, fp.read(head_size)) - - # Make sure it is a tzfile(5) file - assert magic == _byte_string('TZif'), 'Got magic %s' % repr(magic) - - # Read out the transition times, localtime indices and ttinfo structures. - data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict( - timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt) - data_size = calcsize(data_fmt) - data = unpack(data_fmt, fp.read(data_size)) - - # make sure we unpacked the right number of values - assert len(data) == 2 * timecnt + 3 * typecnt + 1 - transitions = [memorized_datetime(trans) - for trans in data[:timecnt]] - lindexes = list(data[timecnt:2 * timecnt]) - ttinfo_raw = data[2 * timecnt:-1] - tznames_raw = data[-1] - del data - - # Process ttinfo into separate structs - ttinfo = [] - tznames = {} - i = 0 - while i < len(ttinfo_raw): - # have we looked up this timezone name yet? - tzname_offset = ttinfo_raw[i + 2] - if tzname_offset not in tznames: - nul = tznames_raw.find(_NULL, tzname_offset) - if nul < 0: - nul = len(tznames_raw) - tznames[tzname_offset] = _std_string( - tznames_raw[tzname_offset:nul]) - ttinfo.append((ttinfo_raw[i], - bool(ttinfo_raw[i + 1]), - tznames[tzname_offset])) - i += 3 - - # Now build the timezone object - if len(ttinfo) == 1 or len(transitions) == 0: - ttinfo[0][0], ttinfo[0][2] - cls = type(zone, (StaticTzInfo,), dict( - zone=zone, - _utcoffset=memorized_timedelta(ttinfo[0][0]), - _tzname=ttinfo[0][2])) - else: - # Early dates use the first standard time ttinfo - i = 0 - while ttinfo[i][1]: - i += 1 - if ttinfo[i] == ttinfo[lindexes[0]]: - transitions[0] = datetime.min - else: - transitions.insert(0, datetime.min) - lindexes.insert(0, i) - - # calculate transition info - transition_info = [] - for i in range(len(transitions)): - inf = ttinfo[lindexes[i]] - utcoffset = inf[0] - if not inf[1]: - dst = 0 - else: - for j in range(i - 1, -1, -1): - prev_inf = ttinfo[lindexes[j]] - if not prev_inf[1]: - break - dst = inf[0] - prev_inf[0] # dst offset - - # Bad dst? Look further. DST > 24 hours happens when - # a timzone has moved across the international dateline. - if dst <= 0 or dst > 3600 * 3: - for j in range(i + 1, len(transitions)): - stdinf = ttinfo[lindexes[j]] - if not stdinf[1]: - dst = inf[0] - stdinf[0] - if dst > 0: - break # Found a useful std time. - - tzname = inf[2] - - # Round utcoffset and dst to the nearest minute or the - # datetime library will complain. Conversions to these timezones - # might be up to plus or minus 30 seconds out, but it is - # the best we can do. - utcoffset = int((utcoffset + 30) // 60) * 60 - dst = int((dst + 30) // 60) * 60 - transition_info.append(memorized_ttinfo(utcoffset, dst, tzname)) - - cls = type(zone, (DstTzInfo,), dict( - zone=zone, - _utc_transition_times=transitions, - _transition_info=transition_info)) - - return cls() - -if __name__ == '__main__': - import os.path - from pprint import pprint - base = os.path.join(os.path.dirname(__file__), 'zoneinfo') - tz = build_tzinfo('Australia/Melbourne', - open(os.path.join(base, 'Australia', 'Melbourne'), 'rb')) - tz = build_tzinfo('US/Eastern', - open(os.path.join(base, 'US', 'Eastern'), 'rb')) - pprint(tz._utc_transition_times) diff --git a/lib/pytz/tzinfo.py b/lib/pytz/tzinfo.py deleted file mode 100644 index 725978d..0000000 --- a/lib/pytz/tzinfo.py +++ /dev/null @@ -1,577 +0,0 @@ -'''Base classes and helpers for building zone specific tzinfo classes''' - -from datetime import datetime, timedelta, tzinfo -from bisect import bisect_right -try: - set -except NameError: - from sets import Set as set - -import pytz -from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError - -__all__ = [] - -_timedelta_cache = {} - - -def memorized_timedelta(seconds): - '''Create only one instance of each distinct timedelta''' - try: - return _timedelta_cache[seconds] - except KeyError: - delta = timedelta(seconds=seconds) - _timedelta_cache[seconds] = delta - return delta - -_epoch = datetime.utcfromtimestamp(0) -_datetime_cache = {0: _epoch} - - -def memorized_datetime(seconds): - '''Create only one instance of each distinct datetime''' - try: - return _datetime_cache[seconds] - except KeyError: - # NB. We can't just do datetime.utcfromtimestamp(seconds) as this - # fails with negative values under Windows (Bug #90096) - dt = _epoch + timedelta(seconds=seconds) - _datetime_cache[seconds] = dt - return dt - -_ttinfo_cache = {} - - -def memorized_ttinfo(*args): - '''Create only one instance of each distinct tuple''' - try: - return _ttinfo_cache[args] - except KeyError: - ttinfo = ( - memorized_timedelta(args[0]), - memorized_timedelta(args[1]), - args[2] - ) - _ttinfo_cache[args] = ttinfo - return ttinfo - -_notime = memorized_timedelta(0) - - -def _to_seconds(td): - '''Convert a timedelta to seconds''' - return td.seconds + td.days * 24 * 60 * 60 - - -class BaseTzInfo(tzinfo): - # Overridden in subclass - _utcoffset = None - _tzname = None - zone = None - - def __str__(self): - return self.zone - - -class StaticTzInfo(BaseTzInfo): - '''A timezone that has a constant offset from UTC - - These timezones are rare, as most locations have changed their - offset at some point in their history - ''' - def fromutc(self, dt): - '''See datetime.tzinfo.fromutc''' - if dt.tzinfo is not None and dt.tzinfo is not self: - raise ValueError('fromutc: dt.tzinfo is not self') - return (dt + self._utcoffset).replace(tzinfo=self) - - def utcoffset(self, dt, is_dst=None): - '''See datetime.tzinfo.utcoffset - - is_dst is ignored for StaticTzInfo, and exists only to - retain compatibility with DstTzInfo. - ''' - return self._utcoffset - - def dst(self, dt, is_dst=None): - '''See datetime.tzinfo.dst - - is_dst is ignored for StaticTzInfo, and exists only to - retain compatibility with DstTzInfo. - ''' - return _notime - - def tzname(self, dt, is_dst=None): - '''See datetime.tzinfo.tzname - - is_dst is ignored for StaticTzInfo, and exists only to - retain compatibility with DstTzInfo. - ''' - return self._tzname - - def localize(self, dt, is_dst=False): - '''Convert naive time to local time''' - if dt.tzinfo is not None: - raise ValueError('Not naive datetime (tzinfo is already set)') - return dt.replace(tzinfo=self) - - def normalize(self, dt, is_dst=False): - '''Correct the timezone information on the given datetime. - - This is normally a no-op, as StaticTzInfo timezones never have - ambiguous cases to correct: - - >>> from pytz import timezone - >>> gmt = timezone('GMT') - >>> isinstance(gmt, StaticTzInfo) - True - >>> dt = datetime(2011, 5, 8, 1, 2, 3, tzinfo=gmt) - >>> gmt.normalize(dt) is dt - True - - The supported method of converting between timezones is to use - datetime.astimezone(). Currently normalize() also works: - - >>> la = timezone('America/Los_Angeles') - >>> dt = la.localize(datetime(2011, 5, 7, 1, 2, 3)) - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - >>> gmt.normalize(dt).strftime(fmt) - '2011-05-07 08:02:03 GMT (+0000)' - ''' - if dt.tzinfo is self: - return dt - if dt.tzinfo is None: - raise ValueError('Naive time - no tzinfo set') - return dt.astimezone(self) - - def __repr__(self): - return '<StaticTzInfo %r>' % (self.zone,) - - def __reduce__(self): - # Special pickle to zone remains a singleton and to cope with - # database changes. - return pytz._p, (self.zone,) - - -class DstTzInfo(BaseTzInfo): - '''A timezone that has a variable offset from UTC - - The offset might change if daylight saving time comes into effect, - or at a point in history when the region decides to change their - timezone definition. - ''' - # Overridden in subclass - - # Sorted list of DST transition times, UTC - _utc_transition_times = None - - # [(utcoffset, dstoffset, tzname)] corresponding to - # _utc_transition_times entries - _transition_info = None - - zone = None - - # Set in __init__ - - _tzinfos = None - _dst = None # DST offset - - def __init__(self, _inf=None, _tzinfos=None): - if _inf: - self._tzinfos = _tzinfos - self._utcoffset, self._dst, self._tzname = _inf - else: - _tzinfos = {} - self._tzinfos = _tzinfos - self._utcoffset, self._dst, self._tzname = ( - self._transition_info[0]) - _tzinfos[self._transition_info[0]] = self - for inf in self._transition_info[1:]: - if inf not in _tzinfos: - _tzinfos[inf] = self.__class__(inf, _tzinfos) - - def fromutc(self, dt): - '''See datetime.tzinfo.fromutc''' - if (dt.tzinfo is not None and - getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos): - raise ValueError('fromutc: dt.tzinfo is not self') - dt = dt.replace(tzinfo=None) - idx = max(0, bisect_right(self._utc_transition_times, dt) - 1) - inf = self._transition_info[idx] - return (dt + inf[0]).replace(tzinfo=self._tzinfos[inf]) - - def normalize(self, dt): - '''Correct the timezone information on the given datetime - - If date arithmetic crosses DST boundaries, the tzinfo - is not magically adjusted. This method normalizes the - tzinfo to the correct one. - - To test, first we need to do some setup - - >>> from pytz import timezone - >>> utc = timezone('UTC') - >>> eastern = timezone('US/Eastern') - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - - We next create a datetime right on an end-of-DST transition point, - the instant when the wallclocks are wound back one hour. - - >>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc) - >>> loc_dt = utc_dt.astimezone(eastern) - >>> loc_dt.strftime(fmt) - '2002-10-27 01:00:00 EST (-0500)' - - Now, if we subtract a few minutes from it, note that the timezone - information has not changed. - - >>> before = loc_dt - timedelta(minutes=10) - >>> before.strftime(fmt) - '2002-10-27 00:50:00 EST (-0500)' - - But we can fix that by calling the normalize method - - >>> before = eastern.normalize(before) - >>> before.strftime(fmt) - '2002-10-27 01:50:00 EDT (-0400)' - - The supported method of converting between timezones is to use - datetime.astimezone(). Currently, normalize() also works: - - >>> th = timezone('Asia/Bangkok') - >>> am = timezone('Europe/Amsterdam') - >>> dt = th.localize(datetime(2011, 5, 7, 1, 2, 3)) - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - >>> am.normalize(dt).strftime(fmt) - '2011-05-06 20:02:03 CEST (+0200)' - ''' - if dt.tzinfo is None: - raise ValueError('Naive time - no tzinfo set') - - # Convert dt in localtime to UTC - offset = dt.tzinfo._utcoffset - dt = dt.replace(tzinfo=None) - dt = dt - offset - # convert it back, and return it - return self.fromutc(dt) - - def localize(self, dt, is_dst=False): - '''Convert naive time to local time. - - This method should be used to construct localtimes, rather - than passing a tzinfo argument to a datetime constructor. - - is_dst is used to determine the correct timezone in the ambigous - period at the end of daylight saving time. - - >>> from pytz import timezone - >>> fmt = '%Y-%m-%d %H:%M:%S %Z (%z)' - >>> amdam = timezone('Europe/Amsterdam') - >>> dt = datetime(2004, 10, 31, 2, 0, 0) - >>> loc_dt1 = amdam.localize(dt, is_dst=True) - >>> loc_dt2 = amdam.localize(dt, is_dst=False) - >>> loc_dt1.strftime(fmt) - '2004-10-31 02:00:00 CEST (+0200)' - >>> loc_dt2.strftime(fmt) - '2004-10-31 02:00:00 CET (+0100)' - >>> str(loc_dt2 - loc_dt1) - '1:00:00' - - Use is_dst=None to raise an AmbiguousTimeError for ambiguous - times at the end of daylight saving time - - >>> try: - ... loc_dt1 = amdam.localize(dt, is_dst=None) - ... except AmbiguousTimeError: - ... print('Ambiguous') - Ambiguous - - is_dst defaults to False - - >>> amdam.localize(dt) == amdam.localize(dt, False) - True - - is_dst is also used to determine the correct timezone in the - wallclock times jumped over at the start of daylight saving time. - - >>> pacific = timezone('US/Pacific') - >>> dt = datetime(2008, 3, 9, 2, 0, 0) - >>> ploc_dt1 = pacific.localize(dt, is_dst=True) - >>> ploc_dt2 = pacific.localize(dt, is_dst=False) - >>> ploc_dt1.strftime(fmt) - '2008-03-09 02:00:00 PDT (-0700)' - >>> ploc_dt2.strftime(fmt) - '2008-03-09 02:00:00 PST (-0800)' - >>> str(ploc_dt2 - ploc_dt1) - '1:00:00' - - Use is_dst=None to raise a NonExistentTimeError for these skipped - times. - - >>> try: - ... loc_dt1 = pacific.localize(dt, is_dst=None) - ... except NonExistentTimeError: - ... print('Non-existent') - Non-existent - ''' - if dt.tzinfo is not None: - raise ValueError('Not naive datetime (tzinfo is already set)') - - # Find the two best possibilities. - possible_loc_dt = set() - for delta in [timedelta(days=-1), timedelta(days=1)]: - loc_dt = dt + delta - idx = max(0, bisect_right( - self._utc_transition_times, loc_dt) - 1) - inf = self._transition_info[idx] - tzinfo = self._tzinfos[inf] - loc_dt = tzinfo.normalize(dt.replace(tzinfo=tzinfo)) - if loc_dt.replace(tzinfo=None) == dt: - possible_loc_dt.add(loc_dt) - - if len(possible_loc_dt) == 1: - return possible_loc_dt.pop() - - # If there are no possibly correct timezones, we are attempting - # to convert a time that never happened - the time period jumped - # during the start-of-DST transition period. - if len(possible_loc_dt) == 0: - # If we refuse to guess, raise an exception. - if is_dst is None: - raise NonExistentTimeError(dt) - - # If we are forcing the pre-DST side of the DST transition, we - # obtain the correct timezone by winding the clock forward a few - # hours. - elif is_dst: - return self.localize( - dt + timedelta(hours=6), is_dst=True) - timedelta(hours=6) - - # If we are forcing the post-DST side of the DST transition, we - # obtain the correct timezone by winding the clock back. - else: - return self.localize( - dt - timedelta(hours=6), - is_dst=False) + timedelta(hours=6) - - # If we get this far, we have multiple possible timezones - this - # is an ambiguous case occuring during the end-of-DST transition. - - # If told to be strict, raise an exception since we have an - # ambiguous case - if is_dst is None: - raise AmbiguousTimeError(dt) - - # Filter out the possiblilities that don't match the requested - # is_dst - filtered_possible_loc_dt = [ - p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst - ] - - # Hopefully we only have one possibility left. Return it. - if len(filtered_possible_loc_dt) == 1: - return filtered_possible_loc_dt[0] - - if len(filtered_possible_loc_dt) == 0: - filtered_possible_loc_dt = list(possible_loc_dt) - - # If we get this far, we have in a wierd timezone transition - # where the clocks have been wound back but is_dst is the same - # in both (eg. Europe/Warsaw 1915 when they switched to CET). - # At this point, we just have to guess unless we allow more - # hints to be passed in (such as the UTC offset or abbreviation), - # but that is just getting silly. - # - # Choose the earliest (by UTC) applicable timezone if is_dst=True - # Choose the latest (by UTC) applicable timezone if is_dst=False - # i.e., behave like end-of-DST transition - dates = {} # utc -> local - for local_dt in filtered_possible_loc_dt: - utc_time = ( - local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset) - assert utc_time not in dates - dates[utc_time] = local_dt - return dates[[min, max][not is_dst](dates)] - - def utcoffset(self, dt, is_dst=None): - '''See datetime.tzinfo.utcoffset - - The is_dst parameter may be used to remove ambiguity during DST - transitions. - - >>> from pytz import timezone - >>> tz = timezone('America/St_Johns') - >>> ambiguous = datetime(2009, 10, 31, 23, 30) - - >>> str(tz.utcoffset(ambiguous, is_dst=False)) - '-1 day, 20:30:00' - - >>> str(tz.utcoffset(ambiguous, is_dst=True)) - '-1 day, 21:30:00' - - >>> try: - ... tz.utcoffset(ambiguous) - ... except AmbiguousTimeError: - ... print('Ambiguous') - Ambiguous - - ''' - if dt is None: - return None - elif dt.tzinfo is not self: - dt = self.localize(dt, is_dst) - return dt.tzinfo._utcoffset - else: - return self._utcoffset - - def dst(self, dt, is_dst=None): - '''See datetime.tzinfo.dst - - The is_dst parameter may be used to remove ambiguity during DST - transitions. - - >>> from pytz import timezone - >>> tz = timezone('America/St_Johns') - - >>> normal = datetime(2009, 9, 1) - - >>> str(tz.dst(normal)) - '1:00:00' - >>> str(tz.dst(normal, is_dst=False)) - '1:00:00' - >>> str(tz.dst(normal, is_dst=True)) - '1:00:00' - - >>> ambiguous = datetime(2009, 10, 31, 23, 30) - - >>> str(tz.dst(ambiguous, is_dst=False)) - '0:00:00' - >>> str(tz.dst(ambiguous, is_dst=True)) - '1:00:00' - >>> try: - ... tz.dst(ambiguous) - ... except AmbiguousTimeError: - ... print('Ambiguous') - Ambiguous - - ''' - if dt is None: - return None - elif dt.tzinfo is not self: - dt = self.localize(dt, is_dst) - return dt.tzinfo._dst - else: - return self._dst - - def tzname(self, dt, is_dst=None): - '''See datetime.tzinfo.tzname - - The is_dst parameter may be used to remove ambiguity during DST - transitions. - - >>> from pytz import timezone - >>> tz = timezone('America/St_Johns') - - >>> normal = datetime(2009, 9, 1) - - >>> tz.tzname(normal) - 'NDT' - >>> tz.tzname(normal, is_dst=False) - 'NDT' - >>> tz.tzname(normal, is_dst=True) - 'NDT' - - >>> ambiguous = datetime(2009, 10, 31, 23, 30) - - >>> tz.tzname(ambiguous, is_dst=False) - 'NST' - >>> tz.tzname(ambiguous, is_dst=True) - 'NDT' - >>> try: - ... tz.tzname(ambiguous) - ... except AmbiguousTimeError: - ... print('Ambiguous') - Ambiguous - ''' - if dt is None: - return self.zone - elif dt.tzinfo is not self: - dt = self.localize(dt, is_dst) - return dt.tzinfo._tzname - else: - return self._tzname - - def __repr__(self): - if self._dst: - dst = 'DST' - else: - dst = 'STD' - if self._utcoffset > _notime: - return '<DstTzInfo %r %s+%s %s>' % ( - self.zone, self._tzname, self._utcoffset, dst - ) - else: - return '<DstTzInfo %r %s%s %s>' % ( - self.zone, self._tzname, self._utcoffset, dst - ) - - def __reduce__(self): - # Special pickle to zone remains a singleton and to cope with - # database changes. - return pytz._p, ( - self.zone, - _to_seconds(self._utcoffset), - _to_seconds(self._dst), - self._tzname - ) - - -def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None): - """Factory function for unpickling pytz tzinfo instances. - - This is shared for both StaticTzInfo and DstTzInfo instances, because - database changes could cause a zones implementation to switch between - these two base classes and we can't break pickles on a pytz version - upgrade. - """ - # Raises a KeyError if zone no longer exists, which should never happen - # and would be a bug. - tz = pytz.timezone(zone) - - # A StaticTzInfo - just return it - if utcoffset is None: - return tz - - # This pickle was created from a DstTzInfo. We need to - # determine which of the list of tzinfo instances for this zone - # to use in order to restore the state of any datetime instances using - # it correctly. - utcoffset = memorized_timedelta(utcoffset) - dstoffset = memorized_timedelta(dstoffset) - try: - return tz._tzinfos[(utcoffset, dstoffset, tzname)] - except KeyError: - # The particular state requested in this timezone no longer exists. - # This indicates a corrupt pickle, or the timezone database has been - # corrected violently enough to make this particular - # (utcoffset,dstoffset) no longer exist in the zone, or the - # abbreviation has been changed. - pass - - # See if we can find an entry differing only by tzname. Abbreviations - # get changed from the initial guess by the database maintainers to - # match reality when this information is discovered. - for localized_tz in tz._tzinfos.values(): - if (localized_tz._utcoffset == utcoffset and - localized_tz._dst == dstoffset): - return localized_tz - - # This (utcoffset, dstoffset) information has been removed from the - # zone. Add it back. This might occur when the database maintainers have - # corrected incorrect information. datetime instances using this - # incorrect information will continue to do so, exactly as they were - # before being pickled. This is purely an overly paranoid safety net - I - # doubt this will ever been needed in real life. - inf = (utcoffset, dstoffset, tzname) - tz._tzinfos[inf] = tz.__class__(inf, tz._tzinfos) - return tz._tzinfos[inf] diff --git a/lib/pytz/zoneinfo/Africa/Abidjan b/lib/pytz/zoneinfo/Africa/Abidjan deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Accra b/lib/pytz/zoneinfo/Africa/Accra deleted file mode 100644 index eaaa818f839bb79a4141e2c657d09e315be1cc1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 828 zcmcK2J!lhQ9LMp0n=A%e7ZpU8ZiS*bf|;`DlqJOnOqUF$AOv)95TVc_Jm}O#I@CZ3 zIHaUT`;ri@X^pQ(Nv$?14+KFv)zYy@7Yl-NeZG&QQ-(g__#8JJ-0z!g?p{72|DA|^ z!o`W(i~GSBUfy50F|8N6e^mKmRmy8|vv_i#Ul#7F%J)n1YO-KnH;b~AyJD7$yLu&c zTdh9sm+I9grZ%3`wZ9EjzuDCF{gm>Deu^JFFpbENZj?%D{cub+o_;fLw)Ui%&zZNC z0sSuhOue7EB_D<Z^YQ*8{ps8<6=Z(v;Al#1UcV-rofXqMKcQQzKh)OooP3_WV79xL z<;%l)^VQGjojV=Xey*jH6w}|=(=QCeHs2nt^+i!)nbE9_cwWMjw7vUxvOSDv-xx9= zGNRKCiHzyAgCe6M!y@A%10y3NLnC7&gCnCO!z1H6Z2%;K(}qA|IBgIliqnQc;vj*L zNJuCo77`4JhJ-`nApwzyP8$-5>9j$Ss7@OeiHihAA|s)Z*hp|BI-XhhnEelUu~Ury BmV^KR diff --git a/lib/pytz/zoneinfo/Africa/Addis_Ababa b/lib/pytz/zoneinfo/Africa/Addis_Ababa deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Africa/Algiers b/lib/pytz/zoneinfo/Africa/Algiers deleted file mode 100644 index a5867a67231692fe7ac02e273e3e1b2d0dba9877..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 751 zcmc(czb^w}7{{M$&lOQl{TRd`ViF`3{sBi2rA=IdMpBay%f%p#7;0pzwSE*eN{k`~ z7CQNzG!cWv{w8+yevUz6GI=iV^Lg@IF1h#fb(4$pz4EIn^@c;W>%-iV(Jecdi06jW zDp%_Aw$HQP&c%q@-T3hK@|`L_cck_+HR_-zq7GMMs?fi#3a6HLRkpnA>9frBb5;I! ztIxjM6uDn+wjU-syr;2{_jdCr@3SW=kiJwwr>MfWC8<3gRCVd3*b6guWX16s<_9v3 zsWrPPo)W_h1b;lHRiPi#(%TS2$h1TPFZyRC5EWCT);7NG5@sj5x3(Ge?4|kZZEW0? zgg#S4lQdTx21gGfhT(tp-P}L;D(hM*j=n;?LEQP&{vZw^9w9CvJ|RvaULkHFej$#1 zYR?eY5Z^wvbBK3{dx(EX29O*eSwQmO%47n`1(FR#K9Gzka)M+9$qSMh{O{bVb(r1J F>Q4~B_9FlQ diff --git a/lib/pytz/zoneinfo/Africa/Asmara b/lib/pytz/zoneinfo/Africa/Asmara deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Africa/Asmera b/lib/pytz/zoneinfo/Africa/Asmera deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Africa/Bamako b/lib/pytz/zoneinfo/Africa/Bamako deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Bangui b/lib/pytz/zoneinfo/Africa/Bangui deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/Banjul b/lib/pytz/zoneinfo/Africa/Banjul deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Bissau b/lib/pytz/zoneinfo/Africa/Bissau deleted file mode 100644 index 82ea5aaf0c6ae2b3ec582013b6d16e6d6f29eb0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194 zcmWHE%1kq2zyQoZ5fBCeCLji}c_uxZGl4TbQGk)@|NoCE3=BZ>0|N_42?K|ZZwP~~ ffgyuCkY->6p%4;G{tpBo(?LcNZvz+5G6OCE+{Pmd diff --git a/lib/pytz/zoneinfo/Africa/Blantyre b/lib/pytz/zoneinfo/Africa/Blantyre deleted file mode 100644 index 31cfad771a5c7c609e495da650e3ffbcf07c974d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c diff --git a/lib/pytz/zoneinfo/Africa/Brazzaville b/lib/pytz/zoneinfo/Africa/Brazzaville deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/Bujumbura b/lib/pytz/zoneinfo/Africa/Bujumbura deleted file mode 100644 index 31cfad771a5c7c609e495da650e3ffbcf07c974d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c diff --git a/lib/pytz/zoneinfo/Africa/Cairo b/lib/pytz/zoneinfo/Africa/Cairo deleted file mode 100644 index 0272fa1ba0a09ae8380be83cc8838d97d0aa6d25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1963 zcmdVaZ)nw39LMo<r>=RO)m6)8UR|~2n$6tOt>w*SdS&XQU)(!N8TDI<Qc)aPg1TY{ ziNZ3%_CQHc$%;z*qn5f*A%mK;+EH18LY;-^GMlHlDZ9GXc^@AJK@WP+ckI3{?txFv z`;%V$^wKi%w;SsIg<r18{qlXJav!bDpPSZ9qdt7~f@%A<OCLG%N%-B*cj@nUbebQ& z>DBFTE;ahYRr=`5p*i;2$3a*5Q(;$cNpNmNOL(p$)W4n2*z;dJCw^~lvfUec#D&jx zOS`I2{jvUx?OD7?Tx=L8duOaueYIQUr3o>0x%`;DJeU;yw`9xy&Nh+hue4VV4XCT9 z4%&hD)~G-C_sGFl_6V`L&_<U1s<N6E$szONVrczOa@gc|ME2~My)L&y<rE}kPWNyT z9k$X&+Y@TU*#Voo^D8y-aH+ihtv+$XM@#IDFBGZR>l@{$+Bz}%`M4ZY(J5|vIIuV8 zjZtH_Hruh8YLO?m*}PMYYFux^-g;1|@f}jew@ecg_H45g)iRa8;e@=cev`O;b)CFp zPQEC3a*`}8OsP8)U&~3^uZp7hC0lg%OI0iyZE;(bn%w!RynB0tC^__towDwIb<ft( zX6m!Ky0od!l+Ewf_dapLOe@xUdR2=lkM7bHML&iW7u$o%oPA;C@twi_U2lgob~gu! z))&JEnpXrfKX^Q>YIrnwaQ$jCtFA#mw7kyDPSol-kIXd5Q3Ju;(tI=5JQGyMvP|{f zn4V`(oB40F1Pe|^!kYR6x@PTTrsmyDkXmt{N$psxQz!R_>4G&uR^&hW8ItwSKiB?W zA>y^}^@-xC5%(0w=ZoRjzSk^Fi)1pzN1DG!_(=bYH$CX?r2`AMBX8U5-Z%2bk#~-~ zb>zJxZytH~$lFKWKhglw0n&ok^?)?tbzLBBAblW>Ae|tsAiW^XAl)GCApIZ>Asrzt zd0kIPQ(o5<(iYMe(iqYi`qubDZ=7om=niQQ>5rp9q(hDtksgsIy{=1+HodM-q*0_( zq*bI>j%JZ=k#>=OIT}Vf=4ct|8EM+<y5?xx>-t6-M><DZM|$UI9_b!wAL*ZC1CSly z*aBn^kWD~#fnyuIZXb}1@VcEqwgTA;j?F-J1KAE_KadSUb_CfHWKWPyL3Rb%7O&eE zWMjN;XOOKy_6FG;WOtD5LG}mPAY_M-EkgDP*(79_kZtn1eL^<M>vjs+DrB#a%|dp| c>$c15_6ylCuiG)N+cNyW?OD`~TS-~;FR%xpXaE2J diff --git a/lib/pytz/zoneinfo/Africa/Casablanca b/lib/pytz/zoneinfo/Africa/Casablanca deleted file mode 100644 index 04d16090dc9af7d28246fd6a855eb902c5d46559..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 969 zcmcK2KS-2u9LMoT?}EcFb0`Xj_*IBQ1)fwkgw;}Fr``EHr+23fEk#uHP+JH-MbJ`> z1#!eTiW(AE1s7>h!Yzpk9U3ZJtcNg69G<@KZ`(OF^gAA2_h(Rk?@v&@e6iEpbkh0_ z59gRZyw5%=uGeOh9noBQJe>Qq61{uY8804r;Vph|k2l(GoZNWxBg|a68)as%#aoxN zUiQM7c-z|(QS-x};r4}M)YASuY@K}R?b&@j?m6`>>Y3gb_U>=>dLO(G&o0eG`SV}H zzQ<Eh|Ka=bxtd5JGcASO6IZzON{XX*r1)`O2DUtvfvY#g@46-a#9r~2PP-sqlG3gb zDUE)1rPpa0JlG|JYuz&B-;<%+<F33jaODq9DsELOWnU`4j=Jh(qg2;wZn$kmh8Jq# zSdt`-4Rx<n-RJt>?Q<%XB)`9KQOzw4fA1{AdoP{u3%mmzIej|#*-r2e_pSd@!kr`c zj@-Se_m4C{I+)r5>0xRU{xDrkZG-gT&<N>-v_g6z&5&*!+9CZ+ZHRO<wI$LMX^M13 z+9G|C#-?^QwKdY))aFQcQ`;l`P2GU0J3zL8?7`Gcn7RvO8>a39*@&q-LAGM*UXab0 kx*JosgY3uD4Iw)+bxSy=m%nK}|6xx}$8}-dhjLAS0KM)Bq5uE@ diff --git a/lib/pytz/zoneinfo/Africa/Ceuta b/lib/pytz/zoneinfo/Africa/Ceuta deleted file mode 100644 index dd75e3e6e4116dcf9c72bf46c0d97b3a81d9aec3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2050 zcmdtiZ%9>l9LMo<s$1gJzQ(GtEL&#Qy6IKZ{!C}>PNmYCc`N&4W|{r7yW3rfw(8z7 zsWn8t16jmq6@`T?*TU8V7g1Csg#^PzMMN9@B}%qMg^2y$r=SOW)RP|UcR2TTxNzY< zIPZ^d(VEIM>py3J`G%VlGB?jd!sg@0KQ;yS?)NWic0CR>-}PHZ+7ETL97?l(-Zv+3 zbjQ2aFFQ9|#{!=4v0LATyAy-cy3afgM69a|N1R{Z_0GL0E2=!dE4n==9MkZ3VBqgX zVM#g=c>hF`HKJZ6e#=)fvMMMaELkC=3Vf1~l`Es&nUWZnBx9o9lO&f{lb%e}v9}X- zT;~w|@Z10$-}+452k+~I#%r2fcTrO|{-&OaBbr)zRnmg3lAhfr6X$*}8N>HTW?GG8 z_Jm~eutuGHqg<wRSL@W1Uis*HiDosGX?ELG{doIy&G{itr&T8C^tvhXNuEo+6~pAy z#Jig7?U6iJyXGg{l>A3`WJcf9GNa>y%>3(s%xY<tf=j2gu%TXN|FmD{`2F%({nuKw z;9H$rwNi^GRjIEaU*`>-t@GXEbpBJ1mJH65k}HFB;ghkl=+}RBac86~*%vFNtq)~s z=pQL-?2u(;ol?H_fiBNEAuCERX+`{QS()9Ul@Z5v)o|5S_jc*(o}jL|P^)Wi_;lUj z#aeZKgRK81Tm4PN^7-m`4Q$Vl4MkZJtQ;y|q>hk{xzA-&tR>YcgSEQrPpOG{sx_C- zO6{Yoy7_36eA&^aTXvt(tu1?WTkSy&HH6geWB2L%^5XH;)z8)c?OeUR-Tlnl$1%o; z9r60vingq{k#;QKThRrDK5IcPcd^g%ng=fr=Gc~PJ3q2*-y6L2z2<DoJZ>Q`KN0{E zfujili2(@$i2?}&i3175(L{oT;%H*wFA|KSi3SOWLp(@8NJL0TNK8mj9HK(PLgGRK zLn1>$Lt;aML!v{%b2RZG0U{A1AtEs%K_XEiVIpxNfg+J2p(3#&!6MNj;X0alk$@dd z#7M|U%t+8k)JWJ!+(_U^<Vfg9>`3rP^ho$f{Kx<}nh`*Tz|o8WG6={hAj5!+12PcE zNFYOjj0G|n$Y>zLfs6+-AdY54kRfq2V}c9{GAhWhAmf4z3^Fpv&>&-j3=T3n$nYTJ zgA5QdLdXy~nlVBK2^l41n2>Qo1_~J|WT=p_LIw*NEo8Wm@j?a+88KwY9L<;^gXU;P s4H-6M+>n7oMh+P|WbE*NH+Yd|2q&bngzm!h<cwsGyD-&*Dd~|f0Onc%B>(^b diff --git a/lib/pytz/zoneinfo/Africa/Conakry b/lib/pytz/zoneinfo/Africa/Conakry deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Dakar b/lib/pytz/zoneinfo/Africa/Dakar deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Dar_es_Salaam b/lib/pytz/zoneinfo/Africa/Dar_es_Salaam deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Africa/Djibouti b/lib/pytz/zoneinfo/Africa/Djibouti deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Africa/Douala b/lib/pytz/zoneinfo/Africa/Douala deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/El_Aaiun b/lib/pytz/zoneinfo/Africa/El_Aaiun deleted file mode 100644 index 7272ec9635d94eb1de70ae8d454b95f884222886..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 839 zcmciAPbh<N9LMn=wpt9kNICtG99V6VT{O9{W@Ga_`-_7MDTk-rwDNFqL2gP*-*Rvu zxlwc2RZd#su!9RZ&c7TV-_O_4g^OqX_Ilb{ZJ+nd{L;dJ`f<7W6&zfCKDhT?MrsdD zH)p#|&tB!7AFnlEjo2+auU2a%Y5RH)t;pz;9VvBL(e4fv-M+OZs%1Mi_h`lUF6_y{ zt=d$PDB&qlBDk*;i$|0!Y*6z4nx@?cG`+k+Mrf6c(@rv~W7>?RDAhhosltO!9lI#q zAENYUm@>vDW!6u0w$0SpJ54$LPPwc>xsM^8Kd-0!XHn1elxe0?wALKQscWeJ?Zf4A zoHtU+ab9Z5rBsWr&ci=jDKGAoCl)uAKM+)Xasu+!_mlaD5&1hg7!sevm?95~3!_2| z3o$Olzz`!t3@!555QB?6I>hi0<3j|1hyW1+B8JF8M2-RxM&vjUfkci35lZA(A_s$r z1`$r=cp?Xch$wPMh?pV=g@`J0ScteH2NpRpL}-y?Lj)H&I{dfrjl=wB2>XMLUnl#* AVE_OC diff --git a/lib/pytz/zoneinfo/Africa/Freetown b/lib/pytz/zoneinfo/Africa/Freetown deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Gaborone b/lib/pytz/zoneinfo/Africa/Gaborone deleted file mode 100644 index 31cfad771a5c7c609e495da650e3ffbcf07c974d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c diff --git a/lib/pytz/zoneinfo/Africa/Harare b/lib/pytz/zoneinfo/Africa/Harare deleted file mode 100644 index 31cfad771a5c7c609e495da650e3ffbcf07c974d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c diff --git a/lib/pytz/zoneinfo/Africa/Johannesburg b/lib/pytz/zoneinfo/Africa/Johannesburg deleted file mode 100644 index b8b9270a142bf3b1b4e3a773a0d7ffc52c489bb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmWHE%1kq2zyK^j5fBCeHXsJEIU9gPliT@>GwXU9&d$p(IM<igaK0|EfRULA2pJfp z9DoWKL?u9~wG0@6q>KUsi;r&zL$G6T2uKbLLP)UkKM>?rJ34@9kkud>WIc!mIRQk2 QoB^WAa0(a5VY)_K034z`A^-pY diff --git a/lib/pytz/zoneinfo/Africa/Juba b/lib/pytz/zoneinfo/Africa/Juba deleted file mode 100644 index 83eca03ab87f54441e6fbff4cfc08408c4e77c16..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 669 zcmcK1t4jny9Ki9}dwI9+spsoFy~9Ne;vt9{EaHm;@c{$3NrRKTGl;<^c$icWi+_N{ z2Z+gHwTNIFs~E<lL4(nx^Ib3*#NaHmpJ8EP`2K>$)s-RfW5w(jHmk>O&Y7#@hevUg zHO}UUjBI_F=u$<;RL#W4UUgzZnYwOYCjBXs5@qd*UgJLP%6gM9-i;^I*Dt2wbX+%{ z$5qqLhRp0etLDm?ZmHg>*4d(No4HW!#buf8J5U|oq0S}ORqpLpcE0bME<Gpn_iLtb zS<|E2B|qZ)@<fFwoN)F2LC4-a?~4>?k)B@&#PnpDS0GAuf3M&V89wa_Q}DCR0VF}P zAZd_1NFpQ?k_yR%Btx==+H^=hBq5R!Nr~h{k|J4=v`AhgF_IZcjpRm>BiTc3dL%zG S0%Q!xDDa=g;o8{@47*>))q+(3 diff --git a/lib/pytz/zoneinfo/Africa/Kampala b/lib/pytz/zoneinfo/Africa/Kampala deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Africa/Khartoum b/lib/pytz/zoneinfo/Africa/Khartoum deleted file mode 100644 index 549dae276737404ef7524627094a0c6b5466b5c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 699 zcmcK1y)Q#i7=ZE9+umxg7WGx7xCw)3B+?ix;ww$WM{<(h1%opwgBWZQO(!H`@dsFh zL`)W|MI^RkH64kBfx*Vad1^2i#NfR-{hi$8Ce8B(3#-d{@nc;3hQsvO!##CzbSK1N z+JrWTq_g#I!iyyxQDx(fJnQI~GBw@4jQJBLE{fU{y~exWk+mjj5?2YCynHfs$D_Lb z)Kv}J8#1-~s2WQrx@qM`HBaYs%hZ`_%`M4v-@a-~9Oz7JU1eUbW&7)%>CiJWd%I>j z&&ztaQt;>B9;6fc<q11NbXDx%7X5>vh!9y{#JP*)*L`*_5R>CYegaXj=X-)bH2JVo z?ZW4_4bTT^gmgk$A-#}hNH?S%(hq4^)pkT$B0Z6&NLQpS(idrrbVgbuy^-cfcceYi eAK5`wy9Z<!$UcyrAbUY}ga5rB&#r7B?|lOQ>V@<G diff --git a/lib/pytz/zoneinfo/Africa/Kigali b/lib/pytz/zoneinfo/Africa/Kigali deleted file mode 100644 index 31cfad771a5c7c609e495da650e3ffbcf07c974d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c diff --git a/lib/pytz/zoneinfo/Africa/Kinshasa b/lib/pytz/zoneinfo/Africa/Kinshasa deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/Lagos b/lib/pytz/zoneinfo/Africa/Lagos deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/Libreville b/lib/pytz/zoneinfo/Africa/Libreville deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/Lome b/lib/pytz/zoneinfo/Africa/Lome deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Luanda b/lib/pytz/zoneinfo/Africa/Luanda deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/Lubumbashi b/lib/pytz/zoneinfo/Africa/Lubumbashi deleted file mode 100644 index 31cfad771a5c7c609e495da650e3ffbcf07c974d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c diff --git a/lib/pytz/zoneinfo/Africa/Lusaka b/lib/pytz/zoneinfo/Africa/Lusaka deleted file mode 100644 index 31cfad771a5c7c609e495da650e3ffbcf07c974d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c diff --git a/lib/pytz/zoneinfo/Africa/Malabo b/lib/pytz/zoneinfo/Africa/Malabo deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/Maputo b/lib/pytz/zoneinfo/Africa/Maputo deleted file mode 100644 index 31cfad771a5c7c609e495da650e3ffbcf07c974d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UIhx##egSgidO)Hw3Jfehz99_Gjv*i}LkI~5{RaZP MhH(K+)ivS*0NorDCIA2c diff --git a/lib/pytz/zoneinfo/Africa/Maseru b/lib/pytz/zoneinfo/Africa/Maseru deleted file mode 100644 index b8b9270a142bf3b1b4e3a773a0d7ffc52c489bb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmWHE%1kq2zyK^j5fBCeHXsJEIU9gPliT@>GwXU9&d$p(IM<igaK0|EfRULA2pJfp z9DoWKL?u9~wG0@6q>KUsi;r&zL$G6T2uKbLLP)UkKM>?rJ34@9kkud>WIc!mIRQk2 QoB^WAa0(a5VY)_K034z`A^-pY diff --git a/lib/pytz/zoneinfo/Africa/Mbabane b/lib/pytz/zoneinfo/Africa/Mbabane deleted file mode 100644 index b8b9270a142bf3b1b4e3a773a0d7ffc52c489bb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmWHE%1kq2zyK^j5fBCeHXsJEIU9gPliT@>GwXU9&d$p(IM<igaK0|EfRULA2pJfp z9DoWKL?u9~wG0@6q>KUsi;r&zL$G6T2uKbLLP)UkKM>?rJ34@9kkud>WIc!mIRQk2 QoB^WAa0(a5VY)_K034z`A^-pY diff --git a/lib/pytz/zoneinfo/Africa/Mogadishu b/lib/pytz/zoneinfo/Africa/Mogadishu deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Africa/Monrovia b/lib/pytz/zoneinfo/Africa/Monrovia deleted file mode 100644 index 2a154f464317b10ba66932061e2635e7a863c7d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmWHE%1kq2zyK^j5fBCeW*`Q!c^ZJkg7};{%%`IA7@3&=|Nr`gfdNdi0Le*E4IDnc lAq>7i><+{*5JG}A|A8Q?YS|nR4YCeo1{qdz0qr;70svU3GM@kd diff --git a/lib/pytz/zoneinfo/Africa/Nairobi b/lib/pytz/zoneinfo/Africa/Nairobi deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Africa/Ndjamena b/lib/pytz/zoneinfo/Africa/Ndjamena deleted file mode 100644 index 8779590e04a66e4287cabe801aee9c8d2d793892..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@K#}vxXF*w88>mwR;JSOpFW+d@>+;J^=<61_l`gMh+j} m5QcC^5DpFj$pAqJ3FiL?0+1abeIR>4G%0p*0qxc`<N^Tu;2NC( diff --git a/lib/pytz/zoneinfo/Africa/Niamey b/lib/pytz/zoneinfo/Africa/Niamey deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/Nouakchott b/lib/pytz/zoneinfo/Africa/Nouakchott deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Ouagadougou b/lib/pytz/zoneinfo/Africa/Ouagadougou deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Porto-Novo b/lib/pytz/zoneinfo/Africa/Porto-Novo deleted file mode 100644 index cbdc0450fc3b97bc436f6d90798a30ebe0ac30b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmWHE%1kq2zyM4@5fBCeMj!^UITi+f4ghkP4M3uN0t_rZz99_Zjv*i}LkI~5{RaZP MhH(K+)ivY-0B~s$p8x;= diff --git a/lib/pytz/zoneinfo/Africa/Sao_Tome b/lib/pytz/zoneinfo/Africa/Sao_Tome deleted file mode 100644 index d2a64bd1d3d5e7bbdf3b44882005ff9420c12e68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 225 zcmWHE%1kq2zyK^j5fBCeW*`Q!c^ZJkq-T8%QJx(Fj7&gbwg3i(|Np-y1I0lAsEAL1 ufy2i)guxw%!yQ9_f{b7sLV`8_fgsN2uK|b#SqC(Lfeb6TfcEPeasdFpyd(Ai diff --git a/lib/pytz/zoneinfo/Africa/Timbuktu b/lib/pytz/zoneinfo/Africa/Timbuktu deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Africa/Tripoli b/lib/pytz/zoneinfo/Africa/Tripoli deleted file mode 100644 index bd885315f84f8615da866553c9c0086ad430ad01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 641 zcmcK1y)Oe{9Ki9Xt$}*US)7;RXlo!MImJk57onkU5{bt^QqwbvK^lJolR?C25Ro)7 zh{0lNBC&~(r-?>FBF7@J@O%%G$>4YQ-1qL5yZf9spI>psuc<P3Sd3#9=Z*WX=ZV|X zW9u${D9dYCR{3FBR|ZB^<)EaWvKM~S*0{8*-<18{r<)&p{g#_W*;+dC+s^J~thnyC z@7cOzyH<5>K1)YUs;;FC-JQEs@pMEdQei)t9FaX^C%&6~k%Q@Bl^R;rGrK!t*1Im` z^2I_p6l{_2eqC`ici4rfTQKh_Vou1sZ-XUjI2ZL()1H{f%yIBU#;l+5{_yc1W&ofd zP#`E6K@A86C8&X+;P6a<C`dsK6a|X{MnR*%QSc~$6hsOn1(O0wL8ZV_a4EnPWC}C| Un*vTjXMm^wf*&=1qTh{v0>JjEDgXcg diff --git a/lib/pytz/zoneinfo/Africa/Tunis b/lib/pytz/zoneinfo/Africa/Tunis deleted file mode 100644 index 0cd8ffbae1f125920be88989f19c140ede9ae0b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 701 zcmci9Jxjwt9DwmlUqlP7CqhK&Ag#3yI#hHiovdvU-wI|>rw$@?3(^nJ(Sk$t132iU zDEJjru&AwXt3!i=-Q)&W(R%_q=;Y#syI&Go2+!YIEw7J@U#mmEVY6a-bKg_d$9Ac> zc#+<8>&v^P`eV$!da~T><FabBXJzxC&uN~SqS-i+H+z%fc4tD~)t=q^4O2cWn5A&N zr6RQ>yZdlW$;yBo+fS=_W<(8*_NzoRq=qx+b~2c>rz&BU%~Zv7qV43e?=l~}bPDm7 zEWEzR8ULv?0zSVl5d6OM(~nDtt}Xqs!j}>OA)<purw|b-!e1wE=H@L?)H|D7;;{73 z4TC?itUqM=&}${%`-bE}5+Rv9bt)tmk_^d)r1RAIJas}OBa#xyi6lj`^1Y-*@*;_m q%t&e^H<BF5j-*HOBO^e@z+x20IFOMbWAW6Z!G9W0NY86575W6_y~}L? diff --git a/lib/pytz/zoneinfo/Africa/Windhoek b/lib/pytz/zoneinfo/Africa/Windhoek deleted file mode 100644 index 6766185683f45d5998f2b84d0c384067a46e19dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 979 zcmc(dO-NKx97q3SI@AbyH6sxh;zBJ^$I(IadsIftZ|BLR5Qw1VqQ=}Ph;UJt6J1xL z3m3L<At<eEDFlg>M4JK^GAl(~2_Yhq-Sbv~D_8v<_npUEyu~}m85$o8$c=gJ8x9NF z!}CLJZ*$@AufA=@_5NyXqQJYJD2eY5myYMEve^^yxBpS)FH>}&D4{CC$GS4($^;Ac zbnxuZRCzaa)sG!hee_O0`M73kzQlCx;)1E$ic9_5G1Kt=jWiC;tEO15gla}rb5B58 za@tgDS(db3K2qVda|s`$s<w+v-Tt}IJpJ`sKl^acbZr0BopYzAYw45j9@#fNQ!Bc+ zdCT+-Z%cpvqKPyui<7#k2J&WP;MAz7ypZVEN%cI@D1#f0@+93!N=d%uC8F-ye<2UD zC0QglPkbVe{T_QyQ=WA9&Hc_UM~eOB6@H0?B925uj<neG@0?rw`)j+`Gab_q2M`Ys z7kn2V5GN2X5H}1z5JwPC5LXai5N9r}H;6lz)*r+n#3RHd#3#fl!z;us!!N`!!!yJ+ z!#BjaOY0rt-lg>qX#mmzqy<P1kR~8qK-z%x0cix%38WQ9FOX&!-9XxLY5RdR<kEHo PX^H*so_sd2l7R0omtxFs diff --git a/lib/pytz/zoneinfo/America/Adak b/lib/pytz/zoneinfo/America/Adak deleted file mode 100644 index 43236498f681cc06f64ca2afa613880331fe6fbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2356 zcmciCZ%ma{0LSsm-|Iy&Dj-Bm!Hl7RhrdBt9tc86<UwwrWC#iolmVg)3ox8hdC|nN zoE0&9(I3~SV{FZ&u`@U43+KAv*1#M!H|MM|UA1J6yq)jK){C0&@;rN<&)MC5`}=yU zn_f<L{p)zlFT9+7^Ky@W%Y4rF75FBW|JFKD=g8X=FQ_}G+8qC<Ug<hk;RGDYmVupF zPEgxM9b8xL3n|akp?MiTcUrV|zrDlfiI~-%;p<M=%}aXzk5j${Q@3Qe9`!B!dP+WU zV$z9tcT_&uciMSq&j<41ra>oi^IjQM+~Y*&*2zbbYMq#bZoSBp@5Baf)v>D*mc{<! z=*3quRNO?mUUDW%J^E#&Ui#rJwXCB^#`jLCgvunjy!m(WSoVCmqGVD$9yKEqSDqG$ zeveKH8x%>?KkJo0^@vqt7j*K)_f*Qz7dmyMORerXqQyXsN^AUFrngI#QPeLpD-u*z z;!c^J5v-nYdu2{syvVthEpz9B#FOV@<Wt{Y6>C(cetPtrc&0yEuYLc7kS()1Z~s}9 zUv^19TmOkFSpAJIEa+2(zuu5VDIbfXi{r95{E#Rf8IdJ3&EomNZ}s}`4ye+ulX}Bf zuc)#u1KK%SqRQ9o)*CyLRYhEt_Es)b-nm>|nRQcDUagdymWGQ>XLID{J2yo2N3rt7 z>2a}T|D1ejY(&)5Ps^=C?}*yc+q&-HNwqEIvfkb}pz6cNbVJc@)i85hHzro8#tZv& zlRH;64cF`DYm3#ZM|<UKz8tZmW4nA^#fp~7LfLwFPPAnw%AGCKqCMIpca>?e%fCW* z<Xl!AKe%;g%$VvNyRP@l9#?M+o!4(p?o(Yo!@B!az3QnstoI&!P6Y%81q6rO>j|Cb zzK@T~_1P7d%kOV+T)}>Sdu_lx`(0pviLm!bzOER*zqd7DiM=mcU+Q&js4#Dpc^$7S z-`w*Hyso@;=CaOQ%n9Jb`Rn5S?~#R>Kk#ynn3sFJ-<-8){usyZgVi<2=#b%A&G?W3 zq8%X@hR88v1O|zW5*a2kPGq3SNRgph%~+AaTFq#Y;UeQj28@gt88R|vWYEZ{kzpg_ zMh1?I92q(?c4Y9#=#k-D&G@Y*07wLo5Fjx?f`CK;2?G)bBoIg>kWe78K!Slp!)n5T z#KUR=f<y!f2@(?|C`eS0upn_k0)s>b2@Mh(BsfTPknkY!v6=uO5kf+Q#0Uuz5+x)| zNSu&BA(28tg~SR877{J12^SJCs|gqqF{=p~5;G)dNYs$9A#p<jheQqu9TGbvcu4fD zCVWWztR{d+1g$27NDPr6B2h%bh{O>IBoav^lt?U*U?R~(!imJwY66Nx)M`SC#MEkn zibNF&D-u^Eut;Q)&?2!#f{R2K2`>^~s|hd?VXFx-5@V|gG7@DZ%t)M(KqHYxLXCH0 z9UK@EdauXrnRg$bziVB+?f+@^KheH>3o}7a6Q=0Nr5UN|sUo>FEiE-IRfPQs4Q6_I diff --git a/lib/pytz/zoneinfo/America/Anchorage b/lib/pytz/zoneinfo/America/Anchorage deleted file mode 100644 index 9bbb2fd3b361ea8aa4c126d14df5fa370343a63f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2371 zcmciCZA{fw0LSqQ0v8C15>k=qB=Y>=0R^EbF9-r60dl(ukxF8BSP2AU_(W1Vaw{?0 z9L`3^IkwuQo#|G(7TvVgCgupY9>$_{YbHzAdYVOYJKvM57d7AI|G)G99PW7g`??!i zp3HIl>j^WzaCr8a!#!oE`Hb$#^NlC`+&11+EPo#_Q!^(vxcqOdkdA>;SHO!YGO#<@ zHLJZu2Q@AC1=l9&kfKDNGdol}UtZ@6i<;75!xOIXAI|FAz8UpJe0f<$`i6bCpB$BU zym`hIb#PeTx#y_st}Xp?cFSH@bbY&wsc3WET~H_Iq^@?&UC^rMg)MQ#2G;7>^ysMA zAB*+;i-{_3e4)PQlvBkY3(@x;zN|!7fxNGGR4wq#mkFD`6AN>%%fyvuL{iMxGCA$2 zNS>M2so{G?>f~2CZK_SAkG!ul&cCEG2M_D4<D1o@o)@%ywMJ!omCWhLQH#r-mrLrR zRc>;#%***zEp@Jt`Ej#F{-qRIF#U_T|Ko7^z{KaGP$%gJ-#sZF+83&q9Xcdjty8*a z*E_1X`mA2wd{C7vdP|p<Y*VE_U65s&1ETEwX;~4uRa6`wk}Iz?iptkM(5pV{R#n@N z=!f5KP}PmQb<Kf7Ra@xQtGnV=U0j8BdmPIBN4oapUR0iM%jKGQzgY88nyjC>AR2}u z<YSYkMdPlk^6`-&v9@_kt{dzV>#M%kO?^ky6Pf4q2Jddw9I5rjGOyZrWxw_&S19i% zow~)Du3CmYdefyy_0)k5`Se(tc&6(SxmibuR?kw|)_+yB=gpJPwvLI8m}%KreN1%v z=jg8dbE<3dH{Cr~tL~8rz2(||wRP}4z3q!mwY}$cz2k&O^{nmH&kf|OfWTP+LBThB zLqeUm@O3yoyykHD{T=HaL4JR4TR^D&M%Z7X>^+9BBi8Tl-x&~Z?+L4_+>W9;a~?IP z#+-8gC@*n4>bX>!OHrk{nJ0h`&tDh!e{U_^`~!#Q6?3?!_|3EI)b&qsM_*AnvOQ#f zR<l85hiJFRg+20^O#-__wu$T$*(kD8WUI(tt!A^xZmnj!$bOLxBRfX6jO-cNG_q@C z+sM9=jUzipwvOx_**vm)Wc$eet)>B1(*dLfNDq)EAYDM(fb;=r1kwql6-Y0TW+2@_ z+F>>QKpJ8-9YI=x^aN=N(iNmFNMDe~Ae}*4gY*Vz4$>W@JxG6$23bvqkQO05LYjnh z32773C!|qGr;t`5y+WFWbPH*h)$|K#nALO)X_?jZ3~3tDHKc7w-;l;3okLoO^bTnr z(mkYoR?|PEfmYK&q=i<~L!^mF7m+q1eMB0GbP{PL(o3Y7NH>voBK<@fYBe22T52^t zMVe|gT}9f8^c86=(pjXnNN<tmBHcyWi}V+1u+?-JX|dJx7-_QAbQx(g(r2X6NT-oj zBfZ8O%?=6-4!POu3=6%5@88kx{$JDmPrGm2!ijnTdC#a?oRyO$Gpe$)v$C^f_@5Pu BZASnA diff --git a/lib/pytz/zoneinfo/America/Anguilla b/lib/pytz/zoneinfo/America/Anguilla deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Antigua b/lib/pytz/zoneinfo/America/Antigua deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Araguaina b/lib/pytz/zoneinfo/America/Araguaina deleted file mode 100644 index bc9a52283bf2cdc3fcf777435fa9651122fc3e42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 896 zcmb8tKS<PJ9LMqBT`F>kwlp-TPl=ntxBFds{-rg@Q22p=h9-?{$-zN~um~qXaZ%9a z8N(r-@oXxn)X75>LG(Oz%)vtKIFqO6{j4tDPky}Qrn}Gk8x4)VN(MKcJMI%+o|Jp} zziMG+TrxG!@|o+Wdi8fozU0Q$Ui!1_{VK}-gCn!QI;ak=?n`btrw$L6Bp-$9e9@YV z@|WuJ>#+Q-uG59SCMm4_P{qWSDSj&GtJ4bkH}g){Zp=#E#ICN_PmBsT^}`RN^62im zZWy>{8ji<QW9uK&xV@(2{iHmu?a|48Wx|R#DwU2TRrsb_RK2upzSgamKTSF_t=bMx zq<yYQcdT2}`L<ejeV>=^7iYTn<B0UBHa%snjmJv<i(9*u4FYRV^06{&cd|j{^PVVZ zmZW<_)^bl&!lv$PSawCW^}2nLjgg&^t-Wq<WOJ|E9oZh)A8CMeKw2O@kS0hMq>b10 zK^l2oC!`h9i{D8zq#M!>>4!8#IwCE-t|!ve>$)Osk-kV{q%+dm>v|*2y{<db9_f$V n0CES&Eg<)R+yrtL$Za6^fm80cmV6<x_^oav(dq7`J52lsO{GRt diff --git a/lib/pytz/zoneinfo/America/Argentina/Buenos_Aires b/lib/pytz/zoneinfo/America/Argentina/Buenos_Aires deleted file mode 100644 index dfebfb99abb86536da12b96677dc0c76c088cab8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IKWLLd9Eb5YHqjUg78Dc_iP%ya5so%7;-B(LhN?764Je%yL=XhU!3v_$#YqTA z5QLV-c5x6qhiYk~J&iTCLkXlJf<r)YQ&eJzReS!Pn-mwFd|xh~Bhw4tCo_2RM7Q_H zvr_)y@bt>zzV5R8j9t&E`xgfM2OH<~<mQ||xp+oRt-oldCdT!2^PXS4@lri}_0~K( zAJH?@8+<dE(~n0+{3p9hs&uJho^ERB*`q;!b~&cX`;w+yyQ`jG$@}x^TYCP(RkM(` zYC*p@)vknIOdU7%y<t@!ZI~~iQ>xM3<~QDr>gL*%X_l+%YkkOHs_xL=N(uA5cu4)Y zGvF@|CzQPw_g9S{)ZzBig{}Ds-QJcjbR<9Pj*sUGom<wZ&dQNOm-k6^>0O0bt*W-& zJRD6np6Q)4G2J(`TKB!qM)xm`=>x@M(X6#Lu%hKJ-%#+Ex5eE<*3QK}&)QPO3tC$? zUf9|fl|ZYtZ)$-y?p|wuI^(6}Y*%qPJ#v2EB=Zj|$p_oB!?*j9wUEV-)sW?!vL3Ra zQ&vQlMAk$WMOH<YMb<?YMpj0aM%G3acgpI>@=jSFDFCU!DJ38^IHd@r3Zx9A4yP1? zRDzV^lv<EtoKg)^4pI+N5K<9R61tkWNKq2MM^#8!NL@%_NM%kb4XMp3#Ua%>r97lQ gq(G!Xq(r1frxb})iIj<1`LzFEXJn5w+uIZQ2~&paD*ylh diff --git a/lib/pytz/zoneinfo/America/Argentina/Catamarca b/lib/pytz/zoneinfo/America/Argentina/Catamarca deleted file mode 100644 index b798105e0f660c7b85a663d945d9eb7f04505e52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IJ!n%=9ES0mHqjUh1{4$#sn}8)6%K7<#gB3%LqW_^14<_c5d=YTu!3lGaS{Sj z1fiwTu0oGPwKUZp8*8*f38X~?hk)XysKgMzdi+1fNnCXDUe5hp?sVaOGJ~g1b_IW2 ztK<uZt4|K^8?MOX&W)V9e{n!R*!<i~ZO!SarL$^!<0U&iIc^G#dwS;P3-$2j8~f-& z%*+-xX*-xRk4H!K+`ghJUM|}wTk2;1cv#P`Bvk2OuPs$4)U&I@dLez=EWE#F7t>BH zns>I+nKVnO6Sj6Bs%m3(`#Ex2)w|-l{&vhX)~9TvR8e1QL;7oFxA|5~+V3+*)Q`IZ zdSxW3-1Q#4di;=yww=juAD%RA@!@=X?<dp#;e5Vh+d9=zK9=tcobEJx^NDIj?YMQc zwYUD%?3qm%UHGW<@><(Jw8r$m%C=^mbD^f@zq}*iUw0EWN1Egrac(XX1kM%9LD;#H z4WiC1mqRViy{?Ahyaz3Z)0rS8XP4@c(=F%sO)~$mlKikcH!`skSqoVVSq)jvE9)T( zdSyjqNn}lAQDjwQS!7*gVPs`wX=H6=aj&e7Ebo=|kphqkyix*EgI9_`szAy>>hMY- zNF_)qUa18s#w*nz<skJS1tAq7C84j07b!~O52y+$3#kh!45`d3r6IL>r8uNIuat+> hhZKlZh?I!b=#?UoD!o!BX64uZf1R=Y(rjOM>?dvN>pB1c diff --git a/lib/pytz/zoneinfo/America/Argentina/ComodRivadavia b/lib/pytz/zoneinfo/America/Argentina/ComodRivadavia deleted file mode 100644 index b798105e0f660c7b85a663d945d9eb7f04505e52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IJ!n%=9ES0mHqjUh1{4$#sn}8)6%K7<#gB3%LqW_^14<_c5d=YTu!3lGaS{Sj z1fiwTu0oGPwKUZp8*8*f38X~?hk)XysKgMzdi+1fNnCXDUe5hp?sVaOGJ~g1b_IW2 ztK<uZt4|K^8?MOX&W)V9e{n!R*!<i~ZO!SarL$^!<0U&iIc^G#dwS;P3-$2j8~f-& z%*+-xX*-xRk4H!K+`ghJUM|}wTk2;1cv#P`Bvk2OuPs$4)U&I@dLez=EWE#F7t>BH zns>I+nKVnO6Sj6Bs%m3(`#Ex2)w|-l{&vhX)~9TvR8e1QL;7oFxA|5~+V3+*)Q`IZ zdSxW3-1Q#4di;=yww=juAD%RA@!@=X?<dp#;e5Vh+d9=zK9=tcobEJx^NDIj?YMQc zwYUD%?3qm%UHGW<@><(Jw8r$m%C=^mbD^f@zq}*iUw0EWN1Egrac(XX1kM%9LD;#H z4WiC1mqRViy{?Ahyaz3Z)0rS8XP4@c(=F%sO)~$mlKikcH!`skSqoVVSq)jvE9)T( zdSyjqNn}lAQDjwQS!7*gVPs`wX=H6=aj&e7Ebo=|kphqkyix*EgI9_`szAy>>hMY- zNF_)qUa18s#w*nz<skJS1tAq7C84j07b!~O52y+$3#kh!45`d3r6IL>r8uNIuat+> hhZKlZh?I!b=#?UoD!o!BX64uZf1R=Y(rjOM>?dvN>pB1c diff --git a/lib/pytz/zoneinfo/America/Argentina/Cordoba b/lib/pytz/zoneinfo/America/Argentina/Cordoba deleted file mode 100644 index 5df3cf6e6377be897b4d09fe438ec75eb8c15ad1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IKWLLd9Eb5YZK5$03@9if60xNuA{=dF#6RVg3{^2p4Je%yL=XhU!3v_$#YqTA z5QLV-c2zuwYH6cAjWyb#1X2;fA)vS^A~D3OJ%7(RiHlC=<=)Sc>4oo;9XffU$NS^A zLjK|K>zBiQ?PYn5U(c)i7Y6+Y8(!$CO?iK6@r;^YchO8wPUxA|J->A0m3sL4oq2RV zqGxB;`(`MwACHduPj;16`BK$9-PqJ~M}z*{aza)1rc9-NS3SQ{@aHqP^!&%GW+7wM zg8pD?@uXf%A2*FXVbvIGnlGVKs@W6uoA1YTYfaj;DmC@BG3+nZw(D=@r1@Stq<-8U z^p{7H%3ka9S56$z;m*^=Erm(l87&mMQlE9#r*p;b&8t;+^++-9SwF6K78CWF+IsVF zEY*Cbcg!aA!0;+P@Fo}Aw=}NzmyX49*4jW@`(Hkx;IF3*+uc26ZMo`s?j5wYV!W`m zFROtLYv0xbQSM&H!A#am%h{&-<n+q<eUt1ztRx?7&yL*D$Xdu^$ZE)PPFW9G&?ze- zOCoC`iz2Hc%OdL{3nMEdOCxI|i#ug?WO=8oj}(AZ;FJ=O8k|xDQUy{5QioFtK`KE? zaY`*nF;1xlDF>+sDF~?uDG6OoT%;(8-=iv|ETk@^Fr+f4l!nyil;V);oKhZAA5tJv dAyOhzqf?4Rszl1foP65<uQRe+n(gn6`~;>^?DhZv diff --git a/lib/pytz/zoneinfo/America/Argentina/Jujuy b/lib/pytz/zoneinfo/America/Argentina/Jujuy deleted file mode 100644 index 7d2ba91c679aca41eb44aec6115745ab107531e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1072 zcmc)IKWLLd9Eb5YHqjV51QZkzDcDjQ5stPoqE${h6vQkwQ0U|!f*>dkRuGLY4k833 z2trGvT}98K8ro{l#+urp1X2;vA*i@nL}G~ldj6i<#6>6Hm&@lRyu%CMCo_EFcz5u} zwO0P&aP`UIdE+JdxqUsS9-JT454Suw#cerVTt2O)HeIk&lM`mTd0)@mc&Q$}dT$?} zi<#N!&Dsv<%#*P(J-4r<N*62k>DGpsKN{Bas|i&;n6l;CJ+*K-uNTv|%;L%wyOefn z$$YfcuB2J+KW6LuqpCjMuwNo4Riitu8z06^b3?yvmaFP(eMEn&?l#{`N&92wi28YF zP_K?AmAlrf*G(KU(e_h??fFU59?us#QlCx7r?Z95#E|WL``YYSxvIO&-a?{QRXcAE zwWS)*%%0hV85mh_2Hs@bvd+2CnwG!3BVlfN8C&E;oSRz+0_RGVAnaV(22tl;R6?!J zy{m=dJO{0Z)0v=O&Mws}r$^53OJx3G8~I#!X7p|m*$CMQ*$UaqE1My^d1X6fKV(B> zM`TN6Ph?YMS7cjcUu0usXRmCH?Cq7!k=>E)y|O>jfLA&|T0nY0n(#^&NE=8WUTFmB z#4D{Jy&%mX-5~8C{h)7%2kA(X4`>PL326%H3TewLeIbo`r8A^8q&K8Fq&uWNq(7uV UuXKo6`IP_NVysUJJ<t>T1rW#RmjD0& diff --git a/lib/pytz/zoneinfo/America/Argentina/La_Rioja b/lib/pytz/zoneinfo/America/Argentina/La_Rioja deleted file mode 100644 index 7654aebf0b084f081db62e2b03dfc287df974e35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1114 zcmd7RJ!n%=9ES0mHqjUh1{4$#sn`z_6%K7<#m^%d3SyQT5IQ+XK@b!ND~MJXCm|p~ z5Lz1TDtH{CrLFeVSfd3?AuS>}1Qa*LN)7d^$NzJj#6>sn<=o#T(}nj*w&!GTTkyxV zLVn?J9hAfSy36voeLbh{U+C5kHa<5an{#?(?u;5;f6<N>ht1g1Jw1NorF!`4t$lPp zW+uiqXxo!BlY@i$@!pauU7EE|Hr343@vxp+PO9>uPFt?tRnM;E^>pTznf`Fq&SadL zG4E|9kur1X6LxWbR4oqG?B~cSRcnju+PfjMv^H&*$`$oxu}^=k>@wd<Df@l=sQPiI zTQ3i!l)KiUR}LRG(WcXdt@)y9isuW>ou5qe$8&|&Eo)Tki(+A0ON&kf3o2pu6q417 z+J5s`V`uHD**%dodTc@I`PH_oZ<XnK-QU>noD0>}|K%MC|GMiu^%0CXH<=9r=Ss6d z*txO|qR!3Fh8moEQw_y=4;qeSvO!wTPSqi&UC!^rWdC6*`C@l=!0bRKLuNy!L+10! zgvg9unG%^3nG~57nHHHBnHZTFnHrfJnH-tjE7K$MdnExR10)5n<bWjMl`N1nkUWq? zypjo$3X+Rgl0mZZN;*hBNJ2<PNJ>af=#%0_vXXy5Kw3y%NMcB4NNQfm4N1-`*&*p6 n`FSNlBts-cBu6AkBulTPiR9^(M6q8!@ju8kwol?c&>s5<b@}Zj diff --git a/lib/pytz/zoneinfo/America/Argentina/Mendoza b/lib/pytz/zoneinfo/America/Argentina/Mendoza deleted file mode 100644 index 1032356430c8cde6ac198d2cb13b71f59766745b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IPe{{Y9LMpWy44a71B3n$A*7g#;6b+*RO1m3Mzo8K0#6;H2m+%+2K`xGIt7D} zAW&R+4SEVJQEN{vTM5ZvMg$#<qFWKM#<KG1`+jcGrB3a~v)6B9upfNhnZe^nyMsSo ztK=IFuaq3_*Ityjn^$w{-q`_tf8%pAwK=D!7Eh|_b?5E$<b;{2-_f(zUa1GK-`j_0 znoVJ5y|#ln^Jr{LKi*YT#S3NoWK+$|9S-Tam6$5^CT(fywt9AHM9-&hnE6ka?LykA z1@qBXx)Nrw?})ALiKyy$&3+A^P_^!;u6-Cc_0~RHFICjH>abp}>@eSp3HxLAfcklB zK(CA@l)Dnwt0(rENc&KJ%gCf@kB;O!l3z^6=hOMl4RPK1qG+}*AG2L%uZb<a)!VKg zYD?Chnw^E1(Wce(9!%N(;WeiJO|~uToNH)o`pYXE;_^QZg`2{S@+s_G;am_nS1bo1 z=Snt+IQO#L(Bj;?rG_Z?LCgMhCg_v1UB%_}$oYMf%s;Fo5A05j-j)Ayj<t}*kkydo zys{p$pjTEzmPFP>7DZM?mPOV@7DiS^mPXb_7Wc~P$nsuUA1MH-z$+ynHF%{6qza@A zqz<nXf>eT(;+0yEV!TofQVvoNQV>!RQWE-_xRIhH{(!2GvXHuv!jQ_mQW{d5SBgWb lL&`(yLkdJHL`p<zM2bYJ^h%kSl}G#kbvEyoW>Y=QzW@=X?P~x4 diff --git a/lib/pytz/zoneinfo/America/Argentina/Rio_Gallegos b/lib/pytz/zoneinfo/America/Argentina/Rio_Gallegos deleted file mode 100644 index 3c849fce2f09e040563440b93d1eae975eee4eea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IPe{{Y9LMqBbgQ+51_eb(7%Ac+cu?1(Kk~#5LgdAS!czwcg23pIL6mjr6bxbn zf#S+*&{JTEv-Z@ol?NG2iJ-%v=vHLdSpR(bzMoUkrB3~RJbV52JN@AE9vD1%qBHp8 zS|e{bTs?Al-gHTRZe7c$`{(=hgRL*j^tOziUOcU4HeawaQ<ElNzpH1jzf=!jy|s_d z#Y`c;MccuQc|1O@pX@8D;>EIk+FUbp$HRJVHK9reQ?^vSqn=;R>iNE#X8yw!yU^#< zf_ZN%9Z9p89<nP3qH1NLW<N(xsaj`T*WOK-`o^@amn!PZ%8342*=@cRllJ@U5%uGC zzg`_nDtEP8ubn((qOHTZ9oZ?<8qen1QlCuQ$FsTi?Hg2k`B<(aSXLcoZ!S@-sGT>C zwxnv$%$`ER==`$MOY3d#$U4*edbDNKITvbZ{L4EM{^f3rG|0b*bA{$0aIRPm!p@a! z5Or>;9BOjzO*ItfIcPfEHxQ)d>{8uwy5#)6$-qCXBp>X~jNKbT)<PCTRzsHa%6iCx zURe=Y5?K>j6j>En7Ficr7+D!v8d)1z+$*ai%X?*gqyVG>uatn);FThfDv&aeI=oT{ zQVCLuS873u@k%vFIY>Q7K}bbNN$6|hL5h<21FAyGLh3>aLn`x1X-I8eDGsU5E9D{e hAq658A|)a<dZkFDO0SfOqw;D0zs}fxX||^;_7l$U>d62A diff --git a/lib/pytz/zoneinfo/America/Argentina/Salta b/lib/pytz/zoneinfo/America/Argentina/Salta deleted file mode 100644 index a4b71c1ff07647569d5284a3bf4832dccc9fae9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1072 zcmc)IKWLLd9Eb5YZKE+13@9ifQn95ZA{=dFM5~-+D2Q2VLFwcmf*@ENtRNa)oP>Y` zL1<~TYteJ4mbTinu|_+TKq4YI1Qa(#B!>8}=kGZuanZ^5<?=Z)z3_d8hEJXB3I2H2 z$UhvOemUGXUY4KR*K_Lrg+cvb%X2fcEvILyBWiZjMLRn^Y37>u^!$yN>fx)m_R;yM zS(w|b?QqUK9v|0>`--Y~scfHYZJ4FwZF*@np-Kl+wp6>To?Xf7<;*R!{Nbuy$vCxQ z-rGt%X{zZHw!S~2>Jtt7Iec0*dSbfqZo)J-q;0cQQD5q#`fFvk`BqHY@AF60kEubu zI+j%KTAyA!dB{XM&lGm#r%h)pU+7AGGF>0f6}q>tSKZ}fg?Qj}-0UqRY8AEf=FyH+ z<Eh!RkT3(I>&(FGY)95P7iww!%QxKi>uteSIbr7(r-Hz_VmWAYu4IFVb1%xFcIV#I zLNV?^`{B$`ke0Jc^~vd#^ZOD*|FDgGt~)z+r;2QZ?1XHE?B$isklnnp9kL&?A+jT~ zC9)^7DY7fFEwV4NF|xB)wnp~$%I3)K$o5{@A8EiV9Uv_rJs?eZr3<7Dqz|t&f^_1Q zR*+thW{_@>c94G1H^fCclH>zgLV7})Lb^iQ@=9MwV_xYDY0WFWA<ZG(A?+dkAq{$^ SL(Iyj{O=Z{{Zi<G-sn&A;N*t@ diff --git a/lib/pytz/zoneinfo/America/Argentina/San_Juan b/lib/pytz/zoneinfo/America/Argentina/San_Juan deleted file mode 100644 index 948a39010420a9981fef631cae134cdcfe844047..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1114 zcmd7RJ7`l;0Eh9LCeauQ1{4$#sn~~!2!}q5_&g;;LCjJELMI0igo5H=1<~l@Bm^V~ zLQA4u1y6@)NuxQM)@Z>JNJRvPfa0d8#1LOSp6@t`i*DY_xxY)M3;!>vv6CnIy+5v1 z_7@J<L3?;#f5|>>UCW5O=SSteO;6SAmW-TTJ}rtHF6d%mTFtd*Wa;_~asTBT{oq_! zmFG4}J(f`qr>5kiy%kZpSkv>HTWaB0KrXDrM0Gf>tBpJ2$>pqEOx{$B@2}{kq!CN% zovufR)N<muZtf3>X0D|_2ggOLKO$RibE>^Aq1)BE_|lw^U+cTnx5|+IUOFOv+#Z!H zlS9H>9h9r552;Z1sr<HVL3Ky6`JVVE)$`$OzHjSV(f6#7-`?9RquxgmReSQWMqTW< zakMMmdaQPrV@eitYS=I2$ix~o@+#exHpcim{C{}|1HbMLhd+oxW6JZMXH2E$1&pa` zFJ#Q~ny=HC*9~8UcdzqsGUX-g*(nC?8L;Q~VN(AvmHlFKW^$&4Ooq&cOoz<pv=bsT zI_;FmoXDietjM&;yvW4J%*fQp+{om}>`ps9GQZO%fMkHA;Iug)NjPm5NE%2UNFq*~ z36ctui_<29WaG5yAo(B(AsHblAvvK-iWi%e{ReoYh2(`KhGd4M=CrvX$vJIyNP0+q lPMaW-A(A4JBa$SNC6XqRr_&~iY5R%)L8jq-Hr@jR;h$gt?34fi diff --git a/lib/pytz/zoneinfo/America/Argentina/San_Luis b/lib/pytz/zoneinfo/America/Argentina/San_Luis deleted file mode 100644 index acfbbe4349fc5fe53196304a7bbcdaefda890918..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1130 zcmd7RPe{{Y9LMqB+<v&E!=RwQbV;#<;6b+*^zRW5E6I!P5S}_j5Clbs45F+{r(jT{ zlj2Ic1wK)hOv}fXt)#@55#5BMTS2kLD)s66evWmiTfgz__2b6|gU@@}z7xmWgFmiD zdBWk^FNgcJm*nl%wVZlzzE?lo_`-~D$?5Th)2g`cf-R1Xn2GvbJ$d7;di3steSEIT zOiirUwl8O<hlcc%-6d7JIB%bBu9=ymF+H==rpldZTV5Ph&oAfoY|l+I`}vBU>v3w% ze6p48DYMXZ%vSftRdu*#zlA4Ntv#V@ABRnSO_!~gE9!f-U;n7=G(SrzyEJ)NE#K+Y zD}yQJt|oQkNVkbMpDJw4kD2B~zR;5XYFd_W7g{$Yb?d8=*_PR4)3s-2*HoL)#bMJK zRXWqZ%4DuD6*84xbKuKaeehn=biY2L2b^<}hUj0u!Wb|4a2O36!Wdr*L+7Sn27z;> z`5@+8*#>dv-pof<JNJGulHfj&d!8KX$p&3=cBrJB4mrQill_Ou<d?fMgQNG6>5%!5 z36UASG9@ynS0+VfMW#jOMJ7gOMy5vQMkYsQN2W*S_eugt23|=4$pJ~iD_J0EcqI=c z5hN2N6|dxiB!gt*m2{ANypj--5t0&;6Ot5?75cQek-X%e5Re#>8Il^38<Lz?vP07I rN`6R!NQOv?NRCL7NS0nn(<^x*i6WUIsp5eA#Q!MQrahAJzK*6}AKLmr diff --git a/lib/pytz/zoneinfo/America/Argentina/Tucuman b/lib/pytz/zoneinfo/America/Argentina/Tucuman deleted file mode 100644 index 085fc9cc9d6ea18c6d158067f2063d35374140f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1128 zcmd7RPe{{Y9LMqBbSoP|gMuO=7AZ0#cu<$oKc3jZigvL<;i(`3At*Xz5M^CD1%ntt zpt!VS)YHKdxAK?ERtB<|5rKz6(XFUhV_Et1eLtO|OWpeYc=q~jryqRYOZD|1@9_S( zmdP(1E-i=W)feSs^lDn&JJ+l4uX|=DHl+2${3$iL=Da^SK4x<DyE=dEm3r{{t^e?B z*i7ZtYQHaS9t{uc=^X`CxKQ#RudkVzBSAg0)UJwqy8UA1j(U12qi2&h%<PBD{#??k zIrH8x$Kqx_am=soYEspan*SwqLe)AVy7q3w)K?|^da<m&RtNNAd8_$Wi2L932i1>T zy?SXVuI!aAy?kuHX=*u{-IN(OEs;#NwfnPa{dgwZwsECuD;>_pJgZ}7d$zq&R-3OM zigwqYm~B(-M&~{${rrXQ8CYR@76zkx7e~#${L$#3wKmYu_?LGm`0H-K#!!R%7P5AF z!Sk#wl)Rv|Mc-?(_GKy1Z0(y$Ai}fPd?1<f5^}bvE;*fYeqSf`4|~Zg+tWk0Zz8)P z+adcQ8#-l2WJ{;)iEN7OifoJQi)@VSjBJhUjcktWj%@Fg{gDQo(gD%}(t}f)K)P^B z8%Q5WBS<GsX$9#8X~rqtAniD%AEY6qBcvsyC!{HKUGX4o$=|@EFQhS~Go&@7H>Wg* wbmx@zkp7&~AkrbyBGMz$B+{i*+C=(vN~1`pNUJy~ulhgq8r~@t@9qr$1mS)4g8%>k diff --git a/lib/pytz/zoneinfo/America/Argentina/Ushuaia b/lib/pytz/zoneinfo/America/Argentina/Ushuaia deleted file mode 100644 index 1fc3256773606e80a3d300ddcbfffafe72b56e45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IJ!n%=9ES0mw$T_11{4$#sn}8)5e{u*#E)_$LqUu~EhwEFL=Xy!gB3)hi#P}Y z34+klXjj40p<3H&j>a18Py%TY!J(kIDJn6<uO9!;nG_eDyqA;T&7Cg1PyfJ)<DJ1D z*9v*V;p&mY^SX=jbL(1G-96W*?`?c;CO2pG<iaU6wf?-Fniw<F^>ICO{e^n)@{N6X zHfjpf8?+tBnnxoe`tk0fDqbktC!1<!_GpuyU5=~LzN9TJ-BHgj<@8+orkQ(x+0Lh( znm6xkr6XY$Qpap{Z$wo`YxZ+^P}MqPy7qR|)YqnLy;M<Oszdr~Wrz7zOxW);ht!YT zeR_E~q1@GOy>e{7iL{=~Z_Q1Z)>tmzmi%PeKAg$7Z&{<-%SZAZ!AI3$cID$s6}9cg z;g)3Wso7bG8-4$v(u=EY@6amK`zq6tan6Mr8vpVRH~n%qh8yHx*ttSI2%IaHgC^%n zHi$U4SPnHi_j)N5<2h(PknRssa<;2(IbCvo-=zN^R+0~Pr-#R{AZsCuA*&(Fd1XCh zL9eWcEQzd%EQ+j(EQ_p*ER3v-ERC#<Ebf)nk>$OzK2iWufmcdEYVb-CNEJvKNF81& z1gQim#VfTS#dxI}q#UFkq#&dsq$Kn;@gPM>`~g)VWg&GTg&~!Br8J~AuM~$==aur1 i`j7&V3Xu|#8og2^Ql(eQ#Eg8}|F1K;N1E;Fiv9#%wCh>` diff --git a/lib/pytz/zoneinfo/America/Aruba b/lib/pytz/zoneinfo/America/Aruba deleted file mode 100644 index d3b318d2d67190354d7d47fc691218577042457f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198 zcmWHE%1kq2zyQoZ5fBCeCLji}`6kQhDSw;s#)FaR|Ns553=IGOAK1ab^8f$w0}Na~ lz99^{1}4S^435DeAYDKZLW0@<fdFJah$h7jE}(TLTmUTSE*}5@ diff --git a/lib/pytz/zoneinfo/America/Asuncion b/lib/pytz/zoneinfo/America/Asuncion deleted file mode 100644 index 831bf843f8c05791ca3490ad286e73936772865b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2068 zcmdVaT};(=9LMn^nh!{N@Ci^!vywzOe2?c0uw(cmHPLwR0aUP3^9hvjpoC%%l55&r zOA42H<!ElTnetVp<wf$to6=RVVYS(&a@TAzvSq~_e}C`)O&48t)%u_Pf3N?}#o5mG z|GdkqYPQV_{NozwzTxIN=x&~a&F=U-<@c6{=H4))?hj~UcY_^!{F)}6`^!GOZ%D?q zW}ER<VGTBHw8>i*>4b`7X5zD7>7@KLGdbhFOi3%YDPwXpWw6<#-q|c^KX%xuU;iZ2 zF6Z0nXQyfU$y)Qs(K4CQeA3R0AJj)T^_p3$N9kjW*O|woF?pgeW@b;W(~OawCga{m z@?`%JGe?Uwv-=A>_tF^2I(^z?$CEVl&P|(B)~`}G!Gv?GW!~D5jf_99(Y&CI{@E?L zQ|{W_zMnPk-gTSb^^MN|{-S-VtzBcCy=FoCMR~fu#TFd>P!?95F=p3(DXi}|&qO}e zMJs#E;)(C-lJI%6<hQsko%*d^+Pg}N2GuU>i0bki@7w3PVp9CEXG_}BrSx#0Eo=5< zMSRGtEUlGQD{h<Bp;9TAE2d(c$eQ3Ov*wRtT|02dtotFPl|9v_>a(D(zx0-U{)4;v z!ijD6#m4Kpp{dAj-0-Vxs?4-6E$o%e^ONkB85d-0YQNp;9hdD1W6kzkO;X+Wi>c{c zFFU%r&CXBuYHiy&^YYu3TK8tF*;O6W_>Oj4UzD!9m%nHCWO&+;xyQbeJY5<SD{SMy zknFvaXZQ8omZocIcE4VcS37deYb~eb_10ALM*NT**ga?tc%C;bVR*ub|MD}!^R5mA z0{-cqzqEnTp7&i(Ac=FBJNc%#q&!gQ?re#=JL2BmzpwIt@z%RP?A07jZb9CDeuV`f zD?pZjtN~dBvI=Aw$U2aPAS*$Zf~*Bu46+(zImmi^-GY!6AxlEmge(eK6|yX3UC6?a zl_5(*)`lz&Ssk)GWPQj2eccL?B_eA?7KyA9SthbhWTD7Pk)<MQMHY*!7Fn*ZTQ9O; zU$<gp$;g_KMI)<5mW`|%SvazCWa-G-k;NmcN0yJQA1Q#Zs{m30UsnU92uKx>G9Yz8 z3V~DtDFspsq!>swka8gPKnj9X1SyHHs|ivRq$)^Rkh&m+K`Mik2B{5F9Hcr(d64=b z1wtx>l*rfB2q}`Us}fQsq)teokV+w?LTZH+3#k@TE~H*a!H|j}B|~b46wTLF4JjK^ zH>7Y#<&e@LwL^-BR1YbiuWP{nzkZ1eToVhT?u-;<m4qb}4oQw=l|({O9ugk_Eqxkv diff --git a/lib/pytz/zoneinfo/America/Atikokan b/lib/pytz/zoneinfo/America/Atikokan deleted file mode 100644 index 629ed4231944255aaa0725f807517df48b39e135..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 336 zcmWHE%1kq2zyNGO5fBCeb|40^B^rRlyd4W0=I{DhaN<XJ!s(8G4VRR^6kJN={J_M> z#K_FT`v3nb83u;`|95U+WcmMp^#TSCFq;QV3V=uk5g*?W24@!_4hG_IAPxv&a0RkK vfDuZDkl?KUKv49qB?Ux-oCl&oP6W{)XM$*uQ$aMyxnP?5PUZso!ITRCV)1m0 diff --git a/lib/pytz/zoneinfo/America/Atka b/lib/pytz/zoneinfo/America/Atka deleted file mode 100644 index 43236498f681cc06f64ca2afa613880331fe6fbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2356 zcmciCZ%ma{0LSsm-|Iy&Dj-Bm!Hl7RhrdBt9tc86<UwwrWC#iolmVg)3ox8hdC|nN zoE0&9(I3~SV{FZ&u`@U43+KAv*1#M!H|MM|UA1J6yq)jK){C0&@;rN<&)MC5`}=yU zn_f<L{p)zlFT9+7^Ky@W%Y4rF75FBW|JFKD=g8X=FQ_}G+8qC<Ug<hk;RGDYmVupF zPEgxM9b8xL3n|akp?MiTcUrV|zrDlfiI~-%;p<M=%}aXzk5j${Q@3Qe9`!B!dP+WU zV$z9tcT_&uciMSq&j<41ra>oi^IjQM+~Y*&*2zbbYMq#bZoSBp@5Baf)v>D*mc{<! z=*3quRNO?mUUDW%J^E#&Ui#rJwXCB^#`jLCgvunjy!m(WSoVCmqGVD$9yKEqSDqG$ zeveKH8x%>?KkJo0^@vqt7j*K)_f*Qz7dmyMORerXqQyXsN^AUFrngI#QPeLpD-u*z z;!c^J5v-nYdu2{syvVthEpz9B#FOV@<Wt{Y6>C(cetPtrc&0yEuYLc7kS()1Z~s}9 zUv^19TmOkFSpAJIEa+2(zuu5VDIbfXi{r95{E#Rf8IdJ3&EomNZ}s}`4ye+ulX}Bf zuc)#u1KK%SqRQ9o)*CyLRYhEt_Es)b-nm>|nRQcDUagdymWGQ>XLID{J2yo2N3rt7 z>2a}T|D1ejY(&)5Ps^=C?}*yc+q&-HNwqEIvfkb}pz6cNbVJc@)i85hHzro8#tZv& zlRH;64cF`DYm3#ZM|<UKz8tZmW4nA^#fp~7LfLwFPPAnw%AGCKqCMIpca>?e%fCW* z<Xl!AKe%;g%$VvNyRP@l9#?M+o!4(p?o(Yo!@B!az3QnstoI&!P6Y%81q6rO>j|Cb zzK@T~_1P7d%kOV+T)}>Sdu_lx`(0pviLm!bzOER*zqd7DiM=mcU+Q&js4#Dpc^$7S z-`w*Hyso@;=CaOQ%n9Jb`Rn5S?~#R>Kk#ynn3sFJ-<-8){usyZgVi<2=#b%A&G?W3 zq8%X@hR88v1O|zW5*a2kPGq3SNRgph%~+AaTFq#Y;UeQj28@gt88R|vWYEZ{kzpg_ zMh1?I92q(?c4Y9#=#k-D&G@Y*07wLo5Fjx?f`CK;2?G)bBoIg>kWe78K!Slp!)n5T z#KUR=f<y!f2@(?|C`eS0upn_k0)s>b2@Mh(BsfTPknkY!v6=uO5kf+Q#0Uuz5+x)| zNSu&BA(28tg~SR877{J12^SJCs|gqqF{=p~5;G)dNYs$9A#p<jheQqu9TGbvcu4fD zCVWWztR{d+1g$27NDPr6B2h%bh{O>IBoav^lt?U*U?R~(!imJwY66Nx)M`SC#MEkn zibNF&D-u^Eut;Q)&?2!#f{R2K2`>^~s|hd?VXFx-5@V|gG7@DZ%t)M(KqHYxLXCH0 z9UK@EdauXrnRg$bziVB+?f+@^KheH>3o}7a6Q=0Nr5UN|sUo>FEiE-IRfPQs4Q6_I diff --git a/lib/pytz/zoneinfo/America/Bahia b/lib/pytz/zoneinfo/America/Bahia deleted file mode 100644 index 143eafc2c0ab9d7f54bb0a21649d32ed1b4489d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1036 zcmbu-KS<PJ9LMqBT_tgqwloB(UlS4QtZ(<b^eQ`=A|Z|7pg%)XNaj#OLk(dKO+ryb zf-TP!(GbrPoIzk<X_FB}V0vC%Q#E9i9cS_Myq~qD@Hrk{$4&S9ecpwWg9Dl9ud~Yk z!sX=s<@@?O4~DiB8+yyd-v`Y2VpboQF34=|q0WA;>D=b`c5eKH%r9@!(%q6Q%#Ufg z5X<*bXMe0YB})^h^=EyPsUA&hb?TYaQm<_7e$_00sncI0mrdh~>)Lejt!Xy9t;92? z<?5hrT{mr7k8iZCAI?i#_cz=2dP>?`8npdM#dJiA+HreCwtd=Qw-04Zdg76G4ou1p zGpaie-V?oYNq05&nanY<aot(T<_emvK9w$M)~=Z|rhDnR%@wc7o`s6;y^%Kirk(9M zS8w)D-qd{mSJOXs$R6pvCBx3SWMT!Mq;u~+Mv-%sa$>b}Z(c@g5BC+KPR;lm^M3s) z{KpFZ4>^`V)(HAVkX4XnkadE7A!MbXUkX_ZSqxbXSq@nbSrAzfSrS<@=odv+4f<t~ zb&-X6PF6;iM%G3aM^;CcN7fJe0+0%Vz67L(pf3WcBIwIN>OcxXDnUv?YC(!YszJ&@ z>Ol%ZDhm3NkeY(ND5NT+ETk@^Fr+f1G^949IHWqHJRJ7#ZUyZnlK)g+s>hd@k5hjD D$US9O diff --git a/lib/pytz/zoneinfo/America/Bahia_Banderas b/lib/pytz/zoneinfo/America/Bahia_Banderas deleted file mode 100644 index cd531078d0e8a4752bc242e83836dfdf0ae307ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1574 zcmdUuOGp(_0EUldSSC2wgD3*2APl>y>CLPxyOx>WG`p2q>b30JP18{;tb80u1R-(^ zf=GlQdQq4`nLQS5auXF5kqUtep{SsfAW0$9`NmZQLGAhvXTD)Jv-v(>VK9&=ep|Ty z!evGJ@;>)+?+5eNXw`6UV5k3{=as)N((Uh?XjM-?*7yfH?kFKlnNZ$hB-YK5D>4H{ zQvSG1Ub5M6Cl1M#;Y*E_`QJp!`$%Kek3o?-7HzHWeJR%TL|AJdc8Rq1&(^xmX0g8d zxs~41DL3TySe~**nX&w=wK1baZi+f$WiC#and2QsR@fq$HBe`48JQ}x`*s*P&t@v` z`B<^_=9tQ@pCR&2zg62xm&p9e`)Ygobm`0KR6Alm$(?bHYL|E>3ua$13x;pW-D3@A z;o}au=S{J>_j<P|y1U*iZnH$mxrL@bSSw1K($u~zpV(g<qsn64;y_xc3Pk72@&zNR zd^AZOoak4<S99c{ch^+K?QvPz|HM4pF(j+5Ts5m}9!ayc&8#UL6t#g0vo7_aII_uW z9-Y@Ejx7(WW8a%aeN?upA1W2czbsQH9(csb7g4I=Qi*Kr{;HZzrOW0suT@B>bEbrZ z{ozav{kLP=T|Oc7(-L8}edD5-X4~yjMDiYD*!KONkXe)8bG$y`-R=_}ee?A7X6Sc) zf1U3yWU}=T3L+ImEQnkX!5nJIAfmxZ+4#39c_JM|JcxV{0U;7XM1;r)5z?WS5+Wu; zPKclkNg<*#WQ7RJkQO2?L|%x%5Q!loLu7^s?NCb%5!<1b8zMMFa){^<*%`t^q-Tf^ zkslHOBmzhXkQg9AK%#(z;ZVl`2?P=eBos(2j9?(qFv5Yv!w3ix5hEl>OpKr)Q9;6T zsN;eJ28j$38YDJIaFFOA;X&eq1PF<c5h5f;Mv#yw8DToqaWVpRs3T>B3W=2wEF@Y; gxR7|^e;06s?%S=qj`JqD5?t|d-h_BpvMWB~Cwj`Z5C8xG diff --git a/lib/pytz/zoneinfo/America/Barbados b/lib/pytz/zoneinfo/America/Barbados deleted file mode 100644 index 7bb7ac4d6ab698cc62fde7d1a357be3c06e34e8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 330 zcmWHE%1kq2zyK^j5fBCeE+7W61y)w7Jl)Zn{FIO3(*wRKJs0?=GdvI|$-5v}_UM67 zNWcZ*`2Pta26h3A%uG=D|NjYT1_m(6^8f$U3yd89{~tfVz~kc^!r%nNjxInP90IZh z3_?h7&3_;Oxd=pqTm>=!<T5Y~bRCEWxe!EyTnVB<E(Osb*Mew}i$OGHuI2)I!GsF{ DnJiMf diff --git a/lib/pytz/zoneinfo/America/Belem b/lib/pytz/zoneinfo/America/Belem deleted file mode 100644 index ab3c8a67ffa6cc4a4af3b50c59adfa47aca7b326..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 588 zcmb8rJxIeq9ES0vejrhFa&R!7&I(7P;2`2E=pba#!AXSf8C;||D~<}f+$au`v32Sq z>ZBAzC~b{6>LU12Qsw+VS%sn(j$bI0_I<*&!e-w2b-LseF2|S4`@q3z6qSY+lhXHs zDZftZ^Jb)KvuC>Yoa*|>V_Yw<sz!TQH;<d@p>d>>Fi>xYHh%9}S09xP{W<8H*2<)A zU0$fvtH$X`%d}s5_1EsU8NJ@u{?@%2Gc&OYZp`>jK~MBwnWD8e=XQ8<)>ii%$J(dF z?Y8#r*6CYX4xK5TmmBc^<%IuODL-ggimc6)#mH)8IkG-e3Lq6Sr36v~DS}i%${=-+ yLP#Z~6jCcwiXqi9r5sWZDacMLA|;WUNKvFJQWlHy-41Qt+@I=tb5eRf@O}Wi)&7<M diff --git a/lib/pytz/zoneinfo/America/Belize b/lib/pytz/zoneinfo/America/Belize deleted file mode 100644 index fd6932140d6795c404dc09d0f93cf789bc484870..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 964 zcmciAPe@cj0LSt7c7=^CBqYL1h^PL5u^4F}=s|-H1uuNY5NM+ZsY@7w1O;A1uZ4&H zT-Hi^>9)F>$gce{&1EtBcZv}Yj&$&pcnFV<^PQ_FFU{ledA#F%f8otL-L>9<Q)d70 za_a5Ncg1kWS}vV#&?A?xs~7#J^vm;i#j6gZN6P~-*0`z1wrfRndQV0_PKfb@mgCu7 zF|oBIGfyhi+tR4a_We_n(V(2V@muAFZp-Pqyqf75(C<z@SF=I4p51+@=2BPm+{QDJ zKYmW<7aoYh&M`foZWr&@&dLw{P2ywWsQh%RS}Z*MD?c~I#p3;+a_LN!`XZ*~@)2JZ zFAdA$)*n@>p3<dKQLXro^-2_}ubaJk_0h8Uw%De>_q-N~u63Cx{0b^N@5#zceGu~x z{lsJOm@(PF^Ng8!<Chte8S#?F6mov~)oY<wle%~z<u$j4UUN&xMY<Cn;=+wRTsm^` z$mJsekO)W!BnA@1X`>)voHh;;$Y~=Xp^#WeFeDlh4vB{ZL?R*~k(fwOr;Un)b=tT{ zV5g0YghpZ`!I9`lcqBeD0AvKn5Rfq-gFr@s48v*1fegfHM}iE+X~%*L1{n=99ArF% UWI#?kB4kL;|2d|lT~}RlA9t(iegFUf diff --git a/lib/pytz/zoneinfo/America/Blanc-Sablon b/lib/pytz/zoneinfo/America/Blanc-Sablon deleted file mode 100644 index f9f13a1679fa9ca7cee6a41bf094a861d4c6824a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 298 zcmWHE%1kq2zyPd35fBCeHXsJEMH+y_ydA9x^LO11INk9m;ga%~f=dZ+FEBAOF|#oJ z|9`54f#LuEs}~qq{{KIIfPn+d<^hueKE5FgjxInP48-9;91sFDje&s?OoWi&ivK_m jXKM*G_y2#8Yd|!}MIajFDi95F8JMPu>$rf9HsJyQxN&4H diff --git a/lib/pytz/zoneinfo/America/Boa_Vista b/lib/pytz/zoneinfo/America/Boa_Vista deleted file mode 100644 index 69e17a00e161ef17bb763868db7793b609a39556..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 644 zcmb8sJ4*vW6hPrkd=TQRv9XB8*eVPrU?L);wkik<f<HhJtZcLk+NKml(85x!rcA_Q zyI?2yC_-M1g%Hqy#*irEJ=<DDXBp10#V+?7cVl~V+WED*<PDn@lg+vR_$pn^4z0gs zzgJDJzNoLuhpLdg(S=$_7e`*bVs1;7+7r5bT2qzMg|4~_>iuNE`{>zG4R2R}4o;fp z+N^Ho?^P>Y^;(y`rd=D>Uzv=F+@9&t{bw_FzT`#YB{P1or(=CjW~TArC9_8=RjKRL zOT?tDwZTA#CunUx>NwUu-UYhl>^Y(371x<kaoGtOh2{QZa`~xcdWwKwQJ_drG$<l| zMTH{cS9B;s6eWrjMT;UvQKQIF^eBRUMUf)uS2QW26jkO#mZD1$rYKXSDcTfqiaJH! XujtdXe7l2xAo!;n2+zrF%qPM>`ved* diff --git a/lib/pytz/zoneinfo/America/Bogota b/lib/pytz/zoneinfo/America/Bogota deleted file mode 100644 index b7ded8e64e79bd69589598da5a5b1b39d92344a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmWHE%1kq2zyK^j5fBCeRv-qk1sZ_FjEK+zE#~3??R~Kh_5c6>XJlq#X8!+w*8~Oz zFv;@&|M3Hi9RL6C+`z!&;~T=@1jM=qCO~Wo)D8h5Bv=Zx<A2;7lMf&oWI4zLkOM$8 V$PpmA9w<kaL%3{!&ayM*0sy2iKmq^& diff --git a/lib/pytz/zoneinfo/America/Boise b/lib/pytz/zoneinfo/America/Boise deleted file mode 100644 index f8d54e27479149d35f6ddff12f709096f08bfac3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2394 zcmd_qeN5F=9LMnkf>&-loJtG<m9!*?i-3|qW>g>|+#tLH8etT8ceMf^x@jSvf@1u| zSvs;zy7Z*2nToF2*bvunZDn(~HLZm`*DM=7Y_fRxb>8RS{_4NBzIVUh>$m&==k41N z*pwswaW3-@5BHLJ_)b>l)%*79&}#$nI`l$%sPC0)`tYjql#jcj^~j<>TSgiZ#9!l2 z%88$9#pJ~rIrUM2m_C&+ox{mO>`#(mozo)xsVO;QYody1n5!czC)Lct3GK=nRj&R8 zI!X?ys3XUvd+c>}-B*1&`qF6;Gt{nQKj;&2?}Vg$xm{e}^_;w6SChCg&?slGFA?#j ztK^*gG;vefZ8BkDv6>s@mWko1D)INlI_aAzl{_*_&pY$8nt%L?UeI?=r3B~6o1Z<Z z7Or#TqRpR(TXMgax2}0lERO$BrlxC=I(=EExwnb5(K9;zicj1=_=e87(4suO&+E(+ zRcc9_UuW%Irn3DVGH3f7wX|fDTvj!%a#L5!J90i(%U$U*FX@`d`!QPPPh1iOXMdG< zelZ|c9CPct1`djq-M{F&59|?z&7bOfIu59ND~{>=8h5LrB`@mYqK&Hfk6~HjX%!_G z4$0D(dQp0!ORl+ED9T=aOP7CEr@XD5`u_J<s*1XRuIx!s53DHDRl#tzHrb<n>%LRI z$v9n|dsbC{pCN1Ke=BN-W99nkVX@)OG5O%=AyL;cA|D#;67`KI<;LDE;$iQ3-O$;o z9`W?+NBsfS7_(0|m6WNbt3mBg^(g<jS{-o3slbr}z4?dV)Rvwa`Plhb@pv#_K5=wR zG*>6dmhKU;HFrvGYd$Hq$6uB^Dtg3@=?NXo>sP_iFZIrZeQM|6X}xPasGjca)6ZP2 zRjqC9diSXU)mGo6_v}wr;bG<%IU^!+=6~wvID6xSaGZlWEW&ZRm6+u??}oyn?OXD{ zm~Fok%Dp~OS!ABIKH;q~Po;VIHve&9_6@#&F*(OveMI6AGCgE|$OMrYB2z@>h)mLI zW{FG_nI|$)WTt4R$_I1h%w&Pta!ePQFEU|d#>kYBIa|%7ky%^Kw2^ru6GvvwF?D3_ z9Fs?8&oO;u{u~KFGQg1nBnKQxK(c_O0m*~aBm&6<k_sdjNHUOYAn8Ezfg}XU2uDhg zoNy!s$%@sa1<8xmBnHV0M{1DVAjv_pgQN$^50W4xLr98{93e?UvV^1w$&=M23ds~l zs*qf9Bn!zFN4k)FaU=}M7?Lt1XGqeJtXWOkki1z<;*iW)P3n-`aU>7P9+EyJe@Ft6 z3?eB+a)=}m$)eSy5y_*~BofJ_)ua;1B}X!mY;vR%$tOobk&JSr6v-))R3xiNT9Le3 zO=6MET1{$^+*(a?k?bPrMe@s$U?jsFDMoV4kz^#x9BD@KY&D5SGHo@fMsjU6$wsn` oq#MaMl5iyBNXqelmUFkM{Bl$I4DZs+oXo5YZ+3QOc4n6QZ`ww0CjbBd diff --git a/lib/pytz/zoneinfo/America/Buenos_Aires b/lib/pytz/zoneinfo/America/Buenos_Aires deleted file mode 100644 index dfebfb99abb86536da12b96677dc0c76c088cab8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IKWLLd9Eb5YHqjUg78Dc_iP%ya5so%7;-B(LhN?764Je%yL=XhU!3v_$#YqTA z5QLV-c5x6qhiYk~J&iTCLkXlJf<r)YQ&eJzReS!Pn-mwFd|xh~Bhw4tCo_2RM7Q_H zvr_)y@bt>zzV5R8j9t&E`xgfM2OH<~<mQ||xp+oRt-oldCdT!2^PXS4@lri}_0~K( zAJH?@8+<dE(~n0+{3p9hs&uJho^ERB*`q;!b~&cX`;w+yyQ`jG$@}x^TYCP(RkM(` zYC*p@)vknIOdU7%y<t@!ZI~~iQ>xM3<~QDr>gL*%X_l+%YkkOHs_xL=N(uA5cu4)Y zGvF@|CzQPw_g9S{)ZzBig{}Ds-QJcjbR<9Pj*sUGom<wZ&dQNOm-k6^>0O0bt*W-& zJRD6np6Q)4G2J(`TKB!qM)xm`=>x@M(X6#Lu%hKJ-%#+Ex5eE<*3QK}&)QPO3tC$? zUf9|fl|ZYtZ)$-y?p|wuI^(6}Y*%qPJ#v2EB=Zj|$p_oB!?*j9wUEV-)sW?!vL3Ra zQ&vQlMAk$WMOH<YMb<?YMpj0aM%G3acgpI>@=jSFDFCU!DJ38^IHd@r3Zx9A4yP1? zRDzV^lv<EtoKg)^4pI+N5K<9R61tkWNKq2MM^#8!NL@%_NM%kb4XMp3#Ua%>r97lQ gq(G!Xq(r1frxb})iIj<1`LzFEXJn5w+uIZQ2~&paD*ylh diff --git a/lib/pytz/zoneinfo/America/Cambridge_Bay b/lib/pytz/zoneinfo/America/Cambridge_Bay deleted file mode 100644 index f8db4b6ebf5b66d3d566fc3975d6151783f6e645..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2084 zcmd^<Z%ov69LGQWDe&^ocQX_P%s=S~a>s#^KxUfIg>*r9A|hfIcss2ibvH}IZwtqG z<ag<qo5^C;eBey{vsQD%IfoBQoI5wyYT^95W!cEuWV5)wukTh*dP0wTv+wWq-EX(M zC%j(ahUk_e`PZJXZ#ZnXJ>2K_Js8s8bWb$#dB3;CH)qPjf`+!yh5JUPMw``>>w}U} z`J$Runk&<D7ply;6?(=5kD8h6)iWO~Fj+Sz=<Im9$vOX%o^|q`d8%(n&yMA&Iq#p) zbJr%+yseky>Eat|{_4+VLB<8;%`@U1yKZtl+a-7Syve&4mPG@fnEb14+Sj|!_)pjB zf{us@99W_YBb};f$27e-xJ50g9o5C&m((*w-|3}kd8#Dqp_JSmlhQ|DkCdhUSW|ZK z7xnD-XJuKx$2@oTh%Dd#vw8mTURlw4*{tk5tY4_^H>;ZW=<<ROO-1=8UGdwX3i{e5 zc=f2NoYW+hr@Pha`zxgCqtDFC*BW)Gz017v#d2NU7&SFLv-PXXs!VMxS+B|Vnef`% zI{e2JQ&)UZ*WJli^|NkC{orJ^er!-0J{?gThL1{PXIyO@=$5ADGip=sHhC>{#cb~C z(y#kYnKvR)-8|`_X$e;8misXi@%nURsNO`=rs(MLGPCvWuX<b0I`!t2$?{gLRK0y- zL|W^zRNMZzyi@$A+TMCbc4S;vJF9zS=h!0?D><cO!#|i^nFsZ*fpg~F(U^X(x6kbU zwqCb)d|>u`S*AOhTFl-<**YaDIVCmavGM(-J)WL6&X11c3;CZDNhFSaoRsRG>50S< zBNG#eulkdedz0brGq_6*cbW6|dp_w41SGUBETI~E*4R^C&Lb?VIQB(_Jt9f&huq_z zYxqC-kg&rEh!+qy9IYP^M<AZ?OSl5@h2adu8-_a&e;^J)JaV)yL41NZ1@Q{v7Q`=z zV-U|Eu0ecbI0x~L;U2_4N9!QOLr3c(#7Bmc5HBHaLi~g{3h@-;D#TZavk-3~?n3;9 zI1KUF(Ynm=8RE2~^%~+f!*7V=49_91Gkk|Q5Ah!2KE!`W0~~D!AT4mTJ%BX9(RKmS z21Xwsjev9l(h5i~AkBbu1JVviKOhZpv>k!8#L@Ny(iBJA6-ZkceStKF(HTf<7`=fs zhtVBKdm#ORGziimNQ)e8k04EQv|WO<$<g)+(kMu$AgyBb3eqe_w;=6e^b683M#ms6 zbF@8!G|ka=4bnD8+c!w#Af1D>4$?bF^B~=W|L693Y%7=AOy-9c`-}X6{7_+`ztA7> F`~~dE4qX5M diff --git a/lib/pytz/zoneinfo/America/Campo_Grande b/lib/pytz/zoneinfo/America/Campo_Grande deleted file mode 100644 index 495ef4568347ab6d3887a2cdd762c960f567a0ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2002 zcmc)KZ%mbC7{~EPD&|mYt~O(Zq0Eeg<8?SEJ)zO1Owsg!<OvD26sAO)BrzgP1ze-e z7jEh_&5G9AvbMV0+60&FUbQ(E3oEfC`7>8-HMU|)M97Z!_wHpc>YWdJ_?$5|#&&&w ziHe%7dBMM)Tij20d79nJ=cz~DZ;E!@vF?wK>&2#XWT~DWXqCR$r`k6>SudskZZCCK zOaDZc4!k}rgZ=O6P-2Pv`Pwvl<+klIYU}js?Kx(wG+)QgekbE;Lw5XRs+kzROaI!x z-(+;1(3y=toBP@eZDyq3%xZa2XHU6cvdU-Ltjl|3PAsr<&iBgPSs6O_%!tVjlA3+^ zm^}E~J$BwUH97VR&E5K~gxcTL`O7~MX>QYS#(I;tE@UIAjS`JjYILke7G&n?f}RGm zaP+*5bsUn12S+sjK)zYjHO(%rz1KYQ<y%@%{I_}Z;8fMnkXh1}s!L~Fm%`F>T6lF( zmc=*QWkVCDX!>C-I`xjM7&mt1(O2ZLzJqpE^V??i>3SRAw9gbDFS8{p+hxtZ7qm39 z$UI*4gsu&;O`>q2CVpKmWm)N3*7bmtr%Y)1$uxQLa=%u*IxOo4cGwNid@UP4TVtPE zVdUvI^X#T@x7plQqLtG>G*#7Mts4JIwiMl|TfV6?)%Rb~>W>qqCiQ!*Ilfb#{iRd4 zHLaEHJ-zn1iic#!N2hFU{tS8kz%jcs{hHL(?y_~)ew6x>jkf;c8EJ?$X~X9iP2=4g zwDE&cd1<Ubn_Ay6yMD;jmn-+0-5n{qXVnSWd-NwwnRIi%lF9DwAV?;A&rP~DnLKNQ z$*bduAXg&pjk*KnN&NRcx4K{R_ndSOH;vr2*WEU9-^h(4ckXq!j@-M~-8^#l$n7Kd zk2HXEfV6=0fHZ-0;dN~weRy3XNGC`u{GarKG=p@5w1f16G=y~IbuA%1d0kUTS6<f^ z(wEmYhIEFshV+IshjfRuhxCUuh;)dwi1g@nO(I=-U7JXsNTW!nNUKP%NV7<{NV`bC zNW)0SNXtmiUe`3zwb!+c^zC(xBb_6yBfTTdBi$qIBmE;Afb0OW1;`#Cn}F<s*KGr` z4_>zs$WD0ORv>$UYzDF$$aWz6fouq}BgmE@dxC5VvMXM<Ey%uj-Nqn0<8@nu?2XrL z4zfGQ_8|L%Y!I?T$QB`cglrPBOJ284$Ub@9Mj<=pbz6n(mDg<+vRlY@A^U}F7_wu? ymLYqF&GY{0p`|6TH+S9m>;Grr(iXe5D~P)9k%F9fI2;OxWWMCY^Fk3mq}>2?XkIn| diff --git a/lib/pytz/zoneinfo/America/Cancun b/lib/pytz/zoneinfo/America/Cancun deleted file mode 100644 index de6930cd8ace99cd0a9658951c64b98b71a5f389..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 802 zcmchUODIH90EW*n@+`_@!%_(g8HVwG%rFh(eOFdjF_Thrv$8y8vhvs}8!4p-E0K+) zlvs*{jgsZrh>+uacVl588~^Ft?{@2MzRwz+jQPZmrI{~WR$(sh1?zh^cG29WT^)}r z-}IYZ6OO3b;al5ZR;m0a!mjh3JN4-$B2fIK0*{}f;r&cCK6_=;-mz-l`H(G#i>h_$ zUbd|`sy%TeJCX}d=fIi_jwhT@RZ@0^hMex)m<*TJIN{e>5%Cr}k?RT3^YG&I?uSM6 zB470_my7<bXEiXBEe7YW)X-R&9G*H*BORI2imoW@;!b!{Q#fAFZzomj?F}Kc-dq)4 ztxu0U8CoCgiX5Jvyl9^#f+Lpb3YlvdH2j|R2XopCd;E<-h((A=h)sx5h*g)yEW|Fv zFvGG-W13+bVjN-}Vjf~2QUFo`QUX!~QUp>3QU+27QV3GXr76Xz<<b;mRD+ab)Podc aRD_gd)Pxj;RE3m<)OGo9g|kd_f7Tb(m(0ol diff --git a/lib/pytz/zoneinfo/America/Caracas b/lib/pytz/zoneinfo/America/Caracas deleted file mode 100644 index 9abd028f94882389d205f07435edbb2577d821b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 280 zcmWHE%1kq2zyK^j5fBCeHXsJEg&KgwWH}w1Z!_L_xJOSa2v=QMQ2+n`e?}%|CT8aU z{|`DaFaSvx2A2Q-5A0yz`2YX-0R}!F-w+08Al5Z7F*X2^KvN(fgao^R*8I<GmvR8n hAUA+$kUKyof!qS3LGA(3^*}jt-Na=BbfKLI7XVz%NX`HN diff --git a/lib/pytz/zoneinfo/America/Catamarca b/lib/pytz/zoneinfo/America/Catamarca deleted file mode 100644 index b798105e0f660c7b85a663d945d9eb7f04505e52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IJ!n%=9ES0mHqjUh1{4$#sn}8)6%K7<#gB3%LqW_^14<_c5d=YTu!3lGaS{Sj z1fiwTu0oGPwKUZp8*8*f38X~?hk)XysKgMzdi+1fNnCXDUe5hp?sVaOGJ~g1b_IW2 ztK<uZt4|K^8?MOX&W)V9e{n!R*!<i~ZO!SarL$^!<0U&iIc^G#dwS;P3-$2j8~f-& z%*+-xX*-xRk4H!K+`ghJUM|}wTk2;1cv#P`Bvk2OuPs$4)U&I@dLez=EWE#F7t>BH zns>I+nKVnO6Sj6Bs%m3(`#Ex2)w|-l{&vhX)~9TvR8e1QL;7oFxA|5~+V3+*)Q`IZ zdSxW3-1Q#4di;=yww=juAD%RA@!@=X?<dp#;e5Vh+d9=zK9=tcobEJx^NDIj?YMQc zwYUD%?3qm%UHGW<@><(Jw8r$m%C=^mbD^f@zq}*iUw0EWN1Egrac(XX1kM%9LD;#H z4WiC1mqRViy{?Ahyaz3Z)0rS8XP4@c(=F%sO)~$mlKikcH!`skSqoVVSq)jvE9)T( zdSyjqNn}lAQDjwQS!7*gVPs`wX=H6=aj&e7Ebo=|kphqkyix*EgI9_`szAy>>hMY- zNF_)qUa18s#w*nz<skJS1tAq7C84j07b!~O52y+$3#kh!45`d3r6IL>r8uNIuat+> hhZKlZh?I!b=#?UoD!o!BX64uZf1R=Y(rjOM>?dvN>pB1c diff --git a/lib/pytz/zoneinfo/America/Cayenne b/lib/pytz/zoneinfo/America/Cayenne deleted file mode 100644 index ff59657820c61230c738875e72036cd133326f41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 210 zcmWHE%1kq2zyQoZ5fBCe7@KF}7wrkZ51SsS|Ns9#BNNmA|K~n1F#P|2`~U;X|NmDn uFmU+zhA`+Fm;kXcL@PrG3C06W{SUGNM1!mW8Bq^p6KNHf4bWOUV=e$vi8s6e diff --git a/lib/pytz/zoneinfo/America/Cayman b/lib/pytz/zoneinfo/America/Cayman deleted file mode 100644 index 55b083463a8b5b19d62b6f2707665c08eca5e65b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194 zcmWHE%1kq2zyQoZ5fBCeCLji}c^ZI3_m{*Mj7<OkZ!KV80Fn|6EdT%S+`z!$;~T=@ g48*R%AwX3i5JG~<|A8R0T}=Q)6K?|-&@xjl0PlY+G5`Po diff --git a/lib/pytz/zoneinfo/America/Chicago b/lib/pytz/zoneinfo/America/Chicago deleted file mode 100644 index a5b1617c7f70bfc77b7d504aaa3f23603082c3cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3576 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 diff --git a/lib/pytz/zoneinfo/America/Chihuahua b/lib/pytz/zoneinfo/America/Chihuahua deleted file mode 100644 index b2687241cd05b6904a7d95bae697642becbe5b1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmd6mZD`F=9LIl`d1$R1A|Vn=wC*142{UHbX6$Y@cQbd`-OOgpHpaRhcZyat97@y- zCGwEAl$odUN+^UBlUkB3DI$4Ff9Lb}MtP;Y@I9SAr~jM(o6jc@tgeigKQ_>Q;j*B; zyoaCa?5l78P}|d4SrodQ@hH?4l@#jw(fmHHs!`$_2G>u^Dwc$TH}#2==c}anZk;@6 zs!AF2SyFnV)b#JqWX7jqW@hI@N$rR<v#zyC+Wxm@c59Q&sk?8|ceU!dc^xLBV!QUm z95nNMr8;xSMw2xuU1xncpt2(->g=ZtYQd`kdSO?g%DFc|{6~^i?u7`+3y)HZT3$<j zS)wVZxh;#+N18y+IVl|f)f7dy$P#&OibtH$#XUF7(ocJI@aAz-@}x#DJ9}L%zm%(2 z>^`PS55?+GwN_<2{8FA>u2!yyk%}okwJL3>R1OQ7s<9uX>cd>K`o|Ngel*Ffd2w0R zUL0s@ZavoP4)mGY)0cGJ`n#sS`LJFe>{S~oH|vHOchttr61{2kdDR#bmd0;;Rd`5H z!rgUh^Se~pay4IVeK1b8om^+Ow~Or9nP-~zy%bMBPJeqMJpcAM&e0YT$7wSX;W+0{ z$Y960(&35X?NPtJ=lBESUmTDOuFt;h{e6MIFvhXN8i+X%dmsiuEOKd0;y>8LFbZN7 z!z_qh5W^srK}>ULZG#xcunuA#!#;?C3=1J9GHiqx39%AlCd5vNp%6<Urn<DYLX353 zt%aBiu@_=6!(xcZ44WZFGpvS~&9EC{IKy&?=?vQ;#=ErEL(GTR4=Dgr0i*;-4Ui%r zRY1yM)B!1kQ3<3JMlFzHT-s_N<uK}j6vU_qQWB&lNKufgAZ0=7f)oa+3{o1THb`+U xZFP|HT-y2|1u`mxl*p(NQY52F`2Uv~X|qkXxkmdFym8*xXn$O+H_;m#`4fYfda(ci diff --git a/lib/pytz/zoneinfo/America/Coral_Harbour b/lib/pytz/zoneinfo/America/Coral_Harbour deleted file mode 100644 index 629ed4231944255aaa0725f807517df48b39e135..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 336 zcmWHE%1kq2zyNGO5fBCeb|40^B^rRlyd4W0=I{DhaN<XJ!s(8G4VRR^6kJN={J_M> z#K_FT`v3nb83u;`|95U+WcmMp^#TSCFq;QV3V=uk5g*?W24@!_4hG_IAPxv&a0RkK vfDuZDkl?KUKv49qB?Ux-oCl&oP6W{)XM$*uQ$aMyxnP?5PUZso!ITRCV)1m0 diff --git a/lib/pytz/zoneinfo/America/Cordoba b/lib/pytz/zoneinfo/America/Cordoba deleted file mode 100644 index 5df3cf6e6377be897b4d09fe438ec75eb8c15ad1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IKWLLd9Eb5YZK5$03@9if60xNuA{=dF#6RVg3{^2p4Je%yL=XhU!3v_$#YqTA z5QLV-c2zuwYH6cAjWyb#1X2;fA)vS^A~D3OJ%7(RiHlC=<=)Sc>4oo;9XffU$NS^A zLjK|K>zBiQ?PYn5U(c)i7Y6+Y8(!$CO?iK6@r;^YchO8wPUxA|J->A0m3sL4oq2RV zqGxB;`(`MwACHduPj;16`BK$9-PqJ~M}z*{aza)1rc9-NS3SQ{@aHqP^!&%GW+7wM zg8pD?@uXf%A2*FXVbvIGnlGVKs@W6uoA1YTYfaj;DmC@BG3+nZw(D=@r1@Stq<-8U z^p{7H%3ka9S56$z;m*^=Erm(l87&mMQlE9#r*p;b&8t;+^++-9SwF6K78CWF+IsVF zEY*Cbcg!aA!0;+P@Fo}Aw=}NzmyX49*4jW@`(Hkx;IF3*+uc26ZMo`s?j5wYV!W`m zFROtLYv0xbQSM&H!A#am%h{&-<n+q<eUt1ztRx?7&yL*D$Xdu^$ZE)PPFW9G&?ze- zOCoC`iz2Hc%OdL{3nMEdOCxI|i#ug?WO=8oj}(AZ;FJ=O8k|xDQUy{5QioFtK`KE? zaY`*nF;1xlDF>+sDF~?uDG6OoT%;(8-=iv|ETk@^Fr+f4l!nyil;V);oKhZAA5tJv dAyOhzqf?4Rszl1foP65<uQRe+n(gn6`~;>^?DhZv diff --git a/lib/pytz/zoneinfo/America/Costa_Rica b/lib/pytz/zoneinfo/America/Costa_Rica deleted file mode 100644 index 525a67ea7913b5a9e2507067c164de30259d0fbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 332 zcmWHE%1kq2zyK^j5fBCeE+7W61sj0G;um7Rf@Yoxg4^=~gvGQIgr^4ts84^8ppm!j zf@b-l1kFP?FEBDQLE-=Z8;uzlz$DB6|2sD@a{m9ndI1Bkk8cP=uosYUb^+qx5TL;j z5JG};{sTc~yOuAA200640?27#8t6O_4RRuw209Z&gPaPYLCyuyASZ)q%ACyw^n)1} E04OF+k^lez diff --git a/lib/pytz/zoneinfo/America/Creston b/lib/pytz/zoneinfo/America/Creston deleted file mode 100644 index 0fba741732f73cf241e702d41f4ccb9be60124d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmWHE%1kq2zyK^j5fBCeW*`Q!c^ZJk>}%cy^L|=0FfuXz|3B#n1H=FSb0;vc{QuwI sz`y}v`}l@1_y&hC1OPD%gpgp(e;|mnE!YF1LDqq2GOXkRy1|?a0DglyQ2+n{ diff --git a/lib/pytz/zoneinfo/America/Cuiaba b/lib/pytz/zoneinfo/America/Cuiaba deleted file mode 100644 index 8a4ee7d08fcae58764f1b1c4e8ce19f548c4734d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1974 zcmc)KZ%mbC7{~EPD&{C_E;nO^VayDL<8?TsM>2LPQ#3swc|t-hg(;CFN{k2!LDy*W zg<COAv%<NytgY_0Ho;}PS8a~j!b&Ph{>-IYjjh-c5!BQDy?fb<dgsF)K4*-Lv0dL^ zyu5l_PVleiHun==o)-7=`K|*WHg|O0v;NPn8%3sjbeWzU+#~(b&$WMKs$NL_!(QmF zl7Yz#9ejI4h6X;+;rLSd>#b?_${jT_X6y9o-C1V5Bu~dr{~!}7!*=3$vY8y2q1X5A zGig0XbXMaf^I&IzO^*zi*=;ZBLwEjYGRkJzjEgNYCmPr}=lW#s>@=NwYSd%~3C-*{ zB=dg1-_GBzCd+=M+1tLCQ0M!)V8v%5t?e34+hB6mhioLdQF5adnmgVr3)8c8VQ+(3 zG<MELyWW(=L!+9vKhHeYGtDlkeZV~a^?RCMbki(tPu68KZ%9GOSuMCaB+Fx4?DFAB zQ#idt3r`%Al@rFUI{2DA(f@{B-FnolIazOGoA;Wc!=<)(Ri~`o`=XYl7n&z4pVD<f zrim9U()e#Hq%<Q{OM4!XvZP5ZJDws>UmVc#*GFW-;10X-xo>6D7i;Y^D~&w+ZjRj? z{>E%+FV>3bADhamuvSi-k*$UI>elb-Ox43zwCdBisZRb$s}Jvz=P!5b_NH}G)7xiX zD1TITd~(9p=FOCy`w!V&sn?{gw%OKQ`$g)DH`)60r=%g;qzzx5H;wmg)W(m-<mK^v zZQApWY5qA~U#Zw_UhPWKmeogO_rYH^Y09nqN+izI1VJLvcXrC{iNt9eOkER;2iX#F zZ!Y)gp7?)XajW|^f5i#+aLLFud)-ANSB+dYa@}5c;mDPH-K8Vfj$Ax)^~mKT*N+r{ zRDhI#)ZleRAXRu>8Au&SA^e|If|P>Pf)s;PgOr2R<8=ig6?t7rNKIZ>6jGJfm4(!W z6oyoWl!nxX6o*uYl!w%Z6o^#lbtNJ-dR>u7l}MRLok*cbrAVnrtw^y*wMe;0y<S%^ zQnA;SjMVIPMI%)sWg~SXg(H<Cr6aW?#Us@t<s<bY3xKSE*DV3E241%a$SQc<G9c@K zECjL=$WkC{fh-2H8pv`W>wzo?vLar$B*>b0-J&3?;&sb{tc%wz46-uF(jaStEDo|d z$nqfTgDeoTLSDB-$QpUwA|b2fb<2dTlh-X2vQo%WA!~&!7P4B%av|%5t@Ho6|F*Wk m-pVcGZ~vb?OIhN!EkD<NkK|{?!r@RjBnu=fmJ^EbA?0s@8dvrJ diff --git a/lib/pytz/zoneinfo/America/Curacao b/lib/pytz/zoneinfo/America/Curacao deleted file mode 100644 index d3b318d2d67190354d7d47fc691218577042457f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198 zcmWHE%1kq2zyQoZ5fBCeCLji}`6kQhDSw;s#)FaR|Ns553=IGOAK1ab^8f$w0}Na~ lz99^{1}4S^435DeAYDKZLW0@<fdFJah$h7jE}(TLTmUTSE*}5@ diff --git a/lib/pytz/zoneinfo/America/Danmarkshavn b/lib/pytz/zoneinfo/America/Danmarkshavn deleted file mode 100644 index 9549adcb657569ea304592a4070ceecb4550a4db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 698 zcmci9JxD@P7=Yn(Q!B*bfTdRUr<MI^T{N27A|lXexKp$hLC_S94hD@4U(i-iLt{Tn zqqRg+1VOk&Lm&|mv_udsr}GL;LD15<obw#+o$mX2bIVIv^<&lBH*8kIZti-gG_Asg z;%rmyQ<{@65((c+)ORh>w{wYAPo(8`Us^A=q^*3W<A=w(eRofHY>MtIuIsLqRoy+m zpc8pdC&wpsPkK!E##6d46w|4|grwd_rT?*41}YI5yljx6(+^1>JWFQ#PKGzGWMr)@ z?&6WSPcyFLuk-!-1dMqtDP>IMM)|+b>Vwm045$4Ur9%0Fr!sEN?yQ=!ccmPM51if~ zsu;T{!=esS2&sgWLTc5t#gJ-9Iiwy^5UGfiL~0^Mk*Y{pq%Kkzsf?6HY9qyK+UiJo eq&_kMWD3Y6kZB+jL8gLCrsls)Cuqmz2EPDo&71K6 diff --git a/lib/pytz/zoneinfo/America/Dawson b/lib/pytz/zoneinfo/America/Dawson deleted file mode 100644 index db9ceadd963f3ad7cd3664d821e2c5cd9676469f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2084 zcmd_qUrg0y9LMnk{y|B$XG;|6V2@%U2!}rc9>xAC?uc=O;&}3ynHqlv{(*#!T7tN- zS~=xgPHn4BkIb9anmDw%S<KvOYtE(MTFb_qo7pPnw6-W6zn=HwWmjEw(eJnC_xhdR z)w%h+BkP(QOML&hrr0kWuD$l~KBwD05@#;-e|R?Am`l_8kM#|8UwCtV|4{btZ9|(% z^&f?C8JpjyQi^_+2b*{3>9I8`y(*$-EMBB$=9cS>xpgvYYKG2CE|bhVrRJgQGsQnN z!(@H&r_4S%W*$CvRdjrTp0n?BnHx>h*)89zd1bfsBdb1BkEUPNIe7_{Gd^l^Q@d5} z$TueM=gn%t;7OA|+$9BxLnbh=Q3|_WF-3=#NpaIVx}<%cEDUw%(z?k~mJ`+GB|pic zl%Nj!C#m4gOuhK-O||5!d%EK4*J^2hhI#Dl$LjIkzswWw9Z*lU{$QT!`AC-444UQj z`=zqbm=%@VWW{g8I#lqM3JssuRcTvQ)xc4`YIL=F`uzb@{cVec_Z&8>KdqLU^*c@N zk$^n2G-jTSE2;BWn8=zt61kUUqGeYkdSi)RJNvd;`=wvU#)s9q<Kz1Ik<)5@&o%wR z;8C@q{=9xMv0H5nkC;tw9G1-m=gdn@JEcCY&oqQ$(lFX%nsO?n>0+a4PRWwyPga_i zA3fP}q(N`J<X79`A-(;?xN42&>bBl%>gDnW^p4i^sy%&FchnqL9o|$E51y0w$S-DB zMxX2&yl8fh^~kG<6Xvz=8f8z{+oto(O6l6rZuY*LC&@|no-i?aQu5@K|KRq#&T5~} z^Lo3JCVJk!Hs2KYKFvLq=Y1ea(|PyJa_?Ey;fOC*X-}O!&)HMUZ~LLveyFu_CAsH) zBmZItp1nF>KX?k+1F{KZ7sxh{eIOfg+MOU<LH5GmU^B>WknJG*K{kZ!2-y;{CuCDj zyDMZ{$i9$`Av;61hU^X59I`uPd&vHf4I(>4w&=8bL^kQPyF|8$>=W52vQuQM$X=1n zBD+Pli|iNKFtTH0%gCOQO*`$bk!>UUMmCP@9N9XucVzR(?vd>y`$rmpbO33A)Aj&q zg41>ZX@k@D0cix%38WQBFOX&+-9Xxb^aE)K(h;O3PTLctDNfrJq%BU{7o;&rXOPw) zy+N9TbO&h<(jTNjNQaOXAw5Ex<g{Hv+T^r-LK@|?okCiL^a^Pf(k-N2NWYMVAss_n zhV%?+n$va-X`9pb4QZUyb`EJB(mSMiNcWKTA^pSuLjzN79D_EF`Qe3ul0Z>@xVSh_ I94Jct8<eRmj{pDw diff --git a/lib/pytz/zoneinfo/America/Dawson_Creek b/lib/pytz/zoneinfo/America/Dawson_Creek deleted file mode 100644 index db9e3396557652707c0c2232e119e13a2dd172fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1050 zcmd7QOGuPa7>Dumjbn)ly@Uf1E)E1<+6Y}RjA#>RkYqK{Wudrez*P!CtxCusH+fnF zZM1`J3L=RJ1SxD2L2a6-oG#OJvti0ovoy2$dY_MWt=#nu=ll+{{XfaWU8xP`pKpc! z!{KYv!+TAyUPGg|{iU&ld(gkur`OGOr#oxp$^IGp)E4B~=EruzJd{l0md)g@%k$zR z|6<(9%j`}6>gqL@y>r38?my>pL&yD_u5S1C<RSlV=U(@|eXIXazsXHDAG9A=?~#0M zlg-a=l)}ntTSzD5(`3<pzA-OT_j2~j`HXxWS@P38V=^<4^|Pn$x^G9v{anXo_kH)U z|FQjyn{Vjz3w6Wtb5+VOmi5Wv!fE>}pOU4CBlh?G7AcN4+wjVIF&FD?aJEunCn{`7 zPo0Z**80+5+QoucNnEdG|4PGfpv9Ol9CSfk-@T?RdRIobaGR=Ktx40iL(e`v2lTWh zO{>27g1>ii8LNc)wQoDN1z87K2w4ePDpJ=%7DHA;mP6J<7L3#tktLBekwuYJk!6u} zk%f_!k)@Hfk;ReKk>!!~kpd#M0;GgUtpO<lsRAhjsRJnlsRStnsRbzpsRk(rsRt<t rsR$_vsVP#6LaIW_Lh8b(FdkIKr=<b4A;lrpA?4x!QD3>{*jWAtB*Xe_ diff --git a/lib/pytz/zoneinfo/America/Denver b/lib/pytz/zoneinfo/America/Denver deleted file mode 100644 index 5fbe26b1d93d1acb2561c390c1e097d07f1a262e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8v<nU)CgtR#>b6-0<P2(NH8!YHnHS1a(Ln-=0RD8?U+ zvtrCL36!+fOng|gv7xTv+REl|Yg!BKxhxw!Y?8peo%dPwPk;4STVM9OuOIjS&-=Po z`_|@&f812_4G-6C9^R)b{@GWcUmFNlJ<liU-dDa?dprTXw{@E6E54}vI`*j#+N1RF zyx$s!>*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC<xjIswP>}}?Nyq3Ob<M?I9d-V=h(6JxW8Uo* zv2XTB`ErZ6w*6Uo-Bypd-d8WDuPPC7rT5Ai`6=Rtlm#+=Zn2sf>5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU<P zcbE6;d+N8TqRba{anTx8{Ogb`NpBJ*XZOp}=vq;Fq+Kq%Tqw$3eO)jAxJEgf+VuVJ zELG(-K3&l@M?J8lOjr6t)rzEa?OOSja!thQs@zkm>gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vM<TJe`zEf=(Jg&En`PI|iz51DRZq?M>qPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(<dr(EuI31^XcR+y*SJQXgpA|XQThwERgFKDhdEUF(_ zA+khdjmRRARU*qo)@d~hMOKO|)oRv?EEZWUvRq`nR<mGa#mJJ8HKScLFRYp~%LdlX zv2bMN$kLIuBa25?Z#BzD)^9ZhKq`Qg0I2~-5s)fylmV#&M<I|(aFhb61xGQEYH*YT zsRvRJq#{;R5~L<bQIM)2WkKqK6b7jbQW~T-9K}JZ!%-fjK2}p8q(W9xBBVwfMMA2C zlnJR5QYfTSNU4xoA;m(fg_H}a7g8{!VpdZ!q-GpNL#oD6Hl%JGg+nUGQ97h{Nb!*B zA>~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5<wa#mE15^&RHNV6pj8 VNOLaC$jQh`b7p5}WM^bK{s0D?lEVN1 diff --git a/lib/pytz/zoneinfo/America/Detroit b/lib/pytz/zoneinfo/America/Detroit deleted file mode 100644 index 5e0226057ac5e154901b0debb90547c096f9083a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2174 zcmdtiUrg0y9LMn=L|_L<C@EB;f{cOux1(rO7_ie(kC+}z(e%;4vm+GW)Xty`Ye9xv zb4`c28q2gswMJ^TT*cI!wK+3omHin$wZ%n?dW<ewY@D9=v+la;uJt>6p4ZvA`Q4n) zJKPlARO$WWNw&XmczW&Odv?!9d29Ap@Ab|;XXIl3?{ZO1=&$?(=8|_nC)Zq-l=4$5 z<@xDyO~xVR^A3v7JgZW5kEDJ5s!l%<k!z24>#1)%V>0${(wV(2=DN=N^!3qznYOw} zX9Ww*4fE6VjfJTuJFieppE71<B&Mnvzxib5_hTyO!q0Nk$@41r@Mm)Kfy3&Sm}hQ% zdXLI${K4dJ9@Mw_Pn%hbUeLE^y>1GMw(5e(kEPJps0&A4lcJyI>Dfa&rFb~3O8TQx zdUUQT>sl=3d$LtUBw{MJ{Hf*yg659p-zk5=Y%{lVNX<)0H&rvg(N&|rn)wqS>IG*m zm^;7i*VTi+$Xy>irSIPTx!m*8MqSf>L>6}MQ1>?MmD=VFs;(?1^>wwXetf_LO4jSZ z@GcWfU#Npe+svY|e7*SPURm;GjS6jVm8I|HsfM*7S=N`N?yoMB<&TZ36*-v_Zv0e* zC&p!^|4p^>$Ejvj?is!6^cAyuazHm78a8W2cIma<$IOF6ZF*hvKC`}msaBzPWy8)^ zwXvj69*Trib9#rg1j<y)Sd&BwGF9YUwM3K0RrKv#**yA%YVBKK+Rk6m565!MBZI@b zy>ZgW?qm8<zcyRi_vx)!r_8p7PQ7jNvc#(TRBYs=bYyp^j-i9n`A3s_yuU}DxKypS zcSYpM_j6U(x}fZM(NhVDS0yE0{U7+m<40zBUOfKRD_&AOe*7J8N<99_iG(zFXSjRX zl2F*IT@m)`IS<&g%$~Y1e|j(B?>qc21`@XqBSD6Oj0G8t(~bri4l*8I#ek3zIqi^; zF(HF;+EF3HLdJy*3>g_RG-PbZ;E>TF!$Zc03=kP1GDKvI$RLqXI_)r#aUugnMv4p- z87neaWVFa|k?|q}Mn;Sb85y(F4jLJ?(+(RMH!^T!<jByGu_J>=Mvn|189x#LBmzhX zkQg9AK%(HZVL;-51OkZ!5(*?1NHCCSAmKpbfdmAJ2oe$`CQcg^Bq~lD79=iC8yF-q zNNAAQAi+VRgM<f(4-z0GLP&^^7&&c_kSIBAn2<O*ZJ>}yA)!KIg#-(U77{KbUP!=@ zh#?_EVul0_iJH@f4T+o61`dgw(}oU-9TGewdPw+?_#pvAB8Y?#i6Ih1B#KTOMkJ0- y8%QLQP8&)jmPjy>Xd>Z6;)w(l|CbT<*~0p5S&Kt+N-Imti$fI^r4^;+zP|v~6Kewi diff --git a/lib/pytz/zoneinfo/America/Dominica b/lib/pytz/zoneinfo/America/Dominica deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Edmonton b/lib/pytz/zoneinfo/America/Edmonton deleted file mode 100644 index 3fa0579891a9762b7c131ec5ece5d6d02495bfc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2388 zcmdtiZ%oxy9LMnkp}>tlrxHU!BP|I6<c}nf85Kl`HwZ3>Mi>RMixs4Hr-gV79MKa` zWy~@ORD#wDQ`cy0pgG+7qjT5gvJx#Ton@mxTP*C}&ig!K>rs!|`riHS>v!St=j~eM zUXw2VansCSc(~Wi!~2XE#!j5?8XVAX4h5_3oiFKb?>4pP#Y=i`+jOz7;S=4Pc~res zc2V|4^{W1ik7d8_Bk^fRnD);9y~$e>Ej};5AWz4AE&iN%MowO;6u!Z1>F<vfK{d1V zw34f8dhVnSP90Mrac6a?JggjIL_5a!sB6#n=&&;*BK&Zxj`*-gM84fE<!i0tx{l}N z^_%L%4enYwV`YJeD!gCL%uWzDCfp{Y=jE$ep$<7aFka36b%BogHdMuWL-d>@KdQO! zU)DGE99MCkIr8SM18QEmU(Rp%Ox%+Bjl6Z)dtyP<Q5m18MZE8vPH?Of31dfe;$@e( zeR!`<I@P3-ySC|+gQaR=OTA8gWsyp&Z<FckXR3^XHF8nugvyM6K;Du5rCJ=ED6?Yz z5Lp+)WcK74k#p>4dFNL{V#$7ozH4Z=Si1cuefO>{BDe8`zNc-My0>`0zOQz(%3Jud z&d*z|@_!qZ1<B2#;8dS146hc22Rr1lE4iZRjb6R{bd_>8x9bN#SgMMv+`6PQPCc}w zNSAs7RatDZc9nmpTvsD?MdmS8@qLo4oO?l3jz-9pzEQDi-?)5utWQ+6dF3O+9iqDS zkX+rhRy^uFscYKX)nmyA^yBqzRU5uT*A*10x+@-CAD^u1k5_7UaHMj-o1+_k_(iSl zTp^!086lqZWXq=p#zkXAjBMKO6;EgWCD%0`66>SR$qmJwVuNo|d$JBF&)8YLF?xsE zI6R^^O?cF^T|N4_FDg}YORL^In4?;%>-3hLu_`cN%IBJ(DL<zE<G*<K`(N!A!tZ~l zJ0QsK->pT6eGjwWa=FtboO$LcGtUb1l(@`ngb1)-u79yKzd6>1EDl*6vOKF<AF@DX zg~$?-H6n{dR*5XrYSxJ?6j`a&EEQQRvRGub$a2xHmlv~Ojuj(IM%Iih8d){6Y-HV5 zvv6eP$kI91jx3&I^~mx$){hi`qXI|?IBI|tfujmY8IU?4g|M1RAf-TRffNI&22u{B z9!NouiXbK7s0mUOj;bJKv6{Ldg|V8-Af@4`4N@GWI!Jkt`XB{DDuk2>sS#2nq)JGc zkUAlSvYJXErQ)a+QY?;YA?4zz7g8{eiXkOKYK9aIsTxu?tEn4OIIF1~QaY=t9a21w z>LKMr>W35%sUT89q=rZlkt!l(w3<31g|wPVBBivNS|Y{ds3uZQj(Q>m<)|o9QjVG; zMMbKLlohEfQdq00EK*vlsV!1mtEnzhUZlQAfjKISl$fK&NRc_JjFg$9&PbuHrqW2M zt)|vUv8|@sNV$=EBLzn)j+7j!IsSi(?l7TWY=WQU%t%R3NlkL5rKO~$q&ofvy_=8y diff --git a/lib/pytz/zoneinfo/America/Eirunepe b/lib/pytz/zoneinfo/America/Eirunepe deleted file mode 100644 index 99b7d06ea467e79107b8abf448cbff3193cb6c80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 676 zcmbW!y-Ncz7=ZEgY(c8%B7%rtoh|rP5E04PK{1t^#KEcHA0UX!)j<$koJ4VPwUd*y z4w9|tbgreX72GU>_*Gg<%yZ}@6f{Tpg&U5`^SY}W=_&DRMeG|kD`q$6?!)X}CfA!5 zxz?J_KPz=vIgrKK8&$0JtI|NrFXcC7xjCsSCk=U5zED+nS-u_(`frhK`K~kSqi0Un zQ%O}X+{;Gv$#0zX>1HjaKKGCH&~;V~Z@uV|)096t{iw%wc2%tFL67TO6;D@Xa@|ua zURZm^7$?;B>2Mrlt};RxQ_!KXG5K@R$yt1#7nfWSpO~>bVXyVG+&`4CKVhhW6bZCd zkTOUeq)?!(gp>-jwUA;+HKZI;4=IRLL`otxk)naNDpEGk)<p^<mANaWk=jUcq&iX_ fsgFzmnF2BiWE$w%Z?~OC$oc0)qVsk_3yJ6#Yr!6s diff --git a/lib/pytz/zoneinfo/America/El_Salvador b/lib/pytz/zoneinfo/America/El_Salvador deleted file mode 100644 index ac774e83f46bb2967f587ea3175d6d2d95a15387..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236 zcmWHE%1kq2zyQoZ5fBCeRv-qkc@|$?rl2tE&I84m*#Syb`yMFU)CMpy0^$GvYZ4e3 z{{P>(fsy6^|J4f^IDC9V7@S>zI5-5T69hs?u;@P!fGh*)16c^7L6(AOki{UHB+I#g I?l9v508!&K$p8QV diff --git a/lib/pytz/zoneinfo/America/Ensenada b/lib/pytz/zoneinfo/America/Ensenada deleted file mode 100644 index ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV<wIXJb{bS^oTWhSsR+x=3Q(zcHtQD2x^t^vvcGX?$clJE5-_G6d;`8?J zsIE+N{_)JU|8RIZ?BPA~wccM_x*7}Xx~H3_dMnH8-i=ny>9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_<F$=t4Cy7@@9=PN^Sy zep8cY2i1@5=hgg?ZnNP0fC}$#Hw)kER*Smc)arP<y6#!giyQ0JlIp#BY3Vi<k>}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*<iwcnXLTDxRpVV}9P{5i>8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;<SEq10?`P*NO|WBl8u#eX%{lw^J- zC70Lh?JIs(+dqlXrL*VMj+3+czTtP&&ejoqf8X<}to)3AptDi!@(r5@pXrd@$^GV` zs{K+Pe!^6EOQmA6)l|jjNYy~4sSb^m>Nhr-n$dtfe5^u0@<oi=)8N&QcF(HXk_27X zHliNOny>fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;<l1wuJw<*7<2XTo-~N9wu7G_Q7&0<sXvo-*!6BnVhKG#L)eaCDAu>c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX<orL;?u~5(^|4NHmaexY~Fi0dchvK|+GW z1PKZf6(lT3T#&#ZkwHR(#0Cit5*;KwNPLh0x!MRJAwpt=1PO@}5+)>0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a diff --git a/lib/pytz/zoneinfo/America/Fort_Nelson b/lib/pytz/zoneinfo/America/Fort_Nelson deleted file mode 100644 index 5a0b7f1ca032be1adf8ba91f863e727c5ec6a026..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2240 zcmdtiUrg0y9LMnk^7jb&%{)qk^`s_(DTW{@nyEO#KtK)_ty#9B;c99Y*4Bs?t>_;Q zI{UMVzS&KzCNgHTkqmEKt*i;lM2yHv7)mNy0ura^{abfkb=P{%p6B)3x&OR__f}S< z`~GpF+&^4Sy}NuT)VQbd;30j#EnvT@OVrNUm$!9po-5y#T{OqdpnRX%Wls3MmhQj- z)7`gEPEH)to(?OgdRz5}rcZ2d`yTzV?sePOxKn?s+-6T#m+Q~@8|*Kea`e}f40|T; z9@9UyL<W+VnStTOG8mm?20H_C_RNGi*K$RMj`W$|8oK3t^SEBv-X<68dv*BP!}ikF z4!wN;hxYfPz52(>7wna^S{<3US4O8)=vYjxjEy{Rt`1bl_=$(jpI_W569@B5=%ZBe zy_I6ZUW$|OrzV?8+vnMc&B+>B;<r(&Vl{fvIU8fnYOH)?{l25>zp&p<?rYY#?xPal z(V*9S)+|$+_8ED*L9VT{X6n;B<hsho&9w3|3FHUO^rcxcBV~z6m{n@8k4-R%;h8pZ zI74qZJ;Nq-PS=@-N9~O*BYM*ZJ=RpsH#a|b$j;grZ)R^fDRVN<n_Jd>E^`CNOmb?Y zBu@-!ioZrudcW1w!3Sl2dyC%MRc#kE?$(8^57@NoCw0;8)%LbWcA4}YbL`^0Crn0Z zl+8@uXqKc8*sSPmlbsYP+5L%T>D7K&c4XY-^n5AH_b2FzwvXlZ`Y~Pk&TDeV)>FEw zw#lw8YS%Rny<&6IRM+M{X4hWoGI<MLmb|V5CO`gB$!~qrtQ%S{cfH%H1;@A8!e{pA z`cDgN(S19$_>G0OWO=!6s1jS6l%v72VH+Huso`PalOo*n-}ps_La&bce4)^LHY_3( zs;}|Ic;9i}E4;pG1%*Lhajv_i?%wTganM)jzByrkzrlYopO8D7R#d%+%m|qhGACqG zo^Dpiw2*lr6GLW(OwH5H4VfG=J7jvu{E!JEGeo9{%n_L+GD~Ed$UKpWA~Qv%>gnc+ zOxDxQ7MU(GUu43_jFBlLb4DhO%o>?CGH+z!$jp(cBXdV4kIdfFO&^&*k^m$F=%s)U za=>v(0J4Cj0m%cB2qY6oDv(?t$w0D!q{GwY14#&y5hNu@PLQM^SwYf*<ON9#k{Kj5 zNN$kiAlX6EgXG84B?!q7k|HEWNRp5&A!$PLgd_^d6p|_=S4gsuY<aqLA^Gxj2}3f5 zqzuU!k~Ac1NZOFRA&EmWholb49g;jGdr10_{CT<rA{j(dh~yASB9cWUjYuAmL?W3) zQi<deNhXp_PnS+4pPnwENJc$fN|Br*Nky`Xq!r04l2|0ONNSPXBFROv>-qmpuiv#e G%l{V~?gk?O diff --git a/lib/pytz/zoneinfo/America/Fort_Wayne b/lib/pytz/zoneinfo/America/Fort_Wayne deleted file mode 100644 index 09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?<VS1QTNoUyPx&yV6)0S+okgc4ypV-t$Iy+nJQS{pbHzbl<;4ZMf*#|+Sq!z zuGlZuhgHiB8S!Gnr(9V)Gh7sRrpS`f!=mJJl-xAbElTT?b=l+3YI9Yj-qO;g%5#ER z9&S}zla#I~Z&2GJ?&$5=HEMfsP*%;Y7gYndW%bl*QQdw<)_ltqI~w=OoxLfdwys$2 zYKsze1(|a9F-MH>+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x<i+?D1o2{`HIJ>7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3R<GEZcp$V`!`B6CG1Yc;b)ri;uMnJ_YAWXi~#kx3)7My8F-8<{vV zb7bmh=gte0=a|_8(?{lyBw#feASqZ)4oDJKlLe9nk_VCqk_nOuk_(ayk`0m$k`I!Q z)ntUEWHmV<Nm)%+NLol<NMcB4NNPxKNODMaNP0+qNP<X)NQzdIBa)=mWQn9{HF+Y5 zBAFtoBDo^TBH1G8BKaZ-BN-zpTTRYL(pHl-lD5_4jU<j_j--y{jwFv{kN<J{q2?DM Y$^0V3_-Dr@#?6ZHCnUrr#LWu*39tolUH||9 diff --git a/lib/pytz/zoneinfo/America/Fortaleza b/lib/pytz/zoneinfo/America/Fortaleza deleted file mode 100644 index e637170a6edfe97e99e4c729ccfc35319e1fba85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 728 zcmb8sJ1j#{0EhA0dPGXFNEp;Xnixn<dP&=8#7Y<>cfep0Nn>CzSfnwSBt{aJGm1g( z*qSV~CL%=QQN<!*(Reg1InH-iOP%KA*L3N<-`|-|%!RE#r^@`o<@A`#_lC{A`PNj^ zNIvy5tkQ4&@*uY)uA}?%`lTdq+aBE8^pv<Ox5(U1PTb#Z%e-TY_bu&y)Xa#_!&&*& z7*xfHURlf>iBjO)E$tRn`L$Mludk@~lMNYMzET}(z!mnX>Re68uKKL%o@{oz9~VU^ zQgA~T84>wBcB82^5xakqvBIbr%xX8j(5RAHYrn6ek6-JXXUo$1DetS+`tr=G8yk15 zJ{dOG=8es9{?Wz!wWbZy$I~=IIw7r)UY@2I(#_MfL;4{Nk&Z}9q$ko8>58;P`g)qi zNM}#e8tIKR=RN6;v`6|Q8z4I%TOfOQnoW>hJk2)9KFCJMPM&5fWG`ehOqyq@Xf>a| NqTK><v*C~(_yw=*Bsl;8 diff --git a/lib/pytz/zoneinfo/America/Glace_Bay b/lib/pytz/zoneinfo/America/Glace_Bay deleted file mode 100644 index 48412a4cbf9241ea83887876b1b8b22c367ff4fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2192 zcmdtiUrg0y9LMp4Akj&T3d#gcv_cGy{5y3N4J?dd5MSk>92NRj5P=m+f{;YDv|wwq zt<W+@bGfXGvW7XbjbUY8Wc7g6Y%P4q;YEw3)>@m%>3RQdyXvm>JA0njZ|CORoX<PF zzUkpS-#?z|?i&uzi|+8A{LF>73;G9l>KjLYlrMUI($NLKZywc2WBZGAyeTAc`m0Wt zQHd`e(JOKeOTxSXy)tRH_<eoqKi?r&*=;)G`&Ufja71T*)NZbRr_Cn4R%5Q|U1zWD zTxzasT4IwUX(pvC)m|T(GB+%|Y-i0+l^YYz*x50cW%h-?G<E!pq>YT}oMXpi?gztq z)BZPQUfV^R{_IYfU;T|;(7fN=96VxgS@oQ`HRV;Cv8cghOns=C{!)`UwnrEJoM~<w z>eQ@L_eggC1`P}>l*Qf4HRol&<TgfZUh6NCUs`Aj)_f(wj5+r9y!Yjf_^1t~o;0Cz z<95lV_sr7IKedHFykwRgvby}^R&(dBPqgUuuqkdipeuH4k-I9N*SqVhq-60Uy0RoF zD}UQ-OS4y-(o<cwEHU4d4b<6HlPTujecQGCyF97rs@40BBunMSB9*>BWc9LiUDI}2 zs?uULTzyo+mnL*=aG$K5h_N+u2TjdqXKiF^uUUWKxZN<;Wj5|OXlsY+OkI7iy}!TM zXvJIlzzenVV0Mo_)L10-iOt$jnl25K<=U7LD~(?Uv?+c<n%+&)=5r@x)9x($=+|e> zW9>8T<A;u$mg@6%^R9ztOK{Y-w)C1OQV!d#m7C1gsS#}t^+@~JfOgDkmX4u5?fkP` zp6uVI+fN6it2?4k4JJu<U7<d`=b}uDnHC%8uK&U(8a;8+=Zi+aH8FASeb|@az0dIO zr1FZeZ$*haRqm{FN5cH`eKFn@{udjGx`&-0TS4}MYzEnlr`ry)A7n%PAUi^~gzO2~ z6tXL1TgbjV-NuleAzMTChHMVm9kM-Sf5--r9U@yq_K0i}*(I_~WS_`JJ>5=`ts;9x zHjC^Q*)FnQWW&ggku4*8MmCM?8rim|+c&atPq%Yq>&V`b%_F-<wvX%|X#mmzqy<P1 zkR~8qK-z%x0cnJ%>jcsYq!&mtkZvIDK>C3+1nCIU5~L?cQ;@D8ZSiz{K^o)fI)k*v z)Aa^v4$>W@JxG6$1|c0nT7>inX%f;Uq)nc#Pe`LYU8j&%dAeR9%|g0`v<vAM(lDfB zNXw9(Ax%TNhO`ao8`3yW*Eythp00OD^E_SmkoF<{LmG&55NRROL!^mF7m+q1eMB1R z={kwD($n=4X{M*^Celu%pGZTIjv_5ZdW!$2rutoK3tV!uD)Iw)ft;+0+}uEJAjkhV DRnuf1 diff --git a/lib/pytz/zoneinfo/America/Godthab b/lib/pytz/zoneinfo/America/Godthab deleted file mode 100644 index 0160308bf63690a6dee51c3c8849f494a47186c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1878 zcmdVaYfQ~?9LMpKG}g?%E(w*86i#(IN4ZrlsfLd0K`zN92??PUaTqhR*1opJ48yc$ zMmz`)a+&KGVzXv$W172}4Rc%j`@R3>kw>1`{M-4z&U$vX@B8DMxwydN_~QvO|KY<^ zYCe2#w`XIaqm#E{VrS2H4T*ZIT{=C|(7;<7`th80Z9cBu?jF$Ymv(5_nX?jpv`!-S z?w1~wDv=eNq-Rly^qRdudT0A2DkVeuIFlthJVyGq>nbrp=^FESpvFFr)_zT0wEvYh zI-vfmI%{5QT=fHu-*Q6}R-aK<{xMC=y)W*Pdhw(-$iT@vB`IQ`B)iKbxy3Jo!>V=g z<9RaVN2v}yn=Zp1=4eW7o~AYo)wHc6b@-le9Z?XaBex8ZQJF!So*yQoqhD%9dW&QR zUDvFrCzAF4g^UTjAY&S@$=K#YGOq53WZyim-l|F&fApYEC@z+Xm78_a^zAyiXrWFS zRHVM_ES(xUUZ*+x>9j8{%?ZhroO>O0#^+d>dFqqSY6_Ow2RcY@{X3cC|0sFYjWRc{ zN#?D8qw|NKmIb*tH9vB<EKIG_g0?4hQH1KESG%;Zr9>BBE7v8DeY*7UEG@daQkHE? z)#BPIvb-=-S8Pp^m6KAWq##sQCH9cj8Q)}02Zxj<glOr9XHwSwi<aHFEamU->)I2w zvaYc~D|TPh^>zDnLwSw*tNiM>EGwXOtH6K$*UGYPZ*({;tLcuT_3wA{(}1>?#XH;U zbHuqk=HoV}7ZC94<@<|kH9ySaVtKe)<Z_YgMJ^b*Vq0^`$TcGuja)Ty*~oPx7mi#x za_PvmBNvZcJ#zWT^&<r!6(A+pni`NIkSdTekUEe;kV=qJkXn#pkZO=}kb01UkcyC! zY)wr_QMRTkq%5Q^q%fp1q%@>9q&TEHq&%cPq(G!Xq(r1fq)1y+B~qrXsS_y_sT3&{ zsTC;}sTL_0sTV02sTe64sTnES)>Ms@ZENaA3P&nON=Ir(ibtwP%17!)764fRWC@Tp zKo$X61!Ng)%{m|pfvg0w6v$d2i-D{LvK+{IAPa)52(l!|njnjUtO~L$wq{+Bg|Rg& zgDefQHpt>2tAi{LvOdTH+5T566r5s~Da~Wv?lh;@6Q30CN{Dkiy@{@0UlW6W0s=0- ADF6Tf diff --git a/lib/pytz/zoneinfo/America/Goose_Bay b/lib/pytz/zoneinfo/America/Goose_Bay deleted file mode 100644 index a3f299079aebb8524bf77e7f92e0a7e6d0a7b6fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3210 zcmeIzZBP_-0LSq=QA894A5kbpO;7;=CC{f2!#EIRQ_#Z{SH%cC(+ov(!)%-pW}0!v z0ZntzCP(o_V~UfPO@t+SIaUiKok_S#kkP5&Q>ZJ~|GU0n##hYrvc1{;{MozV<?oxm zB=^l2({oo}@rIl0lDIjCd>|fWj>3-)o~c(V%Tn!Cj%dr-EqBVl`*@J$`^=j1a|I79 z)zd1<&#wq@To_(j?wozk@k5W3VHZbTc3iws5_ZXS+EF{^{`y+E#aUOB;QA%m%X;~( zajyFD&DLM7J}y_E3)U;4t*$G79kX5y=xw`NTkULU%(wl1Y^}Aia*^#?ahg>tv)HcZ zMq6(bj<7W)4YK~ROt&?MJ+QVU2D<(n7~s4)?y>7;`#oo?cY~|7=CbqemP(iV#A)ZP z^M_owzpb?1IsT2U?cgD6`>unwyW2jr-dnfbc7J)c^+DPy+rzlmtp7}!YwHMG>FoGz zjqTBZNcpkPJoRx$vi9U=gsQv3wWrPjYNw;W<~iD~n)bG7=ACtFkAhmwYkiy4Q@$hj zl4>RI*)?+Ss8f>9s0z7{{~pQLR4V(nZI=3K1#-VDC8}RlrriIlP3nMS8#VuZHZ`Dl zu{LnabahbfG;MHZusS3uNE>SDRELE<)dEKcNyB}vX(P-}r4jAA9CZDf6kO9Nzi{@f z^x}ysdE}>`NuxH_>ml27?V~ds`k1_ehOx2x^_P-!+~bBQdgz2CcWCE6WxVf1_xQ%G z%7j)w_r%I&N_gD_dqi2B5?RsDKB+K7iQ0YJ9-T8wkJ+HwCnt{7rz|*Wj}0~JFUOSF zr+PQ(mY^@(mX>OL+LPVx>F1B?Gp^>lXC6PN%=&tf`;}csl(<hU?%C^#l=vMP_Bm;J z%B$H^?Fo}ol(`AR?Q`!H>WLAt?!-D-Px2e)PO6xrC%YfH=N(wCq|^_!rz-Kv{4WPf zX-nf|sq~@r`pgh{!A6&~FxX2@&p0EcKWUa1#U79rHJdftiw@Oxu1U-6+^;S<T&=y) zsHjVK9MzUpW~o`(#oF?+*{Ym+L|(CTnY1#ZNM4l_CuRHP$*U7Xq}8`l<eX41Dd)#X zIoG>c%KhA5&TF|Kt=SW<*?(?Q*KX>sy?wk|U6;|Ot>1N2eJ8e7+pw-!eRs$yEkA9I zn%`L?Z?Y6gn;I+R&4GE+=E_oeOGk?IURi;>)fFizg_-jE4u7dIYlggS?_J5<!|c_w z%g4J{mp{6G@%-7RcQ-zLy7AlY{NJv>^>{8UHJLn~pGr(UJ)VZcW*>2O8fO>h2A8>? z@$~n2F01Cj;`ddiK#!+MGY3C=laiWln!ixo3F4N-y*S+zFV6AeU3`K#7?=4OJf9uY zyD?B6ab?Y#ITjfzWUP?ELPiT2E@Zrr0YgR%88T$dkU>L64H-6M+ziFQAtQ$jouL>z zWblyDLxv9-KV$%r5k!X2P>dlmh=yVmkzqu}5gABiB$1&+#u6D!WHgcCM8*>tP-H}r zAvF|ZiVUiu7*%9gk#R)^78zM&Xpyl+1{WD!WO$MBMFtodVPuGrF*X!~jEpie%!Xo| zk%2}=8X0Od#+n0z%_T+~7;a>|kpV|W92s(C%#lGyMjaV;WZVtKz#}7%3_UXT$lxQR zj|@LD{zw3j2p}OqVt@nzi2@P^Bo0U*3`Hc6P$02Df`LQ>2?r7nBp^sckdPoTL4txr z1qq9xhzk-JLlGGyG)Qca;2_aK!h^&I2@nz?Bt%GzkRTyZLc)Z^2?>;;h!hekBvweU zkZ2*{LgIx442c*LG9+e5(2%GhVKWqQLjq?gB8P;|P{a-i9uhqyd`SF|03s1YLWsl= z2_h0jB#ee4jz}O4MI@0>8j4sV!9=2ogcFG;5>O<fNJx>GB0)u>ii8!3D-u{k5m_X( zh9b5|a1BLtk?<n%MFNaO7zr^FV<gB(l#wtaaYh1dC?bu7+EBzA3AUk#HWF?m-blca nh$A6KVvhd@{XYu2uL%AW5&rPh$&oRUQQ@i4(UH-SQNI5ILTbFl diff --git a/lib/pytz/zoneinfo/America/Grand_Turk b/lib/pytz/zoneinfo/America/Grand_Turk deleted file mode 100644 index 4597a621de7573d0378e01002af846215caa8062..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1872 zcmdVaUu=zc9LMoTH72F8ZaPJER--x9p8nxb)F^F^qmKG>x<vi^qIGh#*59a;)}OM3 zG~9?(N@8guY*{2r85V*DTiA^u4H2Y%T=YVeAH5NhK7Q}V&63@a>}F5%oL6%$&eiw* ziB!eb7droX2H9_Tc^d4?|HF6LkJe#l`cwASn9rK#>Ca<9GkjULP7V3Y7t@C75xyZN zEz>6>2ltv$2}5Mmt0Xo0aj&G`ex}A;xhWZ^{#IWeJtbq~j``|`R>`b<V#aOg&|e4s zG~?$V&=XRBHd)!5byoi$%9m8BeceB)?59)p#LgC#(-o22_L%aYo+5e8<to25O$wq> zQ@H7kObUj~<RuR!kTuavDeRP~pCp^2(f4&x&kHl{?L|HP+HEuALAx&Qc&=t%*rmVO ze@o3evR2Qo|4o(b-YIh`4yw7;Yos(UuF6X1N?G48CYZZg2fOx}P;!Y5ovt(Ud&lYW zANQ&ScV|m@d#zgdd!|&Z2&+YHiL$sjPc7NfBTLg$RHX7RiM;Jo-v&-d)$uN~ynCNs zvHPr9*;%JoRUa~|+ZSpHpHXXCYGrM1n_3qQOLcOistM*vO>dQoW~E5<MzM+w>XX>7 z8EQk%9jR@bZt8Bn)EndJ=DUtAU0>O6)c&*j`+zo^>JRD7saH)yMU!smf286?Z4&Rk zq8ig0rLpsvYI<EITif@jZTE_0dvjFnIF}*Kt3qn$VMhie4E(SD5<m2pT=&u<$8p_r z2~MKxo<HdfcHQfj2Mp!Y`Skra3&Ihn+`eT??OPggO055UA|G+Hu6<%H8TrWV@;lrw za>K|SBe#s)Gjh|&T_d-R+&6OLp7zd>TSx94xq0O7k=sY^A87#T0BHf~0cis10%^n3 z_JK6wX*)q$L3%-&LApWOLHa=&LOMcPLV7})Lb^iQLi$1)^R%5Its%W3%^}?(?IHak z4I&*PEh0T4O(I<)ZF<^1kw!gjr%0=wwpXNCq+6t2q+g_Aq+_IIq-UgQq-&&YPun-r zIMO-NI?_ARJkmYVKGHw30mu#@TY&5VvI(Ac7m#i6wEKW;gs0sJWGj%pKsE!}4P-lz z{XjMZ*%4$*kUc>*#nbKzvMru=UyzOQv^#@r4YD`L<{-O+Y!9+O_<!D@Bs)$4J5D*_ SN&Z5AeonZcz+d3cPkIL|a{E#M diff --git a/lib/pytz/zoneinfo/America/Grenada b/lib/pytz/zoneinfo/America/Grenada deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Guadeloupe b/lib/pytz/zoneinfo/America/Guadeloupe deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Guatemala b/lib/pytz/zoneinfo/America/Guatemala deleted file mode 100644 index 6118b5ce2d95b66dc88cc40c7470a53105dbd6c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 292 zcmWHE%1kq2zyQoZ5fBCeP9O%cdFIc3b%#B4O#=JbX%{4QDiS2mKM7EO_3nYjiI@v6 zAwdbQ^4<YVj8ORh|7sNmhX4O}ZeU~qvKKIL`1pn}IJ*FGa0tj)AP6DB^8Y{pasWsl n$Pr)~=nxPMatxRTItWCA90j654g=93$AM^)9moZAs~HynI_^WE diff --git a/lib/pytz/zoneinfo/America/Guayaquil b/lib/pytz/zoneinfo/America/Guayaquil deleted file mode 100644 index bf087a0eb5e0fb02109442b5b80cb0b726d9df5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmWHE%1kq2zyK^j5fBCeRv-qk1sZ_Fwk1vy+G2k$Xcs<nsQ>@}KO-{}GxPudTQwLM z{{P>W!NBtW|M3Hi9RL6C+`z!&;~T;d2*kPuCO~Wo)D8h5Bv=Zx<9}wmS_Fs&Sq?G* Y<Nz=YbOea52g;G<5H1^_v+PW{0A990?f?J) diff --git a/lib/pytz/zoneinfo/America/Guyana b/lib/pytz/zoneinfo/America/Guyana deleted file mode 100644 index d1dd2fafcc50b1d973c046bac1e4ae8b13d6065c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmWHE%1kq2zyK^j5fBCe7+Yw@&B_k0+ATKf^}h}3|NsBb$i&S2|NqGf28RFtPrER% z{QrOT0t46o|Hlt7@cH<LFz6Z>o0x(~AU1)R1tddAun}m<e~_&p%^;gWG{|-kO}-1b LY=G{uGvNXNDXcu6 diff --git a/lib/pytz/zoneinfo/America/Halifax b/lib/pytz/zoneinfo/America/Halifax deleted file mode 100644 index 756099abe6cee44295a5566ad6cd0c352fb82e64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3424 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 VBH;<`BBCOk@Px?7h{y;h=x;6=LMH$K diff --git a/lib/pytz/zoneinfo/America/Havana b/lib/pytz/zoneinfo/America/Havana deleted file mode 100644 index 8186060a4b2a81934eff93a2733b581bf60f299a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2428 zcmdtjZA_JA9LMpS#~5l+K1UKJiIp5Y&~X-u_vQl_Ab;X$3SwqF2t=5XkO;Y!lC6zi zlw>kqRL0yYjBH>vyO;PdNAsWLw9%M>`J&CO<&2EvbbYU_t*uwBUUl8O?$5pR{`Wn> zqRR3#=Wi$4{KDn5o6C3HF7tYS^Ow6m8hBm0>q^|y#pQZ>pujzok*#Mwukrem%A~(N z-}}06f}G2^?hU+iRlbS8;EisI($P?pdt=FRy)jbg{Wh4PW1V~4-%lLUn=M=1@m@zm zog<#pHmqSSC%o|bK8@Hq>_%?-UZMh1ylD5h+%hfOjY&KxF{6!MtkWW~KUKLCE>+6J zZ})hUyd1gp=oas`?zbhb>41BC!H;@Jd5<^Q->*|v?)Rn^zo^sZHhR-DN_9qbi8nKT zrOv#v)Vp(Rp2nZu;NCSDtFyW?-Gm*5a(8Q@n^+W(*|p*BJ<AGo&g#o<(wua?*LTvL zJM|S!o<8g)k9W$v(Q|s=&|bO!!V!JoShdXW*{3NTdE#qp(A4HsSx{f3{)!w;du*|$ zXQk+a?s^H#Ixh>;4$H!uJ+dgiUl&~&(1*r8)Q3-gq8Wp)>Ef<)vgEUEn%R0pmL3SI zTVAXymIq|TwO2JOX}V;6cSu%6+>liti#{?kC^_vllG{J3dAs-O>MvGne*GntH-3?V z#gpaH=PpWN{B;Sg`BZ{q7i4XqUDjT{rt1=VbzR?iT|fSo7QNe}#X~!F!%O?M<k&{t zSlXzMceuJK?@f84r9?KT?2sobmP+ZQ4N{ghTgt9xN=0&nRD6{vmC-*)<p<$Xb>);) z?@Q39&W>o!c1NG?I-#{|hIMOer#=(t(`~hT_1UTKX<dG`){P&R?TcQP?L!BpVMdiS z9BYy1f6bL09hK5}(I-2bbEK&^PMSBS$O~;hOISqszkMRZ|MEmd{&!C()P34<%-eG! zL!nb%SWGB%^sqDW&o{s1<^`Q>bC)eQw=ihd<2Yeq7AN=*b{8_IvSnT`vOi>l$PSS$ zTFoAjO<K(^k!@PdK9P+gJ4LpN>=oIp)$A78uGQ=p*)XzWWXs5&(QcX#cFi%{2KJ3? z9N9Utb!6|z=8@ea+qatiBMq>c4j?T+dVn+m=>pOQqz_0VkWL`2Kze~R1L+3R4x}GQ zL#(DFNK25OAWcEKg0uzc3(^>*Ge~QY-XP6Ex`VU_>5tVk2<Z^gBBV!1laMYUZ9@8l zGz#ey(ki4^NVAY`A?-r?Wi<^$I%YL3LwbfZ4e1)vHl%Mz<B-lFtwVZeHO)i1XEp6Z z`e!u_L^_DH5a}V(M5K#I8<9RDjYK+$v=Zs1)ie|7rq#3)>8I5+6zM3^QlzIyQ<1JB zZAJQuG#2SB(psdqNOO_yT1|VA{#s3gkq%o;i;*59O-8zmv>E9$(rBd9NUM=vBh5y- zZ8hyi`fW80M>=jbEk}BeG#%+W(srcpNaOMU-uYM){($)dn4g#KOY<#AT`)h-@Avu5 Hmp}Fofim_s diff --git a/lib/pytz/zoneinfo/America/Hermosillo b/lib/pytz/zoneinfo/America/Hermosillo deleted file mode 100644 index 26c269d9674847059472850a5287339df9213bff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 440 zcmWHE%1kq2zyNGO5fBCeejo<1MV4-RQLz3~-h*vvVF}xvFD2~YF-zF-x9!8}SA_{T zCT{t_z);X&nBF2_R4mqD?4BlI67-|NRNX_s%;;7FBNG@hGqQl;|Nql}Ffjc8KX(EH z%m4qY7cg-A|KH!hzyo443P8ktd_x#~gF_gcfj9t&eO*F8&Vhpv5}f`Y2tdvU(I78? zXplERG{`F;8sr@?4fGO-26+oaL%asEwx9t-g1iW#LEZ$>Ag_XGkas~e$jcy_M&9NE IhKV^B0F_3Lv;Y7A diff --git a/lib/pytz/zoneinfo/America/Indiana/Indianapolis b/lib/pytz/zoneinfo/America/Indiana/Indianapolis deleted file mode 100644 index 09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?<VS1QTNoUyPx&yV6)0S+okgc4ypV-t$Iy+nJQS{pbHzbl<;4ZMf*#|+Sq!z zuGlZuhgHiB8S!Gnr(9V)Gh7sRrpS`f!=mJJl-xAbElTT?b=l+3YI9Yj-qO;g%5#ER z9&S}zla#I~Z&2GJ?&$5=HEMfsP*%;Y7gYndW%bl*QQdw<)_ltqI~w=OoxLfdwys$2 zYKsze1(|a9F-MH>+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x<i+?D1o2{`HIJ>7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3R<GEZcp$V`!`B6CG1Yc;b)ri;uMnJ_YAWXi~#kx3)7My8F-8<{vV zb7bmh=gte0=a|_8(?{lyBw#feASqZ)4oDJKlLe9nk_VCqk_nOuk_(ayk`0m$k`I!Q z)ntUEWHmV<Nm)%+NLol<NMcB4NNPxKNODMaNP0+qNP<X)NQzdIBa)=mWQn9{HF+Y5 zBAFtoBDo^TBH1G8BKaZ-BN-zpTTRYL(pHl-lD5_4jU<j_j--y{jwFv{kN<J{q2?DM Y$^0V3_-Dr@#?6ZHCnUrr#LWu*39tolUH||9 diff --git a/lib/pytz/zoneinfo/America/Indiana/Knox b/lib/pytz/zoneinfo/America/Indiana/Knox deleted file mode 100644 index fcd408d74df43310a9a85c475f83d545f6d75911..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2428 zcmd_rQB2ik7{~Dkfe-{G6GJjEt(buXHk3Bl+S0K@BA5qIA&k@z%Y0QJQKQ$bL0&XV zn~G})i(IZ5T2rwtG^N&R&d?-CCBP(SA+N>-DV@{%eY?zBUH7p6`TTcudiDF_T~hY^ zO!>=&*l&2aJ@(-}THBBMeTjPSC%>tNnz6cZ&jt1M>pp#U+K@V15^B!potKU&r_Fb% zN2ODmO;=Q%boIPtzV{v07f!4<7rS@qOZ(qc-K|ynhpp>WPko{8E%U0r>I{9^GfVwg z9H*}oq?`WCbops^thpK=D_3t$G}r9^eyx4j{M_FszjU;jfiK$R`te>h*xaMd-c#zv zwv&2jX|1|7Tq?J(ddx_tM}Ge@!T4Gd#Q%PTk=+pzP&;Twy*wy^Yr|Dg$rv4+dtKf2 z#DES-{ziqo5wAldKT@Fw-jy)(wi?s3Lx*=AG!Z8%^w?wD&A9#BC9<yE+`YA2##iN= zd&=@<!s0X&<w=u?kH?sMr^iV2)Y)p%=n;t-HA%(XjMn${-d2;_Z|VC#yQE?dUDR=n z$JLa|aq_^HMm06>hD=-asd+H<oII4Z*E}3`SmGbqV&Z-6dV1J0Gw0DtHFwSeHTTz} zk~w3w$vjslo`@Xd`FN9L4WyW--r1$+b<9`Uo2&HvBgrbKs8Hwb9IqCnXXvLZhSb8z zaoU^Lp}ZpjIzP2V<zI=FMX}$SMW2f-_8l=xn);-$d$%citxcY3-DrxJ?~|qVMdsP; zle(m~N<BBDNiQocRLdi3^oq<3wPIkUE{%^<rKhuWSxA5?JCLYX^<P#m?DWWsXZ&V$ zWrDoa+-uh4M~K>X%B)Qtlyz&~GwY+;r97wBl=}vBWm=P}>^`G6MAxVdt%r2g@Jh9@ zeuv)FnWZ*YSLjz-5><6^fqr%OST!oZ{saa&c)jya@ZWrY=fCZ~4gQBe`&a*(-~ZuP zB7Xm|g8@N){|5~++P#On&qzLH!k^#I%l68XbL_LwJ_Yv4^~zlP&IPzn@cxJO`Rx@4 z`WlcGB1=Tph%6FWC9+JXT_>_oWTnVbk+mX=b=uV;%SG0UEEriavSeh<$fA)|Bg;nC zjVv5lIkI$Q?a1PtcJ;{eop$|50gwtHB|vI`6alFMQU;_BNFk6)Af-TRfvy<5Pz}zO zgQFfuK{zUclmw{>QWT^rPFohFE>2q*j>;gVL282(2dNHH9*+7T1>&d>QX-BTAw}Y- z5>h6PIw6JPsFc%|3aJ%RETmdUxsZAx1>>j~QZkO3Aw}b;8d5fnx;bs(kjf#YLu%)= z#p9@+)0U5;eok9JjtU|rL~4i>5vd|lMx>5NA(2WVr9^7!w8ccK>9pnKsHf8wl%t|Z zNjYkY6qTc@NLe}RiWC;9EK*vewn%Z2>N;(Ck@`AqfsqP3ZHbW@BSq$@GE!!aIwOVV zs5DY)j#?wd=BT#QmK&+J(-s`5xYL##sX0<~r0Pi7k-8&=$NyL5!|X4CS@xGfV)kQ6 RGn0}Nvr|%%Qj(Ix{s3RXO|k$0 diff --git a/lib/pytz/zoneinfo/America/Indiana/Marengo b/lib/pytz/zoneinfo/America/Indiana/Marengo deleted file mode 100644 index 1abf75e7e864625b975feebdd0b232d0de624b27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1722 zcmdVaUucbC0LSt7*lf0p5t>?4b|E$U$0SXWtu-8mgBj)-o6~G~Ff;$=T==^ZN}Hsa z$lo;oLt03qB-tYQvpvQ}7|+ZF$$35Br*PrQrT6W9KX2#aT>ZX}FRyq>s`J+sZhqn6 z@|%b6*noM}9!m%uy7o=hZR-;_eBhb9w<8#6ivJ>;3L^CLmYTqelY3-a<+#AB?9uXd z{*XZX@EF;VmF~RhKT5wH7U#VEJV?JY|Mu?TSN*=D&G~TdsqSpN?R?yOU4N=qf#8)` z?H+fPQxnvl?Jrf2wMvJ`pa>N|WX~KW!p67C@Z?(}eAi$Z5q(}poY|)%^)``_R4y|! zCW_4N6FO_eLY38ArL&_ZsO$@+dQxY+ntX7lobq_Q@NO)TQ!ft{)8>0+PIai5o}MIU ztmzOlWBW<pth>VZy<5&sJ0)hf_tm*^jVkwcm!2Cuq4JJ4>v=6zYW|i>dO<^}$}g<Z z3u~t;;k_sewwH)SBdX<MzgHASRmh^OBvI6vC;h|v3IF|cSsd0aiqFK!r5%q%N%dG= z`k+fKE05L7>zmbzSwSs#Us5a6lwP%>My(!rOP5Vsr^<q@vV2UnC~vtbD`F}{MZ*!f z?rWY{U%ORqc#<wQR{G_pD{-PSKU;3z?}#2AbBBal`22PE4Eue1et-S>?O(rKw?4{o zT=(i(PpIpju5)_X@80$u&D$B^x_54PVy1X~&cqD!%rws&^W^xPO!J*-e&h1kH~9Wx zg08vpLxOe46p=Y1lSF2TOcR->)l3wbDKb@LuE=DO*&@?L=8H@inX%PO8JV-yOd6Rr zGHqnu$i$JEqn$b*%$>_j9+*8cePsSf0!Ri(3P=uClLV55)ue&sfh2-tVl}BCxmZmy zNH$iJ4w4U&5Rws+5|R^=6p|H^7Lpf|7?PRQq=w{XHOV2_SxtIKen^5yhDeG?j!2S7 zmPndNo=Bodrbwztu2z#QlC9OGi{xuH2_qRJDI+-}Nh4V!X(M?fi6faKsUx{tP4Y<g zR+B!GzttQ8$T5H%1;}xL90|y=fE*3@pC1pA=F~_tr$&NzWMXP!a)LJ{B{3y2Ir1lW C{a*3_ diff --git a/lib/pytz/zoneinfo/America/Indiana/Petersburg b/lib/pytz/zoneinfo/America/Indiana/Petersburg deleted file mode 100644 index 0133548ecac014f4b37f0abcd471a5a6b4e7ed5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1904 zcmdtiUue~39LMqJxPNZ$m`24e#xC+=i2JJzA{H5Kj8h&r^JungJ~QpOHrv;lYnJKn zqFf0frA&#K3oWED?T@7qVkRa;l#ap*>__Ap*4#5^wY1K!@B6uk#4G8p=kPnP-!^vl zyg#Ytwufh%t4^N&hKJLo5AVf+e)Ydz7VP}+4;k1rHF)*q@8q-RGQp`C7v)rIzWXtK zdw6zckqkA|nxC$}#SNF1nBfaIxpVs8=T1(zpND=hzr6je8##W=oPY5nH@Ytj+|EyA zY|GK$!p7HRymNPOaaphY+PEqB?T$A2y>eA>X>!_Knn;_=!wL82>4f>~#4MLNILkzP z;?C@dn^^CtoAkt}$y!q=*{xH8oTl@VJ9i|=tNK#%UMLOnr@bTjdv}=vw@s#mTZ6)H z_Ph9zwZYUwFS)wPZmF+ZAob%Pn1=Gzu3>PqOT_OC6YqAoyGLe(_q_7F%=>z-O|Ea3 z`S0Is8<!@fsW00usI8QH*ACf*#nUCV=!8vO9+$;6Z`j3WuX9UEj)zN*jk)_Xd&1`3 zgYJPdo5Q8g9B>aF?g(2}Y<J80=Z7}=p0xJ1+vVkb@=!WySHyc{WkaQ1InpfY(&;w+ zd9AeNj@!1qCGyD7SGK+HHrMgRSh%{o*gd*$Fzj5EakAw=xTYp_k9BSjAHVUa>uOvV zc4fYo?pyn8_nD8Sr>MvF9Ns1CCYtS&{m;r%r)%x{4QYA$V2Rz(l8}u%jGYwIPgb^v z*MEO<uK4MnzvSp!PVRsBMA2L2#zfJf{juyQdUL=`_V0!Mdm7*7hA8^<NUUgHGG*?n z(`TVR_vzD=GWGgiufOBO{5Pbo;SNW7TCy?&$X!P6GjgYqdyU*}<bHegjwAORx$DS% zNA5gw@4b5Wk^Aq}29OSr7LXp0CXg<WHjqA$MvzXBR*+thW{_@>cD&jT(h$-S(h~Zf zc%dndcEzDBq%RJQA)O(uA-y5Zd9^#FJ+Jo1p+TfWq(!7hq)DVpq)o5($)QoNc8av> z)n1Wik#3Q8k$#bek&cm;k)DyJk*<-pz1la@xK}$zTK8)2Nb^YdNc%|t$Oa%gfNTM> z2goKMyMSy1vJc2ccy%X`t?=qzAe-UU-9WYj*$-qxkR3s`1lbd0Q;=OjwguT2WMjO# zGsxC>b#IW(@#^j%+k@;6vO&lWAzOs(5&oYxsX(WyMyIMQIj3TFMO9g{y1JseqN?C; Daf7s4 diff --git a/lib/pytz/zoneinfo/America/Indiana/Tell_City b/lib/pytz/zoneinfo/America/Indiana/Tell_City deleted file mode 100644 index 4ce95c152a3d8974fdc67072f944eae97b5997ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1726 zcmdtiOGs5g7{Kwly_JlJ3Kyb_782b|Ev1D*kRn=Fd)(C2?4_xdIhIO3vRQ<-a}n5s zxEYucQ5Jz&3k$PaL_{SL1#;E0(9B5<4C0>VKM;hi-1c40`Hcsc-+#P0C5K|gKNDoW z@Gynu;T)#S%h%>sS04V9Ee%W5)k`1bi?adsX4RB@vp-0`t(+9Sn?|+Ym#YR!ymGKU zQ4OWW%a28g{!gnz<#0-Z_`Ga`{t_83zD%stBMXHXc|EO1hX=*iyYKbbvmWv7(SRIp z_bXlQ)8CuA)x_y*a<Z&dP3>)zKQbCrpfgN6*B!q&AJDGG0e{lEDw&+LT_#W77b$B? zb;{d|A~hsKr{1d-X=5>Z%jNTGYhSwb)K#nWj%b;Ym#ellFO%C764j2%DY-K&LV0)f zN$)S0%8cujnV-T%c6hJOek#Q7K!?t09Td6!7j)j49<isZM&}o_iM_r&z0cFF_BU3` z18ZB=!J=GQ5YnIuQxaw2Sg|UKjF3ey(^Sbqmn^yER)>d&<dNo7arBj=ORLt1W9<XF zEHgyNrYE{QZd@EMyQWX9cr7Y28g)frOjT`cl~w(()XC5Wd9v%Ss-7;EHNJDI_FbB+ ztFKh2I^D89KTDmyutd&rncv)b7LGH2^B0)k%#XAE+i`B+5W;aD+;PowoSQ9Tkv%WA z@1?xOa>sf8*cJNs|7`Vm#pWdQ>@-i7dA4~)vN<Q4Z;Z>n{~WxtSVYH6{@g$dvXID9 zB8!PEC$gZ(l3LB8BFl;_EV8u7;v&n7EHJXf$RZ=lj4U*=)K;_D$Z}iFf+I_gEIP96 z$igE_k1Rg2{73*u1V{)-3}}PkKopD#1Be3&1c}6ILScx-YJy>i#%jW0hzAJ>i3kY^ zi3te`i3$k|i3<q~iOgz3Lt?X<;25H_n(!FnLjq)o5DAeXMkGjvD3LIcIFUe+NRd#H zSgj^lBwDKp7m3$u0!AW6LS~2=37R2lBy5Jbk-!-uM?z<a-D-kIqPLpxk@&6V0zfVS r<RU;W1LQ(LE(PRb!2jxU2sU>}ytzx<p7l|&QPFNsOiWZvRCMrfU)O#s diff --git a/lib/pytz/zoneinfo/America/Indiana/Vevay b/lib/pytz/zoneinfo/America/Indiana/Vevay deleted file mode 100644 index d236b7c07726828984d3afbfa872b3abeae3e809..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1414 zcmdVZPe@cj0LSsSx>`wALV<_HLms;5`X_`4149?FT>s>1wwaElEvDsEwykBhhsyHO zZH)*sijpD{%CHVn5fwxPN)ZG$1$h%j>`+Aen(tgY1znoQywBsEzrRqpYk#rx$603n za5?qn^6iV8*XYf>_|?Zh<k-1Y@oSeq%Gc+U@tNFja;ANS{xY~Fo}BV&_m-=ik)-wv zeO4*Ql}?pO;T6ARTAdOV1yLEOC>MdHab20;rYdKLbyen8Rdv5dZ(GP!+pk=Z)$hwh zaG+b(JX|koTY|DK;T83z1#-u+dC}m@lA*>|BJ^`fHkI5KO<z~*=A0L*`T3&Wl^j>$ z8?$=%+^}jnGpYAX_o&v65#2UgqeSq5Y#-_td-D@=UnD3xGGnr{vOsh$gk@wymWaG5 zm0jseqU&ysJTU)GbSF0Jp0|tYVAQ7%-J4atjY%ypPO8HtN+0PRQAgK3(|xu5sxSFT zMmHrybndB)WyeHp`nv4@9u_A?&&rb@O2xonM4p<;5reH&^7JJ~q<GAe>P<;Y|3{DO zPLw&0>pq(BcwP6-n6un|ue9$qyq&eK`|^n=yE+(h$}7xmFn6c9bs;BUz60hT$7A0R z`im%Bb6QiAV@MoGAV?%gC`c?wFjf-{5)RsU_#hyTi3kV@i3te`i3$k|i3<tLY9d2I zvzpkD;H)M(Bs?TOBtRrWBt#@eBuFGmBupeuBv7k~6baR8Vnu?rnrM-5k$91Sk%*Cy zk(iO7k*JZdk+_k-k;swIttNIPc&mvX3EyhsM+Sh502u-@24oP(D3D<w<3I+2j072q z)r<uhjMa<=8IIMA2N@7DB4kL&n2<psqr(4YSQ%z-C1!7V!Hxc6e_>v*sK{UBFU<H2 D;2*&? diff --git a/lib/pytz/zoneinfo/America/Indiana/Vincennes b/lib/pytz/zoneinfo/America/Indiana/Vincennes deleted file mode 100644 index c818929d19b2ca5925c181696f02a2ba93a7009a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1694 zcmdVaT}+K}0LSs?D6jLDVO_K?xG-CKBQvXwskP|H%kfspKjbWx{*jiqdM+s3xGOU| zGOuF|BjO@2v)IgxFk{O<6hr)T*qH4+{@?#4SKRqOd!Em;b8)VI-zSh?x*^s1;|w*w zaC1t`&3l|{9-)&7?vZOhWJ|+n_voP)^4{Kv`)Kl4`KWlf?%Eo59?$upRj9!2DGkV1 zbs6sKoSE`XFhliDjg@`b>CW46GxWPD@y@$16ZL!ZukRmz*B|;`IUg@P*ZrNholn<$ z<mYqB)m0%qaNw5vrT&BrR~~o2u4$Ct@*3Podz|)8dCF;yXkTNG%1o+}S(%GvR``mO zJ*`4#ckS0Xqvoodi{*NL|75k`@IIOQbdK=vERzc_PZD{Bez~YAS}aaal1sMsiKTHd zGO+Bf2>cAo<!NWc^4>9eWqhYvd3!*wikw#Y$Gdbv_kLBlyG^g|C|5=6oAsK|LM8l{ zWN~AeSUasrt_%9b`cbvABs)oz^ykaqlo%1bpDs&>ghgp<yxiFLM3gnn*5wZdR7Fjk z-gK@@RW6HYd7w>Ir769+vRQ4Ja7$O`ZBx~em$GJ7lc?#wDQjbEMQz71x$S$t*dE#= zcRWiMJL`gSS9`puE6S1e2OTlUXRfGd3$MRlgNK;Q{AlI<UoOvU9dI1ayL#Ri?RjTg zoMHBTq<xO%>5TWhJ2!l>x&DANFVoyh&Ar0hivmuTdCxMxaeVd(fxj4@XHHR6qy-ru zGD2jC$QY49BBQjLVIt#128xUn87eYXWU$C+k>MiaMFxzF*lLE1jM-`ijf@%@HZpEx z;K<04p(A5Q29Jy$89p+8v;pu!1RN6r5Cakf5{1=-!66Q-2?UA6YC=I`L4rY|LBc`e zK>|V|LPA1fLV`k~vYN1vxU42HBr>ZB4T%j24v7v44~Y*65Qz{85s48A5{VKC6N%Gm z0!1RVnoyBgttMC`S|nT~UL;^7VkBfFW+Z4NY9wqVZmS6#iQH;JM`E{{;F0K&@R9hD n4*>EJKt2TcUw;fnm~TXy`9>u8rzfT+CMWn)QW8@VlSljl+LDCq diff --git a/lib/pytz/zoneinfo/America/Indiana/Winamac b/lib/pytz/zoneinfo/America/Indiana/Winamac deleted file mode 100644 index 630935c1e1a8c1a87e728fc1dcc4c930e81b30e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1778 zcmdtiNo-9~7{KxKsHrhEK`fd_SlCpnq=-BT(WWV$@zqe&F*eoW&>^j%?!vsmrbVPG z#28AbBGMp&L`0~X)iIT*a;g?Y@~-ngTQ)46n|ppYZ}k@6|BGbhE*&a<JDuz=yqrAy z@*VHltMYJyxBt=)RkgXhcVO=`b$e^nyFc)oy1$@{sV{0155_c`hRg=>u>T$ND5X|B z7S-lS?>*v)-esOfYrJPy3e5Ay3h%|SovN{})O)#YwbGSyyjQsq^}1}d_a<YwdYj$P zdp9#=n$kn!{fMCXFeoTKeC=aC+JEu!-gnd7^jds6^VGE5xGp~Lx@^8wYcF~s-uOp+ zEh?fWu;rCbO)6GtsZ&*2TdhbRJYT2RZ#Nm;#_Ei-`DT2}Ks{mKHZ}3#7#UudrzV{r zAScfat10C%a%w0^O)F}W)8pe*B=eSx{A^P*QjW_R?|PV7!5ezk^;R=GdQ@i}syA~Q zx9hnZYs|d5e4V|x!pyInq-FS=TCgQgE*xC07UhKH;%+5sNqUl8(vqce2F1yoJ0X?Z zu}$Wl460>K4KlBMl*zx_s+SkXn-$gddSzzRs2w$WRf;yNS61kP-q%dw<WgN2eW{8^ zmdoPCtE!}Li7cr*s7k+Q$u*Um)Y``(xvnfnt-lbIW!V{O!)_tl1?<y4#=+;GpAH@E z6KKc%?~m`Fx+{e5UpyU%@%<B3qO<$%>h7N0qL=UAyb|a;F&q)&Qtdn4zBBDRB_h)7 zcbff;6L2>~{$ebBd$QX{tB~<TMid!SWK@xHMMl<X#}*k~WPFhkM#dN!Wn`R@k#^d# zMn)SMZ)C)gF-Jxn8Fyslk+DZc9~pln0VD$?1tbR~38&2hNyBOLKoUVRK~h0-L6Sk2 z4IiY#vH1WAAsHblIc-iHl5*OtkhGjOFC;M}GbA-6HzYYEJ0v|MKO{jULnK9~%@Ik` zX|qJqblN<TM3GF9RFPbfWRYx<bdh|KgprJql#!f~q@6ZvByFe78%f-0Ge=TKaz~O! zvPaTK@<&zxvIdY<fUE;#B{=O`Kvsj(t_NgAIPIE1Rt2&ykd=X~4P<p7>jVFT6%uPV ZON!kr3E^RhLlcK2gp-pKlM{!;{sIlvy2bzi diff --git a/lib/pytz/zoneinfo/America/Indianapolis b/lib/pytz/zoneinfo/America/Indianapolis deleted file mode 100644 index 09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?<VS1QTNoUyPx&yV6)0S+okgc4ypV-t$Iy+nJQS{pbHzbl<;4ZMf*#|+Sq!z zuGlZuhgHiB8S!Gnr(9V)Gh7sRrpS`f!=mJJl-xAbElTT?b=l+3YI9Yj-qO;g%5#ER z9&S}zla#I~Z&2GJ?&$5=HEMfsP*%;Y7gYndW%bl*QQdw<)_ltqI~w=OoxLfdwys$2 zYKsze1(|a9F-MH>+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x<i+?D1o2{`HIJ>7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3R<GEZcp$V`!`B6CG1Yc;b)ri;uMnJ_YAWXi~#kx3)7My8F-8<{vV zb7bmh=gte0=a|_8(?{lyBw#feASqZ)4oDJKlLe9nk_VCqk_nOuk_(ayk`0m$k`I!Q z)ntUEWHmV<Nm)%+NLol<NMcB4NNPxKNODMaNP0+qNP<X)NQzdIBa)=mWQn9{HF+Y5 zBAFtoBDo^TBH1G8BKaZ-BN-zpTTRYL(pHl-lD5_4jU<j_j--y{jwFv{kN<J{q2?DM Y$^0V3_-Dr@#?6ZHCnUrr#LWu*39tolUH||9 diff --git a/lib/pytz/zoneinfo/America/Inuvik b/lib/pytz/zoneinfo/America/Inuvik deleted file mode 100644 index e107dc44c34fa633deb2f431ebdbb66a31168488..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1914 zcmdVae@xVM9LMpuWE!{7k1Nasm8?7=?l@2q$X^w7A)OGOghp5fUZ)kN?na6DZqgWk z@m?|3X0lv0|7fMDYi({c*YK}w4!72ih5c@`Y+8TFW^sL<-+%g}zxu27zTF<T`}^~J zB{!$HRmeZ?djAh!uG@e4o&WWW^?J4JDV<z8By$F==%#dsyLnSw^6MXPbE}Kxmg1#u z-r_pFH8<qu=Y)0sZ%eG;+gvRizrhxq{#h5kchMFdKC4BU1@5*NkLu!$S$F&PkL8Za zZ`_^h-jyZ!AGvUeNqFjl6^C|7@x*B>xtNr@M&Gc~^E)*%@{&bQG;3LJ%3`l9*YZ@K ztJpnPm&Lca<;{~?8Gg{+UGaskm|5bg3a(1kPdB>iYoANanP1&KU!Ibc$3nL1)FD|t z_=~MMxL?+GePZ|a9n||8kJ<el`?R*~Wvi>*qIJKIxp<^U;^&XJ`n-0jKQZ9eU0N#* zuf1gtecGmpo_<^Z{%UP(OIy=$kv_b#!J0EUx}h*)$&KG@@{ifpQh7#OekgUV3%`@r zu~}}@)R=63^Rjz%;)t~Mjl0K22c*5@q}wvGLmp3@vnTrdb!+6fJ()^tN8XTi#v8Qr zQpQr@h^EfAT6*SeO}|rP+kd>GJBA-|PoJA5&t$6IvqvvWS4)B0IXEuQRsQLAb)A&m z`4`-t#$nkrb<HwW$2BwYm37Y>((cg@?fJ=!zA$pw_I}o?J-r8P-v>3?+umvWUoF&} zX*t(T{|`PqGLdZd?boMG&t?Zza<kb(CV9a>b0V>rG}YE7rD;P_5`6vdzc2YO9&pwl zj~IE#$YbVs(8!}k9yZ70MjklFBS#)O^4O6FALt)F^6-(zj|6~3fP{d=fCPa=frP;k z2NDQJBuFTMJ{BaHKpzbf4o5slKuAPLNJvabP)JlrSV&w*U`S+0Xh>{GaDhHLBs`Az zkN`O%L_*|<5ebqbN+e7qP9#tyQY6$sA1e}UppO;_H_*q61k4dJ5;77q5;PJu5;hVy z5;zh$5_+JI9SJ_rM~{Rb=;KEQfMW!ZA>bGTWDqz;0T~94aX<zF83|-4kg-4pBhZfq zG8}<^Jdgni^do`{2{I<gpm2-|GAtb9f(#7D$RI<*F*eBH1p3iIh9}UE4>CZ3euR)A pLdFOgBxID3VM4|U|F;7b@|(57Z&qnyS+pV=D@~M_N6VwJ&|mGSy~+Rp diff --git a/lib/pytz/zoneinfo/America/Iqaluit b/lib/pytz/zoneinfo/America/Iqaluit deleted file mode 100644 index c8138bdbb3cf141ad42ace037e144bee98d81481..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2032 zcmd_qZ)jC@9LMo<*HXDUV$^EtT)9>2&h6g2+0|*wv~66jcHPoD(`EYAW|!+$`=xf- z%BF!K`9#E+K{1m4AVthS8AUMi4;ErM9Y$2FUqO2iEq=BjX>d=!_b>EGPuAnkIOlaZ zJ8VzR`;%(RJXP!c>j>H>oE$y&<bLjj&cnMem;a$I%Y^<jvZUqOeVL(KGjVg<x=LM? z2$|a#&Cqv*rkmojkj$JmW@hD0ms!6D)$AX~q~y}CYR;*PQhMwwb?4z@a#z+fb6+|r zWvxG%@~s2<?$}v#&+-HM-lBI+Mdfx~kvpS8!B!m_c}G?LvQW<(+^edFQ&QcZQQ;E{ zB+|P=MGqEBO*(CAxBVsa;|X*Bs_!LMG0!Zh9h8Mr3ryYYZ*|>h&RaBb-Yh<M$vp5~ zzpfwnO+EPOZoOpxkb3C#&3b9qakXsEE_t~5Rn^eGNg5+r)zsJ^P2)#Qyn3UK5AQRH zf@L~!qSGuNE7vRDctt((^-@Xh>`*H|DwF2*N!8MqFOSwo)T-x3WpzoRO0|9=sfls5 zCib4J`S}*Jw)C7{d-keXmph`{-X1oOkL=Uy_nb6O40h@b?T5_9{*_viAF54zJ7jZp zpL#N#l=gydwIv>rEn{seT~R3MFY8ri%D7}cC{<fWuSiGVV$*r?s(w0KVxAco)?KYR zqxPTF&&IUb)^$j4FFI{@H23HoxgS)vu1~Tfr&M=ww{#D_rFyQl$@Bd$s~5hhmz}+7 z_2S2+(z_v{cD?4w&4Ij|^71G5GiB=J-ka20d}*HN`=5Un$oKtEj(XF4|H@EchI>-z zu0_88{+r%RejAu`{W)(|N26X-OUi3nZO_I=F7|vZ<u%v`4ffs({PXAS^~PQ5KYaPV zJ^1>O0-Ux2qy(e}{tHDQRUl;`bs&Wxl^~@cwK#1tNHs_~NIghFNJU6VNKHslNL5H# zNL@%_NM%l28d96n7Kc=al!w%Z6o^!al!(-b6p2)cl!?@d6pB=el#0~qw8e5zEmAI0 zFH$g4F;X&8Gg35CHBvTGH&QrKxzm=8)b6y!Bh@=?`AGf90w61ZECI3x$RZ%CfGh*D z4#+|vE8(<DfvknoE(Wq1PP-h)dLRpetO&9s$eJLFf~*R%EXcYb3xli-vNXurIPKyf ztK+oGgRGC!E)cRp$PyuIge(%WO2{%H>x3*6vQo%WA#3Hdi-oL~(=HdXUQWAU$ciCL nhO8N~XvnG|%ZC52bqm@pjM*8iO3n}0hND%<nwoG;I2!yL>(@dH diff --git a/lib/pytz/zoneinfo/America/Jamaica b/lib/pytz/zoneinfo/America/Jamaica deleted file mode 100644 index 162306f88a80d69cfa5053103c285e2e8b221c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 498 zcmb`Dy-vbV7(g$T1ZXQ1e^3~8DkF&lJ4B5z(M6{&V!Cx@F(&>H1}9lu4U2;b0|RVq zK7-qR0h9X#UcUpQE+&4>cTdydrsqT#Nxz|fOjf?IOhuOW;6{$8((EhuSWOGTBrd#- zjcXoaPv58h$BW)vUZuswoi4rJn&7#w%cD!PH8|1R$+6ivuj}2@&{Uef-U~gme-Osi z{HLioUYv0@etE2&J4&t2thI}&%3J%s%=n#dq|Rj9J=s<y|FoXy4<=S786I9kjJN?S vh}nu_2Qh?LLQEmH5Mzin#2jJ|DFCSeDFLYgDFUeiDFdkk|F4iM$&TD_ya{zn diff --git a/lib/pytz/zoneinfo/America/Jujuy b/lib/pytz/zoneinfo/America/Jujuy deleted file mode 100644 index 7d2ba91c679aca41eb44aec6115745ab107531e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1072 zcmc)IKWLLd9Eb5YHqjV51QZkzDcDjQ5stPoqE${h6vQkwQ0U|!f*>dkRuGLY4k833 z2trGvT}98K8ro{l#+urp1X2;vA*i@nL}G~ldj6i<#6>6Hm&@lRyu%CMCo_EFcz5u} zwO0P&aP`UIdE+JdxqUsS9-JT454Suw#cerVTt2O)HeIk&lM`mTd0)@mc&Q$}dT$?} zi<#N!&Dsv<%#*P(J-4r<N*62k>DGpsKN{Bas|i&;n6l;CJ+*K-uNTv|%;L%wyOefn z$$YfcuB2J+KW6LuqpCjMuwNo4Riitu8z06^b3?yvmaFP(eMEn&?l#{`N&92wi28YF zP_K?AmAlrf*G(KU(e_h??fFU59?us#QlCx7r?Z95#E|WL``YYSxvIO&-a?{QRXcAE zwWS)*%%0hV85mh_2Hs@bvd+2CnwG!3BVlfN8C&E;oSRz+0_RGVAnaV(22tl;R6?!J zy{m=dJO{0Z)0v=O&Mws}r$^53OJx3G8~I#!X7p|m*$CMQ*$UaqE1My^d1X6fKV(B> zM`TN6Ph?YMS7cjcUu0usXRmCH?Cq7!k=>E)y|O>jfLA&|T0nY0n(#^&NE=8WUTFmB z#4D{Jy&%mX-5~8C{h)7%2kA(X4`>PL326%H3TewLeIbo`r8A^8q&K8Fq&uWNq(7uV UuXKo6`IP_NVysUJJ<t>T1rW#RmjD0& diff --git a/lib/pytz/zoneinfo/America/Juneau b/lib/pytz/zoneinfo/America/Juneau deleted file mode 100644 index 451f3490096338f40e601628ac70f04112ace51d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2353 zcmciCZA{fw0LSrr5xCqd)GZ+qiH=Voj~=e^RFt6@Arct3n-Ezfc88UqFovQer6aeB zt8ETv(Q%Hgw$wA-O4q{DxwcG3u=Ow(rCYO{y4KTbB)9WD*m@Cf^M7~#pR@n&+uzq; z*Yu3f@t?<SzHoSY&EcN9-Mr53N>U^9er*|PNcBz}FB9RnGrW$zbm4qC)*I0=T}<1! zFcjI4rlMAPLeV8|<&sxIu2+{Sw|6MyK6Fxee$t`o-yKo0U!U{FeY9KMa^i^h)^`pI z@4nsM+jfP-?VDS@GnzJu_}aB1q1-R-C@S#IOwSjIb8AAg+=(J7A}^Gb8ShQ{d8wX# zae+!6nXm6W<x(j}qxD?}epI5pNY3d#tL6q~%X#a65cBh{%LOYw6{!i|%CwAkMcTx; zPWQYf(#O8h8JAl`=HN-4HTsIm?mwV&j%`qjx?a-q^=g$HRMOY#Q;W-9ly}#}tGu*E zneY2u6}TRhg~>BS;iZMLX#6*E&nF&v@A)r9u};<ZeQ-kDABxdS_U{)DG+)*wJKk4I zt3K1q8uzKvMThnB(oVJfmkY8iyHAvjo|fftTSWP>0lDI8y{LHOW4-c=4pq7Jpnmwh zI#pG_Q&;aTR;!AG`jPe&Rg+w<{Q-ya|COl&c^6gS`-kM}l(1NRCPUUvTo82wvGVb; z)1rRIHTlHgfLPNwET8P}5l>ZK(G5KZ)zjHSdTns0YK+r*U0G1AyShgQ)5=xwT$^rk zWvZq_0lofGoO*U|t9))aLp<O9sNA4qMRTA)w)9>T8}nz$P0hoiHDO$~RUH*=;hDO< za7eX}{i!<=wdxo=uQ&g(M{Vi<P<MXSrnYvyrMDdqsIE2L`h~ti<&2o-jGXcpHTg9< z#&r{a4##Mx!x0YmZg)h6!*7P1G4>s6-(J2u<HO<Z7DuAJC)@j+m6d)+b&WZXnNw{} zsozn{G2com%%!Bl+|1+T#WQ*FPdze^`2&Y1WDfV#uz8l8y8bzC>+zjIMu!Z~YQ~3l zfLs_Mj~OB`Mr4r4D3M_z<3t9EjMQp|ij38228)aq87?wjWWdOXks%{vMh1<H8W}b+ zZe-xd$dREVV@C#$jNWR7Z#Cmb0)RvS2>}uVBnU_pkT4)|Kmvh80tp2Y3nUm;6AdIB zRuc~-AV@@zkRUNZf`UW^2@4VzBrr&1kkBBpL4t!s2MLeW#0Low5+Ni+NQ{smAyGoY zgv1F66cQ;UR7k9lU|CJHkZ@T|ypVudO~jCpAu&UOhC~es8xl7pa7g5k&>^uyf@d|+ zL&9e@@k0V=H4#KYh{O;HA`(R;j7S`jKq8SuLW#r@2__OvB%D?gPb8pL6Hz3jRufYs zs7O?iup)6q0*gcz2`v&^B)CX)k?>kge31ZKO@xsUTTP6SAR|#m!i>Zj2{aOEyb<ci yh^Q%l(UV_Y?wFhS`=`|1<Nsr?{|)wyn}?DY(e$j!#W}tlnN^vao0FR(J%0nEhFEz3 diff --git a/lib/pytz/zoneinfo/America/Kentucky/Louisville b/lib/pytz/zoneinfo/America/Kentucky/Louisville deleted file mode 100644 index f4c4cf966fa6a0130b9ee5fbd01fe0790fe8f001..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2772 zcmeIze@s<n9LMp4q6msg`2!WNr4>VwUxvTJ!jRoiubN&=(eTmKJJj-<7(p4{=C5#T z+H_FXNJ~v?j?`>gjntSqTbVMQh2aAwm5DxP>z8?N&-<*LTYvRWe|7H8c|AM--0g1n z{qYwR&ChWDaYflT++2I?%{d`xAJyBFgRj2#n`~a)Ik;t0gKRDS#o5*)PL9P+2p&7* zl~0}v1y2t6Nlwm=HYXdZRQ=XyIhB9PX*gJH&NfyBzuLK88f(jf=PH(nuAUKWD)LKn z`KaL6c|+x!!hXT?N%7L+&2qjS<}u$TdYtcjI_84?_6t8Zn;%-5or|aIOzV+4=hBBw za=Atajc2|5ab~2sezdO+?N2b_H^SPf3YmyCO}fpK+O(CBid5I7-6XBrXP=X(w8JXu z`Cby;b&o{v+Gt|TYSmG%l1^W|rehBVr1OE5y35WLrt3@dbzH?fb4%F+`qm<^={9|! z?w*%qdW`6#dnR=>@%@ujuMVxIcbkr?_m45sr>RvXoVX}`4;@#Y-JeOnExXliC62lM z*$t|HezO^{phn-3S!eDX`+~l!`*xE!uvjOCK9Z!Ee4W&|O$J^VrU%uok>q;68eCl@ zDSL;i)beqXwjo}n2LdKz$?s~2H_zNX`K-!J9At)O)T&`ov1WLmFZJ-2%Vxy25A?_* zC(Jz!)jF%@l8kyksPA3(x!m{i9G$&*kBnZqT-`r$qvXt;t;VF5NbZ;%m3wuE@eZD) zz4dENUhHU{w|9{l+d4pxd!<6gpUzgkWea7(yZzO~nLe3R8L1x3N|nh^w5TZwapKQE zs{Gfk%GAs^)ztG{%rwstJ+1DFnI77q3%1sq8I5c8%$57h!?lZa;oK@St9pV~zISBy znuTi4;7WNU;8SyBOJ$xnRn2QHkU(Ob3VfO+MeVPuqFo+Y&~i#GtQ=_;9lxR<ElDts z)zs_7`5`0g_UXqnwOO*bN*8xOY?e+e(@R6=q-1!dDrr0<rSYYzwDxr=`?WwlRlQ1{ zK9i-El?UXR{T@|bm?z6$bX1#&zdmgvZyf)Ab;EY;Z~8>sbiVohe{~LrYc@GfIDFvk zh{$mGt<6pc_uR>ScID}G3x_{G7!g0-=XY|(*n5h-AF}r(zmsdvx%M4bg!=^lzxd~e z?N!(|v>7P?QURm{NDYu8AXPxh;A-oD6auM)t1X4Atp!pHq#8&$ka{2mK`Mfj1gQy9 z6r?IhS&+IQg+VHVlm@8{QXHf@uC_c#eUJhn6+%jc)CegOQYEBJ=+=n?h2pZ60!oF{ z3Mm#+Eu>sXy^w;r+KM41bG0=?iiT7TDH~EZq;N>(kkTQwLyCt~4=Eo~Kcs+21(6aW zHAIT&YO9Ep5ve0mNTiZTDUn(t#YC!!loP2ZQc$F#NJ(97O_8Fy+NvUDMe2$a7O5;! zTBNo}agpjG<wfd?6d0*6QevdWNReG_m60+dbw&z}R2nHYQfs8xNVSo2BlSiKj#L~e zxvQ-?Qgl~ab)@XBw(dybk;)^bM{180AE`c4ex&}$0w61ZEP<<C17s0g?J6M4;A+<a zSqNk$kflJ@0$B`XHIU^%)&p4(WJQoALDmFW6j!?{$g;TFbwL)!)vgS(G|1W@i-W8V zvOLK8APa=75VAzb8X=41YF7zaCRe*o$U?c=l|q&ZSu13*kkvw#3t2Dxf5G?_Sg;s7 ZY?*f0l6^x`GE&l#ed*~b=_zS3e*(C<z9j$v diff --git a/lib/pytz/zoneinfo/America/Kentucky/Monticello b/lib/pytz/zoneinfo/America/Kentucky/Monticello deleted file mode 100644 index 438e3eab4a6e581c6f5e7661098bf07b1a000593..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2352 zcmd_qUrg0y9LMp8Kk`oyC@Dmuf|4QsF%pRi1I93qkRAxg@BuM~P&_6gl;O`!Gu@h- z655|3%+{>tB#EZANLw@4pCz)I&7xlyvF3(8L>CoyPS5+f?W)#Y-?QI&{m!}nyuJ0U zPvnWeU8wnohwC#B?}?828h9haIr{$ZYVi35&d@7Y)aNhwoJ%Rcs!NUG`o`r7@#lqf z?K_<={KuoTI1(iS_FmV4+pmkDEs-j?alSLJ>Y55Eo_0cWE~?NMQ=RabQ!4z#F%hBr z)O`1l6Z!41jyk)|S#ai{j(+o?h<UkF$L{MCH}%x$n_Fwd!ny(-S5Y8tS&^V`O-m5* z$;;KEup1&FaFI&*CEi*5<D5zy|HHZM-1jQ!o$s96hdxqwbVZ9hcMYlJ>KT!;`9pnI z{)D)D)lt19Zd9Z$-KA4~<4#({Mx8b};w-&drqjn>axx~GROY~TCu^isW%t%OIfs_0 z+?Fnpw>3;HE3Xyzti7u8Qwzntd0(jIp^2hk@z1(o$|qLLeWnY~kL&xc47f!nuc`Y# zaNGwDTvQLf+T<2@oKh=y@01T!_NkKQMp>HOrOHZ6WZCR-U7oqYEuYx0E237q6(j9> z)pUyc@N4_j>TimrXGfb_^Io#7T<=j;{lW5)qHMKx+mu|F7^}S17o>M?R@LOcC2M|) z*0o9J-P%uPbe-?GTmSlmer$5TyMFhmetfLmZD>BMHw>(CrRQDMxVKF<W%jF$Egsn% z)vY#_XUk2~^{OQ`R<?Xuq*_B}W$TF~wR!4m+16jE+rOG|pX^H1Po11_JF0zJ9T;`D z<h%Omj>GQOxQ}&bWslqG`$2UT^vkZvbE-SOTXv5fQ$4@c%V!4msAn%1$sN5d>bcWN zvbUi^?K~o6V1W4q1zULi*PoDi|JldyAMO;w?>{pf5bXEAJt)HLd!+r2@%ukL8?caf z5x?5w6(yzSS!bR{%~RzSW#)I8`OO8`Z}9$ujrq+r1o;M$ts#3uHizsE*&eb#tJxs3 zLu89+_s9#I<jgKPwu$VMW24AUk*y+oMK)_SyG6EZHT&h*FtTH0%gCOQO(VPJ*fz3n zj*TNb=h!;3caF^?yXV+GvVV>SSWO3z79c%9nt*fxX#>&+jz%Dz;AjQX3yx+W-QZ}4 z)${{th}CoiX$jI3q$x;OkhUOwK^lW}hNCq|Z#bHRbcdroNPjpQWHlY)Xc5vQq)AAZ zkTxNGLK=m13TYM6E2LRSw~%&OO}~(aSxv`~mRU{DkftGBL)wP)4QU+GIiz(+?~vvp z-9y@EHT^>xXf+)~T4*&rM4E_n5osgRN2HNRCy`bny+oRcbQ5VO(odwJR?|_WrB>5Z zq^VZZRiv#*Uy;Tlokd!U^cHC@(p{vzNPm$ATTO?N7F$h^ktSPBmytFjeMTCMbQ)<j g(rf%bH9Nv&Jm2Iz!?P?aFDoa*lbf5Bo0Svs7o2gMVgLXD diff --git a/lib/pytz/zoneinfo/America/Knox_IN b/lib/pytz/zoneinfo/America/Knox_IN deleted file mode 100644 index fcd408d74df43310a9a85c475f83d545f6d75911..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2428 zcmd_rQB2ik7{~Dkfe-{G6GJjEt(buXHk3Bl+S0K@BA5qIA&k@z%Y0QJQKQ$bL0&XV zn~G})i(IZ5T2rwtG^N&R&d?-CCBP(SA+N>-DV@{%eY?zBUH7p6`TTcudiDF_T~hY^ zO!>=&*l&2aJ@(-}THBBMeTjPSC%>tNnz6cZ&jt1M>pp#U+K@V15^B!potKU&r_Fb% zN2ODmO;=Q%boIPtzV{v07f!4<7rS@qOZ(qc-K|ynhpp>WPko{8E%U0r>I{9^GfVwg z9H*}oq?`WCbops^thpK=D_3t$G}r9^eyx4j{M_FszjU;jfiK$R`te>h*xaMd-c#zv zwv&2jX|1|7Tq?J(ddx_tM}Ge@!T4Gd#Q%PTk=+pzP&;Twy*wy^Yr|Dg$rv4+dtKf2 z#DES-{ziqo5wAldKT@Fw-jy)(wi?s3Lx*=AG!Z8%^w?wD&A9#BC9<yE+`YA2##iN= zd&=@<!s0X&<w=u?kH?sMr^iV2)Y)p%=n;t-HA%(XjMn${-d2;_Z|VC#yQE?dUDR=n z$JLa|aq_^HMm06>hD=-asd+H<oII4Z*E}3`SmGbqV&Z-6dV1J0Gw0DtHFwSeHTTz} zk~w3w$vjslo`@Xd`FN9L4WyW--r1$+b<9`Uo2&HvBgrbKs8Hwb9IqCnXXvLZhSb8z zaoU^Lp}ZpjIzP2V<zI=FMX}$SMW2f-_8l=xn);-$d$%citxcY3-DrxJ?~|qVMdsP; zle(m~N<BBDNiQocRLdi3^oq<3wPIkUE{%^<rKhuWSxA5?JCLYX^<P#m?DWWsXZ&V$ zWrDoa+-uh4M~K>X%B)Qtlyz&~GwY+;r97wBl=}vBWm=P}>^`G6MAxVdt%r2g@Jh9@ zeuv)FnWZ*YSLjz-5><6^fqr%OST!oZ{saa&c)jya@ZWrY=fCZ~4gQBe`&a*(-~ZuP zB7Xm|g8@N){|5~++P#On&qzLH!k^#I%l68XbL_LwJ_Yv4^~zlP&IPzn@cxJO`Rx@4 z`WlcGB1=Tph%6FWC9+JXT_>_oWTnVbk+mX=b=uV;%SG0UEEriavSeh<$fA)|Bg;nC zjVv5lIkI$Q?a1PtcJ;{eop$|50gwtHB|vI`6alFMQU;_BNFk6)Af-TRfvy<5Pz}zO zgQFfuK{zUclmw{>QWT^rPFohFE>2q*j>;gVL282(2dNHH9*+7T1>&d>QX-BTAw}Y- z5>h6PIw6JPsFc%|3aJ%RETmdUxsZAx1>>j~QZkO3Aw}b;8d5fnx;bs(kjf#YLu%)= z#p9@+)0U5;eok9JjtU|rL~4i>5vd|lMx>5NA(2WVr9^7!w8ccK>9pnKsHf8wl%t|Z zNjYkY6qTc@NLe}RiWC;9EK*vewn%Z2>N;(Ck@`AqfsqP3ZHbW@BSq$@GE!!aIwOVV zs5DY)j#?wd=BT#QmK&+J(-s`5xYL##sX0<~r0Pi7k-8&=$NyL5!|X4CS@xGfV)kQ6 RGn0}Nvr|%%Qj(Ix{s3RXO|k$0 diff --git a/lib/pytz/zoneinfo/America/Kralendijk b/lib/pytz/zoneinfo/America/Kralendijk deleted file mode 100644 index d3b318d2d67190354d7d47fc691218577042457f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198 zcmWHE%1kq2zyQoZ5fBCeCLji}`6kQhDSw;s#)FaR|Ns553=IGOAK1ab^8f$w0}Na~ lz99^{1}4S^435DeAYDKZLW0@<fdFJah$h7jE}(TLTmUTSE*}5@ diff --git a/lib/pytz/zoneinfo/America/La_Paz b/lib/pytz/zoneinfo/America/La_Paz deleted file mode 100644 index 5e5fec56f257d1be477da3a6e294a99b465c2427..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmWHE%1kq2zyK^j5fBCe7+atL$k`z`?a7XJSFhCn|NozniJAHT{{u1%3}BMw|NnD3 zj2!>}A3wmr<Kr8`;0(l0!66K~1|}eLz#xPK`+!#b&uo`Y0ns3P!8FirkU{l84mtL7 L*#KQ*XTk*loeo4t diff --git a/lib/pytz/zoneinfo/America/Lima b/lib/pytz/zoneinfo/America/Lima deleted file mode 100644 index d9fec3716117b4ea513bf99f0cf6d989dfa5d256..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 422 zcmWHE%1kq2zyK^j5fBCeK_CXPc^ZI3Pl>~o!{@yMj<`N{II8b;;h4Pcf#du-7moku zKOiS@BtWh|)<OQ~_X`RI6Aq}(dwfAnwB~@WDszDD>^Tng|NsAIWM)Ev%>VyyKf=HO zB-en*;|Ca7{{P>(fq}!vH-tggzyyd*f!0Dm2nmh_+VwxPU3m|P200o;gB%W`L5>H} zAP<0OkS9Pi$Rl7H=oyfWAP<3Qpr=4I$YUTH<T(%x@*s!?c@ji}JPM|Po(0kMK*K2V MFqaL``*x;W0HElGzW@LL diff --git a/lib/pytz/zoneinfo/America/Los_Angeles b/lib/pytz/zoneinfo/America/Los_Angeles deleted file mode 100644 index 9dad4f4c75b373635ccbe634798f8d9e587e36c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2836 zcmd_rX;76_9LMpCq6mtfOq2-iq$YxZfTFmRxecHqDoA36O9F#ws1Rxy(nOgx#-Gfk zjgDqbP8phGV_AeYIW>)C&^T@pSt6t2f|j^+Z|8g7_Ntdn&ok%woVoAs_m?@lATPo5 zkEetEg~RiiJ=}Yg*-zDbDdz3{A!447GFxB2F5j&SGj;v0Ev=hBKppiK&pB4MQ%-ol zl9RQfPBpwMKkxWZ8fw<cFY8{G#;OAOwP2~7E}bmDrOuGwb7JI7<WOl!o}|uppRSrC zqE&P25Opq~t2$Q~qRuy6Ru^_(S1pI?)Wyo<>QePZxx8$@x>9jOTGt$qtA!uSwYl%e zAL*~kpJSer>w`<AZQwR_quVUG*{NLJY<pJUYR*%)kLBvWzDZHueaYJQew6ZTiPU~C zbW!bAcGm5e4HW<R5vIfRAn7<Z&;-O?kbw2$O`!T-0(X9?gD&rq&W+Wk%kjf1xVF-C z{j^$j+wqZBuT`o$)`{-Esz}{guw3`Zo~c4oGj-1q!&R@yVLG&LhTIhxs>9kPN?7Yq zbNA_95?<HS^geJy`s{8q_iQ~Wx@3^P_n9xGZ&tAGx9EiGpLj{%H|cXVAmm3K5mluk zye%d&s7ysR{9vNaEl`7McAMz>Qi-YBU}E>olfk7=n79q&BtHKYolw+Yh9np3p&1<| zF(OM3OK6ti0ZBS3yn{+Q8>UCxI;%z=x~)f@{8o+L6>9F^|ABg-;-(q%#(MQ&;VCn= ze20unuQB5nz9bU{8#8gj5}A0lUMI)AsFLgV>eS%HDs|6hJ*j1?n*8P-Gv(+aNn5?q zO#Nhvq|aGlrfrIq>7%pFj1nao;iF9E%vQ;~-P>d({v=svM(SC8uBcgGhwE%_y_&t< zs~>LItLBt9>PKoetDJ=g_1vmeYF=7{nZI_UEQqN!kLItCg~8iQZgRHdwv?Ovh*6S% zIL{OW^p=91DP~cVPafNps}~;$S4&Eg_2boERhSj2msT{YWy3n_<%I`TQAmp}PT#JI zeSxMVsa8rF&YP8?+hk?UVY8~OT%N3|HcuVPlhvh_=IMPYQkqj_)@+HAc7FD4@9*IH z-+6t$$^jma&-a%2`TKkoWu8v%-o<^@l(bCGv<dcP*z=G*(=zQp+T-zapUi(z0-t?y z{KIOIA|O>j%7D}XDFjjpr!56i3#1rGHIQ;3^*{=OR0JsrQWK;oNL7%sAay|sgH#47 z4N@DYEe=v0r!5asAEZD?g^&^<HA0GnR0$~)QYWNPNTrZcA+<t^g;WbEm($h@DHu{Q zq-5x7#)YEs*s1|#L+XYU4yhbcI;3_;@tn4LNco($en<h43L+&$YKRmOsUlKFq>e}- zkxC+^L~4l?6R9RrPNbelL7lduNJ){JB1J{2ij)<pD^ggbvPfx>+9Jh8s*9A@Y3qv= z*l8<_lo+WoQe>pcNSTp3BZWpPjg%UxHBxM(+DN&PdLspQ+KMA3M{14~9jQ7}cBJk| z;gQNCrAKOy6d$QRQhukcKe7N$y8_4(IPDrBi-4>GvJA*NAPa%41hN#!S|E#otOl|i zPP-n+f;jDpAWP!3Yl18avMR{3AnSrG46-uF(jaStEDo|d$nqfTgDjBKt`M?BPP<0P zB023UA<KlU6S7dqN+C;ytQE3Y$Z8?Wg{&8{U{1SY$dWnjnjwqkw5x_J8?tW5!XYb% jEFH3T`2StJAUlLfb`Yb}hQubs#zm*a$H&IU#s&Qiukn{d diff --git a/lib/pytz/zoneinfo/America/Louisville b/lib/pytz/zoneinfo/America/Louisville deleted file mode 100644 index f4c4cf966fa6a0130b9ee5fbd01fe0790fe8f001..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2772 zcmeIze@s<n9LMp4q6msg`2!WNr4>VwUxvTJ!jRoiubN&=(eTmKJJj-<7(p4{=C5#T z+H_FXNJ~v?j?`>gjntSqTbVMQh2aAwm5DxP>z8?N&-<*LTYvRWe|7H8c|AM--0g1n z{qYwR&ChWDaYflT++2I?%{d`xAJyBFgRj2#n`~a)Ik;t0gKRDS#o5*)PL9P+2p&7* zl~0}v1y2t6Nlwm=HYXdZRQ=XyIhB9PX*gJH&NfyBzuLK88f(jf=PH(nuAUKWD)LKn z`KaL6c|+x!!hXT?N%7L+&2qjS<}u$TdYtcjI_84?_6t8Zn;%-5or|aIOzV+4=hBBw za=Atajc2|5ab~2sezdO+?N2b_H^SPf3YmyCO}fpK+O(CBid5I7-6XBrXP=X(w8JXu z`Cby;b&o{v+Gt|TYSmG%l1^W|rehBVr1OE5y35WLrt3@dbzH?fb4%F+`qm<^={9|! z?w*%qdW`6#dnR=>@%@ujuMVxIcbkr?_m45sr>RvXoVX}`4;@#Y-JeOnExXliC62lM z*$t|HezO^{phn-3S!eDX`+~l!`*xE!uvjOCK9Z!Ee4W&|O$J^VrU%uok>q;68eCl@ zDSL;i)beqXwjo}n2LdKz$?s~2H_zNX`K-!J9At)O)T&`ov1WLmFZJ-2%Vxy25A?_* zC(Jz!)jF%@l8kyksPA3(x!m{i9G$&*kBnZqT-`r$qvXt;t;VF5NbZ;%m3wuE@eZD) zz4dENUhHU{w|9{l+d4pxd!<6gpUzgkWea7(yZzO~nLe3R8L1x3N|nh^w5TZwapKQE zs{Gfk%GAs^)ztG{%rwstJ+1DFnI77q3%1sq8I5c8%$57h!?lZa;oK@St9pV~zISBy znuTi4;7WNU;8SyBOJ$xnRn2QHkU(Ob3VfO+MeVPuqFo+Y&~i#GtQ=_;9lxR<ElDts z)zs_7`5`0g_UXqnwOO*bN*8xOY?e+e(@R6=q-1!dDrr0<rSYYzwDxr=`?WwlRlQ1{ zK9i-El?UXR{T@|bm?z6$bX1#&zdmgvZyf)Ab;EY;Z~8>sbiVohe{~LrYc@GfIDFvk zh{$mGt<6pc_uR>ScID}G3x_{G7!g0-=XY|(*n5h-AF}r(zmsdvx%M4bg!=^lzxd~e z?N!(|v>7P?QURm{NDYu8AXPxh;A-oD6auM)t1X4Atp!pHq#8&$ka{2mK`Mfj1gQy9 z6r?IhS&+IQg+VHVlm@8{QXHf@uC_c#eUJhn6+%jc)CegOQYEBJ=+=n?h2pZ60!oF{ z3Mm#+Eu>sXy^w;r+KM41bG0=?iiT7TDH~EZq;N>(kkTQwLyCt~4=Eo~Kcs+21(6aW zHAIT&YO9Ep5ve0mNTiZTDUn(t#YC!!loP2ZQc$F#NJ(97O_8Fy+NvUDMe2$a7O5;! zTBNo}agpjG<wfd?6d0*6QevdWNReG_m60+dbw&z}R2nHYQfs8xNVSo2BlSiKj#L~e zxvQ-?Qgl~ab)@XBw(dybk;)^bM{180AE`c4ex&}$0w61ZEP<<C17s0g?J6M4;A+<a zSqNk$kflJ@0$B`XHIU^%)&p4(WJQoALDmFW6j!?{$g;TFbwL)!)vgS(G|1W@i-W8V zvOLK8APa=75VAzb8X=41YF7zaCRe*o$U?c=l|q&ZSu13*kkvw#3t2Dxf5G?_Sg;s7 ZY?*f0l6^x`GE&l#ed*~b=_zS3e*(C<z9j$v diff --git a/lib/pytz/zoneinfo/America/Lower_Princes b/lib/pytz/zoneinfo/America/Lower_Princes deleted file mode 100644 index d3b318d2d67190354d7d47fc691218577042457f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198 zcmWHE%1kq2zyQoZ5fBCeCLji}`6kQhDSw;s#)FaR|Ns553=IGOAK1ab^8f$w0}Na~ lz99^{1}4S^435DeAYDKZLW0@<fdFJah$h7jE}(TLTmUTSE*}5@ diff --git a/lib/pytz/zoneinfo/America/Maceio b/lib/pytz/zoneinfo/America/Maceio deleted file mode 100644 index fec8a8bf1313f452ce9b10e71e86435bf1cd1781..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 756 zcmb8sKP*E*0LSsy`V%QBi@~6d#v&v~38E1z(LwSC3?>mK1_KE}!Xz=0u-qsHd1IL@ zsEG)Xh(4tw3-PBZa_)PprLMVrnl63s`*TO9Cfb}or^x)m<usYg_lnKE$%SNPe<u0U zrw(4)<)J?-ZoBs7?NeUf)jW822O}c=RW1D;U)-m+WyXz(w=M0xmyC&zqjC8ej;P#F zv&@|yi+t$P%kSpY*K?`-USCwTXB#pyccbc5hbLm^s(xikHk7ASbhyfkKF)~7&R4JT z>Qu<(dD&W<P@NwqURQEWbl+!X_v?V@Nog-W9ac+P>tLY3Pf%-r)p4}WW&*`pUtc(7 zg9+DZk!|M2cz1K$e-tu*uBn8SvYJ{*F{Bz&&T8r*1+AtcQWB|&6h*2cWs$l_VWcur z+G=Ve#jU0~QXZ+#d$Its0<r|M2C@jU3bKsVtb;6MH7g-YA!{LvA*&(FS<QOLg2;-v UWS*_S9RtCFmJG$ss=cw$FIB85YXATM diff --git a/lib/pytz/zoneinfo/America/Managua b/lib/pytz/zoneinfo/America/Managua deleted file mode 100644 index 69256c6357fa178dbcc9eb92158e28d2b15f2a03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 454 zcmWHE%1kq2zyNGO5fBCe0U!pkMH+y_UR{qDY|bSKoJ;Hi1OtCO5WJEfAZ&Q+f$)~V z0L{dS3R(xf1GJs*J}^4@KENsBzyoLd%m9~=yareK$OJ|vW+oOOWQNlJ|F4%}U;vU5 z3@rcuuU^2w@&EtM4GcUWl2HI6;^P~_;0wgg!66K;K<w-i0(1oegplC=|3Hx0u9E_y zLH+=l0`d!p2Kfg>gZu=hf&K!~AisfWp#MNL$d4cz<WCR{@+*i2`4>ck{0yQ&{sz$? Pzk_I6`JW3IFlJl;pAK+| diff --git a/lib/pytz/zoneinfo/America/Manaus b/lib/pytz/zoneinfo/America/Manaus deleted file mode 100644 index b10241e68dd415354ef12cc18fb1e61df3558104..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 616 zcmb8ry-Pw-7=ZDseIWL=v@}#!tHL22A|g&7V+Fy5pg+I}np$d;THPb4p|PCKsNry+ zLE56ILDXE@gbMl)El%%q*FfsM9G=4+?&baYo7?GW@7Hw68x9kb!@d6~ms!paZM@{a z*G%DcQD4>$Re7eU%Z-Sxj6B;)VM|rpQ@VE2P><DfUH2E%+wp*X@7Ylwc2|E6#!Yj5 zRyRv`suiu<)<v&rH-`0BHfu(&PxaXTlNmo-vaw{<OdRa#$-V~@w^urmzEv6LTsYM6 z2|HIRdY*IlH=%C1TQ9P*>U#;5l#`ML82=yp$}b%|Q}zxjyHob37*HI7iUq|ZsF+Y( zC^i%yiV?+$Vny+ym{Ht<iXFu-s2EZlDVFSsC&iTFO0lK*Qj96i6l<E1Z*|lTh5zIo KofG@{RP+aMgaz9G diff --git a/lib/pytz/zoneinfo/America/Marigot b/lib/pytz/zoneinfo/America/Marigot deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Martinique b/lib/pytz/zoneinfo/America/Martinique deleted file mode 100644 index 79716de53f325ac9842ca2e0eb8e6e91c5495c90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmWHE%1kq2zyK^j5fBCe7+bIb$eFnKgp06mo`Z;q*ak)>W~TrDkL_V#0Fx~L{~tfV z!1@3G)eDTgKE5FgZf-!rF&KzlLO=$AK?n&J{s)50c9A0>8e}oZ0FdP%nrsJf0bOLm F1pu=NJQV-{ diff --git a/lib/pytz/zoneinfo/America/Matamoros b/lib/pytz/zoneinfo/America/Matamoros deleted file mode 100644 index 5c59984def290712e183eea0655eaf8dc7a133b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1402 zcmc)JPe{{Y9LMozB`A^wfe<7L6?kYZH`C0tELTHwnmYfaW>)Id{_H7rI5T@7(NlQ{ zOeiEoyM$PmVMG#Emnb9%kq$yh5`pvtMMC1Q=l#2O>JWX$kJlLN^m~5-HLcAa@yD$< zzwmHX=HdH>@#y=8Z57|d_O_?m9SjRkdz?)7Rf|1kUt~sVw#f9nmV0B9MAp{NI%{c7 z?ECdfXa9&%`=c{DXL42LKDnt63@)gHccjh>JyVDJZpgzWqbk2KB)z-))Ddr~Ji4h_ z`F5mB-^z7S5R)hi=9@&};!k<(?rl{xS1kQK5mg*blO=Vv>iE!RS(=lp$~wQx@}v|M zsF;(1Rfnp~n39#Bl0|j$J6-)!h!bm3T{HGk)GiF`y1p0U<aD>LZ@e!~jnwG||BO04 z5Rqqgjj6N2TG^P;ubO<hvT3<R1yfUG@J*p=jdjS@38!jXT9oI83dH$YOJ4}@6c;By z=ul;Xkb}?lrHrrQa%fz)C%zROB?G!+ZCQo0#$<S5R&{OZmtE73RAi+^c8}arSLO?4 zPj9EXI^~qT^;PQH@J8ua_fI~SHJT@cWj(kjVl3<NLq}YZKOpkU&EpN2cgh9+A_2=B z8Sp<+;K%_<f+Gtg4URmJL^v`*QsKzO)+ED`4U!I$50Vg)5t0&;6Ot5?6_OT`7m^r~ z8Il^3o2^NXBRgA@9!Gvif=Gr)ib#$~l1P?Fnn<2VqDZE;CRHR?TazqDwzei+j(m}X zIWk64=ExaInj>o@Z6t3baU^pjbtHFNlRT2Wtw|rr-_}e3nE^5djyWKc;Ftw64UTyr z6XBQ%G8K-w*qX^8v#~YPLFQv?CWOognG!N5WKzhikZIw6o>#nCTc%l?)1U70xYC?{ Lx7+1*rN#dSVcugw diff --git a/lib/pytz/zoneinfo/America/Mazatlan b/lib/pytz/zoneinfo/America/Mazatlan deleted file mode 100644 index 43ee12d84a7c7e47aaa92406d01a539ccf93079d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1550 zcmdUuNk~>v97q3_4T#_^8mK6uf+)l(hX%78(kws6PcyCjEc?u|)HKlsy-)~+<u!-` zvw|op2_<r%0a`@^6&ZpRfkae9kQmW~yxw_2n^rAa^t;@59&htD=eYAqykYXk#@b)F zY@>a7pLC(?eR=!Pu7NIZj;A}m*VE%4>FF767<~Al!qeY;eNd!ahZY}FVU<(#q9m^h z&-|t%=C4+fVJ~#lxP@x*jIXlzoxfW0^SLbjGSMvSdMeQ!erEa2R*7l)XjZh;%gVCH zCiYN^j!Ww>@kIx8Lhy03Dxp9p22`1(d9ga_TeC{`ovV}kE7h7eWAxgdY?bn8j<`-m zsnn~!l2$WKr8mBnjKT<$S$a>hVy7B+$`#3;{oUjQHp)7AX>uoD(zye-&H67#bl#n_ zCcm##Z@7F*ZR||dn+~5*1t&tZr$np5I+tut-mJE43YMY;32JN11o2MvnBtkArFbaL zY#Z*AlHPe{`>Sr*ac!(Az57h>Y<_QcUF_6l6%R~#!%1C{_fGBh*6PZo_f=J5zTPvv zO;rciNcE4SswN;$YF?D7+E3B4_eO@=_hgprKflu)XcwtFm}csay%wKQ&Kd3F`wxy~ zosJf<tX3nwmeqDn##>gC7JuG-)X4V~ms?y}Zi%;Vx_w;<Zlw4<_g@HP*+U|TND!GI zLP4Z*Xp04r3yuiJZ_71LM1#l%5e_09L_COm5CI_)I<!TE$jA^9A|*pih@1>TA(Apg zg~$pK79uS~T!_37fgut*v_*!<?9dh(A~i&8h};apA(Ashhse$l9wI$Me2Dyv03Z=C zLV(2J&<+9;1tbhe9FRaDkw8L$!~zKh5)C69NIZ;yAQ3S_g2d#|4hj+#BP>W<jKCm~ zK|+JX1_=%l9V9$Re2@Sk5kf+Q#OTlt5)!3DJ4{HNj6flgGD3yK$_N(z7t#9JMMv2s V2fD(8LW4pAU7;aC5kVn-zW_OOkC6ZX diff --git a/lib/pytz/zoneinfo/America/Mendoza b/lib/pytz/zoneinfo/America/Mendoza deleted file mode 100644 index 1032356430c8cde6ac198d2cb13b71f59766745b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IPe{{Y9LMpWy44a71B3n$A*7g#;6b+*RO1m3Mzo8K0#6;H2m+%+2K`xGIt7D} zAW&R+4SEVJQEN{vTM5ZvMg$#<qFWKM#<KG1`+jcGrB3a~v)6B9upfNhnZe^nyMsSo ztK=IFuaq3_*Ityjn^$w{-q`_tf8%pAwK=D!7Eh|_b?5E$<b;{2-_f(zUa1GK-`j_0 znoVJ5y|#ln^Jr{LKi*YT#S3NoWK+$|9S-Tam6$5^CT(fywt9AHM9-&hnE6ka?LykA z1@qBXx)Nrw?})ALiKyy$&3+A^P_^!;u6-Cc_0~RHFICjH>abp}>@eSp3HxLAfcklB zK(CA@l)Dnwt0(rENc&KJ%gCf@kB;O!l3z^6=hOMl4RPK1qG+}*AG2L%uZb<a)!VKg zYD?Chnw^E1(Wce(9!%N(;WeiJO|~uToNH)o`pYXE;_^QZg`2{S@+s_G;am_nS1bo1 z=Snt+IQO#L(Bj;?rG_Z?LCgMhCg_v1UB%_}$oYMf%s;Fo5A05j-j)Ayj<t}*kkydo zys{p$pjTEzmPFP>7DZM?mPOV@7DiS^mPXb_7Wc~P$nsuUA1MH-z$+ynHF%{6qza@A zqz<nXf>eT(;+0yEV!TofQVvoNQV>!RQWE-_xRIhH{(!2GvXHuv!jQ_mQW{d5SBgWb lL&`(yLkdJHL`p<zM2bYJ^h%kSl}G#kbvEyoW>Y=QzW@=X?P~x4 diff --git a/lib/pytz/zoneinfo/America/Menominee b/lib/pytz/zoneinfo/America/Menominee deleted file mode 100644 index 314613866de53e1457f6cbf2fb617be7e4955edf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2274 zcmd_qT};(=9LMn=X7P|bWKxI(r6ofSN5IgdNm0TW<Pjf^2tpQu9kGmW2}e<*pBqEF zFg7XEYA8i+t(Y3iuol_lMGrH?xtd8nEZ1zTU+G0EJE!0K*JW2-b<y|i|9|~=?mzFq z%Erg@)juxU{=>s{+K2BHZC~97JbK>;S7m=kg6?_cw0ylItWQk4A}8u%&2Y<Qb+YJJ zb2YJCUyBKv@Ibb@spuz(&iziJcV_C?q<1B@?+q1aUX+{9cIgQ}95V68T6DtET_*9Z zT`FmBv$^H@W;L<3(%jlusU}tXOmdk|-8MJX+@6)HQl`$4$uVOpb=+i0{Ub$BId@&s zhOX&5J~<=lhkw)=Js-=Rp+t4pwjP<f{C72N-ACr`f?;(}`93o}`8}06W1Gng59zG9 zdXqKMuV-8;G1-GJY0vO#$?4vxz5T^9v#na^c1@SOU`XX}h>=;PRqEcQm!u$bo|>Ki zjm(KoQ@$y`7~jRPntS~#Gw;i@>b}$OnE40Cbm50DnEN|N^aHPoDQY^VA8g+#4^_Ob z7c?|U@yvEzQoKY;F2AozbJm;E;k~LXzQUCCZ&Br=dFJ8QUe*gwEfW9MR=wy*u2j_4 z=*7Dy%98nodTGlQS(cWe1ItfIU@S^k77R$`&*`cv{iLb-LaFNT5wo)Qys8=5V`|$! zSC0&CHme#As7JeN&13#gb=~$>S)J3X>w`7Y5Z|HKloraG(X~35nIXY%%XMRPlr;8v z^ty`|<cZy7>dE5~vp$rqo;rBWG*!kc>HN$*UGSTFrs++yA$dqOS8O-U;ZYs(^-5^u zxZae~A)5vd>()Ql%I5Aaz2*CI+1l2kpB?Z>+o~1%xjhLoF3SFH81J}|@Bj7}iS(UO zDiS$*C~ABp^7eie<Gv@jcM@+k>F@s+`U7e~v3-`=XN7$h2ULmu=Azsi0{>!05qotT z%j%IiA(KL8g-i>X7cw!Yof$GUWNyghkl7*A<Cq^ZL8qM|GDWAIBgZ6>St8R!=7~%c znJLFqk-2hA7MU%_bdmXTOc<Fl$CQydb4=Q4XN^o7nKv?VWah}!k-2kB9+^GI^pW{< zBml_(M+%%a2S^f}HVa4^kUSuXKr(@(0?7rE3?v&I=|J+qkq{&!94SF^!jTlG%?d|a zki2jt2FVPP8YDMJa**sG=|S>?BnZh6k|L+g5l51oHcK37a@ss`BnrtCk}4!uNV1S@ zA?ZT$g(M8g7?Lul%^8w3r_CBi+MG6T9En3R$B{ZDcO1z>vd57=B!5T(kqja!L~@8E z(P^`Yq|s^fh$PZ!Gl`@U$t6cJk!*6L6Uiq>LXnJeq!h_1M^c?Ot4La%Hm^uxoi?*b pYLVO`$wjh@q!-CA{tpryXA3>smfGW=<<0lzdi;5L-aKz^++RkMdw>7{ diff --git a/lib/pytz/zoneinfo/America/Merida b/lib/pytz/zoneinfo/America/Merida deleted file mode 100644 index b46298e1f202ee4ec22ba3cf9f8079c83ebd6c7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1442 zcmd6mOGs2<97q3}ky&6AO7sAUKm;4#W>%(+I+!#0n$oO%l#WeWerk!8j}H_<D9c3< zNf1Ua3av$w*+PO`L=qHI2}Mvu)S|CIlCq|Irgp7b1pO}GJ(ml2b<c5^dOh*-$L3mZ zIIP$@JTJO9JXI0iJz25TnVTP1(=72#!79N~B8i3XRZ?WCB*#BgYv-+yl;!VC%G)2Z z?%M;CIvu3f58pEzhGzA~(O#2w@}=I?uS|OVn9k@rp*H6YXnR$?%8cpKS(ybYJJh2c zs}q!Crc-i)!c@+)YT5Goi^{$25a+}a<2n;1Tdz->yp{!$fBdN_sEpKwbt9%IW4?Af z`^~n94|;oan<<tFT@rRymAo3#JEmJz=~$oMIa#K5T^*F&x6)Nv&nYQCy-HPh8>O;6 z&Fsl>%igjGQx%mg)oFpov$#;#tbAi?J}2sZ-^Y#jak$?9;)<!gIiu^w@2dlSQ~Kbg z%c{QNu2$VWs-g6`G<s@PQ|hQ3%66&4%g#x2jMp@O?U0txT+=dDDMvo8F-Py%<=CT8 z(|Vy?w+((W?VTCAqxXpk2n?9>4=$hY+N_9mKQBQ(-^dLK<{q%{*BhtHE%qX}WLd|a z>E>tup8GGf@L59*K1C0RA`nd&sz7vMC<D=kp$<eJhC+T?8ZlIY=mb#;q7_6fh+YuI zAeuo`gXjiP4x$}IJ&1k~1^u)%WT@z;r6WU0h?WdBA$l?tg=or96{0IdS%|g}bs_pf z6ozOFQQ1#RXNb}ets!bd^kygy(VU?=M0bYr5bYW2L-c1P0LcK7f}d6nkR%{kK+=HZ z0Z9as2_zLrE|6p(*)Y<9<ikh^k`W^%Kdqb?N%?7I#YhX17b7u9W{}h%xxxQ1xe%+w XWUInxSE4P>78~t~i?t=$VncocWl?nK diff --git a/lib/pytz/zoneinfo/America/Metlakatla b/lib/pytz/zoneinfo/America/Metlakatla deleted file mode 100644 index 26356078f8d158e6cea100856baf7620a3296b84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1409 zcmb``Pe>F|0LSrJU0qk#no9G}K^_H~X3J>!?-WvCVvC0BrdF1vE%wj9h*p|{VIIQK zL)amE5Jnx$p_3i5C_05=9-;`Og6Lw>p+gZlz3-H#LJ)exo6oR&{r-ZrO{a3Tzm7-! z!sV2!%XeCzdc7D*l?&Uywk*`A>nqbG!oHiOYh#(hb=9Xwbj69t)*Yt1K0`(wj5VW+ zz0zYWnVxHVrB|Oby_1hc>=(<7dp9M0Z)WxQCnIvh!zq2^z57DHF`{o8GR5ZZc701z zn@Fgs(i2OAVrx->o|Ks{lDAcwDc)p}8j)v)@;=DW$Nffink}lIXBjms??vr|&p5pJ zOdJ_lHjd6rh`NS(<JkD1I38HC>IZMj6T9ZDli@3}A>OhYOTx18^QaZhD3#&a4y(zt zQ#MV8tka+3<(cd4#@YESajvt<IB)qxbEv>*8D18x`6)(Q^So$J{9$yIKM);ulGRx_ zCp#CvTV2VP?3#INb$=U`7sel17hiYCOTD+Op6QV6t?Rcgj}=N+M5N2T<`WgYjz`m8 zrE8jP5BF(q+rDYKVw|_ndF!^_-=Zb(uPfz1AgEPTs(VP?3U&7dwc;}MLvfk<Qpd&X zH^G0$5TpL)aw^p2JIz*S&(ohTv1iaoMdCmLaa56@6AB;1!coBh(IDX<@gM;q5g{QV zF*&NBkf<D0SV&w*U`S+0Xh>{Ga7c7Wcu0ImfJlT$h)9e`kVuqBm`I#RppGh1BveNg zD-tXcEfOvgFA^{kF%mKoGZHisH4-)wHxjs`iW~{uQN@k~@2H|j!bjpq27rtJ83Hl} zWDv+GkYOO>KnCKdMuH5*QH=!|jH4P2G8|+)$bgU$AwxpOgntI*j)+?GiT14HSzXk? go&U6})nPftGPxoqmmS#c&-EMGft(zFj^Bv=1#$;!g8%>k diff --git a/lib/pytz/zoneinfo/America/Mexico_City b/lib/pytz/zoneinfo/America/Mexico_City deleted file mode 100644 index 1434ab08804dac08e4f595967d8c325691f08aef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1604 zcmd6mU1*JA0LLG5WLQm@3m+vZt>*0OI2dMg#+vQehhv*zJDWLk7<=a9Gz?EoN-^7o ze5{o$U(q%fX3T|@Q)Eq(FwrTJxbRSme7&CEn>$y^h5ys@{@&iJ_u}{Sl?4MS;*X6o zU%0HwT;3<0>v=1?K5dKi1d9FFJ%j$<7`MOo$02?9Ww$?k!c}l@^~cD)cP|PCqQa4Z z>%|2_r{W^dLro8pYeFJrN3=}ME)}k#cXICRG~rHpAm@#qCgx9ltLMM@DHeRYuhWJd zYGKbEy{PNETHM#H(~rGWOWLH)sJpE)4<40E^Uo?zb)C#gJgAms70TtY0hK)~Rc3!_ z5;=}Ine%j`Sn=w!%<at<dHoZ$_hh_Sd1YAVH;fYnN1o`y%Gs)@_J&@SIY#;N+Vtuf z?^SWaK3yXERcYL5SsLk5Yla(T+3i-f_Hnsfcd1jXzm_4(54VVl<1?i{xLs84PuEpB zKCz*EhOUlxi;d|~IxwL~)l7e_Yd*QurXOJ)9Gt8+zqqKkT>YYI!*}J@)*-d+`~_K8 ze@n?jhh=@)GqF9eMea!J6FZlC<*q61B9s`^p|1x-Lu{^Y7^)PzKg`j4ZhFMtfmq$x zQK9yAe$@M$GSz|RM|wmQXQVj}`^nqCJ(krGBZOtOw+M%2T|OhCE$c@2h#31hKF{kD z-c>%~;bxgz;=_~Q^ZkWUmKjz-%!1ejF$`jvO=B9wHi&T$>uehHAokfb20|=^n8+_; zBg06Dl?*c>b}|fwSjsTfrm>Y_EW}!fxe$9H216`{m<+KQVl>2Rh}jUkA%;UNhnQ~D z*v>HCrm>!3KE!@T0gwt9B|vIm6alFMQU;_BNFk6)Af-TRffQrYR0AmoQV*mcNJWg2 zAT=?Hf>gyQ3sM)OFi2&L(jc`#inD2|gOmrU4^kkcLP&{_8X-kOs)UpYsgqGCq*6wy zkXjkV+BDTN%C%|gWfTmlm{BsMW=PSHs^R}%_E;0V+XSEBbvcurNeSNMB<Eab(%4`7 C-Qw5) diff --git a/lib/pytz/zoneinfo/America/Miquelon b/lib/pytz/zoneinfo/America/Miquelon deleted file mode 100644 index 06ceaadfff38762f411606d9a029c986140050af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1682 zcmd7SZ)nVM0LSs$XtXS9DOs&yb76Pe-HgMW@7P>y=f3PVGn;?gtTVeY+swv9N+d#t zXi5Go(H_j-)cO`tB;Tx|)FsxE@<38*+Ryj>d*Xpdp2+X|-s`$&zxuqt;flysuj7wr ztoet-6ETP1lTP0C^(05DRa(oCo_1ijm|prs&)9ZarRVnOjFl~F=H#Q=IeV>gj$W6u z5*Mghu_JQ!=M*)kzg}hz_=KxFDBXQiMOJ5)%s%x~c<PF@w<#ua0xmswjS{)eiF%&* zvY4OnRr^x<m2Y@JFBrL`7CyP77ri^E@_Jv%#Sd$g|HN&%<ic_#8oOlvp=z--)FzkJ z6pDhZ3Rzg-6ouc8>40mQ3JgT`ilhv+qEG6TU&g7Tiw&~)b-D=d4arq^6GW)eBjuS- zVs&1!T+{MQl%#x@;nJHTJn}}C<sKJhL*I0H>J3%?cu=n!J*FzI+|%o0QB`^9ysqk( zYC}zj-q`I`n}X-$=EEVe#nmpW>pY?+sZ!Pkl11$ozpR`2UDUlulaYisB62HUZX13e z>bp|)_LqaIq1B<Iz4ugO=?A^z#Cf$d_pxqj>`=QVU(?N@D%CvtNVfXgMQiM;Y@1Rk z+WHU6JwN=Sy*na1-lmDYokeor{dm#2!6o;f9TNL3%ZiI1^G`nUmi4^c;jpZy$}!fm z2DM|nWqo`fH=#HfcI20Y9j@#gbAC@N{1-zr?_n`E*2mzG(ILY_#)k|L86h%6WQ@ol zZOtf=VcME;A_GN6iVPJQD>7JQw8(Ig@gf68MvM#@88b3yWYoy8ZOyomfg>YFhK`IK z89Xw2WcbMVkpPegkPwg<kRWVL6i67hCJrPJTN4Qq3K9zv3=$0z4iXO%5E2m*5)u;< zl&y&h3Cq^Rg#>16B11w$Vnc#MqC>(%;zI&NB1A$&Vnl*OqC~>9HE|+=+L}m_P;E`D zNU%t>NVrJ6NWe(MNXSUcNYF^sNZ7U}ZX|G96FCyPt%)589*G_aABi8i0FX-nxd?3k g=gS~5&s-1w9P{n<X9jcJUUzn8(BpA?+}Vl00DmU2UH||9 diff --git a/lib/pytz/zoneinfo/America/Moncton b/lib/pytz/zoneinfo/America/Moncton deleted file mode 100644 index 9df8d0f2ec9fc8f1974d83cdd8155c79340007ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3154 zcmeIzZ%ma{0LSr*s3Q_36+<KytxyA0ki>s+EgYc`M+FoUJe9`4TaW^3-qe;(c+uR< zKlx`kt4XdkG|ZtSVgj1Zc?2cR#G?qBfr6TuT)Ca^)648d?|RX>JJ09Yv-|q@O`4PW zcDU_tr@Q&W<(xK`cmGx9v8sK)C@NbccO1MV$|pvt@_j??hXMoCp$*;a6&)^hc=2_6 z<;4bdWaba{@5?LI(TK0?RogyO)qOs;*W^4<$9zuNkH6Zkj<=TBPxMPyC+bgDo-DX3 zYlq&;sg<p&F0WpnwVzh!lFM}c;Jxa{$gTRk?Gtsu&#5oyJoQuO7J0F9mioD2xxBPH zS2R?l$;*W)LKj8LE16N^>WU!Q7#AR}rF+VzVJ^`eJ4j#e?XPYGd+1;KT~jx^H|m@B zYt*mJ)w-qrOVwIatZyBz7Pm{PWZRBn(Y|h%ytA@U+|4eK_vS4Wza?hL`_T#F_mT1P z!LUsAN3SsX&}*7{_+YB;xDl#c=R@?P<Goa8Sx@aQZdbO=ZQ5gPo$9irR(s~OiLUat z>?Uf3*Wgn5gzqNdZQCfl+m?za^%B|R!UpA2nJRl8$XC5~=V{+9v(;0Dvvlue<5i!` zak_76pz@0wp!-F2s;7rN(*8pRi2h!eb%4hs5zy|E1Fl>afoB@zGe?eyfqN_Epq-zK z!FhLd(1&ZpkmR#^Xx2{kY~+6Z+|-ZM^M0S|;1LT{aObyjxOc1?-mqDYxH(+CP?6>g zsT<)4EsA!AmG*FqToL3Pwf>f4bjBoC_#)+qh#lk_GvgaaWUz<p#qbS|vE3V8Q3Lk0 zL^W5t#&xW38DCTEns70<WnxK{b5eOq%S-EaIit5lwb-)@oRe24J6=xAa=tQutRrS* zymLxSf5()&TXk&cbQN3Y)Nwu$Dy}qLPi^&6)3z^{@%7;%!I>ss+uv6t&W)C0<9#uG zVvwAXcV5g4^pr`-2SrjxlT3-+E>fC2^z4CUYIaqlPVL;P=6q4DUvF@#xvPtHT1C1_ zpI@lw71@<c*d^asn<m~2Es$?zM2q=8S#m*akXX<fFEfHYMaHQxnc2NbWPa@{vzm{I zg_}aO<8-5XC%>nDx1?HSC%5UGb;atv$XdN9yHG9m+oy9A7pmOOGcrG_K;$=+%BB8U zVrj)jx$Hr_c)w_gTz)=GI9H^~56XPSiu4I`<>ouW!?Vj{eqFmg=Hui3_+R_xb{~zg z+1%~}du(0Z?sLk+%k4f^Y3pIV`&!Sw@d-(`DKnF7lVi-qS>}o)Ga}}A{Pj%w7xUvb zCw*Y+cgPGOQ-sVBGD((ZmXK+(H1mW^6f#rDR3USPOcpX*$aEp|g-jSSW5|?QnmI!z z&C<*oGHuAbS(=GMW)7J;WbTm3LuL<|K4ktZ%>*Jdh)f|ehsY!%vxrP1GLOhaA~T6h zB{G-DWFoVPOeZp*$b=#@YH6kvnNws^ky%Bi6`5CLVv(6erWTo7WO9+&MWz>-Uu1%j z8Ahhq(#$b3$;d1t(~Q<U^TI@P%uEAQjm$MN*~n}o(~ZnGGU3RKTbe0H<{X)HWY&>s zN9G-wcx2|0sYm7>nS5mSk?BX~A4vd`0VD;MCI?6okSri+K=Obj0?7oD3M3auGLUQ_ z=|J*<B*fBW1WAdd$qAAaBr8Z-kh~y?K{A7+2FVSQ93(qPdXW4e2|_Z2q{!0b2uTu> zB_vHqo{&T#nL<*9<O)d^k}V`%NWPGSS(=O?DYG;=Ly~4`vWBD$$s3Y5By&jWklZ23 zL$ZgY56K^rKueQBB!!kHhe#4FO%{<fB6&m-iDVK<C6Y@dnMgK~bRzjg5{hILNvWmD zDUwu6lT{?GmL{)AVv)=usYP;&Bp1mpl3pagNP>|JBPq5tIYyFfX|jx@+0x`0Ni>pa qB-Kc+kz^y;#{bj(kLmU{O&??09+D6d79KV#Bw_UEu+d?oy#EAS!oC#% diff --git a/lib/pytz/zoneinfo/America/Monterrey b/lib/pytz/zoneinfo/America/Monterrey deleted file mode 100644 index 7dc50577749baded06400fbe5d2e8dbf2579253e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1402 zcmc)JOGs2<7>Ds!6EX{g!WLaXED*uQyP1_~nuF<N>X>HbrF3l6@>5H!ynLVpLRl_? zNP;eOqtIF;nFSK$B9fqpN+^ONq85Edl5{(r=WJWGi2lRj83tx`ey_L8?~9i|Hp~8n zhn3h5-{)Nzo~(>0o|0vCcTHSvtHieis|0tcBo=;9Ns+0N9RE<QoV`R+7QZ(s?|#Xu z?+;AsbdX*>e9x>I`k~j3_L;O}uk^YBWzrkRbVkonwLbrx&a7@!Sus6&Lso&x4)tmG z@&x6c>6V<JFqQMXMmE0rs&a3+C2wMZ@tlm3P1mMPe%oBxeB_xasEX8u4I`!~V~+Ob z4VdEak9tdVhbfT>T^e>umA)R*Tc_Jq*;v2c_Ox7WzcMI0Zl<g9-s4hnB0^RAo29BV z&Fswa%C7QoQyrBoHEDsyH@{HVE`4iiKPT$lKgW&#@j_kq^0KMFF{2yC@2frilX~yP zORBNyj#h_zRa4muY4+8tmef(%m+eve7oC>Y7{6)#)+KGBxu$KhN)CKlVGiETltYg~ zP5b!@-7z?2I=eG;SKkv85cp3%mUZ@vh-F<pBSDrmay=k8&*PQMBKyhm@^bBY|AK&J z4+i`X3JeYu5)2j;8Vnv3A`B)JDhw`8Z88it6gm_>6hah66iO6M6jBsc6j~Hs6k-%+ z6lxT1PHl1wc1~@241N@X42Ben42~3%43-p{6rL2K6s8oa6s{Dq6t+%nx)i<?!W707 z$_&mF(hSxV+6>+l;tb{#>J07_@)Y(?ZTb}cloOzw0p%1Z=Ri3L%2`lOgK{2}6JeYQ z<y08wLOB`6**LXNhjBhm?Gs|05#^K^=R`Rv%2`oPi~i^H3bC&(*}k@DPogW%6&vk| Li*+TrVncodT0Ul| diff --git a/lib/pytz/zoneinfo/America/Montevideo b/lib/pytz/zoneinfo/America/Montevideo deleted file mode 100644 index 0d1e565c02206b1746ef6609d694126ff862ad6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1550 zcmdthOGs2v0Eh8A<zq5UO^8U-F)J;JPHHfS>1bK`Y_fq$3tJT}D~g0_Rw0#(2-6mV zx7yeX4wx{|!zs&0VO+AL7bHDUq6a-l=yf{ZahZ$Sb}#q*F0-4N{}-s;xjo1F<BHLr z@NjL^5AO?`@?PhjInmhJp`QBAeT<jqUu^bXxW7_d>}d*J>f9%<eCY~Z?e3D-9<+w8 zx3|jf%MGD$utDBvt_$6)uai9o_j_*@t&q33)_Qx>(q!N48gIY5Kn%=H^$v`siaW>J zy@RQ}V({elthdt-%eSAFXT2*em+uY_d&ZM?%kgi?o*%yo#m}#aSyszlVLhtxL~J}N zBYN}g=$2#|{rt%(cW-;vwAyMh?dcv*+?<&rF4$<#7!8W}`ki*d<6)6lRADC`St65` zow3t<0%Bg~eow~fS8{$rv%R2vhg>jP;K@vn7n#l`dr?fbTqN`D#Un!^`*@07T=`X$ ztX>*kx%RBHDyc18npWX2{d_3A#=XN?Gqf$dcC5@P>nsVE_h&m5ZS}s&jyR|4K(Vj7 z@q@E&ONP(CE7rfhAktUkfA8OrJn9Pw#~=7U;0r3HTvH;ZPI^a0Mny*Jd*XCYzz2VG zM=Eu<%CgKEp;TYkaw~PO--=c0q2rp#Y3cLXBiAgYUXHj@lzKDf@=Ux}Rt2mo{VWlA z`pL}|ochTzZ#nvHnSPt>3jD*U^mkVb3mF$OFl1!N(2%hqgF{A#3=bI}GC*X6$Pke+ zB7;Omi3}4NCo)iEq{vW_u_A*t>d_*@MaFB?14c${)I&zbY}A8BMs3u?M#hb1;QX+R zoJ$WK7`ssq9vQt+4<8vn5`a-hfP`SwF(5%8Q6OO;aTs+VNF+!oNGwP&NHj<|Mja0l zkWoj3gk;n)AweNgAz>kLA%P*0A)y&{Y)EiM9UT%L5+4#E5+M>I5+f2M5+xEQ5+@QU Z5-Ad@QOAlw{U7~T!NwHmv<q`%egjMWE8+kE diff --git a/lib/pytz/zoneinfo/America/Montreal b/lib/pytz/zoneinfo/America/Montreal deleted file mode 100644 index 6752c5b05285678b86aea170f0921fc5f5e57738..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3494 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 diff --git a/lib/pytz/zoneinfo/America/Montserrat b/lib/pytz/zoneinfo/America/Montserrat deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Nassau b/lib/pytz/zoneinfo/America/Nassau deleted file mode 100644 index 5091eb5d8d3aedf24ddf9ee26a1aefd837e3b4bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2270 zcmdVae@xVM9LMnkN$G%)iXuf)tOx`SuoFZ|R2ZFlVtP10(~kz;PAJ}~oktng!kGNy z8U?l*TWMu&jm#`vjfpw)pEAd4_G9?5E!JAp$5=&>cYU7UfBUQd>V5m{@!kFZdIlOp z+e=*kxM}tu9`}NMd?)vmF7N)WHX^?--KhTXG|I%?d-R{F6*8IFpeHBl&D0M|^k0|C z&9(D$bo6AViM<iiuD+;=>$;-j_h>UsqAEdMlk3)MHNEn(%qTje66X%d^)rtsx9hOD z&9f>=@0aASUezh5gL1>EPM!MZGiK(C+jZK3W^-fv{raX*jk&oYPpA8G%`J<Q^{u%{ zCL=pn-Ih3JX2mC|S--kv_LVV}dGRNiGju^^9s5jff905(8+Ms_Pxq+oy6?>V9RvCf z?^$!_%IEZ5>93odyj?mc`myA?>vZnOYm)b4sa`PHCHcbvRnQ-j!sCmSr(=~A^<=2x zV9=E8oKy>IeCD2Y-zsm;0<)-OP?gR|F=eyA)@7r=n8i~c>GE?I&602Wb;ZEXa_@&v z>H7|TE=yn9rYqatm1Uj#)biTrWku6gRpkjwb=3-0J$}^G6l~Eo!`;T0vP}Dqx0;n> z^YyBi56J2-E0uq5i>!G+Th(sz%i7)q^+1J3);%_=)@P<kpzgd1OpVJ1?^|la_o=2n z>zuAXd(|{VkLt!F!)D`1x8Brw!aO+Gsy8?FnJxWmwDP|vTf17+wt`-HDCk#BDG_O| z@u=ppMhWJmso<v-5}H1)LT_iuj?phvOK-Vpy>L}O9L_Y43=HeGx~P$ZC-kFUZFaWx z>0Rk(%<kHDy*qkY!ezZGJTfGajEIU19+virM)i3AetF_jh1%N@lqXMSsgBJ)*>}jL z;^Y2<zgX;pu*(&TeR4W3Ar?FJjw^AsKj5mavTuEWN!Sbgi{ZrVF&<<<$cT_3A!9-Y zg^bG44htC<GB9Lh=nRby#>Uyf0i#2Phl~#yATmN^h{za`K_a6>hUsX>i3}7ODKb=K ztjJ)I(IUe|#)}LX88I?sWX#B*kx?VVM#k-E2ab#!89Fj{Wbnx7k>MlbM*@IE00{vS z10)DY6dY|BkT^KnKp>GoLV?5r2?i1kBpgUQkboc&K|+GW1PKZf6(lT3TpVp+kjNmR zL1Kdh2Z;_69wa_UfRG3wAwpt=1PO_fqYV=hCr2A7BvOtxR7k9lU?I^$!iB^O2^bPF zBxFd;kf0$^bF^VY;^t@rheXcNh7O4x5<Db&NcfQWApt}ph=dS{AreF+ibxodI6B%u zB9U~op+sWoXoHDF6A330Pb8p7M3ImpF-3xkL=_1u5?4nXSR}HJHnd1=9c^%t=px}o m;)?_ri7*mk{9nh|ZM*EXea`nUEG#K3%J&x+7Zw*5x&H>=bf6;u diff --git a/lib/pytz/zoneinfo/America/New_York b/lib/pytz/zoneinfo/America/New_York deleted file mode 100644 index 2f75480e069b60b6c58a9137c7eebd4796f74226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3536 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 EZ-WKA#Q*>R diff --git a/lib/pytz/zoneinfo/America/Nipigon b/lib/pytz/zoneinfo/America/Nipigon deleted file mode 100644 index f6a856e693420d6d989c45acff2c48b60db69186..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2122 zcmdtie@xVM9LMo5L`4TAloTnELB$T?_+h7LRG6^q)DzOfluSPwcta@Oq1~Vib)lsH zxJJWT?OJKg+8mkLv>KCg=GMxT)%rF1)E0kK*vIG(7aP~-`T4iM`m3$)?elqjcmLc! z_j*Q~+csBu|9Gyn-*9;P?csgqPJ1Oz49F|*|EkVj4mW=KtdR>vf64_lrPJq($TiuA zl+QaTKC?$<=-ra}&1*XAR7|ct*{5f``HabaakI|Z+iI@sxnE!37BVx}mgwBD-`ub; zQ{U*%FnNW3byNC;nU$KMX8qxl**{LG{PVxb%_HYj!O_p<mRFCeIdRY2x@$lcHvMFZ zwhZapf~U>xOP|ws<i25wOSbCb>5s+lYtsI)*QMl_1$ypqzm$$fRav4<0>|d7^4?`q zF_5P!V=+_J^_Q9#3Y$Auey@VXbIts!VYT4uEK@!ETU|Z=yIDB(p<Z<6yt(VUgsvI7 zD0hFbQ{S`y3%U2DO}e)89a-GBL*3W#yew(isOrk&QeU@3)lVKap|TA+G`i1(vli>{ zu@19zqDU`$d9N)0s#Zm|x66w63RT0ph%^qSsRwGxW#tp&YE^!YM4Qg4=+vaF4!)&U z|2)I2DLA9ooW5k%P9N6IhepjqWBc^FzT@WM;SRmN<$&3cSfN$qUD?>*t~Qko$|JFe zYRT%B)=;@>ooJR=agK_8RwHd!O{%sd1+r!QOVvKO$aI{$q#upvo5zMmb!XGGk^RT@ z<3Vk@IuGcrxgVKr4Ly3>^bZoR9#rwM5$VqBR^7t~rRQ?9dNQ$Fp8C2*ZSRfA(<cg4 z@A|Opc)?StDXA$}q;dbJpJeh#t>-0^pPWibOD0dg?WMc-Om}B5k3_xtI(t^x^PoMA zQTvznycBmu|HTxN_UXE~s}`9AG7DrH$UKmVIPFaMA*O=N1(}S~&IXwdG9P3@$c&IF zA#*||h0F?>7BVknV#v&psUdSiCWp+<X{U$G51Ak`Lu87`9Fa*PvqYwe%oCX?GE-!# z$XuOvvdC<mcDl%XkqIL+My8C+8JRRPYh>EUypf3`Ge@S5%pI9LGJB_;J~DqK0Z0ar z6d*Z3l7M6ZNduAxBoRm^kW?VKaN1-b*>Ku)Ao+0GgdiC~Qi9|JNeYq`BrQl@ki;OF zK~jU{#%Ys-WXEaKgXG6)6NF?4NfDAGBuPk?kTfBALK1~!3P}}`D<oM+wwyLyNWPpl zVMxZDHf2c8kfb45L(+!i4M`l5IV5#R?vUgm*>l?TA^CIK1R@!9+7u!=M3RVP5lJJG gM<kK>Kgp!e)-z};S{j)bs0vh+Mk*@<m4OQ1-!F??7ytkO diff --git a/lib/pytz/zoneinfo/America/Nome b/lib/pytz/zoneinfo/America/Nome deleted file mode 100644 index 10998df3bbe67aa8a02602301d10ec2b2c33006b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2367 zcmciCeN2^A0LSqQ0v8C15)_f>@eShD0|-J<UI-&Z<Uy`CAyO!aVaZD{hA*I)j@&BE zHiy&bILB67wlm#KW6@35+OpgYw~}0xZq0PbTCWz#?R-zR{;2t1o@dYVIlH_6{=V*- z=BKh8|2kp%7hcY8{c=y-sz0Z97W+o${nR#EZ@YdUU8Saux7+^L-7d#Kl^yVWt}C!R z$DY={(i>D?=?ku0<P9mz_Bk_Wc%3h<_k~4W^M)P1Y=?jUh4;3v$9xfEzq%s5!@k>3 zjJfW3<D4C}Z`gI`&Ufs)HoWSZ-h9B0uHNE`Debjq<k!1qCfC`qb9=nA!iwy;z_s4E z)aa<VABxoMiwk7@=zMkeNvBLW5~A)ocv)Iq`C?ArX*t(3Tg+Sctu;UAx>&I6BP%iH zE0L7)rj;}`sglEAvXaNYR4G^5tkjYBRNC1WWcuJim2tF1F6@0yiC1iy*(60)N0!Vk z*(MfMMa!I|29cZfi(Kq{Smeb|xAHFCEAl6Qw(k2dT-<;DbE`llst1NoSP$+FRZI5o zw_L4PRN?lwWl_b)>Y;{xvUuTJYH9H%x%9^iq9lF5Dmi;fltyl}N{<eSWmjvhvIFm{ z<)3xS^38|TieojhqIQR}_ZG;?f+n@HD?wJpmnyf%A>DtZDo@Tu>G|dnu{yzTt^OcI zR8L*7YK9`jW8<f++U*nK@sS~GO@mLY9qhO2%D+?f{fFce>1WiFO*>>mq*9F~O|tRo z9@UgoDw{s(RL#y**?ice)?JE}Pw(vz&-hZT^<Aq(i;A#XJ&Q%#t_iC>cb0gz)n|3Y zOp4BmBUYz>rs~Q&BfG|bQ{Ayjc8{D_8?Nn<8wcN2o6dF0&AqRwp5q?byQWWV8OW0X zfztwlf^YVOggS5G<8T~naX9?`M%xkO_jl9<gqm-J`6bHSqy7HAHb<=AziVqiyt&U= zUha0RsM7DF`nBDTB7H9^b~~!Ig=P9uSf+3K1n}Yfb#edq(!%v0csVcYmwTdLpCu=+ ze~LZ*6{E=Vko6h$0+AJ>St1wK$kB@gR*5VVStqhkWTnVbk+mB2Vv*Gv^>UH*A`3=V zj4T;hGqPx8)yT4ubt4N$R*ozkSv#_LWcA4Mk@XvO0gSo=NC}V{AVol`fRq8L15ya2 z5=beKS|G(hs)3ZlsOy0g#HcHRlmw{>QWT^rNLi4&Aca9HgOmoT4N@GWI!Jkt`XB`| z>IxwxLTZE*38@lNCZtYCp^!=;r9x_j6bq>qQZA#e7g8{zt{74>qplfJG^A=s*^s&+ zg+nTbln$vKQaq%3NcoJqen<h0x`Idvjk<<N5s@k)Wkl+T6cVW<Qc9$jNHLLWBIQKt zi4@eRD~goVsB4N8)u^kAlohEfQdp$2NNJJUBE?0ji<B3sFH&Hmt}s$!qpmShWTUP! zQf8#iNTHERBc(=ajW>!N6c`+Gv&R`0dJCVwlie`!KZg6Ca^JXxlk|Mzo>rcnk(D9R M$}=-FGBZT@pJb_W#sB~S diff --git a/lib/pytz/zoneinfo/America/Noronha b/lib/pytz/zoneinfo/America/Noronha deleted file mode 100644 index 95ff8a2573f4dbb03892a576c198573895a01985..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 728 zcmb8sJugF10EhA0BBX?~8q}dF76~=9jm8&1Lehw^2~A8!Hp~oXVqiF<_yXEaBoewc z!P4l9mxUTMp_(Gcd2V8|)V(?R-Q4BgJpcH@Y9i$Pxti=74%d)9Ja_CJuPi6K#<R)q zkV;hsWV(<Lxrmmz@{7FfzS6g;Ns<3-m4$<>DCT!%DIOGW`xX7(G9#*|bMm88sM@Sw z)-Fy&-FL3*N6+fBT$5irYpN%+CH)&2)vE`!96M8e+l#WlJ*@)sULCk!62b7J4qm53 zxSG_F<hqC!Z)EhzqsA{QI=1GhO=FDL)6nfT=I+pOjH$eOnvHoVIc-zZac6jB)SllK z@qcu&pEk5X`ncLgNGGHf(#zF0L%O-zc1S;@A<_|PiS$I8B3+TTNMBdm80qY4TO+-Z z=8Q{sq&?Cf*#OxA*#g<a)oz09;%c`+_CYp6c5<~_A$uX4;imnUhF0@<8`{kmvl~u? Fd_UIk9ytI2 diff --git a/lib/pytz/zoneinfo/America/North_Dakota/Beulah b/lib/pytz/zoneinfo/America/North_Dakota/Beulah deleted file mode 100644 index 246345dde7cada7b0de81cc23fabc66b60d51e79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2380 zcmd_qZ%oxy9LMo<5xsH)JDC^)LTO1LAfOnK6a_?(1mP7>2%|v6{_yh;B!+lOg7Khv zmWxp)L7~-}33GEeHqy-2R_4Oyv^Gjx|19dR++u;;&ig#=QLSfv?|%37yZ8C$?Ok8L zF-83C+~yx1uFE{UM=JBxb0|Z<Htbft?UB0gl^@l|oxyr^@l|KEI!ydI(I_W>C>K)~ z%H*|=GQ{*yy7a#lDMWXKbahONfTyp?8C$|tVD$n$vv^9)%9_;fq%q}wAyo&-6DsKN zp!E1&SJ!>rr-LuPCqho_(Q}UXiR<6)mGb31;)c%W<lN?3abtaroVPAdgy!ET=cmVt zn__R5VT*Frf*_9!4~S9Wzb(-bmxEN~=xn|4<WDMU@QRM^JELxHS}1RM_J~@v)-M-t z{zTlGdP&|^@UB=AdP>H`X%RF1m5%jn6|rL{b=(!NSUU2Cjz8z9gr4Vh;?WYdthG)j z?OUOe>)K?>w)tv#-bT5iWJ0CJ+%H$Ae4*}e$H}yaKSbL1!7_dFqR9C07kTHG!(!Ed zM`sQn5O?kUS>L^Xx5#SvRNvFKU)@_YptEasshnjm>fD?SD);x(GB2S;<ehs{=7&^? z{G*++U_47a@ajRm`h2A-Y-v^x9!qkHDl1fR*IehJRhg=!@v5^XGFo}po_D-cE>)U3 z<dl9JCCZ~d_m!U(V%_vH-}?T`qGGJuSK0P~cz9%|ud3#d*w9nyd!+DvRo&6-Jett2 z9;>TxYC_soZC<8RJHAQP#Y8)GX9`rk+vU_Bj#rz%`^I^^D_=Zu*6(|=X{mVX$R%Gx zX^3$4p7L!;{Z(vjIOy9J`kB~X)ZyDcJ+7M4`kkh+vua0JyR&0tP&H3%a(4FYQ_ox| za9Ua$)vlp<r?sj~?e3oA1h~v+Mxce)|M)X&=D+&*{k<;<;rGAO>k9Py4`?ylz6aZH z>GvNWaLwaggsm?0iozW8tT9iSd5XOv+x*Tpzd4uv2Jb&uo8MejVDJF4I%Ijs`j7=8 zD@2xPHETo`iL4S?CbCYn3+08Ca%QQ(S~(VrtQJ`=vR-7t$cn9I$;g_mX3@y1k!2(6 z=2$qga*m}VYv))zvU-l?BkSiV08#;t5+F4|ihxwXYRZ7r0VxDh38WNAEs$a$)j-OD z)PtiSNJThGg4D!nih@+dYRZDtg`+S?WsuS!wLyx5R0k;!QXiy1NQICRAvHpZgjC6D z%7oO3qfkhtI7)@oilbOawK&R!)C(yXQZb}tNX@LKXh_wprff*vtfp{C<v2=*)D9^g zQaz-6Nd1rkA{9hRh}6((iilLvYRbq_N2@6$M<tO`a?}zjCPy`qa&puYDJW7=q@+kq zk)k41wVJXbb+wwpB9*n8(jv7*ipx=5q`VyUMGDMOVWh+yHAafeQDv(sGg4=(DKt`P ut0^^7YoypnwUKfo^+pPg|F4RBOxRbNz{eLZPfSTniZ4t~PE1Zr^85t^>~W3& diff --git a/lib/pytz/zoneinfo/America/North_Dakota/Center b/lib/pytz/zoneinfo/America/North_Dakota/Center deleted file mode 100644 index 1fa0703778034551487b7b55d80d41ad220f2102..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2380 zcmd_qUrd#C9LMqBLG%EDJ((235lRa}0Rh8+q%0r?c?9*yA0Y~Z5X<=a2NX5>l?>*E z`D`x2{t!5FwK8OGhI1p#YOQoGY|hq3YRego^{l*bf#>P>{@r%vMOS^#p7VOnx&OQa z>ziK46#qJ}{)dO_)(`JKrN4TQ<l66_@~Xa0pWXlVZ|b{!k@nca8*;2JM*MxV#hSWa zDW)$~Shv2(6~W;g%l*(NL{FUM=?aRF9k;9*TVhpc-8_3{*|eIqV#@ZWk1Ox~EIZs9 zRpCd6tO)0@y5n-c9r@$uB5HK6J^N(8xbvev%R01I+_mp@Yff8(xVx#|np;&QqKluh z?#W3O_a;AN#VjaP^TH#n*pMU@`)8sZcQsu3#=`9Rr+-)RLlbsF|2cJE>wN3}*9O&s zb*{B=(^>IA))ni)l21iq^cgE@ku8#fKikO>TSfBtX?xK`Kr9}4Q>9$kAX9sHsI=n+ za!GrYN<Ww=Ga6e(=C&}ow5U=%yk<gXCFP1`nP=rAUZ2R0yY6IP3yPenZ=Kw)FN;Ti z`ovj2IIZ$dz2Q7|U|c=^j&xQukE$m+_RA+r53Bt8W?8VLLlqXRmW6+PCW=xwJ4F`` ziQ=eIr}+48Q8Jm~JpJxlYURaM(!ZxotvZn|OKYlCS@#^ddU>8&({e+u^(Cmlx{ER} z?NQ}f!?OI>cu^VugH!pH5LLkw&ia9?qI$f?sp<GqJTtP(sjWXEp6#u1p7Vd9>blzG zhSUM|d}Fn&kLpwnMR~Gea-(WYN|24`N>r2ABb$z<s7=>?kuP=^i<i#3&gRy|;^o0B zPIGybkO$5<TeAKTTbn;{wncw0wwHD}+k=y;HG4p|j-OXMV>;!|ks;M~bEDkVdr<AZ zR3i7Zx2RW!Q)GK>g?hDTwhZy;&x}w5ueb4;HS<6FxNd)#5U%@ipC{CH-?K%Sd5<*T zmg}B8=9$a8h*{|m2!DZo*6OE1KV<=tuYc$3-<-#ML*O>n=IW~rjXZ{|4p|<uK4gK& z3Xvrm^%{{yBCAA}iL4XNLV01OoL(xhR*uCYt3{TJtQT1@vSOoNGO}i)UNo|5WZB5N zITntroMY+8+Bp`Fte#`}$oe@7fK-5^1V{~#A|O>T>M|g8Knj6W0x1Pj3#1rGHIQ;3 z_24K7QW1`lAT=@Sq99c<>arkp;V2AJ8Kg8wZII$1)j`U`Q6Hp092G)J#8D%pNE}r% z>N0WE2`LmurI1n~wL*%8R0}B=QZJ-nNX3wnAvH7VqH$EssLRGtH=`~bN9B;xA+<w_ zhg1(KA5uT0fJg<A5+XG;>LMalH0m;P)X}I5$x%t9lpM80ipfz;q?{b}L<)*j6e%fE zQ>3U!RgJo=NL`J(ut;T%y0l1bk>YYx7b!1CeUSolR2V5SM~#spb5z-=%Z$|7s0)o$ v+Nev7)EX%^Qf;K%NWGDQ<NvGT5jyP4bl_9`OVcvb(o_5y8EF}5=@I_`+R%Lx diff --git a/lib/pytz/zoneinfo/America/North_Dakota/New_Salem b/lib/pytz/zoneinfo/America/North_Dakota/New_Salem deleted file mode 100644 index 123f2aeecfc88f2e543f99d6a859e02788170852..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2380 zcmd_qZ%kEn9LMqBMfA!I?3allAe5E_0s@KwNm)Pyc?IzbC`3^pVi`aGK%$1fl3+e? zp3RN0lEBE-nhA3=92;R)Yb)o%=Cn3STmLNTtlVOOoPO`~v`4L;^}Rdyb?$xsc?UMs zZ%h+^JFoeNhwCs8@6p<PbsfvHUK{l4?zU*F=j9*uC;LOKk%iaQNOgqxbG%Vb{7^0? zFO|s~A7_cFflTSV87)L-l=QSuiJ)h1$Z1<6b#V1OYkKjdp0RSm@}`Vx?}2nHL=Nka z<NeZSAJsQ~-D8DbeourA@3Ur|?h!Y?)h*@Wed3n=FUZ-=wc^(L8aZcuz6dXPNY2fS z7q`XVEh84>>3JbO85tC(BY#_DMO_Wi(IYdh`DcF8G5y!9*q(Fx_NMvrj^|J41?wES zaPz0)&h#ttuEKZ3qVTgaF2NFUQ(sx}zHK6Y^o*5oEg%*Ty>2C5P&%pWMJxGaiC)rD zXQdokrc>)$W!m<+dTIVfxvXSdr^h`km#2N9@9`$cjHo|E#`mEzbK<hd`sf#V@0WvO zMW4^g9y}uMJNUD8|0{dN%7$V6K<feZV9`;XQ`4Ywm$d4<+%+oi_jg5p(iS`a!eLPm zR%92P+${>nQtd}xeMzsnxLW!5H0#x;QdCi8g)Z)xt=6o_)+LSC)!OJ-9awi!1tvYZ zG<`soej6joV?MXbKNMp9)G2#I?^RJT+G$s|ejpwl+GSVO921XqRoajH-`Cac&FYDy zUj1ZUg{lc_)3y28s&;IXu8WIRb>|9oz1O4ak0<KQ-+iN=>L?ITpLgsnO^d}dC$88H zrC~xHJZo=F|5a>jc*EWv{+ZZO)Nb#X8q-Y~y{c*SyxtknrgjeX>*n!IYFF1Gz57z3 z+SAggpBqS2EmdWDZ|5u(<T0OV!7jZ1>(7ko|G~#`y2pfYoVU9@!H#pp5;NWRQ1@Fp z&gnkS9NtC5Dt|!ubIr5XJZ0u74u~A{JIDOyJnlCH{=wQDb5+5ieaPyN<ss`s7Kp46 zS)!|1BeF<jmB=!Yb)vgaURWt-mI|zuW3k9;k>w)mMHY;#*wri<S+lEIG_q=B*~q#% z7LKf(W9i7+ITnwso@4pQ`Z)@KRDh!dNDYu8AXRWRWkBkH6auLPQVOINNHLIVAmu>n z!BG&TA{-?_YT{~&f>g!Tlm)2^M`4i4Af-WSgA@m;4pJVZK1hL(3LzyzYJ?OCsgkQH z6H+IRLLrsnC>2sGj$$Fz;wTqVFQi~d#gLLAHFGsZ<EWaeDH})KTutFPDu<K~sU1>0 zq<TpCkoqA7L@J1s5UHW7DI!uuS5rogI=Y%da#Rv2B}XlhVscayDJMrgk%A%>MM{d) z6e%iFRaaA1q^_=}ut;THO=*$ZBE{vXE>d2O`XUA9s4!Aujv6CH=BTo(DKk=MS5s)D v(ype|NUf1#Bh^OAjno?{IR3vX?lWOuZUUd^Uz(hjoRa8IO-)WsPVxN((J^&k diff --git a/lib/pytz/zoneinfo/America/Ojinaga b/lib/pytz/zoneinfo/America/Ojinaga deleted file mode 100644 index 37d78301bd100b7c34b183a7e355021f55cb366e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1508 zcmd7RO-NKx9ES0uS&Eomq99NrC=fy?XUxycGOfvUvUHjzGo4BuCQGN3!OD*VNz%$w zhy<n-BrPHo5i<f?*{(1bVL=vTA4Gu#kzjf|&utU53R?8PTz;3?%;tIB<$+p<_~TaS zUwFB4{qntfVDv+9@N50+(OQr1UjCGCEGFGIws>jI?hgq^V^lEJ=@nZ_KL*nh_ZaDp z7cwJqlaU$!U1YwGF|rn(i|lW!)Yj3bB4;RCZF|rsa=SjM?Y-?{N5d19*U>9?mJF%< z>Q-5>`KsDgP$hTAH7V!DJn5XjViZPfkcHEY#@^ZGvgrDNDt=ZWT`iqz-)OEZsR*e3 zJ?rIxoFY{k3YTRG$;y3jR=R)0DNouX>3N%Ec-OrV-icV_@X~}RzcptZnYk@0I;M=H z<CjHc^@vf`UnhL7Np-BVQyxzqRww)cS-rAb)i{e}&F7HvCnih(W3LKC#>v3lY<2S8 z7g^VH$T&3_D^7>9jQYVj(cp<Uf?ZSMOxmJxwqZmxu6boNmGp?Fr3Dqr9G0P(m#TSf zw`?ANsLsuY<oW)a>cX^FwzOSTtz+4;t+Gb7UrUf-;XKRq6A|`rAJgnl5W+P3l!!3R zJ2ym>Y2F_Si{U%W`1O0S%Pm}GZjsMhpuhF|`?>$37*ikBAmt$SAO#^6S?ZGbA8O(# z3aN^tETk@^Fr+f1G)r9@QXEHhNO>IfAq8?&h?K}tBT^(%B~m6*CsHU<DN?GXt`#ZP zQdf(Vi`0u0j8u%2jMR)2jZ}@4jns`4ZmBCrO1IRtBgI?l>XGs}>PHs9u>xcX9BV)p z!LbTt8OS=2g&-?ImSU;bf-J^TuLfC;rCtxRAY?_zk~r3cEQ(`Q$g()rg)EF?WysPj z_1cidS?bjx%d^z$Ll%gv5LqI!Mr4u5D)IkcX0)DdhMsGZE7j((r6jrRcAMRn68#fq Cu8G0` diff --git a/lib/pytz/zoneinfo/America/Panama b/lib/pytz/zoneinfo/America/Panama deleted file mode 100644 index 55b083463a8b5b19d62b6f2707665c08eca5e65b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194 zcmWHE%1kq2zyQoZ5fBCeCLji}c^ZI3_m{*Mj7<OkZ!KV80Fn|6EdT%S+`z!$;~T=@ g48*R%AwX3i5JG~<|A8R0T}=Q)6K?|-&@xjl0PlY+G5`Po diff --git a/lib/pytz/zoneinfo/America/Pangnirtung b/lib/pytz/zoneinfo/America/Pangnirtung deleted file mode 100644 index 3e4e0db6ae0a5a704f8cdd549071cc5b7124e2fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2094 zcmeIyaZFWJ0LStBf|Qu7oKYx92^pa9-UEAuh6Mq=J?+U2AEsb>H0TYXIGHjqLt8q_ zfBd5cZZ(#}Kb);u8LrmGpfzi?)>WdlYH+H>A8oWlwB<72?R-zQ+S=OM`m_6X?&t3Q z^R~D5`zD&(AFgozb}cY(I9&V8;kjgJqVeE|&wMQ|7XBzNiW%>xP`LV1am(nU_SNe8 zEqPvcELYu7neuKdzM^spa^1xlQ))@zio0a?7m@qbl$&>9QY;-CbMs#t6*rw2a&O-I zgIe~~VYi^^6IHnDgm+8ioLau_Y46tT<Em&yn^%+?7A2WcuVivStoW|PTRGAzO2_YY z%LZFS@NBUg>RT(q&*g~nWKvh`{#mSw#`JBCUy4Z4N_~69h`3{ZmafeGTvbk|oYj{< z&}+_L(06_@sH)C<FW0_(P~CORlXt(cQ&o4pE^7`Q5cf1ZFKb(Oh`La>tgovT^>e3m zv~0VIjvv*rtQr+N+o{*h6sq+vJ}dA0v|7aXcgPLr3Pi)!xODr|#r;(w*|>LFY|L9I z6HV`n#N|1;DRNqD`u1AAIsd%c{O+vYk~*cDPmb%B$)jrPp<(^tNT=G?I-s`?Zcrlr zhTL(aL+mW;mk%Z5qBW~WwnamtZKhc!ix!IHM^&<Y-kfNEC138Eo)8`VYjo$wv+9xV zJpJgIan;q7((>4_dMu*!?ydp#c=kJbPeZTTlbVv<mHnc7a!mH*^oX93A=!JWS?n7; zET8zSO6>1T$|v8<7k%4e^1yLNqy?@@OHaG{A3yWv|NhCi=8vcUK>luy<NFg{Al=?G zeE+>sXMyj3qXO62H`%-iEcX4kUUioG{>v{p%Xkhf|Lgm*aM-DNAmP-w=4>>lu8v#6 zspps%wH)*8I05^Wzuo?PPW+2m_~v37$UKmVSj|k3sUUOVyO<0z8)Q1je2@ttGeV|> z%*kpdh0F?>7BVknV#v&psUdSiCWp)pnI1AfWP->Ht!9eI9Ia-O$Sjd*BJ)Hhip&(5 zDl%7OvdC<a=_2z*CXCD&nKCkGtC=(xvqq+k%o~|FGIM0=$lQ_1BeO@QkIWxQ0FnW# zNdb}rt4RWq1*=H|k_RLaNG6a}Ah|%2fn)<o2a*pYAxK87CM8HttR^W)R;(s1NM4Y{ zAeli@gX9KD4w4-tJxG3#1R)thQiSBlYLbLx$!gMs<jHChg=7jz6_P6?SxB~!bRqdd z5{6_9Ng0wet4SJ?HLFP*k~gbK9FjRCbx7`z<RRHZ(ue=&k6*#bG>wdyR+h$B1uKH# N(s+4!usj&f{1s}GV)+07 diff --git a/lib/pytz/zoneinfo/America/Paramaribo b/lib/pytz/zoneinfo/America/Paramaribo deleted file mode 100644 index b95c784234126f6039477b5a53aac0e4a7439d9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmWHE%1kq2zyPd35fBCe7+Yu}Yu}E2THZ%4CFyLD-e<U@{{R2~j7-cdEdT$X@nB#8 zk~Rz=whxFrcY=ZA|NpBO82EgALl^>pSl7VV*Z@RAj0KY+Bv=o$?LWu~AR6Qh5Djt) X$RLn&Ky*D&j6x@I*#MnrXUqiveL_zj diff --git a/lib/pytz/zoneinfo/America/Phoenix b/lib/pytz/zoneinfo/America/Phoenix deleted file mode 100644 index 4d51271a14ab85c1aac83f7145b9f7f8f19c512a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 344 zcmWHE%1kq2zyK^j5fBCeZXgD+1sZ_Fyk%As=I>^2SkNXjVd1Qo4W~PKCY%?)FLS>C z>6#0TQZm1OlnVTQ5y8O32!zZ)$jJ2n|Fm}u4FCVHUckum|Nq<x3>;uKkB@H%gRct^ z2Lo|<2+(i{2qD2q|A8Qmg=YhZ200BxgPaGVK~4nGAZLPTkW)c4$hlw|=wuKLayEzt PIUPh(=zK1qf6Tc6=)Qk) diff --git a/lib/pytz/zoneinfo/America/Port-au-Prince b/lib/pytz/zoneinfo/America/Port-au-Prince deleted file mode 100644 index d9590103bfbf4954025847504b9ac459ee9c1eb7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1446 zcmd7ROGs2v0Eh8AWkP}r7lNs2??KafXjYWLP-9XwdD9um(d<;(;3GXgvayF16hfPj zN?n8ywTK|B%qR%S2x=247PJUEZCnJ^6*o~a)A{bA0$1+3mwSGf8JNv{|6sV`Xn`^B zO4Oh5a8>Gu_mp$`xZcwCYgxw5iFC&|U;6A<Gvm{wFLQFjTs}VH%ew!-%)W8Wx1#T= zx$^u8->T4CwYqUwtton>*6!|8>rw}mGq*!IW1}J`DWr0y2So1YO=|sEx5%3ciu|E! z;TqW}+&$Zb=SrIJMygan`wvlAx>ao`{~)%PMa>;`B|g79Y?k<)zOtHtH2V|8zGAm5 zKRzQWGE-zQ^hyMO&dJK6J7WKfS*t2`TZL~;S=G~*RLzA^>%dsEs;wKa4i4>ABJfZi z>TME-^ZVtINI=vjM`eAfThxCE%ZM{YL|zxmhQvA1a5qaf&b$*%{adW&H?!(kSEki6 zJf&JgF-u+^Rc%GeYHuA-9jVW)&a!UR8T%-^HusCJ>2VoNi;C#jP1*fDEKUwxl&9Vo zi_<+3dFFAJ=&9W$&kh>Gv3Sv+J7LNE`|r04d5g!NdJIFKej_0sfBDR@G#-C)&q)6F zeNP~0n5I5T{Q9W~>Oa^p91a^JxPUa``fBLSHjza51u{WWvDLXC$spMv=^*(a2_YFF zDIqx_Ng-JwX(4$biP`GRkko8-Zb))Sc1U_ien^5yhDeG?j!2S7mPndNo=Bodrbwzt zu1K;-wn(~2zDUBhI%6bdBxfXPBx@vXByS{fBy%KnBzGivBzq)%B!6TAwt5E06m0bz zkV)9;Ss>Fu=7CHEnF%r#WG={Lkl7&9LFR)@$X3q?nUbxZ6EZ1VJu75d$h?q=Au~g! fhRhBB<H;rI4HoGw<^>8}1ujos!0UB+U7n=h#F2)t diff --git a/lib/pytz/zoneinfo/America/Port_of_Spain b/lib/pytz/zoneinfo/America/Port_of_Spain deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Porto_Acre b/lib/pytz/zoneinfo/America/Porto_Acre deleted file mode 100644 index 16b7f923bdbb7d360a851cd7a02ea480e0d0e1bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmbW!JxIeq7=YnxY(b*vB7%roy9@pmL_{)nP`pYead0ZQxd`GC9dvQ&B#Mh`J2^?? zbX(CWb7`xBn?(@+N@_Xp4LS)0y>ProhH&2#cWpB_Eq<+pdBbKU&F0*DTs+K|`g5Yx zURVCJlvnk<Q<=MwmByf~4z+`-zwOjoQ?h;*I=8h;dFQS;uP4L7TVmIFS9|iKZ(cRC z8QCn~JE8p)goOdsYNX`n(Wx4_F3QoJ7d3XC4U#jDYJC4drg|P!X2X-KUR-%vYb(~_ z>9Q=X)s_%imsKpTwSOVHIg9V}(y}WiCTGn~n_+&I`-l0>6*LAEhp5Ja;z2Q?xI{HJ z6rZTZh~h-CqIglvC~g!xiXX+0;uzIfQaqy?Q;I9amRa$o7*m`n))a4wImMk~Pd)Rk R4*D_cpZ@lO`5%jE`wO!17YhIY diff --git a/lib/pytz/zoneinfo/America/Porto_Velho b/lib/pytz/zoneinfo/America/Porto_Velho deleted file mode 100644 index 10cb02b8b9bba9ef4171080ba53fc1220e7a47c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 588 zcmb8rJ4*vW7)IgU^+H&0jfED*TdOdb6e1#{wkik<f<HhJtZcLkrcEz`7M5x?Wg-?^ z2*FMeEkZV?5i9~KcnOIzerH=D=q$st1VZwjV0~+2%K3HL<P$C@DVO)|gY)cDzHjX% z|Gi=guM7I3l2xVYOI@lZbb0VOEEhIa)Ew89qndh%PIWbyS8s<s;d{rn`UrRQXYYim zug>Ut@m4kbYS=jIG|gJS{>tUd(DkuS?me2}lf`f(6`9ffT|L%yZ!*?e&u#H|)?N)b zj<t6;Zo9R`&`B&U2hOBQ$xZYB<plp&DL-ggimZ*5#mH)8IkG-h3Lq6?r36v~DS}i% z${=-+LP#Z~6jCcziXqivr5sWZDacMLA|;WUNKvFJQWi7v-4<<K?@x98St&i2_J07r Cw*I;R diff --git a/lib/pytz/zoneinfo/America/Puerto_Rico b/lib/pytz/zoneinfo/America/Puerto_Rico deleted file mode 100644 index a662a57137b69e8ba445e899566222cdd422a764..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 246 zcmWHE%1kq2zyK^j5fBCe7+atL$T|JZ=)fiAF9nwp-d<p2W@7yR{}4L^!~g%s4=}L& z|9|xYBL|q|@$n5|a0~|G03Z$z0qSG~;}8<;`ws+F&!+AK(I9(4G{|l+O{V=^Ku4Ky F0RY1$N|^uv diff --git a/lib/pytz/zoneinfo/America/Punta_Arenas b/lib/pytz/zoneinfo/America/Punta_Arenas deleted file mode 100644 index a5a8af52c2f26baf6f85a1786f69593491ad5195..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1902 zcmdtiYiP}J9LMqhIcIie7dAu8ZSI%3{OpWvZ1Xc_b3bfsX(S4h7%>m5HWJGNmqij1 zNs{<&F56`JB~6%|^WZcqOKFH?A@|GQ@BJ^Hc;ta6&guVuo%U??eSZp;l&ng&{`f?g zzwq$cVjg~XuQacHB6r5o;-~%-<xiSUS{t=mwza8dYo@H7Y6UJ8mFmlbQv6raPs^*G z)q%Q%7G2kV)L;L-SU22G^f&iiFPqmr4}^S+bf~Oaeh+5IAJt!!-F#nLn;t3G7Qc4O znaZ;$S%)PisZLq*Ww;fiyiwPs_i=}c_z^EV-w3M6w)ZmX>}wI-*dk-90;0?D2HAB@ zt%%(f(s6kQMYmOTI(|f%=sx3!?h#k5dJfyBd%oGMdWA35y@HEX?`KnVLQR_L^PpJv zJ&>UKUCfgGOTVcB`x0ehg+~nB;Fg2Zzly>0+U1bmZDMGerH6jLA%?yEq=&bj6eB*~ z(j#v+s!=UxbkeC4YII$d9<yV+8hd!H9=Bqp5|z8<`0N~&yu3vEGFwE7oGm9rO&1em z>SbzpNTpd_W!k+6k^Uk{PdfKfOuiGNGxju#%(ERjYwZOwr7Eap{#`Y7%_W`fJFlkY z9n#a|_o|%nwKC^jsmhHzD04g9BJWL^oOykNm=!FNv+FH2r)H9z+vFAV{5$mgV;7V^ zyHzhJ->()XeUSOZn^Zy6Ls^*mOcY*xrHiKRQWdssyWB31E6n}t>v5ZJr|^IJ^?Gf) zai?XO_cbfbwi}ccVcWO070uga-l2di_SauR0V{US+yX1#JY&QJ^Q4%^XHL<4T&~|1 z{KX-g|JyFv-R~Q6(8y6Ehm9OJa^T33BZrP0J96;I(IbbC9KWLp0Eqwz0g1uU1c5~1 zXu?3^KqnA>5D6a>3J?nt3=$0z4iXO%5E7B22?>b_2?~h{2@8n}2@Hu02@Q!22@Z)4 z2@i=62@r|U(S(S^=xBmOqC~<(;zR;PB1J+)Vnu>QqD8_*;za^RB6c((BQYaEBT*w^ zBXJ{vBatJaBe5gFBhe$_Bk>~xfQ$e#1jra5gMf^JqZtNd9FT!PMgkcMWGs-uKt=-@ z4rDx#0YOFt84_en9L=C0qvB|W1sNA)V33hPh6WiMWN?ttL52qzA7p@#5kiIt86#wn Y9L*>p!-N&)<o}y-ip(&RmF0{43F9_LCjbBd diff --git a/lib/pytz/zoneinfo/America/Rainy_River b/lib/pytz/zoneinfo/America/Rainy_River deleted file mode 100644 index ea66099155ca930810062eebcfbc7fc99e97097f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2122 zcmds%T};(w9LJwSHjzZhq!0;wDG5CwU?`gOCX7KI@#S~`A@c=8EaR8LNz~})#-uLN zP0DmNj7GIqO^Ib#i|lo+mocQdTE(=MYi`t2d11*oJ)h^ctFGGm{`Wke|Nb}U=6pWk zbq)9Dn}6(T_l3jS-Qj(zx~sFVOOL(%r+j+3a>M73kLj7&m*mXG2{wMlfKEtyLlR7n zCfKLsnsXgG@tYGi@pQ9JI(5iSe&vu!I@)BfeWJ-sX^GhD8X{(Db)iizD>T<HPPI2= zrJ9ski)32-s7Z~RCaJ%t==AefByI37z486;CB5f6&FFehW<)2OnftnA*4p39>}~Jb zn+k@^&E?1Joa8r6=G=WYb8Jwv5;ohc;a;74vBb{ne_De>n<TsQ5zXl>mievKn%gl) z^6H}|e`mZbD6KNLuD&P*nM=&V{4ZqD_%u^E{byTvVazPP@|j)o$vJb|x3Afy$49m3 zodfpvgTs2qvtsXTJgs-NJt=oryr9eK8l`xCo0b%>l9E5(GNswuZRyZaQ<hj^%X;^i z@{v4y&vVb{^0O->w6{f9oXnMq+8SMXc#5oATBNI+FUgv;3=OY6E8)?AMhf~Q@>9C0 zO8?4MeQZqi*h#yt`v+4qe8kqaePr(K-)+~|y=3m|thF0LALz#R7TJ{Dt()s>q%Lv4 zZYeF2EhAgCJ~Kn=zbw~=@d0UgJ*e9*{3s6`E;A1f#O(IyJoC`;^R_XPXyo9B_ThqG z&5p(w?at&u(^S!Jo5n^oTG%bo;Q`&1vR`)f_h`%It+KnbL-%}LE_+*>_0hhdw63qz z$Bs;rxIkRss&U-^=@W~+TxCowcIrf6TrBqL^CsTEPxN=v@=(|;D|Tm%JC*LN47<Oy zF#&&t|Ah)Mw;jK<-vUtrq6I_^h#n9{Je?-|5UN0QfhgnYw1KDt(FdXsL?ehw44oiK zF|>lH#n20)7(+9NY7E^V$}zO_bm~F$gD42m5TYVPM~IRPEg@<$^n@tN&=jI7Lsw6y zEJRyRr!GWah{6z!Au2<3hA0iu8lpBsZ;0Xy%^|8YbcZO<(B9Lj&(I%|07eENDS+ev zk_1Q=AZdW)0g?zvCLpPR<igV>gOLqSmkvfgJY7N<8G)n(k`qW$AX$N=1(Fv?Vj!7; zqy~~3PnR4>c065r82RyZ31Vajk|IWqAW33m36ds8o*;>WWD1fhNUk8sf@I6nr3;cT zPnR%A#ynlhAUT61jgd7-+8B9*B#x0eNa`55gCviUJx`ZDNd7!s0wEdnbSZ@75Rybl m79nYb<Pnlc_&>>Hf~)63SJ7Z-K~8>7ZZMRWmy?&1oA5XN!8Dlw diff --git a/lib/pytz/zoneinfo/America/Rankin_Inlet b/lib/pytz/zoneinfo/America/Rankin_Inlet deleted file mode 100644 index 61ff6fcb7dc92b4d41dd0854a2c9e0be939be6b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1916 zcmdUveN5DK9LGOLPK}M64;Ugsnu+Lf+@a|CK$;w5pokAAf{>YFh^2gdI>ZP3x-tF7 z;t!V(tC2eDpK|J$Pjli!rCX~xhI4Ke(^}TtSl`l3myGNC`uR^=e`J69zWwfXyWQ@u z&+8RkR`*V>{A>N)H$1GtJ$z37b>Dh<lrwL|<M9o5hSrtKu*$GaE)L7^X{q+*tW-%E zGgU_n?3L7n5t{mEiWzzLiKcb_Wky~3QPWTTU^0$g(pO@GWc2Q%I%dfq^6J`)c5Ggk zyjJpw9hZDsGAHb|nSGrmD{+<0>TWj^?ibpL9UmHh*GkQ9Z7_lMh~_j^n&AF%8mf&+ z?xuk{si;CGFS@UJnbT!T?zcL1K$?U{{$|7X`efRZZ|wAIcVx!*U)Y%^dQJX?5A5qL z-DcKDYG>D9Hg7b)uXD<dnSz>njpQ_&!pK4`d~jBZve((7uESECRA!6Yw@XP+$j<%v zpqY1TzLxH2H1p2{wXC|zEI2Sk7tYK#i?%$}#c3HPy5yEddwr%n@0^zZk}ehLH*Lk& zB9(n-?6S6>q^kRnt#1BG-t5?Bm)CqIZ?#t26{T0r+j|>zWp<lcRa>PsNqfxdqI_N5 zv&PhBW@znqC8lnGPwS5R&DwiE>$?Yv<-HqmyDm0S)}Od*>&ufwTfVd#@_v_%^`F{J z$(^#fY_Hwi*JEPgHjQ=PFk4gh=+=%?rt$F_-PXF_Y`<NiJDRqbo#*`8w0x=Ab?7Bc z@Fl$Ptgn90{LBCRNA61`9>0Fs*DoIb{D{05kDov3OX6JyKm8^<7?grYBq{}q-Q!s3 z_|HZE#|_8bxMRpIL++XX<E9yR4Y_T`eM4@Xap#a*XWY9-ck__Dhul8o{vi%PJb<{s z@B!ik!wZNT3_l=_Fg)?-T!HxF(K!S02I3CHABaN`k035Ve1bT|@CxD<!!L+q49_60 zF?{proMU(gagX62#6gIM5EmgnLY#zn32_tRC&W>RryiZF3|~DuXBpmlbnY_zg*XiH z7~(R-XNc1fuOV(j{DwFV@!X?x9pbx3=RCuEkIsFD|BwbSIsj<_qX&>CFuDL~1Edd- zMnF0NX$7Pg9$hma-SFtz0qKWF*APfYAT43^1kw~nS0HU+^aauwMrR<cVf4nMYYwD4 z9$kAN{qg7;1nCf@MUWmrngr<*q)qU7?^B`+*Ay2nf9a$^ZXoC{4TS=sKrr!dp!?_} diff --git a/lib/pytz/zoneinfo/America/Recife b/lib/pytz/zoneinfo/America/Recife deleted file mode 100644 index c6d99b3ac9bde6bfb4c566e2b68ef1d2d99c1ec3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 728 zcmb8sJuHJk0Eh9n^${sii@~4{kr+rWN=h_hBw>(rz+e($VlY@l3?_+@gylvt$c?SZ zLTe&IBu&3C2@CPjwB+1#tEH~F{F*L(-{&8jU0Ue#{+ufN3zySvFW(z>j~3(c;CLba zGpZ7={qiKYD$;|;GW}eVnU;H<Nz90Bxmo58aw4DImxWkZyzd$PQ8On#Pv_-VV@MUJ zdSo$qCQ5;8T{<kP@=LA!-d<O&7dtYvcBk6ZfEM9P)xNnTJL*%abGk`)KCFnY$eZrE zNs7qlxgLyfiJ|<X9C`|>;gr$Q<wmt(jPd&_`uL5>k9nRkj|E?~F}GJ<-Na<f>y>@> zhIwOqV*lu3|Ju+7>EpDGkWNS|q?gk+L%KO_JER}d5b21tM0z4kk*-Kvq_5L9Mmjrf zYos^QocE+V(jMuLY=G>5Y=P|Iw3{HiIPEsbKFCJMPENZOvKO)$ZrEq3Xf>a|qTK>f JyWvPU@C%u!B(VSh diff --git a/lib/pytz/zoneinfo/America/Regina b/lib/pytz/zoneinfo/America/Regina deleted file mode 100644 index 20c9c84df491e4072ec4c5d2c931a7433d9fd394..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 980 zcmc)I%S%*Y9Eb7Wq@_$lyqJMVi$W|41r?0;Dt1v+oCsXRm>A5;gMUCATqHt^7hF5K z5s4sImBW-o-ctz1OIfDJyk9wlE5VNMb9AR0SH8o0K8Ime^L)c~(HBK>;#@M{a5=^1 z@}BkTp#6HRuUB^_((Lz*Rqls^2hPW`Lbp%db>g{K-MAZa5?2bW#P?n2({6_KIet0v zwK?4#sNZr1Yc}1X`|Xk8!U=ceX0J1vy<v?7Cn96}<JPy$caib7kWS|8S;_h=nQVBi zrfQpH`pJ@-xiKd{iigxs>6h7SJ!;nJl3)J^^zSb%GB@9?|GbIW^Zl)Qq0P3PSX3`Y zpWA<5KGsVQOYP-n`FiEfEqk^6ky^_rk@eeoYW-iXY^}O#duCF0hLh?-;M7k_>ZxBJ z{rIBibu5c`-rKG~s(IIv?!Slpr{XD@6_sJBEH$^*+^6PNho!{4a{|ZD@EQJm&m00E z5s(l_3?v8=1qp-1@il>vNWLZ%5(^22L_@+M@sNN>L?k2<6A9{Tq9S3DxJY1M6B!AO z#72T6(UI^-d}IK=W(3F(kTD>GKt_QK0~rT05M(6CP>``8gF!}v3<ntxG9X_wB4kL& OnDE>O6*LRG7d!-P)%6Mh diff --git a/lib/pytz/zoneinfo/America/Resolute b/lib/pytz/zoneinfo/America/Resolute deleted file mode 100644 index 4365a5c816c5c487f7ecc155e6c06c57e5b64139..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1916 zcmdUveMr=I9LGQBVl~0Wwrpljx7u>ndEVKqtL2_N5nW~vmucFw&1^M4TXkkN_Z6i6 zs{V)`#)j3f{%NB^)&r5XwF;sLvBjX09w;J^zI%Wo#oPDw+do48)Sr7FKVQdj-d~^B zD>}FCvs`&<FS<{7Sc7|bpZt4ovw4&=b>ZW88*aW<S1!XU!#24%EW;<I+SjvEC1un^ z9WkU&QWHjK>R%~l<gH#!>wIF~_~nMCAHHrf_MXx=V=v2F&3kp!>_27n@{{)Mye@gC zWVaoY{Ig_^ZMK>HohB=BiOuS6H)9_Z+HoD*jlXNLX16w&Kzl@Unkr3j+ZYYi#w2&u z5FKAsA@9w6pm~{-WkT*noj5p6!Xxk7@V$PS)O*2BK6g{zzxJb@a<I?jpV(qQ*xGGA z{7&sh^{36pEt~a|vL8%AO}$2PT1;VNh8F&PREn}!+M=#+r8ud~7PqgLlAe(L^!uG= z>eXplx}nibI}+5g>MApR$E!MHO1_!7<{zDvmSLi^uWGc<XUg-AY5CoBsYt(KE6$2k z_8+lx+ipu$_byxA@|(=-SZC+g?3V?t)plX&uV&GvMqQlUW|q`eX-(2bv$QB*m-Z|( zwV4@Od#S|K4fbi>Pkyuf-W~mXN3pE99JecD<K&Bjw`_fRlIYem_RGA7@>TslyDGU; zR+nwEtNVLQEZnBC?#pIv%0^w=ao99IUZ(3>x0&^Sl<0=0HRkJMer=jR$9%JEs3!Ol zUU=5mpaI_;`2Jb{f57*+FOhis{Au5yc>KT~c_|)0e#n=^UwP%fPqKqSDTqX(QZUOs zj)jf`7k!Q!j=OQkkXwe_Grz}8GwvF4+l>2$+&JUTA-B%BcaQGoA$JeCeaQVo9DsNL zae?6j#0iEM5H}cpKpbIs;?cPR@x`Na2I39G9f&^=haet7T!Q!naf;y;#4Uzj5XTsv zL0n__=FvIF@DAc0!#{|F5Dy_PLVScc3Gou*Cd5yOqYzI$I#(IKdUVb*y!GhZW%vtm z7~(O+Wr)uZry*WL+=loKaU9~gN9Q`kcaP3_hW8$w`wagf4PbNt(gH>gAWdL&0n!FY zA0Ul@bOO=}NH09PW<a{((X|88509=Pkd8oF!srR4DU7Z_+QR4yq%n-nKw87-jYroU zNOwHC_CWgM(KQItAxMiLJ%Tg|(j`cn;Qzf(i7s3dT)6zD;{&;YpuaQ}3WNf|#D4+9 Crs@X( diff --git a/lib/pytz/zoneinfo/America/Rio_Branco b/lib/pytz/zoneinfo/America/Rio_Branco deleted file mode 100644 index 16b7f923bdbb7d360a851cd7a02ea480e0d0e1bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmbW!JxIeq7=YnxY(b*vB7%roy9@pmL_{)nP`pYead0ZQxd`GC9dvQ&B#Mh`J2^?? zbX(CWb7`xBn?(@+N@_Xp4LS)0y>ProhH&2#cWpB_Eq<+pdBbKU&F0*DTs+K|`g5Yx zURVCJlvnk<Q<=MwmByf~4z+`-zwOjoQ?h;*I=8h;dFQS;uP4L7TVmIFS9|iKZ(cRC z8QCn~JE8p)goOdsYNX`n(Wx4_F3QoJ7d3XC4U#jDYJC4drg|P!X2X-KUR-%vYb(~_ z>9Q=X)s_%imsKpTwSOVHIg9V}(y}WiCTGn~n_+&I`-l0>6*LAEhp5Ja;z2Q?xI{HJ z6rZTZh~h-CqIglvC~g!xiXX+0;uzIfQaqy?Q;I9amRa$o7*m`n))a4wImMk~Pd)Rk R4*D_cpZ@lO`5%jE`wO!17YhIY diff --git a/lib/pytz/zoneinfo/America/Rosario b/lib/pytz/zoneinfo/America/Rosario deleted file mode 100644 index 5df3cf6e6377be897b4d09fe438ec75eb8c15ad1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1100 zcmc)IKWLLd9Eb5YZK5$03@9if60xNuA{=dF#6RVg3{^2p4Je%yL=XhU!3v_$#YqTA z5QLV-c2zuwYH6cAjWyb#1X2;fA)vS^A~D3OJ%7(RiHlC=<=)Sc>4oo;9XffU$NS^A zLjK|K>zBiQ?PYn5U(c)i7Y6+Y8(!$CO?iK6@r;^YchO8wPUxA|J->A0m3sL4oq2RV zqGxB;`(`MwACHduPj;16`BK$9-PqJ~M}z*{aza)1rc9-NS3SQ{@aHqP^!&%GW+7wM zg8pD?@uXf%A2*FXVbvIGnlGVKs@W6uoA1YTYfaj;DmC@BG3+nZw(D=@r1@Stq<-8U z^p{7H%3ka9S56$z;m*^=Erm(l87&mMQlE9#r*p;b&8t;+^++-9SwF6K78CWF+IsVF zEY*Cbcg!aA!0;+P@Fo}Aw=}NzmyX49*4jW@`(Hkx;IF3*+uc26ZMo`s?j5wYV!W`m zFROtLYv0xbQSM&H!A#am%h{&-<n+q<eUt1ztRx?7&yL*D$Xdu^$ZE)PPFW9G&?ze- zOCoC`iz2Hc%OdL{3nMEdOCxI|i#ug?WO=8oj}(AZ;FJ=O8k|xDQUy{5QioFtK`KE? zaY`*nF;1xlDF>+sDF~?uDG6OoT%;(8-=iv|ETk@^Fr+f4l!nyil;V);oKhZAA5tJv dAyOhzqf?4Rszl1foP65<uQRe+n(gn6`~;>^?DhZv diff --git a/lib/pytz/zoneinfo/America/Santa_Isabel b/lib/pytz/zoneinfo/America/Santa_Isabel deleted file mode 100644 index ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV<wIXJb{bS^oTWhSsR+x=3Q(zcHtQD2x^t^vvcGX?$clJE5-_G6d;`8?J zsIE+N{_)JU|8RIZ?BPA~wccM_x*7}Xx~H3_dMnH8-i=ny>9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_<F$=t4Cy7@@9=PN^Sy zep8cY2i1@5=hgg?ZnNP0fC}$#Hw)kER*Smc)arP<y6#!giyQ0JlIp#BY3Vi<k>}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*<iwcnXLTDxRpVV}9P{5i>8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;<SEq10?`P*NO|WBl8u#eX%{lw^J- zC70Lh?JIs(+dqlXrL*VMj+3+czTtP&&ejoqf8X<}to)3AptDi!@(r5@pXrd@$^GV` zs{K+Pe!^6EOQmA6)l|jjNYy~4sSb^m>Nhr-n$dtfe5^u0@<oi=)8N&QcF(HXk_27X zHliNOny>fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;<l1wuJw<*7<2XTo-~N9wu7G_Q7&0<sXvo-*!6BnVhKG#L)eaCDAu>c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX<orL;?u~5(^|4NHmaexY~Fi0dchvK|+GW z1PKZf6(lT3T#&#ZkwHR(#0Cit5*;KwNPLh0x!MRJAwpt=1PO@}5+)>0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a diff --git a/lib/pytz/zoneinfo/America/Santarem b/lib/pytz/zoneinfo/America/Santarem deleted file mode 100644 index 8080efabff78b5b49d544f7ec784bf4a57f5099f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 618 zcmb8sJ4nMo9Dwn(jZ)%kadFW4SQQTAAR=<=(m@3wAUHWGf{QMSOVPEv2s(6BryEmo z$W{agL3Aih?cx?t!AEJ8^S-P?p%;$dks-(bi?_bLIcfb{wt2&5Wz6Q>eR6e}_50S| z{NEK-d|!~))niefzLDi9Eh_`BL8Z7Q!seK)o<-t0ypT0-UVNPP1fLx{;w#vb-@W6i zzB(i8r3cY)YC+?&Q#GT0`IFD9!P|2=bnv1wg~edl4b{lez8vj(QrV4NnbTS)6D^)p zO6xn{vb27@PuN<Q0xQkgvX_=UYeKkYPccfoyf#KQH*3a5Mvpb)BMFcUNQzjK14$BV zvLI=YJV+uW6Oszag(O3=A?adGJ|tnR$%v#xaxy4Mk*r8sBrlQ}$&94NocTtJ*okC& Mxt&?_1Lj=k56syJ*8l(j diff --git a/lib/pytz/zoneinfo/America/Santiago b/lib/pytz/zoneinfo/America/Santiago deleted file mode 100644 index 816a0428188d99f437004312ee73c3860ee0f54f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2529 zcmd_rZA{fw0LStF<?<8{2|^~Qg%n1iaDc0T0Uk+IQt+1eL{~C(AZmcbLn(!^l~`LO zqqU~Z>``6Q16DwjdDa7DQQf;@WRtZuwbh!8o-9po=lfs0>Q(RB`rrM}=kC6|+wXfy z%c?3et$#eB<`-U`m(0ue*xlx67fTlJ_ndbhZ2orOkhMdr@}_~Vraalb(DI&GTdUtn zbh+Qoc~!pStn~K8kLaFr``x`)J-Tl&!96s+S`Kah&ilpLcKzkF(`q<#l^$;FkXL=V z@><7b<sbS~TXkoZqrt5MWRY^N%FuxcDJm#`g$%YLRY>?r8S>4T3cVI1uRG;aVWYpw z@WU5HME{74Z1aliU+j~UwsecACx><P(oQkCqDRLhH;UNBFX$VhJJgg}yY!UtdNnn8 zy`Jh@t>P{$(D7Z_YTD->IelNensGc|-c<Xiy16|;CbT%k%$fk1m~%zkQgTk-8aFDE zvMrtT$0;%Er{DE$XAX(vOCRXl-|JU*jJ&Q>UOuR1_q6Fbt&gkJ=eO##jhmF%y+@`O z6sn93RWdVgM7ZQKIX8Tfm>1bAvx0|Jwlzs+e-bKkzE9EfkNzO;JRPZXpBfT*hsSjO z)?;Epn@`J?AFG91PUwQnH`QHBpVNzCo>7JA-LmlKTD2s)Q!W_`5KG4!WzoqRakp=+ zT-Ix;<z4e-@j!?uakuIf2aYLs!5Mu|^Rw#Sl;7k^Pn{|a|4OdOx**mZ|52~I@guRW z{WD#5>8QA`MrqHHO0~YeS(ooEQumjZ>kU;Y>H(M38;c^<rr1QiIeAQN{v}XXOpXv0 zqcdgY_(id$-zlqnUa|GSdAY5tTWoJ0ln?If6xE(1vSz1JwYg8r9c3M=F6j;Z(ENH; zAGB9D#IIHj=eOy`%h{^w!(#pL*YWC+*V6RP_p`*VC!%%pD{<n{>Qwny>lM*b5+z&w ze!nBY;dBHBOnjUH&LHy!hx|uA!G3@LyOw32fqs9VvO@j-L2X5FI?Orjbwo{^{Jy-n z)LLoYIbyDPUFMxwqQhaPW*&#5^k3}L{6+q%Ju?Q7og!OB_KIv4*)6hNWWUIUksTvj zM)r(s+ScqE**3CoWaGAG=g8KPy(62qHM>W)kM{ogLIZeA2Y?nJJwTd(bOC7t(g&mw zwx$zEE0A6w%|N<=v;*k}(h#I0NK25OAWcEKg0uzc3(^={(;1{Swx%~obCB*J?Lqp3 zGzjSs(jufsNRyB*A#FnXgfz<5bP8z|(krA{NVkx7A^k!chI9;R8PYSPX-L<Qwjq5( z8i#ZaX&ur#q<OZcdr13`{vi!SI*7Co=^@fYq>D%!kv<}gL^_GI($@46X{N2|Celu% zpGZTIjv_5ZdWtj^=_=Azq_0S0k<KEmMS5#%nu~PT*0dMtFVbM7!$^yf9wSXgx{S0L z=`+%3q|>&h)kv>xO|y}1Bke}|jWitTIMQ;Y=Sb6$t|M(n`i?Xn={(YUr1!R_`AGMX z_9OjA?f~Q-K<)zMK0xjS<X%AT23wPk|Kt4-mTPW~e3v;h^Jf?5q^76NOUrQ0E_VI> GE$lDZUi%6F diff --git a/lib/pytz/zoneinfo/America/Santo_Domingo b/lib/pytz/zoneinfo/America/Santo_Domingo deleted file mode 100644 index 4e5eba52b8a86c379efae694a296d51e1008141c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 482 zcmWHE%1kq2zyNGO5fBCeK_CXPr5k|6uKP|2zc{=v{91n4;s5fF7Z{wYPcXJ$zQFja z-h;W|Ljdz#77x~dO98C=J3ZJ9wgj***mHtYU|N9X?>!H!G#@4~GBYu=z#%I$>;M1z zDi|1mWC8=r|NqAiFmnF?zjFfv@BjY?b}$NpL>R<<d_x$5U4VqE3lIl~Fz6bX7#lD+ z0$Io)gap6*2ZGFYSq=~l@)L*#`3p>g{00Ib|A7F|k01)<PcRMiD~Ja97fb{F45C5) i2Gc;lgJ_Wd!89-oKr|>EKr|>UKs2qxg9{iyCR_l1#*DlG diff --git a/lib/pytz/zoneinfo/America/Sao_Paulo b/lib/pytz/zoneinfo/America/Sao_Paulo deleted file mode 100644 index c417ba1da757e94b88919b05df8a21b35a5bf66d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2002 zcmc)Ke`wTo9LMp~a^x({Aj(30+p@7d&-dLeJ<XOn=C<5zEf1TkY&o|%e>5G-re&R0 z77QHrYp@_b_(xbmeuBa&C`+t4B9>ymhMvnozf4Loy-w@;JU{)}AN8N#@!jJXgE4qN zUy0i0#z^2_&o%BRygW(w^7;BV-)sG_XI%ME&!6+mzH^1TKh-9KvG;ZGOh!*k_|Bf# zw^UAEzFAXmrR3DfZXHTQ<wEUf{e9Fjx!AixFO8jK($zst_kALn++#MgD{U_SlA~8T zHk*n4+jUa=*JiT0$4Yd-Oxe<`cZ@k=@@j6hd8gOO)LG~4)Z=}UKV_We9~d?Tfpc2$ zdWYQo^9^>|>WB&Uex%bI4@<~&>5PT%h$c5`cw*c{9u^zTStW(BgchbhmYFhHXAZ11 zvo3yWV?8g)>{G)!r!#2oJ(9I^TgICE4)4&S%HPcWJO0vnp<`zLwzMv|<yR@LKBUE$ zj!H><u`L-oYf8s&)6%_fNLgm5E$@0-9vFPlE=s;;D)u+pc->Q`vb)SymA@(vzR;l6 zGTS^<U#g1(x0^(9fhK;6$&$P=x}^U$sTp}zYj$0crKbn9_LY=8lB%<huI!b^4lc0E z%67`*Z{KO_!UxS0FVENI<Bh3r%G3JH0cj{5r42_{n5Mga(54R-n&zB-ZSJm@Rp0N{ z)tgFW%|M@BTbnOW?s?a?%o#1~Iy>z831_6WWvy-f<8x`Ns<Lh0?vV|#HM-%@XQutu zN^Rf$qijs8Zra*up84h`eRlbJvw80YeQwcqdA{o_9XaCaer2=AD*}ORc6eySwb|^K zUj(kJh$jNmHR9f=J5Zj)f8X;~_iO&1v+m)hk-PS~+eYpixpCyqz3$eLd-uAVNA4cE zedPX;29OSr7LXp0CXg<?t_`FQuWJP91Zjo;lU|T!kZzE6kbaPckdC~rC8Q^>YYOSg z>)Jy4^18;5&XCrS-jL>y?vVD7{*VTd4v`j-9=)zfq)V@B6X_Fa6zLRc73mde7U>pg z7wH#i80i>k8R^;Unnt?zy0($Ny{>VjbEI{occgiwd!&7&e`EuY9YD4K*#l$~kX`V) zZ9w+H>ox+}39s7<WG|4-Kz0M!4rD)&4MBDU*%D+=kWE2$#p|{O*%z<d7-VO>ZflUe z@w&}Hb_dxWWPgwiLUst*B4m${O+t3b>$VBmC$HNmWT(7ttB}3&y3Im%3)wDYzmN?> zb`04vWX~`;?VldDB+uKcyKemT|FdwpbKTk%McwyEQ7|43hr%J9p}}}06y-zi-z1n& AE&u=k diff --git a/lib/pytz/zoneinfo/America/Scoresbysund b/lib/pytz/zoneinfo/America/Scoresbysund deleted file mode 100644 index e20e9e1c4272adc40c07562bb3d6767cb056b550..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1916 zcmdVaT}ah;9LMp$Q<sRux6Fr9b4yLlqn_i_svYgYR9xp+*2y``Ip;&RIk#LB-D+o} zhzOByARAGVqmT%*F03xBw)TW0lOQB&#Uk0(9+0A>#gNnQ{olndcD0+qjvQZqJe&*R z`~LW9wl(Ki|9H(Zf8oum!@PNxLmzo!BfV`a=jA1T5ta0$KRWW3#spu{n1>fNcJh?Y zzt*n{zB-_B=PyY7;E*I7J|YXdRT6i8EYG#J%kx_rWRb@wN$wI!b`(fTe5x#th?UgP zVojaS)3hH`G<_mkmwYozGlu_A$FW~Dv-hTE?YXSkJI<@K@ua$HZ%9u2u;dnwNZy7| zBtM}~3UWH7V8$;?<9c=J?Rr`EXNNAoP%JNgU#;$eIxQSou0?xS>54<~y0SS*SM6CQ zt4l+*xG_%Fr2MQU#WPYG`kj^~{UBviKS_D;B`F^tm6s-u%G#k5;u-r~y*=GhG5DFT zYipDB-Mh8&<^8&$wMjR=(5gO9nQn@y&?-l|Ry}rVbyTTTU!SY5Os7fB*+;s0B3!og z&ym{U-{n>RL#gW>m)Ghhq<+`0`ud7<(oj35jfn@Psc=Y}!_Mf|1l6tgKGl|)cHK7G zrElE!>6^zlYwNe$<*j{%+BUFJ-fl_MclPGX_DZ+3H^<1ku7$Fr<cYjD$C8fhDDAj^ zS2|}u*3PS6OV`v5egE`;d@w$uI}d)TyN3Go!>(iM@A0cY5C{Z46Z~vQ=zshZ5(xa( zVp)N}<Q>aAKe!(h_V=?D#^D7;{Po-8^;wzD9P@Tr8BQmkm=~X!2g~;_F4+9D0j`*@ za>>XwBNvTaHFDXu=DLv!N3I;XbmZERi$|^=xqRgMkphqkkP?s@kRp&OkTQ@uY)v6Z zB}geqEl4p)HAp!~JxD=FMMz0VO-NBlRY+M#UACq$q%vDm8d4il98w)p9#S7tAW|Vx zB2pt#BvK_(CQ>I-C{n4dDHW;J))b3Wi<FDhixiAhjFgPjj1-Mjjg*bljTDYlZfi<M zYPU7TBh@42BlRN-fUE$r1jrg7i-4>GvJA*NAPa%41hN#!TG*P!Kvn};4rD!$1wmE> zSrTMTkVQdO1z8qkU66%ARt8xbWNmEC;vlPIYnBIDA7p`$6+)H>StDeTkX5q%-!D_R d+bmX*%WXER$l=Y+%Fl9UI~`t^(|&S=KLvok!I1y} diff --git a/lib/pytz/zoneinfo/America/Shiprock b/lib/pytz/zoneinfo/America/Shiprock deleted file mode 100644 index 5fbe26b1d93d1acb2561c390c1e097d07f1a262e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8v<nU)CgtR#>b6-0<P2(NH8!YHnHS1a(Ln-=0RD8?U+ zvtrCL36!+fOng|gv7xTv+REl|Yg!BKxhxw!Y?8peo%dPwPk;4STVM9OuOIjS&-=Po z`_|@&f812_4G-6C9^R)b{@GWcUmFNlJ<liU-dDa?dprTXw{@E6E54}vI`*j#+N1RF zyx$s!>*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC<xjIswP>}}?Nyq3Ob<M?I9d-V=h(6JxW8Uo* zv2XTB`ErZ6w*6Uo-Bypd-d8WDuPPC7rT5Ai`6=Rtlm#+=Zn2sf>5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU<P zcbE6;d+N8TqRba{anTx8{Ogb`NpBJ*XZOp}=vq;Fq+Kq%Tqw$3eO)jAxJEgf+VuVJ zELG(-K3&l@M?J8lOjr6t)rzEa?OOSja!thQs@zkm>gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vM<TJe`zEf=(Jg&En`PI|iz51DRZq?M>qPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(<dr(EuI31^XcR+y*SJQXgpA|XQThwERgFKDhdEUF(_ zA+khdjmRRARU*qo)@d~hMOKO|)oRv?EEZWUvRq`nR<mGa#mJJ8HKScLFRYp~%LdlX zv2bMN$kLIuBa25?Z#BzD)^9ZhKq`Qg0I2~-5s)fylmV#&M<I|(aFhb61xGQEYH*YT zsRvRJq#{;R5~L<bQIM)2WkKqK6b7jbQW~T-9K}JZ!%-fjK2}p8q(W9xBBVwfMMA2C zlnJR5QYfTSNU4xoA;m(fg_H}a7g8{!VpdZ!q-GpNL#oD6Hl%JGg+nUGQ97h{Nb!*B zA>~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5<wa#mE15^&RHNV6pj8 VNOLaC$jQh`b7p5}WM^bK{s0D?lEVN1 diff --git a/lib/pytz/zoneinfo/America/Sitka b/lib/pytz/zoneinfo/America/Sitka deleted file mode 100644 index 31f7061371910ad42e4310b7a646ba1a98b6cba4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2329 zcmciCeN2^A0LSrr5x8)LdP+z{v=b^<UOhm(6=f(!hy=#<CPWs6-C-ptjNu!J>Bz0( zYMX;ubev<=mU^aJ=~`I2)|SZ#w_fI=bZeGV*Lqux<aWNNmw#&h)qI|v=X2ox{rmbG zTAs*r{p*P_|KafTnZrGGtNEPTnXJa<{M0tql;%txtq|eYGaOfcx^O=i=R|Z(6;syD z4@EZlRMc{BD7q|GdE}*#=Y_>8))@)K9yl(%-|NuSuMerXFHSr0@9k1I96RLP_{Kru z4D51l+8GizZ)kU>wX7Ej^(&mjO24?Jq{x|`UMP}M>q0YPlSFbvK`1#h!AbsMk)C;e zo=O>;t7o0?sM&|3^{xB9Q=+p(-qv$Ur3PloIcvTZa|^D@c}qVMX^CG+U&folH#wox zy)TON@h^48#Ws;Sd|YRpd0u4??$bF()~W^F&uaNnt;!85nb)4D@+-E<+v^fkfv;H> z=KZ3IJon1tlxd>)!hBgW@w2$&L$AE^>}R4>r|G-iIVSE7#ps25_lkR3FY2=GZ>vQ$ zAM3@<1FC$%LA|7WlUnlQIa!h2FDlNQl$G%tMdgtpx%6_QsCxN*z3kI2RlRw?zW=QT zRnxdb*X}7*%S(g$fzH{gE~QfY11{zNJyQn?&a1#T_sNyB!(!zJ8M1!zoM;$|lMjub z6ph=j$cKlA#H!{|`N&|ec(nSGZtC5y9?Krlj|X?C=6J1FR|M7S%e!^ZSE+)hJ9LXD zQ?(oj=rtGO)suVL<x`^>;_1#6a;=UNt$`xh)^|m$E1V(Mw~mVT#0l9^b69kQr|Zt* z5!E^Vo9;@|s%!YH-tg;gwQ=xWz3KA~wYmFMz2#^?b+78t&-527cf=HT<h9-?PxN(s zU9Pd$T&{4qZ>uXZ9DXI_j<Mf3`*p(Mo;Ftkzq*s`eb%ySzpJ*+oCnRRHK*L~syBa^ zRhdg!mARS6&Br`e|9=lF*8I-l37Nw^HEf=RC$4^iTYB>ckf9-Cvzo!79UT{j$799^ z3=kP1GDKvI$RLqXBEz(raUuh?nvo(yMaGH@78xxvTx7h+fRPa+Lq^7o3>q0VGHhhr z$iR`2BSW{Ev0KgHk<lZ=N5+o?0Eqw+0we}V5RfP!VL;-51OkbK)r10xh1CQDi3So5 zBpygWkcc25L1Kag1&In079=i6V35clp+RC}HNiomgM<f(4-z0GLP&^^7$HGIqJ)GA zi4zhiBvMurDkN4`6D%ZJRue8HUP!=@h#?_EVul0_i5e0%ByLFHkjPn0=#bc1P4JND zSxxwm_#pvAB8Y?#i6Ih1B#KBFkvJlOL?VfV5{aeN1QUs-)r1p?r_}@$i6|0MB&JAE zk*FeJMdFGC7KtnpS|qks6I>*^Ruf(%zE%@pB*I9Dkr*REMxu;_8UKoNWJJ`p-sl+5 nb$tJ>bC2(TZ}dNr{`2Cc-6d2!t2#d?FGpro=jP_*=1A`!V0=z$ diff --git a/lib/pytz/zoneinfo/America/St_Barthelemy b/lib/pytz/zoneinfo/America/St_Barthelemy deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/St_Johns b/lib/pytz/zoneinfo/America/St_Johns deleted file mode 100644 index 65a5b0c720dad151ffdcba3dbe91c8bd638845c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3655 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 AC;$Ke diff --git a/lib/pytz/zoneinfo/America/St_Kitts b/lib/pytz/zoneinfo/America/St_Kitts deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/St_Lucia b/lib/pytz/zoneinfo/America/St_Lucia deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/St_Thomas b/lib/pytz/zoneinfo/America/St_Thomas deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/St_Vincent b/lib/pytz/zoneinfo/America/St_Vincent deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Swift_Current b/lib/pytz/zoneinfo/America/Swift_Current deleted file mode 100644 index 8e9ef255eeb11515b84126d9ee5c0c6b3c72f2a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 560 zcmchTtxH2;6o=29*AK?PHz>Pnf?;Jz(0@Rq17TML+sa@`EoPIj_O2KNb<H5MDhM_$ zYj+<yFbD&gxXlHtyq@P`5tC)Vmvf%O+kQ{DTQ050pUmhFL()3j!SUBgaDG#_7q_!V zjY`T!%b)vEF_7k=XIh0lX>a#TXX#lUSMSY}+(~!w(sW;H($D71TY6sJU&m(9Y0B^+ zGNWokKI$VoKDZXYn6U{jG3D#}{idBe?Ta{fRr7r3&aBMEcPie7Eeo6ZQ1Tl(1)Uw8 ztx(qWCf?5u|54Lvs0yhIsSK$NsUB17Lli(XKvY0<K$JkVK-9!kJrG3@O%PQOT@Ymu RZ4h-3ePjX(lj?LW`3>UA>oEWT diff --git a/lib/pytz/zoneinfo/America/Tegucigalpa b/lib/pytz/zoneinfo/America/Tegucigalpa deleted file mode 100644 index 477e93950c2f09b8c338033c07a53d4ef5c01912..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 264 zcmWHE%1kq2zyQoZ5fBCeb|40^d6xKiyC}@M^FT3Xc7RgVz6Z)SwE-@%Jr7*2ow&fn z2!{Xvud`ub`2T<B21b_u|5q<y;PCMcVQ_W<;@}XFVL%W<f|dV)0AwvlAINGD4YD3Y YgPZ`OLCyftAg6$7(wxHubeS0!03nGyj{pDw diff --git a/lib/pytz/zoneinfo/America/Thule b/lib/pytz/zoneinfo/America/Thule deleted file mode 100644 index 2969ebe59bd26701a6dcf6f71a42b0b427072e58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1514 zcmd7ROGs2v0Eh80LqalKBm_Ocl~y`Bqn4#MTH0XE<S|F_?Wwd$txPkYleLi2l|fjb z;!1K=21ZDMMP+V!oDxF_<b;f?geZ!@a5~@JwrUY{E{EUc!rgs;UwQp$r|pjuZoY6i zHRkfZarkQcuek4BIzCvXcFtGoT{AAVd%9C5%pDeqqZQIV884E0vSjkjNa668X=lr4 zk>bwKsmI@lw7BhhkMq9R8xhj!TVJX4<xhIw>OHmp<#V0!@rKHr&@$_Bi#jmyOuB}B zD!XY+=JYm;g9X>*q1qymn{-Cz<)(?eZ$sLhSg71{0i7R}qVgxJ^^uhrb#$a#dOkRX zH&7*yJ=`n`DqT_x{t$(kvGVw(1yQsuT>6S13Eyf^mZXh{l3<uF-7%#~Uo7dewINkL zHlr&R1FEujTvtt3tLoZ*T{D`kr1y?IalJ~MOze}V{4P-&)gbHKv7&CpBmHsV!vESX z>m!1q{%*8vSe_M)w-WT(w@d0=+a`T}Vn#I;f6*5P##M9Ld)?C1uP(+s(5(fHs&(z1 zY)kJGZHtq#V@reRm>!g!KRu#rv|V;D*hQeHOkSRf7CqG&^2+V6VqMrjeL|rpy*67Y zG_S(eheESYZ5upZpDicX+#(+lnB)74R6^#E3;&S}k`0m$k`Iy)k`a;;k`s~?k`<B` zk{6N~k{Oa3k{gm7lAWbV56KTn5Xlfp5y=rr63G%t6Uh@v6v-4x70K1oB#UHgY0^dV zwKNGM86znpIU`9UStDs9c_WD<nIowqxm%j#k?bu^`bhqkW&+3zkSQQ@Kqi6A0+|Lf z4`d?9OpvJ{b3rD9%*N782bqthnGiA~OEV>8PROK?Ss~Lx=7mfQnHe%QWNyghkl9(9 z=^^v8G!sN-XlbU1%n_L+GD~Ed$UKpW;(s#JNVC{fv)lx4irr~XPVhP$c85JV@;7N6 BlW70| diff --git a/lib/pytz/zoneinfo/America/Thunder_Bay b/lib/pytz/zoneinfo/America/Thunder_Bay deleted file mode 100644 index e504c9acf198fbd447221f120354774e46dbbcc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2202 zcmdtiZ%oxy9LMn=h(;GkC@E4R1B+dR%O6A0s5D@=t6m|!n3Czyz&nKE8yZ0xnqj0p zV9f@#8mqKs|3+rET#ZRNYini7YW*7>+u}hBJ4O#$tb04}v!3;+XRUL0&g<@O_u=Oq zX=vVB=KbT$u)lCQPuR=%tUdPWo3~Sc^0<+6dB4dyHKo0Qm`Uh8uM;2CW}3uQlKNAo z*J+hJcSJ5tKdgM-A@P~VREpjwsb9aS(@sX^vJ*Xe<|~hy^k=r}jQvgK@~+$U70p32 zYkh&v4EfEKi&OPg{uGmy>sMDNPng+>DQfnwKACfVLS>)*S*{s5qjHXZCfB}jRL$-1 z%yo|(RJrv(n7nO6dS3Y{bN%us^$nRXoBV?9IzRTI_<i--KlYLo{Ip2VAMTaH(TFM< zY?i>Wg{ruFg_Io3Ql-(TDQo{jEeM9pjjO*?<@xi?!m?qt=#n&3G3OgyG5(8LJoUa_ za{8>f>DxhFIdnm8es7PyrSEgO_1P_YY1><}tY^2nt@bIYYTT@<i#w#Ix=Phd9x=h9 zO*%Mwz=YD4>CmxOvwR{?uXt|1to(AR3h!!>Rqy1g+6`f;>rYa5R2IwX2gcQ!><o$2 zf2<-?ld`t_RkilVnPy$iX}#{$MYBG3L^m8BHFu32&>MP=o4bcw^~S~lvuSXZR^hj0 zb8m~<Qq(W^M8m2vty7wU#j0tdL8AE?D*9=qG|!k+&2Qw$w(&1iOaBtndgh|Ow<Fu! zH#Dl->SIRwj_do&wP|k~(AzUVFgt3y^p4o~(oxZ`I>tt%Gpkc|4j+=P-y76JgZpIX zSCwj4cT^sJH%E1E49V`NJ(Z9+Eh%yOf8rC5zaH_tc>J~Jy`*^j#G77nJpR$igjDyY zyLZ;gaKx)x6Y*-eciLNLZ?*lKJqdrmk$*9jxIOI`_7)<OL1u$Y$H{y+c0$06kSQT^ zLMDaG3Yiu%FJxj)J2PZz$lQ?0A+tlKhs+O|ATmQ_ipU(1Ng}gErisiGnJ6+-r=2P? zS7frtY?0|A^F=0%%ov$6GG}Dc$gGiRBlC9Ji6b+2+NmRRM<$QV9+^Hee<T4&29OjW zIY5$tWC2M7k_RLaNG6;%6-X|SWFXl<(t+dyNeGe=Bqc~rkfb14LDGWc#c30RWX5Sz zgXG3(lY?XjNe_}ABtb}qkQ5;~LXw1J2}u)@C#Ovmk}0Q66_P8bO%{?ZBwa|pkc1%_ zLsEw13`rW2H6(3F-jKv0nRD9IA-Qwf<RRH}+Vmm$LlTH&5J@4DLnMhv7Lha}c|;P4 zWYTF<iR990lZj;0Y14`16G<qNQ6!~EPLZVI|1_&U+uU;7+QRUHKv|%qFkD(1C=HbO F{sL6nZ3h4V diff --git a/lib/pytz/zoneinfo/America/Tijuana b/lib/pytz/zoneinfo/America/Tijuana deleted file mode 100644 index ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV<wIXJb{bS^oTWhSsR+x=3Q(zcHtQD2x^t^vvcGX?$clJE5-_G6d;`8?J zsIE+N{_)JU|8RIZ?BPA~wccM_x*7}Xx~H3_dMnH8-i=ny>9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_<F$=t4Cy7@@9=PN^Sy zep8cY2i1@5=hgg?ZnNP0fC}$#Hw)kER*Smc)arP<y6#!giyQ0JlIp#BY3Vi<k>}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*<iwcnXLTDxRpVV}9P{5i>8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;<SEq10?`P*NO|WBl8u#eX%{lw^J- zC70Lh?JIs(+dqlXrL*VMj+3+czTtP&&ejoqf8X<}to)3AptDi!@(r5@pXrd@$^GV` zs{K+Pe!^6EOQmA6)l|jjNYy~4sSb^m>Nhr-n$dtfe5^u0@<oi=)8N&QcF(HXk_27X zHliNOny>fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;<l1wuJw<*7<2XTo-~N9wu7G_Q7&0<sXvo-*!6BnVhKG#L)eaCDAu>c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX<orL;?u~5(^|4NHmaexY~Fi0dchvK|+GW z1PKZf6(lT3T#&#ZkwHR(#0Cit5*;KwNPLh0x!MRJAwpt=1PO@}5+)>0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a diff --git a/lib/pytz/zoneinfo/America/Toronto b/lib/pytz/zoneinfo/America/Toronto deleted file mode 100644 index 6752c5b05285678b86aea170f0921fc5f5e57738..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3494 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 diff --git a/lib/pytz/zoneinfo/America/Tortola b/lib/pytz/zoneinfo/America/Tortola deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Vancouver b/lib/pytz/zoneinfo/America/Vancouver deleted file mode 100644 index 0f9f832821b6cff451b5ecccd0b6eac8e53a9190..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2892 zcmd_rZ%oxy9LMns{y|XkWTHf9Cp8gN1QbQlOw(O45tS>68U9INn1+g7v=nvG%KmZ4 z{L?D>Mn1?@qGL9j$<QrVD=NWE)NW*{3?&sU5$WE}`}ed*J!~Cz&g(ax-}lF}uplqV z^^eod{)fw{v6t_@TkKPD=!m>`IKq6rElTPK-&|e4bf{|Z_SPpeH>n@yU)QH}i~2FS zL7#SgqZ%U)>c*yh>Wu${oUJwLoUdAb+WWEb)$EX;x4mwfDvITog4O1HNw)l&HqZQ) zlPVWt$C!)m1^QB-xvDv4f^Kdbty)5&bxVDOx_r^EuN=6gT8}sB-^&}-)v8Xpw&t+9 zUgndw%}33R!dkhx_yhAtMy32Y`2}-pRH?KNt5mmp=SfG8Qq|G^yuQ<%r#esP>c766 zq5Oy3I`Cnfa_x@QK`-@E!RveKE^CIFu1jO2+uShIeM+c=BwR5)^koTE-<mL2orGQ6 zZ+bRWNw3B_6<%8|cYRi+dhgw*)m!E2?rlc**-)hJDOjufX3tj<X&&8g{5aJ=cC3yZ zlxgk_jnq-W$tJ38jNEs=zlpBzCj*Y&HUkf|%l+@4HQGB|Kd|+P8I%>S2d_Azh9qCr z56=8t4UIUVW8x}QjK5W4!?vhc-**z%vP=!HIUpk%O3cWL?Gj(T#EdF=MiRD9HHrCe z=%k_{X0&^q9+TPKB*$dwu}RHlTu6#eiSDLSE=B3_cP^<3$2)cE*{{^Z{gE>1@JH&Q zvJRR2_G{|l!gDgEbg!A3Q6rBmf5l82B{F^5Dl`2?gLaR6S-Bey>a_5cDy@2#p4mEE zJ^D_y%sREgq;K3Ivp=0>G8PrfoSpGz?!;`F=T#;%I#oRL+l;4kfMg|~G+7rW=mi6> zs|8;~>ui66TDZrrANL(pi%OgH6E(Y3&hle=am5C;B;6-VU)*7qjjWX?^NY>$@Jh*b zXPeyCQpt}=HTiXUQV=r06nrv6R$L62r*`J*mET9JRbID#y2`H#vtsq?vL>}=Y)`$m z@R%x!Xw~a7_NaA%Q1PbJ8n5rNtdFcT>uc&{Lwl)twxUX&JDq1XmXyn;Lo-ZCPLXWh z9cO}rg1dCJ&wukT5P0=Xmn#r>*93J91j@F!dN|*`oL9|C_qgUvvp3V;$LyWsvA=Sc zE68~~|Dp~7dvYduuOO8`N`ce@DTbr122u{B9!NouiXbIHYJwES(N+a13sM)PFi2&P z(jc`#ii1=KDGyQ~q(Df8kP;y^LW<;QtAv!v(bfqm6jCXqR7kCmVj<N+%7xSmDHu{Q zq-03VkfI?~L&}EK&CwPPsT@)|q;}{Oj}NNHvE>8mhZGR0AW}l4hDZ^SDk5b>>WCE5 z(N+>EB~nYIm`F8|aw7Fa3W`(|DJfD@q^L+$k+LFnMGA{l*3p(0sV!1mq`F9Xk@_M9 zMk<Vy7^yK*WTeVSnUOj>+Cn3hcC@8NYK;^dsWwt>q~1uuk%}WFM{14~9jQ7}cBJk| z;gQNa+R`JnM~aVBA1Oale`EoW6+o5%Sp#GdkX1mI0a*t}yAa4qINGH^*22*)2C^E+ zav<x0EC{k9$dVvyf-DNMD#)@R>*8n^23Z+LyEMq!INHTQRtH%gWPOkYLRJV_B4mw_ zMM72yStewikcC24%F!+rvR00Ev5?hrw9AF87qVc;iXlsetQoRs$f_aBhO8U1aLCFz z+NDF*&e1L&vU-ko`H=NP77$rMWC@WqL>3YM->VpA$1=r^=7{vs@k#LsBhnKS;}hc( G!u|%QT)5Hz diff --git a/lib/pytz/zoneinfo/America/Virgin b/lib/pytz/zoneinfo/America/Virgin deleted file mode 100644 index bdedd1bd9bc85cbcf8259b6eee3aaa5ab041e954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVPJMuVMWE|5ykE!~g%s4=}L!_=Yez1_QZ35JG}M O|A7FnVO&5{O}GFwj2!6z diff --git a/lib/pytz/zoneinfo/America/Whitehorse b/lib/pytz/zoneinfo/America/Whitehorse deleted file mode 100644 index fb3cd71a69e3038f0d77e8ddd3290a08fb960d9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2084 zcmd_qUue~39LMpq`DeNuJfPFL&ap>l&du%YkJ~&t`)8BmbjMA1JbBFg*Z#1jZMk*S z(m7!i8nJJR8mPy|;f+K%8H!*H6AD5k+ae-kNKi&GA`4n6`}Mq^F1zZgi++da_j>lb z+Rf)3-PF=l>ifqt#eU)N9JGh~+;00yUcK3W_F9fHx2N@=>l^C6d3a&}P|k1dL)**r z??nk2TiB-_1h%T_ExYxM_y(0(9n~|JE>W}cDs<ModYL^nOJ}E+OZNRT^XRv;#6L9C z1U~vh=3E*xkDa?CI<ZL4J@lc>i)HAX*00t4@}KqNt3OZ+GC$Y3`AL;KK5FvPyH(!E zXD0v09ct0wB~vimC56djCOEK7in?Ak#m81iN%K)%+A&`ihdXsy{bVW6jp>TgA7n{d zNQeBBROnu|Ui#pkTK369U3uqIwY)#eJaO$k^<?j#=BZO}tEbz(G0*f|kQKFq=Glg~ zq^ii6l~ucB<*&m!TzEu<hcD~ujGd}_;G|wXx>l_@Jz#3SY?a9V<7VxZ8mZm9$JCt& z%DUxovp%7u-d|~=8}3W=VZg-7zmV8>%k;)Mzo?BL`*nPLSZz8#uAd*dtTy-D)h`U5 zR9hNu=&i|pYFlK)Y=85(>?pi$UToeY4H<o=F&vl1(H_&BTPe-AnoLVtKw92kWm>=Y z<fRjhdgpDw+LZ|FmoJX1wpgBS@4c&Dsdz;1Zo8p6GDmf1?RnMdO*M(obxDl;WcFtD z$=<<RX5Uzkyq3IZUjM2|_IJHw4qRO&U0XWL!FTf|HO2lWOiZ1WIyvn>_<7!;2A|LK zdb?95+Izciin~v9Z{>MsBxMG7-)wge)I_4bc$Gc%_B>}#9e>*ob@oG@l_$l$|2FzB zcHr6Pz#B(SBYQwLf$Rd=2C@%iBTl;$WGl#Ckj?P#up4AM$bOIwAv;30gzO2~l+*4C z*%q=dWMjzAkgXwmLpF!(4%r^EKV*Z*4v{T7?H-X$I_)lzZ6f<bHj3;N*($PEWV6U_ zk?kV;MK+A=7}+wiXJpe(yK7|I$i9({BRfa7j_e)TJhFRa`^f&01|S_kTHv%jK$_sR zT|nC4w0%Gtfph|C1=0(o8Avyfb|C#g8iI5LX^GSJ1Zj%Xb_HpR)Aj{v4AL2-HArue z<{;fc+Jp25X%NyOq(w-NkR~~8myk9&ZJ&@vIc=wqRw2DYnuT-=X&2Hjq+v+Mkd`4m zLz?EaT|?UDw0%Pw=d_(eT8H!wX&%x&q<u*L@c+=jbQ{NzjblM%aj-O4To5TK36=zl G)Bgheu`o{n diff --git a/lib/pytz/zoneinfo/America/Winnipeg b/lib/pytz/zoneinfo/America/Winnipeg deleted file mode 100644 index 3718d47da0baeadef58f3f2719a8983a1a27bece..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2882 zcmeH|ZA_JA9EbnMHxNb5#0UvYD~2Khis1`osbi1`>Jd@M)G)*{9^X)W!GDwa@=49| zWfZBSmTQI1rKpWe&AF9xh?1lcqLNd|Cv^*zINjG>Yi_x<wm$YdyPxYh&xdo)mvdd- zsd=x($=~K@o^aS9bGYxJ=2GIhUte<cV0vlA4*lTZUFXR5;d(T!*!^a0tDf$A$2mQt zqg%apww#$b-90<$tkm>A>3$c#U(QLnTN|`ZYU?+;=gr5RKauTTsN14H?AqYeRTk=x z>*hQ4rCEA0&+9Z4CF-T*A<pG#;d&)9*lA1}AwP8=>|TusldHFSy4TFFxmMNeUT<uW z8#Tw=rsK!t=Z#;uxAV*NPV7wgZr2pO=U*WAnsep;)l6wQmn^@X7$&V1{l)ijsL0kp z@vLc;Hj8gc+j$|{Zbm0<pWdP!MmDKmOr84oKBXPI9o7KZqXF(t?Nq->JJ)_Gfrl4q zmjgSb>#nz@+qPBm$hw8ny>OO1nwKFxrX)#Fa)R_68Yz!OhDdOqG5WZFi-fd^(2!ff z+N+_XhF0CvC%(F_VS6rU_>Pm>yP&H)`Bu61ne>bFo%5|c6<;k+r+g;Q1br?M{WeHM zYn4U@%$LZzz1r`ZSNd1Jt5MaC4k%r$(R(N8z@jM{vuUu#<`ziYf}T1kDN_bdXx8|M zVKOA{dwtd~R1$jqDhZ9PGW33}3_D&g!_Oa*5#=qKc<6n3Zhf6TU$Rn0<{#D<ic9px zw4FLCXN8X1pI<+EV6l!FJ>D5}d%q+NnCm80Z<FM}G&gzgGD&HQbzgdKi;k@s=cFz# z)N%V_oV4sLP2b$z89yRXCoH<-ObiWI@1z>X+v3rT_zEZEN|<DZopCe27Maqz&z)M< zAX#-=-R$CH@^a-;cUsOz@=9s8J3aM?&RA3E%p6drujXbsIf1KnR#Kuft7*38Mua=L zCsQ=f&*S8M8l`g@FFCJoPL?-L`P{h${pHQ_i*9~Kpg8M~y7S_1%KZEf-338avM_Cp zyRfxM3lhqlg1S?>BzU#6q;iiIHqUmJmTuBz=Te;IMT>MrMU+!CZL+T1+Qn((v9Gr6 z{=E6)?Zd9^JN&n=fBeFS{fGyj`Fv$JM0~yjyFKlEzE8G`zrF8luiYMeJ~q`Wqeh!E z(VWTVq<hU@T*Tw?ScrK4g&urnIhS|81JQ(~Q3XU75M@BL0Z|7;9}tCD8jV0y0?`RX zDG;qd)B@29L@|~|GZ58S8r?vY1JMpdJrMms6a>)_L`4uCL6ij15=2c9JwX&@X*2~< zm8H=YL|G7RLDU7&7erwYjX_if(V3-D8boUlwL$a-t>U;rbIhoYp*x217}|rV528Pa z0wEfNs1Tw<h!QQ079nbc=#imFh$b1TWayHiOolcY>SXAXp-_fK87gJy6rxmyRxOQM zA$o-<7NS{*Y9YFXD3_sKh<X|NWhj`TVTOtsI<_=QhG^N+s2QSXhN2;whNv2%YlyNT z+J>l`p>K%785)PEoS}1w(ivK}G-_w)ouPP!<{7Go=$@f`i1r!khv*-Y07wQbO$s16 zfF!}vWPy<eOOppiA}mcN7^#5d0+I|!HX!MM<O7lrNJbzjf#d{|6ibs8NLnmSUKok7 zG?`(f29g^_av<4Zqz958MuH$2f}{wNBS?}US%Rd=(&Pz}C`*$mNUAJNt{};RWQ&n5 zNWK^egJg`6GDyxCNrPmKkv2<{H%Q_vP39n}voyJbBoC53Nctf8gCr1=LHK_P|4Lzi aspJq-%c#^r(Q(l+QK_-9(Xr7n0lx!f>#*4X diff --git a/lib/pytz/zoneinfo/America/Yakutat b/lib/pytz/zoneinfo/America/Yakutat deleted file mode 100644 index da209f9f0a07625ec83d4ec84917216347f5687f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2305 zcmciCZA{fw0LSqQ0v8C165>IklMf(I*TYrdQk0<(ArcVRn-Ezjc88T<useK!xTPbv z3TK<c+2A<GR$I0+-AY$r=~`PRBiwozMd{Wom#+1+isW{_r+iiORrCMfIiLUTef|4- zo7x_CJO1@J%`Y6D0dx4y=rh;p-HY|)f}cAkTb7C0V{2sSRgZ8C7R#{b(?oc8s+_Z> zNJX?1>d5*O6;+n3o$?Rme6e1~Bz&P_4xf>+Ka8mxz8=+apHGSScZc<j$4A9Yue~D^ z_797jcMr*1wg$!AwmzBIxIrXUt(Ui!REXs4a+xx}MWx1M%Czv6DlIoLA??Q%dfxd( zDt&UHzU`z_WgLmpw;%jYi9m_GBY0ZP_sx?FHhn7=7G9ByR(&WklfIH!Ij@VX*%_T3 z`;y3>{!-^$>=3zQCv@J~7gYYxLG3!aSuN>(PRp0;RDoYfcc)t|t$bE4Ye-auS*^0j z{i`Z=-X}fjbA{)^Vp%fti@5WHSb5jfr=nD6>bu`QF7DYAt(PA-Ant9ysLOV~rB>8_ zq*u1?SLI9I&=uv|RmD%|WM%%Ks62a0R>f}<RYynUs!PqH`jz+e>QA~=&5lF*fj65} zZSyW&x35&ym-_XCfeh7<UZuT0hw}cJt9^y%mG7JT<=TvpSo?mCY@9tOnnvQ}!_%ik z^Ullik+Bi6u605_I@B*7tNC8H^dC~|^T+iD|1Q-Uul2@CzuI_dul8qEDgT)+-R8_y zZHIk&(}j5T#J*1X<V22mDzHXw)^VcUS1daQE{iQisq*Re3DKD}BfDykh^|nw4tT~@ zVET96ouXCu*p%M-+g`P8XjE_itV`|a9o9X^e5!X{P(L&1QDNb8!XmEuMnzu7*Wnls zIvk-;u)`4%3JvsyMcX^h-U;?jwEw2AuJJk=8_apgoH}#Ly^gYKdzoL&6UOy7-v1s` zwE3IEv&S61GeYKBe)7u4*wbHp0vQ)FFsm6E+M)5m*m%s~fYBktL&k>;5E&sdL}ZLs zGe~5VRx?awoX9|tks?Dy#)=FU87(qgWW30Lkr5+9M#hW`8W}Y*Y-HS4GjOXJIWlx) z?8xAe(Idl0#*YL5i2xD;BnC(jkSHKwu$nj^fv}oLAfZ5Ffdm7I1`-Y=9!Nlth#(<B zVuAz(i3$=HBrZr`tR^x@Xpq<-!9k*fga?Ta5+Ec(NQjUaAwfc-goMdz;)DdsY9fV% z%4%YT1Ph545-ucONWhSYAt6Izh6D|X8WJ|Ei5n6)tBD*EI;)8t5<Db&NcfQWApt}p zh=dS{AreF+ibxodI3j_xnn)s{w3=8V!L*uaBH={hi3AjhC=ya6rbtkcs3KuS;)(>; zY9fn-)@ovl1lMY!i-Z@6FA`uR!bpgb7~?-djtGyu=Ie~Qj_=hX_n4mkFI~PGW@~I& Zb%VErZs*l3b-7(Kucn~DRp64be*u!YOXvUq diff --git a/lib/pytz/zoneinfo/America/Yellowknife b/lib/pytz/zoneinfo/America/Yellowknife deleted file mode 100644 index e6afa390e879f97cef351e382ae62daf183e8d77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1966 zcmdtiZ%h<)9LMo5V5v9M-)0yJnrV7M+;N~JkeL;9Azc`rh(wqM-cBn>-OUW~*TOL# z@m)FAX0n_%AGDIxwVE5vHGEPwcW$je3s-4vmJO>-HkIr5{yptckJ|dWyO(2QJo&vp z@s{Maa{0&I>3+h8+v`63f9^<ezI@)FtLepmb)~oD%Z<{Ou8Cy_#%Cwn&0U+Kl2`Y* znNw9HcNZ-)`HO1p-01-`FFR=G{jpRFew=O#)6;bR*<bB~53lMyN6y=YsrlyK*G||) z8!~3`_AlhViXY69wI9gRyw6OqSS2`lNs9tIrD*J|7GI6a{Ub-U<YJc%4IR+%sYYAc zlhDYUD{NV!*Oc#?W0yy_nH7x_wj%h1d7%7TyK-u=sVul5mA~Ygs+-?P^|{~8gWsKz zRl@;&=*(eRJ@A`8d}zO{>HJb3={;m0Z5Y<a+V|O-(l@lWX0xsRbJRpb-4eZc+|<oz zle$y=X6>~#Qvde5`s7!wHrCyzPkpr7Hnb*n-QYs|^s0JoOl8~kg&~b^xNPIMW@%H! zIooul#56DXNt#DznoX0V(sFFvJTrD&T6@#x*^z!}Yd>u^5ABfWVi$BvU!UC?I;qbm zlD2)uLG6gv+m34~O$0+WalToTQ)k)a`_;Pr=j(RI;70Spg_-hVs>-}{Vq7|#3QX5P zT3)WWZFY8^mR)(5%<hIk**$qvQ<W!eYV3R6lYh|e8TnLSnMm2aLr3)0ubXXm&+EGH z<7(T})}i~~DzrIS**Q~k?)VS>@!Ui*nfKnwn&Q8wWip3Vax<AvhO><SNnR)tk=Vw# z#MZgbdiSZR;VbTbG0Xot-|>I(tTXO-+Q{=pp19XNbL6Qb&z(QQlSiIC$J0liKSu&c z21p7>4qlf8k_D0mk_VCqk_nOuk_(ayk_|^XNIo11AsKmHN=Qy#mlTo}M_Nc;NMcB4 zNNPxKNODMaNP0+qNP<X)NQy{~UY8`2B}bY_o*aoHnR29x<jRpOk}Z-hk}r}llCjsN zjO6TfNh4W%UD`<A9El^DBdH^~BgrG#Bk3dgBNKqkfY(g{G6!Ba3CJvX-83Ncz%dcX zOmIvEG8Y_^fy@TSbRhGAOb9X~$dn*+;&qdP%!=1d3o<WWH!;Y}AX9_P4aejlv%@hx z$oz0j5Hdp?Q-sWs*G&>KOI|lk$UJ%7L?JVUOcgR$$Yde2g-jRzPv$G&hHa(Ww368J RaCtaV5-TeUmxUvNzX6=<+*SYp diff --git a/lib/pytz/zoneinfo/Antarctica/Casey b/lib/pytz/zoneinfo/Antarctica/Casey deleted file mode 100644 index f100f47461ac8e48a4df03bea0429e464b30a22e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 297 zcmWHE%1kq2zyK^j5fBCe4j=}xdH%_rY4Ezmrr_Ow>Vx0PIST&HXD38(SvN#2;TNd? z|NlQD6C)Ed6C)!?69^PEfb`AU05So@)-^C-&^EAO&^9y#NrTiehLB)C(2D;cH-I#Q m+ySCNZUNCC_kd`Sn?N+kT_76dHV{p&`?zd?F1FLP-~s@fiZV$6 diff --git a/lib/pytz/zoneinfo/Antarctica/Davis b/lib/pytz/zoneinfo/Antarctica/Davis deleted file mode 100644 index 916f2c25926bf444b7c366110a29a3fafb17fbc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 297 zcmWHE%1kq2zyK^j5fBCe4j=}xd7jU4VEE>KU*MnnY6h=cA_m^me>wQATxH<z{QW@v z|Ns9P85o(EnV6YDnm{0_0Hn_?0AvD)t!rSwplx6d#HJu=Mi34m!G53>|3Pj5(I9t# mXpmb#0OTGJ4RRBR2DuAFgWLw9>w#*?aUYir(8YGT=3D^3yE`2K diff --git a/lib/pytz/zoneinfo/Antarctica/DumontDUrville b/lib/pytz/zoneinfo/Antarctica/DumontDUrville deleted file mode 100644 index bd6563ec8fa005d0dc172a8f7850313ae7607807..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 202 zcmWHE%1kq2zyM4@5fBCe79a+(Ij-y}Yq)2Uknr4wsiFS=|No2(jEo=!AkedbfkoHA rfI-{P0L%{|!CatG|3PMhXps3J8e|8^pn4!1pFLbQKpX9J4Gp*e$FV4> diff --git a/lib/pytz/zoneinfo/Antarctica/Macquarie b/lib/pytz/zoneinfo/Antarctica/Macquarie deleted file mode 100644 index 83c308addc4ca5a3f7eac89d19cd0881f7ddce9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1534 zcmdUuNk~<37)QUWK1FjV6EmmO2It{fnWjF|e2$BPL{iLx%5)L5sE{cAAnR>%Q$mt~ zW{9iM#D;}nX5h+bQQ4vaYY`C*i?D^m>wj*zYSp4ezsJ4j&*g3|@0_~Eww7G^V*%y~ z7i%&X=WsQ*z8CF!b0XpUfM@*3TyU!_KJJ#Ku?|_DX^@q(qv{<l6sf<emM2bpN=C%@ zRDt@1#EN5oj5-$bH6VLg171dJVAwkiytkx5-==g;|3(RZ^;SYU7A5rIBMEDMBWrsH zWu0qM!rQxKef%wnIM^Z)D;Fg)ze+av?vtp*V;c3rDbZGiMh_p=nE4cq^|&=|DoEo` zB<rT@W0KI&p^5rblgj#JbK7Uxl5|0~R?o^dpTm-zIwHwG?n}y-c};!ZD`~USnm&F` zGVTm%W>1r3b$c}Xv`cbMwyE<_qvqCMmhHtwx+AYucE-hPUSy5rEsO42$dLTk@3mm~ zlkT2)qJ@)bS~N5-#r?tRx-=~%9ba|N@gXT~ex_w^kL-1gX?fOdb;tK;g`-v~1MW)Y z&q1mBculJ3x};|2jMP48kxsAIYx!8dmY?Gv{QUjRUuo~kfcS~r7_|adpZq?rK9|tU zoE#~2AE-08@;XU(I(gButkw7H{=y*hQQq^8NP(CHu?b=n#43nc5W66TLEAD8n8u84 z0OJ_eG0cP5$1u>Qu@GXSO=Ba(NSnq=h?x*O8HO?}Wthsam0>KyT86m{dl?4XG!`>V zX4uRy+NQCZVYW?UH^gv=<q*>$wnL0(SPwBDVn3q*Mg@!#7&S18uxYAblmV%OQ3#|G zMk$b57{x%UVUz=@2T~BDB1lP$njl5lG*v;$V$=mG3{n}SG)QfX;vm&A%7fI$C=gO1 pqeMuJkRl;fGRm}R>SPqksFYDEqgK{wl>9%64JtAfyE2{5pkF;ZQx5<D diff --git a/lib/pytz/zoneinfo/Antarctica/Mawson b/lib/pytz/zoneinfo/Antarctica/Mawson deleted file mode 100644 index e1f0b09b9ed9e6fb30389a35c89c0a1149bde73d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Oyjf>DFlEhUHg|Ns9pGBH7985lwm7+4q>+yWRlbPWs` lv<=LF*c2)mLW1!?Q~!gk0BHqT1ENW=ipvIQt(~qZ7Xb4-9Ekt` diff --git a/lib/pytz/zoneinfo/Antarctica/McMurdo b/lib/pytz/zoneinfo/Antarctica/McMurdo deleted file mode 100644 index 60bcef686badda46f36652694d06d041c70a9d87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj z<t{CbH~x0rWPQTJwaa=qkKbs$+B(`j2bOLrXs<qR9$a`{Itu#Dcf<STP-3guIjU8< zLh8+~pZfIhwKDU_$IbfQP@ehzIgcKF=ZfdpiI22<+mNTHX`dcncf`}Xd7GZd-R1e9 zs6zXkwVspN4bmSdo`I-x8Ms<wp8EQ=RG*MP)o0%x^}V!5{2KpI|Dod=P<uuLyP7np zut#rxwNA&T?ACGBB|1LrIh|0Dr4vSqH8?R+gD-5Bkg1sx(!W|l9T5`Ryhv{O-d`qd znI*UOT$Hd9Kbic-piIdamZ=-t<+fNK4KMvvrv?3}w>up&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-<KEG6FN9>h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-L<DAb!D#1FYJ&7$+5C9<rPVr zG)~gPswMsUuaYt1mPJ?VH1kZdWSuV2#mB;ANoSU3HyV<&Gg5PF&PrZYfZkI)qDv)0 z?#)nLmg+CA>Akvq@<qw_eoOOj49a~!Jg)`cwabc=rn<XdmizbD$;y^Cec;6sDSTnO zK3JY5Vpi%yd6BXzGhd5h0_5SDiMl%Qk`#|!F2&dUwB+(UF;5R`>E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ<OE72Q%HZSv@fZ}hR$?Xo`Us8&v?l*cE&t{aSe+3?%5TBV6n{Z)}Z z(Gx099!}S%S`+l?-K(T#YlzlvN|R^I-_^_EHR_*k@6lua)7vnbhO9Xl`v)AO4dcx& z!^bdMdN>~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-<jQlOc9wQGD&2X z$TX38A`?YsicHnknkzC{WVXn3k@+GMMrMpm8JROOX=K*Ow2^ru6Gvu_OdXlKtu=XM z_WU+X-`1Kxk^m$FND7b~AW1;7fTRJ*1Cj_N6G$p-tz00<u(h&*q{G(A2a*t5D<eos zkenb%L9&9R1<4DN7$h@DYLMI@$w9J%q{r6E50W4xLr98{93e?UvV^1w$rF+&BvVML zkX#|jLb8RV%ht*lk}xD=NXn3$AxT5BhNKP28<IFAb4cot+#$(BvWKM4*2*7}KqP}m z3XvQlNkp=Uq!Gy@l1L<zNGg$BBFRLuiKG+BCz4QGE2BtCk(?q)MY4*d70D}-SR}JZ zYLVO`$wjh@q!-CAl3*mmwpNOf93x3avW%n|$up8@B-2Q$kz6CmMzW2h8_744a3teM z%5ANjBS}ZHj-(yQJCb-L^GNEE+~fZ!`M&%iM90PFy3<@yIZ4jB&e*7&InFp|Y|L!m FzW~Tp3}^rV diff --git a/lib/pytz/zoneinfo/Antarctica/Palmer b/lib/pytz/zoneinfo/Antarctica/Palmer deleted file mode 100644 index 3dd85f84ff48fb8553b4a964659872ae1fb7d33d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1418 zcmd7ROGs2v0Eh8=$4LiGDqN)YveI6Yb##1;=Hui8P0P%YlrV^bNH<E0+60!1l87Eq z)IzWaM1<+F7P&AwR3ts1M8sSKK`~GS(gV}9Zs)t^s#UaUE|=fo4h%EQ_pja6xIf+c z<K;6?xOs)l&3XR#rCZi`xKmBU73+zCAu;*&u>3xhrKTQc>goQF_|a7(e|BUF=TwG_ zx>lil$<3D+ihdOzxm(8%?o<ikLY>$fREu6T$i?Rp)smh9xwK_kEjyDWlUgIh@@9`* zkuxP$R=tzS39m&;(9$WB_r<Eu6MFUN6|v^qBfa*)J+*G+y7ph~RO|1y>(sVmYQx1Q zy>ah8C61nuX+@<feNUqd<d299St&DPN<>y{pUjRNRzYi?3_kOTocDg6d-H?X^dwg2 zoqR6xZ;a`JrfyN#KB#5rsoH$tjxGvxsp9erx+LzjDowjBOTV<JvIXa5*_cO^e>x<$ z^f!x&!FpNQXQ{2dxpLd%C{b12rnh%=tLmarz2nGPRpTF*p@xI1Hs+PA%MOdWo{ze| z__%6y9LHnNUfu|=Jty*?FRz#X%Ca11KwDnN8GdQ|9OvyDdoE|ooM)cQzH9kXg|JdZ zhPeag{vCmB&wPxr_Ak;fzsMmESCa^miK|Hk$puLU$p%RW$p_tpIGBw1GNgp$gd~Mz z<!aJG@<I|rGDA{Bazm0svP05C@<S3tGDK2Da&$FGB3UA7B6%W-BAFtoBDo^TBH1G8 zBKaZ-BN-zpBRL~UBUvM9BYC@;#F5OA)REkg<dN)=^pX6L2_Q2-rhv==nFKNmS2GP{ z9<F90$V`x_Aag+`gUkk*4l*BPLdcAeDIs%0CWXujnU<@W7cwzyH3`j*%wzwzGmFkM J11tzc{{lwWnyUZ+ diff --git a/lib/pytz/zoneinfo/Antarctica/Rothera b/lib/pytz/zoneinfo/Antarctica/Rothera deleted file mode 100644 index 7940e6efabc20b983592647fe8686df46935f5a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmWHE%1kq2zyM4@5fBCe7@LEaNtdDi|NsAtj1Y<c|F2$PV9_-&V9+%%1`CCdU>ZmR R$V3ppZz`7!(0n^%E&!2a70dtt diff --git a/lib/pytz/zoneinfo/Antarctica/South_Pole b/lib/pytz/zoneinfo/Antarctica/South_Pole deleted file mode 100644 index 60bcef686badda46f36652694d06d041c70a9d87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj z<t{CbH~x0rWPQTJwaa=qkKbs$+B(`j2bOLrXs<qR9$a`{Itu#Dcf<STP-3guIjU8< zLh8+~pZfIhwKDU_$IbfQP@ehzIgcKF=ZfdpiI22<+mNTHX`dcncf`}Xd7GZd-R1e9 zs6zXkwVspN4bmSdo`I-x8Ms<wp8EQ=RG*MP)o0%x^}V!5{2KpI|Dod=P<uuLyP7np zut#rxwNA&T?ACGBB|1LrIh|0Dr4vSqH8?R+gD-5Bkg1sx(!W|l9T5`Ryhv{O-d`qd znI*UOT$Hd9Kbic-piIdamZ=-t<+fNK4KMvvrv?3}w>up&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-<KEG6FN9>h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-L<DAb!D#1FYJ&7$+5C9<rPVr zG)~gPswMsUuaYt1mPJ?VH1kZdWSuV2#mB;ANoSU3HyV<&Gg5PF&PrZYfZkI)qDv)0 z?#)nLmg+CA>Akvq@<qw_eoOOj49a~!Jg)`cwabc=rn<XdmizbD$;y^Cec;6sDSTnO zK3JY5Vpi%yd6BXzGhd5h0_5SDiMl%Qk`#|!F2&dUwB+(UF;5R`>E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ<OE72Q%HZSv@fZ}hR$?Xo`Us8&v?l*cE&t{aSe+3?%5TBV6n{Z)}Z z(Gx099!}S%S`+l?-K(T#YlzlvN|R^I-_^_EHR_*k@6lua)7vnbhO9Xl`v)AO4dcx& z!^bdMdN>~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-<jQlOc9wQGD&2X z$TX38A`?YsicHnknkzC{WVXn3k@+GMMrMpm8JROOX=K*Ow2^ru6Gvu_OdXlKtu=XM z_WU+X-`1Kxk^m$FND7b~AW1;7fTRJ*1Cj_N6G$p-tz00<u(h&*q{G(A2a*t5D<eos zkenb%L9&9R1<4DN7$h@DYLMI@$w9J%q{r6E50W4xLr98{93e?UvV^1w$rF+&BvVML zkX#|jLb8RV%ht*lk}xD=NXn3$AxT5BhNKP28<IFAb4cot+#$(BvWKM4*2*7}KqP}m z3XvQlNkp=Uq!Gy@l1L<zNGg$BBFRLuiKG+BCz4QGE2BtCk(?q)MY4*d70D}-SR}JZ zYLVO`$wjh@q!-CAl3*mmwpNOf93x3avW%n|$up8@B-2Q$kz6CmMzW2h8_744a3teM z%5ANjBS}ZHj-(yQJCb-L^GNEE+~fZ!`M&%iM90PFy3<@yIZ4jB&e*7&InFp|Y|L!m FzW~Tp3}^rV diff --git a/lib/pytz/zoneinfo/Antarctica/Syowa b/lib/pytz/zoneinfo/Antarctica/Syowa deleted file mode 100644 index 4bb041a2639793a50996a19bc811c216c43075e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@Ony#t4S`|Ns9pGD0L67_<x+Sab~x7_<$H!9pP<m<BZH TKgdK7z;7y-4bXf$U1Kf)(drh( diff --git a/lib/pytz/zoneinfo/Antarctica/Troll b/lib/pytz/zoneinfo/Antarctica/Troll deleted file mode 100644 index 5e565da2f6b138b70179cb7e72347163ab44b6ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1162 zcmc)IPe_w-9LMoz{(&({z6Ro<KL!#MO?@_zE^6SP4a-^PGSkfd=+s&(t;AKfLl6-n z-$M`{f})U!7#*?>9ioUZkkLh=Lj(yL5fW5L2U)-OucN2v&@+B}J$nv2JfC;8b1)Wk zR$Y1K35Tn}9PZcDtqnVMp?t0HT`vt=7PYZ{MMC*+G+g>o!b=O%l>0)OGBdJk{;up! zKal3x=Ng$9mzIZjbWb87d&jS6>w)XKFW#qZJK`E`4(tBn7H#)$)AnUoJBmWm@ot?S z{JB*+pZw6SIiDQ5T`1j;zslj%cj>v2kt02Ga&+vA9^3g;j(5*$Z^@+e)uc7%o!0)1 zs{NmD>cGmd4$dU?#D}P!yx*nq*F$pZT8$>A+T`>=iJrMyB}1*%G8`+Gvw=-=uJM<g zFLY$2qDV)+eUj0FWgUI{N|H<O^}?emxtMvTmnL88So)S;PTtehL`qZHY&O@E^RJ&A zUNF~&&7ME++iQP%%Usvl?gq_q9mnIbC;As#@h|LUHJc&3A=@GQAsZq)TFsWoo>sFd zvMaJJvM;hRvNN(ZvNy6hvOBUpvOm%Q(t*{qfb?KBO(0z$Z6JLhjUb&MtsuQ1%^=+% z?I8Ui4Iv#NEg?NwO;bo$NLxr>NMlH6NNY%MNOMScNP9?sNP|d+NQ+31R?{TXrPZ{F w^ocZzbc(c!^olf#bZh+&?fR-s$+fQe4%U_h{gKM@s&cm?;Ex1cdspfE4Vhy68UO$Q diff --git a/lib/pytz/zoneinfo/Antarctica/Vostok b/lib/pytz/zoneinfo/Antarctica/Vostok deleted file mode 100644 index 5696abf51d60ca0489d57f1545f47762378bf2e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@Ol|L}x?&|Ns9P86gr33?T^&EV>2;4B7@}V4)BaOamJ9 UA7mm(BYso4Y=Gw5>6&o?06nM}IRF3v diff --git a/lib/pytz/zoneinfo/Arctic/Longyearbyen b/lib/pytz/zoneinfo/Arctic/Longyearbyen deleted file mode 100644 index c6842af88c290ac7676c84846505884bbdcf652f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2242 zcmdtie@xVM9LMqRfk3fxZ-2m9fKZ61b@Chh#YJ=iGw(FzGExatM68SQG6w#LelX`6 zWA7Tv9JwqVv!>V|lz*VLe%NT?WhQf4t}Rwp8eJmMkFokZzs>oF{?kAG(dWDSKEC^I z_s4DbdInZ(sLQpkIdSF<A5OdZ@O<;r=GN&Nv^r01sp&iHujxO(NRGeZ)bQ(G`Rv7f zIq__Ud>%@alWXGS!l5+1xZfu~z3h>p9hvfTQ>sMjMSiJt$ffd2GCX@wF1t?2i1V2I zDiIycij~pGNu3zp8JXl?Ad~a{(1i30nmFkzbw(do=kU8aW$=*R^2Hv#^}`o5>Bvz@ zKF}>Gue>T#+f-7wJ|k(tkleOvt=#SlNP1DJOmi1XMzTw$-!w&BF<y0z<m-%YGj!%a zqTX>VPVembP2Kx`&{-X4HM8|o&DwNCvuh7(PSqL74fRN#r&scqy(9%GyQMI<NeahW zWKL3t&N;VQ=Kk5J^NxCD{+E?n)K#sX-g$c0_7W}bOxC;W(zT>@uG~`=qu$yiS&(sF zOTA-K7W0Xgr++QwL*L25==Wt|xKHjK+$)Q^-xOc}d+Kj*lf?&K(<KcJa$nnXy7YnP zby;woR?H4+z*nyKI~VJ6_e@<rnyr-yWm0*1qCPk>Lsq<VSyv9k%c?ySq^jqlJk&BQ z)g57}sUDEk+kVtF#fN2WRlnAz?viz$ZmqlFZC#(Dy8io}T0a)j4Smh}@VS6KvVWxp zKi(*h?(k?sSA{%QpQ?{<FOZE(izHO%lqYhg%BIra<;e+_G-f4eW8@oY8b7K{Cq9zq zp)<PqtuEOT?$xckKG1F5yY;E&ecICAqEU`0NA$SsTv0Kx|NUiI@srIT*-B1xjI*rq zV%>P<{?D7M?|uG&<t?q?7T_BWbI?2l{>5zmGAA@NEr`s=)=UVQ5i%uYPROK?Ss~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!c1B#~L#nrZUOnI|$)WTwbek+~w1wKcOv zri;uMnJ_YAWXi~#kx3)7My8F-8<{vVb7bns+>yy6v$r+VN9K<t0LcK70wf1W5|At) zX+ZLTBm&6<k_sdjNHUOY*qU@8`LHz!K{A4*1jz}K6eKH1T9CXTi9s@hqz1_ik{l#E zNP3X`*qQ_(8L~AgLUM#83CR+YCL~WtqL54>sX}svBn!zFk}f1)wkBan#%xW>kene& zL$ZdX4apmlI3#mO>X6(a$wRV-qz}m-l0YPbwkCy04v{1xSwzx^<Pk|El1U_$NG_3N zBH2XJiR2SWD3VcIlTsw7wkD}aR*|$Kc|{V7WEM#+{!eooZwfpshZej2d6@;7*=~PM JHfH6;{|(X%Sn>b> diff --git a/lib/pytz/zoneinfo/Asia/Aden b/lib/pytz/zoneinfo/Asia/Aden deleted file mode 100644 index b2f9a2559a1ca4306a43c2abf074e91d51a64edc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@Om&wAq&W|Ns9pGBPk|p8-i}88EQ;_=YfO8yJJQ3?U?# X1~lkD$V8An{HAi*0L{14HRb{Ue7_k- diff --git a/lib/pytz/zoneinfo/Asia/Almaty b/lib/pytz/zoneinfo/Asia/Almaty deleted file mode 100644 index d93201cfc49134258724c1ce280e12b5db4f67ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1017 zcmd6lPe@a79Ke6GRk8|3U~|rynzn3NbGfzVzps}_FdH!<#2}&)p&&vesPItUI*6=G zco-}?M3*El;^8ZJ^q?SKOz2>T4kg4cVr%|BPlS;tZ~h*?_xZiwdpvmW^GWv)J&Kw= z!((@tj8;2Ydq4SKe4EZ~L@GkBvZpGiyuR`;wZ6?C<CQ<xv;I}Tw`yT18<-u@>d%83 zoV=qoZ+rCg%cRym4r|>&wbu8Z&@*lSv?08qje+la_Slk!+yx2cf6KY`U#X_&?@~?6 zi*kN`PQqWO<idxXG-on$@y(cA8Xl3zgF$JzJE75MPxNxfu(sa1sj=W~jYnHFUPx#n z&?kwtD!sZDk>u=OZCgDl?H~QpF_V|hu`TI(zasK*O}bzI(a~bD=sHxkbGQ$e97m3p zT$J8m?Kb9m13%?j%GmY&r>=5i()Y~q-Sh5_%kKRDOz(}f2}fdfUa>RIYmCbo&eA!h z_u<2SLJ=nrFT4OZ5I+z{4y`AMD~K<MGl(~cJBUAsLx@L+ONdX1Q;1iHTZmtXV~A&m z)-}X8#JNN39pWD1AJPD%14s*y9w1FXx`4C+=>yUTq!UOh4s9=xW*pjXAnic<fiwi^ d2+|UyCrDF}t{`o}QS0O&_T_1}<#jg4J=;$S+>HPL diff --git a/lib/pytz/zoneinfo/Asia/Amman b/lib/pytz/zoneinfo/Asia/Amman deleted file mode 100644 index 281b304e2f8bd4e9bfa58ccc1c799d3ab12f1da8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1863 zcmdVaYfQ~?9LMqhkz7v4-2*e1A|%JDj*?3*p%SID94#SVN-dXgRLgyYN9IA9+02Z1 zKy2pvz&-PT9{A67bN#xFS=ekCoAG=9&1SKghtAnKuhZG-+4ubk7L=9vt=}Hu<{Mr- zD$R?}9ZuEXvD<`JYFkf3pj~vbw0rZ&c84!i_w}xUh?nowbFb3waJ9d5ti5J;Iv6gU zLx=5NM{Y~+J?-s2wO^(0Mh!%U9!u2x#z4Q^OVWSpBRjhItPB`((H@w*R0c)tvtuGw zXw1_ofx*wuOKek#J><*+^)^hlhwjQyU-d~lZd0+u=O567;&kz+Hp;L`F_IXyUy`ET zGTgOAl2_G9^6Py%;^`V4dGmsdYAn>z$M;G~eXWeCtC6ua6*6vTj*KtLlhpE$Iw32h z6H|(Gk~gSnsd1X_8Lk;oT{NS)hi1BclG*e{r@ZbgQyU&=*46itz5TOHJ9tZSD(*|} z@xs9L#61$sJ)twYZ;(9y4$b?zTl2eCYyQLaI`e&j7Mz=>vu>p5?A;S}&apmPxORZf z-8EWdPFtO~y0^?vkCvi>Z&Ez4jVu`TL`uS6Nomi^Qu^YWl!ZOkvMYyW;j>G+=)g8v ze7RniY&)do^_z8R`Bq(4Q>x3Sm20RdQ&;!`T9H*El^y+B<(({5pK_$y6D!qs6J%wx zM^>HgCaas?%9<S?WbH|}uB*B$>$f*+E7#wiVJ_#{I*jL!&n@0twfOtbzxw4{%ZfT` z4$6v+ceS>xUXfP&pYME=l^wLw(x&qk<X0{i?^f_1F7=yvaIO3w7mHjia=DJ?dXWou zG*^sVGIGtxMI%>@TsCsu$b}<Uj$Ar&?a0L=SC3pia{WjFNCij<NDW94j;0Ev45SXE z5Tp{M6r>iU7^E7c9HbtkAfzIsB%~&!C`VHjQWjDdQW#PhQW{blQXEnpQXWztQXo<x zQX*0#QY2EPqbU=q)6o=)REm^})QS{~REw00)Qc31RE(62)a+=AMyhr+Wg~SXg(H<C zr6aW?#Us@t<s<bY3xKQuvINK)Ad7&kf}>dmWE~vMLLe)FECsR_$YLO?fh-5I9tMjA zK~@A=5@bz~ML|}@(JTwHE{<klkd;A}23Z?qagfzPmIwc@^>Ldq8tP}3yxGHiNxsB5 Qf1)=#A>MpUic4^R2T5_%d;kCd diff --git a/lib/pytz/zoneinfo/Asia/Anadyr b/lib/pytz/zoneinfo/Asia/Anadyr deleted file mode 100644 index 6a966013d9cd9007a75318d3c8c757dfe8a72373..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1208 zcmdVYPe_wt9KiACbz6-U6q)H<EB~a+)@++5mO5u@Vw*;=Qz85)3;J`2=nzO)R1joP z(4kB!sANdQ4iy*%9!7YO=nw=}@IVrDi5==-1?%~~BUp6m*!%LlpJ(sh4YuDmwg2dm zsQT;hm^&<vF0<ILo$~r8p57muEerU<>nir%D|ZiO%9pN(D?VR~dl#~q%K5UmZ>C1p zKPnafgi|(L`6e4jK1tQ_w^DszPHGa<Qrk8qn}WBc&Nn9YE6z&5nbv`?xAf-4o4JNF z@m#~35xpgQLI<A=>8%e2b?8Q?ZoC-Q+fG;O@bDTP8H!2tlvkQMs-$_}583W7k(S5@ zY5BP*t(8l<_5DlP@%^3NIg^&Qg%`U0VODoMzpG=HCUxidc^yA|S$AEyBfEN!>Fz`4 zq^B;ad%F+HZdar3tL>4#FA3>))Jy-XknH*Bmc-*#lAMp`2Cn8$j^^`u%TeNRTIO^u zD>|3Ei(VAJ@#o$==2B{Mn13}rtDH(@wdGdo*=x0ut7Wc*`@i>=d1gGLRu}Fk2U02+ ziJH}HR+Cu~zDg;Jqp;<g`iG&K-;`&t$Z+{Q14f374BFNV8yPq<bY$?z@R0zJ2#^qv z7?2>4D3CCaIFLY)NRUvFSZqx&NHj<|wk94VAS5CrBqSyzC?qN*EF>-@FeEZ0G$b}8 zI9n4P5}vJz4+#*75D5{95eX8B5(yKD6A2WF6bTiH6$uuJ775qZ#ES%MYa&KMMq);S WMxw@1GyDG{Y)`w%KNgBaJ-+}-2^o?A diff --git a/lib/pytz/zoneinfo/Asia/Aqtau b/lib/pytz/zoneinfo/Asia/Aqtau deleted file mode 100644 index 78cbcf0ef6617bf5efd26f866fd195dfe08e3891..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1003 zcmd7QPe_w-9LMqB)a95Gp4xP2tE{E-&zf$`nz}CGVZx7iK#+vrp@hnl6m<|hID()< zs3-9tDjh^Zp-vTa@EG+wbd&0L=+dQwbrBuw`+kNLc<R>k@I0@_{Soy2@y72=rq!_{ zEU$2J^vT6@(?;{6d$m?BoQi%(MMB&0$o|ij==Z&?=G{cR<^990*qf3)z4^elE>GIF zr(^ca%!rLo4cN0cJMFn*%%0CT+V*7GUWom$7aP9XM6f|8ejVyd-wsU2qczj<>5J~% z`KXgG-|4RPZQZ@Ns(VTcy7&IPN!^+=mnUAh^z;+gH~7r;-?-;8t<x@>9(CEW=W?;T zCigk(1`dkm%G;>By4z#&&ogH5b(0x-95KU7d!}%E-&`x5bhDL8B~VjaS0iWe#9wE9 zsQN>w{`eUTD%Bg8e}l>uRGm`SwF)Wa8P&)=AortJs?Py=_s=Jbqn=8p`sK>VmF3qc z72v2oa`*lrh5Q8-av(|gK3R}7zLEz?gk(ZeA-RxbNH!!Lk`GCUWJFRTIgzADRwONw z*H;oFnUU1Kk{d~mWJl5?`H=}AGeD*wDCU4n0+|Ie4P+k3M0{l?$W(k~F34n%*&x$F e=7UTKnGrH2WKPJWa8_FUhqDUjWpqQ`necBW<J^e= diff --git a/lib/pytz/zoneinfo/Asia/Aqtobe b/lib/pytz/zoneinfo/Asia/Aqtobe deleted file mode 100644 index 7504052a7113e5fd0061f08aae1434461123e2bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1033 zcmd6lPe_wt9Dv_VS8MaorTnwBPb)ul`EPDx>YVE`IvD(rK@bW%m=t)DL_tB2;ic$e zhlmc6(jh!d$WsL#Itu*`-lV#CiJ*ga2^;HuzFh{Ly7hj%@AK|^KR)>OJo$SMXJh8C z5w>@jjJTcbFTQDh`e1LXoW0~eE?sWgcN&AcPUElB&8BZBrRF2oX*pOdMcx<n%Fcqe zzMR#zCwKMg%58P#hP8dFM>{4WdM(|c*P~^<5&5B=;jiij!s7n?EnUZFh3=)TLigbp z>G}9cqHp)*=Ju}iK6@p7#SQ6yRFr|41sR;))Y$y84vnp8d~#Y7tuva8jcc-;(^O<i zQlGnY_$(tMA3{2M<VgCpD`W5cl3BkX<1daSd;h!KDjHp_R4P7yU45P3UV(Ep*ZE-0 zkJ<zF0b>%+`BPF^Q*TU8OwgEo!PmfQ>{@##Cq8>xeX{xtrxSPbCOQzeGht_vml@;p zMs@G4`G4?YKc#{jh#y{nBZw!6E05L}#2LgJ#2v&R#395Z#3jTh#3{rp#4W@x#4*G( z#5KgXN9!Eo9pc`j^$%$P(gCCeNDq)EAYDM(fb;=r1kwql6-Y0TW<1(%Ankax{XiOm jbOdP$(i5a9NLP@yAbmj^gR9ob|J+$9Z9B~LCPIGz&tTih diff --git a/lib/pytz/zoneinfo/Asia/Ashgabat b/lib/pytz/zoneinfo/Asia/Ashgabat deleted file mode 100644 index 8d9e03c13ae19703ebe48105d418622e53a14b55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 637 zcmci9Jxjwt9DwnQX|=W;T(mFoCB8;2LJ?aOigXZhsE{Ebh(o4=;3{<xy11#|z(MFH z4q4m^`T-pNyMv31i-Wp|mUvG}L2z>Mj>{7+Atb+eyZiNJ^=l039Tp?67w1D)x$}*i zR;N1boCYJ=J3EuOEoMI6F0;?CLGHn^^Y@LwYM;pH^`VSi)Mfl^M<$N9#6DP)$-PCH zs#!8!Ny|*3BeT|v%%vX1iKkrW{mY$y`t%o$T7EMOL&NOr&RG1%>>n^qrOItyX|<|i zN_oDKP-@dv>E5xoW9XgkJzTAAd#X@e(W|Uii3cb{kLV;icz;mPFBl?0(cl4yP*f-~ zQMC?5h@wQ1qG(aXC~6criXKIfqDYaXXi`KesuWp@Zd5HyQKm@Krv8Ti*G^XSUszu% GCBFgayMIXl diff --git a/lib/pytz/zoneinfo/Asia/Ashkhabad b/lib/pytz/zoneinfo/Asia/Ashkhabad deleted file mode 100644 index 8d9e03c13ae19703ebe48105d418622e53a14b55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 637 zcmci9Jxjwt9DwnQX|=W;T(mFoCB8;2LJ?aOigXZhsE{Ebh(o4=;3{<xy11#|z(MFH z4q4m^`T-pNyMv31i-Wp|mUvG}L2z>Mj>{7+Atb+eyZiNJ^=l039Tp?67w1D)x$}*i zR;N1boCYJ=J3EuOEoMI6F0;?CLGHn^^Y@LwYM;pH^`VSi)Mfl^M<$N9#6DP)$-PCH zs#!8!Ny|*3BeT|v%%vX1iKkrW{mY$y`t%o$T7EMOL&NOr&RG1%>>n^qrOItyX|<|i zN_oDKP-@dv>E5xoW9XgkJzTAAd#X@e(W|Uii3cb{kLV;icz;mPFBl?0(cl4yP*f-~ zQMC?5h@wQ1qG(aXC~6criXKIfqDYaXXi`KesuWp@Zd5HyQKm@Krv8Ti*G^XSUszu% GCBFgayMIXl diff --git a/lib/pytz/zoneinfo/Asia/Atyrau b/lib/pytz/zoneinfo/Asia/Atyrau deleted file mode 100644 index 317466d14b4749538893135618839a4c747838b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1011 zcmd6lKS-NF96&E>YDh#JN)wyf##$Rq)P5!=#5VrvA{|mVBv2?7Zm>d&lPC%ngc6+G zbSe&Fr9<fup`BX6!BO-#xQT5B7Z(THMO4yzUuY3Jx%G~_ce#%*ki3_F{%S0t{uzFI z!(t@u;(YyM<D&ku7SK0BJ5JEI9tocQTnQbW6dU)Wk(=Ao#qhU+-1;;nP0M3)d*PYf zdGkaf<Gs@SA|`ipVQI<M$-Vf6w1$u6e&B~hy#XElb*|eE&rJKwd(*!6UB|X|b^P<D z?pRybolC2_tFWjaOct~=I;FcuRwOYoE6KjM(lb0Fsisj$Ck7;aF(jGrYm+%hmwL}~ z=HW)D^k~1!WIv=#-`57yKOZy$?@r9%%Tx2XP+yuWm&=~2>Y6G$z1J?yTHmD)JgD{k zonEg}PLcm3=BOH_hP3i2l{c!6-DCGZH<h!;KFtiOhRS{JX<o&hq+Kby(suRmRZ4jn zl_Pup3OeksD5C_Th4)bd(c{u8f@p%Mg6M)MgJ^@OgXn`OglL4Qgy@7Qg=mGSh3JJS zc4;+3R6}&Tw8|mcA?hLeAqhY-fTRG)0g?nH3rHG}JRpfcGI42Bf#l-SCIiU^k`5#v fNJ5Z|ASpp|f+PjW3eH&<|7lwOtc|a~Gv)sc<x$y% diff --git a/lib/pytz/zoneinfo/Asia/Baghdad b/lib/pytz/zoneinfo/Asia/Baghdad deleted file mode 100644 index 97fa6c73cff714bf9858c9697185f300a1b51347..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 995 zcmc(dKS-2u9EZQBcM>WUE_!-pnr5e7z2}|XRj>NypIlCOhr>Yy9V`-yh8iLyzYv6j zAk<J$NKn)e4Ymk^5Dgm9po>U4{1&OjNe~L-{5{tqG&%Ws`Q7t!yd1~-JlTP<n=SI+ zitINWma>QYlWIR^9oLjQxBT)yCq7sFTJ*ykU0&t)XDd~UU%kl3W&hNx`{wlY4O2bQ zZ_eEAH8n$NQ=5sJvu>rSYc4VMReR=K$+l?-g-058AISNi3mRSjD~;bev}wW7<`4HI z_HtA%Ouy5M6VKJT^F!jp5pD5@CE@v!j6c=Zn#X#nY)MiD^OD;6t?4aSF0Vb(D@%9v z>ij2h-@lQz7hBpswWb{p!{Ut(>b0R+>CDVXSKF#~$G&J!bx5us-<4iT>trsM3ltRo zyMm!ZTtSiOTl-cd`daLLqa(*e(q5qS;C-eqE1mW<ISG4`JT8Gi9+Ex8>$m?hH}+(1 z6QY4%Kt-Nb2Sf=(3q%b>4@40}6GRn67epCE8$=yMA4DNUBSa-cXP#CmLn}lrLoY-z zL^DG*LpMY@L_0%0L_Z?|Mh1`+AUQCS$kS#4NrRCGBoRm^j8qu8K$3xE!$=2`4<jK) bMv#;sIWdxAWCbU!t0PLQ*tNm6#}dW+6bH+- diff --git a/lib/pytz/zoneinfo/Asia/Bahrain b/lib/pytz/zoneinfo/Asia/Bahrain deleted file mode 100644 index f5140926a2fb09bff7636139e0aa450d5ae06f9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@KF|r@00!U6T&f|NsA=k%@_c!5{!6Z{fhe!oZ+qz`)_- p8^WM%U;@O(APFD{A;EZ{ssBM%fb@Z^0nsE_#bpDu)=t-$3jiUoA6WnZ diff --git a/lib/pytz/zoneinfo/Asia/Baku b/lib/pytz/zoneinfo/Asia/Baku deleted file mode 100644 index 8a090d77d04e49ad6f25cc9cc0d1b050a67233b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1255 zcmdVZPe{{Y9LMqR+GcZF>|%2+`)9WNQ>^{5S<_ZJhc;{KQiuu<B^44Y>L&R>B^{)o z@DO?K5Eez9QelTKfeAr}Oo9iZf}}b{V8wdgzYwAh(Xr3?`JO+=U_P&951tqZ$sco@ zJ6tSjE}naC`^H+|y)4a?1#aY5mCpvfuBQp_qU~P!d&&Feqrr-qOL_m)HNAS`g037t ztJjPk(`$!%^}4~7UVkX2H>7KHRlGuPtS`~k{%?9y$p;;9`uYQ(-Tj;26z6K5FXn0= zFXV!67xLQ<&v`;;9(qDga`|x69f@>bmT2WwX$YN^hVQ2(<{we9S3T1B^|;zG9hIH) z9V&jSOEpc_tLBlg+I4fKN(_{#-52X*Px6auIl4kxtLId*<&(5ICsnHInWR3xkaqh6 zX`g*09k0h^@7-}p&)ip?SI*16iR)_rseZ|fo>N_YL#lhQPxWj+qO$2mb-<fYz4d-I zTqqQ5_GLdG4(BhAqUA*nyQ_HVZ-1#>Wa||F>Dr(;MA|Yomq;ujrAt4T?%2$o-(QpN z%t~D_Y_6!eBD{^*tXWU3-?IO3$mTN@IB3dY^DZ1X<<KbyZ`B+=1poyB1p);F1q1~J z1qKBN1qcNR1quZV1q=lZ1r7y|RRf5Ehysa%i2{m(iUP~3!9@YaK*oT^z-HBeW1zEY z;4$z~05T9#AW|?=KvGarU{Y{0fKre$pi;0hz*5j!HE<btts1}##0<y`%oNZJ)D+kZ g+!Wvx<P_)>>=f`6^mN$R_%GnQ<K`>Tyd~oP0VQYu1poj5 diff --git a/lib/pytz/zoneinfo/Asia/Bangkok b/lib/pytz/zoneinfo/Asia/Bangkok deleted file mode 100644 index 7249640294cd596c9445ce175455efa06b5dc6c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% diff --git a/lib/pytz/zoneinfo/Asia/Barnaul b/lib/pytz/zoneinfo/Asia/Barnaul deleted file mode 100644 index 82cc49c487a34f51dba12ca0e4cf2145bfc7965a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1241 zcmdVZUr19?9Ki9roNmkr3CcffWtlCT=5f_rP3@E}(M^LGLS+!zAA_P1LVqYUw0emc zQ4kS?IC_dWVD%6}hYEU;9>VNFq@-Tzp`s)Rjdi|P20;+?*ty*MIlEjgY`^akdyX6m zT7N8$xx>k7GAH|jp;zbT9t=;HF7Y2vEiFo0h3?^k!XK~ui#|-GiYN1}lCixh?`V%& zc6Yb(-D*|KFGtjhvz4m!s86lzT%=aTGO8@{MXe4@t8(v4wI=_m@;g5Z|IAyl_Wc`K zaq5z+7=I$xJsS~$`)N^`zAmb+T@>pFPm1c^9#Q=~-Y*UumNomX>fniEdP9A$-neV0 z-sJ1hp<s;;{S51{w_S#(O7!MgA-9aq>B!__S$C&c)<2q&4HstR)|(Sj9-NZf2EOX; z(Z{l}dt5h_56NibL%kz+KsJ}%(9NH2>z14|y5+@r-TJOm$L@9O_}FRLb|tD4v2UW| z`$yTA$z&Wk3+4}J?r%q)EBnFi%KPi#bUB@t)jr6NoY-%feX`%-wydtCwJ>|l-f@^a z^Uri8+4D@iEnx*j$edww*76$5VV32vxArCR5A!u&DZ`ABIr9Q$jm(?H%x%rwk=Y~j zXVC!C0n!4}1JVT21=0r62hs@A3DOGE3(^eIjjd@1=?7`Z)^volg!F_og>;3qh4f`> z8bdlmT0?q6nnSun+C%!YH4P#i+L{)T9+4)IE|E5oK9NR|PLWoTUXf;zZjpA8evyWe oj%`iLNYA#WX{2kUZKQ9cainvkb?h@u{13f*>dZf(p{myN3kAm+rT_o{ diff --git a/lib/pytz/zoneinfo/Asia/Beirut b/lib/pytz/zoneinfo/Asia/Beirut deleted file mode 100644 index efb24c27f83894eb39286eeb571eb064ca27fb5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2166 zcmdVae@xVM9LMpmB8n(#wPqnC280239vD9|k&-SnIenWal8Jm2PlALJg%mQJX}M*t z?403juGEXlF(ks8xms&YUBi!C^pC@RR;_M|K$F&*x%xc6{n=mrSD)K`AK&lYU$@=s z8C+evG1vLWiLsyX=DcR#+$Zd`mv=+e=2+`4IlgqKI#JxCBk39Dy^J>9asLg|ajjdT znR`rh)FW3XeX6eZD}8Nn&<ys5<@&|VW~lvc8SXlvMq0}BxX4-+oAtaNUlL;`O#V>f z(h5}E^(#6)w$8*~yj>>!JZvVPIwT3_Q`M9M7xdKkW7V|AlR9zKYi3+JqLcd9tK@Di z_w*h#Gs7`D<<NOGtEoWlJ(g`!SA8aFHB~CTa9H0r|6`Srm@2cAqh|Kaa-Heao6Nq6 zI;*EoWgkB!o^Y4)?tW9|Y(1cS8=54iveV=S-q3T4UN;YTw&=X%Mw6egQs>_an!*`t zq;MeLJa}_~%sYFRdFXPA%#Y-$qBCP<!OPQC@!=ok;n45uk=>47=)bH=s=wEdrk_>* zlCSh*u}4*DYDAX~oiU4J_UgrbZ=1(|sMAY2+f3OvuSj73I<xd-tt@LOF|u!oEMKM6 z6PrA;qF|v4mQ9tF)6-OW)-O{2+jv!RXM(QyE~=g!9oDORFQ})!y`<M1{mE4Je5BX5 zoikP8gIZO6VAk#Emi5K^Om$^gHq2}=p`vEl8275GNv@EZXuW#+);y^_w@lRyB*`-! zIcn3_g}Q$49jYOcsGqI*RXw-o550N$ZKg4FMQ`!lFkAg!=%#7?rrA3n%_FDHwke;> zHr;Nv54|HTU9G0I?<IMm{VlblGbAr=Z&7XgOJ(es|Io{EW-YS+VJFi!W}M?Br8pDI z0zt>`U%@5FH}(Yo#UlQ+r@Z&ePmpCG>p&KQtOQvKvKC}9u68xZa$N0tkOjHg6(LJP z)`TnySrxJ@WL^9Y7KW@0SsJo7WO1%`b;$Bu?fQ@fA}d6eh^!G=B(h3mnaDbkg(53O zmWr$uSuC<zWVy(Ckp&|wMwX1M8CkTeT{W_7SG#Ux;mFF7r6X%c7LTkRSw6CUqyR_- zkP;v@aJ5B1s^DtNfYbpg1X2m46i6+QVj$H(%7N4aDF{*#q$Eg9kfI<}akXVZ>f&k( zgH#474N@DVI7oGn@*wp=3WQV$DG^d5q)14WTy2?<I=R|HA(cW(h13cu7E&#wTu8l; zf*}<{N`}-7DH>8Wq-;ptTy5cy$|0pgYKIgLsUA{3q<%;NkqROuL~4i>5vd|lMx>6e zBd6VGD#<gYL~4l?6R9RrPNbelLGk}t(RdqFPcCWA4$SrDd3|$y*?}D2-*@qU0pfCg AcK`qY diff --git a/lib/pytz/zoneinfo/Asia/Bishkek b/lib/pytz/zoneinfo/Asia/Bishkek deleted file mode 100644 index f7a7d5480b39ab958253bf9d9ccc67408a2b3660..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 999 zcmd7QOGp%P0LStFzui<!VLJGjR&BD!mg{3$-ABIK1Y;r>BDNU9Lxd?y9z1obJSa-g zAv{Kh1<ONpQScHy{)fOlbO|~P$U+Arx~PN~S+?!}osgnV-kM?NvkVL@^ZjLRWXGf8 zpQB1Y;o^wti{Hiz^MSFtxMkOc@8{|pSB!?LWvgNL%j3px8#!~`G>&}8=0dOUs-~9{ zs`;s_S{_|eNALA2YcisajW(&)ky>@U?}s`O*-|G%pVg^DA5}P5N`-eeQ>Wi9sxw~` zcH8^sR%Fez+n>!_9ZN;Kb2e*r%@u5W?6P%s{GA<5Pvp7>9(p}jujgaUcf5EsnU9y! zUNUseNfx@ibH#+y`{uCMw{AN9OP%h(tD-YFXSnB|6r9xU@9u?}-Tct-CwHj0U`?0H zWf?f|>kJ<J<E)Us&r12%83+h5yv>)nctr$-xHTp9=gHg@HGB8HC(?f+Yev!;5wT<X zO6V)jt3pcY%Pq5y2>L&l34z4meS#oSkTAYF4iX57goHw3A;FMnNH`=O5)g@qghXN@ zL6N9PSR}5m4va)bLL;$};7D{NJQ5!n05Sq(2*?<aK_H_*hJlR3R}TakiLV|CG8SYo n$Y_w^Amc#>gp3Fo5;7)aP`-LpIIY|K7sIOV*K-@}h*$pvT$S04 diff --git a/lib/pytz/zoneinfo/Asia/Brunei b/lib/pytz/zoneinfo/Asia/Brunei deleted file mode 100644 index 8624c7ae182ca0bd5be20dcb0d9556f73bbe7d56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215 zcmWHE%1kq2zyQoZ5fBCe7@Kcx7n94bboY+>|Ns9pGBGhQWbXmVXI3z<FfbG}FmU<! uhA?Owm>U}aNehq^5QLCm0npt4AWJ|r$Rd#bdLWx<%eZWS7Tf7sZ~*`+LnY$? diff --git a/lib/pytz/zoneinfo/Asia/Calcutta b/lib/pytz/zoneinfo/Asia/Calcutta deleted file mode 100644 index e1cfcb8d09dc8e16f828082396f5095a367881e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 303 zcmWHE%1kq2zyK^j5fBCeHXsJEg&KfF``kUdPTlU&IKx^fab~K~ic2LzGZ>kefslbA z=mt;$gSQ(<wQmFi2LnTN1|yG;ZwP}g5PJrPFlZZ?85@8Ufk6m%yIFyfAPfTRKn!Aw z{RaZIT@o4~+WGlw0}u^z3y21}2SkJ11g3%R0?|;nu`shR!yFfM0pvKK6M>EcI*|wL XL;;W!J#adcfq@I?c3o2|V*@S#`D;p@ diff --git a/lib/pytz/zoneinfo/Asia/Chita b/lib/pytz/zoneinfo/Asia/Chita deleted file mode 100644 index 3baf7528268ac85f44ea7672ee0827901a302740..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1243 zcmdVZO-Pe*9KiAa+I&GJewWf(=1SMHsp%@&(rL9!bd#b+Q4$EfP!=(ws30=v^)TXh zD1zu<^adeadO4W46e>ihJXr_94s|h!2qa?tzfTQDojUe^c%IL*{~iY0@4Mu#gZo3~ zuT^Y!I9V}!axb^u&GtXJJ!0kswx#BmU79Q{yf{-j`>dzz(_m`B#PsCC(bklAD4~l6 zqFR2lP8X*uwBod=@32dkbbryM@hPor8rQ1ei2A)xby@L!4S3#4;Onp~|M1*bee#g6 zdi<WO7``sS$Cst%&MB$A+ADSEyJh9Egw*eiO2h608af)&Rnbmez16R4%4@VSRG^JN z=4#kmCE@oo8u^|tYlq%w)5J$<zWZ6CgRi9J!fR>0F(B*qJ(Bh3Ug(ClYZBY@KsWkN zNLwteoAP!_d*u;rpFXY~?k(CewnICoLK=S<(ZuLx>AK?8Wc;Ps{%y?Hm&s&Y?m52> zPhQTEUy$=6_l-aA3k%$CV|sh+&uUU<jmMaDzpK!gGipk**X)kV?qr`a_L=OyDM@rC zO|U*}f(`Z72p?u#&T?*lUy}bYV*AZ9j2Rg<i*X|(N5<}GM~{pjNdU<JNdd_LNdn0N zNdw6PNd(CRNd?ITNe0OVNypLVgCvAx<Y-euazc_qvO>~A@<I|rGDA{Bazm0svP05C z@^iEaA{jc`6p<W}B#|tUG?6@!M3GF9RFPbfWRYx<bdh|KgprILZOTZ_jy7o|Yb0$X bZzORfa~7#%pKakk%Dt%B{uNqkBSk*}Af^Ur diff --git a/lib/pytz/zoneinfo/Asia/Choibalsan b/lib/pytz/zoneinfo/Asia/Choibalsan deleted file mode 100644 index 79b9d3c8235fd3df4585d9ce14a1e59c4a0d24b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 977 zcmd7QKWGzC0LSr{G(l+swqikzsWwfT)R-n|qgL~$)kGX@oMIG3cr8VcIJ_a7(%uk+ z)(kgfDk>RLY=dJ)N5KwmbrOdgjFQD3sB?VZOoEGUzT@~D2Z3<kU+M0{2eaajb4b78 z=A`t^^RcJ<Un566pUmUEJ>$g3Z`R583(EH<AWyxXQ2ysGIbI)9ryqZjXDVGaac@rs zZoE^I>9!0;cU8z&ms6f+D(u>^!arWwk<Xjf^oI@m?Au3Hv{SLqwQgFmM$w+BC9HTg zZYNfcTFLuMYPJ}cbJ-b{ijB(i{t<P-`(0k{ZJX)NOC!^IVP+eKk*lqn3)K~4adp|e zQYsi%3wbl2nlcK(ka^9Y7uOHk_N}q4X4$>0%0HhpZ+G)*z2EOUoUWn2znpG;Ifba) z;iIf5;Sxd_;;;~Frtk_;Z#qVJcIao`FBMB7nz*R%d3|3J(PV-j{&q_L@a^^A?eqQl zISr5wNDHI~(gf*(v_bkHjgU@AE2J0F4C#inL;4{N2kMSUOQa{#6zPhzMfxI*k<LhK zq&LzW>5jBV`Xd`ac7SXF*#oi(WEaRbkbNK<L3V;{1=$O-84kNawu9_vpxzL&Bm5t> NG@Q{p%f;r0e*s7H(fa@Z diff --git a/lib/pytz/zoneinfo/Asia/Chongqing b/lib/pytz/zoneinfo/Asia/Chongqing deleted file mode 100644 index ce9e00a5db7c447fde613c59b214e8070f30ba2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-<beU|pf zjcbQR>1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_<xx7r6*b|6_9zz#6+EmOik3e$Yf+TG98(ZBtSACDUckAnuPZx3z7!OgCs&SA*qmD WNHQc_qNYRgCH_BQMr*FDXTAZK`l!MH diff --git a/lib/pytz/zoneinfo/Asia/Chungking b/lib/pytz/zoneinfo/Asia/Chungking deleted file mode 100644 index ce9e00a5db7c447fde613c59b214e8070f30ba2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-<beU|pf zjcbQR>1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_<xx7r6*b|6_9zz#6+EmOik3e$Yf+TG98(ZBtSACDUckAnuPZx3z7!OgCs&SA*qmD WNHQc_qNYRgCH_BQMr*FDXTAZK`l!MH diff --git a/lib/pytz/zoneinfo/Asia/Colombo b/lib/pytz/zoneinfo/Asia/Colombo deleted file mode 100644 index 4fc96c898acd87282afde27b9e729a54c34957e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 404 zcmWHE%1kq2zyKUT5fBCeP9O%cB^rQ4``o=Ur=w&v&P>%_aH+Jx!f+XbhEXAdf{Xnt zjr#xp|1&Z%voNu;F)=YPc-;Z2XYhK$z{0@b8v!yRB!Q8Sfgw7BQ3ymb0LhR92A~K? z*2g!5!54_N4NQ#<fTS4+16k-Ggaiiz-Sa<e+e{S@4RSb$200!?gFFDDL7o8F0rCil d26+ZVgFFPH>w#)$?kO%Cu($1WO|6U#xB$>~PwoH! diff --git a/lib/pytz/zoneinfo/Asia/Dacca b/lib/pytz/zoneinfo/Asia/Dacca deleted file mode 100644 index 776f27dac064a5925a2ca5c3cd4c3c60b0e4af37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 361 zcmWHE%1kq2zyNGO5fBCe4j=}xWg39QsoQNE&af6roSAC0;_iw|GrX*i2zcAvm{9-! z|9?g%W+oO^78VAEkRw2)3_%wdfb8fDkO{sK417TG1O^cXhNJ>U2_N4O1`i<CHZU_b z0FtI)(hSHl2Wf?a5E9%6bi)74wzfSW8stt84RR}(2D%qSgWL=<9OP~g4RSk(t_RA| P!u?z}Kp)!ansEUDXNyq) diff --git a/lib/pytz/zoneinfo/Asia/Damascus b/lib/pytz/zoneinfo/Asia/Damascus deleted file mode 100644 index 4b610b5a0836df1e06033c1b28afd4152e2493eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2306 zcmdVaeN0t#9LMn=AVP>}q$?i=q$0+{1+IVv2o@4y7yAVl$Aij`C<zHV3K0s(acw@$ ztSx(3q^$^0tF<)emSb&|Hg(NkwPK6ajuy+F?8t)-Qt7-;fA&}Z)%Wh)*V(;)f8PFz zx(#0Oj|*~s;o)9(9^R+FG5mJxo7<Dk?)3+Zx2kV7dvZFB-W4NeUsSbmDCTSPa7c%J z<+)-pT=|+Y(zL`J&Dv&+mA-3UjrQBuGA@W~lNt7S#J6Jn!by8#`~mUj$A1{t2R|1# zdYX-!{lc8w(PxW;X(FI@&<O0THG>Kx>?zg1nZXG!*i-XjL`e8&M(CmvGxYZfJ1lso z2s`gFrd<jb;qM<ark}|*BkIfT8AqDLOy9G{9iykjoWTZT?!YQBuYcHxDqSd|_Y4^^ z1zW|PyXV*ovYW-i`h)f&??+~AY`(F0_DVA@IL?T>T4^o$>3MnA#d#|J>};8EG(jbH zSLvj#0?V_vT_(4;sg#EIbZV7QUYVws=3G>1sgKL_s83Z!WS3qR_^P#R<h;ClDB8N` z8^6pvU81s1?9|zPJFMleoRm4cK2*6|F6$Nbx$54vP5M6HW-D)bpIn*LtMVgn$os=H ztb+I)Suj?j3a4$-h36)$Rlfwv2an&bd}n@?Mf=Cq>cgkyL(PLq?mi?R_V=sex;?Ta z?RDiZ?$xEUwyQPXU3$%EjVg=WsLQ_Vw91Elx}v|@s{GQU*B;2T)*YRx*LTEPRb5F^ ztqZZL+h)i|vVXQ}Dz3;!7f)4>Wqu_$1Yc6MbC1i~p|h$k=nGwU>K(Q5hhw_FXP<iF zlNWWvz9Uv+_tW~x#=VyDOu62)y3yKPlck$H(rWgV$)_T`R!d^0Y`HwkY7L(+TL)6r zmS1nmttW0%ZRaf6{?eH0_~5$U)-s?v_kXVg1OAgAAr^$Pf%uewDMHMT5x4k?{31WU zgfBn;IOqQt8=7>E9U)sn_JnK-*%h)aWM9a}kewl0L-vMj4%r>DJ!F5#23?&UB3pEI z_K0i}*(I_~WS{76loxi&Ia}q}E3#Q+x5##p{URIY*fFwYS7*=2rd^#~BirWKH?nbL z=g8JM_Ks|xWB17RIrfh<fTIIQ3pjd!G=ZZFNE?toAdNse;p(&k=>^gZq#H;(kbWQy zK{|r81nCLV6r?LiTadmWjX^qtw8qux4bmK>J4kzw{vZuPI)t<c=@HT-q)SMfkUk-e zLOO-C3h9-r(=4Q0u1>p<ejyD*I)=0i=^4^Aq-#jqkiH>}Lpq1F4(Xk%(>$bmu1@=q z{vi!SI*7Co=^@fYq>D%!kv<}gL^_GI66qz<Or)ExPCJo)x;hO-I*POu=_%4wq^n3< zk-j30MLLVL7U?b0T%^0MPJ5C5x;hO;I*ha!=`qq|q{~Q~@&DfEFelYXUZR~?wA7RC PNl#8mEJ{uJ`z`D*kDRCJ diff --git a/lib/pytz/zoneinfo/Asia/Dhaka b/lib/pytz/zoneinfo/Asia/Dhaka deleted file mode 100644 index 776f27dac064a5925a2ca5c3cd4c3c60b0e4af37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 361 zcmWHE%1kq2zyNGO5fBCe4j=}xWg39QsoQNE&af6roSAC0;_iw|GrX*i2zcAvm{9-! z|9?g%W+oO^78VAEkRw2)3_%wdfb8fDkO{sK417TG1O^cXhNJ>U2_N4O1`i<CHZU_b z0FtI)(hSHl2Wf?a5E9%6bi)74wzfSW8stt84RR}(2D%qSgWL=<9OP~g4RSk(t_RA| P!u?z}Kp)!ansEUDXNyq) diff --git a/lib/pytz/zoneinfo/Asia/Dili b/lib/pytz/zoneinfo/Asia/Dili deleted file mode 100644 index f6ce91a159ce514dec19c79115178494e868b227..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 239 zcmWHE%1kq2zyQoZ5fBCeRv-qkc_uxRIC6TX(Fg8V1_hRfXEN0P|NozniIItkfuVE{ zNM%6-0}BH~-2?^>AKwrLZ37D+wggE4K?n&J0nPmnvJ6CnEClHXSqh>-7K1=NP>yKJ Nxom(AvD3BW0swcSF2Mi* diff --git a/lib/pytz/zoneinfo/Asia/Dubai b/lib/pytz/zoneinfo/Asia/Dubai deleted file mode 100644 index 7880d5d7c92a4a5fd994630588e9d74fbd636edb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3^r<p72|NsBb$jHE8d;=t9;lRM+;~T=DZD0c8GK7#| X8qlEsAQM6Q@SDnI12o@G*MtiIL4O;g diff --git a/lib/pytz/zoneinfo/Asia/Dushanbe b/lib/pytz/zoneinfo/Asia/Dushanbe deleted file mode 100644 index 694f6e6aa3973301002bb9852d828b1c49808a98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 607 zcmchTJxc>Y5J30zG$ATnqsf`5@$-a32tpK8<SIdkkm3pv3$aiF7FJeD0=9zw0E+`V zu~D$Im|ASCZ7j5~F^!Fdp7Txuf|ZT4%)DjTU1ndnR@<63zap(2l5jQIJLm3U<NB;w z>h%tyzTB0QO<xwX@6RW>m#4^maGn0UT9m)plfuQe44kgY;L)-S?axVZ$Cu&tf{avp zWOV*h#{8y?=O1OF>s~zjBk<l{gUQ?DYCVo)tG(@e*&RPtXDVe((9j{wQq|bTZ0=e* zQn+EdTea1(w8NRoN@)C2S#wr%hO5TXNH$u+KYU#0iupW50B;}xA_5{Kp$dUWfrx>~ xfe3;~f{231f(V01gNTF3g9wC3gouR5ga}QjQXyhtU1$F{xtRr3zc^LSd;ylKdI<mk diff --git a/lib/pytz/zoneinfo/Asia/Famagusta b/lib/pytz/zoneinfo/Asia/Famagusta deleted file mode 100644 index 653b146a60e5e5641a07bfc82f9590dad1ed69f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2028 zcmdtiZ%kEn9LMo<Lsnk=^Cka~UH)kJ54^yID=G$>5|YK$W8Ab<;+SwjLrfF`|J?@? zbB?*M#@1ZA<s{acL*4xIz{+XP>E`fk>4WB&$86P_ur_n98msd@+9Mx0pL*`@echd% zdmsJYpXkbUu>#|<>v{JMZ?2v0oA1eAew-d0K5)$O_wTop{;qLGe0Rwl*<ECf+H>0; z-5#){d8wVecF20M(lN(GCOBim7tC>)=dJN$y3Le?BUZ}24)diupI8%qs5f8!zRn7K z({868-Q}bmjM?d(wNA$NH|&WG<<6wl+4d_HSx#p02zzoc$(a%uw5KKwIMWi7%xU-i z*7TnT%&hBot?Y}}%o!*9tYG&EGpFmgmD~QQnYU%X6{=}A^Q(HDg2+BQT%yj*oXvJ& zT9Y%YpjBoK#br*QOXu{jk)nTEbndsM^6JfZwYaBROU};Kc^wOM{^zOsS}ao+v=_<3 zMTuHk6Oh-Z{HkT8L$WCGik4^IlJdb{WO2fIS=@J7-n?@}-s=5YDz2W>$lgv_a`dn+ zZEO^?bC)h#u}9x-sMX5Z4H~T|*LPBu=<;BeE`Jcx6?WWy@BVaIdHPRXb;B>Kzetv< zlXvC)_&ura>JzK_hOF7~yM8eLjI6D?sx=u0q_(72V=tW2x-`|g-#*j&p(b5-xlPyi zNA<(6R%yefO|oHki8l6B%EtN({ivf*HZ3cbrdWz>&QF&uWq(O?vLP*b<Fw_EpQLrv z18uc0O55O%y7hREZ0kF#+YemO9leKiXIr<%_r~=(-_yMkhCTU}_~b_(Jz*Fb*ExpS zA>S~=NS$a5|Nryho$Qb?^5LC|NYp4RtK=hU%m{_~mCtAR9ua+tqyDdZaoqU}jy!Vg zk)w|se<T4U10)3`2P6q33nUFB4<r#J6C@Q+Hy0!sBpW0hBp)OpBqJmxBqt;(Br7B> zBrhZ}Br_y6BsWhtIV3wIJtRLQK_o*YMI=WgNhC`oO(ahwQ6y6&RU}tWH(4ZGPd8m8 zUnF59V<crHXC!GPYb0$XZzORfb0l>ncO-cvdrvogB!5qL0+1O%rU01(WD<~BK&Ani z2V^3UnLwrjnG0kxklFBbrvsS}WI~V`L8b(m6J%15SwW@+nHOYYkeNZI2ALaVa*)|U zrpMErA7p}%8A7HAnImM9kXb^e37IEkqL7(FrV5!WWU`RiLZ-{poiAj<Jlz>XrVN=g oWYUmXL#7RxH++UB&hIW?P5~PjjD&Lwb3=LIU?e}}eVgh353H8ZHvj+t diff --git a/lib/pytz/zoneinfo/Asia/Gaza b/lib/pytz/zoneinfo/Asia/Gaza deleted file mode 100644 index cf54deb8f1d44eeb4281a3500415fdb1e4c7ff46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2286 zcmdtiZA?{l0LSrjeISKVBeOY22#F}ly<VPfm;^x=^qSZyuS}4KKgomU(<#wGfZUp~ znJz{TIwuQXFg7-wm}Aa0dSE@8c2B6a)>{8ws0<;F@?hYU{@>%vmYZ)oXXk$YyLb1^ z-Ti(2g<C6=w0~V8>Kz`g7WHtR8&EIb6?`HuT>VnNc;cS+<2R#bu(?v2owepq`-SX# z_j}_~MOWbRHfi>IHjDmIr#=vNP7GWT=8eHgYp{EVHWX~M{yK6?8}2$|Shb4+H(Rb* zw+aiik=^r+(Upe+V;kCx@u-NvZBL(ddn!kpm=|J9{CQrRoLFJqS)8rUsy`@a-R&}G zj}M5@j$||Pv%lq22X2~C7pBAlaa50f`?QGhb(>F@d@L767wfTwZm~$q)Z@a2WZci! z%*8i?a>*Cd_4vz!Vrl0BbJ?dAGU0&3bRJtJU8SA+^168GUj4P{zW#_zEYB55S-m3J zyF{eKw2Rc(P~mYj3D3b1`OK4LA}z$Lr%g4;6~n@O_K#AzGMH_qf1V|uI~8kYv@MjG z%^~LVjWcCd(OWXR<hpzz>#XH<c8HwE@9Mda{wj0(cNwb&=gHM)BTU~9lXA`Rbn)Uz zSL@oc9y9O5iPm+gz4}YlzX^R)i+tJNBVO?y*Vm`C3BPZ<zF}^k%#UBE=a2PT8|R!C z8?Urm1tTX!VRw_Y>C$_m=%X!G@fkyu?0d&3Z9ODKNtIDnzf-=N;WIWD>9Ra(nNg9R zBrEQP8&#1{$g1CmjV*V^<<@V*tk<s25!*V3tnFWoi5*QrYiIi}Vprv9%h-QTyq^1k z^+x$Iu{&;$RqbsMdmek=*y}th_YPMZHQ{x#CYW#34i(EcPo)`k7c*sj^L(S>WQ^Qb z<FOhKXNYNz2j4TM|K~l;F_Zhj-+$;&H7#0K|7&fj+c87aBBQn0_Bq@>N8EpBZJuAt z$yv{fU-Nm@qt5o_xR3uKRy3uqhMqND$eNHvA*(``g{%u%7_u^CX~^1;#UZP+Rm(%x zXR8*7tPoiuvPNW)$SRR#BI`sJimVh_Dza8&vB+v|)pC*b+NuR3D@K;gFUOi~)uNGA zBg;nCjVv5lIkI$Q?a1Ph)g#MC){hhbser920a62`2uKx>G9Yz83V~DtDFspsq!_lU z8b~>idLRWsDuR>*sfn#B3Q`rMEVimHNMVr5Af-WSgA@m;4pJVZK1hL(3LzyzYJ?OC zsgkWK6H+InP)MbaQX#cMiiK1QDHl>Nq+m$Jkdh%aLyCq}4Jn(gsvA-`TU9xvbV%)x z;vv;T%7@esDIiioq=ZNfks=~hM9OHZ>WCE5R#g%yB~nYIm`F8|aw7Fa3W`(|DJfD@ zq^L+$k+LFnMG9-HDvOlXR@D|ME>c~jyhweK0wWd1|8I#yRid3q#5y4_*_Gn*EKf|x LOLV(ZIfecMp5^oY diff --git a/lib/pytz/zoneinfo/Asia/Harbin b/lib/pytz/zoneinfo/Asia/Harbin deleted file mode 100644 index ce9e00a5db7c447fde613c59b214e8070f30ba2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-<beU|pf zjcbQR>1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_<xx7r6*b|6_9zz#6+EmOik3e$Yf+TG98(ZBtSACDUckAnuPZx3z7!OgCs&SA*qmD WNHQc_qNYRgCH_BQMr*FDXTAZK`l!MH diff --git a/lib/pytz/zoneinfo/Asia/Hebron b/lib/pytz/zoneinfo/Asia/Hebron deleted file mode 100644 index 09c876a669d3cf20a53a148dbf9320307b2f2127..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2314 zcmdtieN0tl0LSrjeIbQWGqaf^ghUht<n@M05OhH?p;KO&ATJNei{{%Y(LsPvBR12; z%+fhs_y=QS*~A=c&e03&)wFv;t+i}D{h=^~ILeEGQ+mFSf41D}&(7JopJ(^({&RPK zUvK{A@;Kvfm!EovhwGqvIL~QNFW=;SB+p;{+`Mq&uJQd>Bet)(T-xo`_79Ch_Pq72 zb+N3Y;nEgq_qjKUzL6-iKj4h$zbx$Q0~5|b=Qd-|SMU6B_@*(`aoTdK7dG5DaLu`y zpJxp3oM(-!*xxX^uEiP)p4D*6-Q(Pv%rM61`dQ<@pED-Lmpiu?rJFNq_sJP|I_#Na z{ldR3-VXZYPx<)X8+P#dNikm>F+<)sB|@`0?I()fmkUA)&9M9!vCv30!vh9o_>b4@ zMb~|D@n=)bh)V-vN&9?z>De+Fxz}Y!9bGA-i`&g*H4!pq)t7cm?*lTnG*iT-b&Gh< zVv!KqDiXu|h1=C6-1~;*laH2&BtMUtG+8H?4+;CJ--_i5U%H+AX_|ccWSE`OvOuOb z``ORbPnT&0ugmnJUioa=$BrkeO=LWL$IN{2XPMcz!&*5oSFSqiXJ&mjAy*$y7SDZf zMXo97va{bEKbDi&ZRQjN$ehM2=JS=m2y?>$`GU7gyy!V@u1#nW-mI<Wx;Z^EHzLQ( z9qo43&ps#CUv72shEIt6&L(HW#kWPldz+lX)0QaO^QKjNY`+vm6;?^@cKK3DmbJ0K zl%>H-t+M1eS#~$jst9^SR{T0-ZMr=sH-8=AynJ=G*wQxWZ2e+XY-{p4+gpDUJIYTv zR^u7*O6Ec5)zYJ4XZS9s(o-jPJ@k&XJL-tsJydB`1=h$aU#?X>SSVjRnPk;mNR_qC z^Q^iLL*<?-w^M&8MNDzs|DHDWAMYuy>D>4K{(tz?FhY9OKipUn<C<m|K_SLWeGb&; zS@+&qlkGJ!GS>3qHL^VFQD=R*?&19xdzw^NUDxWzkX<3$LiUAh4A~j7HDqtd=8)YX z+e7xJRU1Ths8w4;_K0i}*(I_~WS_`Jk)0x2MfQqp7TGPbU1YynwP9q(TD4_l&&a0v z<=C}WZ5!D)vT<bR$kvg)Bb!Hdk8B^=KhglC14s*y9%xk)kS-u?K>C0*0_g<O3ZxfE zGmvf|?a-=zAPqq}g0uwb3DOj#D_YeSq%TNgw5l^mYmnX`%|W_@v<K-A(jcTmNQ;mj zAx%QMgtQ6ilU6kf=@il`q*q9@kZvLELi&X?OshJkRV_n$hBOW78qzkTZ%E^i&LOSS zs@@^Z)2i+v?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^Q)>Lt=lt?DMyPNbhmLy?Xm zEk$~YG!^M8(pIFeNMn)CBCSPwi!@iOx{I_|tNM#H80j$5Vx-4NlaVgt|GCZnD%w$T X<U2AuJ~|=Vy(~5|J2oadk(2*lDb4(s diff --git a/lib/pytz/zoneinfo/Asia/Ho_Chi_Minh b/lib/pytz/zoneinfo/Asia/Ho_Chi_Minh deleted file mode 100644 index eab94fe8985949b5d98cd003d26d9c2e70e2d0d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 375 zcmWHE%1kq2zyNGO5fBCeE+7W6MLT+&8zwH+<~nQotl?thxq?g9b|0=9?G|{l?McI1 z{TU2gw#OOj|NsBb$i&RT#0-Q?3=AnC6Bv@eF|aT&Bo#1lGB6Z0Fz_-k)J<Rzgoya~ zhA;$x2yFv%AhrNvORy#w5ki6!fwun#ITJ*KoC=~r&IQpRCxd8^vq3b-=^z^9d=L%t Y0tkS-0Rr_vF)Dk7%LeF4J6&@w02Ah7^8f$< diff --git a/lib/pytz/zoneinfo/Asia/Hong_Kong b/lib/pytz/zoneinfo/Asia/Hong_Kong deleted file mode 100644 index 8e5c5813666a4d023bc9f7f7bd0e53ca1a61dd85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1175 zcmc)IO>E3T9Eb7Q*JhWJHgVxVyk!z8jf<%SZAD7A)q_o%R45M8rRWK#O+_RUA`%G+ ziG$OxhzJMc;;1h|yw+N@-ny%$>ZM&>T5F!agM*Ven9b~Gvd8^C@utM~rRt9pa=&mn zYux2sawyT1>mF*fJ?Z6gE4IJ=e%-dV2Rp8rhbL;~QT1ihd-}ROURh;Ri|0!!Ut!bJ z!jgV6%RU``E6?nteSW(~`p(TX{TDat7Y8QH%ah&uRYStO-g`m6SrRh?&7G2&US_hZ zIwU*3&JNB#B7><#cBrsR-q~XNzP~|+PmS0QU9Ea#-#z<L*6UA=SMBG+<@!tIT{GHJ zs>kMBF}a#i{dGaojL+Dr^Pw#!Kek$b8>lwl`<i9q&SpD#qe^~Us<ef(^F$q+YkkR( z_;&|wU{h9t^%)zg3F`2&fGJv-(M1PWm`J!wM{=DenmMnh^mdr3H;?L)GZm(EPpkNS z{(!sx_EBnQlz%#T+!s`;tzAWUtKwhpy85_U8{5EFT-7>%pZH&_S8#^~krk08ku{M; zkyVjpk#&)Uk(H69J>A;K;+}4GWO-zLqyVG>qy(e}qzI%6qzt4Eq!6SMq!dqA3sQ`y zs|G0tsRt<tsR$_vsR=0xsR}6zsS7C#sSGI%sSPO(sm{}tht!7@h*XG_h+d66D3ar< r1j<C}L<&VJMM_0#MT+%w)gtA3x_Xg<k&2O$k(%-U7aetxmzn5Kw|Ne+ diff --git a/lib/pytz/zoneinfo/Asia/Hovd b/lib/pytz/zoneinfo/Asia/Hovd deleted file mode 100644 index 8eb5f647969380c5645d1af2ca645f70185b1209..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 907 zcmciAJ1j#{7{KvUk3?GHE)uU&??=^J30m(39VE9TL@FZUl`t4R&d>;AI72svI|v5D z87#yi!DJ&ABL-q*lhplpHCcSk&97<Nr1}1zIX*wzD1J?f{Rt1#WIwz&w(h@UnXY#$ z`|VNXJh?>f-Gs`!49fh|fN~#rWWipcDqKsFo+XDWns|}LL(i(D<5re>Z&X>{i7a;= ztBRBxQSo_gRK6UGs)r+^`g%ur&$f-4!$skXEf}@Ykf>W7HtJ{UM8o8qY77m@rof<T z_W5LsyGHplGG+VQmF_sZusRRVbs%<N1*7}AYjwlwo>|vD=9JajAJKiyy;gr|j~;MG zJmIVpJ({{KN56LU*z1VgjK|}S<fK2ol7$F=un%*Jb*xB7nh?_~B7Hby3U9sNe%kD( zm9Hem{KIPPe-vlE{E!tRYerVhux@1K$l8(BBkM;JAQ_MpNDd?kk_AbF<UtZ8v@;>8 z7;+)W7_uSh81f+r88RX%8FC^?k*r8sBrlQ}$&92%awEx+>_~bfKXL-d86c;CoC9(a a$XOt#k<dO5<V5hF&cxYiA5GBL>ihx3y}KI# diff --git a/lib/pytz/zoneinfo/Asia/Irkutsk b/lib/pytz/zoneinfo/Asia/Irkutsk deleted file mode 100644 index e8c53c0d63d4ab9cc7b7c95597e4cb490e782cce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1267 zcmdVZUr19?9Ki9j+-h0i5KU`pr_9z`t2N7}&Ofzmtht9`M2rlqEQB6LL>NepOnQmv zA$yP>eq_EyPZ}XY2tE`@kVb_B{e9^r3?hiKb-s5DMm_b|y`1y8_iS9)J>Ml$y{Cif zuVb}YVQ@su;JPehraPr2k0wUvUFDwc%$myK#R})Js`&mSUHR#8X6?ey#dWiZjC(32 zRrlgjeLE~Q*Zi`+zee=g64`J}$;S8>scn8Qn|$+9=bn+xt0u%_f2TcP=k%73&jQ}Q zivjQRalLhBO#8-f>1}tfYX8kXz5PnB-f=#q1E=D8XV(P@o;xJ<Ek|Y7;d<F!-6Rdc za%otql8`&7L$7~H<Bu}kH1$@R7ryE6*phCUe5G55-srZGN!{Kvt@jSTkbNDaI&$)% z?5`Wr9gz_^;5ehBwfz$PJSefE;}Uz?BL_dUOZ<LRI%m6d;<{Io@t3ZFY&L6KW-rQ} zj^zdC3TMGh;Rk<SuXH$+>Yg@ll-aJ-iL`Pmb?Ju9yy|pDx$<*<#b#FW-(frQ>&n%c zNUE3_zQ7(c8qEmtSfy;aBX^UVlmD>Y=2vEqJh<G3Y&){`Jc;c`5<oJrG$|lCAW0xu zAZZ|ZAc-KEAgLg^Aju%vAn73aAPFHEAt_m!oRFlDtSn7hNM1-{NM=ZCNNz}SNOnki zNPb9yNQOv?NRE~!NhC{4lO~cUk|>fXk}8rbk}Q%fk}i@jk}#4nk}{Grk~EUFrAZsf h+tMVCWR9ec<c=hdWRC-;f&Vo9;;{K+wE9EEzX8aB6L|mt diff --git a/lib/pytz/zoneinfo/Asia/Istanbul b/lib/pytz/zoneinfo/Asia/Istanbul deleted file mode 100644 index 833d4eba3985c0221daf6789c9ac30266bccacd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2157 zcmd_qe`u6-0LSs??shwGZuRtI&13UN&UvR(o7-A9o0~PyF}<xPJtx?Nhn%J;<69O~ z20PjiOu<Jo94ut&TC&6-CD2N56fv8vC!OWmMo)x+2>LBrQNyn9`-!0NpX%Sf56``B z&&K{Z?(<Hp-@3U}yX{O>Pq;bl>gIhYsa~u-6P;bzd1lw;v-Xiq8=a#&Up9Iglja*W zr|jOMu=8fP&F+gt%)W1K*>C-_+d2N>Vq>7~hB=V#x6iMfFh3be8iRu;%nMihjnALH z?p)j!G%g*fG>2=w#^rd9^ToWK_Ls#soh!i&_K5eOIdZMW{_5s2bM#`fF*a<Q<0tpo z-~V{p{GsKHJ+URz{Ap&?*1FF)o^2;>Z}N4=x9T-JW9w<tU;d7f+4`yoR31wPCZE%S zq2BJ`=n^MO2JCEo$jly@W90Nyns@Z>H}34nH}zdRjJu!eOx@E^q1{{eVk)<AJb7QZ zIhE%-mz)-;PK9<KFhXMoQUxDhvZr_D8Z-9pkTYLuk+Zh1mG?KVkh9|t$-=6LoU<S# ziwd&j+^Lf?><^3Z#557P8Wi(}e--mTx-N=Ojfn+4pNfTh+U25-x5Wd`c8ijh{bKQk z_hsob2W8pPL$ZAJ3-ZC~Nm)_)gsiyrq>Sc2FQVhiW##00vE+lf^5M}cQPo>3mcBPb zRPQPhHC>umwmBdk=_rto#;%Fljlap|MS~(%>&RGVsk6d=-l{A7TCcnDonG(j*XxG{ z^p)Qp)mNS9)8iM;Sq=Nft;XYrt;bqhbz^V4_4vA1tkv5$S!<$a^+deRTASBsHB}$7 zntm>^)_u0fXiKNl9-sHWTp9j9FRw2%@J}z_l;CZb->+%;5%rDK^0#Oinl``0Gey%1 zW@$N^7G37Kiziy{-=F{WZ}@GzA)(c)I~H5ROF}CyDOYzH|5Y82I)A)#f6x;DVk_z+ zN;kbba0S^6vKv>m9b`YqhL9a0TSE4PYzo<xtJ)T_FJxoL&Ro^jki8+BLw1L357{5G zL1c%>7Lh$7n?!brY!lh1tJ)~CQ)H{iUXjfryG6E(>=)e)^FgZ}16xM+jBFa&HL`7F z-^j*Y)y_F=-Bs-!**vm)SG9d)|40Ln4j?T+dVn;6Ll=-XAbmg@fph|Cg{$fX(hQ^< zNIQ^zAPqq}g0uwb3DOj#D@a?AzPPH!Ae}*4gY*Vz4$>W@JxG6$1|c0nT7>inX%f;U zq)kYlTvelxPPwX9A-zJHg>(yP7t$}JVMxc2mLWYunuc@@X&cfvSJgPAb4cr4Rqv4I zxvK6V?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^P()k~zA*rvYg|Hp1-RjH;{FD%RY E9a<Tx5dZ)H diff --git a/lib/pytz/zoneinfo/Asia/Jakarta b/lib/pytz/zoneinfo/Asia/Jakarta deleted file mode 100644 index 673d48010de39ac1eeb2fac8fbb08209bd0f98c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 383 zcmWHE%1kq2zyRz(5fBCe4j=}x6&irV%8J$pyTtA#oZjEuaVhLw!F8Ks9Jl_fF?`v& zuY-|^nT45^nT>%VMFFT3M6xh2q$_|-$gE)CV_>M8z#ziFP|(02!N8DIz#!w}8^YiO z#M%btMg}0#7(`kEu?0i8rxVB!BoIP^TmJ)reVS(ihz7YCM1$N7qCsv4(IEGOXpkR3 SG{_$ynjU`P0(wu^oC^Rmt6tjx diff --git a/lib/pytz/zoneinfo/Asia/Jayapura b/lib/pytz/zoneinfo/Asia/Jayapura deleted file mode 100644 index a4c0829721cf845e1bf08332503e1fe742dca5fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237 zcmWHE%1kq2zyK^j5fBCeW*`Q!g?5P@oN*yy=Z7y_7iBOq0fn1ifaL2YfTUYzFmQm` ud_KM*4B7^kAZ%>F5bhZQQUnGeBv|$z2tXEsXpp5Knrw@?fUeNB<N^Tw*D$>R diff --git a/lib/pytz/zoneinfo/Asia/Jerusalem b/lib/pytz/zoneinfo/Asia/Jerusalem deleted file mode 100644 index 2d14c9998e9dc54bd1ad4710332abafc8e811242..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2256 zcmdtie@s<n9LMqJCS!=^WQnzgfMptrmkV5wO43je2;>&GJH>!w1ZFBdCNWLOC;MTB z#pblu{JCXp^uwWITXT`M>1-o7Da%yMryn3oaK|MmZc5Mlv{hSw^;cWpyK`S>_fN*> z9V}U1l`8&m;pP_}uF*WaM=SHS+n4>uiNyzXd(W4FZ$7>yI*wnpI~%LC-Mr5J_Ek%t zeDiC0s<K6Ot<d)Ak_p!73voxkPdY5m#OCO;<NKVmmk#N3t$xw{S-d{4-x5ExcBl*c zzEHZMR{R*M7eBS-TR*4!?O%3Y6Fn0r+PyWW^u?%?s&Cqe=<6%8BkCG-#LX6a^w6(X zWK*gg({^1?+I+>1Z6C4XtoH)(8xL5M^A6khl<n5{#+L@Bl=!Us#GJs?F#~$)cfIzs zOP%`uqoV=|UH#Vd<~V!CkxHGo*<;V#u|Rvvn*&Mf6SOb)n4O&aj!v0f9Y{?+WclyD zWv4}Lx6-aO1!jdss9BvJnK4wZ9_Sv{584Crq5Vs&+3)X_nR^df55KZe&Z*m@=dMYY zj|BEvSp|(^UT%?`pSW6N#|}9;V~a&je}%K4KTYIb9F%$OcZ&SBoz9~l4U2*;qn*O- z(Q4tQ9kQtAnhKO<IgbU;sK;04$zuNz6)cz`pO~;qg%WR<p{pCzqUZ!?QP)zncyNqU z(mGEq=^AjJ++~T<)=sCaK1-BuJK$KQQ^eBx4*67OxTq*?kx$3;iOS5avMM}GEt^y$ zm-Y0jXKse%@?$5|v*$D9iVr?iD~|`ARj+MP)lKQnbCuOfzJ8apdSQcjzVe2%#=A<? zWOq9+j4Kkg-eFmLIa#b7cTTSDxI?VF{JDJblR>e*V~?zRH%is-e_6g%`<vSEc7^ne zy6w+T*k7;z)teBL-GA@+>mp2u={`?{5Hay$tPmM<J>&oQrJyh<^39Vs-#o==UjBZ; zf3ckrbD>Yax`Av6*%7iOWKYPZkX<3$LiUAh4A~j7HDqtDW^>5yknJJ+b2S@8c8F{d z*(0(^WS7V`k$oZ?MRtm871=AYS!B1!c3sVW`TMb9SF>Ye%gCOQO(VNTwvFr?**LOu zWb4S@k<BB!N4D>3_K!3G=>XCKqz6b7kS-u?K>C0*0_g<O3ZxfEGmvf|?Qk{yKpNs| zI)bzW=?T&lq$@~UkiH;|K{|u92I&pb9FFcF?cwMT(jZsUA*4kdJwlqq(Iuo!9DPC> zg>(vO71ArDSxC2#b|L*j8isVt)wB%hnX73U(lw-QNZ*jgadZx89Y^nw=5cfnX&*=b zkOp#e5NRROLs!#8q>D%!kv<}gL^_GI66qz<Or)DgJ30D^G?b&GNJ}|->S~(G(N$N| wR*t?RjpgVp(psdqNOST3+TBP~<C!TY%ZY`lUcc9$l#-rUnC$bWd3}+;1NQXO$p8QV diff --git a/lib/pytz/zoneinfo/Asia/Kabul b/lib/pytz/zoneinfo/Asia/Kabul deleted file mode 100644 index a22cf592553c4bab6ef93cf34fc304162999b655..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 220 zcmWHE%1kq2zyQoZ5fBCe7@MyF$hq+Ix<mc{|Nj}8m>3uw9sorcEF2hE7#Qq57&v@< xLm0FTOhDM!03-zjAtYD;H1~gI+pGm38e|bje?5>*v}Ig2V5{wPO{|O!xB#TGCZ+%Y diff --git a/lib/pytz/zoneinfo/Asia/Kamchatka b/lib/pytz/zoneinfo/Asia/Kamchatka deleted file mode 100644 index b9ed49caca4bd81731958cc54adaa45bf84ee2f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1184 zcmd7QOK1~O6oBDLn`*3rqSU^u_F)@iOifZP)wb!>ww9VyP_j}32VdZ0A>u+PQbh$p z1%)CSMHjw;h-6^}B|;ZLToma-5URofi{M7G&_z{<_n%ORxN_$mZobLPFp!)-zU$Dz zu=UqkV6L!Py=HU1<l)Y1+3~x{sS2;pUS5?*S9<nLRL))vR(-i*S5IftHIo%~ZKhFH zJYFVsx7@Pw;&)j!@>%MSypx9Auca~iT$(x`$!h;iX|BB_EelVH*PT-Dw;Q@<=6cR| z($4wbj_BI#QT0Df=(>B`H89qrt>=Qe{zQWY_b=5@A|m1A)za2oFB^9Jl#O*I(jNLK z?Z0NEqh?k+KD?4mKi=!+OiDVZUuxI=taiV+qmlCuv?qB+?E@FI_v~%iGH_V?_MMjg z=9muj?Uk)%tvcA$FN0sBGE~|kLvI4I?UP5MPZmpTGMpQ}oIf_2&*xpGC56LXR&>mp zU-YH;#-HcrF}G!nW%yT<Y0I2Hn{s(9D>Y#)n){r);xbnX&u}+39Jl<TklAf!7Y62H z@hWG<|6!2kcjOr+GEiQ?P?5nR!*w(RMuv<G8W}b+aAfGn;E~}Y0U!||As{gzK_F2e zVIXlJfjF8-kWi3V98EAtG)Oo|JV-!DL`X<TOh`~jR7hAzTu5L@WR4~@BsNDA91<N8 z9uglCAQB-GA`&AKBoZYOCK4wSC=w|WDiW)s2^NXg(S(b{iv)~BjD(D%X7>MW%;jAs K@<<>QF8>WumKRU} diff --git a/lib/pytz/zoneinfo/Asia/Karachi b/lib/pytz/zoneinfo/Asia/Karachi deleted file mode 100644 index 337e1d58620bad1d24a1e273c34d75e4071c1bdc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 403 zcmWHE%1kq2zyNGO5fBCeZXgD+WjpKsEIBjPXvL)xp&55qJXpcxzuLfNz3T?s`5Fs6 z92XdPa=S0^d~(;o>rJ%+BNHPtD+?GhFxcGznd2M5z{0=~ox#Y(z~C0ZAi%&7lE5ee zW=s0`hA?Owm>L@ZNi#5M$`Ifk90DXlKziXIgar5g2Lh0rK{UwSAR6R$5Djua$WV|U iKs3l7AR6Qs5DoGVhz9uyOauJ|qG{<jE}&<1O}PN?q*Ph} diff --git a/lib/pytz/zoneinfo/Asia/Kashgar b/lib/pytz/zoneinfo/Asia/Kashgar deleted file mode 100644 index 0342b433180b416a02c5ff8764e7a0a57921091b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3mzg;Qy|NsAIWMp6nk^xDDBrve}_=YfO8<>H(3?U?# X1~lkD$V8An{HAi*0L{14HRA#R1VtLz diff --git a/lib/pytz/zoneinfo/Asia/Kathmandu b/lib/pytz/zoneinfo/Asia/Kathmandu deleted file mode 100644 index 2f810b1202a0edf2f9d0963cdc5004246369eeb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmWHE%1kq2zyQoZ5fBCe7+YZBr`i@d34;~&|NsAIWMX1q@c#pn_l;m+VPFWj!@%X^ v8^WM%U}|gtB27#|l0Xnbf)zlM|AVXn=>u5>qDi!l%LZ(@ovx{si76KV$jT&Z diff --git a/lib/pytz/zoneinfo/Asia/Katmandu b/lib/pytz/zoneinfo/Asia/Katmandu deleted file mode 100644 index 2f810b1202a0edf2f9d0963cdc5004246369eeb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224 zcmWHE%1kq2zyQoZ5fBCe7+YZBr`i@d34;~&|NsAIWMX1q@c#pn_l;m+VPFWj!@%X^ v8^WM%U}|gtB27#|l0Xnbf)zlM|AVXn=>u5>qDi!l%LZ(@ovx{si76KV$jT&Z diff --git a/lib/pytz/zoneinfo/Asia/Khandyga b/lib/pytz/zoneinfo/Asia/Khandyga deleted file mode 100644 index 2b2f5bfae4bfdbe898e256cb2d148b8bd17bdcc2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1297 zcmdVZPe_wt9KiAC)v0r4JcmqcnJc%JO>J8xTRN9clermDJ0*e8Un^onhk|e~^v4bo z&mjoJLs8a6!b5eK-qJ%MLFLIh2<}h>CekG$*7JQu7<B5`?`7}zeV@G>Z13kyofteD z6n{;nUg5=b=@-YF9(~Gvd--@)RC$l5H`fd=R@?8aRR5eBsQH>rZ&_Gg+&ULcJExOs z+eAdwjy0?8BXz3ohE%RghT74;s&*!pRDJ8ba{FeL$N5U_s(7ZnwvW>LV@B@Iy>>NR zyXb0|e=7IPJdnN@cV**~>$2(o1=)P7U+%q{l>XBZ88~@f1+T=^zDTdyf6Su})HbS? zV3}(9y-|gnZW;Q#qQbvQ<-zF>s&(OuY#aYBBiXmI{q}nq9i5QT)m-|}nQ_r^Gi!Es z4~y8TF|*4vD7s@q=HZfr=&2ttdzR0c@#0Q1{w{9zF1gLbbDx=<>kxhSD$OI8i;kue zZ$u`a&l|->Yn!d4U@I*vI4OLi#OAP<7j6~hHk%NcD*oiODfIEkL&Gk_kP;i#_v<T$ zURk?lbkdMqDfE?V`_W0ESNK%2FC~2bknjckx?$Y_UoH%5bgZ-WUFsk1z5cm*?mxu< zg9C~MiU+IO1jPl#2E_-(2*nA-3dIY>48;w_4#f|}5XBM262%k6lvVADVvFL-sy0S( zMzKcmMlnZmN3looM=?loNU=!qNHIxqX;s^#__V5xQk+t(QoK^kQruGPQv6!ghAEC2 zEK@u)n5MX9uubvJVBD&9PO)xPd#9MExTn}>@K1RLD9-_t(I)=)XTi~?pO^Ngu;UMw Cz6`_w diff --git a/lib/pytz/zoneinfo/Asia/Kolkata b/lib/pytz/zoneinfo/Asia/Kolkata deleted file mode 100644 index e1cfcb8d09dc8e16f828082396f5095a367881e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 303 zcmWHE%1kq2zyK^j5fBCeHXsJEg&KfF``kUdPTlU&IKx^fab~K~ic2LzGZ>kefslbA z=mt;$gSQ(<wQmFi2LnTN1|yG;ZwP}g5PJrPFlZZ?85@8Ufk6m%yIFyfAPfTRKn!Aw z{RaZIT@o4~+WGlw0}u^z3y21}2SkJ11g3%R0?|;nu`shR!yFfM0pvKK6M>EcI*|wL XL;;W!J#adcfq@I?c3o2|V*@S#`D;p@ diff --git a/lib/pytz/zoneinfo/Asia/Krasnoyarsk b/lib/pytz/zoneinfo/Asia/Krasnoyarsk deleted file mode 100644 index 59efd24ca55cea04557fd8792f5e6ecd10b3d596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1229 zcmdVZPe_wt9Ki8sIcvBjUP^1NmS#(*^=s9%rZzP*opy*t*dL1ih>?)=hdKlbSwTcB zs!MbUM|6nzMRf^J712QoBI+Pfk_8>Ih%OSbp6@#bqfQ-rUf%cf?A^;?`+b+(ey}%U z{#ssphm+M}Pxd7@re}YDFftLG6FQulTb4GZo{^H$={G}VpI@iujeE`fu^lP@XrC^) z-J^l)ox1QsOc$N1(O`c-7w;_A@<c`}V&8O0ctR`vFLkN+v4-4VCG_LHESr26uR3`y zUiIpcR6n^V;rnT+Nne%PD`#c-@CjLQtWRWrkF4A^tdS$TbyZ`puHMqDYXX}z8mZ9e zuWGIL$EE(QPuKn|mvy648XI>@!%eR=KKvw2XTHe#YtJNp;Dt1wp467MyVAPvscxve zEN!i~bYtOhX|Fi1?NgVuqo7|qo*&iD51Tb{XRCIN9g^;gb(&0k3=U>88CSuK?BOoV zJ7&(xdy)UfpL>tnZOpb}e%j<7WA_6?E{`#L2aGRw&E0X?JGp0!eI|F`_`152CM;2V z*4wj=4>PXpa`xFv@*k#azgUI|BU9!Rm^3nN787^0Q%5F`OrJ#oNCij<NDW94NEJvK zNF7KaNF_)qNG(V)NHvbO9HbtkAV*sfQW8=VQWR1ZQWjDdQW#PhQW{blQXEnpQl6u& z4=K>mR)~~{)QA*`REd;{)QJ>|REm^})QS{~REw00)Qc4CXe&lacC<AkMI%)sWg~SX Vh2x-Y;Qy(-sKNdNnriEcegiHK35oy! diff --git a/lib/pytz/zoneinfo/Asia/Kuala_Lumpur b/lib/pytz/zoneinfo/Asia/Kuala_Lumpur deleted file mode 100644 index 6d7d47b9df2426dd083c4d420cfa33ecc0667bc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 415 zcmWHE%1kq2zyKUT5fBCeP9O%c6&ip<TXXB;UFm)k4sbn5IJNm{!s&_e9G5(DKZs5I z%2EIS|9?g%W)@a9R(5s<hWId`dWOVU1{MZ}qymr;=?aWIAd&${W>zo=F)-9kV31&7 zC}?1i@$n5|2nJ$p19K2IG60drAkq?uEkH)0fDjTK4s_T5I<wGKAR6QW5DoGKhz5BC jM1wp7qCp-4(I8KOEChKBMArkw=;k>t8=#l%bS=05RcBzr diff --git a/lib/pytz/zoneinfo/Asia/Kuching b/lib/pytz/zoneinfo/Asia/Kuching deleted file mode 100644 index 4878622dde6ae385b1faf198c0931fa5a6e2248e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 507 zcmWHE%1kq2zyNGO5fBCeVIT&vCDwMaP1u$0-m$-0y<z`n>41Z=YZ?w-T@Y~C?g7K$ zZI>?`RS<4CI)gpnIHNnm@k*-;Cmx?+I2pbF!l@(w8&2DN4mdqAg5i=!?uYvS|Nk>G zGoe5hW@ZM4^a7B5nH3By3=E|Pj9d&11q}=W3=DM>7(^g!AKwrLZ3A;-10ZQ(1SWwj zOOQ4=2qD2sK$rXnc?(2?yau8{-UHJ>FM?>0H^DT}s~{TWT`&#wGKdCw8%zVe4x&Nc x2h+eH0MVde0MVeJ0MVe}0MVcz0n@->0nwnK0ePYx$fjZNaM=KZ&Q8~Y3jpC<wEF-6 diff --git a/lib/pytz/zoneinfo/Asia/Kuwait b/lib/pytz/zoneinfo/Asia/Kuwait deleted file mode 100644 index b2f9a2559a1ca4306a43c2abf074e91d51a64edc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@Om&wAq&W|Ns9pGBPk|p8-i}88EQ;_=YfO8yJJQ3?U?# X1~lkD$V8An{HAi*0L{14HRb{Ue7_k- diff --git a/lib/pytz/zoneinfo/Asia/Macao b/lib/pytz/zoneinfo/Asia/Macao deleted file mode 100644 index d801000dc3d598e39832d263358920f05928b337..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1241 zcmd7RPe>F|9Ki86?!R0$N(#IcMKQ62TM&(h&{S9=&A^4ADD4s%RzvjvuoMvliFDFK z6m<y3q7Fs%i11jFwOFPto2mVA7p={gsjXVt`wn!fL*06hna^WjF7x|NbbG^|wc@uG z&`-En5q)u9bhcqfb7$m;>KuDA_;lvM)@R+b_3=yBx?b!)X?Gvb+Aqs*s-Dd^?B2sw z>Q!m2N)%VBM0VOv7WCN3M4$aSbKmZh?e?4ai0Z$ZR0H?!s=?C_)!UX<^{!@>dVlnU z`cRywhU!}E)clM}m$o_S>?S$9<h(PS2+5JT{mw^OAU_S%IGNT_`MIOf96k12DrbxN zW!oJ&b}npwE$gzz_ZFHHOK)3~k#Y0eikOvMTw65dtGA{m))h?;m0L6Yb<X$rF8QN< zqceM}OwL_i?uh1P(unz-oC99z*_n2{)hX$VcuoI$j}=&%G6QEeSh@ZVGk5Z_6--?> z^LpE?{6|gZ!iyzV;lW17@Z{)iczu7^hX1d(5c>}C&FxddD@4;3W1bMP3nHJpvEbKp z&Q;Y>QBfNep*3L<3YUnA$|x@i!*J2J=s%2hPA|rbjM!C=85uP)Ze--h*pbmA<3|!e zGC)#5a&Xm2AX&KTG>|-yM3797RFGVdWRPr-bdY?IgpiDol#rZUby7%Ht~xCwFC;M} zGbA-6HzYYEJ0v|MKO{jULnK8cM<hukOC(KKohOngk|~lZk}JB&av)olP8Y}*Nf^l( lNg2r*NgB!8Ri};Q?Wz+;GDlKJaz~QK|1|rc{wr2n!Jqj}JSYGF diff --git a/lib/pytz/zoneinfo/Asia/Macau b/lib/pytz/zoneinfo/Asia/Macau deleted file mode 100644 index d801000dc3d598e39832d263358920f05928b337..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1241 zcmd7RPe>F|9Ki86?!R0$N(#IcMKQ62TM&(h&{S9=&A^4ADD4s%RzvjvuoMvliFDFK z6m<y3q7Fs%i11jFwOFPto2mVA7p={gsjXVt`wn!fL*06hna^WjF7x|NbbG^|wc@uG z&`-En5q)u9bhcqfb7$m;>KuDA_;lvM)@R+b_3=yBx?b!)X?Gvb+Aqs*s-Dd^?B2sw z>Q!m2N)%VBM0VOv7WCN3M4$aSbKmZh?e?4ai0Z$ZR0H?!s=?C_)!UX<^{!@>dVlnU z`cRywhU!}E)clM}m$o_S>?S$9<h(PS2+5JT{mw^OAU_S%IGNT_`MIOf96k12DrbxN zW!oJ&b}npwE$gzz_ZFHHOK)3~k#Y0eikOvMTw65dtGA{m))h?;m0L6Yb<X$rF8QN< zqceM}OwL_i?uh1P(unz-oC99z*_n2{)hX$VcuoI$j}=&%G6QEeSh@ZVGk5Z_6--?> z^LpE?{6|gZ!iyzV;lW17@Z{)iczu7^hX1d(5c>}C&FxddD@4;3W1bMP3nHJpvEbKp z&Q;Y>QBfNep*3L<3YUnA$|x@i!*J2J=s%2hPA|rbjM!C=85uP)Ze--h*pbmA<3|!e zGC)#5a&Xm2AX&KTG>|-yM3797RFGVdWRPr-bdY?IgpiDol#rZUby7%Ht~xCwFC;M} zGbA-6HzYYEJ0v|MKO{jULnK8cM<hukOC(KKohOngk|~lZk}JB&av)olP8Y}*Nf^l( lNg2r*NgB!8Ri};Q?Wz+;GDlKJaz~QK|1|rc{wr2n!Jqj}JSYGF diff --git a/lib/pytz/zoneinfo/Asia/Magadan b/lib/pytz/zoneinfo/Asia/Magadan deleted file mode 100644 index b20cc57efc71570ad78cd8060e28141336229dce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1244 zcmdVZPe_wt9Ki8sZMLNzB*;Ilm9}izQnzKx+FaH$v28SD5LrRcAE;e^L<gyZhw2b% zU82K~5-PM<(ZM9b!wx}*BtoQvbP%e81o04FieNq8cML|II`+Ig@8{XGm%;XYml-;L zE~Ne%HD-s&h?~i}?nzU4;>ClhrFFjD>H5ZOp}}?GeZ!CYy^YKF(oKu=h4pjZw0kxn z8}cpU8DA?KNA0rd%8E2+zevmR53)J&M!b=ivc>;cTHQI>T60r;jv4J+nbg}pPpY=z zBdTrThTi`Avi47B^^S)pbo-r@?zk4yotHXw;7qd)o=i&UqEB}AbV%3W8rkKklW_2> zgnxaPZnsl+e_W6~KbQ61*%^r}zSYr3^SbBNQysfLt@q__>)x|>_5Q1QInXz(<EL-Q z!PX(&7Z*8H71jOTLFxZ?Oa|;-GVm@Uhd<Rz;+aR1b0M7?D@rDjUpnd@S0lw@(Pm#& zx*Sy%*J@|Qi^@0tT)UhOhf-sw%+IPuCzM%F6>Ki0a?e$5`Ci_!nVs@8$~;rvSGCDh zM)`vQGec$u`7mWG4QpMxW&Yua%{MD@%*avm2^=?a<jApGnxjXKA4veo07(JK0Z9VM z0!ahO14#tQ1W5(S1xW_U21&=#<bx!{AR|kY5|R^=6p|H^7Lpf|7?K&18j>559FiT9 z9+ID>Nf61<(xiywh$M+*iKL0-i6n|-ilmC<iX@9<i=>O>izJL>Y-v(Pa<(){BUvM9 dBY7i<Bbg(qGe%4c|55JMQS(=bwFiUMzX7{IBjNx6 diff --git a/lib/pytz/zoneinfo/Asia/Makassar b/lib/pytz/zoneinfo/Asia/Makassar deleted file mode 100644 index ed55442e2917b4bbccce34f3e1c531d1f3284ac4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 274 zcmWHE%1kq2zyPd35fBCe79a+(MHhaGov=&n>V(rBvJIEQ-W4!1F$3lDKY$bgNg%tR zfq{d8p>6^L511|B;~T=@3&h$67C>yt5bhb`2vQ9NAtYG-9|%AW0MQ^vfM}3IKr~ex L!v%7jt_2qW<)b^z diff --git a/lib/pytz/zoneinfo/Asia/Manila b/lib/pytz/zoneinfo/Asia/Manila deleted file mode 100644 index 2c9220c99b03e6aec1803b6c3f5683af1fd1c8ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 350 zcmWHE%1kq2zyPd35fBCeP9O%c1sZ_F!8u<V4v85Qoc{Ej;lfJQf=4-)43DlZ`oLf6 z)*z5p^?`|zi5Z9)ng0Km{{U3aP&a{*g@K`<fq?_WX5fLaeSAY00$hMNIE29qh!G$J zyMx$(DnJ+nxPTbM7WfYYA`kC?>1uiV2_Qbmfgl>>NDvKjD2N6*7DR&_45opO1{nr& iIEV&0o{^b}nFWZMm|?CjEC9)o>Ia|`fgaPf-~s>wP+^b& diff --git a/lib/pytz/zoneinfo/Asia/Muscat b/lib/pytz/zoneinfo/Asia/Muscat deleted file mode 100644 index 7880d5d7c92a4a5fd994630588e9d74fbd636edb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3^r<p72|NsBb$jHE8d;=t9;lRM+;~T=DZD0c8GK7#| X8qlEsAQM6Q@SDnI12o@G*MtiIL4O;g diff --git a/lib/pytz/zoneinfo/Asia/Nicosia b/lib/pytz/zoneinfo/Asia/Nicosia deleted file mode 100644 index f7f10ab7665e94ca44fd8cd98a362cd4b304eff1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2002 zcmdVaZAeuI9LMqNOjy@ye{0UQ>qF(rhpu|nY}s_Jnc9QbPV-i-GEb#fYtAi8r(5m5 z$Yg}XzY-#9P-GGju7RsTPxL@E2zOwMF+w`6v5iOxDx!vL=X;=6dll@>&gI_E<)ZKY z-(P6e#&DkJUr&tl3vZr?^XB{bW1l8}H+J}I+dH(^ihWjRkGpWq7~flHPS|zFdZp86 zO6yW9Zo{ZKvC1|k1t;6D=3h4AQ!kmXP3kogqK}#h54()l@9s1w|JZ1}aiziZo$Is` zPwudj4u!4c?s_|A+d^wfQ@K5LO{O)iBEwEC8fU%fkF}@!MywgJ!**IstdaKEYo`A; zY-Id&-^{%FgE4bp(De6yV`TN5GP67P897_`nt{4jBe$mC&I|6b@{84;m9@nxNNTZX z=e5i1(TL3P_2`_TbyE0Oo6bF7B5&WS)}p>zEj~L}-|3pK^A0BJyWv!w-&rW{mBnaD zolh1_|3gblMx`v~do54BE#)J>%cAH@vS{$SEWUeGmh_*HiW?U-xVu{_Pae^w&COzT z@6cr{cj^00^;-2-lZGnFb$LRiuJC8*iYEcBjxUqypC{@EkJDw<=|{TyrdQS+j+2^! z`?5CjP-=Sy#jL$4>$cz1_4CfihMF5%mvTVri~BYF^0(TMq}uT3er+6W(T&$Tbkk5s zKRmu#o33q^kG?F{=DsTVxG_aP=_-)T%Zj8WoFH3rlVxk^Q)!L!NLx<4wmtY&+9y2G zcI&EijQpaXo$8a%2hZxZ1DADs|5y4&N3TY9NA#tr7kfpI`A=USPs&1WF*6V~#^Xtx z;u-t=lV2)=Ax~*(6(1q~Dk{qT2))2<|Lr{7H~-F!BX^G6I&$yG%_Db@+&*&uNCQX* zNDD|0NE1jGNE@zBA4nreCrB$uFGw>;H%L23KS)DJM@UOZPe@ZpS4dk(U#?DLNM}fE zNN-4UNOwqkNPkF!NQX#^NRLR9NS8>PNT04wqe!Q&POC_-NV7<{NV`bCNW)0SNXtmi zNYhByNZUx?NaIN7u1@Pn@2*bsNcTwlNdL$NAUl9;0kQ|kCLp_jYy+|n$VMPL;p%J! zvKOw-W+1zPYzML*$c7+0f@}%0C&;ECyMk;BvM<QSAUlI>4YD_`&gLMygKQ77Kgb3l zJA`ZzvPZ}!A-jZZ6S7apMj<<eY!$LsuFhs5yXER^7qVZ-h9NtKY#Fj=$fn`{b=SPk a%w^><c>Z91c0qO^C*L2;4Y=QCdH(?jq|NOB diff --git a/lib/pytz/zoneinfo/Asia/Novokuznetsk b/lib/pytz/zoneinfo/Asia/Novokuznetsk deleted file mode 100644 index 2576a3b01642c32acde9fd4ea02df316b71ec709..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1183 zcmd7QPe_wt9Ki8sIm?`cmvXgM%VkTa^=s8!O>N7T`7bEe!TwP6M+}6dKU5GXWCani zs4mgX5fKr;urA?YR73|Uh^T`^NfLC(I&_JM^?Y9u7M;5FjQ9OKd*641?e|UZJlq{I zf34Z}3I{7`5BBAEyn)=qq4B`n(2>kMZ`M?l43$^>cst<z{3bJh%wrae?#lQ^dUWC4 zF7@AR(?yr!y7)}3273LvWcM7cY|m>j{!N#L$F<7$N|$+_XvqClLf=2g@`?9}>QfgI z)vq5*&C_8CKgddL_PW$vJ1;8+Ps+;UJt7CYWYwNQjU3&ps~fv@&Gw|O^>5K=B&g9} zH5&6JB=)XM*Zr)N^&^uSA9G8?Esr!l`Xo)~zQ~3f&n0o_r6kWz=*HB2X+H2wH&tDg zRP$}!Tzo=Wf)}-A@`|<=^=j*jW7_s{tG3_Up&g^A6P=e+x&C}U?<$%x^>P;%yk^cS zcvE=e&%J%jZOpcu{WDE^pRxPC0auAJ`}<7U^m+P<%U+p!hPxe|X%m*HJ!1B#=L?LB z<?J}6|6!2!cjOr+GEhFxP?5p17_OroFfwFh&@6_H3>+CcGI(V8NB~F#NC-#_NDxRA zNEk>QNFa_j5+oEP7DpQl5)Bd#5)Tp(5)l#-5)%>>5)~2_5*HE}5}Bh74T;Us28Tq4 zgonh31c*e4gown51c^k6go(t71d2q8go?!KXoE$fb+q9k@ge~u5hEdEzn%Sm8*^!c Kjl8L@zVtVQ`2r{a diff --git a/lib/pytz/zoneinfo/Asia/Novosibirsk b/lib/pytz/zoneinfo/Asia/Novosibirsk deleted file mode 100644 index 95e3c73bcc7aef0eabeca2d943eedd6dc1747fb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1241 zcmdVZUr19?9Ki9joNmkr2}<W$S!&CsbzL=AQ#++gbj~0~Q5l5x7m7wmR0J9(y+n-i zAtDHI^b~O*>mddms>jhoh&>D`sh3_V%9pTO=X*s6dWar7mwP{Fm&=9i_g(V9$zvh$ z$MTpvoUC?pvM(FUdutwz&r~f7oJlXQ$cS?Hcv<<k*FzN_rqbSgsi@2yO#8AY)QbE2 zmH$qcT6rz1R$Z!BRj2%Fb?*|jCSFk0(a)+TIHPKPuhiPo=PKa*WCdp5TI=4wk#*;< z%DTyC*7_F@t>8q)3jXX7^_g+mFghwXTp5uY2hK_B$Z^?t=!OoRJ*_vj4Cu{!cIz$v zy*eCf)Zrfy9r5+Z$aJONI%mmk+3z}<Un-mLd1cF!S=l-=C%508lJe-Z+%fz`?~FZ_ zZT*wFy>?8-+8*m&#ly0r`j+nacvp88UDTZ~FYB&%y*mD&Uw7v&$i(%SPR74l`)=m+ zV4+ZO6fK%RoW;K#B`!X=T_t}#oGzzRh(wkjIeA!^eQL<z7NRdD7B5^E?l{by`Dgl4 z?0Kd;krY8IY|e-|n|O_Im?a$c*1jbFVZP=o6__zHXI{Xpk$JP2xviNyGJ9nHEE+&M zKw3b0K$<|hK-xh1KpH_hL0Un2L7G9ju{G@={U8n5nvRf`ke+N!Q!Khd+Cut58bdlm zT0?q6nnSun+C%!YH4P#i+L{)T9+4)IE|E5oK9NR|PLWoTUXf;zZjpA8evyWej%`iL kNYA#WX{2kUZKQ9cainvkbsRKJ{13f*n$16<wV}!L3kKd85&!@I diff --git a/lib/pytz/zoneinfo/Asia/Omsk b/lib/pytz/zoneinfo/Asia/Omsk deleted file mode 100644 index d805e4f74053b65018bd8a13cffe9fdbb0421928..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1229 zcmdVZUr1A77=ZD!oHf*71g3LZmf5mp&6R8UuQzqdbk>Lw5eAW!2n7)$6@`R(7ZG6) z5i~KniXba5MCc8=3W);gMuZ@{=pu#kCd}6NoG}=6)n(^!&hOi|!(e-#C64u-30i+0 z`DTZUquE@n-J{QB;>FlvpwM?UxxRSZUF3RNUi9Pht>Q29NzbguT{7L5^iB<E>Em84 zyMI(S+=%MND|K3a)~}noOLcQ>tya|k(k=c)t@M7>t@-cO=Ufrr(t>Q8{~E8lcqd*p z^G>$EekK0r<Fe!7m{gC9$j<A-va5eU0;hVV=J;(5UO1<_8~b(7;X_(mc0@zL8V#+4 zHS9eo;kgpsw;YhVsh?UuyG|M&d8Bc2Nt%Y2W&gboBB$r%z|c2sj=YqXo*8Ye9F<7R zgtp}lNqfazZC`kx(VWW~eS1wiK6Pv7lOBytUy`ny5lwV{4-BSKDM!w#^y$paI#;jB zdXfFcpKF)XX<4mr_-WhXmRX-X?{HaG;)GR@xo379W+(HEWuD3GTLrPMgyj#^nrpAQ zLVTFzNFVk(eM|hqbj=q_F=1rNd;*h3rj1P8)=V9lJTiTx0Hgw>1f&L}2&4+645SXE z5Tp{M6r>iU7^E6oQw~xOQjo2w2q_7v2`LJx3MmVz3n>h#3@Ht%4Ji((4k^#p)Q1#k zYbr!aL~2BeM5;u}MCwEeMJh!~MQTNgMXE*0Me0Qgwlx(aCEJ>sk)n~Rk+PAxk-~A% RH1Pjap4VXh0ZrARyx(YH6yN{= diff --git a/lib/pytz/zoneinfo/Asia/Oral b/lib/pytz/zoneinfo/Asia/Oral deleted file mode 100644 index e36aec475db6fbd8c4fc79bc03332a8fca64bacf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1025 zcmd6lF=$g!6hL3vehq1n4%#Hf+NjmEiLEhhLTb}mm*S8@kPm`bxPum2or<D^(7_;x z4x&!tpjaG4B!W&V?ci9-8{BH;4K5BYEe@rYc+Zc6P!~7f<G%BGpA7lu_~)jla%S6# z=ncaf)Zuyei}-`jZ|7Spds2659kIn^H2gXl-B^DT`?gk#ucVTDm#)<k@2hs-%d58Y z@s!<v_pI%@aoQ#?57`6f)AnFFVGotsY<FhM9!~spM_RwTloyxOkBIbq-IU(z&!qR$ zmrDBMs$||Q%hCA->3dj{{_0aXcICcgFWi*lldoKE_O=@sx#tGYTypu&Sy#wSxWbn2 ziisI1t`^+TW?4>rh`HgFekncA%gDQS8NC&ilaJP9?Ba%;s_t@g^?E(j)ZEhCq}$u^ zd+!Xhy$1erdY&=aZv7S}oi#1Sj8{z97+*qdoJP+-Z^kPjeOnwe?T!2Lgl{t00iC=~ zL5EMPE5vF%a{B+^ME^z|FAz8U9zPIA5KjTs6~q_B8N?gJ9mF5RA;crZCB!GhDa0$p zEyORxF~l>(bwKqEaSrhwP~AiPLmGf|0BHfz1EdK^7YaigkUk)dKstf60_i27HUsG< optb|)2htFvBS=e-o*+#@x`MO?=?l)OlmE4`NJ%Rk?aN1g0lS;ty#N3J diff --git a/lib/pytz/zoneinfo/Asia/Phnom_Penh b/lib/pytz/zoneinfo/Asia/Phnom_Penh deleted file mode 100644 index 7249640294cd596c9445ce175455efa06b5dc6c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% diff --git a/lib/pytz/zoneinfo/Asia/Pontianak b/lib/pytz/zoneinfo/Asia/Pontianak deleted file mode 100644 index 9377d03831b60a35283cacbdee4f5a2ac6251642..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 381 zcmWHE%1kq2zyRz(5fBCe4j=}x<-7m)G3*k%&v3eO3CE?dcLmpNj&a=jug36Y>%I=f zTO|#QOw3FyOss4S3~3EO^&pakfg!U3WJ28p20jLcf(8a5Fe%2skW|1R?c*E55CFv5 z2Ij^FK++P3Ef~T*LmYvG6UYoC5JG}e{{sQYxgZ+kWDpH<Hi!l}9Ylki528U{0MQ_C QfUKi~SGa(_(>3P;0J|Ps^8f$< diff --git a/lib/pytz/zoneinfo/Asia/Pyongyang b/lib/pytz/zoneinfo/Asia/Pyongyang deleted file mode 100644 index dd54989fabec15b8a7f9b3fce29fc63c02b457ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253 zcmWHE%1kq2zyK^j5fBCeRv-qkdAhHEoHOZ}*pEy4i3OqOm<poalol{DF*7nVFqA$4 zsVwgRDXg2ozyT&%e0)O~yn{m+ynq-6LP)UiKM;T{1<@djK{Uv6kXaxHfM~KD!3A`c Ht|b=$ZRj<_ diff --git a/lib/pytz/zoneinfo/Asia/Qatar b/lib/pytz/zoneinfo/Asia/Qatar deleted file mode 100644 index f5140926a2fb09bff7636139e0aa450d5ae06f9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@KF|r@00!U6T&f|NsA=k%@_c!5{!6Z{fhe!oZ+qz`)_- p8^WM%U;@O(APFD{A;EZ{ssBM%fb@Z^0nsE_#bpDu)=t-$3jiUoA6WnZ diff --git a/lib/pytz/zoneinfo/Asia/Qyzylorda b/lib/pytz/zoneinfo/Asia/Qyzylorda deleted file mode 100644 index 00b278440592895901730ba41ae6fa14b6f94107..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1017 zcmd6lJxE(o7(j2L(HI?EtUsyt^~XLnwQ3qeY?GKSp@YJQ1PVnVg9?IEsZ_8aNN}-R z+of~}tvE=B2z9arhmM7QLpLcdE~VgLyNHH(&vQxX<mP?cdv5Ma9=x2Bdonj0HUEu} zJz+9pcCv5YY+o&Ie<){K+_ln`wq2(+xaG7S|0%ZpIx4mAxlYHo=cVxHf?oZ&pq=k# zwd>_$z4qdfI`boXeX>t)jD_`PvPo}6%Gw=1)Z2~w>INFc{d+1s2PgU7#W&LXLo<C} zzf0uPuH0GQlK$84<!<4X3_L5y;PiqFO%*lzbV=`}Rx~y-rNf=m8jp@@yqwiUcv2ES zdvxR^E%(2K^ueAZ$qiRhJAO&8HpuAP1If(%mWKtS%auyS=dY=)@!Km<S9R3~g4G}Y z*$0Ehr1JK^Fo}$*H6|;@p5*erCRStD>XWSayt|z9ow4zpi44Z<9JVvgV~p`J&bO?& z3;3|FP{9er3%|z=#1F)gN9zgV3gQdm4B`#q4&o2u5aJQy65<o$6yg=)7UCD;7~<KZ zbq(<iaqiK2hq#CMhcp1`0MY`a2S^i;E+B0{`hYY7=>*b>N81ae8IQIbNIQ^zAPqq} dg0uwb3DOj#D@a>#**dw5eT9;?ymWs&bOuOB+M@sf diff --git a/lib/pytz/zoneinfo/Asia/Rangoon b/lib/pytz/zoneinfo/Asia/Rangoon deleted file mode 100644 index a00282de340141690412a40fb70cb6d7631ff401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmWHE%1kq2zyPd35fBCe7+a_T$XWQQIPmnRKN1&brGKdZ|NlQD6EhPN14EQPNF|VD zVPJ^PVBlb2sGGpR2Vwj8hA;#Hv9^Jku>p{@1Sy4r5E85h+V($eTjxa(4RQvE1~~;p agPa30vL48$&`Df2V29f2npqheZ~*{I;66hD diff --git a/lib/pytz/zoneinfo/Asia/Riyadh b/lib/pytz/zoneinfo/Asia/Riyadh deleted file mode 100644 index b2f9a2559a1ca4306a43c2abf074e91d51a64edc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@Om&wAq&W|Ns9pGBPk|p8-i}88EQ;_=YfO8yJJQ3?U?# X1~lkD$V8An{HAi*0L{14HRb{Ue7_k- diff --git a/lib/pytz/zoneinfo/Asia/Saigon b/lib/pytz/zoneinfo/Asia/Saigon deleted file mode 100644 index eab94fe8985949b5d98cd003d26d9c2e70e2d0d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 375 zcmWHE%1kq2zyNGO5fBCeE+7W6MLT+&8zwH+<~nQotl?thxq?g9b|0=9?G|{l?McI1 z{TU2gw#OOj|NsBb$i&RT#0-Q?3=AnC6Bv@eF|aT&Bo#1lGB6Z0Fz_-k)J<Rzgoya~ zhA;$x2yFv%AhrNvORy#w5ki6!fwun#ITJ*KoC=~r&IQpRCxd8^vq3b-=^z^9d=L%t Y0tkS-0Rr_vF)Dk7%LeF4J6&@w02Ah7^8f$< diff --git a/lib/pytz/zoneinfo/Asia/Sakhalin b/lib/pytz/zoneinfo/Asia/Sakhalin deleted file mode 100644 index 9c94900ce88f7f653b21687cb8118ff3fca27a3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1220 zcmd7QO-R#W9KiA4+I-0hBFb4y)21z3>bA->Us9Ke&C!U_p%(~x!*+Ry4pE>Fltm=b zq0<oSkXR5A5+P%UK|&oYLZX9o>Fv-Zo`XaNv7YZ=gd#e0>-Xos?|%<$u>C&CL;a^B z^2aQ-C)`Zb-aN0qVqQ$ngeJ_Iz|KTzSt{o(KJ&^uf4igX^Jrr2>`ZRmv_Ij?hSd58 zRjPbsm8uwas|^?DRAuUusyh5eZHzrr{+3Bq9lWn<d}+05*>x50JTZZ}akKg3xYVBC zFSW0)nJrTnOz?5aY`uHL)ZL1k`pfO6;ar0Wov1Y7qkSrJHlVh()vN6Xyn07@g>DSb z>&73Vn|!6x^!B}O{{BsNWv6t@?5wnoy_2>~R@#T2%I@^2betTM&Py5H)qPc>$4B&@ zntthy4(PoFG3oIi(>-5K>t1)4?tR&-_kF0=v4=t3H{B`m8!L1&mN5svPRL+BpLe;J zEL@&~U#`NU-`*4!{q6D;c|0OR&-s4`Zi;=rmva@1q#w!h#n;6XmpvK2BP$oq`{GFn zhMFZ9j@UbF?+{-iF2;G{CHW6)wZA6MYANgG^Q@S%X3DCa+I3S_PFXu;^_2Bf2v8VM zC{Q?1NKjZ%Xi#`ih)|eNs8F~#wPYx480a{)d?<t{j3|^SoG7FytSGc7yePyd%qY|- z+$iLnT6PqAPAxwQK?a5tiVPeXNHVZwpvl0KfhdJ3162xF2C@{k40N4Zz7)bvEn^C0 d3TFyw2G$hX6yD6BweX)4FKM;>+v~#NlAmNx4_E*I diff --git a/lib/pytz/zoneinfo/Asia/Samarkand b/lib/pytz/zoneinfo/Asia/Samarkand deleted file mode 100644 index a5d1e97004ff763fab160e6c8a2bcbfad7cf67fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 605 zcmcK0y-UMD7=ZCNX{)8};G+GAAMrD45sFwppp;JHP$5G=5Qk0$!Bt#@E;{L-;2^k( zLmb>hTm^@BcW`lW&_P`UOT4EJB2EtGxO>7u!rd=e-)zpxuczDWFnJ1Q^4@cr-(R>m zY1ewagRn1m?PinbZub54IQRGx=I=bWaJv&amwVcOwygt4O&vU3)1loJb+?vucw<^e z>W+?9GCEdl>$vl*6X|>P5^3Li`|u|pp8ToCRjCz4k!8o?G24tp*N<V_BGns?v{;k4 z$g(d<k)UB^SjDX0JN4B-ilsR-%Vy5=k%(n|Rf0d<+8ik2=9Js>3j`<%6p2oa21SIT zLXn~9P=qK-6e)@pMU0|Gk)!BQ1SyIXNs4BtMwFsTk)<tjhyT}2Rg8Ldrkwf&(-(Yu diff --git a/lib/pytz/zoneinfo/Asia/Seoul b/lib/pytz/zoneinfo/Asia/Seoul deleted file mode 100644 index fa1cbd3952c362552f524061301b11f03770f335..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 517 zcmWHE%1kq2zyNGO5fBCeQ6L7f1-h?)sF?Ij?8hZ%^$(ByH9tJb-u~mMutdhQB<70e zf<7-^=oeJHc>X2h6@O60tK-))UcWb~c(Z&*#@q8^74O<-WqdF#tWa2-FhMadeS%W6 z(*$Kd&k2l7%#18ZkeL+-85qhrKsJ|mFt9K%)J<Rj@_;0e-8zGjhk>DI0V9u(ZwP~T za0r7J5PQ3XfRw{Q2nnA04+J2OfoPEDKs3mMAR6RJ5DoGuhz5BUM1wpGqCuVp(IAh5 zX`tspG$;VTG%yfAG$<fIG$=4YG$=qoG$>F&G$>#|jt2z}hz11^hz11`h^ARUaREb6 H*OChW=SsNu diff --git a/lib/pytz/zoneinfo/Asia/Shanghai b/lib/pytz/zoneinfo/Asia/Shanghai deleted file mode 100644 index ce9e00a5db7c447fde613c59b214e8070f30ba2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-<beU|pf zjcbQR>1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_<xx7r6*b|6_9zz#6+EmOik3e$Yf+TG98(ZBtSACDUckAnuPZx3z7!OgCs&SA*qmD WNHQc_qNYRgCH_BQMr*FDXTAZK`l!MH diff --git a/lib/pytz/zoneinfo/Asia/Singapore b/lib/pytz/zoneinfo/Asia/Singapore deleted file mode 100644 index ebc4b0d9d2ade7381506bc35d054f361f8a942d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 415 zcmWHE%1kq2zyKUT5fBCeP9O%c6&ip<TXXB;UFm)k4sbn5IJNm{!s&_e9G5(DKZs5I z%2EIS|9?g%W)@a9R(5s<hQwH)dJxINz>rh`G9q1pkq1OF0Ljb>1|bH9x(N&t3=9Pg z3^G2xAq>GltZiTp!bS!l(ilWq0<i_iC=?Jvg2RFC`d?=jyck4-JOH9Wo&eDxkAP^9 hXFxQ_Lm(RDDUgLAkAdiVpcvge$7KWbvYoC47XV%lV9Nji diff --git a/lib/pytz/zoneinfo/Asia/Srednekolymsk b/lib/pytz/zoneinfo/Asia/Srednekolymsk deleted file mode 100644 index f8b7bb212660e748d90b78e4303c4cdeae81e167..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1230 zcmdVZPe_wt9Ki8sZRS!B66BxeN?W#Use7lHwYf|)(QPzjm#j|xf!gIqbdWlDs1A|+ zIYfsM6;x=kqJv3<haG|rNrZ?8=^$hW3F0BV6v2AF?--0ab?kk4-_NsWFN5v(U262~ znUMPHs5CoV9DU|uUHz=SW98MOspU=ny~&!obgtHY?qltb2fcNx_mlNY^SRA)zNBY1 zC=J;r@!s1gTPB>c_0pO&roTwju|?Sydndl|YuO%nD$SmZ?5Mmhe%FlluTAQmE0d~a z?1*YvxTbf#y{H4zX}$aLaou_+q1&!Rb^C>O9X!>jtrKwxo%c&ePn&d(Y>++PYU#4R zO4qON((NhN-Jcd@-_KROe|AQ~OYe2$$-M4)^IS)-PU{1iTe|o3U48I!R{93UbpOel za;SM!5A=&1E{o_v--rx;J1RrYP8s?Tmf_De5_{p5_*_UQZsa8u%Px=S^LdAJUEy?< zm7MF#OJ0<|@#os@aw&DYfuDBbp)%{KoWreD=B27C-itd9vr~LVnP-aosw$pHselzU zSIArzAEq3I!(JC|sehQR`C@q{j7*tNVA9C6k%`-ysUwp|rjHbWRDhI#)PNL$RDqO% z)PWR&RDzU()PfX)RAXz(LF(b4AX`%rQW8=VQWR1ZQWjDdQW#PhQW{blQXEnpQl71; z4=K>rREU&_)QA*`REd;{)QJ>|REm^})QS{~REw00)Qc2sYbr)cwly^)MI%)sWg~SX Vh2yws;Qy(-B4Yjl(N@c<_zfbFA+`Vj diff --git a/lib/pytz/zoneinfo/Asia/Taipei b/lib/pytz/zoneinfo/Asia/Taipei deleted file mode 100644 index f9cbe672ab1aa9b6c47f7fed0a0ccd73845a10ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 781 zcmci9Jt)L+9LMqRc|6B}$)YeQgUDnM<rwHp{?sX(l<nuCB+_p&U@>_Z6^k;GMK_?7 z+~DyJ=lS70|6*XcI=?qI3ya_N`@gP#x7_FLv~;wW$&Zt4-*7oa_VPVb+s8^%o!)Z% zdV92A?^Ms5-P!`#^99U)ML<F+?JAu6qQf^+<{(}uhwGc_=+v(xb7GFWy5(e{O`Q%5 z$=UdXId5Ik7rh(mvhqQ$O7qlpey7}I&#Gv`jE=sB%<V-;?shZO{aU(ySgbOSZ!vkA zTs6<(5^eh4RcfqR+>awFEi$Y<!DW+mIIXjth;k*k5`Xm(>5SU{BT7q>$l#c`dAc&b z-uN0E@isbAZ?Ct;;fLSH`NLpwdwPN<2N@0-4;c^{5g8I06B!g46&dzZJ1#OXGBPqW zGBz?eGCDFmGCmRjiGYMaVjw|~C`g!3Z5$*J5(x=~#6p50(U5RRJf<Wdhlof>BqkCR N|0Sx&wk|IBd;k)YUNHaw diff --git a/lib/pytz/zoneinfo/Asia/Tashkent b/lib/pytz/zoneinfo/Asia/Tashkent deleted file mode 100644 index e75bb365a78e6dd0a7ce4d75c8d36ccdb8f7f7c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 621 zcmci9Jxjwt9DwmVjasWh7j4s4t*=pn5QHkING3t3&>@3}Lvc_F4xOBo4z3P<0Ed8^ zxG1=Z+y`)Rc5$eKgLV<bLF5046a*(1FI=8*B;oQ)uu<KfRli0;R+x;eOs-RB_F?zc zY18X>4#I)VWh$MxD5T#Xk26n=(7v}*+1qNEyWTVT^BpsIvTlZsR?YDKf+_5}W@IyO zMoWEWZ1K~KyG=8ZdoYtdcgC?keCPGWpK3gprz<yJJ&GdT-u8W19Y2oFF0GaF-`LVh zSy@VL)wJv@sHoo7xwWEYg?mbCfpWc~%z2q}JgT&e_(D#>AHH5T79l`U;2lU%G$<l* zMTH_m(V+-YlqgaZEs7XLjUq?UqX<$IDUuXTiYP@juE<h!DZ;ca-|&CR$tC#*%QMB~ E7cZ54-v9sr diff --git a/lib/pytz/zoneinfo/Asia/Tbilisi b/lib/pytz/zoneinfo/Asia/Tbilisi deleted file mode 100644 index 09bb06eba4101cc75c67eaede16465a07e55f995..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1071 zcmdUtKWI}y9DwiD)aH*44z*3v`lnT6`bW<;2~SPywV*B)9ufjd3mMvi;LxEs2^DNM zr{ZF9QgIN6H;6;&@B~2zb#iKn+@hi&xVYHL^Y^_pAUHYtj=S%;cRWbGuX6R;{D}JN zNy!P5rzDeo=Pg;Dq*tBK8+*LY+`V9b_dDGce`~saI)3+86{I(dI`gI;WY<@0&(qts z_x_?iaA(dQyjit}u3xl=r(N4OmAC!nj6E`xvPZMq_E_qh&9$d%xgW{e@sEjxd~L(a zzyIJ3ynJB>p9S8~qlaer?gQ_{a^0L<ykYe06@6-EB^Vu>_Y0RN{9<o4D2?>{rC)B~ zW{q|mnc(zRpFXp`9h}{a>GI>W8C(0L$Lm|>-0Ev><{HK{UilNtJ>MtKe_2=x!_aAq z?P`wp-G7XZcw2(OtDX0)dx>}~rc^$ZKZI5G16n1Ny5Ko`m2!>R*IHXAj+`{_xQ0D< zrY==fWm1+wJu0&(v%nXXa^z|5cGk*2Xp&zPR-dmq5M2;ud<tz4br5|KMIl5ZL?uKg zL@7ioL@h)wL@`7&L^VV=L^(t|L_I`5B!P&M0VD-T4iO~@NEVPZAbCI%fn)+n1(FLS z8AvvebRhXa5{f7pK~jn+IYE+wWCckJk{2W~NM?}KAh|`9<RIBW(t}Il=0C_US(Y@% HhYQKyFIeJC diff --git a/lib/pytz/zoneinfo/Asia/Tehran b/lib/pytz/zoneinfo/Asia/Tehran deleted file mode 100644 index ad9058b4937b8786f58e2cbd61adff5ffb6f0657..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1704 zcmdVaNo>qf0LSrvt5n2NZHb7a4MAzg{5w;fr_0Q!XrC!sH7#0O(FI-ThFW_-i1t7n z+SrpyL>&n68qqjZ@}wjPLP~-Q2SS2Gkc{^oCvkFc@SnW-%*>%lli&9ROE*@!tUsP; z^9zTk!W=$N>Z;kT9}dRq(KpV?F-;DCOo!i&d7t4QTU@1M=Lb}r=bessj8jR8EjnrN zq?$2sLeK1*tde`~%aqe4!qG8A&)RoLIqN;rF25;K3pZ(3&PU~Teb8xBo7L?2DKdT7 z1(E)xM0?&mSK`TGIp<D{$mks-GcO(&SzS3g`-oQFoh{N=`$PCwUeI$3oGQmROV3Na zs`6gnk@9hYSa5r>3|xOC^3S*Fg`LrA(ZOqSacib1sD7&p%j(pU{90X<{YfoNIV*z+ z(?#*9N4mr+RV5#W%Vn=R#PSF2az%ffD7|t^mv!f=mB&8IRR>zd>gMTsP1Pl}c3F=u z54hAi@lvi&t`r+4#_5WYovPw{o~(R-PgK3QE35ApiH&_Bz3K9PwfT%&)*S5>wQZHU zuJN6!U)LqK6eo(U^Alu)r&}~mE7DC9o~q{P1G4$sNYV1PS8p5isqF(^z2j!TYVB!| zJ5PNTyV|?;?tRH>Pu(-Qw|tG*8w!OYBBO>xMGpO^uSm<X1xqmRQI?e|tl^fGTNp9g zvV`A?wJe{E82{_{g^Pk#(41u3?Y7PPY;)2$=G*U2@GpjE{?8EOLk7rSVuZ*Lkuiof zgG5G&3=<hAGEiisVa-sHu_A*-MvDv=880$mWW>mjkuf8KMn;Vc8yPn;@UUj&$k36o zBZEgqj|?9fKN0{E0TKcd0}=!h1ri1l2NDPp2@(nt3la<x4H6C#4-ya(5fTy-6A}~> z6%tlh6BiN~5*ZR25*rd65*-pA5+4#E5+M>I5+f2M5+xF5SQ94_C=w|WDiSLaED|je zE)p*iFcL8mG7>WqG!iuuHWGJO6F3q%5;_t)5<C(;5<U_?aseQh0CEw8HEsO2m%)gv e@P*-ZxHH_g`E0HWZ%RPePCF&wN>6jzBYpzV*O<5f diff --git a/lib/pytz/zoneinfo/Asia/Tel_Aviv b/lib/pytz/zoneinfo/Asia/Tel_Aviv deleted file mode 100644 index 2d14c9998e9dc54bd1ad4710332abafc8e811242..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2256 zcmdtie@s<n9LMqJCS!=^WQnzgfMptrmkV5wO43je2;>&GJH>!w1ZFBdCNWLOC;MTB z#pblu{JCXp^uwWITXT`M>1-o7Da%yMryn3oaK|MmZc5Mlv{hSw^;cWpyK`S>_fN*> z9V}U1l`8&m;pP_}uF*WaM=SHS+n4>uiNyzXd(W4FZ$7>yI*wnpI~%LC-Mr5J_Ek%t zeDiC0s<K6Ot<d)Ak_p!73voxkPdY5m#OCO;<NKVmmk#N3t$xw{S-d{4-x5ExcBl*c zzEHZMR{R*M7eBS-TR*4!?O%3Y6Fn0r+PyWW^u?%?s&Cqe=<6%8BkCG-#LX6a^w6(X zWK*gg({^1?+I+>1Z6C4XtoH)(8xL5M^A6khl<n5{#+L@Bl=!Us#GJs?F#~$)cfIzs zOP%`uqoV=|UH#Vd<~V!CkxHGo*<;V#u|Rvvn*&Mf6SOb)n4O&aj!v0f9Y{?+WclyD zWv4}Lx6-aO1!jdss9BvJnK4wZ9_Sv{584Crq5Vs&+3)X_nR^df55KZe&Z*m@=dMYY zj|BEvSp|(^UT%?`pSW6N#|}9;V~a&je}%K4KTYIb9F%$OcZ&SBoz9~l4U2*;qn*O- z(Q4tQ9kQtAnhKO<IgbU;sK;04$zuNz6)cz`pO~;qg%WR<p{pCzqUZ!?QP)zncyNqU z(mGEq=^AjJ++~T<)=sCaK1-BuJK$KQQ^eBx4*67OxTq*?kx$3;iOS5avMM}GEt^y$ zm-Y0jXKse%@?$5|v*$D9iVr?iD~|`ARj+MP)lKQnbCuOfzJ8apdSQcjzVe2%#=A<? zWOq9+j4Kkg-eFmLIa#b7cTTSDxI?VF{JDJblR>e*V~?zRH%is-e_6g%`<vSEc7^ne zy6w+T*k7;z)teBL-GA@+>mp2u={`?{5Hay$tPmM<J>&oQrJyh<^39Vs-#o==UjBZ; zf3ckrbD>Yax`Av6*%7iOWKYPZkX<3$LiUAh4A~j7HDqtDW^>5yknJJ+b2S@8c8F{d z*(0(^WS7V`k$oZ?MRtm871=AYS!B1!c3sVW`TMb9SF>Ye%gCOQO(VNTwvFr?**LOu zWb4S@k<BB!N4D>3_K!3G=>XCKqz6b7kS-u?K>C0*0_g<O3ZxfEGmvf|?Qk{yKpNs| zI)bzW=?T&lq$@~UkiH;|K{|u92I&pb9FFcF?cwMT(jZsUA*4kdJwlqq(Iuo!9DPC> zg>(vO71ArDSxC2#b|L*j8isVt)wB%hnX73U(lw-QNZ*jgadZx89Y^nw=5cfnX&*=b zkOp#e5NRROLs!#8q>D%!kv<}gL^_GI66qz<Or)DgJ30D^G?b&GNJ}|->S~(G(N$N| wR*t?RjpgVp(psdqNOST3+TBP~<C!TY%ZY`lUcc9$l#-rUnC$bWd3}+;1NQXO$p8QV diff --git a/lib/pytz/zoneinfo/Asia/Thimbu b/lib/pytz/zoneinfo/Asia/Thimbu deleted file mode 100644 index 06d3324d057d43c8e3bcc64069e95670861e3c9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215 zcmWHE%1kq2zyQoZ5fBCe7@P0vGtm;oMBf$l|NsAIWMX1q2;l+A`$jOZ0NDu)Tt2=b q4B7^!#s)yr3?u~vAtYD;H1|Kq5|BQSMIf3a%eZWS7Tf8XaRC5@Mj!bA diff --git a/lib/pytz/zoneinfo/Asia/Thimphu b/lib/pytz/zoneinfo/Asia/Thimphu deleted file mode 100644 index 06d3324d057d43c8e3bcc64069e95670861e3c9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215 zcmWHE%1kq2zyQoZ5fBCe7@P0vGtm;oMBf$l|NsAIWMX1q2;l+A`$jOZ0NDu)Tt2=b q4B7^!#s)yr3?u~vAtYD;H1|Kq5|BQSMIf3a%eZWS7Tf8XaRC5@Mj!bA diff --git a/lib/pytz/zoneinfo/Asia/Tokyo b/lib/pytz/zoneinfo/Asia/Tokyo deleted file mode 100644 index 26f4d34d67b46513491f26c2e661c6e653cc130d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309 zcmWHE%1kq2zyK^j5fBCeP9O%cc^ZJkbvvel>u)1J-1zaU;O1HD54YJFKHOd_`{B;B zM<4F?{Qtnr$OM5549(0y^$a}=7=fDWCNOY7NFU!21}_&N4h{iHGlFmk36A&=1gVFX r6o6=uW56`fK_D9BC=d;D7>EWr4om|b2%<rb1kq$Wlndx;T}v(iZ^UHp diff --git a/lib/pytz/zoneinfo/Asia/Tomsk b/lib/pytz/zoneinfo/Asia/Tomsk deleted file mode 100644 index 28da9c901f23e9a3b6892236ead6f86e311fe1af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1241 zcmdVZPe_wt9Ki8+`Rc|d;ia_J%3QW|T2HIyYSSLmWpi2(gRqQ3OALZW1pOh<(CQMg zLqS9k;;2i+17(L0dZ~`$A;buglIq~aqC6B5>-oMSj5>Afd3oQ@vv)6p?e|^k@Ts1V z@z-+eJDjX$eX=hZ`n><$$nZ@0iolu7%Cf9c>KZO7{rR@PY<4oUYRY4*9y^lpj`pfG zk2;j^UaMMrBdXS2u2JQueQJH@a#fMcDSz~<+7O&kmEJdMqvw?hm|sL-{)5=`>AkGF za9vhSycE^1pNQbotf<M}7PYsoiOqxO#g=ouLY(LjTaON^(Ai^ZTYZn(ey~yP@a<FK zkY9y=RjY_sipX@a+PP36c8$)d=#(j94?H3^KC9}V&&!6Z3v&0}NhwcG%f^9k)}Hta z+0;E@HCGPFc+)d$Z{dJ!@!zpp=I&dGf=gCn{EF53vC~RE?zY;-F3R?saVwSlE)FGr zSbe!%&QY*z@h}VXjv{B?i@Y~n&Z0jK(`lN9ao{08a_YFD_vwCz%P_jqM)A^h>5fC+ zS$w7|&7No4+EYeQg!LKGXB{77IP@|c_SU|n{$alQE9ICmGG{)3StIjiF>_lzcVza+ z{8==Bbbz#g^nf&hbb+*i^no;jbb_>k^nx^lbYrXALHa=&veg|SEg?N2O(9(&Z6SRj zjUk;Ots%W3%^}?(?b+)7kOpmahe(S^k4Te9m$tf1q)((#q*J6-q*tU_q+6t2q+g_A pq+?s%GSaiHZW`$tX&dPqX&mVsX&w7?6aPc+?wI~3G}P9)e*^u27jOUo diff --git a/lib/pytz/zoneinfo/Asia/Ujung_Pandang b/lib/pytz/zoneinfo/Asia/Ujung_Pandang deleted file mode 100644 index ed55442e2917b4bbccce34f3e1c531d1f3284ac4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 274 zcmWHE%1kq2zyPd35fBCe79a+(MHhaGov=&n>V(rBvJIEQ-W4!1F$3lDKY$bgNg%tR zfq{d8p>6^L511|B;~T=@3&h$67C>yt5bhb`2vQ9NAtYG-9|%AW0MQ^vfM}3IKr~ex L!v%7jt_2qW<)b^z diff --git a/lib/pytz/zoneinfo/Asia/Ulaanbaatar b/lib/pytz/zoneinfo/Asia/Ulaanbaatar deleted file mode 100644 index 82fd47609e125ee799e3d3be4489d6cfd4782abb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 907 zcmciAJ1j$C7=YowR3b`T5(%pA_bS!ay{-F12T~-|KpKe{{6nV_{-H4;XD}d!GuYS& z2BX1Xa~Z_QV3Ctxlhk>;nk>HN<Y}5VX}<Rh&nzu^#ILcMpYSkV^WlBo=H2^PuI*VX zdb?4@Pv1t#ZAjU#+_LnfRXGj|WZ71}DqsDS70Vy0a`sVHjXtRA-b-2Iyim3F16gO= zSM}C&qyF<;G`#E?jrV(^={jyW&*Gx_C}OyFBcde{G+JXp;f_`qZF4iq6ADOgz^{C+ za@p=EQyn=L+4FX)_nw_-{-a|(u)CuL6We-UOltkn6@4H)p$!g?>q9=DHeBP?M;zmM zlZ6NRRK~oV`dZVcUx(yIGMTibr~UDjE=1JFJ{BbFSl29>LP#aD$3kJ@ba$FhxA}DO zmDGg)uv+sUC0Q>&WW~sukyRt>Mpllj9a%lHek1{s0ZD=6K$0L?kTgghBvDE;6GJK_ z7eg{68$&uIA45VUBST6gCz2G&iljyIB8idANNOZEk{rp7q(|~2CxDy*atg>fASZ#G Y1#%iG&GSG`1pnzwvi#=J1YKQOKO>mJNdN!< diff --git a/lib/pytz/zoneinfo/Asia/Ulan_Bator b/lib/pytz/zoneinfo/Asia/Ulan_Bator deleted file mode 100644 index 82fd47609e125ee799e3d3be4489d6cfd4782abb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 907 zcmciAJ1j$C7=YowR3b`T5(%pA_bS!ay{-F12T~-|KpKe{{6nV_{-H4;XD}d!GuYS& z2BX1Xa~Z_QV3Ctxlhk>;nk>HN<Y}5VX}<Rh&nzu^#ILcMpYSkV^WlBo=H2^PuI*VX zdb?4@Pv1t#ZAjU#+_LnfRXGj|WZ71}DqsDS70Vy0a`sVHjXtRA-b-2Iyim3F16gO= zSM}C&qyF<;G`#E?jrV(^={jyW&*Gx_C}OyFBcde{G+JXp;f_`qZF4iq6ADOgz^{C+ za@p=EQyn=L+4FX)_nw_-{-a|(u)CuL6We-UOltkn6@4H)p$!g?>q9=DHeBP?M;zmM zlZ6NRRK~oV`dZVcUx(yIGMTibr~UDjE=1JFJ{BbFSl29>LP#aD$3kJ@ba$FhxA}DO zmDGg)uv+sUC0Q>&WW~sukyRt>Mpllj9a%lHek1{s0ZD=6K$0L?kTgghBvDE;6GJK_ z7eg{68$&uIA45VUBST6gCz2G&iljyIB8idANNOZEk{rp7q(|~2CxDy*atg>fASZ#G Y1#%iG&GSG`1pnzwvi#=J1YKQOKO>mJNdN!< diff --git a/lib/pytz/zoneinfo/Asia/Urumqi b/lib/pytz/zoneinfo/Asia/Urumqi deleted file mode 100644 index 0342b433180b416a02c5ff8764e7a0a57921091b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3mzg;Qy|NsAIWMp6nk^xDDBrve}_=YfO8<>H(3?U?# X1~lkD$V8An{HAi*0L{14HRA#R1VtLz diff --git a/lib/pytz/zoneinfo/Asia/Ust-Nera b/lib/pytz/zoneinfo/Asia/Ust-Nera deleted file mode 100644 index c0c3767e38042e4d9d1d7c2bb102e1ffdaa2602d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1276 zcmdVZPe_wt9Ki8sZOdsLBuZDd(v~e->b6W~)3sVEwynf=$?DV}s1f{#4pNXoe;~3y zhv+b(KU7#?(ZM8!haHLzNrXrTt5bE5ARc0uB3RG&-DTLRW6#U8@B2RQ80>vMZ+h_P zk&yapEH*1lM%+x!-S<9x%00jTN-gv4ks5a<U+X&duJ-4hn0xMa|MJ=C{E8`Wf8At2 zR*p4@=hhNgHDZ_5rwdY_`6><j-piWAYw<>2$XfqnX{^i2x<!}8=a|sG!l+*VWmGi{ z?N&`Qm-L30r?h`Oqc=X-ubXeAbj$guZavwm1BdE$@IX>RCw#K0t3}!dDrK{$TH1r( zr2Y2~3D;HV@W&bH_%)}uOioB-_Kog*IIX*KPjvLcxZaw*relY1>TT!7WPA6Jjvu@# zI~oUdcU+{$*{OTI1Je6_uk_j5r0;D+c7Cpr#8ZzXr$Rb)r6{|O7h>r|P7fE0McV?0 z-8!5L%Z>_H*+uz{`RA2RmrJROJv?7Is~k$5xvtDg_8E`*QQlTP|J-KQNA9Yn+*Rgo z={u|>Q)%T7gw5<w{$S9|keLB~S}9v;lrBo2>3`UA^G}OxJ7wz`Y(Iqng@IK=fx>}8 zg2IAAgTjMCgu;YEg~EkGhQfwIhr)+Kh{A|M$*SQ*A;o}|RYQxyi$aXTj6#jVjY5vX zjzW*Zk3x{bkV28d(W)UyVQJOSr0}E=r7)#XrEsN?rLd*YrSPQ?rZA>Zrf{Z^rm(hZ mXj6DwHN+{*Dby+4DdZ{aDfH>EvG5=Buj(`}N3=N@sQLp)Ml4nU diff --git a/lib/pytz/zoneinfo/Asia/Vientiane b/lib/pytz/zoneinfo/Asia/Vientiane deleted file mode 100644 index 7249640294cd596c9445ce175455efa06b5dc6c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$XS$?ex&~Y|No3kObiThHXwN*$-=;pRKURD;~T=@ r1jO0~<{$|m2qD3EpsD}EwzVDs(I9I;`s;ygBCX=G0a|OPYt97#+X*B% diff --git a/lib/pytz/zoneinfo/Asia/Vladivostok b/lib/pytz/zoneinfo/Asia/Vladivostok deleted file mode 100644 index 15731abc81dfb016221260ed6ae40af24aefb89e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1230 zcmdVZPe_w-9LMqRbk<OVb;wyuv(%O?+a62NoGWK*tT~9-C2KI~AL1=K*dO!@6%j~u z=ujdNnAE|dLmG_TvQ82n>M}Z5dFc{D9WsdZdp~0^>eR8%*xrwiW1zp+QbWV%I^?g_ zm^(aK#5}onKRyvl&rQ#-Zt_Jlb@i8)Jg(7Y&)U;t^<QQ)oAb*{TNbU1Hy75evjJ_G zYSPAujk@h>g*J_R)9oi$bVuU7Hut{K7XM3a^*+&^HTTu$d@a83^RjDY&fPYecDKE| zC%YGJiT}lA+4E>b+V7r{z1NcxxEPk;nSfa7A?>)(sr&juy8pOGI~y9c%UaW}U!vXK zI_duKNqc_&kOR2|?ak*UH1kpVvN;LgcqIoXAIPDzGtz%8tC8qU88|(qhg*jwIxwb3 zsuB`w&S>oGd5u>_H2yZOM?be{;+bCu7yBi7dxNGD+12qvq2Q>jC_bE3Wsi01%YG>T z#h=$Mr&Hw0H2>PMJ7TUUpF3P46Az`f^jf;(Fn3Dt5%ZqXeW@Kxro<oY5x-@bXOIsQ zM{)Lb@g?;Sr)&PO0w+v4Wj=wErkpnA#O<0>r<^?H^eF-;3MdjN8Ym(tDkw52Iw(RY zN+?n&S}0;DYU~<03_TP<c8wy2B!(u2D26JAEQT(IForUUG>SHgIEp%oJiA68MW9`y zkRp+xks^|zk|C3!lOdF$lp&R(l_8d*mLZp-mm%1$QB09+*J!4Qrl_XKX6U8}r{l)J Q|D(J*WWE96b}Lx@8|YsB0{{R3 diff --git a/lib/pytz/zoneinfo/Asia/Yakutsk b/lib/pytz/zoneinfo/Asia/Yakutsk deleted file mode 100644 index 1f86e77f5806a6c7a19143071de4fd72145bf037..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1229 zcmdVZO-Pe*9KiAaa_Uft-=(ycxze?4YIBur>9kxXx=B%kC@F$o=#3arln@#8dKmFL z6hU<8JqYR2%fY;*P$5C($vOyjs17F4B_h`U`;39qsbl|#=lT5i>|wC|zDsS}zc+0D zTBY_5C#&6_?B(YBFFT&x9x-zQo6~bEE=-hr&QF*Bc($|R^I&@3_|(Mw(YCaAD5(nu zVp@5<K^JDKwCbd&?~qFu^?uXEiAk+)9@CoOi2A)xbxG-c4Y=P);QO#F{rKEhd*Yz4 zcI=)k8@?*R#}}pUPM_3YJ|GQedu932q=a_Iq;dN`4Ihc?ideU<+~n6)m37(_F4m@B zvo+$ak;sQ>js7f>)kAN!dHj>K-2E!C!B^6H?v1ov8;~`79?9A>FSNbmio|z4&~^Uf z(h<+-`ogW!S$$YLr;ce?!A9+Ry+ykx!<u*))#T^~>AB?9RN|%S&t|i(f?2u4T{z<? zDxUFT<{N+RJ#M!#eYf~&)2EHyXKuJW#+*`9mcQojxa^($GsZrXzi-NtJt-3mMNF_U zWR3D+#+6&nK6gp|!*uNz%Q9hP$}A?0OdFZFqn$c3d1U%X0Z0W%2}lh{5l9tC8Au&S zAxI@iDM&3yF-SFzwj87$q##FI5mFLT6H*jX6;c*b7g88f8B!Wj8&Vun9a5g7tq&>C z(N>6*h}4J_iByS{iPVV{id2e}iqwh}i&Tr0i`0u0>}V@SN_Mn0BSj-sBV{9XBZag2 SZ3F*L<s~ilAJAGKE%^<nwgKt@ diff --git a/lib/pytz/zoneinfo/Asia/Yangon b/lib/pytz/zoneinfo/Asia/Yangon deleted file mode 100644 index a00282de340141690412a40fb70cb6d7631ff401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmWHE%1kq2zyPd35fBCe7+a_T$XWQQIPmnRKN1&brGKdZ|NlQD6EhPN14EQPNF|VD zVPJ^PVBlb2sGGpR2Vwj8hA;#Hv9^Jku>p{@1Sy4r5E85h+V($eTjxa(4RQvE1~~;p agPa30vL48$&`Df2V29f2npqheZ~*{I;66hD diff --git a/lib/pytz/zoneinfo/Asia/Yekaterinburg b/lib/pytz/zoneinfo/Asia/Yekaterinburg deleted file mode 100644 index fff9f3b14bfebc4344701c7b66410602de37e6ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1267 zcmdVZPe_wt9Ki8s>S~)FBr2_Ct;}9^S!-=$)|{*5%$o2}gCLX!nNV~ol7a#uMg>I? zb*c`Q;i*3p>XeazN2}kVOGpLfB?1rCC2Xwc`;LLssbkN}^M2m<*?3{^^IhiP(ZQhl zYt)z(CL?Jk*Gq37a$9$oj2EAd781*>%lYN?lfF9Fi$LAa?<4hJzU5cUSiY6hr}N&4 zoUD2<Bn@{4W%ZQ<vgX`w@f~lMwSBF!F71`ZSdFar7o^EMCmX6iish`<){kGhd3HYA za%ME!^7eynoqD7FPbT%o`{TOp<~_YBcSCPJmD7QNA-$!4M1m*J%GQqa657`<;f4W; z1Un^CNJ-S&r=#zirF}lGw>|gB_8Fg!jaj<mS%r>YaO=)nvpR9)tKN}Qva{=zP9FXw zyP6*BuH=-YoY!@C<Ct{MJ(QlxOVacDn(X;}O!huHA?fLhy7%gS$)p{L;bO7qsHmze z9nM7yj>WD8Hw!=b^V;onDiwXl`whKRRpvUOTuNoK4)dy0TGf`%<rRlnDSwCUD6gy9 zbZ<s!GyQ>(nGrL?JXR@3X_RhC=gdEBxA~Ptwj9}Z23yaQ*nT7dBm-NM0+Iug1d;`k z29gJo2$Bhs3X%(w43Z6!4w4U&5Rws+lC8-JNeaoz)})2xg(QY#hNOn%h9rk%hopz( zha`w(h@^<*Xls&0va~g6B6%W-BAFtoBDo^TBH1G8BKaZ-BN-zpBRL~UBU#&;w2{1R fP2x!ANa{%LNb*SbIBXjDPt*6r%pW7(7WVuGNaP;S diff --git a/lib/pytz/zoneinfo/Asia/Yerevan b/lib/pytz/zoneinfo/Asia/Yerevan deleted file mode 100644 index 409c3b17125b803d499e66e78c730030a56d3443..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1199 zcmd7QJ!n%=7=Yn(O`<g|(nXUrw*J%_(?2h15~8LxwYIS~ks`rC9FAxOe};<`bPzIF zokUbruqZl+gA{e>Ak<0*7oqJSPDbH_B7&lb;9ynad9M~qo!q>abDzUWxR88rVgJ!1 zG4<EwHzzEvZnN0e-VcrLdiSbkaAo91X;pB>4pdEN0&~`*;CodHl@oT|)VWgl$rV{W zc2??d4oky@Az5=~pR7HRm30SFvOX7;#&n%*Xswo}@DJHo{ZS&mkdA!y>rHQ$JJFYO zPV=)5PRsafr}gf%({}Z#(?0Uh**tv5u@7H%V$YpYN9QGp?>#Pw`je83<t6#EAgS;% zoqCg$E#Gr`>ts;2mD_dtUP5<H)atJD0lj_ni_Q#v)7>L0WJhmV_Z<2xJDVnTZ_f+q z^WE0j#&OAhnvj0ab?KkECA()&%fN%8<fg9ad_yvmzj#pgjGfZON~L0Xmi)fGz6JNv zWq)q3XW{YUz4_3qRP-_bYO}4(zHiW~QYw{EHS^E;6U&?|{!T7mP%U<cSqZb^yhd5> z<!;?a;U5NSeouv=B7@}x3>O(NGGtdXXk^&Pz>%RNgGYvs1b{?<gn-0=1c5|>gn`6? z1cF3@gyL#qL4rY|LBc`eK>|V|LPA1fLV`k~Lc&7gLIOh~b2XtMvALSykm!)`kob@Q zkqD6xkr<I6ktmTckvNe+kw}qHkyu?#ut>D7CR`+5Bw!?BBxF|;GZHiw&FugCsQqb^ JdskcB{|l@V5AFZ} diff --git a/lib/pytz/zoneinfo/Atlantic/Azores b/lib/pytz/zoneinfo/Atlantic/Azores deleted file mode 100644 index 56593dbfff9bc50bb32e64ff282d0502e75526c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3484 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 diff --git a/lib/pytz/zoneinfo/Atlantic/Bermuda b/lib/pytz/zoneinfo/Atlantic/Bermuda deleted file mode 100644 index 3a5c6dbf7a9ad95ce3a2d7c1bf26e203b6fac30d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1990 zcmdVaUrg0y9LMoPG)E^fEJ+hEu|J5x5e}Y0(9pupFiw1xgK<>oTY&^tBnd(i&1EH5 zwi#OH*jz5xMOnj~<;GAlZ%Q9Co2?BW=is8{X1cXDlhgBlZoBHL^*j6Rb#}IM_kDlj zYg;#j1OIqZ-7ma6+uY0dsfR~%Cer2(>1`RiB^Vgc;MH!q-EPsTKfh<vM-n>it1ffL zC!IFqgL*T)|7m+?Pno%^b+Mh1$Tpc(S@!PINi%c)O*?B|mfVwe(auh}DYJk7OS3Lr zl<c!(nsfZP%=uzO@7@2A%<a5pbGN-E^Xh)I_qFXe_eYM{2Ufmm9?X2-<}GM8d6Qpi zelTkC$M)!gU-QjF!#!GX?g=RzT&JO-`LeKYsTS=DN^wiVhC6<fl4!YIwEDC}@^b9M z;m_rfDM?$J^_?l5xMUaK_{@}jJ8H{+dDkpCWOeDW4)f^F6I$_M+$?K9pv$*!lE-S^ z*2kM_rE=j5x}q{7E3WUg(ZW?Gdal=2rI(nhp+>v%N~U>o-&U>uDJ-$x27T(t45?XP zp)&A?tXh(*t2-}9ZFZ{0>%Nxwjd5KQ*(YnpQ*8a5qo)3w%Qi8&*Q`Bo(yklpHS4z@ zv<<_Jrm?BtJ~Oz?XzXKs_MHZKu5gDw-%=q>>22B^&6VaW)!LGmDlOlKv~|k3w0@eQ zZ4;+t<L(0c;+f0lrLJlA<wGY;d)-z0%FcskQ{)HR(cW)f%{**3*K9PKC(mkE=?>`{ z8`ADsZPGnFpgn(9%WH#MbnAtX^!6q6^`jZm*I2G^?71c>xBSPSWOB4D5J)CZjRbB@ zCeNFcwCY$qu)Nm2m2uA1J@J2W)JgYn*!+*<Mh+Y~a<4md<k*peM~)sjeB}6%0FVff z5Re#<Ado1KFpxNqK#)j~P`oY{Bp4(bBpf6jBp@UrBqSszBq$^*BrGH@Brvaw3<=Ha zVnc#MqC>(%;zI&NB1A$&Vnl*OqC~<(;zR;PB1J;=x>%85k!X={k$91Sk%*Cyk(iO7 zk*JZdk+_k-y)JSjbgzpY3Eu0XN5V(qM+N{H0b~e}F+c_Z83kk*ka0i;!s|u?849l( z3uG|7ZZweLK*j?Z5M)G<Awk9j85CqxkYPc_1sND*WRRipy0Jk9$LmH186K}2A7p@# z5kiIt86#wnkWoU02^lA3ppcP5hRW;43K=Y~8!cqGyl%XZ0YgR%88T$dkU>L64gWX8 a7IZte$n9J~tRxf;6&1vai$lesqTt^>F$c;3 diff --git a/lib/pytz/zoneinfo/Atlantic/Canary b/lib/pytz/zoneinfo/Atlantic/Canary deleted file mode 100644 index f3192156ff043a529461aa9004a8de9dda326f7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1897 zcmdVaUrd#C9LMqJ2q<IDeNhn$5S0+9Bb)<5YM>GUvpA-5QdEdkL@gpEV~`qiI@cUy zpEjm*<+A0Nb4p%NT_Cmo&X%&aWKPYs<;woe(acdgM!)ydO`BKVwE3{Z>ltU`>ihmg z*KTdh_wIVeyT9<^X>}jo6MJH7hcA?l%$yP_@}?HtR#L`qnl|M-CC8js^39Jl{n~qa z;M=2m@Uu6Ra%R9%Pxe~cTW{NpPFeb{JvOtc#b(uRwAocr%P20lhk`|xnVMyDCQi4k zxH4tkny2g^GnF$mO%H!DL67wPrQoq&G`IV*a`%0$yd7s0YB;5E-6hL!>9c~8ew(-Q zpcSSav7-DoD;n*v`6=C+e|5brxYMeI17-Hul^PZI)T^X_p%(2g)#5i(wWKjarTZ4x z;}vl#Ye=ytGOw$=Y}6{^zEWkz_f~o1CtDixi7g#GYfoN#*PiM<VO8foR-~)bmYsY@ zPd7K)^3J_lvHE~kHf>V%qfLrdRqC1KWm*-?(W;S<YLY6f=Hg_nxs`2eKloGYh7zpy zXrk5i{cg{8+_d`cL3^%#$ky-vRT~z6XdCO!sUiKaZ7S(iWBmKtoF;Al`H;4ZwrK0w zc0GSJsuzy0Q`48*?Zwwi)Z9~TFKtQJw*7^+eMPagG$z~2;Tg80{BL_D(X-aPB()Cz zU~Q8|)b`Ei)_&uXUOnAoI|uu<>+q-A-Frl@wI5SQSBC=QVq)X|_n)z`KjeAt_ples znR)S^H^~AM|NCAQiF$KGVQ+PL)P1U>d>04={v~=3r#t2z&KEgh{sU*s!zm-@jGQ!b z*1qnvk@H4Q96593)RA*XP98aX<n)pAM-o6XKvF<*K$1YRK+^Dac_4`(nINekxgg0P z*&yj4`5*})86hblIUz|QSs`ipy1bCYd|hTpYDjKKa!7VadPsgqf=Gr)ib#$~l1P?F znn<2VqP{LuBvoIRE0QddEs`#hFOo2lF_JQpGm<otHIg=xH<Gxo%N$AF*X52Rk7SRe zkK~U`05Su}6d-eeOad|s$TT4HfJ_836UbEfy176m1DOqEI*|E5CIp!gWJ-`ZK_&&6 z6=YhFc|j%ynHgkieBIn2ljG}V2bmsZevk=5W(b)gWRCDYo}>gfR3U~c7%9jt%njv* LgOPBEw}gKHM@+Br diff --git a/lib/pytz/zoneinfo/Atlantic/Cape_Verde b/lib/pytz/zoneinfo/Atlantic/Cape_Verde deleted file mode 100644 index e2a49d248de086306d2dd16a2b2d497a008c8f07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270 zcmWHE%1kq2zyPd35fBCe7@KF(vsDYuOr4`}sia1LTl~92{r~^}8JU<_SpNTi`GtYu z|NqAi7=Y}L9~e0hYz7V=-w*~}10x_dWME(fnFu06NU#`a&wr5RAR6QV5Djt!$SjaU TKy*D&jBLkn*#I49XUGKr1p`uu diff --git a/lib/pytz/zoneinfo/Atlantic/Faeroe b/lib/pytz/zoneinfo/Atlantic/Faeroe deleted file mode 100644 index 4dab7ef0859c244b916d61b7489d7371881e0ca2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1815 zcmdVaT};h!9LMpFG}f>$R-qD-ila_ZNC~Ni%0ov*lJr0%6s?eE%#7B)w#E#TY0WH$ zi*S*Lc^s2wvt}NeP4jHcM#HS-_x`(d<;LdU{(Jp*F1q@>zs?oKMUifQJpIitygcRR z<$LhKjg47efgja-_zU%Mf2clRuIY%b^E&czgO0j&NPVwd6~AVe_#Zzhqia<HcJ7uj zC1o;pWxk9{aY|rpqKvmkOORi%Oc*pmf;|#6xM!+{ybjXP4sV@!XMj#>`L6cH_d2=$ znTG9spy6AusH5PVM&vw|$g&oh64xqImmZcV{}U1&St-%IH8S0|UZ;2F$&8;B8gn&4 zW<Jl-*rr^KYmL!a`{ro;F+ZJM6sU9d&XBoD9-2_#EAxWhYGOjKBzfG|<iMAb-2F!8 zyWf!c?e}Cs*J)YkIx8t{*EO}SR??bJ>7vq7SzNnEmt-E$r6q-$KCMKZDapFbCrvZ# zp_=i{p;=x@lJ#VmF7FAE6_>thc88~|Y#1szEuUmn%@@h7Z<p1%9g<i5LD$4zmi(MH zEeJR&g>fz|8hBCH`m3&ecSP6qmTB?5DqY{{)D35{wdC#=*|<MWOPkVV)4BlNye~?& zEQyt}A|EM_7%f{9f5^6>Zc-8Mr4^rFN#&4lTKVX<RCPbq?H8J4M|-R8JbF{BT_<!` zRioC_)u^qX`@jC{>-%wrJ(<VMX^7Yc{{Bu$b-HCH@}@h@FE$&m^DlPUXCAi6zhj@s zMv<Lbnyn&xMK+7<7TGScUu46`j*%@Rdqy^m>>AlNvTtPL$j*_iTbjKin@4t!Y#-S_ z(g4x{(gM;0(ge~4(gxB8(g@NC(u$?&1!>08bc3{m^n)~nbcD2o^n^5pbcM8q^o2Br zbcVEs^oBHNX}Uw&vo!r74I&*PEh0T4O(I<)Z6bXljUt^Qts=c5&03mnk#;RjzevMK z$4JXa&q&iq*GSt)-$>&~=Sb^F?@04V_elGerhnuHAa?+{1;{->ZUS-_klTRV2joT| zcLKQ;$h|;r268u$+hJ+$2XaF!%^g8*335-6n}XaG<hCI91^<T|<7uvrgR5gtoe~xm O<_M3lr$#vV^85v)jhDp$ diff --git a/lib/pytz/zoneinfo/Atlantic/Faroe b/lib/pytz/zoneinfo/Atlantic/Faroe deleted file mode 100644 index 4dab7ef0859c244b916d61b7489d7371881e0ca2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1815 zcmdVaT};h!9LMpFG}f>$R-qD-ila_ZNC~Ni%0ov*lJr0%6s?eE%#7B)w#E#TY0WH$ zi*S*Lc^s2wvt}NeP4jHcM#HS-_x`(d<;LdU{(Jp*F1q@>zs?oKMUifQJpIitygcRR z<$LhKjg47efgja-_zU%Mf2clRuIY%b^E&czgO0j&NPVwd6~AVe_#Zzhqia<HcJ7uj zC1o;pWxk9{aY|rpqKvmkOORi%Oc*pmf;|#6xM!+{ybjXP4sV@!XMj#>`L6cH_d2=$ znTG9spy6AusH5PVM&vw|$g&oh64xqImmZcV{}U1&St-%IH8S0|UZ;2F$&8;B8gn&4 zW<Jl-*rr^KYmL!a`{ro;F+ZJM6sU9d&XBoD9-2_#EAxWhYGOjKBzfG|<iMAb-2F!8 zyWf!c?e}Cs*J)YkIx8t{*EO}SR??bJ>7vq7SzNnEmt-E$r6q-$KCMKZDapFbCrvZ# zp_=i{p;=x@lJ#VmF7FAE6_>thc88~|Y#1szEuUmn%@@h7Z<p1%9g<i5LD$4zmi(MH zEeJR&g>fz|8hBCH`m3&ecSP6qmTB?5DqY{{)D35{wdC#=*|<MWOPkVV)4BlNye~?& zEQyt}A|EM_7%f{9f5^6>Zc-8Mr4^rFN#&4lTKVX<RCPbq?H8J4M|-R8JbF{BT_<!` zRioC_)u^qX`@jC{>-%wrJ(<VMX^7Yc{{Bu$b-HCH@}@h@FE$&m^DlPUXCAi6zhj@s zMv<Lbnyn&xMK+7<7TGScUu46`j*%@Rdqy^m>>AlNvTtPL$j*_iTbjKin@4t!Y#-S_ z(g4x{(gM;0(ge~4(gxB8(g@NC(u$?&1!>08bc3{m^n)~nbcD2o^n^5pbcM8q^o2Br zbcVEs^oBHNX}Uw&vo!r74I&*PEh0T4O(I<)Z6bXljUt^Qts=c5&03mnk#;RjzevMK z$4JXa&q&iq*GSt)-$>&~=Sb^F?@04V_elGerhnuHAa?+{1;{->ZUS-_klTRV2joT| zcLKQ;$h|;r268u$+hJ+$2XaF!%^g8*335-6n}XaG<hCI91^<T|<7uvrgR5gtoe~xm O<_M3lr$#vV^85v)jhDp$ diff --git a/lib/pytz/zoneinfo/Atlantic/Jan_Mayen b/lib/pytz/zoneinfo/Atlantic/Jan_Mayen deleted file mode 100644 index c6842af88c290ac7676c84846505884bbdcf652f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2242 zcmdtie@xVM9LMqRfk3fxZ-2m9fKZ61b@Chh#YJ=iGw(FzGExatM68SQG6w#LelX`6 zWA7Tv9JwqVv!>V|lz*VLe%NT?WhQf4t}Rwp8eJmMkFokZzs>oF{?kAG(dWDSKEC^I z_s4DbdInZ(sLQpkIdSF<A5OdZ@O<;r=GN&Nv^r01sp&iHujxO(NRGeZ)bQ(G`Rv7f zIq__Ud>%@alWXGS!l5+1xZfu~z3h>p9hvfTQ>sMjMSiJt$ffd2GCX@wF1t?2i1V2I zDiIycij~pGNu3zp8JXl?Ad~a{(1i30nmFkzbw(do=kU8aW$=*R^2Hv#^}`o5>Bvz@ zKF}>Gue>T#+f-7wJ|k(tkleOvt=#SlNP1DJOmi1XMzTw$-!w&BF<y0z<m-%YGj!%a zqTX>VPVembP2Kx`&{-X4HM8|o&DwNCvuh7(PSqL74fRN#r&scqy(9%GyQMI<NeahW zWKL3t&N;VQ=Kk5J^NxCD{+E?n)K#sX-g$c0_7W}bOxC;W(zT>@uG~`=qu$yiS&(sF zOTA-K7W0Xgr++QwL*L25==Wt|xKHjK+$)Q^-xOc}d+Kj*lf?&K(<KcJa$nnXy7YnP zby;woR?H4+z*nyKI~VJ6_e@<rnyr-yWm0*1qCPk>Lsq<VSyv9k%c?ySq^jqlJk&BQ z)g57}sUDEk+kVtF#fN2WRlnAz?viz$ZmqlFZC#(Dy8io}T0a)j4Smh}@VS6KvVWxp zKi(*h?(k?sSA{%QpQ?{<FOZE(izHO%lqYhg%BIra<;e+_G-f4eW8@oY8b7K{Cq9zq zp)<PqtuEOT?$xckKG1F5yY;E&ecICAqEU`0NA$SsTv0Kx|NUiI@srIT*-B1xjI*rq zV%>P<{?D7M?|uG&<t?q?7T_BWbI?2l{>5zmGAA@NEr`s=)=UVQ5i%uYPROK?Ss~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!c1B#~L#nrZUOnI|$)WTwbek+~w1wKcOv zri;uMnJ_YAWXi~#kx3)7My8F-8<{vVb7bns+>yy6v$r+VN9K<t0LcK70wf1W5|At) zX+ZLTBm&6<k_sdjNHUOY*qU@8`LHz!K{A4*1jz}K6eKH1T9CXTi9s@hqz1_ik{l#E zNP3X`*qQ_(8L~AgLUM#83CR+YCL~WtqL54>sX}svBn!zFk}f1)wkBan#%xW>kene& zL$ZdX4apmlI3#mO>X6(a$wRV-qz}m-l0YPbwkCy04v{1xSwzx^<Pk|El1U_$NG_3N zBH2XJiR2SWD3VcIlTsw7wkD}aR*|$Kc|{V7WEM#+{!eooZwfpshZej2d6@;7*=~PM JHfH6;{|(X%Sn>b> diff --git a/lib/pytz/zoneinfo/Atlantic/Madeira b/lib/pytz/zoneinfo/Atlantic/Madeira deleted file mode 100644 index 5213761f891303f95e1962570568ae62ed387950..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3475 zcmeI!dvwor9LMp`uUVK&A4bfKvSe!U-Gq@P7CUmO<`<)8xnEjhjpLH`sdQt<dD~eE zleyN}(fSfmEXj45HMByOBKP4ZMD1vOp5JpiN2h=ENB!0JcYeRe{LG(T&$oZ@*io_S zZ%-Zj4R4-u`{w5eV?VM_<jTU{)6K%)%Op3q(kv>PYu?Q?W^w*@m6tlqESY`HEbVsE zEE_+=ERPIVD;`~~@`JCN{L72f2Sp#Ng7;$8hj}T+eZ7HN{pK{aW`birN}p%eK33nX zj~#3_-1d#xP_@&1a$$qnv}=bd+^}AKw)k1I`JMUd^95CE%arNri!o_xYjTR()-}{@ zkL#*-bY7u$CVy&n9z17?eDS8}VjJ`2(TQsJx}j=!#p*z@85Af!yjtyfD?#nqI%q;k zx*I6jzQ=qusipdA)ucdaQg)zp#fN6^6V=t;tbu`ju^EAVug*352klp1_u8h)IvsJo zX<h1k8@9zcP;-rQ;MZl&!P7a;;ZH_8<!iFt@0Y&f9?8yde|UMM`(ye5_vo-h_gGS# z`&0MU?(x_r?uiz)+>_xQol~`<oKuxy&gruu&Y44%&e<<3oQhA&opbpm&iMtKoeQ&E zCopY^b8%d@^YidR_fr1?_j1Bw_ewi)e~Em_y&9VCUJDxTUc2OX>nsdcb!XRBx6SNm zbVih^HzLf`_cxZ%gi6z(-EYz`@~qq*dRQ6-?U6>8K9k1B3iHDD=Sh>Tb0vIJp>DdW zKu6>**3Gg+H-F(JeMeflzVoT!x<wzqj_lGy-xb|Kw`|%<-(9=0ZWWv$t*+cJQO8?K z>$2PBp6%7;-nG{xdiiO&FXxc7nYmlqW^58?#0L_Sa!|*nuhs41H|qP7=IQnki*$$B zDZ0b;OdS`VBXQ+Jb*IY7(z!4}Kk!|$bXl1y@f$nKgRgayt_z#WL!%<4+ssb7d(U7= z7!j@?Zh2CABwW`$gNr0F@`z46e?s>P+M;`v?9z`MTc&##<m*1go2BoZEZuL_5_xp| zc>P$`EAsfjS(21CQu=pH6MvsXd7@E284%rC23&O{xnWP8{B3<1bR|j;{`j&ySr($7 z%B!bS)>h~tnU{2GPKkaxwM-A4aYlx9U9X3y6w8R_^YzI10vT0fos5nU8GY()8FM{d z#_pOb<I4T=%==GD+K!3(**D^4{L23NxiQUU!fWmI#K*hn^ihrU^D)hIMvq_hq<TtE zZre~M2af6~b+5{l(rtR``GYdeU8$#+Y?K$~e<3pp7RifKmrLfHO!3`%OHj4on|xKb zzjx#Nrv6<>jhZ*SYSsP^UsY9sD5bp5YIa+cuSQkX#ek}P<IjJ7Q&;=6Quea}f2+d& z`pNfw`ubH*`@Yw4)O}8jHAW5W(cf=B{N8W-yhr(b-k+3*&*%RK3s_~JA@QNf$PyxJ z=xG-bSw&<Sk#$5C5?M)PDUr2A786-bWI2)b^t20#tSGXi$eJRHimWQKtjM||3yZ8Q zvb3IdZIQ)ARu@@bWPOnZMphVEVq}exMMhQ`S!QINk%jiOD~&9*r(J7gv60pGw9AdG zx2IijWW_!0k|S&GX%`(?bx*tO$hv#lg-2H2(=I)-_MUd}k<~|*A6b8-07wOp5_sAg zAVol`fRq8L15ya25=beKS|G(hs)3XPsRvRJq#~ZSBuGt=q99d4%7WAdDGX8>q%=rv zkm4ZKLCS;F2PqIzA*4i}wnqGiqDVYciH9;FbwUb-R0=5-QY)lbNVSl1dD?m*1w$%^ zlnkjEQZ%G$NZF9OA%#OKhm;Pf9a21`dPw<@`gz&{A{9hRh|~}%B2q=9j7S}kLL!w! zN{Q4GDJD`)q?|}SJ#9geihA0TA~i*dic}RTD^gdaut;T*(jv7*ii=biDKAoAq`*jp zJ#C4R8hhFzBUMJqjMNz^G*W4#)JUz7Vk6Z?%8k?;DL7JbPg`=N=AO3bNY#<DBXvg# zk5nEhJyLt5_(=7U@+0*}E&${TKrR8~8t}9i0df@}mjQAeAQu91B_NjqaxEYi19CMW zmjiM=AQuF3MIe_1a!q*JivqbSJndzHTo=fNfm|8LrGZ=<$i;#GUmf-W`PZ+G5POt3 XTqn_e+qG%m#%UW9-8aVJr;tAYB=HW> diff --git a/lib/pytz/zoneinfo/Atlantic/Reykjavik b/lib/pytz/zoneinfo/Atlantic/Reykjavik deleted file mode 100644 index ac6bd69731d25fc20724798abd4f683f1f2ccbd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1174 zcmd7QPe_w-9LMozO-ES7AW$-}Z<HEHu>K$jMYysKwL@$Wb#X)mML|W;t;7<dl=M)i zRo}!@^bZ2T*dYiKHLYdZT4sOEsr}I`n`J%y-aii=I@PJ)!}fZ3o}Ko2N4D+WwcPpR z_{<YNoOR~Iz5jIdxW*a^ob!p3^%o9quDaOc^=r7sxzt=*-?XZ@s;T9W|86MXX<c0` zt*N{lZAELf?M{(&_zJb-N>K0hbxLRLV(p4wm-~Bt`XJUIiH&J}SaVgoOWO6(&NJFm zmXO|x1NwO0O-UApH92`!Qgil8>d6s#I*_M*EnDST*GlPcJgm>J<;sidE&8%9Bd@lX z>Fa$dc@vzk1EILQUHC%>OOu+Ol`liNw{&QDN`@z5I?~=R?|P5w`^&fGLvvI=o@$iQ zb3q;3b3#5HtCaDu>gURG`Ld!~C)O;IuXA^3W<j=O#@FlQ&q4Xty+psKy*d@IkQtue zmpAL5u58yGiJJe`98Z?(j*U7qr@yD4*cY=mg(6N#AmA(wEOR!Pdw%Tk*mq9kFZOfI zVMAm`WJ_dEWK(2UWLsoktJxUY8QI!u_C_|hn%$A@t!96u0i*+@1*8Y038V|84WtjG z5u_8O6{HuW8LR0AX~$~%K^j6jLRvz4LYhLlLfS(5LK;IlLs~<6Lz+XnL)x>N{*VTd x4v`j-9+4)IE|E5oK9NR|PLWoTUXf;zZjpAareCCCq+_II{9k&`F@XniegRAxR~rBT diff --git a/lib/pytz/zoneinfo/Atlantic/South_Georgia b/lib/pytz/zoneinfo/Atlantic/South_Georgia deleted file mode 100644 index b3311b6331471833893fcb776ac88e2a90a607a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34$+|NnOnFfjc8|M&p|i;r&zgRTL@j1Uq` X0~+){v+Xa?973jY*#OPAGvWdOF=!;J diff --git a/lib/pytz/zoneinfo/Atlantic/St_Helena b/lib/pytz/zoneinfo/Atlantic/St_Helena deleted file mode 100644 index 65d19ec2651aeb46c42ce7a74ae6ecbf3001edbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmWHE%1kq2zyM4@5fBCeMj!^UIVL@u<iYs={~rsGC=jst_=YgJ12G7MkYLb%Ai!%F J7tmA#E&%jo7hwPZ diff --git a/lib/pytz/zoneinfo/Atlantic/Stanley b/lib/pytz/zoneinfo/Atlantic/Stanley deleted file mode 100644 index 2fd42a2c34e3f4cd0e107b818abe108f3be02d4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1242 zcmd7RK}b_^9Ki9nR4nU|P~bFmR*EytzfNcBw7)r~EyGp2xDL&T&>*62on{3kL6CLt zP$5x4|GIQ&$RnrAm9&bWEMg=b3WB<oLI3{Wcapl)t@rr7&*Oi4{Jt+58$H}?{c#nV z6NanL4Daj4%;R$KKwx?>W~cmlVx}=*&#wO<(j`MOtzGKTw@x|tZc$}kmCMIdGwR8G zE$98GguD2sbiYWd(yu8`+08>@!?Q_G`T3BjxUpYV9`Q@hc$caQm&lDHm1>iBNp9|U zsp?g4Wld9ssQEZ0Yl|&W`+ULnet9qI?j`Ll*_5cibj5B^lVa<MF}v~NxDa~}+D#{~ ztLDyx^bMU=EoH;9wS7dj{^*r$?yzcmQ!TfBuUG943T4OZx@_k}CKq_v7wI~FJJ)^H z8`&P4i_6oiBRw6r;yZ>vM|w*y#DhIgBf+I(@le&>NN8bCyuWBZ+dq9YxAW7TZ1~z> zZdYa^J8-5cm(W_f@>VRn3-Zmq{NV523ktNJOIeol%-8y5*0oaWv~8`?dNJo(%ZX)9 zI3L&@wf3255I!?4W`18|^dDAhzCe$jD^4OSM%Ik18d*28a%Am}X7$MWkpz$okQ9&{ zkR(h=7Fjr&G>|-yM3797RFGVdWRPr-bdY?IgpiDol#rYpO;Si!jwUT6FC;M}GbA-6 zHzYYEJ0v|MKO{jULnK8cM@N$+lBJ_b6Uh@v6v-4x70DGz7ReS#7s(e%7|9q(8Ohnv lB#mV4XwpXVb~K42nIowqxg*JA!tC-t%)TgKeu-{h(J%cobp8MU diff --git a/lib/pytz/zoneinfo/Australia/ACT b/lib/pytz/zoneinfo/Australia/ACT deleted file mode 100644 index 4ed4467ff0f7a47e2951d3674fcc542fa3f2129e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2214 zcmds%TTGU99LK+J!U$5)avYUVGr^F9ib0`y0E84nc{!FwfGBqG1yZpRf2obyJfvO7 zj2Y65E-H-HG{+*A8!H4>7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H<SHM_>5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8<u81!2Tq>SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^<e)_tvy$(b)BUuIFYCIEtz^~_W~6Lifuz>u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiE<ccH`M?2eTi{hA=zAY>EG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsce<vzXmtwu{*>X2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v<cHEOrtQJ!n6w0E6=W3m~LU(h3OZjVVI5qEdzQ6G!5t) z&^DlNK;wYU0j&di2Q&}UJ<qOvnEqiJi0L4vg_s^<nuzHlrj3|B0*wSZ3A7UECD2Tu zo1R@efqnuF1v(0}6zD0YshF-}+KT@#eGQ|x3)50*Z&E>8YI0g~YEr=xl(IPNPxDR! Aa{vGU diff --git a/lib/pytz/zoneinfo/Australia/Adelaide b/lib/pytz/zoneinfo/Australia/Adelaide deleted file mode 100644 index 190b0e33fabb8dd6c01f6d939df0f09e288c562a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2233 zcmd7S|4&tQ9LMo<zs37ff*50@L43)O;>!ihfgxT+Qj#l#Hv<CFR1k=|q2U!x$zr^V z&B~hIIk!q9u+<N_2I|}zb4f+TH0|5k8f6(<n^{}mwxZATL|d)?0MFSukF$I4?sm8P zdIsxSn)0px93Jxu7snoR@f_1_ZU<Mr_R_(V8NZALPsrG4WB9${27Q0PAO7HlE*bAl z3%f#ob<ao_PgR<Fep;+iKDS1Fl&ap?Z`3>F(&$TX>($Sv$TgpQEi-mGCFa<u#5P`# znUNzBS8`frh5O{%<UxtAX_xpvo|J^bN=dl*vL>dsY2v8rY)3$6547r>iCn$Dw?dOf z;xu{NVZCAhaY?D|(bVqqnzr<iq__MaH>N!<bIZ@kP0l9C$Q+W4@ed`_8<EVh4SMrr zm)sILqgiM7X?EW{%{dy@+%09|+gqo34eK<&qgV?TmFoQJWW6<Mt}e*8biwaYS~w?3 z3O}FJqTi-u;mI+*?Xw9f?*CR6oj4^WJ@3lm1O4LP_^K><Y($rq?UB;;hjm%bD_U0C zqqlpiWqEqDF8}$6thjhU%0KRtJ4ScN%HejY7-*Eh{z_TZ>zB%{c~TXs*E`n+w7RN9 zSNn4{=u6SNQsQ*YOsB4y`cdz`bU|yrIIp$ir?u|QVZG<rS5iNCP8uR_Nuv(Qz2N~_ zyD1{;YMzk$R&>ew!Zz73uU<AL2Bax2tj&%VZN6Nmn<iFj%b8+r9m&?V*OT@B7h|>k z8A~7NzN`<1zSK}_j6763B^~A8%jTj9*^+ry9=`6Bbb8;A&R_dw>*PKOf7&bC-U&<B z@fO*B^f}#es7D{!+o?NuH|wsB8g)9RPnY`&I4$eZ@5~?BdUnv^wyd6r<^AV#MIdM` zDP6{Gb&!ua9Newo|L^1~^A;D!Q|971X389!6Z+zD`z%)o*^RB)4zeFxvms<h$d-^j zA)DgM*%h)aWM8&sW5~{I&DM~;*_zEEyF<2z><`%>vO{Ex$R3eRBD+MkiR{zXY!un4 zt=THFS6j1LWVg0vyU2c#4I?{7wv6l<*)+0iWZTHTk&PoeN4Ada-PUX#*}bjVKC*wL z0Z0ds79c%9nt*fxX#>&+q!CCbkX9hQur<v<x?yYDf%F4u2+|RxB}h+@rXXEG+Jf{2 zX$;aCq%}xykmexWu{G^M`hzqG=@8N)q(?}TkS-x@Li&U>3h5NmDx_CPvyg7tnsy=m zLK=p23~3qCGo)!q*O0a$eM1_DbPj19(mSMiNcWKTA^o#84MaMKv=Heb(nO?-NE?wp zB8@~kiL?^wCDKf!n@Bs6e%hLbA{|9qiu4p|D$-S?tw>*y#v+|{xSZ3c+j9k+e}{JG z)0PRX$*l>k$?d<M|H`c?t=(LH$aHV5XabyFw6MU(TUmiTUrt_*FDoz~atosW1Y3a$ ArvLx| diff --git a/lib/pytz/zoneinfo/Australia/Brisbane b/lib/pytz/zoneinfo/Australia/Brisbane deleted file mode 100644 index 26ffd9acb76d06cf1d6569e7bcf545da8c61a6ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 443 zcmWHE%1kq2zyPd35fBCeK_CXP`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIUj8MqT0)Y$+{S`oS z8D?!@WMN?FS-`-F%=YmOVQ_SH0TIC=AZ3h<Ad(@31ZV#Tf}*$cIY2bX`CuC81rQDL z2ABqV1w@0q1EN7*0?{CEfoPD|K=y*X2ckh<1ObpY!8FjTAOP|%m<D<o1VG*f)70}i L7bqxn4Gp*eQCf5O diff --git a/lib/pytz/zoneinfo/Australia/Broken_Hill b/lib/pytz/zoneinfo/Australia/Broken_Hill deleted file mode 100644 index 874c86505c896406516a16dd21b9ddd8d0ba2d95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2269 zcmbW%eN0t#9LMo<d5ABjAnJ@Xh=&+aJY2vW7$S<KBv%M;1_Y$}fPfTX;T26;%J`P8 z)JpFhTc$Cv)m&Df&aE+*R8&mUves6VJ@6M%TTffj@BJG#xA=>5ch2jKbI%wXpSQQ7 zc4LO~za!ZEgp=cWb8;WnYAzis-*~m-Y~mjS-ZL`LU+w#-w@N?m%JY44q(uhX<9vaQ zc^Wh=UV=;FH2Akg8sZMpkk4W@H2en*?FrN=Bk${tFUQE#E8oksT>%nys$asZugY}) zaf!%1FEf05WoC4@%qnY;S^qpEv$Kljrm6chGPYhL`&H+-JUXYVPUjA#=`HO=8r2t} z(c6yct%puaOnIBewqDk_B}XN`_7}M=ZjU4sUXp~7QAtedk;K7IB`MS|NhjWsJBC~2 zPX7f>zIaemd~a#$NuQ>by`=5~6`EePRx_G%G;?9T&MS@9yP^_weuks-{|?ctxlxj} z`NWm1uZA`IuQ6G0c0lj`a!7JIf0Bh~&Pi_DhqCB!r{ryTT^8@|(<KGZN&dQHx-|7Q zEy!=vdxA@4S$vHy`~A2q|M9RCe!f@k?cX6QdK;vut6DsVie+Vco)m9Qmy*Uxy>E?2 zOG|QfRbHBU-7$K9OoXnU9-ynoe$@v?u4>uWm$iKGyjHy1s}G*~PAa=ENtORSsn#BO z$k!!nHu+_3+0*jy@)lW_RWIvruapgu9@!Y-(;8Q;){Iu@rlA#Ddm%^b`ckz1&1l{H zO1L)ccl43gQGK-WTWzchlgG-(q^a;{*^)gZk0)J}CvH9`&7p5g^Piovb@-t8zG#<i zANZu@bggVZ`J(PP+NMt)Xx5!i)#$FKG7WHze*%Lh`EgD5#fjc?oIM@pzu9@A+ZE(E zZGI>8`uie}*IAsul*=kFUvs&*I^K!L5@H^4a_l!J_cO=Lv1PWd$L65UZ*cpJz(!<5 zRx>1IOja`}WK_tokZ~ac<CicpWN66PtY&b?=&WXV$oQ;gfXE1uAtGZ$28oOk874AL zWT41Mk)a}EwVJ^qqeX^`jMr)gjEvZ7hK!6E88k9#WZ1~Kk%1#4M~04!9T_|_daD^e zGJdNG01^SK2>}uVBnU_pkT4)|Kmvh80tp2Y3nUmwG>~v0@vxeJAQ7>ekXTJjkf0z@ zLBfK>1qloi86-4FY>?m}(Lut4#0Low5+Ni+Rudy6NJx~BFd=b50)<2h2^A76Bv?qa zkZ>XKLIQ?F3<;Ul#0&`<5;Y`jNZgRXA(2Buhr|vE9uhqyd`SF|03s1YLWsl=38K|R z5eXv_M<kF)B#}@eu|$H2L=y=o5>F(cNJNp4A~8jRYBf<s!ivNd2`my>B(z9uk>DcH zMZ)U}_^*G#H%#&qIN28`dhc3#pFQH3^zPg@p5CzQGCY27GhAQ_Y%^?w%wKh_#-?LD aNXcH1>E>awC*7Tzp6X8a%!9PdDgOd<<r<Ix diff --git a/lib/pytz/zoneinfo/Australia/Canberra b/lib/pytz/zoneinfo/Australia/Canberra deleted file mode 100644 index 4ed4467ff0f7a47e2951d3674fcc542fa3f2129e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2214 zcmds%TTGU99LK+J!U$5)avYUVGr^F9ib0`y0E84nc{!FwfGBqG1yZpRf2obyJfvO7 zj2Y65E-H-HG{+*A8!H4>7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H<SHM_>5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8<u81!2Tq>SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^<e)_tvy$(b)BUuIFYCIEtz^~_W~6Lifuz>u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiE<ccH`M?2eTi{hA=zAY>EG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsce<vzXmtwu{*>X2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v<cHEOrtQJ!n6w0E6=W3m~LU(h3OZjVVI5qEdzQ6G!5t) z&^DlNK;wYU0j&di2Q&}UJ<qOvnEqiJi0L4vg_s^<nuzHlrj3|B0*wSZ3A7UECD2Tu zo1R@efqnuF1v(0}6zD0YshF-}+KT@#eGQ|x3)50*Z&E>8YI0g~YEr=xl(IPNPxDR! Aa{vGU diff --git a/lib/pytz/zoneinfo/Australia/Currie b/lib/pytz/zoneinfo/Australia/Currie deleted file mode 100644 index 865801e5e0befe4e6d1b17a10ed15f721c5ac335..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2214 zcmds%YfP1O9LK*$U<9dTIpv~+R&ogs2QSDeB{e`$NsPlaMMC6anFm27%hPXWo?fJO zag$|Bq%nHqRJv#zi<oV$5m;OIG^1WLx>;MZoS8kJXZ5OAy=#4*J^#<MXJ<Qap6@46 z)6zIc{~*eF!wvhKo9Bt<9EU#auowDMei{nAqM<XD!FNwq*l@2uIMUUov*Dy*M9^=M z6Q(JuG|8ea<yv&sRg3;8(PH8*Sj=m`TkOR_yZ&&3#((mKCWNkN;;FY3S9@MJbarb} z-hgiG*sYtAk7#mLqbC3HxTfS5X==nO#m{K4_^-2-=qa?s-u0F=nr73&1vY&!){>i2 z?dF4hN~sRn3>&eTOAqOmmhW`y%$>Tecv!Q>ty60HF{S?Uy3&3awe$}<l`(wUZhv)$ zGLQDy>;r4$-5a*7_B{DEwOIDr8k<wGM{^e~w0Swrnm;|+a;7X-&Q;lhOPR_&_m%m3 z$Lx;&x9!e>3|rVUszry#Tiz3=byw&|TfCu1OKRV<r3GQ#o!4ji-j}Q(`G6Hht<bXQ z6I%9bw~D@bPQ@SZ*77r3wc=!>R`yn^<Y1BR3H!BbdxlDb<+i%6(C#f=U}cNbEZ|G9 z@|0Msh`VAH*S@#Pi|4KC!iZI$9k7~#<96Svb6WHAu-10IsoE!xX<bK`>YAU?{Z+eE zU(}}cxeaPaE7ykjLN!imwI)x!HT_&>&7=9&^8S3=I5^85I5E{8JaV0Fdit_GWZ&B6 z;O7?H7_HXo-?XK8Ol|(p)Sf=1tqJ|w7IReFF2AVlWBb+dsp;W&TD9YidOgy&*B<Q- z+0JJ-TWD9EJ+@`FdB**9MMVAwE@{t~&WEh0x)dorf7}!E_sN@;B>^odDC5Wv@H3Bx zQ-Oc6lWPv`-pCZpUND=%?8ddT9n5|(8^Y`evnBqQJz+Kl?8>#XEzG`LI~&97%(b&M z%-%4Y19k^&57-~DL12f#7J)qin*?_0+Sw+sPhg|KPF*`&1@`LN*(_$anC)Wri`g({ z$H0~`d&X=U*fp?iVBf&Tft|Z{whrtavw2|mnC%1m$20)w0Hy^%4=_!@bOC4srVl_P zFr9Giv;xx$pc$BMVA_G{2c{uFM=&h`dV*;R&=pKufWBZFgXs*WH9&7%JIw*Q1GER| z56~c>LqLmw9sx}Px`b&HrcangVLF9r6{c6Non~RWg=rV2UzmnrItH{1=o!#7pld+e zfW85Z13Cw^4(J`wJWTgoJMF{t57R(Q2Qe+g^bpfTOcyb2#PktpB+yBql|V0nW&+)G y?X(l<C(ux!qd-f6o?@Dc=_;nJ`2W(^SfV>S+edaYOR|05Y_Bh~WFE?z8~Z2nWBGsp diff --git a/lib/pytz/zoneinfo/Australia/Darwin b/lib/pytz/zoneinfo/Australia/Darwin deleted file mode 100644 index cf42d1d878b364a3d720997b1511ae71da458b06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318 zcmWHE%1kq2zyQoZ5fBCeP9O%c`5J)49KW?o=Il}baXMrd$LZs76=$}`cAQ=AP;qWS z703B@r3xlSC}d!$S^-qgFnI+d3j;&z3<gdg-w*~zXBQ9=90HO8f)MQ119dQfFbHq~ zF^J9g9|(%YCPjegY9ZzV5Djt;m<BotM1!0Krh!fa(IDr6Xpj>@G{~7C8st<)W+o_v mxwUQr$R?nRfnEl>m<8fupku+#1~~=<xIpgLwX`xe-~s?}-C{lf diff --git a/lib/pytz/zoneinfo/Australia/Eucla b/lib/pytz/zoneinfo/Australia/Eucla deleted file mode 100644 index c49d499cdcea0175fbdbe51d7caf571286c01790..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 494 zcmWHE%1kq2zyPd35fBCe5g-P!1sZ_F9KRg~bM_duoDMlM<@E7PmNVNkM9wbvX5r-j zD8jjoPeh6_LPTnLsfea(kcj5`{Zm{Ew@h(kjhW)ETqNTDu2RI4IYh+sT(n62|NsA) z7{QR41p!$Y7%DCRtzqbtVFa?Pc^J5m**?A@4B7^kCZ<5r0!RV@BO{1p2qD2AK&$_k zEaTb$qCx%v(?CCgXpq0aG|+D#8st9^4e}$%9*{r5G|;ag8suLv4fHdJ2KgII1N{!7 uLH-BPpfCW@pl|@wz_0+(pzr|Gz%T*P^+3C*87^El;E=M@wXiZV<pKaw-)|cL diff --git a/lib/pytz/zoneinfo/Australia/Hobart b/lib/pytz/zoneinfo/Australia/Hobart deleted file mode 100644 index 92d1215d60f929545276892330917df09eb8d1e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2326 zcmds%YfM*l9EU%DE+fcGmQ(Jbm0UtDUJz385+JA~#+?)ik&9*i5k#`2@64jLSY<9* zwnQ4EHx8tWwy}t8V~xPtx~Dm+t<lZen&lAbJZJT)SG{XJXXp2vot>BG^AwcTRWH&% z@OR#D!}HF~^9*y2V;?u$i~Ui*3>Unr;h|#h`=^U+q&v+!+R>nMtzq7YciMEduSJtX zrMlMCYg66JrJ}>;X^oK2svh}z)68#rxcu|N%>Qz#1td;dz$c*=IP;<fzJAq$E)ClC z$3ryjv#&J0WkNH~yseq#7j#2=r)H%O=*H$lx+(I6W|vfJ_O(YfCpBAheK#q1UX=xZ zldMormW6h2x3IAo3vbP|h`}I>tckXpkM=35w8iGxsLfw{Ot;kipj+o3&}}&*THv!y z(Xl;>{_PFL{4{2<AGIrP<gDHP+J42K?6QSNwkV;k)e;-im9(qQlDC%GqN2yOc;yOP zl2WUs5s{WMXT4G;Wy>zdEA{->mexIPcl5t&%Ln3YMc0^C9-n6EkDb-3mY;3)jxMb! z|G?H}w(8FGKFdgW*)k)KSeE|=tqVA%b-#Bi`@83q^XVb2AKI%8r>nKGyI8qLvvpT% znl|l=Q=YfbHdkcX-FeF_e|3x%B!yUERFD<ToUo#)AFcS(1uMBYYNh7}tZbmy?m2T_ zTV5H_*7mnl{zQ+qHFv0@_F3Iqa!{4o4ceYsrK*@h?Fi0N^{jfU@l;yPulZIxmSJ@t zF14M53+%pAbM5{U*V(RTuGj<iz3ujXY2KXysxSRRdveCrkoJWdV~4djq+d;eC)IT2 zCG8u3TFsxE9(=D}``@h8Lw#-baA%7hIK10h4p!JBdp4WL=da7x?>}@&Plh``yq@Zi zpY%enC-Co+Hyd*cv?ep3Bcp)NJRVLJ{EIzJIW#8dTg<L7+rsS2wX-qI&M;fU><zOy z%<eGT!|V^c8{~l<g0n@$9)V2)yToi0*r#h}qnMq#cD9Pyt7~VonB8Kw3+xxzFtB4_ z%fOz2O#{0IwhiptwX<<x=fKv1y}Ncc5A5Exvwh6|F%7_U0Mi0Y4}d0Mx`1f|&<CIq zKqr7!0KIVSGy~`crX4^(Fbx4Zf@ulR6HHTpu3*}N=?l;pOlN@BV0z=)X%414KzlI# z!88cdAxw*a9$}gUbP3ZYpih`a0iD9M3ezi0vw&{7cG?B>3uqY7F`#8Y&w!=@T?5(% z^bONEOy@AI!}Jc*JWTgoJMF{t57R(Q2Qe+g^blww&_$q)Kp%ld0-Xd}3G@<ZCeTfw zotS>Qb{dN5D5j;Do?@Dc=_;nJn7(2fi|H)TTA;T;bAj#x?FIVl+G#M*VW7o8kAWrw nUB<K-(`QVh@&B#ULBxAuauWHD&rMEBNKQzK&s~BN7YF?bI&LW~ diff --git a/lib/pytz/zoneinfo/Australia/LHI b/lib/pytz/zoneinfo/Australia/LHI deleted file mode 100644 index 8c6c7dd0b7c464bccd98e87483967fa55c833cf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1880 zcmdVaTWpMJ9LMo@l#9AphtO1usza))X5UwlDQ(rEsBO!(gBEovHRJfGL;ubVp$^Ti zkkYttN~E%hIEE5-iAW<Nq7j=#P%fl#;aS<lBN5~IzHzr#dm}NEna^~RxoGnL{=&1O zRl}VBJUZELc=1?kUwrOWYu|c&u6mge$}<UX_C^v@D<rXHc_b+?Mv|I}Bgt*4(!0)$ z^tlx$eK-87QV#!a`jsn{TH7N1$JOfr(br~R|CM@B!5uT`-BX>G-el4q+|cRqdrbP- zWBSMTWoAg@b(PV&TZYO}6}+%iek!X~?twz_Dhk!G>Y!xi1l918t}>!eS2famFC#y_ zSE1w|Oz7DomDTi0MqR(GMn7pa*{51{&b3P>cjqNN=ExzFxA>4AyW^sa%NISqvOy*U z8&v-I8kyKBTTdELE|WeS)RW)rGX;OG(^Fbjn4hoA)P+q`P0`^jU0mmxsWr)ZnktrG z=7&_tG*?RVl2q93F4I#!sTp1Vl^L<;YUaDgQugAeDu2){v;I7(W;Z`Fb58%ED;h7G zxx0_+dD3X+N4Dz)WgE@H$%}MlXpUJlc%ojMm}{!KRjMU{*|OxHd=+WSk?6xTwe(hs zEIS{smY)d7iv4fZ%G%emO5Il~8Z)cQ+jVt8n^}`}Pp?hCZPxWVqu0luGV4G7rfb^k zjQqV?Z@3#Z8?Tn?O&4~_=95ZoIS`Sp+e_58>M61<7K;VqI(&V0jQd8<j*inX(4LV~ zf84R3Pc#SO9p~U_r;FnpITz?zSQK`~=9h+@A)c4%+IQD~_xQ!vNBDmnto<KjYZ7Xn z@ly^LIbL6Tz{nB%+CxT;+1DO4a@5FSBgc&#ICA93p(DqR96WOL$l)W$?`s1<BJi~# zATjvbAdo1KFpxNqK#)j~P>@)VV325#aFBSAfRKoMZAeH=zBVW%DkLl<E+jA{G9)x4 zHY7MCIwU+KJ|sXSLL@{aMqe8w5+xEQ5+@QU5-AcY5-Soc5-k!g5-$=k5-}1o60@%j z8i^VS8;KhU9EltW9f=(Y9*G_aABi6s0AvJ^Awb3e83be$eC;qG<A4kVG7`v8AY*|H z1~MARa3JG>3<xqJ$dDjof(!~WD!z7Dka0l<1{oP-Xppf%1_v1(WO$JA@%>&0sB^YI dOSwZmcT}cp|M2WbFQdqFgI>_hC>jo(zW~-0_U8Zq diff --git a/lib/pytz/zoneinfo/Australia/Lindeman b/lib/pytz/zoneinfo/Australia/Lindeman deleted file mode 100644 index 8ee1a6f548b0518bf38c99632a6cdbac5d23dc32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 513 zcmWHE%1kq2zyPd35fBCeF(3x9`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIi0T~apUKTTG&p6ef zbE&^Uw_;5L6C)Hdvp^sdh+<&qUIVm{Vb%slAiHM)11B=u$2Ww*(bWY+1c!i>F*1Tk zh7c0`^B)L`-Y&WVqCx%w(?Gw0XpsNFG|-PA8stw94e~392Kg66gZvD#7vygc4e~n( zfcy`pfnfjwpl|@wz_0)TP<Vi8V3>daC|tlaFl;~o6h0st6h<JL+Tp|n3O8Lt11<oQ C5{I_{ diff --git a/lib/pytz/zoneinfo/Australia/Lord_Howe b/lib/pytz/zoneinfo/Australia/Lord_Howe deleted file mode 100644 index 8c6c7dd0b7c464bccd98e87483967fa55c833cf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1880 zcmdVaTWpMJ9LMo@l#9AphtO1usza))X5UwlDQ(rEsBO!(gBEovHRJfGL;ubVp$^Ti zkkYttN~E%hIEE5-iAW<Nq7j=#P%fl#;aS<lBN5~IzHzr#dm}NEna^~RxoGnL{=&1O zRl}VBJUZELc=1?kUwrOWYu|c&u6mge$}<UX_C^v@D<rXHc_b+?Mv|I}Bgt*4(!0)$ z^tlx$eK-87QV#!a`jsn{TH7N1$JOfr(br~R|CM@B!5uT`-BX>G-el4q+|cRqdrbP- zWBSMTWoAg@b(PV&TZYO}6}+%iek!X~?twz_Dhk!G>Y!xi1l918t}>!eS2famFC#y_ zSE1w|Oz7DomDTi0MqR(GMn7pa*{51{&b3P>cjqNN=ExzFxA>4AyW^sa%NISqvOy*U z8&v-I8kyKBTTdELE|WeS)RW)rGX;OG(^Fbjn4hoA)P+q`P0`^jU0mmxsWr)ZnktrG z=7&_tG*?RVl2q93F4I#!sTp1Vl^L<;YUaDgQugAeDu2){v;I7(W;Z`Fb58%ED;h7G zxx0_+dD3X+N4Dz)WgE@H$%}MlXpUJlc%ojMm}{!KRjMU{*|OxHd=+WSk?6xTwe(hs zEIS{smY)d7iv4fZ%G%emO5Il~8Z)cQ+jVt8n^}`}Pp?hCZPxWVqu0luGV4G7rfb^k zjQqV?Z@3#Z8?Tn?O&4~_=95ZoIS`Sp+e_58>M61<7K;VqI(&V0jQd8<j*inX(4LV~ zf84R3Pc#SO9p~U_r;FnpITz?zSQK`~=9h+@A)c4%+IQD~_xQ!vNBDmnto<KjYZ7Xn z@ly^LIbL6Tz{nB%+CxT;+1DO4a@5FSBgc&#ICA93p(DqR96WOL$l)W$?`s1<BJi~# zATjvbAdo1KFpxNqK#)j~P>@)VV325#aFBSAfRKoMZAeH=zBVW%DkLl<E+jA{G9)x4 zHY7MCIwU+KJ|sXSLL@{aMqe8w5+xEQ5+@QU5-AcY5-Soc5-k!g5-$=k5-}1o60@%j z8i^VS8;KhU9EltW9f=(Y9*G_aABi6s0AvJ^Awb3e83be$eC;qG<A4kVG7`v8AY*|H z1~MARa3JG>3<xqJ$dDjof(!~WD!z7Dka0l<1{oP-Xppf%1_v1(WO$JA@%>&0sB^YI dOSwZmcT}cp|M2WbFQdqFgI>_hC>jo(zW~-0_U8Zq diff --git a/lib/pytz/zoneinfo/Australia/Melbourne b/lib/pytz/zoneinfo/Australia/Melbourne deleted file mode 100644 index 3f2d3d7f176b9e461f9730117bbf4771528da823..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2214 zcmds%TTGU99LK+}!U#&qa>zjmwG<5DMa7_$JODz9p}ZVRB|sEA_yQ_eNk31ci^{Y% znK4D0(M5&Pn&w!<YGZ}S>Y@W_j&6)<vu-M8X8+H#F50^4uJw8L{6Ej0z1w^9d_RGT zy4nT$2chl_H@x6(o+q0-+Sk6;-rk$=(_r8o4GxqB&z~u=p{~r}g^orIx5fo01~V;W z(sYFu##!hu85Wj$-NHVPweZNx7XI$<7IC%TZaxvCTfY28lUlB6^4Sj*x$%;2ZR^yO ztUle=d_cFypVZW{T21|9zoIho6m{i<MbE6U=z+Z!>&vy+u4;=LO|<E)IX0s|!s2(g z+Z{)Hlu+JcGn-!4tQD_lcHIxUbJiZ+l|Q686E<mXQn%&~pI1`&VI_6HrFmnGy1VTY zOaAPT&2N6gQhMqwbw`%`M~W<MLxnA9NVkPcm)N59(=2^Pye&=}x5d|G8F5j{xcIGQ zc8%G+y&u_qBO_XJYE(<l^(yP&hr0jRaV^_?O3U}3vlTh5T3Ow0*(tAD&dL_c4PC8O zvny@Yubs-f@{;nuIG_gxc53yRTCM3SRl(6bt!>TJgH7{P7%aARmASUQ@E$8#mS_Qg zj1?zDSV`nHE4lHbm0rDMWtT5l`EZ|A^qsbc&R*2RuMcTM+xy!1Y_~Qwcc^mfVLei| zPgQx1s?MlUO=7V&N9U?`O1*9IRoRxGi)`y?w$*)_Zrl3j*rRVxv&T-}WRJf%Zco_v zwmtZ@1-FH%zWg^e<d3N_b3{9m2DLM$SG&UB)UNU4Y8pGF=C4e<Kd#r4?^Wrkp6Bi9 z&KBEqWV^NOtF&ht*4YH#U)RKt|G*`+e&;@99Xss{k)G>N_}`z`6a=(9C!3=vz|VX> zP6htOPHs3FqYg%6_Tt%X2D2N_ZabL$U^ax=5oSyLFMGml3fPrrw=K-RJiCoycIMe_ z4YN1Q=78M++XMCoY!KKXuti{xz$SrRdUo3c_UYMe6th#$ZmXERdUl(|>=v_K%ziN& z#_Sl_GG@=frh#1p+XnUxY#i9RXSa1=@1EV}f!zb!$Lt@|089rkEdY9eX#%DTm^NVg zfN2D#6P{fwFum~Xnt|yCrX84m01W{;0<;9^3D6XvE10$beZe#a(-}-_fZlj^%>lXt zv<K)9&>)~gK#PDL0ZjtBglQ9|PnbqwI)!N!rdOU_voPJlv<uTOOv5l816l_33}_nA zHK1)k-+;yeoda42^bTkqrhA@U`!M~(G!WB4ObanR#557pMNAtpeFPc_bP{MK&`Y41 zKsP<Rb^`qb8VYn2XerQBOj9vk#k3XwU-}wBZ|A4^X>W2tnm;8i#h+ZT2&FEJ_!Glc B{)PYm diff --git a/lib/pytz/zoneinfo/Australia/NSW b/lib/pytz/zoneinfo/Australia/NSW deleted file mode 100644 index 4ed4467ff0f7a47e2951d3674fcc542fa3f2129e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2214 zcmds%TTGU99LK+J!U$5)avYUVGr^F9ib0`y0E84nc{!FwfGBqG1yZpRf2obyJfvO7 zj2Y65E-H-HG{+*A8!H4>7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H<SHM_>5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8<u81!2Tq>SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^<e)_tvy$(b)BUuIFYCIEtz^~_W~6Lifuz>u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiE<ccH`M?2eTi{hA=zAY>EG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsce<vzXmtwu{*>X2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v<cHEOrtQJ!n6w0E6=W3m~LU(h3OZjVVI5qEdzQ6G!5t) z&^DlNK;wYU0j&di2Q&}UJ<qOvnEqiJi0L4vg_s^<nuzHlrj3|B0*wSZ3A7UECD2Tu zo1R@efqnuF1v(0}6zD0YshF-}+KT@#eGQ|x3)50*Z&E>8YI0g~YEr=xl(IPNPxDR! Aa{vGU diff --git a/lib/pytz/zoneinfo/Australia/North b/lib/pytz/zoneinfo/Australia/North deleted file mode 100644 index cf42d1d878b364a3d720997b1511ae71da458b06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318 zcmWHE%1kq2zyQoZ5fBCeP9O%c`5J)49KW?o=Il}baXMrd$LZs76=$}`cAQ=AP;qWS z703B@r3xlSC}d!$S^-qgFnI+d3j;&z3<gdg-w*~zXBQ9=90HO8f)MQ119dQfFbHq~ zF^J9g9|(%YCPjegY9ZzV5Djt;m<BotM1!0Krh!fa(IDr6Xpj>@G{~7C8st<)W+o_v mxwUQr$R?nRfnEl>m<8fupku+#1~~=<xIpgLwX`xe-~s?}-C{lf diff --git a/lib/pytz/zoneinfo/Australia/Perth b/lib/pytz/zoneinfo/Australia/Perth deleted file mode 100644 index d38b67e2f953dcdfe942ab3a5123f63d24313515..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 470 zcmWHE%1kq2zyPd35fBCeVIT&v`5J)49KS<*=IpT*I303c;q>w131_w!EjYV8bOI;; z?**LOBo;_9CM}R!UcW$7HD-b4`cn$7h5HrUSko2Um1`EbziVFL$sD)9^IYlzCPpx1 zW<fv(hMYM-8yM;)FtRW(6f`h!BC~ybLl_*xT|h)|2uK+tBZy=OA;AOxfuLlW*b@*9 z@(7p)dIm&;JOrkJo&wPzkAY~A=Ro#=JP4+No&?b#kAi8SXF)W`!(bZdX%G$aIEV&$ c9z=rz089e|0YrlW0!&jgFt|X0qiewh0Jz3>O#lD@ diff --git a/lib/pytz/zoneinfo/Australia/Queensland b/lib/pytz/zoneinfo/Australia/Queensland deleted file mode 100644 index 26ffd9acb76d06cf1d6569e7bcf545da8c61a6ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 443 zcmWHE%1kq2zyPd35fBCeK_CXP`5J)49KU6A=Il}Ua5`i&!|CJU1!uO0HJn{;S#WMa zF~j+G=>p~g%LW!+83t9)vka>5Uoog_u4hm`e}+Mmb0UN0`gIIUj8MqT0)Y$+{S`oS z8D?!@WMN?FS-`-F%=YmOVQ_SH0TIC=AZ3h<Ad(@31ZV#Tf}*$cIY2bX`CuC81rQDL z2ABqV1w@0q1EN7*0?{CEfoPD|K=y*X2ckh<1ObpY!8FjTAOP|%m<D<o1VG*f)70}i L7bqxn4Gp*eQCf5O diff --git a/lib/pytz/zoneinfo/Australia/South b/lib/pytz/zoneinfo/Australia/South deleted file mode 100644 index 190b0e33fabb8dd6c01f6d939df0f09e288c562a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2233 zcmd7S|4&tQ9LMo<zs37ff*50@L43)O;>!ihfgxT+Qj#l#Hv<CFR1k=|q2U!x$zr^V z&B~hIIk!q9u+<N_2I|}zb4f+TH0|5k8f6(<n^{}mwxZATL|d)?0MFSukF$I4?sm8P zdIsxSn)0px93Jxu7snoR@f_1_ZU<Mr_R_(V8NZALPsrG4WB9${27Q0PAO7HlE*bAl z3%f#ob<ao_PgR<Fep;+iKDS1Fl&ap?Z`3>F(&$TX>($Sv$TgpQEi-mGCFa<u#5P`# znUNzBS8`frh5O{%<UxtAX_xpvo|J^bN=dl*vL>dsY2v8rY)3$6547r>iCn$Dw?dOf z;xu{NVZCAhaY?D|(bVqqnzr<iq__MaH>N!<bIZ@kP0l9C$Q+W4@ed`_8<EVh4SMrr zm)sILqgiM7X?EW{%{dy@+%09|+gqo34eK<&qgV?TmFoQJWW6<Mt}e*8biwaYS~w?3 z3O}FJqTi-u;mI+*?Xw9f?*CR6oj4^WJ@3lm1O4LP_^K><Y($rq?UB;;hjm%bD_U0C zqqlpiWqEqDF8}$6thjhU%0KRtJ4ScN%HejY7-*Eh{z_TZ>zB%{c~TXs*E`n+w7RN9 zSNn4{=u6SNQsQ*YOsB4y`cdz`bU|yrIIp$ir?u|QVZG<rS5iNCP8uR_Nuv(Qz2N~_ zyD1{;YMzk$R&>ew!Zz73uU<AL2Bax2tj&%VZN6Nmn<iFj%b8+r9m&?V*OT@B7h|>k z8A~7NzN`<1zSK}_j6763B^~A8%jTj9*^+ry9=`6Bbb8;A&R_dw>*PKOf7&bC-U&<B z@fO*B^f}#es7D{!+o?NuH|wsB8g)9RPnY`&I4$eZ@5~?BdUnv^wyd6r<^AV#MIdM` zDP6{Gb&!ua9Newo|L^1~^A;D!Q|971X389!6Z+zD`z%)o*^RB)4zeFxvms<h$d-^j zA)DgM*%h)aWM8&sW5~{I&DM~;*_zEEyF<2z><`%>vO{Ex$R3eRBD+MkiR{zXY!un4 zt=THFS6j1LWVg0vyU2c#4I?{7wv6l<*)+0iWZTHTk&PoeN4Ada-PUX#*}bjVKC*wL z0Z0ds79c%9nt*fxX#>&+q!CCbkX9hQur<v<x?yYDf%F4u2+|RxB}h+@rXXEG+Jf{2 zX$;aCq%}xykmexWu{G^M`hzqG=@8N)q(?}TkS-x@Li&U>3h5NmDx_CPvyg7tnsy=m zLK=p23~3qCGo)!q*O0a$eM1_DbPj19(mSMiNcWKTA^o#84MaMKv=Heb(nO?-NE?wp zB8@~kiL?^wCDKf!n@Bs6e%hLbA{|9qiu4p|D$-S?tw>*y#v+|{xSZ3c+j9k+e}{JG z)0PRX$*l>k$?d<M|H`c?t=(LH$aHV5XabyFw6MU(TUmiTUrt_*FDoz~atosW1Y3a$ ArvLx| diff --git a/lib/pytz/zoneinfo/Australia/Sydney b/lib/pytz/zoneinfo/Australia/Sydney deleted file mode 100644 index 4ed4467ff0f7a47e2951d3674fcc542fa3f2129e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2214 zcmds%TTGU99LK+J!U$5)avYUVGr^F9ib0`y0E84nc{!FwfGBqG1yZpRf2obyJfvO7 zj2Y65E-H-HG{+*A8!H4>7ad4*bkndl>ZW35_WwNVqOGg$TAydn|MTqGyS+Eh_v5dq zt6ijj5bWM?!x4A$Jk{LMw*HN_wx0N(2K?`7puaS5>0F5oc4h{K+8Z_85*wHl$h4p- zGZkDIYr(%{SV+nZ3;80(Lc_0G==;B0*m$4a_FA+ifAy`VG+)=$^B*aE^A$~N?a=h) zy}G^Wpzeq}tr=ytn(@a0MP%eDa`H<SHM_>5`k%KLU#`V;R$J_7g3WBnu~~g#7PqI( z?mW@0`0{3(Z9_I^)vKCY_k-@5vsd%-2Q_c}n&u~VY5wpfC59eTV%IxbFxIGhT0ga< z&yU)|rnfA)yWUdDj#%pPB1_v;VT&5lZSk@!Te30I(r3ll(zFR%dP9~G8=;KL-&tno znBCWN(e57^QP!DJExXX8<u81!2Tq>SiY;ff^1uaKmD8ft)oqrY{JQ0=ZnoUuwOTW` z($@Uip}g;3QT~?)^<e)_tvy$(b)BUuIFYCIEtz^~_W~6Lifuz>u5B#5*NRpom_Iez zisQqqB>cLS-2Bl>$FEq~)gdb%?zM{Ev-a@$%X;L^L2YXNP@7-u(xXl7s@!%=kCp9L zRbHd2Gip?mP^>LcxvHIBZ(Ds;w)N*C+cuhQb)Ti%_P)FA@pmKbiPN{*lP^!$Q+CaE z1irDr_7K&V|E7lgF*Rn6=;_1(?TqfxuF$u&YvPo4j~!Lh*QPz6)a#iKs`PC4VSBEl z+4dgaVa@w1ZC}F%yT$j{H7V#na7l+Rx(`_=&-#L-L+uLv`}4X2zgFgCa}@dcna{^5 z|G(JDO-EzIzAVgMJiE<ccH`M?2eTi{hA=zAY>EG6Pnb;syYlR|h1r*9w=vAlJiD!7 z_J-LUusdLT!2W;@0y_k@2<#EqB(O`*Zkxb9fsJBz>e+1-vsce<vzXmtwu{*>X2X~r z16#)I8MA3%*TA-ceS3Br2X^k+Z5`OVXSaD^_rUfs`^Pi@(*aBiFg?ID0n-Ic8!&yq zGy>BJ&#o1iUU+uRz;pxC4op9Qh5#J_S_1S0XbR93Ok04yU>bwz45l?eZ#=u^0NnxF z1M~-I5YQo@ML>^$CIMZ-v<cHEOrtQJ!n6w0E6=W3m~LU(h3OZjVVI5qEdzQ6G!5t) z&^DlNK;wYU0j&di2Q&}UJ<qOvnEqiJi0L4vg_s^<nuzHlrj3|B0*wSZ3A7UECD2Tu zo1R@efqnuF1v(0}6zD0YshF-}+KT@#eGQ|x3)50*Z&E>8YI0g~YEr=xl(IPNPxDR! Aa{vGU diff --git a/lib/pytz/zoneinfo/Australia/Tasmania b/lib/pytz/zoneinfo/Australia/Tasmania deleted file mode 100644 index 92d1215d60f929545276892330917df09eb8d1e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2326 zcmds%YfM*l9EU%DE+fcGmQ(Jbm0UtDUJz385+JA~#+?)ik&9*i5k#`2@64jLSY<9* zwnQ4EHx8tWwy}t8V~xPtx~Dm+t<lZen&lAbJZJT)SG{XJXXp2vot>BG^AwcTRWH&% z@OR#D!}HF~^9*y2V;?u$i~Ui*3>Unr;h|#h`=^U+q&v+!+R>nMtzq7YciMEduSJtX zrMlMCYg66JrJ}>;X^oK2svh}z)68#rxcu|N%>Qz#1td;dz$c*=IP;<fzJAq$E)ClC z$3ryjv#&J0WkNH~yseq#7j#2=r)H%O=*H$lx+(I6W|vfJ_O(YfCpBAheK#q1UX=xZ zldMormW6h2x3IAo3vbP|h`}I>tckXpkM=35w8iGxsLfw{Ot;kipj+o3&}}&*THv!y z(Xl;>{_PFL{4{2<AGIrP<gDHP+J42K?6QSNwkV;k)e;-im9(qQlDC%GqN2yOc;yOP zl2WUs5s{WMXT4G;Wy>zdEA{->mexIPcl5t&%Ln3YMc0^C9-n6EkDb-3mY;3)jxMb! z|G?H}w(8FGKFdgW*)k)KSeE|=tqVA%b-#Bi`@83q^XVb2AKI%8r>nKGyI8qLvvpT% znl|l=Q=YfbHdkcX-FeF_e|3x%B!yUERFD<ToUo#)AFcS(1uMBYYNh7}tZbmy?m2T_ zTV5H_*7mnl{zQ+qHFv0@_F3Iqa!{4o4ceYsrK*@h?Fi0N^{jfU@l;yPulZIxmSJ@t zF14M53+%pAbM5{U*V(RTuGj<iz3ujXY2KXysxSRRdveCrkoJWdV~4djq+d;eC)IT2 zCG8u3TFsxE9(=D}``@h8Lw#-baA%7hIK10h4p!JBdp4WL=da7x?>}@&Plh``yq@Zi zpY%enC-Co+Hyd*cv?ep3Bcp)NJRVLJ{EIzJIW#8dTg<L7+rsS2wX-qI&M;fU><zOy z%<eGT!|V^c8{~l<g0n@$9)V2)yToi0*r#h}qnMq#cD9Pyt7~VonB8Kw3+xxzFtB4_ z%fOz2O#{0IwhiptwX<<x=fKv1y}Ncc5A5Exvwh6|F%7_U0Mi0Y4}d0Mx`1f|&<CIq zKqr7!0KIVSGy~`crX4^(Fbx4Zf@ulR6HHTpu3*}N=?l;pOlN@BV0z=)X%414KzlI# z!88cdAxw*a9$}gUbP3ZYpih`a0iD9M3ezi0vw&{7cG?B>3uqY7F`#8Y&w!=@T?5(% z^bONEOy@AI!}Jc*JWTgoJMF{t57R(Q2Qe+g^blww&_$q)Kp%ld0-Xd}3G@<ZCeTfw zotS>Qb{dN5D5j;Do?@Dc=_;nJn7(2fi|H)TTA;T;bAj#x?FIVl+G#M*VW7o8kAWrw nUB<K-(`QVh@&B#ULBxAuauWHD&rMEBNKQzK&s~BN7YF?bI&LW~ diff --git a/lib/pytz/zoneinfo/Australia/Victoria b/lib/pytz/zoneinfo/Australia/Victoria deleted file mode 100644 index 3f2d3d7f176b9e461f9730117bbf4771528da823..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2214 zcmds%TTGU99LK+}!U#&qa>zjmwG<5DMa7_$JODz9p}ZVRB|sEA_yQ_eNk31ci^{Y% znK4D0(M5&Pn&w!<YGZ}S>Y@W_j&6)<vu-M8X8+H#F50^4uJw8L{6Ej0z1w^9d_RGT zy4nT$2chl_H@x6(o+q0-+Sk6;-rk$=(_r8o4GxqB&z~u=p{~r}g^orIx5fo01~V;W z(sYFu##!hu85Wj$-NHVPweZNx7XI$<7IC%TZaxvCTfY28lUlB6^4Sj*x$%;2ZR^yO ztUle=d_cFypVZW{T21|9zoIho6m{i<MbE6U=z+Z!>&vy+u4;=LO|<E)IX0s|!s2(g z+Z{)Hlu+JcGn-!4tQD_lcHIxUbJiZ+l|Q686E<mXQn%&~pI1`&VI_6HrFmnGy1VTY zOaAPT&2N6gQhMqwbw`%`M~W<MLxnA9NVkPcm)N59(=2^Pye&=}x5d|G8F5j{xcIGQ zc8%G+y&u_qBO_XJYE(<l^(yP&hr0jRaV^_?O3U}3vlTh5T3Ow0*(tAD&dL_c4PC8O zvny@Yubs-f@{;nuIG_gxc53yRTCM3SRl(6bt!>TJgH7{P7%aARmASUQ@E$8#mS_Qg zj1?zDSV`nHE4lHbm0rDMWtT5l`EZ|A^qsbc&R*2RuMcTM+xy!1Y_~Qwcc^mfVLei| zPgQx1s?MlUO=7V&N9U?`O1*9IRoRxGi)`y?w$*)_Zrl3j*rRVxv&T-}WRJf%Zco_v zwmtZ@1-FH%zWg^e<d3N_b3{9m2DLM$SG&UB)UNU4Y8pGF=C4e<Kd#r4?^Wrkp6Bi9 z&KBEqWV^NOtF&ht*4YH#U)RKt|G*`+e&;@99Xss{k)G>N_}`z`6a=(9C!3=vz|VX> zP6htOPHs3FqYg%6_Tt%X2D2N_ZabL$U^ax=5oSyLFMGml3fPrrw=K-RJiCoycIMe_ z4YN1Q=78M++XMCoY!KKXuti{xz$SrRdUo3c_UYMe6th#$ZmXERdUl(|>=v_K%ziN& z#_Sl_GG@=frh#1p+XnUxY#i9RXSa1=@1EV}f!zb!$Lt@|089rkEdY9eX#%DTm^NVg zfN2D#6P{fwFum~Xnt|yCrX84m01W{;0<;9^3D6XvE10$beZe#a(-}-_fZlj^%>lXt zv<K)9&>)~gK#PDL0ZjtBglQ9|PnbqwI)!N!rdOU_voPJlv<uTOOv5l816l_33}_nA zHK1)k-+;yeoda42^bTkqrhA@U`!M~(G!WB4ObanR#557pMNAtpeFPc_bP{MK&`Y41 zKsP<Rb^`qb8VYn2XerQBOj9vk#k3XwU-}wBZ|A4^X>W2tnm;8i#h+ZT2&FEJ_!Glc B{)PYm diff --git a/lib/pytz/zoneinfo/Australia/West b/lib/pytz/zoneinfo/Australia/West deleted file mode 100644 index d38b67e2f953dcdfe942ab3a5123f63d24313515..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 470 zcmWHE%1kq2zyPd35fBCeVIT&v`5J)49KS<*=IpT*I303c;q>w131_w!EjYV8bOI;; z?**LOBo;_9CM}R!UcW$7HD-b4`cn$7h5HrUSko2Um1`EbziVFL$sD)9^IYlzCPpx1 zW<fv(hMYM-8yM;)FtRW(6f`h!BC~ybLl_*xT|h)|2uK+tBZy=OA;AOxfuLlW*b@*9 z@(7p)dIm&;JOrkJo&wPzkAY~A=Ro#=JP4+No&?b#kAi8SXF)W`!(bZdX%G$aIEV&$ c9z=rz089e|0YrlW0!&jgFt|X0qiewh0Jz3>O#lD@ diff --git a/lib/pytz/zoneinfo/Australia/Yancowinna b/lib/pytz/zoneinfo/Australia/Yancowinna deleted file mode 100644 index 874c86505c896406516a16dd21b9ddd8d0ba2d95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2269 zcmbW%eN0t#9LMo<d5ABjAnJ@Xh=&+aJY2vW7$S<KBv%M;1_Y$}fPfTX;T26;%J`P8 z)JpFhTc$Cv)m&Df&aE+*R8&mUves6VJ@6M%TTffj@BJG#xA=>5ch2jKbI%wXpSQQ7 zc4LO~za!ZEgp=cWb8;WnYAzis-*~m-Y~mjS-ZL`LU+w#-w@N?m%JY44q(uhX<9vaQ zc^Wh=UV=;FH2Akg8sZMpkk4W@H2en*?FrN=Bk${tFUQE#E8oksT>%nys$asZugY}) zaf!%1FEf05WoC4@%qnY;S^qpEv$Kljrm6chGPYhL`&H+-JUXYVPUjA#=`HO=8r2t} z(c6yct%puaOnIBewqDk_B}XN`_7}M=ZjU4sUXp~7QAtedk;K7IB`MS|NhjWsJBC~2 zPX7f>zIaemd~a#$NuQ>by`=5~6`EePRx_G%G;?9T&MS@9yP^_weuks-{|?ctxlxj} z`NWm1uZA`IuQ6G0c0lj`a!7JIf0Bh~&Pi_DhqCB!r{ryTT^8@|(<KGZN&dQHx-|7Q zEy!=vdxA@4S$vHy`~A2q|M9RCe!f@k?cX6QdK;vut6DsVie+Vco)m9Qmy*Uxy>E?2 zOG|QfRbHBU-7$K9OoXnU9-ynoe$@v?u4>uWm$iKGyjHy1s}G*~PAa=ENtORSsn#BO z$k!!nHu+_3+0*jy@)lW_RWIvruapgu9@!Y-(;8Q;){Iu@rlA#Ddm%^b`ckz1&1l{H zO1L)ccl43gQGK-WTWzchlgG-(q^a;{*^)gZk0)J}CvH9`&7p5g^Piovb@-t8zG#<i zANZu@bggVZ`J(PP+NMt)Xx5!i)#$FKG7WHze*%Lh`EgD5#fjc?oIM@pzu9@A+ZE(E zZGI>8`uie}*IAsul*=kFUvs&*I^K!L5@H^4a_l!J_cO=Lv1PWd$L65UZ*cpJz(!<5 zRx>1IOja`}WK_tokZ~ac<CicpWN66PtY&b?=&WXV$oQ;gfXE1uAtGZ$28oOk874AL zWT41Mk)a}EwVJ^qqeX^`jMr)gjEvZ7hK!6E88k9#WZ1~Kk%1#4M~04!9T_|_daD^e zGJdNG01^SK2>}uVBnU_pkT4)|Kmvh80tp2Y3nUmwG>~v0@vxeJAQ7>ekXTJjkf0z@ zLBfK>1qloi86-4FY>?m}(Lut4#0Low5+Ni+Rudy6NJx~BFd=b50)<2h2^A76Bv?qa zkZ>XKLIQ?F3<;Ul#0&`<5;Y`jNZgRXA(2Buhr|vE9uhqyd`SF|03s1YLWsl=38K|R z5eXv_M<kF)B#}@eu|$H2L=y=o5>F(cNJNp4A~8jRYBf<s!ivNd2`my>B(z9uk>DcH zMZ)U}_^*G#H%#&qIN28`dhc3#pFQH3^zPg@p5CzQGCY27GhAQ_Y%^?w%wKh_#-?LD aNXcH1>E>awC*7Tzp6X8a%!9PdDgOd<<r<Ix diff --git a/lib/pytz/zoneinfo/Brazil/Acre b/lib/pytz/zoneinfo/Brazil/Acre deleted file mode 100644 index 16b7f923bdbb7d360a851cd7a02ea480e0d0e1bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmbW!JxIeq7=YnxY(b*vB7%roy9@pmL_{)nP`pYead0ZQxd`GC9dvQ&B#Mh`J2^?? zbX(CWb7`xBn?(@+N@_Xp4LS)0y>ProhH&2#cWpB_Eq<+pdBbKU&F0*DTs+K|`g5Yx zURVCJlvnk<Q<=MwmByf~4z+`-zwOjoQ?h;*I=8h;dFQS;uP4L7TVmIFS9|iKZ(cRC z8QCn~JE8p)goOdsYNX`n(Wx4_F3QoJ7d3XC4U#jDYJC4drg|P!X2X-KUR-%vYb(~_ z>9Q=X)s_%imsKpTwSOVHIg9V}(y}WiCTGn~n_+&I`-l0>6*LAEhp5Ja;z2Q?xI{HJ z6rZTZh~h-CqIglvC~g!xiXX+0;uzIfQaqy?Q;I9amRa$o7*m`n))a4wImMk~Pd)Rk R4*D_cpZ@lO`5%jE`wO!17YhIY diff --git a/lib/pytz/zoneinfo/Brazil/DeNoronha b/lib/pytz/zoneinfo/Brazil/DeNoronha deleted file mode 100644 index 95ff8a2573f4dbb03892a576c198573895a01985..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 728 zcmb8sJugF10EhA0BBX?~8q}dF76~=9jm8&1Lehw^2~A8!Hp~oXVqiF<_yXEaBoewc z!P4l9mxUTMp_(Gcd2V8|)V(?R-Q4BgJpcH@Y9i$Pxti=74%d)9Ja_CJuPi6K#<R)q zkV;hsWV(<Lxrmmz@{7FfzS6g;Ns<3-m4$<>DCT!%DIOGW`xX7(G9#*|bMm88sM@Sw z)-Fy&-FL3*N6+fBT$5irYpN%+CH)&2)vE`!96M8e+l#WlJ*@)sULCk!62b7J4qm53 zxSG_F<hqC!Z)EhzqsA{QI=1GhO=FDL)6nfT=I+pOjH$eOnvHoVIc-zZac6jB)SllK z@qcu&pEk5X`ncLgNGGHf(#zF0L%O-zc1S;@A<_|PiS$I8B3+TTNMBdm80qY4TO+-Z z=8Q{sq&?Cf*#OxA*#g<a)oz09;%c`+_CYp6c5<~_A$uX4;imnUhF0@<8`{kmvl~u? Fd_UIk9ytI2 diff --git a/lib/pytz/zoneinfo/Brazil/East b/lib/pytz/zoneinfo/Brazil/East deleted file mode 100644 index c417ba1da757e94b88919b05df8a21b35a5bf66d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2002 zcmc)Ke`wTo9LMp~a^x({Aj(30+p@7d&-dLeJ<XOn=C<5zEf1TkY&o|%e>5G-re&R0 z77QHrYp@_b_(xbmeuBa&C`+t4B9>ymhMvnozf4Loy-w@;JU{)}AN8N#@!jJXgE4qN zUy0i0#z^2_&o%BRygW(w^7;BV-)sG_XI%ME&!6+mzH^1TKh-9KvG;ZGOh!*k_|Bf# zw^UAEzFAXmrR3DfZXHTQ<wEUf{e9Fjx!AixFO8jK($zst_kALn++#MgD{U_SlA~8T zHk*n4+jUa=*JiT0$4Yd-Oxe<`cZ@k=@@j6hd8gOO)LG~4)Z=}UKV_We9~d?Tfpc2$ zdWYQo^9^>|>WB&Uex%bI4@<~&>5PT%h$c5`cw*c{9u^zTStW(BgchbhmYFhHXAZ11 zvo3yWV?8g)>{G)!r!#2oJ(9I^TgICE4)4&S%HPcWJO0vnp<`zLwzMv|<yR@LKBUE$ zj!H><u`L-oYf8s&)6%_fNLgm5E$@0-9vFPlE=s;;D)u+pc->Q`vb)SymA@(vzR;l6 zGTS^<U#g1(x0^(9fhK;6$&$P=x}^U$sTp}zYj$0crKbn9_LY=8lB%<huI!b^4lc0E z%67`*Z{KO_!UxS0FVENI<Bh3r%G3JH0cj{5r42_{n5Mga(54R-n&zB-ZSJm@Rp0N{ z)tgFW%|M@BTbnOW?s?a?%o#1~Iy>z831_6WWvy-f<8x`Ns<Lh0?vV|#HM-%@XQutu zN^Rf$qijs8Zra*up84h`eRlbJvw80YeQwcqdA{o_9XaCaer2=AD*}ORc6eySwb|^K zUj(kJh$jNmHR9f=J5Zj)f8X;~_iO&1v+m)hk-PS~+eYpixpCyqz3$eLd-uAVNA4cE zedPX;29OSr7LXp0CXg<?t_`FQuWJP91Zjo;lU|T!kZzE6kbaPckdC~rC8Q^>YYOSg z>)Jy4^18;5&XCrS-jL>y?vVD7{*VTd4v`j-9=)zfq)V@B6X_Fa6zLRc73mde7U>pg z7wH#i80i>k8R^;Unnt?zy0($Ny{>VjbEI{occgiwd!&7&e`EuY9YD4K*#l$~kX`V) zZ9w+H>ox+}39s7<WG|4-Kz0M!4rD)&4MBDU*%D+=kWE2$#p|{O*%z<d7-VO>ZflUe z@w&}Hb_dxWWPgwiLUst*B4m${O+t3b>$VBmC$HNmWT(7ttB}3&y3Im%3)wDYzmN?> zb`04vWX~`;?VldDB+uKcyKemT|FdwpbKTk%McwyEQ7|43hr%J9p}}}06y-zi-z1n& AE&u=k diff --git a/lib/pytz/zoneinfo/Brazil/West b/lib/pytz/zoneinfo/Brazil/West deleted file mode 100644 index b10241e68dd415354ef12cc18fb1e61df3558104..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 616 zcmb8ry-Pw-7=ZDseIWL=v@}#!tHL22A|g&7V+Fy5pg+I}np$d;THPb4p|PCKsNry+ zLE56ILDXE@gbMl)El%%q*FfsM9G=4+?&baYo7?GW@7Hw68x9kb!@d6~ms!paZM@{a z*G%DcQD4>$Re7eU%Z-Sxj6B;)VM|rpQ@VE2P><DfUH2E%+wp*X@7Ylwc2|E6#!Yj5 zRyRv`suiu<)<v&rH-`0BHfu(&PxaXTlNmo-vaw{<OdRa#$-V~@w^urmzEv6LTsYM6 z2|HIRdY*IlH=%C1TQ9P*>U#;5l#`ML82=yp$}b%|Q}zxjyHob37*HI7iUq|ZsF+Y( zC^i%yiV?+$Vny+ym{Ht<iXFu-s2EZlDVFSsC&iTFO0lK*Qj96i6l<E1Z*|lTh5zIo KofG@{RP+aMgaz9G diff --git a/lib/pytz/zoneinfo/CET b/lib/pytz/zoneinfo/CET deleted file mode 100644 index d585656f82482fabc07f21131c45bdab03465eb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2102 zcmdVae@xVM9LMoPVkR?sqrs7YSVU-@{D#yZwew&mP|9Vb5*mnFL~#*=(pbuzGv;13 zmUBp2F;>m4{=iy8YyBK8B&)H8<=k{-zvkS`Svppq=eIxmtN-eI``+XBz5C<-_`IIs zHCvhreE+zva!<IqcDb9+)B2|S*Y}N|IKO`)s{Vn)dhuQ9K=)paOl#HX*%fx|HNVDQ z4%!#b7uoUcS@vZl*}ke?qH6{kl<@2<yS6(dYb#M=W4305Q#7+`nO#?SSxK3{D0$|0 z^2Z*N|H6ApIrFBj|N5Y2ee|4Cj~unMLs3iLf52w<$TGG*V{@7#cEh>`yRkBCnWZ7S zDNtfrY1wx3^c2gE3o83!k>-A%rFrAYy5*Cpx^?Jx1qOc7{N5AF>Hb`~ZAX;X_=fW9 zCafSbWQAqJR<!B`D^BmXl7bE^ne4I!sl8foa)T|r(ym2EgLeD3wJIH~SK06)-LZ42 z7WbuTNmHhlb}zI$%i|PmOtocMXB7%gT6x@Os>nQL73Y4i<*^^w^05)S>&z>*BKo>j zj=ry|Jv~-^=q26V(qi}Y?9j^lc57AhM%CQbtZ-$8?)6t|bzq)WU&>Q$Qn}TBGeh@Z zoNH^|{!MGg<89r+M5`P6)gI`&VD-IYw!VJcHf;M@4=#Sk8tO*Xn6b|`mPOTc^<iyF zmp1*lSDPmz+A`9qhfap|@T+Up{AsH_va3ujgEjW(<_tZyv)Ec!mRh9AZ;$8Cwzkk8 z_C%u3+H;fCe*Uy|BwSL*@sF+Z+=QNdYtWt=8`jo+A8K2)Ur%=qsB2G`Vti9#{|hgl zFTpstX>(%Sx&8Z{C&5tI-8EtNRZI*&2>*-2|EIecfTtS)G6ZA{$RLnWAj3e$feZv0 z2{II9EXZJx(eVEm4l*8OK*)%YAt7Tz28E0Y85S}wWMH0dWXRBvu_1#)Mu!X!86Pr0 zWQ52Nkuf5JL`I1W6B#EmP-LW@Zm7suk-;LPMTU!v7a1@zVr0n3n2|vvqeg~}j2js^ zGICEhbY$$FZt%$Hk>MlbM*@IE00{vS10)DY6p%0=aX<orL;?u~5(`fk3?v$!E*wZa zkboc&K|+GW1PKZf6(lT3T#&#ZkwHR(#KzMF2Z@fS3l9<>BtS@nkPsm;LV|=u2?-Mt zCnQivq>xY{u|k4{M9b5K3yBvJFeG9~$dH&JK|`X3gbj%s5;!DsNa&E*A;CkU=jp<S v#Lv?O5Q!iXLL`Pr5RoV%VZ{Gs9OGRs=jYSQKviK*aZX;KDnAc%3*-L+v+4}i diff --git a/lib/pytz/zoneinfo/CST6CDT b/lib/pytz/zoneinfo/CST6CDT deleted file mode 100644 index 41c4136feb20c3748b8951372fb0e689ac028305..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2294 zcmdtiUrg0y9LMqJAjKa!NEU@iP+BtNfPkTBQWh|FC<OJ0AY`E!Vi{i(P}JygV^A07 zCTUm=rO4IFsj&=ek^Nok&kSkKW-+bhoEz~}UYO81J@4nXtL|Fgv)}J^cCOCN=N(wp z@Mw<u$A#Npc(}dx;d@-qA)h|*-k)-?BUbmmcuu~0Ca6!(ydtM-qs+Cd^=kZ*-%OnK ztG~WTH^G5)6?)xkRJT`oI>$`dmN7MHQ;dYyPS)3z|0>rn9@P<9-%7-;R2>!fwnQC% zRYjZUW%Bth9rOKB<2~7|V^8ccQ{LF4;`TS08=h@aQ(G&{jSUs*rm8#>@6S^=FGw`E zq$R3^8S`aY<b+BLn<k0BC+O)H#wBU!n!fd;A0_$N4?3myL%FSGin@JgugqBSo0_@d z19M02u)4G4fSDElj!K=q)1(H6bXxRUlQz<?XJ0Nd>4ST<Z+MMl^lZ_Y{e?28tx9Kg z&64c;7L~I(Qsx#{s=Jn5mfX~ZYF^IQGCv|o<xRh2@-7C|g7GiS!q3mEyU)F479E+; z`R_kx?%6$}?|n(k;>MHuzV=;mf7vU#psrC0=d|mh!lhF5$GfUHW1}e^-mm=LGUM;x zu1ZF;%>ysLpdUQ5L`rwG>Ltgsq^zb|m+zY@OBdzqWzAP)c~XiFtT-cq36HMG9gvD& zl2v8$cc$_)rK*C*&8ohiRP{)=scHXIJv6w@tgbtx9`30zkCc9_Ydc$IO-7$yTVE}8 z-VVL4IA7L{uGjUcDN_GUiEfDSNW)>D-f;0}d2F9wJ$@==HnyazCyrb&jTK%cyFW3T za>vw@jjx%_@k6SqtkX0FM|DeHpR|me(pwWcWb5ED-FkJsZ0qUL+s~HBj<#m~)PPUg zR<G1gcgKoHc~saW`}!|^LZLSgDisQyIO+*^?@0HKDOes*1%>umX`k|d-Gu$y_6hup z5rynABxFp;ppa1^!$QX8v;#v%h71iE8!|X_qvH$1<Lvl228fK1V~EHYkwGG(M26|K z<3tAPv?JvhDl%4Nu*hhU;UeSZ7%(zojv*ss<`^_GYK~ze<K`GRGIEZgJMGw!!6Tzb zhL4ON2>=oSju0R*;0OW|1&%Nvao`Aq(?$Xbh113Y2?i1kBpgUQkboc&K|+GWgd-?O zR5-$d#Dya;NMtxd<Fv8i2o4e*j_@GyK>~zC2ni7qBP2*jl#nnXaY6#+w2|TnmD9$G zBUnxwEsk&@@j?QIL<|WT5;G)dNYs$9A#p<j=d_VSLg%!x;|QM9Mvo(WNc=bgh(wSh zgh&iIf`~*B2_q6mB#=lXkx)8qERkS3Z8VW^I&D0WfFcp)2q_X%j-Vn@<p?VhSB}6U zk>v=j)5aDFuG2;r39r+}7YQ&DVI;&zjFBKCQO5sGn4@j6=h<TWO6O+gWM=tFv$Hd^ IGqa-q2E;CRmH+?% diff --git a/lib/pytz/zoneinfo/Canada/Atlantic b/lib/pytz/zoneinfo/Canada/Atlantic deleted file mode 100644 index 756099abe6cee44295a5566ad6cd0c352fb82e64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3424 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 VBH;<`BBCOk@Px?7h{y;h=x;6=LMH$K diff --git a/lib/pytz/zoneinfo/Canada/Central b/lib/pytz/zoneinfo/Canada/Central deleted file mode 100644 index 3718d47da0baeadef58f3f2719a8983a1a27bece..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2882 zcmeH|ZA_JA9EbnMHxNb5#0UvYD~2Khis1`osbi1`>Jd@M)G)*{9^X)W!GDwa@=49| zWfZBSmTQI1rKpWe&AF9xh?1lcqLNd|Cv^*zINjG>Yi_x<wm$YdyPxYh&xdo)mvdd- zsd=x($=~K@o^aS9bGYxJ=2GIhUte<cV0vlA4*lTZUFXR5;d(T!*!^a0tDf$A$2mQt zqg%apww#$b-90<$tkm>A>3$c#U(QLnTN|`ZYU?+;=gr5RKauTTsN14H?AqYeRTk=x z>*hQ4rCEA0&+9Z4CF-T*A<pG#;d&)9*lA1}AwP8=>|TusldHFSy4TFFxmMNeUT<uW z8#Tw=rsK!t=Z#;uxAV*NPV7wgZr2pO=U*WAnsep;)l6wQmn^@X7$&V1{l)ijsL0kp z@vLc;Hj8gc+j$|{Zbm0<pWdP!MmDKmOr84oKBXPI9o7KZqXF(t?Nq->JJ)_Gfrl4q zmjgSb>#nz@+qPBm$hw8ny>OO1nwKFxrX)#Fa)R_68Yz!OhDdOqG5WZFi-fd^(2!ff z+N+_XhF0CvC%(F_VS6rU_>Pm>yP&H)`Bu61ne>bFo%5|c6<;k+r+g;Q1br?M{WeHM zYn4U@%$LZzz1r`ZSNd1Jt5MaC4k%r$(R(N8z@jM{vuUu#<`ziYf}T1kDN_bdXx8|M zVKOA{dwtd~R1$jqDhZ9PGW33}3_D&g!_Oa*5#=qKc<6n3Zhf6TU$Rn0<{#D<ic9px zw4FLCXN8X1pI<+EV6l!FJ>D5}d%q+NnCm80Z<FM}G&gzgGD&HQbzgdKi;k@s=cFz# z)N%V_oV4sLP2b$z89yRXCoH<-ObiWI@1z>X+v3rT_zEZEN|<DZopCe27Maqz&z)M< zAX#-=-R$CH@^a-;cUsOz@=9s8J3aM?&RA3E%p6drujXbsIf1KnR#Kuft7*38Mua=L zCsQ=f&*S8M8l`g@FFCJoPL?-L`P{h${pHQ_i*9~Kpg8M~y7S_1%KZEf-338avM_Cp zyRfxM3lhqlg1S?>BzU#6q;iiIHqUmJmTuBz=Te;IMT>MrMU+!CZL+T1+Qn((v9Gr6 z{=E6)?Zd9^JN&n=fBeFS{fGyj`Fv$JM0~yjyFKlEzE8G`zrF8luiYMeJ~q`Wqeh!E z(VWTVq<hU@T*Tw?ScrK4g&urnIhS|81JQ(~Q3XU75M@BL0Z|7;9}tCD8jV0y0?`RX zDG;qd)B@29L@|~|GZ58S8r?vY1JMpdJrMms6a>)_L`4uCL6ij15=2c9JwX&@X*2~< zm8H=YL|G7RLDU7&7erwYjX_if(V3-D8boUlwL$a-t>U;rbIhoYp*x217}|rV528Pa z0wEfNs1Tw<h!QQ079nbc=#imFh$b1TWayHiOolcY>SXAXp-_fK87gJy6rxmyRxOQM zA$o-<7NS{*Y9YFXD3_sKh<X|NWhj`TVTOtsI<_=QhG^N+s2QSXhN2;whNv2%YlyNT z+J>l`p>K%785)PEoS}1w(ivK}G-_w)ouPP!<{7Go=$@f`i1r!khv*-Y07wQbO$s16 zfF!}vWPy<eOOppiA}mcN7^#5d0+I|!HX!MM<O7lrNJbzjf#d{|6ibs8NLnmSUKok7 zG?`(f29g^_av<4Zqz958MuH$2f}{wNBS?}US%Rd=(&Pz}C`*$mNUAJNt{};RWQ&n5 zNWK^egJg`6GDyxCNrPmKkv2<{H%Q_vP39n}voyJbBoC53Nctf8gCr1=LHK_P|4Lzi aspJq-%c#^r(Q(l+QK_-9(Xr7n0lx!f>#*4X diff --git a/lib/pytz/zoneinfo/Canada/Eastern b/lib/pytz/zoneinfo/Canada/Eastern deleted file mode 100644 index 6752c5b05285678b86aea170f0921fc5f5e57738..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3494 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 diff --git a/lib/pytz/zoneinfo/Canada/Mountain b/lib/pytz/zoneinfo/Canada/Mountain deleted file mode 100644 index 3fa0579891a9762b7c131ec5ece5d6d02495bfc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2388 zcmdtiZ%oxy9LMnkp}>tlrxHU!BP|I6<c}nf85Kl`HwZ3>Mi>RMixs4Hr-gV79MKa` zWy~@ORD#wDQ`cy0pgG+7qjT5gvJx#Ton@mxTP*C}&ig!K>rs!|`riHS>v!St=j~eM zUXw2VansCSc(~Wi!~2XE#!j5?8XVAX4h5_3oiFKb?>4pP#Y=i`+jOz7;S=4Pc~res zc2V|4^{W1ik7d8_Bk^fRnD);9y~$e>Ej};5AWz4AE&iN%MowO;6u!Z1>F<vfK{d1V zw34f8dhVnSP90Mrac6a?JggjIL_5a!sB6#n=&&;*BK&Zxj`*-gM84fE<!i0tx{l}N z^_%L%4enYwV`YJeD!gCL%uWzDCfp{Y=jE$ep$<7aFka36b%BogHdMuWL-d>@KdQO! zU)DGE99MCkIr8SM18QEmU(Rp%Ox%+Bjl6Z)dtyP<Q5m18MZE8vPH?Of31dfe;$@e( zeR!`<I@P3-ySC|+gQaR=OTA8gWsyp&Z<FckXR3^XHF8nugvyM6K;Du5rCJ=ED6?Yz z5Lp+)WcK74k#p>4dFNL{V#$7ozH4Z=Si1cuefO>{BDe8`zNc-My0>`0zOQz(%3Jud z&d*z|@_!qZ1<B2#;8dS146hc22Rr1lE4iZRjb6R{bd_>8x9bN#SgMMv+`6PQPCc}w zNSAs7RatDZc9nmpTvsD?MdmS8@qLo4oO?l3jz-9pzEQDi-?)5utWQ+6dF3O+9iqDS zkX+rhRy^uFscYKX)nmyA^yBqzRU5uT*A*10x+@-CAD^u1k5_7UaHMj-o1+_k_(iSl zTp^!086lqZWXq=p#zkXAjBMKO6;EgWCD%0`66>SR$qmJwVuNo|d$JBF&)8YLF?xsE zI6R^^O?cF^T|N4_FDg}YORL^In4?;%>-3hLu_`cN%IBJ(DL<zE<G*<K`(N!A!tZ~l zJ0QsK->pT6eGjwWa=FtboO$LcGtUb1l(@`ngb1)-u79yKzd6>1EDl*6vOKF<AF@DX zg~$?-H6n{dR*5XrYSxJ?6j`a&EEQQRvRGub$a2xHmlv~Ojuj(IM%Iih8d){6Y-HV5 zvv6eP$kI91jx3&I^~mx$){hi`qXI|?IBI|tfujmY8IU?4g|M1RAf-TRffNI&22u{B z9!NouiXbK7s0mUOj;bJKv6{Ldg|V8-Af@4`4N@GWI!Jkt`XB{DDuk2>sS#2nq)JGc zkUAlSvYJXErQ)a+QY?;YA?4zz7g8{eiXkOKYK9aIsTxu?tEn4OIIF1~QaY=t9a21w z>LKMr>W35%sUT89q=rZlkt!l(w3<31g|wPVBBivNS|Y{ds3uZQj(Q>m<)|o9QjVG; zMMbKLlohEfQdq00EK*vlsV!1mtEnzhUZlQAfjKISl$fK&NRc_JjFg$9&PbuHrqW2M zt)|vUv8|@sNV$=EBLzn)j+7j!IsSi(?l7TWY=WQU%t%R3NlkL5rKO~$q&ofvy_=8y diff --git a/lib/pytz/zoneinfo/Canada/Newfoundland b/lib/pytz/zoneinfo/Canada/Newfoundland deleted file mode 100644 index 65a5b0c720dad151ffdcba3dbe91c8bd638845c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3655 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 AC;$Ke diff --git a/lib/pytz/zoneinfo/Canada/Pacific b/lib/pytz/zoneinfo/Canada/Pacific deleted file mode 100644 index 0f9f832821b6cff451b5ecccd0b6eac8e53a9190..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2892 zcmd_rZ%oxy9LMns{y|XkWTHf9Cp8gN1QbQlOw(O45tS>68U9INn1+g7v=nvG%KmZ4 z{L?D>Mn1?@qGL9j$<QrVD=NWE)NW*{3?&sU5$WE}`}ed*J!~Cz&g(ax-}lF}uplqV z^^eod{)fw{v6t_@TkKPD=!m>`IKq6rElTPK-&|e4bf{|Z_SPpeH>n@yU)QH}i~2FS zL7#SgqZ%U)>c*yh>Wu${oUJwLoUdAb+WWEb)$EX;x4mwfDvITog4O1HNw)l&HqZQ) zlPVWt$C!)m1^QB-xvDv4f^Kdbty)5&bxVDOx_r^EuN=6gT8}sB-^&}-)v8Xpw&t+9 zUgndw%}33R!dkhx_yhAtMy32Y`2}-pRH?KNt5mmp=SfG8Qq|G^yuQ<%r#esP>c766 zq5Oy3I`Cnfa_x@QK`-@E!RveKE^CIFu1jO2+uShIeM+c=BwR5)^koTE-<mL2orGQ6 zZ+bRWNw3B_6<%8|cYRi+dhgw*)m!E2?rlc**-)hJDOjufX3tj<X&&8g{5aJ=cC3yZ zlxgk_jnq-W$tJ38jNEs=zlpBzCj*Y&HUkf|%l+@4HQGB|Kd|+P8I%>S2d_Azh9qCr z56=8t4UIUVW8x}QjK5W4!?vhc-**z%vP=!HIUpk%O3cWL?Gj(T#EdF=MiRD9HHrCe z=%k_{X0&^q9+TPKB*$dwu}RHlTu6#eiSDLSE=B3_cP^<3$2)cE*{{^Z{gE>1@JH&Q zvJRR2_G{|l!gDgEbg!A3Q6rBmf5l82B{F^5Dl`2?gLaR6S-Bey>a_5cDy@2#p4mEE zJ^D_y%sREgq;K3Ivp=0>G8PrfoSpGz?!;`F=T#;%I#oRL+l;4kfMg|~G+7rW=mi6> zs|8;~>ui66TDZrrANL(pi%OgH6E(Y3&hle=am5C;B;6-VU)*7qjjWX?^NY>$@Jh*b zXPeyCQpt}=HTiXUQV=r06nrv6R$L62r*`J*mET9JRbID#y2`H#vtsq?vL>}=Y)`$m z@R%x!Xw~a7_NaA%Q1PbJ8n5rNtdFcT>uc&{Lwl)twxUX&JDq1XmXyn;Lo-ZCPLXWh z9cO}rg1dCJ&wukT5P0=Xmn#r>*93J91j@F!dN|*`oL9|C_qgUvvp3V;$LyWsvA=Sc zE68~~|Dp~7dvYduuOO8`N`ce@DTbr122u{B9!NouiXbIHYJwES(N+a13sM)PFi2&P z(jc`#ii1=KDGyQ~q(Df8kP;y^LW<;QtAv!v(bfqm6jCXqR7kCmVj<N+%7xSmDHu{Q zq-03VkfI?~L&}EK&CwPPsT@)|q;}{Oj}NNHvE>8mhZGR0AW}l4hDZ^SDk5b>>WCE5 z(N+>EB~nYIm`F8|aw7Fa3W`(|DJfD@q^L+$k+LFnMGA{l*3p(0sV!1mq`F9Xk@_M9 zMk<Vy7^yK*WTeVSnUOj>+Cn3hcC@8NYK;^dsWwt>q~1uuk%}WFM{14~9jQ7}cBJk| z;gQNa+R`JnM~aVBA1Oale`EoW6+o5%Sp#GdkX1mI0a*t}yAa4qINGH^*22*)2C^E+ zav<x0EC{k9$dVvyf-DNMD#)@R>*8n^23Z+LyEMq!INHTQRtH%gWPOkYLRJV_B4mw_ zMM72yStewikcC24%F!+rvR00Ev5?hrw9AF87qVc;iXlsetQoRs$f_aBhO8U1aLCFz z+NDF*&e1L&vU-ko`H=NP77$rMWC@WqL>3YM->VpA$1=r^=7{vs@k#LsBhnKS;}hc( G!u|%QT)5Hz diff --git a/lib/pytz/zoneinfo/Canada/Saskatchewan b/lib/pytz/zoneinfo/Canada/Saskatchewan deleted file mode 100644 index 20c9c84df491e4072ec4c5d2c931a7433d9fd394..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 980 zcmc)I%S%*Y9Eb7Wq@_$lyqJMVi$W|41r?0;Dt1v+oCsXRm>A5;gMUCATqHt^7hF5K z5s4sImBW-o-ctz1OIfDJyk9wlE5VNMb9AR0SH8o0K8Ime^L)c~(HBK>;#@M{a5=^1 z@}BkTp#6HRuUB^_((Lz*Rqls^2hPW`Lbp%db>g{K-MAZa5?2bW#P?n2({6_KIet0v zwK?4#sNZr1Yc}1X`|Xk8!U=ceX0J1vy<v?7Cn96}<JPy$caib7kWS|8S;_h=nQVBi zrfQpH`pJ@-xiKd{iigxs>6h7SJ!;nJl3)J^^zSb%GB@9?|GbIW^Zl)Qq0P3PSX3`Y zpWA<5KGsVQOYP-n`FiEfEqk^6ky^_rk@eeoYW-iXY^}O#duCF0hLh?-;M7k_>ZxBJ z{rIBibu5c`-rKG~s(IIv?!Slpr{XD@6_sJBEH$^*+^6PNho!{4a{|ZD@EQJm&m00E z5s(l_3?v8=1qp-1@il>vNWLZ%5(^22L_@+M@sNN>L?k2<6A9{Tq9S3DxJY1M6B!AO z#72T6(UI^-d}IK=W(3F(kTD>GKt_QK0~rT05M(6CP>``8gF!}v3<ntxG9X_wB4kL& OnDE>O6*LRG7d!-P)%6Mh diff --git a/lib/pytz/zoneinfo/Canada/Yukon b/lib/pytz/zoneinfo/Canada/Yukon deleted file mode 100644 index fb3cd71a69e3038f0d77e8ddd3290a08fb960d9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2084 zcmd_qUue~39LMpq`DeNuJfPFL&ap>l&du%YkJ~&t`)8BmbjMA1JbBFg*Z#1jZMk*S z(m7!i8nJJR8mPy|;f+K%8H!*H6AD5k+ae-kNKi&GA`4n6`}Mq^F1zZgi++da_j>lb z+Rf)3-PF=l>ifqt#eU)N9JGh~+;00yUcK3W_F9fHx2N@=>l^C6d3a&}P|k1dL)**r z??nk2TiB-_1h%T_ExYxM_y(0(9n~|JE>W}cDs<ModYL^nOJ}E+OZNRT^XRv;#6L9C z1U~vh=3E*xkDa?CI<ZL4J@lc>i)HAX*00t4@}KqNt3OZ+GC$Y3`AL;KK5FvPyH(!E zXD0v09ct0wB~vimC56djCOEK7in?Ak#m81iN%K)%+A&`ihdXsy{bVW6jp>TgA7n{d zNQeBBROnu|Ui#pkTK369U3uqIwY)#eJaO$k^<?j#=BZO}tEbz(G0*f|kQKFq=Glg~ zq^ii6l~ucB<*&m!TzEu<hcD~ujGd}_;G|wXx>l_@Jz#3SY?a9V<7VxZ8mZm9$JCt& z%DUxovp%7u-d|~=8}3W=VZg-7zmV8>%k;)Mzo?BL`*nPLSZz8#uAd*dtTy-D)h`U5 zR9hNu=&i|pYFlK)Y=85(>?pi$UToeY4H<o=F&vl1(H_&BTPe-AnoLVtKw92kWm>=Y z<fRjhdgpDw+LZ|FmoJX1wpgBS@4c&Dsdz;1Zo8p6GDmf1?RnMdO*M(obxDl;WcFtD z$=<<RX5Uzkyq3IZUjM2|_IJHw4qRO&U0XWL!FTf|HO2lWOiZ1WIyvn>_<7!;2A|LK zdb?95+Izciin~v9Z{>MsBxMG7-)wge)I_4bc$Gc%_B>}#9e>*ob@oG@l_$l$|2FzB zcHr6Pz#B(SBYQwLf$Rd=2C@%iBTl;$WGl#Ckj?P#up4AM$bOIwAv;30gzO2~l+*4C z*%q=dWMjzAkgXwmLpF!(4%r^EKV*Z*4v{T7?H-X$I_)lzZ6f<bHj3;N*($PEWV6U_ zk?kV;MK+A=7}+wiXJpe(yK7|I$i9({BRfa7j_e)TJhFRa`^f&01|S_kTHv%jK$_sR zT|nC4w0%Gtfph|C1=0(o8Avyfb|C#g8iI5LX^GSJ1Zj%Xb_HpR)Aj{v4AL2-HArue z<{;fc+Jp25X%NyOq(w-NkR~~8myk9&ZJ&@vIc=wqRw2DYnuT-=X&2Hjq+v+Mkd`4m zLz?EaT|?UDw0%Pw=d_(eT8H!wX&%x&q<u*L@c+=jbQ{NzjblM%aj-O4To5TK36=zl G)Bgheu`o{n diff --git a/lib/pytz/zoneinfo/Chile/Continental b/lib/pytz/zoneinfo/Chile/Continental deleted file mode 100644 index 816a0428188d99f437004312ee73c3860ee0f54f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2529 zcmd_rZA{fw0LStF<?<8{2|^~Qg%n1iaDc0T0Uk+IQt+1eL{~C(AZmcbLn(!^l~`LO zqqU~Z>``6Q16DwjdDa7DQQf;@WRtZuwbh!8o-9po=lfs0>Q(RB`rrM}=kC6|+wXfy z%c?3et$#eB<`-U`m(0ue*xlx67fTlJ_ndbhZ2orOkhMdr@}_~Vraalb(DI&GTdUtn zbh+Qoc~!pStn~K8kLaFr``x`)J-Tl&!96s+S`Kah&ilpLcKzkF(`q<#l^$;FkXL=V z@><7b<sbS~TXkoZqrt5MWRY^N%FuxcDJm#`g$%YLRY>?r8S>4T3cVI1uRG;aVWYpw z@WU5HME{74Z1aliU+j~UwsecACx><P(oQkCqDRLhH;UNBFX$VhJJgg}yY!UtdNnn8 zy`Jh@t>P{$(D7Z_YTD->IelNensGc|-c<Xiy16|;CbT%k%$fk1m~%zkQgTk-8aFDE zvMrtT$0;%Er{DE$XAX(vOCRXl-|JU*jJ&Q>UOuR1_q6Fbt&gkJ=eO##jhmF%y+@`O z6sn93RWdVgM7ZQKIX8Tfm>1bAvx0|Jwlzs+e-bKkzE9EfkNzO;JRPZXpBfT*hsSjO z)?;Epn@`J?AFG91PUwQnH`QHBpVNzCo>7JA-LmlKTD2s)Q!W_`5KG4!WzoqRakp=+ zT-Ix;<z4e-@j!?uakuIf2aYLs!5Mu|^Rw#Sl;7k^Pn{|a|4OdOx**mZ|52~I@guRW z{WD#5>8QA`MrqHHO0~YeS(ooEQumjZ>kU;Y>H(M38;c^<rr1QiIeAQN{v}XXOpXv0 zqcdgY_(id$-zlqnUa|GSdAY5tTWoJ0ln?If6xE(1vSz1JwYg8r9c3M=F6j;Z(ENH; zAGB9D#IIHj=eOy`%h{^w!(#pL*YWC+*V6RP_p`*VC!%%pD{<n{>Qwny>lM*b5+z&w ze!nBY;dBHBOnjUH&LHy!hx|uA!G3@LyOw32fqs9VvO@j-L2X5FI?Orjbwo{^{Jy-n z)LLoYIbyDPUFMxwqQhaPW*&#5^k3}L{6+q%Ju?Q7og!OB_KIv4*)6hNWWUIUksTvj zM)r(s+ScqE**3CoWaGAG=g8KPy(62qHM>W)kM{ogLIZeA2Y?nJJwTd(bOC7t(g&mw zwx$zEE0A6w%|N<=v;*k}(h#I0NK25OAWcEKg0uzc3(^={(;1{Swx%~obCB*J?Lqp3 zGzjSs(jufsNRyB*A#FnXgfz<5bP8z|(krA{NVkx7A^k!chI9;R8PYSPX-L<Qwjq5( z8i#ZaX&ur#q<OZcdr13`{vi!SI*7Co=^@fYq>D%!kv<}gL^_GI($@46X{N2|Celu% zpGZTIjv_5ZdWtj^=_=Azq_0S0k<KEmMS5#%nu~PT*0dMtFVbM7!$^yf9wSXgx{S0L z=`+%3q|>&h)kv>xO|y}1Bke}|jWitTIMQ;Y=Sb6$t|M(n`i?Xn={(YUr1!R_`AGMX z_9OjA?f~Q-K<)zMK0xjS<X%AT23wPk|Kt4-mTPW~e3v;h^Jf?5q^76NOUrQ0E_VI> GE$lDZUi%6F diff --git a/lib/pytz/zoneinfo/Chile/EasterIsland b/lib/pytz/zoneinfo/Chile/EasterIsland deleted file mode 100644 index cae3744096402e8a452336544edf96ca9ae5ad8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2233 zcmdtiT};(=9LMqh;W^+W2$|3mS{MbwkLQQ*_=_YVJxF=dBs5cpfF(pgrWD3jVzpF8 zYi_!%R{7BM3tcFi%-?#l2P@BoBU`MSbgMNPJy}}*`@R3wRqLXgF8ZJI`@jA>7w6*a zeBPmkmZn1IZ&$4Sgv0f$Jv^swwzrYvy8pLurM@(9LEIA`8>iz7@paXk2wf|YcNdtb zjBJSxEYdNKUt$xE>ew$QB<@m*zU)|7;>Ul~3470}#L+SB??0(7-#wzIG!Lt!r%svV znn5+S>99%3>Q<?@?=)8=56HAxo6NMyPMIFF+)NKIk+idOP5MxoT=i+AzIsQxTyrR( zuWkQTuG^NOGkPP{jJ60pv;3mEzV0i1L)y5?EOSieFUQoZ?|wEno_<MXoqxyN^wy}{ zJocK&e)&boIoxk%_dOxGFSMGxRjWm9-lFrXs-<9Mi!Piqri%0eU7RpamH3b7(wI|H z=1kFLAH}Kiud_|X{%_PRANWn>(<juNy%Q$TdQi>n4;#JsL%Fs2O;c6)hTK;3yqTBs zoK)uz>+0{@Wq$IYo<9+xY9_mN?a?-MNBADS;D{p&hbnaNy;xOO-)9!>Iw<v3r_G%` z+vTq8pY-C!4hbcErk9qURZ9<jYnEO4zFM~J6Vq^hzq+?gOyj<_vb?j$tk_yB_k~uN zl`YwFe~~t;YW=c0b*5R9H6d$$h%!x66IIjr483;poN6A8)GgtYs&&^Hy>4h&J<xMp zKe%I1t#90?+ct`{S3aX3Y8a4?%-7As6`j%<z14K3FOjY@>rD5BGI`|PpxN+wx;*-7 zp4s?zsoL~pvgvsxO+B_gS3ll&QT5g(>0Z}$eNhpS|M-fI`7d8FuDf%C<9PQd*FCVu z7w5XWw>yb{-4E<>>?b4QOIjEVIo0;eRwee7+EZ-*_dXx*KM4Jc&Dfv8ZP`*~zuSJh z-43!J^ftr;JL0li0``P#3fUF1Eo5KF#*m$P+N~jbLpF!(4%r^EKV*Z*4v{S)dqg&g z>=M}~vQK2A$WA@&R*}7W+RY-nMYfCV7uhhfV`R(7o{>!>yGFK+>>JrQvU5+nb!6|z z=8@ea+eh|~Gyv%U(gLIhNE47QAZ<YUfHVT>1kwtm7f3UZZg|>uApJlZf^-CF3DOg! zDM(k4wjg~$8iRBOX${gFPum=%JD#>ZNPmz9Ass?mg!Bk$64E84O-P@RMj@R-T7~oq zX_lw$7Sb+H+b^VHNXL+tAw5HyhI9>S8`3wVaY*No)_L0AA<gr&-9y@k^bctu(m|w! zNDq-FB3(q<i1ZO@B+^Nwl}Im<W_sFgBJD)_i8K`HDAH1-r$|$gt|Dzk`s!)Z@qcY> ee5LJgpv2yb13AI+-2B{<yn=$9V9}pX@xKE%a7T*( diff --git a/lib/pytz/zoneinfo/Cuba b/lib/pytz/zoneinfo/Cuba deleted file mode 100644 index 8186060a4b2a81934eff93a2733b581bf60f299a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2428 zcmdtjZA_JA9LMpS#~5l+K1UKJiIp5Y&~X-u_vQl_Ab;X$3SwqF2t=5XkO;Y!lC6zi zlw>kqRL0yYjBH>vyO;PdNAsWLw9%M>`J&CO<&2EvbbYU_t*uwBUUl8O?$5pR{`Wn> zqRR3#=Wi$4{KDn5o6C3HF7tYS^Ow6m8hBm0>q^|y#pQZ>pujzok*#Mwukrem%A~(N z-}}06f}G2^?hU+iRlbS8;EisI($P?pdt=FRy)jbg{Wh4PW1V~4-%lLUn=M=1@m@zm zog<#pHmqSSC%o|bK8@Hq>_%?-UZMh1ylD5h+%hfOjY&KxF{6!MtkWW~KUKLCE>+6J zZ})hUyd1gp=oas`?zbhb>41BC!H;@Jd5<^Q->*|v?)Rn^zo^sZHhR-DN_9qbi8nKT zrOv#v)Vp(Rp2nZu;NCSDtFyW?-Gm*5a(8Q@n^+W(*|p*BJ<AGo&g#o<(wua?*LTvL zJM|S!o<8g)k9W$v(Q|s=&|bO!!V!JoShdXW*{3NTdE#qp(A4HsSx{f3{)!w;du*|$ zXQk+a?s^H#Ixh>;4$H!uJ+dgiUl&~&(1*r8)Q3-gq8Wp)>Ef<)vgEUEn%R0pmL3SI zTVAXymIq|TwO2JOX}V;6cSu%6+>liti#{?kC^_vllG{J3dAs-O>MvGne*GntH-3?V z#gpaH=PpWN{B;Sg`BZ{q7i4XqUDjT{rt1=VbzR?iT|fSo7QNe}#X~!F!%O?M<k&{t zSlXzMceuJK?@f84r9?KT?2sobmP+ZQ4N{ghTgt9xN=0&nRD6{vmC-*)<p<$Xb>);) z?@Q39&W>o!c1NG?I-#{|hIMOer#=(t(`~hT_1UTKX<dG`){P&R?TcQP?L!BpVMdiS z9BYy1f6bL09hK5}(I-2bbEK&^PMSBS$O~;hOISqszkMRZ|MEmd{&!C()P34<%-eG! zL!nb%SWGB%^sqDW&o{s1<^`Q>bC)eQw=ihd<2Yeq7AN=*b{8_IvSnT`vOi>l$PSS$ zTFoAjO<K(^k!@PdK9P+gJ4LpN>=oIp)$A78uGQ=p*)XzWWXs5&(QcX#cFi%{2KJ3? z9N9Utb!6|z=8@ea+qatiBMq>c4j?T+dVn+m=>pOQqz_0VkWL`2Kze~R1L+3R4x}GQ zL#(DFNK25OAWcEKg0uzc3(^>*Ge~QY-XP6Ex`VU_>5tVk2<Z^gBBV!1laMYUZ9@8l zGz#ey(ki4^NVAY`A?-r?Wi<^$I%YL3LwbfZ4e1)vHl%Mz<B-lFtwVZeHO)i1XEp6Z z`e!u_L^_DH5a}V(M5K#I8<9RDjYK+$v=Zs1)ie|7rq#3)>8I5+6zM3^QlzIyQ<1JB zZAJQuG#2SB(psdqNOO_yT1|VA{#s3gkq%o;i;*59O-8zmv>E9$(rBd9NUM=vBh5y- zZ8hyi`fW80M>=jbEk}BeG#%+W(srcpNaOMU-uYM){($)dn4g#KOY<#AT`)h-@Avu5 Hmp}Fofim_s diff --git a/lib/pytz/zoneinfo/EET b/lib/pytz/zoneinfo/EET deleted file mode 100644 index d2f54c9bae1f689433b7da8bc38655c7f2aad22d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1876 zcmd7ST}+h)9LMp4#4Kj?M?*+nlL*iw99~0%h~U6fFoBbjN~F99MHEB~2x2Y9S~2!( zV>)Lpos2nW)CJiEP)n=nH0I`*^KEUpGRrYHb83yP=lk4t)m1%bXP;;1;#~c|zrePZ zrcBR2o<+_te0h4EFYgaMJXWu;4_DYDgML|OuEHCVwKNc=Wfdj%Xx5*KNc>&Pmt9e0 z@DGZ-{gtAweWu6GAJmGIA1Qiz#$t|5SnS82SlqBIzVBU2Xzj4achuX;@_;4g`|SyD zt|i4J+mlP8EIFiD$#=4q@^g|>=ay^LcZ;=p@}9h7H?(H-lF|mxD7|x9zJ^bgQG3xc zJ0>lwV9K(q_E=8r0n5$ow%mmQ%ZnaW-qoG9_OC9jn<=)ZFV`r4yiNsE>-5Y}kqY<6 z=-H-36%DSn=So9V+z@RgN!R5sUa-=T(<)2+(aPq3vGu{<*!tO1w&B`g+c<I5%FlkS zijiU4bnK9xZ)>y7!|$nb>j$c8ZB+GBtqPQv>4nHm+Tu;smb*UHM3h?1g{6A&PKs?i z{)e{Dh1!mT;Z{3&(_R|5ZFQrw_Hx~v?d<<ey9&Rw`r5N<h~H<81rus|@C)sZm3IHS zSIr9@YB|-TSFQ&1>XGeg{l48^d%r+!<JI<hbG+Ud%CYv!eCue6v^O*2tkZwr-U|0v zS9*lHZvAB4VRzMi?py1bzo_2N$L;OeDfR6;q5g>jdZ%Yh10w?p^88mHk0&O<xirrz zU(f@7f57ASR|h;n&J*|-XUZRNs>rz_CySgda=OU*A}5TTF>=btIU^_S>YO!l+Q@k$ zCytysa_Y#rBPWlXJ#zZU`6CG+86YVjIUq?OS-3iBAbB8(AekVkAh{sPAlV@4Ao(B( zAsHblAvqyQAz8UPX(4&JI*B2fA*ms`A;}@xA?YFcAqgTGA}Jy{B1s}yB55Ldx;lv> znYucuBDo^TBH1G8BKaZ-BN-zpBRL~UBUvM9BYC?zi6fc2I;kVMBgrG#Bk3dgBNKqk z05S#093Yc`%mOkE$UGnufy{)fGZn~OAd`X21~MJUd>|8o%m^|i$ebXPg3JmsEy%ne z6NAi*t1~so+_*ZEgUk*xJ;?kZ6NJnVGDY}5oug1^oYrKrPTq>Fw45|wdX~2$!{@$b GhyD%6ikYzh diff --git a/lib/pytz/zoneinfo/EST b/lib/pytz/zoneinfo/EST deleted file mode 100644 index 074a4fc76ad816447121db6cd004aa83ea41d437..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 rcmWHE%1kq2zyORu5fFv}5S!)y|D78c7+ixxfSeFA^>G2Un{ojFs8bC{ diff --git a/lib/pytz/zoneinfo/EST5EDT b/lib/pytz/zoneinfo/EST5EDT deleted file mode 100644 index 087b641d2cfe4a7db66b627cf3543f5a7baa3537..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2294 zcmdtiZ%oxy9LMnslIR5zDhd^;pkg5Z?J6P_CXB9jh4f-bre6)bLnuyaHz>oJDCyQ* z)1ZH&EHi6!WM<Q9OwE~FGgDTxKf_~NtX0%8df+7Q?Yz&^9`&qs?#_ArcAtOV!G`A5 zC7yqrIQtKm^Mbv6C-?2iP_KRTi@d(YqjtS~N<M$qsIysrs<SetFJG!RV?WK&f1N8c zS58jTkwfVw`gT-%dLt&L`+|;rT$^z!B5`s>T~(`Pe8qVcUvy03pPQ^EChwMs`*s?y z*&+#gvr7Et4V`o(q^>^Pr6;|!$t1tDTBmGnGS_t6qpxkQHrFl9)v5j*bN%c@eM3%y zNz2TU$rDD*l-LBB@`qPVy)Yu_XMR=Fj-Hl`{a>gXckP#(!X9(;Gdm=+?gx{#reELU zJ8o{B|Gd5}b&tu;U8l1npQ;>hoz5A0OXdDNOHUu@R(XR#$?t1c1qWtIVdnx>v?EQ5 zLm^YL_Oi^V_M6)meJ{T3>1JlhfXs?dGNn_$)uqF~o7rQZ=&}=M%$)D~bb0@8>W+_} z)^~3IQr-2+DqYd`zM9*$QSPqUspd7Vl*+=es;ZnPRik@Nb^Z!nJ-E&IljdswfmSnr zBug)Nb*sAf>k0{MXi*D4%#@mC0ae=*C-;>Xszr|t%i{DD6|6fc!Ld=b#P_Z&`Ein| z&p4s$k6$!PBYSnj?m_dw&^EoS>!5jXpj9t#>@_R;7HSE6pjLLb$g2Dv^-w4vjY;jQ zsk%^_MjBKoJ4HgL%2o6DQEA?nq1Ft4B`rN=ruFni{ct$lJkmd?+v*}lZ9k|V^=Y%V ztyiy0J!aO|bm;Yw^D120BjKT=sy(e;+6UfL9hVy9iN4M1$#dnhp);hOI+P)u%l&HO ziyn#b#CT%I+2_CXi$)K>=kY|NpB;&bbMFNACRQyDda5ezz2Dy2AmfPP2LHu~qV_N( zWK77QkWnGSLdNB^14BlJ3=J6@GB|Xj<AdRG?D&8IA|phGh>Q^#Br-~5m`*!RWS~wv zQe>#eSdqaZqeX^`j29U&GGb)N$e58qBcn!!jf@)^I5Ki%=uSI!Wbnx7k>MlbM*@IE z00{vS10)DY6p%0=aX<p$w2?qU;k2<pf`LQ>2?r7nBp^sckdPoTL4txr1qllh7bGx9 zWRTD}ZETR>Akjg>gTx035E3CIL`aN~AR$pg!i2;L36#@D3JI0d#tI3R(?$yk7ZNWd zU`WJ}kRdTcf`&v52^$hOBydg}IV5yW8#^R;P8&TWd`SF|03s1YLWsl=2_h0jB#cNL zkw7AmL_+Dbu|$IDw9!Pu>9p}g0*XWw2`Lg&B&bMKk+33hMFNXN774A>#uf>#(?%Bw uuhYgC2{002B*aLJksu>c#{W&2y|&msTkO2RjDnJaqP#$HaY1oGk@s&Yp|6hs diff --git a/lib/pytz/zoneinfo/Egypt b/lib/pytz/zoneinfo/Egypt deleted file mode 100644 index 0272fa1ba0a09ae8380be83cc8838d97d0aa6d25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1963 zcmdVaZ)nw39LMo<r>=RO)m6)8UR|~2n$6tOt>w*SdS&XQU)(!N8TDI<Qc)aPg1TY{ ziNZ3%_CQHc$%;z*qn5f*A%mK;+EH18LY;-^GMlHlDZ9GXc^@AJK@WP+ckI3{?txFv z`;%V$^wKi%w;SsIg<r18{qlXJav!bDpPSZ9qdt7~f@%A<OCLG%N%-B*cj@nUbebQ& z>DBFTE;ahYRr=`5p*i;2$3a*5Q(;$cNpNmNOL(p$)W4n2*z;dJCw^~lvfUec#D&jx zOS`I2{jvUx?OD7?Tx=L8duOaueYIQUr3o>0x%`;DJeU;yw`9xy&Nh+hue4VV4XCT9 z4%&hD)~G-C_sGFl_6V`L&_<U1s<N6E$szONVrczOa@gc|ME2~My)L&y<rE}kPWNyT z9k$X&+Y@TU*#Voo^D8y-aH+ihtv+$XM@#IDFBGZR>l@{$+Bz}%`M4ZY(J5|vIIuV8 zjZtH_Hruh8YLO?m*}PMYYFux^-g;1|@f}jew@ecg_H45g)iRa8;e@=cev`O;b)CFp zPQEC3a*`}8OsP8)U&~3^uZp7hC0lg%OI0iyZE;(bn%w!RynB0tC^__towDwIb<ft( zX6m!Ky0od!l+Ewf_dapLOe@xUdR2=lkM7bHML&iW7u$o%oPA;C@twi_U2lgob~gu! z))&JEnpXrfKX^Q>YIrnwaQ$jCtFA#mw7kyDPSol-kIXd5Q3Ju;(tI=5JQGyMvP|{f zn4V`(oB40F1Pe|^!kYR6x@PTTrsmyDkXmt{N$psxQz!R_>4G&uR^&hW8ItwSKiB?W zA>y^}^@-xC5%(0w=ZoRjzSk^Fi)1pzN1DG!_(=bYH$CX?r2`AMBX8U5-Z%2bk#~-~ zb>zJxZytH~$lFKWKhglw0n&ok^?)?tbzLBBAblW>Ae|tsAiW^XAl)GCApIZ>Asrzt zd0kIPQ(o5<(iYMe(iqYi`qubDZ=7om=niQQ>5rp9q(hDtksgsIy{=1+HodM-q*0_( zq*bI>j%JZ=k#>=OIT}Vf=4ct|8EM+<y5?xx>-t6-M><DZM|$UI9_b!wAL*ZC1CSly z*aBn^kWD~#fnyuIZXb}1@VcEqwgTA;j?F-J1KAE_KadSUb_CfHWKWPyL3Rb%7O&eE zWMjN;XOOKy_6FG;WOtD5LG}mPAY_M-EkgDP*(79_kZtn1eL^<M>vjs+DrB#a%|dp| c>$c15_6ylCuiG)N+cNyW?OD`~TS-~;FR%xpXaE2J diff --git a/lib/pytz/zoneinfo/Eire b/lib/pytz/zoneinfo/Eire deleted file mode 100644 index 5c5a7a3b8d67f5a5c9e44a56e70a727967d972e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3522 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 diff --git a/lib/pytz/zoneinfo/Etc/GMT b/lib/pytz/zoneinfo/Etc/GMT deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/Etc/GMT+0 b/lib/pytz/zoneinfo/Etc/GMT+0 deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/Etc/GMT+1 b/lib/pytz/zoneinfo/Etc/GMT+1 deleted file mode 100644 index 087d1f92576a312c68393936662125d9e21e232a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|BoLS7<3H`ft(OB^>Nt%_1hV80RYaP4U_-? diff --git a/lib/pytz/zoneinfo/Etc/GMT+10 b/lib/pytz/zoneinfo/Etc/GMT+10 deleted file mode 100644 index 6437c684f8d14b58dbdcc75322149fc60b08bd82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5S!)y|KbD&23<n~ASZ-OeOxv`{dR^1TmX893#b49 diff --git a/lib/pytz/zoneinfo/Etc/GMT+11 b/lib/pytz/zoneinfo/Etc/GMT+11 deleted file mode 100644 index 72a912e050e1af97d12a541f69ee01584c52f509..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5S!)y|I`2m23<o#ASZ-OeOxv`{dR_iTmWzU3vd7c diff --git a/lib/pytz/zoneinfo/Etc/GMT+12 b/lib/pytz/zoneinfo/Etc/GMT+12 deleted file mode 100644 index 6938a1aff2b9a786015ebc0d0ea4d95bcf146f64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5S!)y|8NHe23<pt`VcbpaoGU%+Zh^h0RUxj3pfA( diff --git a/lib/pytz/zoneinfo/Etc/GMT+2 b/lib/pytz/zoneinfo/Etc/GMT+2 deleted file mode 100644 index a3155777077f680d885a3e820fe0a84d5b238d58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 scmWHE%1kq2zyORu5fFv}5S!)y|Hls)7<3Il>O;uX$7KW5Z)d~>0K`lUTmS$7 diff --git a/lib/pytz/zoneinfo/Etc/GMT+3 b/lib/pytz/zoneinfo/Etc/GMT+3 deleted file mode 100644 index ee776199ab76fcb46bcbb3d82e99d0b88cb36e57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|Em`m7<3Jcft(OB^>Nt%_1hV90RXxB4I}^n diff --git a/lib/pytz/zoneinfo/Etc/GMT+4 b/lib/pytz/zoneinfo/Etc/GMT+4 deleted file mode 100644 index 1ea7da29dc7d975896c4cac40ee5724b5b037147..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|KkT37<3IxfSeFA^>Nt%_1l?n0RXS44D0{^ diff --git a/lib/pytz/zoneinfo/Etc/GMT+5 b/lib/pytz/zoneinfo/Etc/GMT+5 deleted file mode 100644 index dda1a9e11ef7dc78cbec231e4e0463bc41f999ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|D78c7<3Ixft(OB^>Nt%_1l?p0RW{|4730M diff --git a/lib/pytz/zoneinfo/Etc/GMT+6 b/lib/pytz/zoneinfo/Etc/GMT+6 deleted file mode 100644 index f4a0385567fe2a6998abccd61bfffb4291b9d814..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|J4f^7<3KHfSeFA^>Nt%_1l?o0RWo>4153p diff --git a/lib/pytz/zoneinfo/Etc/GMT+7 b/lib/pytz/zoneinfo/Etc/GMT+7 deleted file mode 100644 index 2d2ccd005b9256ece18b7c48860feb77f08a2e54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 tcmWHE%1kq2zyORu5fFv}5S!)y|G5(w7<3KHft(OB^>Nt%_1l?q0RWJ)3`76` diff --git a/lib/pytz/zoneinfo/Etc/GMT+8 b/lib/pytz/zoneinfo/Etc/GMT+8 deleted file mode 100644 index 826c77001b6645680d430fd46e04699e003e34bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 scmWHE%1kq2zyORu5fFv}5S!)y|NaIB23-RSASZ-OeOxv`{dN{y0ES=;3;+NC diff --git a/lib/pytz/zoneinfo/Etc/GMT+9 b/lib/pytz/zoneinfo/Etc/GMT+9 deleted file mode 100644 index b125ad2bcf93ad431545d6390ac76ea2e8aafe07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 scmWHE%1kq2zyORu5fFv}5S!)y|Hc9a23-S7ASZ-OeOxv`{dSgI0DKM$)Bpeg diff --git a/lib/pytz/zoneinfo/Etc/GMT-0 b/lib/pytz/zoneinfo/Etc/GMT-0 deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/Etc/GMT-1 b/lib/pytz/zoneinfo/Etc/GMT-1 deleted file mode 100644 index dde682d83c0910e3aaa4de1fc4a56846483f5b93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8flq*eLEFF($O$1+AD0bKzn!ij7XWsh1~~u# diff --git a/lib/pytz/zoneinfo/Etc/GMT-10 b/lib/pytz/zoneinfo/Etc/GMT-10 deleted file mode 100644 index 352ec08a14a107fbcff0844659e60e8bf3952a92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8p=SXDgSMdokP||tJ}w)eemh-511<pJPY8to diff --git a/lib/pytz/zoneinfo/Etc/GMT-11 b/lib/pytz/zoneinfo/Etc/GMT-11 deleted file mode 100644 index dfa27fec76820a74da31e40ff8698fcfea3fbc8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8Vb%r)25mz_ASZ-OeOxv`{dT&BhFk#b%Lv5) diff --git a/lib/pytz/zoneinfo/Etc/GMT-12 b/lib/pytz/zoneinfo/Etc/GMT-12 deleted file mode 100644 index eef949df27f2d63a9d508ee691637b8326829e04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8VZ{Lk25m!-`VcbpaoGU%+vyq_aRC7MM+pT0 diff --git a/lib/pytz/zoneinfo/Etc/GMT-13 b/lib/pytz/zoneinfo/Etc/GMT-13 deleted file mode 100644 index f9363b24f0a4c737afbead84994debb5fa2c9bda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8VcP`;25m!QASZ-OeOxv`{dT&B##{gc(g{TX diff --git a/lib/pytz/zoneinfo/Etc/GMT-14 b/lib/pytz/zoneinfo/Etc/GMT-14 deleted file mode 100644 index 35add05a605a4ed9d7f353c78ee3db8b014e8141..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 ucmWHE%1kq2zyORu5fFv}5SxX8;m89925mzVASZ-OeOxv`{dT&BCR_jyP6>qo diff --git a/lib/pytz/zoneinfo/Etc/GMT-2 b/lib/pytz/zoneinfo/Etc/GMT-2 deleted file mode 100644 index 315cae4f9e535ee35b868a0e3709e2a7fab6a606..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 scmWHE%1kq2zyORu5fFv}5SxX8K}LarLE8YNK7>qtTsA=ccDhDf0D;;Dc>n+a diff --git a/lib/pytz/zoneinfo/Etc/GMT-3 b/lib/pytz/zoneinfo/Etc/GMT-3 deleted file mode 100644 index 7489a153dbb61b904090d6bb484c6f29770f44a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8LCb)FLEFF>$O$1+AD0bKzn!iz7XXbH2DtzL diff --git a/lib/pytz/zoneinfo/Etc/GMT-4 b/lib/pytz/zoneinfo/Etc/GMT-4 deleted file mode 100644 index 560243e841ff12e0554eb328e40dbcbdd65b1327..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8!NP%oLEFFt$O$1+AD0bKzn!iL7XX-42KfL0 diff --git a/lib/pytz/zoneinfo/Etc/GMT-5 b/lib/pytz/zoneinfo/Etc/GMT-5 deleted file mode 100644 index b2bbe977df886770874563aaf86d7a358814ac00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8!7YG+LEFF-$O$1+AD0bKzn!ir7XYJ?2RQ%$ diff --git a/lib/pytz/zoneinfo/Etc/GMT-6 b/lib/pytz/zoneinfo/Etc/GMT-6 deleted file mode 100644 index b979dbbc5c86789f34638aebc9644e7af0fb83bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8AtZr;LEFF#$O$1+AD0bKzn!ib7XYr#2YCPh diff --git a/lib/pytz/zoneinfo/Etc/GMT-7 b/lib/pytz/zoneinfo/Etc/GMT-7 deleted file mode 100644 index 365ab1f64683d2b1867072378c1adcdf289e432a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8A*q0YLEFF_$O$1+AD0bKzn!i*7XZ2o2e|+M diff --git a/lib/pytz/zoneinfo/Etc/GMT-8 b/lib/pytz/zoneinfo/Etc/GMT-8 deleted file mode 100644 index 742082fcd4fcf8bc9c1a8b8f35af7533d74b0a3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8p`d|*LEFFr$O$1+AD0bKzn!iH7XZab2l)U1 diff --git a/lib/pytz/zoneinfo/Etc/GMT-9 b/lib/pytz/zoneinfo/Etc/GMT-9 deleted file mode 100644 index abc0b2758a3b5fffe3acff6d42973ce9632acc33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121 tcmWHE%1kq2zyORu5fFv}5SxX8p>6^LgSLSskP||tJ}w)eemh-DE&$Ml2sr=% diff --git a/lib/pytz/zoneinfo/Etc/GMT0 b/lib/pytz/zoneinfo/Etc/GMT0 deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/Etc/Greenwich b/lib/pytz/zoneinfo/Etc/Greenwich deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/Etc/UCT b/lib/pytz/zoneinfo/Etc/UCT deleted file mode 100644 index a88c4b665b3ec94711c735fc7593f460668958cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U33UzuGD67I#|6}Gzy$z!Xa;ov diff --git a/lib/pytz/zoneinfo/Etc/UTC b/lib/pytz/zoneinfo/Etc/UTC deleted file mode 100644 index 5583f5b0c6e6949372648a7d75502e4d01b44931..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N diff --git a/lib/pytz/zoneinfo/Etc/Universal b/lib/pytz/zoneinfo/Etc/Universal deleted file mode 100644 index 5583f5b0c6e6949372648a7d75502e4d01b44931..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N diff --git a/lib/pytz/zoneinfo/Etc/Zulu b/lib/pytz/zoneinfo/Etc/Zulu deleted file mode 100644 index 5583f5b0c6e6949372648a7d75502e4d01b44931..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N diff --git a/lib/pytz/zoneinfo/Europe/Amsterdam b/lib/pytz/zoneinfo/Europe/Amsterdam deleted file mode 100644 index ed064ed4ac9d86707173d480921c6ec27bb69916..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2940 zcmeIzdrVe!9LMnog)}<iS4<$qTSRz4d;ldiNeR(RAk-%!1uu!HMTSTWOT$X7)o<>b zW0_O)hM*1+4Q~Uy-<nERqttY5y0VLrn>n?{>i0gaE!RJ_{^+01;rw3DbNCnDAK!#o zd2yb<TtW5?57$on@VVzgd#rR~uC8qQEPK_tJ<jU#{m%Q9k<OasA<kM&GV6+OIqM4| z%!bVKPI*Rv*_cr8R76fRl~D)Hrb(C0<~AG5=H>~`mMe=*Rn#S?>i7(^^@lvCx^lGH z*0{pdywTTe-`>saD0s~IVA(jQHlvTTbM9iZ%j-D1eS4if?VFvw!%NKGAI~^-VRKB~ z*{i1h_C}{+Povp){;b*mUX3}>kYf&(EHj5HPnts)a<UI+6%`yQJyLLVP-elgDL1bj z?^$^5#E2akr>=~V)0;!JaecCUv}}N!c`Z^tE)0=R(uZriJ}m|9%BN}2+nuHTq7;>) z1nrO;rNO>1?U*(~?v4LhJ4Jl2AssJhXzSA&dVQaUUEZemeY#dVAAU!>)Hg_YRkd_o zwMM!bmG1Ljl^*$p(ldLS+@I=`h@mO+Ky-pchDXVRPMAamBx}@-0ov=UNbTJeqJ56G z(Z02}G`jp-?N@eQV-}s%{zdib&E2Z8Ip-v<uvX#|>tw*VcV%GL)shfbA_*;}GN?<L z4!Ss12LDp5LmHCh;V-guXvJhrtQ(?_EJ@N~E5mhoUW6ts8Z3{F4AA7<E;1tWil!vD z$jE>XbyUQcGOGD&8QuDzjBY$3k6+#(W2(1E>Z$#jw$w;^)jEA*_H4;8uj|;S-qLaT z({=nq`RYp@rB8;YYi4wB&AjQ=tWF~(>+@iJ`bICAu<IwC*c2$)Ydc6z?NymndR-=$ zHOiF9O)_=Cclyk*-7+obl;(C{A=4A9HLvYXozYcw#y87#W=o;YIx$b5z39{DHciz0 zV{_#BHxqSs#dvvPW_K-EGEnA>9V&%+p)xnNn-rzomKQsCq_}@4ExvY1N`h``$(bWE zulbz5R8t`@H`eL=6^C>|^=f@(Ub&VoE!9?jty=s4&AUy&UG@%ad$)VHyT|#jZjZ;m z%>FNWg3JB<-Op|B{On)jaqOD|e!+j7LE;JXc*1-5h2MGp{+=^6&F4uU@AD*C`+B`` zUVFycGdaa)ANvb?`Ps`J{QT*%-+cd|BEQ<JRq)t+q$aMmC`eV1vLJOq3WHPzDGgE^ zq&P@*kn$k)K?;Oa2q}@Ptr1crq)M)~Oh}!OLLrqxN`=%4DHc*Kq+Ce7kb)r<LrR9! z%+(eRshX=T8&Wr<a7g8l(jm1&iicFs)s_#bA5uW1f=CIG8X`qRs)&>ksUuQIq>@M} zky`SXKrvlyHIZ^6^+XDaR1_&GQd6X;NL7)tB6W4Og+(fhloqKiQe33ENO_U^A_Yb& zjFcFuF;Zlt%1D`!IwOU4wUtInjno<`Hd1Y*+(^BVf+H12N{-YVDLPVhr0huDk;1#$ z$|I$BwY5izk5nHiKT>~W0gx3ymH=4;WD$^6K$Zbn2V^0Tl|Yul)vg7y7_N3TkmW$u z16dGcMUW*y)&yA;WL1!5LDmIX7-VITrE#@ugDj4#T^(e3ko7?p2w5RyiI6oy771A; zWSNk4LKX^HDP*aTwL%ul)vgw@T*!JM3x=#1vSi4bA&Z8r8nSH2x*-dPtQ@j*$l4)` z=W16ESw2_0e#inMD~K#1vWCbaBCCl1FQX^OT};k@wTyvwKl{ZppwVgZF#}_~(P^<> I>>nTa8?hLa1^@s6 diff --git a/lib/pytz/zoneinfo/Europe/Andorra b/lib/pytz/zoneinfo/Europe/Andorra deleted file mode 100644 index 5962550392fa78514061582e9371c32b9f1d929b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1742 zcmdVaU1*H~9LMqJm=i7OKjo!thM5`1UTtPJ%yG7`jqN;aUS?kA*f_?HS(~>*){@rx zUo2WIL`&kN3>PC2B3dpi&5|OoNs5KEc)pKIS8kMNJ?C>;=koXcn=7k)DaId<)A|oD zkKcOv9<ks0c%0K`M4k^x)bSHCu305|&jA_l56Fbo8)Ra>DKS|dndC~B*vL4UJZy}_ zIo%r9KSQUyiq-g@Q9AX`5S`ZfO<iZ+>Gam8n$Y+_6Kk(&QpE*LF6)()K&PZ;cgc+6 zBa#-?F6k-tl0FcWjIphn@ob&U`cbEu*WEI^uT---$~C(yQ|C10=-gwGI?orQIgPVq zey&s96=P*V>>KsC2PD^dU-M#~OWw!VvM}tXEbP7~i(a0V#i4VO|L}$uv@}cM`BS>2 zx>}YtAJn3f!&>a$sNR`=HS_ay+2}%D?uystUz4<SWUiDx8KEosr^w1HUvyPZxU4=I zA!VH(WKHn1l(%-v+VUP*xA(oSpL<m{ls(jn=r-Ay9a7)W%epB_b<^9US~(EVs(TH( z`I)I(&aP7b-5S|?C|j#Lyt1t_TDLc)Nlj6f1bm}qNAftS^?a9|5r)(yj?}tOFQk6> zSFL|^TN*z0>aL3&vb(!W_q5&8y`gs9*KkIIEkSh*3LE^d{tUyxPIv|z#&9u)8b;)J z$FSeu^9xL)#A6z6`}Laq%;B&<%)c1mPwUy2eyJ51A`fFk28oOk874ALWT41Mk)a}E zMFxwE78$OsHC|-E$cT|4BV$Gejf@%@HZpEx;K<04p(A5Q29Jy$8NRJGek1@}D*_}0 zBnBi1Bnl)9Bn~7HBoZVPBo-tXBpM_fBpxInTPq?YBwH&cBq$^*BrGH@Brqg0Bs3&8 zBse5GBs?TOBtTm$LL@|6D@G(pBuXSqBu*qyBvK?)BvvF?Bw8d~Bwi$7Bw{3FTPtQH zXe4SRY$R?Za3pdhbR>2pcqDoxd?bG403b&IIRv)WF+dK2t#uTT!+;zI<Uk-t0yz}O hvB3Z4V1!#oWO}mO<279csR?NbNv?wABuq>V{{?7%e9iy> diff --git a/lib/pytz/zoneinfo/Europe/Astrakhan b/lib/pytz/zoneinfo/Europe/Astrakhan deleted file mode 100644 index 5e069ea5b3a9c8eaaeb9c48abe3d9a4b89ee3b1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1183 zcmd7QJ7`l;7{Kupn>00Xa8MiDq&{jI`iRCfDN)m!nx>(eLXps+Dij|DLGVK|C<qm- zom5Z+5k&-pgYQ8ns|0Zo+fHs(IN;<1Ig1Dujpu)(lY_W<FX#L&hnqn1{flFVC*tal zYpFe9arN89^YYt)T<LMyKe94%rLrn8A8Txw%QSv-Jq&zMmDLN$Skvs8O7qM`9lU#5 zhpv@$%h^ME&B;-{_DEi@+ndts3+=i!-J~~k`E@wxGj0Aw6Y)eO@+BbcuRo1<y!a@c zkKRaEc}}92p2^0k2ePSjLt+On$mV@BCVuR)>F&L561z{BWayOXiSIEz-}jqT^KnVN z9WYxK$7Ji%HnVLZE$LfX>3z~7ebZstepN|k!Yln#L9-+GUa|*%n1S#s$z`<}^xTtt z>vNO;{L&27mCexn9kcV@wApq4oGHxSl;QJ7O|cLfAN{&QCacw|t8Pi{c6)x^_1=1~ zo9*wn+v|2K6<x-^+7VOsd2qzlpj0ZO>{)I9ed4kwwRbo#3>Q^rtlO@nT?xKGxwxDs zw&Fhw(*BMr!$bzk=NT$8SY)`4cEHGxkwGKFMh1=y9T_|_d?WxQ0we?^1|$e13M339 z4kQpq8wnB$5(^Rx5)Bd#5)Tp(5)l#-5)%>>5)~2_5|^V542jIqhK9t31cyY2gonh3 z1c*e4gown51c^k6go(t71d2rJXhTI}b+o}E(IVj@@ge~u5hEevq@DeL8?!HMBkzkQ Gd_MuxBoHP5 diff --git a/lib/pytz/zoneinfo/Europe/Athens b/lib/pytz/zoneinfo/Europe/Athens deleted file mode 100644 index 9f3a0678d766881389e129c93def7fffd74f14f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2262 zcmd_qUrd#C9LMqJ2qYerzDWE@Kr|xMj&S(vpCF>+ftd$GI2x%42BH{|5-~`OV=>np zbKe%LoS9aPIcCQ$DCdw`R!$4Zt+8h1V7W5OIhHzW#p?HdYHO{lZo27r_B^lWJmZ3k z^LhK%)z@WO|2R(bhKsY;Ts+SXnA?HCyugX}%i||bA3JyU$C~kv<w)t!rIj+==bIRL zJ#PHd7wWf<MZH=*cHCJy?aIdL>2cSqrw<3M8H0zcnLTR)5nYvmTRRsAWZSP2S-W#$ zR$1RfRM9ilw`H9wjZXeSW1_y&*pOk3z4orgT|B0@e|A`BfAGA<pB$8gqd`f0<&ezj zQb}sxEpr>1WZs4vxx?p|<UFt3>B^OqgjBg}Mx3OEc{KG#w$A@1MHh_5=)#Yt>fQZ6 zsjKI*F6#b5(>g!V^yZW5u6<K8D$h%1Q@>>84@h>|i;|OgP;xU{C3mtzmc)1Kk_($; z=`St1Y|tb3d|9D+y;Yh&uuSjWQ>X<867;^hWG(DmD)$$Ksi!txmZw}&uV+$<!p>@O z@|YA~{Z>|lye}(8&d387-;kBTKJg8`rzQKkWYy8v^ufkPDeZbzS3k5@%NjOo`Qirk z`-*i<>?&RBTA*uhy0s#@NGd*`r4QejFYDg9uIopgvf*%~RQ6wyjUCsds(VDLt43wh zuJ84cg5y$CIi$5o`(<-}Q0v0q)-8#uTfTcqw@x-`{h2o1cEPWY9$Bvqr+3I>&*f`l zZ@E0aHA%Pc$&nqa^Q5URR-VY1BhB8Q<;h4(TGFGnW#VgTjku|;=RTCStLOEpw|Zsg z$bh!*Kc%~Z2leT;9_`rIp^lI#AydLa!$OU>Otn9}2??L}Uw<qsCeQq6xrbWTLboH_ zvJ&Q6Gc7AZtVo_6(SLvc*WdU`{8pj4yxwwde#>KD=F1<)Va}hAmc!xy7d!gRoaTa; zmm_;ZHf3veg=`Di7qT&AXUNu&y&;>kHM`@#VtdH`Y|REa?2yA2kv$@tM0Sa66WJ%S zQDmpcR*}6To3%B&MYfCV7uhhfV`R(7o{>!>yGFK+>>JrQvU6nX$lj67BfGaX+eh|~ zGyv%U(gLIhNE47QAZ<YUfHVT>1kwtm7f3UZZrGZ3ApNj44M94Bv;^r1(iEgCNL!G; zAdNvfgR}<e4bmK>J4kzw{@9uZAsw<cEkb&PGzsYv(k7%&NTZNWA+17sg)|H47Sb-H zU$&-UNXKkV%aEQSO+&hdv<>MS(m14ZNb8W^A<aX&hqMprAJRaigSMuHNDq-FB3(q< zi1ZO@B+^Nwl}Im<W+L50+KKcNX(-ZBThmgcr?#f4NLP`zB7H>~i*y!gE&gA7>oi4P Ylu4UiC0S`XY3}qaS4oE3e#&<K4n&?MSpWb4 diff --git a/lib/pytz/zoneinfo/Europe/Belfast b/lib/pytz/zoneinfo/Europe/Belfast deleted file mode 100644 index a340326e837ac8dd173701dc722fe2f9a272aeb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 diff --git a/lib/pytz/zoneinfo/Europe/Belgrade b/lib/pytz/zoneinfo/Europe/Belgrade deleted file mode 100644 index 32a572233d4850a4bb4b1278cc2d25480ffc6a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ diff --git a/lib/pytz/zoneinfo/Europe/Berlin b/lib/pytz/zoneinfo/Europe/Berlin deleted file mode 100644 index 7ddd510ec65b70cb833aba2f9500282b2c0fad98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2326 zcmd_qZ%kEn9LMo<`IExPeZ}CKfKWt;UHK1*L1tHA23J#VMk*nJphXy0V^A7a%(=$g z*T!HD$v-h>&8P=xYpBisjTYVnyV=!TTdv4zj?J7_WA%HVwrcB9PkPeta?b1A^N`)o z+q-5{eYW+F6Jg$ParT&t=ja{g)*Izq-y1kTxi2`Vef>xEm3LJ4cl78;M6-@gl*#GW zoa#U1mQP>Kl`}7-$e1ry#*TGc<CQ0Oj2~Pf;}>!#&(_B2XJyG6C>To$G)!pV@D}Uy zv>`d?nj>FCbW1Sglm;)iO0>Uca+YVQ+>-N)#w7osv9rEZXXt5lUOlO^FTJU^o;{#* z-ru8f$4^N7kwHn=w_oOVsw8fEUXmJpa@)FExxL&g$%Vyohbv!F;#1|$sM(Sl?$*?6 zxjOHg6io}n>iiF<>0LuN)YX4k7j&K1^o~z7W9xCvtb0SVs)Ca38<L!&VaZ+9EqMuj zlAqlw`BUw(Fs@4%UaXOVUt4t13AfyHp;8M6s<mi%k>0y=i7xJq*Zb;|bxB8o++Px| z?z%Wxnlhoq?kOn=|5!_tzmn1`-^#MkQ?krIA`e`8Rmuimm-5kf)w8=(mLEB&E1H_* z!Oj<T<-;%Os>Tgkad)G7%S-hk=W<=`O4HTXGqp0NL@Lik>m%3Z$(mz7>DoY~tUEAM zs)l})N87JTb(de(R|lkK`}g|T;<u%?YE<hI_sWK%L9L&DR2vdh8@}t&jZ;3|G}5Mz zU-aq|hu3Q3N6qr&t|Dz3sF2MY6ZNT`dD6VHP<-`H*^)I^wif>^PtUZZB_l>#Ccl=} z8P~P-%m>nTC8*E5H6YLWhjrWD_jLPUpFY>tukE|rHN+7T8usTW+!4mtn;iD5kZIHZ z`;TQs?J~b>D?Z5)W?3_YpZedQ=Vv-@wyZzyf4wjFcrABv1vf79%^Zir{5g0s$Kmk) zi)sC4&gMfkH;{?hnwcR}L*|A|4w)S?J!F2!1d$mcQ$*&7Ow!iO5}Bs0nWwFpC^A!I zs>obz&17xOY?0|A^F=0%%-GgUng0lLMkbBS8ksgSZ)D=OX6DG$k+~z2M`n*qADKUr z03-uQ3XmKiNkFoIqyfnTk_aRdwk8!wE|6p(*+9~P<O4|vk`W{&NKTNXAX!1ug5(8B z43ZgJlNuy9wkA19c98TS`9TtdWC%$Sk|QKZNS2T^A$dX)g=7jz6_P7klPn}#wkBOj zzL10=8ADQr<P1p~k~Ji4NZyddA(=x`hvd%IBoE1+tw|q}KO})j29XpZIYg3(WD!Xt zl1C(wNG6d~BDqA8iDc8(q!Y;}l29b0NJ^2MB1uKEili0EE0S0wvq)-@+#<<EvTJM7 wi{#hVBpAssl42ysNRp8(BWcF}S)L<Jx))>-ahE42Juf}e<;lv#jGV~d0abN>yZ`_I diff --git a/lib/pytz/zoneinfo/Europe/Bratislava b/lib/pytz/zoneinfo/Europe/Bratislava deleted file mode 100644 index 85036de352d20683bdc6cd5e43ded7f1d5ec307a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2329 zcmc)Me@xVM9LMqRfyj?3d!xaT{464*PJTmZkl6{01XH<;R08!xEh4y#L1|bq=NfZw ziRm1YW{g!cY71)(t@U%X5Px2V<=S#(S~)j!T8-7``HdJY|M&al?(yNvb{iY7=kP3B zUz=t9?+P(bcyVnvFU}F0&0E(LXHA#?^rhV+ecIh~Kwo}ebx+$)9Sm*Mp>qr5@as+; z-shGh9XWFJ`D8ifi;`pe-l;jhDp*czj@6T;$K~Wp{fYhnU!uP(U%pE1ms748@^$DA z8F4ho$oXcGU%d?x-V~kYiPq`m^W~=OKQuDwXN{WvtvUk_tMl>)8h!RHz4^pmo$<+b zjX8KoV)yq+-0nRR->#Cd@i|GX^T{nMR?Dqr9!V-FlG|K)k{p{Nw@-<dlpwdJT*=Xy zKO}3aKT7ZTELiXCzoxF9^E#{Zw5GLvsp%UIYKHes&8!-cEMLE57Y<0yk{yy8*DZNj z&5}3TD)}*;ntx`c%>J`U=Nxj&-QQGdL2tDd4$RSew#?JHU9oy^ZIaGwn=SVh2dUc| zBlDBbX_0$Wii5t;lBDmX<l>J~8u*cv4iC!xXJ3^CeQ!wF(1%*Stz8!Ge?=dtua`yb zFX-ZjUeqOZYqa97I`x#5=!4FMy401bORr{VWn{5bo|>i)UzsV(-u+FN`@>|#-UzAc z|3w~Yy)4z8!%|c2mzA3?=&HHz$?B>h^(O3+HHCdz8*)I`#;LCTX{W9m_38S-7Jc-L zM<07_xz>H&D35O~)cW2Ed176HHf+h2#>EBVt98ngnenor=y!Q4!jh)+NNu|Gy)=hk z)#jt0O3TF&efsTQd1iP(H}3jaH}!Svvn@T^x~)|M907ro#&3r?28}%km>hf~Zp)gw z)%;ysv5AgJmK82m=zq_a<(NA0Nm;qaau-$b=CMl5H|BCU__8mD!*l&bnUCe8?W<$# z9Ql{I;!8WOVcn4nwk(YASsAi4WNpaekkui}L)M2Z5LqFzL}ZP~B5lnok!2$5L>6jm zR*Edu)~pp-EV5c;xyX8L&4T$YSuwI?WX;H;kyRthM%Ili+}5ldSvs<IWbw%Ak>w-n zM+$&c04V`d1EdH@6_7F@bwCP%RKnJj0;vU345S)JIgolF1wkr;lmw{>QWT^rNLi4& zAca9HV{1x-)W+5n2dNHH9;7}<fshIzB|>V16bY#kQYNHMNTHBQA*DiUWowFsRLj<s z3#k`UFr;Eg$&i{MMMJ8FlntpHQaGe?Na>K;*_z@Z)w4C_L+XbV5UC(iLZpUB5s@k) zWkl+T6cVW<Qc9$jNHLLW+M04A^+XDaR1_&GQd6X;NL7)tB6URyi&PdVEmB*gxJY$v zO?i>}+L{6*6-G*o)EFr;Qe~vfNSz&lCdNVIcYrxg9(xcN9C9P>fAef2ZSrg)ZT=Gp v7wexSkDpC~BPRZoNH4lhs3(@%oWo4RXJt}zS9x|?Zd!(`JTn8+v%~%dH&JpN diff --git a/lib/pytz/zoneinfo/Europe/Brussels b/lib/pytz/zoneinfo/Europe/Brussels deleted file mode 100644 index d0d0a08a29c0743517ec537c18ef3111ece34ca5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2961 zcmc)Ldu&d39LMqJ(dTg~>RVQIsd=`pD^|T~hcTBbDXr@>I(3~Yrn*OGQs$a9#)y0^ zGNjBw)n$}fUF$M;GCG^v*hE6cY>S=Egl!S~y^n~5E%Bdodd}-S&1w2i`~LU`j>%1Q zJ#o}D-{IoeWiFoU&oH;eUD9V1*NO@hpS@@=y-=phO1A2!J5`q#Ez;$ccjK*LtL+uj zDgrBG>)NX(XV@jlGwszwA_JvS$@ZG)VtZ{1x4o{~RD0dcR)O``huWW=?`>~5+R5H{ ze2rbUCeq$y^$KkM&})A_XRN&?kYRmMu*E9x9cyh(AL!c_FV^<NiPnxfe^@*2U$u7r zcE;LuZmzH5yWf4gk6rQY*>T#pcljJ^-{P&lFN+)d_7^Vl9T+{^cQ9*`?@)(0>u~FA z-;uWaeMhs-Sw}C{u#R<a=sR(}hn!p&u9YiO<?D}I$*Jsp!%pQ7(QgXs%eOr{>gfd? z<xG!At=-~Ypmxa!4Vl|W>J+7^Oi0ntoM^4<Z=m%uyU24%e`{FeFIvCe4;mhHQp0cU z)dp8LX~WZ&HafUK8}HsD5gW>+$<pQW{5+MWQ)Y@UuRvZHG(uj?@JnR7G<hi|MWQ02 z<>gupB-)#*(YIS^vmc|h`Pur~;&3&6rTnhOl-$sk3omMH(N`KbVYkNTY}ACo7bLNu zT$0*XNUOe|NOF^9l9E^`DfgyJ>&6SU^`+s`=AVh$c2BCj_I;MND;=usE86PovpQ*q z;t1`S8>yX&+DPZFUQNwuEM20mYg+0(>FPbM-6Ahbx0}}_J!rqAS00n@S64}ovh|X2 zVxMNtpC>&xtk7Qh`O<sd2ij-AN7^@Ur1pC?PyHF)^o{VI+CQeb_P-OaSz%oz>wH~( z^L8^Cxb08PJ{v59tWX(T{;Ld`eoKZftdwCx&&u$rKkHi^w#$gYCp4$&5*gXPOmnMm z)lp4UNBy)&N8c;ZF~=t9+n4-0c5Sxi9T_j<X1CY;(th&J=%yN&l`P}?w3C9|aCtZ3 zd6|&*m%JD1l8JF)I?=u&g&}ve@YErhbn}8v-drm0S61khB?okB*)p9rsYIvGpRVpI z-YP+!AW!wG)t>s~{U9$lPn8<M&+2r!JdMmhDTl}93K3Uzmn*{OuJ!n{JM`f@58izA zPDZBRH6YFJN_DP&d@wh2xZUQ<9L^6qpWXidZio48E-sD=bMf5hzB&4DyL=7D-N-9N zT7vY%(KH3=3epy&FGyn?O=pnSAiY7FgLDUJ57Hl`K}d&?79l-yG)+RfgtQ6i6VfQ8 zQ%I|jULnmwx`nh0=@-&4q+>|Ske)f3rXgKJ+J^KEX&llyq;*K|kme!XL)wS*4{0FM zL8OI94;@VtkuD-_MEZy{66qw;N=MU6q?t%Jk#-{e<d1=dI+~6mEk$~YG!^M8(pIFe zNMn)CBCSPw>u8#bbQfta(qE*(NQaRYBRxi%jC2`kGty_I(MYF}RwKPen(b)1jkFu- zH_~vV<4DVqo+C|1x{kCR={wSRr1MDYk=`TCcQoBc+V5!kk8A+41IQL2dw^^LvJ1#I zAp3x91hNyzRv>$UYzDF$$aXlI{XjOv(d-DaCCHv2n}X~LvMtEIARB}146-%I-XNQU z><+R$j%I(54RSO)glrMAN6022yM$~LvQNlHAv=X^6|z^zW+A(UY!|X$$c8zZ9YeMZ z*)wF*kX=Kz4cRwj<B*+0whq}lWb=^SL$(juKV$<P%?=`4=xFv3*+gU)k!?iw5!py& zC-K2n22~C6cs<@4)v7=B$@`wF+`K`-HJ{b_a4gManz1y88Oso1uFM?D<InD}hwnUi p^U*s@X6OH#%_nw~6%97)+A<;4ENe_=QfzW;d`xCSJjNvj{|lpI#FPL4 diff --git a/lib/pytz/zoneinfo/Europe/Bucharest b/lib/pytz/zoneinfo/Europe/Bucharest deleted file mode 100644 index 4eb7ed0dfaed722604aa3909fffeec9abde87dd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2212 zcmdtie@xVM9LMp`1y}6)u@4e~0ivRTcEACmXy7jbvN%OKja1}oBBGs?sO(7eZt0ve za_@{)mTtHlvu4B}lo=YSWVE@Hez?{g^Sf--nzJ@^t~pkp=ePc8u0PuPtM~2haXWr& zY`mU<<?9>r)omANKH<sjHBa8hN6gFdo-gB0%pS2${BqFx=<-We--R94$-%enQy;!z ze|+>syZ`kz`;$Ez?bD%E_L;^-_Su@b_P}F#_NPS??Q>ZP_GihlcKo|vSYxW~ss#Vt zL-!X9S&13Lkp~i@k))XZNYY48B>BqW$k?y;M;<)8Gm>)hJ(YT_Q>7hzS&eIdL8Z5C zR^x;9YQoB;YGP%%$|#<pCV8{e<kYe1p?f?k(_I+J4ENfZH#}C>_qVJmUthJR4h&ll z_eL%6k$x+CQm38W(PQQ8dZj9N%YMsOzqKl_w%g8c-ZoTF(x;~_c~uvt9n#bDLwfpX zSQn*qNYU_WJ@ZzJ%sN%7AGuT`#a(q$(l<+H@17@f-b|Ib4H+_T*GxUX+%2W`Df-dL zKT28Ys4jPZE)^M<b;Y$G^n#evdO>thKYryc{Y3XWy7GL#`1iHzg~txdqNXNY)xJ{} zKe<<y1lLOSjGzQ6D`aWXLRsdWBFk?2q$aUk*L*WZp1P5xmw#|wR$TPxm7Vdrw)d)D z6&}%b9Z_x7UDT_$|13|>Ij+~#o|pRc1A1*qw=~4PFYD4o*8TLFG>$gQ`oUJ&FdUF) zj;@g4xlMZGo)T&5s@Bgorpt4?3-zYO#k#p6NpH>@r?-^-rnkl`-IALqEx&%JL-*a3 z(9nQxz4om<-_xbHMf;@fz!}-zeMok+9+B|Au(;fJ#Qgmc>yEwcGrr@m56xG3+<Di3 z{ZJ}7Wd3P+ja6!@&t?9PQ^%`&|LJ|DKcMEDXIWV_F9GwTF4vz=evQi&_!o2e-CXd3 z9f!;YnU15G4>BQSM#z+qIU$omW`#@(nHMrKWM;_Jkhvj~LuSWc!1R#$ArnMqh)fZg zBQi-xGfQNe$UKpWA~WTfD#u(oCd)Bfj_GpDmt(>lGv=5w$DBDP%`t07Gi^sRZ)D=g z%#o=hb4MnR%-+#VADKUr03-uQ3XmKiNkFoIqyfo;qe%pk2}hF(Bo|0BkZd67K=Oek z1jz`J5+o-`Qjn}5X+iRWBnHWhqe%^t8%L8IBs)lYko+JCLNbJ;2+0wWBqU2nnvgsp zi9#~vXi|mb%F!eX$rh3>Bwt9vkc=TILvn^B4apjkHY9II;*iWCsY7z-Xp)Cy4@n=A zKO})j29XpZIYg3(WD!Xtl1C(wNG6d~BDr)l$wad0Xwr$~6G<qNQ6!~EPLZVI|2(T6 a)7tEOs_XR^<P_%katplvJfHJ!n&%H8T0s{8 diff --git a/lib/pytz/zoneinfo/Europe/Budapest b/lib/pytz/zoneinfo/Europe/Budapest deleted file mode 100644 index dfdc6d247faa6497db386a7714a32e3ac743e922..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2396 zcmdtiUrd#C9LMp8f0@kOmkf^v2t|Yx^6vnNL1rglB$&v_NF*>3|3rjD3`(Ptx#pPr z=9mr<MI*9i)E3$WS_##3ArVFf>M~Kl{xFK1Wn=YwKR2y))lC=uo}K6Q@SJhO_`KaK z))&WF|2R|38$O%{^WpiX7tO74nxn9>e{g)yK$|wT9M-)txn+Cbh-ltdyGxsQ_l4~L zs8df)bd`0FES6JmI<)72OFrKbFTF2^N$+)!vM(B{F7$2dC_KHiRlm&Wlm4VG`6{Aa z&dh#YzMi^M20V}Jz?CW){4rY34(G|m0|&>4vVShTwB=mcWoOuh;fim^NBoWyj=KA- z(VseXMpmMAi}Skrhkma!XPi@q*C};Oe4w+2-qG2ooAuVNS2f`1F$p}>CP91l$(%Zs z;7u<{NJ+WewsN)Hp6Qm*<W#vMGEu?;!{yFtvn1TdrQuiOb?)Ubjp(1LcYQKN?{5E9 zBU`TMy!t_ns{Kr(D~@VR(c2oEKOk}C?cz-7koaXgB_XIu662~Qak56{2h{8Q^Q$E3 z_ex!G%q91p&DG@A0!`^yp!aQEqzfAZ_5R{eT~wPS52X31t0+Jo3>(o@*QBKRoYeHt zZzO$eSTekhOGeKLd1&Z0S={!PWOje3S=;Mm$)WxFaA~P5t=pp6k8aatC2KV2o)UFu zrt2e)CAvH^LYH5Q(OmyD$vtzkK6Z7ktT^(E=Jor^%4R>wZ~s9aubGg7`W`7P=$BQS zztblczAvluyR|5|LDr<SY4MGRb#0L9+R<IQZn9k0pQzRi=iU0`!8|SbbfY}=N{W`Y z=E&3Qg0*aGf^5uAmhxhUJQF)dDpG%wXZ<XxjP}>c@rzP5{hC(wek|2v1Nz*%t@3<N zhi+=<)Xi;8`a*S!)@-j)PY+KoZ*w!3x0ku>pLyW2U-OmE4gdLNS$>zy|GyO&;^A#s z)5V(l_vgRfWM;W7S85J7H^1Rw4w?t|zgXKJ=4=>yFA!OstyvzjK4gK&3T@32ku@TV zL{^C`6Imy+P-LaZQjxXVn#CfkMV5=K7g;c}Vr0q4nvq2#tF|@EM%Ili99cQCbY$(w z;*r%O%SYC4YYM<$mI`nv0a62`2uKxdO&O3nAca6Gfs_KN1yT&88b~>idLRWsDuR>* zsR>dPq$)^RY)xH|!XTAFN`uq}DGpK{q&!G{kOCnULP~_x2q_X$C8SKYrcOwqY)z$* zQX#cMiiK1QDHl>Nq+m$Jkdh%aLyCq}4JjK^H>7a3rgBK>Y)$Qu;vv;T%7@esDIiio zq=ZNfks=~hM9PTN5h<jtsU%WLTT@G<m`F8|aw7Fa3W`(|DJfD@q^L+$k+LFnMGA{l z7AdW*sV!1mq`F9Xk@_M9Mk<Vy7^yK*WTeVSnUOjpg+?lkl-kzR8Y#A|sWwt>q~1uu kk%}WFM{17$|DyYv$j^%<_mNr7sD!AP$gJ2HjCT6|3H&I{GXMYp diff --git a/lib/pytz/zoneinfo/Europe/Busingen b/lib/pytz/zoneinfo/Europe/Busingen deleted file mode 100644 index ad6cf59281a1046d9dcd045fda521585e3e33e06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1909 zcmciCX-L$09LMqhq+=x|UkjT`&1!PZnmp6((5_jPcE=8#Ej!E(waeVJGVQV`Btqi5 zAVpMc%Z5ah^}y<Z9c&jJCJUQH7eUcw5kZ9=Nd4abC5WxZ{r}9ohV0?@{qfIST%2Tm z^*GJH@Zni)KK$;!(R^KTEwQfLFSD+;`>f`(xmK9_nfB^=M_mEe)b;AL_I_|g`~164 z`=0w<!%v=)h(iq$x#th*SE~}WZj<ycDVG7W7sx=LU)*UKGRTuE(GfB7L$}@%<Me9G zo8db6VYJ4!_R=92I_uEJx9ZvdREO2w(zq>GHGbtuO(;C9iTO7rsk~8=)0<>?&JIb5 z+$*U`m6F;~EhEC~bj00xGV()(jymO)(YNz7t-e6hn?~uFn(;bzcZ7~BcI)^pBV|IS zQ@w@Z@>BF<&G2?ert`99x$jBVi$^js;BT4Oa!G!E@R$73a8P{BXEb|ztxP)fr%o;{ zl_|BGb?WqOnp0Awxj&Yu-<PGox+du~PpnRBPtd%uOv$^^Lub4hEHjV4)>*B=GJ9XB z<TpN-In}SEpsq#c7PQK|^=&$T><L+r->ijEyQC<+L5sT_(}j_$3!m)NMIGh3_)?WF zx$D=Z2WDx>#WGp8HC;>VbLF>1QM$Y)Marh8NqMnLRwVY5l^O43Rj4Hu@nKr=^1f7t zv}@%*=cVe!O<i-eUe>lW>AGEKb$!EL-B7h(tG8EcCx>|h0>AfbSzXLgSyn`UN1$be zh}HGW-@a_W<;}?D%g_IEIP5R~w{JGc{E-h&rTOqX^rLwOy=>cvW!HmhkQ=r&cZ}RJ za?d>6G;-I-ZQGjrMs6IrbL7^Mdq-{_xqIaHk^4s)KsrELKzcx$K)OKMK>DyXjUb&M ztsuQ1%^=+%?I8Ui4Iv#NEg?N2O(9(&Z6STxn#PdMY)xxOZ%A`UcSw6ke@KH!he(S^ zk4Te9mq?pPpGc!fr?#e5q*q(hEYdB~F48a3Fw!y7GSV~BG}1NFHqtlJIMTVTX&vd^ z)-;cFkF<~Uk8A+41IQL2dw^^LvJ1#IAp3x91hNyzRv>#}Yc>Pf4P-lz{XjMZ*%4$* zkUc>*1=$s3TabN0HU`-lWNVPUu{E26?2fJ39%O%z4MKJZ*&<|*kWE5%$q~@Wyn)W| z{eB*%p!b#;CNocFr$WT){^f7xX~O>|>c5RL-@#_Hh9$CIp6ukfl(+;>c47j?CkKB5 Dj=i{v diff --git a/lib/pytz/zoneinfo/Europe/Chisinau b/lib/pytz/zoneinfo/Europe/Chisinau deleted file mode 100644 index 5bc1bfeba62d14ffa8995c1d08ba7500956c494a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2436 zcmeIzeN0t#9LMqRjYkpbx<)*!VFD`hD2gRYUcnTuUIdX$@RWooXrW@5fWJA%Xd0SW zvtq?sQ`wZ^Vw8eLWy*?x8e28A&ekk@n4VS6mbv=94`bE(tG52@b9e3=?wE7V?~8&p z8_QDUZ*%HTcvzEu_&P@Ex0fqk34EjDWB=0&el$*BZ!yk%@r=<uSa0<7wV7w%e9=62 ze4qK&ky^92akKe$O^NwV`3m#hqD=GrgURN5>CxtexVy}Yq26X+PqXp<lXaf{uXCIq zwuKxVc-7?`JT%)mwEUnE9D8i&?$Aq_A^sztkYDcX4gEG~C~U-)8Q$MK6w%XaMV@%p zifY+oP1?WBnp|6IO{rXE-Lp2^iq4&DO`RWS-5VcfO^Xb$V#eRHVqCdqtWTPXy%D40 zu7s%R##Lkb@b|`y^Mjt5odZVvYo|Q34tE#{dz(Fp+YcE@rPZF~;&wA->#m{HjFU2Z zWuv4;9gsOGn`F-IDoKxMQ0bTRW!|lFb>G=ExqoDtntwD;Wpu}@1r4cc;Y&d(vn*IW zuy>{`%DSmsr4wZFw9izQ>$+t7bgLy(KawTaK9!|juglVbGqUW<=Om}SMRNO3DR)!7 zEI-k#R%|iEv%gy9t*%uoD_5)hxh1L~cb-}mlBo*gBUItdXjK%PE=8YstA}oc%bKpQ z)WgHS%Gy>hDenASN_Jn7b%%S!Sa(q#sk@{eUDzh;i_fXj$@}E7j8;|Ve@tzN+M_mH zeOi^@E>#=*D%7US*{b5WTUB0IFPj?^)s~}K@_6|~^+ZFgROKbe*0OQ3E!ipCv%ZsR z=MS<Y(NFELhNULx8&xyZB|EQupq}bDBD)4os@fMiRbBf5RloCjwY#ZGIqvZB^8V|~ zcbuQUpa1xP(O2MvKVH82Cw+YX!<R^ezy6Ob;XjGDNNA1d?`9-90!1RH$i%Vt(NBV$ z63i!#u(9{_OvfaCUZTg|$37YR6LQ@J;?mC|{bXh3^QJ$rAN`Fxf3bdY_zO53qmTUs zAN+X*|KKiv(<|)i`<Zf|lpCeosa<`mlzXMzEah$~w@bNS$_-QQm~zXMd#2np<*q5W zO}TH%jZ^NNa_f|Pr`$Z{?kTrVxqtp0Fo5C!#R9w91BwY07brGRe4rRXae`t6#S4lV z6gMb#Q2d}6LUDv*iCygp#T2{R6^bnsU+ijQD9%u<p?E_vhvE*!9*RE{gD4JBEMj=X zFp1%kU2PM?C%f7xhEo))7+z7#qPRt|i{cl>Fp6Ug%P5{POryBQu#MuIU2Po0IlJ0A zhIb6}817N*WB5lgkl`T3LW+kJ6DclIY^3;TR~t!j(yq3W;w8gOikl2O8GbShWjM;P zl;J7GREDb*TN%DmjHNhhS6fT*mSQf&U5dRFe;Ec-9A;Qd@t9#U!)1oe44)ZBGn}@o zt!8*_SDVdnn_@S^Z;Ig*$0?T6|2_YAo(JgP0<%*1eGu<XO-M^figza`(Ztk%-vMds BjH>_u diff --git a/lib/pytz/zoneinfo/Europe/Copenhagen b/lib/pytz/zoneinfo/Europe/Copenhagen deleted file mode 100644 index cb2ec0671a372099fbbb7fe8f049ef0fd70889ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2151 zcmciCeN0t#9LMo<@8yb-vM(xJizgEatt-!v8f10_W^y&<s-z;Ch*CsyGX|y6gHdbD zeRE9b(52>BIida_{R1DCTEm4`O7EDKv*pU3W^U%}VXS`d;~1{`+uzwaud~D0_=C?o zu(YN++xpLmG+($mE#~HZ^eg7kp47UsJ$$sk*SBPS-<7#?`U8)K_WI<^)?Dd-Emgh< z#>tnHM9((8A?M;dHN2uz!k500fs#`?aO}8@32nbN)<0Xu<@~9!DL-r6*ss;&I<21T zM|J$=5A~k2yY=1^?`ZttBa(2SOA>d!D@m;?$s1pl3AI7FZ~1DOSQ?O&f+CsZ&6m`K zG`WA&cu8~nH0?&NPW~=cr-bA5flnj#!S3JHyYEMx+IB(HH-D}f8xCt`Ri|cEoR{oi zx8xM|NbbUIl9#wk^0ONyf3R7m#kc9Si>qY%Uky6rh)*8=s$2^?Dz&g@hCb3VOJ}wx z=%dvsI&1TEd92v2zN&bcoq9!!e1lT#KBXlom!#zC_cF)ziOdQ0%Hx;clet~{rL^yu z`nR;oyaRi5eqEhB(fYbBSn{?mtX-pJ57la*v_uzq=ILVZ6kU8XQ_EwErF>wFK6zuZ zEIs&}E(=G=^4&2~(fy08Xud9$Z6R4%8J1O>e$uCA9+K4+eOi^gL)H{_Y4ymDb#0>R z+8?&-y1}5<^fu|!7X$jt`^&WUvwC^<twOEqD3j;bCF}Z@JgHw$Ai-*nJfD>$8;bsr z7h)`F$cWX3Yu`#^^i6H-KPgRD&+CgHb;wJh9^JU(xNhp&r7t(_)8;MB>U20Au87+% zr+dVmzbxzSKJyQ1B}{NcSXQ)HcMUyneHs2<>JM1HqB0%<e#2o7nwP-;&tatbAvfnu zbMqeaw>dQ}XS%U|)UUb7JZ#NGkeMJ;LFR%?2AK^q9b`Vpgpe5_Q$prsYbJ%v%GOMa zKaP1J6GLW(ObwYEGC5l_J7jvu{E!JEGeo9{%n_L+GD~Ed$UKpWA~Qv%ip&+6EHYbL zGhJl9$b^v@BU47^j7%DtH8O2v-pIs}nIlt2=8jArnZ2!<J~Dq>lK><GND7b~AW1;7 zfTRJ*1Cj_N6G$qMTp-CnvVo)n$%m~;2$B(7lM*B+NK%ljAZbDJf+PmX43ZioH%M}j z>>%ku@?&cfgk;FpqzK6ok|ZQcNScs5A&EjVg`^6}6_P9@TS&T)d?5)#GG=R1hU5%M z8j>|6ZAjjb#37kOQitRYNgk3tBz;K!kOU$bv^6P2a%gLkh-48-Ba%lXkw_+yR3f=_ zxSTF$g!{J3H6rrPUqih#)ik{{bu>|n7Hjm-^VXN)?{+o+RnFmbnztyE)2Ug6)$7km P&r8qr`m-`IBPZ$~7FsAY diff --git a/lib/pytz/zoneinfo/Europe/Dublin b/lib/pytz/zoneinfo/Europe/Dublin deleted file mode 100644 index 5c5a7a3b8d67f5a5c9e44a56e70a727967d972e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3522 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 diff --git a/lib/pytz/zoneinfo/Europe/Gibraltar b/lib/pytz/zoneinfo/Europe/Gibraltar deleted file mode 100644 index 117aadb8364cd7901388098503f4538c7b445aeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3052 zcmeIzSx}W_9LMo<Ledz=9-ROa#K#N~Lj+uMBTGc0fLu<ByQZQhk&+R(WKCvG<2e5s ziX0#xlh851C0udI4L3v)A;nD7+#R#bQB$<%`!-!w7v6N$`_6kl&zza}?ws$>Gi-87 zl<RL-L;DR6SGj#Sw|K{X<hCs~xwYOp?_h+<FW6ze$jdj2a#|Sk{zknx<F5H~LY`hS zbB`%VT5rDUeMc7tkI_p*%Js7LVS3r+TV}bd+AOc})n8w{ri*r`>f*hJb;;5sy~1Rf zl^;atRi7L(tEVQIZ_-zr(*EIQP5dyuHbR+oQ5k0aqraLB&63TApO2W07hSsS=r4NH z@gaKi`f9yp)jhNI^ELY0+yK2TGe>WqQLlF-XX%{-3e2u<!*zL&ZF+Zdt=V1oh}q}Y zR`%r#mHkygQt?(#Ik3tlm1C0CK{+jl(nd*Dx}U1L6QvGMzNf3lg_<Mrr*utFYg5yw zUTRLBQng7%y7r!zIU2q|AHDslu4^(?*ImA%kDv87-|w%~CwAU8Ka^fFCrkYFsq6~# zWBT*v^pxrP%)s{MCy$u3QH{;H=wf|7AXHy?%wzn$4v7EtEz<nV3VFQRNQ)gGOTebh zDzK<Twai<jg64~AHS1laQ`1%J5#!Vo2_Ds^SFCz6tg8wR3{h<xH&-FvF%oj4v$Q)K zETJ`i(tcM%d8+i5gcV$r4%u}QK6k%#oW4mS#urQE$YUxhy;OCIE>oQcex|y#%vW8b zGF8|5IjUPgwsbo&R&~FVAw4$7sGi4@rB~r-i7x9Q&&+#Hdglhpvng%lxw+j{pMKsF zGd@7|4L&ciG4-mS_g;x>b5g}!J*VQmwyF5aJ?e!sOVo=c%T+?fR!LkiUnLbSmY34f zRR8(!$$%jr$-vYJGU(}4@g&5_%l=6+I4o2K-;9uC-+n6jyJj-<MmsfZ?Qb%?#z&1X zP1VTKD{9o7>uPj%r5ZE3Mva|)NnYu_PK_H`A>&&uR1>00B&E?Bnb=Zf;)NWURG%)B z_hiYdCp<D`*>FiMpQc{>Fj~?I2dUR5wUVjxx~ORbda3jjfAvOWkeVKQTfN!Tr7}AD zO2)O*DznK=$vm`EWnDccGgcO=nU!TSYvFd8U6L<vXBEht1#{#f_k-SE?!Ru{^!lgw z8+bqb-`@A{|9;p0!Cg(1y8*fyxm<zT-I#A&O`WsvpXY=`kE_ohk1G!Q+Cxk%k3G2D zZnu4$*WGT<1GMG7y@pizG(Z~TXgh<n2I&pb9Hcu)dyxJh4MIAEv<T@D(j=ryNShpO zpO8j5+D;*@LVATX3+WcpE~H;b!;p?4Ekk;SG!5w*(l(@TNaGxB=aAMRy+fLZbPs7C z(m$kuNC%M?B0WT!h;$KYBhp7l+eoC7j<%IZFOg;<-9*}n^b=_)(osj-QlzIyQ<1JB zZFRJLb+nB|I_qd#i}cpfHW%rxqirwJU!=iEhmjT|Jw}?0bQx(g(r2X6NT-ojBfUnN z%}<<eJKA<5{YDy&bR20p(sQKgNY|0JBYj62k8~bsJ<@xm`AGMX_B-1CBO8G10I~(h z9w3{5>;ke4$UYz&f$Rjb706y7n}O^GvK@|gKadS^v^#=q39={1rXah5Yzwk4$i^T$ zgKQ15H^}B7yMt^GvOmZMIocgUw#d=$5wc0hE+N~5>=Uw4$W9?!h3plwS;%f7+lA~G zvSE&P$B->^w0nkZ8nSE1wjuk5Y#g$4$kriyhio3Qd&u@7`-f~GvV+JLI@&!%HWArH zWE+uvL^cxHNn|ULy+k$>*-d0Sk^MwA6xmT^OC9Z=BAe=HcNN)IWM7euMRpe1T4ZnW g|IPjP&GoTc+#!-N4omD5-X%ODEHN?yJ9hH<16u-G00000 diff --git a/lib/pytz/zoneinfo/Europe/Guernsey b/lib/pytz/zoneinfo/Europe/Guernsey deleted file mode 100644 index a340326e837ac8dd173701dc722fe2f9a272aeb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 diff --git a/lib/pytz/zoneinfo/Europe/Helsinki b/lib/pytz/zoneinfo/Europe/Helsinki deleted file mode 100644 index b4f8f9cbb57450549933f83ac90dd56a2ca75344..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1900 zcmdVaYiP}J9LMqh%+NyL(Hv|u%xpNev#Z<YHfM9$FguvbY}n>D!>~5Db3~GszG{&W zvX;c`!B9r-BI~5Igq9-LB!!R`zxUr0<&h`KIi3IOv`%~UeSbXjSCl4Nf4n-GzwqHz zX+C@p@tH^6`ZZzq{JBLfS6>u`Mz#5R_4NB3fmeKvkBz?G&(CU~2gkJUjeQz+>9T~M zZjgw>N2OnlO5~R9(!Z=i1}t1E1G7C6mFAW~&QysGkCDM$drM4EhQ@qO*4P)(I;6Fi z4!zY`hc$gwXWbheUi(<%cHYzY4VTnad`1%r9!X+FlO&}#OY*G!k`i%5QWL8rwcRTt z!)kS8+hQ5@y;4VC&X6%r@-?l#P}7@7>)2frbljnE9bX!y6LyZ0iJ3u~Q5+_dqF<>y zqg^tC?rK)lQ^|V&Ql<o6lPUf?GWGchnbvShvRkfb&fXfCe)_o1C@+_pH9ItS?jD_0 zR-$<$%G8scrL!H=b&hk0&iUff{LoCvf7nCkeU6p+=RfI!)?it9EJO;L-pL~GM=7lJ zOHpB~EZ+K7myEk0OAA`GIP##Bq&H}3mvg!-LUq~e1G>DuLRZ|W)|G7@U3GGSmfc<_ zt9Pesd3~O&SstltccsX>+%%~ub;$aJezL*+O*V#DQW+nrl^>o-RrfDib^oSRzkj5g z8tY}Vzgf2&ysldtj_9`PI`!`LYCvEI``t0<U%oBNQDP2?XGhB#>I&#$S>gSyZohxe z&hc22&ByJ|<Kf}=RzSe7r{^zD_lJ4qT^xJ}Ibr0CkyGYBa?Z#}BWG=EP8&II<iwFP zM@}6%cjV-evqw%JIe#PpBm*P`BnKo3Bnu=BTayQp2$Bhs3X%(w43Z6!4w4U&5Rws+ z5|R^=6p|H^maWMPNzB${hNOn%h9rk%hopz(ha`w(h@^<*h$M+*iKL0-i6m-kGDT9g zHMt_mBH1G8BKaZ-BN-zpBRL~UBUvM9BY7i<+nUUg)NM`fNb*SbNcu?r$OIrWfJ^~0 z2goEKvw%zkG7rc^ATxnXg{_$jWHON1K&Atk4`f1+89}B5nG<AEkXb>d1(_FQVvw0Z zrpDIH4Kg{lW_FP2LFNaUAY_J+DMIE5|KmvtHXAiOk+pK>B*mq~x#E+YISDTNTXOJE D35>6g diff --git a/lib/pytz/zoneinfo/Europe/Isle_of_Man b/lib/pytz/zoneinfo/Europe/Isle_of_Man deleted file mode 100644 index a340326e837ac8dd173701dc722fe2f9a272aeb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 diff --git a/lib/pytz/zoneinfo/Europe/Istanbul b/lib/pytz/zoneinfo/Europe/Istanbul deleted file mode 100644 index 833d4eba3985c0221daf6789c9ac30266bccacd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2157 zcmd_qe`u6-0LSs??shwGZuRtI&13UN&UvR(o7-A9o0~PyF}<xPJtx?Nhn%J;<69O~ z20PjiOu<Jo94ut&TC&6-CD2N56fv8vC!OWmMo)x+2>LBrQNyn9`-!0NpX%Sf56``B z&&K{Z?(<Hp-@3U}yX{O>Pq;bl>gIhYsa~u-6P;bzd1lw;v-Xiq8=a#&Up9Iglja*W zr|jOMu=8fP&F+gt%)W1K*>C-_+d2N>Vq>7~hB=V#x6iMfFh3be8iRu;%nMihjnALH z?p)j!G%g*fG>2=w#^rd9^ToWK_Ls#soh!i&_K5eOIdZMW{_5s2bM#`fF*a<Q<0tpo z-~V{p{GsKHJ+URz{Ap&?*1FF)o^2;>Z}N4=x9T-JW9w<tU;d7f+4`yoR31wPCZE%S zq2BJ`=n^MO2JCEo$jly@W90Nyns@Z>H}34nH}zdRjJu!eOx@E^q1{{eVk)<AJb7QZ zIhE%-mz)-;PK9<KFhXMoQUxDhvZr_D8Z-9pkTYLuk+Zh1mG?KVkh9|t$-=6LoU<S# ziwd&j+^Lf?><^3Z#557P8Wi(}e--mTx-N=Ojfn+4pNfTh+U25-x5Wd`c8ijh{bKQk z_hsob2W8pPL$ZAJ3-ZC~Nm)_)gsiyrq>Sc2FQVhiW##00vE+lf^5M}cQPo>3mcBPb zRPQPhHC>umwmBdk=_rto#;%Fljlap|MS~(%>&RGVsk6d=-l{A7TCcnDonG(j*XxG{ z^p)Qp)mNS9)8iM;Sq=Nft;XYrt;bqhbz^V4_4vA1tkv5$S!<$a^+deRTASBsHB}$7 zntm>^)_u0fXiKNl9-sHWTp9j9FRw2%@J}z_l;CZb->+%;5%rDK^0#Oinl``0Gey%1 zW@$N^7G37Kiziy{-=F{WZ}@GzA)(c)I~H5ROF}CyDOYzH|5Y82I)A)#f6x;DVk_z+ zN;kbba0S^6vKv>m9b`YqhL9a0TSE4PYzo<xtJ)T_FJxoL&Ro^jki8+BLw1L357{5G zL1c%>7Lh$7n?!brY!lh1tJ)~CQ)H{iUXjfryG6E(>=)e)^FgZ}16xM+jBFa&HL`7F z-^j*Y)y_F=-Bs-!**vm)SG9d)|40Ln4j?T+dVn;6Ll=-XAbmg@fph|Cg{$fX(hQ^< zNIQ^zAPqq}g0uwb3DOj#D@a?AzPPH!Ae}*4gY*Vz4$>W@JxG6$1|c0nT7>inX%f;U zq)kYlTvelxPPwX9A-zJHg>(yP7t$}JVMxc2mLWYunuc@@X&cfvSJgPAb4cr4Rqv4I zxvK6V?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^P()k~zA*rvYg|Hp1-RjH;{FD%RY E9a<Tx5dZ)H diff --git a/lib/pytz/zoneinfo/Europe/Jersey b/lib/pytz/zoneinfo/Europe/Jersey deleted file mode 100644 index a340326e837ac8dd173701dc722fe2f9a272aeb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 diff --git a/lib/pytz/zoneinfo/Europe/Kaliningrad b/lib/pytz/zoneinfo/Europe/Kaliningrad deleted file mode 100644 index 982d82a3ac959624e4cd5be0b33f40ed03a46e46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1509 zcmd^;OGs2<97q4xS2~S45jH+XGtEan%Em`iX<6fFzQ}Ug^Z*wk6+v1pBR)hVB5Ha_ z3oS#$2vUe@Q6{u#5j5E-2-y?_5rK=kn~034?_3j#Hnr||x%XT?24-N+@$Ws<;g&y^ zs86`rPJQ#9rSzlU?riIyd1(&Jjs>m`-wRy39|#QgTnRjh>k0(lH#(l(bOt7eypHD= z%N(KZ97ky0=$z{7X`4PX+A<x^eUp@TBeWv<RkbrV+~S;1e3U$w5K0Mqt5buQLTL}L zo32|I%=Ce7Gvn-WbLGi)GxNwEbJhM@GplKXnO&K0t}aS3b6hEsYqv>Wbh*m=l&kXJ zIn|n(SXJ=&iz>W3r;3Kds`zqHt?ijm>rRZRlJ-Hh{=idl9~+U<s!=K1eOk)X&r5~- zuvC2O5>HyM^1N=9jb9I{O%vs^IozNsM|P>I@glXQw^VKIPf*nzNore9p=|ekR^Ik_ zsmYmDKJQ1VjUHEZ*;7*Y;f2&k-jVvr2h#AiPa4Mtq$zk?HJ>{r{-(mt2qPjgYVnGR zj)`5eaK+hvUQ6wNf5qEHmV5NSA+Ag#N+iJ{algM8lyL6K<lIwj#ETS^$g;)f;)#)D zeJ0g9&scbGYW9oQ*UG~$&8;>1ZWJE}K7CMFQp(pF#=_Ik<L#IE|3RqldPj%$?SP17 zQOgAp3?dmsG>B{v;ULmM#DmBO5fCDwMJ*ykMu?CWwUqpP#DvHR5!9lV6e21_R*0|= zX(8f5<b?<fkr*N}L}rN45UC+zL*!-%&XC-q79AoxM0kkw5b+`MLjr(A00{vS10)DW z6pSz!aWDd5L}F2g!idG94u%m8BpgOOkboc&K|+GW1PKZf6(cN2T#UdVkugGp#AZ<k c$B53N4v!HZBS1!kkPz8_6{AfzTkN*|07r&%X8-^I diff --git a/lib/pytz/zoneinfo/Europe/Kiev b/lib/pytz/zoneinfo/Europe/Kiev deleted file mode 100644 index 9337c9ea27c0a61b1082f4be37cfb0f9484cf5e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2088 zcmeIye@xVM9LMqRSD@%LeCwj~0)!+L?SunF(IBz&U<RkBoJ1;d)`?m~ipHQcTshYo zxwn()T)J5?G^flqv_G)*W3H^hb${fVBUh`H%{lkos=0EkKF_bC{^+m%=)b<-eZP+{ zU&d~`@p?wrZfPm8{&C!G-f(ernTzKcyUp#S?|A%dpD*_LNMUmLYORdC<JH7lK}n39 z*qS-}c=Fz9Usrk4M<d?fCx1BYT1L`08mzi+_&_r2{4po{)P5)D$ji?3p6yQV&bX7; zy2iPGeYG>AZlRN3QQ$n__c=4OJ<hC&x13q8=Y3o7gLn6wFR#buUic#N;9w%=?|U~g zulG!>aL<8A(T?*Ph@aNtrpr>&J}9M?Lo$E$K`F~QDho<FWx-gtlxOv7`IQZ_=vId= zem^J=jW%jUf3sE&E!KzkF4ZM(WNTGRzAoLfNFJ$9(_lPHmd(7Xq2QQQr+ub1`QJ#* zjUQ!s%15$1aZw)q{%xrpI4O0*A82@AkE}TLrmk#jlSt1iy6Umlbam@StzX!x(YhL4 z<6WT*{yExkGoX!`)zbLYRDJx{0$F?R7hQMBBkPY%k*2}x@<jK~(%hSnSo0;>u<M#W zx#T^0s%co`xrb$A<$$)NpVdt{s+)c~q?^aub<4#refmmNpE<EkTR+(*&%RozZT<D~ z+~!=}y0=WWt*Vgr7O!kCo-R8=zsd7cEa@o9)DGu6>CCvPoyiN*b>p(WaHd~gObqGH z!yoIefus6TSD$w8>sEV0${nd`sVR5fKGW~|`}9n>eNLS8U!0beeZ>5KaZfe(JS*L@ z<_4@umX#rv@W#Gp{9ayV`^JAe{%q&)hC6>-7mixB<_d+PR=B>_3L1l<dVW}DUYZ9E z+jcMb>#*(UznH`y=4d$gX&jjbG7n@T$V`x_Aaijwli_2T4Kf{MKCWg$$c&IFA#*|| zh0F?>7BVknV#v&psUdSiCWp)pnI1AfS2ICmhR76=IU<upW{FG_nI|$)WTwbek+~w1 zMP`dk7n!fCnJ_YAS2JZ~&d8*ZStHX%=8a4onK?3bWbVl1k=Y~DN9K<t0Lg%>Ndb}r zSCa%J3rHG}JRpfcGJ&K5$pw-OBpXONkbEEsK{DcMQi9~f)g%SU3X&EiFGymL%pj>j za)TrX$qtepBtJ-kkPIOyLUQD4l7wUlNfVMMBvDAFkW?YLLXw4K3rQD}FC<||#*maD zIde5hL$c;-(uU*>NgR?nBy~vckmTY2*)v~)ZF@{B=atY#f4H=;tT0ei>JJwO+^6}T FKLL|r@~!{? diff --git a/lib/pytz/zoneinfo/Europe/Kirov b/lib/pytz/zoneinfo/Europe/Kirov deleted file mode 100644 index a3b5320a0bd139c07b8642c4efd7b98f57c6e8dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1153 zcmd7QJ7`l;9LMn!oAj2%!9^R}q&{jI`t+JKCDpV>(>AoGP$W21h2o<i2>y`{3PKCk zPAVvZiXw`^#pj@tRf4#PZ6~)X9B}f1oW;eW@qDj#au7G~&Ap#+Ng(9+Esh=;PpChR z8vBHcBWo|-ueznWr=FBTBdg<A%WJ|5Zf(tcw)UIvQTU@OuU$;Jb#rIR^|Kds<lbo= zy*{BE&K}n5PVLd_kLLA;eQCY1(5xFXb$U}<NXH^U(-c}V@jzVSU&GS;=JQy~%TLn! z_^q^+=B548bLp6VD4Qp4ihJmSY}r3+62~u_WX}!Lx%;F^MW;+xVz24?e!!&bPe}S* zpXpv2m95X3%(lghWbWjo=V^oV&ctN<H6__`M|Mm{%+CG~k~{dr^u=CFe@>f$z<tR# zzA*VOugqXo$qX*sHAC-b%&rILOkwV}44*${iiPOdWTjH^Rjv4S`UAhuYNy)qbNl=0 zcO0cUuGs%kwYbW!)WC?({;TP%TDg2*e&VxF_)KBAs9N2my;An-RLW;x_CSu}KWt}z zeue#z4f#GhB3mMRdfH8qU6E~(eUXikosq4Py^+n4-I48){gDQc4v-d*9*`zHZ5K!z zNFPWeNGC`uNH0h;NH<73NIytJNJmIZNKc-&DWoe;+ZNIn(iqYi(i+km(j3wq(jL+u w(jd|y(jw9$(j?NQr)?AI)6+JJbc(c!^olf#bc>UA^Z%{gV8)i++nx;m1PZbYN&o-= diff --git a/lib/pytz/zoneinfo/Europe/Lisbon b/lib/pytz/zoneinfo/Europe/Lisbon deleted file mode 100644 index 355817b52b1b05680bbb57e4dc8de358eff27a39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3469 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 Y{v0ZPotiXi*2uqcfM2Hof8Le;4LU9nuK)l5 diff --git a/lib/pytz/zoneinfo/Europe/Ljubljana b/lib/pytz/zoneinfo/Europe/Ljubljana deleted file mode 100644 index 32a572233d4850a4bb4b1278cc2d25480ffc6a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ diff --git a/lib/pytz/zoneinfo/Europe/London b/lib/pytz/zoneinfo/Europe/London deleted file mode 100644 index a340326e837ac8dd173701dc722fe2f9a272aeb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 diff --git a/lib/pytz/zoneinfo/Europe/Luxembourg b/lib/pytz/zoneinfo/Europe/Luxembourg deleted file mode 100644 index 6c194a5cdcb22da9319183df65478ec4e55555fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2960 zcmeIze{{`t9LMo{o5hyR+px)oTWbx?es3Xbm~mwD^TzU9X2_arX=f9b(~iiF^VV^a zM!RM<#Hfa8Lm1}7QcNf(ha;tqZpqP)v!l=Ro9cA(PyN$BeZS}XeSFXTvA=eoFXy;f zS+Tai9scGOE{+;=@f=uGwymzDOYxMFrt_Y)*D5crD_`S!bB}6i(P}NdSnqOA+2~on z=!&~6GQhK8{wyg^SnSz2DOoGR5<HdRTWp(p*gczDz9E~h$Gf&%&9S|8w#>El=m^`k zW0jt&%2?ZW_i)#am)qOkUN+nFjw|}bcXOAzsz*e+cMcin*cC1A-LZM@nl|6NYi?h5 z@A>w$`@J*E9kpk_bJR6la_p@+<=9uc%)S5hosRcQf*l9)S33^QT<SQKQQ)Xgj&dLF zHQsTg&jH8Lj5F?|=Y8A_1EL+ruMU+Hn}f8mEJZ$iHC|56*&!e02Fl07`st~a{p9q} zQ1$E4?D8w0rvA%2Nt>cnm3fKUHZxoUoE^1Y`ao$P_p^2g{aORteWpQPCp75BKJ9pU zyWW4wt(^|7)Zn_k60)^QI<GC2F2yQc7c3S>cCI`yVVXRc=9JLBsq#=nqJ)Kn%fqcZ zO1O85hTn|WZdbyzdsCqHINVYnss2$T%D>W{tIlg=(FYneuTG;gw`t78a}t|dEpbV; z5<luSN$9*z5@YivvAIxs1+UUx7p6+@U-Goi-V}N4(+urfF<Fyp`{?6K`fGAYi1y10 z)&51jWxybBP00+FfnirQHKkbwc^}ikp`Xj(>tDzauLClqu|b}=yg`OmZIQI&`!#(< zu?*X~UWe!8$cW;XbmZ7qbX4|q9sOvwI@1R0lR?9DOhk7bb1PaiIt-GGPXhF*o84sG zt{-%Kldnv0x0Q+2-^irG8!~xSqfD9HBvTh&)2EYn%e0BdHM8p)nVwXoS*>>JjLxbv zzFe&{n{#zmLxDbX!Kt%1jo0iWbLH8kNt#nJTIS5`s;(soGIwNO$;}Fq=VH3ZywsoM z`L;I6i|U|xo=cMNe@pXE)=R<lb2@)Vg}l&Ms|(f~)P+^+bWuUM7Op7Nd+g@)YH_dk z-OiS+?snei<Ll#VzT?&6p4NW<)oHVN*YiUSaoAg!pQ_k={`%}Mc0b;?1^j;h?G^J$ z^NRVFdH0X|oxhNl?zE+(j^^gHjpbpic`%3F&SmaA{CU{z&VNvn+vcDqNKq_JRgkhE zbwLV)RL0Vj2B{5F9Hcr(d64=b1wtx>lnAL2QY1@LC8SJ9osdEyl|o8|)CwsUQZ1xh zNWGAPAr(VPhSUrxnx&~4QZ}S+Na2vmA*DlVhZGN~9#TG}en<h43L+&$YKRok(o_*C zBT`4CkVqwwQX;iPiiuPcDJN1-{y!+FrKu=VQlzFxQIV=5Wku?W6c(v0Qd*?8mZrE! zb&>KS^+gJdR2V5SQe&jZNR^Q?BXvd!jZ_*bHBxJ&*p{Z+NV$=EBLzn)j+7j!IZ||_ z>PXp<x+8^0Dvy*NsXbDBOH+NM{FbKv$O0fMfGh#B2FM~HtAH#6vJS{XAS;0^1+o^% zVj!!5EQh684`e|s&59sPf~*O$D9EZH%Yv*6vM|WXAWMU+4YD}M>LANwY1Ri>AWO4C z$PyuIge(%WO2{%H>x3*6vQo%WA!~&!7P4B%av|%5ESRNPF=WY*HA5B+Sv6$Ykaa^A z4p}*5>5#QU77tlHWciTwLl)4|tRS+4mSzo+MMPE+Sw>_Xk%dH768~SyJA3)Bm(tg4 YYR?$fH6lGOG9fZLB0VM=qvCvj1?KmuQ2+n{ diff --git a/lib/pytz/zoneinfo/Europe/Madrid b/lib/pytz/zoneinfo/Europe/Madrid deleted file mode 100644 index ccc9d85750eaddf9b5eafac627bace09f4c72043..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2628 zcmeIzT};(=9LMoLA`1BQ1x2g_sF9%s$b*m=goZ#S_{2#`B#w%xg>)ctK7-S#ImW&< zs<AS0GHj%1g}H^+Qc0aama;}=v0Ry@cA7GKV)T3eyJ=+?U3Js{zjI#af7s3i+wc8x zE?ij@XZ`IOWWM3Wwb#7(?5H$vufEgj*<F`p)v3d?XLE67qt6fS#>*wvfiwF&P5a`k zgLSjrhqjNf-gtJM^`<+f@~yHR?&djxmB-Iq^_<Afw@yBs>N&Z}(cPL<?m9I#ukut+ zwdZWl7p}G!N<Hl-X1Y4+I^7@cjdGo<JmUUn%P`l+u3Go`B|o|@OkMBp3d`v3x{~Md z9n#+Id-Hj>-?f9?{uj#KgIYqm2fyy{45?3Y4c*!68MZZDr94sPTA@3jFhT>JqjY%o zbQux<iw1>%uOo+luEE~z8hqnj9d+rT-f_N8?>zCGI*uNbko`>(`pRwzt5O-gVY7@W zE|al~mdagOP6<y=m%AeqWn4&v+%tHTMEIm>M9(B0|MfVX&^c1?Z5^ohHUFfM^;a~i z=2MN{dR8ZvAJv$`0~(v(C2?iV5}(o{ljglB38A|rF>Z|{_EyMbM~zOtxLA^YU9D4& zrO5-I<!W+6fu^)f(FeCp)6|zkbZSw!PTQI!4`ujhTA@RxkGrhtX}yx+b4D}6zm&}D z-^dK__hm-MX?f(*UYXg{C|PaqX?As$%-X+4XP1=7oT@E4cfn4bSG-Jf?k`qnR;E50 zJWJ<CPSE+cVl+1>LvlX})W>?p%fh#BYF?+mEUF8T{N}6jc*PAVsOb<_L8mO<_?<3E zJt9l<+q7_Wtt?Av(jwo(x;#{M`L{3Wirz9^d3voranY$yzLuxOAC$_f9VuGUkRwm6 z7_IJY2~s*YS;~rn<>}ZkDNp}do(ZsI^~4}u?fFX9485gm&YhIC*SmDxp$1vs(V`n_ zkL$*!UAk#)y;f9LXn(JMf4RNA`VH{8%{|a}fS><=argE8R%K3%HB`L%qvdB=A!EFT z{ONNbf3<?`?{D)tE8A%;NYCMomw8I#Da|}tmKVR{WiI>o{&;Zyi__d^u1$%DE#y=q zCmT84$O%VIxve?r$Z1DTJaX!hlaHK!qyR_-kP;v@K#G7=!Pb-ksRL37TT=<76i6+Q zVj$H(%7N4aDF{*#q$Eg9kfPX{s_-AAEViaDNMVr5Af-WSgA@m;4pJVZKDMSnNQICR zAvHpZgj5MB6H+HzQz)cTNU4xoA;m(fg_H}a7g8{!Vo1r5nju9)s)m#esT)!_TT?lt zbV%)x;vv;T%7@esDIiioq=ZNfks=~hM9PTN5h<jtsU%WLTT@G<m`F8|aw7Fa3W`(| zDJfD@q^L+$k+LFnMGA{l7AdW*sV!1mTT@-6yhweK0wWbhN{rMPDKb)Jq|8X2kwPPt zMoMjKYK;`z)>IoQH&Sn;;7G-hk|Q-oijGtrDLYbkr0_`Pk<ufzM~ZK2s*jW(sXwv+ z$O<4!fUE(s2*@fR%Ydu{vJl8hAWMO)1+o~nW;KxIur=#}EC{k9$dVvyf-DNMD)|4h d{L`}do6(7iWqu;F<D(OzV<NL-V{l@;|L;A&?*9M) diff --git a/lib/pytz/zoneinfo/Europe/Malta b/lib/pytz/zoneinfo/Europe/Malta deleted file mode 100644 index bf2452da40314be196f61e6a7cdd48eaf5c426f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2620 zcmd_rZA_JQ7{~F$lLDz-_yDE{wa^en9z^gV(Uj1c2TM7bsVFL<7SSykL8%<eTs7vd zmf{$g_z=sc)D(4v)-q`{kr2bwaygw@+3eoTkSW^#dpqq#uX@v)?laEkaFmzV_vf9x zEO)r;A5UlV4L>|P%@3cu`pjF!sOwfmYvZ}f`lI&d1Fr1%-nwONsdAsI%6{M8x_Wo^ zwz(s%?Vdi_4SC6S<E5!`Y-fZvZA+7H^t?9b&(q@t;nL!1u}_Rk)NiM>NNZ%9buwm? zee$wTPlc38d(u9;{q&J@H{31OjZbRrn>TB%j`A}5*2QM~_G^1BSN$H_Z{bGEzjC$} zF!5z8@Qp${Xz06kr#?wm=g@&xa74Hjd}f3d($-T$4|nz5ck)l|vh{ag*Zrro+nVFP z`^!F6S+Lg^R#>UsCv5cfNS`A;hwO3nin^@fJ$}%LfMXhQ^)u~#;Uj(EM3wgW@*VAa z@Q}E7)ktLJ7U@^2(tqU|8IV^X56)dA4|%*2H8x!a#-_+1ceFg*rME-}rD^oF5jyzd zAdP7a*CF+tb!hE%jotB!4%=`><JKP4_!S2=A!oNHW}TMd1+|itS|=lBlu2^rW=R=d zBq<#wGOF(e9d&krjJ{E*V-BUsBj3-`vDNc6wQh`#TQ@<+SGe`j+$f!}cC<{K6r^c6 zedV!1moz=CLnZ}%t&^jEl*w0qk|_aS$dslAdHlk5nOgIqcp5*~jP<25ZP!+PB0pcA zEPY+4KmE4O$Xl$LBlFbjnXFGmOw*aMF*>s?L1*=vB(uKjuFqT>EVK9jp>tY8Wo}iN zWYzvA^GdGD{0&W#J-<~Jtol_Kjz1uavKlp~f4MA9t<l`zeYzx4b;-}0bZJL{E^8>( zXU}@|xew-O-dD@z`8QKFzdBQ1SlVB$b;+`P`dBH*jgS`;`^k#*c6lkxC57?5w9q~; zMcvx8sQHK#UpcL>d|WNBHr46M^255SX0xs?-k~MyOEkdG|K2Mg(7gQoc{i`S-ucb> zmwW%yKd94x{WAAdY3|A89^e<~a&;3|$ldol-~9c(C&TMXOV8xZ%U}4J2h9iXzqsDp z=CKZ)$U&~y(Ofce&B#R~SB+dYa^1*<BUg@GI&$sE#UodbTt0IBNCA)vASFO*fD{3# z0#XJ?QwO9FNF^LiDUez?nqnZ;K+1tmJ@`ODcuYlrk{~rfih@)HDGO2;q%cTj98GDE z+91V2s)LjVsSi>hq(VrEkQyOHLaKz6iHABNg+eOjXiA0D3Mm#+Eu>sXy^w+-6+=pf z)C?&aQZ=M(NZpXaA(cZ)=V)q&6c4E$Qa+@9NCA-wA|*s>h!hd2B2q@Ajz}SqN+P9n zG_^#E>1e8nloP2ZQc$F#NJ){JB1J{2ij)<pD^ggbvPfx>+9JhuG}T4Q>uBnW6d0*6 zQevdWNRg2$BV|VFj1(HFG*W7$)=04(O|_A7JDPeU1xG55lpLu!Qgo#1NZFCPBZWsQ zkCYy%JyLw6`bha5P5qGtKvn=*0%Q%4ML<>oSq5YskcB{20$B=VEs(`PRs&fMN3$Nt wf;gHLL6!tr6J$}4RY8^oSr`2OElj8xoneX0Pi#g~Tyk7OY(`=N#wUgT1<GOd9smFU diff --git a/lib/pytz/zoneinfo/Europe/Mariehamn b/lib/pytz/zoneinfo/Europe/Mariehamn deleted file mode 100644 index b4f8f9cbb57450549933f83ac90dd56a2ca75344..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1900 zcmdVaYiP}J9LMqh%+NyL(Hv|u%xpNev#Z<YHfM9$FguvbY}n>D!>~5Db3~GszG{&W zvX;c`!B9r-BI~5Igq9-LB!!R`zxUr0<&h`KIi3IOv`%~UeSbXjSCl4Nf4n-GzwqHz zX+C@p@tH^6`ZZzq{JBLfS6>u`Mz#5R_4NB3fmeKvkBz?G&(CU~2gkJUjeQz+>9T~M zZjgw>N2OnlO5~R9(!Z=i1}t1E1G7C6mFAW~&QysGkCDM$drM4EhQ@qO*4P)(I;6Fi z4!zY`hc$gwXWbheUi(<%cHYzY4VTnad`1%r9!X+FlO&}#OY*G!k`i%5QWL8rwcRTt z!)kS8+hQ5@y;4VC&X6%r@-?l#P}7@7>)2frbljnE9bX!y6LyZ0iJ3u~Q5+_dqF<>y zqg^tC?rK)lQ^|V&Ql<o6lPUf?GWGchnbvShvRkfb&fXfCe)_o1C@+_pH9ItS?jD_0 zR-$<$%G8scrL!H=b&hk0&iUff{LoCvf7nCkeU6p+=RfI!)?it9EJO;L-pL~GM=7lJ zOHpB~EZ+K7myEk0OAA`GIP##Bq&H}3mvg!-LUq~e1G>DuLRZ|W)|G7@U3GGSmfc<_ zt9Pesd3~O&SstltccsX>+%%~ub;$aJezL*+O*V#DQW+nrl^>o-RrfDib^oSRzkj5g z8tY}Vzgf2&ysldtj_9`PI`!`LYCvEI``t0<U%oBNQDP2?XGhB#>I&#$S>gSyZohxe z&hc22&ByJ|<Kf}=RzSe7r{^zD_lJ4qT^xJ}Ibr0CkyGYBa?Z#}BWG=EP8&II<iwFP zM@}6%cjV-evqw%JIe#PpBm*P`BnKo3Bnu=BTayQp2$Bhs3X%(w43Z6!4w4U&5Rws+ z5|R^=6p|H^maWMPNzB${hNOn%h9rk%hopz(ha`w(h@^<*h$M+*iKL0-i6m-kGDT9g zHMt_mBH1G8BKaZ-BN-zpBRL~UBUvM9BY7i<+nUUg)NM`fNb*SbNcu?r$OIrWfJ^~0 z2goEKvw%zkG7rc^ATxnXg{_$jWHON1K&Atk4`f1+89}B5nG<AEkXb>d1(_FQVvw0Z zrpDIH4Kg{lW_FP2LFNaUAY_J+DMIE5|KmvtHXAiOk+pK>B*mq~x#E+YISDTNTXOJE D35>6g diff --git a/lib/pytz/zoneinfo/Europe/Minsk b/lib/pytz/zoneinfo/Europe/Minsk deleted file mode 100644 index 801aead7d245b98f0bf90ec9d9a59d1bb53a8794..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1361 zcmds$O-NKx7>2)boSG(`W|56IXUa6w94&LyrW$Q(9K}DdoLWSMi$N+{ShkEjgh-3% zzg1EZVk9L*KZ{Ig(IRZJX%QGxtBAlw-AzPAOy{{V5wxjo@8O*1+;h2Hxc7PEyY~0h z${)$sCrr|!C;L@OFXP5|=-#N&JH59*m3-gfd3M8lX69Pd^ZZ<$C*{iNEhxX9T2lBr z;&rCGy<ZnSDx7ntjC3@zG<iN%^zf?byLs01j~q9bog6TiA5ECW2X~k&cDI-%o$Jlg zaD}<D$}r1(F0<_NKzI44!v2bPUbXtAQ&m3xq5^m4RMlu&RbNP|HA7Qs?U4yplenVR z?RhG-hsPw?cuVTGpOAY0X=$iEAPwISNvLR8h2HeYhHv}S#;JPQlx|nyv2ChxvPv}# z2i4|FZWZY(P+NuqvNiTuMHBhbT=q)Eq93KjF{xThUr6hRS!uK1k+zu!(*E|MbWDs$ zXY#h{I(1A2GntGn$38FDk?XL}|2dosehs^8;optCJP~77e_%15NUn&lSezo2HKNbm z9`SHuTbx}(5u1^{o88;|zdcpb*%g;AJ!3J=o(_p>Mq@o(H0wjX3Dg96o!D$l9qTH) z#Q(ty{RuKV?@w1i{D3$D@dV-u#21J&5N{yv@NWEpI0W&?qIL=56T~TqR}i-#enA|A zcm{C|;v2*{h<6b8ApSudw5UCVxCrqP;v~dNh?@{UA&x>kg}4gw72+(xTY|d;e+do~ zJhrG^CirYoJ5BH!;x@r=h~p5?A+AGwhd2-Mp5Q*je?kW!Js@-e(g%yW6NFw^)ZHNT agU}H|Pas_(gZf|pk9~24b)TB5gRURG6*U|H diff --git a/lib/pytz/zoneinfo/Europe/Monaco b/lib/pytz/zoneinfo/Europe/Monaco deleted file mode 100644 index 686ae8831550bb0fe033409c8b4df460fcd61a04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2944 zcmeIzYfu$+7{~ENMG<cgnqY!r5~7COAvFo@h-3nyo|IGs6H$|FC3Da;baG71aUL7S zGBi;UZ-`cS3%uX(Mpk2{<<#h;T^##2=GbM-_PdkQi(dJvH|@@z&+fiBug>}YyrZWS z#99A3S~zdGIQBXh_o4HgkL7t;L(9^NP1%J=dsW?Xd-cJm&3lXgwAYmPH0604%-VS^ z&AO<zX8r7RQ;|5|Y#0}2SB58=jS*#LQ+79dbF;Z-^UVZ%%k^Bd^+J}ZIx)a(JGs$R zZwxcr%ZJ)K-taZ=FP>^Xu)EqH7A>-CvYOaC2S=M-(bw$Vaq0G+Hud(NdwcD@KW(u; z`aI9nesSE?)$ca@_G~iy*W}s<-dSP}zH7{(;zDzHa*jEYooSAy_}a&M$C%@NSC|vo zRrZNX&C5>(jWMULc)vNlZHhT_eTbah9Hb5FJo52d336`n&*qb&K>0MYzqY9DW3}8G zY+6-J(AIBulr{@eRc0h<Kz@X_^>)y983W~^_}{gC*pC|6?kf#yd{%>Q?bi-hx9h{7 zm21Z%%Qd)epM+FZOQ%(9r1Mgh&^hy^OJR{bGIoM=P4`Mzzf^fNGD*TiBBWc(4ie$( z(TLj#+Wp&b?QuR(dmd}1kJbFDkrm%-uhL5zwcxZy&#2Rw{B0VWb5Y`oY9v0nRuYD< zki<@_B`L00lJ1sB@8DAHeR;g}`D3Q`-RF@fzRcEsmART++gG1lI6za%LbQKDm=0Lb zN1htwtDgK|85n+DQ$2TOknc%N3;SBqZeEkYjStD-hI)DC>UtScy+zW`9MFtqOC_^v ztqz?wO|q80uERzx(cy&?b;RR^>P=76XM-|zWMmH=c_&7*+YgfL3vKne+uddKu3vP_ zd4CyO9w0e2H)LGNEy*oyki6XUGJft4`h3c6nUHfv^Fvq4#N=u%@Y|`AI;l?j?roiX zw@9bd&(aqzdv)rjF<N+hy1ck3S*KNwke4Qhs=Y8#rVr~UMFm0fa%^Xrk@}mw5@5;9 z==M6(T#@3|ceME2QJHn~qR!q?DX%uv>YSB_b#C=)eQj2SmMkk#pQeo(KiJvC_x|bE z%&)ose|1_`(;LqJ(`poJHL<ML;^W7Y)$0CzNEe@g`){24K;B!ymi5nldWP4^a;{OS zBlzHQo;*BxoF~ij@o~T4Z@BmI@jgI5?m5TQ`qWaSBS=eJot_{~LArvp1?daY7^E{u zYmnX`%|W_@v<K-A(jcTmu1<@P9wALax`ea|=@Zf@q*F+%kX|9pLb`>t3+WfqFr;Iy zPRo#<Ax%TNhO`ao8`3zWb4cru-XYCHx`(t6=^xTSq=T+b3y~hWI!#2nh_n&uBhpBu zlSnI(ULwupmq9l<XeZK7q@hShk(MGoMVg9q6=^HdSER8>XI-7vBE3bLi*y%hFVbJ6 z!AOUZ79%}Inv8TAX*1Giq|r#Hkyg7py+)dibQ@_m(r={UNXL<uBRxl&j&vPqJJNTg z@kr;9*1I~rN1E^IbRTIy(tl(FkR3p_0NDd%6Odg%wgK4(WFwHBK(+$e3uH4~o!vmT z!`0ajWJ8c0LAC_h6J%46T|u@5*%xGEkexxc2H6{Ab6lO>LAJ-$*&k$skR3v{2-zcK zlaO6Pwh7rMWTTLsLbeLoD`c~f-9om@)!8p(!;l?AwhY-bWYdscL$(dsH)P|GokO+` z**j$OkljPJ&(+yKWCLBD9YnSe*+XO#kzGW#5!pxle<L4!BmJHE>=nz9MrOoEB}T<W LX2ix|biDsxtOUee diff --git a/lib/pytz/zoneinfo/Europe/Moscow b/lib/pytz/zoneinfo/Europe/Moscow deleted file mode 100644 index ddb3f4e99a1030f33b56fad986c8d9c16e59eb32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1535 zcmd_pOGs2v0Eh8&eK(rb!qglm&2)U$sA*0)IyEzjsUbNPSW%Qng3+OZ6j}@+wy8i0 zTv(<w>Y|s6YLnFvfkYNAS_B#d(ZT{b1Q9Ad&UZz$TD9&D_x_Go1;Ov{Z)$BR5`SH5 z^c!xj-TLO770{2~!?v;O6<<2~a%X1yzByZObnc(+f7>=aAe@1L@*#I{^@&i>RWve~ zaC~IY6&@P4`5GPslaD0WhbPu1O}P_eCL0pxR)vy2#ZM$pdfe;AuS}$j_ABe{Zk2lN zys}+9t=6AwR%vZ}Rr<jywV`gS$|%oP8}pM@rq!adV&|1T(k|^^lVtYC#6V8_(?HIf zIhp(Xv&_3cCG&%?WWm)Za#QC$x%o`LbToI%!b78~=v0p?cJ-+(dpcA}YCx419Z;p; zkE*hic3Jk$tDN&qa@*r9wSBT&mJfNP>yb@XbY;rQULoBr(Q-$pRqgamOV6<%%A5I8 z`aJJdRpcF6o$*Xn&%97I;XzgN`j*=Dp-a`?y`<{KZ_4`1CzZc0^@tH379J565g8R7 z6CJf8Dth5#iCy-ITe<9u<=^=89B&aK!>RufJR^iCykNxW^I6W7Jw}`mWo|?Nw{jgK zVewqmU?dA+O%tiVzt43T>5K2n+)F>t@7C4(MLl<;zP&sez51>dd5#j{^ZE6yUz(S} z(^$BcUYI8#{QuC`Pkrrs7#c%5Ls~<6Gu6!@-68EE{h8_pkq%9Di%5^Ax=Ex<q)q-* z`a~K<IyKd;BE2HbBHbeGBK;x_BON0xBRwNcBV8kHBYh){Bb_6yo9f<?=8^7Ab^A#F z$Oe!dAX`B8fNTQU1+oofAIL_KogiC5_F}3xgY3psZwJ{AvLR$g$d-^jA)7*Wg=`Di s7qT&AXUNu&y&;=Jc4w-$hwRT(ZxGobvPEQ%$R_cB-=#%wxuDqc3lb5KyZ`_I diff --git a/lib/pytz/zoneinfo/Europe/Nicosia b/lib/pytz/zoneinfo/Europe/Nicosia deleted file mode 100644 index f7f10ab7665e94ca44fd8cd98a362cd4b304eff1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2002 zcmdVaZAeuI9LMqNOjy@ye{0UQ>qF(rhpu|nY}s_Jnc9QbPV-i-GEb#fYtAi8r(5m5 z$Yg}XzY-#9P-GGju7RsTPxL@E2zOwMF+w`6v5iOxDx!vL=X;=6dll@>&gI_E<)ZKY z-(P6e#&DkJUr&tl3vZr?^XB{bW1l8}H+J}I+dH(^ihWjRkGpWq7~flHPS|zFdZp86 zO6yW9Zo{ZKvC1|k1t;6D=3h4AQ!kmXP3kogqK}#h54()l@9s1w|JZ1}aiziZo$Is` zPwudj4u!4c?s_|A+d^wfQ@K5LO{O)iBEwEC8fU%fkF}@!MywgJ!**IstdaKEYo`A; zY-Id&-^{%FgE4bp(De6yV`TN5GP67P897_`nt{4jBe$mC&I|6b@{84;m9@nxNNTZX z=e5i1(TL3P_2`_TbyE0Oo6bF7B5&WS)}p>zEj~L}-|3pK^A0BJyWv!w-&rW{mBnaD zolh1_|3gblMx`v~do54BE#)J>%cAH@vS{$SEWUeGmh_*HiW?U-xVu{_Pae^w&COzT z@6cr{cj^00^;-2-lZGnFb$LRiuJC8*iYEcBjxUqypC{@EkJDw<=|{TyrdQS+j+2^! z`?5CjP-=Sy#jL$4>$cz1_4CfihMF5%mvTVri~BYF^0(TMq}uT3er+6W(T&$Tbkk5s zKRmu#o33q^kG?F{=DsTVxG_aP=_-)T%Zj8WoFH3rlVxk^Q)!L!NLx<4wmtY&+9y2G zcI&EijQpaXo$8a%2hZxZ1DADs|5y4&N3TY9NA#tr7kfpI`A=USPs&1WF*6V~#^Xtx z;u-t=lV2)=Ax~*(6(1q~Dk{qT2))2<|Lr{7H~-F!BX^G6I&$yG%_Db@+&*&uNCQX* zNDD|0NE1jGNE@zBA4nreCrB$uFGw>;H%L23KS)DJM@UOZPe@ZpS4dk(U#?DLNM}fE zNN-4UNOwqkNPkF!NQX#^NRLR9NS8>PNT04wqe!Q&POC_-NV7<{NV`bCNW)0SNXtmi zNYhByNZUx?NaIN7u1@Pn@2*bsNcTwlNdL$NAUl9;0kQ|kCLp_jYy+|n$VMPL;p%J! zvKOw-W+1zPYzML*$c7+0f@}%0C&;ECyMk;BvM<QSAUlI>4YD_`&gLMygKQ77Kgb3l zJA`ZzvPZ}!A-jZZ6S7apMj<<eY!$LsuFhs5yXER^7qVZ-h9NtKY#Fj=$fn`{b=SPk a%w^><c>Z91c0qO^C*L2;4Y=QCdH(?jq|NOB diff --git a/lib/pytz/zoneinfo/Europe/Oslo b/lib/pytz/zoneinfo/Europe/Oslo deleted file mode 100644 index c6842af88c290ac7676c84846505884bbdcf652f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2242 zcmdtie@xVM9LMqRfk3fxZ-2m9fKZ61b@Chh#YJ=iGw(FzGExatM68SQG6w#LelX`6 zWA7Tv9JwqVv!>V|lz*VLe%NT?WhQf4t}Rwp8eJmMkFokZzs>oF{?kAG(dWDSKEC^I z_s4DbdInZ(sLQpkIdSF<A5OdZ@O<;r=GN&Nv^r01sp&iHujxO(NRGeZ)bQ(G`Rv7f zIq__Ud>%@alWXGS!l5+1xZfu~z3h>p9hvfTQ>sMjMSiJt$ffd2GCX@wF1t?2i1V2I zDiIycij~pGNu3zp8JXl?Ad~a{(1i30nmFkzbw(do=kU8aW$=*R^2Hv#^}`o5>Bvz@ zKF}>Gue>T#+f-7wJ|k(tkleOvt=#SlNP1DJOmi1XMzTw$-!w&BF<y0z<m-%YGj!%a zqTX>VPVembP2Kx`&{-X4HM8|o&DwNCvuh7(PSqL74fRN#r&scqy(9%GyQMI<NeahW zWKL3t&N;VQ=Kk5J^NxCD{+E?n)K#sX-g$c0_7W}bOxC;W(zT>@uG~`=qu$yiS&(sF zOTA-K7W0Xgr++QwL*L25==Wt|xKHjK+$)Q^-xOc}d+Kj*lf?&K(<KcJa$nnXy7YnP zby;woR?H4+z*nyKI~VJ6_e@<rnyr-yWm0*1qCPk>Lsq<VSyv9k%c?ySq^jqlJk&BQ z)g57}sUDEk+kVtF#fN2WRlnAz?viz$ZmqlFZC#(Dy8io}T0a)j4Smh}@VS6KvVWxp zKi(*h?(k?sSA{%QpQ?{<FOZE(izHO%lqYhg%BIra<;e+_G-f4eW8@oY8b7K{Cq9zq zp)<PqtuEOT?$xckKG1F5yY;E&ecICAqEU`0NA$SsTv0Kx|NUiI@srIT*-B1xjI*rq zV%>P<{?D7M?|uG&<t?q?7T_BWbI?2l{>5zmGAA@NEr`s=)=UVQ5i%uYPROK?Ss~Lx z=7mfQnHe%QWNyghkl7*AL*|D}5Sbw|MP!c1B#~L#nrZUOnI|$)WTwbek+~w1wKcOv zri;uMnJ_YAWXi~#kx3)7My8F-8<{vVb7bns+>yy6v$r+VN9K<t0LcK70wf1W5|At) zX+ZLTBm&6<k_sdjNHUOY*qU@8`LHz!K{A4*1jz}K6eKH1T9CXTi9s@hqz1_ik{l#E zNP3X`*qQ_(8L~AgLUM#83CR+YCL~WtqL54>sX}svBn!zFk}f1)wkBan#%xW>kene& zL$ZdX4apmlI3#mO>X6(a$wRV-qz}m-l0YPbwkCy04v{1xSwzx^<Pk|El1U_$NG_3N zBH2XJiR2SWD3VcIlTsw7wkD}aR*|$Kc|{V7WEM#+{!eooZwfpshZej2d6@;7*=~PM JHfH6;{|(X%Sn>b> diff --git a/lib/pytz/zoneinfo/Europe/Paris b/lib/pytz/zoneinfo/Europe/Paris deleted file mode 100644 index ca854351687d88b3919ff33138f0a71994356b29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2962 zcmeIzYfw~m7{~ENK@briG{gkOBt#9lLuwM*ilhRfZb~YGiKt0-By-R-baKqwIFF5E z8JehwH$*E`47}fPQnDH|Eho!KyN&%DbL=u^=X-ilO|P6@^ro|O&gblzo!K|L-=Am9 z)S?8}KaSS+6Hbmg`{X`+zI|C)kh82jqtujNh_==?thCl0e%!pV<Tq=5MNd;vu-R;w zA7D1dv^AUNWSYw41!nX32&*bG*=&g_H(PVNS=)T)nQb?ctnJtH&5jE>rux(Xv-9*8 zQ?n()?5Y@M?S9$Eyt!nWdCTf*y<M`{s?G7X_6&|Sdt<Lz`w}v(x;Bkg-QE4x{_nS2 z?|f8X>OVed8XEVR19e-?!S(spq1Tq0!>=22q_o%^oswsc<z}1XX+GA8-f`w+-__<+ zZnbslQp<|Q;;1WUt`C*7+d{Nyqg&p4B}vW=`~1qeB|n(=OWMl^+5NRueIHlAj!@IO za-s$<>nLp&rmM_M(V)U8ZR_cv?Xm{SeTlzlaKyLTzTKx9()_H3+&riquI|$NKdjJ> z$5v`+!vP7au8~e_*GuQ+D&cb%NSERgd0^Z`>6+=0h<@qvV04N^hDAxYfDRJn<JPEK zN!tC(NbPaHz4koes}I%wq|udMYp+$8G-lzu8auN=;|h0beBMP#D5;gi)Otx8v09Ql zt&^05Qc1Z}CcQ&fY46Juq|dLjwC@48Jn~7d_N&U*)cU^q=%N9dRvxDPiz0Nu!anlY zARl!XhRVRm>zeMqBZGWSYevLpl5yjj3~qiz1~)azkgJ<yXw7!XJab61RxFq7>J2(< z`gF-z{*n$Ky;MgOPtuVO7po^TL!SuA)=|+tbkyxQ%?%zTxfj~%lefCdn7u#h*z^7} zt|CbCYQK~5Wj7^%Rg)CtpO*>qzR{=B_Q}M&Gg=tFMkb}!Xp!F@o!m)v@>j3wlshFl zwQ;sSec7YawvN@}lQZO*#i=^IYNR|nC0wmV$ueVjKPf2+k>}z&%gpqj<@q3&%!&=x zS>_8V4ZN+T=Z?$l8y9uX?kahqsb1%<IjZw&*6EA0E46GznR@v&YkqGpZ@(74Eqq&g zxAgb(Zx!%gy<Dzl@%CSntGCM)C|-WN<3IcNdsmRl71qTo_%GLveCE&R+-GKaTsihR zI(;M;kIU_x&Kvu&%jM<u$LZWS-^9hs%X1GYxoaQO8iy=Hl7eK#(M}7J7bG!AW{}h% zxj~YHWCuwPk{={NNQRIUAvr>l<Y;FJNfVMMBvDAFkW?YLLXw4K3rQD}FC<||#*maD zIYW}>XlD&c8<IC9aY*Kn)FHV;l80muNgt9wB!Nf<krX01M3U%eXAwywl1C(wNG6d~ zBDr+5lZj*#NhiMu^2tF$k&GfKMRJNH70D`+RwS=TVv)=usYP<@XeSrRE|Oj(zes|S z3?nH<a*QMy$ug2=B+p2qkxV10MskfL+tJQ8l5Qm5NWzhfBPmC6jwBt)I+AuI??~d2 z%p<8sa*rh6(at`Sen&h1$OIrWfJ^~02goEKvw%zkG7rc^ATxnX1u_@NWFWJFOoyXA zAIO9_+B1So2{I?hq#(0`ObaqE$iyHsgG>!FH^}55vx7{Jqdh;!1UcF>giH}KN5~{0 zvxH0&GEc}vAv1+c6*5=IWFfPKOcyd=$b>oCGlonVGH1x7A+v@|8!~Um#33_>OdT?J z$mAijhfE(bf5-$n+B1kup`$&A$Rr}Oh)g3gkH|zKGl~CCB@-I>Z>G}U-qc?4ZhK*) Svl3&HW8$K-;^Qzj(f@Z_2E5k* diff --git a/lib/pytz/zoneinfo/Europe/Podgorica b/lib/pytz/zoneinfo/Europe/Podgorica deleted file mode 100644 index 32a572233d4850a4bb4b1278cc2d25480ffc6a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ diff --git a/lib/pytz/zoneinfo/Europe/Prague b/lib/pytz/zoneinfo/Europe/Prague deleted file mode 100644 index 85036de352d20683bdc6cd5e43ded7f1d5ec307a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2329 zcmc)Me@xVM9LMqRfyj?3d!xaT{464*PJTmZkl6{01XH<;R08!xEh4y#L1|bq=NfZw ziRm1YW{g!cY71)(t@U%X5Px2V<=S#(S~)j!T8-7``HdJY|M&al?(yNvb{iY7=kP3B zUz=t9?+P(bcyVnvFU}F0&0E(LXHA#?^rhV+ecIh~Kwo}ebx+$)9Sm*Mp>qr5@as+; z-shGh9XWFJ`D8ifi;`pe-l;jhDp*czj@6T;$K~Wp{fYhnU!uP(U%pE1ms748@^$DA z8F4ho$oXcGU%d?x-V~kYiPq`m^W~=OKQuDwXN{WvtvUk_tMl>)8h!RHz4^pmo$<+b zjX8KoV)yq+-0nRR->#Cd@i|GX^T{nMR?Dqr9!V-FlG|K)k{p{Nw@-<dlpwdJT*=Xy zKO}3aKT7ZTELiXCzoxF9^E#{Zw5GLvsp%UIYKHes&8!-cEMLE57Y<0yk{yy8*DZNj z&5}3TD)}*;ntx`c%>J`U=Nxj&-QQGdL2tDd4$RSew#?JHU9oy^ZIaGwn=SVh2dUc| zBlDBbX_0$Wii5t;lBDmX<l>J~8u*cv4iC!xXJ3^CeQ!wF(1%*Stz8!Ge?=dtua`yb zFX-ZjUeqOZYqa97I`x#5=!4FMy401bORr{VWn{5bo|>i)UzsV(-u+FN`@>|#-UzAc z|3w~Yy)4z8!%|c2mzA3?=&HHz$?B>h^(O3+HHCdz8*)I`#;LCTX{W9m_38S-7Jc-L zM<07_xz>H&D35O~)cW2Ed176HHf+h2#>EBVt98ngnenor=y!Q4!jh)+NNu|Gy)=hk z)#jt0O3TF&efsTQd1iP(H}3jaH}!Svvn@T^x~)|M907ro#&3r?28}%km>hf~Zp)gw z)%;ysv5AgJmK82m=zq_a<(NA0Nm;qaau-$b=CMl5H|BCU__8mD!*l&bnUCe8?W<$# z9Ql{I;!8WOVcn4nwk(YASsAi4WNpaekkui}L)M2Z5LqFzL}ZP~B5lnok!2$5L>6jm zR*Edu)~pp-EV5c;xyX8L&4T$YSuwI?WX;H;kyRthM%Ili+}5ldSvs<IWbw%Ak>w-n zM+$&c04V`d1EdH@6_7F@bwCP%RKnJj0;vU345S)JIgolF1wkr;lmw{>QWT^rNLi4& zAca9HV{1x-)W+5n2dNHH9;7}<fshIzB|>V16bY#kQYNHMNTHBQA*DiUWowFsRLj<s z3#k`UFr;Eg$&i{MMMJ8FlntpHQaGe?Na>K;*_z@Z)w4C_L+XbV5UC(iLZpUB5s@k) zWkl+T6cVW<Qc9$jNHLLW+M04A^+XDaR1_&GQd6X;NL7)tB6URyi&PdVEmB*gxJY$v zO?i>}+L{6*6-G*o)EFr;Qe~vfNSz&lCdNVIcYrxg9(xcN9C9P>fAef2ZSrg)ZT=Gp v7wexSkDpC~BPRZoNH4lhs3(@%oWo4RXJt}zS9x|?Zd!(`JTn8+v%~%dH&JpN diff --git a/lib/pytz/zoneinfo/Europe/Riga b/lib/pytz/zoneinfo/Europe/Riga deleted file mode 100644 index 8495c506e8cecf39abac3f8322a8723184ae6f1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2226 zcmeIydrXye9LMqB2`D1MuPr<fAR2PfkUL5Si5-C%978w~sfZb%7Lm6cl**Yhv&PuB zVl`JLnWM{$S~FXr>oV6Uk<LG?HM+Q5(O9$7$~oJhe(wiZYyHt$|Mq)!p4W38#>O9f z-o7<knsUtF&J6p8565pmJg4yR!n==69O>wq=s8%rx99A>iPuMe{(9hzh7F%?Y^)p{ zEthkzx^?KNM?QNhPXharC7_RQjZS?zFe~QEVt4pRrTe!TAH<AB1!6}$#WxK;9f&*C z6O2FpWH90ReZkqCJA#S38-jD1R|n^=D-GUUu{fAim=>Iu9ve)Kk2NWg5hgV(-=BIZ z#h><_+dqFe+`r(=75^<K#{B8WM*IuA2mKixr~R2b`}|oANBr4!XHAaxl*zd;c0RYL zU-GJ+k^F?ivM8rj7LB({L0q>My!X@jC8s@d+eobz_SS1r{}R3Zz%pI>T)Y-HCF!z` zTjh?@F!eOV$@1h;E%A&?Y1k)Pmh_F3UHo2FgnTF~h6d!$3ol7|-)mAa_`Z6(I%VbY z7xk`|7OCvqudD8ULaUk^wR&;0`YOtFwR@%3q|euy%UN0*T`IMo-=OzgN|QAwf7P|a z5wfmlrqrGIQP#KrBK6%vvY~!h?%gw{8<)N#_tgz*L*gN6Eb7yy@V9kSg6gIpp4H9c z+jYx8o8JGGPak+?tu}wWO&&a0q%FPG^3di)-FhHjwyi3Z?M-gkkv&^>mRymCXByd+ z8Lhj5-%4xLWo-?dk+zFp=p%3T%A-U5y8F;ax~K23?rl4!?OpBay2@T5q1S|6ZLiSq z$?MvPsq4DPzg{8NM{!Lt`Q^XejhS`S{tI#sHD=yhu5e@G=a@)i7GxPQCQ8hWym7@$ z-wTRe3DZBFes<0M^S7p-E4)5aWj__wK2uWSGv4ZQ<FR^5><e%8a{JOgILGBO#^s#J z?^Ab|%l9vq^t(MZC(o@%7KN+|S(c++7qT#9WysQywIPc`R);JPS)Zd_Ab%t)M3#uG z(a|mvStYVeWSz)Dk(DA#Mb?Te7FjK_Tx7k-f{_&?OXjd<4vTiQt45ZMtQ%Q4vT|hU z$l8&`BdbT2kF1|V0UT`wkP;v@K#G7=!O@lhse_{}1X2m46i6+QVj$H(%7N4aDF{*# zq$Eg9kfI<}LCWH2>w*-<(N+d24N@DVI7oGn@*wp=3WQV$DG^d5q)14WkTN;iIw6H} zw3R|ih13cu7E&#wTu8l;f*}<{N`}-7DH>8Wq-;ptkit3I$|0pgYKIgLsUA{3q<%;N zkqROuL~4i>5vd|lMx>5NAsuZcky1L^S|Y_ns)>{nsV7oUq@wtLN&l&&5jMdKb4YHw UH#Z|cBP%mE-J6}|Jmp3F0Y>aD<p2Nx diff --git a/lib/pytz/zoneinfo/Europe/Rome b/lib/pytz/zoneinfo/Europe/Rome deleted file mode 100644 index 78a131b9fff014c3ea895a9cc7bf4197462b6602..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2683 zcmciDdrVe!9LMqJ!Xt=EUo^!8#3EA@5kwSBG(}`4Sjv-{SG=JX(UFj}RJ`TNG50M^ z$JE42u8UAB%r(?jsk4b_7?zcjWoAWlXEUb+XutPij9LBdIXvg}a2S97-XH(8#W{mq z|G5Iq8$MhI%!lutR-4E6q+8bZ+N!!8$4}Wi54p16e*Lz!t2CmnH2WQU_o}k&Ju`+{ zdoy}upUh3PtFDfh)9;08_1c2E>OHA)=FP!!=JJWUvw5@hoBVE4lTl-z8xgPHj;oQ{ z$eY&re%tKx^{e&!_FJVP;h^1c;aFW`M2*$>S%uxyRADujY_)G+Icwip`-$Z`{;}<z z`<CU|G0pOhe#r`Wwb1tU-)y(b+iV4PSZW8Q4YPylr`W-<Q>@@q$#%z(WUJ!`;dZBA z>g<q>&Gvn#n=L8mVTG1mvN~rqS)Hr5*<CWn$o&a_YuD&{?bhWd4fCGXu<J*)`{fVy zfpewW<Eu9`{O}QpI8ZK;J9bIWVwI>BYou3hzC1W%o;;M{m+0YX@^H)$=^fEW9_iR! z`uI|{&yB&__eyW=SKCb=Jszn2D{g7bzTb7gmLD{B{V5$-a9HE!mudXW3o<CbLK0Fc zW$>h}k{G#Dh74LJLz;^uDSV42U7RDyjfFbwNUA*c<5V5Kf3~Jn4$~1EM(N1y5&C#e zw2oSzEThNzG<ANsjOl$<(^8vdtnX`0kG>@7*M608-Y;Za^+|c+@?IHV{=Q^XeXg0C zie<uq-TGu+o;+2&PA5*;sFQLRXx7kN^=G8((_s^Ia!fy+d^1j`b{#8Izw4~e+~_OQ zKK)Cl*9Oas(omUM@u$oxx-PS~R7>{kTA8!*51l*mkj$G|rSqdoWI;-~<^&zog^{WY zf7_;un)7w>$>sX&MZZ4x?sUyPu|%GKEk*P8XUPkTqSV@uC`%>|m;9VCc`?4H6r?rC z(omNa4(zIh_Rq2`<fbk=drX#JyPz+Bv|nDSuGAGJU+T*8ow{oIJ}uf*q#k$M?G14A z<Msr2ox9bR-|l*PeC_W2?Q(UfFvrUk(aRm+a)pSiee1L3P22m7Ous8NEsKYr|8ScN z%@_awALbzQM?PF-=EL{UJLXC`+OrC+!)q+$a66g<jvR92pd*JJIq=A#M-D!6_>ll0 z5kNwK!~h9`qlp3%21gSIBoIg>kWe78K!Slp0||$ti3bu8BqEL`BuGpgO;C`iAYnl# zE_@*{TqZI=Xpq<-!9k*fga?Ta5+Ec(jwVD%j2umnkSIBtFd=b50)<2h2^A76Bv_6n zT1dE%cp(8pB8G&_(Zmc18WJ@mY)IUYz#)-CLWjf-2_6zXBz#ExkN_ePL_&zf(9r}D zi6Rn4B#uZRkw_w;L}G~q6Nx4gP9&a4K#_<dAw^>9Xo8AF)zO3%i7OIVB(g|ok=P=^ zMWTy@7l|(tU?jpwh>;j0K}MqNXu^!d+0g_Vi8K;wB-Ti<k!T~~M&gYG9Ems*awO(R z(2=M+ny@2rcQk=VB9DX~i9Hg0B>G7Bk@zD6fQ$e#1jra5gMf?zG7QK#IGTY#MgkcM zWGs-uKt=-@4rDx#0YOFt84_enkU>F41sN7(TpZ27AS2^wh6WiMWN?ttL52qzA7p@# z5psJyZFhjr!;i=73vljMTYkIi>1`Ky@9)+XGFxO;$ZU?8iV$&iYJIl6X?xEWP5Rd! fwGEQ_7HmdpKs<95lbH~k7#kOp86SrO6N3K%dd(YT diff --git a/lib/pytz/zoneinfo/Europe/Samara b/lib/pytz/zoneinfo/Europe/Samara deleted file mode 100644 index 97d5dd9e6ed7fc924c9bcb514f991cc8b52061b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1215 zcmdVYPe_wt9Ki8sx_@jDUTVuf{WDvey4KvrtZ6gTVQnBWIz+((WzZjJh=&fr1T6?6 zLLw-Nkfc+H2RoUxL(s)`kZwcxL3D|T5p^hu^?cvir6B0o^X}pE?B(T!?f1=}x^O<K z{#agfhs_!=n{(5w>YaQ(=N;V=xL?}pFGqatH)-E@+k*dtDs8L8Bh4$<OD!*Er1ja9 zv^|`V?YG8c$F-BP^KwRZoleT`Y*5-$&9bM<D;=$#>R#`9HQ)#o0$=@weeZpfLG@Y% z-+t7gS8KX+v8=o1Uh3|<3pzYKtM^aL=*YP#ec;TzM8|JRPv0Ghowy|NwsA>BbCURx zmt@ODom@*u?|N1rT=vVMN?50!#&zFPlkUIa(}y2?*6FctdSH6992u(U!LwC4+Oe#M z23KX+@mOct7bWv)Nk$s)$w>K;9D8?Fj?Wh*yYi%vyM3ivtkr6^hQ|73cWhivm(%5T zHT?SeH=QoKU8(RF^Jl71M459kt=vitkJ>i<ezuwW^=Cp6oAo4jcs`rUtIkM|*)g-@ zO4-b(zBq2I{67rV{H_|qMFz|(7&0<wWZ0Hw;K<OC!6U;*0ze`_LO^0bf<U4`!a(9c z0zo1{LP26dg0VEwAmJeKSek&4h>(zwn2?~5sF1LbxD1NGkjRkGkl2vmkmxK;cu0Jf zCO{-YBt#@eBuFGmBupeuBv2$$Bvd3;Bv>R`BwQq3OA{~>v84$ai5UqRi5dwTi)Qx! OP28T8iNC))=J^Tns}05g diff --git a/lib/pytz/zoneinfo/Europe/San_Marino b/lib/pytz/zoneinfo/Europe/San_Marino deleted file mode 100644 index 78a131b9fff014c3ea895a9cc7bf4197462b6602..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2683 zcmciDdrVe!9LMqJ!Xt=EUo^!8#3EA@5kwSBG(}`4Sjv-{SG=JX(UFj}RJ`TNG50M^ z$JE42u8UAB%r(?jsk4b_7?zcjWoAWlXEUb+XutPij9LBdIXvg}a2S97-XH(8#W{mq z|G5Iq8$MhI%!lutR-4E6q+8bZ+N!!8$4}Wi54p16e*Lz!t2CmnH2WQU_o}k&Ju`+{ zdoy}upUh3PtFDfh)9;08_1c2E>OHA)=FP!!=JJWUvw5@hoBVE4lTl-z8xgPHj;oQ{ z$eY&re%tKx^{e&!_FJVP;h^1c;aFW`M2*$>S%uxyRADujY_)G+Icwip`-$Z`{;}<z z`<CU|G0pOhe#r`Wwb1tU-)y(b+iV4PSZW8Q4YPylr`W-<Q>@@q$#%z(WUJ!`;dZBA z>g<q>&Gvn#n=L8mVTG1mvN~rqS)Hr5*<CWn$o&a_YuD&{?bhWd4fCGXu<J*)`{fVy zfpewW<Eu9`{O}QpI8ZK;J9bIWVwI>BYou3hzC1W%o;;M{m+0YX@^H)$=^fEW9_iR! z`uI|{&yB&__eyW=SKCb=Jszn2D{g7bzTb7gmLD{B{V5$-a9HE!mudXW3o<CbLK0Fc zW$>h}k{G#Dh74LJLz;^uDSV42U7RDyjfFbwNUA*c<5V5Kf3~Jn4$~1EM(N1y5&C#e zw2oSzEThNzG<ANsjOl$<(^8vdtnX`0kG>@7*M608-Y;Za^+|c+@?IHV{=Q^XeXg0C zie<uq-TGu+o;+2&PA5*;sFQLRXx7kN^=G8((_s^Ia!fy+d^1j`b{#8Izw4~e+~_OQ zKK)Cl*9Oas(omUM@u$oxx-PS~R7>{kTA8!*51l*mkj$G|rSqdoWI;-~<^&zog^{WY zf7_;un)7w>$>sX&MZZ4x?sUyPu|%GKEk*P8XUPkTqSV@uC`%>|m;9VCc`?4H6r?rC z(omNa4(zIh_Rq2`<fbk=drX#JyPz+Bv|nDSuGAGJU+T*8ow{oIJ}uf*q#k$M?G14A z<Msr2ox9bR-|l*PeC_W2?Q(UfFvrUk(aRm+a)pSiee1L3P22m7Ous8NEsKYr|8ScN z%@_awALbzQM?PF-=EL{UJLXC`+OrC+!)q+$a66g<jvR92pd*JJIq=A#M-D!6_>ll0 z5kNwK!~h9`qlp3%21gSIBoIg>kWe78K!Slp0||$ti3bu8BqEL`BuGpgO;C`iAYnl# zE_@*{TqZI=Xpq<-!9k*fga?Ta5+Ec(jwVD%j2umnkSIBtFd=b50)<2h2^A76Bv_6n zT1dE%cp(8pB8G&_(Zmc18WJ@mY)IUYz#)-CLWjf-2_6zXBz#ExkN_ePL_&zf(9r}D zi6Rn4B#uZRkw_w;L}G~q6Nx4gP9&a4K#_<dAw^>9Xo8AF)zO3%i7OIVB(g|ok=P=^ zMWTy@7l|(tU?jpwh>;j0K}MqNXu^!d+0g_Vi8K;wB-Ti<k!T~~M&gYG9Ems*awO(R z(2=M+ny@2rcQk=VB9DX~i9Hg0B>G7Bk@zD6fQ$e#1jra5gMf?zG7QK#IGTY#MgkcM zWGs-uKt=-@4rDx#0YOFt84_enkU>F41sN7(TpZ27AS2^wh6WiMWN?ttL52qzA7p@# z5psJyZFhjr!;i=73vljMTYkIi>1`Ky@9)+XGFxO;$ZU?8iV$&iYJIl6X?xEWP5Rd! fwGEQ_7HmdpKs<95lbH~k7#kOp86SrO6N3K%dd(YT diff --git a/lib/pytz/zoneinfo/Europe/Sarajevo b/lib/pytz/zoneinfo/Europe/Sarajevo deleted file mode 100644 index 32a572233d4850a4bb4b1278cc2d25480ffc6a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ diff --git a/lib/pytz/zoneinfo/Europe/Saratov b/lib/pytz/zoneinfo/Europe/Saratov deleted file mode 100644 index 8fd5f6d4b881457d13fdcdd35abb6fc5429d7084..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1183 zcmd7QO-R#W9Ki8sxiy;|x|B0Fd$GB6T5E1HYuaqSV9k&i5mq3*2tm+~@K6vaWS%N0 zf`}rDMwea>b;@K!mq<Nzv)~V%dLe$7E=jHD`xj3gqFc{@&;Rr1*%)lUZ(-=fNW%QF zR@f6ZtIKYlSKT%3<Ijs#gR7%AN^631@#@OiZ1oS%)8J=Qs+mv4*Unrh)lOY?LJ!Y7 z;aj6l-Nob1x^w%T^(XtB4TsXs#(bkwpV_RNnrk!?3TQ*sf<}E&iGB}C<GZiJO|QR5 z?Ad#1F3w8JwQ1Qh@kF+c-jVpRE3)nIlqODJ*Vc~Pn%s9*Q{i!KOB~d;pGP!Zdq&b9 zy0v{_NVdOh&>iy`$=uIL$BR1YoQ%lMn?|xDe(9PB>8_qnk~{iKyCZL<C+BFd?~(M? zztX;MZ?wOnsQq&fboa+e-Sha8=4bB7z~xg~$cKjy<o3!~xm@;CEL*(1KKEMg=khM{ zx4YNx^%@g%|L>-_vCqAOo=RiVS+jEKzI5WTCySrq-TXko#Nw@Xr|eD|<FPLm5AG`b z!yxVNC^JlCpnL&CMFxuu*VPUf88R|xWZ1~Sk)b1lM~06CfJA_VfW&|Vfkc6Xfy99X z;%Xy7LP26df<dA|!a?Fe0zx7}LPBCff<mG~!b0M5wSgg#x!TZ>*pT3m=#cP`_>cgR z2$2wx7?B{6D3LIcIFUe+NL_8HNUW|lSR`5`TqIs3U?gHBWE``z|8HXsWNhS}Ey=)d D1dR{C diff --git a/lib/pytz/zoneinfo/Europe/Simferopol b/lib/pytz/zoneinfo/Europe/Simferopol deleted file mode 100644 index e82dbbc78647086170f0d291ee449122ed18e875..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1481 zcmd_pTS!xJ0LStF+N`-u{|}Wew=~Tzre@Q0n$~PiF}GT7U5G9V%m~DXjOZas%6bTm ze<i(ivl1hbD5zd6x*(Bcx`*B@5`u(?IFC_C{l7=_rHAObb2#U7JYypG{Y7^jYVwFb zjuicdizB2jp0nEYt<Tw$`KZSkdt>X24^Py~w|7(3$SuDdy;v+qZ+OQSEWZ<9nD*Y6 zY99-y{z!SA_9Z#)9P|4Y4PT0<Kj}7H{TB@P)l<gej#eY%c%zZoyv<m$yT-^0mKxa= zD~zRiP9w+VFmkT9hL?2~7%Sg>jpYuGhF3itiRJY?2<La*kLAC76)$K%<6C{CJzUs$ z(6?q!cieNhS9pd_#EU8iWO2h~xz>G6mUxcIlF5_Oo8G0oANI?#pGQ^sQ@>m{wo_H~ z?o*Wm<!b%eDz)K?OZl3zRaJYL+*p&K{Eg|dI%h%!{FAaKVMx_xf0VV~KFd1m3t2bv zT5kGuTh`y}lfmI<Ds;X>ZtlOSwzRa!aK~x2b;mi?(0o8eO82%!gYKARmUXr*!Iog1 zGj-Y%XHM%Z2PYpS+O2bw{&vo@3z6|%{|`jQxUdP~$`txdZlOpLB3TN_8_WFZyVqxN zPJcT6Y;p63`_y;6KEBIu2!^5}qOU+8DngNZ;n(^D5q_xFFZBb5#bRC>{V;RzHu?`4 z{nE$w$AfkxCnPB(D<mx>FC;M}GgF-!e*n25$syU9>hzHOkOYwokra^}ktC5Uku;G! zkwlS9kyMdfkz`GEwn(~2zDUAI#z@LY&ZatPBx@vXByS{fBy&@pI+8n*Jd!<<zNyY1 znSiOD0Wt+-4#*^sSs>Fu=7CHEnF%r#WG={Lkl7&9LFR)@$W+e=nUblV6EZ1eR>-uF cc_9-+W`;}+nHw@WWOn!;P0ykCnqTDj4a}fXaR2}S diff --git a/lib/pytz/zoneinfo/Europe/Skopje b/lib/pytz/zoneinfo/Europe/Skopje deleted file mode 100644 index 32a572233d4850a4bb4b1278cc2d25480ffc6a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ diff --git a/lib/pytz/zoneinfo/Europe/Sofia b/lib/pytz/zoneinfo/Europe/Sofia deleted file mode 100644 index dcfdd0822defde60d3949775489f4edfaf5cb2f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2121 zcmb`{Z%kEn9LMqB6;Qk~_C-TfKrkTCuJYf1L1YAs1iQ+GNF}a`h(&~`3{vAxopX)3 zZ^UY@+_W64=7=`32h{qbYqrtlwsNhNi$%-U)Y{CsYOH?mLw9ak&-|U8`#PSp@yO5H zw{b^vp1JL~?Hg`RkG*-G8?Zm4m)6SXZzgE)wNe@DE0EBE3<>?|YX71mJvDCYuin|U zCF66dXNB%6Tr?i<xe}T?=cD$7n6Xg8wV}YgiIai(U-ktSoIMyweE)5ebozuze)R>j zu=jvT*|*E2wl<kXn`_P8RplnFXoX4lWSNYl`R1M&x5<nu3}p5Uk7r(Y`?G$$;$8gJ zW&e_MW8Qm*gMQDccf3pchy2S9z39!}-RI9~+T+b_7#z>**c-|(9+83#FG*qYaao?% zCCev!WMyK%uDrNKR{hnft3N1}`@XH$qJc&&9$BsTA6}y+uO#UM&1t&k&?<SbJW5NO z66K+cOIlVsDdkb0X+_%iQgQVssf_qoDud_c;fdE}?ciHdHTt1eAMKTOr%&o5ZEfQ1 zJ*ev+eO@=TZqu3-t?H|)(AtD`TIX4;bvJUfKE7P)zn-m+UC)w@XMWdB7u>S>M4U7X zUzW#vu1RBmQ2dP-WXt|v^of%9WNX8yHl-YsZN-Dy9R03tPgdRj^UK;Y*`Yhmck7cE zefrcJo3!=Qo$~aNVr?6!k!M;`wEb|Q>|9?Y9nA@{D|eynF8f3F#2M+#j@QoLevq!% z8`>2*C*4=S(PxJSWN&ap_Z|C0_YWS|=ekd6&(R)rO^dKsq$_Ibnm*%}EBwJRdgh(J zjEVow{_itMsV35xSTWH&yJEu6OLAOs;jdHAH{VoM`%GDx&y+fM4gat|#<*PE{7xO0 z%lF@7m}S3na{~6}dBNZI)SU^YW5?NvdB`}BfgmG6hJuX6(GCU~js07O;UMEd284_V z84@xkWKfQFRLHQ9aUlakM#f=i9LC0Ba2!U*VR#(I$6<gRM#y1^9LC6DkQ_$oXou-& z$B7IS87VSUWUR<wk<lW<MaGK^7#T4#WMs_9ppj7{!*;aeMh5O^M~(~~89Op~Wc0}J zk?|t|Kq7#I0Eqz-1SASb7?3z1fpD~uKtkbYV}S$%i3So5BpygWkcc25L1Kag1&In0 z79=i6U>t2^kkB~V*dW0{qJxA7i4PJWBtl4tkQgCBLZXC(35gRDC?rxys2pvqkYFLv zLc)c_3ket!F(hP2%#ffVQA5Ip#0?1?5;-Jvjy85k@EmRQknkb#Ljs6I5D6g?L;N?0 zkr9y*QPZYRT{EI@xxybDXS(n76)s|83q`bDv_*^+V~c3JDB)*Y!T9jkspp$-=wjvn iwDGn$+81sc(WQCB(^H+ltZ-RQcD|=NH^+G@aQ_2r6zRYK diff --git a/lib/pytz/zoneinfo/Europe/Stockholm b/lib/pytz/zoneinfo/Europe/Stockholm deleted file mode 100644 index f3e0c7f0f25f0a7290e56281c91190e3611498a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1909 zcmciCYfQ~?9LMqhQ3q?ZZ%F8dB$uPBax1r^8ai$r<dR&HTS7<?G0n`HeQk|d3}elV zcn}`sGS@LQo1K~4Y|P!vhPkcrd;c3A@Yrep|EryP_<eu8(-##aT7P|<<{KV9Ys|y% zZ8w@%O+?k~8sGi*?LDKUL((@5j(VdV+dtG0zgrse;hc7QdR#l-*{@wL?a<IOXC>@t zorLe%ClOUDk>#7DYhkf;n>kOqXL%(mHC=kRQY1PoMtZjCBr#66#(e6py`DvDZ(m34 zbETE`t^cB~L$9=7^?i-4yrFTc&S-r8F-^$5CyB-Nl9bjU{U_~|<nX<cl2|G!O%*aQ zv|0x~nj?e0m+0WLZW;0*M^kI_G_7H<4&5?Bht-7X@Pa5EQ8`FPW;oTIA1b4wUue3! zNiv+*H8bk5WWIYYqx~+(=*DX@=IKEhTX#gVZk|`q_9_{7^ni{pDv}9Rn|0#UZ91uN zzGe?7RBu+MP7WETQ(V1u%IA2^3C@t5yX|z^r(QDs)JL7+3y_)ngCw{9t<0+UAbHh| zGCR*FbJoAsxx-G&yxg0bAGurRr`2ge>yx@5Ty??AUAnNTSQlL@)5VXxy5#T-Exfuy zmTpbcqS|a(wlGqcZ%LLF6H}$QAVgLsM98Z2ud+JGl9IS!EqVV$N&`P@>Fvu>_U@jp zJy9#`8XL5H_eEV_w^uim9ny;J73yf=@bmxwKb9qL%~e@}V)<KESXW2uUvIw2@^~$G zI#0Hj|8h9&m-pW{+tU1zhfk?__&w-{`FMT%s<C|X%DKo5+nPJ(pSfk^o{^hI?i#sm zTXWyYjU#uC+&Xga$ju{nkK8_T|40K!2S^J@4@eV87f2gOAGW3uq!XkSq!*+aq#L9i zq#vXqq$8vyq$i{)q${K?q%T|37}A-oX$|QOX%6WQX%FcSX%OiUX%XoWX%guYX%p!a zX%y+y*0hTBYHONBx<%SW`b8Q>I!0PXdPbT?x<=YY`bHW@I=3~gBfZ<2=8^7^_L2UP z4M27P*#cw_kWD~#0oev*ACQeeb^_T7WG`&ZW+1zPYzML*$c7+0f@}%0C&;ECyMk;B zvM<QSAUlI>4YD`3W^<6;u{GO+><_X*$POV}gzOQrN!Ywgel7f+|NrOrFhwv-fnqfe sQyY7p%$skRr)+zk{!CQ!Mwxej8LoZ_ESJlZ6q_6y@A4$XV_Z_ePe)m?eE<Le diff --git a/lib/pytz/zoneinfo/Europe/Tallinn b/lib/pytz/zoneinfo/Europe/Tallinn deleted file mode 100644 index 3a744cc6f28f3a886952ab19c8ec93f0e66cefcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2178 zcmeIye@xVM9LMp`fk)RR=Z7{ri8~=lLFC8rQ)`ge3CuVQ<s^R*{Xo<rR3Hb1@m(?J z8nbuB#$4r(V)SFi{6lTce$P3|i|!xR8ns-lY^=HOR?gYR>ht{8^^bqFw*KsM`+gr^ z?(UEKgV!^<WmiY3y6$N64i{&@Ts$wf%<ahL!q|~_MUD=Q%5k-P&sfjy$mGe>7e6~c z+7!Fgpg(`pFH^6E^%pOc>v->li|M(q$8TBqb&cOM)8zkc!AA>ceesN$aLui;7vq`l z9kT-`p0|T9KW#4>?y<85+U@MljrMKZ>g>gl6?RTlo_%{khP@<^p_b0~s$6%4m3w8W zmG^_+S~l&m^3PqfmY<rn3dUxv!jYI&H1xhz+&f`~+K*Z#trt{j_qZynep{7qIH)Ru z&#B6k51J}V`*h{ppkA3dB30vUQhoMLx$D4cS@lvtYC3Xc_0S#q?mD-G+cWi=C6}Z& zJg4j2pGtksx4QoFk9uwL$9nD5q`v3-*L1_gTRIZ^P#O;n>vboN%e`G)x@q_sS%2TN zvY~UkG_UBCXrx{?`qxQI!7^#N8j?-vb$Zj6H_PTLd3wv4UuEmGS8qF(rdv<_tnVNE zMYoMi=?B`T^@ICo<)KyY=!aWl(w=o#Z?B$^4$o=X5fs_+)2Qs6>y}-U{qo2+QF-)@ zt<rgZuYT-cwRDX)>)kuEWY2*Ly?1?;?(XpGo{~knxAvNTJWc6+#p$xo{!aJ#u1a70 zobJE;l{|5BTt7K=Rt64#BKs#^k*E5{Wbn|SxaK7#yWA-^Bqt>&rFi~)-RS-6b<_Oo zUJFuvZeQwu^HR!RZvNf4&r>Rp?eZw)(<(J_4`iz}rSd~6J@M(v8dpZ*oy4;%$ftZW z@qOOO54nPo#;A&#D_q;mEvjnG)!5u%t~KV_ys4DS#V;^$oX5X!=I`jgSkCX}w48~> zkOeuK6(LJP)`TnySrxJ@WL?O@kd+}zL)OOcV{yppkmWg=^&tyHR){PSStGJYWR=J= zk#!;qMOKO|6<I5?SY)*vmdjzij%LBgijgHFYep80tQuK1vTkJI$jXtWb67iv#dBCa zhvjovzoRLDqp1K=0;C2=5s)e%WkBkH6auLPQVOINNHLIVAmwm0^*{>ZXexq~1gQy9 z6r?IhS&+IQg+VHVlm@8{QXHf@NO>GheUJh<nhGH$LTZE*38@lNCZtYCp^!=;r9x_j z6bq>qQZA%kNWmOU#gLLAHA9MqR1GN`Qa7Y<Nac{yA+<w_hg1(KA5uT0fR3huNC_QH v4Ur-uRYb~&)DbBpQc3*3l>bypuW4&xDN!wGEGw!g3Kf?XG?s*%r*iKf0hJHV diff --git a/lib/pytz/zoneinfo/Europe/Tirane b/lib/pytz/zoneinfo/Europe/Tirane deleted file mode 100644 index 0b86017d243f1b7bbb41d6b4feefcb2b7edfc7d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2084 zcmdVaZ%kEn9LMoPdKbGQeZlZ%fLMs2U4hF#MM1M0pb46CRs4e-6A=^XK*Yd5v6fkD z%zYo+S+1PA98;&{8vSwTtfmepGS|kMI?L6{XpQbx&YZIRz0V^z*Mpw4b<WOxo!#Bt zN59WIv}#jbj`h&xG2ifTy=5NW$L=|rSKqhgZKwa{Lb-Irr<cAM(&&uBNc8V>Y_F#+ z;=SB-W6aQEC#Gk<J@%W;k=XDw`>_-E9BGNM<McZxzH-<e=X~irKKrDdF#n`8e%vRv zFEAMK-5a<u!3sMQuGQF2_J15nJat-<j&)1&hx=t>r%KB9H)K*nvpltCy*ynWlGLIS znd~o+w4`*I67z(ldxDyND^D|iO4F%><8|7(NA;PWztn$dNT-LdYUa+1n$>bjvulrQ zp!$mBH1|kuaj)bp-6Q$Q`=lVJO$tUjWM*PmXI@_?g?C$Z*6E<kzE-70T{T+VJ4@&6 znx}I=NYZEPQgz<WLV2##qruulc|Pr?mIOzn)N?`0Qoon7;h$x}sIO$fK%czu<43Zv z`>2%npV5llowDfIL0#O~C@*%tsY_P8t4kX;XyuFs4V9PaGT$Oy?w_j5Z)a;&La9`J z8?P&GWyq?}{?yfjURiS>PO5u;leHaxNKJS^?3zJYx8qlRY3}E;zPev)Q})V+;%=>r z{!}+6t8V<|J*^*U)=how`ttRVZa%zP8_qY$mUoJ^v8z&EsZUX7SH3hYDU#+opS&8F zC@m#-<h3|UTC)<gHS&YB#opGo%V(v1_=;{l(IwjkdUgBWuXRWFK7GCYkaq0u(5OfL z=^i!uKf5g}{(VkrtXQKhD``?x^n>r^6(K8F!c!UIS5Z;!N9bRi{J+h`=|>iTtN>Yp zt62ko&mvsSDv)Kknsp!xK~{n+1z8KS7-Tiba**{P3qn?eED2c?vM5)xDr8x%W?jg_ zkd+}zL)L~Y4p|+tJY;>y0+AIWOGMU)ED~9zt63(pPGq6TN|B`^Yeg1|tQJ`=vR-7t z$cm9ABWp$$jjY<$EE`$3t64a*a%Abq+L6U0t4EfPtRE==QURm{NDYu8AXPxhfYia& z6auM)t0@Ii3#1rGHIQ;3^*{=OR0JsrQWK;oNL7%sAa!vyg+VIgYD$CD1}P3w9i%)+ zeUJhn6+%jc)CegOQYEBJNS%;EA(e79r9x_j6bq>qQZA%kNWqYbAtgg<h7=8{8d5f- zZb;#f%DI}-A+>Wg#Y3uxln<#NQb44FND1-(T|=)a<n#cE^jG9&=4WR6D+1Y=mFv9^ D;y&Xi diff --git a/lib/pytz/zoneinfo/Europe/Tiraspol b/lib/pytz/zoneinfo/Europe/Tiraspol deleted file mode 100644 index 5bc1bfeba62d14ffa8995c1d08ba7500956c494a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2436 zcmeIzeN0t#9LMqRjYkpbx<)*!VFD`hD2gRYUcnTuUIdX$@RWooXrW@5fWJA%Xd0SW zvtq?sQ`wZ^Vw8eLWy*?x8e28A&ekk@n4VS6mbv=94`bE(tG52@b9e3=?wE7V?~8&p z8_QDUZ*%HTcvzEu_&P@Ex0fqk34EjDWB=0&el$*BZ!yk%@r=<uSa0<7wV7w%e9=62 ze4qK&ky^92akKe$O^NwV`3m#hqD=GrgURN5>CxtexVy}Yq26X+PqXp<lXaf{uXCIq zwuKxVc-7?`JT%)mwEUnE9D8i&?$Aq_A^sztkYDcX4gEG~C~U-)8Q$MK6w%XaMV@%p zifY+oP1?WBnp|6IO{rXE-Lp2^iq4&DO`RWS-5VcfO^Xb$V#eRHVqCdqtWTPXy%D40 zu7s%R##Lkb@b|`y^Mjt5odZVvYo|Q34tE#{dz(Fp+YcE@rPZF~;&wA->#m{HjFU2Z zWuv4;9gsOGn`F-IDoKxMQ0bTRW!|lFb>G=ExqoDtntwD;Wpu}@1r4cc;Y&d(vn*IW zuy>{`%DSmsr4wZFw9izQ>$+t7bgLy(KawTaK9!|juglVbGqUW<=Om}SMRNO3DR)!7 zEI-k#R%|iEv%gy9t*%uoD_5)hxh1L~cb-}mlBo*gBUItdXjK%PE=8YstA}oc%bKpQ z)WgHS%Gy>hDenASN_Jn7b%%S!Sa(q#sk@{eUDzh;i_fXj$@}E7j8;|Ve@tzN+M_mH zeOi^@E>#=*D%7US*{b5WTUB0IFPj?^)s~}K@_6|~^+ZFgROKbe*0OQ3E!ipCv%ZsR z=MS<Y(NFELhNULx8&xyZB|EQupq}bDBD)4os@fMiRbBf5RloCjwY#ZGIqvZB^8V|~ zcbuQUpa1xP(O2MvKVH82Cw+YX!<R^ezy6Ob;XjGDNNA1d?`9-90!1RH$i%Vt(NBV$ z63i!#u(9{_OvfaCUZTg|$37YR6LQ@J;?mC|{bXh3^QJ$rAN`Fxf3bdY_zO53qmTUs zAN+X*|KKiv(<|)i`<Zf|lpCeosa<`mlzXMzEah$~w@bNS$_-QQm~zXMd#2np<*q5W zO}TH%jZ^NNa_f|Pr`$Z{?kTrVxqtp0Fo5C!#R9w91BwY07brGRe4rRXae`t6#S4lV z6gMb#Q2d}6LUDv*iCygp#T2{R6^bnsU+ijQD9%u<p?E_vhvE*!9*RE{gD4JBEMj=X zFp1%kU2PM?C%f7xhEo))7+z7#qPRt|i{cl>Fp6Ug%P5{POryBQu#MuIU2Po0IlJ0A zhIb6}817N*WB5lgkl`T3LW+kJ6DclIY^3;TR~t!j(yq3W;w8gOikl2O8GbShWjM;P zl;J7GREDb*TN%DmjHNhhS6fT*mSQf&U5dRFe;Ec-9A;Qd@t9#U!)1oe44)ZBGn}@o zt!8*_SDVdnn_@S^Z;Ig*$0?T6|2_YAo(JgP0<%*1eGu<XO-M^figza`(Ztk%-vMds BjH>_u diff --git a/lib/pytz/zoneinfo/Europe/Ulyanovsk b/lib/pytz/zoneinfo/Europe/Ulyanovsk deleted file mode 100644 index 7b61bdc522b5b7f4397fdb9246185f4d972f4b6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1267 zcmdVZPe{{I0KoD0%gwE=LkDY1H?wT6O;^9>CbOokrVeX@#PDDR(jOrRdV~jqz(MAz zf+C10qG)vK@1agvj_4Apr*0PfAUaeK?-GGh>wSOIQ<pmSetW;qkH=%M-}|mGd}1^% z{upcY3X_r5ljpiSqO<s{<Q-fWzFuDMpErH(xr}eoc;f#e${QAvX8r8>a>Mi$EAZf) z6}&lSHC{MwZ9F?<HJ!{^n+~O{&H0GcoUXUFw0W&iz@u8cODgOP%kX!<-1^B^jEv6| zBkvYv^z~QS`t-eQE6vH+)t7SH<YT#g?6x$IU6wlzPpkOpYpT8LmP#Brqmsd*>WCjy z9Y2q#RKvJTedtl0OT%*Kix#zOAuZGQva;)WqwJmv$=x@E%#6C^p2>jP+xuB&kN#3U zp|`R(YpFixLz!!SrE=fisQ#Lg>Yu-__I;dE`yX9Y`PsX2;L<5o$OlJ;e$>f{N~L1d ztg2oP=kitSs&%<n>)YR44wu6rL~KOARuMIYe(oDI+(M)>yz1(GWyR1d)jd(u&^rT7 zVl8`EXJ>w(AX?3KJ(GGS^wbAx=+E-td1Vy-;kfm$tZ?MWvGW}qJ#zd=0=7B>Bn2b~ zBnc!7Bn>1FBoQPNBo!nVBpD<dBpoClBq1atTb&Y;6Oxpz&I(Bj$qPvg$qY#i$qh*k z$qq>m$qz{o$q-4=R_BN$X{)nD(nRt^5=AmaQblq_l0~vb(naz`5=JscQbuw{lD5@Z kBWc^}yphC_%#qZQ+>zvw>~TW3@SmpdN$WpHcP!!g4SK2@ng9R* diff --git a/lib/pytz/zoneinfo/Europe/Uzhgorod b/lib/pytz/zoneinfo/Europe/Uzhgorod deleted file mode 100644 index 677f0887b0c6c965fba6fb3c8a4d47c9f6f51695..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2094 zcmeIyZ%kEn9LMo<QWS4RUs`k_E`JKiKV9MSPthRJ>xC&?yUNu_C9Z||C!zpjU>dHR zYmMBuWI0zlEJxXl(MI(ETYu&nZFIeDx#q~#YNd0Iovoa!#_IPzW?Sn~4|>))JLh%w zdvc%L&pWzddrPtPkLzZ0!o#)8JbY*GHLtN*?K*y;Rz5%B)%a^cIrCDfB=(+&C5|2a z_KP2;oU}P_ByLaty2_h85%K<d%SY*#QW6;x!KypP4<<5C4LH7&FFIMTKI3Hfbvik_ zo1NU&b<Ules-3&)7CCtpbDi1#3@6{`aq^FLM(%#0z?pMp`M|s{FURJeJ`=fTBp&l0 ze<!k_e>hgK?_i{G=P3;|zpX`$=cKq}L`o`0rF8AfQkHc@78ZBQ!l_;<&+OOo^P6Sy zZ(X|NgP`0y(V!KBO<Fm+MDN?bOqagm)2fy{UAAwrEU!+|U~{Id$iJYW;FMG+eWo>e z-$>2mkFs*w$Fed$Cab=GU22EklDhE^HGH5?R-Zhk_qVl4r0-c>^T6}EwsnivFKX3j zU5&2uuGaPbdAj~;KpWDkrQxev^}#E1Wy5<v>&CMl*))(UjU$)kq28aQsXs2Urn9nn z&n10$>HG3X<G42G9F{GWL)w!3u5Qgz-FoqmZky`R?PEQ<<9t*feRHF>e%daNJy)r1 zgZ1+Gwj6z8f0?wesgRBquXGk=%g)d*@?@$dU4?1d<$NdIDOa^SaawvN&*`q=L3t`Z zs=E(=qI-sp=+iyNwf8`;+BZxy_jG$k(zSbL@?ZB1dzQz&Z@lThxGn3pi{?*erHD1% zvV6IA^54J9t8DX6oFA}KufMOI_;Rf@PV9{9Z%*pMQ7ahYsgGLW`W5D>wL<0~#DQhm z_Mb2JgWu({?dZSQ#P8<XF*%%$Yy;T`vJqq_$X1ZOxSGu%yWyAF4zeF)L#}2=$d-^j zA)7*Wg=`Di7qT&AXUNu&y&;=Jc86>a*`KS~AhJVbi^v|4O(MHQwu$T$*(kD8WUI(t zk<B8zMYfCV7um3@*)g(ZSF>ki)5xxoZ6o_eHjeBZ**da!Wb?@Gk?kY<M;d^10BM1% z=>gINSJMTg4M-o5Mj)L)T7mQeX$H~_q#Z~<kcJ=~L0aN!dV(~?)pP}E3(^;)F-T{S z)*!t>nuBx)X%EsLq(MlBkQO05LYm}ix`ea|=@Zf@q*F+%kX|9pLb`>t3+WfqFr;Hh z%aEQSO>;F}L)zwQ`i3+P=^WBJq<2X3knZ9C?Xzu<>EnW80_hKz6qFSN3QPRqqJaBV G>iGj{t@?!k diff --git a/lib/pytz/zoneinfo/Europe/Vaduz b/lib/pytz/zoneinfo/Europe/Vaduz deleted file mode 100644 index ad6cf59281a1046d9dcd045fda521585e3e33e06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1909 zcmciCX-L$09LMqhq+=x|UkjT`&1!PZnmp6((5_jPcE=8#Ej!E(waeVJGVQV`Btqi5 zAVpMc%Z5ah^}y<Z9c&jJCJUQH7eUcw5kZ9=Nd4abC5WxZ{r}9ohV0?@{qfIST%2Tm z^*GJH@Zni)KK$;!(R^KTEwQfLFSD+;`>f`(xmK9_nfB^=M_mEe)b;AL_I_|g`~164 z`=0w<!%v=)h(iq$x#th*SE~}WZj<ycDVG7W7sx=LU)*UKGRTuE(GfB7L$}@%<Me9G zo8db6VYJ4!_R=92I_uEJx9ZvdREO2w(zq>GHGbtuO(;C9iTO7rsk~8=)0<>?&JIb5 z+$*U`m6F;~EhEC~bj00xGV()(jymO)(YNz7t-e6hn?~uFn(;bzcZ7~BcI)^pBV|IS zQ@w@Z@>BF<&G2?ert`99x$jBVi$^js;BT4Oa!G!E@R$73a8P{BXEb|ztxP)fr%o;{ zl_|BGb?WqOnp0Awxj&Yu-<PGox+du~PpnRBPtd%uOv$^^Lub4hEHjV4)>*B=GJ9XB z<TpN-In}SEpsq#c7PQK|^=&$T><L+r->ijEyQC<+L5sT_(}j_$3!m)NMIGh3_)?WF zx$D=Z2WDx>#WGp8HC;>VbLF>1QM$Y)Marh8NqMnLRwVY5l^O43Rj4Hu@nKr=^1f7t zv}@%*=cVe!O<i-eUe>lW>AGEKb$!EL-B7h(tG8EcCx>|h0>AfbSzXLgSyn`UN1$be zh}HGW-@a_W<;}?D%g_IEIP5R~w{JGc{E-h&rTOqX^rLwOy=>cvW!HmhkQ=r&cZ}RJ za?d>6G;-I-ZQGjrMs6IrbL7^Mdq-{_xqIaHk^4s)KsrELKzcx$K)OKMK>DyXjUb&M ztsuQ1%^=+%?I8Ui4Iv#NEg?N2O(9(&Z6STxn#PdMY)xxOZ%A`UcSw6ke@KH!he(S^ zk4Te9mq?pPpGc!fr?#e5q*q(hEYdB~F48a3Fw!y7GSV~BG}1NFHqtlJIMTVTX&vd^ z)-;cFkF<~Uk8A+41IQL2dw^^LvJ1#IAp3x91hNyzRv>#}Yc>Pf4P-lz{XjMZ*%4$* zkUc>*1=$s3TabN0HU`-lWNVPUu{E26?2fJ39%O%z4MKJZ*&<|*kWE5%$q~@Wyn)W| z{eB*%p!b#;CNocFr$WT){^f7xX~O>|>c5RL-@#_Hh9$CIp6ukfl(+;>c47j?CkKB5 Dj=i{v diff --git a/lib/pytz/zoneinfo/Europe/Vatican b/lib/pytz/zoneinfo/Europe/Vatican deleted file mode 100644 index 78a131b9fff014c3ea895a9cc7bf4197462b6602..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2683 zcmciDdrVe!9LMqJ!Xt=EUo^!8#3EA@5kwSBG(}`4Sjv-{SG=JX(UFj}RJ`TNG50M^ z$JE42u8UAB%r(?jsk4b_7?zcjWoAWlXEUb+XutPij9LBdIXvg}a2S97-XH(8#W{mq z|G5Iq8$MhI%!lutR-4E6q+8bZ+N!!8$4}Wi54p16e*Lz!t2CmnH2WQU_o}k&Ju`+{ zdoy}upUh3PtFDfh)9;08_1c2E>OHA)=FP!!=JJWUvw5@hoBVE4lTl-z8xgPHj;oQ{ z$eY&re%tKx^{e&!_FJVP;h^1c;aFW`M2*$>S%uxyRADujY_)G+Icwip`-$Z`{;}<z z`<CU|G0pOhe#r`Wwb1tU-)y(b+iV4PSZW8Q4YPylr`W-<Q>@@q$#%z(WUJ!`;dZBA z>g<q>&Gvn#n=L8mVTG1mvN~rqS)Hr5*<CWn$o&a_YuD&{?bhWd4fCGXu<J*)`{fVy zfpewW<Eu9`{O}QpI8ZK;J9bIWVwI>BYou3hzC1W%o;;M{m+0YX@^H)$=^fEW9_iR! z`uI|{&yB&__eyW=SKCb=Jszn2D{g7bzTb7gmLD{B{V5$-a9HE!mudXW3o<CbLK0Fc zW$>h}k{G#Dh74LJLz;^uDSV42U7RDyjfFbwNUA*c<5V5Kf3~Jn4$~1EM(N1y5&C#e zw2oSzEThNzG<ANsjOl$<(^8vdtnX`0kG>@7*M608-Y;Za^+|c+@?IHV{=Q^XeXg0C zie<uq-TGu+o;+2&PA5*;sFQLRXx7kN^=G8((_s^Ia!fy+d^1j`b{#8Izw4~e+~_OQ zKK)Cl*9Oas(omUM@u$oxx-PS~R7>{kTA8!*51l*mkj$G|rSqdoWI;-~<^&zog^{WY zf7_;un)7w>$>sX&MZZ4x?sUyPu|%GKEk*P8XUPkTqSV@uC`%>|m;9VCc`?4H6r?rC z(omNa4(zIh_Rq2`<fbk=drX#JyPz+Bv|nDSuGAGJU+T*8ow{oIJ}uf*q#k$M?G14A z<Msr2ox9bR-|l*PeC_W2?Q(UfFvrUk(aRm+a)pSiee1L3P22m7Ous8NEsKYr|8ScN z%@_awALbzQM?PF-=EL{UJLXC`+OrC+!)q+$a66g<jvR92pd*JJIq=A#M-D!6_>ll0 z5kNwK!~h9`qlp3%21gSIBoIg>kWe78K!Slp0||$ti3bu8BqEL`BuGpgO;C`iAYnl# zE_@*{TqZI=Xpq<-!9k*fga?Ta5+Ec(jwVD%j2umnkSIBtFd=b50)<2h2^A76Bv_6n zT1dE%cp(8pB8G&_(Zmc18WJ@mY)IUYz#)-CLWjf-2_6zXBz#ExkN_ePL_&zf(9r}D zi6Rn4B#uZRkw_w;L}G~q6Nx4gP9&a4K#_<dAw^>9Xo8AF)zO3%i7OIVB(g|ok=P=^ zMWTy@7l|(tU?jpwh>;j0K}MqNXu^!d+0g_Vi8K;wB-Ti<k!T~~M&gYG9Ems*awO(R z(2=M+ny@2rcQk=VB9DX~i9Hg0B>G7Bk@zD6fQ$e#1jra5gMf?zG7QK#IGTY#MgkcM zWGs-uKt=-@4rDx#0YOFt84_enkU>F41sN7(TpZ27AS2^wh6WiMWN?ttL52qzA7p@# z5psJyZFhjr!;i=73vljMTYkIi>1`Ky@9)+XGFxO;$ZU?8iV$&iYJIl6X?xEWP5Rd! fwGEQ_7HmdpKs<95lbH~k7#kOp86SrO6N3K%dd(YT diff --git a/lib/pytz/zoneinfo/Europe/Vienna b/lib/pytz/zoneinfo/Europe/Vienna deleted file mode 100644 index 9e2d0c94860438443e8d8307f2d5be74f6eea2af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2228 zcmdtie@xVM9LMo5zcQHF+YC<mAr=y1C%-{4$m~2A%2~*zBoY{inuKr}gVL@a%(X`D ztz$Zeq#|S0?CKA!HT*HN8Z9K$E<<x|x%^ShG0j<PEI-fhAN|w%qksCN&+WdC&u+Uv zZoAjhyK;Sfmi4zY!F<A-^QL)ozw2f5=$h!L=?b13+cO%_?%qTC*2k)Q+jr^y>&KdU znquX^qDmc%Y}TR6OXTD`4jt}s$yYmaWaQOkIpvFyQ+~IcKA$sorZ!H$E;%j1{1fs` zYQLOy#>%&mJ7tO|W$fPUk(%hFpEYL6cj^c`sg4^*b?U|Ub=sMI8hc{9#vMK)@dpEv zuxGDK?@&qH{EEzI@X39vYUTb?uOt<?Wu`MvlH*h4fr(QkCBmgCH*<8>kI9-EjM3Sj zP0$DXe^Y1gRh`p$PSe`I)bve<HKXo5&8!-gEMLE57Y<0yvYnEf&@Fjct&%tHmw9oW zI`6`2$^WB8=O1y&L+2~Bps!jB2j=U;TNmnru6TW<K1mn0=gXr-5$dXolSRpw)$JOW zqKGfFIO%&SzII7U!j4PH@SrTdctDl}-j&j!W9r$~AxjVL*T)(g<?)W!v~0y2x~yT1 zmd|ZaZ)veU;aIB6ovFI~R)$tY7fHq0d-Tbhvt;FmziMSLN>=TgELHu#$W#6sQr$T$ zHPt~`z2zr;dcj9hTQ#J0iMwS@VL<EeJfv$ARM%eFrR&Chx_+=tpSj@G4R2R!!{^QN z?CXWv*jFwa*ClGx)?8^WD-d73L!Qf=E}PuH%kz^hX-SXPma!kCb<!<u9r;w+u8ryo zAN0wK!vnf`_b0j~(5)}E^{RiHUqeGe!@|SD?FW~?t|6fjcl_5c%euSF{D-ah86n}8 zHA$?<f4Y}?yq3#d&cn+$Ld-#P@&1d&{Atd{p6YaDIksj!$byg+AxlEmge(eK6|yX3 zUC6?al_5(*)`lz&S)Hv}9<n}UfyfGxB_eA?7KyBqU(Pa-bs`HzR*Edu)~pp-EV5c; zxyX8v1tTj)mW-?!Sv0b0WZB5Nk%c2GN0yGP-PSA~Sv|6RWc^41kP09rKx%*#0jUB~ z2BZ#1A&^QSr9f(7Yl?wX!`74osRvRJq#{U3keVPxL8^k31*r>C7^E^tX^`3=#X+iL zYs!Pv$JP`GsSr{kq((@QkSZZ%Lh6JR3aJ!QDx_9Ov5;!nnsOoavNZ)mDu$E{sToo< zq-sdnkh&p-Ln?=q4yhedJfwO^`H=eAngSvfL`sO%5Gf*3MWl>K9g#vJl|)L3)DkHs zQca|sNIh*$L6M5unvx<lMT&}46)7uHSER7`|Ez433GbXt672M3r{$()I6aven4TSV E8yebJwEzGB diff --git a/lib/pytz/zoneinfo/Europe/Vilnius b/lib/pytz/zoneinfo/Europe/Vilnius deleted file mode 100644 index 46ce484fb415aed15a6484e34a757c1b30a60eec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2190 zcmeIyZ%kEn9LMqB4NxM&FBo135QzjNgv&p*O%&tb6i}2a{w1jpv4|ASfoYtTIoFu` zR&3?c<-bumVm>f?kS%MjQQh!9VXaxK)yl@2JFT3vjn(gcfLis)t%v>24zIIwf$@OP zJGgOsTao$KNw9CYIeYERbCzSj99@;(aN@ZYublij;GSyhZW$iDyyKG%x#7^&TKViv zzmA-&kjpQYNH|dTO=9-z;aN#vmiwcxH28m;aWQEuE}VR&qWtdAOW~9Y$KBMy7u>X? z&$zSucDd>MTHQGvYu${^)$ToY3*F4cIqtoA$?n|LWRn#eW3r=4o$TvbPR{p!XWnSE zlY8l=bKki!C-3wXXMTUk$v^O(Q?Pr;3ADcE6gGcqin<0&aoJm@WX&N{a^_-7Y1%Pa zP}D68#(QN^O1~~inAx)A;4)qMYO0pEW$Lm6_sav-QCiWOB9(KmYE{L!R7ZWRHJM*a z&9xt7dBg{@d}LTwe0NG}ht5b{=zXm}+$SpsPw1-7PHE_SR#&fkUe|PN)y9P#8mz0) zwf>dbls8YCZUl6FVzsRQJV7^H&ykJif7MN+F|zr1yfmNtNgnL|MYi;hNXwQ{d1(Kb zKD_iDd89d{t?5T(YuS*tMZc}v(p0zo__DT-cj@-w9)0wypg#7-ChhoWr#ybBOgje} z<%#xm-Epu~cCKD5U2T5ZRXAIASKX8+<BjYoNYp*<x6&PVL%YM5r03cfy7%mWJT>yJ z?mP0K?jJg)PxqYG-ow4>yDcIzA}V^?^l8(hZlAbf?wGt{@BHT#88;(NBID)1xQy{% zu>WLc=2;VI%&c}3ZA_e)SYuM>7%?U{VD9?sjW3D!d|g!TOPl(1>e-hu^-~ky^Y`E5 z%c!dlnsxTBwRc5T&{WyGzOmLmH}X(vU)YC<<MSEgn>>@R{`|z}3*N$pez&LTe5exH z60#>`Q^>B6Z6W(YHiqmB*&4DpWOMvk><-x;vOiC|L1c%>7Lh$7n?!brY!lfhvQcEG z$X1cPBAZ2ai)@#}emQK|)9x7AGO}l6)5xxoZ6o_eHjeBZ**da!4x8t&dk)+8wEIUI zfONpqwgBk?(gaW21*8o~ACN{Moj_WF^a5!H(hZ~?p0*!ILp*Iqkd`1lL7IYe1!)V? z7o;&rXOPw)y+N9TbO&jVr|l2YAWz#Nq(w-NkR~BrLfVA%327A4DWp|MuaIUT-9p-h z^b2X2r|lThGNfln(~zzqZA1EoG!E$;(mJGfNb`{HA?-u@hcwXBb`WWyr|luqM5K#I p8<9RDjYK+$|F`niTNz_BJHLpu=G7PHm*xiwiu39V1Kv|f%pWP&2jTz# diff --git a/lib/pytz/zoneinfo/Europe/Volgograd b/lib/pytz/zoneinfo/Europe/Volgograd deleted file mode 100644 index 8f170dd97adfbb9a93d3ce6b727cec5d40e64e13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1183 zcmd7QJ!lhQ9LMqJ?8Vg7!9|<eq`uTNrZ0M?Nr{@))U*lJ6p93gR-yP(5Ck8jgMv`O z+DQdP5K%-ixcEBgWR)N;V%w=(D;zla%75u%(fE9?gP?=BdB@%72uUFMeT##~M&jy^ zV}*Hzi=*3IJg>U5keh#2su@@vy<T1uoN;Qar!%$RtS7;bs$4ghbk<ItFV|0AaYGNz zx#63mZo`G6?z%Jk-Sx-w?uG*?cVi*uHl{brrq()%gaXo3vnWwtR7V$rIyU=xxcT)b z-STu^x0a@L+trtP%lKoxb@aA&j$GE;4o*t^<TYvUyd{Z!rzIIaD;@D6>G*y~QuU{F z>Rpd)UmVmsUNp(hxwKB-%j(YO4Z3S0qIca;Iy2(e-QywI-TOgj5C4#!$Xnf;btUI} zsPm1lB>(k|^i`FlZ|1)2c|Re0A6=Bf)Lq?w>4X#u;o+g#FEUoCRIFuHOQ-Gg&gFhi zdx^i@ZNF_R6)W+tHap5Zr!uNqsoa3I^7m)Uy#DJwh5n*yaoWw5FjtZ<P!@;x#I5)b zLz>@FVN7IDKF_Ggu*kTcW?*DwWN2h;WN>73WO!tJBmg7=Bm^V|BnTu5Bn%`DBoI#% z2@(nt3la<x4H6C#4-ya(5fTy-6A}~>6%rN_m!}B~iOkc4hQx*hheU^jhs1{jh(w5l zh{T8liA0HniNuKnibU#ZLPcWrG{GX#BH<$OA^{^2BO&9M+5W$cIgmD?ceNz~KLI3! B7OnsQ diff --git a/lib/pytz/zoneinfo/Europe/Warsaw b/lib/pytz/zoneinfo/Europe/Warsaw deleted file mode 100644 index d6bb1561db672f94bfd5bccc95fe1a8d18a158d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2696 zcmeIzdrXye9LMn=97!a#FWyiDFA=G9R1if)$c&E81oLvd;RQ5AEi$}BXcEtoxn|CN zbBOa+P{hzFxplN9I%l`hGQtZQHf!Zdab%d9wZ`oCeq3wK*47{W*YDYRUe7r@|32@J zKXX~`Fmu<r*Z#tXQ*A#yM>_Vly*jR8XUB-_osH*PcQw`M?#hGu+Iy<6mu%DW9fwTC z;-jXjXkB()!B=wP(j@t8PlVRLktUyS87>XZp6rH_!{+4HE%~Q5)@GkxbUXjdq!?{n zuwTv&3dlKcq<qn#Oqzm^Yg2QfT=t(bm#+n!=5O9|uAD4$TDp~)mc#FuANE$7t2?%u zAJ1f(*0s-@Yk?H=Q|26Vy|j<HkvzuSEJ}8Mj*K>);@a{%RnBlaztYj%S2EI()dQXI zoL){Bf0)xXBgu42Y;n5BTyT1Ht#=|k$DD}k2b`W4E1X`Zw>Xg>tao}JdD}$oD>u=* zUNwC-y=3~XTV?v?<(U5SW|;ox&$iy5?w6PppFH4AlGvyL@?giFG9V;P2izR41HX&a zL5)2$?xXhlP~aE!RyOP4((^i`<Wn8G`iREo?AL_(O)_j{KoV1HW%#r*84<l(l7<yZ zQd_Z%>Rqa%E-aMMzZGcm(KH$J<!nu<%F@)@WPNzUI32q)N*~FM(QzfC<<apWnwHaB z9*e!CzO*(OAM%M#i1}J3T>V}qdXCG)`Z{_1;+rz5X0N25IHnn!H_7CE75c>T<uYZ{ zdYw9JqfX0PtkXy4sXu*!&WM<-Grfa!=B;?0-F>{wKG#L(+#D#Ghi>TH#xR*z9xn3( zEwZ5ax@48sOLkVHEG)XBi^jeyPtHG~IeoXw;?x?=4Lzt!qE(k%-lj|2R_e04HTu*A zzdl_(SMxqzA<w*=s>`dU%d<=SYW{{1vSMnAtjvv&RSA7$weMGXF5F1L(C%8$`mGdp zzNLi?AIh4mO}h3#mAp`2tLwJEuSGSx^~E)nTD-YfgFL~Wb|LLT?`iJ|4zUlxcfRv@ z*To<I=JIq1`|mGfx*o8v68Cn-MD+^_HKwzePJexliw_Ft7t`a<`yc;I&+waB_LJtD z&dqOpJoxMbC&(UqbD!^g_y3Ex{I)$a4>e3d-ge}TceQUl^5!FNKT-gs0!Rsv8X!eL zs(_RMsRL37q!LIeTx~6oVj$H(%7N4aDF{*#q$Eg9kfONSsvu=?wRJ%XgH#47jjOE< zQXHf@NO_R@AO%7ygp>%W5mF?iN*v1MYU_j)ibJK4QX#cMiiK2*L%EQ8Aq7J!=4wlZ z)C?&aQZ=M(9O}lQa2zVfp>!N-$Dw$xwt7hUkoqA7L@J1s5UHW7Eh17yq>M-%kwPMs zL`sR&5-BE9O{APiJ&}SU6-7#l)YR1$6{)JLEh|!2q_9Y3k<ucyMT(157b!1NU!=fD zg^>~?HAaezRN2**8L6|YEi_VTq|`{Qkzym&M#_!U8!0$aairu(&5@!bRd=;zN9yis z3y)MDDLqnqr1(hnk@6$;M-~8C0b~h~H9!^tSp{Snkacji3xTWzvJ}W#Ad7*l2C^K; zdLRpetO&9s$eJLFf~*R%EXcaJ+J!+@#?>wjvNp)#AghBc53)YU0^$EF^iL}kW|wMk W0-NQ{NE|X^NW3>AAs&Y&hW!qIlp4DL diff --git a/lib/pytz/zoneinfo/Europe/Zagreb b/lib/pytz/zoneinfo/Europe/Zagreb deleted file mode 100644 index 32a572233d4850a4bb4b1278cc2d25480ffc6a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1948 zcmdVaYfQ~?9LMqhiLi!!F^Wn^ayhz}P{?u0rE=*Ym*kS%l7v=nxim9t_GOJ3=DLOv z55j|(xy~gCYlazPt~0Y?ZfpGB|IKFe$P=6Y>6}-qwe{fp{&*(O%Z;-Bc$%A^@a8Eo zZ@zD}#(Z4&ihWZ1a+KUW?5lPAU2<z{jMT3Sk@|0rg4_Gb<xct#^>b$z_&qzW9q;bd zP8YYR|CzHAaI{JSckPkR<tjld*GiYXLg_knmUK(?NN|E%x;x_}Bp_6JwDgluZ<mIC ziqW3WL$p^z2km{ix%R34qRxY_wQt1(4J*5$;Y-hGM9wjd%(^d8h1C+BSR*mxwn=Q@ zZi$O3mbk`JiTAJ2_(wCO|MwytaMmRQA7*MoWws{P4A4Ovl63IS03DJWtVw14WoWXu zx^nzwSjbCtyBa0g`<kW%KbDktFJwfM^D?6Ds*HSgKt@#^k<{9Anzp%I(vR-b(fRo@ zrhL7Qow!NI<;~WNetGIiP0{hb={mvLODBAe(9HJ9l6kMKPWseSCZGDKQyP3^>fSbz zRsB|`m41-yiaME|-5@hoz0sM2Ps^;VTFnXCA+r;!G`Gb`ofD`!=hb$d+gPacu9oQh zM;={pXo}`tSu6`TCTf0VhAf&Jqy-ydW%1YqDa`eiC6S$Fsr#!eYhy`KczZ2+|5S=w zf7asqOH%UgzAiseDJ$w~bmfi<x~giot}Z#KrJGCD(bTJnc{$9Nce8)_vaELT=Dw`f zVm1Bs8PLVi!m@t<<hQA59?RwCo#8Qm;BfH8<8XNX;+lV$XIjGh;mB1ZmyKLEa^c98 zBbRP#t{u5}<m&kkxO`i4{YU{w1xN`<4M-746-XIK9Y`TaB}geqEl4p)HAp$OrXHjq zq#~pwq$Z>&q$;E=q%Nc|q%x#5q&B2Dq&lQLTT>rWpslG8DG{j=DH5p?DHEv^DHN#` zDHW*|DHf>~DHo|1DcIIjjFfC^YDS7isz%C2>P8AjDo093YDbDksz=I4>PHs9)~o=s z1h!@kkVQaN0a*rQ9gu}URsvZHWG#@zKvn};4rD!$1wmE>SrS{bCdi^7tAZ>GvM$KN zAS;6`4YD@K;vlPoEDy3i$O0iNge;M*StDeTY|Sbm%Y>{GvQWrMAxnj<75@K=<ztqt WZzNOZOp6YS4U2H5MMhwFw9ij!n9hj+ diff --git a/lib/pytz/zoneinfo/Europe/Zaporozhye b/lib/pytz/zoneinfo/Europe/Zaporozhye deleted file mode 100644 index e42edfc8506b9b99362b36d90c8b8c4db67d50d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2106 zcmeIye@xVM9LMp`2^5`$Z{6@9Ku9!D5spI?4H7#KW`Lb?5~;*lh+0I5#=taMIp-R4 z?<T9ca<gPq%$RLxf1vA^Yu4zxKeFb?wN;De%zd|VR*u!@`E|6m{_2nZ>-*jJ`}p!@ z?6w=PXJq4!)`0oPaff}w#j(d;JkNX9-iFeT`%ev|M?W2!h>uOw$Y*c)H1>K>VrReX zNX>gAK0EE}N?-DL*!TO4_tP$?#M8%vm3NLEj%S=X=476D(aC!CIcHAaE+>0$i<8r~ z!MSU5l{2??nUh<d@60Pmcjjk$ox7*saPpG!I`Xcib>x5lQ+UA_SE38geI8yk5{niL zyc1sBe==IQ|8Tfy_ZjuKysgDe7bVa+A|(~0vSj^BQkr#CmIk_I>13~zW%O&=r7g1j zMwhNQ8<cy-8?}6}St~|Y=)DJ4>B?6!wX!u=SM6Ue_f;inuq8uQ&!5mxa8jz0KGEvj zZ>0L_53(lV16dP0FZX|UTxy1gq;~8*tvl2wYfrqU545*SxbJyg_uvb<zHO`4FKg3C zZMAOjt<{Et1=?`kuZ^iy()iUZedt=gY&`voZo1%=&Bta+)5sNhxc6si?vF{d`GRcO zcUd1<`K~<LG^Q=tM`UZou(l?j(rsC)+kSjmw@-HJj`KbG*rkX*{^lla`*^23aj-($ z2kYg@?b+IKpj39QE0@kzpX@50BfCSt$x}0pbQPs)m-DT3r(D<W_&MphdQtbB9F(VH zqq_IVhq`b0s6Nv(puLBB)iW*Omc*pQgj;W($+!LO^iI2ZPQU%XIE~5q)&7&2oVZCe zCNsx)jale7DaNFTnZ+B=?5TTMr6*(Rw^PraY~FC^Z)@u!W|2P-@S9L5V(RK^Owbw( z)$_w@`_evecs%X}e;poA<X<e~4|_D6{wNt)2(l7nDacxo#UQJ3waejSSr4)xWJRua zNywUzMIoy~mW8YfSs1c1WNFCSki{XZLzaiE4_P3xLS%`qc8$m)kyRqgMAnHc6j>>< zRAjBlVv*G%%SG0UEEriavSe4gW@OQ>cGbwTk#!>rM^=t39a%fFcx3g+@{#o;1wbl* zlmMv#QUq691*8nFwhl-kkV+t>Kx%;$1E~g54x}DPL6C|dB|&O}6vfq61u2WGtqW2Z zq%ufpklG-{L8^n42dNKIAf!S_iI5s0MMA2Cl*!fB2`Lm(DWp_Lt&n0N)k4aJ)C(yX zQZb}tNX?L<Ayq@l=4$JP6wcLF4k;Z{JEV9>^^o!*^~3)Q$hSZdy*8VR17xzGuB5QE Q&|g$iP*?1CpO$$41Tp&eZ~y=R diff --git a/lib/pytz/zoneinfo/Europe/Zurich b/lib/pytz/zoneinfo/Europe/Zurich deleted file mode 100644 index ad6cf59281a1046d9dcd045fda521585e3e33e06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1909 zcmciCX-L$09LMqhq+=x|UkjT`&1!PZnmp6((5_jPcE=8#Ej!E(waeVJGVQV`Btqi5 zAVpMc%Z5ah^}y<Z9c&jJCJUQH7eUcw5kZ9=Nd4abC5WxZ{r}9ohV0?@{qfIST%2Tm z^*GJH@Zni)KK$;!(R^KTEwQfLFSD+;`>f`(xmK9_nfB^=M_mEe)b;AL_I_|g`~164 z`=0w<!%v=)h(iq$x#th*SE~}WZj<ycDVG7W7sx=LU)*UKGRTuE(GfB7L$}@%<Me9G zo8db6VYJ4!_R=92I_uEJx9ZvdREO2w(zq>GHGbtuO(;C9iTO7rsk~8=)0<>?&JIb5 z+$*U`m6F;~EhEC~bj00xGV()(jymO)(YNz7t-e6hn?~uFn(;bzcZ7~BcI)^pBV|IS zQ@w@Z@>BF<&G2?ert`99x$jBVi$^js;BT4Oa!G!E@R$73a8P{BXEb|ztxP)fr%o;{ zl_|BGb?WqOnp0Awxj&Yu-<PGox+du~PpnRBPtd%uOv$^^Lub4hEHjV4)>*B=GJ9XB z<TpN-In}SEpsq#c7PQK|^=&$T><L+r->ijEyQC<+L5sT_(}j_$3!m)NMIGh3_)?WF zx$D=Z2WDx>#WGp8HC;>VbLF>1QM$Y)Marh8NqMnLRwVY5l^O43Rj4Hu@nKr=^1f7t zv}@%*=cVe!O<i-eUe>lW>AGEKb$!EL-B7h(tG8EcCx>|h0>AfbSzXLgSyn`UN1$be zh}HGW-@a_W<;}?D%g_IEIP5R~w{JGc{E-h&rTOqX^rLwOy=>cvW!HmhkQ=r&cZ}RJ za?d>6G;-I-ZQGjrMs6IrbL7^Mdq-{_xqIaHk^4s)KsrELKzcx$K)OKMK>DyXjUb&M ztsuQ1%^=+%?I8Ui4Iv#NEg?N2O(9(&Z6STxn#PdMY)xxOZ%A`UcSw6ke@KH!he(S^ zk4Te9mq?pPpGc!fr?#e5q*q(hEYdB~F48a3Fw!y7GSV~BG}1NFHqtlJIMTVTX&vd^ z)-;cFkF<~Uk8A+41IQL2dw^^LvJ1#IAp3x91hNyzRv>#}Yc>Pf4P-lz{XjMZ*%4$* zkUc>*1=$s3TabN0HU`-lWNVPUu{E26?2fJ39%O%z4MKJZ*&<|*kWE5%$q~@Wyn)W| z{eB*%p!b#;CNocFr$WT){^f7xX~O>|>c5RL-@#_Hh9$CIp6ukfl(+;>c47j?CkKB5 Dj=i{v diff --git a/lib/pytz/zoneinfo/Factory b/lib/pytz/zoneinfo/Factory deleted file mode 100644 index 95bc3a3b1a0c21d0314dd63c80d4e2e8ac0513fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 ocmWHE%1kq2zyORu5fFv}5Ss<U(KRptGD67I$7KW5Z)d;-0A>3H(f|Me diff --git a/lib/pytz/zoneinfo/GB b/lib/pytz/zoneinfo/GB deleted file mode 100644 index a340326e837ac8dd173701dc722fe2f9a272aeb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 diff --git a/lib/pytz/zoneinfo/GB-Eire b/lib/pytz/zoneinfo/GB-Eire deleted file mode 100644 index a340326e837ac8dd173701dc722fe2f9a272aeb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3678 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 Yx$RPlNVqq$bELatR766Qn|A?!0JySS0ssI2 diff --git a/lib/pytz/zoneinfo/GMT b/lib/pytz/zoneinfo/GMT deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/GMT+0 b/lib/pytz/zoneinfo/GMT+0 deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/GMT-0 b/lib/pytz/zoneinfo/GMT-0 deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/GMT0 b/lib/pytz/zoneinfo/GMT0 deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/Greenwich b/lib/pytz/zoneinfo/Greenwich deleted file mode 100644 index 2ee14295f108ab15ee013cd912e7688407fa3cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<UarX@YGD67I#|6}Gzy$zy_6BJH diff --git a/lib/pytz/zoneinfo/HST b/lib/pytz/zoneinfo/HST deleted file mode 100644 index 616c31bc5ea69cbc5ba90dba190a0f500fe8cc35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119 rcmWHE%1kq2zyORu5fFv}5S!)y|KbD&29MwnASZ-OeOy58h6Y>!g3%07 diff --git a/lib/pytz/zoneinfo/Hongkong b/lib/pytz/zoneinfo/Hongkong deleted file mode 100644 index 8e5c5813666a4d023bc9f7f7bd0e53ca1a61dd85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1175 zcmc)IO>E3T9Eb7Q*JhWJHgVxVyk!z8jf<%SZAD7A)q_o%R45M8rRWK#O+_RUA`%G+ ziG$OxhzJMc;;1h|yw+N@-ny%$>ZM&>T5F!agM*Ven9b~Gvd8^C@utM~rRt9pa=&mn zYux2sawyT1>mF*fJ?Z6gE4IJ=e%-dV2Rp8rhbL;~QT1ihd-}ROURh;Ri|0!!Ut!bJ z!jgV6%RU``E6?nteSW(~`p(TX{TDat7Y8QH%ah&uRYStO-g`m6SrRh?&7G2&US_hZ zIwU*3&JNB#B7><#cBrsR-q~XNzP~|+PmS0QU9Ea#-#z<L*6UA=SMBG+<@!tIT{GHJ zs>kMBF}a#i{dGaojL+Dr^Pw#!Kek$b8>lwl`<i9q&SpD#qe^~Us<ef(^F$q+YkkR( z_;&|wU{h9t^%)zg3F`2&fGJv-(M1PWm`J!wM{=DenmMnh^mdr3H;?L)GZm(EPpkNS z{(!sx_EBnQlz%#T+!s`;tzAWUtKwhpy85_U8{5EFT-7>%pZH&_S8#^~krk08ku{M; zkyVjpk#&)Uk(H69J>A;K;+}4GWO-zLqyVG>qy(e}qzI%6qzt4Eq!6SMq!dqA3sQ`y zs|G0tsRt<tsR$_vsR=0xsR}6zsS7C#sSGI%sSPO(sm{}tht!7@h*XG_h+d66D3ar< r1j<C}L<&VJMM_0#MT+%w)gtA3x_Xg<k&2O$k(%-U7aetxmzn5Kw|Ne+ diff --git a/lib/pytz/zoneinfo/Iceland b/lib/pytz/zoneinfo/Iceland deleted file mode 100644 index ac6bd69731d25fc20724798abd4f683f1f2ccbd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1174 zcmd7QPe_w-9LMozO-ES7AW$-}Z<HEHu>K$jMYysKwL@$Wb#X)mML|W;t;7<dl=M)i zRo}!@^bZ2T*dYiKHLYdZT4sOEsr}I`n`J%y-aii=I@PJ)!}fZ3o}Ko2N4D+WwcPpR z_{<YNoOR~Iz5jIdxW*a^ob!p3^%o9quDaOc^=r7sxzt=*-?XZ@s;T9W|86MXX<c0` zt*N{lZAELf?M{(&_zJb-N>K0hbxLRLV(p4wm-~Bt`XJUIiH&J}SaVgoOWO6(&NJFm zmXO|x1NwO0O-UApH92`!Qgil8>d6s#I*_M*EnDST*GlPcJgm>J<;sidE&8%9Bd@lX z>Fa$dc@vzk1EILQUHC%>OOu+Ol`liNw{&QDN`@z5I?~=R?|P5w`^&fGLvvI=o@$iQ zb3q;3b3#5HtCaDu>gURG`Ld!~C)O;IuXA^3W<j=O#@FlQ&q4Xty+psKy*d@IkQtue zmpAL5u58yGiJJe`98Z?(j*U7qr@yD4*cY=mg(6N#AmA(wEOR!Pdw%Tk*mq9kFZOfI zVMAm`WJ_dEWK(2UWLsoktJxUY8QI!u_C_|hn%$A@t!96u0i*+@1*8Y038V|84WtjG z5u_8O6{HuW8LR0AX~$~%K^j6jLRvz4LYhLlLfS(5LK;IlLs~<6Lz+XnL)x>N{*VTd x4v`j-9+4)IE|E5oK9NR|PLWoTUXf;zZjpAareCCCq+_II{9k&`F@XniegRAxR~rBT diff --git a/lib/pytz/zoneinfo/Indian/Antananarivo b/lib/pytz/zoneinfo/Indian/Antananarivo deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Indian/Chagos b/lib/pytz/zoneinfo/Indian/Chagos deleted file mode 100644 index f609611c8fc76edae40cfcfede2767156b11429d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@McF?)w~rXLmQ$|NsA=k%@_c!TAYD-YtNEg@GX?fq}!v pH-tgkz!ZqhKoUR@LW1!?Q~!gk0O<o+1ENW=ipvIQt(~qJ7XZtCBcA{O diff --git a/lib/pytz/zoneinfo/Indian/Christmas b/lib/pytz/zoneinfo/Indian/Christmas deleted file mode 100644 index 6babdeead509d64c1317373a537b4bd7391af8e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14FV5NGhp-fyKu+ghAWD9K>Y^A;C1D XLH~=zRz3mg!*42=4bXf$U2`q~*SHut diff --git a/lib/pytz/zoneinfo/Indian/Cocos b/lib/pytz/zoneinfo/Indian/Cocos deleted file mode 100644 index 58f80514e13d1929df97047c7a3de221640d93f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 182 zcmWHE%1kq2zyM4@5fBCe7@MmB$f^JT|34!m14GmukW_RA1B;Ju2!pnPnXv&#fFXng c^MFSEuL)IK0@6pwTrL~19d^2AR>lTg02q%N)Bpeg diff --git a/lib/pytz/zoneinfo/Indian/Comoro b/lib/pytz/zoneinfo/Indian/Comoro deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Indian/Kerguelen b/lib/pytz/zoneinfo/Indian/Kerguelen deleted file mode 100644 index 2cb6f3e357b6ea6832fa9a5d56ad915a110b23f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@Ol(Vp2o>|Ns9P86gr33~m7oEV>2;4B7^!V4)BaOamJ9 UA7mm(BYso4Y=Gw5>6&r@01o#Tp8x;= diff --git a/lib/pytz/zoneinfo/Indian/Mahe b/lib/pytz/zoneinfo/Indian/Mahe deleted file mode 100644 index 49e23e5a0a8d5e90a9da0838a08c17c20eefaac0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@MOb<ylMp|Ns9P85tOi|A3?{92i)9d_x$t4NO2>h7b}= X0~+)nWFkl(ep9(@facrjns5OC04Eyn diff --git a/lib/pytz/zoneinfo/Indian/Maldives b/lib/pytz/zoneinfo/Indian/Maldives deleted file mode 100644 index ffa33658444a473605f9a04a78f3ec345e0fbe7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 211 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$a$-Oct-vI|Nj}8m>3vbUV!9*BntzBTL1%xk8cQr rFA!@Rn1UpLAcO?tfu{Zs+csYUM1!mW>8}T}iL{E#257CFt|=D)o|z^D diff --git a/lib/pytz/zoneinfo/Indian/Mauritius b/lib/pytz/zoneinfo/Indian/Mauritius deleted file mode 100644 index b23e2cee1f1bca4abedd105b04824431f40e8392..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 253 zcmWHE%1kq2zyQoZ5fBCeHXsJEc{=M^XGpNVb&$ASWZ=m>?SbdH{tNa0|Nm!V1VSbT z2GbWH<!%9tEDQ`54h$SVz99_S2Btu40+IlN5E5(y8vGw*BS;^}Rxk~;8AO9@2h%_o RfN0WO!DRz<k)5sy7XT^`DjNU* diff --git a/lib/pytz/zoneinfo/Indian/Mayotte b/lib/pytz/zoneinfo/Indian/Mayotte deleted file mode 100644 index 6e19601f7d3a420c1d4832178352c1eafbd08146..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271 zcmWHE%1kq2zyPd35fBCe79a+(MK->>^=HeWSr?8^bieUd|KSxzCT2zk2Bj8|A}s@u zI%N$84h9CbH4J<Z5g*?W23N-r25kc)V*?OrVhU0W1tBEZ{T~QG_Je4U8$dM39Uz(- MZs7ttQ`eXa0Kl#|w*UYD diff --git a/lib/pytz/zoneinfo/Indian/Reunion b/lib/pytz/zoneinfo/Indian/Reunion deleted file mode 100644 index 11c6002e2ec443e44f07313dd8d169195b5c74fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@K3_8Ow(H|Ns9pGBPljfTb)P7+8FKLm0FTAp8&#OamJ9 UA7moPApE9s*#OPA(>37&07mc_c>n+a diff --git a/lib/pytz/zoneinfo/Iran b/lib/pytz/zoneinfo/Iran deleted file mode 100644 index ad9058b4937b8786f58e2cbd61adff5ffb6f0657..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1704 zcmdVaNo>qf0LSrvt5n2NZHb7a4MAzg{5w;fr_0Q!XrC!sH7#0O(FI-ThFW_-i1t7n z+SrpyL>&n68qqjZ@}wjPLP~-Q2SS2Gkc{^oCvkFc@SnW-%*>%lli&9ROE*@!tUsP; z^9zTk!W=$N>Z;kT9}dRq(KpV?F-;DCOo!i&d7t4QTU@1M=Lb}r=bessj8jR8EjnrN zq?$2sLeK1*tde`~%aqe4!qG8A&)RoLIqN;rF25;K3pZ(3&PU~Teb8xBo7L?2DKdT7 z1(E)xM0?&mSK`TGIp<D{$mks-GcO(&SzS3g`-oQFoh{N=`$PCwUeI$3oGQmROV3Na zs`6gnk@9hYSa5r>3|xOC^3S*Fg`LrA(ZOqSacib1sD7&p%j(pU{90X<{YfoNIV*z+ z(?#*9N4mr+RV5#W%Vn=R#PSF2az%ffD7|t^mv!f=mB&8IRR>zd>gMTsP1Pl}c3F=u z54hAi@lvi&t`r+4#_5WYovPw{o~(R-PgK3QE35ApiH&_Bz3K9PwfT%&)*S5>wQZHU zuJN6!U)LqK6eo(U^Alu)r&}~mE7DC9o~q{P1G4$sNYV1PS8p5isqF(^z2j!TYVB!| zJ5PNTyV|?;?tRH>Pu(-Qw|tG*8w!OYBBO>xMGpO^uSm<X1xqmRQI?e|tl^fGTNp9g zvV`A?wJe{E82{_{g^Pk#(41u3?Y7PPY;)2$=G*U2@GpjE{?8EOLk7rSVuZ*Lkuiof zgG5G&3=<hAGEiisVa-sHu_A*-MvDv=880$mWW>mjkuf8KMn;Vc8yPn;@UUj&$k36o zBZEgqj|?9fKN0{E0TKcd0}=!h1ri1l2NDPp2@(nt3la<x4H6C#4-ya(5fTy-6A}~> z6%tlh6BiN~5*ZR25*rd65*-pA5+4#E5+M>I5+f2M5+xF5SQ94_C=w|WDiSLaED|je zE)p*iFcL8mG7>WqG!iuuHWGJO6F3q%5;_t)5<C(;5<U_?aseQh0CEw8HEsO2m%)gv e@P*-ZxHH_g`E0HWZ%RPePCF&wN>6jzBYpzV*O<5f diff --git a/lib/pytz/zoneinfo/Israel b/lib/pytz/zoneinfo/Israel deleted file mode 100644 index 2d14c9998e9dc54bd1ad4710332abafc8e811242..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2256 zcmdtie@s<n9LMqJCS!=^WQnzgfMptrmkV5wO43je2;>&GJH>!w1ZFBdCNWLOC;MTB z#pblu{JCXp^uwWITXT`M>1-o7Da%yMryn3oaK|MmZc5Mlv{hSw^;cWpyK`S>_fN*> z9V}U1l`8&m;pP_}uF*WaM=SHS+n4>uiNyzXd(W4FZ$7>yI*wnpI~%LC-Mr5J_Ek%t zeDiC0s<K6Ot<d)Ak_p!73voxkPdY5m#OCO;<NKVmmk#N3t$xw{S-d{4-x5ExcBl*c zzEHZMR{R*M7eBS-TR*4!?O%3Y6Fn0r+PyWW^u?%?s&Cqe=<6%8BkCG-#LX6a^w6(X zWK*gg({^1?+I+>1Z6C4XtoH)(8xL5M^A6khl<n5{#+L@Bl=!Us#GJs?F#~$)cfIzs zOP%`uqoV=|UH#Vd<~V!CkxHGo*<;V#u|Rvvn*&Mf6SOb)n4O&aj!v0f9Y{?+WclyD zWv4}Lx6-aO1!jdss9BvJnK4wZ9_Sv{584Crq5Vs&+3)X_nR^df55KZe&Z*m@=dMYY zj|BEvSp|(^UT%?`pSW6N#|}9;V~a&je}%K4KTYIb9F%$OcZ&SBoz9~l4U2*;qn*O- z(Q4tQ9kQtAnhKO<IgbU;sK;04$zuNz6)cz`pO~;qg%WR<p{pCzqUZ!?QP)zncyNqU z(mGEq=^AjJ++~T<)=sCaK1-BuJK$KQQ^eBx4*67OxTq*?kx$3;iOS5avMM}GEt^y$ zm-Y0jXKse%@?$5|v*$D9iVr?iD~|`ARj+MP)lKQnbCuOfzJ8apdSQcjzVe2%#=A<? zWOq9+j4Kkg-eFmLIa#b7cTTSDxI?VF{JDJblR>e*V~?zRH%is-e_6g%`<vSEc7^ne zy6w+T*k7;z)teBL-GA@+>mp2u={`?{5Hay$tPmM<J>&oQrJyh<^39Vs-#o==UjBZ; zf3ckrbD>Yax`Av6*%7iOWKYPZkX<3$LiUAh4A~j7HDqtDW^>5yknJJ+b2S@8c8F{d z*(0(^WS7V`k$oZ?MRtm871=AYS!B1!c3sVW`TMb9SF>Ye%gCOQO(VNTwvFr?**LOu zWb4S@k<BB!N4D>3_K!3G=>XCKqz6b7kS-u?K>C0*0_g<O3ZxfEGmvf|?Qk{yKpNs| zI)bzW=?T&lq$@~UkiH;|K{|u92I&pb9FFcF?cwMT(jZsUA*4kdJwlqq(Iuo!9DPC> zg>(vO71ArDSxC2#b|L*j8isVt)wB%hnX73U(lw-QNZ*jgadZx89Y^nw=5cfnX&*=b zkOp#e5NRROLs!#8q>D%!kv<}gL^_GI66qz<Or)DgJ30D^G?b&GNJ}|->S~(G(N$N| wR*t?RjpgVp(psdqNOST3+TBP~<C!TY%ZY`lUcc9$l#-rUnC$bWd3}+;1NQXO$p8QV diff --git a/lib/pytz/zoneinfo/Jamaica b/lib/pytz/zoneinfo/Jamaica deleted file mode 100644 index 162306f88a80d69cfa5053103c285e2e8b221c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 498 zcmb`Dy-vbV7(g$T1ZXQ1e^3~8DkF&lJ4B5z(M6{&V!Cx@F(&>H1}9lu4U2;b0|RVq zK7-qR0h9X#UcUpQE+&4>cTdydrsqT#Nxz|fOjf?IOhuOW;6{$8((EhuSWOGTBrd#- zjcXoaPv58h$BW)vUZuswoi4rJn&7#w%cD!PH8|1R$+6ivuj}2@&{Uef-U~gme-Osi z{HLioUYv0@etE2&J4&t2thI}&%3J%s%=n#dq|Rj9J=s<y|FoXy4<=S786I9kjJN?S vh}nu_2Qh?LLQEmH5Mzin#2jJ|DFCSeDFLYgDFUeiDFdkk|F4iM$&TD_ya{zn diff --git a/lib/pytz/zoneinfo/Japan b/lib/pytz/zoneinfo/Japan deleted file mode 100644 index 26f4d34d67b46513491f26c2e661c6e653cc130d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309 zcmWHE%1kq2zyK^j5fBCeP9O%cc^ZJkbvvel>u)1J-1zaU;O1HD54YJFKHOd_`{B;B zM<4F?{Qtnr$OM5549(0y^$a}=7=fDWCNOY7NFU!21}_&N4h{iHGlFmk36A&=1gVFX r6o6=uW56`fK_D9BC=d;D7>EWr4om|b2%<rb1kq$Wlndx;T}v(iZ^UHp diff --git a/lib/pytz/zoneinfo/Kwajalein b/lib/pytz/zoneinfo/Kwajalein deleted file mode 100644 index 54bd71ff2dc7d7fc6f3ddb6e3e5ceed4c92b72f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$obzU9iUUP=Rp1c|Nj}8n3)+E<~#rjGtAn+!1Dip zxB~+R1H*~~3_L!*Aq?7vh77ufMnD>2LkJ1>0j>C7XC|QlqCxh8>;c&gqU(V|<k-(; L19XiY&{bRj+D$OG diff --git a/lib/pytz/zoneinfo/Libya b/lib/pytz/zoneinfo/Libya deleted file mode 100644 index bd885315f84f8615da866553c9c0086ad430ad01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 641 zcmcK1y)Oe{9Ki9Xt$}*US)7;RXlo!MImJk57onkU5{bt^QqwbvK^lJolR?C25Ro)7 zh{0lNBC&~(r-?>FBF7@J@O%%G$>4YQ-1qL5yZf9spI>psuc<P3Sd3#9=Z*WX=ZV|X zW9u${D9dYCR{3FBR|ZB^<)EaWvKM~S*0{8*-<18{r<)&p{g#_W*;+dC+s^J~thnyC z@7cOzyH<5>K1)YUs;;FC-JQEs@pMEdQei)t9FaX^C%&6~k%Q@Bl^R;rGrK!t*1Im` z^2I_p6l{_2eqC`ici4rfTQKh_Vou1sZ-XUjI2ZL()1H{f%yIBU#;l+5{_yc1W&ofd zP#`E6K@A86C8&X+;P6a<C`dsK6a|X{MnR*%QSc~$6hsOn1(O0wL8ZV_a4EnPWC}C| Un*vTjXMm^wf*&=1qTh{v0>JjEDgXcg diff --git a/lib/pytz/zoneinfo/MET b/lib/pytz/zoneinfo/MET deleted file mode 100644 index 388dd7442a540317163ddc7d83fae62ad9bc9155..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2102 zcmdVae@xVM9LMn^VkI+rqrs7Yn1pDZ{D#yZwew&mM=6()N@yTz5yeFeN@EpsPMLev zSk577#h5j_`U7hXt@U%XkgUcUmTSwE{hD(#XFta3^ZfQ_fAwE|Z{K^|zIT7zAD`DV zvTj>*k?$YZ<?abL*DiPSd0PK;|Hl4_Q|Av%#xyW^RL{LB9qies(P?cOJG;tGyyVyT z3qkwjnG!qsWR86rO|{Qzmg%a&MkVc;WmorvWbI{2Zpzh+NSbDbSJ*YhzbhsCC#BB( zO8)p0@?UsMX=h&5wVxl-toNQ)`my7daU^D$2M*foURl=mCu~kj)UMmmXmhJ0mR%mQ z>jPz$laXulrl(nMLQuIEOEmx694(kg)eRq9p&N&PRbcQ3E$lm`yq=Gh-+oL5O|Pi1 ze$tAf!&Y1|VkK*ywbIN1D=X@>vZ-!cl-{RBr#IQ+KRUGJc+hVCvQFhg4XPMfqFZ(@ z*V6tBEo;u!@}9+ZYh{9hP3g8G=d41(DXUEQNLATiTh+PmY-RjAwsL&bZaed$t%|*D z)njifytmhCjy$j1TU+gp-lw$suH9PGvRSn^wJ1_ur91sKS{qoPwU-K1mr`kUU(C?m z7w6l$*MHIaiA39QDB0?Vf3|zNFIYq0xNU5huuVID)V)jJu*Ui^HD&F$%@r{<UwKqp zGNmow@6*<)sJ4xE>Auqu-G6w!T0U&E2X<Acb*R=J+?u6_c9vS(>T-)V`|aVv+14KV z%^peiSx0_~I?jJ%ok^F}dGdYhIyb3DUmLQ=#z(Y$|GU}|8_?rjgX-SftvKIh@&ARF z&zEEz+>AMK?%e+U&XaH`;_ljr`zkJuA4LAe;s4WJ48YTk02u-@24oP(D3D<w<3I+2 zj0719G8SYo$Y}U~3<ntxG9Y9`$dHgRA%j9jg$xTB7cwwUH!@^s$k>p<A)`Zvhl~#y zATmN^h{za`K_a6>hKY<587MMRPd8L#tjJ)I(IUe|#)}LX88I?sWX#B*kx?VVM#hZ{ z92vQ%8#*#}Pd9jE^vLj$@go61B7lSdi2)J>Bnn6vkT@WLKq7&J0*QsE3kDJmPZtg( z9!Nlth#(<BVuAz(i3$=HBrZr`kjNmRL1N?Sf`dfI(}f3#4-z0GLP&^^7$HGIqJ)GA zi4zhiBvMGIkXRwXLZapA!iB^O2^bPFBxFd;kf0$^L&Ao{4GA0)IV5yQ?2zCg(erfS vL*nP@0*FKq2_X_gB#1~9kuc)_GLDHZmkSH&WguLfSDIH42p1M$esSVoBHs-X diff --git a/lib/pytz/zoneinfo/MST b/lib/pytz/zoneinfo/MST deleted file mode 100644 index da3e926d23e76bc4ded05861c01c1f494b9c8d27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 rcmWHE%1kq2zyORu5fFv}5S!)y|G5(w7<_|6fSeFA^>G2Un{xpGmY)pj diff --git a/lib/pytz/zoneinfo/MST7MDT b/lib/pytz/zoneinfo/MST7MDT deleted file mode 100644 index ddca8d1967d93336bb0432f4df79da3efa7ac689..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2294 zcmdtidrZ}39LMo5NFgUKPZtaU&9o#45Kt0MW>nA#=>*{s&<LYIcC-R7owN|2f@1v9 zd{)LRlc1ZmW?t89Y^ZCvwz9e7*0dIOzgRZ9Y_YhUp7--_fAwGM%bwrsjQ>CHz^dSy z0{O?q*#Gdj=k4RS>U+a$ULK6q{ZFTxffs+&ANItX@Vr0T!i_2N*VrR!{D(T3I8&=8 zKk&-bkzy5jGhJk7nu_Y4lITY#)wK1gI;L@^xu|MFUtBV7V)I6H>@$TXP94*6dk-kj z=qvh?F9uBf*`tzhtkWdEHy}xG^(*y4r(D|eq`GWNvs@l*Qdg|>Npkt^YDRIUT$y>D zN|{@xXU2I{YIKH9{cXNU`zlVSho_ra$A8kZ51cnw4V=_-LbKG>PwdlkS47mjwI9hf zg<q;`m%StNliybvSw=FZ&Y4Wl2FV;bZnDk?<ocmkP4?+FozwS}$vsr97j(3kyyq6_ z{FW|NuyKZ7=v$)}RgdYyj62i~1)u80v017p?N2HCK3)}%pB3+kU(}7C4a$=J9&^*+ zZdtnhXLIweZBo+uvALydm%g=fzqzext1eyetSKv9t;>ENR=%8e@txkI$`cx-{7{ct zcA-QnUV7c!`ANO@w|ASn-d(CI>w~7McaFY$NrkBnMeF71IVP~;8y%QPGBt%Kbj`Qf zs&4kzQa7BaR!$Aes@F!<JtKRhzALQm9qN&Wro(D=-#WR^f66p=ckBCe4w?sAg1RYT zhiUdz=;jL{(~^;+TTa%QU~G~O?)93r-~FoB_13BfPbJDjp<?y$zENqdNmFgx!?M0` zQf+8GEE|*0sZEu=vT17Ego+O8(8%XzbIK0AdFZIwG8WR0^$nQE&(!Jmj!v`nh*x(s zG@ETZ({+?YN%S=P`7ixNBD;-9B=UBDRE+yhci(vb@__hD?W?t~D!@3Rc!7U0qKG|) zgp3Ip6f!DgSjf1Xc3{ZJkf9-CLk5R#bo^j=oE;x9K#mb2Lqx`i3=$b7GEAo(Co)i{ z9Vs$YWUR<wIYx^Nmt(xhfH_8t44Grh$e=k!jSQP(+{nO@kt0KQ+OZ>pM@Ekf9~nOq z03-rP2#^>cLEwl25(bVqAc1h&NFbqb+E^gL;D`ni4kR8(K#+(aAwgn-1O<r-5*8#b zNMMl2Afa*E*dW2-hz=4Sj`$z};)oCuB90g#K|-R0gb9fg5-6vQ6cQ?@jTI6sr;Qd8 zE{=F10Yf5&gbaxp5;P=gNZ63LA%Sz+$RVL~+Snn%bK2-3;p2!O5<rd!A|d36AreH6 zC?a7*;)nzii6jzAr;Q~NOs9<|5>BU$ClXL3qDV+NVu}QnBdSPPIpT^0mLsxAXq`5; zNN}Atx=47PHoi!Jkq9FpMq-Qv8HqCfZ^G=c#a?WSo$X(kTacTV?a$B8&CkvA{0**M BL=gZ0 diff --git a/lib/pytz/zoneinfo/Mexico/BajaNorte b/lib/pytz/zoneinfo/Mexico/BajaNorte deleted file mode 100644 index ada6bf78b2815d3d99c97d521ab9a6b35c8af8c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2342 zcmdtiUrg0y9LMn=gpw%wq@u(hds2#laOA%z_Rpvz$O)7qi5Z#kXHW)-p%f81w$_^C ziw?_G^yKV<wIXJb{bS^oTWhSsR+x=3Q(zcHtQD2x^t^vvcGX?$clJE5-_G6d;`8?J zsIE+N{_)JU|8RIZ?BPA~wccM_x*7}Xx~H3_dMnH8-i=ny>9Fak&n6DH46gd6Zt(c~ zb>BpnIz#PmPhD)@EZ^sCl}lyGaycPGM!orJZ1EN~9-pMfr_<F$=t4Cy7@@9=PN^Sy zep8cY2i1@5=hgg?ZnNP0fC}$#Hw)kER*Smc)arP<y6#!giyQ0JlIp#BY3Vi<k>}UT z)~!{`6S8#V%3`^GUZjo+&XlO>3=@5Exx@@EGqE54E-QLw%nh$z5Z$m^-+1sNSy>XU zSJiy0;xd2IH|2k*ZjSg;$0v5G_}NL55Z0m+hCern6T8*wz8;fwu33^hj~dUZU9zV6 zag%a%qoh_H(P{N@lJ4E7Gm7U*W_*dxN*kB8q1ie+W{%1pi_+`<98>GhUe!4lK2;mu ziZr);@VdIS?GJO?i-*<iwcnXLTDxRpVV}9P{5i>8W6WK-d*tp#hm1F_P`op*=)90r z$s0PT^Dixt%`crY1z*>Quc^b_(_0{gJNKKSV;<SEq10?`P*NO|WBl8u#eX%{lw^J- zC70Lh?JIs(+dqlXrL*VMj+3+czTtP&&ejoqf8X<}to)3AptDi!@(r5@pXrd@$^GV` zs{K+Pe!^6EOQmA6)l|jjNYy~4sSb^m>Nhr-n$dtfe5^u0@<oi=)8N&QcF(HXk_27X zHliNOny>fPo>BD?lX_p_NwqI9&opHBOT+LLb0G4B9OxS`jWezCL}#~oa;Q?8n%m7& zr#DG+S-pAsg+vJo4hp^|IAo5!{yV=w;7Ebv1OhLM6A}otwK&)E9<;!{m3uEO@cA8I zvEM1;<l1wuJw<*7<2XTo-~N9wu7G_Q7&0<sXvo-*!6BnVhKG#L)eaCDAu>c{jL0C7 zQ6j@c#)%9R8L6usDl%4AJ6L42$Z(PIA_L~j88I?sWX#B*kx?VVM#hZ{92q$>bY$$v z;E~ZI!$-!C1i;ls00{vS10)DY6p%0=aX<orL;?u~5(^|4NHmaexY~Fi0dchvK|+GW z1PKZf6(lT3T#&#ZkwHR(#0Cit5*;KwNPLh0x!MRJAwpt=1PO@}5+)>0NT85NA)!KI zg#-(U77{KbUP!=PZN!j}x!RZ^K|`X3gbj%s5;!DsNa&E*A;CkUhlJ17#t#XgtBoKM zLRT9@B#1~9kuV~0L;{IK5(y;|OC*>`G?8#3@k9dZY9oq-)YZlm3974&DiT&Cu1H{! z$ReRdVv7V9i7paeB)&+1U2TMs5WCtKBSChxQAWay#2E=R5@{sVNUZUHAM7w&^K4u5 UBwxBG&6ASkOHK8pdQ!sv0^LWd&Hw-a diff --git a/lib/pytz/zoneinfo/Mexico/BajaSur b/lib/pytz/zoneinfo/Mexico/BajaSur deleted file mode 100644 index 43ee12d84a7c7e47aaa92406d01a539ccf93079d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1550 zcmdUuNk~>v97q3_4T#_^8mK6uf+)l(hX%78(kws6PcyCjEc?u|)HKlsy-)~+<u!-` zvw|op2_<r%0a`@^6&ZpRfkae9kQmW~yxw_2n^rAa^t;@59&htD=eYAqykYXk#@b)F zY@>a7pLC(?eR=!Pu7NIZj;A}m*VE%4>FF767<~Al!qeY;eNd!ahZY}FVU<(#q9m^h z&-|t%=C4+fVJ~#lxP@x*jIXlzoxfW0^SLbjGSMvSdMeQ!erEa2R*7l)XjZh;%gVCH zCiYN^j!Ww>@kIx8Lhy03Dxp9p22`1(d9ga_TeC{`ovV}kE7h7eWAxgdY?bn8j<`-m zsnn~!l2$WKr8mBnjKT<$S$a>hVy7B+$`#3;{oUjQHp)7AX>uoD(zye-&H67#bl#n_ zCcm##Z@7F*ZR||dn+~5*1t&tZr$np5I+tut-mJE43YMY;32JN11o2MvnBtkArFbaL zY#Z*AlHPe{`>Sr*ac!(Az57h>Y<_QcUF_6l6%R~#!%1C{_fGBh*6PZo_f=J5zTPvv zO;rciNcE4SswN;$YF?D7+E3B4_eO@=_hgprKflu)XcwtFm}csay%wKQ&Kd3F`wxy~ zosJf<tX3nwmeqDn##>gC7JuG-)X4V~ms?y}Zi%;Vx_w;<Zlw4<_g@HP*+U|TND!GI zLP4Z*Xp04r3yuiJZ_71LM1#l%5e_09L_COm5CI_)I<!TE$jA^9A|*pih@1>TA(Apg zg~$pK79uS~T!_37fgut*v_*!<?9dh(A~i&8h};apA(Ashhse$l9wI$Me2Dyv03Z=C zLV(2J&<+9;1tbhe9FRaDkw8L$!~zKh5)C69NIZ;yAQ3S_g2d#|4hj+#BP>W<jKCm~ zK|+JX1_=%l9V9$Re2@Sk5kf+Q#OTlt5)!3DJ4{HNj6flgGD3yK$_N(z7t#9JMMv2s V2fD(8LW4pAU7;aC5kVn-zW_OOkC6ZX diff --git a/lib/pytz/zoneinfo/Mexico/General b/lib/pytz/zoneinfo/Mexico/General deleted file mode 100644 index 1434ab08804dac08e4f595967d8c325691f08aef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1604 zcmd6mU1*JA0LLG5WLQm@3m+vZt>*0OI2dMg#+vQehhv*zJDWLk7<=a9Gz?EoN-^7o ze5{o$U(q%fX3T|@Q)Eq(FwrTJxbRSme7&CEn>$y^h5ys@{@&iJ_u}{Sl?4MS;*X6o zU%0HwT;3<0>v=1?K5dKi1d9FFJ%j$<7`MOo$02?9Ww$?k!c}l@^~cD)cP|PCqQa4Z z>%|2_r{W^dLro8pYeFJrN3=}ME)}k#cXICRG~rHpAm@#qCgx9ltLMM@DHeRYuhWJd zYGKbEy{PNETHM#H(~rGWOWLH)sJpE)4<40E^Uo?zb)C#gJgAms70TtY0hK)~Rc3!_ z5;=}Ine%j`Sn=w!%<at<dHoZ$_hh_Sd1YAVH;fYnN1o`y%Gs)@_J&@SIY#;N+Vtuf z?^SWaK3yXERcYL5SsLk5Yla(T+3i-f_Hnsfcd1jXzm_4(54VVl<1?i{xLs84PuEpB zKCz*EhOUlxi;d|~IxwL~)l7e_Yd*QurXOJ)9Gt8+zqqKkT>YYI!*}J@)*-d+`~_K8 ze@n?jhh=@)GqF9eMea!J6FZlC<*q61B9s`^p|1x-Lu{^Y7^)PzKg`j4ZhFMtfmq$x zQK9yAe$@M$GSz|RM|wmQXQVj}`^nqCJ(krGBZOtOw+M%2T|OhCE$c@2h#31hKF{kD z-c>%~;bxgz;=_~Q^ZkWUmKjz-%!1ejF$`jvO=B9wHi&T$>uehHAokfb20|=^n8+_; zBg06Dl?*c>b}|fwSjsTfrm>Y_EW}!fxe$9H216`{m<+KQVl>2Rh}jUkA%;UNhnQ~D z*v>HCrm>!3KE!@T0gwt9B|vIm6alFMQU;_BNFk6)Af-TRffQrYR0AmoQV*mcNJWg2 zAT=?Hf>gyQ3sM)OFi2&L(jc`#inD2|gOmrU4^kkcLP&{_8X-kOs)UpYsgqGCq*6wy zkXjkV+BDTN%C%|gWfTmlm{BsMW=PSHs^R}%_E;0V+XSEBbvcurNeSNMB<Eab(%4`7 C-Qw5) diff --git a/lib/pytz/zoneinfo/NZ b/lib/pytz/zoneinfo/NZ deleted file mode 100644 index 60bcef686badda46f36652694d06d041c70a9d87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj z<t{CbH~x0rWPQTJwaa=qkKbs$+B(`j2bOLrXs<qR9$a`{Itu#Dcf<STP-3guIjU8< zLh8+~pZfIhwKDU_$IbfQP@ehzIgcKF=ZfdpiI22<+mNTHX`dcncf`}Xd7GZd-R1e9 zs6zXkwVspN4bmSdo`I-x8Ms<wp8EQ=RG*MP)o0%x^}V!5{2KpI|Dod=P<uuLyP7np zut#rxwNA&T?ACGBB|1LrIh|0Dr4vSqH8?R+gD-5Bkg1sx(!W|l9T5`Ryhv{O-d`qd znI*UOT$Hd9Kbic-piIdamZ=-t<+fNK4KMvvrv?3}w>up&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-<KEG6FN9>h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-L<DAb!D#1FYJ&7$+5C9<rPVr zG)~gPswMsUuaYt1mPJ?VH1kZdWSuV2#mB;ANoSU3HyV<&Gg5PF&PrZYfZkI)qDv)0 z?#)nLmg+CA>Akvq@<qw_eoOOj49a~!Jg)`cwabc=rn<XdmizbD$;y^Cec;6sDSTnO zK3JY5Vpi%yd6BXzGhd5h0_5SDiMl%Qk`#|!F2&dUwB+(UF;5R`>E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ<OE72Q%HZSv@fZ}hR$?Xo`Us8&v?l*cE&t{aSe+3?%5TBV6n{Z)}Z z(Gx099!}S%S`+l?-K(T#YlzlvN|R^I-_^_EHR_*k@6lua)7vnbhO9Xl`v)AO4dcx& z!^bdMdN>~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-<jQlOc9wQGD&2X z$TX38A`?YsicHnknkzC{WVXn3k@+GMMrMpm8JROOX=K*Ow2^ru6Gvu_OdXlKtu=XM z_WU+X-`1Kxk^m$FND7b~AW1;7fTRJ*1Cj_N6G$p-tz00<u(h&*q{G(A2a*t5D<eos zkenb%L9&9R1<4DN7$h@DYLMI@$w9J%q{r6E50W4xLr98{93e?UvV^1w$rF+&BvVML zkX#|jLb8RV%ht*lk}xD=NXn3$AxT5BhNKP28<IFAb4cot+#$(BvWKM4*2*7}KqP}m z3XvQlNkp=Uq!Gy@l1L<zNGg$BBFRLuiKG+BCz4QGE2BtCk(?q)MY4*d70D}-SR}JZ zYLVO`$wjh@q!-CAl3*mmwpNOf93x3avW%n|$up8@B-2Q$kz6CmMzW2h8_744a3teM z%5ANjBS}ZHj-(yQJCb-L^GNEE+~fZ!`M&%iM90PFy3<@yIZ4jB&e*7&InFp|Y|L!m FzW~Tp3}^rV diff --git a/lib/pytz/zoneinfo/NZ-CHAT b/lib/pytz/zoneinfo/NZ-CHAT deleted file mode 100644 index abe09cb9138504ee3f29cff16411e0a3b0b957ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2078 zcmdVaYfRO39LMqhA$8+$Q*#W-P}96{xE*eS(FqL@lp|g6q(t;dlLH!QNkSLVR4V6O zO`G<Jn&r%4i?of}D$uQ34=S@YyDa-=TRv!LuGUS1e(!$|d(@-WIXl1C|NOV};B5cT zyRoUgwb1*=YrMO|hu3!Z;W~YrJ5GPO|E<J~KP~a-Aq!qSq@=zpmi+k%OX)dlsc*b! zX)VJx@wr}`6gg;<yW@6s)=rzUvd*TAwpw~w$kNa6P{zz!WxT&unSpF&9$unrzD?G& zC+F+h;S0)&Cu#Z%r!=ExL^HP?({%-jHmm(3o1ON9U7r`woP@<TH|0CcJvZCtjl8S* zCns3;hldsFpQW5vb}4uFc;)R`ul#lARnQStc<Bc!jI~=)cE5^Cmsv@0k4n>Xt@NiO zw&0gEEBkb}m49~079Q`g8-`L<v9G~y?E6uXO<}vK=d>!DmupeW>snk<pqnH6RW)t0 zs<XOPefekAj5cY>uf0}#wnBAh;<ogiEG-+Xv-&<y(E}k{zU7=6x>D?x)=|4v*}AR9 z?DooJHO@I{E2dviY{E+x8#|>tzJJ12esfH#-fOd_p=WjHtG#M|dBE;^I<A(d_S)SW z>ZG<#yQd+f)wMBOlb@n{=at#o)Qf7Jv_h@FC0YE^0=1nPvGz}<YTe++wtnEU?mO_B zbv*O6?(f=X8#WGUqXzWAs;}(9%DviD{<d|_=+r|~pSR6kOq+jy+`6nx-CwM+hlexu z$eY#n=#f%;?BHr`*_B~E+pDy-?GM{JHZ~R*mvGg8`cCk?{wwZb^o~|}<2>&~b0Fw> z$Lqa`f1mZyMlTdD441gGsF<_Rot_s6@Egx-{1?x>`@3U{2CH@=&pyBB`S*1jKz4v^ z0oenx31k<@HjsTF8$ou0Yz5hiuiFf=8(+5_WIw)cL&%PN-IkC&A)7*Wg=`Di7qT&A zXUNu&y&;=Jc86>a*`Kf5AhJVbi^v|4O(MHQwu$T$*(kD8WUI(tk<B8zMYfCV*Vk<r z*)g(ZWY5T^kzFI(M)r+t9N9Utb!6|z=8@ea+eh~A>l%P`0BHfz1EdK^7mzj}eLxz4 zbOLDw(hH;+NH>snApJlZf^@{!wFK!2(iEgCNL!G;AdNvfgR}<e4bmK>J4kzw{vZuP zI)t>y*YyZ#64E84O-P@RMj@R-T7~oqX%^Bgq+LkAkcJ^0Lt2LP%-1yy=^D~Dq;E*$ zkj^2kLwbia59yw-Yry}#eZdO9hLPNG;lkn)_r(P=k`pb@E6FR!=T=U%I4|t3i`;ea EFB%CJV*mgE diff --git a/lib/pytz/zoneinfo/Navajo b/lib/pytz/zoneinfo/Navajo deleted file mode 100644 index 5fbe26b1d93d1acb2561c390c1e097d07f1a262e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8v<nU)CgtR#>b6-0<P2(NH8!YHnHS1a(Ln-=0RD8?U+ zvtrCL36!+fOng|gv7xTv+REl|Yg!BKxhxw!Y?8peo%dPwPk;4STVM9OuOIjS&-=Po z`_|@&f812_4G-6C9^R)b{@GWcUmFNlJ<liU-dDa?dprTXw{@E6E54}vI`*j#+N1RF zyx$s!>*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC<xjIswP>}}?Nyq3Ob<M?I9d-V=h(6JxW8Uo* zv2XTB`ErZ6w*6Uo-Bypd-d8WDuPPC7rT5Ai`6=Rtlm#+=Zn2sf>5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU<P zcbE6;d+N8TqRba{anTx8{Ogb`NpBJ*XZOp}=vq;Fq+Kq%Tqw$3eO)jAxJEgf+VuVJ zELG(-K3&l@M?J8lOjr6t)rzEa?OOSja!thQs@zkm>gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vM<TJe`zEf=(Jg&En`PI|iz51DRZq?M>qPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(<dr(EuI31^XcR+y*SJQXgpA|XQThwERgFKDhdEUF(_ zA+khdjmRRARU*qo)@d~hMOKO|)oRv?EEZWUvRq`nR<mGa#mJJ8HKScLFRYp~%LdlX zv2bMN$kLIuBa25?Z#BzD)^9ZhKq`Qg0I2~-5s)fylmV#&M<I|(aFhb61xGQEYH*YT zsRvRJq#{;R5~L<bQIM)2WkKqK6b7jbQW~T-9K}JZ!%-fjK2}p8q(W9xBBVwfMMA2C zlnJR5QYfTSNU4xoA;m(fg_H}a7g8{!VpdZ!q-GpNL#oD6Hl%JGg+nUGQ97h{Nb!*B zA>~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5<wa#mE15^&RHNV6pj8 VNOLaC$jQh`b7p5}WM^bK{s0D?lEVN1 diff --git a/lib/pytz/zoneinfo/PRC b/lib/pytz/zoneinfo/PRC deleted file mode 100644 index ce9e00a5db7c447fde613c59b214e8070f30ba2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 545 zcmbu*KQBX37{~Fa>ThGXu?mi&+QDKF36auDBdLYEFw~^d?V4RTCt+f_yM-6v4M?P` zrdx~ZyEy4);`!cH4B|AWpQd-YzpsDXsISV8lh%K@oN2xMp0xV)a#XXeiO-<beU|pf zjcbQR>1=Gd?(Kzr-Fb9xyIFa!HeGMCDIcTtpg%LP{q4}rJ{_33#$9Zp>-+h=%Q$=X zU=|7|@nYr5EKP-8Zu!*Y1~o4~Rx$Zb(Hlzr`Vl$r>6=Itr-nrWE92FDUrJ@YhdvMV z_<xx7r6*b|6_9zz#6+EmOik3e$Yf+TG98(ZBtSACDUckAnuPZx3z7!OgCs&SA*qmD WNHQc_qNYRgCH_BQMr*FDXTAZK`l!MH diff --git a/lib/pytz/zoneinfo/PST8PDT b/lib/pytz/zoneinfo/PST8PDT deleted file mode 100644 index d773e28f1e7743936188263954598b2b521c15b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2294 zcmdtieN5F=9LMnsqI7wv(*sHbc2bk>6|R7SDE6S@E|>_$mBh@%co?LiLI`DKi&fSf zUvj8D(b2h<Yle>5Y!;DQt=8xPTC;47wfV4$Rn`_M-P?JefBUQdTIcTkUccSHe*b*l zp>_4OIi7!<82b;G(`_&Bs^|40^V+E-F;Dx=!I%D!Pj{!7p_#vL9jcnGE{{K@uUxEE z6K87lUmq8#@X4T#yx~`#13n$~Y=Vkzjn|X5-6k<r>1OikIGM7<Yhts0lGr`Vj8}ao z-lOm9xL=M*{AjO9_-0Tg4s@BT-|1D?9C=Ht7rWH8yG38u)}*el->z?{T%(eTLwf4M z1!|fvPbbePmm9swIwd++QZCImH+?@%QirCRv=9E2>Bq;-%?HnlZkeNRdGbA(QIV); zHhimQ<^HU1UGc7(o%E$n_xGsu@R;$%?NYvx&yD|wO=?d6ag#CJE}1=hO`vb1%x!<f zWbIul*>x}IoTgbaFW9W-mrs%0^a`Dqb5Rz==Ii{_$twRsie7l-f?D*^gf2MyiCTOt z+1!5WO?5}-Wpn4td(>Tx-<c&HM`USfzgbrOycEtgW_jTjS^nFw4rV^5g2N|tapGoG z+_zt^7+a<8ex=W>{JKF(c6OUpZ?BZn^*c=2zJRP=TxsrUQBs~-U_xsyNoXR?ROEgo z73UV|wbOr9Yd=iYmEmEv?r>P&H*!L)?-<qh_wQF5s!!_&dfL>+k`Yt&Ot);x954^o z?U3rkL#8HJDK%pqrY^le>IQ2~eQcW4A1yKs=Ogmaz8byxi&V9xC8!_n4XefqpWfOz zs<!3D>+OxFRa4TKZZ18nnj>D*l0P6VBR`tf<U`WhKWN&<JLJ)xUh~+QTG`p&Wgb6S zB<&lT%o7Lv66J~VL{GAh|I#lK>D=Y<L?W+BRE&FG<(>&;<sna5p}qIoTNbjLu%B&j z=wFN|Vh=+?#)J$C85J@tWL!==Fl1!N(2%hqgF`nuUKk$7jt>|hGD2jC$QY49BBMlx z>9pfS2I{mUMTUxu6&WltT4cD$c##1kBSwadj2Rg;GHPVl$heV#BO^zK?zCe^29Jy$ z89p+8BmhVRkPsj-K!Sio0SN;V2P6<q8wn&7P8$m(7)Ugba3JwO0)j*Y2?-JtBq&H! zkgy<eK>~wB1__PR#s&!v5*;KwNPLh0ArV4Cgv1C55)vgOOh}xNKsjxskWe{otdL+i zZM2YZA@M>2hC~br84@!jXh_tMupx0n0_U`mLqg}Yu|tCAw9!Mthr|yFAQC|&gh&jL zAR<vj!idBX2_zCpB$Q4YOC*?18%-pfP8&}oph!fKkRmZff{H{H2`ds;B(O+ik<dDA zY?0tPZFG_FI&FNB03#7bLX5;12{IC8{NIE*&K5i07CWP4ULYrsl~IzN9mo!3#r+Mb C_f!G^ diff --git a/lib/pytz/zoneinfo/Pacific/Apia b/lib/pytz/zoneinfo/Pacific/Apia deleted file mode 100644 index fd03ff765eace5b92fbe13e3f151a1bc49c88d03..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1125 zcmd7QPi%{E9LMort*fML2?w!VI3c=qd$j11S$04sS+}7p8@8{`y3U_%tXBVKNC<Jj zNOqiv11Ap$vV%-SBu?fei1^0A4RQ1T=lAYO;>5|*JkRS%lm5x`d8a!2vn$LW$87fw zCr7<IxvxCoE|*Hbo0pkjqIq(upPYzj%jjEa`EmYgYoScrvOjgBESF8|RW~=zmDG}l z+P>(6q(j#=ZD(Z5yED4=%^m4@oYT(fE3)m@Y1w|m*Bux7r0eXsb`Ne4$>nutYeX_l zY3->glU>Ua+FSlnvh&&{`+1J`eOfKK=kK-uS%vJr_f+@z-(>H^ZQXbAwG0$4>)_C| z42duMJ6`F5#&H?0yRZ4hc{x}ysUs#WBVSHxK@)Q5MUNhSP$@^I*6GoktMu5ljEtUJ zsK-yOm9gAc9kbR3W(7-vC85%Pc!iAd-Q%*h+dVUDyP8bU+F=QVt?g|y<;L8dG4qXi zq=5y^$&{(~yjZQfa(X7>#oQTppZxtu{l!x5&)b1DPuwqC%VNlCd>_jp>mdskbt@uE zB5NXxBC8_HBI_axBP$nmOCxI|izBNe%OmR}1t1k5B_K5*MIcomWgvARg&>t6r4)6w zAjKfnAmt$SAO#^6AtfO-Aw?loA!Q+TA%!88A*CU;6?MfS)gk2}^&tf!6(S`fH6leK mRTgzy{$FK=>s_%8)n3dQZ>UPvMOQ{^YhqQ&c+_(@@$fIA@G;;3 diff --git a/lib/pytz/zoneinfo/Pacific/Auckland b/lib/pytz/zoneinfo/Pacific/Auckland deleted file mode 100644 index 60bcef686badda46f36652694d06d041c70a9d87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2451 zcmd_rdrXye9LMqJpwhVKWemws!@O`gTvUW2LIninl6fRN8GVz42MwVlp$lXhnPXVf zmaRD|$pO<6kpwLTF@#zdvNfreebbp|D>PSA%fg=b^Ka{q{_2mOXV3Hc?QG|tvwhyj z<t{CbH~x0rWPQTJwaa=qkKbs$+B(`j2bOLrXs<qR9$a`{Itu#Dcf<STP-3guIjU8< zLh8+~pZfIhwKDU_$IbfQP@ehzIgcKF=ZfdpiI22<+mNTHX`dcncf`}Xd7GZd-R1e9 zs6zXkwVspN4bmSdo`I-x8Ms<wp8EQ=RG*MP)o0%x^}V!5{2KpI|Dod=P<uuLyP7np zut#rxwNA&T?ACGBB|1LrIh|0Dr4vSqH8?R+gD-5Bkg1sx(!W|l9T5`Ryhv{O-d`qd znI*UOT$Hd9Kbic-piIdamZ=-t<+fNK4KMvvrv?3}w>up&-D`o)2skG*&Q8;r!+kQV z*IOe#X_m;n;S%-sR*9}3BhH4k60_!l#Fphq+~N-<KEG6FN9>h32}^XYZ-XQRM{B|_ ztvc^YkS2anuSs8C);kWC>7CtylDs2N?`r&6Qr5@m-L<DAb!D#1FYJ&7$+5C9<rPVr zG)~gPswMsUuaYt1mPJ?VH1kZdWSuV2#mB;ANoSU3HyV<&Gg5PF&PrZYfZkI)qDv)0 z?#)nLmg+CA>Akvq@<qw_eoOOj49a~!Jg)`cwabc=rn<XdmizbD$;y^Cec;6sDSTnO zK3JY5Vpi%yd6BXzGhd5h0_5SDiMl%Qk`#|!F2&dUwB+(UF;5R`>E{z=P3LF2w(Yt+ zvh#qJz4WcDtJ<OE72Q%HZSv@fZ}hR$?Xo`Us8&v?l*cE&t{aSe+3?%5TBV6n{Z)}Z z(Gx099!}S%S`+l?-K(T#YlzlvN|R^I-_^_EHR_*k@6lua)7vnbhO9Xl`v)AO4dcx& z!^bdMdN>~%bOdrXtTXTI9G8*nUdGElrMdW?;c(bkFW0}A;0^1V-<jQlOc9wQGD&2X z$TX38A`?YsicHnknkzC{WVXn3k@+GMMrMpm8JROOX=K*Ow2^ru6Gvu_OdXlKtu=XM z_WU+X-`1Kxk^m$FND7b~AW1;7fTRJ*1Cj_N6G$p-tz00<u(h&*q{G(A2a*t5D<eos zkenb%L9&9R1<4DN7$h@DYLMI@$w9J%q{r6E50W4xLr98{93e?UvV^1w$rF+&BvVML zkX#|jLb8RV%ht*lk}xD=NXn3$AxT5BhNKP28<IFAb4cot+#$(BvWKM4*2*7}KqP}m z3XvQlNkp=Uq!Gy@l1L<zNGg$BBFRLuiKG+BCz4QGE2BtCk(?q)MY4*d70D}-SR}JZ zYLVO`$wjh@q!-CAl3*mmwpNOf93x3avW%n|$up8@B-2Q$kz6CmMzW2h8_744a3teM z%5ANjBS}ZHj-(yQJCb-L^GNEE+~fZ!`M&%iM90PFy3<@yIZ4jB&e*7&InFp|Y|L!m FzW~Tp3}^rV diff --git a/lib/pytz/zoneinfo/Pacific/Bougainville b/lib/pytz/zoneinfo/Pacific/Bougainville deleted file mode 100644 index 6a6c2da28faa75b344004f55ad2a90caf9046091..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 286 zcmWHE%1kq2zyK^j5fBCeRv-qk1sZ_F8E3PEOWHXfLgrm>sQ>@}KO++(GcyCj#2Y|4 zhMol=g>@4cI2agaZD8Q>@eN_nHZ)++Hn0TJh9D(i5Q5!OkVPO20&GAGVv7Rp_#d_{ zNCQL{y<IW^M1vdwqCpM;DF!(PMArkw7@3%vSb(m8xTy03$VEVRaf02&3wD<v$Xx-x SI2^~oz-0q;tevi*Ar}CQuRnzV diff --git a/lib/pytz/zoneinfo/Pacific/Chatham b/lib/pytz/zoneinfo/Pacific/Chatham deleted file mode 100644 index abe09cb9138504ee3f29cff16411e0a3b0b957ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2078 zcmdVaYfRO39LMqhA$8+$Q*#W-P}96{xE*eS(FqL@lp|g6q(t;dlLH!QNkSLVR4V6O zO`G<Jn&r%4i?of}D$uQ34=S@YyDa-=TRv!LuGUS1e(!$|d(@-WIXl1C|NOV};B5cT zyRoUgwb1*=YrMO|hu3!Z;W~YrJ5GPO|E<J~KP~a-Aq!qSq@=zpmi+k%OX)dlsc*b! zX)VJx@wr}`6gg;<yW@6s)=rzUvd*TAwpw~w$kNa6P{zz!WxT&unSpF&9$unrzD?G& zC+F+h;S0)&Cu#Z%r!=ExL^HP?({%-jHmm(3o1ON9U7r`woP@<TH|0CcJvZCtjl8S* zCns3;hldsFpQW5vb}4uFc;)R`ul#lARnQStc<Bc!jI~=)cE5^Cmsv@0k4n>Xt@NiO zw&0gEEBkb}m49~079Q`g8-`L<v9G~y?E6uXO<}vK=d>!DmupeW>snk<pqnH6RW)t0 zs<XOPefekAj5cY>uf0}#wnBAh;<ogiEG-+Xv-&<y(E}k{zU7=6x>D?x)=|4v*}AR9 z?DooJHO@I{E2dviY{E+x8#|>tzJJ12esfH#-fOd_p=WjHtG#M|dBE;^I<A(d_S)SW z>ZG<#yQd+f)wMBOlb@n{=at#o)Qf7Jv_h@FC0YE^0=1nPvGz}<YTe++wtnEU?mO_B zbv*O6?(f=X8#WGUqXzWAs;}(9%DviD{<d|_=+r|~pSR6kOq+jy+`6nx-CwM+hlexu z$eY#n=#f%;?BHr`*_B~E+pDy-?GM{JHZ~R*mvGg8`cCk?{wwZb^o~|}<2>&~b0Fw> z$Lqa`f1mZyMlTdD441gGsF<_Rot_s6@Egx-{1?x>`@3U{2CH@=&pyBB`S*1jKz4v^ z0oenx31k<@HjsTF8$ou0Yz5hiuiFf=8(+5_WIw)cL&%PN-IkC&A)7*Wg=`Di7qT&A zXUNu&y&;=Jc86>a*`Kf5AhJVbi^v|4O(MHQwu$T$*(kD8WUI(tk<B8zMYfCV*Vk<r z*)g(ZWY5T^kzFI(M)r+t9N9Utb!6|z=8@ea+eh~A>l%P`0BHfz1EdK^7mzj}eLxz4 zbOLDw(hH;+NH>snApJlZf^@{!wFK!2(iEgCNL!G;AdNvfgR}<e4bmK>J4kzw{vZuP zI)t>y*YyZ#64E84O-P@RMj@R-T7~oqX%^Bgq+LkAkcJ^0Lt2LP%-1yy=^D~Dq;E*$ zkj^2kLwbia59yw-Yry}#eZdO9hLPNG;lkn)_r(P=k`pb@E6FR!=T=U%I4|t3i`;ea EFB%CJV*mgE diff --git a/lib/pytz/zoneinfo/Pacific/Chuuk b/lib/pytz/zoneinfo/Pacific/Chuuk deleted file mode 100644 index e79bca2dafa7e914a9baa568edec1b428a2ac63d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f<scix2!*42=4bXf$T|)yd06J?KGXMYp diff --git a/lib/pytz/zoneinfo/Pacific/Easter b/lib/pytz/zoneinfo/Pacific/Easter deleted file mode 100644 index cae3744096402e8a452336544edf96ca9ae5ad8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2233 zcmdtiT};(=9LMqh;W^+W2$|3mS{MbwkLQQ*_=_YVJxF=dBs5cpfF(pgrWD3jVzpF8 zYi_!%R{7BM3tcFi%-?#l2P@BoBU`MSbgMNPJy}}*`@R3wRqLXgF8ZJI`@jA>7w6*a zeBPmkmZn1IZ&$4Sgv0f$Jv^swwzrYvy8pLurM@(9LEIA`8>iz7@paXk2wf|YcNdtb zjBJSxEYdNKUt$xE>ew$QB<@m*zU)|7;>Ul~3470}#L+SB??0(7-#wzIG!Lt!r%svV znn5+S>99%3>Q<?@?=)8=56HAxo6NMyPMIFF+)NKIk+idOP5MxoT=i+AzIsQxTyrR( zuWkQTuG^NOGkPP{jJ60pv;3mEzV0i1L)y5?EOSieFUQoZ?|wEno_<MXoqxyN^wy}{ zJocK&e)&boIoxk%_dOxGFSMGxRjWm9-lFrXs-<9Mi!Piqri%0eU7RpamH3b7(wI|H z=1kFLAH}Kiud_|X{%_PRANWn>(<juNy%Q$TdQi>n4;#JsL%Fs2O;c6)hTK;3yqTBs zoK)uz>+0{@Wq$IYo<9+xY9_mN?a?-MNBADS;D{p&hbnaNy;xOO-)9!>Iw<v3r_G%` z+vTq8pY-C!4hbcErk9qURZ9<jYnEO4zFM~J6Vq^hzq+?gOyj<_vb?j$tk_yB_k~uN zl`YwFe~~t;YW=c0b*5R9H6d$$h%!x66IIjr483;poN6A8)GgtYs&&^Hy>4h&J<xMp zKe%I1t#90?+ct`{S3aX3Y8a4?%-7As6`j%<z14K3FOjY@>rD5BGI`|PpxN+wx;*-7 zp4s?zsoL~pvgvsxO+B_gS3ll&QT5g(>0Z}$eNhpS|M-fI`7d8FuDf%C<9PQd*FCVu z7w5XWw>yb{-4E<>>?b4QOIjEVIo0;eRwee7+EZ-*_dXx*KM4Jc&Dfv8ZP`*~zuSJh z-43!J^ftr;JL0li0``P#3fUF1Eo5KF#*m$P+N~jbLpF!(4%r^EKV*Z*4v{S)dqg&g z>=M}~vQK2A$WA@&R*}7W+RY-nMYfCV7uhhfV`R(7o{>!>yGFK+>>JrQvU5+nb!6|z z=8@ea+eh|~Gyv%U(gLIhNE47QAZ<YUfHVT>1kwtm7f3UZZg|>uApJlZf^-CF3DOg! zDM(k4wjg~$8iRBOX${gFPum=%JD#>ZNPmz9Ass?mg!Bk$64E84O-P@RMj@R-T7~oq zX_lw$7Sb+H+b^VHNXL+tAw5HyhI9>S8`3wVaY*No)_L0AA<gr&-9y@k^bctu(m|w! zNDq-FB3(q<i1ZO@B+^Nwl}Im<W_sFgBJD)_i8K`HDAH1-r$|$gt|Dzk`s!)Z@qcY> ee5LJgpv2yb13AI+-2B{<yn=$9V9}pX@xKE%a7T*( diff --git a/lib/pytz/zoneinfo/Pacific/Efate b/lib/pytz/zoneinfo/Pacific/Efate deleted file mode 100644 index d650a056d9e7c734f886bb4a82d2256898140458..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 478 zcmWHE%1kq2zyQoZ5fBCeF(3x9c_w{5v_<mL_X|>oZXJ-mm3Bd9(Vhdcv%dw%HO)I9 zUwtD$A$zxjV)U*6CGWWo%GPrNRJ7|IRHf?z)VLEJ)P5%fsK0i0(0EdFL9;Q?LF=W* z1?@y*hx-5j|1&XSflLexbI*XRUvYqug@Iw#1_llv-w+0ELn9zI1W5ov2nntSTJ#^} z1CTzDFF-WNCm<T+8xRfh5r_u)3Pgi^2BJZ}1JNKKf@qL0K{UvxAR6Rb5DoG%hz9u@ tM1y<|rh&c((V!3j(V$QO(V&n3(V)-((V!3k(UgS>mklt4>~swcxd1L<X6XO` diff --git a/lib/pytz/zoneinfo/Pacific/Enderbury b/lib/pytz/zoneinfo/Pacific/Enderbury deleted file mode 100644 index 80873503faeb389853e1bc3f04b71f5156b48a14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$Po%-IiSyKxuO35|No3k%*_A)$IoG4`2RoLfq~`! z|I`2m4hDv87Z`Yad_x#?4UK@<kU`tf7-S9@gpgn#(2D<c=AkJdjUanLG{|lcO|Jc1 NHbB?d=^7ey0RWqlFUJ4? diff --git a/lib/pytz/zoneinfo/Pacific/Fakaofo b/lib/pytz/zoneinfo/Pacific/Fakaofo deleted file mode 100644 index 4fa169f3cc6cfbe9414982b9eef32eb7f6718251..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 212 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$npC-b3^_A|Nj}8nEwBduV7&K|35W=frWu#+XV&= vAKwrLT|+|#Z9`*_Mj!|w!FZsl|Le>{J3#tC)_`adtm3i(T5G3kXv_rwq0K43 diff --git a/lib/pytz/zoneinfo/Pacific/Fiji b/lib/pytz/zoneinfo/Pacific/Fiji deleted file mode 100644 index 61a669535f14d380929c8fed6708b5ca9f8d5bae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1090 zcmciAO-R#m9LMp$D<Uk=>L7&Dq3EJ5w~1Mn)TPTF68j@Md-#f|%&Ex|)Acau&>^8q z#6svYqKC^MN`)X8BZwkMND!jnOCUj=k|2<3ec!)prwICuAFr|Thq2GwPo{<<-XCX! zeZu7&wafdhb2}cDTHEWib!A=J_OwXb;(Lj1Ytm@3Mq=g963-mdMB%lZ*!f&LuNI|q z{hoHEUuoC7?2_+)694IlcDKBg?zx!uG^}b*HmJQnUuZH}t9>6H>ZyZkdivR{p4nX^ zXYY)u1iniDwd>OV@vRIDjmkiIUIvdRWU!Ez;l0f=Jo{Kvo3=|TeM?7HzezfJK}X-z z>R4m1ju+ST+?IBoxUsAgUm7$szMz@qLo%7Tt&>mcW$M7ZOx^q?({*{7KCd$KeMYh; z%W`q0PcB97%H_o)a;3hcx%@84{U~UzQmF*0{^=F)y!$2lAH9X8s*RrapyX|i#eJ_a z6tNrT3p@T_>|sBt!X`Y&E>7D9*~e)cAv-y3D`YQZGh{bpJ7hnnZHVmXv@MZ6owg~m ztJAhc_I29E$j->t$ll22$nMDY$o@zJNC!@90qMbMO(0!3tqr6Pr!|6f;<Q$fUXW&x zZjg46evpQcj-1vK(v#DgLb`HVTS#9{YYgekX{{l>A<ZG(A?+dkAq^rOI;}<Lf9kRN eu(jE-FBCQ-9Zm62C>RPho564}ygw9<RR0E(xg)Ls diff --git a/lib/pytz/zoneinfo/Pacific/Funafuti b/lib/pytz/zoneinfo/Pacific/Funafuti deleted file mode 100644 index e6a154474bd8d6619556bbab19b0921f81a13d2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H%dykkpC;3@kprAq?7v5HmtZFb!zX X|2i|CKOl4Po62PaG~Z6w(1;5FNf#M~ diff --git a/lib/pytz/zoneinfo/Pacific/Galapagos b/lib/pytz/zoneinfo/Pacific/Galapagos deleted file mode 100644 index 859b76d94d52a9979e757fa3d5869436458aec8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254 zcmWHE%1kq2zyK^j5fBCeRv-qkdA2R_X^@jR5}+;4^+3DuOF;eq|Nj}8nV6aX|6c=? z|NnpI1_l-o$p|D@FJR#C@eN_nH82HYGoU;Kgpgn%(ER@(OF=q87K3Pz<zO1<01!=< NBe-mUPO>xO0stI?I=lb? diff --git a/lib/pytz/zoneinfo/Pacific/Gambier b/lib/pytz/zoneinfo/Pacific/Gambier deleted file mode 100644 index 4e9e36c5a7edb3ebbe08ccc56308ac262df9f58f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmWHE%1kq2zyM4@5fBCe7@K2CfCo$c|Ns9P8UO!ptYKgPk_8MbKE5Fgx(1eDr6DAk W1~lkD$V8Av{HAi*0L{0v<N^SLeIXVA diff --git a/lib/pytz/zoneinfo/Pacific/Guadalcanal b/lib/pytz/zoneinfo/Pacific/Guadalcanal deleted file mode 100644 index 908ccc144ef9e69da0c108dde21f8dade3203ff2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@K2?zj06f|Ns9P85tO+egR3%+Q7i#;~T=DZD<JMGK7#| Y8qlEsAQM6Q@SDnI12o@G*U*p)07Whw2LJ#7 diff --git a/lib/pytz/zoneinfo/Pacific/Guam b/lib/pytz/zoneinfo/Pacific/Guam deleted file mode 100644 index ffdf8c24b86220d1bc67908fa080a37f250544db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216 zcmWHE%1kq2zyQoZ5fBCeCLji}c^iO)m2+GIBh&x?W+p%mL(c*R7BI=-;~T=@9vs5p zoB<>tAOyS7Kn);GU;r`}#OD1E1R@WQo&nKyX1YEgS%_84FuU5ffCTZ{$iTn_vR~KG GfC~UJHYHvF diff --git a/lib/pytz/zoneinfo/Pacific/Honolulu b/lib/pytz/zoneinfo/Pacific/Honolulu deleted file mode 100644 index c7cd060159bd22fc5e6f10ac5a2089afb2c19c6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 329 zcmWHE%1kq2zyNGO5fBCeb|40^MH+y_ZdPZH-HL?~r#o#=TvGm0a4FH#;%aZP2O|?B zGYcc@|Nl8m3=BXrf`R4#|Edf|4lv0BCI$ZgFHT@!@$n5|@CXKC7a$G?;(!pK!3+$H uP%?xBC;bP4k_QF*Ks3l{U>fK=5Dju7hz2<mOaq+?qN(g$E}&lw4Y&a7X=UL6 diff --git a/lib/pytz/zoneinfo/Pacific/Johnston b/lib/pytz/zoneinfo/Pacific/Johnston deleted file mode 100644 index c7cd060159bd22fc5e6f10ac5a2089afb2c19c6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 329 zcmWHE%1kq2zyNGO5fBCeb|40^MH+y_ZdPZH-HL?~r#o#=TvGm0a4FH#;%aZP2O|?B zGYcc@|Nl8m3=BXrf`R4#|Edf|4lv0BCI$ZgFHT@!@$n5|@CXKC7a$G?;(!pK!3+$H uP%?xBC;bP4k_QF*Ks3l{U>fK=5Dju7hz2<mOaq+?qN(g$E}&lw4Y&a7X=UL6 diff --git a/lib/pytz/zoneinfo/Pacific/Kiritimati b/lib/pytz/zoneinfo/Pacific/Kiritimati deleted file mode 100644 index cf5b3bd348fcc7d6a39dd6b64b1658046444f71f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254 zcmWHE%1kq2zyK^j5fBCe7+a_T$Po(t#Gucry`cX8|No3k%*_A)=KzKO|IY%d`~SZ< zfq{#G;m899J|Eu@23<n~69W**plxUZ)C&P2B-jYF<bR#HM*~PF$Yu}#*$$$~cLA3T L&^>m#h9+D9eoZjO diff --git a/lib/pytz/zoneinfo/Pacific/Kosrae b/lib/pytz/zoneinfo/Pacific/Kosrae deleted file mode 100644 index b6bd4b089416a12b02a37eba8a1082dfa28b7797..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 242 zcmWHE%1kq2zyK^j5fBCe7@Ma7$obzU9bnd-?oj{#|9?g%Mn(pP8D~I>W^DkeTXBGa z1H$(44PnqWGz4OV=^-T81vLJDotcaYhz8jSvIAr<h^_|;k!m-W4bU}qx`u{a0Gwtk AUjP6A diff --git a/lib/pytz/zoneinfo/Pacific/Kwajalein b/lib/pytz/zoneinfo/Pacific/Kwajalein deleted file mode 100644 index 54bd71ff2dc7d7fc6f3ddb6e3e5ceed4c92b72f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 250 zcmWHE%1kq2zyK^j5fBCe7+atL$obzU9iUUP=Rp1c|Nj}8n3)+E<~#rjGtAn+!1Dip zxB~+R1H*~~3_L!*Aq?7vh77ufMnD>2LkJ1>0j>C7XC|QlqCxh8>;c&gqU(V|<k-(; L19XiY&{bRj+D$OG diff --git a/lib/pytz/zoneinfo/Pacific/Majuro b/lib/pytz/zoneinfo/Pacific/Majuro deleted file mode 100644 index 53f32886d08156820aaf860bcaedd3009c35f601..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 212 zcmWHE%1kq2zyQoZ5fBCe7@Ma7$obzU9Z>)O|9?g%CI*HDAQ6UH8yHv^7*-r$;PCMc rVbC@-1Y($xAtV?NH1&U-nMebO23Z3#yB^3S(kd<+ptW|ohDKZfj`Jm{ diff --git a/lib/pytz/zoneinfo/Pacific/Marquesas b/lib/pytz/zoneinfo/Pacific/Marquesas deleted file mode 100644 index 5fad0e1b201fb0cf49f3d2c27b117e9a029501a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181 zcmWHE%1kq2zyM4@5fBCe7@KQKfR9K0|Ns9P8UO#UwP0ZQ|Gz4OfyKu+ghAK9(%1m3 cID`cAfJXfXnF-QJ$XqTPupM@mR>lTg0Jwo7!T<mO diff --git a/lib/pytz/zoneinfo/Pacific/Midway b/lib/pytz/zoneinfo/Pacific/Midway deleted file mode 100644 index 72707b5e15cccac9888e5ba22bb1b4e92170df5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 diff --git a/lib/pytz/zoneinfo/Pacific/Nauru b/lib/pytz/zoneinfo/Pacific/Nauru deleted file mode 100644 index 7e7d920e11396d3b210f28c1d69389bc68d33548..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 268 zcmWHE%1kq2zyK^j5fBCeRv-qkg%&^8W;wlOzsCiqfC9m8mJ{{=|Nm!XVq|7!V3<<_ zQn_#kNMYRs1}+AM6$cpje0)O~v<(f74H&cyEP*V9MIj_u3$*1w$Z8M`vK~Z(oB*;B X<O~p94-_NQDO@%{huP^G8gT&t{~b3+ diff --git a/lib/pytz/zoneinfo/Pacific/Niue b/lib/pytz/zoneinfo/Pacific/Niue deleted file mode 100644 index 1d58fe36f47433fcde7743c626a8073f65dd331f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 257 zcmWHE%1kq2zyK^j5fBCe7+a(P$hqTenjlbe%A)@N|No3k%*_A)Cv0J00FnzBSpNTy z^I+im|35W=LBPj1ghAKP(8vHp8iPrYSzr)Cf}KEX{@0m@_<(4T-5>)%_JinppcuJs N;IaX_%g)e{3jo^+IynFU diff --git a/lib/pytz/zoneinfo/Pacific/Norfolk b/lib/pytz/zoneinfo/Pacific/Norfolk deleted file mode 100644 index f630a65d5778d651b9d6842159d593c3cc18a996..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 314 zcmWHE%1kq2zyPd35fBCeHXsJEr5b?59mgLHocyOUIJe%62;+a2QUCw{e?}%|CKeV3 zhPf3$1q^c=7=Y}BI~cea7}lR)6kuSOwShs*$2WvQ+tAR^2t*ni07)Y-X$aB+1tBE3 t1!(R6Iy3PO5Djt@$Ow?Tz%<ZpAR6R85M2*cLoGLQ*#O;dr)y})1pov^I^+NV diff --git a/lib/pytz/zoneinfo/Pacific/Noumea b/lib/pytz/zoneinfo/Pacific/Noumea deleted file mode 100644 index 99f6bca20d23a1ea93182d86b066cabb764abc0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 314 zcmWHE%1kq2zyPd35fBCe4j=}xc_w{5Qo{FaR{($5?gIkH<^%|uzjrWN;Qhf^!lj`8 z|Ns9?j6lfD!ot8XhX<r_#Q{bjd)5Y!2?(~2ZwQ07p%D-pf)p__f=GrC65Ii_>OaUW pAblYBfM}4LKs3l*AR6R05Dju4hz7Y4L{sWcE*qe`?Q{(dxd2h3I*kAT diff --git a/lib/pytz/zoneinfo/Pacific/Pago_Pago b/lib/pytz/zoneinfo/Pacific/Pago_Pago deleted file mode 100644 index 72707b5e15cccac9888e5ba22bb1b4e92170df5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 diff --git a/lib/pytz/zoneinfo/Pacific/Palau b/lib/pytz/zoneinfo/Pacific/Palau deleted file mode 100644 index 968f1956c8eefeb78d96651051befba0b1a3ab82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Eq%NUClE1B;Ju2!pnPC5X!qLV{^P XgZ|f<nY{t&!*42=4bXf$T}v(i_(&L^ diff --git a/lib/pytz/zoneinfo/Pacific/Pitcairn b/lib/pytz/zoneinfo/Pacific/Pitcairn deleted file mode 100644 index 9092e481678c18c41823cd24d370ad9623398b5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmWHE%1kq2zyQoZ5fBCe7@MyF$T4+s;;8@s|34!W)Bpc%JPZu~|94a{u>Aku-@w4- s;~T=DYhYn)03tzJfgpqg3xMYSuQS*C0@4Sv2t<=)8J7*vVmk{i0FU=74*&oF diff --git a/lib/pytz/zoneinfo/Pacific/Pohnpei b/lib/pytz/zoneinfo/Pacific/Pohnpei deleted file mode 100644 index d3393a20d85209df94887e0de0c88e08442e4009..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+UMkkqUV3@kprAq?7vh9E9O2nnVE Y4f<barmzO255K8gHbC?3bPWx;08pJ7fdBvi diff --git a/lib/pytz/zoneinfo/Pacific/Ponape b/lib/pytz/zoneinfo/Pacific/Ponape deleted file mode 100644 index d3393a20d85209df94887e0de0c88e08442e4009..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+UMkkqUV3@kprAq?7vh9E9O2nnVE Y4f<barmzO255K8gHbC?3bPWx;08pJ7fdBvi diff --git a/lib/pytz/zoneinfo/Pacific/Port_Moresby b/lib/pytz/zoneinfo/Pacific/Port_Moresby deleted file mode 100644 index f6fd51cb93f23872e8809dd4b2e99ca444fac10e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 196 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14HKzkW|kC1{NRR5C&~S0}z)X1iNX> zKq(Lg0T`PXXwd(#ZBZN`y6Eka2_VUOAe)g1Xd%S-&JPSMU`sebmIU}B*~GxWWdpR; JPS?<Y3jkR5BfJ0r diff --git a/lib/pytz/zoneinfo/Pacific/Rarotonga b/lib/pytz/zoneinfo/Pacific/Rarotonga deleted file mode 100644 index 9708b8707230d12731657b936499626858049654..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 593 zcmcK0u}Z^09LMo%EC?B-YAAo&)SlH)5ekV8S`-~bgrZvp>!b)$i<^VAgWAm(5N|v; zhpxUrr=p9GAWn7j3H<y29XN@DgBR{TM<9g%H$6B#SyDe%R^DJ^g|cxEuI0$}iwl#R zk2KZk>FM$v1<hj}aEJ2sZJi(88mb&w8eW)!w`B_tO;db}Y<RO~N`rAb(<+(r{kzpW zY-W4UwvvQ2*B<Hl+$TkiCmnsfQgyMft1kn(V&=@4_uJz!w^x;7KHFDW=JP}4Gk05= ziQT<a)slEQ#;Iy?U2fki{ll#Cd%j$04Dl^yhL{^-c8K{Q8aSl`L<^_%fN0{BE)Z>; u(g&guL??(=5WOIpL3D#?2hk6rAw)-rmQLvj(bOqj;eWLC*QNJH68m5DID0_= diff --git a/lib/pytz/zoneinfo/Pacific/Saipan b/lib/pytz/zoneinfo/Pacific/Saipan deleted file mode 100644 index ffdf8c24b86220d1bc67908fa080a37f250544db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216 zcmWHE%1kq2zyQoZ5fBCeCLji}c^iO)m2+GIBh&x?W+p%mL(c*R7BI=-;~T=@9vs5p zoB<>tAOyS7Kn);GU;r`}#OD1E1R@WQo&nKyX1YEgS%_84FuU5ffCTZ{$iTn_vR~KG GfC~UJHYHvF diff --git a/lib/pytz/zoneinfo/Pacific/Samoa b/lib/pytz/zoneinfo/Pacific/Samoa deleted file mode 100644 index 72707b5e15cccac9888e5ba22bb1b4e92170df5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 diff --git a/lib/pytz/zoneinfo/Pacific/Tahiti b/lib/pytz/zoneinfo/Pacific/Tahiti deleted file mode 100644 index 37e4e883368265e85b7db406348aa4b25350f100..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWHE%1kq2zyM4@5fBCe7@K2CK<JM8|Ns9pGXDQxe1d@iNG33_`1pn}=o%V;m4=XD X8qlEsAQM3v@tewJ12o^x(0~g7G|wXF diff --git a/lib/pytz/zoneinfo/Pacific/Tarawa b/lib/pytz/zoneinfo/Pacific/Tarawa deleted file mode 100644 index e23c0cd2cb4a1542809e253e0980646caae226d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H&Q{kkpC;3@kprAq?7v5HmtZFb!zX X|2i|FGaz&Do62PaG~Z6w(1;5FS@#*Q diff --git a/lib/pytz/zoneinfo/Pacific/Tongatapu b/lib/pytz/zoneinfo/Pacific/Tongatapu deleted file mode 100644 index 35c9e2c64642a3f2e17341e2d7f704b2bf5f156b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384 zcmWHE%1kq2zyNGO5fBCeZXgD+g&Kgw$zrDo=D*!9SX{2XV8y}_U{$ok!TRy50Gps$ z4iU1~FGN%+T&Vy5|34!WGYcyd2r@A+tn~ouWLR6kz{0?=?E(WA1H+LAjC{y!AKwrL zZ9^j?10ZP(#3mpOjEo=>2tr73B+%~vb!O~4Ks3m)ApIZ*gJ_VWK{UwWU>fLn5DoGG ghz5BAM1wp6rh%RT(e*&HsOlju8=yDsbPbKU0LCg#(f|Me diff --git a/lib/pytz/zoneinfo/Pacific/Truk b/lib/pytz/zoneinfo/Pacific/Truk deleted file mode 100644 index e79bca2dafa7e914a9baa568edec1b428a2ac63d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f<scix2!*42=4bXf$T|)yd06J?KGXMYp diff --git a/lib/pytz/zoneinfo/Pacific/Wake b/lib/pytz/zoneinfo/Pacific/Wake deleted file mode 100644 index 837ce1f5c7f7ad25955108003ce9cd44ec9f2f1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H&8>kkpC;3@kprAq?7v5HmtZFb!zX X|2i{?Gaz&Do62PaG~Z6w(1;5FSBDv? diff --git a/lib/pytz/zoneinfo/Pacific/Wallis b/lib/pytz/zoneinfo/Pacific/Wallis deleted file mode 100644 index 8be9ac4d3bbe8f54267e85eebc8b11e1d612c9a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m1H+mKkkpC;3@kprAq?7v5HmtZFb!zX X|2i{{6(Do)o62PaG~Z6w(1;5FY=ary diff --git a/lib/pytz/zoneinfo/Pacific/Yap b/lib/pytz/zoneinfo/Pacific/Yap deleted file mode 100644 index e79bca2dafa7e914a9baa568edec1b428a2ac63d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmWHE%1kq2zyM4@5fBCe7@MO3$f^JT|34!m14Ew=NUCQ61B;Ju2!pnv0f@^GLV{^P YgZ|f<scix2!*42=4bXf$T|)yd06J?KGXMYp diff --git a/lib/pytz/zoneinfo/Poland b/lib/pytz/zoneinfo/Poland deleted file mode 100644 index d6bb1561db672f94bfd5bccc95fe1a8d18a158d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2696 zcmeIzdrXye9LMn=97!a#FWyiDFA=G9R1if)$c&E81oLvd;RQ5AEi$}BXcEtoxn|CN zbBOa+P{hzFxplN9I%l`hGQtZQHf!Zdab%d9wZ`oCeq3wK*47{W*YDYRUe7r@|32@J zKXX~`Fmu<r*Z#tXQ*A#yM>_Vly*jR8XUB-_osH*PcQw`M?#hGu+Iy<6mu%DW9fwTC z;-jXjXkB()!B=wP(j@t8PlVRLktUyS87>XZp6rH_!{+4HE%~Q5)@GkxbUXjdq!?{n zuwTv&3dlKcq<qn#Oqzm^Yg2QfT=t(bm#+n!=5O9|uAD4$TDp~)mc#FuANE$7t2?%u zAJ1f(*0s-@Yk?H=Q|26Vy|j<HkvzuSEJ}8Mj*K>);@a{%RnBlaztYj%S2EI()dQXI zoL){Bf0)xXBgu42Y;n5BTyT1Ht#=|k$DD}k2b`W4E1X`Zw>Xg>tao}JdD}$oD>u=* zUNwC-y=3~XTV?v?<(U5SW|;ox&$iy5?w6PppFH4AlGvyL@?giFG9V;P2izR41HX&a zL5)2$?xXhlP~aE!RyOP4((^i`<Wn8G`iREo?AL_(O)_j{KoV1HW%#r*84<l(l7<yZ zQd_Z%>Rqa%E-aMMzZGcm(KH$J<!nu<%F@)@WPNzUI32q)N*~FM(QzfC<<apWnwHaB z9*e!CzO*(OAM%M#i1}J3T>V}qdXCG)`Z{_1;+rz5X0N25IHnn!H_7CE75c>T<uYZ{ zdYw9JqfX0PtkXy4sXu*!&WM<-Grfa!=B;?0-F>{wKG#L(+#D#Ghi>TH#xR*z9xn3( zEwZ5ax@48sOLkVHEG)XBi^jeyPtHG~IeoXw;?x?=4Lzt!qE(k%-lj|2R_e04HTu*A zzdl_(SMxqzA<w*=s>`dU%d<=SYW{{1vSMnAtjvv&RSA7$weMGXF5F1L(C%8$`mGdp zzNLi?AIh4mO}h3#mAp`2tLwJEuSGSx^~E)nTD-YfgFL~Wb|LLT?`iJ|4zUlxcfRv@ z*To<I=JIq1`|mGfx*o8v68Cn-MD+^_HKwzePJexliw_Ft7t`a<`yc;I&+waB_LJtD z&dqOpJoxMbC&(UqbD!^g_y3Ex{I)$a4>e3d-ge}TceQUl^5!FNKT-gs0!Rsv8X!eL zs(_RMsRL37q!LIeTx~6oVj$H(%7N4aDF{*#q$Eg9kfONSsvu=?wRJ%XgH#47jjOE< zQXHf@NO_R@AO%7ygp>%W5mF?iN*v1MYU_j)ibJK4QX#cMiiK2*L%EQ8Aq7J!=4wlZ z)C?&aQZ=M(9O}lQa2zVfp>!N-$Dw$xwt7hUkoqA7L@J1s5UHW7Eh17yq>M-%kwPMs zL`sR&5-BE9O{APiJ&}SU6-7#l)YR1$6{)JLEh|!2q_9Y3k<ucyMT(157b!1NU!=fD zg^>~?HAaezRN2**8L6|YEi_VTq|`{Qkzym&M#_!U8!0$aairu(&5@!bRd=;zN9yis z3y)MDDLqnqr1(hnk@6$;M-~8C0b~h~H9!^tSp{Snkacji3xTWzvJ}W#Ad7*l2C^K; zdLRpetO&9s$eJLFf~*R%EXcaJ+J!+@#?>wjvNp)#AghBc53)YU0^$EF^iL}kW|wMk W0-NQ{NE|X^NW3>AAs&Y&hW!qIlp4DL diff --git a/lib/pytz/zoneinfo/Portugal b/lib/pytz/zoneinfo/Portugal deleted file mode 100644 index 355817b52b1b05680bbb57e4dc8de358eff27a39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3469 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 Y{v0ZPotiXi*2uqcfM2Hof8Le;4LU9nuK)l5 diff --git a/lib/pytz/zoneinfo/ROC b/lib/pytz/zoneinfo/ROC deleted file mode 100644 index f9cbe672ab1aa9b6c47f7fed0a0ccd73845a10ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 781 zcmci9Jt)L+9LMqRc|6B}$)YeQgUDnM<rwHp{?sX(l<nuCB+_p&U@>_Z6^k;GMK_?7 z+~DyJ=lS70|6*XcI=?qI3ya_N`@gP#x7_FLv~;wW$&Zt4-*7oa_VPVb+s8^%o!)Z% zdV92A?^Ms5-P!`#^99U)ML<F+?JAu6qQf^+<{(}uhwGc_=+v(xb7GFWy5(e{O`Q%5 z$=UdXId5Ik7rh(mvhqQ$O7qlpey7}I&#Gv`jE=sB%<V-;?shZO{aU(ySgbOSZ!vkA zTs6<(5^eh4RcfqR+>awFEi$Y<!DW+mIIXjth;k*k5`Xm(>5SU{BT7q>$l#c`dAc&b z-uN0E@isbAZ?Ct;;fLSH`NLpwdwPN<2N@0-4;c^{5g8I06B!g46&dzZJ1#OXGBPqW zGBz?eGCDFmGCmRjiGYMaVjw|~C`g!3Z5$*J5(x=~#6p50(U5RRJf<Wdhlof>BqkCR N|0Sx&wk|IBd;k)YUNHaw diff --git a/lib/pytz/zoneinfo/ROK b/lib/pytz/zoneinfo/ROK deleted file mode 100644 index fa1cbd3952c362552f524061301b11f03770f335..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 517 zcmWHE%1kq2zyNGO5fBCeQ6L7f1-h?)sF?Ij?8hZ%^$(ByH9tJb-u~mMutdhQB<70e zf<7-^=oeJHc>X2h6@O60tK-))UcWb~c(Z&*#@q8^74O<-WqdF#tWa2-FhMadeS%W6 z(*$Kd&k2l7%#18ZkeL+-85qhrKsJ|mFt9K%)J<Rj@_;0e-8zGjhk>DI0V9u(ZwP~T za0r7J5PQ3XfRw{Q2nnA04+J2OfoPEDKs3mMAR6RJ5DoGuhz5BUM1wpGqCuVp(IAh5 zX`tspG$;VTG%yfAG$<fIG$=4YG$=qoG$>F&G$>#|jt2z}hz11^hz11`h^ARUaREb6 H*OChW=SsNu diff --git a/lib/pytz/zoneinfo/Singapore b/lib/pytz/zoneinfo/Singapore deleted file mode 100644 index ebc4b0d9d2ade7381506bc35d054f361f8a942d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 415 zcmWHE%1kq2zyKUT5fBCeP9O%c6&ip<TXXB;UFm)k4sbn5IJNm{!s&_e9G5(DKZs5I z%2EIS|9?g%W)@a9R(5s<hQwH)dJxINz>rh`G9q1pkq1OF0Ljb>1|bH9x(N&t3=9Pg z3^G2xAq>GltZiTp!bS!l(ilWq0<i_iC=?Jvg2RFC`d?=jyck4-JOH9Wo&eDxkAP^9 hXFxQ_Lm(RDDUgLAkAdiVpcvge$7KWbvYoC47XV%lV9Nji diff --git a/lib/pytz/zoneinfo/Turkey b/lib/pytz/zoneinfo/Turkey deleted file mode 100644 index 833d4eba3985c0221daf6789c9ac30266bccacd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2157 zcmd_qe`u6-0LSs??shwGZuRtI&13UN&UvR(o7-A9o0~PyF}<xPJtx?Nhn%J;<69O~ z20PjiOu<Jo94ut&TC&6-CD2N56fv8vC!OWmMo)x+2>LBrQNyn9`-!0NpX%Sf56``B z&&K{Z?(<Hp-@3U}yX{O>Pq;bl>gIhYsa~u-6P;bzd1lw;v-Xiq8=a#&Up9Iglja*W zr|jOMu=8fP&F+gt%)W1K*>C-_+d2N>Vq>7~hB=V#x6iMfFh3be8iRu;%nMihjnALH z?p)j!G%g*fG>2=w#^rd9^ToWK_Ls#soh!i&_K5eOIdZMW{_5s2bM#`fF*a<Q<0tpo z-~V{p{GsKHJ+URz{Ap&?*1FF)o^2;>Z}N4=x9T-JW9w<tU;d7f+4`yoR31wPCZE%S zq2BJ`=n^MO2JCEo$jly@W90Nyns@Z>H}34nH}zdRjJu!eOx@E^q1{{eVk)<AJb7QZ zIhE%-mz)-;PK9<KFhXMoQUxDhvZr_D8Z-9pkTYLuk+Zh1mG?KVkh9|t$-=6LoU<S# ziwd&j+^Lf?><^3Z#557P8Wi(}e--mTx-N=Ojfn+4pNfTh+U25-x5Wd`c8ijh{bKQk z_hsob2W8pPL$ZAJ3-ZC~Nm)_)gsiyrq>Sc2FQVhiW##00vE+lf^5M}cQPo>3mcBPb zRPQPhHC>umwmBdk=_rto#;%Fljlap|MS~(%>&RGVsk6d=-l{A7TCcnDonG(j*XxG{ z^p)Qp)mNS9)8iM;Sq=Nft;XYrt;bqhbz^V4_4vA1tkv5$S!<$a^+deRTASBsHB}$7 zntm>^)_u0fXiKNl9-sHWTp9j9FRw2%@J}z_l;CZb->+%;5%rDK^0#Oinl``0Gey%1 zW@$N^7G37Kiziy{-=F{WZ}@GzA)(c)I~H5ROF}CyDOYzH|5Y82I)A)#f6x;DVk_z+ zN;kbba0S^6vKv>m9b`YqhL9a0TSE4PYzo<xtJ)T_FJxoL&Ro^jki8+BLw1L357{5G zL1c%>7Lh$7n?!brY!lh1tJ)~CQ)H{iUXjfryG6E(>=)e)^FgZ}16xM+jBFa&HL`7F z-^j*Y)y_F=-Bs-!**vm)SG9d)|40Ln4j?T+dVn;6Ll=-XAbmg@fph|Cg{$fX(hQ^< zNIQ^zAPqq}g0uwb3DOj#D@a?AzPPH!Ae}*4gY*Vz4$>W@JxG6$1|c0nT7>inX%f;U zq)kYlTvelxPPwX9A-zJHg>(yP7t$}JVMxc2mLWYunuc@@X&cfvSJgPAb4cr4Rqv4I zxvK6V?L+#9G!W?^(n6$%NE4ARB5g$ah%^%EB+^P()k~zA*rvYg|Hp1-RjH;{FD%RY E9a<Tx5dZ)H diff --git a/lib/pytz/zoneinfo/UCT b/lib/pytz/zoneinfo/UCT deleted file mode 100644 index a88c4b665b3ec94711c735fc7593f460668958cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U33UzuGD67I#|6}Gzy$z!Xa;ov diff --git a/lib/pytz/zoneinfo/US/Alaska b/lib/pytz/zoneinfo/US/Alaska deleted file mode 100644 index 9bbb2fd3b361ea8aa4c126d14df5fa370343a63f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2371 zcmciCZA{fw0LSqQ0v8C15>k=qB=Y>=0R^EbF9-r60dl(ukxF8BSP2AU_(W1Vaw{?0 z9L`3^IkwuQo#|G(7TvVgCgupY9>$_{YbHzAdYVOYJKvM57d7AI|G)G99PW7g`??!i zp3HIl>j^WzaCr8a!#!oE`Hb$#^NlC`+&11+EPo#_Q!^(vxcqOdkdA>;SHO!YGO#<@ zHLJZu2Q@AC1=l9&kfKDNGdol}UtZ@6i<;75!xOIXAI|FAz8UpJe0f<$`i6bCpB$BU zym`hIb#PeTx#y_st}Xp?cFSH@bbY&wsc3WET~H_Iq^@?&UC^rMg)MQ#2G;7>^ysMA zAB*+;i-{_3e4)PQlvBkY3(@x;zN|!7fxNGGR4wq#mkFD`6AN>%%fyvuL{iMxGCA$2 zNS>M2so{G?>f~2CZK_SAkG!ul&cCEG2M_D4<D1o@o)@%ywMJ!omCWhLQH#r-mrLrR zRc>;#%***zEp@Jt`Ej#F{-qRIF#U_T|Ko7^z{KaGP$%gJ-#sZF+83&q9Xcdjty8*a z*E_1X`mA2wd{C7vdP|p<Y*VE_U65s&1ETEwX;~4uRa6`wk}Iz?iptkM(5pV{R#n@N z=!f5KP}PmQb<Kf7Ra@xQtGnV=U0j8BdmPIBN4oapUR0iM%jKGQzgY88nyjC>AR2}u z<YSYkMdPlk^6`-&v9@_kt{dzV>#M%kO?^ky6Pf4q2Jddw9I5rjGOyZrWxw_&S19i% zow~)Du3CmYdefyy_0)k5`Se(tc&6(SxmibuR?kw|)_+yB=gpJPwvLI8m}%KreN1%v z=jg8dbE<3dH{Cr~tL~8rz2(||wRP}4z3q!mwY}$cz2k&O^{nmH&kf|OfWTP+LBThB zLqeUm@O3yoyykHD{T=HaL4JR4TR^D&M%Z7X>^+9BBi8Tl-x&~Z?+L4_+>W9;a~?IP z#+-8gC@*n4>bX>!OHrk{nJ0h`&tDh!e{U_^`~!#Q6?3?!_|3EI)b&qsM_*AnvOQ#f zR<l85hiJFRg+20^O#-__wu$T$*(kD8WUI(tt!A^xZmnj!$bOLxBRfX6jO-cNG_q@C z+sM9=jUzipwvOx_**vm)Wc$eet)>B1(*dLfNDq)EAYDM(fb;=r1kwql6-Y0TW+2@_ z+F>>QKpJ8-9YI=x^aN=N(iNmFNMDe~Ae}*4gY*Vz4$>W@JxG6$23bvqkQO05LYjnh z32773C!|qGr;t`5y+WFWbPH*h)$|K#nALO)X_?jZ3~3tDHKc7w-;l;3okLoO^bTnr z(mkYoR?|PEfmYK&q=i<~L!^mF7m+q1eMB0GbP{PL(o3Y7NH>voBK<@fYBe22T52^t zMVe|gT}9f8^c86=(pjXnNN<tmBHcyWi}V+1u+?-JX|dJx7-_QAbQx(g(r2X6NT-oj zBfZ8O%?=6-4!POu3=6%5@88kx{$JDmPrGm2!ijnTdC#a?oRyO$Gpe$)v$C^f_@5Pu BZASnA diff --git a/lib/pytz/zoneinfo/US/Aleutian b/lib/pytz/zoneinfo/US/Aleutian deleted file mode 100644 index 43236498f681cc06f64ca2afa613880331fe6fbb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2356 zcmciCZ%ma{0LSsm-|Iy&Dj-Bm!Hl7RhrdBt9tc86<UwwrWC#iolmVg)3ox8hdC|nN zoE0&9(I3~SV{FZ&u`@U43+KAv*1#M!H|MM|UA1J6yq)jK){C0&@;rN<&)MC5`}=yU zn_f<L{p)zlFT9+7^Ky@W%Y4rF75FBW|JFKD=g8X=FQ_}G+8qC<Ug<hk;RGDYmVupF zPEgxM9b8xL3n|akp?MiTcUrV|zrDlfiI~-%;p<M=%}aXzk5j${Q@3Qe9`!B!dP+WU zV$z9tcT_&uciMSq&j<41ra>oi^IjQM+~Y*&*2zbbYMq#bZoSBp@5Baf)v>D*mc{<! z=*3quRNO?mUUDW%J^E#&Ui#rJwXCB^#`jLCgvunjy!m(WSoVCmqGVD$9yKEqSDqG$ zeveKH8x%>?KkJo0^@vqt7j*K)_f*Qz7dmyMORerXqQyXsN^AUFrngI#QPeLpD-u*z z;!c^J5v-nYdu2{syvVthEpz9B#FOV@<Wt{Y6>C(cetPtrc&0yEuYLc7kS()1Z~s}9 zUv^19TmOkFSpAJIEa+2(zuu5VDIbfXi{r95{E#Rf8IdJ3&EomNZ}s}`4ye+ulX}Bf zuc)#u1KK%SqRQ9o)*CyLRYhEt_Es)b-nm>|nRQcDUagdymWGQ>XLID{J2yo2N3rt7 z>2a}T|D1ejY(&)5Ps^=C?}*yc+q&-HNwqEIvfkb}pz6cNbVJc@)i85hHzro8#tZv& zlRH;64cF`DYm3#ZM|<UKz8tZmW4nA^#fp~7LfLwFPPAnw%AGCKqCMIpca>?e%fCW* z<Xl!AKe%;g%$VvNyRP@l9#?M+o!4(p?o(Yo!@B!az3QnstoI&!P6Y%81q6rO>j|Cb zzK@T~_1P7d%kOV+T)}>Sdu_lx`(0pviLm!bzOER*zqd7DiM=mcU+Q&js4#Dpc^$7S z-`w*Hyso@;=CaOQ%n9Jb`Rn5S?~#R>Kk#ynn3sFJ-<-8){usyZgVi<2=#b%A&G?W3 zq8%X@hR88v1O|zW5*a2kPGq3SNRgph%~+AaTFq#Y;UeQj28@gt88R|vWYEZ{kzpg_ zMh1?I92q(?c4Y9#=#k-D&G@Y*07wLo5Fjx?f`CK;2?G)bBoIg>kWe78K!Slp!)n5T z#KUR=f<y!f2@(?|C`eS0upn_k0)s>b2@Mh(BsfTPknkY!v6=uO5kf+Q#0Uuz5+x)| zNSu&BA(28tg~SR877{J12^SJCs|gqqF{=p~5;G)dNYs$9A#p<jheQqu9TGbvcu4fD zCVWWztR{d+1g$27NDPr6B2h%bh{O>IBoav^lt?U*U?R~(!imJwY66Nx)M`SC#MEkn zibNF&D-u^Eut;Q)&?2!#f{R2K2`>^~s|hd?VXFx-5@V|gG7@DZ%t)M(KqHYxLXCH0 z9UK@EdauXrnRg$bziVB+?f+@^KheH>3o}7a6Q=0Nr5UN|sUo>FEiE-IRfPQs4Q6_I diff --git a/lib/pytz/zoneinfo/US/Arizona b/lib/pytz/zoneinfo/US/Arizona deleted file mode 100644 index 4d51271a14ab85c1aac83f7145b9f7f8f19c512a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 344 zcmWHE%1kq2zyK^j5fBCeZXgD+1sZ_Fyk%As=I>^2SkNXjVd1Qo4W~PKCY%?)FLS>C z>6#0TQZm1OlnVTQ5y8O32!zZ)$jJ2n|Fm}u4FCVHUckum|Nq<x3>;uKkB@H%gRct^ z2Lo|<2+(i{2qD2q|A8Qmg=YhZ200BxgPaGVK~4nGAZLPTkW)c4$hlw|=wuKLayEzt PIUPh(=zK1qf6Tc6=)Qk) diff --git a/lib/pytz/zoneinfo/US/Central b/lib/pytz/zoneinfo/US/Central deleted file mode 100644 index a5b1617c7f70bfc77b7d504aaa3f23603082c3cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3576 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 diff --git a/lib/pytz/zoneinfo/US/East-Indiana b/lib/pytz/zoneinfo/US/East-Indiana deleted file mode 100644 index 09511ccdcf97a5baa8e1b0eb75e040eee6b6e0c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1666 zcmdVaT};kV0LStFL*AOuCUenZx^UBrvdmhxOs$2dygYfS)X7^*(Gj&G`CoXy!A)V7 zj1gwph`4Am!&tLPDN)B;GiE#Fg4v$G^F7?Tvbk}do&V?GbJNZ5`vh`JHYPfMoH6Db zE@z#&yhpm`(ReP#J$385Y}z-$J$<5IK3qA&eb}2J9~}s~PolrdCq?6QSLLwtH1(tI z&gph~rg!RRNjIEcr$zTg9C!NEQT;sF>h^bR(=P@Z+?N-Q$bt46ckp0^RE>G=tCE0x zT{q8tlQ~DeEtuxM|1w2?F#kQ+7OB1So^l$3+PD9eN{g?O>1hi@`f#((h%HnZU59jL z*nE|FwM;Mk6s;DWJSZ3UqzZp+sm!`QLuBXs<&ydku{0%KE~^|8%Ok^OAm@Py{1}!i zk}irB?<VS1QTNoUyPx&yV6)0S+okgc4ypV-t$Iy+nJQS{pbHzbl<;4ZMf*#|+Sq!z zuGlZuhgHiB8S!Gnr(9V)Gh7sRrpS`f!=mJJl-xAbElTT?b=l+3YI9Yj-qO;g%5#ER z9&S}zla#I~Z&2GJ?&$5=HEMfsP*%;Y7gYndW%bl*QQdw<)_ltqI~w=OoxLfdwys$2 zYKsze1(|a9F-MH>+0V$3-!H%Z{Pi3)V$|q=@bSEsWXJKmn^$}xo_DFq8EfCi+vg;n z&ScNK-{G6O*dK5fq?x<i+?D1o2{`HIJ>7iA@!2N?{$g&PIRztwO~~w!=^^t&CWy?? zYNm+H5t*db%o3R<GEZcp$V`!`B6CG1Yc;b)ri;uMnJ_YAWXi~#kx3)7My8F-8<{vV zb7bmh=gte0=a|_8(?{lyBw#feASqZ)4oDJKlLe9nk_VCqk_nOuk_(ayk`0m$k`I!Q z)ntUEWHmV<Nm)%+NLol<NMcB4NNPxKNODMaNP0+qNP<X)NQzdIBa)=mWQn9{HF+Y5 zBAFtoBDo^TBH1G8BKaZ-BN-zpTTRYL(pHl-lD5_4jU<j_j--y{jwFv{kN<J{q2?DM Y$^0V3_-Dr@#?6ZHCnUrr#LWu*39tolUH||9 diff --git a/lib/pytz/zoneinfo/US/Eastern b/lib/pytz/zoneinfo/US/Eastern deleted file mode 100644 index 2f75480e069b60b6c58a9137c7eebd4796f74226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3536 zcmeI!Sx}W_9LMpa;)WucQm$lLAu8a83sO>PgnGmjT+r~zKnAt=mx@@5l_ud#S(Afp zgQ+NJDQ=jk;TkzM<%0WykET>6`Y0}>c}~ywz3nD0y6a`$^Et!dc=!AM;}TLQ^>F>; zscV13%X8Jfd~fl#{m5MvC`-5fp}tz+l4YO&q?RXNloj)S*LjoI$;$A2wQA%6lOK?+ z3VMEH3Op<In&uyxHRW0Q>nbtdl%(plWh2bG+#$MfQ!leVGemFr@<rL0GFWYz-BUJ4 zcU48>17u536ZLKXyRx;OQN?XeNpZyywcY2o*<QL??YMNpd{=l#m+UJxI~Q%#yYjv; zyVDlyJ@e<7y|L+fU(y8geb^XX>Ygn>_($mdA&IiTdbB#=7bOQy_ESH;Z{$eFTXIC* z*JU#<nWItX^s)F-bG-ddeImTToOCVIrvet5Q+l30?a7xjyOQ<U@@zS``dw9CGDXg3 zCn=rlmJ6xRtBaXo@=Hu7bt$o#Tpk^&E22ZpuYH>8--7(j?+@S9SL)p`SMD6ue^iv2 ztH-zK%F-fpZD*OfUU)>z(js+Z(Pp_hcZsS>%aL0XW~tk;8FFX9ICVEHL8?2=)PMR% z%Do0-^}Xsb=KgQ}^<O6=%!B>yv}bEu<IVSK*AkDZm32Yao~cb8@hBhlK<W<Hs$SH2 zso!mns{cVNY1lMRHC(&c_?iW(k$z7apIWZ{cBM#@;`!Qt^*qz`vq`#HcCvYB)(g6M zYP4xFwzCe12{sS+Ypfp$Ze&_^2v)5cRGQYc8>!YeeWlHXO4au8RcW{TpbFgZvpl+N zgKD4dGLOCUiRuu4(R7?#s2>mCXPy}Rv3@dOl?m!RO$T}QO0aLd4lZ9Qov-xKT}rZ~ zYgwEM$xW5eO}$lE<`C)jNlVo|CB^i3<DTjn9b<ZpIIF^gx|rTQN>rcvex`4m)4FfP zb<^+u4joZ?*z`Y>t0N1q$y3|k)=w`wBm=&fsH4(0$}{uls%K*t%X3LDtASzZGHBp) zYEV^yi4K{dqstbW7{6z9%%-VkaAik5<jZUsdOS+GXHSt~TRN!N@opKO<D*`T43iNv zD%8lf%_J^<zlytGC8NUEs8N^w&6vPaJ!anxGuBg}6Y|Q;xblU1{QM&GQpr@En6$)9 z$Q`DYd$YWpHAPJf$&pu5+$za0Lz1JzRB~m4qy#lnDL+L@YP~9zx;9WIR~%DQaw5#s zgE#c6>21wxg=IP|-eY7@k$yc~n>W&y=xG6a%=Fk<db;Plr1#BH>E*j6qh*H5C|M!1 zsuR?kx$ntaCnMGD%oLfkHBe<H#>m`HU8;7i8vfMrso_7U>3{Iw{k_+_E!XApdVkne z%g5_2Uhit)d~fW0HXZ7Ya}643-;wqmZQtQ>cFSC@TFysY4K~ngpTs)mBV-GaJw!GU z*+pa<k$prq64^;)E0MiKHq+7WCbFH5c0Z8~MRpX~Qe;n&O+|JU*;Zs<k&Q)m7TH>4 zZ;{PKb{E-RN4vks20PjvMz$E)V`P(&T}HMU*=J;<k)1}i8rf@Pvyt6Kw%gI}H?rZ5 zcE^z|NA}#&ZaT8-$hIT<j%+-#^T^gCd+%sBAK86m`;q-e8h~^FX#vs$qzOnDkTxKF zKpKH`0%--(3#1uHHymv{kbWQyK{|r81nCLV6r?LiTadmWjX^qtv<B%7(j25aNP8S@ ze~<<t9YR`!PLKFPlXz^GfHon0LK=m13TYM6E2LSDwp&QM9Bsdlh9Mn8T88utX&TZs zq-{vwkj5dMLt2OQ4rw0JJ*0g||Bwbc+72QuM0$uc5$Ph*Mx>8OBau!btwef>G!yA2 z(oRR)Po$xawxdW(k)9$=MY@W#73nL|SfsN^Ymwd}%|*J4v=`|w(qKp1VWh=KkC7%L zT}IlB^ciV1(rKjCNUxD*Bi%;Y?P&XrG~Cg49BH|u?K#qPr0YoAk-j61M>>zR9_c;O ze5CtG`yFlnksH9#-T}xh;Armw<R(Dw0^~M8?gQjTK<)(ORzU6r<Yqwb2IO`??g!+C zaI|*>a!WYcdjh#B9PM3!+!n}vf!r9#oq^mM$i0Ew9LU{)+#bmNf!rXD_6|XA5l4HE zAUBDly-SeW1i4R;8wI&jkXr@0SMdLv<=@{dzV?&}w<k?kchArsq20Q=yLS)m9@@?K EZ-WKA#Q*>R diff --git a/lib/pytz/zoneinfo/US/Hawaii b/lib/pytz/zoneinfo/US/Hawaii deleted file mode 100644 index c7cd060159bd22fc5e6f10ac5a2089afb2c19c6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 329 zcmWHE%1kq2zyNGO5fBCeb|40^MH+y_ZdPZH-HL?~r#o#=TvGm0a4FH#;%aZP2O|?B zGYcc@|Nl8m3=BXrf`R4#|Edf|4lv0BCI$ZgFHT@!@$n5|@CXKC7a$G?;(!pK!3+$H uP%?xBC;bP4k_QF*Ks3l{U>fK=5Dju7hz2<mOaq+?qN(g$E}&lw4Y&a7X=UL6 diff --git a/lib/pytz/zoneinfo/US/Indiana-Starke b/lib/pytz/zoneinfo/US/Indiana-Starke deleted file mode 100644 index fcd408d74df43310a9a85c475f83d545f6d75911..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2428 zcmd_rQB2ik7{~Dkfe-{G6GJjEt(buXHk3Bl+S0K@BA5qIA&k@z%Y0QJQKQ$bL0&XV zn~G})i(IZ5T2rwtG^N&R&d?-CCBP(SA+N>-DV@{%eY?zBUH7p6`TTcudiDF_T~hY^ zO!>=&*l&2aJ@(-}THBBMeTjPSC%>tNnz6cZ&jt1M>pp#U+K@V15^B!potKU&r_Fb% zN2ODmO;=Q%boIPtzV{v07f!4<7rS@qOZ(qc-K|ynhpp>WPko{8E%U0r>I{9^GfVwg z9H*}oq?`WCbops^thpK=D_3t$G}r9^eyx4j{M_FszjU;jfiK$R`te>h*xaMd-c#zv zwv&2jX|1|7Tq?J(ddx_tM}Ge@!T4Gd#Q%PTk=+pzP&;Twy*wy^Yr|Dg$rv4+dtKf2 z#DES-{ziqo5wAldKT@Fw-jy)(wi?s3Lx*=AG!Z8%^w?wD&A9#BC9<yE+`YA2##iN= zd&=@<!s0X&<w=u?kH?sMr^iV2)Y)p%=n;t-HA%(XjMn${-d2;_Z|VC#yQE?dUDR=n z$JLa|aq_^HMm06>hD=-asd+H<oII4Z*E}3`SmGbqV&Z-6dV1J0Gw0DtHFwSeHTTz} zk~w3w$vjslo`@Xd`FN9L4WyW--r1$+b<9`Uo2&HvBgrbKs8Hwb9IqCnXXvLZhSb8z zaoU^Lp}ZpjIzP2V<zI=FMX}$SMW2f-_8l=xn);-$d$%citxcY3-DrxJ?~|qVMdsP; zle(m~N<BBDNiQocRLdi3^oq<3wPIkUE{%^<rKhuWSxA5?JCLYX^<P#m?DWWsXZ&V$ zWrDoa+-uh4M~K>X%B)Qtlyz&~GwY+;r97wBl=}vBWm=P}>^`G6MAxVdt%r2g@Jh9@ zeuv)FnWZ*YSLjz-5><6^fqr%OST!oZ{saa&c)jya@ZWrY=fCZ~4gQBe`&a*(-~ZuP zB7Xm|g8@N){|5~++P#On&qzLH!k^#I%l68XbL_LwJ_Yv4^~zlP&IPzn@cxJO`Rx@4 z`WlcGB1=Tph%6FWC9+JXT_>_oWTnVbk+mX=b=uV;%SG0UEEriavSeh<$fA)|Bg;nC zjVv5lIkI$Q?a1PtcJ;{eop$|50gwtHB|vI`6alFMQU;_BNFk6)Af-TRfvy<5Pz}zO zgQFfuK{zUclmw{>QWT^rPFohFE>2q*j>;gVL282(2dNHH9*+7T1>&d>QX-BTAw}Y- z5>h6PIw6JPsFc%|3aJ%RETmdUxsZAx1>>j~QZkO3Aw}b;8d5fnx;bs(kjf#YLu%)= z#p9@+)0U5;eok9JjtU|rL~4i>5vd|lMx>5NA(2WVr9^7!w8ccK>9pnKsHf8wl%t|Z zNjYkY6qTc@NLe}RiWC;9EK*vewn%Z2>N;(Ck@`AqfsqP3ZHbW@BSq$@GE!!aIwOVV zs5DY)j#?wd=BT#QmK&+J(-s`5xYL##sX0<~r0Pi7k-8&=$NyL5!|X4CS@xGfV)kQ6 RGn0}Nvr|%%Qj(Ix{s3RXO|k$0 diff --git a/lib/pytz/zoneinfo/US/Michigan b/lib/pytz/zoneinfo/US/Michigan deleted file mode 100644 index 5e0226057ac5e154901b0debb90547c096f9083a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2174 zcmdtiUrg0y9LMn=L|_L<C@EB;f{cOux1(rO7_ie(kC+}z(e%;4vm+GW)Xty`Ye9xv zb4`c28q2gswMJ^TT*cI!wK+3omHin$wZ%n?dW<ewY@D9=v+la;uJt>6p4ZvA`Q4n) zJKPlARO$WWNw&XmczW&Odv?!9d29Ap@Ab|;XXIl3?{ZO1=&$?(=8|_nC)Zq-l=4$5 z<@xDyO~xVR^A3v7JgZW5kEDJ5s!l%<k!z24>#1)%V>0${(wV(2=DN=N^!3qznYOw} zX9Ww*4fE6VjfJTuJFieppE71<B&Mnvzxib5_hTyO!q0Nk$@41r@Mm)Kfy3&Sm}hQ% zdXLI${K4dJ9@Mw_Pn%hbUeLE^y>1GMw(5e(kEPJps0&A4lcJyI>Dfa&rFb~3O8TQx zdUUQT>sl=3d$LtUBw{MJ{Hf*yg659p-zk5=Y%{lVNX<)0H&rvg(N&|rn)wqS>IG*m zm^;7i*VTi+$Xy>irSIPTx!m*8MqSf>L>6}MQ1>?MmD=VFs;(?1^>wwXetf_LO4jSZ z@GcWfU#Npe+svY|e7*SPURm;GjS6jVm8I|HsfM*7S=N`N?yoMB<&TZ36*-v_Zv0e* zC&p!^|4p^>$Ejvj?is!6^cAyuazHm78a8W2cIma<$IOF6ZF*hvKC`}msaBzPWy8)^ zwXvj69*Trib9#rg1j<y)Sd&BwGF9YUwM3K0RrKv#**yA%YVBKK+Rk6m565!MBZI@b zy>ZgW?qm8<zcyRi_vx)!r_8p7PQ7jNvc#(TRBYs=bYyp^j-i9n`A3s_yuU}DxKypS zcSYpM_j6U(x}fZM(NhVDS0yE0{U7+m<40zBUOfKRD_&AOe*7J8N<99_iG(zFXSjRX zl2F*IT@m)`IS<&g%$~Y1e|j(B?>qc21`@XqBSD6Oj0G8t(~bri4l*8I#ek3zIqi^; zF(HF;+EF3HLdJy*3>g_RG-PbZ;E>TF!$Zc03=kP1GDKvI$RLqXI_)r#aUugnMv4p- z87neaWVFa|k?|q}Mn;Sb85y(F4jLJ?(+(RMH!^T!<jByGu_J>=Mvn|189x#LBmzhX zkQg9AK%(HZVL;-51OkZ!5(*?1NHCCSAmKpbfdmAJ2oe$`CQcg^Bq~lD79=iC8yF-q zNNAAQAi+VRgM<f(4-z0GLP&^^7&&c_kSIBAn2<O*ZJ>}yA)!KIg#-(U77{KbUP!=@ zh#?_EVul0_iJH@f4T+o61`dgw(}oU-9TGewdPw+?_#pvAB8Y?#i6Ih1B#KTOMkJ0- y8%QLQP8&)jmPjy>Xd>Z6;)w(l|CbT<*~0p5S&Kt+N-Imti$fI^r4^;+zP|v~6Kewi diff --git a/lib/pytz/zoneinfo/US/Mountain b/lib/pytz/zoneinfo/US/Mountain deleted file mode 100644 index 5fbe26b1d93d1acb2561c390c1e097d07f1a262e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2444 zcmdtjeN5F=9LMnkqQH%ZQ;8v<nU)CgtR#>b6-0<P2(NH8!YHnHS1a(Ln-=0RD8?U+ zvtrCL36!+fOng|gv7xTv+REl|Yg!BKxhxw!Y?8peo%dPwPk;4STVM9OuOIjS&-=Po z`_|@&f812_4G-6C9^R)b{@GWcUmFNlJ<liU-dDa?dprTXw{@E6E54}vI`*j#+N1RF zyx$s!>*B?gOursm&?$b8b?d7UesOi|Njd(VTTGm*mXq%nh`_OY8GIv2h@FWtq%9yq zpPH0YHYBL9x|w=v#e|wxIIhF9MpXC<xjIswP>}}?Nyq3Ob<M?I9d-V=h(6JxW8Uo* zv2XTB`ErZ6w*6Uo-Bypd-d8WDuPPC7rT5Ai`6=Rtlm#+=Zn2sf>5vJb$tvNO`8x57 zNR>1kp=X`^LCrpNN#EFeTFvp#k~i%*sOGK=%6aQP6gTI7E^k@(wwNFHo=i^FA~|qD zr#Lo>l#!D<^^!~6I=EM-oo!U<-OuTaBb6$%*{ic&TBNeQtuklR47IRitz1+&rgD?- zlegu3q85jz%DluYBJbNMnLmDB6rB1=-u~%;Skmv%cMR+nOFMqlckbFQ3L8GsceU<P zcbE6;d+N8TqRba{anTx8{Ogb`NpBJ*XZOp}=vq;Fq+Kq%Tqw$3eO)jAxJEgf+VuVJ zELG(-K3&l@M?J8lOjr6t)rzEa?OOSja!thQs@zkm>gzP=p8ch855>q;fg!QFZ&W@w zvR~A+4$FrI+eK~tQMsmjy?EGpM%T5qsYlWe>qoslRUh4{JtbwzbJ?%G$?3{_+O2)z zvC4O#K(G7eXSKeoT0V9rMm+A%mrooV6%AF1vaw@WY{;FI8yk*_O>r0G=JGDFIWVsM zd54vM<TJe`zEf=(Jg&En`PI|iz51DRZq?M>qPHC@P|dX-y?tkr3Jv-5Z%WwTuYY~@ z-y00>?i3;ze5)rU%)Dz6Vc(<dr(EuI31^XcR+y*SJQXgpA|XQThwERgFKDhdEUF(_ zA+khdjmRRARU*qo)@d~hMOKO|)oRv?EEZWUvRq`nR<mGa#mJJ8HKScLFRYp~%LdlX zv2bMN$kLIuBa25?Z#BzD)^9ZhKq`Qg0I2~-5s)fylmV#&M<I|(aFhb61xGQEYH*YT zsRvRJq#{;R5~L<bQIM)2WkKqK6b7jbQW~T-9K}JZ!%-fjK2}p8q(W9xBBVwfMMA2C zlnJR5QYfTSNU4xoA;m(fg_H}a7g8{!VpdZ!q-GpNL#oD6Hl%JGg+nUGQ97h{Nb!*B zA>~8rXEg;xDrhw&L~3X?MMSE|QAVVWNFk9*BBexXi4+s5CQ?qMo>o&(q@q?+QlzF< zQ&gm?9A!o7%28OPvK*yFYRgevq`F9Xk@_M9Mk;JIB}Qs&HAP0MY&B&@>WmZ`sWeBa zky>*U8>u!&xsiHv6db9z)s!5mxz!XMsk+sa9jQA~c%<@3>5<wa#mE15^&RHNV6pj8 VNOLaC$jQh`b7p5}WM^bK{s0D?lEVN1 diff --git a/lib/pytz/zoneinfo/US/Pacific b/lib/pytz/zoneinfo/US/Pacific deleted file mode 100644 index 9dad4f4c75b373635ccbe634798f8d9e587e36c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2836 zcmd_rX;76_9LMpCq6mtfOq2-iq$YxZfTFmRxecHqDoA36O9F#ws1Rxy(nOgx#-Gfk zjgDqbP8phGV_AeYIW>)C&^T@pSt6t2f|j^+Z|8g7_Ntdn&ok%woVoAs_m?@lATPo5 zkEetEg~RiiJ=}Yg*-zDbDdz3{A!447GFxB2F5j&SGj;v0Ev=hBKppiK&pB4MQ%-ol zl9RQfPBpwMKkxWZ8fw<cFY8{G#;OAOwP2~7E}bmDrOuGwb7JI7<WOl!o}|uppRSrC zqE&P25Opq~t2$Q~qRuy6Ru^_(S1pI?)Wyo<>QePZxx8$@x>9jOTGt$qtA!uSwYl%e zAL*~kpJSer>w`<AZQwR_quVUG*{NLJY<pJUYR*%)kLBvWzDZHueaYJQew6ZTiPU~C zbW!bAcGm5e4HW<R5vIfRAn7<Z&;-O?kbw2$O`!T-0(X9?gD&rq&W+Wk%kjf1xVF-C z{j^$j+wqZBuT`o$)`{-Esz}{guw3`Zo~c4oGj-1q!&R@yVLG&LhTIhxs>9kPN?7Yq zbNA_95?<HS^geJy`s{8q_iQ~Wx@3^P_n9xGZ&tAGx9EiGpLj{%H|cXVAmm3K5mluk zye%d&s7ysR{9vNaEl`7McAMz>Qi-YBU}E>olfk7=n79q&BtHKYolw+Yh9np3p&1<| zF(OM3OK6ti0ZBS3yn{+Q8>UCxI;%z=x~)f@{8o+L6>9F^|ABg-;-(q%#(MQ&;VCn= ze20unuQB5nz9bU{8#8gj5}A0lUMI)AsFLgV>eS%HDs|6hJ*j1?n*8P-Gv(+aNn5?q zO#Nhvq|aGlrfrIq>7%pFj1nao;iF9E%vQ;~-P>d({v=svM(SC8uBcgGhwE%_y_&t< zs~>LItLBt9>PKoetDJ=g_1vmeYF=7{nZI_UEQqN!kLItCg~8iQZgRHdwv?Ovh*6S% zIL{OW^p=91DP~cVPafNps}~;$S4&Eg_2boERhSj2msT{YWy3n_<%I`TQAmp}PT#JI zeSxMVsa8rF&YP8?+hk?UVY8~OT%N3|HcuVPlhvh_=IMPYQkqj_)@+HAc7FD4@9*IH z-+6t$$^jma&-a%2`TKkoWu8v%-o<^@l(bCGv<dcP*z=G*(=zQp+T-zapUi(z0-t?y z{KIOIA|O>j%7D}XDFjjpr!56i3#1rGHIQ;3^*{=OR0JsrQWK;oNL7%sAay|sgH#47 z4N@DYEe=v0r!5asAEZD?g^&^<HA0GnR0$~)QYWNPNTrZcA+<t^g;WbEm($h@DHu{Q zq-5x7#)YEs*s1|#L+XYU4yhbcI;3_;@tn4LNco($en<h43L+&$YKRmOsUlKFq>e}- zkxC+^L~4l?6R9RrPNbelL7lduNJ){JB1J{2ij)<pD^ggbvPfx>+9Jh8s*9A@Y3qv= z*l8<_lo+WoQe>pcNSTp3BZWpPjg%UxHBxM(+DN&PdLspQ+KMA3M{14~9jQ7}cBJk| z;gQNCrAKOy6d$QRQhukcKe7N$y8_4(IPDrBi-4>GvJA*NAPa%41hN#!S|E#otOl|i zPP-n+f;jDpAWP!3Yl18avMR{3AnSrG46-uF(jaStEDo|d$nqfTgDjBKt`M?BPP<0P zB023UA<KlU6S7dqN+C;ytQE3Y$Z8?Wg{&8{U{1SY$dWnjnjwqkw5x_J8?tW5!XYb% jEFH3T`2StJAUlLfb`Yb}hQubs#zm*a$H&IU#s&Qiukn{d diff --git a/lib/pytz/zoneinfo/US/Samoa b/lib/pytz/zoneinfo/US/Samoa deleted file mode 100644 index 72707b5e15cccac9888e5ba22bb1b4e92170df5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 187 zcmWHE%1kq2zyQoZ5fBCeCLji}IU0b(MAqLNj6ji%6$}jj|HuCTk*NU;EIz&=48g%6 cKouYmLV~IPfgsQJ1P6#F&U7xIMTUl40GV|pSO5S3 diff --git a/lib/pytz/zoneinfo/UTC b/lib/pytz/zoneinfo/UTC deleted file mode 100644 index 5583f5b0c6e6949372648a7d75502e4d01b44931..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N diff --git a/lib/pytz/zoneinfo/Universal b/lib/pytz/zoneinfo/Universal deleted file mode 100644 index 5583f5b0c6e6949372648a7d75502e4d01b44931..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N diff --git a/lib/pytz/zoneinfo/W-SU b/lib/pytz/zoneinfo/W-SU deleted file mode 100644 index ddb3f4e99a1030f33b56fad986c8d9c16e59eb32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1535 zcmd_pOGs2v0Eh8&eK(rb!qglm&2)U$sA*0)IyEzjsUbNPSW%Qng3+OZ6j}@+wy8i0 zTv(<w>Y|s6YLnFvfkYNAS_B#d(ZT{b1Q9Ad&UZz$TD9&D_x_Go1;Ov{Z)$BR5`SH5 z^c!xj-TLO770{2~!?v;O6<<2~a%X1yzByZObnc(+f7>=aAe@1L@*#I{^@&i>RWve~ zaC~IY6&@P4`5GPslaD0WhbPu1O}P_eCL0pxR)vy2#ZM$pdfe;AuS}$j_ABe{Zk2lN zys}+9t=6AwR%vZ}Rr<jywV`gS$|%oP8}pM@rq!adV&|1T(k|^^lVtYC#6V8_(?HIf zIhp(Xv&_3cCG&%?WWm)Za#QC$x%o`LbToI%!b78~=v0p?cJ-+(dpcA}YCx419Z;p; zkE*hic3Jk$tDN&qa@*r9wSBT&mJfNP>yb@XbY;rQULoBr(Q-$pRqgamOV6<%%A5I8 z`aJJdRpcF6o$*Xn&%97I;XzgN`j*=Dp-a`?y`<{KZ_4`1CzZc0^@tH379J565g8R7 z6CJf8Dth5#iCy-ITe<9u<=^=89B&aK!>RufJR^iCykNxW^I6W7Jw}`mWo|?Nw{jgK zVewqmU?dA+O%tiVzt43T>5K2n+)F>t@7C4(MLl<;zP&sez51>dd5#j{^ZE6yUz(S} z(^$BcUYI8#{QuC`Pkrrs7#c%5Ls~<6Gu6!@-68EE{h8_pkq%9Di%5^Ax=Ex<q)q-* z`a~K<IyKd;BE2HbBHbeGBK;x_BON0xBRwNcBV8kHBYh){Bb_6yo9f<?=8^7Ab^A#F z$Oe!dAX`B8fNTQU1+oofAIL_KogiC5_F}3xgY3psZwJ{AvLR$g$d-^jA)7*Wg=`Di s7qT&AXUNu&y&;=Jc4w-$hwRT(ZxGobvPEQ%$R_cB-=#%wxuDqc3lb5KyZ`_I diff --git a/lib/pytz/zoneinfo/WET b/lib/pytz/zoneinfo/WET deleted file mode 100644 index 9b03a17f41153b8673c144b42dd3cb0adc3f4ba8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1873 zcmd7ST}+h)9LMoP#448b2jeAqONl@|!T|(@ATvBL3ozs;gd!=3Scrniz_ha#bFG;B zxiOndCoRTWv!fSS7hulU;Uwnfn6q?kx!%;dHgi^v&FA~vcGXorXJ?;h=i*%bzrR5J z=9XN~zn;gOU-<IubiTYlHDjnrQ6JB;XZrlIt^!3jXDB9+pqZ7+?Ag436q|NSaWk(e zKKwhy-~CnzH;(GL%Y&MAW}gzrPFT{BkR=~HY$^S+)Lrk}^KG3rdu@}=sR&qFvEN?s z7Fc>xhQ0W7f@MUMD&yWlW&WJ5xl?hPckT(zANfn(q2IM&;Cp5DT~v10n0(D&DW~D8 z<#vu(UdgB}T)E%!lMh)zZjTjA2W@fUfEHh0Z-o!KRdk}%UizU<#lwv%87<PwdzNa+ zr%8IHB~45F3hmXh2$eP`+OqUt<u9GKvWN>RPy5lz@BCuR!%x}r$#Hw_#^?5W=u4}Z zIH}6L{Z@74Gp%TEw>SDfP<8D`TG_TyHH+F5s3_N}_$saT&eiJsKGnsRS>2Twy?HOw z>c9R|Yo;P??O?PujNG<$!MoNtFllc!PTBh1H??8OH`dfJq2|;Bwy`9nmM4#EQ?j(_ zw@=hM-Kovvy?Xn4K<|99Ms4RiY|DowY9Fq#t*xorwkO{@s*A0&CEm8@q*#~#Z`%>= zvF_|xb^q~`^+erQ&!w~0d*`Zl9vilICr7pGz-jFc9nyQfLkjK<D$MhLeK<FsC<}YM z)*tZH`fCE7Fy{&Uhco35I92zLoO4A^7CBqwbdmE#P8d03<dl(fMo!w*Icwy!k@H4Q z96593)RA*XP98aX<n)pAM-o6XKvF<*K$1YRaCOo^@<0+nGC@*7azT<ovO&^8@<9?p zGD1>9azc_qvT}9OLh^ET5<@aWQbTe>l0&jX(nIn?5=1gYQbck@l0>pZ(nRufbrMA~ zb#+ojaz&CwvPIHG@<kFxGDcEHaz>IyvPRNI@^*C+M>2PHQb%$}l1H*f(ns<~CIFcM zWD1ZuKqdj11!NkKc|axtnF&{CDv-HACIgubWIB-fKqds45oAh`IYA}`nH6MOka<BS z2ALUGXKIkSadjpKnH^+$koiF-2$>;ditrJgqey3*e2kN~GA}DX%a@(wt<3T97Woif Cu$Y+u diff --git a/lib/pytz/zoneinfo/Zulu b/lib/pytz/zoneinfo/Zulu deleted file mode 100644 index 5583f5b0c6e6949372648a7d75502e4d01b44931..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118 mcmWHE%1kq2zyORu5fFv}5Ss<U2@P=uGD67I#|6}Gzy$z!n+A0N diff --git a/lib/pytz/zoneinfo/iso3166.tab b/lib/pytz/zoneinfo/iso3166.tab deleted file mode 100644 index c2e0f8e..0000000 --- a/lib/pytz/zoneinfo/iso3166.tab +++ /dev/null @@ -1,274 +0,0 @@ -# ISO 3166 alpha-2 country codes -# -# This file is in the public domain, so clarified as of -# 2009-05-17 by Arthur David Olson. -# -# From Paul Eggert (2015-05-02): -# This file contains a table of two-letter country codes. Columns are -# separated by a single tab. Lines beginning with '#' are comments. -# All text uses UTF-8 encoding. The columns of the table are as follows: -# -# 1. ISO 3166-1 alpha-2 country code, current as of -# ISO 3166-1 N905 (2016-11-15). See: Updates on ISO 3166-1 -# http://isotc.iso.org/livelink/livelink/Open/16944257 -# 2. The usual English name for the coded region, -# chosen so that alphabetic sorting of subsets produces helpful lists. -# This is not the same as the English name in the ISO 3166 tables. -# -# The table is sorted by country code. -# -# This table is intended as an aid for users, to help them select time -# zone data appropriate for their practical needs. It is not intended -# to take or endorse any position on legal or territorial claims. -# -#country- -#code name of country, territory, area, or subdivision -AD Andorra -AE United Arab Emirates -AF Afghanistan -AG Antigua & Barbuda -AI Anguilla -AL Albania -AM Armenia -AO Angola -AQ Antarctica -AR Argentina -AS Samoa (American) -AT Austria -AU Australia -AW Aruba -AX Åland Islands -AZ Azerbaijan -BA Bosnia & Herzegovina -BB Barbados -BD Bangladesh -BE Belgium -BF Burkina Faso -BG Bulgaria -BH Bahrain -BI Burundi -BJ Benin -BL St Barthelemy -BM Bermuda -BN Brunei -BO Bolivia -BQ Caribbean NL -BR Brazil -BS Bahamas -BT Bhutan -BV Bouvet Island -BW Botswana -BY Belarus -BZ Belize -CA Canada -CC Cocos (Keeling) Islands -CD Congo (Dem. Rep.) -CF Central African Rep. -CG Congo (Rep.) -CH Switzerland -CI Côte d'Ivoire -CK Cook Islands -CL Chile -CM Cameroon -CN China -CO Colombia -CR Costa Rica -CU Cuba -CV Cape Verde -CW Curaçao -CX Christmas Island -CY Cyprus -CZ Czech Republic -DE Germany -DJ Djibouti -DK Denmark -DM Dominica -DO Dominican Republic -DZ Algeria -EC Ecuador -EE Estonia -EG Egypt -EH Western Sahara -ER Eritrea -ES Spain -ET Ethiopia -FI Finland -FJ Fiji -FK Falkland Islands -FM Micronesia -FO Faroe Islands -FR France -GA Gabon -GB Britain (UK) -GD Grenada -GE Georgia -GF French Guiana -GG Guernsey -GH Ghana -GI Gibraltar -GL Greenland -GM Gambia -GN Guinea -GP Guadeloupe -GQ Equatorial Guinea -GR Greece -GS South Georgia & the South Sandwich Islands -GT Guatemala -GU Guam -GW Guinea-Bissau -GY Guyana -HK Hong Kong -HM Heard Island & McDonald Islands -HN Honduras -HR Croatia -HT Haiti -HU Hungary -ID Indonesia -IE Ireland -IL Israel -IM Isle of Man -IN India -IO British Indian Ocean Territory -IQ Iraq -IR Iran -IS Iceland -IT Italy -JE Jersey -JM Jamaica -JO Jordan -JP Japan -KE Kenya -KG Kyrgyzstan -KH Cambodia -KI Kiribati -KM Comoros -KN St Kitts & Nevis -KP Korea (North) -KR Korea (South) -KW Kuwait -KY Cayman Islands -KZ Kazakhstan -LA Laos -LB Lebanon -LC St Lucia -LI Liechtenstein -LK Sri Lanka -LR Liberia -LS Lesotho -LT Lithuania -LU Luxembourg -LV Latvia -LY Libya -MA Morocco -MC Monaco -MD Moldova -ME Montenegro -MF St Martin (French) -MG Madagascar -MH Marshall Islands -MK Macedonia -ML Mali -MM Myanmar (Burma) -MN Mongolia -MO Macau -MP Northern Mariana Islands -MQ Martinique -MR Mauritania -MS Montserrat -MT Malta -MU Mauritius -MV Maldives -MW Malawi -MX Mexico -MY Malaysia -MZ Mozambique -NA Namibia -NC New Caledonia -NE Niger -NF Norfolk Island -NG Nigeria -NI Nicaragua -NL Netherlands -NO Norway -NP Nepal -NR Nauru -NU Niue -NZ New Zealand -OM Oman -PA Panama -PE Peru -PF French Polynesia -PG Papua New Guinea -PH Philippines -PK Pakistan -PL Poland -PM St Pierre & Miquelon -PN Pitcairn -PR Puerto Rico -PS Palestine -PT Portugal -PW Palau -PY Paraguay -QA Qatar -RE Réunion -RO Romania -RS Serbia -RU Russia -RW Rwanda -SA Saudi Arabia -SB Solomon Islands -SC Seychelles -SD Sudan -SE Sweden -SG Singapore -SH St Helena -SI Slovenia -SJ Svalbard & Jan Mayen -SK Slovakia -SL Sierra Leone -SM San Marino -SN Senegal -SO Somalia -SR Suriname -SS South Sudan -ST Sao Tome & Principe -SV El Salvador -SX St Maarten (Dutch) -SY Syria -SZ Swaziland -TC Turks & Caicos Is -TD Chad -TF French Southern & Antarctic Lands -TG Togo -TH Thailand -TJ Tajikistan -TK Tokelau -TL East Timor -TM Turkmenistan -TN Tunisia -TO Tonga -TR Turkey -TT Trinidad & Tobago -TV Tuvalu -TW Taiwan -TZ Tanzania -UA Ukraine -UG Uganda -UM US minor outlying islands -US United States -UY Uruguay -UZ Uzbekistan -VA Vatican City -VC St Vincent -VE Venezuela -VG Virgin Islands (UK) -VI Virgin Islands (US) -VN Vietnam -VU Vanuatu -WF Wallis & Futuna -WS Samoa (western) -YE Yemen -YT Mayotte -ZA South Africa -ZM Zambia -ZW Zimbabwe diff --git a/lib/pytz/zoneinfo/leapseconds b/lib/pytz/zoneinfo/leapseconds deleted file mode 100644 index 148aa8e..0000000 --- a/lib/pytz/zoneinfo/leapseconds +++ /dev/null @@ -1,66 +0,0 @@ -# Allowance for leap seconds added to each time zone file. - -# This file is in the public domain. - -# This file is generated automatically from the data in the public-domain -# leap-seconds.list file, which can be copied from -# <ftp://ftp.nist.gov/pub/time/leap-seconds.list> -# or <ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list> -# or <ftp://tycho.usno.navy.mil/pub/ntp/leap-seconds.list>. -# For more about leap-seconds.list, please see -# The NTP Timescale and Leap Seconds -# <https://www.eecis.udel.edu/~mills/leap.html>. - -# The International Earth Rotation and Reference Systems Service -# periodically uses leap seconds to keep UTC to within 0.9 s of UT1 -# (which measures the true angular orientation of the earth in space) -# and publishes leap second data in a copyrighted file -# <https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat>. -# See: Levine J. Coordinated Universal Time and the leap second. -# URSI Radio Sci Bull. 2016;89(4):30-6. doi:10.23919/URSIRSB.2016.7909995 -# <https://ieeexplore.ieee.org/document/7909995>. -# There were no leap seconds before 1972, because the official mechanism -# accounting for the discrepancy between atomic time and the earth's rotation -# did not exist. - -# The correction (+ or -) is made at the given time, so lines -# will typically look like: -# Leap YEAR MON DAY 23:59:60 + R/S -# or -# Leap YEAR MON DAY 23:59:59 - R/S - -# If the leap second is Rolling (R) the given time is local time (unused here). -Leap 1972 Jun 30 23:59:60 + S -Leap 1972 Dec 31 23:59:60 + S -Leap 1973 Dec 31 23:59:60 + S -Leap 1974 Dec 31 23:59:60 + S -Leap 1975 Dec 31 23:59:60 + S -Leap 1976 Dec 31 23:59:60 + S -Leap 1977 Dec 31 23:59:60 + S -Leap 1978 Dec 31 23:59:60 + S -Leap 1979 Dec 31 23:59:60 + S -Leap 1981 Jun 30 23:59:60 + S -Leap 1982 Jun 30 23:59:60 + S -Leap 1983 Jun 30 23:59:60 + S -Leap 1985 Jun 30 23:59:60 + S -Leap 1987 Dec 31 23:59:60 + S -Leap 1989 Dec 31 23:59:60 + S -Leap 1990 Dec 31 23:59:60 + S -Leap 1992 Jun 30 23:59:60 + S -Leap 1993 Jun 30 23:59:60 + S -Leap 1994 Jun 30 23:59:60 + S -Leap 1995 Dec 31 23:59:60 + S -Leap 1997 Jun 30 23:59:60 + S -Leap 1998 Dec 31 23:59:60 + S -Leap 2005 Dec 31 23:59:60 + S -Leap 2008 Dec 31 23:59:60 + S -Leap 2012 Jun 30 23:59:60 + S -Leap 2015 Jun 30 23:59:60 + S -Leap 2016 Dec 31 23:59:60 + S - -# POSIX timestamps for the data in this file: -#updated 1467936000 -#expires 1561680000 - -# Updated through IERS Bulletin C56 -# File expires on: 28 June 2019 diff --git a/lib/pytz/zoneinfo/posixrules b/lib/pytz/zoneinfo/posixrules deleted file mode 100644 index 2f75480e069b60b6c58a9137c7eebd4796f74226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3536 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 EZ-WKA#Q*>R diff --git a/lib/pytz/zoneinfo/tzdata.zi b/lib/pytz/zoneinfo/tzdata.zi deleted file mode 100644 index 7eb8193..0000000 --- a/lib/pytz/zoneinfo/tzdata.zi +++ /dev/null @@ -1,4177 +0,0 @@ -# version unknown -# This zic input file is in the public domain. -R d 1916 o - Jun 14 23s 1 S -R d 1916 1919 - O Sun>=1 23s 0 - -R d 1917 o - Mar 24 23s 1 S -R d 1918 o - Mar 9 23s 1 S -R d 1919 o - Mar 1 23s 1 S -R d 1920 o - F 14 23s 1 S -R d 1920 o - O 23 23s 0 - -R d 1921 o - Mar 14 23s 1 S -R d 1921 o - Jun 21 23s 0 - -R d 1939 o - S 11 23s 1 S -R d 1939 o - N 19 1 0 - -R d 1944 1945 - Ap M>=1 2 1 S -R d 1944 o - O 8 2 0 - -R d 1945 o - S 16 1 0 - -R d 1971 o - Ap 25 23s 1 S -R d 1971 o - S 26 23s 0 - -R d 1977 o - May 6 0 1 S -R d 1977 o - O 21 0 0 - -R d 1978 o - Mar 24 1 1 S -R d 1978 o - S 22 3 0 - -R d 1980 o - Ap 25 0 1 S -R d 1980 o - O 31 2 0 - -Z Africa/Algiers 0:12:12 - LMT 1891 Mar 15 0:1 -0:9:21 - PMT 1911 Mar 11 -0 d WE%sT 1940 F 25 2 -1 d CE%sT 1946 O 7 -0 - WET 1956 Ja 29 -1 - CET 1963 Ap 14 -0 d WE%sT 1977 O 21 -1 d CE%sT 1979 O 26 -0 d WE%sT 1981 May -1 - CET -Z Atlantic/Cape_Verde -1:34:4 - LMT 1912 Ja 1 2u --2 - -02 1942 S --2 1 -01 1945 O 15 --2 - -02 1975 N 25 2 --1 - -01 -Z Africa/Ndjamena 1:0:12 - LMT 1912 -1 - WAT 1979 O 14 -1 1 WAST 1980 Mar 8 -1 - WAT -Z Africa/Abidjan -0:16:8 - LMT 1912 -0 - GMT -Li Africa/Abidjan Africa/Bamako -Li Africa/Abidjan Africa/Banjul -Li Africa/Abidjan Africa/Conakry -Li Africa/Abidjan Africa/Dakar -Li Africa/Abidjan Africa/Freetown -Li Africa/Abidjan Africa/Lome -Li Africa/Abidjan Africa/Nouakchott -Li Africa/Abidjan Africa/Ouagadougou -Li Africa/Abidjan Atlantic/St_Helena -R K 1940 o - Jul 15 0 1 S -R K 1940 o - O 1 0 0 - -R K 1941 o - Ap 15 0 1 S -R K 1941 o - S 16 0 0 - -R K 1942 1944 - Ap 1 0 1 S -R K 1942 o - O 27 0 0 - -R K 1943 1945 - N 1 0 0 - -R K 1945 o - Ap 16 0 1 S -R K 1957 o - May 10 0 1 S -R K 1957 1958 - O 1 0 0 - -R K 1958 o - May 1 0 1 S -R K 1959 1981 - May 1 1 1 S -R K 1959 1965 - S 30 3 0 - -R K 1966 1994 - O 1 3 0 - -R K 1982 o - Jul 25 1 1 S -R K 1983 o - Jul 12 1 1 S -R K 1984 1988 - May 1 1 1 S -R K 1989 o - May 6 1 1 S -R K 1990 1994 - May 1 1 1 S -R K 1995 2010 - Ap lastF 0s 1 S -R K 1995 2005 - S lastTh 24 0 - -R K 2006 o - S 21 24 0 - -R K 2007 o - S Th>=1 24 0 - -R K 2008 o - Au lastTh 24 0 - -R K 2009 o - Au 20 24 0 - -R K 2010 o - Au 10 24 0 - -R K 2010 o - S 9 24 1 S -R K 2010 o - S lastTh 24 0 - -R K 2014 o - May 15 24 1 S -R K 2014 o - Jun 26 24 0 - -R K 2014 o - Jul 31 24 1 S -R K 2014 o - S lastTh 24 0 - -Z Africa/Cairo 2:5:9 - LMT 1900 O -2 K EE%sT -R GH 1920 1942 - S 1 0 0:20 - -R GH 1920 1942 - D 31 0 0 - -Z Africa/Accra -0:0:52 - LMT 1918 -0 GH GMT/+0020 -Z Africa/Bissau -1:2:20 - LMT 1912 Ja 1 1u --1 - -01 1975 -0 - GMT -Z Africa/Nairobi 2:27:16 - LMT 1928 Jul -3 - EAT 1930 -2:30 - +0230 1940 -2:45 - +0245 1960 -3 - EAT -Li Africa/Nairobi Africa/Addis_Ababa -Li Africa/Nairobi Africa/Asmara -Li Africa/Nairobi Africa/Dar_es_Salaam -Li Africa/Nairobi Africa/Djibouti -Li Africa/Nairobi Africa/Kampala -Li Africa/Nairobi Africa/Mogadishu -Li Africa/Nairobi Indian/Antananarivo -Li Africa/Nairobi Indian/Comoro -Li Africa/Nairobi Indian/Mayotte -Z Africa/Monrovia -0:43:8 - LMT 1882 --0:43:8 - MMT 1919 Mar --0:44:30 - MMT 1972 Ja 7 -0 - GMT -R L 1951 o - O 14 2 1 S -R L 1952 o - Ja 1 0 0 - -R L 1953 o - O 9 2 1 S -R L 1954 o - Ja 1 0 0 - -R L 1955 o - S 30 0 1 S -R L 1956 o - Ja 1 0 0 - -R L 1982 1984 - Ap 1 0 1 S -R L 1982 1985 - O 1 0 0 - -R L 1985 o - Ap 6 0 1 S -R L 1986 o - Ap 4 0 1 S -R L 1986 o - O 3 0 0 - -R L 1987 1989 - Ap 1 0 1 S -R L 1987 1989 - O 1 0 0 - -R L 1997 o - Ap 4 0 1 S -R L 1997 o - O 4 0 0 - -R L 2013 o - Mar lastF 1 1 S -R L 2013 o - O lastF 2 0 - -Z Africa/Tripoli 0:52:44 - LMT 1920 -1 L CE%sT 1959 -2 - EET 1982 -1 L CE%sT 1990 May 4 -2 - EET 1996 S 30 -1 L CE%sT 1997 O 4 -2 - EET 2012 N 10 2 -1 L CE%sT 2013 O 25 2 -2 - EET -R MU 1982 o - O 10 0 1 - -R MU 1983 o - Mar 21 0 0 - -R MU 2008 o - O lastSun 2 1 - -R MU 2009 o - Mar lastSun 2 0 - -Z Indian/Mauritius 3:50 - LMT 1907 -4 MU +04/+05 -R M 1939 o - S 12 0 1 - -R M 1939 o - N 19 0 0 - -R M 1940 o - F 25 0 1 - -R M 1945 o - N 18 0 0 - -R M 1950 o - Jun 11 0 1 - -R M 1950 o - O 29 0 0 - -R M 1967 o - Jun 3 12 1 - -R M 1967 o - O 1 0 0 - -R M 1974 o - Jun 24 0 1 - -R M 1974 o - S 1 0 0 - -R M 1976 1977 - May 1 0 1 - -R M 1976 o - Au 1 0 0 - -R M 1977 o - S 28 0 0 - -R M 1978 o - Jun 1 0 1 - -R M 1978 o - Au 4 0 0 - -R M 2008 o - Jun 1 0 1 - -R M 2008 o - S 1 0 0 - -R M 2009 o - Jun 1 0 1 - -R M 2009 o - Au 21 0 0 - -R M 2010 o - May 2 0 1 - -R M 2010 o - Au 8 0 0 - -R M 2011 o - Ap 3 0 1 - -R M 2011 o - Jul 31 0 0 - -R M 2012 2013 - Ap lastSun 2 1 - -R M 2012 o - Jul 20 3 0 - -R M 2012 o - Au 20 2 1 - -R M 2012 o - S 30 3 0 - -R M 2013 o - Jul 7 3 0 - -R M 2013 o - Au 10 2 1 - -R M 2013 2018 - O lastSun 3 0 - -R M 2014 2018 - Mar lastSun 2 1 - -R M 2014 o - Jun 28 3 0 - -R M 2014 o - Au 2 2 1 - -R M 2015 o - Jun 14 3 0 - -R M 2015 o - Jul 19 2 1 - -R M 2016 o - Jun 5 3 0 - -R M 2016 o - Jul 10 2 1 - -R M 2017 o - May 21 3 0 - -R M 2017 o - Jul 2 2 1 - -R M 2018 o - May 13 3 0 - -R M 2018 o - Jun 17 2 1 - -Z Africa/Casablanca -0:30:20 - LMT 1913 O 26 -0 M +00/+01 1984 Mar 16 -1 - +01 1986 -0 M +00/+01 2018 O 27 -1 - +01 -Z Africa/El_Aaiun -0:52:48 - LMT 1934 --1 - -01 1976 Ap 14 -0 M +00/+01 2018 O 27 -1 - +01 -Z Africa/Maputo 2:10:20 - LMT 1903 Mar -2 - CAT -Li Africa/Maputo Africa/Blantyre -Li Africa/Maputo Africa/Bujumbura -Li Africa/Maputo Africa/Gaborone -Li Africa/Maputo Africa/Harare -Li Africa/Maputo Africa/Kigali -Li Africa/Maputo Africa/Lubumbashi -Li Africa/Maputo Africa/Lusaka -R NA 1994 o - Mar 21 0 -1 WAT -R NA 1994 2017 - S Sun>=1 2 0 CAT -R NA 1995 2017 - Ap Sun>=1 2 -1 WAT -Z Africa/Windhoek 1:8:24 - LMT 1892 F 8 -1:30 - +0130 1903 Mar -2 - SAST 1942 S 20 2 -2 1 SAST 1943 Mar 21 2 -2 - SAST 1990 Mar 21 -2 NA %s -Z Africa/Lagos 0:13:36 - LMT 1919 S -1 - WAT -Li Africa/Lagos Africa/Bangui -Li Africa/Lagos Africa/Brazzaville -Li Africa/Lagos Africa/Douala -Li Africa/Lagos Africa/Kinshasa -Li Africa/Lagos Africa/Libreville -Li Africa/Lagos Africa/Luanda -Li Africa/Lagos Africa/Malabo -Li Africa/Lagos Africa/Niamey -Li Africa/Lagos Africa/Porto-Novo -Z Indian/Reunion 3:41:52 - LMT 1911 Jun -4 - +04 -Z Africa/Sao_Tome 0:26:56 - LMT 1884 --0:36:45 - LMT 1912 Ja 1 0u -0 - GMT 2018 Ja 1 1 -1 - WAT -Z Indian/Mahe 3:41:48 - LMT 1906 Jun -4 - +04 -R SA 1942 1943 - S Sun>=15 2 1 - -R SA 1943 1944 - Mar Sun>=15 2 0 - -Z Africa/Johannesburg 1:52 - LMT 1892 F 8 -1:30 - SAST 1903 Mar -2 SA SAST -Li Africa/Johannesburg Africa/Maseru -Li Africa/Johannesburg Africa/Mbabane -R SD 1970 o - May 1 0 1 S -R SD 1970 1985 - O 15 0 0 - -R SD 1971 o - Ap 30 0 1 S -R SD 1972 1985 - Ap lastSun 0 1 S -Z Africa/Khartoum 2:10:8 - LMT 1931 -2 SD CA%sT 2000 Ja 15 12 -3 - EAT 2017 N -2 - CAT -Z Africa/Juba 2:6:28 - LMT 1931 -2 SD CA%sT 2000 Ja 15 12 -3 - EAT -R n 1939 o - Ap 15 23s 1 S -R n 1939 o - N 18 23s 0 - -R n 1940 o - F 25 23s 1 S -R n 1941 o - O 6 0 0 - -R n 1942 o - Mar 9 0 1 S -R n 1942 o - N 2 3 0 - -R n 1943 o - Mar 29 2 1 S -R n 1943 o - Ap 17 2 0 - -R n 1943 o - Ap 25 2 1 S -R n 1943 o - O 4 2 0 - -R n 1944 1945 - Ap M>=1 2 1 S -R n 1944 o - O 8 0 0 - -R n 1945 o - S 16 0 0 - -R n 1977 o - Ap 30 0s 1 S -R n 1977 o - S 24 0s 0 - -R n 1978 o - May 1 0s 1 S -R n 1978 o - O 1 0s 0 - -R n 1988 o - Jun 1 0s 1 S -R n 1988 1990 - S lastSun 0s 0 - -R n 1989 o - Mar 26 0s 1 S -R n 1990 o - May 1 0s 1 S -R n 2005 o - May 1 0s 1 S -R n 2005 o - S 30 1s 0 - -R n 2006 2008 - Mar lastSun 2s 1 S -R n 2006 2008 - O lastSun 2s 0 - -Z Africa/Tunis 0:40:44 - LMT 1881 May 12 -0:9:21 - PMT 1911 Mar 11 -1 n CE%sT -Z Antarctica/Casey 0 - -00 1969 -8 - +08 2009 O 18 2 -11 - +11 2010 Mar 5 2 -8 - +08 2011 O 28 2 -11 - +11 2012 F 21 17u -8 - +08 2016 O 22 -11 - +11 2018 Mar 11 4 -8 - +08 -Z Antarctica/Davis 0 - -00 1957 Ja 13 -7 - +07 1964 N -0 - -00 1969 F -7 - +07 2009 O 18 2 -5 - +05 2010 Mar 10 20u -7 - +07 2011 O 28 2 -5 - +05 2012 F 21 20u -7 - +07 -Z Antarctica/Mawson 0 - -00 1954 F 13 -6 - +06 2009 O 18 2 -5 - +05 -Z Indian/Kerguelen 0 - -00 1950 -5 - +05 -Z Antarctica/DumontDUrville 0 - -00 1947 -10 - +10 1952 Ja 14 -0 - -00 1956 N -10 - +10 -Z Antarctica/Syowa 0 - -00 1957 Ja 29 -3 - +03 -R Tr 2005 ma - Mar lastSun 1u 2 +02 -R Tr 2004 ma - O lastSun 1u 0 +00 -Z Antarctica/Troll 0 - -00 2005 F 12 -0 Tr %s -Z Antarctica/Vostok 0 - -00 1957 D 16 -6 - +06 -Z Antarctica/Rothera 0 - -00 1976 D --3 - -03 -Z Asia/Kabul 4:36:48 - LMT 1890 -4 - +04 1945 -4:30 - +0430 -R AM 2011 o - Mar lastSun 2s 1 - -R AM 2011 o - O lastSun 2s 0 - -Z Asia/Yerevan 2:58 - LMT 1924 May 2 -3 - +03 1957 Mar -4 R +04/+05 1991 Mar 31 2s -3 R +03/+04 1995 S 24 2s -4 - +04 1997 -4 R +04/+05 2011 -4 AM +04/+05 -R AZ 1997 2015 - Mar lastSun 4 1 - -R AZ 1997 2015 - O lastSun 5 0 - -Z Asia/Baku 3:19:24 - LMT 1924 May 2 -3 - +03 1957 Mar -4 R +04/+05 1991 Mar 31 2s -3 R +03/+04 1992 S lastSun 2s -4 - +04 1996 -4 E +04/+05 1997 -4 AZ +04/+05 -R BD 2009 o - Jun 19 23 1 - -R BD 2009 o - D 31 24 0 - -Z Asia/Dhaka 6:1:40 - LMT 1890 -5:53:20 - HMT 1941 O -6:30 - +0630 1942 May 15 -5:30 - +0530 1942 S -6:30 - +0630 1951 S 30 -6 - +06 2009 -6 BD +06/+07 -Z Asia/Thimphu 5:58:36 - LMT 1947 Au 15 -5:30 - +0530 1987 O -6 - +06 -Z Indian/Chagos 4:49:40 - LMT 1907 -5 - +05 1996 -6 - +06 -Z Asia/Brunei 7:39:40 - LMT 1926 Mar -7:30 - +0730 1933 -8 - +08 -Z Asia/Yangon 6:24:47 - LMT 1880 -6:24:47 - RMT 1920 -6:30 - +0630 1942 May -9 - +09 1945 May 3 -6:30 - +0630 -R Sh 1940 o - Jun 1 0 1 D -R Sh 1940 o - O 12 24 0 S -R Sh 1941 o - Mar 15 0 1 D -R Sh 1941 o - N 1 24 0 S -R Sh 1942 o - Ja 31 0 1 D -R Sh 1945 o - S 1 24 0 S -R Sh 1946 o - May 15 0 1 D -R Sh 1946 o - S 30 24 0 S -R Sh 1947 o - Ap 15 0 1 D -R Sh 1947 o - O 31 24 0 S -R Sh 1948 1949 - May 1 0 1 D -R Sh 1948 1949 - S 30 24 0 S -R CN 1986 o - May 4 2 1 D -R CN 1986 1991 - S Sun>=11 2 0 S -R CN 1987 1991 - Ap Sun>=11 2 1 D -Z Asia/Shanghai 8:5:43 - LMT 1901 -8 Sh C%sT 1949 May 28 -8 CN C%sT -Z Asia/Urumqi 5:50:20 - LMT 1928 -6 - +06 -R HK 1941 o - Ap 1 3:30 1 S -R HK 1941 o - S 30 3:30 0 - -R HK 1946 o - Ap 20 3:30 1 S -R HK 1946 o - D 1 3:30 0 - -R HK 1947 o - Ap 13 3:30 1 S -R HK 1947 o - D 30 3:30 0 - -R HK 1948 o - May 2 3:30 1 S -R HK 1948 1951 - O lastSun 3:30 0 - -R HK 1952 o - O 25 3:30 0 - -R HK 1949 1953 - Ap Sun>=1 3:30 1 S -R HK 1953 o - N 1 3:30 0 - -R HK 1954 1964 - Mar Sun>=18 3:30 1 S -R HK 1954 o - O 31 3:30 0 - -R HK 1955 1964 - N Sun>=1 3:30 0 - -R HK 1965 1976 - Ap Sun>=16 3:30 1 S -R HK 1965 1976 - O Sun>=16 3:30 0 - -R HK 1973 o - D 30 3:30 1 S -R HK 1979 o - May Sun>=8 3:30 1 S -R HK 1979 o - O Sun>=16 3:30 0 - -Z Asia/Hong_Kong 7:36:42 - LMT 1904 O 30 -8 HK HK%sT 1941 D 25 -9 - JST 1945 S 15 -8 HK HK%sT -R f 1946 o - May 15 0 1 D -R f 1946 o - O 1 0 0 S -R f 1947 o - Ap 15 0 1 D -R f 1947 o - N 1 0 0 S -R f 1948 1951 - May 1 0 1 D -R f 1948 1951 - O 1 0 0 S -R f 1952 o - Mar 1 0 1 D -R f 1952 1954 - N 1 0 0 S -R f 1953 1959 - Ap 1 0 1 D -R f 1955 1961 - O 1 0 0 S -R f 1960 1961 - Jun 1 0 1 D -R f 1974 1975 - Ap 1 0 1 D -R f 1974 1975 - O 1 0 0 S -R f 1979 o - Jul 1 0 1 D -R f 1979 o - O 1 0 0 S -Z Asia/Taipei 8:6 - LMT 1896 -8 - CST 1937 O -9 - JST 1945 S 21 1 -8 f C%sT -R _ 1942 1943 - Ap 30 23 1 - -R _ 1942 o - N 17 23 0 - -R _ 1943 o - S 30 23 0 S -R _ 1946 o - Ap 30 23s 1 D -R _ 1946 o - S 30 23s 0 S -R _ 1947 o - Ap 19 23s 1 D -R _ 1947 o - N 30 23s 0 S -R _ 1948 o - May 2 23s 1 D -R _ 1948 o - O 31 23s 0 S -R _ 1949 1950 - Ap Sat>=1 23s 1 D -R _ 1949 1950 - O lastSat 23s 0 S -R _ 1951 o - Mar 31 23s 1 D -R _ 1951 o - O 28 23s 0 S -R _ 1952 1953 - Ap Sat>=1 23s 1 D -R _ 1952 o - N 1 23s 0 S -R _ 1953 1954 - O lastSat 23s 0 S -R _ 1954 1956 - Mar Sat>=17 23s 1 D -R _ 1955 o - N 5 23s 0 S -R _ 1956 1964 - N Sun>=1 3:30 0 S -R _ 1957 1964 - Mar Sun>=18 3:30 1 D -R _ 1965 1973 - Ap Sun>=16 3:30 1 D -R _ 1965 1966 - O Sun>=16 2:30 0 S -R _ 1967 1976 - O Sun>=16 3:30 0 S -R _ 1973 o - D 30 3:30 1 D -R _ 1975 1976 - Ap Sun>=16 3:30 1 D -R _ 1979 o - May 13 3:30 1 D -R _ 1979 o - O Sun>=16 3:30 0 S -Z Asia/Macau 7:34:10 - LMT 1904 O 30 -8 - CST 1941 D 21 23 -9 _ +09/+10 1945 S 30 24 -8 _ C%sT -R CY 1975 o - Ap 13 0 1 S -R CY 1975 o - O 12 0 0 - -R CY 1976 o - May 15 0 1 S -R CY 1976 o - O 11 0 0 - -R CY 1977 1980 - Ap Sun>=1 0 1 S -R CY 1977 o - S 25 0 0 - -R CY 1978 o - O 2 0 0 - -R CY 1979 1997 - S lastSun 0 0 - -R CY 1981 1998 - Mar lastSun 0 1 S -Z Asia/Nicosia 2:13:28 - LMT 1921 N 14 -2 CY EE%sT 1998 S -2 E EE%sT -Z Asia/Famagusta 2:15:48 - LMT 1921 N 14 -2 CY EE%sT 1998 S -2 E EE%sT 2016 S 8 -3 - +03 2017 O 29 1u -2 E EE%sT -Li Asia/Nicosia Europe/Nicosia -Z Asia/Tbilisi 2:59:11 - LMT 1880 -2:59:11 - TBMT 1924 May 2 -3 - +03 1957 Mar -4 R +04/+05 1991 Mar 31 2s -3 R +03/+04 1992 -3 e +03/+04 1994 S lastSun -4 e +04/+05 1996 O lastSun -4 1 +05 1997 Mar lastSun -4 e +04/+05 2004 Jun 27 -3 R +03/+04 2005 Mar lastSun 2 -4 - +04 -Z Asia/Dili 8:22:20 - LMT 1912 -8 - +08 1942 F 21 23 -9 - +09 1976 May 3 -8 - +08 2000 S 17 -9 - +09 -Z Asia/Kolkata 5:53:28 - LMT 1854 Jun 28 -5:53:20 - HMT 1870 -5:21:10 - MMT 1906 -5:30 - IST 1941 O -5:30 1 +0630 1942 May 15 -5:30 - IST 1942 S -5:30 1 +0630 1945 O 15 -5:30 - IST -Z Asia/Jakarta 7:7:12 - LMT 1867 Au 10 -7:7:12 - BMT 1923 D 31 23:47:12 -7:20 - +0720 1932 N -7:30 - +0730 1942 Mar 23 -9 - +09 1945 S 23 -7:30 - +0730 1948 May -8 - +08 1950 May -7:30 - +0730 1964 -7 - WIB -Z Asia/Pontianak 7:17:20 - LMT 1908 May -7:17:20 - PMT 1932 N -7:30 - +0730 1942 Ja 29 -9 - +09 1945 S 23 -7:30 - +0730 1948 May -8 - +08 1950 May -7:30 - +0730 1964 -8 - WITA 1988 -7 - WIB -Z Asia/Makassar 7:57:36 - LMT 1920 -7:57:36 - MMT 1932 N -8 - +08 1942 F 9 -9 - +09 1945 S 23 -8 - WITA -Z Asia/Jayapura 9:22:48 - LMT 1932 N -9 - +09 1944 S -9:30 - +0930 1964 -9 - WIT -R i 1978 1980 - Mar 21 0 1 - -R i 1978 o - O 21 0 0 - -R i 1979 o - S 19 0 0 - -R i 1980 o - S 23 0 0 - -R i 1991 o - May 3 0 1 - -R i 1992 1995 - Mar 22 0 1 - -R i 1991 1995 - S 22 0 0 - -R i 1996 o - Mar 21 0 1 - -R i 1996 o - S 21 0 0 - -R i 1997 1999 - Mar 22 0 1 - -R i 1997 1999 - S 22 0 0 - -R i 2000 o - Mar 21 0 1 - -R i 2000 o - S 21 0 0 - -R i 2001 2003 - Mar 22 0 1 - -R i 2001 2003 - S 22 0 0 - -R i 2004 o - Mar 21 0 1 - -R i 2004 o - S 21 0 0 - -R i 2005 o - Mar 22 0 1 - -R i 2005 o - S 22 0 0 - -R i 2008 o - Mar 21 0 1 - -R i 2008 o - S 21 0 0 - -R i 2009 2011 - Mar 22 0 1 - -R i 2009 2011 - S 22 0 0 - -R i 2012 o - Mar 21 0 1 - -R i 2012 o - S 21 0 0 - -R i 2013 2015 - Mar 22 0 1 - -R i 2013 2015 - S 22 0 0 - -R i 2016 o - Mar 21 0 1 - -R i 2016 o - S 21 0 0 - -R i 2017 2019 - Mar 22 0 1 - -R i 2017 2019 - S 22 0 0 - -R i 2020 o - Mar 21 0 1 - -R i 2020 o - S 21 0 0 - -R i 2021 2023 - Mar 22 0 1 - -R i 2021 2023 - S 22 0 0 - -R i 2024 o - Mar 21 0 1 - -R i 2024 o - S 21 0 0 - -R i 2025 2027 - Mar 22 0 1 - -R i 2025 2027 - S 22 0 0 - -R i 2028 2029 - Mar 21 0 1 - -R i 2028 2029 - S 21 0 0 - -R i 2030 2031 - Mar 22 0 1 - -R i 2030 2031 - S 22 0 0 - -R i 2032 2033 - Mar 21 0 1 - -R i 2032 2033 - S 21 0 0 - -R i 2034 2035 - Mar 22 0 1 - -R i 2034 2035 - S 22 0 0 - -R i 2036 ma - Mar 21 0 1 - -R i 2036 ma - S 21 0 0 - -Z Asia/Tehran 3:25:44 - LMT 1916 -3:25:44 - TMT 1946 -3:30 - +0330 1977 N -4 i +04/+05 1979 -3:30 i +0330/+0430 -R IQ 1982 o - May 1 0 1 - -R IQ 1982 1984 - O 1 0 0 - -R IQ 1983 o - Mar 31 0 1 - -R IQ 1984 1985 - Ap 1 0 1 - -R IQ 1985 1990 - S lastSun 1s 0 - -R IQ 1986 1990 - Mar lastSun 1s 1 - -R IQ 1991 2007 - Ap 1 3s 1 - -R IQ 1991 2007 - O 1 3s 0 - -Z Asia/Baghdad 2:57:40 - LMT 1890 -2:57:36 - BMT 1918 -3 - +03 1982 May -3 IQ +03/+04 -R Z 1940 o - Jun 1 0 1 D -R Z 1942 1944 - N 1 0 0 S -R Z 1943 o - Ap 1 2 1 D -R Z 1944 o - Ap 1 0 1 D -R Z 1945 o - Ap 16 0 1 D -R Z 1945 o - N 1 2 0 S -R Z 1946 o - Ap 16 2 1 D -R Z 1946 o - N 1 0 0 S -R Z 1948 o - May 23 0 2 DD -R Z 1948 o - S 1 0 1 D -R Z 1948 1949 - N 1 2 0 S -R Z 1949 o - May 1 0 1 D -R Z 1950 o - Ap 16 0 1 D -R Z 1950 o - S 15 3 0 S -R Z 1951 o - Ap 1 0 1 D -R Z 1951 o - N 11 3 0 S -R Z 1952 o - Ap 20 2 1 D -R Z 1952 o - O 19 3 0 S -R Z 1953 o - Ap 12 2 1 D -R Z 1953 o - S 13 3 0 S -R Z 1954 o - Jun 13 0 1 D -R Z 1954 o - S 12 0 0 S -R Z 1955 o - Jun 11 2 1 D -R Z 1955 o - S 11 0 0 S -R Z 1956 o - Jun 3 0 1 D -R Z 1956 o - S 30 3 0 S -R Z 1957 o - Ap 29 2 1 D -R Z 1957 o - S 22 0 0 S -R Z 1974 o - Jul 7 0 1 D -R Z 1974 o - O 13 0 0 S -R Z 1975 o - Ap 20 0 1 D -R Z 1975 o - Au 31 0 0 S -R Z 1985 o - Ap 14 0 1 D -R Z 1985 o - S 15 0 0 S -R Z 1986 o - May 18 0 1 D -R Z 1986 o - S 7 0 0 S -R Z 1987 o - Ap 15 0 1 D -R Z 1987 o - S 13 0 0 S -R Z 1988 o - Ap 10 0 1 D -R Z 1988 o - S 4 0 0 S -R Z 1989 o - Ap 30 0 1 D -R Z 1989 o - S 3 0 0 S -R Z 1990 o - Mar 25 0 1 D -R Z 1990 o - Au 26 0 0 S -R Z 1991 o - Mar 24 0 1 D -R Z 1991 o - S 1 0 0 S -R Z 1992 o - Mar 29 0 1 D -R Z 1992 o - S 6 0 0 S -R Z 1993 o - Ap 2 0 1 D -R Z 1993 o - S 5 0 0 S -R Z 1994 o - Ap 1 0 1 D -R Z 1994 o - Au 28 0 0 S -R Z 1995 o - Mar 31 0 1 D -R Z 1995 o - S 3 0 0 S -R Z 1996 o - Mar 15 0 1 D -R Z 1996 o - S 16 0 0 S -R Z 1997 o - Mar 21 0 1 D -R Z 1997 o - S 14 0 0 S -R Z 1998 o - Mar 20 0 1 D -R Z 1998 o - S 6 0 0 S -R Z 1999 o - Ap 2 2 1 D -R Z 1999 o - S 3 2 0 S -R Z 2000 o - Ap 14 2 1 D -R Z 2000 o - O 6 1 0 S -R Z 2001 o - Ap 9 1 1 D -R Z 2001 o - S 24 1 0 S -R Z 2002 o - Mar 29 1 1 D -R Z 2002 o - O 7 1 0 S -R Z 2003 o - Mar 28 1 1 D -R Z 2003 o - O 3 1 0 S -R Z 2004 o - Ap 7 1 1 D -R Z 2004 o - S 22 1 0 S -R Z 2005 o - Ap 1 2 1 D -R Z 2005 o - O 9 2 0 S -R Z 2006 2010 - Mar F>=26 2 1 D -R Z 2006 o - O 1 2 0 S -R Z 2007 o - S 16 2 0 S -R Z 2008 o - O 5 2 0 S -R Z 2009 o - S 27 2 0 S -R Z 2010 o - S 12 2 0 S -R Z 2011 o - Ap 1 2 1 D -R Z 2011 o - O 2 2 0 S -R Z 2012 o - Mar F>=26 2 1 D -R Z 2012 o - S 23 2 0 S -R Z 2013 ma - Mar F>=23 2 1 D -R Z 2013 ma - O lastSun 2 0 S -Z Asia/Jerusalem 2:20:54 - LMT 1880 -2:20:40 - JMT 1918 -2 Z I%sT -R JP 1948 o - May Sat>=1 24 1 D -R JP 1948 1951 - S Sat>=8 25 0 S -R JP 1949 o - Ap Sat>=1 24 1 D -R JP 1950 1951 - May Sat>=1 24 1 D -Z Asia/Tokyo 9:18:59 - LMT 1887 D 31 15u -9 JP J%sT -R J 1973 o - Jun 6 0 1 S -R J 1973 1975 - O 1 0 0 - -R J 1974 1977 - May 1 0 1 S -R J 1976 o - N 1 0 0 - -R J 1977 o - O 1 0 0 - -R J 1978 o - Ap 30 0 1 S -R J 1978 o - S 30 0 0 - -R J 1985 o - Ap 1 0 1 S -R J 1985 o - O 1 0 0 - -R J 1986 1988 - Ap F>=1 0 1 S -R J 1986 1990 - O F>=1 0 0 - -R J 1989 o - May 8 0 1 S -R J 1990 o - Ap 27 0 1 S -R J 1991 o - Ap 17 0 1 S -R J 1991 o - S 27 0 0 - -R J 1992 o - Ap 10 0 1 S -R J 1992 1993 - O F>=1 0 0 - -R J 1993 1998 - Ap F>=1 0 1 S -R J 1994 o - S F>=15 0 0 - -R J 1995 1998 - S F>=15 0s 0 - -R J 1999 o - Jul 1 0s 1 S -R J 1999 2002 - S lastF 0s 0 - -R J 2000 2001 - Mar lastTh 0s 1 S -R J 2002 2012 - Mar lastTh 24 1 S -R J 2003 o - O 24 0s 0 - -R J 2004 o - O 15 0s 0 - -R J 2005 o - S lastF 0s 0 - -R J 2006 2011 - O lastF 0s 0 - -R J 2013 o - D 20 0 0 - -R J 2014 ma - Mar lastTh 24 1 S -R J 2014 ma - O lastF 0s 0 - -Z Asia/Amman 2:23:44 - LMT 1931 -2 J EE%sT -Z Asia/Almaty 5:7:48 - LMT 1924 May 2 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2s -5 R +05/+06 1992 Ja 19 2s -6 R +06/+07 2004 O 31 2s -6 - +06 -Z Asia/Qyzylorda 4:21:52 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 - +05 1981 Ap -5 1 +06 1981 O -6 - +06 1982 Ap -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1991 S 29 2s -5 R +05/+06 1992 Ja 19 2s -6 R +06/+07 1992 Mar 29 2s -5 R +05/+06 2004 O 31 2s -6 - +06 -Z Asia/Aqtobe 3:48:40 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 - +05 1981 Ap -5 1 +06 1981 O -6 - +06 1982 Ap -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 2004 O 31 2s -5 - +05 -Z Asia/Aqtau 3:21:4 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 - +05 1981 O -6 - +06 1982 Ap -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 1994 S 25 2s -4 R +04/+05 2004 O 31 2s -5 - +05 -Z Asia/Atyrau 3:27:44 - LMT 1924 May 2 -3 - +03 1930 Jun 21 -5 - +05 1981 O -6 - +06 1982 Ap -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 1999 Mar 28 2s -4 R +04/+05 2004 O 31 2s -5 - +05 -Z Asia/Oral 3:25:24 - LMT 1924 May 2 -3 - +03 1930 Jun 21 -5 - +05 1981 Ap -5 1 +06 1981 O -6 - +06 1982 Ap -5 R +05/+06 1989 Mar 26 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 1992 Mar 29 2s -4 R +04/+05 2004 O 31 2s -5 - +05 -R KG 1992 1996 - Ap Sun>=7 0s 1 - -R KG 1992 1996 - S lastSun 0 0 - -R KG 1997 2005 - Mar lastSun 2:30 1 - -R KG 1997 2004 - O lastSun 2:30 0 - -Z Asia/Bishkek 4:58:24 - LMT 1924 May 2 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2s -5 R +05/+06 1991 Au 31 2 -5 KG +05/+06 2005 Au 12 -6 - +06 -R KR 1948 o - Jun 1 0 1 D -R KR 1948 o - S 13 0 0 S -R KR 1949 o - Ap 3 0 1 D -R KR 1949 1951 - S Sun>=8 0 0 S -R KR 1950 o - Ap 1 0 1 D -R KR 1951 o - May 6 0 1 D -R KR 1955 o - May 5 0 1 D -R KR 1955 o - S 9 0 0 S -R KR 1956 o - May 20 0 1 D -R KR 1956 o - S 30 0 0 S -R KR 1957 1960 - May Sun>=1 0 1 D -R KR 1957 1960 - S Sun>=18 0 0 S -R KR 1987 1988 - May Sun>=8 2 1 D -R KR 1987 1988 - O Sun>=8 3 0 S -Z Asia/Seoul 8:27:52 - LMT 1908 Ap -8:30 - KST 1912 -9 - JST 1945 S 8 -9 - KST 1954 Mar 21 -8:30 KR K%sT 1961 Au 10 -9 KR K%sT -Z Asia/Pyongyang 8:23 - LMT 1908 Ap -8:30 - KST 1912 -9 - JST 1945 Au 24 -9 - KST 2015 Au 15 -8:30 - KST 2018 May 4 23:30 -9 - KST -R l 1920 o - Mar 28 0 1 S -R l 1920 o - O 25 0 0 - -R l 1921 o - Ap 3 0 1 S -R l 1921 o - O 3 0 0 - -R l 1922 o - Mar 26 0 1 S -R l 1922 o - O 8 0 0 - -R l 1923 o - Ap 22 0 1 S -R l 1923 o - S 16 0 0 - -R l 1957 1961 - May 1 0 1 S -R l 1957 1961 - O 1 0 0 - -R l 1972 o - Jun 22 0 1 S -R l 1972 1977 - O 1 0 0 - -R l 1973 1977 - May 1 0 1 S -R l 1978 o - Ap 30 0 1 S -R l 1978 o - S 30 0 0 - -R l 1984 1987 - May 1 0 1 S -R l 1984 1991 - O 16 0 0 - -R l 1988 o - Jun 1 0 1 S -R l 1989 o - May 10 0 1 S -R l 1990 1992 - May 1 0 1 S -R l 1992 o - O 4 0 0 - -R l 1993 ma - Mar lastSun 0 1 S -R l 1993 1998 - S lastSun 0 0 - -R l 1999 ma - O lastSun 0 0 - -Z Asia/Beirut 2:22 - LMT 1880 -2 l EE%sT -R NB 1935 1941 - S 14 0 0:20 - -R NB 1935 1941 - D 14 0 0 - -Z Asia/Kuala_Lumpur 6:46:46 - LMT 1901 -6:55:25 - SMT 1905 Jun -7 - +07 1933 -7 0:20 +0720 1936 -7:20 - +0720 1941 S -7:30 - +0730 1942 F 16 -9 - +09 1945 S 12 -7:30 - +0730 1982 -8 - +08 -Z Asia/Kuching 7:21:20 - LMT 1926 Mar -7:30 - +0730 1933 -8 NB +08/+0820 1942 F 16 -9 - +09 1945 S 12 -8 - +08 -Z Indian/Maldives 4:54 - LMT 1880 -4:54 - MMT 1960 -5 - +05 -R X 1983 1984 - Ap 1 0 1 - -R X 1983 o - O 1 0 0 - -R X 1985 1998 - Mar lastSun 0 1 - -R X 1984 1998 - S lastSun 0 0 - -R X 2001 o - Ap lastSat 2 1 - -R X 2001 2006 - S lastSat 2 0 - -R X 2002 2006 - Mar lastSat 2 1 - -R X 2015 2016 - Mar lastSat 2 1 - -R X 2015 2016 - S lastSat 0 0 - -Z Asia/Hovd 6:6:36 - LMT 1905 Au -6 - +06 1978 -7 X +07/+08 -Z Asia/Ulaanbaatar 7:7:32 - LMT 1905 Au -7 - +07 1978 -8 X +08/+09 -Z Asia/Choibalsan 7:38 - LMT 1905 Au -7 - +07 1978 -8 - +08 1983 Ap -9 X +09/+10 2008 Mar 31 -8 X +08/+09 -Z Asia/Kathmandu 5:41:16 - LMT 1920 -5:30 - +0530 1986 -5:45 - +0545 -R PK 2002 o - Ap Sun>=2 0 1 S -R PK 2002 o - O Sun>=2 0 0 - -R PK 2008 o - Jun 1 0 1 S -R PK 2008 2009 - N 1 0 0 - -R PK 2009 o - Ap 15 0 1 S -Z Asia/Karachi 4:28:12 - LMT 1907 -5:30 - +0530 1942 S -5:30 1 +0630 1945 O 15 -5:30 - +0530 1951 S 30 -5 - +05 1971 Mar 26 -5 PK PK%sT -R P 1999 2005 - Ap F>=15 0 1 S -R P 1999 2003 - O F>=15 0 0 - -R P 2004 o - O 1 1 0 - -R P 2005 o - O 4 2 0 - -R P 2006 2007 - Ap 1 0 1 S -R P 2006 o - S 22 0 0 - -R P 2007 o - S Th>=8 2 0 - -R P 2008 2009 - Mar lastF 0 1 S -R P 2008 o - S 1 0 0 - -R P 2009 o - S F>=1 1 0 - -R P 2010 o - Mar 26 0 1 S -R P 2010 o - Au 11 0 0 - -R P 2011 o - Ap 1 0:1 1 S -R P 2011 o - Au 1 0 0 - -R P 2011 o - Au 30 0 1 S -R P 2011 o - S 30 0 0 - -R P 2012 2014 - Mar lastTh 24 1 S -R P 2012 o - S 21 1 0 - -R P 2013 o - S F>=21 0 0 - -R P 2014 2015 - O F>=21 0 0 - -R P 2015 o - Mar lastF 24 1 S -R P 2016 ma - Mar Sat>=22 1 1 S -R P 2016 ma - O lastSat 1 0 - -Z Asia/Gaza 2:17:52 - LMT 1900 O -2 Z EET/EEST 1948 May 15 -2 K EE%sT 1967 Jun 5 -2 Z I%sT 1996 -2 J EE%sT 1999 -2 P EE%sT 2008 Au 29 -2 - EET 2008 S -2 P EE%sT 2010 -2 - EET 2010 Mar 27 0:1 -2 P EE%sT 2011 Au -2 - EET 2012 -2 P EE%sT -Z Asia/Hebron 2:20:23 - LMT 1900 O -2 Z EET/EEST 1948 May 15 -2 K EE%sT 1967 Jun 5 -2 Z I%sT 1996 -2 J EE%sT 1999 -2 P EE%sT -R PH 1936 o - N 1 0 1 D -R PH 1937 o - F 1 0 0 S -R PH 1954 o - Ap 12 0 1 D -R PH 1954 o - Jul 1 0 0 S -R PH 1978 o - Mar 22 0 1 D -R PH 1978 o - S 21 0 0 S -Z Asia/Manila -15:56 - LMT 1844 D 31 -8:4 - LMT 1899 May 11 -8 PH P%sT 1942 May -9 - JST 1944 N -8 PH P%sT -Z Asia/Qatar 3:26:8 - LMT 1920 -4 - +04 1972 Jun -3 - +03 -Li Asia/Qatar Asia/Bahrain -Z Asia/Riyadh 3:6:52 - LMT 1947 Mar 14 -3 - +03 -Li Asia/Riyadh Asia/Aden -Li Asia/Riyadh Asia/Kuwait -Z Asia/Singapore 6:55:25 - LMT 1901 -6:55:25 - SMT 1905 Jun -7 - +07 1933 -7 0:20 +0720 1936 -7:20 - +0720 1941 S -7:30 - +0730 1942 F 16 -9 - +09 1945 S 12 -7:30 - +0730 1982 -8 - +08 -Z Asia/Colombo 5:19:24 - LMT 1880 -5:19:32 - MMT 1906 -5:30 - +0530 1942 Ja 5 -5:30 0:30 +06 1942 S -5:30 1 +0630 1945 O 16 2 -5:30 - +0530 1996 May 25 -6:30 - +0630 1996 O 26 0:30 -6 - +06 2006 Ap 15 0:30 -5:30 - +0530 -R S 1920 1923 - Ap Sun>=15 2 1 S -R S 1920 1923 - O Sun>=1 2 0 - -R S 1962 o - Ap 29 2 1 S -R S 1962 o - O 1 2 0 - -R S 1963 1965 - May 1 2 1 S -R S 1963 o - S 30 2 0 - -R S 1964 o - O 1 2 0 - -R S 1965 o - S 30 2 0 - -R S 1966 o - Ap 24 2 1 S -R S 1966 1976 - O 1 2 0 - -R S 1967 1978 - May 1 2 1 S -R S 1977 1978 - S 1 2 0 - -R S 1983 1984 - Ap 9 2 1 S -R S 1983 1984 - O 1 2 0 - -R S 1986 o - F 16 2 1 S -R S 1986 o - O 9 2 0 - -R S 1987 o - Mar 1 2 1 S -R S 1987 1988 - O 31 2 0 - -R S 1988 o - Mar 15 2 1 S -R S 1989 o - Mar 31 2 1 S -R S 1989 o - O 1 2 0 - -R S 1990 o - Ap 1 2 1 S -R S 1990 o - S 30 2 0 - -R S 1991 o - Ap 1 0 1 S -R S 1991 1992 - O 1 0 0 - -R S 1992 o - Ap 8 0 1 S -R S 1993 o - Mar 26 0 1 S -R S 1993 o - S 25 0 0 - -R S 1994 1996 - Ap 1 0 1 S -R S 1994 2005 - O 1 0 0 - -R S 1997 1998 - Mar lastM 0 1 S -R S 1999 2006 - Ap 1 0 1 S -R S 2006 o - S 22 0 0 - -R S 2007 o - Mar lastF 0 1 S -R S 2007 o - N F>=1 0 0 - -R S 2008 o - Ap F>=1 0 1 S -R S 2008 o - N 1 0 0 - -R S 2009 o - Mar lastF 0 1 S -R S 2010 2011 - Ap F>=1 0 1 S -R S 2012 ma - Mar lastF 0 1 S -R S 2009 ma - O lastF 0 0 - -Z Asia/Damascus 2:25:12 - LMT 1920 -2 S EE%sT -Z Asia/Dushanbe 4:35:12 - LMT 1924 May 2 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2s -5 1 +05/+06 1991 S 9 2s -5 - +05 -Z Asia/Bangkok 6:42:4 - LMT 1880 -6:42:4 - BMT 1920 Ap -7 - +07 -Li Asia/Bangkok Asia/Phnom_Penh -Li Asia/Bangkok Asia/Vientiane -Z Asia/Ashgabat 3:53:32 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 R +05/+06 1991 Mar 31 2 -4 R +04/+05 1992 Ja 19 2 -5 - +05 -Z Asia/Dubai 3:41:12 - LMT 1920 -4 - +04 -Li Asia/Dubai Asia/Muscat -Z Asia/Samarkand 4:27:53 - LMT 1924 May 2 -4 - +04 1930 Jun 21 -5 - +05 1981 Ap -5 1 +06 1981 O -6 - +06 1982 Ap -5 R +05/+06 1992 -5 - +05 -Z Asia/Tashkent 4:37:11 - LMT 1924 May 2 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2 -5 R +05/+06 1992 -5 - +05 -Z Asia/Ho_Chi_Minh 7:6:40 - LMT 1906 Jul -7:6:30 - PLMT 1911 May -7 - +07 1942 D 31 23 -8 - +08 1945 Mar 14 23 -9 - +09 1945 S 2 -7 - +07 1947 Ap -8 - +08 1955 Jul -7 - +07 1959 D 31 23 -8 - +08 1975 Jun 13 -7 - +07 -R AU 1917 o - Ja 1 0:1 1 D -R AU 1917 o - Mar 25 2 0 S -R AU 1942 o - Ja 1 2 1 D -R AU 1942 o - Mar 29 2 0 S -R AU 1942 o - S 27 2 1 D -R AU 1943 1944 - Mar lastSun 2 0 S -R AU 1943 o - O 3 2 1 D -Z Australia/Darwin 8:43:20 - LMT 1895 F -9 - ACST 1899 May -9:30 AU AC%sT -R AW 1974 o - O lastSun 2s 1 D -R AW 1975 o - Mar Sun>=1 2s 0 S -R AW 1983 o - O lastSun 2s 1 D -R AW 1984 o - Mar Sun>=1 2s 0 S -R AW 1991 o - N 17 2s 1 D -R AW 1992 o - Mar Sun>=1 2s 0 S -R AW 2006 o - D 3 2s 1 D -R AW 2007 2009 - Mar lastSun 2s 0 S -R AW 2007 2008 - O lastSun 2s 1 D -Z Australia/Perth 7:43:24 - LMT 1895 D -8 AU AW%sT 1943 Jul -8 AW AW%sT -Z Australia/Eucla 8:35:28 - LMT 1895 D -8:45 AU +0845/+0945 1943 Jul -8:45 AW +0845/+0945 -R AQ 1971 o - O lastSun 2s 1 D -R AQ 1972 o - F lastSun 2s 0 S -R AQ 1989 1991 - O lastSun 2s 1 D -R AQ 1990 1992 - Mar Sun>=1 2s 0 S -R Ho 1992 1993 - O lastSun 2s 1 D -R Ho 1993 1994 - Mar Sun>=1 2s 0 S -Z Australia/Brisbane 10:12:8 - LMT 1895 -10 AU AE%sT 1971 -10 AQ AE%sT -Z Australia/Lindeman 9:55:56 - LMT 1895 -10 AU AE%sT 1971 -10 AQ AE%sT 1992 Jul -10 Ho AE%sT -R AS 1971 1985 - O lastSun 2s 1 D -R AS 1986 o - O 19 2s 1 D -R AS 1987 2007 - O lastSun 2s 1 D -R AS 1972 o - F 27 2s 0 S -R AS 1973 1985 - Mar Sun>=1 2s 0 S -R AS 1986 1990 - Mar Sun>=15 2s 0 S -R AS 1991 o - Mar 3 2s 0 S -R AS 1992 o - Mar 22 2s 0 S -R AS 1993 o - Mar 7 2s 0 S -R AS 1994 o - Mar 20 2s 0 S -R AS 1995 2005 - Mar lastSun 2s 0 S -R AS 2006 o - Ap 2 2s 0 S -R AS 2007 o - Mar lastSun 2s 0 S -R AS 2008 ma - Ap Sun>=1 2s 0 S -R AS 2008 ma - O Sun>=1 2s 1 D -Z Australia/Adelaide 9:14:20 - LMT 1895 F -9 - ACST 1899 May -9:30 AU AC%sT 1971 -9:30 AS AC%sT -R AT 1967 o - O Sun>=1 2s 1 D -R AT 1968 o - Mar lastSun 2s 0 S -R AT 1968 1985 - O lastSun 2s 1 D -R AT 1969 1971 - Mar Sun>=8 2s 0 S -R AT 1972 o - F lastSun 2s 0 S -R AT 1973 1981 - Mar Sun>=1 2s 0 S -R AT 1982 1983 - Mar lastSun 2s 0 S -R AT 1984 1986 - Mar Sun>=1 2s 0 S -R AT 1986 o - O Sun>=15 2s 1 D -R AT 1987 1990 - Mar Sun>=15 2s 0 S -R AT 1987 o - O Sun>=22 2s 1 D -R AT 1988 1990 - O lastSun 2s 1 D -R AT 1991 1999 - O Sun>=1 2s 1 D -R AT 1991 2005 - Mar lastSun 2s 0 S -R AT 2000 o - Au lastSun 2s 1 D -R AT 2001 ma - O Sun>=1 2s 1 D -R AT 2006 o - Ap Sun>=1 2s 0 S -R AT 2007 o - Mar lastSun 2s 0 S -R AT 2008 ma - Ap Sun>=1 2s 0 S -Z Australia/Hobart 9:49:16 - LMT 1895 S -10 - AEST 1916 O 1 2 -10 1 AEDT 1917 F -10 AU AE%sT 1967 -10 AT AE%sT -Z Australia/Currie 9:35:28 - LMT 1895 S -10 - AEST 1916 O 1 2 -10 1 AEDT 1917 F -10 AU AE%sT 1971 Jul -10 AT AE%sT -R AV 1971 1985 - O lastSun 2s 1 D -R AV 1972 o - F lastSun 2s 0 S -R AV 1973 1985 - Mar Sun>=1 2s 0 S -R AV 1986 1990 - Mar Sun>=15 2s 0 S -R AV 1986 1987 - O Sun>=15 2s 1 D -R AV 1988 1999 - O lastSun 2s 1 D -R AV 1991 1994 - Mar Sun>=1 2s 0 S -R AV 1995 2005 - Mar lastSun 2s 0 S -R AV 2000 o - Au lastSun 2s 1 D -R AV 2001 2007 - O lastSun 2s 1 D -R AV 2006 o - Ap Sun>=1 2s 0 S -R AV 2007 o - Mar lastSun 2s 0 S -R AV 2008 ma - Ap Sun>=1 2s 0 S -R AV 2008 ma - O Sun>=1 2s 1 D -Z Australia/Melbourne 9:39:52 - LMT 1895 F -10 AU AE%sT 1971 -10 AV AE%sT -R AN 1971 1985 - O lastSun 2s 1 D -R AN 1972 o - F 27 2s 0 S -R AN 1973 1981 - Mar Sun>=1 2s 0 S -R AN 1982 o - Ap Sun>=1 2s 0 S -R AN 1983 1985 - Mar Sun>=1 2s 0 S -R AN 1986 1989 - Mar Sun>=15 2s 0 S -R AN 1986 o - O 19 2s 1 D -R AN 1987 1999 - O lastSun 2s 1 D -R AN 1990 1995 - Mar Sun>=1 2s 0 S -R AN 1996 2005 - Mar lastSun 2s 0 S -R AN 2000 o - Au lastSun 2s 1 D -R AN 2001 2007 - O lastSun 2s 1 D -R AN 2006 o - Ap Sun>=1 2s 0 S -R AN 2007 o - Mar lastSun 2s 0 S -R AN 2008 ma - Ap Sun>=1 2s 0 S -R AN 2008 ma - O Sun>=1 2s 1 D -Z Australia/Sydney 10:4:52 - LMT 1895 F -10 AU AE%sT 1971 -10 AN AE%sT -Z Australia/Broken_Hill 9:25:48 - LMT 1895 F -10 - AEST 1896 Au 23 -9 - ACST 1899 May -9:30 AU AC%sT 1971 -9:30 AN AC%sT 2000 -9:30 AS AC%sT -R LH 1981 1984 - O lastSun 2 1 - -R LH 1982 1985 - Mar Sun>=1 2 0 - -R LH 1985 o - O lastSun 2 0:30 - -R LH 1986 1989 - Mar Sun>=15 2 0 - -R LH 1986 o - O 19 2 0:30 - -R LH 1987 1999 - O lastSun 2 0:30 - -R LH 1990 1995 - Mar Sun>=1 2 0 - -R LH 1996 2005 - Mar lastSun 2 0 - -R LH 2000 o - Au lastSun 2 0:30 - -R LH 2001 2007 - O lastSun 2 0:30 - -R LH 2006 o - Ap Sun>=1 2 0 - -R LH 2007 o - Mar lastSun 2 0 - -R LH 2008 ma - Ap Sun>=1 2 0 - -R LH 2008 ma - O Sun>=1 2 0:30 - -Z Australia/Lord_Howe 10:36:20 - LMT 1895 F -10 - AEST 1981 Mar -10:30 LH +1030/+1130 1985 Jul -10:30 LH +1030/+11 -Z Antarctica/Macquarie 0 - -00 1899 N -10 - AEST 1916 O 1 2 -10 1 AEDT 1917 F -10 AU AE%sT 1919 Ap 1 0s -0 - -00 1948 Mar 25 -10 AU AE%sT 1967 -10 AT AE%sT 2010 Ap 4 3 -11 - +11 -Z Indian/Christmas 7:2:52 - LMT 1895 F -7 - +07 -Z Indian/Cocos 6:27:40 - LMT 1900 -6:30 - +0630 -R FJ 1998 1999 - N Sun>=1 2 1 - -R FJ 1999 2000 - F lastSun 3 0 - -R FJ 2009 o - N 29 2 1 - -R FJ 2010 o - Mar lastSun 3 0 - -R FJ 2010 2013 - O Sun>=21 2 1 - -R FJ 2011 o - Mar Sun>=1 3 0 - -R FJ 2012 2013 - Ja Sun>=18 3 0 - -R FJ 2014 o - Ja Sun>=18 2 0 - -R FJ 2014 ma - N Sun>=1 2 1 - -R FJ 2015 ma - Ja Sun>=13 3 0 - -Z Pacific/Fiji 11:55:44 - LMT 1915 O 26 -12 FJ +12/+13 -Z Pacific/Gambier -8:59:48 - LMT 1912 O --9 - -09 -Z Pacific/Marquesas -9:18 - LMT 1912 O --9:30 - -0930 -Z Pacific/Tahiti -9:58:16 - LMT 1912 O --10 - -10 -Z Pacific/Guam -14:21 - LMT 1844 D 31 -9:39 - LMT 1901 -10 - GST 2000 D 23 -10 - ChST -Li Pacific/Guam Pacific/Saipan -Z Pacific/Tarawa 11:32:4 - LMT 1901 -12 - +12 -Z Pacific/Enderbury -11:24:20 - LMT 1901 --12 - -12 1979 O --11 - -11 1994 D 31 -13 - +13 -Z Pacific/Kiritimati -10:29:20 - LMT 1901 --10:40 - -1040 1979 O --10 - -10 1994 D 31 -14 - +14 -Z Pacific/Majuro 11:24:48 - LMT 1901 -11 - +11 1969 O -12 - +12 -Z Pacific/Kwajalein 11:9:20 - LMT 1901 -11 - +11 1969 O --12 - -12 1993 Au 20 -12 - +12 -Z Pacific/Chuuk 10:7:8 - LMT 1901 -10 - +10 -Z Pacific/Pohnpei 10:32:52 - LMT 1901 -11 - +11 -Z Pacific/Kosrae 10:51:56 - LMT 1901 -11 - +11 1969 O -12 - +12 1999 -11 - +11 -Z Pacific/Nauru 11:7:40 - LMT 1921 Ja 15 -11:30 - +1130 1942 Mar 15 -9 - +09 1944 Au 15 -11:30 - +1130 1979 May -12 - +12 -R NC 1977 1978 - D Sun>=1 0 1 - -R NC 1978 1979 - F 27 0 0 - -R NC 1996 o - D 1 2s 1 - -R NC 1997 o - Mar 2 2s 0 - -Z Pacific/Noumea 11:5:48 - LMT 1912 Ja 13 -11 NC +11/+12 -R NZ 1927 o - N 6 2 1 S -R NZ 1928 o - Mar 4 2 0 M -R NZ 1928 1933 - O Sun>=8 2 0:30 S -R NZ 1929 1933 - Mar Sun>=15 2 0 M -R NZ 1934 1940 - Ap lastSun 2 0 M -R NZ 1934 1940 - S lastSun 2 0:30 S -R NZ 1946 o - Ja 1 0 0 S -R NZ 1974 o - N Sun>=1 2s 1 D -R k 1974 o - N Sun>=1 2:45s 1 - -R NZ 1975 o - F lastSun 2s 0 S -R k 1975 o - F lastSun 2:45s 0 - -R NZ 1975 1988 - O lastSun 2s 1 D -R k 1975 1988 - O lastSun 2:45s 1 - -R NZ 1976 1989 - Mar Sun>=1 2s 0 S -R k 1976 1989 - Mar Sun>=1 2:45s 0 - -R NZ 1989 o - O Sun>=8 2s 1 D -R k 1989 o - O Sun>=8 2:45s 1 - -R NZ 1990 2006 - O Sun>=1 2s 1 D -R k 1990 2006 - O Sun>=1 2:45s 1 - -R NZ 1990 2007 - Mar Sun>=15 2s 0 S -R k 1990 2007 - Mar Sun>=15 2:45s 0 - -R NZ 2007 ma - S lastSun 2s 1 D -R k 2007 ma - S lastSun 2:45s 1 - -R NZ 2008 ma - Ap Sun>=1 2s 0 S -R k 2008 ma - Ap Sun>=1 2:45s 0 - -Z Pacific/Auckland 11:39:4 - LMT 1868 N 2 -11:30 NZ NZ%sT 1946 -12 NZ NZ%sT -Z Pacific/Chatham 12:13:48 - LMT 1868 N 2 -12:15 - +1215 1946 -12:45 k +1245/+1345 -Li Pacific/Auckland Antarctica/McMurdo -R CK 1978 o - N 12 0 0:30 - -R CK 1979 1991 - Mar Sun>=1 0 0 - -R CK 1979 1990 - O lastSun 0 0:30 - -Z Pacific/Rarotonga -10:39:4 - LMT 1901 --10:30 - -1030 1978 N 12 --10 CK -10/-0930 -Z Pacific/Niue -11:19:40 - LMT 1901 --11:20 - -1120 1951 --11:30 - -1130 1978 O --11 - -11 -Z Pacific/Norfolk 11:11:52 - LMT 1901 -11:12 - +1112 1951 -11:30 - +1130 1974 O 27 2 -11:30 1 +1230 1975 Mar 2 2 -11:30 - +1130 2015 O 4 2 -11 - +11 -Z Pacific/Palau 8:57:56 - LMT 1901 -9 - +09 -Z Pacific/Port_Moresby 9:48:40 - LMT 1880 -9:48:32 - PMMT 1895 -10 - +10 -Z Pacific/Bougainville 10:22:16 - LMT 1880 -9:48:32 - PMMT 1895 -10 - +10 1942 Jul -9 - +09 1945 Au 21 -10 - +10 2014 D 28 2 -11 - +11 -Z Pacific/Pitcairn -8:40:20 - LMT 1901 --8:30 - -0830 1998 Ap 27 --8 - -08 -Z Pacific/Pago_Pago 12:37:12 - LMT 1892 Jul 5 --11:22:48 - LMT 1911 --11 - SST -Li Pacific/Pago_Pago Pacific/Midway -R WS 2010 o - S lastSun 0 1 - -R WS 2011 o - Ap Sat>=1 4 0 - -R WS 2011 o - S lastSat 3 1 - -R WS 2012 ma - Ap Sun>=1 4 0 - -R WS 2012 ma - S lastSun 3 1 - -Z Pacific/Apia 12:33:4 - LMT 1892 Jul 5 --11:26:56 - LMT 1911 --11:30 - -1130 1950 --11 WS -11/-10 2011 D 29 24 -13 WS +13/+14 -Z Pacific/Guadalcanal 10:39:48 - LMT 1912 O -11 - +11 -Z Pacific/Fakaofo -11:24:56 - LMT 1901 --11 - -11 2011 D 30 -13 - +13 -R TO 1999 o - O 7 2s 1 - -R TO 2000 o - Mar 19 2s 0 - -R TO 2000 2001 - N Sun>=1 2 1 - -R TO 2001 2002 - Ja lastSun 2 0 - -R TO 2016 o - N Sun>=1 2 1 - -R TO 2017 o - Ja Sun>=15 3 0 - -Z Pacific/Tongatapu 12:19:20 - LMT 1901 -12:20 - +1220 1941 -13 - +13 1999 -13 TO +13/+14 -Z Pacific/Funafuti 11:56:52 - LMT 1901 -12 - +12 -Z Pacific/Wake 11:6:28 - LMT 1901 -12 - +12 -R VU 1983 o - S 25 0 1 - -R VU 1984 1991 - Mar Sun>=23 0 0 - -R VU 1984 o - O 23 0 1 - -R VU 1985 1991 - S Sun>=23 0 1 - -R VU 1992 1993 - Ja Sun>=23 0 0 - -R VU 1992 o - O Sun>=23 0 1 - -Z Pacific/Efate 11:13:16 - LMT 1912 Ja 13 -11 VU +11/+12 -Z Pacific/Wallis 12:15:20 - LMT 1901 -12 - +12 -R G 1916 o - May 21 2s 1 BST -R G 1916 o - O 1 2s 0 GMT -R G 1917 o - Ap 8 2s 1 BST -R G 1917 o - S 17 2s 0 GMT -R G 1918 o - Mar 24 2s 1 BST -R G 1918 o - S 30 2s 0 GMT -R G 1919 o - Mar 30 2s 1 BST -R G 1919 o - S 29 2s 0 GMT -R G 1920 o - Mar 28 2s 1 BST -R G 1920 o - O 25 2s 0 GMT -R G 1921 o - Ap 3 2s 1 BST -R G 1921 o - O 3 2s 0 GMT -R G 1922 o - Mar 26 2s 1 BST -R G 1922 o - O 8 2s 0 GMT -R G 1923 o - Ap Sun>=16 2s 1 BST -R G 1923 1924 - S Sun>=16 2s 0 GMT -R G 1924 o - Ap Sun>=9 2s 1 BST -R G 1925 1926 - Ap Sun>=16 2s 1 BST -R G 1925 1938 - O Sun>=2 2s 0 GMT -R G 1927 o - Ap Sun>=9 2s 1 BST -R G 1928 1929 - Ap Sun>=16 2s 1 BST -R G 1930 o - Ap Sun>=9 2s 1 BST -R G 1931 1932 - Ap Sun>=16 2s 1 BST -R G 1933 o - Ap Sun>=9 2s 1 BST -R G 1934 o - Ap Sun>=16 2s 1 BST -R G 1935 o - Ap Sun>=9 2s 1 BST -R G 1936 1937 - Ap Sun>=16 2s 1 BST -R G 1938 o - Ap Sun>=9 2s 1 BST -R G 1939 o - Ap Sun>=16 2s 1 BST -R G 1939 o - N Sun>=16 2s 0 GMT -R G 1940 o - F Sun>=23 2s 1 BST -R G 1941 o - May Sun>=2 1s 2 BDST -R G 1941 1943 - Au Sun>=9 1s 1 BST -R G 1942 1944 - Ap Sun>=2 1s 2 BDST -R G 1944 o - S Sun>=16 1s 1 BST -R G 1945 o - Ap M>=2 1s 2 BDST -R G 1945 o - Jul Sun>=9 1s 1 BST -R G 1945 1946 - O Sun>=2 2s 0 GMT -R G 1946 o - Ap Sun>=9 2s 1 BST -R G 1947 o - Mar 16 2s 1 BST -R G 1947 o - Ap 13 1s 2 BDST -R G 1947 o - Au 10 1s 1 BST -R G 1947 o - N 2 2s 0 GMT -R G 1948 o - Mar 14 2s 1 BST -R G 1948 o - O 31 2s 0 GMT -R G 1949 o - Ap 3 2s 1 BST -R G 1949 o - O 30 2s 0 GMT -R G 1950 1952 - Ap Sun>=14 2s 1 BST -R G 1950 1952 - O Sun>=21 2s 0 GMT -R G 1953 o - Ap Sun>=16 2s 1 BST -R G 1953 1960 - O Sun>=2 2s 0 GMT -R G 1954 o - Ap Sun>=9 2s 1 BST -R G 1955 1956 - Ap Sun>=16 2s 1 BST -R G 1957 o - Ap Sun>=9 2s 1 BST -R G 1958 1959 - Ap Sun>=16 2s 1 BST -R G 1960 o - Ap Sun>=9 2s 1 BST -R G 1961 1963 - Mar lastSun 2s 1 BST -R G 1961 1968 - O Sun>=23 2s 0 GMT -R G 1964 1967 - Mar Sun>=19 2s 1 BST -R G 1968 o - F 18 2s 1 BST -R G 1972 1980 - Mar Sun>=16 2s 1 BST -R G 1972 1980 - O Sun>=23 2s 0 GMT -R G 1981 1995 - Mar lastSun 1u 1 BST -R G 1981 1989 - O Sun>=23 1u 0 GMT -R G 1990 1995 - O Sun>=22 1u 0 GMT -Z Europe/London -0:1:15 - LMT 1847 D 1 0s -0 G %s 1968 O 27 -1 - BST 1971 O 31 2u -0 G %s 1996 -0 E GMT/BST -Li Europe/London Europe/Jersey -Li Europe/London Europe/Guernsey -Li Europe/London Europe/Isle_of_Man -R IE 1971 o - O 31 2u -1 - -R IE 1972 1980 - Mar Sun>=16 2u 0 - -R IE 1972 1980 - O Sun>=23 2u -1 - -R IE 1981 ma - Mar lastSun 1u 0 - -R IE 1981 1989 - O Sun>=23 1u -1 - -R IE 1990 1995 - O Sun>=22 1u -1 - -R IE 1996 ma - O lastSun 1u -1 - -Z Europe/Dublin -0:25 - LMT 1880 Au 2 --0:25:21 - DMT 1916 May 21 2s --0:25:21 1 IST 1916 O 1 2s -0 G %s 1921 D 6 -0 G GMT/IST 1940 F 25 2s -0 1 IST 1946 O 6 2s -0 - GMT 1947 Mar 16 2s -0 1 IST 1947 N 2 2s -0 - GMT 1948 Ap 18 2s -0 G GMT/IST 1968 O 27 -1 IE IST/GMT -R E 1977 1980 - Ap Sun>=1 1u 1 S -R E 1977 o - S lastSun 1u 0 - -R E 1978 o - O 1 1u 0 - -R E 1979 1995 - S lastSun 1u 0 - -R E 1981 ma - Mar lastSun 1u 1 S -R E 1996 ma - O lastSun 1u 0 - -R W- 1977 1980 - Ap Sun>=1 1s 1 S -R W- 1977 o - S lastSun 1s 0 - -R W- 1978 o - O 1 1s 0 - -R W- 1979 1995 - S lastSun 1s 0 - -R W- 1981 ma - Mar lastSun 1s 1 S -R W- 1996 ma - O lastSun 1s 0 - -R c 1916 o - Ap 30 23 1 S -R c 1916 o - O 1 1 0 - -R c 1917 1918 - Ap M>=15 2s 1 S -R c 1917 1918 - S M>=15 2s 0 - -R c 1940 o - Ap 1 2s 1 S -R c 1942 o - N 2 2s 0 - -R c 1943 o - Mar 29 2s 1 S -R c 1943 o - O 4 2s 0 - -R c 1944 1945 - Ap M>=1 2s 1 S -R c 1944 o - O 2 2s 0 - -R c 1945 o - S 16 2s 0 - -R c 1977 1980 - Ap Sun>=1 2s 1 S -R c 1977 o - S lastSun 2s 0 - -R c 1978 o - O 1 2s 0 - -R c 1979 1995 - S lastSun 2s 0 - -R c 1981 ma - Mar lastSun 2s 1 S -R c 1996 ma - O lastSun 2s 0 - -R e 1977 1980 - Ap Sun>=1 0 1 S -R e 1977 o - S lastSun 0 0 - -R e 1978 o - O 1 0 0 - -R e 1979 1995 - S lastSun 0 0 - -R e 1981 ma - Mar lastSun 0 1 S -R e 1996 ma - O lastSun 0 0 - -R R 1917 o - Jul 1 23 1 MST -R R 1917 o - D 28 0 0 MMT -R R 1918 o - May 31 22 2 MDST -R R 1918 o - S 16 1 1 MST -R R 1919 o - May 31 23 2 MDST -R R 1919 o - Jul 1 0u 1 MSD -R R 1919 o - Au 16 0 0 MSK -R R 1921 o - F 14 23 1 MSD -R R 1921 o - Mar 20 23 2 +05 -R R 1921 o - S 1 0 1 MSD -R R 1921 o - O 1 0 0 - -R R 1981 1984 - Ap 1 0 1 S -R R 1981 1983 - O 1 0 0 - -R R 1984 1995 - S lastSun 2s 0 - -R R 1985 2010 - Mar lastSun 2s 1 S -R R 1996 2010 - O lastSun 2s 0 - -Z WET 0 E WE%sT -Z CET 1 c CE%sT -Z MET 1 c ME%sT -Z EET 2 E EE%sT -R q 1940 o - Jun 16 0 1 S -R q 1942 o - N 2 3 0 - -R q 1943 o - Mar 29 2 1 S -R q 1943 o - Ap 10 3 0 - -R q 1974 o - May 4 0 1 S -R q 1974 o - O 2 0 0 - -R q 1975 o - May 1 0 1 S -R q 1975 o - O 2 0 0 - -R q 1976 o - May 2 0 1 S -R q 1976 o - O 3 0 0 - -R q 1977 o - May 8 0 1 S -R q 1977 o - O 2 0 0 - -R q 1978 o - May 6 0 1 S -R q 1978 o - O 1 0 0 - -R q 1979 o - May 5 0 1 S -R q 1979 o - S 30 0 0 - -R q 1980 o - May 3 0 1 S -R q 1980 o - O 4 0 0 - -R q 1981 o - Ap 26 0 1 S -R q 1981 o - S 27 0 0 - -R q 1982 o - May 2 0 1 S -R q 1982 o - O 3 0 0 - -R q 1983 o - Ap 18 0 1 S -R q 1983 o - O 1 0 0 - -R q 1984 o - Ap 1 0 1 S -Z Europe/Tirane 1:19:20 - LMT 1914 -1 - CET 1940 Jun 16 -1 q CE%sT 1984 Jul -1 E CE%sT -Z Europe/Andorra 0:6:4 - LMT 1901 -0 - WET 1946 S 30 -1 - CET 1985 Mar 31 2 -1 E CE%sT -R a 1920 o - Ap 5 2s 1 S -R a 1920 o - S 13 2s 0 - -R a 1946 o - Ap 14 2s 1 S -R a 1946 1948 - O Sun>=1 2s 0 - -R a 1947 o - Ap 6 2s 1 S -R a 1948 o - Ap 18 2s 1 S -R a 1980 o - Ap 6 0 1 S -R a 1980 o - S 28 0 0 - -Z Europe/Vienna 1:5:21 - LMT 1893 Ap -1 c CE%sT 1920 -1 a CE%sT 1940 Ap 1 2s -1 c CE%sT 1945 Ap 2 2s -1 1 CEST 1945 Ap 12 2s -1 - CET 1946 -1 a CE%sT 1981 -1 E CE%sT -Z Europe/Minsk 1:50:16 - LMT 1880 -1:50 - MMT 1924 May 2 -2 - EET 1930 Jun 21 -3 - MSK 1941 Jun 28 -1 c CE%sT 1944 Jul 3 -3 R MSK/MSD 1990 -3 - MSK 1991 Mar 31 2s -2 R EE%sT 2011 Mar 27 2s -3 - +03 -R b 1918 o - Mar 9 0s 1 S -R b 1918 1919 - O Sat>=1 23s 0 - -R b 1919 o - Mar 1 23s 1 S -R b 1920 o - F 14 23s 1 S -R b 1920 o - O 23 23s 0 - -R b 1921 o - Mar 14 23s 1 S -R b 1921 o - O 25 23s 0 - -R b 1922 o - Mar 25 23s 1 S -R b 1922 1927 - O Sat>=1 23s 0 - -R b 1923 o - Ap 21 23s 1 S -R b 1924 o - Mar 29 23s 1 S -R b 1925 o - Ap 4 23s 1 S -R b 1926 o - Ap 17 23s 1 S -R b 1927 o - Ap 9 23s 1 S -R b 1928 o - Ap 14 23s 1 S -R b 1928 1938 - O Sun>=2 2s 0 - -R b 1929 o - Ap 21 2s 1 S -R b 1930 o - Ap 13 2s 1 S -R b 1931 o - Ap 19 2s 1 S -R b 1932 o - Ap 3 2s 1 S -R b 1933 o - Mar 26 2s 1 S -R b 1934 o - Ap 8 2s 1 S -R b 1935 o - Mar 31 2s 1 S -R b 1936 o - Ap 19 2s 1 S -R b 1937 o - Ap 4 2s 1 S -R b 1938 o - Mar 27 2s 1 S -R b 1939 o - Ap 16 2s 1 S -R b 1939 o - N 19 2s 0 - -R b 1940 o - F 25 2s 1 S -R b 1944 o - S 17 2s 0 - -R b 1945 o - Ap 2 2s 1 S -R b 1945 o - S 16 2s 0 - -R b 1946 o - May 19 2s 1 S -R b 1946 o - O 7 2s 0 - -Z Europe/Brussels 0:17:30 - LMT 1880 -0:17:30 - BMT 1892 May 1 12 -0 - WET 1914 N 8 -1 - CET 1916 May -1 c CE%sT 1918 N 11 11u -0 b WE%sT 1940 May 20 2s -1 c CE%sT 1944 S 3 -1 b CE%sT 1977 -1 E CE%sT -R BG 1979 o - Mar 31 23 1 S -R BG 1979 o - O 1 1 0 - -R BG 1980 1982 - Ap Sat>=1 23 1 S -R BG 1980 o - S 29 1 0 - -R BG 1981 o - S 27 2 0 - -Z Europe/Sofia 1:33:16 - LMT 1880 -1:56:56 - IMT 1894 N 30 -2 - EET 1942 N 2 3 -1 c CE%sT 1945 -1 - CET 1945 Ap 2 3 -2 - EET 1979 Mar 31 23 -2 BG EE%sT 1982 S 26 3 -2 c EE%sT 1991 -2 e EE%sT 1997 -2 E EE%sT -R CZ 1945 o - Ap M>=1 2s 1 S -R CZ 1945 o - O 1 2s 0 - -R CZ 1946 o - May 6 2s 1 S -R CZ 1946 1949 - O Sun>=1 2s 0 - -R CZ 1947 1948 - Ap Sun>=15 2s 1 S -R CZ 1949 o - Ap 9 2s 1 S -Z Europe/Prague 0:57:44 - LMT 1850 -0:57:44 - PMT 1891 O -1 c CE%sT 1945 May 9 -1 CZ CE%sT 1946 D 1 3 -1 -1 GMT 1947 F 23 2 -1 CZ CE%sT 1979 -1 E CE%sT -R D 1916 o - May 14 23 1 S -R D 1916 o - S 30 23 0 - -R D 1940 o - May 15 0 1 S -R D 1945 o - Ap 2 2s 1 S -R D 1945 o - Au 15 2s 0 - -R D 1946 o - May 1 2s 1 S -R D 1946 o - S 1 2s 0 - -R D 1947 o - May 4 2s 1 S -R D 1947 o - Au 10 2s 0 - -R D 1948 o - May 9 2s 1 S -R D 1948 o - Au 8 2s 0 - -Z Europe/Copenhagen 0:50:20 - LMT 1890 -0:50:20 - CMT 1894 -1 D CE%sT 1942 N 2 2s -1 c CE%sT 1945 Ap 2 2 -1 D CE%sT 1980 -1 E CE%sT -Z Atlantic/Faroe -0:27:4 - LMT 1908 Ja 11 -0 - WET 1981 -0 E WE%sT -R Th 1991 1992 - Mar lastSun 2 1 D -R Th 1991 1992 - S lastSun 2 0 S -R Th 1993 2006 - Ap Sun>=1 2 1 D -R Th 1993 2006 - O lastSun 2 0 S -R Th 2007 ma - Mar Sun>=8 2 1 D -R Th 2007 ma - N Sun>=1 2 0 S -Z America/Danmarkshavn -1:14:40 - LMT 1916 Jul 28 --3 - -03 1980 Ap 6 2 --3 E -03/-02 1996 -0 - GMT -Z America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 --2 - -02 1980 Ap 6 2 --2 c -02/-01 1981 Mar 29 --1 E -01/+00 -Z America/Godthab -3:26:56 - LMT 1916 Jul 28 --3 - -03 1980 Ap 6 2 --3 E -03/-02 -Z America/Thule -4:35:8 - LMT 1916 Jul 28 --4 Th A%sT -Z Europe/Tallinn 1:39 - LMT 1880 -1:39 - TMT 1918 F -1 c CE%sT 1919 Jul -1:39 - TMT 1921 May -2 - EET 1940 Au 6 -3 - MSK 1941 S 15 -1 c CE%sT 1944 S 22 -3 R MSK/MSD 1989 Mar 26 2s -2 1 EEST 1989 S 24 2s -2 c EE%sT 1998 S 22 -2 E EE%sT 1999 O 31 4 -2 - EET 2002 F 21 -2 E EE%sT -R FI 1942 o - Ap 2 24 1 S -R FI 1942 o - O 4 1 0 - -R FI 1981 1982 - Mar lastSun 2 1 S -R FI 1981 1982 - S lastSun 3 0 - -Z Europe/Helsinki 1:39:49 - LMT 1878 May 31 -1:39:49 - HMT 1921 May -2 FI EE%sT 1983 -2 E EE%sT -Li Europe/Helsinki Europe/Mariehamn -R F 1916 o - Jun 14 23s 1 S -R F 1916 1919 - O Sun>=1 23s 0 - -R F 1917 o - Mar 24 23s 1 S -R F 1918 o - Mar 9 23s 1 S -R F 1919 o - Mar 1 23s 1 S -R F 1920 o - F 14 23s 1 S -R F 1920 o - O 23 23s 0 - -R F 1921 o - Mar 14 23s 1 S -R F 1921 o - O 25 23s 0 - -R F 1922 o - Mar 25 23s 1 S -R F 1922 1938 - O Sat>=1 23s 0 - -R F 1923 o - May 26 23s 1 S -R F 1924 o - Mar 29 23s 1 S -R F 1925 o - Ap 4 23s 1 S -R F 1926 o - Ap 17 23s 1 S -R F 1927 o - Ap 9 23s 1 S -R F 1928 o - Ap 14 23s 1 S -R F 1929 o - Ap 20 23s 1 S -R F 1930 o - Ap 12 23s 1 S -R F 1931 o - Ap 18 23s 1 S -R F 1932 o - Ap 2 23s 1 S -R F 1933 o - Mar 25 23s 1 S -R F 1934 o - Ap 7 23s 1 S -R F 1935 o - Mar 30 23s 1 S -R F 1936 o - Ap 18 23s 1 S -R F 1937 o - Ap 3 23s 1 S -R F 1938 o - Mar 26 23s 1 S -R F 1939 o - Ap 15 23s 1 S -R F 1939 o - N 18 23s 0 - -R F 1940 o - F 25 2 1 S -R F 1941 o - May 5 0 2 M -R F 1941 o - O 6 0 1 S -R F 1942 o - Mar 9 0 2 M -R F 1942 o - N 2 3 1 S -R F 1943 o - Mar 29 2 2 M -R F 1943 o - O 4 3 1 S -R F 1944 o - Ap 3 2 2 M -R F 1944 o - O 8 1 1 S -R F 1945 o - Ap 2 2 2 M -R F 1945 o - S 16 3 0 - -R F 1976 o - Mar 28 1 1 S -R F 1976 o - S 26 1 0 - -Z Europe/Paris 0:9:21 - LMT 1891 Mar 15 0:1 -0:9:21 - PMT 1911 Mar 11 0:1 -0 F WE%sT 1940 Jun 14 23 -1 c CE%sT 1944 Au 25 -0 F WE%sT 1945 S 16 3 -1 F CE%sT 1977 -1 E CE%sT -R DE 1946 o - Ap 14 2s 1 S -R DE 1946 o - O 7 2s 0 - -R DE 1947 1949 - O Sun>=1 2s 0 - -R DE 1947 o - Ap 6 3s 1 S -R DE 1947 o - May 11 2s 2 M -R DE 1947 o - Jun 29 3 1 S -R DE 1948 o - Ap 18 2s 1 S -R DE 1949 o - Ap 10 2s 1 S -R So 1945 o - May 24 2 2 M -R So 1945 o - S 24 3 1 S -R So 1945 o - N 18 2s 0 - -Z Europe/Berlin 0:53:28 - LMT 1893 Ap -1 c CE%sT 1945 May 24 2 -1 So CE%sT 1946 -1 DE CE%sT 1980 -1 E CE%sT -Li Europe/Zurich Europe/Busingen -Z Europe/Gibraltar -0:21:24 - LMT 1880 Au 2 0s -0 G %s 1957 Ap 14 2 -1 - CET 1982 -1 E CE%sT -R g 1932 o - Jul 7 0 1 S -R g 1932 o - S 1 0 0 - -R g 1941 o - Ap 7 0 1 S -R g 1942 o - N 2 3 0 - -R g 1943 o - Mar 30 0 1 S -R g 1943 o - O 4 0 0 - -R g 1952 o - Jul 1 0 1 S -R g 1952 o - N 2 0 0 - -R g 1975 o - Ap 12 0s 1 S -R g 1975 o - N 26 0s 0 - -R g 1976 o - Ap 11 2s 1 S -R g 1976 o - O 10 2s 0 - -R g 1977 1978 - Ap Sun>=1 2s 1 S -R g 1977 o - S 26 2s 0 - -R g 1978 o - S 24 4 0 - -R g 1979 o - Ap 1 9 1 S -R g 1979 o - S 29 2 0 - -R g 1980 o - Ap 1 0 1 S -R g 1980 o - S 28 0 0 - -Z Europe/Athens 1:34:52 - LMT 1895 S 14 -1:34:52 - AMT 1916 Jul 28 0:1 -2 g EE%sT 1941 Ap 30 -1 g CE%sT 1944 Ap 4 -2 g EE%sT 1981 -2 E EE%sT -R h 1918 o - Ap 1 3 1 S -R h 1918 o - S 16 3 0 - -R h 1919 o - Ap 15 3 1 S -R h 1919 o - N 24 3 0 - -R h 1945 o - May 1 23 1 S -R h 1945 o - N 1 0 0 - -R h 1946 o - Mar 31 2s 1 S -R h 1946 1949 - O Sun>=1 2s 0 - -R h 1947 1949 - Ap Sun>=4 2s 1 S -R h 1950 o - Ap 17 2s 1 S -R h 1950 o - O 23 2s 0 - -R h 1954 1955 - May 23 0 1 S -R h 1954 1955 - O 3 0 0 - -R h 1956 o - Jun Sun>=1 0 1 S -R h 1956 o - S lastSun 0 0 - -R h 1957 o - Jun Sun>=1 1 1 S -R h 1957 o - S lastSun 3 0 - -R h 1980 o - Ap 6 1 1 S -Z Europe/Budapest 1:16:20 - LMT 1890 O -1 c CE%sT 1918 -1 h CE%sT 1941 Ap 8 -1 c CE%sT 1945 -1 h CE%sT 1980 S 28 2s -1 E CE%sT -R w 1917 1919 - F 19 23 1 - -R w 1917 o - O 21 1 0 - -R w 1918 1919 - N 16 1 0 - -R w 1921 o - Mar 19 23 1 - -R w 1921 o - Jun 23 1 0 - -R w 1939 o - Ap 29 23 1 - -R w 1939 o - O 29 2 0 - -R w 1940 o - F 25 2 1 - -R w 1940 1941 - N Sun>=2 1s 0 - -R w 1941 1942 - Mar Sun>=2 1s 1 - -R w 1943 1946 - Mar Sun>=1 1s 1 - -R w 1942 1948 - O Sun>=22 1s 0 - -R w 1947 1967 - Ap Sun>=1 1s 1 - -R w 1949 o - O 30 1s 0 - -R w 1950 1966 - O Sun>=22 1s 0 - -R w 1967 o - O 29 1s 0 - -Z Atlantic/Reykjavik -1:28 - LMT 1908 --1 w -01/+00 1968 Ap 7 1s -0 - GMT -R I 1916 o - Jun 3 24 1 S -R I 1916 1917 - S 30 24 0 - -R I 1917 o - Mar 31 24 1 S -R I 1918 o - Mar 9 24 1 S -R I 1918 o - O 6 24 0 - -R I 1919 o - Mar 1 24 1 S -R I 1919 o - O 4 24 0 - -R I 1920 o - Mar 20 24 1 S -R I 1920 o - S 18 24 0 - -R I 1940 o - Jun 14 24 1 S -R I 1942 o - N 2 2s 0 - -R I 1943 o - Mar 29 2s 1 S -R I 1943 o - O 4 2s 0 - -R I 1944 o - Ap 2 2s 1 S -R I 1944 o - S 17 2s 0 - -R I 1945 o - Ap 2 2 1 S -R I 1945 o - S 15 1 0 - -R I 1946 o - Mar 17 2s 1 S -R I 1946 o - O 6 2s 0 - -R I 1947 o - Mar 16 0s 1 S -R I 1947 o - O 5 0s 0 - -R I 1948 o - F 29 2s 1 S -R I 1948 o - O 3 2s 0 - -R I 1966 1968 - May Sun>=22 0s 1 S -R I 1966 o - S 24 24 0 - -R I 1967 1969 - S Sun>=22 0s 0 - -R I 1969 o - Jun 1 0s 1 S -R I 1970 o - May 31 0s 1 S -R I 1970 o - S lastSun 0s 0 - -R I 1971 1972 - May Sun>=22 0s 1 S -R I 1971 o - S lastSun 0s 0 - -R I 1972 o - O 1 0s 0 - -R I 1973 o - Jun 3 0s 1 S -R I 1973 1974 - S lastSun 0s 0 - -R I 1974 o - May 26 0s 1 S -R I 1975 o - Jun 1 0s 1 S -R I 1975 1977 - S lastSun 0s 0 - -R I 1976 o - May 30 0s 1 S -R I 1977 1979 - May Sun>=22 0s 1 S -R I 1978 o - O 1 0s 0 - -R I 1979 o - S 30 0s 0 - -Z Europe/Rome 0:49:56 - LMT 1866 S 22 -0:49:56 - RMT 1893 O 31 23:49:56 -1 I CE%sT 1943 S 10 -1 c CE%sT 1944 Jun 4 -1 I CE%sT 1980 -1 E CE%sT -Li Europe/Rome Europe/Vatican -Li Europe/Rome Europe/San_Marino -R LV 1989 1996 - Mar lastSun 2s 1 S -R LV 1989 1996 - S lastSun 2s 0 - -Z Europe/Riga 1:36:34 - LMT 1880 -1:36:34 - RMT 1918 Ap 15 2 -1:36:34 1 LST 1918 S 16 3 -1:36:34 - RMT 1919 Ap 1 2 -1:36:34 1 LST 1919 May 22 3 -1:36:34 - RMT 1926 May 11 -2 - EET 1940 Au 5 -3 - MSK 1941 Jul -1 c CE%sT 1944 O 13 -3 R MSK/MSD 1989 Mar lastSun 2s -2 1 EEST 1989 S lastSun 2s -2 LV EE%sT 1997 Ja 21 -2 E EE%sT 2000 F 29 -2 - EET 2001 Ja 2 -2 E EE%sT -Li Europe/Zurich Europe/Vaduz -Z Europe/Vilnius 1:41:16 - LMT 1880 -1:24 - WMT 1917 -1:35:36 - KMT 1919 O 10 -1 - CET 1920 Jul 12 -2 - EET 1920 O 9 -1 - CET 1940 Au 3 -3 - MSK 1941 Jun 24 -1 c CE%sT 1944 Au -3 R MSK/MSD 1989 Mar 26 2s -2 R EE%sT 1991 S 29 2s -2 c EE%sT 1998 -2 - EET 1998 Mar 29 1u -1 E CE%sT 1999 O 31 1u -2 - EET 2003 -2 E EE%sT -R LX 1916 o - May 14 23 1 S -R LX 1916 o - O 1 1 0 - -R LX 1917 o - Ap 28 23 1 S -R LX 1917 o - S 17 1 0 - -R LX 1918 o - Ap M>=15 2s 1 S -R LX 1918 o - S M>=15 2s 0 - -R LX 1919 o - Mar 1 23 1 S -R LX 1919 o - O 5 3 0 - -R LX 1920 o - F 14 23 1 S -R LX 1920 o - O 24 2 0 - -R LX 1921 o - Mar 14 23 1 S -R LX 1921 o - O 26 2 0 - -R LX 1922 o - Mar 25 23 1 S -R LX 1922 o - O Sun>=2 1 0 - -R LX 1923 o - Ap 21 23 1 S -R LX 1923 o - O Sun>=2 2 0 - -R LX 1924 o - Mar 29 23 1 S -R LX 1924 1928 - O Sun>=2 1 0 - -R LX 1925 o - Ap 5 23 1 S -R LX 1926 o - Ap 17 23 1 S -R LX 1927 o - Ap 9 23 1 S -R LX 1928 o - Ap 14 23 1 S -R LX 1929 o - Ap 20 23 1 S -Z Europe/Luxembourg 0:24:36 - LMT 1904 Jun -1 LX CE%sT 1918 N 25 -0 LX WE%sT 1929 O 6 2s -0 b WE%sT 1940 May 14 3 -1 c WE%sT 1944 S 18 3 -1 b CE%sT 1977 -1 E CE%sT -R MT 1973 o - Mar 31 0s 1 S -R MT 1973 o - S 29 0s 0 - -R MT 1974 o - Ap 21 0s 1 S -R MT 1974 o - S 16 0s 0 - -R MT 1975 1979 - Ap Sun>=15 2 1 S -R MT 1975 1980 - S Sun>=15 2 0 - -R MT 1980 o - Mar 31 2 1 S -Z Europe/Malta 0:58:4 - LMT 1893 N 2 0s -1 I CE%sT 1973 Mar 31 -1 MT CE%sT 1981 -1 E CE%sT -R MD 1997 ma - Mar lastSun 2 1 S -R MD 1997 ma - O lastSun 3 0 - -Z Europe/Chisinau 1:55:20 - LMT 1880 -1:55 - CMT 1918 F 15 -1:44:24 - BMT 1931 Jul 24 -2 z EE%sT 1940 Au 15 -2 1 EEST 1941 Jul 17 -1 c CE%sT 1944 Au 24 -3 R MSK/MSD 1990 May 6 2 -2 R EE%sT 1992 -2 e EE%sT 1997 -2 MD EE%sT -Z Europe/Monaco 0:29:32 - LMT 1891 Mar 15 -0:9:21 - PMT 1911 Mar 11 -0 F WE%sT 1945 S 16 3 -1 F CE%sT 1977 -1 E CE%sT -R N 1916 o - May 1 0 1 NST -R N 1916 o - O 1 0 0 AMT -R N 1917 o - Ap 16 2s 1 NST -R N 1917 o - S 17 2s 0 AMT -R N 1918 1921 - Ap M>=1 2s 1 NST -R N 1918 1921 - S lastM 2s 0 AMT -R N 1922 o - Mar lastSun 2s 1 NST -R N 1922 1936 - O Sun>=2 2s 0 AMT -R N 1923 o - Jun F>=1 2s 1 NST -R N 1924 o - Mar lastSun 2s 1 NST -R N 1925 o - Jun F>=1 2s 1 NST -R N 1926 1931 - May 15 2s 1 NST -R N 1932 o - May 22 2s 1 NST -R N 1933 1936 - May 15 2s 1 NST -R N 1937 o - May 22 2s 1 NST -R N 1937 o - Jul 1 0 1 S -R N 1937 1939 - O Sun>=2 2s 0 - -R N 1938 1939 - May 15 2s 1 S -R N 1945 o - Ap 2 2s 1 S -R N 1945 o - S 16 2s 0 - -Z Europe/Amsterdam 0:19:32 - LMT 1835 -0:19:32 N %s 1937 Jul -0:20 N +0020/+0120 1940 May 16 -1 c CE%sT 1945 Ap 2 2 -1 N CE%sT 1977 -1 E CE%sT -R NO 1916 o - May 22 1 1 S -R NO 1916 o - S 30 0 0 - -R NO 1945 o - Ap 2 2s 1 S -R NO 1945 o - O 1 2s 0 - -R NO 1959 1964 - Mar Sun>=15 2s 1 S -R NO 1959 1965 - S Sun>=15 2s 0 - -R NO 1965 o - Ap 25 2s 1 S -Z Europe/Oslo 0:43 - LMT 1895 -1 NO CE%sT 1940 Au 10 23 -1 c CE%sT 1945 Ap 2 2 -1 NO CE%sT 1980 -1 E CE%sT -Li Europe/Oslo Arctic/Longyearbyen -R O 1918 1919 - S 16 2s 0 - -R O 1919 o - Ap 15 2s 1 S -R O 1944 o - Ap 3 2s 1 S -R O 1944 o - O 4 2 0 - -R O 1945 o - Ap 29 0 1 S -R O 1945 o - N 1 0 0 - -R O 1946 o - Ap 14 0s 1 S -R O 1946 o - O 7 2s 0 - -R O 1947 o - May 4 2s 1 S -R O 1947 1949 - O Sun>=1 2s 0 - -R O 1948 o - Ap 18 2s 1 S -R O 1949 o - Ap 10 2s 1 S -R O 1957 o - Jun 2 1s 1 S -R O 1957 1958 - S lastSun 1s 0 - -R O 1958 o - Mar 30 1s 1 S -R O 1959 o - May 31 1s 1 S -R O 1959 1961 - O Sun>=1 1s 0 - -R O 1960 o - Ap 3 1s 1 S -R O 1961 1964 - May lastSun 1s 1 S -R O 1962 1964 - S lastSun 1s 0 - -Z Europe/Warsaw 1:24 - LMT 1880 -1:24 - WMT 1915 Au 5 -1 c CE%sT 1918 S 16 3 -2 O EE%sT 1922 Jun -1 O CE%sT 1940 Jun 23 2 -1 c CE%sT 1944 O -1 O CE%sT 1977 -1 W- CE%sT 1988 -1 E CE%sT -R p 1916 o - Jun 17 23 1 S -R p 1916 o - N 1 1 0 - -R p 1917 o - F 28 23s 1 S -R p 1917 1921 - O 14 23s 0 - -R p 1918 o - Mar 1 23s 1 S -R p 1919 o - F 28 23s 1 S -R p 1920 o - F 29 23s 1 S -R p 1921 o - F 28 23s 1 S -R p 1924 o - Ap 16 23s 1 S -R p 1924 o - O 14 23s 0 - -R p 1926 o - Ap 17 23s 1 S -R p 1926 1929 - O Sat>=1 23s 0 - -R p 1927 o - Ap 9 23s 1 S -R p 1928 o - Ap 14 23s 1 S -R p 1929 o - Ap 20 23s 1 S -R p 1931 o - Ap 18 23s 1 S -R p 1931 1932 - O Sat>=1 23s 0 - -R p 1932 o - Ap 2 23s 1 S -R p 1934 o - Ap 7 23s 1 S -R p 1934 1938 - O Sat>=1 23s 0 - -R p 1935 o - Mar 30 23s 1 S -R p 1936 o - Ap 18 23s 1 S -R p 1937 o - Ap 3 23s 1 S -R p 1938 o - Mar 26 23s 1 S -R p 1939 o - Ap 15 23s 1 S -R p 1939 o - N 18 23s 0 - -R p 1940 o - F 24 23s 1 S -R p 1940 1941 - O 5 23s 0 - -R p 1941 o - Ap 5 23s 1 S -R p 1942 1945 - Mar Sat>=8 23s 1 S -R p 1942 o - Ap 25 22s 2 M -R p 1942 o - Au 15 22s 1 S -R p 1942 1945 - O Sat>=24 23s 0 - -R p 1943 o - Ap 17 22s 2 M -R p 1943 1945 - Au Sat>=25 22s 1 S -R p 1944 1945 - Ap Sat>=21 22s 2 M -R p 1946 o - Ap Sat>=1 23s 1 S -R p 1946 o - O Sat>=1 23s 0 - -R p 1947 1949 - Ap Sun>=1 2s 1 S -R p 1947 1949 - O Sun>=1 2s 0 - -R p 1951 1965 - Ap Sun>=1 2s 1 S -R p 1951 1965 - O Sun>=1 2s 0 - -R p 1977 o - Mar 27 0s 1 S -R p 1977 o - S 25 0s 0 - -R p 1978 1979 - Ap Sun>=1 0s 1 S -R p 1978 o - O 1 0s 0 - -R p 1979 1982 - S lastSun 1s 0 - -R p 1980 o - Mar lastSun 0s 1 S -R p 1981 1982 - Mar lastSun 1s 1 S -R p 1983 o - Mar lastSun 2s 1 S -Z Europe/Lisbon -0:36:45 - LMT 1884 --0:36:45 - LMT 1912 Ja 1 0u -0 p WE%sT 1966 Ap 3 2 -1 - CET 1976 S 26 1 -0 p WE%sT 1983 S 25 1s -0 W- WE%sT 1992 S 27 1s -1 E CE%sT 1996 Mar 31 1u -0 E WE%sT -Z Atlantic/Azores -1:42:40 - LMT 1884 --1:54:32 - HMT 1912 Ja 1 2u --2 p -02/-01 1942 Ap 25 22s --2 p +00 1942 Au 15 22s --2 p -02/-01 1943 Ap 17 22s --2 p +00 1943 Au 28 22s --2 p -02/-01 1944 Ap 22 22s --2 p +00 1944 Au 26 22s --2 p -02/-01 1945 Ap 21 22s --2 p +00 1945 Au 25 22s --2 p -02/-01 1966 Ap 3 2 --1 p -01/+00 1983 S 25 1s --1 W- -01/+00 1992 S 27 1s -0 E WE%sT 1993 Mar 28 1u --1 E -01/+00 -Z Atlantic/Madeira -1:7:36 - LMT 1884 --1:7:36 - FMT 1912 Ja 1 1u --1 p -01/+00 1942 Ap 25 22s --1 p +01 1942 Au 15 22s --1 p -01/+00 1943 Ap 17 22s --1 p +01 1943 Au 28 22s --1 p -01/+00 1944 Ap 22 22s --1 p +01 1944 Au 26 22s --1 p -01/+00 1945 Ap 21 22s --1 p +01 1945 Au 25 22s --1 p -01/+00 1966 Ap 3 2 -0 p WE%sT 1983 S 25 1s -0 E WE%sT -R z 1932 o - May 21 0s 1 S -R z 1932 1939 - O Sun>=1 0s 0 - -R z 1933 1939 - Ap Sun>=2 0s 1 S -R z 1979 o - May 27 0 1 S -R z 1979 o - S lastSun 0 0 - -R z 1980 o - Ap 5 23 1 S -R z 1980 o - S lastSun 1 0 - -R z 1991 1993 - Mar lastSun 0s 1 S -R z 1991 1993 - S lastSun 0s 0 - -Z Europe/Bucharest 1:44:24 - LMT 1891 O -1:44:24 - BMT 1931 Jul 24 -2 z EE%sT 1981 Mar 29 2s -2 c EE%sT 1991 -2 z EE%sT 1994 -2 e EE%sT 1997 -2 E EE%sT -Z Europe/Kaliningrad 1:22 - LMT 1893 Ap -1 c CE%sT 1945 -2 O CE%sT 1946 -3 R MSK/MSD 1989 Mar 26 2s -2 R EE%sT 2011 Mar 27 2s -3 - +03 2014 O 26 2s -2 - EET -Z Europe/Moscow 2:30:17 - LMT 1880 -2:30:17 - MMT 1916 Jul 3 -2:31:19 R %s 1919 Jul 1 0u -3 R %s 1921 O -3 R MSK/MSD 1922 O -2 - EET 1930 Jun 21 -3 R MSK/MSD 1991 Mar 31 2s -2 R EE%sT 1992 Ja 19 2s -3 R MSK/MSD 2011 Mar 27 2s -4 - MSK 2014 O 26 2s -3 - MSK -Z Europe/Simferopol 2:16:24 - LMT 1880 -2:16 - SMT 1924 May 2 -2 - EET 1930 Jun 21 -3 - MSK 1941 N -1 c CE%sT 1944 Ap 13 -3 R MSK/MSD 1990 -3 - MSK 1990 Jul 1 2 -2 - EET 1992 -2 e EE%sT 1994 May -3 e MSK/MSD 1996 Mar 31 0s -3 1 MSD 1996 O 27 3s -3 R MSK/MSD 1997 -3 - MSK 1997 Mar lastSun 1u -2 E EE%sT 2014 Mar 30 2 -4 - MSK 2014 O 26 2s -3 - MSK -Z Europe/Astrakhan 3:12:12 - LMT 1924 May -3 - +03 1930 Jun 21 -4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2016 Mar 27 2s -4 - +04 -Z Europe/Volgograd 2:57:40 - LMT 1920 Ja 3 -3 - +03 1930 Jun 21 -4 - +04 1961 N 11 -4 R +04/+05 1988 Mar 27 2s -3 R +03/+04 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2018 O 28 2s -4 - +04 -Z Europe/Saratov 3:4:18 - LMT 1919 Jul 1 0u -3 - +03 1930 Jun 21 -4 R +04/+05 1988 Mar 27 2s -3 R +03/+04 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2016 D 4 2s -4 - +04 -Z Europe/Kirov 3:18:48 - LMT 1919 Jul 1 0u -3 - +03 1930 Jun 21 -4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s -4 - +04 1992 Mar 29 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 -Z Europe/Samara 3:20:20 - LMT 1919 Jul 1 0u -3 - +03 1930 Jun 21 -4 - +04 1935 Ja 27 -4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s -2 R +02/+03 1991 S 29 2s -3 - +03 1991 O 20 3 -4 R +04/+05 2010 Mar 28 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 -Z Europe/Ulyanovsk 3:13:36 - LMT 1919 Jul 1 0u -3 - +03 1930 Jun 21 -4 R +04/+05 1989 Mar 26 2s -3 R +03/+04 1991 Mar 31 2s -2 R +02/+03 1992 Ja 19 2s -3 R +03/+04 2011 Mar 27 2s -4 - +04 2014 O 26 2s -3 - +03 2016 Mar 27 2s -4 - +04 -Z Asia/Yekaterinburg 4:2:33 - LMT 1916 Jul 3 -3:45:5 - PMT 1919 Jul 15 4 -4 - +04 1930 Jun 21 -5 R +05/+06 1991 Mar 31 2s -4 R +04/+05 1992 Ja 19 2s -5 R +05/+06 2011 Mar 27 2s -6 - +06 2014 O 26 2s -5 - +05 -Z Asia/Omsk 4:53:30 - LMT 1919 N 14 -5 - +05 1930 Jun 21 -6 R +06/+07 1991 Mar 31 2s -5 R +05/+06 1992 Ja 19 2s -6 R +06/+07 2011 Mar 27 2s -7 - +07 2014 O 26 2s -6 - +06 -Z Asia/Barnaul 5:35 - LMT 1919 D 10 -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 1995 May 28 -6 R +06/+07 2011 Mar 27 2s -7 - +07 2014 O 26 2s -6 - +06 2016 Mar 27 2s -7 - +07 -Z Asia/Novosibirsk 5:31:40 - LMT 1919 D 14 6 -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 1993 May 23 -6 R +06/+07 2011 Mar 27 2s -7 - +07 2014 O 26 2s -6 - +06 2016 Jul 24 2s -7 - +07 -Z Asia/Tomsk 5:39:51 - LMT 1919 D 22 -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 2002 May 1 3 -6 R +06/+07 2011 Mar 27 2s -7 - +07 2014 O 26 2s -6 - +06 2016 May 29 2s -7 - +07 -Z Asia/Novokuznetsk 5:48:48 - LMT 1924 May -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 2010 Mar 28 2s -6 R +06/+07 2011 Mar 27 2s -7 - +07 -Z Asia/Krasnoyarsk 6:11:26 - LMT 1920 Ja 6 -6 - +06 1930 Jun 21 -7 R +07/+08 1991 Mar 31 2s -6 R +06/+07 1992 Ja 19 2s -7 R +07/+08 2011 Mar 27 2s -8 - +08 2014 O 26 2s -7 - +07 -Z Asia/Irkutsk 6:57:5 - LMT 1880 -6:57:5 - IMT 1920 Ja 25 -7 - +07 1930 Jun 21 -8 R +08/+09 1991 Mar 31 2s -7 R +07/+08 1992 Ja 19 2s -8 R +08/+09 2011 Mar 27 2s -9 - +09 2014 O 26 2s -8 - +08 -Z Asia/Chita 7:33:52 - LMT 1919 D 15 -8 - +08 1930 Jun 21 -9 R +09/+10 1991 Mar 31 2s -8 R +08/+09 1992 Ja 19 2s -9 R +09/+10 2011 Mar 27 2s -10 - +10 2014 O 26 2s -8 - +08 2016 Mar 27 2 -9 - +09 -Z Asia/Yakutsk 8:38:58 - LMT 1919 D 15 -8 - +08 1930 Jun 21 -9 R +09/+10 1991 Mar 31 2s -8 R +08/+09 1992 Ja 19 2s -9 R +09/+10 2011 Mar 27 2s -10 - +10 2014 O 26 2s -9 - +09 -Z Asia/Vladivostok 8:47:31 - LMT 1922 N 15 -9 - +09 1930 Jun 21 -10 R +10/+11 1991 Mar 31 2s -9 R +09/+10 1992 Ja 19 2s -10 R +10/+11 2011 Mar 27 2s -11 - +11 2014 O 26 2s -10 - +10 -Z Asia/Khandyga 9:2:13 - LMT 1919 D 15 -8 - +08 1930 Jun 21 -9 R +09/+10 1991 Mar 31 2s -8 R +08/+09 1992 Ja 19 2s -9 R +09/+10 2004 -10 R +10/+11 2011 Mar 27 2s -11 - +11 2011 S 13 0s -10 - +10 2014 O 26 2s -9 - +09 -Z Asia/Sakhalin 9:30:48 - LMT 1905 Au 23 -9 - +09 1945 Au 25 -11 R +11/+12 1991 Mar 31 2s -10 R +10/+11 1992 Ja 19 2s -11 R +11/+12 1997 Mar lastSun 2s -10 R +10/+11 2011 Mar 27 2s -11 - +11 2014 O 26 2s -10 - +10 2016 Mar 27 2s -11 - +11 -Z Asia/Magadan 10:3:12 - LMT 1924 May 2 -10 - +10 1930 Jun 21 -11 R +11/+12 1991 Mar 31 2s -10 R +10/+11 1992 Ja 19 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 2014 O 26 2s -10 - +10 2016 Ap 24 2s -11 - +11 -Z Asia/Srednekolymsk 10:14:52 - LMT 1924 May 2 -10 - +10 1930 Jun 21 -11 R +11/+12 1991 Mar 31 2s -10 R +10/+11 1992 Ja 19 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 2014 O 26 2s -11 - +11 -Z Asia/Ust-Nera 9:32:54 - LMT 1919 D 15 -8 - +08 1930 Jun 21 -9 R +09/+10 1981 Ap -11 R +11/+12 1991 Mar 31 2s -10 R +10/+11 1992 Ja 19 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 2011 S 13 0s -11 - +11 2014 O 26 2s -10 - +10 -Z Asia/Kamchatka 10:34:36 - LMT 1922 N 10 -11 - +11 1930 Jun 21 -12 R +12/+13 1991 Mar 31 2s -11 R +11/+12 1992 Ja 19 2s -12 R +12/+13 2010 Mar 28 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 -Z Asia/Anadyr 11:49:56 - LMT 1924 May 2 -12 - +12 1930 Jun 21 -13 R +13/+14 1982 Ap 1 0s -12 R +12/+13 1991 Mar 31 2s -11 R +11/+12 1992 Ja 19 2s -12 R +12/+13 2010 Mar 28 2s -11 R +11/+12 2011 Mar 27 2s -12 - +12 -Z Europe/Belgrade 1:22 - LMT 1884 -1 - CET 1941 Ap 18 23 -1 c CE%sT 1945 -1 - CET 1945 May 8 2s -1 1 CEST 1945 S 16 2s -1 - CET 1982 N 27 -1 E CE%sT -Li Europe/Belgrade Europe/Ljubljana -Li Europe/Belgrade Europe/Podgorica -Li Europe/Belgrade Europe/Sarajevo -Li Europe/Belgrade Europe/Skopje -Li Europe/Belgrade Europe/Zagreb -Li Europe/Prague Europe/Bratislava -R s 1918 o - Ap 15 23 1 S -R s 1918 1919 - O 6 24s 0 - -R s 1919 o - Ap 6 23 1 S -R s 1924 o - Ap 16 23 1 S -R s 1924 o - O 4 24s 0 - -R s 1926 o - Ap 17 23 1 S -R s 1926 1929 - O Sat>=1 24s 0 - -R s 1927 o - Ap 9 23 1 S -R s 1928 o - Ap 15 0 1 S -R s 1929 o - Ap 20 23 1 S -R s 1937 o - Jun 16 23 1 S -R s 1937 o - O 2 24s 0 - -R s 1938 o - Ap 2 23 1 S -R s 1938 o - Ap 30 23 2 M -R s 1938 o - O 2 24 1 S -R s 1939 o - O 7 24s 0 - -R s 1942 o - May 2 23 1 S -R s 1942 o - S 1 1 0 - -R s 1943 1946 - Ap Sat>=13 23 1 S -R s 1943 1944 - O Sun>=1 1 0 - -R s 1945 1946 - S lastSun 1 0 - -R s 1949 o - Ap 30 23 1 S -R s 1949 o - O 2 1 0 - -R s 1974 1975 - Ap Sat>=12 23 1 S -R s 1974 1975 - O Sun>=1 1 0 - -R s 1976 o - Mar 27 23 1 S -R s 1976 1977 - S lastSun 1 0 - -R s 1977 o - Ap 2 23 1 S -R s 1978 o - Ap 2 2s 1 S -R s 1978 o - O 1 2s 0 - -R Sp 1967 o - Jun 3 12 1 S -R Sp 1967 o - O 1 0 0 - -R Sp 1974 o - Jun 24 0 1 S -R Sp 1974 o - S 1 0 0 - -R Sp 1976 1977 - May 1 0 1 S -R Sp 1976 o - Au 1 0 0 - -R Sp 1977 o - S 28 0 0 - -R Sp 1978 o - Jun 1 0 1 S -R Sp 1978 o - Au 4 0 0 - -Z Europe/Madrid -0:14:44 - LMT 1900 D 31 23:45:16 -0 s WE%sT 1940 Mar 16 23 -1 s CE%sT 1979 -1 E CE%sT -Z Africa/Ceuta -0:21:16 - LMT 1900 D 31 23:38:44 -0 - WET 1918 May 6 23 -0 1 WEST 1918 O 7 23 -0 - WET 1924 -0 s WE%sT 1929 -0 - WET 1967 -0 Sp WE%sT 1984 Mar 16 -1 - CET 1986 -1 E CE%sT -Z Atlantic/Canary -1:1:36 - LMT 1922 Mar --1 - -01 1946 S 30 1 -0 - WET 1980 Ap 6 0s -0 1 WEST 1980 S 28 1u -0 E WE%sT -Z Europe/Stockholm 1:12:12 - LMT 1879 -1:0:14 - SET 1900 -1 - CET 1916 May 14 23 -1 1 CEST 1916 O 1 1 -1 - CET 1980 -1 E CE%sT -R CH 1941 1942 - May M>=1 1 1 S -R CH 1941 1942 - O M>=1 2 0 - -Z Europe/Zurich 0:34:8 - LMT 1853 Jul 16 -0:29:46 - BMT 1894 Jun -1 CH CE%sT 1981 -1 E CE%sT -R T 1916 o - May 1 0 1 S -R T 1916 o - O 1 0 0 - -R T 1920 o - Mar 28 0 1 S -R T 1920 o - O 25 0 0 - -R T 1921 o - Ap 3 0 1 S -R T 1921 o - O 3 0 0 - -R T 1922 o - Mar 26 0 1 S -R T 1922 o - O 8 0 0 - -R T 1924 o - May 13 0 1 S -R T 1924 1925 - O 1 0 0 - -R T 1925 o - May 1 0 1 S -R T 1940 o - Jun 30 0 1 S -R T 1940 o - O 5 0 0 - -R T 1940 o - D 1 0 1 S -R T 1941 o - S 21 0 0 - -R T 1942 o - Ap 1 0 1 S -R T 1942 o - N 1 0 0 - -R T 1945 o - Ap 2 0 1 S -R T 1945 o - O 8 0 0 - -R T 1946 o - Jun 1 0 1 S -R T 1946 o - O 1 0 0 - -R T 1947 1948 - Ap Sun>=16 0 1 S -R T 1947 1950 - O Sun>=2 0 0 - -R T 1949 o - Ap 10 0 1 S -R T 1950 o - Ap 19 0 1 S -R T 1951 o - Ap 22 0 1 S -R T 1951 o - O 8 0 0 - -R T 1962 o - Jul 15 0 1 S -R T 1962 o - O 8 0 0 - -R T 1964 o - May 15 0 1 S -R T 1964 o - O 1 0 0 - -R T 1970 1972 - May Sun>=2 0 1 S -R T 1970 1972 - O Sun>=2 0 0 - -R T 1973 o - Jun 3 1 1 S -R T 1973 o - N 4 3 0 - -R T 1974 o - Mar 31 2 1 S -R T 1974 o - N 3 5 0 - -R T 1975 o - Mar 30 0 1 S -R T 1975 1976 - O lastSun 0 0 - -R T 1976 o - Jun 1 0 1 S -R T 1977 1978 - Ap Sun>=1 0 1 S -R T 1977 o - O 16 0 0 - -R T 1979 1980 - Ap Sun>=1 3 1 S -R T 1979 1982 - O M>=11 0 0 - -R T 1981 1982 - Mar lastSun 3 1 S -R T 1983 o - Jul 31 0 1 S -R T 1983 o - O 2 0 0 - -R T 1985 o - Ap 20 0 1 S -R T 1985 o - S 28 0 0 - -R T 1986 1993 - Mar lastSun 1s 1 S -R T 1986 1995 - S lastSun 1s 0 - -R T 1994 o - Mar 20 1s 1 S -R T 1995 2006 - Mar lastSun 1s 1 S -R T 1996 2006 - O lastSun 1s 0 - -Z Europe/Istanbul 1:55:52 - LMT 1880 -1:56:56 - IMT 1910 O -2 T EE%sT 1978 O 15 -3 T +03/+04 1985 Ap 20 -2 T EE%sT 2007 -2 E EE%sT 2011 Mar 27 1u -2 - EET 2011 Mar 28 1u -2 E EE%sT 2014 Mar 30 1u -2 - EET 2014 Mar 31 1u -2 E EE%sT 2015 O 25 1u -2 1 EEST 2015 N 8 1u -2 E EE%sT 2016 S 7 -3 - +03 -Li Europe/Istanbul Asia/Istanbul -Z Europe/Kiev 2:2:4 - LMT 1880 -2:2:4 - KMT 1924 May 2 -2 - EET 1930 Jun 21 -3 - MSK 1941 S 20 -1 c CE%sT 1943 N 6 -3 R MSK/MSD 1990 Jul 1 2 -2 1 EEST 1991 S 29 3 -2 e EE%sT 1995 -2 E EE%sT -Z Europe/Uzhgorod 1:29:12 - LMT 1890 O -1 - CET 1940 -1 c CE%sT 1944 O -1 1 CEST 1944 O 26 -1 - CET 1945 Jun 29 -3 R MSK/MSD 1990 -3 - MSK 1990 Jul 1 2 -1 - CET 1991 Mar 31 3 -2 - EET 1992 -2 e EE%sT 1995 -2 E EE%sT -Z Europe/Zaporozhye 2:20:40 - LMT 1880 -2:20 - +0220 1924 May 2 -2 - EET 1930 Jun 21 -3 - MSK 1941 Au 25 -1 c CE%sT 1943 O 25 -3 R MSK/MSD 1991 Mar 31 2 -2 e EE%sT 1995 -2 E EE%sT -R u 1918 1919 - Mar lastSun 2 1 D -R u 1918 1919 - O lastSun 2 0 S -R u 1942 o - F 9 2 1 W -R u 1945 o - Au 14 23u 1 P -R u 1945 o - S lastSun 2 0 S -R u 1967 2006 - O lastSun 2 0 S -R u 1967 1973 - Ap lastSun 2 1 D -R u 1974 o - Ja 6 2 1 D -R u 1975 o - F 23 2 1 D -R u 1976 1986 - Ap lastSun 2 1 D -R u 1987 2006 - Ap Sun>=1 2 1 D -R u 2007 ma - Mar Sun>=8 2 1 D -R u 2007 ma - N Sun>=1 2 0 S -Z EST -5 - EST -Z MST -7 - MST -Z HST -10 - HST -Z EST5EDT -5 u E%sT -Z CST6CDT -6 u C%sT -Z MST7MDT -7 u M%sT -Z PST8PDT -8 u P%sT -R NY 1920 o - Mar lastSun 2 1 D -R NY 1920 o - O lastSun 2 0 S -R NY 1921 1966 - Ap lastSun 2 1 D -R NY 1921 1954 - S lastSun 2 0 S -R NY 1955 1966 - O lastSun 2 0 S -Z America/New_York -4:56:2 - LMT 1883 N 18 12:3:58 --5 u E%sT 1920 --5 NY E%sT 1942 --5 u E%sT 1946 --5 NY E%sT 1967 --5 u E%sT -R Ch 1920 o - Jun 13 2 1 D -R Ch 1920 1921 - O lastSun 2 0 S -R Ch 1921 o - Mar lastSun 2 1 D -R Ch 1922 1966 - Ap lastSun 2 1 D -R Ch 1922 1954 - S lastSun 2 0 S -R Ch 1955 1966 - O lastSun 2 0 S -Z America/Chicago -5:50:36 - LMT 1883 N 18 12:9:24 --6 u C%sT 1920 --6 Ch C%sT 1936 Mar 1 2 --5 - EST 1936 N 15 2 --6 Ch C%sT 1942 --6 u C%sT 1946 --6 Ch C%sT 1967 --6 u C%sT -Z America/North_Dakota/Center -6:45:12 - LMT 1883 N 18 12:14:48 --7 u M%sT 1992 O 25 2 --6 u C%sT -Z America/North_Dakota/New_Salem -6:45:39 - LMT 1883 N 18 12:14:21 --7 u M%sT 2003 O 26 2 --6 u C%sT -Z America/North_Dakota/Beulah -6:47:7 - LMT 1883 N 18 12:12:53 --7 u M%sT 2010 N 7 2 --6 u C%sT -R De 1920 1921 - Mar lastSun 2 1 D -R De 1920 o - O lastSun 2 0 S -R De 1921 o - May 22 2 0 S -R De 1965 1966 - Ap lastSun 2 1 D -R De 1965 1966 - O lastSun 2 0 S -Z America/Denver -6:59:56 - LMT 1883 N 18 12:0:4 --7 u M%sT 1920 --7 De M%sT 1942 --7 u M%sT 1946 --7 De M%sT 1967 --7 u M%sT -R CA 1948 o - Mar 14 2:1 1 D -R CA 1949 o - Ja 1 2 0 S -R CA 1950 1966 - Ap lastSun 1 1 D -R CA 1950 1961 - S lastSun 2 0 S -R CA 1962 1966 - O lastSun 2 0 S -Z America/Los_Angeles -7:52:58 - LMT 1883 N 18 12:7:2 --8 u P%sT 1946 --8 CA P%sT 1967 --8 u P%sT -Z America/Juneau 15:2:19 - LMT 1867 O 19 15:33:32 --8:57:41 - LMT 1900 Au 20 12 --8 - PST 1942 --8 u P%sT 1946 --8 - PST 1969 --8 u P%sT 1980 Ap 27 2 --9 u Y%sT 1980 O 26 2 --8 u P%sT 1983 O 30 2 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Sitka 14:58:47 - LMT 1867 O 19 15:30 --9:1:13 - LMT 1900 Au 20 12 --8 - PST 1942 --8 u P%sT 1946 --8 - PST 1969 --8 u P%sT 1983 O 30 2 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Metlakatla 15:13:42 - LMT 1867 O 19 15:44:55 --8:46:18 - LMT 1900 Au 20 12 --8 - PST 1942 --8 u P%sT 1946 --8 - PST 1969 --8 u P%sT 1983 O 30 2 --8 - PST 2015 N 1 2 --9 u AK%sT -Z America/Yakutat 14:41:5 - LMT 1867 O 19 15:12:18 --9:18:55 - LMT 1900 Au 20 12 --9 - YST 1942 --9 u Y%sT 1946 --9 - YST 1969 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Anchorage 14:0:24 - LMT 1867 O 19 14:31:37 --9:59:36 - LMT 1900 Au 20 12 --10 - AST 1942 --10 u A%sT 1967 Ap --10 - AHST 1969 --10 u AH%sT 1983 O 30 2 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Nome 12:58:22 - LMT 1867 O 19 13:29:35 --11:1:38 - LMT 1900 Au 20 12 --11 - NST 1942 --11 u N%sT 1946 --11 - NST 1967 Ap --11 - BST 1969 --11 u B%sT 1983 O 30 2 --9 u Y%sT 1983 N 30 --9 u AK%sT -Z America/Adak 12:13:22 - LMT 1867 O 19 12:44:35 --11:46:38 - LMT 1900 Au 20 12 --11 - NST 1942 --11 u N%sT 1946 --11 - NST 1967 Ap --11 - BST 1969 --11 u B%sT 1983 O 30 2 --10 u AH%sT 1983 N 30 --10 u H%sT -Z Pacific/Honolulu -10:31:26 - LMT 1896 Ja 13 12 --10:30 - HST 1933 Ap 30 2 --10:30 1 HDT 1933 May 21 12 --10:30 u H%sT 1947 Jun 8 2 --10 - HST -Z America/Phoenix -7:28:18 - LMT 1883 N 18 11:31:42 --7 u M%sT 1944 Ja 1 0:1 --7 - MST 1944 Ap 1 0:1 --7 u M%sT 1944 O 1 0:1 --7 - MST 1967 --7 u M%sT 1968 Mar 21 --7 - MST -Z America/Boise -7:44:49 - LMT 1883 N 18 12:15:11 --8 u P%sT 1923 May 13 2 --7 u M%sT 1974 --7 - MST 1974 F 3 2 --7 u M%sT -R In 1941 o - Jun 22 2 1 D -R In 1941 1954 - S lastSun 2 0 S -R In 1946 1954 - Ap lastSun 2 1 D -Z America/Indiana/Indianapolis -5:44:38 - LMT 1883 N 18 12:15:22 --6 u C%sT 1920 --6 In C%sT 1942 --6 u C%sT 1946 --6 In C%sT 1955 Ap 24 2 --5 - EST 1957 S 29 2 --6 - CST 1958 Ap 27 2 --5 - EST 1969 --5 u E%sT 1971 --5 - EST 2006 --5 u E%sT -R Ma 1951 o - Ap lastSun 2 1 D -R Ma 1951 o - S lastSun 2 0 S -R Ma 1954 1960 - Ap lastSun 2 1 D -R Ma 1954 1960 - S lastSun 2 0 S -Z America/Indiana/Marengo -5:45:23 - LMT 1883 N 18 12:14:37 --6 u C%sT 1951 --6 Ma C%sT 1961 Ap 30 2 --5 - EST 1969 --5 u E%sT 1974 Ja 6 2 --6 1 CDT 1974 O 27 2 --5 u E%sT 1976 --5 - EST 2006 --5 u E%sT -R V 1946 o - Ap lastSun 2 1 D -R V 1946 o - S lastSun 2 0 S -R V 1953 1954 - Ap lastSun 2 1 D -R V 1953 1959 - S lastSun 2 0 S -R V 1955 o - May 1 0 1 D -R V 1956 1963 - Ap lastSun 2 1 D -R V 1960 o - O lastSun 2 0 S -R V 1961 o - S lastSun 2 0 S -R V 1962 1963 - O lastSun 2 0 S -Z America/Indiana/Vincennes -5:50:7 - LMT 1883 N 18 12:9:53 --6 u C%sT 1946 --6 V C%sT 1964 Ap 26 2 --5 - EST 1969 --5 u E%sT 1971 --5 - EST 2006 Ap 2 2 --6 u C%sT 2007 N 4 2 --5 u E%sT -R Pe 1946 o - Ap lastSun 2 1 D -R Pe 1946 o - S lastSun 2 0 S -R Pe 1953 1954 - Ap lastSun 2 1 D -R Pe 1953 1959 - S lastSun 2 0 S -R Pe 1955 o - May 1 0 1 D -R Pe 1956 1963 - Ap lastSun 2 1 D -R Pe 1960 o - O lastSun 2 0 S -R Pe 1961 o - S lastSun 2 0 S -R Pe 1962 1963 - O lastSun 2 0 S -Z America/Indiana/Tell_City -5:47:3 - LMT 1883 N 18 12:12:57 --6 u C%sT 1946 --6 Pe C%sT 1964 Ap 26 2 --5 - EST 1969 --5 u E%sT 1971 --5 - EST 2006 Ap 2 2 --6 u C%sT -R Pi 1955 o - May 1 0 1 D -R Pi 1955 1960 - S lastSun 2 0 S -R Pi 1956 1964 - Ap lastSun 2 1 D -R Pi 1961 1964 - O lastSun 2 0 S -Z America/Indiana/Petersburg -5:49:7 - LMT 1883 N 18 12:10:53 --6 u C%sT 1955 --6 Pi C%sT 1965 Ap 25 2 --5 - EST 1966 O 30 2 --6 u C%sT 1977 O 30 2 --5 - EST 2006 Ap 2 2 --6 u C%sT 2007 N 4 2 --5 u E%sT -R St 1947 1961 - Ap lastSun 2 1 D -R St 1947 1954 - S lastSun 2 0 S -R St 1955 1956 - O lastSun 2 0 S -R St 1957 1958 - S lastSun 2 0 S -R St 1959 1961 - O lastSun 2 0 S -Z America/Indiana/Knox -5:46:30 - LMT 1883 N 18 12:13:30 --6 u C%sT 1947 --6 St C%sT 1962 Ap 29 2 --5 - EST 1963 O 27 2 --6 u C%sT 1991 O 27 2 --5 - EST 2006 Ap 2 2 --6 u C%sT -R Pu 1946 1960 - Ap lastSun 2 1 D -R Pu 1946 1954 - S lastSun 2 0 S -R Pu 1955 1956 - O lastSun 2 0 S -R Pu 1957 1960 - S lastSun 2 0 S -Z America/Indiana/Winamac -5:46:25 - LMT 1883 N 18 12:13:35 --6 u C%sT 1946 --6 Pu C%sT 1961 Ap 30 2 --5 - EST 1969 --5 u E%sT 1971 --5 - EST 2006 Ap 2 2 --6 u C%sT 2007 Mar 11 2 --5 u E%sT -Z America/Indiana/Vevay -5:40:16 - LMT 1883 N 18 12:19:44 --6 u C%sT 1954 Ap 25 2 --5 - EST 1969 --5 u E%sT 1973 --5 - EST 2006 --5 u E%sT -R v 1921 o - May 1 2 1 D -R v 1921 o - S 1 2 0 S -R v 1941 1961 - Ap lastSun 2 1 D -R v 1941 o - S lastSun 2 0 S -R v 1946 o - Jun 2 2 0 S -R v 1950 1955 - S lastSun 2 0 S -R v 1956 1960 - O lastSun 2 0 S -Z America/Kentucky/Louisville -5:43:2 - LMT 1883 N 18 12:16:58 --6 u C%sT 1921 --6 v C%sT 1942 --6 u C%sT 1946 --6 v C%sT 1961 Jul 23 2 --5 - EST 1968 --5 u E%sT 1974 Ja 6 2 --6 1 CDT 1974 O 27 2 --5 u E%sT -Z America/Kentucky/Monticello -5:39:24 - LMT 1883 N 18 12:20:36 --6 u C%sT 1946 --6 - CST 1968 --6 u C%sT 2000 O 29 2 --5 u E%sT -R Dt 1948 o - Ap lastSun 2 1 D -R Dt 1948 o - S lastSun 2 0 S -Z America/Detroit -5:32:11 - LMT 1905 --6 - CST 1915 May 15 2 --5 - EST 1942 --5 u E%sT 1946 --5 Dt E%sT 1973 --5 u E%sT 1975 --5 - EST 1975 Ap 27 2 --5 u E%sT -R Me 1946 o - Ap lastSun 2 1 D -R Me 1946 o - S lastSun 2 0 S -R Me 1966 o - Ap lastSun 2 1 D -R Me 1966 o - O lastSun 2 0 S -Z America/Menominee -5:50:27 - LMT 1885 S 18 12 --6 u C%sT 1946 --6 Me C%sT 1969 Ap 27 2 --5 - EST 1973 Ap 29 2 --6 u C%sT -R C 1918 o - Ap 14 2 1 D -R C 1918 o - O 27 2 0 S -R C 1942 o - F 9 2 1 W -R C 1945 o - Au 14 23u 1 P -R C 1945 o - S 30 2 0 S -R C 1974 1986 - Ap lastSun 2 1 D -R C 1974 2006 - O lastSun 2 0 S -R C 1987 2006 - Ap Sun>=1 2 1 D -R C 2007 ma - Mar Sun>=8 2 1 D -R C 2007 ma - N Sun>=1 2 0 S -R j 1917 o - Ap 8 2 1 D -R j 1917 o - S 17 2 0 S -R j 1919 o - May 5 23 1 D -R j 1919 o - Au 12 23 0 S -R j 1920 1935 - May Sun>=1 23 1 D -R j 1920 1935 - O lastSun 23 0 S -R j 1936 1941 - May M>=9 0 1 D -R j 1936 1941 - O M>=2 0 0 S -R j 1946 1950 - May Sun>=8 2 1 D -R j 1946 1950 - O Sun>=2 2 0 S -R j 1951 1986 - Ap lastSun 2 1 D -R j 1951 1959 - S lastSun 2 0 S -R j 1960 1986 - O lastSun 2 0 S -R j 1987 o - Ap Sun>=1 0:1 1 D -R j 1987 2006 - O lastSun 0:1 0 S -R j 1988 o - Ap Sun>=1 0:1 2 DD -R j 1989 2006 - Ap Sun>=1 0:1 1 D -R j 2007 2011 - Mar Sun>=8 0:1 1 D -R j 2007 2010 - N Sun>=1 0:1 0 S -Z America/St_Johns -3:30:52 - LMT 1884 --3:30:52 j N%sT 1918 --3:30:52 C N%sT 1919 --3:30:52 j N%sT 1935 Mar 30 --3:30 j N%sT 1942 May 11 --3:30 C N%sT 1946 --3:30 j N%sT 2011 N --3:30 C N%sT -Z America/Goose_Bay -4:1:40 - LMT 1884 --3:30:52 - NST 1918 --3:30:52 C N%sT 1919 --3:30:52 - NST 1935 Mar 30 --3:30 - NST 1936 --3:30 j N%sT 1942 May 11 --3:30 C N%sT 1946 --3:30 j N%sT 1966 Mar 15 2 --4 j A%sT 2011 N --4 C A%sT -R H 1916 o - Ap 1 0 1 D -R H 1916 o - O 1 0 0 S -R H 1920 o - May 9 0 1 D -R H 1920 o - Au 29 0 0 S -R H 1921 o - May 6 0 1 D -R H 1921 1922 - S 5 0 0 S -R H 1922 o - Ap 30 0 1 D -R H 1923 1925 - May Sun>=1 0 1 D -R H 1923 o - S 4 0 0 S -R H 1924 o - S 15 0 0 S -R H 1925 o - S 28 0 0 S -R H 1926 o - May 16 0 1 D -R H 1926 o - S 13 0 0 S -R H 1927 o - May 1 0 1 D -R H 1927 o - S 26 0 0 S -R H 1928 1931 - May Sun>=8 0 1 D -R H 1928 o - S 9 0 0 S -R H 1929 o - S 3 0 0 S -R H 1930 o - S 15 0 0 S -R H 1931 1932 - S M>=24 0 0 S -R H 1932 o - May 1 0 1 D -R H 1933 o - Ap 30 0 1 D -R H 1933 o - O 2 0 0 S -R H 1934 o - May 20 0 1 D -R H 1934 o - S 16 0 0 S -R H 1935 o - Jun 2 0 1 D -R H 1935 o - S 30 0 0 S -R H 1936 o - Jun 1 0 1 D -R H 1936 o - S 14 0 0 S -R H 1937 1938 - May Sun>=1 0 1 D -R H 1937 1941 - S M>=24 0 0 S -R H 1939 o - May 28 0 1 D -R H 1940 1941 - May Sun>=1 0 1 D -R H 1946 1949 - Ap lastSun 2 1 D -R H 1946 1949 - S lastSun 2 0 S -R H 1951 1954 - Ap lastSun 2 1 D -R H 1951 1954 - S lastSun 2 0 S -R H 1956 1959 - Ap lastSun 2 1 D -R H 1956 1959 - S lastSun 2 0 S -R H 1962 1973 - Ap lastSun 2 1 D -R H 1962 1973 - O lastSun 2 0 S -Z America/Halifax -4:14:24 - LMT 1902 Jun 15 --4 H A%sT 1918 --4 C A%sT 1919 --4 H A%sT 1942 F 9 2s --4 C A%sT 1946 --4 H A%sT 1974 --4 C A%sT -Z America/Glace_Bay -3:59:48 - LMT 1902 Jun 15 --4 C A%sT 1953 --4 H A%sT 1954 --4 - AST 1972 --4 H A%sT 1974 --4 C A%sT -R o 1933 1935 - Jun Sun>=8 1 1 D -R o 1933 1935 - S Sun>=8 1 0 S -R o 1936 1938 - Jun Sun>=1 1 1 D -R o 1936 1938 - S Sun>=1 1 0 S -R o 1939 o - May 27 1 1 D -R o 1939 1941 - S Sat>=21 1 0 S -R o 1940 o - May 19 1 1 D -R o 1941 o - May 4 1 1 D -R o 1946 1972 - Ap lastSun 2 1 D -R o 1946 1956 - S lastSun 2 0 S -R o 1957 1972 - O lastSun 2 0 S -R o 1993 2006 - Ap Sun>=1 0:1 1 D -R o 1993 2006 - O lastSun 0:1 0 S -Z America/Moncton -4:19:8 - LMT 1883 D 9 --5 - EST 1902 Jun 15 --4 C A%sT 1933 --4 o A%sT 1942 --4 C A%sT 1946 --4 o A%sT 1973 --4 C A%sT 1993 --4 o A%sT 2007 --4 C A%sT -Z America/Blanc-Sablon -3:48:28 - LMT 1884 --4 C A%sT 1970 --4 - AST -R t 1919 o - Mar 30 23:30 1 D -R t 1919 o - O 26 0 0 S -R t 1920 o - May 2 2 1 D -R t 1920 o - S 26 0 0 S -R t 1921 o - May 15 2 1 D -R t 1921 o - S 15 2 0 S -R t 1922 1923 - May Sun>=8 2 1 D -R t 1922 1926 - S Sun>=15 2 0 S -R t 1924 1927 - May Sun>=1 2 1 D -R t 1927 1932 - S lastSun 2 0 S -R t 1928 1931 - Ap lastSun 2 1 D -R t 1932 o - May 1 2 1 D -R t 1933 1940 - Ap lastSun 2 1 D -R t 1933 o - O 1 2 0 S -R t 1934 1939 - S lastSun 2 0 S -R t 1945 1946 - S lastSun 2 0 S -R t 1946 o - Ap lastSun 2 1 D -R t 1947 1949 - Ap lastSun 0 1 D -R t 1947 1948 - S lastSun 0 0 S -R t 1949 o - N lastSun 0 0 S -R t 1950 1973 - Ap lastSun 2 1 D -R t 1950 o - N lastSun 2 0 S -R t 1951 1956 - S lastSun 2 0 S -R t 1957 1973 - O lastSun 2 0 S -Z America/Toronto -5:17:32 - LMT 1895 --5 C E%sT 1919 --5 t E%sT 1942 F 9 2s --5 C E%sT 1946 --5 t E%sT 1974 --5 C E%sT -Z America/Thunder_Bay -5:57 - LMT 1895 --6 - CST 1910 --5 - EST 1942 --5 C E%sT 1970 --5 t E%sT 1973 --5 - EST 1974 --5 C E%sT -Z America/Nipigon -5:53:4 - LMT 1895 --5 C E%sT 1940 S 29 --5 1 EDT 1942 F 9 2s --5 C E%sT -Z America/Rainy_River -6:18:16 - LMT 1895 --6 C C%sT 1940 S 29 --6 1 CDT 1942 F 9 2s --6 C C%sT -Z America/Atikokan -6:6:28 - LMT 1895 --6 C C%sT 1940 S 29 --6 1 CDT 1942 F 9 2s --6 C C%sT 1945 S 30 2 --5 - EST -R W 1916 o - Ap 23 0 1 D -R W 1916 o - S 17 0 0 S -R W 1918 o - Ap 14 2 1 D -R W 1918 o - O 27 2 0 S -R W 1937 o - May 16 2 1 D -R W 1937 o - S 26 2 0 S -R W 1942 o - F 9 2 1 W -R W 1945 o - Au 14 23u 1 P -R W 1945 o - S lastSun 2 0 S -R W 1946 o - May 12 2 1 D -R W 1946 o - O 13 2 0 S -R W 1947 1949 - Ap lastSun 2 1 D -R W 1947 1949 - S lastSun 2 0 S -R W 1950 o - May 1 2 1 D -R W 1950 o - S 30 2 0 S -R W 1951 1960 - Ap lastSun 2 1 D -R W 1951 1958 - S lastSun 2 0 S -R W 1959 o - O lastSun 2 0 S -R W 1960 o - S lastSun 2 0 S -R W 1963 o - Ap lastSun 2 1 D -R W 1963 o - S 22 2 0 S -R W 1966 1986 - Ap lastSun 2s 1 D -R W 1966 2005 - O lastSun 2s 0 S -R W 1987 2005 - Ap Sun>=1 2s 1 D -Z America/Winnipeg -6:28:36 - LMT 1887 Jul 16 --6 W C%sT 2006 --6 C C%sT -R r 1918 o - Ap 14 2 1 D -R r 1918 o - O 27 2 0 S -R r 1930 1934 - May Sun>=1 0 1 D -R r 1930 1934 - O Sun>=1 0 0 S -R r 1937 1941 - Ap Sun>=8 0 1 D -R r 1937 o - O Sun>=8 0 0 S -R r 1938 o - O Sun>=1 0 0 S -R r 1939 1941 - O Sun>=8 0 0 S -R r 1942 o - F 9 2 1 W -R r 1945 o - Au 14 23u 1 P -R r 1945 o - S lastSun 2 0 S -R r 1946 o - Ap Sun>=8 2 1 D -R r 1946 o - O Sun>=8 2 0 S -R r 1947 1957 - Ap lastSun 2 1 D -R r 1947 1957 - S lastSun 2 0 S -R r 1959 o - Ap lastSun 2 1 D -R r 1959 o - O lastSun 2 0 S -R Sw 1957 o - Ap lastSun 2 1 D -R Sw 1957 o - O lastSun 2 0 S -R Sw 1959 1961 - Ap lastSun 2 1 D -R Sw 1959 o - O lastSun 2 0 S -R Sw 1960 1961 - S lastSun 2 0 S -Z America/Regina -6:58:36 - LMT 1905 S --7 r M%sT 1960 Ap lastSun 2 --6 - CST -Z America/Swift_Current -7:11:20 - LMT 1905 S --7 C M%sT 1946 Ap lastSun 2 --7 r M%sT 1950 --7 Sw M%sT 1972 Ap lastSun 2 --6 - CST -R Ed 1918 1919 - Ap Sun>=8 2 1 D -R Ed 1918 o - O 27 2 0 S -R Ed 1919 o - May 27 2 0 S -R Ed 1920 1923 - Ap lastSun 2 1 D -R Ed 1920 o - O lastSun 2 0 S -R Ed 1921 1923 - S lastSun 2 0 S -R Ed 1942 o - F 9 2 1 W -R Ed 1945 o - Au 14 23u 1 P -R Ed 1945 o - S lastSun 2 0 S -R Ed 1947 o - Ap lastSun 2 1 D -R Ed 1947 o - S lastSun 2 0 S -R Ed 1967 o - Ap lastSun 2 1 D -R Ed 1967 o - O lastSun 2 0 S -R Ed 1969 o - Ap lastSun 2 1 D -R Ed 1969 o - O lastSun 2 0 S -R Ed 1972 1986 - Ap lastSun 2 1 D -R Ed 1972 2006 - O lastSun 2 0 S -Z America/Edmonton -7:33:52 - LMT 1906 S --7 Ed M%sT 1987 --7 C M%sT -R Va 1918 o - Ap 14 2 1 D -R Va 1918 o - O 27 2 0 S -R Va 1942 o - F 9 2 1 W -R Va 1945 o - Au 14 23u 1 P -R Va 1945 o - S 30 2 0 S -R Va 1946 1986 - Ap lastSun 2 1 D -R Va 1946 o - O 13 2 0 S -R Va 1947 1961 - S lastSun 2 0 S -R Va 1962 2006 - O lastSun 2 0 S -Z America/Vancouver -8:12:28 - LMT 1884 --8 Va P%sT 1987 --8 C P%sT -Z America/Dawson_Creek -8:0:56 - LMT 1884 --8 C P%sT 1947 --8 Va P%sT 1972 Au 30 2 --7 - MST -Z America/Fort_Nelson -8:10:47 - LMT 1884 --8 Va P%sT 1946 --8 - PST 1947 --8 Va P%sT 1987 --8 C P%sT 2015 Mar 8 2 --7 - MST -Z America/Creston -7:46:4 - LMT 1884 --7 - MST 1916 O --8 - PST 1918 Jun 2 --7 - MST -R Y 1918 o - Ap 14 2 1 D -R Y 1918 o - O 27 2 0 S -R Y 1919 o - May 25 2 1 D -R Y 1919 o - N 1 0 0 S -R Y 1942 o - F 9 2 1 W -R Y 1945 o - Au 14 23u 1 P -R Y 1945 o - S 30 2 0 S -R Y 1965 o - Ap lastSun 0 2 DD -R Y 1965 o - O lastSun 2 0 S -R Y 1980 1986 - Ap lastSun 2 1 D -R Y 1980 2006 - O lastSun 2 0 S -R Y 1987 2006 - Ap Sun>=1 2 1 D -Z America/Pangnirtung 0 - -00 1921 --4 Y A%sT 1995 Ap Sun>=1 2 --5 C E%sT 1999 O 31 2 --6 C C%sT 2000 O 29 2 --5 C E%sT -Z America/Iqaluit 0 - -00 1942 Au --5 Y E%sT 1999 O 31 2 --6 C C%sT 2000 O 29 2 --5 C E%sT -Z America/Resolute 0 - -00 1947 Au 31 --6 Y C%sT 2000 O 29 2 --5 - EST 2001 Ap 1 3 --6 C C%sT 2006 O 29 2 --5 - EST 2007 Mar 11 3 --6 C C%sT -Z America/Rankin_Inlet 0 - -00 1957 --6 Y C%sT 2000 O 29 2 --5 - EST 2001 Ap 1 3 --6 C C%sT -Z America/Cambridge_Bay 0 - -00 1920 --7 Y M%sT 1999 O 31 2 --6 C C%sT 2000 O 29 2 --5 - EST 2000 N 5 --6 - CST 2001 Ap 1 3 --7 C M%sT -Z America/Yellowknife 0 - -00 1935 --7 Y M%sT 1980 --7 C M%sT -Z America/Inuvik 0 - -00 1953 --8 Y P%sT 1979 Ap lastSun 2 --7 Y M%sT 1980 --7 C M%sT -Z America/Whitehorse -9:0:12 - LMT 1900 Au 20 --9 Y Y%sT 1967 May 28 --8 Y P%sT 1980 --8 C P%sT -Z America/Dawson -9:17:40 - LMT 1900 Au 20 --9 Y Y%sT 1973 O 28 --8 Y P%sT 1980 --8 C P%sT -R m 1939 o - F 5 0 1 D -R m 1939 o - Jun 25 0 0 S -R m 1940 o - D 9 0 1 D -R m 1941 o - Ap 1 0 0 S -R m 1943 o - D 16 0 1 W -R m 1944 o - May 1 0 0 S -R m 1950 o - F 12 0 1 D -R m 1950 o - Jul 30 0 0 S -R m 1996 2000 - Ap Sun>=1 2 1 D -R m 1996 2000 - O lastSun 2 0 S -R m 2001 o - May Sun>=1 2 1 D -R m 2001 o - S lastSun 2 0 S -R m 2002 ma - Ap Sun>=1 2 1 D -R m 2002 ma - O lastSun 2 0 S -Z America/Cancun -5:47:4 - LMT 1922 Ja 1 0:12:56 --6 - CST 1981 D 23 --5 m E%sT 1998 Au 2 2 --6 m C%sT 2015 F 1 2 --5 - EST -Z America/Merida -5:58:28 - LMT 1922 Ja 1 0:1:32 --6 - CST 1981 D 23 --5 - EST 1982 D 2 --6 m C%sT -Z America/Matamoros -6:40 - LMT 1921 D 31 23:20 --6 - CST 1988 --6 u C%sT 1989 --6 m C%sT 2010 --6 u C%sT -Z America/Monterrey -6:41:16 - LMT 1921 D 31 23:18:44 --6 - CST 1988 --6 u C%sT 1989 --6 m C%sT -Z America/Mexico_City -6:36:36 - LMT 1922 Ja 1 0:23:24 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 m C%sT 2001 S 30 2 --6 - CST 2002 F 20 --6 m C%sT -Z America/Ojinaga -6:57:40 - LMT 1922 Ja 1 0:2:20 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1996 --6 m C%sT 1998 --6 - CST 1998 Ap Sun>=1 3 --7 m M%sT 2010 --7 u M%sT -Z America/Chihuahua -7:4:20 - LMT 1921 D 31 23:55:40 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1996 --6 m C%sT 1998 --6 - CST 1998 Ap Sun>=1 3 --7 m M%sT -Z America/Hermosillo -7:23:52 - LMT 1921 D 31 23:36:8 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1942 Ap 24 --7 - MST 1949 Ja 14 --8 - PST 1970 --7 m M%sT 1999 --7 - MST -Z America/Mazatlan -7:5:40 - LMT 1921 D 31 23:54:20 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1942 Ap 24 --7 - MST 1949 Ja 14 --8 - PST 1970 --7 m M%sT -Z America/Bahia_Banderas -7:1 - LMT 1921 D 31 23:59 --7 - MST 1927 Jun 10 23 --6 - CST 1930 N 15 --7 - MST 1931 May 1 23 --6 - CST 1931 O --7 - MST 1932 Ap --6 - CST 1942 Ap 24 --7 - MST 1949 Ja 14 --8 - PST 1970 --7 m M%sT 2010 Ap 4 2 --6 m C%sT -Z America/Tijuana -7:48:4 - LMT 1922 Ja 1 0:11:56 --7 - MST 1924 --8 - PST 1927 Jun 10 23 --7 - MST 1930 N 15 --8 - PST 1931 Ap --8 1 PDT 1931 S 30 --8 - PST 1942 Ap 24 --8 1 PWT 1945 Au 14 23u --8 1 PPT 1945 N 12 --8 - PST 1948 Ap 5 --8 1 PDT 1949 Ja 14 --8 - PST 1954 --8 CA P%sT 1961 --8 - PST 1976 --8 u P%sT 1996 --8 m P%sT 2001 --8 u P%sT 2002 F 20 --8 m P%sT 2010 --8 u P%sT -R BS 1964 1975 - O lastSun 2 0 S -R BS 1964 1975 - Ap lastSun 2 1 D -Z America/Nassau -5:9:30 - LMT 1912 Mar 2 --5 BS E%sT 1976 --5 u E%sT -R BB 1977 o - Jun 12 2 1 D -R BB 1977 1978 - O Sun>=1 2 0 S -R BB 1978 1980 - Ap Sun>=15 2 1 D -R BB 1979 o - S 30 2 0 S -R BB 1980 o - S 25 2 0 S -Z America/Barbados -3:58:29 - LMT 1924 --3:58:29 - BMT 1932 --4 BB A%sT -R BZ 1918 1942 - O Sun>=2 0 0:30 -0530 -R BZ 1919 1943 - F Sun>=9 0 0 CST -R BZ 1973 o - D 5 0 1 CDT -R BZ 1974 o - F 9 0 0 CST -R BZ 1982 o - D 18 0 1 CDT -R BZ 1983 o - F 12 0 0 CST -Z America/Belize -5:52:48 - LMT 1912 Ap --6 BZ %s -Z Atlantic/Bermuda -4:19:18 - LMT 1930 Ja 1 2 --4 - AST 1974 Ap 28 2 --4 C A%sT 1976 --4 u A%sT -R CR 1979 1980 - F lastSun 0 1 D -R CR 1979 1980 - Jun Sun>=1 0 0 S -R CR 1991 1992 - Ja Sat>=15 0 1 D -R CR 1991 o - Jul 1 0 0 S -R CR 1992 o - Mar 15 0 0 S -Z America/Costa_Rica -5:36:13 - LMT 1890 --5:36:13 - SJMT 1921 Ja 15 --6 CR C%sT -R Q 1928 o - Jun 10 0 1 D -R Q 1928 o - O 10 0 0 S -R Q 1940 1942 - Jun Sun>=1 0 1 D -R Q 1940 1942 - S Sun>=1 0 0 S -R Q 1945 1946 - Jun Sun>=1 0 1 D -R Q 1945 1946 - S Sun>=1 0 0 S -R Q 1965 o - Jun 1 0 1 D -R Q 1965 o - S 30 0 0 S -R Q 1966 o - May 29 0 1 D -R Q 1966 o - O 2 0 0 S -R Q 1967 o - Ap 8 0 1 D -R Q 1967 1968 - S Sun>=8 0 0 S -R Q 1968 o - Ap 14 0 1 D -R Q 1969 1977 - Ap lastSun 0 1 D -R Q 1969 1971 - O lastSun 0 0 S -R Q 1972 1974 - O 8 0 0 S -R Q 1975 1977 - O lastSun 0 0 S -R Q 1978 o - May 7 0 1 D -R Q 1978 1990 - O Sun>=8 0 0 S -R Q 1979 1980 - Mar Sun>=15 0 1 D -R Q 1981 1985 - May Sun>=5 0 1 D -R Q 1986 1989 - Mar Sun>=14 0 1 D -R Q 1990 1997 - Ap Sun>=1 0 1 D -R Q 1991 1995 - O Sun>=8 0s 0 S -R Q 1996 o - O 6 0s 0 S -R Q 1997 o - O 12 0s 0 S -R Q 1998 1999 - Mar lastSun 0s 1 D -R Q 1998 2003 - O lastSun 0s 0 S -R Q 2000 2003 - Ap Sun>=1 0s 1 D -R Q 2004 o - Mar lastSun 0s 1 D -R Q 2006 2010 - O lastSun 0s 0 S -R Q 2007 o - Mar Sun>=8 0s 1 D -R Q 2008 o - Mar Sun>=15 0s 1 D -R Q 2009 2010 - Mar Sun>=8 0s 1 D -R Q 2011 o - Mar Sun>=15 0s 1 D -R Q 2011 o - N 13 0s 0 S -R Q 2012 o - Ap 1 0s 1 D -R Q 2012 ma - N Sun>=1 0s 0 S -R Q 2013 ma - Mar Sun>=8 0s 1 D -Z America/Havana -5:29:28 - LMT 1890 --5:29:36 - HMT 1925 Jul 19 12 --5 Q C%sT -R DO 1966 o - O 30 0 1 EDT -R DO 1967 o - F 28 0 0 EST -R DO 1969 1973 - O lastSun 0 0:30 -0430 -R DO 1970 o - F 21 0 0 EST -R DO 1971 o - Ja 20 0 0 EST -R DO 1972 1974 - Ja 21 0 0 EST -Z America/Santo_Domingo -4:39:36 - LMT 1890 --4:40 - SDMT 1933 Ap 1 12 --5 DO %s 1974 O 27 --4 - AST 2000 O 29 2 --5 u E%sT 2000 D 3 1 --4 - AST -R SV 1987 1988 - May Sun>=1 0 1 D -R SV 1987 1988 - S lastSun 0 0 S -Z America/El_Salvador -5:56:48 - LMT 1921 --6 SV C%sT -R GT 1973 o - N 25 0 1 D -R GT 1974 o - F 24 0 0 S -R GT 1983 o - May 21 0 1 D -R GT 1983 o - S 22 0 0 S -R GT 1991 o - Mar 23 0 1 D -R GT 1991 o - S 7 0 0 S -R GT 2006 o - Ap 30 0 1 D -R GT 2006 o - O 1 0 0 S -Z America/Guatemala -6:2:4 - LMT 1918 O 5 --6 GT C%sT -R HT 1983 o - May 8 0 1 D -R HT 1984 1987 - Ap lastSun 0 1 D -R HT 1983 1987 - O lastSun 0 0 S -R HT 1988 1997 - Ap Sun>=1 1s 1 D -R HT 1988 1997 - O lastSun 1s 0 S -R HT 2005 2006 - Ap Sun>=1 0 1 D -R HT 2005 2006 - O lastSun 0 0 S -R HT 2012 2015 - Mar Sun>=8 2 1 D -R HT 2012 2015 - N Sun>=1 2 0 S -R HT 2017 ma - Mar Sun>=8 2 1 D -R HT 2017 ma - N Sun>=1 2 0 S -Z America/Port-au-Prince -4:49:20 - LMT 1890 --4:49 - PPMT 1917 Ja 24 12 --5 HT E%sT -R HN 1987 1988 - May Sun>=1 0 1 D -R HN 1987 1988 - S lastSun 0 0 S -R HN 2006 o - May Sun>=1 0 1 D -R HN 2006 o - Au M>=1 0 0 S -Z America/Tegucigalpa -5:48:52 - LMT 1921 Ap --6 HN C%sT -Z America/Jamaica -5:7:10 - LMT 1890 --5:7:10 - KMT 1912 F --5 - EST 1974 --5 u E%sT 1984 --5 - EST -Z America/Martinique -4:4:20 - LMT 1890 --4:4:20 - FFMT 1911 May --4 - AST 1980 Ap 6 --4 1 ADT 1980 S 28 --4 - AST -R NI 1979 1980 - Mar Sun>=16 0 1 D -R NI 1979 1980 - Jun M>=23 0 0 S -R NI 2005 o - Ap 10 0 1 D -R NI 2005 o - O Sun>=1 0 0 S -R NI 2006 o - Ap 30 2 1 D -R NI 2006 o - O Sun>=1 1 0 S -Z America/Managua -5:45:8 - LMT 1890 --5:45:12 - MMT 1934 Jun 23 --6 - CST 1973 May --5 - EST 1975 F 16 --6 NI C%sT 1992 Ja 1 4 --5 - EST 1992 S 24 --6 - CST 1993 --5 - EST 1997 --6 NI C%sT -Z America/Panama -5:18:8 - LMT 1890 --5:19:36 - CMT 1908 Ap 22 --5 - EST -Li America/Panama America/Cayman -Z America/Puerto_Rico -4:24:25 - LMT 1899 Mar 28 12 --4 - AST 1942 May 3 --4 u A%sT 1946 --4 - AST -Z America/Miquelon -3:44:40 - LMT 1911 May 15 --4 - AST 1980 May --3 - -03 1987 --3 C -03/-02 -Z America/Grand_Turk -4:44:32 - LMT 1890 --5:7:10 - KMT 1912 F --5 - EST 1979 --5 u E%sT 2015 N Sun>=1 2 --4 - AST 2018 Mar 11 3 --5 u E%sT -R A 1930 o - D 1 0 1 - -R A 1931 o - Ap 1 0 0 - -R A 1931 o - O 15 0 1 - -R A 1932 1940 - Mar 1 0 0 - -R A 1932 1939 - N 1 0 1 - -R A 1940 o - Jul 1 0 1 - -R A 1941 o - Jun 15 0 0 - -R A 1941 o - O 15 0 1 - -R A 1943 o - Au 1 0 0 - -R A 1943 o - O 15 0 1 - -R A 1946 o - Mar 1 0 0 - -R A 1946 o - O 1 0 1 - -R A 1963 o - O 1 0 0 - -R A 1963 o - D 15 0 1 - -R A 1964 1966 - Mar 1 0 0 - -R A 1964 1966 - O 15 0 1 - -R A 1967 o - Ap 2 0 0 - -R A 1967 1968 - O Sun>=1 0 1 - -R A 1968 1969 - Ap Sun>=1 0 0 - -R A 1974 o - Ja 23 0 1 - -R A 1974 o - May 1 0 0 - -R A 1988 o - D 1 0 1 - -R A 1989 1993 - Mar Sun>=1 0 0 - -R A 1989 1992 - O Sun>=15 0 1 - -R A 1999 o - O Sun>=1 0 1 - -R A 2000 o - Mar 3 0 0 - -R A 2007 o - D 30 0 1 - -R A 2008 2009 - Mar Sun>=15 0 0 - -R A 2008 o - O Sun>=15 0 1 - -Z America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 A -03/-02 -Z America/Argentina/Cordoba -4:16:48 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar 3 --4 - -04 1991 O 20 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 A -03/-02 -Z America/Argentina/Salta -4:21:40 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar 3 --4 - -04 1991 O 20 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Tucuman -4:20:52 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar 3 --4 - -04 1991 O 20 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 Jun --4 - -04 2004 Jun 13 --3 A -03/-02 -Z America/Argentina/La_Rioja -4:27:24 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar --4 - -04 1991 May 7 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 Jun --4 - -04 2004 Jun 20 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/San_Juan -4:34:4 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar --4 - -04 1991 May 7 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 May 31 --4 - -04 2004 Jul 25 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Jujuy -4:21:12 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1990 Mar 4 --4 - -04 1990 O 28 --4 1 -03 1991 Mar 17 --4 - -04 1991 O 6 --3 1 -02 1992 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Catamarca -4:23:8 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1991 Mar 3 --4 - -04 1991 O 20 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 Jun --4 - -04 2004 Jun 20 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Mendoza -4:35:16 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1990 Mar 4 --4 - -04 1990 O 15 --4 1 -03 1991 Mar --4 - -04 1991 O 15 --4 1 -03 1992 Mar --4 - -04 1992 O 18 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 May 23 --4 - -04 2004 S 26 --3 A -03/-02 2008 O 18 --3 - -03 -R Sa 2008 2009 - Mar Sun>=8 0 0 - -R Sa 2007 2008 - O Sun>=8 0 1 - -Z America/Argentina/San_Luis -4:25:24 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1990 --3 1 -02 1990 Mar 14 --4 - -04 1990 O 15 --4 1 -03 1991 Mar --4 - -04 1991 Jun --3 - -03 1999 O 3 --4 1 -03 2000 Mar 3 --3 - -03 2004 May 31 --4 - -04 2004 Jul 25 --3 A -03/-02 2008 Ja 21 --4 Sa -04/-03 2009 O 11 --3 - -03 -Z America/Argentina/Rio_Gallegos -4:36:52 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 Jun --4 - -04 2004 Jun 20 --3 A -03/-02 2008 O 18 --3 - -03 -Z America/Argentina/Ushuaia -4:33:12 - LMT 1894 O 31 --4:16:48 - CMT 1920 May --4 - -04 1930 D --4 A -04/-03 1969 O 5 --3 A -03/-02 1999 O 3 --4 A -04/-03 2000 Mar 3 --3 - -03 2004 May 30 --4 - -04 2004 Jun 20 --3 A -03/-02 2008 O 18 --3 - -03 -Li America/Curacao America/Aruba -Z America/La_Paz -4:32:36 - LMT 1890 --4:32:36 - CMT 1931 O 15 --4:32:36 1 BST 1932 Mar 21 --4 - -04 -R B 1931 o - O 3 11 1 - -R B 1932 1933 - Ap 1 0 0 - -R B 1932 o - O 3 0 1 - -R B 1949 1952 - D 1 0 1 - -R B 1950 o - Ap 16 1 0 - -R B 1951 1952 - Ap 1 0 0 - -R B 1953 o - Mar 1 0 0 - -R B 1963 o - D 9 0 1 - -R B 1964 o - Mar 1 0 0 - -R B 1965 o - Ja 31 0 1 - -R B 1965 o - Mar 31 0 0 - -R B 1965 o - D 1 0 1 - -R B 1966 1968 - Mar 1 0 0 - -R B 1966 1967 - N 1 0 1 - -R B 1985 o - N 2 0 1 - -R B 1986 o - Mar 15 0 0 - -R B 1986 o - O 25 0 1 - -R B 1987 o - F 14 0 0 - -R B 1987 o - O 25 0 1 - -R B 1988 o - F 7 0 0 - -R B 1988 o - O 16 0 1 - -R B 1989 o - Ja 29 0 0 - -R B 1989 o - O 15 0 1 - -R B 1990 o - F 11 0 0 - -R B 1990 o - O 21 0 1 - -R B 1991 o - F 17 0 0 - -R B 1991 o - O 20 0 1 - -R B 1992 o - F 9 0 0 - -R B 1992 o - O 25 0 1 - -R B 1993 o - Ja 31 0 0 - -R B 1993 1995 - O Sun>=11 0 1 - -R B 1994 1995 - F Sun>=15 0 0 - -R B 1996 o - F 11 0 0 - -R B 1996 o - O 6 0 1 - -R B 1997 o - F 16 0 0 - -R B 1997 o - O 6 0 1 - -R B 1998 o - Mar 1 0 0 - -R B 1998 o - O 11 0 1 - -R B 1999 o - F 21 0 0 - -R B 1999 o - O 3 0 1 - -R B 2000 o - F 27 0 0 - -R B 2000 2001 - O Sun>=8 0 1 - -R B 2001 2006 - F Sun>=15 0 0 - -R B 2002 o - N 3 0 1 - -R B 2003 o - O 19 0 1 - -R B 2004 o - N 2 0 1 - -R B 2005 o - O 16 0 1 - -R B 2006 o - N 5 0 1 - -R B 2007 o - F 25 0 0 - -R B 2007 o - O Sun>=8 0 1 - -R B 2008 2017 - O Sun>=15 0 1 - -R B 2008 2011 - F Sun>=15 0 0 - -R B 2012 o - F Sun>=22 0 0 - -R B 2013 2014 - F Sun>=15 0 0 - -R B 2015 o - F Sun>=22 0 0 - -R B 2016 2022 - F Sun>=15 0 0 - -R B 2018 ma - N Sun>=1 0 1 - -R B 2023 o - F Sun>=22 0 0 - -R B 2024 2025 - F Sun>=15 0 0 - -R B 2026 o - F Sun>=22 0 0 - -R B 2027 2033 - F Sun>=15 0 0 - -R B 2034 o - F Sun>=22 0 0 - -R B 2035 2036 - F Sun>=15 0 0 - -R B 2037 o - F Sun>=22 0 0 - -R B 2038 ma - F Sun>=15 0 0 - -Z America/Noronha -2:9:40 - LMT 1914 --2 B -02/-01 1990 S 17 --2 - -02 1999 S 30 --2 B -02/-01 2000 O 15 --2 - -02 2001 S 13 --2 B -02/-01 2002 O --2 - -02 -Z America/Belem -3:13:56 - LMT 1914 --3 B -03/-02 1988 S 12 --3 - -03 -Z America/Santarem -3:38:48 - LMT 1914 --4 B -04/-03 1988 S 12 --4 - -04 2008 Jun 24 --3 - -03 -Z America/Fortaleza -2:34 - LMT 1914 --3 B -03/-02 1990 S 17 --3 - -03 1999 S 30 --3 B -03/-02 2000 O 22 --3 - -03 2001 S 13 --3 B -03/-02 2002 O --3 - -03 -Z America/Recife -2:19:36 - LMT 1914 --3 B -03/-02 1990 S 17 --3 - -03 1999 S 30 --3 B -03/-02 2000 O 15 --3 - -03 2001 S 13 --3 B -03/-02 2002 O --3 - -03 -Z America/Araguaina -3:12:48 - LMT 1914 --3 B -03/-02 1990 S 17 --3 - -03 1995 S 14 --3 B -03/-02 2003 S 24 --3 - -03 2012 O 21 --3 B -03/-02 2013 S --3 - -03 -Z America/Maceio -2:22:52 - LMT 1914 --3 B -03/-02 1990 S 17 --3 - -03 1995 O 13 --3 B -03/-02 1996 S 4 --3 - -03 1999 S 30 --3 B -03/-02 2000 O 22 --3 - -03 2001 S 13 --3 B -03/-02 2002 O --3 - -03 -Z America/Bahia -2:34:4 - LMT 1914 --3 B -03/-02 2003 S 24 --3 - -03 2011 O 16 --3 B -03/-02 2012 O 21 --3 - -03 -Z America/Sao_Paulo -3:6:28 - LMT 1914 --3 B -03/-02 1963 O 23 --3 1 -02 1964 --3 B -03/-02 -Z America/Campo_Grande -3:38:28 - LMT 1914 --4 B -04/-03 -Z America/Cuiaba -3:44:20 - LMT 1914 --4 B -04/-03 2003 S 24 --4 - -04 2004 O --4 B -04/-03 -Z America/Porto_Velho -4:15:36 - LMT 1914 --4 B -04/-03 1988 S 12 --4 - -04 -Z America/Boa_Vista -4:2:40 - LMT 1914 --4 B -04/-03 1988 S 12 --4 - -04 1999 S 30 --4 B -04/-03 2000 O 15 --4 - -04 -Z America/Manaus -4:0:4 - LMT 1914 --4 B -04/-03 1988 S 12 --4 - -04 1993 S 28 --4 B -04/-03 1994 S 22 --4 - -04 -Z America/Eirunepe -4:39:28 - LMT 1914 --5 B -05/-04 1988 S 12 --5 - -05 1993 S 28 --5 B -05/-04 1994 S 22 --5 - -05 2008 Jun 24 --4 - -04 2013 N 10 --5 - -05 -Z America/Rio_Branco -4:31:12 - LMT 1914 --5 B -05/-04 1988 S 12 --5 - -05 2008 Jun 24 --4 - -04 2013 N 10 --5 - -05 -R x 1927 1931 - S 1 0 1 - -R x 1928 1932 - Ap 1 0 0 - -R x 1968 o - N 3 4u 1 - -R x 1969 o - Mar 30 3u 0 - -R x 1969 o - N 23 4u 1 - -R x 1970 o - Mar 29 3u 0 - -R x 1971 o - Mar 14 3u 0 - -R x 1970 1972 - O Sun>=9 4u 1 - -R x 1972 1986 - Mar Sun>=9 3u 0 - -R x 1973 o - S 30 4u 1 - -R x 1974 1987 - O Sun>=9 4u 1 - -R x 1987 o - Ap 12 3u 0 - -R x 1988 1990 - Mar Sun>=9 3u 0 - -R x 1988 1989 - O Sun>=9 4u 1 - -R x 1990 o - S 16 4u 1 - -R x 1991 1996 - Mar Sun>=9 3u 0 - -R x 1991 1997 - O Sun>=9 4u 1 - -R x 1997 o - Mar 30 3u 0 - -R x 1998 o - Mar Sun>=9 3u 0 - -R x 1998 o - S 27 4u 1 - -R x 1999 o - Ap 4 3u 0 - -R x 1999 2010 - O Sun>=9 4u 1 - -R x 2000 2007 - Mar Sun>=9 3u 0 - -R x 2008 o - Mar 30 3u 0 - -R x 2009 o - Mar Sun>=9 3u 0 - -R x 2010 o - Ap Sun>=1 3u 0 - -R x 2011 o - May Sun>=2 3u 0 - -R x 2011 o - Au Sun>=16 4u 1 - -R x 2012 2014 - Ap Sun>=23 3u 0 - -R x 2012 2014 - S Sun>=2 4u 1 - -R x 2016 2018 - May Sun>=9 3u 0 - -R x 2016 2018 - Au Sun>=9 4u 1 - -R x 2019 ma - Ap Sun>=2 3u 0 - -R x 2019 ma - S Sun>=2 4u 1 - -Z America/Santiago -4:42:46 - LMT 1890 --4:42:46 - SMT 1910 Ja 10 --5 - -05 1916 Jul --4:42:46 - SMT 1918 S 10 --4 - -04 1919 Jul --4:42:46 - SMT 1927 S --5 x -05/-04 1932 S --4 - -04 1942 Jun --5 - -05 1942 Au --4 - -04 1946 Jul 15 --4 1 -03 1946 S --4 - -04 1947 Ap --5 - -05 1947 May 21 23 --4 x -04/-03 -Z America/Punta_Arenas -4:43:40 - LMT 1890 --4:42:46 - SMT 1910 Ja 10 --5 - -05 1916 Jul --4:42:46 - SMT 1918 S 10 --4 - -04 1919 Jul --4:42:46 - SMT 1927 S --5 x -05/-04 1932 S --4 - -04 1942 Jun --5 - -05 1942 Au --4 - -04 1947 Ap --5 - -05 1947 May 21 23 --4 x -04/-03 2016 D 4 --3 - -03 -Z Pacific/Easter -7:17:28 - LMT 1890 --7:17:28 - EMT 1932 S --7 x -07/-06 1982 Mar 14 3u --6 x -06/-05 -Z Antarctica/Palmer 0 - -00 1965 --4 A -04/-03 1969 O 5 --3 A -03/-02 1982 May --4 x -04/-03 2016 D 4 --3 - -03 -R CO 1992 o - May 3 0 1 - -R CO 1993 o - Ap 4 0 0 - -Z America/Bogota -4:56:16 - LMT 1884 Mar 13 --4:56:16 - BMT 1914 N 23 --5 CO -05/-04 -Z America/Curacao -4:35:47 - LMT 1912 F 12 --4:30 - -0430 1965 --4 - AST -Li America/Curacao America/Lower_Princes -Li America/Curacao America/Kralendijk -R EC 1992 o - N 28 0 1 - -R EC 1993 o - F 5 0 0 - -Z America/Guayaquil -5:19:20 - LMT 1890 --5:14 - QMT 1931 --5 EC -05/-04 -Z Pacific/Galapagos -5:58:24 - LMT 1931 --5 - -05 1986 --6 EC -06/-05 -R FK 1937 1938 - S lastSun 0 1 - -R FK 1938 1942 - Mar Sun>=19 0 0 - -R FK 1939 o - O 1 0 1 - -R FK 1940 1942 - S lastSun 0 1 - -R FK 1943 o - Ja 1 0 0 - -R FK 1983 o - S lastSun 0 1 - -R FK 1984 1985 - Ap lastSun 0 0 - -R FK 1984 o - S 16 0 1 - -R FK 1985 2000 - S Sun>=9 0 1 - -R FK 1986 2000 - Ap Sun>=16 0 0 - -R FK 2001 2010 - Ap Sun>=15 2 0 - -R FK 2001 2010 - S Sun>=1 2 1 - -Z Atlantic/Stanley -3:51:24 - LMT 1890 --3:51:24 - SMT 1912 Mar 12 --4 FK -04/-03 1983 May --3 FK -03/-02 1985 S 15 --4 FK -04/-03 2010 S 5 2 --3 - -03 -Z America/Cayenne -3:29:20 - LMT 1911 Jul --4 - -04 1967 O --3 - -03 -Z America/Guyana -3:52:40 - LMT 1915 Mar --3:45 - -0345 1975 Jul 31 --3 - -03 1991 --4 - -04 -R y 1975 1988 - O 1 0 1 - -R y 1975 1978 - Mar 1 0 0 - -R y 1979 1991 - Ap 1 0 0 - -R y 1989 o - O 22 0 1 - -R y 1990 o - O 1 0 1 - -R y 1991 o - O 6 0 1 - -R y 1992 o - Mar 1 0 0 - -R y 1992 o - O 5 0 1 - -R y 1993 o - Mar 31 0 0 - -R y 1993 1995 - O 1 0 1 - -R y 1994 1995 - F lastSun 0 0 - -R y 1996 o - Mar 1 0 0 - -R y 1996 2001 - O Sun>=1 0 1 - -R y 1997 o - F lastSun 0 0 - -R y 1998 2001 - Mar Sun>=1 0 0 - -R y 2002 2004 - Ap Sun>=1 0 0 - -R y 2002 2003 - S Sun>=1 0 1 - -R y 2004 2009 - O Sun>=15 0 1 - -R y 2005 2009 - Mar Sun>=8 0 0 - -R y 2010 ma - O Sun>=1 0 1 - -R y 2010 2012 - Ap Sun>=8 0 0 - -R y 2013 ma - Mar Sun>=22 0 0 - -Z America/Asuncion -3:50:40 - LMT 1890 --3:50:40 - AMT 1931 O 10 --4 - -04 1972 O --3 - -03 1974 Ap --4 y -04/-03 -R PE 1938 o - Ja 1 0 1 - -R PE 1938 o - Ap 1 0 0 - -R PE 1938 1939 - S lastSun 0 1 - -R PE 1939 1940 - Mar Sun>=24 0 0 - -R PE 1986 1987 - Ja 1 0 1 - -R PE 1986 1987 - Ap 1 0 0 - -R PE 1990 o - Ja 1 0 1 - -R PE 1990 o - Ap 1 0 0 - -R PE 1994 o - Ja 1 0 1 - -R PE 1994 o - Ap 1 0 0 - -Z America/Lima -5:8:12 - LMT 1890 --5:8:36 - LMT 1908 Jul 28 --5 PE -05/-04 -Z Atlantic/South_Georgia -2:26:8 - LMT 1890 --2 - -02 -Z America/Paramaribo -3:40:40 - LMT 1911 --3:40:52 - PMT 1935 --3:40:36 - PMT 1945 O --3:30 - -0330 1984 O --3 - -03 -Z America/Port_of_Spain -4:6:4 - LMT 1912 Mar 2 --4 - AST -Li America/Port_of_Spain America/Anguilla -Li America/Port_of_Spain America/Antigua -Li America/Port_of_Spain America/Dominica -Li America/Port_of_Spain America/Grenada -Li America/Port_of_Spain America/Guadeloupe -Li America/Port_of_Spain America/Marigot -Li America/Port_of_Spain America/Montserrat -Li America/Port_of_Spain America/St_Barthelemy -Li America/Port_of_Spain America/St_Kitts -Li America/Port_of_Spain America/St_Lucia -Li America/Port_of_Spain America/St_Thomas -Li America/Port_of_Spain America/St_Vincent -Li America/Port_of_Spain America/Tortola -R U 1923 1925 - O 1 0 0:30 - -R U 1924 1926 - Ap 1 0 0 - -R U 1933 1938 - O lastSun 0 0:30 - -R U 1934 1941 - Mar lastSat 24 0 - -R U 1939 o - O 1 0 0:30 - -R U 1940 o - O 27 0 0:30 - -R U 1941 o - Au 1 0 0:30 - -R U 1942 o - D 14 0 0:30 - -R U 1943 o - Mar 14 0 0 - -R U 1959 o - May 24 0 0:30 - -R U 1959 o - N 15 0 0 - -R U 1960 o - Ja 17 0 1 - -R U 1960 o - Mar 6 0 0 - -R U 1965 o - Ap 4 0 1 - -R U 1965 o - S 26 0 0 - -R U 1968 o - May 27 0 0:30 - -R U 1968 o - D 1 0 0 - -R U 1970 o - Ap 25 0 1 - -R U 1970 o - Jun 14 0 0 - -R U 1972 o - Ap 23 0 1 - -R U 1972 o - Jul 16 0 0 - -R U 1974 o - Ja 13 0 1:30 - -R U 1974 o - Mar 10 0 0:30 - -R U 1974 o - S 1 0 0 - -R U 1974 o - D 22 0 1 - -R U 1975 o - Mar 30 0 0 - -R U 1976 o - D 19 0 1 - -R U 1977 o - Mar 6 0 0 - -R U 1977 o - D 4 0 1 - -R U 1978 1979 - Mar Sun>=1 0 0 - -R U 1978 o - D 17 0 1 - -R U 1979 o - Ap 29 0 1 - -R U 1980 o - Mar 16 0 0 - -R U 1987 o - D 14 0 1 - -R U 1988 o - F 28 0 0 - -R U 1988 o - D 11 0 1 - -R U 1989 o - Mar 5 0 0 - -R U 1989 o - O 29 0 1 - -R U 1990 o - F 25 0 0 - -R U 1990 1991 - O Sun>=21 0 1 - -R U 1991 1992 - Mar Sun>=1 0 0 - -R U 1992 o - O 18 0 1 - -R U 1993 o - F 28 0 0 - -R U 2004 o - S 19 0 1 - -R U 2005 o - Mar 27 2 0 - -R U 2005 o - O 9 2 1 - -R U 2006 2015 - Mar Sun>=8 2 0 - -R U 2006 2014 - O Sun>=1 2 1 - -Z America/Montevideo -3:44:51 - LMT 1908 Jun 10 --3:44:51 - MMT 1920 May --4 - -04 1923 O --3:30 U -0330/-03 1942 D 14 --3 U -03/-0230 1960 --3 U -03/-02 1968 --3 U -03/-0230 1970 --3 U -03/-02 1974 --3 U -03/-0130 1974 Mar 10 --3 U -03/-0230 1974 D 22 --3 U -03/-02 -Z America/Caracas -4:27:44 - LMT 1890 --4:27:40 - CMT 1912 F 12 --4:30 - -0430 1965 --4 - -04 2007 D 9 3 --4:30 - -0430 2016 May 1 2:30 --4 - -04 -Z Etc/GMT 0 - GMT -Z Etc/UTC 0 - UTC -Z Etc/UCT 0 - UCT -Li Etc/GMT GMT -Li Etc/UTC Etc/Universal -Li Etc/UTC Etc/Zulu -Li Etc/GMT Etc/Greenwich -Li Etc/GMT Etc/GMT-0 -Li Etc/GMT Etc/GMT+0 -Li Etc/GMT Etc/GMT0 -Z Etc/GMT-14 14 - +14 -Z Etc/GMT-13 13 - +13 -Z Etc/GMT-12 12 - +12 -Z Etc/GMT-11 11 - +11 -Z Etc/GMT-10 10 - +10 -Z Etc/GMT-9 9 - +09 -Z Etc/GMT-8 8 - +08 -Z Etc/GMT-7 7 - +07 -Z Etc/GMT-6 6 - +06 -Z Etc/GMT-5 5 - +05 -Z Etc/GMT-4 4 - +04 -Z Etc/GMT-3 3 - +03 -Z Etc/GMT-2 2 - +02 -Z Etc/GMT-1 1 - +01 -Z Etc/GMT+1 -1 - -01 -Z Etc/GMT+2 -2 - -02 -Z Etc/GMT+3 -3 - -03 -Z Etc/GMT+4 -4 - -04 -Z Etc/GMT+5 -5 - -05 -Z Etc/GMT+6 -6 - -06 -Z Etc/GMT+7 -7 - -07 -Z Etc/GMT+8 -8 - -08 -Z Etc/GMT+9 -9 - -09 -Z Etc/GMT+10 -10 - -10 -Z Etc/GMT+11 -11 - -11 -Z Etc/GMT+12 -12 - -12 -Z Factory 0 - -00 -Li Africa/Nairobi Africa/Asmera -Li Africa/Abidjan Africa/Timbuktu -Li America/Argentina/Catamarca America/Argentina/ComodRivadavia -Li America/Adak America/Atka -Li America/Argentina/Buenos_Aires America/Buenos_Aires -Li America/Argentina/Catamarca America/Catamarca -Li America/Atikokan America/Coral_Harbour -Li America/Argentina/Cordoba America/Cordoba -Li America/Tijuana America/Ensenada -Li America/Indiana/Indianapolis America/Fort_Wayne -Li America/Indiana/Indianapolis America/Indianapolis -Li America/Argentina/Jujuy America/Jujuy -Li America/Indiana/Knox America/Knox_IN -Li America/Kentucky/Louisville America/Louisville -Li America/Argentina/Mendoza America/Mendoza -Li America/Toronto America/Montreal -Li America/Rio_Branco America/Porto_Acre -Li America/Argentina/Cordoba America/Rosario -Li America/Tijuana America/Santa_Isabel -Li America/Denver America/Shiprock -Li America/Port_of_Spain America/Virgin -Li Pacific/Auckland Antarctica/South_Pole -Li Asia/Ashgabat Asia/Ashkhabad -Li Asia/Kolkata Asia/Calcutta -Li Asia/Shanghai Asia/Chongqing -Li Asia/Shanghai Asia/Chungking -Li Asia/Dhaka Asia/Dacca -Li Asia/Shanghai Asia/Harbin -Li Asia/Urumqi Asia/Kashgar -Li Asia/Kathmandu Asia/Katmandu -Li Asia/Macau Asia/Macao -Li Asia/Yangon Asia/Rangoon -Li Asia/Ho_Chi_Minh Asia/Saigon -Li Asia/Jerusalem Asia/Tel_Aviv -Li Asia/Thimphu Asia/Thimbu -Li Asia/Makassar Asia/Ujung_Pandang -Li Asia/Ulaanbaatar Asia/Ulan_Bator -Li Atlantic/Faroe Atlantic/Faeroe -Li Europe/Oslo Atlantic/Jan_Mayen -Li Australia/Sydney Australia/ACT -Li Australia/Sydney Australia/Canberra -Li Australia/Lord_Howe Australia/LHI -Li Australia/Sydney Australia/NSW -Li Australia/Darwin Australia/North -Li Australia/Brisbane Australia/Queensland -Li Australia/Adelaide Australia/South -Li Australia/Hobart Australia/Tasmania -Li Australia/Melbourne Australia/Victoria -Li Australia/Perth Australia/West -Li Australia/Broken_Hill Australia/Yancowinna -Li America/Rio_Branco Brazil/Acre -Li America/Noronha Brazil/DeNoronha -Li America/Sao_Paulo Brazil/East -Li America/Manaus Brazil/West -Li America/Halifax Canada/Atlantic -Li America/Winnipeg Canada/Central -Li America/Toronto Canada/Eastern -Li America/Edmonton Canada/Mountain -Li America/St_Johns Canada/Newfoundland -Li America/Vancouver Canada/Pacific -Li America/Regina Canada/Saskatchewan -Li America/Whitehorse Canada/Yukon -Li America/Santiago Chile/Continental -Li Pacific/Easter Chile/EasterIsland -Li America/Havana Cuba -Li Africa/Cairo Egypt -Li Europe/Dublin Eire -Li Europe/London Europe/Belfast -Li Europe/Chisinau Europe/Tiraspol -Li Europe/London GB -Li Europe/London GB-Eire -Li Etc/GMT GMT+0 -Li Etc/GMT GMT-0 -Li Etc/GMT GMT0 -Li Etc/GMT Greenwich -Li Asia/Hong_Kong Hongkong -Li Atlantic/Reykjavik Iceland -Li Asia/Tehran Iran -Li Asia/Jerusalem Israel -Li America/Jamaica Jamaica -Li Asia/Tokyo Japan -Li Pacific/Kwajalein Kwajalein -Li Africa/Tripoli Libya -Li America/Tijuana Mexico/BajaNorte -Li America/Mazatlan Mexico/BajaSur -Li America/Mexico_City Mexico/General -Li Pacific/Auckland NZ -Li Pacific/Chatham NZ-CHAT -Li America/Denver Navajo -Li Asia/Shanghai PRC -Li Pacific/Honolulu Pacific/Johnston -Li Pacific/Pohnpei Pacific/Ponape -Li Pacific/Pago_Pago Pacific/Samoa -Li Pacific/Chuuk Pacific/Truk -Li Pacific/Chuuk Pacific/Yap -Li Europe/Warsaw Poland -Li Europe/Lisbon Portugal -Li Asia/Taipei ROC -Li Asia/Seoul ROK -Li Asia/Singapore Singapore -Li Europe/Istanbul Turkey -Li Etc/UCT UCT -Li America/Anchorage US/Alaska -Li America/Adak US/Aleutian -Li America/Phoenix US/Arizona -Li America/Chicago US/Central -Li America/Indiana/Indianapolis US/East-Indiana -Li America/New_York US/Eastern -Li Pacific/Honolulu US/Hawaii -Li America/Indiana/Knox US/Indiana-Starke -Li America/Detroit US/Michigan -Li America/Denver US/Mountain -Li America/Los_Angeles US/Pacific -Li Pacific/Pago_Pago US/Samoa -Li Etc/UTC UTC -Li Etc/UTC Universal -Li Europe/Moscow W-SU -Li Etc/UTC Zulu diff --git a/lib/pytz/zoneinfo/zone.tab b/lib/pytz/zoneinfo/zone.tab deleted file mode 100644 index dcb6e1d..0000000 --- a/lib/pytz/zoneinfo/zone.tab +++ /dev/null @@ -1,448 +0,0 @@ -# tzdb timezone descriptions (deprecated version) -# -# This file is in the public domain, so clarified as of -# 2009-05-17 by Arthur David Olson. -# -# From Paul Eggert (2018-06-27): -# This file is intended as a backward-compatibility aid for older programs. -# New programs should use zone1970.tab. This file is like zone1970.tab (see -# zone1970.tab's comments), but with the following additional restrictions: -# -# 1. This file contains only ASCII characters. -# 2. The first data column contains exactly one country code. -# -# Because of (2), each row stands for an area that is the intersection -# of a region identified by a country code and of a timezone where civil -# clocks have agreed since 1970; this is a narrower definition than -# that of zone1970.tab. -# -# This table is intended as an aid for users, to help them select timezones -# appropriate for their practical needs. It is not intended to take or -# endorse any position on legal or territorial claims. -# -#country- -#code coordinates TZ comments -AD +4230+00131 Europe/Andorra -AE +2518+05518 Asia/Dubai -AF +3431+06912 Asia/Kabul -AG +1703-06148 America/Antigua -AI +1812-06304 America/Anguilla -AL +4120+01950 Europe/Tirane -AM +4011+04430 Asia/Yerevan -AO -0848+01314 Africa/Luanda -AQ -7750+16636 Antarctica/McMurdo New Zealand time - McMurdo, South Pole -AQ -6617+11031 Antarctica/Casey Casey -AQ -6835+07758 Antarctica/Davis Davis -AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville -AQ -6736+06253 Antarctica/Mawson Mawson -AQ -6448-06406 Antarctica/Palmer Palmer -AQ -6734-06808 Antarctica/Rothera Rothera -AQ -690022+0393524 Antarctica/Syowa Syowa -AQ -720041+0023206 Antarctica/Troll Troll -AQ -7824+10654 Antarctica/Vostok Vostok -AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) -AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) -AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) -AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) -AR -2649-06513 America/Argentina/Tucuman Tucuman (TM) -AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH) -AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) -AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) -AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) -AR -3319-06621 America/Argentina/San_Luis San Luis (SL) -AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) -AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) -AS -1416-17042 Pacific/Pago_Pago -AT +4813+01620 Europe/Vienna -AU -3133+15905 Australia/Lord_Howe Lord Howe Island -AU -5430+15857 Antarctica/Macquarie Macquarie Island -AU -4253+14719 Australia/Hobart Tasmania (most areas) -AU -3956+14352 Australia/Currie Tasmania (King Island) -AU -3749+14458 Australia/Melbourne Victoria -AU -3352+15113 Australia/Sydney New South Wales (most areas) -AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) -AU -2728+15302 Australia/Brisbane Queensland (most areas) -AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) -AU -3455+13835 Australia/Adelaide South Australia -AU -1228+13050 Australia/Darwin Northern Territory -AU -3157+11551 Australia/Perth Western Australia (most areas) -AU -3143+12852 Australia/Eucla Western Australia (Eucla) -AW +1230-06958 America/Aruba -AX +6006+01957 Europe/Mariehamn -AZ +4023+04951 Asia/Baku -BA +4352+01825 Europe/Sarajevo -BB +1306-05937 America/Barbados -BD +2343+09025 Asia/Dhaka -BE +5050+00420 Europe/Brussels -BF +1222-00131 Africa/Ouagadougou -BG +4241+02319 Europe/Sofia -BH +2623+05035 Asia/Bahrain -BI -0323+02922 Africa/Bujumbura -BJ +0629+00237 Africa/Porto-Novo -BL +1753-06251 America/St_Barthelemy -BM +3217-06446 Atlantic/Bermuda -BN +0456+11455 Asia/Brunei -BO -1630-06809 America/La_Paz -BQ +120903-0681636 America/Kralendijk -BR -0351-03225 America/Noronha Atlantic islands -BR -0127-04829 America/Belem Para (east); Amapa -BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) -BR -0803-03454 America/Recife Pernambuco -BR -0712-04812 America/Araguaina Tocantins -BR -0940-03543 America/Maceio Alagoas, Sergipe -BR -1259-03831 America/Bahia Bahia -BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) -BR -2027-05437 America/Campo_Grande Mato Grosso do Sul -BR -1535-05605 America/Cuiaba Mato Grosso -BR -0226-05452 America/Santarem Para (west) -BR -0846-06354 America/Porto_Velho Rondonia -BR +0249-06040 America/Boa_Vista Roraima -BR -0308-06001 America/Manaus Amazonas (east) -BR -0640-06952 America/Eirunepe Amazonas (west) -BR -0958-06748 America/Rio_Branco Acre -BS +2505-07721 America/Nassau -BT +2728+08939 Asia/Thimphu -BW -2439+02555 Africa/Gaborone -BY +5354+02734 Europe/Minsk -BZ +1730-08812 America/Belize -CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast) -CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE -CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) -CA +4606-06447 America/Moncton Atlantic - New Brunswick -CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) -CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) -CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) -CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) -CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) -CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) -CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) -CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) -CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba -CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) -CA +744144-0944945 America/Resolute Central - NU (Resolute) -CA +624900-0920459 America/Rankin_Inlet Central - NU (central) -CA +5024-10439 America/Regina CST - SK (most areas) -CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) -CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) -CA +6227-11421 America/Yellowknife Mountain - NT (central) -CA +682059-1334300 America/Inuvik Mountain - NT (west) -CA +4906-11631 America/Creston MST - BC (Creston) -CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) -CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) -CA +4916-12307 America/Vancouver Pacific - BC (most areas) -CA +6043-13503 America/Whitehorse Pacific - Yukon (south) -CA +6404-13925 America/Dawson Pacific - Yukon (north) -CC -1210+09655 Indian/Cocos -CD -0418+01518 Africa/Kinshasa Dem. Rep. of Congo (west) -CD -1140+02728 Africa/Lubumbashi Dem. Rep. of Congo (east) -CF +0422+01835 Africa/Bangui -CG -0416+01517 Africa/Brazzaville -CH +4723+00832 Europe/Zurich -CI +0519-00402 Africa/Abidjan -CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago Chile (most areas) -CL -5309-07055 America/Punta_Arenas Region of Magallanes -CL -2709-10926 Pacific/Easter Easter Island -CM +0403+00942 Africa/Douala -CN +3114+12128 Asia/Shanghai Beijing Time -CN +4348+08735 Asia/Urumqi Xinjiang Time -CO +0436-07405 America/Bogota -CR +0956-08405 America/Costa_Rica -CU +2308-08222 America/Havana -CV +1455-02331 Atlantic/Cape_Verde -CW +1211-06900 America/Curacao -CX -1025+10543 Indian/Christmas -CY +3510+03322 Asia/Nicosia Cyprus (most areas) -CY +3507+03357 Asia/Famagusta Northern Cyprus -CZ +5005+01426 Europe/Prague -DE +5230+01322 Europe/Berlin Germany (most areas) -DE +4742+00841 Europe/Busingen Busingen -DJ +1136+04309 Africa/Djibouti -DK +5540+01235 Europe/Copenhagen -DM +1518-06124 America/Dominica -DO +1828-06954 America/Santo_Domingo -DZ +3647+00303 Africa/Algiers -EC -0210-07950 America/Guayaquil Ecuador (mainland) -EC -0054-08936 Pacific/Galapagos Galapagos Islands -EE +5925+02445 Europe/Tallinn -EG +3003+03115 Africa/Cairo -EH +2709-01312 Africa/El_Aaiun -ER +1520+03853 Africa/Asmara -ES +4024-00341 Europe/Madrid Spain (mainland) -ES +3553-00519 Africa/Ceuta Ceuta, Melilla -ES +2806-01524 Atlantic/Canary Canary Islands -ET +0902+03842 Africa/Addis_Ababa -FI +6010+02458 Europe/Helsinki -FJ -1808+17825 Pacific/Fiji -FK -5142-05751 Atlantic/Stanley -FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap -FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape -FM +0519+16259 Pacific/Kosrae Kosrae -FO +6201-00646 Atlantic/Faroe -FR +4852+00220 Europe/Paris -GA +0023+00927 Africa/Libreville -GB +513030-0000731 Europe/London -GD +1203-06145 America/Grenada -GE +4143+04449 Asia/Tbilisi -GF +0456-05220 America/Cayenne -GG +492717-0023210 Europe/Guernsey -GH +0533-00013 Africa/Accra -GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Godthab Greenland (most areas) -GL +7646-01840 America/Danmarkshavn National Park (east coast) -GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit -GL +7634-06847 America/Thule Thule/Pituffik -GM +1328-01639 Africa/Banjul -GN +0931-01343 Africa/Conakry -GP +1614-06132 America/Guadeloupe -GQ +0345+00847 Africa/Malabo -GR +3758+02343 Europe/Athens -GS -5416-03632 Atlantic/South_Georgia -GT +1438-09031 America/Guatemala -GU +1328+14445 Pacific/Guam -GW +1151-01535 Africa/Bissau -GY +0648-05810 America/Guyana -HK +2217+11409 Asia/Hong_Kong -HN +1406-08713 America/Tegucigalpa -HR +4548+01558 Europe/Zagreb -HT +1832-07220 America/Port-au-Prince -HU +4730+01905 Europe/Budapest -ID -0610+10648 Asia/Jakarta Java, Sumatra -ID -0002+10920 Asia/Pontianak Borneo (west, central) -ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west) -ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas -IE +5320-00615 Europe/Dublin -IL +314650+0351326 Asia/Jerusalem -IM +5409-00428 Europe/Isle_of_Man -IN +2232+08822 Asia/Kolkata -IO -0720+07225 Indian/Chagos -IQ +3321+04425 Asia/Baghdad -IR +3540+05126 Asia/Tehran -IS +6409-02151 Atlantic/Reykjavik -IT +4154+01229 Europe/Rome -JE +491101-0020624 Europe/Jersey -JM +175805-0764736 America/Jamaica -JO +3157+03556 Asia/Amman -JP +353916+1394441 Asia/Tokyo -KE -0117+03649 Africa/Nairobi -KG +4254+07436 Asia/Bishkek -KH +1133+10455 Asia/Phnom_Penh -KI +0125+17300 Pacific/Tarawa Gilbert Islands -KI -0308-17105 Pacific/Enderbury Phoenix Islands -KI +0152-15720 Pacific/Kiritimati Line Islands -KM -1141+04316 Indian/Comoro -KN +1718-06243 America/St_Kitts -KP +3901+12545 Asia/Pyongyang -KR +3733+12658 Asia/Seoul -KW +2920+04759 Asia/Kuwait -KY +1918-08123 America/Cayman -KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) -KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda -KZ +5017+05710 Asia/Aqtobe Aqtobe/Aktobe -KZ +4431+05016 Asia/Aqtau Mangghystau/Mankistau -KZ +4707+05156 Asia/Atyrau Atyrau/Atirau/Gur'yev -KZ +5113+05121 Asia/Oral West Kazakhstan -LA +1758+10236 Asia/Vientiane -LB +3353+03530 Asia/Beirut -LC +1401-06100 America/St_Lucia -LI +4709+00931 Europe/Vaduz -LK +0656+07951 Asia/Colombo -LR +0618-01047 Africa/Monrovia -LS -2928+02730 Africa/Maseru -LT +5441+02519 Europe/Vilnius -LU +4936+00609 Europe/Luxembourg -LV +5657+02406 Europe/Riga -LY +3254+01311 Africa/Tripoli -MA +3339-00735 Africa/Casablanca -MC +4342+00723 Europe/Monaco -MD +4700+02850 Europe/Chisinau -ME +4226+01916 Europe/Podgorica -MF +1804-06305 America/Marigot -MG -1855+04731 Indian/Antananarivo -MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) -MH +0905+16720 Pacific/Kwajalein Kwajalein -MK +4159+02126 Europe/Skopje -ML +1239-00800 Africa/Bamako -MM +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) -MN +4801+09139 Asia/Hovd Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan -MN +4804+11430 Asia/Choibalsan Dornod, Sukhbaatar -MO +221150+1133230 Asia/Macau -MP +1512+14545 Pacific/Saipan -MQ +1436-06105 America/Martinique -MR +1806-01557 Africa/Nouakchott -MS +1643-06213 America/Montserrat -MT +3554+01431 Europe/Malta -MU -2010+05730 Indian/Mauritius -MV +0410+07330 Indian/Maldives -MW -1547+03500 Africa/Blantyre -MX +1924-09909 America/Mexico_City Central Time -MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo -MX +2058-08937 America/Merida Central Time - Campeche, Yucatan -MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo Leon, Tamaulipas (most areas) -MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo Leon, Tamaulipas (US border) -MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa -MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) -MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) -MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora -MX +3232-11701 America/Tijuana Pacific Time US - Baja California -MX +2048-10515 America/Bahia_Banderas Central Time - Bahia de Banderas -MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) -MY +0133+11020 Asia/Kuching Sabah, Sarawak -MZ -2558+03235 Africa/Maputo -NA -2234+01706 Africa/Windhoek -NC -2216+16627 Pacific/Noumea -NE +1331+00207 Africa/Niamey -NF -2903+16758 Pacific/Norfolk -NG +0627+00324 Africa/Lagos -NI +1209-08617 America/Managua -NL +5222+00454 Europe/Amsterdam -NO +5955+01045 Europe/Oslo -NP +2743+08519 Asia/Kathmandu -NR -0031+16655 Pacific/Nauru -NU -1901-16955 Pacific/Niue -NZ -3652+17446 Pacific/Auckland New Zealand (most areas) -NZ -4357-17633 Pacific/Chatham Chatham Islands -OM +2336+05835 Asia/Muscat -PA +0858-07932 America/Panama -PE -1203-07703 America/Lima -PF -1732-14934 Pacific/Tahiti Society Islands -PF -0900-13930 Pacific/Marquesas Marquesas Islands -PF -2308-13457 Pacific/Gambier Gambier Islands -PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) -PG -0613+15534 Pacific/Bougainville Bougainville -PH +1435+12100 Asia/Manila -PK +2452+06703 Asia/Karachi -PL +5215+02100 Europe/Warsaw -PM +4703-05620 America/Miquelon -PN -2504-13005 Pacific/Pitcairn -PR +182806-0660622 America/Puerto_Rico -PS +3130+03428 Asia/Gaza Gaza Strip -PS +313200+0350542 Asia/Hebron West Bank -PT +3843-00908 Europe/Lisbon Portugal (mainland) -PT +3238-01654 Atlantic/Madeira Madeira Islands -PT +3744-02540 Atlantic/Azores Azores -PW +0720+13429 Pacific/Palau -PY -2516-05740 America/Asuncion -QA +2517+05132 Asia/Qatar -RE -2052+05528 Indian/Reunion -RO +4426+02606 Europe/Bucharest -RS +4450+02030 Europe/Belgrade -RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad -RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area -RU +4457+03406 Europe/Simferopol MSK+00 - Crimea -RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd -RU +5836+04939 Europe/Kirov MSK+00 - Kirov -RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan -RU +5134+04602 Europe/Saratov MSK+01 - Saratov -RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk -RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia -RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals -RU +5500+07324 Asia/Omsk MSK+03 - Omsk -RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk -RU +5322+08345 Asia/Barnaul MSK+04 - Altai -RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk -RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo -RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area -RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia -RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky -RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River -RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky -RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River -RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky -RU +5934+15048 Asia/Magadan MSK+08 - Magadan -RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is -RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka -RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea -RW -0157+03004 Africa/Kigali -SA +2438+04643 Asia/Riyadh -SB -0932+16012 Pacific/Guadalcanal -SC -0440+05528 Indian/Mahe -SD +1536+03232 Africa/Khartoum -SE +5920+01803 Europe/Stockholm -SG +0117+10351 Asia/Singapore -SH -1555-00542 Atlantic/St_Helena -SI +4603+01431 Europe/Ljubljana -SJ +7800+01600 Arctic/Longyearbyen -SK +4809+01707 Europe/Bratislava -SL +0830-01315 Africa/Freetown -SM +4355+01228 Europe/San_Marino -SN +1440-01726 Africa/Dakar -SO +0204+04522 Africa/Mogadishu -SR +0550-05510 America/Paramaribo -SS +0451+03137 Africa/Juba -ST +0020+00644 Africa/Sao_Tome -SV +1342-08912 America/El_Salvador -SX +180305-0630250 America/Lower_Princes -SY +3330+03618 Asia/Damascus -SZ -2618+03106 Africa/Mbabane -TC +2128-07108 America/Grand_Turk -TD +1207+01503 Africa/Ndjamena -TF -492110+0701303 Indian/Kerguelen -TG +0608+00113 Africa/Lome -TH +1345+10031 Asia/Bangkok -TJ +3835+06848 Asia/Dushanbe -TK -0922-17114 Pacific/Fakaofo -TL -0833+12535 Asia/Dili -TM +3757+05823 Asia/Ashgabat -TN +3648+01011 Africa/Tunis -TO -2110-17510 Pacific/Tongatapu -TR +4101+02858 Europe/Istanbul -TT +1039-06131 America/Port_of_Spain -TV -0831+17913 Pacific/Funafuti -TW +2503+12130 Asia/Taipei -TZ -0648+03917 Africa/Dar_es_Salaam -UA +5026+03031 Europe/Kiev Ukraine (most areas) -UA +4837+02218 Europe/Uzhgorod Ruthenia -UA +4750+03510 Europe/Zaporozhye Zaporozh'ye/Zaporizhia; Lugansk/Luhansk (east) -UG +0019+03225 Africa/Kampala -UM +2813-17722 Pacific/Midway Midway Islands -UM +1917+16637 Pacific/Wake Wake Island -US +404251-0740023 America/New_York Eastern (most areas) -US +421953-0830245 America/Detroit Eastern - MI (most areas) -US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) -US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) -US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) -US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) -US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) -US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) -US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) -US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) -US +415100-0873900 America/Chicago Central (most areas) -US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) -US +411745-0863730 America/Indiana/Knox Central - IN (Starke) -US +450628-0873651 America/Menominee Central - MI (Wisconsin border) -US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) -US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) -US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) -US +394421-1045903 America/Denver Mountain (most areas) -US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) -US +340308-1181434 America/Los_Angeles Pacific -US +611305-1495401 America/Anchorage Alaska (most areas) -US +581807-1342511 America/Juneau Alaska - Juneau area -US +571035-1351807 America/Sitka Alaska - Sitka area -US +550737-1313435 America/Metlakatla Alaska - Annette Island -US +593249-1394338 America/Yakutat Alaska - Yakutat -US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Aleutian Islands -US +211825-1575130 Pacific/Honolulu Hawaii -UY -345433-0561245 America/Montevideo -UZ +3940+06648 Asia/Samarkand Uzbekistan (west) -UZ +4120+06918 Asia/Tashkent Uzbekistan (east) -VA +415408+0122711 Europe/Vatican -VC +1309-06114 America/St_Vincent -VE +1030-06656 America/Caracas -VG +1827-06437 America/Tortola -VI +1821-06456 America/St_Thomas -VN +1045+10640 Asia/Ho_Chi_Minh -VU -1740+16825 Pacific/Efate -WF -1318-17610 Pacific/Wallis -WS -1350-17144 Pacific/Apia -YE +1245+04512 Asia/Aden -YT -1247+04514 Indian/Mayotte -ZA -2615+02800 Africa/Johannesburg -ZM -1525+02817 Africa/Lusaka -ZW -1750+03103 Africa/Harare diff --git a/lib/pytz/zoneinfo/zone1970.tab b/lib/pytz/zoneinfo/zone1970.tab deleted file mode 100644 index 7c86fb6..0000000 --- a/lib/pytz/zoneinfo/zone1970.tab +++ /dev/null @@ -1,382 +0,0 @@ -# tzdb timezone descriptions -# -# This file is in the public domain. -# -# From Paul Eggert (2018-06-27): -# This file contains a table where each row stands for a timezone where -# civil timestamps have agreed since 1970. Columns are separated by -# a single tab. Lines beginning with '#' are comments. All text uses -# UTF-8 encoding. The columns of the table are as follows: -# -# 1. The countries that overlap the timezone, as a comma-separated list -# of ISO 3166 2-character country codes. See the file 'iso3166.tab'. -# 2. Latitude and longitude of the timezone's principal location -# in ISO 6709 sign-degrees-minutes-seconds format, -# either ±DDMM±DDDMM or ±DDMMSS±DDDMMSS, -# first latitude (+ is north), then longitude (+ is east). -# 3. Timezone name used in value of TZ environment variable. -# Please see the theory.html file for how these names are chosen. -# If multiple timezones overlap a country, each has a row in the -# table, with each column 1 containing the country code. -# 4. Comments; present if and only if a country has multiple timezones. -# -# If a timezone covers multiple countries, the most-populous city is used, -# and that country is listed first in column 1; any other countries -# are listed alphabetically by country code. The table is sorted -# first by country code, then (if possible) by an order within the -# country that (1) makes some geographical sense, and (2) puts the -# most populous timezones first, where that does not contradict (1). -# -# This table is intended as an aid for users, to help them select timezones -# appropriate for their practical needs. It is not intended to take or -# endorse any position on legal or territorial claims. -# -#country- -#codes coordinates TZ comments -AD +4230+00131 Europe/Andorra -AE,OM +2518+05518 Asia/Dubai -AF +3431+06912 Asia/Kabul -AL +4120+01950 Europe/Tirane -AM +4011+04430 Asia/Yerevan -AQ -6617+11031 Antarctica/Casey Casey -AQ -6835+07758 Antarctica/Davis Davis -AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville -AQ -6736+06253 Antarctica/Mawson Mawson -AQ -6448-06406 Antarctica/Palmer Palmer -AQ -6734-06808 Antarctica/Rothera Rothera -AQ -690022+0393524 Antarctica/Syowa Syowa -AQ -720041+0023206 Antarctica/Troll Troll -AQ -7824+10654 Antarctica/Vostok Vostok -AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) -AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) -AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) -AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) -AR -2649-06513 America/Argentina/Tucuman Tucumán (TM) -AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH) -AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) -AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) -AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) -AR -3319-06621 America/Argentina/San_Luis San Luis (SL) -AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) -AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) -AS,UM -1416-17042 Pacific/Pago_Pago Samoa, Midway -AT +4813+01620 Europe/Vienna -AU -3133+15905 Australia/Lord_Howe Lord Howe Island -AU -5430+15857 Antarctica/Macquarie Macquarie Island -AU -4253+14719 Australia/Hobart Tasmania (most areas) -AU -3956+14352 Australia/Currie Tasmania (King Island) -AU -3749+14458 Australia/Melbourne Victoria -AU -3352+15113 Australia/Sydney New South Wales (most areas) -AU -3157+14127 Australia/Broken_Hill New South Wales (Yancowinna) -AU -2728+15302 Australia/Brisbane Queensland (most areas) -AU -2016+14900 Australia/Lindeman Queensland (Whitsunday Islands) -AU -3455+13835 Australia/Adelaide South Australia -AU -1228+13050 Australia/Darwin Northern Territory -AU -3157+11551 Australia/Perth Western Australia (most areas) -AU -3143+12852 Australia/Eucla Western Australia (Eucla) -AZ +4023+04951 Asia/Baku -BB +1306-05937 America/Barbados -BD +2343+09025 Asia/Dhaka -BE +5050+00420 Europe/Brussels -BG +4241+02319 Europe/Sofia -BM +3217-06446 Atlantic/Bermuda -BN +0456+11455 Asia/Brunei -BO -1630-06809 America/La_Paz -BR -0351-03225 America/Noronha Atlantic islands -BR -0127-04829 America/Belem Pará (east); Amapá -BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) -BR -0803-03454 America/Recife Pernambuco -BR -0712-04812 America/Araguaina Tocantins -BR -0940-03543 America/Maceio Alagoas, Sergipe -BR -1259-03831 America/Bahia Bahia -BR -2332-04637 America/Sao_Paulo Brazil (southeast: GO, DF, MG, ES, RJ, SP, PR, SC, RS) -BR -2027-05437 America/Campo_Grande Mato Grosso do Sul -BR -1535-05605 America/Cuiaba Mato Grosso -BR -0226-05452 America/Santarem Pará (west) -BR -0846-06354 America/Porto_Velho Rondônia -BR +0249-06040 America/Boa_Vista Roraima -BR -0308-06001 America/Manaus Amazonas (east) -BR -0640-06952 America/Eirunepe Amazonas (west) -BR -0958-06748 America/Rio_Branco Acre -BS +2505-07721 America/Nassau -BT +2728+08939 Asia/Thimphu -BY +5354+02734 Europe/Minsk -BZ +1730-08812 America/Belize -CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast) -CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE -CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) -CA +4606-06447 America/Moncton Atlantic - New Brunswick -CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) -CA +5125-05707 America/Blanc-Sablon AST - QC (Lower North Shore) -CA +4339-07923 America/Toronto Eastern - ON, QC (most areas) -CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) -CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) -CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) -CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) -CA +484531-0913718 America/Atikokan EST - ON (Atikokan); NU (Coral H) -CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba -CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) -CA +744144-0944945 America/Resolute Central - NU (Resolute) -CA +624900-0920459 America/Rankin_Inlet Central - NU (central) -CA +5024-10439 America/Regina CST - SK (most areas) -CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) -CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) -CA +6227-11421 America/Yellowknife Mountain - NT (central) -CA +682059-1334300 America/Inuvik Mountain - NT (west) -CA +4906-11631 America/Creston MST - BC (Creston) -CA +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) -CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) -CA +4916-12307 America/Vancouver Pacific - BC (most areas) -CA +6043-13503 America/Whitehorse Pacific - Yukon (south) -CA +6404-13925 America/Dawson Pacific - Yukon (north) -CC -1210+09655 Indian/Cocos -CH,DE,LI +4723+00832 Europe/Zurich Swiss time -CI,BF,GM,GN,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan -CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago Chile (most areas) -CL -5309-07055 America/Punta_Arenas Region of Magallanes -CL -2709-10926 Pacific/Easter Easter Island -CN +3114+12128 Asia/Shanghai Beijing Time -CN +4348+08735 Asia/Urumqi Xinjiang Time -CO +0436-07405 America/Bogota -CR +0956-08405 America/Costa_Rica -CU +2308-08222 America/Havana -CV +1455-02331 Atlantic/Cape_Verde -CW,AW,BQ,SX +1211-06900 America/Curacao -CX -1025+10543 Indian/Christmas -CY +3510+03322 Asia/Nicosia Cyprus (most areas) -CY +3507+03357 Asia/Famagusta Northern Cyprus -CZ,SK +5005+01426 Europe/Prague -DE +5230+01322 Europe/Berlin Germany (most areas) -DK +5540+01235 Europe/Copenhagen -DO +1828-06954 America/Santo_Domingo -DZ +3647+00303 Africa/Algiers -EC -0210-07950 America/Guayaquil Ecuador (mainland) -EC -0054-08936 Pacific/Galapagos Galápagos Islands -EE +5925+02445 Europe/Tallinn -EG +3003+03115 Africa/Cairo -EH +2709-01312 Africa/El_Aaiun -ES +4024-00341 Europe/Madrid Spain (mainland) -ES +3553-00519 Africa/Ceuta Ceuta, Melilla -ES +2806-01524 Atlantic/Canary Canary Islands -FI,AX +6010+02458 Europe/Helsinki -FJ -1808+17825 Pacific/Fiji -FK -5142-05751 Atlantic/Stanley -FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap -FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape -FM +0519+16259 Pacific/Kosrae Kosrae -FO +6201-00646 Atlantic/Faroe -FR +4852+00220 Europe/Paris -GB,GG,IM,JE +513030-0000731 Europe/London -GE +4143+04449 Asia/Tbilisi -GF +0456-05220 America/Cayenne -GH +0533-00013 Africa/Accra -GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Godthab Greenland (most areas) -GL +7646-01840 America/Danmarkshavn National Park (east coast) -GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit -GL +7634-06847 America/Thule Thule/Pituffik -GR +3758+02343 Europe/Athens -GS -5416-03632 Atlantic/South_Georgia -GT +1438-09031 America/Guatemala -GU,MP +1328+14445 Pacific/Guam -GW +1151-01535 Africa/Bissau -GY +0648-05810 America/Guyana -HK +2217+11409 Asia/Hong_Kong -HN +1406-08713 America/Tegucigalpa -HT +1832-07220 America/Port-au-Prince -HU +4730+01905 Europe/Budapest -ID -0610+10648 Asia/Jakarta Java, Sumatra -ID -0002+10920 Asia/Pontianak Borneo (west, central) -ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west) -ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas -IE +5320-00615 Europe/Dublin -IL +314650+0351326 Asia/Jerusalem -IN +2232+08822 Asia/Kolkata -IO -0720+07225 Indian/Chagos -IQ +3321+04425 Asia/Baghdad -IR +3540+05126 Asia/Tehran -IS +6409-02151 Atlantic/Reykjavik -IT,SM,VA +4154+01229 Europe/Rome -JM +175805-0764736 America/Jamaica -JO +3157+03556 Asia/Amman -JP +353916+1394441 Asia/Tokyo -KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi -KG +4254+07436 Asia/Bishkek -KI +0125+17300 Pacific/Tarawa Gilbert Islands -KI -0308-17105 Pacific/Enderbury Phoenix Islands -KI +0152-15720 Pacific/Kiritimati Line Islands -KP +3901+12545 Asia/Pyongyang -KR +3733+12658 Asia/Seoul -KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) -KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda -KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe -KZ +4431+05016 Asia/Aqtau Mangghystaū/Mankistau -KZ +4707+05156 Asia/Atyrau Atyraū/Atirau/Gur'yev -KZ +5113+05121 Asia/Oral West Kazakhstan -LB +3353+03530 Asia/Beirut -LK +0656+07951 Asia/Colombo -LR +0618-01047 Africa/Monrovia -LT +5441+02519 Europe/Vilnius -LU +4936+00609 Europe/Luxembourg -LV +5657+02406 Europe/Riga -LY +3254+01311 Africa/Tripoli -MA +3339-00735 Africa/Casablanca -MC +4342+00723 Europe/Monaco -MD +4700+02850 Europe/Chisinau -MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) -MH +0905+16720 Pacific/Kwajalein Kwajalein -MM +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) -MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan -MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar -MO +221150+1133230 Asia/Macau -MQ +1436-06105 America/Martinique -MT +3554+01431 Europe/Malta -MU -2010+05730 Indian/Mauritius -MV +0410+07330 Indian/Maldives -MX +1924-09909 America/Mexico_City Central Time -MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo -MX +2058-08937 America/Merida Central Time - Campeche, Yucatán -MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo León, Tamaulipas (most areas) -MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo León, Tamaulipas (US border) -MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa -MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) -MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) -MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora -MX +3232-11701 America/Tijuana Pacific Time US - Baja California -MX +2048-10515 America/Bahia_Banderas Central Time - Bahía de Banderas -MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) -MY +0133+11020 Asia/Kuching Sabah, Sarawak -MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time -NA -2234+01706 Africa/Windhoek -NC -2216+16627 Pacific/Noumea -NF -2903+16758 Pacific/Norfolk -NG,AO,BJ,CD,CF,CG,CM,GA,GQ,NE +0627+00324 Africa/Lagos West Africa Time -NI +1209-08617 America/Managua -NL +5222+00454 Europe/Amsterdam -NO,SJ +5955+01045 Europe/Oslo -NP +2743+08519 Asia/Kathmandu -NR -0031+16655 Pacific/Nauru -NU -1901-16955 Pacific/Niue -NZ,AQ -3652+17446 Pacific/Auckland New Zealand time -NZ -4357-17633 Pacific/Chatham Chatham Islands -PA,KY +0858-07932 America/Panama -PE -1203-07703 America/Lima -PF -1732-14934 Pacific/Tahiti Society Islands -PF -0900-13930 Pacific/Marquesas Marquesas Islands -PF -2308-13457 Pacific/Gambier Gambier Islands -PG -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas) -PG -0613+15534 Pacific/Bougainville Bougainville -PH +1435+12100 Asia/Manila -PK +2452+06703 Asia/Karachi -PL +5215+02100 Europe/Warsaw -PM +4703-05620 America/Miquelon -PN -2504-13005 Pacific/Pitcairn -PR +182806-0660622 America/Puerto_Rico -PS +3130+03428 Asia/Gaza Gaza Strip -PS +313200+0350542 Asia/Hebron West Bank -PT +3843-00908 Europe/Lisbon Portugal (mainland) -PT +3238-01654 Atlantic/Madeira Madeira Islands -PT +3744-02540 Atlantic/Azores Azores -PW +0720+13429 Pacific/Palau -PY -2516-05740 America/Asuncion -QA,BH +2517+05132 Asia/Qatar -RE,TF -2052+05528 Indian/Reunion Réunion, Crozet, Scattered Islands -RO +4426+02606 Europe/Bucharest -RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade -RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad -RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area -RU +4457+03406 Europe/Simferopol MSK+00 - Crimea -RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd -RU +5836+04939 Europe/Kirov MSK+00 - Kirov -RU +4621+04803 Europe/Astrakhan MSK+01 - Astrakhan -RU +5134+04602 Europe/Saratov MSK+01 - Saratov -RU +5420+04824 Europe/Ulyanovsk MSK+01 - Ulyanovsk -RU +5312+05009 Europe/Samara MSK+01 - Samara, Udmurtia -RU +5651+06036 Asia/Yekaterinburg MSK+02 - Urals -RU +5500+07324 Asia/Omsk MSK+03 - Omsk -RU +5502+08255 Asia/Novosibirsk MSK+04 - Novosibirsk -RU +5322+08345 Asia/Barnaul MSK+04 - Altai -RU +5630+08458 Asia/Tomsk MSK+04 - Tomsk -RU +5345+08707 Asia/Novokuznetsk MSK+04 - Kemerovo -RU +5601+09250 Asia/Krasnoyarsk MSK+04 - Krasnoyarsk area -RU +5216+10420 Asia/Irkutsk MSK+05 - Irkutsk, Buryatia -RU +5203+11328 Asia/Chita MSK+06 - Zabaykalsky -RU +6200+12940 Asia/Yakutsk MSK+06 - Lena River -RU +623923+1353314 Asia/Khandyga MSK+06 - Tomponsky, Ust-Maysky -RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River -RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky -RU +5934+15048 Asia/Magadan MSK+08 - Magadan -RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is -RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka -RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea -SA,KW,YE +2438+04643 Asia/Riyadh -SB -0932+16012 Pacific/Guadalcanal -SC -0440+05528 Indian/Mahe -SD +1536+03232 Africa/Khartoum -SE +5920+01803 Europe/Stockholm -SG +0117+10351 Asia/Singapore -SR +0550-05510 America/Paramaribo -SS +0451+03137 Africa/Juba -ST +0020+00644 Africa/Sao_Tome -SV +1342-08912 America/El_Salvador -SY +3330+03618 Asia/Damascus -TC +2128-07108 America/Grand_Turk -TD +1207+01503 Africa/Ndjamena -TF -492110+0701303 Indian/Kerguelen Kerguelen, St Paul Island, Amsterdam Island -TH,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) -TJ +3835+06848 Asia/Dushanbe -TK -0922-17114 Pacific/Fakaofo -TL -0833+12535 Asia/Dili -TM +3757+05823 Asia/Ashgabat -TN +3648+01011 Africa/Tunis -TO -2110-17510 Pacific/Tongatapu -TR +4101+02858 Europe/Istanbul -TT,AG,AI,BL,DM,GD,GP,KN,LC,MF,MS,VC,VG,VI +1039-06131 America/Port_of_Spain -TV -0831+17913 Pacific/Funafuti -TW +2503+12130 Asia/Taipei -UA +5026+03031 Europe/Kiev Ukraine (most areas) -UA +4837+02218 Europe/Uzhgorod Ruthenia -UA +4750+03510 Europe/Zaporozhye Zaporozh'ye/Zaporizhia; Lugansk/Luhansk (east) -UM +1917+16637 Pacific/Wake Wake Island -US +404251-0740023 America/New_York Eastern (most areas) -US +421953-0830245 America/Detroit Eastern - MI (most areas) -US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) -US +364947-0845057 America/Kentucky/Monticello Eastern - KY (Wayne) -US +394606-0860929 America/Indiana/Indianapolis Eastern - IN (most areas) -US +384038-0873143 America/Indiana/Vincennes Eastern - IN (Da, Du, K, Mn) -US +410305-0863611 America/Indiana/Winamac Eastern - IN (Pulaski) -US +382232-0862041 America/Indiana/Marengo Eastern - IN (Crawford) -US +382931-0871643 America/Indiana/Petersburg Eastern - IN (Pike) -US +384452-0850402 America/Indiana/Vevay Eastern - IN (Switzerland) -US +415100-0873900 America/Chicago Central (most areas) -US +375711-0864541 America/Indiana/Tell_City Central - IN (Perry) -US +411745-0863730 America/Indiana/Knox Central - IN (Starke) -US +450628-0873651 America/Menominee Central - MI (Wisconsin border) -US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) -US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) -US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) -US +394421-1045903 America/Denver Mountain (most areas) -US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US +332654-1120424 America/Phoenix MST - Arizona (except Navajo) -US +340308-1181434 America/Los_Angeles Pacific -US +611305-1495401 America/Anchorage Alaska (most areas) -US +581807-1342511 America/Juneau Alaska - Juneau area -US +571035-1351807 America/Sitka Alaska - Sitka area -US +550737-1313435 America/Metlakatla Alaska - Annette Island -US +593249-1394338 America/Yakutat Alaska - Yakutat -US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Aleutian Islands -US,UM +211825-1575130 Pacific/Honolulu Hawaii -UY -345433-0561245 America/Montevideo -UZ +3940+06648 Asia/Samarkand Uzbekistan (west) -UZ +4120+06918 Asia/Tashkent Uzbekistan (east) -VE +1030-06656 America/Caracas -VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south) -VU -1740+16825 Pacific/Efate -WF -1318-17610 Pacific/Wallis -WS -1350-17144 Pacific/Apia -ZA,LS,SZ -2615+02800 Africa/Johannesburg diff --git a/lib/requests/__init__.py b/lib/requests/__init__.py deleted file mode 100644 index bc168ee..0000000 --- a/lib/requests/__init__.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- - -# __ -# /__) _ _ _ _ _/ _ -# / ( (- (/ (/ (- _) / _) -# / - -""" -Requests HTTP Library -~~~~~~~~~~~~~~~~~~~~~ - -Requests is an HTTP library, written in Python, for human beings. Basic GET -usage: - - >>> import requests - >>> r = requests.get('https://www.python.org') - >>> r.status_code - 200 - >>> 'Python is a programming language' in r.content - True - -... or POST: - - >>> payload = dict(key1='value1', key2='value2') - >>> r = requests.post('https://httpbin.org/post', data=payload) - >>> print(r.text) - { - ... - "form": { - "key2": "value2", - "key1": "value1" - }, - ... - } - -The other HTTP methods are supported - see `requests.api`. Full documentation -is at <http://python-requests.org>. - -:copyright: (c) 2017 by Kenneth Reitz. -:license: Apache 2.0, see LICENSE for more details. -""" - -import urllib3 -import chardet -import warnings -from .exceptions import RequestsDependencyWarning - - -def check_compatibility(urllib3_version, chardet_version): - urllib3_version = urllib3_version.split('.') - assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. - - # Sometimes, urllib3 only reports its version as 16.1. - if len(urllib3_version) == 2: - urllib3_version.append('0') - - # Check urllib3 for compatibility. - major, minor, patch = urllib3_version # noqa: F811 - major, minor, patch = int(major), int(minor), int(patch) - # urllib3 >= 1.21.1, <= 1.24 - assert major == 1 - assert minor >= 21 - assert minor <= 24 - - # Check chardet for compatibility. - major, minor, patch = chardet_version.split('.')[:3] - major, minor, patch = int(major), int(minor), int(patch) - # chardet >= 3.0.2, < 3.1.0 - assert major == 3 - assert minor < 1 - assert patch >= 2 - - -def _check_cryptography(cryptography_version): - # cryptography < 1.3.4 - try: - cryptography_version = list(map(int, cryptography_version.split('.'))) - except ValueError: - return - - if cryptography_version < [1, 3, 4]: - warning = 'Old version of cryptography ({}) may cause slowdown.'.format(cryptography_version) - warnings.warn(warning, RequestsDependencyWarning) - -# Check imported dependencies for compatibility. -try: - check_compatibility(urllib3.__version__, chardet.__version__) -except (AssertionError, ValueError): - warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported " - "version!".format(urllib3.__version__, chardet.__version__), - RequestsDependencyWarning) - -# Attempt to enable urllib3's SNI support, if possible -try: - from urllib3.contrib import pyopenssl - pyopenssl.inject_into_urllib3() - - # Check cryptography version - from cryptography import __version__ as cryptography_version - _check_cryptography(cryptography_version) -except ImportError: - pass - -# urllib3's DependencyWarnings should be silenced. -from urllib3.exceptions import DependencyWarning -warnings.simplefilter('ignore', DependencyWarning) - -from .__version__ import __title__, __description__, __url__, __version__ -from .__version__ import __build__, __author__, __author_email__, __license__ -from .__version__ import __copyright__, __cake__ - -from . import utils -from . import packages -from .models import Request, Response, PreparedRequest -from .api import request, get, head, post, patch, put, delete, options -from .sessions import session, Session -from .status_codes import codes -from .exceptions import ( - RequestException, Timeout, URLRequired, - TooManyRedirects, HTTPError, ConnectionError, - FileModeWarning, ConnectTimeout, ReadTimeout -) - -# Set default logging handler to avoid "No handler found" warnings. -import logging -from logging import NullHandler - -logging.getLogger(__name__).addHandler(NullHandler()) - -# FileModeWarnings go off per the default. -warnings.simplefilter('default', FileModeWarning, append=True) diff --git a/lib/requests/__version__.py b/lib/requests/__version__.py deleted file mode 100644 index 803773a..0000000 --- a/lib/requests/__version__.py +++ /dev/null @@ -1,14 +0,0 @@ -# .-. .-. .-. . . .-. .-. .-. .-. -# |( |- |.| | | |- `-. | `-. -# ' ' `-' `-`.`-' `-' `-' ' `-' - -__title__ = 'requests' -__description__ = 'Python HTTP for Humans.' -__url__ = 'http://python-requests.org' -__version__ = '2.20.1' -__build__ = 0x022001 -__author__ = 'Kenneth Reitz' -__author_email__ = 'me@kennethreitz.org' -__license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2018 Kenneth Reitz' -__cake__ = u'\u2728 \U0001f370 \u2728' diff --git a/lib/requests/_internal_utils.py b/lib/requests/_internal_utils.py deleted file mode 100644 index 759d9a5..0000000 --- a/lib/requests/_internal_utils.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests._internal_utils -~~~~~~~~~~~~~~ - -Provides utility functions that are consumed internally by Requests -which depend on extremely few external helpers (such as compat) -""" - -from .compat import is_py2, builtin_str, str - - -def to_native_string(string, encoding='ascii'): - """Given a string object, regardless of type, returns a representation of - that string in the native string type, encoding and decoding where - necessary. This assumes ASCII unless told otherwise. - """ - if isinstance(string, builtin_str): - out = string - else: - if is_py2: - out = string.encode(encoding) - else: - out = string.decode(encoding) - - return out - - -def unicode_is_ascii(u_string): - """Determine if unicode string only contains ASCII characters. - - :param str u_string: unicode string to check. Must be unicode - and not Python 2 `str`. - :rtype: bool - """ - assert isinstance(u_string, str) - try: - u_string.encode('ascii') - return True - except UnicodeEncodeError: - return False diff --git a/lib/requests/adapters.py b/lib/requests/adapters.py deleted file mode 100644 index fa4d9b3..0000000 --- a/lib/requests/adapters.py +++ /dev/null @@ -1,533 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.adapters -~~~~~~~~~~~~~~~~~ - -This module contains the transport adapters that Requests uses to define -and maintain connections. -""" - -import os.path -import socket - -from urllib3.poolmanager import PoolManager, proxy_from_url -from urllib3.response import HTTPResponse -from urllib3.util import parse_url -from urllib3.util import Timeout as TimeoutSauce -from urllib3.util.retry import Retry -from urllib3.exceptions import ClosedPoolError -from urllib3.exceptions import ConnectTimeoutError -from urllib3.exceptions import HTTPError as _HTTPError -from urllib3.exceptions import MaxRetryError -from urllib3.exceptions import NewConnectionError -from urllib3.exceptions import ProxyError as _ProxyError -from urllib3.exceptions import ProtocolError -from urllib3.exceptions import ReadTimeoutError -from urllib3.exceptions import SSLError as _SSLError -from urllib3.exceptions import ResponseError -from urllib3.exceptions import LocationValueError - -from .models import Response -from .compat import urlparse, basestring -from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths, - get_encoding_from_headers, prepend_scheme_if_needed, - get_auth_from_url, urldefragauth, select_proxy) -from .structures import CaseInsensitiveDict -from .cookies import extract_cookies_to_jar -from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, - ProxyError, RetryError, InvalidSchema, InvalidProxyURL, - InvalidURL) -from .auth import _basic_auth_str - -try: - from urllib3.contrib.socks import SOCKSProxyManager -except ImportError: - def SOCKSProxyManager(*args, **kwargs): - raise InvalidSchema("Missing dependencies for SOCKS support.") - -DEFAULT_POOLBLOCK = False -DEFAULT_POOLSIZE = 10 -DEFAULT_RETRIES = 0 -DEFAULT_POOL_TIMEOUT = None - - -class BaseAdapter(object): - """The Base Transport Adapter""" - - def __init__(self): - super(BaseAdapter, self).__init__() - - def send(self, request, stream=False, timeout=None, verify=True, - cert=None, proxies=None): - """Sends PreparedRequest object. Returns Response object. - - :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. - :param stream: (optional) Whether to stream the request content. - :param timeout: (optional) How long to wait for the server to send - data before giving up, as a float, or a :ref:`(connect timeout, - read timeout) <timeouts>` tuple. - :type timeout: float or tuple - :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string, in which case it must be a path - to a CA bundle to use - :param cert: (optional) Any user-provided SSL certificate to be trusted. - :param proxies: (optional) The proxies dictionary to apply to the request. - """ - raise NotImplementedError - - def close(self): - """Cleans up adapter specific items.""" - raise NotImplementedError - - -class HTTPAdapter(BaseAdapter): - """The built-in HTTP Adapter for urllib3. - - Provides a general-case interface for Requests sessions to contact HTTP and - HTTPS urls by implementing the Transport Adapter interface. This class will - usually be created by the :class:`Session <Session>` class under the - covers. - - :param pool_connections: The number of urllib3 connection pools to cache. - :param pool_maxsize: The maximum number of connections to save in the pool. - :param max_retries: The maximum number of retries each connection - should attempt. Note, this applies only to failed DNS lookups, socket - connections and connection timeouts, never to requests where data has - made it to the server. By default, Requests does not retry failed - connections. If you need granular control over the conditions under - which we retry a request, import urllib3's ``Retry`` class and pass - that instead. - :param pool_block: Whether the connection pool should block for connections. - - Usage:: - - >>> import requests - >>> s = requests.Session() - >>> a = requests.adapters.HTTPAdapter(max_retries=3) - >>> s.mount('http://', a) - """ - __attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize', - '_pool_block'] - - def __init__(self, pool_connections=DEFAULT_POOLSIZE, - pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES, - pool_block=DEFAULT_POOLBLOCK): - if max_retries == DEFAULT_RETRIES: - self.max_retries = Retry(0, read=False) - else: - self.max_retries = Retry.from_int(max_retries) - self.config = {} - self.proxy_manager = {} - - super(HTTPAdapter, self).__init__() - - self._pool_connections = pool_connections - self._pool_maxsize = pool_maxsize - self._pool_block = pool_block - - self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) - - def __getstate__(self): - return {attr: getattr(self, attr, None) for attr in self.__attrs__} - - def __setstate__(self, state): - # Can't handle by adding 'proxy_manager' to self.__attrs__ because - # self.poolmanager uses a lambda function, which isn't pickleable. - self.proxy_manager = {} - self.config = {} - - for attr, value in state.items(): - setattr(self, attr, value) - - self.init_poolmanager(self._pool_connections, self._pool_maxsize, - block=self._pool_block) - - def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): - """Initializes a urllib3 PoolManager. - - This method should not be called from user code, and is only - exposed for use when subclassing the - :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. - - :param connections: The number of urllib3 connection pools to cache. - :param maxsize: The maximum number of connections to save in the pool. - :param block: Block when no free connections are available. - :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. - """ - # save these values for pickling - self._pool_connections = connections - self._pool_maxsize = maxsize - self._pool_block = block - - self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, - block=block, strict=True, **pool_kwargs) - - def proxy_manager_for(self, proxy, **proxy_kwargs): - """Return urllib3 ProxyManager for the given proxy. - - This method should not be called from user code, and is only - exposed for use when subclassing the - :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. - - :param proxy: The proxy to return a urllib3 ProxyManager for. - :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. - :returns: ProxyManager - :rtype: urllib3.ProxyManager - """ - if proxy in self.proxy_manager: - manager = self.proxy_manager[proxy] - elif proxy.lower().startswith('socks'): - username, password = get_auth_from_url(proxy) - manager = self.proxy_manager[proxy] = SOCKSProxyManager( - proxy, - username=username, - password=password, - num_pools=self._pool_connections, - maxsize=self._pool_maxsize, - block=self._pool_block, - **proxy_kwargs - ) - else: - proxy_headers = self.proxy_headers(proxy) - manager = self.proxy_manager[proxy] = proxy_from_url( - proxy, - proxy_headers=proxy_headers, - num_pools=self._pool_connections, - maxsize=self._pool_maxsize, - block=self._pool_block, - **proxy_kwargs) - - return manager - - def cert_verify(self, conn, url, verify, cert): - """Verify a SSL certificate. This method should not be called from user - code, and is only exposed for use when subclassing the - :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. - - :param conn: The urllib3 connection object associated with the cert. - :param url: The requested URL. - :param verify: Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string, in which case it must be a path - to a CA bundle to use - :param cert: The SSL certificate to verify. - """ - if url.lower().startswith('https') and verify: - - cert_loc = None - - # Allow self-specified cert location. - if verify is not True: - cert_loc = verify - - if not cert_loc: - cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) - - if not cert_loc or not os.path.exists(cert_loc): - raise IOError("Could not find a suitable TLS CA certificate bundle, " - "invalid path: {}".format(cert_loc)) - - conn.cert_reqs = 'CERT_REQUIRED' - - if not os.path.isdir(cert_loc): - conn.ca_certs = cert_loc - else: - conn.ca_cert_dir = cert_loc - else: - conn.cert_reqs = 'CERT_NONE' - conn.ca_certs = None - conn.ca_cert_dir = None - - if cert: - if not isinstance(cert, basestring): - conn.cert_file = cert[0] - conn.key_file = cert[1] - else: - conn.cert_file = cert - conn.key_file = None - if conn.cert_file and not os.path.exists(conn.cert_file): - raise IOError("Could not find the TLS certificate file, " - "invalid path: {}".format(conn.cert_file)) - if conn.key_file and not os.path.exists(conn.key_file): - raise IOError("Could not find the TLS key file, " - "invalid path: {}".format(conn.key_file)) - - def build_response(self, req, resp): - """Builds a :class:`Response <requests.Response>` object from a urllib3 - response. This should not be called from user code, and is only exposed - for use when subclassing the - :class:`HTTPAdapter <requests.adapters.HTTPAdapter>` - - :param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response. - :param resp: The urllib3 response object. - :rtype: requests.Response - """ - response = Response() - - # Fallback to None if there's no status_code, for whatever reason. - response.status_code = getattr(resp, 'status', None) - - # Make headers case-insensitive. - response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {})) - - # Set encoding. - response.encoding = get_encoding_from_headers(response.headers) - response.raw = resp - response.reason = response.raw.reason - - if isinstance(req.url, bytes): - response.url = req.url.decode('utf-8') - else: - response.url = req.url - - # Add new cookies from the server. - extract_cookies_to_jar(response.cookies, req, resp) - - # Give the Response some context. - response.request = req - response.connection = self - - return response - - def get_connection(self, url, proxies=None): - """Returns a urllib3 connection for the given URL. This should not be - called from user code, and is only exposed for use when subclassing the - :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. - - :param url: The URL to connect to. - :param proxies: (optional) A Requests-style dictionary of proxies used on this request. - :rtype: urllib3.ConnectionPool - """ - proxy = select_proxy(url, proxies) - - if proxy: - proxy = prepend_scheme_if_needed(proxy, 'http') - proxy_url = parse_url(proxy) - if not proxy_url.host: - raise InvalidProxyURL("Please check proxy URL. It is malformed" - " and could be missing the host.") - proxy_manager = self.proxy_manager_for(proxy) - conn = proxy_manager.connection_from_url(url) - else: - # Only scheme should be lower case - parsed = urlparse(url) - url = parsed.geturl() - conn = self.poolmanager.connection_from_url(url) - - return conn - - def close(self): - """Disposes of any internal state. - - Currently, this closes the PoolManager and any active ProxyManager, - which closes any pooled connections. - """ - self.poolmanager.clear() - for proxy in self.proxy_manager.values(): - proxy.clear() - - def request_url(self, request, proxies): - """Obtain the url to use when making the final request. - - If the message is being sent through a HTTP proxy, the full URL has to - be used. Otherwise, we should only use the path portion of the URL. - - This should not be called from user code, and is only exposed for use - when subclassing the - :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. - - :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. - :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. - :rtype: str - """ - proxy = select_proxy(request.url, proxies) - scheme = urlparse(request.url).scheme - - is_proxied_http_request = (proxy and scheme != 'https') - using_socks_proxy = False - if proxy: - proxy_scheme = urlparse(proxy).scheme.lower() - using_socks_proxy = proxy_scheme.startswith('socks') - - url = request.path_url - if is_proxied_http_request and not using_socks_proxy: - url = urldefragauth(request.url) - - return url - - def add_headers(self, request, **kwargs): - """Add any headers needed by the connection. As of v2.0 this does - nothing by default, but is left for overriding by users that subclass - the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. - - This should not be called from user code, and is only exposed for use - when subclassing the - :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. - - :param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to. - :param kwargs: The keyword arguments from the call to send(). - """ - pass - - def proxy_headers(self, proxy): - """Returns a dictionary of the headers to add to any request sent - through a proxy. This works with urllib3 magic to ensure that they are - correctly sent to the proxy, rather than in a tunnelled request if - CONNECT is being used. - - This should not be called from user code, and is only exposed for use - when subclassing the - :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`. - - :param proxy: The url of the proxy being used for this request. - :rtype: dict - """ - headers = {} - username, password = get_auth_from_url(proxy) - - if username: - headers['Proxy-Authorization'] = _basic_auth_str(username, - password) - - return headers - - def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): - """Sends PreparedRequest object. Returns Response object. - - :param request: The :class:`PreparedRequest <PreparedRequest>` being sent. - :param stream: (optional) Whether to stream the request content. - :param timeout: (optional) How long to wait for the server to send - data before giving up, as a float, or a :ref:`(connect timeout, - read timeout) <timeouts>` tuple. - :type timeout: float or tuple or urllib3 Timeout object - :param verify: (optional) Either a boolean, in which case it controls whether - we verify the server's TLS certificate, or a string, in which case it - must be a path to a CA bundle to use - :param cert: (optional) Any user-provided SSL certificate to be trusted. - :param proxies: (optional) The proxies dictionary to apply to the request. - :rtype: requests.Response - """ - - try: - conn = self.get_connection(request.url, proxies) - except LocationValueError as e: - raise InvalidURL(e, request=request) - - self.cert_verify(conn, request.url, verify, cert) - url = self.request_url(request, proxies) - self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies) - - chunked = not (request.body is None or 'Content-Length' in request.headers) - - if isinstance(timeout, tuple): - try: - connect, read = timeout - timeout = TimeoutSauce(connect=connect, read=read) - except ValueError as e: - # this may raise a string formatting error. - err = ("Invalid timeout {}. Pass a (connect, read) " - "timeout tuple, or a single float to set " - "both timeouts to the same value".format(timeout)) - raise ValueError(err) - elif isinstance(timeout, TimeoutSauce): - pass - else: - timeout = TimeoutSauce(connect=timeout, read=timeout) - - try: - if not chunked: - resp = conn.urlopen( - method=request.method, - url=url, - body=request.body, - headers=request.headers, - redirect=False, - assert_same_host=False, - preload_content=False, - decode_content=False, - retries=self.max_retries, - timeout=timeout - ) - - # Send the request. - else: - if hasattr(conn, 'proxy_pool'): - conn = conn.proxy_pool - - low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) - - try: - low_conn.putrequest(request.method, - url, - skip_accept_encoding=True) - - for header, value in request.headers.items(): - low_conn.putheader(header, value) - - low_conn.endheaders() - - for i in request.body: - low_conn.send(hex(len(i))[2:].encode('utf-8')) - low_conn.send(b'\r\n') - low_conn.send(i) - low_conn.send(b'\r\n') - low_conn.send(b'0\r\n\r\n') - - # Receive the response from the server - try: - # For Python 2.7, use buffering of HTTP responses - r = low_conn.getresponse(buffering=True) - except TypeError: - # For compatibility with Python 3.3+ - r = low_conn.getresponse() - - resp = HTTPResponse.from_httplib( - r, - pool=conn, - connection=low_conn, - preload_content=False, - decode_content=False - ) - except: - # If we hit any problems here, clean up the connection. - # Then, reraise so that we can handle the actual exception. - low_conn.close() - raise - - except (ProtocolError, socket.error) as err: - raise ConnectionError(err, request=request) - - except MaxRetryError as e: - if isinstance(e.reason, ConnectTimeoutError): - # TODO: Remove this in 3.0.0: see #2811 - if not isinstance(e.reason, NewConnectionError): - raise ConnectTimeout(e, request=request) - - if isinstance(e.reason, ResponseError): - raise RetryError(e, request=request) - - if isinstance(e.reason, _ProxyError): - raise ProxyError(e, request=request) - - if isinstance(e.reason, _SSLError): - # This branch is for urllib3 v1.22 and later. - raise SSLError(e, request=request) - - raise ConnectionError(e, request=request) - - except ClosedPoolError as e: - raise ConnectionError(e, request=request) - - except _ProxyError as e: - raise ProxyError(e) - - except (_SSLError, _HTTPError) as e: - if isinstance(e, _SSLError): - # This branch is for urllib3 versions earlier than v1.22 - raise SSLError(e, request=request) - elif isinstance(e, ReadTimeoutError): - raise ReadTimeout(e, request=request) - else: - raise - - return self.build_response(request, resp) diff --git a/lib/requests/api.py b/lib/requests/api.py deleted file mode 100644 index abada96..0000000 --- a/lib/requests/api.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.api -~~~~~~~~~~~~ - -This module implements the Requests API. - -:copyright: (c) 2012 by Kenneth Reitz. -:license: Apache2, see LICENSE for more details. -""" - -from . import sessions - - -def request(method, url, **kwargs): - """Constructs and sends a :class:`Request <Request>`. - - :param method: method for the new :class:`Request` object. - :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary, list of tuples or bytes to send - in the body of the :class:`Request`. - :param data: (optional) Dictionary, list of tuples, bytes, or file-like - object to send in the body of the :class:`Request`. - :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. - :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. - :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. - :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. - ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` - or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string - defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers - to add for the file. - :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. - :param timeout: (optional) How many seconds to wait for the server to send data - before giving up, as a float, or a :ref:`(connect timeout, read - timeout) <timeouts>` tuple. - :type timeout: float or tuple - :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. - :type allow_redirects: bool - :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. - :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. - :param stream: (optional) if ``False``, the response content will be immediately downloaded. - :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. - :return: :class:`Response <Response>` object - :rtype: requests.Response - - Usage:: - - >>> import requests - >>> req = requests.request('GET', 'https://httpbin.org/get') - <Response [200]> - """ - - # By using the 'with' statement we are sure the session is closed, thus we - # avoid leaving sockets open which can trigger a ResourceWarning in some - # cases, and look like a memory leak in others. - with sessions.Session() as session: - return session.request(method=method, url=url, **kwargs) - - -def get(url, params=None, **kwargs): - r"""Sends a GET request. - - :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary, list of tuples or bytes to send - in the body of the :class:`Request`. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :return: :class:`Response <Response>` object - :rtype: requests.Response - """ - - kwargs.setdefault('allow_redirects', True) - return request('get', url, params=params, **kwargs) - - -def options(url, **kwargs): - r"""Sends an OPTIONS request. - - :param url: URL for the new :class:`Request` object. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :return: :class:`Response <Response>` object - :rtype: requests.Response - """ - - kwargs.setdefault('allow_redirects', True) - return request('options', url, **kwargs) - - -def head(url, **kwargs): - r"""Sends a HEAD request. - - :param url: URL for the new :class:`Request` object. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :return: :class:`Response <Response>` object - :rtype: requests.Response - """ - - kwargs.setdefault('allow_redirects', False) - return request('head', url, **kwargs) - - -def post(url, data=None, json=None, **kwargs): - r"""Sends a POST request. - - :param url: URL for the new :class:`Request` object. - :param data: (optional) Dictionary, list of tuples, bytes, or file-like - object to send in the body of the :class:`Request`. - :param json: (optional) json data to send in the body of the :class:`Request`. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :return: :class:`Response <Response>` object - :rtype: requests.Response - """ - - return request('post', url, data=data, json=json, **kwargs) - - -def put(url, data=None, **kwargs): - r"""Sends a PUT request. - - :param url: URL for the new :class:`Request` object. - :param data: (optional) Dictionary, list of tuples, bytes, or file-like - object to send in the body of the :class:`Request`. - :param json: (optional) json data to send in the body of the :class:`Request`. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :return: :class:`Response <Response>` object - :rtype: requests.Response - """ - - return request('put', url, data=data, **kwargs) - - -def patch(url, data=None, **kwargs): - r"""Sends a PATCH request. - - :param url: URL for the new :class:`Request` object. - :param data: (optional) Dictionary, list of tuples, bytes, or file-like - object to send in the body of the :class:`Request`. - :param json: (optional) json data to send in the body of the :class:`Request`. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :return: :class:`Response <Response>` object - :rtype: requests.Response - """ - - return request('patch', url, data=data, **kwargs) - - -def delete(url, **kwargs): - r"""Sends a DELETE request. - - :param url: URL for the new :class:`Request` object. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :return: :class:`Response <Response>` object - :rtype: requests.Response - """ - - return request('delete', url, **kwargs) diff --git a/lib/requests/auth.py b/lib/requests/auth.py deleted file mode 100644 index bdde51c..0000000 --- a/lib/requests/auth.py +++ /dev/null @@ -1,305 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.auth -~~~~~~~~~~~~~ - -This module contains the authentication handlers for Requests. -""" - -import os -import re -import time -import hashlib -import threading -import warnings - -from base64 import b64encode - -from .compat import urlparse, str, basestring -from .cookies import extract_cookies_to_jar -from ._internal_utils import to_native_string -from .utils import parse_dict_header - -CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' -CONTENT_TYPE_MULTI_PART = 'multipart/form-data' - - -def _basic_auth_str(username, password): - """Returns a Basic Auth string.""" - - # "I want us to put a big-ol' comment on top of it that - # says that this behaviour is dumb but we need to preserve - # it because people are relying on it." - # - Lukasa - # - # These are here solely to maintain backwards compatibility - # for things like ints. This will be removed in 3.0.0. - if not isinstance(username, basestring): - warnings.warn( - "Non-string usernames will no longer be supported in Requests " - "3.0.0. Please convert the object you've passed in ({!r}) to " - "a string or bytes object in the near future to avoid " - "problems.".format(username), - category=DeprecationWarning, - ) - username = str(username) - - if not isinstance(password, basestring): - warnings.warn( - "Non-string passwords will no longer be supported in Requests " - "3.0.0. Please convert the object you've passed in ({!r}) to " - "a string or bytes object in the near future to avoid " - "problems.".format(password), - category=DeprecationWarning, - ) - password = str(password) - # -- End Removal -- - - if isinstance(username, str): - username = username.encode('latin1') - - if isinstance(password, str): - password = password.encode('latin1') - - authstr = 'Basic ' + to_native_string( - b64encode(b':'.join((username, password))).strip() - ) - - return authstr - - -class AuthBase(object): - """Base class that all auth implementations derive from""" - - def __call__(self, r): - raise NotImplementedError('Auth hooks must be callable.') - - -class HTTPBasicAuth(AuthBase): - """Attaches HTTP Basic Authentication to the given Request object.""" - - def __init__(self, username, password): - self.username = username - self.password = password - - def __eq__(self, other): - return all([ - self.username == getattr(other, 'username', None), - self.password == getattr(other, 'password', None) - ]) - - def __ne__(self, other): - return not self == other - - def __call__(self, r): - r.headers['Authorization'] = _basic_auth_str(self.username, self.password) - return r - - -class HTTPProxyAuth(HTTPBasicAuth): - """Attaches HTTP Proxy Authentication to a given Request object.""" - - def __call__(self, r): - r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) - return r - - -class HTTPDigestAuth(AuthBase): - """Attaches HTTP Digest Authentication to the given Request object.""" - - def __init__(self, username, password): - self.username = username - self.password = password - # Keep state in per-thread local storage - self._thread_local = threading.local() - - def init_per_thread_state(self): - # Ensure state is initialized just once per-thread - if not hasattr(self._thread_local, 'init'): - self._thread_local.init = True - self._thread_local.last_nonce = '' - self._thread_local.nonce_count = 0 - self._thread_local.chal = {} - self._thread_local.pos = None - self._thread_local.num_401_calls = None - - def build_digest_header(self, method, url): - """ - :rtype: str - """ - - realm = self._thread_local.chal['realm'] - nonce = self._thread_local.chal['nonce'] - qop = self._thread_local.chal.get('qop') - algorithm = self._thread_local.chal.get('algorithm') - opaque = self._thread_local.chal.get('opaque') - hash_utf8 = None - - if algorithm is None: - _algorithm = 'MD5' - else: - _algorithm = algorithm.upper() - # lambdas assume digest modules are imported at the top level - if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': - def md5_utf8(x): - if isinstance(x, str): - x = x.encode('utf-8') - return hashlib.md5(x).hexdigest() - hash_utf8 = md5_utf8 - elif _algorithm == 'SHA': - def sha_utf8(x): - if isinstance(x, str): - x = x.encode('utf-8') - return hashlib.sha1(x).hexdigest() - hash_utf8 = sha_utf8 - elif _algorithm == 'SHA-256': - def sha256_utf8(x): - if isinstance(x, str): - x = x.encode('utf-8') - return hashlib.sha256(x).hexdigest() - hash_utf8 = sha256_utf8 - elif _algorithm == 'SHA-512': - def sha512_utf8(x): - if isinstance(x, str): - x = x.encode('utf-8') - return hashlib.sha512(x).hexdigest() - hash_utf8 = sha512_utf8 - - KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) - - if hash_utf8 is None: - return None - - # XXX not implemented yet - entdig = None - p_parsed = urlparse(url) - #: path is request-uri defined in RFC 2616 which should not be empty - path = p_parsed.path or "/" - if p_parsed.query: - path += '?' + p_parsed.query - - A1 = '%s:%s:%s' % (self.username, realm, self.password) - A2 = '%s:%s' % (method, path) - - HA1 = hash_utf8(A1) - HA2 = hash_utf8(A2) - - if nonce == self._thread_local.last_nonce: - self._thread_local.nonce_count += 1 - else: - self._thread_local.nonce_count = 1 - ncvalue = '%08x' % self._thread_local.nonce_count - s = str(self._thread_local.nonce_count).encode('utf-8') - s += nonce.encode('utf-8') - s += time.ctime().encode('utf-8') - s += os.urandom(8) - - cnonce = (hashlib.sha1(s).hexdigest()[:16]) - if _algorithm == 'MD5-SESS': - HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) - - if not qop: - respdig = KD(HA1, "%s:%s" % (nonce, HA2)) - elif qop == 'auth' or 'auth' in qop.split(','): - noncebit = "%s:%s:%s:%s:%s" % ( - nonce, ncvalue, cnonce, 'auth', HA2 - ) - respdig = KD(HA1, noncebit) - else: - # XXX handle auth-int. - return None - - self._thread_local.last_nonce = nonce - - # XXX should the partial digests be encoded too? - base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ - 'response="%s"' % (self.username, realm, nonce, path, respdig) - if opaque: - base += ', opaque="%s"' % opaque - if algorithm: - base += ', algorithm="%s"' % algorithm - if entdig: - base += ', digest="%s"' % entdig - if qop: - base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) - - return 'Digest %s' % (base) - - def handle_redirect(self, r, **kwargs): - """Reset num_401_calls counter on redirects.""" - if r.is_redirect: - self._thread_local.num_401_calls = 1 - - def handle_401(self, r, **kwargs): - """ - Takes the given response and tries digest-auth, if needed. - - :rtype: requests.Response - """ - - # If response is not 4xx, do not auth - # See https://github.com/requests/requests/issues/3772 - if not 400 <= r.status_code < 500: - self._thread_local.num_401_calls = 1 - return r - - if self._thread_local.pos is not None: - # Rewind the file position indicator of the body to where - # it was to resend the request. - r.request.body.seek(self._thread_local.pos) - s_auth = r.headers.get('www-authenticate', '') - - if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: - - self._thread_local.num_401_calls += 1 - pat = re.compile(r'digest ', flags=re.IGNORECASE) - self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) - - # Consume content and release the original connection - # to allow our new request to reuse the same one. - r.content - r.close() - prep = r.request.copy() - extract_cookies_to_jar(prep._cookies, r.request, r.raw) - prep.prepare_cookies(prep._cookies) - - prep.headers['Authorization'] = self.build_digest_header( - prep.method, prep.url) - _r = r.connection.send(prep, **kwargs) - _r.history.append(r) - _r.request = prep - - return _r - - self._thread_local.num_401_calls = 1 - return r - - def __call__(self, r): - # Initialize per-thread state, if needed - self.init_per_thread_state() - # If we have a saved nonce, skip the 401 - if self._thread_local.last_nonce: - r.headers['Authorization'] = self.build_digest_header(r.method, r.url) - try: - self._thread_local.pos = r.body.tell() - except AttributeError: - # In the case of HTTPDigestAuth being reused and the body of - # the previous request was a file-like object, pos has the - # file position of the previous body. Ensure it's set to - # None. - self._thread_local.pos = None - r.register_hook('response', self.handle_401) - r.register_hook('response', self.handle_redirect) - self._thread_local.num_401_calls = 1 - - return r - - def __eq__(self, other): - return all([ - self.username == getattr(other, 'username', None), - self.password == getattr(other, 'password', None) - ]) - - def __ne__(self, other): - return not self == other diff --git a/lib/requests/certs.py b/lib/requests/certs.py deleted file mode 100644 index d1a378d..0000000 --- a/lib/requests/certs.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -requests.certs -~~~~~~~~~~~~~~ - -This module returns the preferred default CA certificate bundle. There is -only one — the one from the certifi package. - -If you are packaging Requests, e.g., for a Linux distribution or a managed -environment, you can change the definition of where() to return a separately -packaged CA bundle. -""" -from certifi import where - -if __name__ == '__main__': - print(where()) diff --git a/lib/requests/compat.py b/lib/requests/compat.py deleted file mode 100644 index c44b35e..0000000 --- a/lib/requests/compat.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.compat -~~~~~~~~~~~~~~~ - -This module handles import compatibility issues between Python 2 and -Python 3. -""" - -import chardet - -import sys - -# ------- -# Pythons -# ------- - -# Syntax sugar. -_ver = sys.version_info - -#: Python 2.x? -is_py2 = (_ver[0] == 2) - -#: Python 3.x? -is_py3 = (_ver[0] == 3) - -try: - import simplejson as json -except ImportError: - import json - -# --------- -# Specifics -# --------- - -if is_py2: - from urllib import ( - quote, unquote, quote_plus, unquote_plus, urlencode, getproxies, - proxy_bypass, proxy_bypass_environment, getproxies_environment) - from urlparse import urlparse, urlunparse, urljoin, urlsplit, urldefrag - from urllib2 import parse_http_list - import cookielib - from Cookie import Morsel - from StringIO import StringIO - from collections import Callable, Mapping, MutableMapping, OrderedDict - - - builtin_str = str - bytes = str - str = unicode - basestring = basestring - numeric_types = (int, long, float) - integer_types = (int, long) - -elif is_py3: - from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag - from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment - from http import cookiejar as cookielib - from http.cookies import Morsel - from io import StringIO - from collections import OrderedDict - from collections.abc import Callable, Mapping, MutableMapping - - builtin_str = str - str = str - bytes = bytes - basestring = (str, bytes) - numeric_types = (int, float) - integer_types = (int,) diff --git a/lib/requests/cookies.py b/lib/requests/cookies.py deleted file mode 100644 index 56fccd9..0000000 --- a/lib/requests/cookies.py +++ /dev/null @@ -1,549 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.cookies -~~~~~~~~~~~~~~~~ - -Compatibility code to be able to use `cookielib.CookieJar` with requests. - -requests.utils imports from here, so be careful with imports. -""" - -import copy -import time -import calendar - -from ._internal_utils import to_native_string -from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping - -try: - import threading -except ImportError: - import dummy_threading as threading - - -class MockRequest(object): - """Wraps a `requests.Request` to mimic a `urllib2.Request`. - - The code in `cookielib.CookieJar` expects this interface in order to correctly - manage cookie policies, i.e., determine whether a cookie can be set, given the - domains of the request and the cookie. - - The original request object is read-only. The client is responsible for collecting - the new headers via `get_new_headers()` and interpreting them appropriately. You - probably want `get_cookie_header`, defined below. - """ - - def __init__(self, request): - self._r = request - self._new_headers = {} - self.type = urlparse(self._r.url).scheme - - def get_type(self): - return self.type - - def get_host(self): - return urlparse(self._r.url).netloc - - def get_origin_req_host(self): - return self.get_host() - - def get_full_url(self): - # Only return the response's URL if the user hadn't set the Host - # header - if not self._r.headers.get('Host'): - return self._r.url - # If they did set it, retrieve it and reconstruct the expected domain - host = to_native_string(self._r.headers['Host'], encoding='utf-8') - parsed = urlparse(self._r.url) - # Reconstruct the URL as we expect it - return urlunparse([ - parsed.scheme, host, parsed.path, parsed.params, parsed.query, - parsed.fragment - ]) - - def is_unverifiable(self): - return True - - def has_header(self, name): - return name in self._r.headers or name in self._new_headers - - def get_header(self, name, default=None): - return self._r.headers.get(name, self._new_headers.get(name, default)) - - def add_header(self, key, val): - """cookielib has no legitimate use for this method; add it back if you find one.""" - raise NotImplementedError("Cookie headers should be added with add_unredirected_header()") - - def add_unredirected_header(self, name, value): - self._new_headers[name] = value - - def get_new_headers(self): - return self._new_headers - - @property - def unverifiable(self): - return self.is_unverifiable() - - @property - def origin_req_host(self): - return self.get_origin_req_host() - - @property - def host(self): - return self.get_host() - - -class MockResponse(object): - """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. - - ...what? Basically, expose the parsed HTTP headers from the server response - the way `cookielib` expects to see them. - """ - - def __init__(self, headers): - """Make a MockResponse for `cookielib` to read. - - :param headers: a httplib.HTTPMessage or analogous carrying the headers - """ - self._headers = headers - - def info(self): - return self._headers - - def getheaders(self, name): - self._headers.getheaders(name) - - -def extract_cookies_to_jar(jar, request, response): - """Extract the cookies from the response into a CookieJar. - - :param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar) - :param request: our own requests.Request object - :param response: urllib3.HTTPResponse object - """ - if not (hasattr(response, '_original_response') and - response._original_response): - return - # the _original_response field is the wrapped httplib.HTTPResponse object, - req = MockRequest(request) - # pull out the HTTPMessage with the headers and put it in the mock: - res = MockResponse(response._original_response.msg) - jar.extract_cookies(res, req) - - -def get_cookie_header(jar, request): - """ - Produce an appropriate Cookie header string to be sent with `request`, or None. - - :rtype: str - """ - r = MockRequest(request) - jar.add_cookie_header(r) - return r.get_new_headers().get('Cookie') - - -def remove_cookie_by_name(cookiejar, name, domain=None, path=None): - """Unsets a cookie by name, by default over all domains and paths. - - Wraps CookieJar.clear(), is O(n). - """ - clearables = [] - for cookie in cookiejar: - if cookie.name != name: - continue - if domain is not None and domain != cookie.domain: - continue - if path is not None and path != cookie.path: - continue - clearables.append((cookie.domain, cookie.path, cookie.name)) - - for domain, path, name in clearables: - cookiejar.clear(domain, path, name) - - -class CookieConflictError(RuntimeError): - """There are two cookies that meet the criteria specified in the cookie jar. - Use .get and .set and include domain and path args in order to be more specific. - """ - - -class RequestsCookieJar(cookielib.CookieJar, MutableMapping): - """Compatibility class; is a cookielib.CookieJar, but exposes a dict - interface. - - This is the CookieJar we create by default for requests and sessions that - don't specify one, since some clients may expect response.cookies and - session.cookies to support dict operations. - - Requests does not use the dict interface internally; it's just for - compatibility with external client code. All requests code should work - out of the box with externally provided instances of ``CookieJar``, e.g. - ``LWPCookieJar`` and ``FileCookieJar``. - - Unlike a regular CookieJar, this class is pickleable. - - .. warning:: dictionary operations that are normally O(1) may be O(n). - """ - - def get(self, name, default=None, domain=None, path=None): - """Dict-like get() that also supports optional domain and path args in - order to resolve naming collisions from using one cookie jar over - multiple domains. - - .. warning:: operation is O(n), not O(1). - """ - try: - return self._find_no_duplicates(name, domain, path) - except KeyError: - return default - - def set(self, name, value, **kwargs): - """Dict-like set() that also supports optional domain and path args in - order to resolve naming collisions from using one cookie jar over - multiple domains. - """ - # support client code that unsets cookies by assignment of a None value: - if value is None: - remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path')) - return - - if isinstance(value, Morsel): - c = morsel_to_cookie(value) - else: - c = create_cookie(name, value, **kwargs) - self.set_cookie(c) - return c - - def iterkeys(self): - """Dict-like iterkeys() that returns an iterator of names of cookies - from the jar. - - .. seealso:: itervalues() and iteritems(). - """ - for cookie in iter(self): - yield cookie.name - - def keys(self): - """Dict-like keys() that returns a list of names of cookies from the - jar. - - .. seealso:: values() and items(). - """ - return list(self.iterkeys()) - - def itervalues(self): - """Dict-like itervalues() that returns an iterator of values of cookies - from the jar. - - .. seealso:: iterkeys() and iteritems(). - """ - for cookie in iter(self): - yield cookie.value - - def values(self): - """Dict-like values() that returns a list of values of cookies from the - jar. - - .. seealso:: keys() and items(). - """ - return list(self.itervalues()) - - def iteritems(self): - """Dict-like iteritems() that returns an iterator of name-value tuples - from the jar. - - .. seealso:: iterkeys() and itervalues(). - """ - for cookie in iter(self): - yield cookie.name, cookie.value - - def items(self): - """Dict-like items() that returns a list of name-value tuples from the - jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a - vanilla python dict of key value pairs. - - .. seealso:: keys() and values(). - """ - return list(self.iteritems()) - - def list_domains(self): - """Utility method to list all the domains in the jar.""" - domains = [] - for cookie in iter(self): - if cookie.domain not in domains: - domains.append(cookie.domain) - return domains - - def list_paths(self): - """Utility method to list all the paths in the jar.""" - paths = [] - for cookie in iter(self): - if cookie.path not in paths: - paths.append(cookie.path) - return paths - - def multiple_domains(self): - """Returns True if there are multiple domains in the jar. - Returns False otherwise. - - :rtype: bool - """ - domains = [] - for cookie in iter(self): - if cookie.domain is not None and cookie.domain in domains: - return True - domains.append(cookie.domain) - return False # there is only one domain in jar - - def get_dict(self, domain=None, path=None): - """Takes as an argument an optional domain and path and returns a plain - old Python dict of name-value pairs of cookies that meet the - requirements. - - :rtype: dict - """ - dictionary = {} - for cookie in iter(self): - if ( - (domain is None or cookie.domain == domain) and - (path is None or cookie.path == path) - ): - dictionary[cookie.name] = cookie.value - return dictionary - - def __contains__(self, name): - try: - return super(RequestsCookieJar, self).__contains__(name) - except CookieConflictError: - return True - - def __getitem__(self, name): - """Dict-like __getitem__() for compatibility with client code. Throws - exception if there are more than one cookie with name. In that case, - use the more explicit get() method instead. - - .. warning:: operation is O(n), not O(1). - """ - return self._find_no_duplicates(name) - - def __setitem__(self, name, value): - """Dict-like __setitem__ for compatibility with client code. Throws - exception if there is already a cookie of that name in the jar. In that - case, use the more explicit set() method instead. - """ - self.set(name, value) - - def __delitem__(self, name): - """Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s - ``remove_cookie_by_name()``. - """ - remove_cookie_by_name(self, name) - - def set_cookie(self, cookie, *args, **kwargs): - if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'): - cookie.value = cookie.value.replace('\\"', '') - return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs) - - def update(self, other): - """Updates this jar with cookies from another CookieJar or dict-like""" - if isinstance(other, cookielib.CookieJar): - for cookie in other: - self.set_cookie(copy.copy(cookie)) - else: - super(RequestsCookieJar, self).update(other) - - def _find(self, name, domain=None, path=None): - """Requests uses this method internally to get cookie values. - - If there are conflicting cookies, _find arbitrarily chooses one. - See _find_no_duplicates if you want an exception thrown if there are - conflicting cookies. - - :param name: a string containing name of cookie - :param domain: (optional) string containing domain of cookie - :param path: (optional) string containing path of cookie - :return: cookie.value - """ - for cookie in iter(self): - if cookie.name == name: - if domain is None or cookie.domain == domain: - if path is None or cookie.path == path: - return cookie.value - - raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) - - def _find_no_duplicates(self, name, domain=None, path=None): - """Both ``__get_item__`` and ``get`` call this function: it's never - used elsewhere in Requests. - - :param name: a string containing name of cookie - :param domain: (optional) string containing domain of cookie - :param path: (optional) string containing path of cookie - :raises KeyError: if cookie is not found - :raises CookieConflictError: if there are multiple cookies - that match name and optionally domain and path - :return: cookie.value - """ - toReturn = None - for cookie in iter(self): - if cookie.name == name: - if domain is None or cookie.domain == domain: - if path is None or cookie.path == path: - if toReturn is not None: # if there are multiple cookies that meet passed in criteria - raise CookieConflictError('There are multiple cookies with name, %r' % (name)) - toReturn = cookie.value # we will eventually return this as long as no cookie conflict - - if toReturn: - return toReturn - raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) - - def __getstate__(self): - """Unlike a normal CookieJar, this class is pickleable.""" - state = self.__dict__.copy() - # remove the unpickleable RLock object - state.pop('_cookies_lock') - return state - - def __setstate__(self, state): - """Unlike a normal CookieJar, this class is pickleable.""" - self.__dict__.update(state) - if '_cookies_lock' not in self.__dict__: - self._cookies_lock = threading.RLock() - - def copy(self): - """Return a copy of this RequestsCookieJar.""" - new_cj = RequestsCookieJar() - new_cj.set_policy(self.get_policy()) - new_cj.update(self) - return new_cj - - def get_policy(self): - """Return the CookiePolicy instance used.""" - return self._policy - - -def _copy_cookie_jar(jar): - if jar is None: - return None - - if hasattr(jar, 'copy'): - # We're dealing with an instance of RequestsCookieJar - return jar.copy() - # We're dealing with a generic CookieJar instance - new_jar = copy.copy(jar) - new_jar.clear() - for cookie in jar: - new_jar.set_cookie(copy.copy(cookie)) - return new_jar - - -def create_cookie(name, value, **kwargs): - """Make a cookie from underspecified parameters. - - By default, the pair of `name` and `value` will be set for the domain '' - and sent on every request (this is sometimes called a "supercookie"). - """ - result = { - 'version': 0, - 'name': name, - 'value': value, - 'port': None, - 'domain': '', - 'path': '/', - 'secure': False, - 'expires': None, - 'discard': True, - 'comment': None, - 'comment_url': None, - 'rest': {'HttpOnly': None}, - 'rfc2109': False, - } - - badargs = set(kwargs) - set(result) - if badargs: - err = 'create_cookie() got unexpected keyword arguments: %s' - raise TypeError(err % list(badargs)) - - result.update(kwargs) - result['port_specified'] = bool(result['port']) - result['domain_specified'] = bool(result['domain']) - result['domain_initial_dot'] = result['domain'].startswith('.') - result['path_specified'] = bool(result['path']) - - return cookielib.Cookie(**result) - - -def morsel_to_cookie(morsel): - """Convert a Morsel object into a Cookie containing the one k/v pair.""" - - expires = None - if morsel['max-age']: - try: - expires = int(time.time() + int(morsel['max-age'])) - except ValueError: - raise TypeError('max-age: %s must be integer' % morsel['max-age']) - elif morsel['expires']: - time_template = '%a, %d-%b-%Y %H:%M:%S GMT' - expires = calendar.timegm( - time.strptime(morsel['expires'], time_template) - ) - return create_cookie( - comment=morsel['comment'], - comment_url=bool(morsel['comment']), - discard=False, - domain=morsel['domain'], - expires=expires, - name=morsel.key, - path=morsel['path'], - port=None, - rest={'HttpOnly': morsel['httponly']}, - rfc2109=False, - secure=bool(morsel['secure']), - value=morsel.value, - version=morsel['version'] or 0, - ) - - -def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): - """Returns a CookieJar from a key/value dictionary. - - :param cookie_dict: Dict of key/values to insert into CookieJar. - :param cookiejar: (optional) A cookiejar to add the cookies to. - :param overwrite: (optional) If False, will not replace cookies - already in the jar with new ones. - :rtype: CookieJar - """ - if cookiejar is None: - cookiejar = RequestsCookieJar() - - if cookie_dict is not None: - names_from_jar = [cookie.name for cookie in cookiejar] - for name in cookie_dict: - if overwrite or (name not in names_from_jar): - cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) - - return cookiejar - - -def merge_cookies(cookiejar, cookies): - """Add cookies to cookiejar and returns a merged CookieJar. - - :param cookiejar: CookieJar object to add the cookies to. - :param cookies: Dictionary or CookieJar object to be added. - :rtype: CookieJar - """ - if not isinstance(cookiejar, cookielib.CookieJar): - raise ValueError('You can only merge into CookieJar') - - if isinstance(cookies, dict): - cookiejar = cookiejar_from_dict( - cookies, cookiejar=cookiejar, overwrite=False) - elif isinstance(cookies, cookielib.CookieJar): - try: - cookiejar.update(cookies) - except AttributeError: - for cookie_in_jar in cookies: - cookiejar.set_cookie(cookie_in_jar) - - return cookiejar diff --git a/lib/requests/exceptions.py b/lib/requests/exceptions.py deleted file mode 100644 index a80cad8..0000000 --- a/lib/requests/exceptions.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.exceptions -~~~~~~~~~~~~~~~~~~~ - -This module contains the set of Requests' exceptions. -""" -from urllib3.exceptions import HTTPError as BaseHTTPError - - -class RequestException(IOError): - """There was an ambiguous exception that occurred while handling your - request. - """ - - def __init__(self, *args, **kwargs): - """Initialize RequestException with `request` and `response` objects.""" - response = kwargs.pop('response', None) - self.response = response - self.request = kwargs.pop('request', None) - if (response is not None and not self.request and - hasattr(response, 'request')): - self.request = self.response.request - super(RequestException, self).__init__(*args, **kwargs) - - -class HTTPError(RequestException): - """An HTTP error occurred.""" - - -class ConnectionError(RequestException): - """A Connection error occurred.""" - - -class ProxyError(ConnectionError): - """A proxy error occurred.""" - - -class SSLError(ConnectionError): - """An SSL error occurred.""" - - -class Timeout(RequestException): - """The request timed out. - - Catching this error will catch both - :exc:`~requests.exceptions.ConnectTimeout` and - :exc:`~requests.exceptions.ReadTimeout` errors. - """ - - -class ConnectTimeout(ConnectionError, Timeout): - """The request timed out while trying to connect to the remote server. - - Requests that produced this error are safe to retry. - """ - - -class ReadTimeout(Timeout): - """The server did not send any data in the allotted amount of time.""" - - -class URLRequired(RequestException): - """A valid URL is required to make a request.""" - - -class TooManyRedirects(RequestException): - """Too many redirects.""" - - -class MissingSchema(RequestException, ValueError): - """The URL schema (e.g. http or https) is missing.""" - - -class InvalidSchema(RequestException, ValueError): - """See defaults.py for valid schemas.""" - - -class InvalidURL(RequestException, ValueError): - """The URL provided was somehow invalid.""" - - -class InvalidHeader(RequestException, ValueError): - """The header value provided was somehow invalid.""" - - -class InvalidProxyURL(InvalidURL): - """The proxy URL provided is invalid.""" - - -class ChunkedEncodingError(RequestException): - """The server declared chunked encoding but sent an invalid chunk.""" - - -class ContentDecodingError(RequestException, BaseHTTPError): - """Failed to decode response content""" - - -class StreamConsumedError(RequestException, TypeError): - """The content for this response was already consumed""" - - -class RetryError(RequestException): - """Custom retries logic failed""" - - -class UnrewindableBodyError(RequestException): - """Requests encountered an error when trying to rewind a body""" - -# Warnings - - -class RequestsWarning(Warning): - """Base warning for Requests.""" - pass - - -class FileModeWarning(RequestsWarning, DeprecationWarning): - """A file was opened in text mode, but Requests determined its binary length.""" - pass - - -class RequestsDependencyWarning(RequestsWarning): - """An imported dependency doesn't match the expected version range.""" - pass diff --git a/lib/requests/help.py b/lib/requests/help.py deleted file mode 100644 index e53d35e..0000000 --- a/lib/requests/help.py +++ /dev/null @@ -1,119 +0,0 @@ -"""Module containing bug report helper(s).""" -from __future__ import print_function - -import json -import platform -import sys -import ssl - -import idna -import urllib3 -import chardet - -from . import __version__ as requests_version - -try: - from urllib3.contrib import pyopenssl -except ImportError: - pyopenssl = None - OpenSSL = None - cryptography = None -else: - import OpenSSL - import cryptography - - -def _implementation(): - """Return a dict with the Python implementation and version. - - Provide both the name and the version of the Python implementation - currently running. For example, on CPython 2.7.5 it will return - {'name': 'CPython', 'version': '2.7.5'}. - - This function works best on CPython and PyPy: in particular, it probably - doesn't work for Jython or IronPython. Future investigation should be done - to work out the correct shape of the code for those platforms. - """ - implementation = platform.python_implementation() - - if implementation == 'CPython': - implementation_version = platform.python_version() - elif implementation == 'PyPy': - implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major, - sys.pypy_version_info.minor, - sys.pypy_version_info.micro) - if sys.pypy_version_info.releaselevel != 'final': - implementation_version = ''.join([ - implementation_version, sys.pypy_version_info.releaselevel - ]) - elif implementation == 'Jython': - implementation_version = platform.python_version() # Complete Guess - elif implementation == 'IronPython': - implementation_version = platform.python_version() # Complete Guess - else: - implementation_version = 'Unknown' - - return {'name': implementation, 'version': implementation_version} - - -def info(): - """Generate information for a bug report.""" - try: - platform_info = { - 'system': platform.system(), - 'release': platform.release(), - } - except IOError: - platform_info = { - 'system': 'Unknown', - 'release': 'Unknown', - } - - implementation_info = _implementation() - urllib3_info = {'version': urllib3.__version__} - chardet_info = {'version': chardet.__version__} - - pyopenssl_info = { - 'version': None, - 'openssl_version': '', - } - if OpenSSL: - pyopenssl_info = { - 'version': OpenSSL.__version__, - 'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, - } - cryptography_info = { - 'version': getattr(cryptography, '__version__', ''), - } - idna_info = { - 'version': getattr(idna, '__version__', ''), - } - - system_ssl = ssl.OPENSSL_VERSION_NUMBER - system_ssl_info = { - 'version': '%x' % system_ssl if system_ssl is not None else '' - } - - return { - 'platform': platform_info, - 'implementation': implementation_info, - 'system_ssl': system_ssl_info, - 'using_pyopenssl': pyopenssl is not None, - 'pyOpenSSL': pyopenssl_info, - 'urllib3': urllib3_info, - 'chardet': chardet_info, - 'cryptography': cryptography_info, - 'idna': idna_info, - 'requests': { - 'version': requests_version, - }, - } - - -def main(): - """Pretty-print the bug information as JSON.""" - print(json.dumps(info(), sort_keys=True, indent=2)) - - -if __name__ == '__main__': - main() diff --git a/lib/requests/hooks.py b/lib/requests/hooks.py deleted file mode 100644 index 7a51f21..0000000 --- a/lib/requests/hooks.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.hooks -~~~~~~~~~~~~~~ - -This module provides the capabilities for the Requests hooks system. - -Available hooks: - -``response``: - The response generated from a Request. -""" -HOOKS = ['response'] - - -def default_hooks(): - return {event: [] for event in HOOKS} - -# TODO: response is the only one - - -def dispatch_hook(key, hooks, hook_data, **kwargs): - """Dispatches a hook dictionary on a given piece of data.""" - hooks = hooks or {} - hooks = hooks.get(key) - if hooks: - if hasattr(hooks, '__call__'): - hooks = [hooks] - for hook in hooks: - _hook_data = hook(hook_data, **kwargs) - if _hook_data is not None: - hook_data = _hook_data - return hook_data diff --git a/lib/requests/models.py b/lib/requests/models.py deleted file mode 100644 index 3dded57..0000000 --- a/lib/requests/models.py +++ /dev/null @@ -1,953 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.models -~~~~~~~~~~~~~~~ - -This module contains the primary objects that power Requests. -""" - -import datetime -import sys - -# Import encoding now, to avoid implicit import later. -# Implicit import within threads may cause LookupError when standard library is in a ZIP, -# such as in Embedded Python. See https://github.com/requests/requests/issues/3578. -import encodings.idna - -from urllib3.fields import RequestField -from urllib3.filepost import encode_multipart_formdata -from urllib3.util import parse_url -from urllib3.exceptions import ( - DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) - -from io import UnsupportedOperation -from .hooks import default_hooks -from .structures import CaseInsensitiveDict - -from .auth import HTTPBasicAuth -from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar -from .exceptions import ( - HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, - ContentDecodingError, ConnectionError, StreamConsumedError) -from ._internal_utils import to_native_string, unicode_is_ascii -from .utils import ( - guess_filename, get_auth_from_url, requote_uri, - stream_decode_response_unicode, to_key_val_list, parse_header_links, - iter_slices, guess_json_utf, super_len, check_header_validity) -from .compat import ( - Callable, Mapping, - cookielib, urlunparse, urlsplit, urlencode, str, bytes, - is_py2, chardet, builtin_str, basestring) -from .compat import json as complexjson -from .status_codes import codes - -#: The set of HTTP status codes that indicate an automatically -#: processable redirect. -REDIRECT_STATI = ( - codes.moved, # 301 - codes.found, # 302 - codes.other, # 303 - codes.temporary_redirect, # 307 - codes.permanent_redirect, # 308 -) - -DEFAULT_REDIRECT_LIMIT = 30 -CONTENT_CHUNK_SIZE = 10 * 1024 -ITER_CHUNK_SIZE = 512 - - -class RequestEncodingMixin(object): - @property - def path_url(self): - """Build the path URL to use.""" - - url = [] - - p = urlsplit(self.url) - - path = p.path - if not path: - path = '/' - - url.append(path) - - query = p.query - if query: - url.append('?') - url.append(query) - - return ''.join(url) - - @staticmethod - def _encode_params(data): - """Encode parameters in a piece of data. - - Will successfully encode parameters when passed as a dict or a list of - 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary - if parameters are supplied as a dict. - """ - - if isinstance(data, (str, bytes)): - return data - elif hasattr(data, 'read'): - return data - elif hasattr(data, '__iter__'): - result = [] - for k, vs in to_key_val_list(data): - if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): - vs = [vs] - for v in vs: - if v is not None: - result.append( - (k.encode('utf-8') if isinstance(k, str) else k, - v.encode('utf-8') if isinstance(v, str) else v)) - return urlencode(result, doseq=True) - else: - return data - - @staticmethod - def _encode_files(files, data): - """Build the body for a multipart/form-data request. - - Will successfully encode files when passed as a dict or a list of - tuples. Order is retained if data is a list of tuples but arbitrary - if parameters are supplied as a dict. - The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) - or 4-tuples (filename, fileobj, contentype, custom_headers). - """ - if (not files): - raise ValueError("Files must be provided.") - elif isinstance(data, basestring): - raise ValueError("Data must not be a string.") - - new_fields = [] - fields = to_key_val_list(data or {}) - files = to_key_val_list(files or {}) - - for field, val in fields: - if isinstance(val, basestring) or not hasattr(val, '__iter__'): - val = [val] - for v in val: - if v is not None: - # Don't call str() on bytestrings: in Py3 it all goes wrong. - if not isinstance(v, bytes): - v = str(v) - - new_fields.append( - (field.decode('utf-8') if isinstance(field, bytes) else field, - v.encode('utf-8') if isinstance(v, str) else v)) - - for (k, v) in files: - # support for explicit filename - ft = None - fh = None - if isinstance(v, (tuple, list)): - if len(v) == 2: - fn, fp = v - elif len(v) == 3: - fn, fp, ft = v - else: - fn, fp, ft, fh = v - else: - fn = guess_filename(v) or k - fp = v - - if isinstance(fp, (str, bytes, bytearray)): - fdata = fp - elif hasattr(fp, 'read'): - fdata = fp.read() - elif fp is None: - continue - else: - fdata = fp - - rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) - rf.make_multipart(content_type=ft) - new_fields.append(rf) - - body, content_type = encode_multipart_formdata(new_fields) - - return body, content_type - - -class RequestHooksMixin(object): - def register_hook(self, event, hook): - """Properly register a hook.""" - - if event not in self.hooks: - raise ValueError('Unsupported event specified, with event name "%s"' % (event)) - - if isinstance(hook, Callable): - self.hooks[event].append(hook) - elif hasattr(hook, '__iter__'): - self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) - - def deregister_hook(self, event, hook): - """Deregister a previously registered hook. - Returns True if the hook existed, False if not. - """ - - try: - self.hooks[event].remove(hook) - return True - except ValueError: - return False - - -class Request(RequestHooksMixin): - """A user-created :class:`Request <Request>` object. - - Used to prepare a :class:`PreparedRequest <PreparedRequest>`, which is sent to the server. - - :param method: HTTP method to use. - :param url: URL to send. - :param headers: dictionary of headers to send. - :param files: dictionary of {filename: fileobject} files to multipart upload. - :param data: the body to attach to the request. If a dictionary or - list of tuples ``[(key, value)]`` is provided, form-encoding will - take place. - :param json: json for the body to attach to the request (if files or data is not specified). - :param params: URL parameters to append to the URL. If a dictionary or - list of tuples ``[(key, value)]`` is provided, form-encoding will - take place. - :param auth: Auth handler or (user, pass) tuple. - :param cookies: dictionary or CookieJar of cookies to attach to this request. - :param hooks: dictionary of callback hooks, for internal usage. - - Usage:: - - >>> import requests - >>> req = requests.Request('GET', 'https://httpbin.org/get') - >>> req.prepare() - <PreparedRequest [GET]> - """ - - def __init__(self, - method=None, url=None, headers=None, files=None, data=None, - params=None, auth=None, cookies=None, hooks=None, json=None): - - # Default empty dicts for dict params. - data = [] if data is None else data - files = [] if files is None else files - headers = {} if headers is None else headers - params = {} if params is None else params - hooks = {} if hooks is None else hooks - - self.hooks = default_hooks() - for (k, v) in list(hooks.items()): - self.register_hook(event=k, hook=v) - - self.method = method - self.url = url - self.headers = headers - self.files = files - self.data = data - self.json = json - self.params = params - self.auth = auth - self.cookies = cookies - - def __repr__(self): - return '<Request [%s]>' % (self.method) - - def prepare(self): - """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it.""" - p = PreparedRequest() - p.prepare( - method=self.method, - url=self.url, - headers=self.headers, - files=self.files, - data=self.data, - json=self.json, - params=self.params, - auth=self.auth, - cookies=self.cookies, - hooks=self.hooks, - ) - return p - - -class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): - """The fully mutable :class:`PreparedRequest <PreparedRequest>` object, - containing the exact bytes that will be sent to the server. - - Generated from either a :class:`Request <Request>` object or manually. - - Usage:: - - >>> import requests - >>> req = requests.Request('GET', 'https://httpbin.org/get') - >>> r = req.prepare() - <PreparedRequest [GET]> - - >>> s = requests.Session() - >>> s.send(r) - <Response [200]> - """ - - def __init__(self): - #: HTTP verb to send to the server. - self.method = None - #: HTTP URL to send the request to. - self.url = None - #: dictionary of HTTP headers. - self.headers = None - # The `CookieJar` used to create the Cookie header will be stored here - # after prepare_cookies is called - self._cookies = None - #: request body to send to the server. - self.body = None - #: dictionary of callback hooks, for internal usage. - self.hooks = default_hooks() - #: integer denoting starting position of a readable file-like body. - self._body_position = None - - def prepare(self, - method=None, url=None, headers=None, files=None, data=None, - params=None, auth=None, cookies=None, hooks=None, json=None): - """Prepares the entire request with the given parameters.""" - - self.prepare_method(method) - self.prepare_url(url, params) - self.prepare_headers(headers) - self.prepare_cookies(cookies) - self.prepare_body(data, files, json) - self.prepare_auth(auth, url) - - # Note that prepare_auth must be last to enable authentication schemes - # such as OAuth to work on a fully prepared request. - - # This MUST go after prepare_auth. Authenticators could add a hook - self.prepare_hooks(hooks) - - def __repr__(self): - return '<PreparedRequest [%s]>' % (self.method) - - def copy(self): - p = PreparedRequest() - p.method = self.method - p.url = self.url - p.headers = self.headers.copy() if self.headers is not None else None - p._cookies = _copy_cookie_jar(self._cookies) - p.body = self.body - p.hooks = self.hooks - p._body_position = self._body_position - return p - - def prepare_method(self, method): - """Prepares the given HTTP method.""" - self.method = method - if self.method is not None: - self.method = to_native_string(self.method.upper()) - - @staticmethod - def _get_idna_encoded_host(host): - import idna - - try: - host = idna.encode(host, uts46=True).decode('utf-8') - except idna.IDNAError: - raise UnicodeError - return host - - def prepare_url(self, url, params): - """Prepares the given HTTP URL.""" - #: Accept objects that have string representations. - #: We're unable to blindly call unicode/str functions - #: as this will include the bytestring indicator (b'') - #: on python 3.x. - #: https://github.com/requests/requests/pull/2238 - if isinstance(url, bytes): - url = url.decode('utf8') - else: - url = unicode(url) if is_py2 else str(url) - - # Remove leading whitespaces from url - url = url.lstrip() - - # Don't do any URL preparation for non-HTTP schemes like `mailto`, - # `data` etc to work around exceptions from `url_parse`, which - # handles RFC 3986 only. - if ':' in url and not url.lower().startswith('http'): - self.url = url - return - - # Support for unicode domain names and paths. - try: - scheme, auth, host, port, path, query, fragment = parse_url(url) - except LocationParseError as e: - raise InvalidURL(*e.args) - - if not scheme: - error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?") - error = error.format(to_native_string(url, 'utf8')) - - raise MissingSchema(error) - - if not host: - raise InvalidURL("Invalid URL %r: No host supplied" % url) - - # In general, we want to try IDNA encoding the hostname if the string contains - # non-ASCII characters. This allows users to automatically get the correct IDNA - # behaviour. For strings containing only ASCII characters, we need to also verify - # it doesn't start with a wildcard (*), before allowing the unencoded hostname. - if not unicode_is_ascii(host): - try: - host = self._get_idna_encoded_host(host) - except UnicodeError: - raise InvalidURL('URL has an invalid label.') - elif host.startswith(u'*'): - raise InvalidURL('URL has an invalid label.') - - # Carefully reconstruct the network location - netloc = auth or '' - if netloc: - netloc += '@' - netloc += host - if port: - netloc += ':' + str(port) - - # Bare domains aren't valid URLs. - if not path: - path = '/' - - if is_py2: - if isinstance(scheme, str): - scheme = scheme.encode('utf-8') - if isinstance(netloc, str): - netloc = netloc.encode('utf-8') - if isinstance(path, str): - path = path.encode('utf-8') - if isinstance(query, str): - query = query.encode('utf-8') - if isinstance(fragment, str): - fragment = fragment.encode('utf-8') - - if isinstance(params, (str, bytes)): - params = to_native_string(params) - - enc_params = self._encode_params(params) - if enc_params: - if query: - query = '%s&%s' % (query, enc_params) - else: - query = enc_params - - url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) - self.url = url - - def prepare_headers(self, headers): - """Prepares the given HTTP headers.""" - - self.headers = CaseInsensitiveDict() - if headers: - for header in headers.items(): - # Raise exception on invalid header value. - check_header_validity(header) - name, value = header - self.headers[to_native_string(name)] = value - - def prepare_body(self, data, files, json=None): - """Prepares the given HTTP body data.""" - - # Check if file, fo, generator, iterator. - # If not, run through normal process. - - # Nottin' on you. - body = None - content_type = None - - if not data and json is not None: - # urllib3 requires a bytes-like body. Python 2's json.dumps - # provides this natively, but Python 3 gives a Unicode string. - content_type = 'application/json' - body = complexjson.dumps(json) - if not isinstance(body, bytes): - body = body.encode('utf-8') - - is_stream = all([ - hasattr(data, '__iter__'), - not isinstance(data, (basestring, list, tuple, Mapping)) - ]) - - try: - length = super_len(data) - except (TypeError, AttributeError, UnsupportedOperation): - length = None - - if is_stream: - body = data - - if getattr(body, 'tell', None) is not None: - # Record the current file position before reading. - # This will allow us to rewind a file in the event - # of a redirect. - try: - self._body_position = body.tell() - except (IOError, OSError): - # This differentiates from None, allowing us to catch - # a failed `tell()` later when trying to rewind the body - self._body_position = object() - - if files: - raise NotImplementedError('Streamed bodies and files are mutually exclusive.') - - if length: - self.headers['Content-Length'] = builtin_str(length) - else: - self.headers['Transfer-Encoding'] = 'chunked' - else: - # Multi-part file uploads. - if files: - (body, content_type) = self._encode_files(files, data) - else: - if data: - body = self._encode_params(data) - if isinstance(data, basestring) or hasattr(data, 'read'): - content_type = None - else: - content_type = 'application/x-www-form-urlencoded' - - self.prepare_content_length(body) - - # Add content-type if it wasn't explicitly provided. - if content_type and ('content-type' not in self.headers): - self.headers['Content-Type'] = content_type - - self.body = body - - def prepare_content_length(self, body): - """Prepare Content-Length header based on request method and body""" - if body is not None: - length = super_len(body) - if length: - # If length exists, set it. Otherwise, we fallback - # to Transfer-Encoding: chunked. - self.headers['Content-Length'] = builtin_str(length) - elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: - # Set Content-Length to 0 for methods that can have a body - # but don't provide one. (i.e. not GET or HEAD) - self.headers['Content-Length'] = '0' - - def prepare_auth(self, auth, url=''): - """Prepares the given HTTP auth data.""" - - # If no Auth is explicitly provided, extract it from the URL first. - if auth is None: - url_auth = get_auth_from_url(self.url) - auth = url_auth if any(url_auth) else None - - if auth: - if isinstance(auth, tuple) and len(auth) == 2: - # special-case basic HTTP auth - auth = HTTPBasicAuth(*auth) - - # Allow auth to make its changes. - r = auth(self) - - # Update self to reflect the auth changes. - self.__dict__.update(r.__dict__) - - # Recompute Content-Length - self.prepare_content_length(self.body) - - def prepare_cookies(self, cookies): - """Prepares the given HTTP cookie data. - - This function eventually generates a ``Cookie`` header from the - given cookies using cookielib. Due to cookielib's design, the header - will not be regenerated if it already exists, meaning this function - can only be called once for the life of the - :class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls - to ``prepare_cookies`` will have no actual effect, unless the "Cookie" - header is removed beforehand. - """ - if isinstance(cookies, cookielib.CookieJar): - self._cookies = cookies - else: - self._cookies = cookiejar_from_dict(cookies) - - cookie_header = get_cookie_header(self._cookies, self) - if cookie_header is not None: - self.headers['Cookie'] = cookie_header - - def prepare_hooks(self, hooks): - """Prepares the given hooks.""" - # hooks can be passed as None to the prepare method and to this - # method. To prevent iterating over None, simply use an empty list - # if hooks is False-y - hooks = hooks or [] - for event in hooks: - self.register_hook(event, hooks[event]) - - -class Response(object): - """The :class:`Response <Response>` object, which contains a - server's response to an HTTP request. - """ - - __attrs__ = [ - '_content', 'status_code', 'headers', 'url', 'history', - 'encoding', 'reason', 'cookies', 'elapsed', 'request' - ] - - def __init__(self): - self._content = False - self._content_consumed = False - self._next = None - - #: Integer Code of responded HTTP Status, e.g. 404 or 200. - self.status_code = None - - #: Case-insensitive Dictionary of Response Headers. - #: For example, ``headers['content-encoding']`` will return the - #: value of a ``'Content-Encoding'`` response header. - self.headers = CaseInsensitiveDict() - - #: File-like object representation of response (for advanced usage). - #: Use of ``raw`` requires that ``stream=True`` be set on the request. - # This requirement does not apply for use internally to Requests. - self.raw = None - - #: Final URL location of Response. - self.url = None - - #: Encoding to decode with when accessing r.text. - self.encoding = None - - #: A list of :class:`Response <Response>` objects from - #: the history of the Request. Any redirect responses will end - #: up here. The list is sorted from the oldest to the most recent request. - self.history = [] - - #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". - self.reason = None - - #: A CookieJar of Cookies the server sent back. - self.cookies = cookiejar_from_dict({}) - - #: The amount of time elapsed between sending the request - #: and the arrival of the response (as a timedelta). - #: This property specifically measures the time taken between sending - #: the first byte of the request and finishing parsing the headers. It - #: is therefore unaffected by consuming the response content or the - #: value of the ``stream`` keyword argument. - self.elapsed = datetime.timedelta(0) - - #: The :class:`PreparedRequest <PreparedRequest>` object to which this - #: is a response. - self.request = None - - def __enter__(self): - return self - - def __exit__(self, *args): - self.close() - - def __getstate__(self): - # Consume everything; accessing the content attribute makes - # sure the content has been fully read. - if not self._content_consumed: - self.content - - return {attr: getattr(self, attr, None) for attr in self.__attrs__} - - def __setstate__(self, state): - for name, value in state.items(): - setattr(self, name, value) - - # pickled objects do not have .raw - setattr(self, '_content_consumed', True) - setattr(self, 'raw', None) - - def __repr__(self): - return '<Response [%s]>' % (self.status_code) - - def __bool__(self): - """Returns True if :attr:`status_code` is less than 400. - - This attribute checks if the status code of the response is between - 400 and 600 to see if there was a client error or a server error. If - the status code, is between 200 and 400, this will return True. This - is **not** a check to see if the response code is ``200 OK``. - """ - return self.ok - - def __nonzero__(self): - """Returns True if :attr:`status_code` is less than 400. - - This attribute checks if the status code of the response is between - 400 and 600 to see if there was a client error or a server error. If - the status code, is between 200 and 400, this will return True. This - is **not** a check to see if the response code is ``200 OK``. - """ - return self.ok - - def __iter__(self): - """Allows you to use a response as an iterator.""" - return self.iter_content(128) - - @property - def ok(self): - """Returns True if :attr:`status_code` is less than 400, False if not. - - This attribute checks if the status code of the response is between - 400 and 600 to see if there was a client error or a server error. If - the status code is between 200 and 400, this will return True. This - is **not** a check to see if the response code is ``200 OK``. - """ - try: - self.raise_for_status() - except HTTPError: - return False - return True - - @property - def is_redirect(self): - """True if this Response is a well-formed HTTP redirect that could have - been processed automatically (by :meth:`Session.resolve_redirects`). - """ - return ('location' in self.headers and self.status_code in REDIRECT_STATI) - - @property - def is_permanent_redirect(self): - """True if this Response one of the permanent versions of redirect.""" - return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect)) - - @property - def next(self): - """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" - return self._next - - @property - def apparent_encoding(self): - """The apparent encoding, provided by the chardet library.""" - return chardet.detect(self.content)['encoding'] - - def iter_content(self, chunk_size=1, decode_unicode=False): - """Iterates over the response data. When stream=True is set on the - request, this avoids reading the content at once into memory for - large responses. The chunk size is the number of bytes it should - read into memory. This is not necessarily the length of each item - returned as decoding can take place. - - chunk_size must be of type int or None. A value of None will - function differently depending on the value of `stream`. - stream=True will read data as it arrives in whatever size the - chunks are received. If stream=False, data is returned as - a single chunk. - - If decode_unicode is True, content will be decoded using the best - available encoding based on the response. - """ - - def generate(): - # Special case for urllib3. - if hasattr(self.raw, 'stream'): - try: - for chunk in self.raw.stream(chunk_size, decode_content=True): - yield chunk - except ProtocolError as e: - raise ChunkedEncodingError(e) - except DecodeError as e: - raise ContentDecodingError(e) - except ReadTimeoutError as e: - raise ConnectionError(e) - else: - # Standard file-like object. - while True: - chunk = self.raw.read(chunk_size) - if not chunk: - break - yield chunk - - self._content_consumed = True - - if self._content_consumed and isinstance(self._content, bool): - raise StreamConsumedError() - elif chunk_size is not None and not isinstance(chunk_size, int): - raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size)) - # simulate reading small chunks of the content - reused_chunks = iter_slices(self._content, chunk_size) - - stream_chunks = generate() - - chunks = reused_chunks if self._content_consumed else stream_chunks - - if decode_unicode: - chunks = stream_decode_response_unicode(chunks, self) - - return chunks - - def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None): - """Iterates over the response data, one line at a time. When - stream=True is set on the request, this avoids reading the - content at once into memory for large responses. - - .. note:: This method is not reentrant safe. - """ - - pending = None - - for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): - - if pending is not None: - chunk = pending + chunk - - if delimiter: - lines = chunk.split(delimiter) - else: - lines = chunk.splitlines() - - if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: - pending = lines.pop() - else: - pending = None - - for line in lines: - yield line - - if pending is not None: - yield pending - - @property - def content(self): - """Content of the response, in bytes.""" - - if self._content is False: - # Read the contents. - if self._content_consumed: - raise RuntimeError( - 'The content for this response was already consumed') - - if self.status_code == 0 or self.raw is None: - self._content = None - else: - self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b'' - - self._content_consumed = True - # don't need to release the connection; that's been handled by urllib3 - # since we exhausted the data. - return self._content - - @property - def text(self): - """Content of the response, in unicode. - - If Response.encoding is None, encoding will be guessed using - ``chardet``. - - The encoding of the response content is determined based solely on HTTP - headers, following RFC 2616 to the letter. If you can take advantage of - non-HTTP knowledge to make a better guess at the encoding, you should - set ``r.encoding`` appropriately before accessing this property. - """ - - # Try charset from content-type - content = None - encoding = self.encoding - - if not self.content: - return str('') - - # Fallback to auto-detected encoding. - if self.encoding is None: - encoding = self.apparent_encoding - - # Decode unicode from given encoding. - try: - content = str(self.content, encoding, errors='replace') - except (LookupError, TypeError): - # A LookupError is raised if the encoding was not found which could - # indicate a misspelling or similar mistake. - # - # A TypeError can be raised if encoding is None - # - # So we try blindly encoding. - content = str(self.content, errors='replace') - - return content - - def json(self, **kwargs): - r"""Returns the json-encoded content of a response, if any. - - :param \*\*kwargs: Optional arguments that ``json.loads`` takes. - :raises ValueError: If the response body does not contain valid json. - """ - - if not self.encoding and self.content and len(self.content) > 3: - # No encoding set. JSON RFC 4627 section 3 states we should expect - # UTF-8, -16 or -32. Detect which one to use; If the detection or - # decoding fails, fall back to `self.text` (using chardet to make - # a best guess). - encoding = guess_json_utf(self.content) - if encoding is not None: - try: - return complexjson.loads( - self.content.decode(encoding), **kwargs - ) - except UnicodeDecodeError: - # Wrong UTF codec detected; usually because it's not UTF-8 - # but some other 8-bit codec. This is an RFC violation, - # and the server didn't bother to tell us what codec *was* - # used. - pass - return complexjson.loads(self.text, **kwargs) - - @property - def links(self): - """Returns the parsed header links of the response, if any.""" - - header = self.headers.get('link') - - # l = MultiDict() - l = {} - - if header: - links = parse_header_links(header) - - for link in links: - key = link.get('rel') or link.get('url') - l[key] = link - - return l - - def raise_for_status(self): - """Raises stored :class:`HTTPError`, if one occurred.""" - - http_error_msg = '' - if isinstance(self.reason, bytes): - # We attempt to decode utf-8 first because some servers - # choose to localize their reason strings. If the string - # isn't utf-8, we fall back to iso-8859-1 for all other - # encodings. (See PR #3538) - try: - reason = self.reason.decode('utf-8') - except UnicodeDecodeError: - reason = self.reason.decode('iso-8859-1') - else: - reason = self.reason - - if 400 <= self.status_code < 500: - http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url) - - elif 500 <= self.status_code < 600: - http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url) - - if http_error_msg: - raise HTTPError(http_error_msg, response=self) - - def close(self): - """Releases the connection back to the pool. Once this method has been - called the underlying ``raw`` object must not be accessed again. - - *Note: Should not normally need to be called explicitly.* - """ - if not self._content_consumed: - self.raw.close() - - release_conn = getattr(self.raw, 'release_conn', None) - if release_conn is not None: - release_conn() diff --git a/lib/requests/packages.py b/lib/requests/packages.py deleted file mode 100644 index 7232fe0..0000000 --- a/lib/requests/packages.py +++ /dev/null @@ -1,14 +0,0 @@ -import sys - -# This code exists for backwards compatibility reasons. -# I don't like it either. Just look the other way. :) - -for package in ('urllib3', 'idna', 'chardet'): - locals()[package] = __import__(package) - # This traversal is apparently necessary such that the identities are - # preserved (requests.packages.urllib3.* is urllib3.*) - for mod in list(sys.modules): - if mod == package or mod.startswith(package + '.'): - sys.modules['requests.packages.' + mod] = sys.modules[mod] - -# Kinda cool, though, right? diff --git a/lib/requests/sessions.py b/lib/requests/sessions.py deleted file mode 100644 index d73d700..0000000 --- a/lib/requests/sessions.py +++ /dev/null @@ -1,770 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.session -~~~~~~~~~~~~~~~~ - -This module provides a Session object to manage and persist settings across -requests (cookies, auth, proxies). -""" -import os -import sys -import time -from datetime import timedelta - -from .auth import _basic_auth_str -from .compat import cookielib, is_py3, OrderedDict, urljoin, urlparse, Mapping -from .cookies import ( - cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) -from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT -from .hooks import default_hooks, dispatch_hook -from ._internal_utils import to_native_string -from .utils import to_key_val_list, default_headers, DEFAULT_PORTS -from .exceptions import ( - TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) - -from .structures import CaseInsensitiveDict -from .adapters import HTTPAdapter - -from .utils import ( - requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies, - get_auth_from_url, rewind_body -) - -from .status_codes import codes - -# formerly defined here, reexposed here for backward compatibility -from .models import REDIRECT_STATI - -# Preferred clock, based on which one is more accurate on a given system. -if sys.platform == 'win32': - try: # Python 3.4+ - preferred_clock = time.perf_counter - except AttributeError: # Earlier than Python 3. - preferred_clock = time.clock -else: - preferred_clock = time.time - - -def merge_setting(request_setting, session_setting, dict_class=OrderedDict): - """Determines appropriate setting for a given request, taking into account - the explicit setting on that request, and the setting in the session. If a - setting is a dictionary, they will be merged together using `dict_class` - """ - - if session_setting is None: - return request_setting - - if request_setting is None: - return session_setting - - # Bypass if not a dictionary (e.g. verify) - if not ( - isinstance(session_setting, Mapping) and - isinstance(request_setting, Mapping) - ): - return request_setting - - merged_setting = dict_class(to_key_val_list(session_setting)) - merged_setting.update(to_key_val_list(request_setting)) - - # Remove keys that are set to None. Extract keys first to avoid altering - # the dictionary during iteration. - none_keys = [k for (k, v) in merged_setting.items() if v is None] - for key in none_keys: - del merged_setting[key] - - return merged_setting - - -def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): - """Properly merges both requests and session hooks. - - This is necessary because when request_hooks == {'response': []}, the - merge breaks Session hooks entirely. - """ - if session_hooks is None or session_hooks.get('response') == []: - return request_hooks - - if request_hooks is None or request_hooks.get('response') == []: - return session_hooks - - return merge_setting(request_hooks, session_hooks, dict_class) - - -class SessionRedirectMixin(object): - - def get_redirect_target(self, resp): - """Receives a Response. Returns a redirect URI or ``None``""" - # Due to the nature of how requests processes redirects this method will - # be called at least once upon the original response and at least twice - # on each subsequent redirect response (if any). - # If a custom mixin is used to handle this logic, it may be advantageous - # to cache the redirect location onto the response object as a private - # attribute. - if resp.is_redirect: - location = resp.headers['location'] - # Currently the underlying http module on py3 decode headers - # in latin1, but empirical evidence suggests that latin1 is very - # rarely used with non-ASCII characters in HTTP headers. - # It is more likely to get UTF8 header rather than latin1. - # This causes incorrect handling of UTF8 encoded location headers. - # To solve this, we re-encode the location in latin1. - if is_py3: - location = location.encode('latin1') - return to_native_string(location, 'utf8') - return None - - def should_strip_auth(self, old_url, new_url): - """Decide whether Authorization header should be removed when redirecting""" - old_parsed = urlparse(old_url) - new_parsed = urlparse(new_url) - if old_parsed.hostname != new_parsed.hostname: - return True - # Special case: allow http -> https redirect when using the standard - # ports. This isn't specified by RFC 7235, but is kept to avoid - # breaking backwards compatibility with older versions of requests - # that allowed any redirects on the same host. - if (old_parsed.scheme == 'http' and old_parsed.port in (80, None) - and new_parsed.scheme == 'https' and new_parsed.port in (443, None)): - return False - - # Handle default port usage corresponding to scheme. - changed_port = old_parsed.port != new_parsed.port - changed_scheme = old_parsed.scheme != new_parsed.scheme - default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) - if (not changed_scheme and old_parsed.port in default_port - and new_parsed.port in default_port): - return False - - # Standard case: root URI must match - return changed_port or changed_scheme - - def resolve_redirects(self, resp, req, stream=False, timeout=None, - verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs): - """Receives a Response. Returns a generator of Responses or Requests.""" - - hist = [] # keep track of history - - url = self.get_redirect_target(resp) - previous_fragment = urlparse(req.url).fragment - while url: - prepared_request = req.copy() - - # Update history and keep track of redirects. - # resp.history must ignore the original request in this loop - hist.append(resp) - resp.history = hist[1:] - - try: - resp.content # Consume socket so it can be released - except (ChunkedEncodingError, ContentDecodingError, RuntimeError): - resp.raw.read(decode_content=False) - - if len(resp.history) >= self.max_redirects: - raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp) - - # Release the connection back into the pool. - resp.close() - - # Handle redirection without scheme (see: RFC 1808 Section 4) - if url.startswith('//'): - parsed_rurl = urlparse(resp.url) - url = '%s:%s' % (to_native_string(parsed_rurl.scheme), url) - - # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) - parsed = urlparse(url) - if parsed.fragment == '' and previous_fragment: - parsed = parsed._replace(fragment=previous_fragment) - elif parsed.fragment: - previous_fragment = parsed.fragment - url = parsed.geturl() - - # Facilitate relative 'location' headers, as allowed by RFC 7231. - # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') - # Compliant with RFC3986, we percent encode the url. - if not parsed.netloc: - url = urljoin(resp.url, requote_uri(url)) - else: - url = requote_uri(url) - - prepared_request.url = to_native_string(url) - - self.rebuild_method(prepared_request, resp) - - # https://github.com/requests/requests/issues/1084 - if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): - # https://github.com/requests/requests/issues/3490 - purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding') - for header in purged_headers: - prepared_request.headers.pop(header, None) - prepared_request.body = None - - headers = prepared_request.headers - try: - del headers['Cookie'] - except KeyError: - pass - - # Extract any cookies sent on the response to the cookiejar - # in the new request. Because we've mutated our copied prepared - # request, use the old one that we haven't yet touched. - extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) - merge_cookies(prepared_request._cookies, self.cookies) - prepared_request.prepare_cookies(prepared_request._cookies) - - # Rebuild auth and proxy information. - proxies = self.rebuild_proxies(prepared_request, proxies) - self.rebuild_auth(prepared_request, resp) - - # A failed tell() sets `_body_position` to `object()`. This non-None - # value ensures `rewindable` will be True, allowing us to raise an - # UnrewindableBodyError, instead of hanging the connection. - rewindable = ( - prepared_request._body_position is not None and - ('Content-Length' in headers or 'Transfer-Encoding' in headers) - ) - - # Attempt to rewind consumed file-like object. - if rewindable: - rewind_body(prepared_request) - - # Override the original request. - req = prepared_request - - if yield_requests: - yield req - else: - - resp = self.send( - req, - stream=stream, - timeout=timeout, - verify=verify, - cert=cert, - proxies=proxies, - allow_redirects=False, - **adapter_kwargs - ) - - extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) - - # extract redirect url, if any, for the next loop - url = self.get_redirect_target(resp) - yield resp - - def rebuild_auth(self, prepared_request, response): - """When being redirected we may want to strip authentication from the - request to avoid leaking credentials. This method intelligently removes - and reapplies authentication where possible to avoid credential loss. - """ - headers = prepared_request.headers - url = prepared_request.url - - if 'Authorization' in headers and self.should_strip_auth(response.request.url, url): - # If we get redirected to a new host, we should strip out any - # authentication headers. - del headers['Authorization'] - - # .netrc might have more auth for us on our new host. - new_auth = get_netrc_auth(url) if self.trust_env else None - if new_auth is not None: - prepared_request.prepare_auth(new_auth) - - return - - def rebuild_proxies(self, prepared_request, proxies): - """This method re-evaluates the proxy configuration by considering the - environment variables. If we are redirected to a URL covered by - NO_PROXY, we strip the proxy configuration. Otherwise, we set missing - proxy keys for this URL (in case they were stripped by a previous - redirect). - - This method also replaces the Proxy-Authorization header where - necessary. - - :rtype: dict - """ - proxies = proxies if proxies is not None else {} - headers = prepared_request.headers - url = prepared_request.url - scheme = urlparse(url).scheme - new_proxies = proxies.copy() - no_proxy = proxies.get('no_proxy') - - bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy) - if self.trust_env and not bypass_proxy: - environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) - - proxy = environ_proxies.get(scheme, environ_proxies.get('all')) - - if proxy: - new_proxies.setdefault(scheme, proxy) - - if 'Proxy-Authorization' in headers: - del headers['Proxy-Authorization'] - - try: - username, password = get_auth_from_url(new_proxies[scheme]) - except KeyError: - username, password = None, None - - if username and password: - headers['Proxy-Authorization'] = _basic_auth_str(username, password) - - return new_proxies - - def rebuild_method(self, prepared_request, response): - """When being redirected we may want to change the method of the request - based on certain specs or browser behavior. - """ - method = prepared_request.method - - # https://tools.ietf.org/html/rfc7231#section-6.4.4 - if response.status_code == codes.see_other and method != 'HEAD': - method = 'GET' - - # Do what the browsers do, despite standards... - # First, turn 302s into GETs. - if response.status_code == codes.found and method != 'HEAD': - method = 'GET' - - # Second, if a POST is responded to with a 301, turn it into a GET. - # This bizarre behaviour is explained in Issue 1704. - if response.status_code == codes.moved and method == 'POST': - method = 'GET' - - prepared_request.method = method - - -class Session(SessionRedirectMixin): - """A Requests session. - - Provides cookie persistence, connection-pooling, and configuration. - - Basic Usage:: - - >>> import requests - >>> s = requests.Session() - >>> s.get('https://httpbin.org/get') - <Response [200]> - - Or as a context manager:: - - >>> with requests.Session() as s: - >>> s.get('https://httpbin.org/get') - <Response [200]> - """ - - __attrs__ = [ - 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', - 'cert', 'prefetch', 'adapters', 'stream', 'trust_env', - 'max_redirects', - ] - - def __init__(self): - - #: A case-insensitive dictionary of headers to be sent on each - #: :class:`Request <Request>` sent from this - #: :class:`Session <Session>`. - self.headers = default_headers() - - #: Default Authentication tuple or object to attach to - #: :class:`Request <Request>`. - self.auth = None - - #: Dictionary mapping protocol or protocol and host to the URL of the proxy - #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to - #: be used on each :class:`Request <Request>`. - self.proxies = {} - - #: Event-handling hooks. - self.hooks = default_hooks() - - #: Dictionary of querystring data to attach to each - #: :class:`Request <Request>`. The dictionary values may be lists for - #: representing multivalued query parameters. - self.params = {} - - #: Stream response content default. - self.stream = False - - #: SSL Verification default. - self.verify = True - - #: SSL client certificate default, if String, path to ssl client - #: cert file (.pem). If Tuple, ('cert', 'key') pair. - self.cert = None - - #: Maximum number of redirects allowed. If the request exceeds this - #: limit, a :class:`TooManyRedirects` exception is raised. - #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is - #: 30. - self.max_redirects = DEFAULT_REDIRECT_LIMIT - - #: Trust environment settings for proxy configuration, default - #: authentication and similar. - self.trust_env = True - - #: A CookieJar containing all currently outstanding cookies set on this - #: session. By default it is a - #: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but - #: may be any other ``cookielib.CookieJar`` compatible object. - self.cookies = cookiejar_from_dict({}) - - # Default connection adapters. - self.adapters = OrderedDict() - self.mount('https://', HTTPAdapter()) - self.mount('http://', HTTPAdapter()) - - def __enter__(self): - return self - - def __exit__(self, *args): - self.close() - - def prepare_request(self, request): - """Constructs a :class:`PreparedRequest <PreparedRequest>` for - transmission and returns it. The :class:`PreparedRequest` has settings - merged from the :class:`Request <Request>` instance and those of the - :class:`Session`. - - :param request: :class:`Request` instance to prepare with this - session's settings. - :rtype: requests.PreparedRequest - """ - cookies = request.cookies or {} - - # Bootstrap CookieJar. - if not isinstance(cookies, cookielib.CookieJar): - cookies = cookiejar_from_dict(cookies) - - # Merge with session cookies - merged_cookies = merge_cookies( - merge_cookies(RequestsCookieJar(), self.cookies), cookies) - - # Set environment's basic authentication if not explicitly set. - auth = request.auth - if self.trust_env and not auth and not self.auth: - auth = get_netrc_auth(request.url) - - p = PreparedRequest() - p.prepare( - method=request.method.upper(), - url=request.url, - files=request.files, - data=request.data, - json=request.json, - headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), - params=merge_setting(request.params, self.params), - auth=merge_setting(auth, self.auth), - cookies=merged_cookies, - hooks=merge_hooks(request.hooks, self.hooks), - ) - return p - - def request(self, method, url, - params=None, data=None, headers=None, cookies=None, files=None, - auth=None, timeout=None, allow_redirects=True, proxies=None, - hooks=None, stream=None, verify=None, cert=None, json=None): - """Constructs a :class:`Request <Request>`, prepares it and sends it. - Returns :class:`Response <Response>` object. - - :param method: method for the new :class:`Request` object. - :param url: URL for the new :class:`Request` object. - :param params: (optional) Dictionary or bytes to be sent in the query - string for the :class:`Request`. - :param data: (optional) Dictionary, list of tuples, bytes, or file-like - object to send in the body of the :class:`Request`. - :param json: (optional) json to send in the body of the - :class:`Request`. - :param headers: (optional) Dictionary of HTTP Headers to send with the - :class:`Request`. - :param cookies: (optional) Dict or CookieJar object to send with the - :class:`Request`. - :param files: (optional) Dictionary of ``'filename': file-like-objects`` - for multipart encoding upload. - :param auth: (optional) Auth tuple or callable to enable - Basic/Digest/Custom HTTP Auth. - :param timeout: (optional) How long to wait for the server to send - data before giving up, as a float, or a :ref:`(connect timeout, - read timeout) <timeouts>` tuple. - :type timeout: float or tuple - :param allow_redirects: (optional) Set to True by default. - :type allow_redirects: bool - :param proxies: (optional) Dictionary mapping protocol or protocol and - hostname to the URL of the proxy. - :param stream: (optional) whether to immediately download the response - content. Defaults to ``False``. - :param verify: (optional) Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. - :param cert: (optional) if String, path to ssl client cert file (.pem). - If Tuple, ('cert', 'key') pair. - :rtype: requests.Response - """ - # Create the Request. - req = Request( - method=method.upper(), - url=url, - headers=headers, - files=files, - data=data or {}, - json=json, - params=params or {}, - auth=auth, - cookies=cookies, - hooks=hooks, - ) - prep = self.prepare_request(req) - - proxies = proxies or {} - - settings = self.merge_environment_settings( - prep.url, proxies, stream, verify, cert - ) - - # Send the request. - send_kwargs = { - 'timeout': timeout, - 'allow_redirects': allow_redirects, - } - send_kwargs.update(settings) - resp = self.send(prep, **send_kwargs) - - return resp - - def get(self, url, **kwargs): - r"""Sends a GET request. Returns :class:`Response` object. - - :param url: URL for the new :class:`Request` object. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :rtype: requests.Response - """ - - kwargs.setdefault('allow_redirects', True) - return self.request('GET', url, **kwargs) - - def options(self, url, **kwargs): - r"""Sends a OPTIONS request. Returns :class:`Response` object. - - :param url: URL for the new :class:`Request` object. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :rtype: requests.Response - """ - - kwargs.setdefault('allow_redirects', True) - return self.request('OPTIONS', url, **kwargs) - - def head(self, url, **kwargs): - r"""Sends a HEAD request. Returns :class:`Response` object. - - :param url: URL for the new :class:`Request` object. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :rtype: requests.Response - """ - - kwargs.setdefault('allow_redirects', False) - return self.request('HEAD', url, **kwargs) - - def post(self, url, data=None, json=None, **kwargs): - r"""Sends a POST request. Returns :class:`Response` object. - - :param url: URL for the new :class:`Request` object. - :param data: (optional) Dictionary, list of tuples, bytes, or file-like - object to send in the body of the :class:`Request`. - :param json: (optional) json to send in the body of the :class:`Request`. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :rtype: requests.Response - """ - - return self.request('POST', url, data=data, json=json, **kwargs) - - def put(self, url, data=None, **kwargs): - r"""Sends a PUT request. Returns :class:`Response` object. - - :param url: URL for the new :class:`Request` object. - :param data: (optional) Dictionary, list of tuples, bytes, or file-like - object to send in the body of the :class:`Request`. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :rtype: requests.Response - """ - - return self.request('PUT', url, data=data, **kwargs) - - def patch(self, url, data=None, **kwargs): - r"""Sends a PATCH request. Returns :class:`Response` object. - - :param url: URL for the new :class:`Request` object. - :param data: (optional) Dictionary, list of tuples, bytes, or file-like - object to send in the body of the :class:`Request`. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :rtype: requests.Response - """ - - return self.request('PATCH', url, data=data, **kwargs) - - def delete(self, url, **kwargs): - r"""Sends a DELETE request. Returns :class:`Response` object. - - :param url: URL for the new :class:`Request` object. - :param \*\*kwargs: Optional arguments that ``request`` takes. - :rtype: requests.Response - """ - - return self.request('DELETE', url, **kwargs) - - def send(self, request, **kwargs): - """Send a given PreparedRequest. - - :rtype: requests.Response - """ - # Set defaults that the hooks can utilize to ensure they always have - # the correct parameters to reproduce the previous request. - kwargs.setdefault('stream', self.stream) - kwargs.setdefault('verify', self.verify) - kwargs.setdefault('cert', self.cert) - kwargs.setdefault('proxies', self.proxies) - - # It's possible that users might accidentally send a Request object. - # Guard against that specific failure case. - if isinstance(request, Request): - raise ValueError('You can only send PreparedRequests.') - - # Set up variables needed for resolve_redirects and dispatching of hooks - allow_redirects = kwargs.pop('allow_redirects', True) - stream = kwargs.get('stream') - hooks = request.hooks - - # Get the appropriate adapter to use - adapter = self.get_adapter(url=request.url) - - # Start time (approximately) of the request - start = preferred_clock() - - # Send the request - r = adapter.send(request, **kwargs) - - # Total elapsed time of the request (approximately) - elapsed = preferred_clock() - start - r.elapsed = timedelta(seconds=elapsed) - - # Response manipulation hooks - r = dispatch_hook('response', hooks, r, **kwargs) - - # Persist cookies - if r.history: - - # If the hooks create history then we want those cookies too - for resp in r.history: - extract_cookies_to_jar(self.cookies, resp.request, resp.raw) - - extract_cookies_to_jar(self.cookies, request, r.raw) - - # Redirect resolving generator. - gen = self.resolve_redirects(r, request, **kwargs) - - # Resolve redirects if allowed. - history = [resp for resp in gen] if allow_redirects else [] - - # Shuffle things around if there's history. - if history: - # Insert the first (original) request at the start - history.insert(0, r) - # Get the last request made - r = history.pop() - r.history = history - - # If redirects aren't being followed, store the response on the Request for Response.next(). - if not allow_redirects: - try: - r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs)) - except StopIteration: - pass - - if not stream: - r.content - - return r - - def merge_environment_settings(self, url, proxies, stream, verify, cert): - """ - Check the environment and merge it with some settings. - - :rtype: dict - """ - # Gather clues from the surrounding environment. - if self.trust_env: - # Set environment's proxies. - no_proxy = proxies.get('no_proxy') if proxies is not None else None - env_proxies = get_environ_proxies(url, no_proxy=no_proxy) - for (k, v) in env_proxies.items(): - proxies.setdefault(k, v) - - # Look for requests environment configuration and be compatible - # with cURL. - if verify is True or verify is None: - verify = (os.environ.get('REQUESTS_CA_BUNDLE') or - os.environ.get('CURL_CA_BUNDLE')) - - # Merge all the kwargs. - proxies = merge_setting(proxies, self.proxies) - stream = merge_setting(stream, self.stream) - verify = merge_setting(verify, self.verify) - cert = merge_setting(cert, self.cert) - - return {'verify': verify, 'proxies': proxies, 'stream': stream, - 'cert': cert} - - def get_adapter(self, url): - """ - Returns the appropriate connection adapter for the given URL. - - :rtype: requests.adapters.BaseAdapter - """ - for (prefix, adapter) in self.adapters.items(): - - if url.lower().startswith(prefix.lower()): - return adapter - - # Nothing matches :-/ - raise InvalidSchema("No connection adapters were found for '%s'" % url) - - def close(self): - """Closes all adapters and as such the session""" - for v in self.adapters.values(): - v.close() - - def mount(self, prefix, adapter): - """Registers a connection adapter to a prefix. - - Adapters are sorted in descending order by prefix length. - """ - self.adapters[prefix] = adapter - keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] - - for key in keys_to_move: - self.adapters[key] = self.adapters.pop(key) - - def __getstate__(self): - state = {attr: getattr(self, attr, None) for attr in self.__attrs__} - return state - - def __setstate__(self, state): - for attr, value in state.items(): - setattr(self, attr, value) - - -def session(): - """ - Returns a :class:`Session` for context-management. - - .. deprecated:: 1.0.0 - - This method has been deprecated since version 1.0.0 and is only kept for - backwards compatibility. New code should use :class:`~requests.sessions.Session` - to create a session. This may be removed at a future date. - - :rtype: Session - """ - return Session() diff --git a/lib/requests/status_codes.py b/lib/requests/status_codes.py deleted file mode 100644 index 813e8c4..0000000 --- a/lib/requests/status_codes.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- - -r""" -The ``codes`` object defines a mapping from common names for HTTP statuses -to their numerical codes, accessible either as attributes or as dictionary -items. - ->>> requests.codes['temporary_redirect'] -307 ->>> requests.codes.teapot -418 ->>> requests.codes['\o/'] -200 - -Some codes have multiple names, and both upper- and lower-case versions of -the names are allowed. For example, ``codes.ok``, ``codes.OK``, and -``codes.okay`` all correspond to the HTTP status code 200. -""" - -from .structures import LookupDict - -_codes = { - - # Informational. - 100: ('continue',), - 101: ('switching_protocols',), - 102: ('processing',), - 103: ('checkpoint',), - 122: ('uri_too_long', 'request_uri_too_long'), - 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'), - 201: ('created',), - 202: ('accepted',), - 203: ('non_authoritative_info', 'non_authoritative_information'), - 204: ('no_content',), - 205: ('reset_content', 'reset'), - 206: ('partial_content', 'partial'), - 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), - 208: ('already_reported',), - 226: ('im_used',), - - # Redirection. - 300: ('multiple_choices',), - 301: ('moved_permanently', 'moved', '\\o-'), - 302: ('found',), - 303: ('see_other', 'other'), - 304: ('not_modified',), - 305: ('use_proxy',), - 306: ('switch_proxy',), - 307: ('temporary_redirect', 'temporary_moved', 'temporary'), - 308: ('permanent_redirect', - 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 - - # Client Error. - 400: ('bad_request', 'bad'), - 401: ('unauthorized',), - 402: ('payment_required', 'payment'), - 403: ('forbidden',), - 404: ('not_found', '-o-'), - 405: ('method_not_allowed', 'not_allowed'), - 406: ('not_acceptable',), - 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), - 408: ('request_timeout', 'timeout'), - 409: ('conflict',), - 410: ('gone',), - 411: ('length_required',), - 412: ('precondition_failed', 'precondition'), - 413: ('request_entity_too_large',), - 414: ('request_uri_too_large',), - 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), - 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), - 417: ('expectation_failed',), - 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), - 421: ('misdirected_request',), - 422: ('unprocessable_entity', 'unprocessable'), - 423: ('locked',), - 424: ('failed_dependency', 'dependency'), - 425: ('unordered_collection', 'unordered'), - 426: ('upgrade_required', 'upgrade'), - 428: ('precondition_required', 'precondition'), - 429: ('too_many_requests', 'too_many'), - 431: ('header_fields_too_large', 'fields_too_large'), - 444: ('no_response', 'none'), - 449: ('retry_with', 'retry'), - 450: ('blocked_by_windows_parental_controls', 'parental_controls'), - 451: ('unavailable_for_legal_reasons', 'legal_reasons'), - 499: ('client_closed_request',), - - # Server Error. - 500: ('internal_server_error', 'server_error', '/o\\', '✗'), - 501: ('not_implemented',), - 502: ('bad_gateway',), - 503: ('service_unavailable', 'unavailable'), - 504: ('gateway_timeout',), - 505: ('http_version_not_supported', 'http_version'), - 506: ('variant_also_negotiates',), - 507: ('insufficient_storage',), - 509: ('bandwidth_limit_exceeded', 'bandwidth'), - 510: ('not_extended',), - 511: ('network_authentication_required', 'network_auth', 'network_authentication'), -} - -codes = LookupDict(name='status_codes') - -def _init(): - for code, titles in _codes.items(): - for title in titles: - setattr(codes, title, code) - if not title.startswith(('\\', '/')): - setattr(codes, title.upper(), code) - - def doc(code): - names = ', '.join('``%s``' % n for n in _codes[code]) - return '* %d: %s' % (code, names) - - global __doc__ - __doc__ = (__doc__ + '\n' + - '\n'.join(doc(code) for code in sorted(_codes)) - if __doc__ is not None else None) - -_init() diff --git a/lib/requests/structures.py b/lib/requests/structures.py deleted file mode 100644 index da930e2..0000000 --- a/lib/requests/structures.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.structures -~~~~~~~~~~~~~~~~~~~ - -Data structures that power Requests. -""" - -from .compat import OrderedDict, Mapping, MutableMapping - - -class CaseInsensitiveDict(MutableMapping): - """A case-insensitive ``dict``-like object. - - Implements all methods and operations of - ``MutableMapping`` as well as dict's ``copy``. Also - provides ``lower_items``. - - All keys are expected to be strings. The structure remembers the - case of the last key to be set, and ``iter(instance)``, - ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` - will contain case-sensitive keys. However, querying and contains - testing is case insensitive:: - - cid = CaseInsensitiveDict() - cid['Accept'] = 'application/json' - cid['aCCEPT'] == 'application/json' # True - list(cid) == ['Accept'] # True - - For example, ``headers['content-encoding']`` will return the - value of a ``'Content-Encoding'`` response header, regardless - of how the header name was originally stored. - - If the constructor, ``.update``, or equality comparison - operations are given keys that have equal ``.lower()``s, the - behavior is undefined. - """ - - def __init__(self, data=None, **kwargs): - self._store = OrderedDict() - if data is None: - data = {} - self.update(data, **kwargs) - - def __setitem__(self, key, value): - # Use the lowercased key for lookups, but store the actual - # key alongside the value. - self._store[key.lower()] = (key, value) - - def __getitem__(self, key): - return self._store[key.lower()][1] - - def __delitem__(self, key): - del self._store[key.lower()] - - def __iter__(self): - return (casedkey for casedkey, mappedvalue in self._store.values()) - - def __len__(self): - return len(self._store) - - def lower_items(self): - """Like iteritems(), but with all lowercase keys.""" - return ( - (lowerkey, keyval[1]) - for (lowerkey, keyval) - in self._store.items() - ) - - def __eq__(self, other): - if isinstance(other, Mapping): - other = CaseInsensitiveDict(other) - else: - return NotImplemented - # Compare insensitively - return dict(self.lower_items()) == dict(other.lower_items()) - - # Copy is required - def copy(self): - return CaseInsensitiveDict(self._store.values()) - - def __repr__(self): - return str(dict(self.items())) - - -class LookupDict(dict): - """Dictionary lookup object.""" - - def __init__(self, name=None): - self.name = name - super(LookupDict, self).__init__() - - def __repr__(self): - return '<lookup \'%s\'>' % (self.name) - - def __getitem__(self, key): - # We allow fall-through here, so values default to None - - return self.__dict__.get(key, None) - - def get(self, key, default=None): - return self.__dict__.get(key, default) diff --git a/lib/requests/utils.py b/lib/requests/utils.py deleted file mode 100644 index 8170a8d..0000000 --- a/lib/requests/utils.py +++ /dev/null @@ -1,977 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -requests.utils -~~~~~~~~~~~~~~ - -This module provides utility functions that are used within Requests -that are also useful for external consumption. -""" - -import codecs -import contextlib -import io -import os -import re -import socket -import struct -import sys -import tempfile -import warnings -import zipfile - -from .__version__ import __version__ -from . import certs -# to_native_string is unused here, but imported here for backwards compatibility -from ._internal_utils import to_native_string -from .compat import parse_http_list as _parse_list_header -from .compat import ( - quote, urlparse, bytes, str, OrderedDict, unquote, getproxies, - proxy_bypass, urlunparse, basestring, integer_types, is_py3, - proxy_bypass_environment, getproxies_environment, Mapping) -from .cookies import cookiejar_from_dict -from .structures import CaseInsensitiveDict -from .exceptions import ( - InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError) - -NETRC_FILES = ('.netrc', '_netrc') - -DEFAULT_CA_BUNDLE_PATH = certs.where() - -DEFAULT_PORTS = {'http': 80, 'https': 443} - - -if sys.platform == 'win32': - # provide a proxy_bypass version on Windows without DNS lookups - - def proxy_bypass_registry(host): - try: - if is_py3: - import winreg - else: - import _winreg as winreg - except ImportError: - return False - - try: - internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER, - r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') - # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it - proxyEnable = int(winreg.QueryValueEx(internetSettings, - 'ProxyEnable')[0]) - # ProxyOverride is almost always a string - proxyOverride = winreg.QueryValueEx(internetSettings, - 'ProxyOverride')[0] - except OSError: - return False - if not proxyEnable or not proxyOverride: - return False - - # make a check value list from the registry entry: replace the - # '<local>' string by the localhost entry and the corresponding - # canonical entry. - proxyOverride = proxyOverride.split(';') - # now check if we match one of the registry values. - for test in proxyOverride: - if test == '<local>': - if '.' not in host: - return True - test = test.replace(".", r"\.") # mask dots - test = test.replace("*", r".*") # change glob sequence - test = test.replace("?", r".") # change glob char - if re.match(test, host, re.I): - return True - return False - - def proxy_bypass(host): # noqa - """Return True, if the host should be bypassed. - - Checks proxy settings gathered from the environment, if specified, - or the registry. - """ - if getproxies_environment(): - return proxy_bypass_environment(host) - else: - return proxy_bypass_registry(host) - - -def dict_to_sequence(d): - """Returns an internal sequence dictionary update.""" - - if hasattr(d, 'items'): - d = d.items() - - return d - - -def super_len(o): - total_length = None - current_position = 0 - - if hasattr(o, '__len__'): - total_length = len(o) - - elif hasattr(o, 'len'): - total_length = o.len - - elif hasattr(o, 'fileno'): - try: - fileno = o.fileno() - except io.UnsupportedOperation: - pass - else: - total_length = os.fstat(fileno).st_size - - # Having used fstat to determine the file length, we need to - # confirm that this file was opened up in binary mode. - if 'b' not in o.mode: - warnings.warn(( - "Requests has determined the content-length for this " - "request using the binary size of the file: however, the " - "file has been opened in text mode (i.e. without the 'b' " - "flag in the mode). This may lead to an incorrect " - "content-length. In Requests 3.0, support will be removed " - "for files in text mode."), - FileModeWarning - ) - - if hasattr(o, 'tell'): - try: - current_position = o.tell() - except (OSError, IOError): - # This can happen in some weird situations, such as when the file - # is actually a special file descriptor like stdin. In this - # instance, we don't know what the length is, so set it to zero and - # let requests chunk it instead. - if total_length is not None: - current_position = total_length - else: - if hasattr(o, 'seek') and total_length is None: - # StringIO and BytesIO have seek but no useable fileno - try: - # seek to end of file - o.seek(0, 2) - total_length = o.tell() - - # seek back to current position to support - # partially read file-like objects - o.seek(current_position or 0) - except (OSError, IOError): - total_length = 0 - - if total_length is None: - total_length = 0 - - return max(0, total_length - current_position) - - -def get_netrc_auth(url, raise_errors=False): - """Returns the Requests tuple auth for a given url from netrc.""" - - try: - from netrc import netrc, NetrcParseError - - netrc_path = None - - for f in NETRC_FILES: - try: - loc = os.path.expanduser('~/{}'.format(f)) - except KeyError: - # os.path.expanduser can fail when $HOME is undefined and - # getpwuid fails. See https://bugs.python.org/issue20164 & - # https://github.com/requests/requests/issues/1846 - return - - if os.path.exists(loc): - netrc_path = loc - break - - # Abort early if there isn't one. - if netrc_path is None: - return - - ri = urlparse(url) - - # Strip port numbers from netloc. This weird `if...encode`` dance is - # used for Python 3.2, which doesn't support unicode literals. - splitstr = b':' - if isinstance(url, str): - splitstr = splitstr.decode('ascii') - host = ri.netloc.split(splitstr)[0] - - try: - _netrc = netrc(netrc_path).authenticators(host) - if _netrc: - # Return with login / password - login_i = (0 if _netrc[0] else 1) - return (_netrc[login_i], _netrc[2]) - except (NetrcParseError, IOError): - # If there was a parsing error or a permissions issue reading the file, - # we'll just skip netrc auth unless explicitly asked to raise errors. - if raise_errors: - raise - - # AppEngine hackiness. - except (ImportError, AttributeError): - pass - - -def guess_filename(obj): - """Tries to guess the filename of the given object.""" - name = getattr(obj, 'name', None) - if (name and isinstance(name, basestring) and name[0] != '<' and - name[-1] != '>'): - return os.path.basename(name) - - -def extract_zipped_paths(path): - """Replace nonexistent paths that look like they refer to a member of a zip - archive with the location of an extracted copy of the target, or else - just return the provided path unchanged. - """ - if os.path.exists(path): - # this is already a valid path, no need to do anything further - return path - - # find the first valid part of the provided path and treat that as a zip archive - # assume the rest of the path is the name of a member in the archive - archive, member = os.path.split(path) - while archive and not os.path.exists(archive): - archive, prefix = os.path.split(archive) - member = '/'.join([prefix, member]) - - if not zipfile.is_zipfile(archive): - return path - - zip_file = zipfile.ZipFile(archive) - if member not in zip_file.namelist(): - return path - - # we have a valid zip archive and a valid member of that archive - tmp = tempfile.gettempdir() - extracted_path = os.path.join(tmp, *member.split('/')) - if not os.path.exists(extracted_path): - extracted_path = zip_file.extract(member, path=tmp) - - return extracted_path - - -def from_key_val_list(value): - """Take an object and test to see if it can be represented as a - dictionary. Unless it can not be represented as such, return an - OrderedDict, e.g., - - :: - - >>> from_key_val_list([('key', 'val')]) - OrderedDict([('key', 'val')]) - >>> from_key_val_list('string') - ValueError: cannot encode objects that are not 2-tuples - >>> from_key_val_list({'key': 'val'}) - OrderedDict([('key', 'val')]) - - :rtype: OrderedDict - """ - if value is None: - return None - - if isinstance(value, (str, bytes, bool, int)): - raise ValueError('cannot encode objects that are not 2-tuples') - - return OrderedDict(value) - - -def to_key_val_list(value): - """Take an object and test to see if it can be represented as a - dictionary. If it can be, return a list of tuples, e.g., - - :: - - >>> to_key_val_list([('key', 'val')]) - [('key', 'val')] - >>> to_key_val_list({'key': 'val'}) - [('key', 'val')] - >>> to_key_val_list('string') - ValueError: cannot encode objects that are not 2-tuples. - - :rtype: list - """ - if value is None: - return None - - if isinstance(value, (str, bytes, bool, int)): - raise ValueError('cannot encode objects that are not 2-tuples') - - if isinstance(value, Mapping): - value = value.items() - - return list(value) - - -# From mitsuhiko/werkzeug (used with permission). -def parse_list_header(value): - """Parse lists as described by RFC 2068 Section 2. - - In particular, parse comma-separated lists where the elements of - the list may include quoted-strings. A quoted-string could - contain a comma. A non-quoted string could have quotes in the - middle. Quotes are removed automatically after parsing. - - It basically works like :func:`parse_set_header` just that items - may appear multiple times and case sensitivity is preserved. - - The return value is a standard :class:`list`: - - >>> parse_list_header('token, "quoted value"') - ['token', 'quoted value'] - - To create a header from the :class:`list` again, use the - :func:`dump_header` function. - - :param value: a string with a list header. - :return: :class:`list` - :rtype: list - """ - result = [] - for item in _parse_list_header(value): - if item[:1] == item[-1:] == '"': - item = unquote_header_value(item[1:-1]) - result.append(item) - return result - - -# From mitsuhiko/werkzeug (used with permission). -def parse_dict_header(value): - """Parse lists of key, value pairs as described by RFC 2068 Section 2 and - convert them into a python dict: - - >>> d = parse_dict_header('foo="is a fish", bar="as well"') - >>> type(d) is dict - True - >>> sorted(d.items()) - [('bar', 'as well'), ('foo', 'is a fish')] - - If there is no value for a key it will be `None`: - - >>> parse_dict_header('key_without_value') - {'key_without_value': None} - - To create a header from the :class:`dict` again, use the - :func:`dump_header` function. - - :param value: a string with a dict header. - :return: :class:`dict` - :rtype: dict - """ - result = {} - for item in _parse_list_header(value): - if '=' not in item: - result[item] = None - continue - name, value = item.split('=', 1) - if value[:1] == value[-1:] == '"': - value = unquote_header_value(value[1:-1]) - result[name] = value - return result - - -# From mitsuhiko/werkzeug (used with permission). -def unquote_header_value(value, is_filename=False): - r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). - This does not use the real unquoting but what browsers are actually - using for quoting. - - :param value: the header value to unquote. - :rtype: str - """ - if value and value[0] == value[-1] == '"': - # this is not the real unquoting, but fixing this so that the - # RFC is met will result in bugs with internet explorer and - # probably some other browsers as well. IE for example is - # uploading files with "C:\foo\bar.txt" as filename - value = value[1:-1] - - # if this is a filename and the starting characters look like - # a UNC path, then just return the value without quotes. Using the - # replace sequence below on a UNC path has the effect of turning - # the leading double slash into a single slash and then - # _fix_ie_filename() doesn't work correctly. See #458. - if not is_filename or value[:2] != '\\\\': - return value.replace('\\\\', '\\').replace('\\"', '"') - return value - - -def dict_from_cookiejar(cj): - """Returns a key/value dictionary from a CookieJar. - - :param cj: CookieJar object to extract cookies from. - :rtype: dict - """ - - cookie_dict = {} - - for cookie in cj: - cookie_dict[cookie.name] = cookie.value - - return cookie_dict - - -def add_dict_to_cookiejar(cj, cookie_dict): - """Returns a CookieJar from a key/value dictionary. - - :param cj: CookieJar to insert cookies into. - :param cookie_dict: Dict of key/values to insert into CookieJar. - :rtype: CookieJar - """ - - return cookiejar_from_dict(cookie_dict, cj) - - -def get_encodings_from_content(content): - """Returns encodings from given content string. - - :param content: bytestring to extract encodings from. - """ - warnings.warn(( - 'In requests 3.0, get_encodings_from_content will be removed. For ' - 'more information, please see the discussion on issue #2266. (This' - ' warning should only appear once.)'), - DeprecationWarning) - - charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I) - pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I) - xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') - - return (charset_re.findall(content) + - pragma_re.findall(content) + - xml_re.findall(content)) - - -def _parse_content_type_header(header): - """Returns content type and parameters from given header - - :param header: string - :return: tuple containing content type and dictionary of - parameters - """ - - tokens = header.split(';') - content_type, params = tokens[0].strip(), tokens[1:] - params_dict = {} - items_to_strip = "\"' " - - for param in params: - param = param.strip() - if param: - key, value = param, True - index_of_equals = param.find("=") - if index_of_equals != -1: - key = param[:index_of_equals].strip(items_to_strip) - value = param[index_of_equals + 1:].strip(items_to_strip) - params_dict[key.lower()] = value - return content_type, params_dict - - -def get_encoding_from_headers(headers): - """Returns encodings from given HTTP Header Dict. - - :param headers: dictionary to extract encoding from. - :rtype: str - """ - - content_type = headers.get('content-type') - - if not content_type: - return None - - content_type, params = _parse_content_type_header(content_type) - - if 'charset' in params: - return params['charset'].strip("'\"") - - if 'text' in content_type: - return 'ISO-8859-1' - - -def stream_decode_response_unicode(iterator, r): - """Stream decodes a iterator.""" - - if r.encoding is None: - for item in iterator: - yield item - return - - decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace') - for chunk in iterator: - rv = decoder.decode(chunk) - if rv: - yield rv - rv = decoder.decode(b'', final=True) - if rv: - yield rv - - -def iter_slices(string, slice_length): - """Iterate over slices of a string.""" - pos = 0 - if slice_length is None or slice_length <= 0: - slice_length = len(string) - while pos < len(string): - yield string[pos:pos + slice_length] - pos += slice_length - - -def get_unicode_from_response(r): - """Returns the requested content back in unicode. - - :param r: Response object to get unicode content from. - - Tried: - - 1. charset from content-type - 2. fall back and replace all unicode characters - - :rtype: str - """ - warnings.warn(( - 'In requests 3.0, get_unicode_from_response will be removed. For ' - 'more information, please see the discussion on issue #2266. (This' - ' warning should only appear once.)'), - DeprecationWarning) - - tried_encodings = [] - - # Try charset from content-type - encoding = get_encoding_from_headers(r.headers) - - if encoding: - try: - return str(r.content, encoding) - except UnicodeError: - tried_encodings.append(encoding) - - # Fall back: - try: - return str(r.content, encoding, errors='replace') - except TypeError: - return r.content - - -# The unreserved URI characters (RFC 3986) -UNRESERVED_SET = frozenset( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~") - - -def unquote_unreserved(uri): - """Un-escape any percent-escape sequences in a URI that are unreserved - characters. This leaves all reserved, illegal and non-ASCII bytes encoded. - - :rtype: str - """ - parts = uri.split('%') - for i in range(1, len(parts)): - h = parts[i][0:2] - if len(h) == 2 and h.isalnum(): - try: - c = chr(int(h, 16)) - except ValueError: - raise InvalidURL("Invalid percent-escape sequence: '%s'" % h) - - if c in UNRESERVED_SET: - parts[i] = c + parts[i][2:] - else: - parts[i] = '%' + parts[i] - else: - parts[i] = '%' + parts[i] - return ''.join(parts) - - -def requote_uri(uri): - """Re-quote the given URI. - - This function passes the given URI through an unquote/quote cycle to - ensure that it is fully and consistently quoted. - - :rtype: str - """ - safe_with_percent = "!#$%&'()*+,/:;=?@[]~" - safe_without_percent = "!#$&'()*+,/:;=?@[]~" - try: - # Unquote only the unreserved characters - # Then quote only illegal characters (do not quote reserved, - # unreserved, or '%') - return quote(unquote_unreserved(uri), safe=safe_with_percent) - except InvalidURL: - # We couldn't unquote the given URI, so let's try quoting it, but - # there may be unquoted '%'s in the URI. We need to make sure they're - # properly quoted so they do not cause issues elsewhere. - return quote(uri, safe=safe_without_percent) - - -def address_in_network(ip, net): - """This function allows you to check if an IP belongs to a network subnet - - Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 - returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 - - :rtype: bool - """ - ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0] - netaddr, bits = net.split('/') - netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0] - network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask - return (ipaddr & netmask) == (network & netmask) - - -def dotted_netmask(mask): - """Converts mask from /xx format to xxx.xxx.xxx.xxx - - Example: if mask is 24 function returns 255.255.255.0 - - :rtype: str - """ - bits = 0xffffffff ^ (1 << 32 - mask) - 1 - return socket.inet_ntoa(struct.pack('>I', bits)) - - -def is_ipv4_address(string_ip): - """ - :rtype: bool - """ - try: - socket.inet_aton(string_ip) - except socket.error: - return False - return True - - -def is_valid_cidr(string_network): - """ - Very simple check of the cidr format in no_proxy variable. - - :rtype: bool - """ - if string_network.count('/') == 1: - try: - mask = int(string_network.split('/')[1]) - except ValueError: - return False - - if mask < 1 or mask > 32: - return False - - try: - socket.inet_aton(string_network.split('/')[0]) - except socket.error: - return False - else: - return False - return True - - -@contextlib.contextmanager -def set_environ(env_name, value): - """Set the environment variable 'env_name' to 'value' - - Save previous value, yield, and then restore the previous value stored in - the environment variable 'env_name'. - - If 'value' is None, do nothing""" - value_changed = value is not None - if value_changed: - old_value = os.environ.get(env_name) - os.environ[env_name] = value - try: - yield - finally: - if value_changed: - if old_value is None: - del os.environ[env_name] - else: - os.environ[env_name] = old_value - - -def should_bypass_proxies(url, no_proxy): - """ - Returns whether we should bypass proxies or not. - - :rtype: bool - """ - # Prioritize lowercase environment variables over uppercase - # to keep a consistent behaviour with other http projects (curl, wget). - get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper()) - - # First check whether no_proxy is defined. If it is, check that the URL - # we're getting isn't in the no_proxy list. - no_proxy_arg = no_proxy - if no_proxy is None: - no_proxy = get_proxy('no_proxy') - parsed = urlparse(url) - - if parsed.hostname is None: - # URLs don't always have hostnames, e.g. file:/// urls. - return True - - if no_proxy: - # We need to check whether we match here. We need to see if we match - # the end of the hostname, both with and without the port. - no_proxy = ( - host for host in no_proxy.replace(' ', '').split(',') if host - ) - - if is_ipv4_address(parsed.hostname): - for proxy_ip in no_proxy: - if is_valid_cidr(proxy_ip): - if address_in_network(parsed.hostname, proxy_ip): - return True - elif parsed.hostname == proxy_ip: - # If no_proxy ip was defined in plain IP notation instead of cidr notation & - # matches the IP of the index - return True - else: - host_with_port = parsed.hostname - if parsed.port: - host_with_port += ':{}'.format(parsed.port) - - for host in no_proxy: - if parsed.hostname.endswith(host) or host_with_port.endswith(host): - # The URL does match something in no_proxy, so we don't want - # to apply the proxies on this URL. - return True - - with set_environ('no_proxy', no_proxy_arg): - # parsed.hostname can be `None` in cases such as a file URI. - try: - bypass = proxy_bypass(parsed.hostname) - except (TypeError, socket.gaierror): - bypass = False - - if bypass: - return True - - return False - - -def get_environ_proxies(url, no_proxy=None): - """ - Return a dict of environment proxies. - - :rtype: dict - """ - if should_bypass_proxies(url, no_proxy=no_proxy): - return {} - else: - return getproxies() - - -def select_proxy(url, proxies): - """Select a proxy for the url, if applicable. - - :param url: The url being for the request - :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs - """ - proxies = proxies or {} - urlparts = urlparse(url) - if urlparts.hostname is None: - return proxies.get(urlparts.scheme, proxies.get('all')) - - proxy_keys = [ - urlparts.scheme + '://' + urlparts.hostname, - urlparts.scheme, - 'all://' + urlparts.hostname, - 'all', - ] - proxy = None - for proxy_key in proxy_keys: - if proxy_key in proxies: - proxy = proxies[proxy_key] - break - - return proxy - - -def default_user_agent(name="python-requests"): - """ - Return a string representing the default user agent. - - :rtype: str - """ - return '%s/%s' % (name, __version__) - - -def default_headers(): - """ - :rtype: requests.structures.CaseInsensitiveDict - """ - return CaseInsensitiveDict({ - 'User-Agent': default_user_agent(), - 'Accept-Encoding': ', '.join(('gzip', 'deflate')), - 'Accept': '*/*', - 'Connection': 'keep-alive', - }) - - -def parse_header_links(value): - """Return a list of parsed link headers proxies. - - i.e. Link: <http:/.../front.jpeg>; rel=front; type="image/jpeg",<http://.../back.jpeg>; rel=back;type="image/jpeg" - - :rtype: list - """ - - links = [] - - replace_chars = ' \'"' - - value = value.strip(replace_chars) - if not value: - return links - - for val in re.split(', *<', value): - try: - url, params = val.split(';', 1) - except ValueError: - url, params = val, '' - - link = {'url': url.strip('<> \'"')} - - for param in params.split(';'): - try: - key, value = param.split('=') - except ValueError: - break - - link[key.strip(replace_chars)] = value.strip(replace_chars) - - links.append(link) - - return links - - -# Null bytes; no need to recreate these on each call to guess_json_utf -_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3 -_null2 = _null * 2 -_null3 = _null * 3 - - -def guess_json_utf(data): - """ - :rtype: str - """ - # JSON always starts with two ASCII characters, so detection is as - # easy as counting the nulls and from their location and count - # determine the encoding. Also detect a BOM, if present. - sample = data[:4] - if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): - return 'utf-32' # BOM included - if sample[:3] == codecs.BOM_UTF8: - return 'utf-8-sig' # BOM included, MS style (discouraged) - if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): - return 'utf-16' # BOM included - nullcount = sample.count(_null) - if nullcount == 0: - return 'utf-8' - if nullcount == 2: - if sample[::2] == _null2: # 1st and 3rd are null - return 'utf-16-be' - if sample[1::2] == _null2: # 2nd and 4th are null - return 'utf-16-le' - # Did not detect 2 valid UTF-16 ascii-range characters - if nullcount == 3: - if sample[:3] == _null3: - return 'utf-32-be' - if sample[1:] == _null3: - return 'utf-32-le' - # Did not detect a valid UTF-32 ascii-range character - return None - - -def prepend_scheme_if_needed(url, new_scheme): - """Given a URL that may or may not have a scheme, prepend the given scheme. - Does not replace a present scheme with the one provided as an argument. - - :rtype: str - """ - scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme) - - # urlparse is a finicky beast, and sometimes decides that there isn't a - # netloc present. Assume that it's being over-cautious, and switch netloc - # and path if urlparse decided there was no netloc. - if not netloc: - netloc, path = path, netloc - - return urlunparse((scheme, netloc, path, params, query, fragment)) - - -def get_auth_from_url(url): - """Given a url with authentication components, extract them into a tuple of - username,password. - - :rtype: (str,str) - """ - parsed = urlparse(url) - - try: - auth = (unquote(parsed.username), unquote(parsed.password)) - except (AttributeError, TypeError): - auth = ('', '') - - return auth - - -# Moved outside of function to avoid recompile every call -_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$') -_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$') - - -def check_header_validity(header): - """Verifies that header value is a string which doesn't contain - leading whitespace or return characters. This prevents unintended - header injection. - - :param header: tuple, in the format (name, value). - """ - name, value = header - - if isinstance(value, bytes): - pat = _CLEAN_HEADER_REGEX_BYTE - else: - pat = _CLEAN_HEADER_REGEX_STR - try: - if not pat.match(value): - raise InvalidHeader("Invalid return character or leading space in header: %s" % name) - except TypeError: - raise InvalidHeader("Value for header {%s: %s} must be of type str or " - "bytes, not %s" % (name, value, type(value))) - - -def urldefragauth(url): - """ - Given a url remove the fragment and the authentication part. - - :rtype: str - """ - scheme, netloc, path, params, query, fragment = urlparse(url) - - # see func:`prepend_scheme_if_needed` - if not netloc: - netloc, path = path, netloc - - netloc = netloc.rsplit('@', 1)[-1] - - return urlunparse((scheme, netloc, path, params, query, '')) - - -def rewind_body(prepared_request): - """Move file pointer back to its recorded starting position - so it can be read again on redirect. - """ - body_seek = getattr(prepared_request.body, 'seek', None) - if body_seek is not None and isinstance(prepared_request._body_position, integer_types): - try: - body_seek(prepared_request._body_position) - except (IOError, OSError): - raise UnrewindableBodyError("An error occurred when rewinding request " - "body for redirect.") - else: - raise UnrewindableBodyError("Unable to rewind request body for redirect.") diff --git a/lib/schedule/__init__.py b/lib/schedule/__init__.py deleted file mode 100644 index 3f6daa8..0000000 --- a/lib/schedule/__init__.py +++ /dev/null @@ -1,528 +0,0 @@ -""" -Python job scheduling for humans. - -github.com/dbader/schedule - -An in-process scheduler for periodic jobs that uses the builder pattern -for configuration. Schedule lets you run Python functions (or any other -callable) periodically at pre-determined intervals using a simple, -human-friendly syntax. - -Inspired by Addam Wiggins' article "Rethinking Cron" [1] and the -"clockwork" Ruby module [2][3]. - -Features: - - A simple to use API for scheduling jobs. - - Very lightweight and no external dependencies. - - Excellent test coverage. - - Works with Python 2.7 and 3.3 - -Usage: - >>> import schedule - >>> import time - - >>> def job(message='stuff'): - >>> print("I'm working on:", message) - - >>> schedule.every(10).minutes.do(job) - >>> schedule.every(5).to(10).days.do(job) - >>> schedule.every().hour.do(job, message='things') - >>> schedule.every().day.at("10:30").do(job) - - >>> while True: - >>> schedule.run_pending() - >>> time.sleep(1) - -[1] https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ -[2] https://github.com/Rykian/clockwork -[3] https://adam.herokuapp.com/past/2010/6/30/replace_cron_with_clockwork/ -""" -import collections -import datetime -import functools -import logging -import random -import time - -logger = logging.getLogger('schedule') - - -class CancelJob(object): - """ - Can be returned from a job to unschedule itself. - """ - pass - - -class Scheduler(object): - """ - Objects instantiated by the :class:`Scheduler <Scheduler>` are - factories to create jobs, keep record of scheduled jobs and - handle their execution. - """ - def __init__(self): - self.jobs = [] - - def run_pending(self): - """ - Run all jobs that are scheduled to run. - - Please note that it is *intended behavior that run_pending() - does not run missed jobs*. For example, if you've registered a job - that should run every minute and you only call run_pending() - in one hour increments then your job won't be run 60 times in - between but only once. - """ - runnable_jobs = (job for job in self.jobs if job.should_run) - for job in sorted(runnable_jobs): - self._run_job(job) - - def run_all(self, delay_seconds=0): - """ - Run all jobs regardless if they are scheduled to run or not. - - A delay of `delay` seconds is added between each job. This helps - distribute system load generated by the jobs more evenly - over time. - - :param delay_seconds: A delay added between every executed job - """ - logger.info('Running *all* %i jobs with %is delay inbetween', - len(self.jobs), delay_seconds) - for job in self.jobs[:]: - self._run_job(job) - time.sleep(delay_seconds) - - def clear(self, tag=None): - """ - Deletes scheduled jobs marked with the given tag, or all jobs - if tag is omitted. - - :param tag: An identifier used to identify a subset of - jobs to delete - """ - if tag is None: - del self.jobs[:] - else: - self.jobs[:] = (job for job in self.jobs if tag not in job.tags) - - def cancel_job(self, job): - """ - Delete a scheduled job. - - :param job: The job to be unscheduled - """ - try: - self.jobs.remove(job) - except ValueError: - pass - - def every(self, interval=1): - """ - Schedule a new periodic job. - - :param interval: A quantity of a certain time unit - :return: An unconfigured :class:`Job <Job>` - """ - job = Job(interval, self) - return job - - def _run_job(self, job): - ret = job.run() - if isinstance(ret, CancelJob) or ret is CancelJob: - self.cancel_job(job) - - @property - def next_run(self): - """ - Datetime when the next job should run. - - :return: A :class:`~datetime.datetime` object - """ - if not self.jobs: - return None - return min(self.jobs).next_run - - @property - def idle_seconds(self): - """ - :return: Number of seconds until - :meth:`next_run <Scheduler.next_run>`. - """ - return (self.next_run - datetime.datetime.now()).total_seconds() - - -class Job(object): - """ - A periodic job as used by :class:`Scheduler`. - - :param interval: A quantity of a certain time unit - :param scheduler: The :class:`Scheduler <Scheduler>` instance that - this job will register itself with once it has - been fully configured in :meth:`Job.do()`. - - Every job runs at a given fixed time interval that is defined by: - - * a :meth:`time unit <Job.second>` - * a quantity of `time units` defined by `interval` - - A job is usually created and returned by :meth:`Scheduler.every` - method, which also defines its `interval`. - """ - def __init__(self, interval, scheduler=None): - self.interval = interval # pause interval * unit between runs - self.latest = None # upper limit to the interval - self.job_func = None # the job job_func to run - self.unit = None # time units, e.g. 'minutes', 'hours', ... - self.at_time = None # optional time at which this job runs - self.last_run = None # datetime of the last run - self.next_run = None # datetime of the next run - self.period = None # timedelta between runs, only valid for - self.start_day = None # Specific day of the week to start on - self.tags = set() # unique set of tags for the job - self.scheduler = scheduler # scheduler to register with - - def __lt__(self, other): - """ - PeriodicJobs are sortable based on the scheduled time they - run next. - """ - return self.next_run < other.next_run - - def __repr__(self): - def format_time(t): - return t.strftime('%Y-%m-%d %H:%M:%S') if t else '[never]' - - timestats = '(last run: %s, next run: %s)' % ( - format_time(self.last_run), format_time(self.next_run)) - - if hasattr(self.job_func, '__name__'): - job_func_name = self.job_func.__name__ - else: - job_func_name = repr(self.job_func) - args = [repr(x) for x in self.job_func.args] - kwargs = ['%s=%s' % (k, repr(v)) - for k, v in self.job_func.keywords.items()] - call_repr = job_func_name + '(' + ', '.join(args + kwargs) + ')' - - if self.at_time is not None: - return 'Every %s %s at %s do %s %s' % ( - self.interval, - self.unit[:-1] if self.interval == 1 else self.unit, - self.at_time, call_repr, timestats) - else: - fmt = ( - 'Every %(interval)s ' + - ('to %(latest)s ' if self.latest is not None else '') + - '%(unit)s do %(call_repr)s %(timestats)s' - ) - - return fmt % dict( - interval=self.interval, - latest=self.latest, - unit=(self.unit[:-1] if self.interval == 1 else self.unit), - call_repr=call_repr, - timestats=timestats - ) - - @property - def second(self): - assert self.interval == 1, 'Use seconds instead of second' - return self.seconds - - @property - def seconds(self): - self.unit = 'seconds' - return self - - @property - def minute(self): - assert self.interval == 1, 'Use minutes instead of minute' - return self.minutes - - @property - def minutes(self): - self.unit = 'minutes' - return self - - @property - def hour(self): - assert self.interval == 1, 'Use hours instead of hour' - return self.hours - - @property - def hours(self): - self.unit = 'hours' - return self - - @property - def day(self): - assert self.interval == 1, 'Use days instead of day' - return self.days - - @property - def days(self): - self.unit = 'days' - return self - - @property - def week(self): - assert self.interval == 1, 'Use weeks instead of week' - return self.weeks - - @property - def weeks(self): - self.unit = 'weeks' - return self - - @property - def monday(self): - assert self.interval == 1, 'Use mondays instead of monday' - self.start_day = 'monday' - return self.weeks - - @property - def tuesday(self): - assert self.interval == 1, 'Use tuesdays instead of tuesday' - self.start_day = 'tuesday' - return self.weeks - - @property - def wednesday(self): - assert self.interval == 1, 'Use wedesdays instead of wednesday' - self.start_day = 'wednesday' - return self.weeks - - @property - def thursday(self): - assert self.interval == 1, 'Use thursday instead of thursday' - self.start_day = 'thursday' - return self.weeks - - @property - def friday(self): - assert self.interval == 1, 'Use fridays instead of friday' - self.start_day = 'friday' - return self.weeks - - @property - def saturday(self): - assert self.interval == 1, 'Use saturdays instead of saturday' - self.start_day = 'saturday' - return self.weeks - - @property - def sunday(self): - assert self.interval == 1, 'Use sundays instead of sunday' - self.start_day = 'sunday' - return self.weeks - - def tag(self, *tags): - """ - Tags the job with one or more unique indentifiers. - - Tags must be hashable. Duplicate tags are discarded. - - :param tags: A unique list of ``Hashable`` tags. - :return: The invoked job instance - """ - if any([not isinstance(tag, collections.Hashable) for tag in tags]): - raise TypeError('Every tag should be hashable') - - if not all(isinstance(tag, collections.Hashable) for tag in tags): - raise TypeError('Tags must be hashable') - self.tags.update(tags) - return self - - def at(self, time_str): - """ - Schedule the job every day at a specific time. - - Calling this is only valid for jobs scheduled to run - every N day(s). - - :param time_str: A string in `XX:YY` format. - :return: The invoked job instance - """ - assert self.unit in ('days', 'hours') or self.start_day - hour, minute = time_str.split(':') - minute = int(minute) - if self.unit == 'days' or self.start_day: - hour = int(hour) - assert 0 <= hour <= 23 - elif self.unit == 'hours': - hour = 0 - assert 0 <= minute <= 59 - self.at_time = datetime.time(hour, minute) - return self - - def to(self, latest): - """ - Schedule the job to run at an irregular (randomized) interval. - - The job's interval will randomly vary from the value given - to `every` to `latest`. The range defined is inclusive on - both ends. For example, `every(A).to(B).seconds` executes - the job function every N seconds such that A <= N <= B. - - :param latest: Maximum interval between randomized job runs - :return: The invoked job instance - """ - self.latest = latest - return self - - def do(self, job_func, *args, **kwargs): - """ - Specifies the job_func that should be called every time the - job runs. - - Any additional arguments are passed on to job_func when - the job runs. - - :param job_func: The function to be scheduled - :return: The invoked job instance - """ - self.job_func = functools.partial(job_func, *args, **kwargs) - try: - functools.update_wrapper(self.job_func, job_func) - except AttributeError: - # job_funcs already wrapped by functools.partial won't have - # __name__, __module__ or __doc__ and the update_wrapper() - # call will fail. - pass - self._schedule_next_run() - self.scheduler.jobs.append(self) - return self - - @property - def should_run(self): - """ - :return: ``True`` if the job should be run now. - """ - return datetime.datetime.now() >= self.next_run - - def run(self): - """ - Run the job and immediately reschedule it. - - :return: The return value returned by the `job_func` - """ - logger.info('Running job %s', self) - ret = self.job_func() - self.last_run = datetime.datetime.now() - self._schedule_next_run() - return ret - - def _schedule_next_run(self): - """ - Compute the instant when this job should run next. - """ - assert self.unit in ('seconds', 'minutes', 'hours', 'days', 'weeks') - - if self.latest is not None: - assert self.latest >= self.interval - interval = random.randint(self.interval, self.latest) - else: - interval = self.interval - - self.period = datetime.timedelta(**{self.unit: interval}) - self.next_run = datetime.datetime.now() + self.period - if self.start_day is not None: - assert self.unit == 'weeks' - weekdays = ( - 'monday', - 'tuesday', - 'wednesday', - 'thursday', - 'friday', - 'saturday', - 'sunday' - ) - assert self.start_day in weekdays - weekday = weekdays.index(self.start_day) - days_ahead = weekday - self.next_run.weekday() - if days_ahead <= 0: # Target day already happened this week - days_ahead += 7 - self.next_run += datetime.timedelta(days_ahead) - self.period - if self.at_time is not None: - assert self.unit in ('days', 'hours') or self.start_day is not None - kwargs = { - 'minute': self.at_time.minute, - 'second': self.at_time.second, - 'microsecond': 0 - } - if self.unit == 'days' or self.start_day is not None: - kwargs['hour'] = self.at_time.hour - self.next_run = self.next_run.replace(**kwargs) - # If we are running for the first time, make sure we run - # at the specified time *today* (or *this hour*) as well - if not self.last_run: - now = datetime.datetime.now() - if (self.unit == 'days' and self.at_time > now.time() and - self.interval == 1): - self.next_run = self.next_run - datetime.timedelta(days=1) - elif self.unit == 'hours' and self.at_time.minute > now.minute: - self.next_run = self.next_run - datetime.timedelta(hours=1) - if self.start_day is not None and self.at_time is not None: - # Let's see if we will still make that time we specified today - if (self.next_run - datetime.datetime.now()).days >= 7: - self.next_run -= self.period - - -# The following methods are shortcuts for not having to -# create a Scheduler instance: - -#: Default :class:`Scheduler <Scheduler>` object -default_scheduler = Scheduler() - -#: Default :class:`Jobs <Job>` list -jobs = default_scheduler.jobs # todo: should this be a copy, e.g. jobs()? - - -def every(interval=1): - """Calls :meth:`every <Scheduler.every>` on the - :data:`default scheduler instance <default_scheduler>`. - """ - return default_scheduler.every(interval) - - -def run_pending(): - """Calls :meth:`run_pending <Scheduler.run_pending>` on the - :data:`default scheduler instance <default_scheduler>`. - """ - default_scheduler.run_pending() - - -def run_all(delay_seconds=0): - """Calls :meth:`run_all <Scheduler.run_all>` on the - :data:`default scheduler instance <default_scheduler>`. - """ - default_scheduler.run_all(delay_seconds=delay_seconds) - - -def clear(tag=None): - """Calls :meth:`clear <Scheduler.clear>` on the - :data:`default scheduler instance <default_scheduler>`. - """ - default_scheduler.clear(tag) - - -def cancel_job(job): - """Calls :meth:`cancel_job <Scheduler.cancel_job>` on the - :data:`default scheduler instance <default_scheduler>`. - """ - default_scheduler.cancel_job(job) - - -def next_run(): - """Calls :meth:`next_run <Scheduler.next_run>` on the - :data:`default scheduler instance <default_scheduler>`. - """ - return default_scheduler.next_run - - -def idle_seconds(): - """Calls :meth:`idle_seconds <Scheduler.idle_seconds>` on the - :data:`default scheduler instance <default_scheduler>`. - """ - return default_scheduler.idle_seconds diff --git a/lib/setuptools/__init__.py b/lib/setuptools/__init__.py deleted file mode 100644 index e438036..0000000 --- a/lib/setuptools/__init__.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Extensions to the 'distutils' for large or complex distributions""" - -import os -import sys -import functools -import distutils.core -import distutils.filelist -from distutils.util import convert_path -from fnmatch import fnmatchcase - -from ._deprecation_warning import SetuptoolsDeprecationWarning - -from setuptools.extern.six import PY3 -from setuptools.extern.six.moves import filter, map - -import setuptools.version -from setuptools.extension import Extension -from setuptools.dist import Distribution, Feature -from setuptools.depends import Require -from . import monkey - -__metaclass__ = type - - -__all__ = [ - 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', - 'SetuptoolsDeprecationWarning', - 'find_packages' -] - -if PY3: - __all__.append('find_namespace_packages') - -__version__ = setuptools.version.__version__ - -bootstrap_install_from = None - -# If we run 2to3 on .py files, should we also convert docstrings? -# Default: yes; assume that we can detect doctests reliably -run_2to3_on_doctests = True -# Standard package names for fixer packages -lib2to3_fixer_packages = ['lib2to3.fixes'] - - -class PackageFinder: - """ - Generate a list of all Python packages found within a directory - """ - - @classmethod - def find(cls, where='.', exclude=(), include=('*',)): - """Return a list all Python packages found within directory 'where' - - 'where' is the root directory which will be searched for packages. It - should be supplied as a "cross-platform" (i.e. URL-style) path; it will - be converted to the appropriate local path syntax. - - 'exclude' is a sequence of package names to exclude; '*' can be used - as a wildcard in the names, such that 'foo.*' will exclude all - subpackages of 'foo' (but not 'foo' itself). - - 'include' is a sequence of package names to include. If it's - specified, only the named packages will be included. If it's not - specified, all found packages will be included. 'include' can contain - shell style wildcard patterns just like 'exclude'. - """ - - return list(cls._find_packages_iter( - convert_path(where), - cls._build_filter('ez_setup', '*__pycache__', *exclude), - cls._build_filter(*include))) - - @classmethod - def _find_packages_iter(cls, where, exclude, include): - """ - All the packages found in 'where' that pass the 'include' filter, but - not the 'exclude' filter. - """ - for root, dirs, files in os.walk(where, followlinks=True): - # Copy dirs to iterate over it, then empty dirs. - all_dirs = dirs[:] - dirs[:] = [] - - for dir in all_dirs: - full_path = os.path.join(root, dir) - rel_path = os.path.relpath(full_path, where) - package = rel_path.replace(os.path.sep, '.') - - # Skip directory trees that are not valid packages - if ('.' in dir or not cls._looks_like_package(full_path)): - continue - - # Should this package be included? - if include(package) and not exclude(package): - yield package - - # Keep searching subdirectories, as there may be more packages - # down there, even if the parent was excluded. - dirs.append(dir) - - @staticmethod - def _looks_like_package(path): - """Does a directory look like a package?""" - return os.path.isfile(os.path.join(path, '__init__.py')) - - @staticmethod - def _build_filter(*patterns): - """ - Given a list of patterns, return a callable that will be true only if - the input matches at least one of the patterns. - """ - return lambda name: any(fnmatchcase(name, pat=pat) for pat in patterns) - - -class PEP420PackageFinder(PackageFinder): - @staticmethod - def _looks_like_package(path): - return True - - -find_packages = PackageFinder.find - -if PY3: - find_namespace_packages = PEP420PackageFinder.find - - -def _install_setup_requires(attrs): - # Note: do not use `setuptools.Distribution` directly, as - # our PEP 517 backend patch `distutils.core.Distribution`. - dist = distutils.core.Distribution(dict( - (k, v) for k, v in attrs.items() - if k in ('dependency_links', 'setup_requires') - )) - # Honor setup.cfg's options. - dist.parse_config_files(ignore_option_errors=True) - if dist.setup_requires: - dist.fetch_build_eggs(dist.setup_requires) - - -def setup(**attrs): - # Make sure we have any requirements needed to interpret 'attrs'. - _install_setup_requires(attrs) - return distutils.core.setup(**attrs) - -setup.__doc__ = distutils.core.setup.__doc__ - - -_Command = monkey.get_unpatched(distutils.core.Command) - - -class Command(_Command): - __doc__ = _Command.__doc__ - - command_consumes_arguments = False - - def __init__(self, dist, **kw): - """ - Construct the command for dist, updating - vars(self) with any keyword parameters. - """ - _Command.__init__(self, dist) - vars(self).update(kw) - - def reinitialize_command(self, command, reinit_subcommands=0, **kw): - cmd = _Command.reinitialize_command(self, command, reinit_subcommands) - vars(cmd).update(kw) - return cmd - - -def _find_all_simple(path): - """ - Find all files under 'path' - """ - results = ( - os.path.join(base, file) - for base, dirs, files in os.walk(path, followlinks=True) - for file in files - ) - return filter(os.path.isfile, results) - - -def findall(dir=os.curdir): - """ - Find all files under 'dir' and return the list of full filenames. - Unless dir is '.', return full filenames with dir prepended. - """ - files = _find_all_simple(dir) - if dir == os.curdir: - make_rel = functools.partial(os.path.relpath, start=dir) - files = map(make_rel, files) - return list(files) - - -# Apply monkey patches -monkey.patch_all() diff --git a/lib/setuptools/_deprecation_warning.py b/lib/setuptools/_deprecation_warning.py deleted file mode 100644 index 086b64d..0000000 --- a/lib/setuptools/_deprecation_warning.py +++ /dev/null @@ -1,7 +0,0 @@ -class SetuptoolsDeprecationWarning(Warning): - """ - Base class for warning deprecations in ``setuptools`` - - This class is not derived from ``DeprecationWarning``, and as such is - visible by default. - """ diff --git a/lib/setuptools/_vendor/__init__.py b/lib/setuptools/_vendor/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/setuptools/_vendor/packaging/__about__.py b/lib/setuptools/_vendor/packaging/__about__.py deleted file mode 100644 index 95d330e..0000000 --- a/lib/setuptools/_vendor/packaging/__about__.py +++ /dev/null @@ -1,21 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -__all__ = [ - "__title__", "__summary__", "__uri__", "__version__", "__author__", - "__email__", "__license__", "__copyright__", -] - -__title__ = "packaging" -__summary__ = "Core utilities for Python packages" -__uri__ = "https://github.com/pypa/packaging" - -__version__ = "16.8" - -__author__ = "Donald Stufft and individual contributors" -__email__ = "donald@stufft.io" - -__license__ = "BSD or Apache License, Version 2.0" -__copyright__ = "Copyright 2014-2016 %s" % __author__ diff --git a/lib/setuptools/_vendor/packaging/__init__.py b/lib/setuptools/_vendor/packaging/__init__.py deleted file mode 100644 index 5ee6220..0000000 --- a/lib/setuptools/_vendor/packaging/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -from .__about__ import ( - __author__, __copyright__, __email__, __license__, __summary__, __title__, - __uri__, __version__ -) - -__all__ = [ - "__title__", "__summary__", "__uri__", "__version__", "__author__", - "__email__", "__license__", "__copyright__", -] diff --git a/lib/setuptools/_vendor/packaging/_compat.py b/lib/setuptools/_vendor/packaging/_compat.py deleted file mode 100644 index 210bb80..0000000 --- a/lib/setuptools/_vendor/packaging/_compat.py +++ /dev/null @@ -1,30 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import sys - - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -# flake8: noqa - -if PY3: - string_types = str, -else: - string_types = basestring, - - -def with_metaclass(meta, *bases): - """ - Create a base class with a metaclass. - """ - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(meta): - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) diff --git a/lib/setuptools/_vendor/packaging/_structures.py b/lib/setuptools/_vendor/packaging/_structures.py deleted file mode 100644 index ccc2786..0000000 --- a/lib/setuptools/_vendor/packaging/_structures.py +++ /dev/null @@ -1,68 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - - -class Infinity(object): - - def __repr__(self): - return "Infinity" - - def __hash__(self): - return hash(repr(self)) - - def __lt__(self, other): - return False - - def __le__(self, other): - return False - - def __eq__(self, other): - return isinstance(other, self.__class__) - - def __ne__(self, other): - return not isinstance(other, self.__class__) - - def __gt__(self, other): - return True - - def __ge__(self, other): - return True - - def __neg__(self): - return NegativeInfinity - -Infinity = Infinity() - - -class NegativeInfinity(object): - - def __repr__(self): - return "-Infinity" - - def __hash__(self): - return hash(repr(self)) - - def __lt__(self, other): - return True - - def __le__(self, other): - return True - - def __eq__(self, other): - return isinstance(other, self.__class__) - - def __ne__(self, other): - return not isinstance(other, self.__class__) - - def __gt__(self, other): - return False - - def __ge__(self, other): - return False - - def __neg__(self): - return Infinity - -NegativeInfinity = NegativeInfinity() diff --git a/lib/setuptools/_vendor/packaging/markers.py b/lib/setuptools/_vendor/packaging/markers.py deleted file mode 100644 index 031332a..0000000 --- a/lib/setuptools/_vendor/packaging/markers.py +++ /dev/null @@ -1,301 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import operator -import os -import platform -import sys - -from setuptools.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd -from setuptools.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString -from setuptools.extern.pyparsing import Literal as L # noqa - -from ._compat import string_types -from .specifiers import Specifier, InvalidSpecifier - - -__all__ = [ - "InvalidMarker", "UndefinedComparison", "UndefinedEnvironmentName", - "Marker", "default_environment", -] - - -class InvalidMarker(ValueError): - """ - An invalid marker was found, users should refer to PEP 508. - """ - - -class UndefinedComparison(ValueError): - """ - An invalid operation was attempted on a value that doesn't support it. - """ - - -class UndefinedEnvironmentName(ValueError): - """ - A name was attempted to be used that does not exist inside of the - environment. - """ - - -class Node(object): - - def __init__(self, value): - self.value = value - - def __str__(self): - return str(self.value) - - def __repr__(self): - return "<{0}({1!r})>".format(self.__class__.__name__, str(self)) - - def serialize(self): - raise NotImplementedError - - -class Variable(Node): - - def serialize(self): - return str(self) - - -class Value(Node): - - def serialize(self): - return '"{0}"'.format(self) - - -class Op(Node): - - def serialize(self): - return str(self) - - -VARIABLE = ( - L("implementation_version") | - L("platform_python_implementation") | - L("implementation_name") | - L("python_full_version") | - L("platform_release") | - L("platform_version") | - L("platform_machine") | - L("platform_system") | - L("python_version") | - L("sys_platform") | - L("os_name") | - L("os.name") | # PEP-345 - L("sys.platform") | # PEP-345 - L("platform.version") | # PEP-345 - L("platform.machine") | # PEP-345 - L("platform.python_implementation") | # PEP-345 - L("python_implementation") | # undocumented setuptools legacy - L("extra") -) -ALIASES = { - 'os.name': 'os_name', - 'sys.platform': 'sys_platform', - 'platform.version': 'platform_version', - 'platform.machine': 'platform_machine', - 'platform.python_implementation': 'platform_python_implementation', - 'python_implementation': 'platform_python_implementation' -} -VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0]))) - -VERSION_CMP = ( - L("===") | - L("==") | - L(">=") | - L("<=") | - L("!=") | - L("~=") | - L(">") | - L("<") -) - -MARKER_OP = VERSION_CMP | L("not in") | L("in") -MARKER_OP.setParseAction(lambda s, l, t: Op(t[0])) - -MARKER_VALUE = QuotedString("'") | QuotedString('"') -MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0])) - -BOOLOP = L("and") | L("or") - -MARKER_VAR = VARIABLE | MARKER_VALUE - -MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR) -MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0])) - -LPAREN = L("(").suppress() -RPAREN = L(")").suppress() - -MARKER_EXPR = Forward() -MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN) -MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR) - -MARKER = stringStart + MARKER_EXPR + stringEnd - - -def _coerce_parse_result(results): - if isinstance(results, ParseResults): - return [_coerce_parse_result(i) for i in results] - else: - return results - - -def _format_marker(marker, first=True): - assert isinstance(marker, (list, tuple, string_types)) - - # Sometimes we have a structure like [[...]] which is a single item list - # where the single item is itself it's own list. In that case we want skip - # the rest of this function so that we don't get extraneous () on the - # outside. - if (isinstance(marker, list) and len(marker) == 1 and - isinstance(marker[0], (list, tuple))): - return _format_marker(marker[0]) - - if isinstance(marker, list): - inner = (_format_marker(m, first=False) for m in marker) - if first: - return " ".join(inner) - else: - return "(" + " ".join(inner) + ")" - elif isinstance(marker, tuple): - return " ".join([m.serialize() for m in marker]) - else: - return marker - - -_operators = { - "in": lambda lhs, rhs: lhs in rhs, - "not in": lambda lhs, rhs: lhs not in rhs, - "<": operator.lt, - "<=": operator.le, - "==": operator.eq, - "!=": operator.ne, - ">=": operator.ge, - ">": operator.gt, -} - - -def _eval_op(lhs, op, rhs): - try: - spec = Specifier("".join([op.serialize(), rhs])) - except InvalidSpecifier: - pass - else: - return spec.contains(lhs) - - oper = _operators.get(op.serialize()) - if oper is None: - raise UndefinedComparison( - "Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs) - ) - - return oper(lhs, rhs) - - -_undefined = object() - - -def _get_env(environment, name): - value = environment.get(name, _undefined) - - if value is _undefined: - raise UndefinedEnvironmentName( - "{0!r} does not exist in evaluation environment.".format(name) - ) - - return value - - -def _evaluate_markers(markers, environment): - groups = [[]] - - for marker in markers: - assert isinstance(marker, (list, tuple, string_types)) - - if isinstance(marker, list): - groups[-1].append(_evaluate_markers(marker, environment)) - elif isinstance(marker, tuple): - lhs, op, rhs = marker - - if isinstance(lhs, Variable): - lhs_value = _get_env(environment, lhs.value) - rhs_value = rhs.value - else: - lhs_value = lhs.value - rhs_value = _get_env(environment, rhs.value) - - groups[-1].append(_eval_op(lhs_value, op, rhs_value)) - else: - assert marker in ["and", "or"] - if marker == "or": - groups.append([]) - - return any(all(item) for item in groups) - - -def format_full_version(info): - version = '{0.major}.{0.minor}.{0.micro}'.format(info) - kind = info.releaselevel - if kind != 'final': - version += kind[0] + str(info.serial) - return version - - -def default_environment(): - if hasattr(sys, 'implementation'): - iver = format_full_version(sys.implementation.version) - implementation_name = sys.implementation.name - else: - iver = '0' - implementation_name = '' - - return { - "implementation_name": implementation_name, - "implementation_version": iver, - "os_name": os.name, - "platform_machine": platform.machine(), - "platform_release": platform.release(), - "platform_system": platform.system(), - "platform_version": platform.version(), - "python_full_version": platform.python_version(), - "platform_python_implementation": platform.python_implementation(), - "python_version": platform.python_version()[:3], - "sys_platform": sys.platform, - } - - -class Marker(object): - - def __init__(self, marker): - try: - self._markers = _coerce_parse_result(MARKER.parseString(marker)) - except ParseException as e: - err_str = "Invalid marker: {0!r}, parse error at {1!r}".format( - marker, marker[e.loc:e.loc + 8]) - raise InvalidMarker(err_str) - - def __str__(self): - return _format_marker(self._markers) - - def __repr__(self): - return "<Marker({0!r})>".format(str(self)) - - def evaluate(self, environment=None): - """Evaluate a marker. - - Return the boolean from evaluating the given marker against the - environment. environment is an optional argument to override all or - part of the determined environment. - - The environment is determined from the current Python process. - """ - current_environment = default_environment() - if environment is not None: - current_environment.update(environment) - - return _evaluate_markers(self._markers, current_environment) diff --git a/lib/setuptools/_vendor/packaging/requirements.py b/lib/setuptools/_vendor/packaging/requirements.py deleted file mode 100644 index 5b49341..0000000 --- a/lib/setuptools/_vendor/packaging/requirements.py +++ /dev/null @@ -1,127 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import string -import re - -from setuptools.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException -from setuptools.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine -from setuptools.extern.pyparsing import Literal as L # noqa -from setuptools.extern.six.moves.urllib import parse as urlparse - -from .markers import MARKER_EXPR, Marker -from .specifiers import LegacySpecifier, Specifier, SpecifierSet - - -class InvalidRequirement(ValueError): - """ - An invalid requirement was found, users should refer to PEP 508. - """ - - -ALPHANUM = Word(string.ascii_letters + string.digits) - -LBRACKET = L("[").suppress() -RBRACKET = L("]").suppress() -LPAREN = L("(").suppress() -RPAREN = L(")").suppress() -COMMA = L(",").suppress() -SEMICOLON = L(";").suppress() -AT = L("@").suppress() - -PUNCTUATION = Word("-_.") -IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM) -IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END)) - -NAME = IDENTIFIER("name") -EXTRA = IDENTIFIER - -URI = Regex(r'[^ ]+')("url") -URL = (AT + URI) - -EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA) -EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras") - -VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE) -VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE) - -VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY -VERSION_MANY = Combine(VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), - joinString=",", adjacent=False)("_raw_spec") -_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)) -_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or '') - -VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier") -VERSION_SPEC.setParseAction(lambda s, l, t: t[1]) - -MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker") -MARKER_EXPR.setParseAction( - lambda s, l, t: Marker(s[t._original_start:t._original_end]) -) -MARKER_SEPERATOR = SEMICOLON -MARKER = MARKER_SEPERATOR + MARKER_EXPR - -VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER) -URL_AND_MARKER = URL + Optional(MARKER) - -NAMED_REQUIREMENT = \ - NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER) - -REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd - - -class Requirement(object): - """Parse a requirement. - - Parse a given requirement string into its parts, such as name, specifier, - URL, and extras. Raises InvalidRequirement on a badly-formed requirement - string. - """ - - # TODO: Can we test whether something is contained within a requirement? - # If so how do we do that? Do we need to test against the _name_ of - # the thing as well as the version? What about the markers? - # TODO: Can we normalize the name and extra name? - - def __init__(self, requirement_string): - try: - req = REQUIREMENT.parseString(requirement_string) - except ParseException as e: - raise InvalidRequirement( - "Invalid requirement, parse error at \"{0!r}\"".format( - requirement_string[e.loc:e.loc + 8])) - - self.name = req.name - if req.url: - parsed_url = urlparse.urlparse(req.url) - if not (parsed_url.scheme and parsed_url.netloc) or ( - not parsed_url.scheme and not parsed_url.netloc): - raise InvalidRequirement("Invalid URL given") - self.url = req.url - else: - self.url = None - self.extras = set(req.extras.asList() if req.extras else []) - self.specifier = SpecifierSet(req.specifier) - self.marker = req.marker if req.marker else None - - def __str__(self): - parts = [self.name] - - if self.extras: - parts.append("[{0}]".format(",".join(sorted(self.extras)))) - - if self.specifier: - parts.append(str(self.specifier)) - - if self.url: - parts.append("@ {0}".format(self.url)) - - if self.marker: - parts.append("; {0}".format(self.marker)) - - return "".join(parts) - - def __repr__(self): - return "<Requirement({0!r})>".format(str(self)) diff --git a/lib/setuptools/_vendor/packaging/specifiers.py b/lib/setuptools/_vendor/packaging/specifiers.py deleted file mode 100644 index 7f5a76c..0000000 --- a/lib/setuptools/_vendor/packaging/specifiers.py +++ /dev/null @@ -1,774 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import abc -import functools -import itertools -import re - -from ._compat import string_types, with_metaclass -from .version import Version, LegacyVersion, parse - - -class InvalidSpecifier(ValueError): - """ - An invalid specifier was found, users should refer to PEP 440. - """ - - -class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): - - @abc.abstractmethod - def __str__(self): - """ - Returns the str representation of this Specifier like object. This - should be representative of the Specifier itself. - """ - - @abc.abstractmethod - def __hash__(self): - """ - Returns a hash value for this Specifier like object. - """ - - @abc.abstractmethod - def __eq__(self, other): - """ - Returns a boolean representing whether or not the two Specifier like - objects are equal. - """ - - @abc.abstractmethod - def __ne__(self, other): - """ - Returns a boolean representing whether or not the two Specifier like - objects are not equal. - """ - - @abc.abstractproperty - def prereleases(self): - """ - Returns whether or not pre-releases as a whole are allowed by this - specifier. - """ - - @prereleases.setter - def prereleases(self, value): - """ - Sets whether or not pre-releases as a whole are allowed by this - specifier. - """ - - @abc.abstractmethod - def contains(self, item, prereleases=None): - """ - Determines if the given item is contained within this specifier. - """ - - @abc.abstractmethod - def filter(self, iterable, prereleases=None): - """ - Takes an iterable of items and filters them so that only items which - are contained within this specifier are allowed in it. - """ - - -class _IndividualSpecifier(BaseSpecifier): - - _operators = {} - - def __init__(self, spec="", prereleases=None): - match = self._regex.search(spec) - if not match: - raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec)) - - self._spec = ( - match.group("operator").strip(), - match.group("version").strip(), - ) - - # Store whether or not this Specifier should accept prereleases - self._prereleases = prereleases - - def __repr__(self): - pre = ( - ", prereleases={0!r}".format(self.prereleases) - if self._prereleases is not None - else "" - ) - - return "<{0}({1!r}{2})>".format( - self.__class__.__name__, - str(self), - pre, - ) - - def __str__(self): - return "{0}{1}".format(*self._spec) - - def __hash__(self): - return hash(self._spec) - - def __eq__(self, other): - if isinstance(other, string_types): - try: - other = self.__class__(other) - except InvalidSpecifier: - return NotImplemented - elif not isinstance(other, self.__class__): - return NotImplemented - - return self._spec == other._spec - - def __ne__(self, other): - if isinstance(other, string_types): - try: - other = self.__class__(other) - except InvalidSpecifier: - return NotImplemented - elif not isinstance(other, self.__class__): - return NotImplemented - - return self._spec != other._spec - - def _get_operator(self, op): - return getattr(self, "_compare_{0}".format(self._operators[op])) - - def _coerce_version(self, version): - if not isinstance(version, (LegacyVersion, Version)): - version = parse(version) - return version - - @property - def operator(self): - return self._spec[0] - - @property - def version(self): - return self._spec[1] - - @property - def prereleases(self): - return self._prereleases - - @prereleases.setter - def prereleases(self, value): - self._prereleases = value - - def __contains__(self, item): - return self.contains(item) - - def contains(self, item, prereleases=None): - # Determine if prereleases are to be allowed or not. - if prereleases is None: - prereleases = self.prereleases - - # Normalize item to a Version or LegacyVersion, this allows us to have - # a shortcut for ``"2.0" in Specifier(">=2") - item = self._coerce_version(item) - - # Determine if we should be supporting prereleases in this specifier - # or not, if we do not support prereleases than we can short circuit - # logic if this version is a prereleases. - if item.is_prerelease and not prereleases: - return False - - # Actually do the comparison to determine if this item is contained - # within this Specifier or not. - return self._get_operator(self.operator)(item, self.version) - - def filter(self, iterable, prereleases=None): - yielded = False - found_prereleases = [] - - kw = {"prereleases": prereleases if prereleases is not None else True} - - # Attempt to iterate over all the values in the iterable and if any of - # them match, yield them. - for version in iterable: - parsed_version = self._coerce_version(version) - - if self.contains(parsed_version, **kw): - # If our version is a prerelease, and we were not set to allow - # prereleases, then we'll store it for later incase nothing - # else matches this specifier. - if (parsed_version.is_prerelease and not - (prereleases or self.prereleases)): - found_prereleases.append(version) - # Either this is not a prerelease, or we should have been - # accepting prereleases from the begining. - else: - yielded = True - yield version - - # Now that we've iterated over everything, determine if we've yielded - # any values, and if we have not and we have any prereleases stored up - # then we will go ahead and yield the prereleases. - if not yielded and found_prereleases: - for version in found_prereleases: - yield version - - -class LegacySpecifier(_IndividualSpecifier): - - _regex_str = ( - r""" - (?P<operator>(==|!=|<=|>=|<|>)) - \s* - (?P<version> - [^,;\s)]* # Since this is a "legacy" specifier, and the version - # string can be just about anything, we match everything - # except for whitespace, a semi-colon for marker support, - # a closing paren since versions can be enclosed in - # them, and a comma since it's a version separator. - ) - """ - ) - - _regex = re.compile( - r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) - - _operators = { - "==": "equal", - "!=": "not_equal", - "<=": "less_than_equal", - ">=": "greater_than_equal", - "<": "less_than", - ">": "greater_than", - } - - def _coerce_version(self, version): - if not isinstance(version, LegacyVersion): - version = LegacyVersion(str(version)) - return version - - def _compare_equal(self, prospective, spec): - return prospective == self._coerce_version(spec) - - def _compare_not_equal(self, prospective, spec): - return prospective != self._coerce_version(spec) - - def _compare_less_than_equal(self, prospective, spec): - return prospective <= self._coerce_version(spec) - - def _compare_greater_than_equal(self, prospective, spec): - return prospective >= self._coerce_version(spec) - - def _compare_less_than(self, prospective, spec): - return prospective < self._coerce_version(spec) - - def _compare_greater_than(self, prospective, spec): - return prospective > self._coerce_version(spec) - - -def _require_version_compare(fn): - @functools.wraps(fn) - def wrapped(self, prospective, spec): - if not isinstance(prospective, Version): - return False - return fn(self, prospective, spec) - return wrapped - - -class Specifier(_IndividualSpecifier): - - _regex_str = ( - r""" - (?P<operator>(~=|==|!=|<=|>=|<|>|===)) - (?P<version> - (?: - # The identity operators allow for an escape hatch that will - # do an exact string match of the version you wish to install. - # This will not be parsed by PEP 440 and we cannot determine - # any semantic meaning from it. This operator is discouraged - # but included entirely as an escape hatch. - (?<====) # Only match for the identity operator - \s* - [^\s]* # We just match everything, except for whitespace - # since we are only testing for strict identity. - ) - | - (?: - # The (non)equality operators allow for wild card and local - # versions to be specified so we have to define these two - # operators separately to enable that. - (?<===|!=) # Only match for equals and not equals - - \s* - v? - (?:[0-9]+!)? # epoch - [0-9]+(?:\.[0-9]+)* # release - (?: # pre release - [-_\.]? - (a|b|c|rc|alpha|beta|pre|preview) - [-_\.]? - [0-9]* - )? - (?: # post release - (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) - )? - - # You cannot use a wild card and a dev or local version - # together so group them with a | and make them optional. - (?: - (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release - (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local - | - \.\* # Wild card syntax of .* - )? - ) - | - (?: - # The compatible operator requires at least two digits in the - # release segment. - (?<=~=) # Only match for the compatible operator - - \s* - v? - (?:[0-9]+!)? # epoch - [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) - (?: # pre release - [-_\.]? - (a|b|c|rc|alpha|beta|pre|preview) - [-_\.]? - [0-9]* - )? - (?: # post release - (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) - )? - (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release - ) - | - (?: - # All other operators only allow a sub set of what the - # (non)equality operators do. Specifically they do not allow - # local versions to be specified nor do they allow the prefix - # matching wild cards. - (?<!==|!=|~=) # We have special cases for these - # operators so we want to make sure they - # don't match here. - - \s* - v? - (?:[0-9]+!)? # epoch - [0-9]+(?:\.[0-9]+)* # release - (?: # pre release - [-_\.]? - (a|b|c|rc|alpha|beta|pre|preview) - [-_\.]? - [0-9]* - )? - (?: # post release - (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) - )? - (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release - ) - ) - """ - ) - - _regex = re.compile( - r"^\s*" + _regex_str + r"\s*$", re.VERBOSE | re.IGNORECASE) - - _operators = { - "~=": "compatible", - "==": "equal", - "!=": "not_equal", - "<=": "less_than_equal", - ">=": "greater_than_equal", - "<": "less_than", - ">": "greater_than", - "===": "arbitrary", - } - - @_require_version_compare - def _compare_compatible(self, prospective, spec): - # Compatible releases have an equivalent combination of >= and ==. That - # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to - # implement this in terms of the other specifiers instead of - # implementing it ourselves. The only thing we need to do is construct - # the other specifiers. - - # We want everything but the last item in the version, but we want to - # ignore post and dev releases and we want to treat the pre-release as - # it's own separate segment. - prefix = ".".join( - list( - itertools.takewhile( - lambda x: (not x.startswith("post") and not - x.startswith("dev")), - _version_split(spec), - ) - )[:-1] - ) - - # Add the prefix notation to the end of our string - prefix += ".*" - - return (self._get_operator(">=")(prospective, spec) and - self._get_operator("==")(prospective, prefix)) - - @_require_version_compare - def _compare_equal(self, prospective, spec): - # We need special logic to handle prefix matching - if spec.endswith(".*"): - # In the case of prefix matching we want to ignore local segment. - prospective = Version(prospective.public) - # Split the spec out by dots, and pretend that there is an implicit - # dot in between a release segment and a pre-release segment. - spec = _version_split(spec[:-2]) # Remove the trailing .* - - # Split the prospective version out by dots, and pretend that there - # is an implicit dot in between a release segment and a pre-release - # segment. - prospective = _version_split(str(prospective)) - - # Shorten the prospective version to be the same length as the spec - # so that we can determine if the specifier is a prefix of the - # prospective version or not. - prospective = prospective[:len(spec)] - - # Pad out our two sides with zeros so that they both equal the same - # length. - spec, prospective = _pad_version(spec, prospective) - else: - # Convert our spec string into a Version - spec = Version(spec) - - # If the specifier does not have a local segment, then we want to - # act as if the prospective version also does not have a local - # segment. - if not spec.local: - prospective = Version(prospective.public) - - return prospective == spec - - @_require_version_compare - def _compare_not_equal(self, prospective, spec): - return not self._compare_equal(prospective, spec) - - @_require_version_compare - def _compare_less_than_equal(self, prospective, spec): - return prospective <= Version(spec) - - @_require_version_compare - def _compare_greater_than_equal(self, prospective, spec): - return prospective >= Version(spec) - - @_require_version_compare - def _compare_less_than(self, prospective, spec): - # Convert our spec to a Version instance, since we'll want to work with - # it as a version. - spec = Version(spec) - - # Check to see if the prospective version is less than the spec - # version. If it's not we can short circuit and just return False now - # instead of doing extra unneeded work. - if not prospective < spec: - return False - - # This special case is here so that, unless the specifier itself - # includes is a pre-release version, that we do not accept pre-release - # versions for the version mentioned in the specifier (e.g. <3.1 should - # not match 3.1.dev0, but should match 3.0.dev0). - if not spec.is_prerelease and prospective.is_prerelease: - if Version(prospective.base_version) == Version(spec.base_version): - return False - - # If we've gotten to here, it means that prospective version is both - # less than the spec version *and* it's not a pre-release of the same - # version in the spec. - return True - - @_require_version_compare - def _compare_greater_than(self, prospective, spec): - # Convert our spec to a Version instance, since we'll want to work with - # it as a version. - spec = Version(spec) - - # Check to see if the prospective version is greater than the spec - # version. If it's not we can short circuit and just return False now - # instead of doing extra unneeded work. - if not prospective > spec: - return False - - # This special case is here so that, unless the specifier itself - # includes is a post-release version, that we do not accept - # post-release versions for the version mentioned in the specifier - # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). - if not spec.is_postrelease and prospective.is_postrelease: - if Version(prospective.base_version) == Version(spec.base_version): - return False - - # Ensure that we do not allow a local version of the version mentioned - # in the specifier, which is techincally greater than, to match. - if prospective.local is not None: - if Version(prospective.base_version) == Version(spec.base_version): - return False - - # If we've gotten to here, it means that prospective version is both - # greater than the spec version *and* it's not a pre-release of the - # same version in the spec. - return True - - def _compare_arbitrary(self, prospective, spec): - return str(prospective).lower() == str(spec).lower() - - @property - def prereleases(self): - # If there is an explicit prereleases set for this, then we'll just - # blindly use that. - if self._prereleases is not None: - return self._prereleases - - # Look at all of our specifiers and determine if they are inclusive - # operators, and if they are if they are including an explicit - # prerelease. - operator, version = self._spec - if operator in ["==", ">=", "<=", "~=", "==="]: - # The == specifier can include a trailing .*, if it does we - # want to remove before parsing. - if operator == "==" and version.endswith(".*"): - version = version[:-2] - - # Parse the version, and if it is a pre-release than this - # specifier allows pre-releases. - if parse(version).is_prerelease: - return True - - return False - - @prereleases.setter - def prereleases(self, value): - self._prereleases = value - - -_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") - - -def _version_split(version): - result = [] - for item in version.split("."): - match = _prefix_regex.search(item) - if match: - result.extend(match.groups()) - else: - result.append(item) - return result - - -def _pad_version(left, right): - left_split, right_split = [], [] - - # Get the release segment of our versions - left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) - right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) - - # Get the rest of our versions - left_split.append(left[len(left_split[0]):]) - right_split.append(right[len(right_split[0]):]) - - # Insert our padding - left_split.insert( - 1, - ["0"] * max(0, len(right_split[0]) - len(left_split[0])), - ) - right_split.insert( - 1, - ["0"] * max(0, len(left_split[0]) - len(right_split[0])), - ) - - return ( - list(itertools.chain(*left_split)), - list(itertools.chain(*right_split)), - ) - - -class SpecifierSet(BaseSpecifier): - - def __init__(self, specifiers="", prereleases=None): - # Split on , to break each indidivual specifier into it's own item, and - # strip each item to remove leading/trailing whitespace. - specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] - - # Parsed each individual specifier, attempting first to make it a - # Specifier and falling back to a LegacySpecifier. - parsed = set() - for specifier in specifiers: - try: - parsed.add(Specifier(specifier)) - except InvalidSpecifier: - parsed.add(LegacySpecifier(specifier)) - - # Turn our parsed specifiers into a frozen set and save them for later. - self._specs = frozenset(parsed) - - # Store our prereleases value so we can use it later to determine if - # we accept prereleases or not. - self._prereleases = prereleases - - def __repr__(self): - pre = ( - ", prereleases={0!r}".format(self.prereleases) - if self._prereleases is not None - else "" - ) - - return "<SpecifierSet({0!r}{1})>".format(str(self), pre) - - def __str__(self): - return ",".join(sorted(str(s) for s in self._specs)) - - def __hash__(self): - return hash(self._specs) - - def __and__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - specifier = SpecifierSet() - specifier._specs = frozenset(self._specs | other._specs) - - if self._prereleases is None and other._prereleases is not None: - specifier._prereleases = other._prereleases - elif self._prereleases is not None and other._prereleases is None: - specifier._prereleases = self._prereleases - elif self._prereleases == other._prereleases: - specifier._prereleases = self._prereleases - else: - raise ValueError( - "Cannot combine SpecifierSets with True and False prerelease " - "overrides." - ) - - return specifier - - def __eq__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif isinstance(other, _IndividualSpecifier): - other = SpecifierSet(str(other)) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - return self._specs == other._specs - - def __ne__(self, other): - if isinstance(other, string_types): - other = SpecifierSet(other) - elif isinstance(other, _IndividualSpecifier): - other = SpecifierSet(str(other)) - elif not isinstance(other, SpecifierSet): - return NotImplemented - - return self._specs != other._specs - - def __len__(self): - return len(self._specs) - - def __iter__(self): - return iter(self._specs) - - @property - def prereleases(self): - # If we have been given an explicit prerelease modifier, then we'll - # pass that through here. - if self._prereleases is not None: - return self._prereleases - - # If we don't have any specifiers, and we don't have a forced value, - # then we'll just return None since we don't know if this should have - # pre-releases or not. - if not self._specs: - return None - - # Otherwise we'll see if any of the given specifiers accept - # prereleases, if any of them do we'll return True, otherwise False. - return any(s.prereleases for s in self._specs) - - @prereleases.setter - def prereleases(self, value): - self._prereleases = value - - def __contains__(self, item): - return self.contains(item) - - def contains(self, item, prereleases=None): - # Ensure that our item is a Version or LegacyVersion instance. - if not isinstance(item, (LegacyVersion, Version)): - item = parse(item) - - # Determine if we're forcing a prerelease or not, if we're not forcing - # one for this particular filter call, then we'll use whatever the - # SpecifierSet thinks for whether or not we should support prereleases. - if prereleases is None: - prereleases = self.prereleases - - # We can determine if we're going to allow pre-releases by looking to - # see if any of the underlying items supports them. If none of them do - # and this item is a pre-release then we do not allow it and we can - # short circuit that here. - # Note: This means that 1.0.dev1 would not be contained in something - # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 - if not prereleases and item.is_prerelease: - return False - - # We simply dispatch to the underlying specs here to make sure that the - # given version is contained within all of them. - # Note: This use of all() here means that an empty set of specifiers - # will always return True, this is an explicit design decision. - return all( - s.contains(item, prereleases=prereleases) - for s in self._specs - ) - - def filter(self, iterable, prereleases=None): - # Determine if we're forcing a prerelease or not, if we're not forcing - # one for this particular filter call, then we'll use whatever the - # SpecifierSet thinks for whether or not we should support prereleases. - if prereleases is None: - prereleases = self.prereleases - - # If we have any specifiers, then we want to wrap our iterable in the - # filter method for each one, this will act as a logical AND amongst - # each specifier. - if self._specs: - for spec in self._specs: - iterable = spec.filter(iterable, prereleases=bool(prereleases)) - return iterable - # If we do not have any specifiers, then we need to have a rough filter - # which will filter out any pre-releases, unless there are no final - # releases, and which will filter out LegacyVersion in general. - else: - filtered = [] - found_prereleases = [] - - for item in iterable: - # Ensure that we some kind of Version class for this item. - if not isinstance(item, (LegacyVersion, Version)): - parsed_version = parse(item) - else: - parsed_version = item - - # Filter out any item which is parsed as a LegacyVersion - if isinstance(parsed_version, LegacyVersion): - continue - - # Store any item which is a pre-release for later unless we've - # already found a final version or we are accepting prereleases - if parsed_version.is_prerelease and not prereleases: - if not filtered: - found_prereleases.append(item) - else: - filtered.append(item) - - # If we've found no items except for pre-releases, then we'll go - # ahead and use the pre-releases - if not filtered and found_prereleases and prereleases is None: - return found_prereleases - - return filtered diff --git a/lib/setuptools/_vendor/packaging/utils.py b/lib/setuptools/_vendor/packaging/utils.py deleted file mode 100644 index 942387c..0000000 --- a/lib/setuptools/_vendor/packaging/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import re - - -_canonicalize_regex = re.compile(r"[-_.]+") - - -def canonicalize_name(name): - # This is taken from PEP 503. - return _canonicalize_regex.sub("-", name).lower() diff --git a/lib/setuptools/_vendor/packaging/version.py b/lib/setuptools/_vendor/packaging/version.py deleted file mode 100644 index 83b5ee8..0000000 --- a/lib/setuptools/_vendor/packaging/version.py +++ /dev/null @@ -1,393 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. -from __future__ import absolute_import, division, print_function - -import collections -import itertools -import re - -from ._structures import Infinity - - -__all__ = [ - "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN" -] - - -_Version = collections.namedtuple( - "_Version", - ["epoch", "release", "dev", "pre", "post", "local"], -) - - -def parse(version): - """ - Parse the given version string and return either a :class:`Version` object - or a :class:`LegacyVersion` object depending on if the given version is - a valid PEP 440 version or a legacy version. - """ - try: - return Version(version) - except InvalidVersion: - return LegacyVersion(version) - - -class InvalidVersion(ValueError): - """ - An invalid version was found, users should refer to PEP 440. - """ - - -class _BaseVersion(object): - - def __hash__(self): - return hash(self._key) - - def __lt__(self, other): - return self._compare(other, lambda s, o: s < o) - - def __le__(self, other): - return self._compare(other, lambda s, o: s <= o) - - def __eq__(self, other): - return self._compare(other, lambda s, o: s == o) - - def __ge__(self, other): - return self._compare(other, lambda s, o: s >= o) - - def __gt__(self, other): - return self._compare(other, lambda s, o: s > o) - - def __ne__(self, other): - return self._compare(other, lambda s, o: s != o) - - def _compare(self, other, method): - if not isinstance(other, _BaseVersion): - return NotImplemented - - return method(self._key, other._key) - - -class LegacyVersion(_BaseVersion): - - def __init__(self, version): - self._version = str(version) - self._key = _legacy_cmpkey(self._version) - - def __str__(self): - return self._version - - def __repr__(self): - return "<LegacyVersion({0})>".format(repr(str(self))) - - @property - def public(self): - return self._version - - @property - def base_version(self): - return self._version - - @property - def local(self): - return None - - @property - def is_prerelease(self): - return False - - @property - def is_postrelease(self): - return False - - -_legacy_version_component_re = re.compile( - r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, -) - -_legacy_version_replacement_map = { - "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@", -} - - -def _parse_version_parts(s): - for part in _legacy_version_component_re.split(s): - part = _legacy_version_replacement_map.get(part, part) - - if not part or part == ".": - continue - - if part[:1] in "0123456789": - # pad for numeric comparison - yield part.zfill(8) - else: - yield "*" + part - - # ensure that alpha/beta/candidate are before final - yield "*final" - - -def _legacy_cmpkey(version): - # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch - # greater than or equal to 0. This will effectively put the LegacyVersion, - # which uses the defacto standard originally implemented by setuptools, - # as before all PEP 440 versions. - epoch = -1 - - # This scheme is taken from pkg_resources.parse_version setuptools prior to - # it's adoption of the packaging library. - parts = [] - for part in _parse_version_parts(version.lower()): - if part.startswith("*"): - # remove "-" before a prerelease tag - if part < "*final": - while parts and parts[-1] == "*final-": - parts.pop() - - # remove trailing zeros from each series of numeric parts - while parts and parts[-1] == "00000000": - parts.pop() - - parts.append(part) - parts = tuple(parts) - - return epoch, parts - -# Deliberately not anchored to the start and end of the string, to make it -# easier for 3rd party code to reuse -VERSION_PATTERN = r""" - v? - (?: - (?:(?P<epoch>[0-9]+)!)? # epoch - (?P<release>[0-9]+(?:\.[0-9]+)*) # release segment - (?P<pre> # pre-release - [-_\.]? - (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview)) - [-_\.]? - (?P<pre_n>[0-9]+)? - )? - (?P<post> # post release - (?:-(?P<post_n1>[0-9]+)) - | - (?: - [-_\.]? - (?P<post_l>post|rev|r) - [-_\.]? - (?P<post_n2>[0-9]+)? - ) - )? - (?P<dev> # dev release - [-_\.]? - (?P<dev_l>dev) - [-_\.]? - (?P<dev_n>[0-9]+)? - )? - ) - (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version -""" - - -class Version(_BaseVersion): - - _regex = re.compile( - r"^\s*" + VERSION_PATTERN + r"\s*$", - re.VERBOSE | re.IGNORECASE, - ) - - def __init__(self, version): - # Validate the version and parse it into pieces - match = self._regex.search(version) - if not match: - raise InvalidVersion("Invalid version: '{0}'".format(version)) - - # Store the parsed out pieces of the version - self._version = _Version( - epoch=int(match.group("epoch")) if match.group("epoch") else 0, - release=tuple(int(i) for i in match.group("release").split(".")), - pre=_parse_letter_version( - match.group("pre_l"), - match.group("pre_n"), - ), - post=_parse_letter_version( - match.group("post_l"), - match.group("post_n1") or match.group("post_n2"), - ), - dev=_parse_letter_version( - match.group("dev_l"), - match.group("dev_n"), - ), - local=_parse_local_version(match.group("local")), - ) - - # Generate a key which will be used for sorting - self._key = _cmpkey( - self._version.epoch, - self._version.release, - self._version.pre, - self._version.post, - self._version.dev, - self._version.local, - ) - - def __repr__(self): - return "<Version({0})>".format(repr(str(self))) - - def __str__(self): - parts = [] - - # Epoch - if self._version.epoch != 0: - parts.append("{0}!".format(self._version.epoch)) - - # Release segment - parts.append(".".join(str(x) for x in self._version.release)) - - # Pre-release - if self._version.pre is not None: - parts.append("".join(str(x) for x in self._version.pre)) - - # Post-release - if self._version.post is not None: - parts.append(".post{0}".format(self._version.post[1])) - - # Development release - if self._version.dev is not None: - parts.append(".dev{0}".format(self._version.dev[1])) - - # Local version segment - if self._version.local is not None: - parts.append( - "+{0}".format(".".join(str(x) for x in self._version.local)) - ) - - return "".join(parts) - - @property - def public(self): - return str(self).split("+", 1)[0] - - @property - def base_version(self): - parts = [] - - # Epoch - if self._version.epoch != 0: - parts.append("{0}!".format(self._version.epoch)) - - # Release segment - parts.append(".".join(str(x) for x in self._version.release)) - - return "".join(parts) - - @property - def local(self): - version_string = str(self) - if "+" in version_string: - return version_string.split("+", 1)[1] - - @property - def is_prerelease(self): - return bool(self._version.dev or self._version.pre) - - @property - def is_postrelease(self): - return bool(self._version.post) - - -def _parse_letter_version(letter, number): - if letter: - # We consider there to be an implicit 0 in a pre-release if there is - # not a numeral associated with it. - if number is None: - number = 0 - - # We normalize any letters to their lower case form - letter = letter.lower() - - # We consider some words to be alternate spellings of other words and - # in those cases we want to normalize the spellings to our preferred - # spelling. - if letter == "alpha": - letter = "a" - elif letter == "beta": - letter = "b" - elif letter in ["c", "pre", "preview"]: - letter = "rc" - elif letter in ["rev", "r"]: - letter = "post" - - return letter, int(number) - if not letter and number: - # We assume if we are given a number, but we are not given a letter - # then this is using the implicit post release syntax (e.g. 1.0-1) - letter = "post" - - return letter, int(number) - - -_local_version_seperators = re.compile(r"[\._-]") - - -def _parse_local_version(local): - """ - Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve"). - """ - if local is not None: - return tuple( - part.lower() if not part.isdigit() else int(part) - for part in _local_version_seperators.split(local) - ) - - -def _cmpkey(epoch, release, pre, post, dev, local): - # When we compare a release version, we want to compare it with all of the - # trailing zeros removed. So we'll use a reverse the list, drop all the now - # leading zeros until we come to something non zero, then take the rest - # re-reverse it back into the correct order and make it a tuple and use - # that for our sorting key. - release = tuple( - reversed(list( - itertools.dropwhile( - lambda x: x == 0, - reversed(release), - ) - )) - ) - - # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0. - # We'll do this by abusing the pre segment, but we _only_ want to do this - # if there is not a pre or a post segment. If we have one of those then - # the normal sorting rules will handle this case correctly. - if pre is None and post is None and dev is not None: - pre = -Infinity - # Versions without a pre-release (except as noted above) should sort after - # those with one. - elif pre is None: - pre = Infinity - - # Versions without a post segment should sort before those with one. - if post is None: - post = -Infinity - - # Versions without a development segment should sort after those with one. - if dev is None: - dev = Infinity - - if local is None: - # Versions without a local segment should sort before those with one. - local = -Infinity - else: - # Versions with a local segment need that segment parsed to implement - # the sorting rules in PEP440. - # - Alpha numeric segments sort before numeric segments - # - Alpha numeric segments sort lexicographically - # - Numeric segments sort numerically - # - Shorter versions sort before longer versions when the prefixes - # match exactly - local = tuple( - (i, "") if isinstance(i, int) else (-Infinity, i) - for i in local - ) - - return epoch, release, pre, post, dev, local diff --git a/lib/setuptools/_vendor/pyparsing.py b/lib/setuptools/_vendor/pyparsing.py deleted file mode 100644 index 4aa30ee..0000000 --- a/lib/setuptools/_vendor/pyparsing.py +++ /dev/null @@ -1,5742 +0,0 @@ -# module pyparsing.py -# -# Copyright (c) 2003-2018 Paul T. McGuire -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# - -__doc__ = \ -""" -pyparsing module - Classes and methods to define and execute parsing grammars -============================================================================= - -The pyparsing module is an alternative approach to creating and executing simple grammars, -vs. the traditional lex/yacc approach, or the use of regular expressions. With pyparsing, you -don't need to learn a new syntax for defining grammars or matching expressions - the parsing module -provides a library of classes that you use to construct the grammar directly in Python. - -Here is a program to parse "Hello, World!" (or any greeting of the form -C{"<salutation>, <addressee>!"}), built up using L{Word}, L{Literal}, and L{And} elements -(L{'+'<ParserElement.__add__>} operator gives L{And} expressions, strings are auto-converted to -L{Literal} expressions):: - - from pyparsing import Word, alphas - - # define grammar of a greeting - greet = Word(alphas) + "," + Word(alphas) + "!" - - hello = "Hello, World!" - print (hello, "->", greet.parseString(hello)) - -The program outputs the following:: - - Hello, World! -> ['Hello', ',', 'World', '!'] - -The Python representation of the grammar is quite readable, owing to the self-explanatory -class names, and the use of '+', '|' and '^' operators. - -The L{ParseResults} object returned from L{ParserElement.parseString<ParserElement.parseString>} can be accessed as a nested list, a dictionary, or an -object with named attributes. - -The pyparsing module handles some of the problems that are typically vexing when writing text parsers: - - extra or missing whitespace (the above program will also handle "Hello,World!", "Hello , World !", etc.) - - quoted strings - - embedded comments - - -Getting Started - ------------------ -Visit the classes L{ParserElement} and L{ParseResults} to see the base classes that most other pyparsing -classes inherit from. Use the docstrings for examples of how to: - - construct literal match expressions from L{Literal} and L{CaselessLiteral} classes - - construct character word-group expressions using the L{Word} class - - see how to create repetitive expressions using L{ZeroOrMore} and L{OneOrMore} classes - - use L{'+'<And>}, L{'|'<MatchFirst>}, L{'^'<Or>}, and L{'&'<Each>} operators to combine simple expressions into more complex ones - - associate names with your parsed results using L{ParserElement.setResultsName} - - find some helpful expression short-cuts like L{delimitedList} and L{oneOf} - - find more useful common expressions in the L{pyparsing_common} namespace class -""" - -__version__ = "2.2.1" -__versionTime__ = "18 Sep 2018 00:49 UTC" -__author__ = "Paul McGuire <ptmcg@users.sourceforge.net>" - -import string -from weakref import ref as wkref -import copy -import sys -import warnings -import re -import sre_constants -import collections -import pprint -import traceback -import types -from datetime import datetime - -try: - from _thread import RLock -except ImportError: - from threading import RLock - -try: - # Python 3 - from collections.abc import Iterable - from collections.abc import MutableMapping -except ImportError: - # Python 2.7 - from collections import Iterable - from collections import MutableMapping - -try: - from collections import OrderedDict as _OrderedDict -except ImportError: - try: - from ordereddict import OrderedDict as _OrderedDict - except ImportError: - _OrderedDict = None - -#~ sys.stderr.write( "testing pyparsing module, version %s, %s\n" % (__version__,__versionTime__ ) ) - -__all__ = [ -'And', 'CaselessKeyword', 'CaselessLiteral', 'CharsNotIn', 'Combine', 'Dict', 'Each', 'Empty', -'FollowedBy', 'Forward', 'GoToColumn', 'Group', 'Keyword', 'LineEnd', 'LineStart', 'Literal', -'MatchFirst', 'NoMatch', 'NotAny', 'OneOrMore', 'OnlyOnce', 'Optional', 'Or', -'ParseBaseException', 'ParseElementEnhance', 'ParseException', 'ParseExpression', 'ParseFatalException', -'ParseResults', 'ParseSyntaxException', 'ParserElement', 'QuotedString', 'RecursiveGrammarException', -'Regex', 'SkipTo', 'StringEnd', 'StringStart', 'Suppress', 'Token', 'TokenConverter', -'White', 'Word', 'WordEnd', 'WordStart', 'ZeroOrMore', -'alphanums', 'alphas', 'alphas8bit', 'anyCloseTag', 'anyOpenTag', 'cStyleComment', 'col', -'commaSeparatedList', 'commonHTMLEntity', 'countedArray', 'cppStyleComment', 'dblQuotedString', -'dblSlashComment', 'delimitedList', 'dictOf', 'downcaseTokens', 'empty', 'hexnums', -'htmlComment', 'javaStyleComment', 'line', 'lineEnd', 'lineStart', 'lineno', -'makeHTMLTags', 'makeXMLTags', 'matchOnlyAtCol', 'matchPreviousExpr', 'matchPreviousLiteral', -'nestedExpr', 'nullDebugAction', 'nums', 'oneOf', 'opAssoc', 'operatorPrecedence', 'printables', -'punc8bit', 'pythonStyleComment', 'quotedString', 'removeQuotes', 'replaceHTMLEntity', -'replaceWith', 'restOfLine', 'sglQuotedString', 'srange', 'stringEnd', -'stringStart', 'traceParseAction', 'unicodeString', 'upcaseTokens', 'withAttribute', -'indentedBlock', 'originalTextFor', 'ungroup', 'infixNotation','locatedExpr', 'withClass', -'CloseMatch', 'tokenMap', 'pyparsing_common', -] - -system_version = tuple(sys.version_info)[:3] -PY_3 = system_version[0] == 3 -if PY_3: - _MAX_INT = sys.maxsize - basestring = str - unichr = chr - _ustr = str - - # build list of single arg builtins, that can be used as parse actions - singleArgBuiltins = [sum, len, sorted, reversed, list, tuple, set, any, all, min, max] - -else: - _MAX_INT = sys.maxint - range = xrange - - def _ustr(obj): - """Drop-in replacement for str(obj) that tries to be Unicode friendly. It first tries - str(obj). If that fails with a UnicodeEncodeError, then it tries unicode(obj). It - then < returns the unicode object | encodes it with the default encoding | ... >. - """ - if isinstance(obj,unicode): - return obj - - try: - # If this works, then _ustr(obj) has the same behaviour as str(obj), so - # it won't break any existing code. - return str(obj) - - except UnicodeEncodeError: - # Else encode it - ret = unicode(obj).encode(sys.getdefaultencoding(), 'xmlcharrefreplace') - xmlcharref = Regex(r'&#\d+;') - xmlcharref.setParseAction(lambda t: '\\u' + hex(int(t[0][2:-1]))[2:]) - return xmlcharref.transformString(ret) - - # build list of single arg builtins, tolerant of Python version, that can be used as parse actions - singleArgBuiltins = [] - import __builtin__ - for fname in "sum len sorted reversed list tuple set any all min max".split(): - try: - singleArgBuiltins.append(getattr(__builtin__,fname)) - except AttributeError: - continue - -_generatorType = type((y for y in range(1))) - -def _xml_escape(data): - """Escape &, <, >, ", ', etc. in a string of data.""" - - # ampersand must be replaced first - from_symbols = '&><"\'' - to_symbols = ('&'+s+';' for s in "amp gt lt quot apos".split()) - for from_,to_ in zip(from_symbols, to_symbols): - data = data.replace(from_, to_) - return data - -class _Constants(object): - pass - -alphas = string.ascii_uppercase + string.ascii_lowercase -nums = "0123456789" -hexnums = nums + "ABCDEFabcdef" -alphanums = alphas + nums -_bslash = chr(92) -printables = "".join(c for c in string.printable if c not in string.whitespace) - -class ParseBaseException(Exception): - """base exception class for all parsing runtime exceptions""" - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, pstr, loc=0, msg=None, elem=None ): - self.loc = loc - if msg is None: - self.msg = pstr - self.pstr = "" - else: - self.msg = msg - self.pstr = pstr - self.parserElement = elem - self.args = (pstr, loc, msg) - - @classmethod - def _from_exception(cls, pe): - """ - internal factory method to simplify creating one type of ParseException - from another - avoids having __init__ signature conflicts among subclasses - """ - return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement) - - def __getattr__( self, aname ): - """supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - """ - if( aname == "lineno" ): - return lineno( self.loc, self.pstr ) - elif( aname in ("col", "column") ): - return col( self.loc, self.pstr ) - elif( aname == "line" ): - return line( self.loc, self.pstr ) - else: - raise AttributeError(aname) - - def __str__( self ): - return "%s (at char %d), (line:%d, col:%d)" % \ - ( self.msg, self.loc, self.lineno, self.column ) - def __repr__( self ): - return _ustr(self) - def markInputline( self, markerString = ">!<" ): - """Extracts the exception line from the input string, and marks - the location of the exception with a special symbol. - """ - line_str = self.line - line_column = self.column - 1 - if markerString: - line_str = "".join((line_str[:line_column], - markerString, line_str[line_column:])) - return line_str.strip() - def __dir__(self): - return "lineno col line".split() + dir(type(self)) - -class ParseException(ParseBaseException): - """ - Exception thrown when parse expressions don't match class; - supported attributes by name are: - - lineno - returns the line number of the exception text - - col - returns the column number of the exception text - - line - returns the line containing the exception text - - Example:: - try: - Word(nums).setName("integer").parseString("ABC") - except ParseException as pe: - print(pe) - print("column: {}".format(pe.col)) - - prints:: - Expected integer (at char 0), (line:1, col:1) - column: 1 - """ - pass - -class ParseFatalException(ParseBaseException): - """user-throwable exception thrown when inconsistent parse content - is found; stops all parsing immediately""" - pass - -class ParseSyntaxException(ParseFatalException): - """just like L{ParseFatalException}, but thrown internally when an - L{ErrorStop<And._ErrorStop>} ('-' operator) indicates that parsing is to stop - immediately because an unbacktrackable syntax error has been found""" - pass - -#~ class ReparseException(ParseBaseException): - #~ """Experimental class - parse actions can raise this exception to cause - #~ pyparsing to reparse the input string: - #~ - with a modified input string, and/or - #~ - with a modified start location - #~ Set the values of the ReparseException in the constructor, and raise the - #~ exception in a parse action to cause pyparsing to use the new string/location. - #~ Setting the values as None causes no change to be made. - #~ """ - #~ def __init_( self, newstring, restartLoc ): - #~ self.newParseText = newstring - #~ self.reparseLoc = restartLoc - -class RecursiveGrammarException(Exception): - """exception thrown by L{ParserElement.validate} if the grammar could be improperly recursive""" - def __init__( self, parseElementList ): - self.parseElementTrace = parseElementList - - def __str__( self ): - return "RecursiveGrammarException: %s" % self.parseElementTrace - -class _ParseResultsWithOffset(object): - def __init__(self,p1,p2): - self.tup = (p1,p2) - def __getitem__(self,i): - return self.tup[i] - def __repr__(self): - return repr(self.tup[0]) - def setOffset(self,i): - self.tup = (self.tup[0],i) - -class ParseResults(object): - """ - Structured parse results, to provide multiple means of access to the parsed data: - - as a list (C{len(results)}) - - by list index (C{results[0], results[1]}, etc.) - - by attribute (C{results.<resultsName>} - see L{ParserElement.setResultsName}) - - Example:: - integer = Word(nums) - date_str = (integer.setResultsName("year") + '/' - + integer.setResultsName("month") + '/' - + integer.setResultsName("day")) - # equivalent form: - # date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - # parseString returns a ParseResults object - result = date_str.parseString("1999/12/31") - - def test(s, fn=repr): - print("%s -> %s" % (s, fn(eval(s)))) - test("list(result)") - test("result[0]") - test("result['month']") - test("result.day") - test("'month' in result") - test("'minutes' in result") - test("result.dump()", str) - prints:: - list(result) -> ['1999', '/', '12', '/', '31'] - result[0] -> '1999' - result['month'] -> '12' - result.day -> '31' - 'month' in result -> True - 'minutes' in result -> False - result.dump() -> ['1999', '/', '12', '/', '31'] - - day: 31 - - month: 12 - - year: 1999 - """ - def __new__(cls, toklist=None, name=None, asList=True, modal=True ): - if isinstance(toklist, cls): - return toklist - retobj = object.__new__(cls) - retobj.__doinit = True - return retobj - - # Performance tuning: we construct a *lot* of these, so keep this - # constructor as small and fast as possible - def __init__( self, toklist=None, name=None, asList=True, modal=True, isinstance=isinstance ): - if self.__doinit: - self.__doinit = False - self.__name = None - self.__parent = None - self.__accumNames = {} - self.__asList = asList - self.__modal = modal - if toklist is None: - toklist = [] - if isinstance(toklist, list): - self.__toklist = toklist[:] - elif isinstance(toklist, _generatorType): - self.__toklist = list(toklist) - else: - self.__toklist = [toklist] - self.__tokdict = dict() - - if name is not None and name: - if not modal: - self.__accumNames[name] = 0 - if isinstance(name,int): - name = _ustr(name) # will always return a str, but use _ustr for consistency - self.__name = name - if not (isinstance(toklist, (type(None), basestring, list)) and toklist in (None,'',[])): - if isinstance(toklist,basestring): - toklist = [ toklist ] - if asList: - if isinstance(toklist,ParseResults): - self[name] = _ParseResultsWithOffset(toklist.copy(),0) - else: - self[name] = _ParseResultsWithOffset(ParseResults(toklist[0]),0) - self[name].__name = name - else: - try: - self[name] = toklist[0] - except (KeyError,TypeError,IndexError): - self[name] = toklist - - def __getitem__( self, i ): - if isinstance( i, (int,slice) ): - return self.__toklist[i] - else: - if i not in self.__accumNames: - return self.__tokdict[i][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[i] ]) - - def __setitem__( self, k, v, isinstance=isinstance ): - if isinstance(v,_ParseResultsWithOffset): - self.__tokdict[k] = self.__tokdict.get(k,list()) + [v] - sub = v[0] - elif isinstance(k,(int,slice)): - self.__toklist[k] = v - sub = v - else: - self.__tokdict[k] = self.__tokdict.get(k,list()) + [_ParseResultsWithOffset(v,0)] - sub = v - if isinstance(sub,ParseResults): - sub.__parent = wkref(self) - - def __delitem__( self, i ): - if isinstance(i,(int,slice)): - mylen = len( self.__toklist ) - del self.__toklist[i] - - # convert int to slice - if isinstance(i, int): - if i < 0: - i += mylen - i = slice(i, i+1) - # get removed indices - removed = list(range(*i.indices(mylen))) - removed.reverse() - # fixup indices in token dictionary - for name,occurrences in self.__tokdict.items(): - for j in removed: - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position - (position > j)) - else: - del self.__tokdict[i] - - def __contains__( self, k ): - return k in self.__tokdict - - def __len__( self ): return len( self.__toklist ) - def __bool__(self): return ( not not self.__toklist ) - __nonzero__ = __bool__ - def __iter__( self ): return iter( self.__toklist ) - def __reversed__( self ): return iter( self.__toklist[::-1] ) - def _iterkeys( self ): - if hasattr(self.__tokdict, "iterkeys"): - return self.__tokdict.iterkeys() - else: - return iter(self.__tokdict) - - def _itervalues( self ): - return (self[k] for k in self._iterkeys()) - - def _iteritems( self ): - return ((k, self[k]) for k in self._iterkeys()) - - if PY_3: - keys = _iterkeys - """Returns an iterator of all named result keys (Python 3.x only).""" - - values = _itervalues - """Returns an iterator of all named result values (Python 3.x only).""" - - items = _iteritems - """Returns an iterator of all named result key-value tuples (Python 3.x only).""" - - else: - iterkeys = _iterkeys - """Returns an iterator of all named result keys (Python 2.x only).""" - - itervalues = _itervalues - """Returns an iterator of all named result values (Python 2.x only).""" - - iteritems = _iteritems - """Returns an iterator of all named result key-value tuples (Python 2.x only).""" - - def keys( self ): - """Returns all named result keys (as a list in Python 2.x, as an iterator in Python 3.x).""" - return list(self.iterkeys()) - - def values( self ): - """Returns all named result values (as a list in Python 2.x, as an iterator in Python 3.x).""" - return list(self.itervalues()) - - def items( self ): - """Returns all named result key-values (as a list of tuples in Python 2.x, as an iterator in Python 3.x).""" - return list(self.iteritems()) - - def haskeys( self ): - """Since keys() returns an iterator, this method is helpful in bypassing - code that looks for the existence of any defined results names.""" - return bool(self.__tokdict) - - def pop( self, *args, **kwargs): - """ - Removes and returns item at specified index (default=C{last}). - Supports both C{list} and C{dict} semantics for C{pop()}. If passed no - argument or an integer argument, it will use C{list} semantics - and pop tokens from the list of parsed tokens. If passed a - non-integer argument (most likely a string), it will use C{dict} - semantics and pop the corresponding value from any defined - results names. A second default return value argument is - supported, just as in C{dict.pop()}. - - Example:: - def remove_first(tokens): - tokens.pop(0) - print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] - print(OneOrMore(Word(nums)).addParseAction(remove_first).parseString("0 123 321")) # -> ['123', '321'] - - label = Word(alphas) - patt = label("LABEL") + OneOrMore(Word(nums)) - print(patt.parseString("AAB 123 321").dump()) - - # Use pop() in a parse action to remove named result (note that corresponding value is not - # removed from list form of results) - def remove_LABEL(tokens): - tokens.pop("LABEL") - return tokens - patt.addParseAction(remove_LABEL) - print(patt.parseString("AAB 123 321").dump()) - prints:: - ['AAB', '123', '321'] - - LABEL: AAB - - ['AAB', '123', '321'] - """ - if not args: - args = [-1] - for k,v in kwargs.items(): - if k == 'default': - args = (args[0], v) - else: - raise TypeError("pop() got an unexpected keyword argument '%s'" % k) - if (isinstance(args[0], int) or - len(args) == 1 or - args[0] in self): - index = args[0] - ret = self[index] - del self[index] - return ret - else: - defaultvalue = args[1] - return defaultvalue - - def get(self, key, defaultValue=None): - """ - Returns named result matching the given key, or if there is no - such name, then returns the given C{defaultValue} or C{None} if no - C{defaultValue} is specified. - - Similar to C{dict.get()}. - - Example:: - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString("1999/12/31") - print(result.get("year")) # -> '1999' - print(result.get("hour", "not specified")) # -> 'not specified' - print(result.get("hour")) # -> None - """ - if key in self: - return self[key] - else: - return defaultValue - - def insert( self, index, insStr ): - """ - Inserts new element at location index in the list of parsed tokens. - - Similar to C{list.insert()}. - - Example:: - print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] - - # use a parse action to insert the parse location in the front of the parsed results - def insert_locn(locn, tokens): - tokens.insert(0, locn) - print(OneOrMore(Word(nums)).addParseAction(insert_locn).parseString("0 123 321")) # -> [0, '0', '123', '321'] - """ - self.__toklist.insert(index, insStr) - # fixup indices in token dictionary - for name,occurrences in self.__tokdict.items(): - for k, (value, position) in enumerate(occurrences): - occurrences[k] = _ParseResultsWithOffset(value, position + (position > index)) - - def append( self, item ): - """ - Add single element to end of ParseResults list of elements. - - Example:: - print(OneOrMore(Word(nums)).parseString("0 123 321")) # -> ['0', '123', '321'] - - # use a parse action to compute the sum of the parsed integers, and add it to the end - def append_sum(tokens): - tokens.append(sum(map(int, tokens))) - print(OneOrMore(Word(nums)).addParseAction(append_sum).parseString("0 123 321")) # -> ['0', '123', '321', 444] - """ - self.__toklist.append(item) - - def extend( self, itemseq ): - """ - Add sequence of elements to end of ParseResults list of elements. - - Example:: - patt = OneOrMore(Word(alphas)) - - # use a parse action to append the reverse of the matched strings, to make a palindrome - def make_palindrome(tokens): - tokens.extend(reversed([t[::-1] for t in tokens])) - return ''.join(tokens) - print(patt.addParseAction(make_palindrome).parseString("lskdj sdlkjf lksd")) # -> 'lskdjsdlkjflksddsklfjkldsjdksl' - """ - if isinstance(itemseq, ParseResults): - self += itemseq - else: - self.__toklist.extend(itemseq) - - def clear( self ): - """ - Clear all elements and results names. - """ - del self.__toklist[:] - self.__tokdict.clear() - - def __getattr__( self, name ): - try: - return self[name] - except KeyError: - return "" - - if name in self.__tokdict: - if name not in self.__accumNames: - return self.__tokdict[name][-1][0] - else: - return ParseResults([ v[0] for v in self.__tokdict[name] ]) - else: - return "" - - def __add__( self, other ): - ret = self.copy() - ret += other - return ret - - def __iadd__( self, other ): - if other.__tokdict: - offset = len(self.__toklist) - addoffset = lambda a: offset if a<0 else a+offset - otheritems = other.__tokdict.items() - otherdictitems = [(k, _ParseResultsWithOffset(v[0],addoffset(v[1])) ) - for (k,vlist) in otheritems for v in vlist] - for k,v in otherdictitems: - self[k] = v - if isinstance(v[0],ParseResults): - v[0].__parent = wkref(self) - - self.__toklist += other.__toklist - self.__accumNames.update( other.__accumNames ) - return self - - def __radd__(self, other): - if isinstance(other,int) and other == 0: - # useful for merging many ParseResults using sum() builtin - return self.copy() - else: - # this may raise a TypeError - so be it - return other + self - - def __repr__( self ): - return "(%s, %s)" % ( repr( self.__toklist ), repr( self.__tokdict ) ) - - def __str__( self ): - return '[' + ', '.join(_ustr(i) if isinstance(i, ParseResults) else repr(i) for i in self.__toklist) + ']' - - def _asStringList( self, sep='' ): - out = [] - for item in self.__toklist: - if out and sep: - out.append(sep) - if isinstance( item, ParseResults ): - out += item._asStringList() - else: - out.append( _ustr(item) ) - return out - - def asList( self ): - """ - Returns the parse results as a nested list of matching tokens, all converted to strings. - - Example:: - patt = OneOrMore(Word(alphas)) - result = patt.parseString("sldkj lsdkj sldkj") - # even though the result prints in string-like form, it is actually a pyparsing ParseResults - print(type(result), result) # -> <class 'pyparsing.ParseResults'> ['sldkj', 'lsdkj', 'sldkj'] - - # Use asList() to create an actual list - result_list = result.asList() - print(type(result_list), result_list) # -> <class 'list'> ['sldkj', 'lsdkj', 'sldkj'] - """ - return [res.asList() if isinstance(res,ParseResults) else res for res in self.__toklist] - - def asDict( self ): - """ - Returns the named parse results as a nested dictionary. - - Example:: - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString('12/31/1999') - print(type(result), repr(result)) # -> <class 'pyparsing.ParseResults'> (['12', '/', '31', '/', '1999'], {'day': [('1999', 4)], 'year': [('12', 0)], 'month': [('31', 2)]}) - - result_dict = result.asDict() - print(type(result_dict), repr(result_dict)) # -> <class 'dict'> {'day': '1999', 'year': '12', 'month': '31'} - - # even though a ParseResults supports dict-like access, sometime you just need to have a dict - import json - print(json.dumps(result)) # -> Exception: TypeError: ... is not JSON serializable - print(json.dumps(result.asDict())) # -> {"month": "31", "day": "1999", "year": "12"} - """ - if PY_3: - item_fn = self.items - else: - item_fn = self.iteritems - - def toItem(obj): - if isinstance(obj, ParseResults): - if obj.haskeys(): - return obj.asDict() - else: - return [toItem(v) for v in obj] - else: - return obj - - return dict((k,toItem(v)) for k,v in item_fn()) - - def copy( self ): - """ - Returns a new copy of a C{ParseResults} object. - """ - ret = ParseResults( self.__toklist ) - ret.__tokdict = self.__tokdict.copy() - ret.__parent = self.__parent - ret.__accumNames.update( self.__accumNames ) - ret.__name = self.__name - return ret - - def asXML( self, doctag=None, namedItemsOnly=False, indent="", formatted=True ): - """ - (Deprecated) Returns the parse results as XML. Tags are created for tokens and lists that have defined results names. - """ - nl = "\n" - out = [] - namedItems = dict((v[1],k) for (k,vlist) in self.__tokdict.items() - for v in vlist) - nextLevelIndent = indent + " " - - # collapse out indents if formatting is not desired - if not formatted: - indent = "" - nextLevelIndent = "" - nl = "" - - selfTag = None - if doctag is not None: - selfTag = doctag - else: - if self.__name: - selfTag = self.__name - - if not selfTag: - if namedItemsOnly: - return "" - else: - selfTag = "ITEM" - - out += [ nl, indent, "<", selfTag, ">" ] - - for i,res in enumerate(self.__toklist): - if isinstance(res,ParseResults): - if i in namedItems: - out += [ res.asXML(namedItems[i], - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - out += [ res.asXML(None, - namedItemsOnly and doctag is None, - nextLevelIndent, - formatted)] - else: - # individual token, see if there is a name for it - resTag = None - if i in namedItems: - resTag = namedItems[i] - if not resTag: - if namedItemsOnly: - continue - else: - resTag = "ITEM" - xmlBodyText = _xml_escape(_ustr(res)) - out += [ nl, nextLevelIndent, "<", resTag, ">", - xmlBodyText, - "</", resTag, ">" ] - - out += [ nl, indent, "</", selfTag, ">" ] - return "".join(out) - - def __lookup(self,sub): - for k,vlist in self.__tokdict.items(): - for v,loc in vlist: - if sub is v: - return k - return None - - def getName(self): - r""" - Returns the results name for this token expression. Useful when several - different expressions might match at a particular location. - - Example:: - integer = Word(nums) - ssn_expr = Regex(r"\d\d\d-\d\d-\d\d\d\d") - house_number_expr = Suppress('#') + Word(nums, alphanums) - user_data = (Group(house_number_expr)("house_number") - | Group(ssn_expr)("ssn") - | Group(integer)("age")) - user_info = OneOrMore(user_data) - - result = user_info.parseString("22 111-22-3333 #221B") - for item in result: - print(item.getName(), ':', item[0]) - prints:: - age : 22 - ssn : 111-22-3333 - house_number : 221B - """ - if self.__name: - return self.__name - elif self.__parent: - par = self.__parent() - if par: - return par.__lookup(self) - else: - return None - elif (len(self) == 1 and - len(self.__tokdict) == 1 and - next(iter(self.__tokdict.values()))[0][1] in (0,-1)): - return next(iter(self.__tokdict.keys())) - else: - return None - - def dump(self, indent='', depth=0, full=True): - """ - Diagnostic method for listing out the contents of a C{ParseResults}. - Accepts an optional C{indent} argument so that this string can be embedded - in a nested display of other data. - - Example:: - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - result = date_str.parseString('12/31/1999') - print(result.dump()) - prints:: - ['12', '/', '31', '/', '1999'] - - day: 1999 - - month: 31 - - year: 12 - """ - out = [] - NL = '\n' - out.append( indent+_ustr(self.asList()) ) - if full: - if self.haskeys(): - items = sorted((str(k), v) for k,v in self.items()) - for k,v in items: - if out: - out.append(NL) - out.append( "%s%s- %s: " % (indent,(' '*depth), k) ) - if isinstance(v,ParseResults): - if v: - out.append( v.dump(indent,depth+1) ) - else: - out.append(_ustr(v)) - else: - out.append(repr(v)) - elif any(isinstance(vv,ParseResults) for vv in self): - v = self - for i,vv in enumerate(v): - if isinstance(vv,ParseResults): - out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),vv.dump(indent,depth+1) )) - else: - out.append("\n%s%s[%d]:\n%s%s%s" % (indent,(' '*(depth)),i,indent,(' '*(depth+1)),_ustr(vv))) - - return "".join(out) - - def pprint(self, *args, **kwargs): - """ - Pretty-printer for parsed results as a list, using the C{pprint} module. - Accepts additional positional or keyword args as defined for the - C{pprint.pprint} method. (U{http://docs.python.org/3/library/pprint.html#pprint.pprint}) - - Example:: - ident = Word(alphas, alphanums) - num = Word(nums) - func = Forward() - term = ident | num | Group('(' + func + ')') - func <<= ident + Group(Optional(delimitedList(term))) - result = func.parseString("fna a,b,(fnb c,d,200),100") - result.pprint(width=40) - prints:: - ['fna', - ['a', - 'b', - ['(', 'fnb', ['c', 'd', '200'], ')'], - '100']] - """ - pprint.pprint(self.asList(), *args, **kwargs) - - # add support for pickle protocol - def __getstate__(self): - return ( self.__toklist, - ( self.__tokdict.copy(), - self.__parent is not None and self.__parent() or None, - self.__accumNames, - self.__name ) ) - - def __setstate__(self,state): - self.__toklist = state[0] - (self.__tokdict, - par, - inAccumNames, - self.__name) = state[1] - self.__accumNames = {} - self.__accumNames.update(inAccumNames) - if par is not None: - self.__parent = wkref(par) - else: - self.__parent = None - - def __getnewargs__(self): - return self.__toklist, self.__name, self.__asList, self.__modal - - def __dir__(self): - return (dir(type(self)) + list(self.keys())) - -MutableMapping.register(ParseResults) - -def col (loc,strg): - """Returns current column within a string, counting newlines as line separators. - The first column is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - s = strg - return 1 if 0<loc<len(s) and s[loc-1] == '\n' else loc - s.rfind("\n", 0, loc) - -def lineno(loc,strg): - """Returns current line number within a string, counting newlines as line separators. - The first line is number 1. - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{ParserElement.parseString}<ParserElement.parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - """ - return strg.count("\n",0,loc) + 1 - -def line( loc, strg ): - """Returns the line of text containing loc within a string, counting newlines as line separators. - """ - lastCR = strg.rfind("\n", 0, loc) - nextCR = strg.find("\n", loc) - if nextCR >= 0: - return strg[lastCR+1:nextCR] - else: - return strg[lastCR+1:] - -def _defaultStartDebugAction( instring, loc, expr ): - print (("Match " + _ustr(expr) + " at loc " + _ustr(loc) + "(%d,%d)" % ( lineno(loc,instring), col(loc,instring) ))) - -def _defaultSuccessDebugAction( instring, startloc, endloc, expr, toks ): - print ("Matched " + _ustr(expr) + " -> " + str(toks.asList())) - -def _defaultExceptionDebugAction( instring, loc, expr, exc ): - print ("Exception raised:" + _ustr(exc)) - -def nullDebugAction(*args): - """'Do-nothing' debug action, to suppress debugging output during parsing.""" - pass - -# Only works on Python 3.x - nonlocal is toxic to Python 2 installs -#~ 'decorator to trim function calls to match the arity of the target' -#~ def _trim_arity(func, maxargs=3): - #~ if func in singleArgBuiltins: - #~ return lambda s,l,t: func(t) - #~ limit = 0 - #~ foundArity = False - #~ def wrapper(*args): - #~ nonlocal limit,foundArity - #~ while 1: - #~ try: - #~ ret = func(*args[limit:]) - #~ foundArity = True - #~ return ret - #~ except TypeError: - #~ if limit == maxargs or foundArity: - #~ raise - #~ limit += 1 - #~ continue - #~ return wrapper - -# this version is Python 2.x-3.x cross-compatible -'decorator to trim function calls to match the arity of the target' -def _trim_arity(func, maxargs=2): - if func in singleArgBuiltins: - return lambda s,l,t: func(t) - limit = [0] - foundArity = [False] - - # traceback return data structure changed in Py3.5 - normalize back to plain tuples - if system_version[:2] >= (3,5): - def extract_stack(limit=0): - # special handling for Python 3.5.0 - extra deep call stack by 1 - offset = -3 if system_version == (3,5,0) else -2 - frame_summary = traceback.extract_stack(limit=-offset+limit-1)[offset] - return [frame_summary[:2]] - def extract_tb(tb, limit=0): - frames = traceback.extract_tb(tb, limit=limit) - frame_summary = frames[-1] - return [frame_summary[:2]] - else: - extract_stack = traceback.extract_stack - extract_tb = traceback.extract_tb - - # synthesize what would be returned by traceback.extract_stack at the call to - # user's parse action 'func', so that we don't incur call penalty at parse time - - LINE_DIFF = 6 - # IF ANY CODE CHANGES, EVEN JUST COMMENTS OR BLANK LINES, BETWEEN THE NEXT LINE AND - # THE CALL TO FUNC INSIDE WRAPPER, LINE_DIFF MUST BE MODIFIED!!!! - this_line = extract_stack(limit=2)[-1] - pa_call_line_synth = (this_line[0], this_line[1]+LINE_DIFF) - - def wrapper(*args): - while 1: - try: - ret = func(*args[limit[0]:]) - foundArity[0] = True - return ret - except TypeError: - # re-raise TypeErrors if they did not come from our arity testing - if foundArity[0]: - raise - else: - try: - tb = sys.exc_info()[-1] - if not extract_tb(tb, limit=2)[-1][:2] == pa_call_line_synth: - raise - finally: - del tb - - if limit[0] <= maxargs: - limit[0] += 1 - continue - raise - - # copy func name to wrapper for sensible debug output - func_name = "<parse action>" - try: - func_name = getattr(func, '__name__', - getattr(func, '__class__').__name__) - except Exception: - func_name = str(func) - wrapper.__name__ = func_name - - return wrapper - -class ParserElement(object): - """Abstract base level parser element class.""" - DEFAULT_WHITE_CHARS = " \n\t\r" - verbose_stacktrace = False - - @staticmethod - def setDefaultWhitespaceChars( chars ): - r""" - Overrides the default whitespace chars - - Example:: - # default whitespace chars are space, <TAB> and newline - OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def', 'ghi', 'jkl'] - - # change to just treat newline as significant - ParserElement.setDefaultWhitespaceChars(" \t") - OneOrMore(Word(alphas)).parseString("abc def\nghi jkl") # -> ['abc', 'def'] - """ - ParserElement.DEFAULT_WHITE_CHARS = chars - - @staticmethod - def inlineLiteralsUsing(cls): - """ - Set class to be used for inclusion of string literals into a parser. - - Example:: - # default literal class used is Literal - integer = Word(nums) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31'] - - - # change to Suppress - ParserElement.inlineLiteralsUsing(Suppress) - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - - date_str.parseString("1999/12/31") # -> ['1999', '12', '31'] - """ - ParserElement._literalStringClass = cls - - def __init__( self, savelist=False ): - self.parseAction = list() - self.failAction = None - #~ self.name = "<unknown>" # don't define self.name, let subclasses try/except upcall - self.strRepr = None - self.resultsName = None - self.saveAsList = savelist - self.skipWhitespace = True - self.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - self.copyDefaultWhiteChars = True - self.mayReturnEmpty = False # used when checking for left-recursion - self.keepTabs = False - self.ignoreExprs = list() - self.debug = False - self.streamlined = False - self.mayIndexError = True # used to optimize exception handling for subclasses that don't advance parse index - self.errmsg = "" - self.modalResults = True # used to mark results names as modal (report only last) or cumulative (list all) - self.debugActions = ( None, None, None ) #custom debug actions - self.re = None - self.callPreparse = True # used to avoid redundant calls to preParse - self.callDuringTry = False - - def copy( self ): - """ - Make a copy of this C{ParserElement}. Useful for defining different parse actions - for the same parsing pattern, using copies of the original parse element. - - Example:: - integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - integerK = integer.copy().addParseAction(lambda toks: toks[0]*1024) + Suppress("K") - integerM = integer.copy().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") - - print(OneOrMore(integerK | integerM | integer).parseString("5K 100 640K 256M")) - prints:: - [5120, 100, 655360, 268435456] - Equivalent form of C{expr.copy()} is just C{expr()}:: - integerM = integer().addParseAction(lambda toks: toks[0]*1024*1024) + Suppress("M") - """ - cpy = copy.copy( self ) - cpy.parseAction = self.parseAction[:] - cpy.ignoreExprs = self.ignoreExprs[:] - if self.copyDefaultWhiteChars: - cpy.whiteChars = ParserElement.DEFAULT_WHITE_CHARS - return cpy - - def setName( self, name ): - """ - Define name for this expression, makes debugging and exception messages clearer. - - Example:: - Word(nums).parseString("ABC") # -> Exception: Expected W:(0123...) (at char 0), (line:1, col:1) - Word(nums).setName("integer").parseString("ABC") # -> Exception: Expected integer (at char 0), (line:1, col:1) - """ - self.name = name - self.errmsg = "Expected " + self.name - if hasattr(self,"exception"): - self.exception.msg = self.errmsg - return self - - def setResultsName( self, name, listAllMatches=False ): - """ - Define name for referencing matching tokens as a nested attribute - of the returned parse results. - NOTE: this returns a *copy* of the original C{ParserElement} object; - this is so that the client can define a basic element, such as an - integer, and reference it in multiple places with different names. - - You can also set results names using the abbreviated syntax, - C{expr("name")} in place of C{expr.setResultsName("name")} - - see L{I{__call__}<__call__>}. - - Example:: - date_str = (integer.setResultsName("year") + '/' - + integer.setResultsName("month") + '/' - + integer.setResultsName("day")) - - # equivalent form: - date_str = integer("year") + '/' + integer("month") + '/' + integer("day") - """ - newself = self.copy() - if name.endswith("*"): - name = name[:-1] - listAllMatches=True - newself.resultsName = name - newself.modalResults = not listAllMatches - return newself - - def setBreak(self,breakFlag = True): - """Method to invoke the Python pdb debugger when this element is - about to be parsed. Set C{breakFlag} to True to enable, False to - disable. - """ - if breakFlag: - _parseMethod = self._parse - def breaker(instring, loc, doActions=True, callPreParse=True): - import pdb - pdb.set_trace() - return _parseMethod( instring, loc, doActions, callPreParse ) - breaker._originalParseMethod = _parseMethod - self._parse = breaker - else: - if hasattr(self._parse,"_originalParseMethod"): - self._parse = self._parse._originalParseMethod - return self - - def setParseAction( self, *fns, **kwargs ): - """ - Define one or more actions to perform when successfully matching parse element definition. - Parse action fn is a callable method with 0-3 arguments, called as C{fn(s,loc,toks)}, - C{fn(loc,toks)}, C{fn(toks)}, or just C{fn()}, where: - - s = the original string being parsed (see note below) - - loc = the location of the matching substring - - toks = a list of the matched tokens, packaged as a C{L{ParseResults}} object - If the functions in fns modify the tokens, they can return them as the return - value from fn, and the modified list of tokens will replace the original. - Otherwise, fn does not need to return any value. - - Optional keyword arguments: - - callDuringTry = (default=C{False}) indicate if parse action should be run during lookaheads and alternate testing - - Note: the default parsing behavior is to expand tabs in the input string - before starting the parsing process. See L{I{parseString}<parseString>} for more information - on parsing strings containing C{<TAB>}s, and suggested methods to maintain a - consistent view of the parsed string, the parse location, and line and column - positions within the parsed string. - - Example:: - integer = Word(nums) - date_str = integer + '/' + integer + '/' + integer - - date_str.parseString("1999/12/31") # -> ['1999', '/', '12', '/', '31'] - - # use parse action to convert to ints at parse time - integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - date_str = integer + '/' + integer + '/' + integer - - # note that integer fields are now ints, not strings - date_str.parseString("1999/12/31") # -> [1999, '/', 12, '/', 31] - """ - self.parseAction = list(map(_trim_arity, list(fns))) - self.callDuringTry = kwargs.get("callDuringTry", False) - return self - - def addParseAction( self, *fns, **kwargs ): - """ - Add one or more parse actions to expression's list of parse actions. See L{I{setParseAction}<setParseAction>}. - - See examples in L{I{copy}<copy>}. - """ - self.parseAction += list(map(_trim_arity, list(fns))) - self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) - return self - - def addCondition(self, *fns, **kwargs): - """Add a boolean predicate function to expression's list of parse actions. See - L{I{setParseAction}<setParseAction>} for function call signatures. Unlike C{setParseAction}, - functions passed to C{addCondition} need to return boolean success/fail of the condition. - - Optional keyword arguments: - - message = define a custom message to be used in the raised exception - - fatal = if True, will raise ParseFatalException to stop parsing immediately; otherwise will raise ParseException - - Example:: - integer = Word(nums).setParseAction(lambda toks: int(toks[0])) - year_int = integer.copy() - year_int.addCondition(lambda toks: toks[0] >= 2000, message="Only support years 2000 and later") - date_str = year_int + '/' + integer + '/' + integer - - result = date_str.parseString("1999/12/31") # -> Exception: Only support years 2000 and later (at char 0), (line:1, col:1) - """ - msg = kwargs.get("message", "failed user-defined condition") - exc_type = ParseFatalException if kwargs.get("fatal", False) else ParseException - for fn in fns: - def pa(s,l,t): - if not bool(_trim_arity(fn)(s,l,t)): - raise exc_type(s,l,msg) - self.parseAction.append(pa) - self.callDuringTry = self.callDuringTry or kwargs.get("callDuringTry", False) - return self - - def setFailAction( self, fn ): - """Define action to perform if parsing fails at this expression. - Fail acton fn is a callable function that takes the arguments - C{fn(s,loc,expr,err)} where: - - s = string being parsed - - loc = location where expression match was attempted and failed - - expr = the parse expression that failed - - err = the exception thrown - The function returns no value. It may throw C{L{ParseFatalException}} - if it is desired to stop parsing immediately.""" - self.failAction = fn - return self - - def _skipIgnorables( self, instring, loc ): - exprsFound = True - while exprsFound: - exprsFound = False - for e in self.ignoreExprs: - try: - while 1: - loc,dummy = e._parse( instring, loc ) - exprsFound = True - except ParseException: - pass - return loc - - def preParse( self, instring, loc ): - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - - if self.skipWhitespace: - wt = self.whiteChars - instrlen = len(instring) - while loc < instrlen and instring[loc] in wt: - loc += 1 - - return loc - - def parseImpl( self, instring, loc, doActions=True ): - return loc, [] - - def postParse( self, instring, loc, tokenlist ): - return tokenlist - - #~ @profile - def _parseNoCache( self, instring, loc, doActions=True, callPreParse=True ): - debugging = ( self.debug ) #and doActions ) - - if debugging or self.failAction: - #~ print ("Match",self,"at loc",loc,"(%d,%d)" % ( lineno(loc,instring), col(loc,instring) )) - if (self.debugActions[0] ): - self.debugActions[0]( instring, loc, self ) - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - try: - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - except ParseBaseException as err: - #~ print ("Exception raised:", err) - if self.debugActions[2]: - self.debugActions[2]( instring, tokensStart, self, err ) - if self.failAction: - self.failAction( instring, tokensStart, self, err ) - raise - else: - if callPreParse and self.callPreparse: - preloc = self.preParse( instring, loc ) - else: - preloc = loc - tokensStart = preloc - if self.mayIndexError or preloc >= len(instring): - try: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - except IndexError: - raise ParseException( instring, len(instring), self.errmsg, self ) - else: - loc,tokens = self.parseImpl( instring, preloc, doActions ) - - tokens = self.postParse( instring, loc, tokens ) - - retTokens = ParseResults( tokens, self.resultsName, asList=self.saveAsList, modal=self.modalResults ) - if self.parseAction and (doActions or self.callDuringTry): - if debugging: - try: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - except ParseBaseException as err: - #~ print "Exception raised in user parse action:", err - if (self.debugActions[2] ): - self.debugActions[2]( instring, tokensStart, self, err ) - raise - else: - for fn in self.parseAction: - tokens = fn( instring, tokensStart, retTokens ) - if tokens is not None: - retTokens = ParseResults( tokens, - self.resultsName, - asList=self.saveAsList and isinstance(tokens,(ParseResults,list)), - modal=self.modalResults ) - if debugging: - #~ print ("Matched",self,"->",retTokens.asList()) - if (self.debugActions[1] ): - self.debugActions[1]( instring, tokensStart, loc, self, retTokens ) - - return loc, retTokens - - def tryParse( self, instring, loc ): - try: - return self._parse( instring, loc, doActions=False )[0] - except ParseFatalException: - raise ParseException( instring, loc, self.errmsg, self) - - def canParseNext(self, instring, loc): - try: - self.tryParse(instring, loc) - except (ParseException, IndexError): - return False - else: - return True - - class _UnboundedCache(object): - def __init__(self): - cache = {} - self.not_in_cache = not_in_cache = object() - - def get(self, key): - return cache.get(key, not_in_cache) - - def set(self, key, value): - cache[key] = value - - def clear(self): - cache.clear() - - def cache_len(self): - return len(cache) - - self.get = types.MethodType(get, self) - self.set = types.MethodType(set, self) - self.clear = types.MethodType(clear, self) - self.__len__ = types.MethodType(cache_len, self) - - if _OrderedDict is not None: - class _FifoCache(object): - def __init__(self, size): - self.not_in_cache = not_in_cache = object() - - cache = _OrderedDict() - - def get(self, key): - return cache.get(key, not_in_cache) - - def set(self, key, value): - cache[key] = value - while len(cache) > size: - try: - cache.popitem(False) - except KeyError: - pass - - def clear(self): - cache.clear() - - def cache_len(self): - return len(cache) - - self.get = types.MethodType(get, self) - self.set = types.MethodType(set, self) - self.clear = types.MethodType(clear, self) - self.__len__ = types.MethodType(cache_len, self) - - else: - class _FifoCache(object): - def __init__(self, size): - self.not_in_cache = not_in_cache = object() - - cache = {} - key_fifo = collections.deque([], size) - - def get(self, key): - return cache.get(key, not_in_cache) - - def set(self, key, value): - cache[key] = value - while len(key_fifo) > size: - cache.pop(key_fifo.popleft(), None) - key_fifo.append(key) - - def clear(self): - cache.clear() - key_fifo.clear() - - def cache_len(self): - return len(cache) - - self.get = types.MethodType(get, self) - self.set = types.MethodType(set, self) - self.clear = types.MethodType(clear, self) - self.__len__ = types.MethodType(cache_len, self) - - # argument cache for optimizing repeated calls when backtracking through recursive expressions - packrat_cache = {} # this is set later by enabledPackrat(); this is here so that resetCache() doesn't fail - packrat_cache_lock = RLock() - packrat_cache_stats = [0, 0] - - # this method gets repeatedly called during backtracking with the same arguments - - # we can cache these arguments and save ourselves the trouble of re-parsing the contained expression - def _parseCache( self, instring, loc, doActions=True, callPreParse=True ): - HIT, MISS = 0, 1 - lookup = (self, instring, loc, callPreParse, doActions) - with ParserElement.packrat_cache_lock: - cache = ParserElement.packrat_cache - value = cache.get(lookup) - if value is cache.not_in_cache: - ParserElement.packrat_cache_stats[MISS] += 1 - try: - value = self._parseNoCache(instring, loc, doActions, callPreParse) - except ParseBaseException as pe: - # cache a copy of the exception, without the traceback - cache.set(lookup, pe.__class__(*pe.args)) - raise - else: - cache.set(lookup, (value[0], value[1].copy())) - return value - else: - ParserElement.packrat_cache_stats[HIT] += 1 - if isinstance(value, Exception): - raise value - return (value[0], value[1].copy()) - - _parse = _parseNoCache - - @staticmethod - def resetCache(): - ParserElement.packrat_cache.clear() - ParserElement.packrat_cache_stats[:] = [0] * len(ParserElement.packrat_cache_stats) - - _packratEnabled = False - @staticmethod - def enablePackrat(cache_size_limit=128): - """Enables "packrat" parsing, which adds memoizing to the parsing logic. - Repeated parse attempts at the same string location (which happens - often in many complex grammars) can immediately return a cached value, - instead of re-executing parsing/validating code. Memoizing is done of - both valid results and parsing exceptions. - - Parameters: - - cache_size_limit - (default=C{128}) - if an integer value is provided - will limit the size of the packrat cache; if None is passed, then - the cache size will be unbounded; if 0 is passed, the cache will - be effectively disabled. - - This speedup may break existing programs that use parse actions that - have side-effects. For this reason, packrat parsing is disabled when - you first import pyparsing. To activate the packrat feature, your - program must call the class method C{ParserElement.enablePackrat()}. If - your program uses C{psyco} to "compile as you go", you must call - C{enablePackrat} before calling C{psyco.full()}. If you do not do this, - Python will crash. For best results, call C{enablePackrat()} immediately - after importing pyparsing. - - Example:: - import pyparsing - pyparsing.ParserElement.enablePackrat() - """ - if not ParserElement._packratEnabled: - ParserElement._packratEnabled = True - if cache_size_limit is None: - ParserElement.packrat_cache = ParserElement._UnboundedCache() - else: - ParserElement.packrat_cache = ParserElement._FifoCache(cache_size_limit) - ParserElement._parse = ParserElement._parseCache - - def parseString( self, instring, parseAll=False ): - """ - Execute the parse expression with the given string. - This is the main interface to the client code, once the complete - expression has been built. - - If you want the grammar to require that the entire input string be - successfully parsed, then set C{parseAll} to True (equivalent to ending - the grammar with C{L{StringEnd()}}). - - Note: C{parseString} implicitly calls C{expandtabs()} on the input string, - in order to report proper column numbers in parse actions. - If the input string contains tabs and - the grammar uses parse actions that use the C{loc} argument to index into the - string being parsed, you can ensure you have a consistent view of the input - string by: - - calling C{parseWithTabs} on your grammar before calling C{parseString} - (see L{I{parseWithTabs}<parseWithTabs>}) - - define your parse action using the full C{(s,loc,toks)} signature, and - reference the input string using the parse action's C{s} argument - - explictly expand the tabs in your input string before calling - C{parseString} - - Example:: - Word('a').parseString('aaaaabaaa') # -> ['aaaaa'] - Word('a').parseString('aaaaabaaa', parseAll=True) # -> Exception: Expected end of text - """ - ParserElement.resetCache() - if not self.streamlined: - self.streamline() - #~ self.saveAsList = True - for e in self.ignoreExprs: - e.streamline() - if not self.keepTabs: - instring = instring.expandtabs() - try: - loc, tokens = self._parse( instring, 0 ) - if parseAll: - loc = self.preParse( instring, loc ) - se = Empty() + StringEnd() - se._parse( instring, loc ) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - else: - return tokens - - def scanString( self, instring, maxMatches=_MAX_INT, overlap=False ): - """ - Scan the input string for expression matches. Each match will return the - matching tokens, start location, and end location. May be called with optional - C{maxMatches} argument, to clip scanning after 'n' matches are found. If - C{overlap} is specified, then overlapping matches will be reported. - - Note that the start and end locations are reported relative to the string - being parsed. See L{I{parseString}<parseString>} for more information on parsing - strings with embedded tabs. - - Example:: - source = "sldjf123lsdjjkf345sldkjf879lkjsfd987" - print(source) - for tokens,start,end in Word(alphas).scanString(source): - print(' '*start + '^'*(end-start)) - print(' '*start + tokens[0]) - - prints:: - - sldjf123lsdjjkf345sldkjf879lkjsfd987 - ^^^^^ - sldjf - ^^^^^^^ - lsdjjkf - ^^^^^^ - sldkjf - ^^^^^^ - lkjsfd - """ - if not self.streamlined: - self.streamline() - for e in self.ignoreExprs: - e.streamline() - - if not self.keepTabs: - instring = _ustr(instring).expandtabs() - instrlen = len(instring) - loc = 0 - preparseFn = self.preParse - parseFn = self._parse - ParserElement.resetCache() - matches = 0 - try: - while loc <= instrlen and matches < maxMatches: - try: - preloc = preparseFn( instring, loc ) - nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) - except ParseException: - loc = preloc+1 - else: - if nextLoc > loc: - matches += 1 - yield tokens, preloc, nextLoc - if overlap: - nextloc = preparseFn( instring, loc ) - if nextloc > loc: - loc = nextLoc - else: - loc += 1 - else: - loc = nextLoc - else: - loc = preloc+1 - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def transformString( self, instring ): - """ - Extension to C{L{scanString}}, to modify matching text with modified tokens that may - be returned from a parse action. To use C{transformString}, define a grammar and - attach a parse action to it that modifies the returned token list. - Invoking C{transformString()} on a target string will then scan for matches, - and replace the matched text patterns according to the logic in the parse - action. C{transformString()} returns the resulting transformed string. - - Example:: - wd = Word(alphas) - wd.setParseAction(lambda toks: toks[0].title()) - - print(wd.transformString("now is the winter of our discontent made glorious summer by this sun of york.")) - Prints:: - Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York. - """ - out = [] - lastE = 0 - # force preservation of <TAB>s, to minimize unwanted transformation of string, and to - # keep string locs straight between transformString and scanString - self.keepTabs = True - try: - for t,s,e in self.scanString( instring ): - out.append( instring[lastE:s] ) - if t: - if isinstance(t,ParseResults): - out += t.asList() - elif isinstance(t,list): - out += t - else: - out.append(t) - lastE = e - out.append(instring[lastE:]) - out = [o for o in out if o] - return "".join(map(_ustr,_flatten(out))) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def searchString( self, instring, maxMatches=_MAX_INT ): - """ - Another extension to C{L{scanString}}, simplifying the access to the tokens found - to match the given parse expression. May be called with optional - C{maxMatches} argument, to clip searching after 'n' matches are found. - - Example:: - # a capitalized word starts with an uppercase letter, followed by zero or more lowercase letters - cap_word = Word(alphas.upper(), alphas.lower()) - - print(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity")) - - # the sum() builtin can be used to merge results into a single ParseResults object - print(sum(cap_word.searchString("More than Iron, more than Lead, more than Gold I need Electricity"))) - prints:: - [['More'], ['Iron'], ['Lead'], ['Gold'], ['I'], ['Electricity']] - ['More', 'Iron', 'Lead', 'Gold', 'I', 'Electricity'] - """ - try: - return ParseResults([ t for t,s,e in self.scanString( instring, maxMatches ) ]) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False): - """ - Generator method to split a string using the given expression as a separator. - May be called with optional C{maxsplit} argument, to limit the number of splits; - and the optional C{includeSeparators} argument (default=C{False}), if the separating - matching text should be included in the split results. - - Example:: - punc = oneOf(list(".,;:/-!?")) - print(list(punc.split("This, this?, this sentence, is badly punctuated!"))) - prints:: - ['This', ' this', '', ' this sentence', ' is badly punctuated', ''] - """ - splits = 0 - last = 0 - for t,s,e in self.scanString(instring, maxMatches=maxsplit): - yield instring[last:s] - if includeSeparators: - yield t[0] - last = e - yield instring[last:] - - def __add__(self, other ): - """ - Implementation of + operator - returns C{L{And}}. Adding strings to a ParserElement - converts them to L{Literal}s by default. - - Example:: - greet = Word(alphas) + "," + Word(alphas) + "!" - hello = "Hello, World!" - print (hello, "->", greet.parseString(hello)) - Prints:: - Hello, World! -> ['Hello', ',', 'World', '!'] - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return And( [ self, other ] ) - - def __radd__(self, other ): - """ - Implementation of + operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other + self - - def __sub__(self, other): - """ - Implementation of - operator, returns C{L{And}} with error stop - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return self + And._ErrorStop() + other - - def __rsub__(self, other ): - """ - Implementation of - operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other - self - - def __mul__(self,other): - """ - Implementation of * operator, allows use of C{expr * 3} in place of - C{expr + expr + expr}. Expressions may also me multiplied by a 2-integer - tuple, similar to C{{min,max}} multipliers in regular expressions. Tuples - may also include C{None} as in: - - C{expr*(n,None)} or C{expr*(n,)} is equivalent - to C{expr*n + L{ZeroOrMore}(expr)} - (read as "at least n instances of C{expr}") - - C{expr*(None,n)} is equivalent to C{expr*(0,n)} - (read as "0 to n instances of C{expr}") - - C{expr*(None,None)} is equivalent to C{L{ZeroOrMore}(expr)} - - C{expr*(1,None)} is equivalent to C{L{OneOrMore}(expr)} - - Note that C{expr*(None,n)} does not raise an exception if - more than n exprs exist in the input stream; that is, - C{expr*(None,n)} does not enforce a maximum number of expr - occurrences. If this behavior is desired, then write - C{expr*(None,n) + ~expr} - """ - if isinstance(other,int): - minElements, optElements = other,0 - elif isinstance(other,tuple): - other = (other + (None, None))[:2] - if other[0] is None: - other = (0, other[1]) - if isinstance(other[0],int) and other[1] is None: - if other[0] == 0: - return ZeroOrMore(self) - if other[0] == 1: - return OneOrMore(self) - else: - return self*other[0] + ZeroOrMore(self) - elif isinstance(other[0],int) and isinstance(other[1],int): - minElements, optElements = other - optElements -= minElements - else: - raise TypeError("cannot multiply 'ParserElement' and ('%s','%s') objects", type(other[0]),type(other[1])) - else: - raise TypeError("cannot multiply 'ParserElement' and '%s' objects", type(other)) - - if minElements < 0: - raise ValueError("cannot multiply ParserElement by negative value") - if optElements < 0: - raise ValueError("second tuple value must be greater or equal to first tuple value") - if minElements == optElements == 0: - raise ValueError("cannot multiply ParserElement by 0 or (0,0)") - - if (optElements): - def makeOptionalList(n): - if n>1: - return Optional(self + makeOptionalList(n-1)) - else: - return Optional(self) - if minElements: - if minElements == 1: - ret = self + makeOptionalList(optElements) - else: - ret = And([self]*minElements) + makeOptionalList(optElements) - else: - ret = makeOptionalList(optElements) - else: - if minElements == 1: - ret = self - else: - ret = And([self]*minElements) - return ret - - def __rmul__(self, other): - return self.__mul__(other) - - def __or__(self, other ): - """ - Implementation of | operator - returns C{L{MatchFirst}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return MatchFirst( [ self, other ] ) - - def __ror__(self, other ): - """ - Implementation of | operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other | self - - def __xor__(self, other ): - """ - Implementation of ^ operator - returns C{L{Or}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Or( [ self, other ] ) - - def __rxor__(self, other ): - """ - Implementation of ^ operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other ^ self - - def __and__(self, other ): - """ - Implementation of & operator - returns C{L{Each}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return Each( [ self, other ] ) - - def __rand__(self, other ): - """ - Implementation of & operator when left operand is not a C{L{ParserElement}} - """ - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - if not isinstance( other, ParserElement ): - warnings.warn("Cannot combine element of type %s with ParserElement" % type(other), - SyntaxWarning, stacklevel=2) - return None - return other & self - - def __invert__( self ): - """ - Implementation of ~ operator - returns C{L{NotAny}} - """ - return NotAny( self ) - - def __call__(self, name=None): - """ - Shortcut for C{L{setResultsName}}, with C{listAllMatches=False}. - - If C{name} is given with a trailing C{'*'} character, then C{listAllMatches} will be - passed as C{True}. - - If C{name} is omitted, same as calling C{L{copy}}. - - Example:: - # these are equivalent - userdata = Word(alphas).setResultsName("name") + Word(nums+"-").setResultsName("socsecno") - userdata = Word(alphas)("name") + Word(nums+"-")("socsecno") - """ - if name is not None: - return self.setResultsName(name) - else: - return self.copy() - - def suppress( self ): - """ - Suppresses the output of this C{ParserElement}; useful to keep punctuation from - cluttering up returned output. - """ - return Suppress( self ) - - def leaveWhitespace( self ): - """ - Disables the skipping of whitespace before matching the characters in the - C{ParserElement}'s defined pattern. This is normally only used internally by - the pyparsing module, but may be needed in some whitespace-sensitive grammars. - """ - self.skipWhitespace = False - return self - - def setWhitespaceChars( self, chars ): - """ - Overrides the default whitespace chars - """ - self.skipWhitespace = True - self.whiteChars = chars - self.copyDefaultWhiteChars = False - return self - - def parseWithTabs( self ): - """ - Overrides default behavior to expand C{<TAB>}s to spaces before parsing the input string. - Must be called before C{parseString} when the input grammar contains elements that - match C{<TAB>} characters. - """ - self.keepTabs = True - return self - - def ignore( self, other ): - """ - Define expression to be ignored (e.g., comments) while doing pattern - matching; may be called repeatedly, to define multiple comment or other - ignorable patterns. - - Example:: - patt = OneOrMore(Word(alphas)) - patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj'] - - patt.ignore(cStyleComment) - patt.parseString('ablaj /* comment */ lskjd') # -> ['ablaj', 'lskjd'] - """ - if isinstance(other, basestring): - other = Suppress(other) - - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - self.ignoreExprs.append(other) - else: - self.ignoreExprs.append( Suppress( other.copy() ) ) - return self - - def setDebugActions( self, startAction, successAction, exceptionAction ): - """ - Enable display of debugging messages while doing pattern matching. - """ - self.debugActions = (startAction or _defaultStartDebugAction, - successAction or _defaultSuccessDebugAction, - exceptionAction or _defaultExceptionDebugAction) - self.debug = True - return self - - def setDebug( self, flag=True ): - """ - Enable display of debugging messages while doing pattern matching. - Set C{flag} to True to enable, False to disable. - - Example:: - wd = Word(alphas).setName("alphaword") - integer = Word(nums).setName("numword") - term = wd | integer - - # turn on debugging for wd - wd.setDebug() - - OneOrMore(term).parseString("abc 123 xyz 890") - - prints:: - Match alphaword at loc 0(1,1) - Matched alphaword -> ['abc'] - Match alphaword at loc 3(1,4) - Exception raised:Expected alphaword (at char 4), (line:1, col:5) - Match alphaword at loc 7(1,8) - Matched alphaword -> ['xyz'] - Match alphaword at loc 11(1,12) - Exception raised:Expected alphaword (at char 12), (line:1, col:13) - Match alphaword at loc 15(1,16) - Exception raised:Expected alphaword (at char 15), (line:1, col:16) - - The output shown is that produced by the default debug actions - custom debug actions can be - specified using L{setDebugActions}. Prior to attempting - to match the C{wd} expression, the debugging message C{"Match <exprname> at loc <n>(<line>,<col>)"} - is shown. Then if the parse succeeds, a C{"Matched"} message is shown, or an C{"Exception raised"} - message is shown. Also note the use of L{setName} to assign a human-readable name to the expression, - which makes debugging and exception messages easier to understand - for instance, the default - name created for the C{Word} expression without calling C{setName} is C{"W:(ABCD...)"}. - """ - if flag: - self.setDebugActions( _defaultStartDebugAction, _defaultSuccessDebugAction, _defaultExceptionDebugAction ) - else: - self.debug = False - return self - - def __str__( self ): - return self.name - - def __repr__( self ): - return _ustr(self) - - def streamline( self ): - self.streamlined = True - self.strRepr = None - return self - - def checkRecursion( self, parseElementList ): - pass - - def validate( self, validateTrace=[] ): - """ - Check defined expressions for valid structure, check for infinite recursive definitions. - """ - self.checkRecursion( [] ) - - def parseFile( self, file_or_filename, parseAll=False ): - """ - Execute the parse expression on the given file or filename. - If a filename is specified (instead of a file object), - the entire file is opened, read, and closed before parsing. - """ - try: - file_contents = file_or_filename.read() - except AttributeError: - with open(file_or_filename, "r") as f: - file_contents = f.read() - try: - return self.parseString(file_contents, parseAll) - except ParseBaseException as exc: - if ParserElement.verbose_stacktrace: - raise - else: - # catch and re-raise exception from here, clears out pyparsing internal stack trace - raise exc - - def __eq__(self,other): - if isinstance(other, ParserElement): - return self is other or vars(self) == vars(other) - elif isinstance(other, basestring): - return self.matches(other) - else: - return super(ParserElement,self)==other - - def __ne__(self,other): - return not (self == other) - - def __hash__(self): - return hash(id(self)) - - def __req__(self,other): - return self == other - - def __rne__(self,other): - return not (self == other) - - def matches(self, testString, parseAll=True): - """ - Method for quick testing of a parser against a test string. Good for simple - inline microtests of sub expressions while building up larger parser. - - Parameters: - - testString - to test against this expression for a match - - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests - - Example:: - expr = Word(nums) - assert expr.matches("100") - """ - try: - self.parseString(_ustr(testString), parseAll=parseAll) - return True - except ParseBaseException: - return False - - def runTests(self, tests, parseAll=True, comment='#', fullDump=True, printResults=True, failureTests=False): - """ - Execute the parse expression on a series of test strings, showing each - test, the parsed results or where the parse failed. Quick and easy way to - run a parse expression against a list of sample strings. - - Parameters: - - tests - a list of separate test strings, or a multiline string of test strings - - parseAll - (default=C{True}) - flag to pass to C{L{parseString}} when running tests - - comment - (default=C{'#'}) - expression for indicating embedded comments in the test - string; pass None to disable comment filtering - - fullDump - (default=C{True}) - dump results as list followed by results names in nested outline; - if False, only dump nested list - - printResults - (default=C{True}) prints test output to stdout - - failureTests - (default=C{False}) indicates if these tests are expected to fail parsing - - Returns: a (success, results) tuple, where success indicates that all tests succeeded - (or failed if C{failureTests} is True), and the results contain a list of lines of each - test's output - - Example:: - number_expr = pyparsing_common.number.copy() - - result = number_expr.runTests(''' - # unsigned integer - 100 - # negative integer - -100 - # float with scientific notation - 6.02e23 - # integer with scientific notation - 1e-12 - ''') - print("Success" if result[0] else "Failed!") - - result = number_expr.runTests(''' - # stray character - 100Z - # missing leading digit before '.' - -.100 - # too many '.' - 3.14.159 - ''', failureTests=True) - print("Success" if result[0] else "Failed!") - prints:: - # unsigned integer - 100 - [100] - - # negative integer - -100 - [-100] - - # float with scientific notation - 6.02e23 - [6.02e+23] - - # integer with scientific notation - 1e-12 - [1e-12] - - Success - - # stray character - 100Z - ^ - FAIL: Expected end of text (at char 3), (line:1, col:4) - - # missing leading digit before '.' - -.100 - ^ - FAIL: Expected {real number with scientific notation | real number | signed integer} (at char 0), (line:1, col:1) - - # too many '.' - 3.14.159 - ^ - FAIL: Expected end of text (at char 4), (line:1, col:5) - - Success - - Each test string must be on a single line. If you want to test a string that spans multiple - lines, create a test like this:: - - expr.runTest(r"this is a test\\n of strings that spans \\n 3 lines") - - (Note that this is a raw string literal, you must include the leading 'r'.) - """ - if isinstance(tests, basestring): - tests = list(map(str.strip, tests.rstrip().splitlines())) - if isinstance(comment, basestring): - comment = Literal(comment) - allResults = [] - comments = [] - success = True - for t in tests: - if comment is not None and comment.matches(t, False) or comments and not t: - comments.append(t) - continue - if not t: - continue - out = ['\n'.join(comments), t] - comments = [] - try: - t = t.replace(r'\n','\n') - result = self.parseString(t, parseAll=parseAll) - out.append(result.dump(full=fullDump)) - success = success and not failureTests - except ParseBaseException as pe: - fatal = "(FATAL)" if isinstance(pe, ParseFatalException) else "" - if '\n' in t: - out.append(line(pe.loc, t)) - out.append(' '*(col(pe.loc,t)-1) + '^' + fatal) - else: - out.append(' '*pe.loc + '^' + fatal) - out.append("FAIL: " + str(pe)) - success = success and failureTests - result = pe - except Exception as exc: - out.append("FAIL-EXCEPTION: " + str(exc)) - success = success and failureTests - result = exc - - if printResults: - if fullDump: - out.append('') - print('\n'.join(out)) - - allResults.append((t, result)) - - return success, allResults - - -class Token(ParserElement): - """ - Abstract C{ParserElement} subclass, for defining atomic matching patterns. - """ - def __init__( self ): - super(Token,self).__init__( savelist=False ) - - -class Empty(Token): - """ - An empty token, will always match. - """ - def __init__( self ): - super(Empty,self).__init__() - self.name = "Empty" - self.mayReturnEmpty = True - self.mayIndexError = False - - -class NoMatch(Token): - """ - A token that will never match. - """ - def __init__( self ): - super(NoMatch,self).__init__() - self.name = "NoMatch" - self.mayReturnEmpty = True - self.mayIndexError = False - self.errmsg = "Unmatchable token" - - def parseImpl( self, instring, loc, doActions=True ): - raise ParseException(instring, loc, self.errmsg, self) - - -class Literal(Token): - """ - Token to exactly match a specified string. - - Example:: - Literal('blah').parseString('blah') # -> ['blah'] - Literal('blah').parseString('blahfooblah') # -> ['blah'] - Literal('blah').parseString('bla') # -> Exception: Expected "blah" - - For case-insensitive matching, use L{CaselessLiteral}. - - For keyword matching (force word break before and after the matched string), - use L{Keyword} or L{CaselessKeyword}. - """ - def __init__( self, matchString ): - super(Literal,self).__init__() - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Literal; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.__class__ = Empty - self.name = '"%s"' % _ustr(self.match) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - - # Performance tuning: this routine gets called a *lot* - # if this is a single character match string and the first character matches, - # short-circuit as quickly as possible, and avoid calling startswith - #~ @profile - def parseImpl( self, instring, loc, doActions=True ): - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) ): - return loc+self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) -_L = Literal -ParserElement._literalStringClass = Literal - -class Keyword(Token): - """ - Token to exactly match a specified string as a keyword, that is, it must be - immediately followed by a non-keyword character. Compare with C{L{Literal}}: - - C{Literal("if")} will match the leading C{'if'} in C{'ifAndOnlyIf'}. - - C{Keyword("if")} will not; it will only match the leading C{'if'} in C{'if x=1'}, or C{'if(y==2)'} - Accepts two optional constructor arguments in addition to the keyword string: - - C{identChars} is a string of characters that would be valid identifier characters, - defaulting to all alphanumerics + "_" and "$" - - C{caseless} allows case-insensitive matching, default is C{False}. - - Example:: - Keyword("start").parseString("start") # -> ['start'] - Keyword("start").parseString("starting") # -> Exception - - For case-insensitive matching, use L{CaselessKeyword}. - """ - DEFAULT_KEYWORD_CHARS = alphanums+"_$" - - def __init__( self, matchString, identChars=None, caseless=False ): - super(Keyword,self).__init__() - if identChars is None: - identChars = Keyword.DEFAULT_KEYWORD_CHARS - self.match = matchString - self.matchLen = len(matchString) - try: - self.firstMatchChar = matchString[0] - except IndexError: - warnings.warn("null string passed to Keyword; use Empty() instead", - SyntaxWarning, stacklevel=2) - self.name = '"%s"' % self.match - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = False - self.mayIndexError = False - self.caseless = caseless - if caseless: - self.caselessmatch = matchString.upper() - identChars = identChars.upper() - self.identChars = set(identChars) - - def parseImpl( self, instring, loc, doActions=True ): - if self.caseless: - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) and - (loc == 0 or instring[loc-1].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - else: - if (instring[loc] == self.firstMatchChar and - (self.matchLen==1 or instring.startswith(self.match,loc)) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen] not in self.identChars) and - (loc == 0 or instring[loc-1] not in self.identChars) ): - return loc+self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) - - def copy(self): - c = super(Keyword,self).copy() - c.identChars = Keyword.DEFAULT_KEYWORD_CHARS - return c - - @staticmethod - def setDefaultKeywordChars( chars ): - """Overrides the default Keyword chars - """ - Keyword.DEFAULT_KEYWORD_CHARS = chars - -class CaselessLiteral(Literal): - """ - Token to match a specified string, ignoring case of letters. - Note: the matched results will always be in the case of the given - match string, NOT the case of the input text. - - Example:: - OneOrMore(CaselessLiteral("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD', 'CMD'] - - (Contrast with example for L{CaselessKeyword}.) - """ - def __init__( self, matchString ): - super(CaselessLiteral,self).__init__( matchString.upper() ) - # Preserve the defining literal. - self.returnString = matchString - self.name = "'%s'" % self.returnString - self.errmsg = "Expected " + self.name - - def parseImpl( self, instring, loc, doActions=True ): - if instring[ loc:loc+self.matchLen ].upper() == self.match: - return loc+self.matchLen, self.returnString - raise ParseException(instring, loc, self.errmsg, self) - -class CaselessKeyword(Keyword): - """ - Caseless version of L{Keyword}. - - Example:: - OneOrMore(CaselessKeyword("CMD")).parseString("cmd CMD Cmd10") # -> ['CMD', 'CMD'] - - (Contrast with example for L{CaselessLiteral}.) - """ - def __init__( self, matchString, identChars=None ): - super(CaselessKeyword,self).__init__( matchString, identChars, caseless=True ) - - def parseImpl( self, instring, loc, doActions=True ): - if ( (instring[ loc:loc+self.matchLen ].upper() == self.caselessmatch) and - (loc >= len(instring)-self.matchLen or instring[loc+self.matchLen].upper() not in self.identChars) ): - return loc+self.matchLen, self.match - raise ParseException(instring, loc, self.errmsg, self) - -class CloseMatch(Token): - """ - A variation on L{Literal} which matches "close" matches, that is, - strings with at most 'n' mismatching characters. C{CloseMatch} takes parameters: - - C{match_string} - string to be matched - - C{maxMismatches} - (C{default=1}) maximum number of mismatches allowed to count as a match - - The results from a successful parse will contain the matched text from the input string and the following named results: - - C{mismatches} - a list of the positions within the match_string where mismatches were found - - C{original} - the original match_string used to compare against the input string - - If C{mismatches} is an empty list, then the match was an exact match. - - Example:: - patt = CloseMatch("ATCATCGAATGGA") - patt.parseString("ATCATCGAAXGGA") # -> (['ATCATCGAAXGGA'], {'mismatches': [[9]], 'original': ['ATCATCGAATGGA']}) - patt.parseString("ATCAXCGAAXGGA") # -> Exception: Expected 'ATCATCGAATGGA' (with up to 1 mismatches) (at char 0), (line:1, col:1) - - # exact match - patt.parseString("ATCATCGAATGGA") # -> (['ATCATCGAATGGA'], {'mismatches': [[]], 'original': ['ATCATCGAATGGA']}) - - # close match allowing up to 2 mismatches - patt = CloseMatch("ATCATCGAATGGA", maxMismatches=2) - patt.parseString("ATCAXCGAAXGGA") # -> (['ATCAXCGAAXGGA'], {'mismatches': [[4, 9]], 'original': ['ATCATCGAATGGA']}) - """ - def __init__(self, match_string, maxMismatches=1): - super(CloseMatch,self).__init__() - self.name = match_string - self.match_string = match_string - self.maxMismatches = maxMismatches - self.errmsg = "Expected %r (with up to %d mismatches)" % (self.match_string, self.maxMismatches) - self.mayIndexError = False - self.mayReturnEmpty = False - - def parseImpl( self, instring, loc, doActions=True ): - start = loc - instrlen = len(instring) - maxloc = start + len(self.match_string) - - if maxloc <= instrlen: - match_string = self.match_string - match_stringloc = 0 - mismatches = [] - maxMismatches = self.maxMismatches - - for match_stringloc,s_m in enumerate(zip(instring[loc:maxloc], self.match_string)): - src,mat = s_m - if src != mat: - mismatches.append(match_stringloc) - if len(mismatches) > maxMismatches: - break - else: - loc = match_stringloc + 1 - results = ParseResults([instring[start:loc]]) - results['original'] = self.match_string - results['mismatches'] = mismatches - return loc, results - - raise ParseException(instring, loc, self.errmsg, self) - - -class Word(Token): - """ - Token for matching words composed of allowed character sets. - Defined with string containing all allowed initial characters, - an optional string containing allowed body characters (if omitted, - defaults to the initial character set), and an optional minimum, - maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. An optional - C{excludeChars} parameter can list characters that might be found in - the input C{bodyChars} string; useful to define a word of all printables - except for one or two characters, for instance. - - L{srange} is useful for defining custom character set strings for defining - C{Word} expressions, using range notation from regular expression character sets. - - A common mistake is to use C{Word} to match a specific literal string, as in - C{Word("Address")}. Remember that C{Word} uses the string argument to define - I{sets} of matchable characters. This expression would match "Add", "AAA", - "dAred", or any other word made up of the characters 'A', 'd', 'r', 'e', and 's'. - To match an exact literal string, use L{Literal} or L{Keyword}. - - pyparsing includes helper strings for building Words: - - L{alphas} - - L{nums} - - L{alphanums} - - L{hexnums} - - L{alphas8bit} (alphabetic characters in ASCII range 128-255 - accented, tilded, umlauted, etc.) - - L{punc8bit} (non-alphabetic characters in ASCII range 128-255 - currency, symbols, superscripts, diacriticals, etc.) - - L{printables} (any non-whitespace character) - - Example:: - # a word composed of digits - integer = Word(nums) # equivalent to Word("0123456789") or Word(srange("0-9")) - - # a word with a leading capital, and zero or more lowercase - capital_word = Word(alphas.upper(), alphas.lower()) - - # hostnames are alphanumeric, with leading alpha, and '-' - hostname = Word(alphas, alphanums+'-') - - # roman numeral (not a strict parser, accepts invalid mix of characters) - roman = Word("IVXLCDM") - - # any string of non-whitespace characters, except for ',' - csv_value = Word(printables, excludeChars=",") - """ - def __init__( self, initChars, bodyChars=None, min=1, max=0, exact=0, asKeyword=False, excludeChars=None ): - super(Word,self).__init__() - if excludeChars: - initChars = ''.join(c for c in initChars if c not in excludeChars) - if bodyChars: - bodyChars = ''.join(c for c in bodyChars if c not in excludeChars) - self.initCharsOrig = initChars - self.initChars = set(initChars) - if bodyChars : - self.bodyCharsOrig = bodyChars - self.bodyChars = set(bodyChars) - else: - self.bodyCharsOrig = initChars - self.bodyChars = set(initChars) - - self.maxSpecified = max > 0 - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(Word()) if zero-length word is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.asKeyword = asKeyword - - if ' ' not in self.initCharsOrig+self.bodyCharsOrig and (min==1 and max==0 and exact==0): - if self.bodyCharsOrig == self.initCharsOrig: - self.reString = "[%s]+" % _escapeRegexRangeChars(self.initCharsOrig) - elif len(self.initCharsOrig) == 1: - self.reString = "%s[%s]*" % \ - (re.escape(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - else: - self.reString = "[%s][%s]*" % \ - (_escapeRegexRangeChars(self.initCharsOrig), - _escapeRegexRangeChars(self.bodyCharsOrig),) - if self.asKeyword: - self.reString = r"\b"+self.reString+r"\b" - try: - self.re = re.compile( self.reString ) - except Exception: - self.re = None - - def parseImpl( self, instring, loc, doActions=True ): - if self.re: - result = self.re.match(instring,loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - return loc, result.group() - - if not(instring[ loc ] in self.initChars): - raise ParseException(instring, loc, self.errmsg, self) - - start = loc - loc += 1 - instrlen = len(instring) - bodychars = self.bodyChars - maxloc = start + self.maxLen - maxloc = min( maxloc, instrlen ) - while loc < maxloc and instring[loc] in bodychars: - loc += 1 - - throwException = False - if loc - start < self.minLen: - throwException = True - if self.maxSpecified and loc < instrlen and instring[loc] in bodychars: - throwException = True - if self.asKeyword: - if (start>0 and instring[start-1] in bodychars) or (loc<instrlen and instring[loc] in bodychars): - throwException = True - - if throwException: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(Word,self).__str__() - except Exception: - pass - - - if self.strRepr is None: - - def charsAsStr(s): - if len(s)>4: - return s[:4]+"..." - else: - return s - - if ( self.initCharsOrig != self.bodyCharsOrig ): - self.strRepr = "W:(%s,%s)" % ( charsAsStr(self.initCharsOrig), charsAsStr(self.bodyCharsOrig) ) - else: - self.strRepr = "W:(%s)" % charsAsStr(self.initCharsOrig) - - return self.strRepr - - -class Regex(Token): - r""" - Token for matching strings that match a given regular expression. - Defined with string specifying the regular expression in a form recognized by the inbuilt Python re module. - If the given regex contains named groups (defined using C{(?P<name>...)}), these will be preserved as - named parse results. - - Example:: - realnum = Regex(r"[+-]?\d+\.\d*") - date = Regex(r'(?P<year>\d{4})-(?P<month>\d\d?)-(?P<day>\d\d?)') - # ref: http://stackoverflow.com/questions/267399/how-do-you-match-only-valid-roman-numerals-with-a-regular-expression - roman = Regex(r"M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})") - """ - compiledREtype = type(re.compile("[A-Z]")) - def __init__( self, pattern, flags=0): - """The parameters C{pattern} and C{flags} are passed to the C{re.compile()} function as-is. See the Python C{re} module for an explanation of the acceptable patterns and flags.""" - super(Regex,self).__init__() - - if isinstance(pattern, basestring): - if not pattern: - warnings.warn("null string passed to Regex; use Empty() instead", - SyntaxWarning, stacklevel=2) - - self.pattern = pattern - self.flags = flags - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % pattern, - SyntaxWarning, stacklevel=2) - raise - - elif isinstance(pattern, Regex.compiledREtype): - self.re = pattern - self.pattern = \ - self.reString = str(pattern) - self.flags = flags - - else: - raise ValueError("Regex may only be constructed with a string or a compiled RE object") - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = self.re.match(instring,loc) - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - d = result.groupdict() - ret = ParseResults(result.group()) - if d: - for k in d: - ret[k] = d[k] - return loc,ret - - def __str__( self ): - try: - return super(Regex,self).__str__() - except Exception: - pass - - if self.strRepr is None: - self.strRepr = "Re:(%s)" % repr(self.pattern) - - return self.strRepr - - -class QuotedString(Token): - r""" - Token for matching strings that are delimited by quoting characters. - - Defined with the following parameters: - - quoteChar - string of one or more characters defining the quote delimiting string - - escChar - character to escape quotes, typically backslash (default=C{None}) - - escQuote - special quote sequence to escape an embedded quote string (such as SQL's "" to escape an embedded ") (default=C{None}) - - multiline - boolean indicating whether quotes can span multiple lines (default=C{False}) - - unquoteResults - boolean indicating whether the matched text should be unquoted (default=C{True}) - - endQuoteChar - string of one or more characters defining the end of the quote delimited string (default=C{None} => same as quoteChar) - - convertWhitespaceEscapes - convert escaped whitespace (C{'\t'}, C{'\n'}, etc.) to actual whitespace (default=C{True}) - - Example:: - qs = QuotedString('"') - print(qs.searchString('lsjdf "This is the quote" sldjf')) - complex_qs = QuotedString('{{', endQuoteChar='}}') - print(complex_qs.searchString('lsjdf {{This is the "quote"}} sldjf')) - sql_qs = QuotedString('"', escQuote='""') - print(sql_qs.searchString('lsjdf "This is the quote with ""embedded"" quotes" sldjf')) - prints:: - [['This is the quote']] - [['This is the "quote"']] - [['This is the quote with "embedded" quotes']] - """ - def __init__( self, quoteChar, escChar=None, escQuote=None, multiline=False, unquoteResults=True, endQuoteChar=None, convertWhitespaceEscapes=True): - super(QuotedString,self).__init__() - - # remove white space from quote chars - wont work anyway - quoteChar = quoteChar.strip() - if not quoteChar: - warnings.warn("quoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - if endQuoteChar is None: - endQuoteChar = quoteChar - else: - endQuoteChar = endQuoteChar.strip() - if not endQuoteChar: - warnings.warn("endQuoteChar cannot be the empty string",SyntaxWarning,stacklevel=2) - raise SyntaxError() - - self.quoteChar = quoteChar - self.quoteCharLen = len(quoteChar) - self.firstQuoteChar = quoteChar[0] - self.endQuoteChar = endQuoteChar - self.endQuoteCharLen = len(endQuoteChar) - self.escChar = escChar - self.escQuote = escQuote - self.unquoteResults = unquoteResults - self.convertWhitespaceEscapes = convertWhitespaceEscapes - - if multiline: - self.flags = re.MULTILINE | re.DOTALL - self.pattern = r'%s(?:[^%s%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - else: - self.flags = 0 - self.pattern = r'%s(?:[^%s\n\r%s]' % \ - ( re.escape(self.quoteChar), - _escapeRegexRangeChars(self.endQuoteChar[0]), - (escChar is not None and _escapeRegexRangeChars(escChar) or '') ) - if len(self.endQuoteChar) > 1: - self.pattern += ( - '|(?:' + ')|(?:'.join("%s[^%s]" % (re.escape(self.endQuoteChar[:i]), - _escapeRegexRangeChars(self.endQuoteChar[i])) - for i in range(len(self.endQuoteChar)-1,0,-1)) + ')' - ) - if escQuote: - self.pattern += (r'|(?:%s)' % re.escape(escQuote)) - if escChar: - self.pattern += (r'|(?:%s.)' % re.escape(escChar)) - self.escCharReplacePattern = re.escape(self.escChar)+"(.)" - self.pattern += (r')*%s' % re.escape(self.endQuoteChar)) - - try: - self.re = re.compile(self.pattern, self.flags) - self.reString = self.pattern - except sre_constants.error: - warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, - SyntaxWarning, stacklevel=2) - raise - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayIndexError = False - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - result = instring[loc] == self.firstQuoteChar and self.re.match(instring,loc) or None - if not result: - raise ParseException(instring, loc, self.errmsg, self) - - loc = result.end() - ret = result.group() - - if self.unquoteResults: - - # strip off quotes - ret = ret[self.quoteCharLen:-self.endQuoteCharLen] - - if isinstance(ret,basestring): - # replace escaped whitespace - if '\\' in ret and self.convertWhitespaceEscapes: - ws_map = { - r'\t' : '\t', - r'\n' : '\n', - r'\f' : '\f', - r'\r' : '\r', - } - for wslit,wschar in ws_map.items(): - ret = ret.replace(wslit, wschar) - - # replace escaped characters - if self.escChar: - ret = re.sub(self.escCharReplacePattern, r"\g<1>", ret) - - # replace escaped quotes - if self.escQuote: - ret = ret.replace(self.escQuote, self.endQuoteChar) - - return loc, ret - - def __str__( self ): - try: - return super(QuotedString,self).__str__() - except Exception: - pass - - if self.strRepr is None: - self.strRepr = "quoted string, starting with %s ending with %s" % (self.quoteChar, self.endQuoteChar) - - return self.strRepr - - -class CharsNotIn(Token): - """ - Token for matching words composed of characters I{not} in a given set (will - include whitespace in matched characters if not listed in the provided exclusion set - see example). - Defined with string containing all disallowed characters, and an optional - minimum, maximum, and/or exact length. The default value for C{min} is 1 (a - minimum value < 1 is not valid); the default values for C{max} and C{exact} - are 0, meaning no maximum or exact length restriction. - - Example:: - # define a comma-separated-value as anything that is not a ',' - csv_value = CharsNotIn(',') - print(delimitedList(csv_value).parseString("dkls,lsdkjf,s12 34,@!#,213")) - prints:: - ['dkls', 'lsdkjf', 's12 34', '@!#', '213'] - """ - def __init__( self, notChars, min=1, max=0, exact=0 ): - super(CharsNotIn,self).__init__() - self.skipWhitespace = False - self.notChars = notChars - - if min < 1: - raise ValueError("cannot specify a minimum length < 1; use Optional(CharsNotIn()) if zero-length char group is permitted") - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - self.name = _ustr(self) - self.errmsg = "Expected " + self.name - self.mayReturnEmpty = ( self.minLen == 0 ) - self.mayIndexError = False - - def parseImpl( self, instring, loc, doActions=True ): - if instring[loc] in self.notChars: - raise ParseException(instring, loc, self.errmsg, self) - - start = loc - loc += 1 - notchars = self.notChars - maxlen = min( start+self.maxLen, len(instring) ) - while loc < maxlen and \ - (instring[loc] not in notchars): - loc += 1 - - if loc - start < self.minLen: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - def __str__( self ): - try: - return super(CharsNotIn, self).__str__() - except Exception: - pass - - if self.strRepr is None: - if len(self.notChars) > 4: - self.strRepr = "!W:(%s...)" % self.notChars[:4] - else: - self.strRepr = "!W:(%s)" % self.notChars - - return self.strRepr - -class White(Token): - """ - Special matching class for matching whitespace. Normally, whitespace is ignored - by pyparsing grammars. This class is included when some whitespace structures - are significant. Define with a string containing the whitespace characters to be - matched; default is C{" \\t\\r\\n"}. Also takes optional C{min}, C{max}, and C{exact} arguments, - as defined for the C{L{Word}} class. - """ - whiteStrs = { - " " : "<SPC>", - "\t": "<TAB>", - "\n": "<LF>", - "\r": "<CR>", - "\f": "<FF>", - } - def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0): - super(White,self).__init__() - self.matchWhite = ws - self.setWhitespaceChars( "".join(c for c in self.whiteChars if c not in self.matchWhite) ) - #~ self.leaveWhitespace() - self.name = ("".join(White.whiteStrs[c] for c in self.matchWhite)) - self.mayReturnEmpty = True - self.errmsg = "Expected " + self.name - - self.minLen = min - - if max > 0: - self.maxLen = max - else: - self.maxLen = _MAX_INT - - if exact > 0: - self.maxLen = exact - self.minLen = exact - - def parseImpl( self, instring, loc, doActions=True ): - if not(instring[ loc ] in self.matchWhite): - raise ParseException(instring, loc, self.errmsg, self) - start = loc - loc += 1 - maxloc = start + self.maxLen - maxloc = min( maxloc, len(instring) ) - while loc < maxloc and instring[loc] in self.matchWhite: - loc += 1 - - if loc - start < self.minLen: - raise ParseException(instring, loc, self.errmsg, self) - - return loc, instring[start:loc] - - -class _PositionToken(Token): - def __init__( self ): - super(_PositionToken,self).__init__() - self.name=self.__class__.__name__ - self.mayReturnEmpty = True - self.mayIndexError = False - -class GoToColumn(_PositionToken): - """ - Token to advance to a specific column of input text; useful for tabular report scraping. - """ - def __init__( self, colno ): - super(GoToColumn,self).__init__() - self.col = colno - - def preParse( self, instring, loc ): - if col(loc,instring) != self.col: - instrlen = len(instring) - if self.ignoreExprs: - loc = self._skipIgnorables( instring, loc ) - while loc < instrlen and instring[loc].isspace() and col( loc, instring ) != self.col : - loc += 1 - return loc - - def parseImpl( self, instring, loc, doActions=True ): - thiscol = col( loc, instring ) - if thiscol > self.col: - raise ParseException( instring, loc, "Text not in expected column", self ) - newloc = loc + self.col - thiscol - ret = instring[ loc: newloc ] - return newloc, ret - - -class LineStart(_PositionToken): - """ - Matches if current position is at the beginning of a line within the parse string - - Example:: - - test = '''\ - AAA this line - AAA and this line - AAA but not this one - B AAA and definitely not this one - ''' - - for t in (LineStart() + 'AAA' + restOfLine).searchString(test): - print(t) - - Prints:: - ['AAA', ' this line'] - ['AAA', ' and this line'] - - """ - def __init__( self ): - super(LineStart,self).__init__() - self.errmsg = "Expected start of line" - - def parseImpl( self, instring, loc, doActions=True ): - if col(loc, instring) == 1: - return loc, [] - raise ParseException(instring, loc, self.errmsg, self) - -class LineEnd(_PositionToken): - """ - Matches if current position is at the end of a line within the parse string - """ - def __init__( self ): - super(LineEnd,self).__init__() - self.setWhitespaceChars( ParserElement.DEFAULT_WHITE_CHARS.replace("\n","") ) - self.errmsg = "Expected end of line" - - def parseImpl( self, instring, loc, doActions=True ): - if loc<len(instring): - if instring[loc] == "\n": - return loc+1, "\n" - else: - raise ParseException(instring, loc, self.errmsg, self) - elif loc == len(instring): - return loc+1, [] - else: - raise ParseException(instring, loc, self.errmsg, self) - -class StringStart(_PositionToken): - """ - Matches if current position is at the beginning of the parse string - """ - def __init__( self ): - super(StringStart,self).__init__() - self.errmsg = "Expected start of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc != 0: - # see if entire string up to here is just whitespace and ignoreables - if loc != self.preParse( instring, 0 ): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - -class StringEnd(_PositionToken): - """ - Matches if current position is at the end of the parse string - """ - def __init__( self ): - super(StringEnd,self).__init__() - self.errmsg = "Expected end of text" - - def parseImpl( self, instring, loc, doActions=True ): - if loc < len(instring): - raise ParseException(instring, loc, self.errmsg, self) - elif loc == len(instring): - return loc+1, [] - elif loc > len(instring): - return loc, [] - else: - raise ParseException(instring, loc, self.errmsg, self) - -class WordStart(_PositionToken): - """ - Matches if the current position is at the beginning of a Word, and - is not preceded by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordStart(alphanums)}. C{WordStart} will also match at the beginning of - the string being parsed, or at the beginning of a line. - """ - def __init__(self, wordChars = printables): - super(WordStart,self).__init__() - self.wordChars = set(wordChars) - self.errmsg = "Not at the start of a word" - - def parseImpl(self, instring, loc, doActions=True ): - if loc != 0: - if (instring[loc-1] in self.wordChars or - instring[loc] not in self.wordChars): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - -class WordEnd(_PositionToken): - """ - Matches if the current position is at the end of a Word, and - is not followed by any character in a given set of C{wordChars} - (default=C{printables}). To emulate the C{\b} behavior of regular expressions, - use C{WordEnd(alphanums)}. C{WordEnd} will also match at the end of - the string being parsed, or at the end of a line. - """ - def __init__(self, wordChars = printables): - super(WordEnd,self).__init__() - self.wordChars = set(wordChars) - self.skipWhitespace = False - self.errmsg = "Not at the end of a word" - - def parseImpl(self, instring, loc, doActions=True ): - instrlen = len(instring) - if instrlen>0 and loc<instrlen: - if (instring[loc] in self.wordChars or - instring[loc-1] not in self.wordChars): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - - -class ParseExpression(ParserElement): - """ - Abstract subclass of ParserElement, for combining and post-processing parsed tokens. - """ - def __init__( self, exprs, savelist = False ): - super(ParseExpression,self).__init__(savelist) - if isinstance( exprs, _generatorType ): - exprs = list(exprs) - - if isinstance( exprs, basestring ): - self.exprs = [ ParserElement._literalStringClass( exprs ) ] - elif isinstance( exprs, Iterable ): - exprs = list(exprs) - # if sequence of strings provided, wrap with Literal - if all(isinstance(expr, basestring) for expr in exprs): - exprs = map(ParserElement._literalStringClass, exprs) - self.exprs = list(exprs) - else: - try: - self.exprs = list( exprs ) - except TypeError: - self.exprs = [ exprs ] - self.callPreparse = False - - def __getitem__( self, i ): - return self.exprs[i] - - def append( self, other ): - self.exprs.append( other ) - self.strRepr = None - return self - - def leaveWhitespace( self ): - """Extends C{leaveWhitespace} defined in base class, and also invokes C{leaveWhitespace} on - all contained expressions.""" - self.skipWhitespace = False - self.exprs = [ e.copy() for e in self.exprs ] - for e in self.exprs: - e.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - else: - super( ParseExpression, self).ignore( other ) - for e in self.exprs: - e.ignore( self.ignoreExprs[-1] ) - return self - - def __str__( self ): - try: - return super(ParseExpression,self).__str__() - except Exception: - pass - - if self.strRepr is None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.exprs) ) - return self.strRepr - - def streamline( self ): - super(ParseExpression,self).streamline() - - for e in self.exprs: - e.streamline() - - # collapse nested And's of the form And( And( And( a,b), c), d) to And( a,b,c,d ) - # but only if there are no parse actions or resultsNames on the nested And's - # (likewise for Or's and MatchFirst's) - if ( len(self.exprs) == 2 ): - other = self.exprs[0] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = other.exprs[:] + [ self.exprs[1] ] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - other = self.exprs[-1] - if ( isinstance( other, self.__class__ ) and - not(other.parseAction) and - other.resultsName is None and - not other.debug ): - self.exprs = self.exprs[:-1] + other.exprs[:] - self.strRepr = None - self.mayReturnEmpty |= other.mayReturnEmpty - self.mayIndexError |= other.mayIndexError - - self.errmsg = "Expected " + _ustr(self) - - return self - - def setResultsName( self, name, listAllMatches=False ): - ret = super(ParseExpression,self).setResultsName(name,listAllMatches) - return ret - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - for e in self.exprs: - e.validate(tmp) - self.checkRecursion( [] ) - - def copy(self): - ret = super(ParseExpression,self).copy() - ret.exprs = [e.copy() for e in self.exprs] - return ret - -class And(ParseExpression): - """ - Requires all given C{ParseExpression}s to be found in the given order. - Expressions may be separated by whitespace. - May be constructed using the C{'+'} operator. - May also be constructed using the C{'-'} operator, which will suppress backtracking. - - Example:: - integer = Word(nums) - name_expr = OneOrMore(Word(alphas)) - - expr = And([integer("id"),name_expr("name"),integer("age")]) - # more easily written as: - expr = integer("id") + name_expr("name") + integer("age") - """ - - class _ErrorStop(Empty): - def __init__(self, *args, **kwargs): - super(And._ErrorStop,self).__init__(*args, **kwargs) - self.name = '-' - self.leaveWhitespace() - - def __init__( self, exprs, savelist = True ): - super(And,self).__init__(exprs, savelist) - self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - self.setWhitespaceChars( self.exprs[0].whiteChars ) - self.skipWhitespace = self.exprs[0].skipWhitespace - self.callPreparse = True - - def parseImpl( self, instring, loc, doActions=True ): - # pass False as last arg to _parse for first element, since we already - # pre-parsed the string as part of our And pre-parsing - loc, resultlist = self.exprs[0]._parse( instring, loc, doActions, callPreParse=False ) - errorStop = False - for e in self.exprs[1:]: - if isinstance(e, And._ErrorStop): - errorStop = True - continue - if errorStop: - try: - loc, exprtokens = e._parse( instring, loc, doActions ) - except ParseSyntaxException: - raise - except ParseBaseException as pe: - pe.__traceback__ = None - raise ParseSyntaxException._from_exception(pe) - except IndexError: - raise ParseSyntaxException(instring, len(instring), self.errmsg, self) - else: - loc, exprtokens = e._parse( instring, loc, doActions ) - if exprtokens or exprtokens.haskeys(): - resultlist += exprtokens - return loc, resultlist - - def __iadd__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #And( [ self, other ] ) - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - if not e.mayReturnEmpty: - break - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - -class Or(ParseExpression): - """ - Requires that at least one C{ParseExpression} is found. - If two expressions match, the expression that matches the longest string will be used. - May be constructed using the C{'^'} operator. - - Example:: - # construct Or using '^' operator - - number = Word(nums) ^ Combine(Word(nums) + '.' + Word(nums)) - print(number.searchString("123 3.1416 789")) - prints:: - [['123'], ['3.1416'], ['789']] - """ - def __init__( self, exprs, savelist = False ): - super(Or,self).__init__(exprs, savelist) - if self.exprs: - self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) - else: - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxException = None - matches = [] - for e in self.exprs: - try: - loc2 = e.tryParse( instring, loc ) - except ParseException as err: - err.__traceback__ = None - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - else: - # save match among all matches, to retry longest to shortest - matches.append((loc2, e)) - - if matches: - matches.sort(key=lambda x: -x[0]) - for _,e in matches: - try: - return e._parse( instring, loc, doActions ) - except ParseException as err: - err.__traceback__ = None - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - - if maxException is not None: - maxException.msg = self.errmsg - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - - def __ixor__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #Or( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " ^ ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class MatchFirst(ParseExpression): - """ - Requires that at least one C{ParseExpression} is found. - If two expressions match, the first one listed is the one that will match. - May be constructed using the C{'|'} operator. - - Example:: - # construct MatchFirst using '|' operator - - # watch the order of expressions to match - number = Word(nums) | Combine(Word(nums) + '.' + Word(nums)) - print(number.searchString("123 3.1416 789")) # Fail! -> [['123'], ['3'], ['1416'], ['789']] - - # put more selective expression first - number = Combine(Word(nums) + '.' + Word(nums)) | Word(nums) - print(number.searchString("123 3.1416 789")) # Better -> [['123'], ['3.1416'], ['789']] - """ - def __init__( self, exprs, savelist = False ): - super(MatchFirst,self).__init__(exprs, savelist) - if self.exprs: - self.mayReturnEmpty = any(e.mayReturnEmpty for e in self.exprs) - else: - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - maxExcLoc = -1 - maxException = None - for e in self.exprs: - try: - ret = e._parse( instring, loc, doActions ) - return ret - except ParseException as err: - if err.loc > maxExcLoc: - maxException = err - maxExcLoc = err.loc - except IndexError: - if len(instring) > maxExcLoc: - maxException = ParseException(instring,len(instring),e.errmsg,self) - maxExcLoc = len(instring) - - # only got here if no expression matched, raise exception for match that made it the furthest - else: - if maxException is not None: - maxException.msg = self.errmsg - raise maxException - else: - raise ParseException(instring, loc, "no defined alternatives to match", self) - - def __ior__(self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass( other ) - return self.append( other ) #MatchFirst( [ self, other ] ) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " | ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class Each(ParseExpression): - """ - Requires all given C{ParseExpression}s to be found, but in any order. - Expressions may be separated by whitespace. - May be constructed using the C{'&'} operator. - - Example:: - color = oneOf("RED ORANGE YELLOW GREEN BLUE PURPLE BLACK WHITE BROWN") - shape_type = oneOf("SQUARE CIRCLE TRIANGLE STAR HEXAGON OCTAGON") - integer = Word(nums) - shape_attr = "shape:" + shape_type("shape") - posn_attr = "posn:" + Group(integer("x") + ',' + integer("y"))("posn") - color_attr = "color:" + color("color") - size_attr = "size:" + integer("size") - - # use Each (using operator '&') to accept attributes in any order - # (shape and posn are required, color and size are optional) - shape_spec = shape_attr & posn_attr & Optional(color_attr) & Optional(size_attr) - - shape_spec.runTests(''' - shape: SQUARE color: BLACK posn: 100, 120 - shape: CIRCLE size: 50 color: BLUE posn: 50,80 - color:GREEN size:20 shape:TRIANGLE posn:20,40 - ''' - ) - prints:: - shape: SQUARE color: BLACK posn: 100, 120 - ['shape:', 'SQUARE', 'color:', 'BLACK', 'posn:', ['100', ',', '120']] - - color: BLACK - - posn: ['100', ',', '120'] - - x: 100 - - y: 120 - - shape: SQUARE - - - shape: CIRCLE size: 50 color: BLUE posn: 50,80 - ['shape:', 'CIRCLE', 'size:', '50', 'color:', 'BLUE', 'posn:', ['50', ',', '80']] - - color: BLUE - - posn: ['50', ',', '80'] - - x: 50 - - y: 80 - - shape: CIRCLE - - size: 50 - - - color: GREEN size: 20 shape: TRIANGLE posn: 20,40 - ['color:', 'GREEN', 'size:', '20', 'shape:', 'TRIANGLE', 'posn:', ['20', ',', '40']] - - color: GREEN - - posn: ['20', ',', '40'] - - x: 20 - - y: 40 - - shape: TRIANGLE - - size: 20 - """ - def __init__( self, exprs, savelist = True ): - super(Each,self).__init__(exprs, savelist) - self.mayReturnEmpty = all(e.mayReturnEmpty for e in self.exprs) - self.skipWhitespace = True - self.initExprGroups = True - - def parseImpl( self, instring, loc, doActions=True ): - if self.initExprGroups: - self.opt1map = dict((id(e.expr),e) for e in self.exprs if isinstance(e,Optional)) - opt1 = [ e.expr for e in self.exprs if isinstance(e,Optional) ] - opt2 = [ e for e in self.exprs if e.mayReturnEmpty and not isinstance(e,Optional)] - self.optionals = opt1 + opt2 - self.multioptionals = [ e.expr for e in self.exprs if isinstance(e,ZeroOrMore) ] - self.multirequired = [ e.expr for e in self.exprs if isinstance(e,OneOrMore) ] - self.required = [ e for e in self.exprs if not isinstance(e,(Optional,ZeroOrMore,OneOrMore)) ] - self.required += self.multirequired - self.initExprGroups = False - tmpLoc = loc - tmpReqd = self.required[:] - tmpOpt = self.optionals[:] - matchOrder = [] - - keepMatching = True - while keepMatching: - tmpExprs = tmpReqd + tmpOpt + self.multioptionals + self.multirequired - failed = [] - for e in tmpExprs: - try: - tmpLoc = e.tryParse( instring, tmpLoc ) - except ParseException: - failed.append(e) - else: - matchOrder.append(self.opt1map.get(id(e),e)) - if e in tmpReqd: - tmpReqd.remove(e) - elif e in tmpOpt: - tmpOpt.remove(e) - if len(failed) == len(tmpExprs): - keepMatching = False - - if tmpReqd: - missing = ", ".join(_ustr(e) for e in tmpReqd) - raise ParseException(instring,loc,"Missing one or more required elements (%s)" % missing ) - - # add any unmatched Optionals, in case they have default values defined - matchOrder += [e for e in self.exprs if isinstance(e,Optional) and e.expr in tmpOpt] - - resultlist = [] - for e in matchOrder: - loc,results = e._parse(instring,loc,doActions) - resultlist.append(results) - - finalResults = sum(resultlist, ParseResults([])) - return loc, finalResults - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + " & ".join(_ustr(e) for e in self.exprs) + "}" - - return self.strRepr - - def checkRecursion( self, parseElementList ): - subRecCheckList = parseElementList[:] + [ self ] - for e in self.exprs: - e.checkRecursion( subRecCheckList ) - - -class ParseElementEnhance(ParserElement): - """ - Abstract subclass of C{ParserElement}, for combining and post-processing parsed tokens. - """ - def __init__( self, expr, savelist=False ): - super(ParseElementEnhance,self).__init__(savelist) - if isinstance( expr, basestring ): - if issubclass(ParserElement._literalStringClass, Token): - expr = ParserElement._literalStringClass(expr) - else: - expr = ParserElement._literalStringClass(Literal(expr)) - self.expr = expr - self.strRepr = None - if expr is not None: - self.mayIndexError = expr.mayIndexError - self.mayReturnEmpty = expr.mayReturnEmpty - self.setWhitespaceChars( expr.whiteChars ) - self.skipWhitespace = expr.skipWhitespace - self.saveAsList = expr.saveAsList - self.callPreparse = expr.callPreparse - self.ignoreExprs.extend(expr.ignoreExprs) - - def parseImpl( self, instring, loc, doActions=True ): - if self.expr is not None: - return self.expr._parse( instring, loc, doActions, callPreParse=False ) - else: - raise ParseException("",loc,self.errmsg,self) - - def leaveWhitespace( self ): - self.skipWhitespace = False - self.expr = self.expr.copy() - if self.expr is not None: - self.expr.leaveWhitespace() - return self - - def ignore( self, other ): - if isinstance( other, Suppress ): - if other not in self.ignoreExprs: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - else: - super( ParseElementEnhance, self).ignore( other ) - if self.expr is not None: - self.expr.ignore( self.ignoreExprs[-1] ) - return self - - def streamline( self ): - super(ParseElementEnhance,self).streamline() - if self.expr is not None: - self.expr.streamline() - return self - - def checkRecursion( self, parseElementList ): - if self in parseElementList: - raise RecursiveGrammarException( parseElementList+[self] ) - subRecCheckList = parseElementList[:] + [ self ] - if self.expr is not None: - self.expr.checkRecursion( subRecCheckList ) - - def validate( self, validateTrace=[] ): - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion( [] ) - - def __str__( self ): - try: - return super(ParseElementEnhance,self).__str__() - except Exception: - pass - - if self.strRepr is None and self.expr is not None: - self.strRepr = "%s:(%s)" % ( self.__class__.__name__, _ustr(self.expr) ) - return self.strRepr - - -class FollowedBy(ParseElementEnhance): - """ - Lookahead matching of the given parse expression. C{FollowedBy} - does I{not} advance the parsing position within the input string, it only - verifies that the specified parse expression matches at the current - position. C{FollowedBy} always returns a null token list. - - Example:: - # use FollowedBy to match a label only if it is followed by a ':' - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - - OneOrMore(attr_expr).parseString("shape: SQUARE color: BLACK posn: upper left").pprint() - prints:: - [['shape', 'SQUARE'], ['color', 'BLACK'], ['posn', 'upper left']] - """ - def __init__( self, expr ): - super(FollowedBy,self).__init__(expr) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - self.expr.tryParse( instring, loc ) - return loc, [] - - -class NotAny(ParseElementEnhance): - """ - Lookahead to disallow matching with the given parse expression. C{NotAny} - does I{not} advance the parsing position within the input string, it only - verifies that the specified parse expression does I{not} match at the current - position. Also, C{NotAny} does I{not} skip over leading whitespace. C{NotAny} - always returns a null token list. May be constructed using the '~' operator. - - Example:: - - """ - def __init__( self, expr ): - super(NotAny,self).__init__(expr) - #~ self.leaveWhitespace() - self.skipWhitespace = False # do NOT use self.leaveWhitespace(), don't want to propagate to exprs - self.mayReturnEmpty = True - self.errmsg = "Found unwanted token, "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - if self.expr.canParseNext(instring, loc): - raise ParseException(instring, loc, self.errmsg, self) - return loc, [] - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "~{" + _ustr(self.expr) + "}" - - return self.strRepr - -class _MultipleMatch(ParseElementEnhance): - def __init__( self, expr, stopOn=None): - super(_MultipleMatch, self).__init__(expr) - self.saveAsList = True - ender = stopOn - if isinstance(ender, basestring): - ender = ParserElement._literalStringClass(ender) - self.not_ender = ~ender if ender is not None else None - - def parseImpl( self, instring, loc, doActions=True ): - self_expr_parse = self.expr._parse - self_skip_ignorables = self._skipIgnorables - check_ender = self.not_ender is not None - if check_ender: - try_not_ender = self.not_ender.tryParse - - # must be at least one (but first see if we are the stopOn sentinel; - # if so, fail) - if check_ender: - try_not_ender(instring, loc) - loc, tokens = self_expr_parse( instring, loc, doActions, callPreParse=False ) - try: - hasIgnoreExprs = (not not self.ignoreExprs) - while 1: - if check_ender: - try_not_ender(instring, loc) - if hasIgnoreExprs: - preloc = self_skip_ignorables( instring, loc ) - else: - preloc = loc - loc, tmptokens = self_expr_parse( instring, preloc, doActions ) - if tmptokens or tmptokens.haskeys(): - tokens += tmptokens - except (ParseException,IndexError): - pass - - return loc, tokens - -class OneOrMore(_MultipleMatch): - """ - Repetition of one or more of the given expression. - - Parameters: - - expr - expression that must match one or more times - - stopOn - (default=C{None}) - expression for a terminating sentinel - (only required if the sentinel would ordinarily match the repetition - expression) - - Example:: - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join)) - - text = "shape: SQUARE posn: upper left color: BLACK" - OneOrMore(attr_expr).parseString(text).pprint() # Fail! read 'color' as data instead of next label -> [['shape', 'SQUARE color']] - - # use stopOn attribute for OneOrMore to avoid reading label string as part of the data - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - OneOrMore(attr_expr).parseString(text).pprint() # Better -> [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'BLACK']] - - # could also be written as - (attr_expr * (1,)).parseString(text).pprint() - """ - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "{" + _ustr(self.expr) + "}..." - - return self.strRepr - -class ZeroOrMore(_MultipleMatch): - """ - Optional repetition of zero or more of the given expression. - - Parameters: - - expr - expression that must match zero or more times - - stopOn - (default=C{None}) - expression for a terminating sentinel - (only required if the sentinel would ordinarily match the repetition - expression) - - Example: similar to L{OneOrMore} - """ - def __init__( self, expr, stopOn=None): - super(ZeroOrMore,self).__init__(expr, stopOn=stopOn) - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - try: - return super(ZeroOrMore, self).parseImpl(instring, loc, doActions) - except (ParseException,IndexError): - return loc, [] - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]..." - - return self.strRepr - -class _NullToken(object): - def __bool__(self): - return False - __nonzero__ = __bool__ - def __str__(self): - return "" - -_optionalNotMatched = _NullToken() -class Optional(ParseElementEnhance): - """ - Optional matching of the given expression. - - Parameters: - - expr - expression that must match zero or more times - - default (optional) - value to be returned if the optional expression is not found. - - Example:: - # US postal code can be a 5-digit zip, plus optional 4-digit qualifier - zip = Combine(Word(nums, exact=5) + Optional('-' + Word(nums, exact=4))) - zip.runTests(''' - # traditional ZIP code - 12345 - - # ZIP+4 form - 12101-0001 - - # invalid ZIP - 98765- - ''') - prints:: - # traditional ZIP code - 12345 - ['12345'] - - # ZIP+4 form - 12101-0001 - ['12101-0001'] - - # invalid ZIP - 98765- - ^ - FAIL: Expected end of text (at char 5), (line:1, col:6) - """ - def __init__( self, expr, default=_optionalNotMatched ): - super(Optional,self).__init__( expr, savelist=False ) - self.saveAsList = self.expr.saveAsList - self.defaultValue = default - self.mayReturnEmpty = True - - def parseImpl( self, instring, loc, doActions=True ): - try: - loc, tokens = self.expr._parse( instring, loc, doActions, callPreParse=False ) - except (ParseException,IndexError): - if self.defaultValue is not _optionalNotMatched: - if self.expr.resultsName: - tokens = ParseResults([ self.defaultValue ]) - tokens[self.expr.resultsName] = self.defaultValue - else: - tokens = [ self.defaultValue ] - else: - tokens = [] - return loc, tokens - - def __str__( self ): - if hasattr(self,"name"): - return self.name - - if self.strRepr is None: - self.strRepr = "[" + _ustr(self.expr) + "]" - - return self.strRepr - -class SkipTo(ParseElementEnhance): - """ - Token for skipping over all undefined text until the matched expression is found. - - Parameters: - - expr - target expression marking the end of the data to be skipped - - include - (default=C{False}) if True, the target expression is also parsed - (the skipped text and target expression are returned as a 2-element list). - - ignore - (default=C{None}) used to define grammars (typically quoted strings and - comments) that might contain false matches to the target expression - - failOn - (default=C{None}) define expressions that are not allowed to be - included in the skipped test; if found before the target expression is found, - the SkipTo is not a match - - Example:: - report = ''' - Outstanding Issues Report - 1 Jan 2000 - - # | Severity | Description | Days Open - -----+----------+-------------------------------------------+----------- - 101 | Critical | Intermittent system crash | 6 - 94 | Cosmetic | Spelling error on Login ('log|n') | 14 - 79 | Minor | System slow when running too many reports | 47 - ''' - integer = Word(nums) - SEP = Suppress('|') - # use SkipTo to simply match everything up until the next SEP - # - ignore quoted strings, so that a '|' character inside a quoted string does not match - # - parse action will call token.strip() for each matched token, i.e., the description body - string_data = SkipTo(SEP, ignore=quotedString) - string_data.setParseAction(tokenMap(str.strip)) - ticket_expr = (integer("issue_num") + SEP - + string_data("sev") + SEP - + string_data("desc") + SEP - + integer("days_open")) - - for tkt in ticket_expr.searchString(report): - print tkt.dump() - prints:: - ['101', 'Critical', 'Intermittent system crash', '6'] - - days_open: 6 - - desc: Intermittent system crash - - issue_num: 101 - - sev: Critical - ['94', 'Cosmetic', "Spelling error on Login ('log|n')", '14'] - - days_open: 14 - - desc: Spelling error on Login ('log|n') - - issue_num: 94 - - sev: Cosmetic - ['79', 'Minor', 'System slow when running too many reports', '47'] - - days_open: 47 - - desc: System slow when running too many reports - - issue_num: 79 - - sev: Minor - """ - def __init__( self, other, include=False, ignore=None, failOn=None ): - super( SkipTo, self ).__init__( other ) - self.ignoreExpr = ignore - self.mayReturnEmpty = True - self.mayIndexError = False - self.includeMatch = include - self.asList = False - if isinstance(failOn, basestring): - self.failOn = ParserElement._literalStringClass(failOn) - else: - self.failOn = failOn - self.errmsg = "No match found for "+_ustr(self.expr) - - def parseImpl( self, instring, loc, doActions=True ): - startloc = loc - instrlen = len(instring) - expr = self.expr - expr_parse = self.expr._parse - self_failOn_canParseNext = self.failOn.canParseNext if self.failOn is not None else None - self_ignoreExpr_tryParse = self.ignoreExpr.tryParse if self.ignoreExpr is not None else None - - tmploc = loc - while tmploc <= instrlen: - if self_failOn_canParseNext is not None: - # break if failOn expression matches - if self_failOn_canParseNext(instring, tmploc): - break - - if self_ignoreExpr_tryParse is not None: - # advance past ignore expressions - while 1: - try: - tmploc = self_ignoreExpr_tryParse(instring, tmploc) - except ParseBaseException: - break - - try: - expr_parse(instring, tmploc, doActions=False, callPreParse=False) - except (ParseException, IndexError): - # no match, advance loc in string - tmploc += 1 - else: - # matched skipto expr, done - break - - else: - # ran off the end of the input string without matching skipto expr, fail - raise ParseException(instring, loc, self.errmsg, self) - - # build up return values - loc = tmploc - skiptext = instring[startloc:loc] - skipresult = ParseResults(skiptext) - - if self.includeMatch: - loc, mat = expr_parse(instring,loc,doActions,callPreParse=False) - skipresult += mat - - return loc, skipresult - -class Forward(ParseElementEnhance): - """ - Forward declaration of an expression to be defined later - - used for recursive grammars, such as algebraic infix notation. - When the expression is known, it is assigned to the C{Forward} variable using the '<<' operator. - - Note: take care when assigning to C{Forward} not to overlook precedence of operators. - Specifically, '|' has a lower precedence than '<<', so that:: - fwdExpr << a | b | c - will actually be evaluated as:: - (fwdExpr << a) | b | c - thereby leaving b and c out as parseable alternatives. It is recommended that you - explicitly group the values inserted into the C{Forward}:: - fwdExpr << (a | b | c) - Converting to use the '<<=' operator instead will avoid this problem. - - See L{ParseResults.pprint} for an example of a recursive parser created using - C{Forward}. - """ - def __init__( self, other=None ): - super(Forward,self).__init__( other, savelist=False ) - - def __lshift__( self, other ): - if isinstance( other, basestring ): - other = ParserElement._literalStringClass(other) - self.expr = other - self.strRepr = None - self.mayIndexError = self.expr.mayIndexError - self.mayReturnEmpty = self.expr.mayReturnEmpty - self.setWhitespaceChars( self.expr.whiteChars ) - self.skipWhitespace = self.expr.skipWhitespace - self.saveAsList = self.expr.saveAsList - self.ignoreExprs.extend(self.expr.ignoreExprs) - return self - - def __ilshift__(self, other): - return self << other - - def leaveWhitespace( self ): - self.skipWhitespace = False - return self - - def streamline( self ): - if not self.streamlined: - self.streamlined = True - if self.expr is not None: - self.expr.streamline() - return self - - def validate( self, validateTrace=[] ): - if self not in validateTrace: - tmp = validateTrace[:]+[self] - if self.expr is not None: - self.expr.validate(tmp) - self.checkRecursion([]) - - def __str__( self ): - if hasattr(self,"name"): - return self.name - return self.__class__.__name__ + ": ..." - - # stubbed out for now - creates awful memory and perf issues - self._revertClass = self.__class__ - self.__class__ = _ForwardNoRecurse - try: - if self.expr is not None: - retString = _ustr(self.expr) - else: - retString = "None" - finally: - self.__class__ = self._revertClass - return self.__class__.__name__ + ": " + retString - - def copy(self): - if self.expr is not None: - return super(Forward,self).copy() - else: - ret = Forward() - ret <<= self - return ret - -class _ForwardNoRecurse(Forward): - def __str__( self ): - return "..." - -class TokenConverter(ParseElementEnhance): - """ - Abstract subclass of C{ParseExpression}, for converting parsed results. - """ - def __init__( self, expr, savelist=False ): - super(TokenConverter,self).__init__( expr )#, savelist ) - self.saveAsList = False - -class Combine(TokenConverter): - """ - Converter to concatenate all matching tokens to a single string. - By default, the matching patterns must also be contiguous in the input string; - this can be disabled by specifying C{'adjacent=False'} in the constructor. - - Example:: - real = Word(nums) + '.' + Word(nums) - print(real.parseString('3.1416')) # -> ['3', '.', '1416'] - # will also erroneously match the following - print(real.parseString('3. 1416')) # -> ['3', '.', '1416'] - - real = Combine(Word(nums) + '.' + Word(nums)) - print(real.parseString('3.1416')) # -> ['3.1416'] - # no match when there are internal spaces - print(real.parseString('3. 1416')) # -> Exception: Expected W:(0123...) - """ - def __init__( self, expr, joinString="", adjacent=True ): - super(Combine,self).__init__( expr ) - # suppress whitespace-stripping in contained parse expressions, but re-enable it on the Combine itself - if adjacent: - self.leaveWhitespace() - self.adjacent = adjacent - self.skipWhitespace = True - self.joinString = joinString - self.callPreparse = True - - def ignore( self, other ): - if self.adjacent: - ParserElement.ignore(self, other) - else: - super( Combine, self).ignore( other ) - return self - - def postParse( self, instring, loc, tokenlist ): - retToks = tokenlist.copy() - del retToks[:] - retToks += ParseResults([ "".join(tokenlist._asStringList(self.joinString)) ], modal=self.modalResults) - - if self.resultsName and retToks.haskeys(): - return [ retToks ] - else: - return retToks - -class Group(TokenConverter): - """ - Converter to return the matched tokens as a list - useful for returning tokens of C{L{ZeroOrMore}} and C{L{OneOrMore}} expressions. - - Example:: - ident = Word(alphas) - num = Word(nums) - term = ident | num - func = ident + Optional(delimitedList(term)) - print(func.parseString("fn a,b,100")) # -> ['fn', 'a', 'b', '100'] - - func = ident + Group(Optional(delimitedList(term))) - print(func.parseString("fn a,b,100")) # -> ['fn', ['a', 'b', '100']] - """ - def __init__( self, expr ): - super(Group,self).__init__( expr ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - return [ tokenlist ] - -class Dict(TokenConverter): - """ - Converter to return a repetitive expression as a list, but also as a dictionary. - Each element can also be referenced using the first token in the expression as its key. - Useful for tabular report scraping when the first column can be used as a item key. - - Example:: - data_word = Word(alphas) - label = data_word + FollowedBy(':') - attr_expr = Group(label + Suppress(':') + OneOrMore(data_word).setParseAction(' '.join)) - - text = "shape: SQUARE posn: upper left color: light blue texture: burlap" - attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - - # print attributes as plain groups - print(OneOrMore(attr_expr).parseString(text).dump()) - - # instead of OneOrMore(expr), parse using Dict(OneOrMore(Group(expr))) - Dict will auto-assign names - result = Dict(OneOrMore(Group(attr_expr))).parseString(text) - print(result.dump()) - - # access named fields as dict entries, or output as dict - print(result['shape']) - print(result.asDict()) - prints:: - ['shape', 'SQUARE', 'posn', 'upper left', 'color', 'light blue', 'texture', 'burlap'] - - [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] - - color: light blue - - posn: upper left - - shape: SQUARE - - texture: burlap - SQUARE - {'color': 'light blue', 'posn': 'upper left', 'texture': 'burlap', 'shape': 'SQUARE'} - See more examples at L{ParseResults} of accessing fields by results name. - """ - def __init__( self, expr ): - super(Dict,self).__init__( expr ) - self.saveAsList = True - - def postParse( self, instring, loc, tokenlist ): - for i,tok in enumerate(tokenlist): - if len(tok) == 0: - continue - ikey = tok[0] - if isinstance(ikey,int): - ikey = _ustr(tok[0]).strip() - if len(tok)==1: - tokenlist[ikey] = _ParseResultsWithOffset("",i) - elif len(tok)==2 and not isinstance(tok[1],ParseResults): - tokenlist[ikey] = _ParseResultsWithOffset(tok[1],i) - else: - dictvalue = tok.copy() #ParseResults(i) - del dictvalue[0] - if len(dictvalue)!= 1 or (isinstance(dictvalue,ParseResults) and dictvalue.haskeys()): - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue,i) - else: - tokenlist[ikey] = _ParseResultsWithOffset(dictvalue[0],i) - - if self.resultsName: - return [ tokenlist ] - else: - return tokenlist - - -class Suppress(TokenConverter): - """ - Converter for ignoring the results of a parsed expression. - - Example:: - source = "a, b, c,d" - wd = Word(alphas) - wd_list1 = wd + ZeroOrMore(',' + wd) - print(wd_list1.parseString(source)) - - # often, delimiters that are useful during parsing are just in the - # way afterward - use Suppress to keep them out of the parsed output - wd_list2 = wd + ZeroOrMore(Suppress(',') + wd) - print(wd_list2.parseString(source)) - prints:: - ['a', ',', 'b', ',', 'c', ',', 'd'] - ['a', 'b', 'c', 'd'] - (See also L{delimitedList}.) - """ - def postParse( self, instring, loc, tokenlist ): - return [] - - def suppress( self ): - return self - - -class OnlyOnce(object): - """ - Wrapper for parse actions, to ensure they are only called once. - """ - def __init__(self, methodCall): - self.callable = _trim_arity(methodCall) - self.called = False - def __call__(self,s,l,t): - if not self.called: - results = self.callable(s,l,t) - self.called = True - return results - raise ParseException(s,l,"") - def reset(self): - self.called = False - -def traceParseAction(f): - """ - Decorator for debugging parse actions. - - When the parse action is called, this decorator will print C{">> entering I{method-name}(line:I{current_source_line}, I{parse_location}, I{matched_tokens})".} - When the parse action completes, the decorator will print C{"<<"} followed by the returned value, or any exception that the parse action raised. - - Example:: - wd = Word(alphas) - - @traceParseAction - def remove_duplicate_chars(tokens): - return ''.join(sorted(set(''.join(tokens)))) - - wds = OneOrMore(wd).setParseAction(remove_duplicate_chars) - print(wds.parseString("slkdjs sld sldd sdlf sdljf")) - prints:: - >>entering remove_duplicate_chars(line: 'slkdjs sld sldd sdlf sdljf', 0, (['slkdjs', 'sld', 'sldd', 'sdlf', 'sdljf'], {})) - <<leaving remove_duplicate_chars (ret: 'dfjkls') - ['dfjkls'] - """ - f = _trim_arity(f) - def z(*paArgs): - thisFunc = f.__name__ - s,l,t = paArgs[-3:] - if len(paArgs)>3: - thisFunc = paArgs[0].__class__.__name__ + '.' + thisFunc - sys.stderr.write( ">>entering %s(line: '%s', %d, %r)\n" % (thisFunc,line(l,s),l,t) ) - try: - ret = f(*paArgs) - except Exception as exc: - sys.stderr.write( "<<leaving %s (exception: %s)\n" % (thisFunc,exc) ) - raise - sys.stderr.write( "<<leaving %s (ret: %r)\n" % (thisFunc,ret) ) - return ret - try: - z.__name__ = f.__name__ - except AttributeError: - pass - return z - -# -# global helpers -# -def delimitedList( expr, delim=",", combine=False ): - """ - Helper to define a delimited list of expressions - the delimiter defaults to ','. - By default, the list elements and delimiters can have intervening whitespace, and - comments, but this can be overridden by passing C{combine=True} in the constructor. - If C{combine} is set to C{True}, the matching tokens are returned as a single token - string, with the delimiters included; otherwise, the matching tokens are returned - as a list of tokens, with the delimiters suppressed. - - Example:: - delimitedList(Word(alphas)).parseString("aa,bb,cc") # -> ['aa', 'bb', 'cc'] - delimitedList(Word(hexnums), delim=':', combine=True).parseString("AA:BB:CC:DD:EE") # -> ['AA:BB:CC:DD:EE'] - """ - dlName = _ustr(expr)+" ["+_ustr(delim)+" "+_ustr(expr)+"]..." - if combine: - return Combine( expr + ZeroOrMore( delim + expr ) ).setName(dlName) - else: - return ( expr + ZeroOrMore( Suppress( delim ) + expr ) ).setName(dlName) - -def countedArray( expr, intExpr=None ): - """ - Helper to define a counted list of expressions. - This helper defines a pattern of the form:: - integer expr expr expr... - where the leading integer tells how many expr expressions follow. - The matched tokens returns the array of expr tokens as a list - the leading count token is suppressed. - - If C{intExpr} is specified, it should be a pyparsing expression that produces an integer value. - - Example:: - countedArray(Word(alphas)).parseString('2 ab cd ef') # -> ['ab', 'cd'] - - # in this parser, the leading integer value is given in binary, - # '10' indicating that 2 values are in the array - binaryConstant = Word('01').setParseAction(lambda t: int(t[0], 2)) - countedArray(Word(alphas), intExpr=binaryConstant).parseString('10 ab cd ef') # -> ['ab', 'cd'] - """ - arrayExpr = Forward() - def countFieldParseAction(s,l,t): - n = t[0] - arrayExpr << (n and Group(And([expr]*n)) or Group(empty)) - return [] - if intExpr is None: - intExpr = Word(nums).setParseAction(lambda t:int(t[0])) - else: - intExpr = intExpr.copy() - intExpr.setName("arrayLen") - intExpr.addParseAction(countFieldParseAction, callDuringTry=True) - return ( intExpr + arrayExpr ).setName('(len) ' + _ustr(expr) + '...') - -def _flatten(L): - ret = [] - for i in L: - if isinstance(i,list): - ret.extend(_flatten(i)) - else: - ret.append(i) - return ret - -def matchPreviousLiteral(expr): - """ - Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousLiteral(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches a - previous literal, will also match the leading C{"1:1"} in C{"1:10"}. - If this is not desired, use C{matchPreviousExpr}. - Do I{not} use with packrat parsing enabled. - """ - rep = Forward() - def copyTokenToRepeater(s,l,t): - if t: - if len(t) == 1: - rep << t[0] - else: - # flatten t tokens - tflat = _flatten(t.asList()) - rep << And(Literal(tt) for tt in tflat) - else: - rep << Empty() - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - rep.setName('(prev) ' + _ustr(expr)) - return rep - -def matchPreviousExpr(expr): - """ - Helper to define an expression that is indirectly defined from - the tokens matched in a previous expression, that is, it looks - for a 'repeat' of a previous expression. For example:: - first = Word(nums) - second = matchPreviousExpr(first) - matchExpr = first + ":" + second - will match C{"1:1"}, but not C{"1:2"}. Because this matches by - expressions, will I{not} match the leading C{"1:1"} in C{"1:10"}; - the expressions are evaluated first, and then compared, so - C{"1"} is compared with C{"10"}. - Do I{not} use with packrat parsing enabled. - """ - rep = Forward() - e2 = expr.copy() - rep <<= e2 - def copyTokenToRepeater(s,l,t): - matchTokens = _flatten(t.asList()) - def mustMatchTheseTokens(s,l,t): - theseTokens = _flatten(t.asList()) - if theseTokens != matchTokens: - raise ParseException("",0,"") - rep.setParseAction( mustMatchTheseTokens, callDuringTry=True ) - expr.addParseAction(copyTokenToRepeater, callDuringTry=True) - rep.setName('(prev) ' + _ustr(expr)) - return rep - -def _escapeRegexRangeChars(s): - #~ escape these chars: ^-] - for c in r"\^-]": - s = s.replace(c,_bslash+c) - s = s.replace("\n",r"\n") - s = s.replace("\t",r"\t") - return _ustr(s) - -def oneOf( strs, caseless=False, useRegex=True ): - """ - Helper to quickly define a set of alternative Literals, and makes sure to do - longest-first testing when there is a conflict, regardless of the input order, - but returns a C{L{MatchFirst}} for best performance. - - Parameters: - - strs - a string of space-delimited literals, or a collection of string literals - - caseless - (default=C{False}) - treat all literals as caseless - - useRegex - (default=C{True}) - as an optimization, will generate a Regex - object; otherwise, will generate a C{MatchFirst} object (if C{caseless=True}, or - if creating a C{Regex} raises an exception) - - Example:: - comp_oper = oneOf("< = > <= >= !=") - var = Word(alphas) - number = Word(nums) - term = var | number - comparison_expr = term + comp_oper + term - print(comparison_expr.searchString("B = 12 AA=23 B<=AA AA>12")) - prints:: - [['B', '=', '12'], ['AA', '=', '23'], ['B', '<=', 'AA'], ['AA', '>', '12']] - """ - if caseless: - isequal = ( lambda a,b: a.upper() == b.upper() ) - masks = ( lambda a,b: b.upper().startswith(a.upper()) ) - parseElementClass = CaselessLiteral - else: - isequal = ( lambda a,b: a == b ) - masks = ( lambda a,b: b.startswith(a) ) - parseElementClass = Literal - - symbols = [] - if isinstance(strs,basestring): - symbols = strs.split() - elif isinstance(strs, Iterable): - symbols = list(strs) - else: - warnings.warn("Invalid argument to oneOf, expected string or iterable", - SyntaxWarning, stacklevel=2) - if not symbols: - return NoMatch() - - i = 0 - while i < len(symbols)-1: - cur = symbols[i] - for j,other in enumerate(symbols[i+1:]): - if ( isequal(other, cur) ): - del symbols[i+j+1] - break - elif ( masks(cur, other) ): - del symbols[i+j+1] - symbols.insert(i,other) - cur = other - break - else: - i += 1 - - if not caseless and useRegex: - #~ print (strs,"->", "|".join( [ _escapeRegexChars(sym) for sym in symbols] )) - try: - if len(symbols)==len("".join(symbols)): - return Regex( "[%s]" % "".join(_escapeRegexRangeChars(sym) for sym in symbols) ).setName(' | '.join(symbols)) - else: - return Regex( "|".join(re.escape(sym) for sym in symbols) ).setName(' | '.join(symbols)) - except Exception: - warnings.warn("Exception creating Regex for oneOf, building MatchFirst", - SyntaxWarning, stacklevel=2) - - - # last resort, just use MatchFirst - return MatchFirst(parseElementClass(sym) for sym in symbols).setName(' | '.join(symbols)) - -def dictOf( key, value ): - """ - Helper to easily and clearly define a dictionary by specifying the respective patterns - for the key and value. Takes care of defining the C{L{Dict}}, C{L{ZeroOrMore}}, and C{L{Group}} tokens - in the proper order. The key pattern can include delimiting markers or punctuation, - as long as they are suppressed, thereby leaving the significant key text. The value - pattern can include named results, so that the C{Dict} results can include named token - fields. - - Example:: - text = "shape: SQUARE posn: upper left color: light blue texture: burlap" - attr_expr = (label + Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join)) - print(OneOrMore(attr_expr).parseString(text).dump()) - - attr_label = label - attr_value = Suppress(':') + OneOrMore(data_word, stopOn=label).setParseAction(' '.join) - - # similar to Dict, but simpler call format - result = dictOf(attr_label, attr_value).parseString(text) - print(result.dump()) - print(result['shape']) - print(result.shape) # object attribute access works too - print(result.asDict()) - prints:: - [['shape', 'SQUARE'], ['posn', 'upper left'], ['color', 'light blue'], ['texture', 'burlap']] - - color: light blue - - posn: upper left - - shape: SQUARE - - texture: burlap - SQUARE - SQUARE - {'color': 'light blue', 'shape': 'SQUARE', 'posn': 'upper left', 'texture': 'burlap'} - """ - return Dict( ZeroOrMore( Group ( key + value ) ) ) - -def originalTextFor(expr, asString=True): - """ - Helper to return the original, untokenized text for a given expression. Useful to - restore the parsed fields of an HTML start tag into the raw tag text itself, or to - revert separate tokens with intervening whitespace back to the original matching - input text. By default, returns astring containing the original parsed text. - - If the optional C{asString} argument is passed as C{False}, then the return value is a - C{L{ParseResults}} containing any results names that were originally matched, and a - single token containing the original matched text from the input string. So if - the expression passed to C{L{originalTextFor}} contains expressions with defined - results names, you must set C{asString} to C{False} if you want to preserve those - results name values. - - Example:: - src = "this is test <b> bold <i>text</i> </b> normal text " - for tag in ("b","i"): - opener,closer = makeHTMLTags(tag) - patt = originalTextFor(opener + SkipTo(closer) + closer) - print(patt.searchString(src)[0]) - prints:: - ['<b> bold <i>text</i> </b>'] - ['<i>text</i>'] - """ - locMarker = Empty().setParseAction(lambda s,loc,t: loc) - endlocMarker = locMarker.copy() - endlocMarker.callPreparse = False - matchExpr = locMarker("_original_start") + expr + endlocMarker("_original_end") - if asString: - extractText = lambda s,l,t: s[t._original_start:t._original_end] - else: - def extractText(s,l,t): - t[:] = [s[t.pop('_original_start'):t.pop('_original_end')]] - matchExpr.setParseAction(extractText) - matchExpr.ignoreExprs = expr.ignoreExprs - return matchExpr - -def ungroup(expr): - """ - Helper to undo pyparsing's default grouping of And expressions, even - if all but one are non-empty. - """ - return TokenConverter(expr).setParseAction(lambda t:t[0]) - -def locatedExpr(expr): - """ - Helper to decorate a returned token with its starting and ending locations in the input string. - This helper adds the following results names: - - locn_start = location where matched expression begins - - locn_end = location where matched expression ends - - value = the actual parsed results - - Be careful if the input text contains C{<TAB>} characters, you may want to call - C{L{ParserElement.parseWithTabs}} - - Example:: - wd = Word(alphas) - for match in locatedExpr(wd).searchString("ljsdf123lksdjjf123lkkjj1222"): - print(match) - prints:: - [[0, 'ljsdf', 5]] - [[8, 'lksdjjf', 15]] - [[18, 'lkkjj', 23]] - """ - locator = Empty().setParseAction(lambda s,l,t: l) - return Group(locator("locn_start") + expr("value") + locator.copy().leaveWhitespace()("locn_end")) - - -# convenience constants for positional expressions -empty = Empty().setName("empty") -lineStart = LineStart().setName("lineStart") -lineEnd = LineEnd().setName("lineEnd") -stringStart = StringStart().setName("stringStart") -stringEnd = StringEnd().setName("stringEnd") - -_escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1]) -_escapedHexChar = Regex(r"\\0?[xX][0-9a-fA-F]+").setParseAction(lambda s,l,t:unichr(int(t[0].lstrip(r'\0x'),16))) -_escapedOctChar = Regex(r"\\0[0-7]+").setParseAction(lambda s,l,t:unichr(int(t[0][1:],8))) -_singleChar = _escapedPunc | _escapedHexChar | _escapedOctChar | CharsNotIn(r'\]', exact=1) -_charRange = Group(_singleChar + Suppress("-") + _singleChar) -_reBracketExpr = Literal("[") + Optional("^").setResultsName("negate") + Group( OneOrMore( _charRange | _singleChar ) ).setResultsName("body") + "]" - -def srange(s): - r""" - Helper to easily define string ranges for use in Word construction. Borrows - syntax from regexp '[]' string range definitions:: - srange("[0-9]") -> "0123456789" - srange("[a-z]") -> "abcdefghijklmnopqrstuvwxyz" - srange("[a-z$_]") -> "abcdefghijklmnopqrstuvwxyz$_" - The input string must be enclosed in []'s, and the returned string is the expanded - character set joined into a single string. - The values enclosed in the []'s may be: - - a single character - - an escaped character with a leading backslash (such as C{\-} or C{\]}) - - an escaped hex character with a leading C{'\x'} (C{\x21}, which is a C{'!'} character) - (C{\0x##} is also supported for backwards compatibility) - - an escaped octal character with a leading C{'\0'} (C{\041}, which is a C{'!'} character) - - a range of any of the above, separated by a dash (C{'a-z'}, etc.) - - any combination of the above (C{'aeiouy'}, C{'a-zA-Z0-9_$'}, etc.) - """ - _expanded = lambda p: p if not isinstance(p,ParseResults) else ''.join(unichr(c) for c in range(ord(p[0]),ord(p[1])+1)) - try: - return "".join(_expanded(part) for part in _reBracketExpr.parseString(s).body) - except Exception: - return "" - -def matchOnlyAtCol(n): - """ - Helper method for defining parse actions that require matching at a specific - column in the input text. - """ - def verifyCol(strg,locn,toks): - if col(locn,strg) != n: - raise ParseException(strg,locn,"matched token not at column %d" % n) - return verifyCol - -def replaceWith(replStr): - """ - Helper method for common parse actions that simply return a literal value. Especially - useful when used with C{L{transformString<ParserElement.transformString>}()}. - - Example:: - num = Word(nums).setParseAction(lambda toks: int(toks[0])) - na = oneOf("N/A NA").setParseAction(replaceWith(math.nan)) - term = na | num - - OneOrMore(term).parseString("324 234 N/A 234") # -> [324, 234, nan, 234] - """ - return lambda s,l,t: [replStr] - -def removeQuotes(s,l,t): - """ - Helper parse action for removing quotation marks from parsed quoted strings. - - Example:: - # by default, quotation marks are included in parsed results - quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["'Now is the Winter of our Discontent'"] - - # use removeQuotes to strip quotation marks from parsed results - quotedString.setParseAction(removeQuotes) - quotedString.parseString("'Now is the Winter of our Discontent'") # -> ["Now is the Winter of our Discontent"] - """ - return t[0][1:-1] - -def tokenMap(func, *args): - """ - Helper to define a parse action by mapping a function to all elements of a ParseResults list.If any additional - args are passed, they are forwarded to the given function as additional arguments after - the token, as in C{hex_integer = Word(hexnums).setParseAction(tokenMap(int, 16))}, which will convert the - parsed data to an integer using base 16. - - Example (compare the last to example in L{ParserElement.transformString}:: - hex_ints = OneOrMore(Word(hexnums)).setParseAction(tokenMap(int, 16)) - hex_ints.runTests(''' - 00 11 22 aa FF 0a 0d 1a - ''') - - upperword = Word(alphas).setParseAction(tokenMap(str.upper)) - OneOrMore(upperword).runTests(''' - my kingdom for a horse - ''') - - wd = Word(alphas).setParseAction(tokenMap(str.title)) - OneOrMore(wd).setParseAction(' '.join).runTests(''' - now is the winter of our discontent made glorious summer by this sun of york - ''') - prints:: - 00 11 22 aa FF 0a 0d 1a - [0, 17, 34, 170, 255, 10, 13, 26] - - my kingdom for a horse - ['MY', 'KINGDOM', 'FOR', 'A', 'HORSE'] - - now is the winter of our discontent made glorious summer by this sun of york - ['Now Is The Winter Of Our Discontent Made Glorious Summer By This Sun Of York'] - """ - def pa(s,l,t): - return [func(tokn, *args) for tokn in t] - - try: - func_name = getattr(func, '__name__', - getattr(func, '__class__').__name__) - except Exception: - func_name = str(func) - pa.__name__ = func_name - - return pa - -upcaseTokens = tokenMap(lambda t: _ustr(t).upper()) -"""(Deprecated) Helper parse action to convert tokens to upper case. Deprecated in favor of L{pyparsing_common.upcaseTokens}""" - -downcaseTokens = tokenMap(lambda t: _ustr(t).lower()) -"""(Deprecated) Helper parse action to convert tokens to lower case. Deprecated in favor of L{pyparsing_common.downcaseTokens}""" - -def _makeTags(tagStr, xml): - """Internal helper to construct opening and closing tag expressions, given a tag name""" - if isinstance(tagStr,basestring): - resname = tagStr - tagStr = Keyword(tagStr, caseless=not xml) - else: - resname = tagStr.name - - tagAttrName = Word(alphas,alphanums+"_-:") - if (xml): - tagAttrValue = dblQuotedString.copy().setParseAction( removeQuotes ) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName + Suppress("=") + tagAttrValue ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - else: - printablesLessRAbrack = "".join(c for c in printables if c not in ">") - tagAttrValue = quotedString.copy().setParseAction( removeQuotes ) | Word(printablesLessRAbrack) - openTag = Suppress("<") + tagStr("tag") + \ - Dict(ZeroOrMore(Group( tagAttrName.setParseAction(downcaseTokens) + \ - Optional( Suppress("=") + tagAttrValue ) ))) + \ - Optional("/",default=[False]).setResultsName("empty").setParseAction(lambda s,l,t:t[0]=='/') + Suppress(">") - closeTag = Combine(_L("</") + tagStr + ">") - - openTag = openTag.setResultsName("start"+"".join(resname.replace(":"," ").title().split())).setName("<%s>" % resname) - closeTag = closeTag.setResultsName("end"+"".join(resname.replace(":"," ").title().split())).setName("</%s>" % resname) - openTag.tag = resname - closeTag.tag = resname - return openTag, closeTag - -def makeHTMLTags(tagStr): - """ - Helper to construct opening and closing tag expressions for HTML, given a tag name. Matches - tags in either upper or lower case, attributes with namespaces and with quoted or unquoted values. - - Example:: - text = '<td>More info at the <a href="http://pyparsing.wikispaces.com">pyparsing</a> wiki page</td>' - # makeHTMLTags returns pyparsing expressions for the opening and closing tags as a 2-tuple - a,a_end = makeHTMLTags("A") - link_expr = a + SkipTo(a_end)("link_text") + a_end - - for link in link_expr.searchString(text): - # attributes in the <A> tag (like "href" shown here) are also accessible as named results - print(link.link_text, '->', link.href) - prints:: - pyparsing -> http://pyparsing.wikispaces.com - """ - return _makeTags( tagStr, False ) - -def makeXMLTags(tagStr): - """ - Helper to construct opening and closing tag expressions for XML, given a tag name. Matches - tags only in the given upper/lower case. - - Example: similar to L{makeHTMLTags} - """ - return _makeTags( tagStr, True ) - -def withAttribute(*args,**attrDict): - """ - Helper to create a validating parse action to be used with start tags created - with C{L{makeXMLTags}} or C{L{makeHTMLTags}}. Use C{withAttribute} to qualify a starting tag - with a required attribute value, to avoid false matches on common tags such as - C{<TD>} or C{<DIV>}. - - Call C{withAttribute} with a series of attribute names and values. Specify the list - of filter attributes names and values as: - - keyword arguments, as in C{(align="right")}, or - - as an explicit dict with C{**} operator, when an attribute name is also a Python - reserved word, as in C{**{"class":"Customer", "align":"right"}} - - a list of name-value tuples, as in ( ("ns1:class", "Customer"), ("ns2:align","right") ) - For attribute names with a namespace prefix, you must use the second form. Attribute - names are matched insensitive to upper/lower case. - - If just testing for C{class} (with or without a namespace), use C{L{withClass}}. - - To verify that the attribute exists, but without specifying a value, pass - C{withAttribute.ANY_VALUE} as the value. - - Example:: - html = ''' - <div> - Some text - <div type="grid">1 4 0 1 0</div> - <div type="graph">1,3 2,3 1,1</div> - <div>this has no type</div> - </div> - - ''' - div,div_end = makeHTMLTags("div") - - # only match div tag having a type attribute with value "grid" - div_grid = div().setParseAction(withAttribute(type="grid")) - grid_expr = div_grid + SkipTo(div | div_end)("body") - for grid_header in grid_expr.searchString(html): - print(grid_header.body) - - # construct a match with any div tag having a type attribute, regardless of the value - div_any_type = div().setParseAction(withAttribute(type=withAttribute.ANY_VALUE)) - div_expr = div_any_type + SkipTo(div | div_end)("body") - for div_header in div_expr.searchString(html): - print(div_header.body) - prints:: - 1 4 0 1 0 - - 1 4 0 1 0 - 1,3 2,3 1,1 - """ - if args: - attrs = args[:] - else: - attrs = attrDict.items() - attrs = [(k,v) for k,v in attrs] - def pa(s,l,tokens): - for attrName,attrValue in attrs: - if attrName not in tokens: - raise ParseException(s,l,"no matching attribute " + attrName) - if attrValue != withAttribute.ANY_VALUE and tokens[attrName] != attrValue: - raise ParseException(s,l,"attribute '%s' has value '%s', must be '%s'" % - (attrName, tokens[attrName], attrValue)) - return pa -withAttribute.ANY_VALUE = object() - -def withClass(classname, namespace=''): - """ - Simplified version of C{L{withAttribute}} when matching on a div class - made - difficult because C{class} is a reserved word in Python. - - Example:: - html = ''' - <div> - Some text - <div class="grid">1 4 0 1 0</div> - <div class="graph">1,3 2,3 1,1</div> - <div>this &lt;div&gt; has no class</div> - </div> - - ''' - div,div_end = makeHTMLTags("div") - div_grid = div().setParseAction(withClass("grid")) - - grid_expr = div_grid + SkipTo(div | div_end)("body") - for grid_header in grid_expr.searchString(html): - print(grid_header.body) - - div_any_type = div().setParseAction(withClass(withAttribute.ANY_VALUE)) - div_expr = div_any_type + SkipTo(div | div_end)("body") - for div_header in div_expr.searchString(html): - print(div_header.body) - prints:: - 1 4 0 1 0 - - 1 4 0 1 0 - 1,3 2,3 1,1 - """ - classattr = "%s:class" % namespace if namespace else "class" - return withAttribute(**{classattr : classname}) - -opAssoc = _Constants() -opAssoc.LEFT = object() -opAssoc.RIGHT = object() - -def infixNotation( baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')') ): - """ - Helper method for constructing grammars of expressions made up of - operators working in a precedence hierarchy. Operators may be unary or - binary, left- or right-associative. Parse actions can also be attached - to operator expressions. The generated parser will also recognize the use - of parentheses to override operator precedences (see example below). - - Note: if you define a deep operator list, you may see performance issues - when using infixNotation. See L{ParserElement.enablePackrat} for a - mechanism to potentially improve your parser performance. - - Parameters: - - baseExpr - expression representing the most basic element for the nested - - opList - list of tuples, one for each operator precedence level in the - expression grammar; each tuple is of the form - (opExpr, numTerms, rightLeftAssoc, parseAction), where: - - opExpr is the pyparsing expression for the operator; - may also be a string, which will be converted to a Literal; - if numTerms is 3, opExpr is a tuple of two expressions, for the - two operators separating the 3 terms - - numTerms is the number of terms for this operator (must - be 1, 2, or 3) - - rightLeftAssoc is the indicator whether the operator is - right or left associative, using the pyparsing-defined - constants C{opAssoc.RIGHT} and C{opAssoc.LEFT}. - - parseAction is the parse action to be associated with - expressions matching this operator expression (the - parse action tuple member may be omitted); if the parse action - is passed a tuple or list of functions, this is equivalent to - calling C{setParseAction(*fn)} (L{ParserElement.setParseAction}) - - lpar - expression for matching left-parentheses (default=C{Suppress('(')}) - - rpar - expression for matching right-parentheses (default=C{Suppress(')')}) - - Example:: - # simple example of four-function arithmetic with ints and variable names - integer = pyparsing_common.signed_integer - varname = pyparsing_common.identifier - - arith_expr = infixNotation(integer | varname, - [ - ('-', 1, opAssoc.RIGHT), - (oneOf('* /'), 2, opAssoc.LEFT), - (oneOf('+ -'), 2, opAssoc.LEFT), - ]) - - arith_expr.runTests(''' - 5+3*6 - (5+3)*6 - -2--11 - ''', fullDump=False) - prints:: - 5+3*6 - [[5, '+', [3, '*', 6]]] - - (5+3)*6 - [[[5, '+', 3], '*', 6]] - - -2--11 - [[['-', 2], '-', ['-', 11]]] - """ - ret = Forward() - lastExpr = baseExpr | ( lpar + ret + rpar ) - for i,operDef in enumerate(opList): - opExpr,arity,rightLeftAssoc,pa = (operDef + (None,))[:4] - termName = "%s term" % opExpr if arity < 3 else "%s%s term" % opExpr - if arity == 3: - if opExpr is None or len(opExpr) != 2: - raise ValueError("if numterms=3, opExpr must be a tuple or list of two expressions") - opExpr1, opExpr2 = opExpr - thisExpr = Forward().setName(termName) - if rightLeftAssoc == opAssoc.LEFT: - if arity == 1: - matchExpr = FollowedBy(lastExpr + opExpr) + Group( lastExpr + OneOrMore( opExpr ) ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + lastExpr) + Group( lastExpr + OneOrMore( opExpr + lastExpr ) ) - else: - matchExpr = FollowedBy(lastExpr+lastExpr) + Group( lastExpr + OneOrMore(lastExpr) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr) + \ - Group( lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - elif rightLeftAssoc == opAssoc.RIGHT: - if arity == 1: - # try to avoid LR with this extra test - if not isinstance(opExpr, Optional): - opExpr = Optional(opExpr) - matchExpr = FollowedBy(opExpr.expr + thisExpr) + Group( opExpr + thisExpr ) - elif arity == 2: - if opExpr is not None: - matchExpr = FollowedBy(lastExpr + opExpr + thisExpr) + Group( lastExpr + OneOrMore( opExpr + thisExpr ) ) - else: - matchExpr = FollowedBy(lastExpr + thisExpr) + Group( lastExpr + OneOrMore( thisExpr ) ) - elif arity == 3: - matchExpr = FollowedBy(lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr) + \ - Group( lastExpr + opExpr1 + thisExpr + opExpr2 + thisExpr ) - else: - raise ValueError("operator must be unary (1), binary (2), or ternary (3)") - else: - raise ValueError("operator must indicate right or left associativity") - if pa: - if isinstance(pa, (tuple, list)): - matchExpr.setParseAction(*pa) - else: - matchExpr.setParseAction(pa) - thisExpr <<= ( matchExpr.setName(termName) | lastExpr ) - lastExpr = thisExpr - ret <<= lastExpr - return ret - -operatorPrecedence = infixNotation -"""(Deprecated) Former name of C{L{infixNotation}}, will be dropped in a future release.""" - -dblQuotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"').setName("string enclosed in double quotes") -sglQuotedString = Combine(Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("string enclosed in single quotes") -quotedString = Combine(Regex(r'"(?:[^"\n\r\\]|(?:"")|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*')+'"'| - Regex(r"'(?:[^'\n\r\\]|(?:'')|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*")+"'").setName("quotedString using single or double quotes") -unicodeString = Combine(_L('u') + quotedString.copy()).setName("unicode string literal") - -def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedString.copy()): - """ - Helper method for defining nested lists enclosed in opening and closing - delimiters ("(" and ")" are the default). - - Parameters: - - opener - opening character for a nested list (default=C{"("}); can also be a pyparsing expression - - closer - closing character for a nested list (default=C{")"}); can also be a pyparsing expression - - content - expression for items within the nested lists (default=C{None}) - - ignoreExpr - expression for ignoring opening and closing delimiters (default=C{quotedString}) - - If an expression is not provided for the content argument, the nested - expression will capture all whitespace-delimited content between delimiters - as a list of separate values. - - Use the C{ignoreExpr} argument to define expressions that may contain - opening or closing characters that should not be treated as opening - or closing characters for nesting, such as quotedString or a comment - expression. Specify multiple expressions using an C{L{Or}} or C{L{MatchFirst}}. - The default is L{quotedString}, but if no expressions are to be ignored, - then pass C{None} for this argument. - - Example:: - data_type = oneOf("void int short long char float double") - decl_data_type = Combine(data_type + Optional(Word('*'))) - ident = Word(alphas+'_', alphanums+'_') - number = pyparsing_common.number - arg = Group(decl_data_type + ident) - LPAR,RPAR = map(Suppress, "()") - - code_body = nestedExpr('{', '}', ignoreExpr=(quotedString | cStyleComment)) - - c_function = (decl_data_type("type") - + ident("name") - + LPAR + Optional(delimitedList(arg), [])("args") + RPAR - + code_body("body")) - c_function.ignore(cStyleComment) - - source_code = ''' - int is_odd(int x) { - return (x%2); - } - - int dec_to_hex(char hchar) { - if (hchar >= '0' && hchar <= '9') { - return (ord(hchar)-ord('0')); - } else { - return (10+ord(hchar)-ord('A')); - } - } - ''' - for func in c_function.searchString(source_code): - print("%(name)s (%(type)s) args: %(args)s" % func) - - prints:: - is_odd (int) args: [['int', 'x']] - dec_to_hex (int) args: [['char', 'hchar']] - """ - if opener == closer: - raise ValueError("opening and closing strings cannot be the same") - if content is None: - if isinstance(opener,basestring) and isinstance(closer,basestring): - if len(opener) == 1 and len(closer)==1: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (empty.copy()+CharsNotIn(opener+closer+ParserElement.DEFAULT_WHITE_CHARS - ).setParseAction(lambda t:t[0].strip())) - else: - if ignoreExpr is not None: - content = (Combine(OneOrMore(~ignoreExpr + - ~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - content = (Combine(OneOrMore(~Literal(opener) + ~Literal(closer) + - CharsNotIn(ParserElement.DEFAULT_WHITE_CHARS,exact=1)) - ).setParseAction(lambda t:t[0].strip())) - else: - raise ValueError("opening and closing arguments must be strings if no content expression is given") - ret = Forward() - if ignoreExpr is not None: - ret <<= Group( Suppress(opener) + ZeroOrMore( ignoreExpr | ret | content ) + Suppress(closer) ) - else: - ret <<= Group( Suppress(opener) + ZeroOrMore( ret | content ) + Suppress(closer) ) - ret.setName('nested %s%s expression' % (opener,closer)) - return ret - -def indentedBlock(blockStatementExpr, indentStack, indent=True): - """ - Helper method for defining space-delimited indentation blocks, such as - those used to define block statements in Python source code. - - Parameters: - - blockStatementExpr - expression defining syntax of statement that - is repeated within the indented block - - indentStack - list created by caller to manage indentation stack - (multiple statementWithIndentedBlock expressions within a single grammar - should share a common indentStack) - - indent - boolean indicating whether block must be indented beyond the - the current level; set to False for block of left-most statements - (default=C{True}) - - A valid block must contain at least one C{blockStatement}. - - Example:: - data = ''' - def A(z): - A1 - B = 100 - G = A2 - A2 - A3 - B - def BB(a,b,c): - BB1 - def BBA(): - bba1 - bba2 - bba3 - C - D - def spam(x,y): - def eggs(z): - pass - ''' - - - indentStack = [1] - stmt = Forward() - - identifier = Word(alphas, alphanums) - funcDecl = ("def" + identifier + Group( "(" + Optional( delimitedList(identifier) ) + ")" ) + ":") - func_body = indentedBlock(stmt, indentStack) - funcDef = Group( funcDecl + func_body ) - - rvalue = Forward() - funcCall = Group(identifier + "(" + Optional(delimitedList(rvalue)) + ")") - rvalue << (funcCall | identifier | Word(nums)) - assignment = Group(identifier + "=" + rvalue) - stmt << ( funcDef | assignment | identifier ) - - module_body = OneOrMore(stmt) - - parseTree = module_body.parseString(data) - parseTree.pprint() - prints:: - [['def', - 'A', - ['(', 'z', ')'], - ':', - [['A1'], [['B', '=', '100']], [['G', '=', 'A2']], ['A2'], ['A3']]], - 'B', - ['def', - 'BB', - ['(', 'a', 'b', 'c', ')'], - ':', - [['BB1'], [['def', 'BBA', ['(', ')'], ':', [['bba1'], ['bba2'], ['bba3']]]]]], - 'C', - 'D', - ['def', - 'spam', - ['(', 'x', 'y', ')'], - ':', - [[['def', 'eggs', ['(', 'z', ')'], ':', [['pass']]]]]]] - """ - def checkPeerIndent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if curCol != indentStack[-1]: - if curCol > indentStack[-1]: - raise ParseFatalException(s,l,"illegal nesting") - raise ParseException(s,l,"not a peer entry") - - def checkSubIndent(s,l,t): - curCol = col(l,s) - if curCol > indentStack[-1]: - indentStack.append( curCol ) - else: - raise ParseException(s,l,"not a subentry") - - def checkUnindent(s,l,t): - if l >= len(s): return - curCol = col(l,s) - if not(indentStack and curCol < indentStack[-1] and curCol <= indentStack[-2]): - raise ParseException(s,l,"not an unindent") - indentStack.pop() - - NL = OneOrMore(LineEnd().setWhitespaceChars("\t ").suppress()) - INDENT = (Empty() + Empty().setParseAction(checkSubIndent)).setName('INDENT') - PEER = Empty().setParseAction(checkPeerIndent).setName('') - UNDENT = Empty().setParseAction(checkUnindent).setName('UNINDENT') - if indent: - smExpr = Group( Optional(NL) + - #~ FollowedBy(blockStatementExpr) + - INDENT + (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) + UNDENT) - else: - smExpr = Group( Optional(NL) + - (OneOrMore( PEER + Group(blockStatementExpr) + Optional(NL) )) ) - blockStatementExpr.ignore(_bslash + LineEnd()) - return smExpr.setName('indented block') - -alphas8bit = srange(r"[\0xc0-\0xd6\0xd8-\0xf6\0xf8-\0xff]") -punc8bit = srange(r"[\0xa1-\0xbf\0xd7\0xf7]") - -anyOpenTag,anyCloseTag = makeHTMLTags(Word(alphas,alphanums+"_:").setName('any tag')) -_htmlEntityMap = dict(zip("gt lt amp nbsp quot apos".split(),'><& "\'')) -commonHTMLEntity = Regex('&(?P<entity>' + '|'.join(_htmlEntityMap.keys()) +");").setName("common HTML entity") -def replaceHTMLEntity(t): - """Helper parser action to replace common HTML entities with their special characters""" - return _htmlEntityMap.get(t.entity) - -# it's easy to get these comment structures wrong - they're very common, so may as well make them available -cStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/').setName("C style comment") -"Comment of the form C{/* ... */}" - -htmlComment = Regex(r"<!--[\s\S]*?-->").setName("HTML comment") -"Comment of the form C{<!-- ... -->}" - -restOfLine = Regex(r".*").leaveWhitespace().setName("rest of line") -dblSlashComment = Regex(r"//(?:\\\n|[^\n])*").setName("// comment") -"Comment of the form C{// ... (to end of line)}" - -cppStyleComment = Combine(Regex(r"/\*(?:[^*]|\*(?!/))*") + '*/'| dblSlashComment).setName("C++ style comment") -"Comment of either form C{L{cStyleComment}} or C{L{dblSlashComment}}" - -javaStyleComment = cppStyleComment -"Same as C{L{cppStyleComment}}" - -pythonStyleComment = Regex(r"#.*").setName("Python style comment") -"Comment of the form C{# ... (to end of line)}" - -_commasepitem = Combine(OneOrMore(Word(printables, excludeChars=',') + - Optional( Word(" \t") + - ~Literal(",") + ~LineEnd() ) ) ).streamline().setName("commaItem") -commaSeparatedList = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("commaSeparatedList") -"""(Deprecated) Predefined expression of 1 or more printable words or quoted strings, separated by commas. - This expression is deprecated in favor of L{pyparsing_common.comma_separated_list}.""" - -# some other useful expressions - using lower-case class name since we are really using this as a namespace -class pyparsing_common: - """ - Here are some common low-level expressions that may be useful in jump-starting parser development: - - numeric forms (L{integers<integer>}, L{reals<real>}, L{scientific notation<sci_real>}) - - common L{programming identifiers<identifier>} - - network addresses (L{MAC<mac_address>}, L{IPv4<ipv4_address>}, L{IPv6<ipv6_address>}) - - ISO8601 L{dates<iso8601_date>} and L{datetime<iso8601_datetime>} - - L{UUID<uuid>} - - L{comma-separated list<comma_separated_list>} - Parse actions: - - C{L{convertToInteger}} - - C{L{convertToFloat}} - - C{L{convertToDate}} - - C{L{convertToDatetime}} - - C{L{stripHTMLTags}} - - C{L{upcaseTokens}} - - C{L{downcaseTokens}} - - Example:: - pyparsing_common.number.runTests(''' - # any int or real number, returned as the appropriate type - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - ''') - - pyparsing_common.fnumber.runTests(''' - # any int or real number, returned as float - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - ''') - - pyparsing_common.hex_integer.runTests(''' - # hex numbers - 100 - FF - ''') - - pyparsing_common.fraction.runTests(''' - # fractions - 1/2 - -3/4 - ''') - - pyparsing_common.mixed_integer.runTests(''' - # mixed fractions - 1 - 1/2 - -3/4 - 1-3/4 - ''') - - import uuid - pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) - pyparsing_common.uuid.runTests(''' - # uuid - 12345678-1234-5678-1234-567812345678 - ''') - prints:: - # any int or real number, returned as the appropriate type - 100 - [100] - - -100 - [-100] - - +100 - [100] - - 3.14159 - [3.14159] - - 6.02e23 - [6.02e+23] - - 1e-12 - [1e-12] - - # any int or real number, returned as float - 100 - [100.0] - - -100 - [-100.0] - - +100 - [100.0] - - 3.14159 - [3.14159] - - 6.02e23 - [6.02e+23] - - 1e-12 - [1e-12] - - # hex numbers - 100 - [256] - - FF - [255] - - # fractions - 1/2 - [0.5] - - -3/4 - [-0.75] - - # mixed fractions - 1 - [1] - - 1/2 - [0.5] - - -3/4 - [-0.75] - - 1-3/4 - [1.75] - - # uuid - 12345678-1234-5678-1234-567812345678 - [UUID('12345678-1234-5678-1234-567812345678')] - """ - - convertToInteger = tokenMap(int) - """ - Parse action for converting parsed integers to Python int - """ - - convertToFloat = tokenMap(float) - """ - Parse action for converting parsed numbers to Python float - """ - - integer = Word(nums).setName("integer").setParseAction(convertToInteger) - """expression that parses an unsigned integer, returns an int""" - - hex_integer = Word(hexnums).setName("hex integer").setParseAction(tokenMap(int,16)) - """expression that parses a hexadecimal integer, returns an int""" - - signed_integer = Regex(r'[+-]?\d+').setName("signed integer").setParseAction(convertToInteger) - """expression that parses an integer with optional leading sign, returns an int""" - - fraction = (signed_integer().setParseAction(convertToFloat) + '/' + signed_integer().setParseAction(convertToFloat)).setName("fraction") - """fractional expression of an integer divided by an integer, returns a float""" - fraction.addParseAction(lambda t: t[0]/t[-1]) - - mixed_integer = (fraction | signed_integer + Optional(Optional('-').suppress() + fraction)).setName("fraction or mixed integer-fraction") - """mixed integer of the form 'integer - fraction', with optional leading integer, returns float""" - mixed_integer.addParseAction(sum) - - real = Regex(r'[+-]?\d+\.\d*').setName("real number").setParseAction(convertToFloat) - """expression that parses a floating point number and returns a float""" - - sci_real = Regex(r'[+-]?\d+([eE][+-]?\d+|\.\d*([eE][+-]?\d+)?)').setName("real number with scientific notation").setParseAction(convertToFloat) - """expression that parses a floating point number with optional scientific notation and returns a float""" - - # streamlining this expression makes the docs nicer-looking - number = (sci_real | real | signed_integer).streamline() - """any numeric expression, returns the corresponding Python type""" - - fnumber = Regex(r'[+-]?\d+\.?\d*([eE][+-]?\d+)?').setName("fnumber").setParseAction(convertToFloat) - """any int or real number, returned as float""" - - identifier = Word(alphas+'_', alphanums+'_').setName("identifier") - """typical code identifier (leading alpha or '_', followed by 0 or more alphas, nums, or '_')""" - - ipv4_address = Regex(r'(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})){3}').setName("IPv4 address") - "IPv4 address (C{0.0.0.0 - 255.255.255.255})" - - _ipv6_part = Regex(r'[0-9a-fA-F]{1,4}').setName("hex_integer") - _full_ipv6_address = (_ipv6_part + (':' + _ipv6_part)*7).setName("full IPv6 address") - _short_ipv6_address = (Optional(_ipv6_part + (':' + _ipv6_part)*(0,6)) + "::" + Optional(_ipv6_part + (':' + _ipv6_part)*(0,6))).setName("short IPv6 address") - _short_ipv6_address.addCondition(lambda t: sum(1 for tt in t if pyparsing_common._ipv6_part.matches(tt)) < 8) - _mixed_ipv6_address = ("::ffff:" + ipv4_address).setName("mixed IPv6 address") - ipv6_address = Combine((_full_ipv6_address | _mixed_ipv6_address | _short_ipv6_address).setName("IPv6 address")).setName("IPv6 address") - "IPv6 address (long, short, or mixed form)" - - mac_address = Regex(r'[0-9a-fA-F]{2}([:.-])[0-9a-fA-F]{2}(?:\1[0-9a-fA-F]{2}){4}').setName("MAC address") - "MAC address xx:xx:xx:xx:xx (may also have '-' or '.' delimiters)" - - @staticmethod - def convertToDate(fmt="%Y-%m-%d"): - """ - Helper to create a parse action for converting parsed date string to Python datetime.date - - Params - - - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%d"}) - - Example:: - date_expr = pyparsing_common.iso8601_date.copy() - date_expr.setParseAction(pyparsing_common.convertToDate()) - print(date_expr.parseString("1999-12-31")) - prints:: - [datetime.date(1999, 12, 31)] - """ - def cvt_fn(s,l,t): - try: - return datetime.strptime(t[0], fmt).date() - except ValueError as ve: - raise ParseException(s, l, str(ve)) - return cvt_fn - - @staticmethod - def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"): - """ - Helper to create a parse action for converting parsed datetime string to Python datetime.datetime - - Params - - - fmt - format to be passed to datetime.strptime (default=C{"%Y-%m-%dT%H:%M:%S.%f"}) - - Example:: - dt_expr = pyparsing_common.iso8601_datetime.copy() - dt_expr.setParseAction(pyparsing_common.convertToDatetime()) - print(dt_expr.parseString("1999-12-31T23:59:59.999")) - prints:: - [datetime.datetime(1999, 12, 31, 23, 59, 59, 999000)] - """ - def cvt_fn(s,l,t): - try: - return datetime.strptime(t[0], fmt) - except ValueError as ve: - raise ParseException(s, l, str(ve)) - return cvt_fn - - iso8601_date = Regex(r'(?P<year>\d{4})(?:-(?P<month>\d\d)(?:-(?P<day>\d\d))?)?').setName("ISO8601 date") - "ISO8601 date (C{yyyy-mm-dd})" - - iso8601_datetime = Regex(r'(?P<year>\d{4})-(?P<month>\d\d)-(?P<day>\d\d)[T ](?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d(\.\d*)?)?)?(?P<tz>Z|[+-]\d\d:?\d\d)?').setName("ISO8601 datetime") - "ISO8601 datetime (C{yyyy-mm-ddThh:mm:ss.s(Z|+-00:00)}) - trailing seconds, milliseconds, and timezone optional; accepts separating C{'T'} or C{' '}" - - uuid = Regex(r'[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}').setName("UUID") - "UUID (C{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx})" - - _html_stripper = anyOpenTag.suppress() | anyCloseTag.suppress() - @staticmethod - def stripHTMLTags(s, l, tokens): - """ - Parse action to remove HTML tags from web page HTML source - - Example:: - # strip HTML links from normal text - text = '<td>More info at the <a href="http://pyparsing.wikispaces.com">pyparsing</a> wiki page</td>' - td,td_end = makeHTMLTags("TD") - table_text = td + SkipTo(td_end).setParseAction(pyparsing_common.stripHTMLTags)("body") + td_end - - print(table_text.parseString(text).body) # -> 'More info at the pyparsing wiki page' - """ - return pyparsing_common._html_stripper.transformString(tokens[0]) - - _commasepitem = Combine(OneOrMore(~Literal(",") + ~LineEnd() + Word(printables, excludeChars=',') - + Optional( White(" \t") ) ) ).streamline().setName("commaItem") - comma_separated_list = delimitedList( Optional( quotedString.copy() | _commasepitem, default="") ).setName("comma separated list") - """Predefined expression of 1 or more printable words or quoted strings, separated by commas.""" - - upcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).upper())) - """Parse action to convert tokens to upper case.""" - - downcaseTokens = staticmethod(tokenMap(lambda t: _ustr(t).lower())) - """Parse action to convert tokens to lower case.""" - - -if __name__ == "__main__": - - selectToken = CaselessLiteral("select") - fromToken = CaselessLiteral("from") - - ident = Word(alphas, alphanums + "_$") - - columnName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) - columnNameList = Group(delimitedList(columnName)).setName("columns") - columnSpec = ('*' | columnNameList) - - tableName = delimitedList(ident, ".", combine=True).setParseAction(upcaseTokens) - tableNameList = Group(delimitedList(tableName)).setName("tables") - - simpleSQL = selectToken("command") + columnSpec("columns") + fromToken + tableNameList("tables") - - # demo runTests method, including embedded comments in test string - simpleSQL.runTests(""" - # '*' as column list and dotted table name - select * from SYS.XYZZY - - # caseless match on "SELECT", and casts back to "select" - SELECT * from XYZZY, ABC - - # list of column names, and mixed case SELECT keyword - Select AA,BB,CC from Sys.dual - - # multiple tables - Select A, B, C from Sys.dual, Table2 - - # invalid SELECT keyword - should fail - Xelect A, B, C from Sys.dual - - # incomplete command - should fail - Select - - # invalid column name - should fail - Select ^^^ frox Sys.dual - - """) - - pyparsing_common.number.runTests(""" - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - """) - - # any int or real number, returned as float - pyparsing_common.fnumber.runTests(""" - 100 - -100 - +100 - 3.14159 - 6.02e23 - 1e-12 - """) - - pyparsing_common.hex_integer.runTests(""" - 100 - FF - """) - - import uuid - pyparsing_common.uuid.setParseAction(tokenMap(uuid.UUID)) - pyparsing_common.uuid.runTests(""" - 12345678-1234-5678-1234-567812345678 - """) diff --git a/lib/setuptools/_vendor/six.py b/lib/setuptools/_vendor/six.py deleted file mode 100644 index 190c023..0000000 --- a/lib/setuptools/_vendor/six.py +++ /dev/null @@ -1,868 +0,0 @@ -"""Utilities for writing code that runs on Python 2 and 3""" - -# Copyright (c) 2010-2015 Benjamin Peterson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import absolute_import - -import functools -import itertools -import operator -import sys -import types - -__author__ = "Benjamin Peterson <benjamin@python.org>" -__version__ = "1.10.0" - - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -PY34 = sys.version_info[0:2] >= (3, 4) - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) # Invokes __set__. - try: - # This is a bit ugly, but it avoids running this again by - # removing this descriptor. - delattr(obj.__class__, self.name) - except AttributeError: - pass - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - def __getattr__(self, attr): - _module = self._resolve() - value = getattr(_module, attr) - setattr(self, attr, value) - return value - - -class _LazyModule(types.ModuleType): - - def __init__(self, name): - super(_LazyModule, self).__init__(name) - self.__doc__ = self.__class__.__doc__ - - def __dir__(self): - attrs = ["__doc__", "__name__"] - attrs += [attr.name for attr in self._moved_attributes] - return attrs - - # Subclasses should override this - _moved_attributes = [] - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _SixMetaPathImporter(object): - - """ - A meta path importer to import six.moves and its submodules. - - This class implements a PEP302 finder and loader. It should be compatible - with Python 2.5 and all existing versions of Python3 - """ - - def __init__(self, six_module_name): - self.name = six_module_name - self.known_modules = {} - - def _add_module(self, mod, *fullnames): - for fullname in fullnames: - self.known_modules[self.name + "." + fullname] = mod - - def _get_module(self, fullname): - return self.known_modules[self.name + "." + fullname] - - def find_module(self, fullname, path=None): - if fullname in self.known_modules: - return self - return None - - def __get_module(self, fullname): - try: - return self.known_modules[fullname] - except KeyError: - raise ImportError("This loader does not know module " + fullname) - - def load_module(self, fullname): - try: - # in case of a reload - return sys.modules[fullname] - except KeyError: - pass - mod = self.__get_module(fullname) - if isinstance(mod, MovedModule): - mod = mod._resolve() - else: - mod.__loader__ = self - sys.modules[fullname] = mod - return mod - - def is_package(self, fullname): - """ - Return true, if the named module is a package. - - We need this method to get correct spec objects with - Python 3.4 (see PEP451) - """ - return hasattr(self.__get_module(fullname), "__path__") - - def get_code(self, fullname): - """Return None - - Required, if is_package is implemented""" - self.__get_module(fullname) # eventually raises ImportError - return None - get_source = get_code # same as get_code - -_importer = _SixMetaPathImporter(__name__) - - -class _MovedItems(_LazyModule): - - """Lazy loading of moved objects""" - __path__ = [] # mark as package - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("intern", "__builtin__", "sys"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), - MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), - MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("UserDict", "UserDict", "collections"), - MovedAttribute("UserList", "UserList", "collections"), - MovedAttribute("UserString", "UserString", "collections"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("copyreg", "copy_reg"), - MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("_thread", "thread", "_thread"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), - MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), - MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), - MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), -] -# Add windows specific modules. -if sys.platform == "win32": - _moved_attributes += [ - MovedModule("winreg", "_winreg"), - ] - -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) - if isinstance(attr, MovedModule): - _importer._add_module(attr, "moves." + attr.name) -del attr - -_MovedItems._moved_attributes = _moved_attributes - -moves = _MovedItems(__name__ + ".moves") -_importer._add_module(moves, "moves") - - -class Module_six_moves_urllib_parse(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_parse""" - - -_urllib_parse_moved_attributes = [ - MovedAttribute("ParseResult", "urlparse", "urllib.parse"), - MovedAttribute("SplitResult", "urlparse", "urllib.parse"), - MovedAttribute("parse_qs", "urlparse", "urllib.parse"), - MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), - MovedAttribute("urldefrag", "urlparse", "urllib.parse"), - MovedAttribute("urljoin", "urlparse", "urllib.parse"), - MovedAttribute("urlparse", "urlparse", "urllib.parse"), - MovedAttribute("urlsplit", "urlparse", "urllib.parse"), - MovedAttribute("urlunparse", "urlparse", "urllib.parse"), - MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), - MovedAttribute("quote", "urllib", "urllib.parse"), - MovedAttribute("quote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote", "urllib", "urllib.parse"), - MovedAttribute("unquote_plus", "urllib", "urllib.parse"), - MovedAttribute("urlencode", "urllib", "urllib.parse"), - MovedAttribute("splitquery", "urllib", "urllib.parse"), - MovedAttribute("splittag", "urllib", "urllib.parse"), - MovedAttribute("splituser", "urllib", "urllib.parse"), - MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), - MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), - MovedAttribute("uses_params", "urlparse", "urllib.parse"), - MovedAttribute("uses_query", "urlparse", "urllib.parse"), - MovedAttribute("uses_relative", "urlparse", "urllib.parse"), -] -for attr in _urllib_parse_moved_attributes: - setattr(Module_six_moves_urllib_parse, attr.name, attr) -del attr - -Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes - -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") - - -class Module_six_moves_urllib_error(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_error""" - - -_urllib_error_moved_attributes = [ - MovedAttribute("URLError", "urllib2", "urllib.error"), - MovedAttribute("HTTPError", "urllib2", "urllib.error"), - MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), -] -for attr in _urllib_error_moved_attributes: - setattr(Module_six_moves_urllib_error, attr.name, attr) -del attr - -Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes - -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") - - -class Module_six_moves_urllib_request(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_request""" - - -_urllib_request_moved_attributes = [ - MovedAttribute("urlopen", "urllib2", "urllib.request"), - MovedAttribute("install_opener", "urllib2", "urllib.request"), - MovedAttribute("build_opener", "urllib2", "urllib.request"), - MovedAttribute("pathname2url", "urllib", "urllib.request"), - MovedAttribute("url2pathname", "urllib", "urllib.request"), - MovedAttribute("getproxies", "urllib", "urllib.request"), - MovedAttribute("Request", "urllib2", "urllib.request"), - MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), - MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), - MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), - MovedAttribute("BaseHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), - MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), - MovedAttribute("FileHandler", "urllib2", "urllib.request"), - MovedAttribute("FTPHandler", "urllib2", "urllib.request"), - MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), - MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), - MovedAttribute("urlretrieve", "urllib", "urllib.request"), - MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), - MovedAttribute("proxy_bypass", "urllib", "urllib.request"), -] -for attr in _urllib_request_moved_attributes: - setattr(Module_six_moves_urllib_request, attr.name, attr) -del attr - -Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes - -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") - - -class Module_six_moves_urllib_response(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_response""" - - -_urllib_response_moved_attributes = [ - MovedAttribute("addbase", "urllib", "urllib.response"), - MovedAttribute("addclosehook", "urllib", "urllib.response"), - MovedAttribute("addinfo", "urllib", "urllib.response"), - MovedAttribute("addinfourl", "urllib", "urllib.response"), -] -for attr in _urllib_response_moved_attributes: - setattr(Module_six_moves_urllib_response, attr.name, attr) -del attr - -Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes - -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") - - -class Module_six_moves_urllib_robotparser(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_robotparser""" - - -_urllib_robotparser_moved_attributes = [ - MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), -] -for attr in _urllib_robotparser_moved_attributes: - setattr(Module_six_moves_urllib_robotparser, attr.name, attr) -del attr - -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes - -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") - - -class Module_six_moves_urllib(types.ModuleType): - - """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" - __path__ = [] # mark as package - parse = _importer._get_module("moves.urllib_parse") - error = _importer._get_module("moves.urllib_error") - request = _importer._get_module("moves.urllib_request") - response = _importer._get_module("moves.urllib_response") - robotparser = _importer._get_module("moves.urllib_robotparser") - - def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] - -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - - -if PY3: - def get_unbound_function(unbound): - return unbound - - create_bound_method = types.MethodType - - def create_unbound_method(func, cls): - return func - - Iterator = object -else: - def get_unbound_function(unbound): - return unbound.im_func - - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - - def create_unbound_method(func, cls): - return types.MethodType(func, None, cls) - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -if PY3: - def iterkeys(d, **kw): - return iter(d.keys(**kw)) - - def itervalues(d, **kw): - return iter(d.values(**kw)) - - def iteritems(d, **kw): - return iter(d.items(**kw)) - - def iterlists(d, **kw): - return iter(d.lists(**kw)) - - viewkeys = operator.methodcaller("keys") - - viewvalues = operator.methodcaller("values") - - viewitems = operator.methodcaller("items") -else: - def iterkeys(d, **kw): - return d.iterkeys(**kw) - - def itervalues(d, **kw): - return d.itervalues(**kw) - - def iteritems(d, **kw): - return d.iteritems(**kw) - - def iterlists(d, **kw): - return d.iterlists(**kw) - - viewkeys = operator.methodcaller("viewkeys") - - viewvalues = operator.methodcaller("viewvalues") - - viewitems = operator.methodcaller("viewitems") - -_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") -_add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - unichr = chr - import struct - int2byte = struct.Struct(">B").pack - del struct - byte2int = operator.itemgetter(0) - indexbytes = operator.getitem - iterbytes = iter - import io - StringIO = io.StringIO - BytesIO = io.BytesIO - _assertCountEqual = "assertCountEqual" - if sys.version_info[1] <= 1: - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - else: - _assertRaisesRegex = "assertRaisesRegex" - _assertRegex = "assertRegex" -else: - def b(s): - return s - # Workaround for standalone backslash - - def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") - unichr = unichr - int2byte = chr - - def byte2int(bs): - return ord(bs[0]) - - def indexbytes(buf, i): - return ord(buf[i]) - iterbytes = functools.partial(itertools.imap, ord) - import StringIO - StringIO = BytesIO = StringIO.StringIO - _assertCountEqual = "assertItemsEqual" - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -def assertCountEqual(self, *args, **kwargs): - return getattr(self, _assertCountEqual)(*args, **kwargs) - - -def assertRaisesRegex(self, *args, **kwargs): - return getattr(self, _assertRaisesRegex)(*args, **kwargs) - - -def assertRegex(self, *args, **kwargs): - return getattr(self, _assertRegex)(*args, **kwargs) - - -if PY3: - exec_ = getattr(moves.builtins, "exec") - - def reraise(tp, value, tb=None): - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") - - -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - if from_value is None: - raise value - raise value from from_value -""") -elif sys.version_info[:2] > (3, 2): - exec_("""def raise_from(value, from_value): - raise value from from_value -""") -else: - def raise_from(value, from_value): - raise value - - -print_ = getattr(moves.builtins, "print", None) -if print_ is None: - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(fp.encoding, errors) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) -if sys.version_info[:2] < (3, 3): - _print = print_ - - def print_(*args, **kwargs): - fp = kwargs.get("file", sys.stdout) - flush = kwargs.pop("flush", False) - _print(*args, **kwargs) - if flush and fp is not None: - fp.flush() - -_add_doc(reraise, """Reraise an exception.""") - -if sys.version_info[0:2] < (3, 4): - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f - return wrapper -else: - wraps = functools.wraps - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(meta): - - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -def python_2_unicode_compatible(klass): - """ - A decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - """ - if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass - - -# Complete the moves implementation. -# This code is at the end of this module to speed up module loading. -# Turn this module into a package. -__path__ = [] # required for PEP 302 and PEP 451 -__package__ = __name__ # see PEP 366 @ReservedAssignment -if globals().get("__spec__") is not None: - __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable -# Remove other six meta path importers, since they cause problems. This can -# happen if six is removed from sys.modules and then reloaded. (Setuptools does -# this for some reason.) -if sys.meta_path: - for i, importer in enumerate(sys.meta_path): - # Here's some real nastiness: Another "instance" of the six module might - # be floating around. Therefore, we can't use isinstance() to check for - # the six meta path importer, since the other six instance will have - # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): - del sys.meta_path[i] - break - del i, importer -# Finally, add the importer to the meta path import hook. -sys.meta_path.append(_importer) diff --git a/lib/setuptools/archive_util.py b/lib/setuptools/archive_util.py deleted file mode 100644 index 8143604..0000000 --- a/lib/setuptools/archive_util.py +++ /dev/null @@ -1,173 +0,0 @@ -"""Utilities for extracting common archive formats""" - -import zipfile -import tarfile -import os -import shutil -import posixpath -import contextlib -from distutils.errors import DistutilsError - -from pkg_resources import ensure_directory - -__all__ = [ - "unpack_archive", "unpack_zipfile", "unpack_tarfile", "default_filter", - "UnrecognizedFormat", "extraction_drivers", "unpack_directory", -] - - -class UnrecognizedFormat(DistutilsError): - """Couldn't recognize the archive type""" - - -def default_filter(src, dst): - """The default progress/filter callback; returns True for all files""" - return dst - - -def unpack_archive(filename, extract_dir, progress_filter=default_filter, - drivers=None): - """Unpack `filename` to `extract_dir`, or raise ``UnrecognizedFormat`` - - `progress_filter` is a function taking two arguments: a source path - internal to the archive ('/'-separated), and a filesystem path where it - will be extracted. The callback must return the desired extract path - (which may be the same as the one passed in), or else ``None`` to skip - that file or directory. The callback can thus be used to report on the - progress of the extraction, as well as to filter the items extracted or - alter their extraction paths. - - `drivers`, if supplied, must be a non-empty sequence of functions with the - same signature as this function (minus the `drivers` argument), that raise - ``UnrecognizedFormat`` if they do not support extracting the designated - archive type. The `drivers` are tried in sequence until one is found that - does not raise an error, or until all are exhausted (in which case - ``UnrecognizedFormat`` is raised). If you do not supply a sequence of - drivers, the module's ``extraction_drivers`` constant will be used, which - means that ``unpack_zipfile`` and ``unpack_tarfile`` will be tried, in that - order. - """ - for driver in drivers or extraction_drivers: - try: - driver(filename, extract_dir, progress_filter) - except UnrecognizedFormat: - continue - else: - return - else: - raise UnrecognizedFormat( - "Not a recognized archive type: %s" % filename - ) - - -def unpack_directory(filename, extract_dir, progress_filter=default_filter): - """"Unpack" a directory, using the same interface as for archives - - Raises ``UnrecognizedFormat`` if `filename` is not a directory - """ - if not os.path.isdir(filename): - raise UnrecognizedFormat("%s is not a directory" % filename) - - paths = { - filename: ('', extract_dir), - } - for base, dirs, files in os.walk(filename): - src, dst = paths[base] - for d in dirs: - paths[os.path.join(base, d)] = src + d + '/', os.path.join(dst, d) - for f in files: - target = os.path.join(dst, f) - target = progress_filter(src + f, target) - if not target: - # skip non-files - continue - ensure_directory(target) - f = os.path.join(base, f) - shutil.copyfile(f, target) - shutil.copystat(f, target) - - -def unpack_zipfile(filename, extract_dir, progress_filter=default_filter): - """Unpack zip `filename` to `extract_dir` - - Raises ``UnrecognizedFormat`` if `filename` is not a zipfile (as determined - by ``zipfile.is_zipfile()``). See ``unpack_archive()`` for an explanation - of the `progress_filter` argument. - """ - - if not zipfile.is_zipfile(filename): - raise UnrecognizedFormat("%s is not a zip file" % (filename,)) - - with zipfile.ZipFile(filename) as z: - for info in z.infolist(): - name = info.filename - - # don't extract absolute paths or ones with .. in them - if name.startswith('/') or '..' in name.split('/'): - continue - - target = os.path.join(extract_dir, *name.split('/')) - target = progress_filter(name, target) - if not target: - continue - if name.endswith('/'): - # directory - ensure_directory(target) - else: - # file - ensure_directory(target) - data = z.read(info.filename) - with open(target, 'wb') as f: - f.write(data) - unix_attributes = info.external_attr >> 16 - if unix_attributes: - os.chmod(target, unix_attributes) - - -def unpack_tarfile(filename, extract_dir, progress_filter=default_filter): - """Unpack tar/tar.gz/tar.bz2 `filename` to `extract_dir` - - Raises ``UnrecognizedFormat`` if `filename` is not a tarfile (as determined - by ``tarfile.open()``). See ``unpack_archive()`` for an explanation - of the `progress_filter` argument. - """ - try: - tarobj = tarfile.open(filename) - except tarfile.TarError: - raise UnrecognizedFormat( - "%s is not a compressed or uncompressed tar file" % (filename,) - ) - with contextlib.closing(tarobj): - # don't do any chowning! - tarobj.chown = lambda *args: None - for member in tarobj: - name = member.name - # don't extract absolute paths or ones with .. in them - if not name.startswith('/') and '..' not in name.split('/'): - prelim_dst = os.path.join(extract_dir, *name.split('/')) - - # resolve any links and to extract the link targets as normal - # files - while member is not None and (member.islnk() or member.issym()): - linkpath = member.linkname - if member.issym(): - base = posixpath.dirname(member.name) - linkpath = posixpath.join(base, linkpath) - linkpath = posixpath.normpath(linkpath) - member = tarobj._getmember(linkpath) - - if member is not None and (member.isfile() or member.isdir()): - final_dst = progress_filter(name, prelim_dst) - if final_dst: - if final_dst.endswith(os.sep): - final_dst = final_dst[:-1] - try: - # XXX Ugh - tarobj._extract_member(member, final_dst) - except tarfile.ExtractError: - # chown/chmod/mkfifo/mknode/makedev failed - pass - return True - - -extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile diff --git a/lib/setuptools/build_meta.py b/lib/setuptools/build_meta.py deleted file mode 100644 index 0067a7a..0000000 --- a/lib/setuptools/build_meta.py +++ /dev/null @@ -1,182 +0,0 @@ -"""A PEP 517 interface to setuptools - -Previously, when a user or a command line tool (let's call it a "frontend") -needed to make a request of setuptools to take a certain action, for -example, generating a list of installation requirements, the frontend would -would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line. - -PEP 517 defines a different method of interfacing with setuptools. Rather -than calling "setup.py" directly, the frontend should: - - 1. Set the current directory to the directory with a setup.py file - 2. Import this module into a safe python interpreter (one in which - setuptools can potentially set global variables or crash hard). - 3. Call one of the functions defined in PEP 517. - -What each function does is defined in PEP 517. However, here is a "casual" -definition of the functions (this definition should not be relied on for -bug reports or API stability): - - - `build_wheel`: build a wheel in the folder and return the basename - - `get_requires_for_build_wheel`: get the `setup_requires` to build - - `prepare_metadata_for_build_wheel`: get the `install_requires` - - `build_sdist`: build an sdist in the folder and return the basename - - `get_requires_for_build_sdist`: get the `setup_requires` to build - -Again, this is not a formal definition! Just a "taste" of the module. -""" - -import os -import sys -import tokenize -import shutil -import contextlib - -import setuptools -import distutils - - -class SetupRequirementsError(BaseException): - def __init__(self, specifiers): - self.specifiers = specifiers - - -class Distribution(setuptools.dist.Distribution): - def fetch_build_eggs(self, specifiers): - raise SetupRequirementsError(specifiers) - - @classmethod - @contextlib.contextmanager - def patch(cls): - """ - Replace - distutils.dist.Distribution with this class - for the duration of this context. - """ - orig = distutils.core.Distribution - distutils.core.Distribution = cls - try: - yield - finally: - distutils.core.Distribution = orig - - -def _to_str(s): - """ - Convert a filename to a string (on Python 2, explicitly - a byte string, not Unicode) as distutils checks for the - exact type str. - """ - if sys.version_info[0] == 2 and not isinstance(s, str): - # Assume it's Unicode, as that's what the PEP says - # should be provided. - return s.encode(sys.getfilesystemencoding()) - return s - - -def _run_setup(setup_script='setup.py'): - # Note that we can reuse our build directory between calls - # Correctness comes first, then optimization later - __file__ = setup_script - __name__ = '__main__' - f = getattr(tokenize, 'open', open)(__file__) - code = f.read().replace('\\r\\n', '\\n') - f.close() - exec(compile(code, __file__, 'exec'), locals()) - - -def _fix_config(config_settings): - config_settings = config_settings or {} - config_settings.setdefault('--global-option', []) - return config_settings - - -def _get_build_requires(config_settings, requirements): - config_settings = _fix_config(config_settings) - - sys.argv = sys.argv[:1] + ['egg_info'] + \ - config_settings["--global-option"] - try: - with Distribution.patch(): - _run_setup() - except SetupRequirementsError as e: - requirements += e.specifiers - - return requirements - - -def _get_immediate_subdirectories(a_dir): - return [name for name in os.listdir(a_dir) - if os.path.isdir(os.path.join(a_dir, name))] - - -def get_requires_for_build_wheel(config_settings=None): - config_settings = _fix_config(config_settings) - return _get_build_requires(config_settings, requirements=['setuptools', 'wheel']) - - -def get_requires_for_build_sdist(config_settings=None): - config_settings = _fix_config(config_settings) - return _get_build_requires(config_settings, requirements=['setuptools']) - - -def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): - sys.argv = sys.argv[:1] + ['dist_info', '--egg-base', _to_str(metadata_directory)] - _run_setup() - - dist_info_directory = metadata_directory - while True: - dist_infos = [f for f in os.listdir(dist_info_directory) - if f.endswith('.dist-info')] - - if len(dist_infos) == 0 and \ - len(_get_immediate_subdirectories(dist_info_directory)) == 1: - dist_info_directory = os.path.join( - dist_info_directory, os.listdir(dist_info_directory)[0]) - continue - - assert len(dist_infos) == 1 - break - - # PEP 517 requires that the .dist-info directory be placed in the - # metadata_directory. To comply, we MUST copy the directory to the root - if dist_info_directory != metadata_directory: - shutil.move( - os.path.join(dist_info_directory, dist_infos[0]), - metadata_directory) - shutil.rmtree(dist_info_directory, ignore_errors=True) - - return dist_infos[0] - - -def build_wheel(wheel_directory, config_settings=None, - metadata_directory=None): - config_settings = _fix_config(config_settings) - wheel_directory = os.path.abspath(wheel_directory) - sys.argv = sys.argv[:1] + ['bdist_wheel'] + \ - config_settings["--global-option"] - _run_setup() - if wheel_directory != 'dist': - shutil.rmtree(wheel_directory) - shutil.copytree('dist', wheel_directory) - - wheels = [f for f in os.listdir(wheel_directory) - if f.endswith('.whl')] - - assert len(wheels) == 1 - return wheels[0] - - -def build_sdist(sdist_directory, config_settings=None): - config_settings = _fix_config(config_settings) - sdist_directory = os.path.abspath(sdist_directory) - sys.argv = sys.argv[:1] + ['sdist'] + \ - config_settings["--global-option"] + \ - ["--dist-dir", sdist_directory] - _run_setup() - - sdists = [f for f in os.listdir(sdist_directory) - if f.endswith('.tar.gz')] - - assert len(sdists) == 1 - return sdists[0] diff --git a/lib/setuptools/cli-32.exe b/lib/setuptools/cli-32.exe deleted file mode 100644 index b1487b7819e7286577a043c7726fbe0ca1543083..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65536 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 diff --git a/lib/setuptools/cli-64.exe b/lib/setuptools/cli-64.exe deleted file mode 100644 index 675e6bf3743f3d3011c238657e7128ee9960ef7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74752 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 diff --git a/lib/setuptools/cli.exe b/lib/setuptools/cli.exe deleted file mode 100644 index b1487b7819e7286577a043c7726fbe0ca1543083..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65536 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 diff --git a/lib/setuptools/command/__init__.py b/lib/setuptools/command/__init__.py deleted file mode 100644 index fe619e2..0000000 --- a/lib/setuptools/command/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -__all__ = [ - 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop', - 'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts', - 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts', - 'register', 'bdist_wininst', 'upload_docs', 'upload', 'build_clib', - 'dist_info', -] - -from distutils.command.bdist import bdist -import sys - -from setuptools.command import install_scripts - -if 'egg' not in bdist.format_commands: - bdist.format_command['egg'] = ('bdist_egg', "Python .egg file") - bdist.format_commands.append('egg') - -del bdist, sys diff --git a/lib/setuptools/command/alias.py b/lib/setuptools/command/alias.py deleted file mode 100644 index 4532b1c..0000000 --- a/lib/setuptools/command/alias.py +++ /dev/null @@ -1,80 +0,0 @@ -from distutils.errors import DistutilsOptionError - -from setuptools.extern.six.moves import map - -from setuptools.command.setopt import edit_config, option_base, config_file - - -def shquote(arg): - """Quote an argument for later parsing by shlex.split()""" - for c in '"', "'", "\\", "#": - if c in arg: - return repr(arg) - if arg.split() != [arg]: - return repr(arg) - return arg - - -class alias(option_base): - """Define a shortcut that invokes one or more commands""" - - description = "define a shortcut to invoke one or more commands" - command_consumes_arguments = True - - user_options = [ - ('remove', 'r', 'remove (unset) the alias'), - ] + option_base.user_options - - boolean_options = option_base.boolean_options + ['remove'] - - def initialize_options(self): - option_base.initialize_options(self) - self.args = None - self.remove = None - - def finalize_options(self): - option_base.finalize_options(self) - if self.remove and len(self.args) != 1: - raise DistutilsOptionError( - "Must specify exactly one argument (the alias name) when " - "using --remove" - ) - - def run(self): - aliases = self.distribution.get_option_dict('aliases') - - if not self.args: - print("Command Aliases") - print("---------------") - for alias in aliases: - print("setup.py alias", format_alias(alias, aliases)) - return - - elif len(self.args) == 1: - alias, = self.args - if self.remove: - command = None - elif alias in aliases: - print("setup.py alias", format_alias(alias, aliases)) - return - else: - print("No alias definition found for %r" % alias) - return - else: - alias = self.args[0] - command = ' '.join(map(shquote, self.args[1:])) - - edit_config(self.filename, {'aliases': {alias: command}}, self.dry_run) - - -def format_alias(name, aliases): - source, command = aliases[name] - if source == config_file('global'): - source = '--global-config ' - elif source == config_file('user'): - source = '--user-config ' - elif source == config_file('local'): - source = '' - else: - source = '--filename=%r' % source - return source + name + ' ' + command diff --git a/lib/setuptools/command/bdist_egg.py b/lib/setuptools/command/bdist_egg.py deleted file mode 100644 index 9f8df91..0000000 --- a/lib/setuptools/command/bdist_egg.py +++ /dev/null @@ -1,502 +0,0 @@ -"""setuptools.command.bdist_egg - -Build .egg distributions""" - -from distutils.errors import DistutilsSetupError -from distutils.dir_util import remove_tree, mkpath -from distutils import log -from types import CodeType -import sys -import os -import re -import textwrap -import marshal - -from setuptools.extern import six - -from pkg_resources import get_build_platform, Distribution, ensure_directory -from pkg_resources import EntryPoint -from setuptools.extension import Library -from setuptools import Command - -try: - # Python 2.7 or >=3.2 - from sysconfig import get_path, get_python_version - - def _get_purelib(): - return get_path("purelib") -except ImportError: - from distutils.sysconfig import get_python_lib, get_python_version - - def _get_purelib(): - return get_python_lib(False) - - -def strip_module(filename): - if '.' in filename: - filename = os.path.splitext(filename)[0] - if filename.endswith('module'): - filename = filename[:-6] - return filename - - -def sorted_walk(dir): - """Do os.walk in a reproducible way, - independent of indeterministic filesystem readdir order - """ - for base, dirs, files in os.walk(dir): - dirs.sort() - files.sort() - yield base, dirs, files - - -def write_stub(resource, pyfile): - _stub_template = textwrap.dedent(""" - def __bootstrap__(): - global __bootstrap__, __loader__, __file__ - import sys, pkg_resources, imp - __file__ = pkg_resources.resource_filename(__name__, %r) - __loader__ = None; del __bootstrap__, __loader__ - imp.load_dynamic(__name__,__file__) - __bootstrap__() - """).lstrip() - with open(pyfile, 'w') as f: - f.write(_stub_template % resource) - - -class bdist_egg(Command): - description = "create an \"egg\" distribution" - - user_options = [ - ('bdist-dir=', 'b', - "temporary directory for creating the distribution"), - ('plat-name=', 'p', "platform name to embed in generated filenames " - "(default: %s)" % get_build_platform()), - ('exclude-source-files', None, - "remove all .py files from the generated egg"), - ('keep-temp', 'k', - "keep the pseudo-installation tree around after " + - "creating the distribution archive"), - ('dist-dir=', 'd', - "directory to put final built distributions in"), - ('skip-build', None, - "skip rebuilding everything (for testing/debugging)"), - ] - - boolean_options = [ - 'keep-temp', 'skip-build', 'exclude-source-files' - ] - - def initialize_options(self): - self.bdist_dir = None - self.plat_name = None - self.keep_temp = 0 - self.dist_dir = None - self.skip_build = 0 - self.egg_output = None - self.exclude_source_files = None - - def finalize_options(self): - ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info") - self.egg_info = ei_cmd.egg_info - - if self.bdist_dir is None: - bdist_base = self.get_finalized_command('bdist').bdist_base - self.bdist_dir = os.path.join(bdist_base, 'egg') - - if self.plat_name is None: - self.plat_name = get_build_platform() - - self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) - - if self.egg_output is None: - - # Compute filename of the output egg - basename = Distribution( - None, None, ei_cmd.egg_name, ei_cmd.egg_version, - get_python_version(), - self.distribution.has_ext_modules() and self.plat_name - ).egg_name() - - self.egg_output = os.path.join(self.dist_dir, basename + '.egg') - - def do_install_data(self): - # Hack for packages that install data to install's --install-lib - self.get_finalized_command('install').install_lib = self.bdist_dir - - site_packages = os.path.normcase(os.path.realpath(_get_purelib())) - old, self.distribution.data_files = self.distribution.data_files, [] - - for item in old: - if isinstance(item, tuple) and len(item) == 2: - if os.path.isabs(item[0]): - realpath = os.path.realpath(item[0]) - normalized = os.path.normcase(realpath) - if normalized == site_packages or normalized.startswith( - site_packages + os.sep - ): - item = realpath[len(site_packages) + 1:], item[1] - # XXX else: raise ??? - self.distribution.data_files.append(item) - - try: - log.info("installing package data to %s", self.bdist_dir) - self.call_command('install_data', force=0, root=None) - finally: - self.distribution.data_files = old - - def get_outputs(self): - return [self.egg_output] - - def call_command(self, cmdname, **kw): - """Invoke reinitialized command `cmdname` with keyword args""" - for dirname in INSTALL_DIRECTORY_ATTRS: - kw.setdefault(dirname, self.bdist_dir) - kw.setdefault('skip_build', self.skip_build) - kw.setdefault('dry_run', self.dry_run) - cmd = self.reinitialize_command(cmdname, **kw) - self.run_command(cmdname) - return cmd - - def run(self): - # Generate metadata first - self.run_command("egg_info") - # We run install_lib before install_data, because some data hacks - # pull their data path from the install_lib command. - log.info("installing library code to %s", self.bdist_dir) - instcmd = self.get_finalized_command('install') - old_root = instcmd.root - instcmd.root = None - if self.distribution.has_c_libraries() and not self.skip_build: - self.run_command('build_clib') - cmd = self.call_command('install_lib', warn_dir=0) - instcmd.root = old_root - - all_outputs, ext_outputs = self.get_ext_outputs() - self.stubs = [] - to_compile = [] - for (p, ext_name) in enumerate(ext_outputs): - filename, ext = os.path.splitext(ext_name) - pyfile = os.path.join(self.bdist_dir, strip_module(filename) + - '.py') - self.stubs.append(pyfile) - log.info("creating stub loader for %s", ext_name) - if not self.dry_run: - write_stub(os.path.basename(ext_name), pyfile) - to_compile.append(pyfile) - ext_outputs[p] = ext_name.replace(os.sep, '/') - - if to_compile: - cmd.byte_compile(to_compile) - if self.distribution.data_files: - self.do_install_data() - - # Make the EGG-INFO directory - archive_root = self.bdist_dir - egg_info = os.path.join(archive_root, 'EGG-INFO') - self.mkpath(egg_info) - if self.distribution.scripts: - script_dir = os.path.join(egg_info, 'scripts') - log.info("installing scripts to %s", script_dir) - self.call_command('install_scripts', install_dir=script_dir, - no_ep=1) - - self.copy_metadata_to(egg_info) - native_libs = os.path.join(egg_info, "native_libs.txt") - if all_outputs: - log.info("writing %s", native_libs) - if not self.dry_run: - ensure_directory(native_libs) - libs_file = open(native_libs, 'wt') - libs_file.write('\n'.join(all_outputs)) - libs_file.write('\n') - libs_file.close() - elif os.path.isfile(native_libs): - log.info("removing %s", native_libs) - if not self.dry_run: - os.unlink(native_libs) - - write_safety_flag( - os.path.join(archive_root, 'EGG-INFO'), self.zip_safe() - ) - - if os.path.exists(os.path.join(self.egg_info, 'depends.txt')): - log.warn( - "WARNING: 'depends.txt' will not be used by setuptools 0.6!\n" - "Use the install_requires/extras_require setup() args instead." - ) - - if self.exclude_source_files: - self.zap_pyfiles() - - # Make the archive - make_zipfile(self.egg_output, archive_root, verbose=self.verbose, - dry_run=self.dry_run, mode=self.gen_header()) - if not self.keep_temp: - remove_tree(self.bdist_dir, dry_run=self.dry_run) - - # Add to 'Distribution.dist_files' so that the "upload" command works - getattr(self.distribution, 'dist_files', []).append( - ('bdist_egg', get_python_version(), self.egg_output)) - - def zap_pyfiles(self): - log.info("Removing .py files from temporary directory") - for base, dirs, files in walk_egg(self.bdist_dir): - for name in files: - path = os.path.join(base, name) - - if name.endswith('.py'): - log.debug("Deleting %s", path) - os.unlink(path) - - if base.endswith('__pycache__'): - path_old = path - - pattern = r'(?P<name>.+)\.(?P<magic>[^.]+)\.pyc' - m = re.match(pattern, name) - path_new = os.path.join( - base, os.pardir, m.group('name') + '.pyc') - log.info( - "Renaming file from [%s] to [%s]" - % (path_old, path_new)) - try: - os.remove(path_new) - except OSError: - pass - os.rename(path_old, path_new) - - def zip_safe(self): - safe = getattr(self.distribution, 'zip_safe', None) - if safe is not None: - return safe - log.warn("zip_safe flag not set; analyzing archive contents...") - return analyze_egg(self.bdist_dir, self.stubs) - - def gen_header(self): - epm = EntryPoint.parse_map(self.distribution.entry_points or '') - ep = epm.get('setuptools.installation', {}).get('eggsecutable') - if ep is None: - return 'w' # not an eggsecutable, do it the usual way. - - if not ep.attrs or ep.extras: - raise DistutilsSetupError( - "eggsecutable entry point (%r) cannot have 'extras' " - "or refer to a module" % (ep,) - ) - - pyver = sys.version[:3] - pkg = ep.module_name - full = '.'.join(ep.attrs) - base = ep.attrs[0] - basename = os.path.basename(self.egg_output) - - header = ( - "#!/bin/sh\n" - 'if [ `basename $0` = "%(basename)s" ]\n' - 'then exec python%(pyver)s -c "' - "import sys, os; sys.path.insert(0, os.path.abspath('$0')); " - "from %(pkg)s import %(base)s; sys.exit(%(full)s())" - '" "$@"\n' - 'else\n' - ' echo $0 is not the correct name for this egg file.\n' - ' echo Please rename it back to %(basename)s and try again.\n' - ' exec false\n' - 'fi\n' - ) % locals() - - if not self.dry_run: - mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run) - f = open(self.egg_output, 'w') - f.write(header) - f.close() - return 'a' - - def copy_metadata_to(self, target_dir): - "Copy metadata (egg info) to the target_dir" - # normalize the path (so that a forward-slash in egg_info will - # match using startswith below) - norm_egg_info = os.path.normpath(self.egg_info) - prefix = os.path.join(norm_egg_info, '') - for path in self.ei_cmd.filelist.files: - if path.startswith(prefix): - target = os.path.join(target_dir, path[len(prefix):]) - ensure_directory(target) - self.copy_file(path, target) - - def get_ext_outputs(self): - """Get a list of relative paths to C extensions in the output distro""" - - all_outputs = [] - ext_outputs = [] - - paths = {self.bdist_dir: ''} - for base, dirs, files in sorted_walk(self.bdist_dir): - for filename in files: - if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS: - all_outputs.append(paths[base] + filename) - for filename in dirs: - paths[os.path.join(base, filename)] = (paths[base] + - filename + '/') - - if self.distribution.has_ext_modules(): - build_cmd = self.get_finalized_command('build_ext') - for ext in build_cmd.extensions: - if isinstance(ext, Library): - continue - fullname = build_cmd.get_ext_fullname(ext.name) - filename = build_cmd.get_ext_filename(fullname) - if not os.path.basename(filename).startswith('dl-'): - if os.path.exists(os.path.join(self.bdist_dir, filename)): - ext_outputs.append(filename) - - return all_outputs, ext_outputs - - -NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split()) - - -def walk_egg(egg_dir): - """Walk an unpacked egg's contents, skipping the metadata directory""" - walker = sorted_walk(egg_dir) - base, dirs, files = next(walker) - if 'EGG-INFO' in dirs: - dirs.remove('EGG-INFO') - yield base, dirs, files - for bdf in walker: - yield bdf - - -def analyze_egg(egg_dir, stubs): - # check for existing flag in EGG-INFO - for flag, fn in safety_flags.items(): - if os.path.exists(os.path.join(egg_dir, 'EGG-INFO', fn)): - return flag - if not can_scan(): - return False - safe = True - for base, dirs, files in walk_egg(egg_dir): - for name in files: - if name.endswith('.py') or name.endswith('.pyw'): - continue - elif name.endswith('.pyc') or name.endswith('.pyo'): - # always scan, even if we already know we're not safe - safe = scan_module(egg_dir, base, name, stubs) and safe - return safe - - -def write_safety_flag(egg_dir, safe): - # Write or remove zip safety flag file(s) - for flag, fn in safety_flags.items(): - fn = os.path.join(egg_dir, fn) - if os.path.exists(fn): - if safe is None or bool(safe) != flag: - os.unlink(fn) - elif safe is not None and bool(safe) == flag: - f = open(fn, 'wt') - f.write('\n') - f.close() - - -safety_flags = { - True: 'zip-safe', - False: 'not-zip-safe', -} - - -def scan_module(egg_dir, base, name, stubs): - """Check whether module possibly uses unsafe-for-zipfile stuff""" - - filename = os.path.join(base, name) - if filename[:-1] in stubs: - return True # Extension module - pkg = base[len(egg_dir) + 1:].replace(os.sep, '.') - module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0] - if six.PY2: - skip = 8 # skip magic & date - elif sys.version_info < (3, 7): - skip = 12 # skip magic & date & file size - else: - skip = 16 # skip magic & reserved? & date & file size - f = open(filename, 'rb') - f.read(skip) - code = marshal.load(f) - f.close() - safe = True - symbols = dict.fromkeys(iter_symbols(code)) - for bad in ['__file__', '__path__']: - if bad in symbols: - log.warn("%s: module references %s", module, bad) - safe = False - if 'inspect' in symbols: - for bad in [ - 'getsource', 'getabsfile', 'getsourcefile', 'getfile' - 'getsourcelines', 'findsource', 'getcomments', 'getframeinfo', - 'getinnerframes', 'getouterframes', 'stack', 'trace' - ]: - if bad in symbols: - log.warn("%s: module MAY be using inspect.%s", module, bad) - safe = False - return safe - - -def iter_symbols(code): - """Yield names and strings used by `code` and its nested code objects""" - for name in code.co_names: - yield name - for const in code.co_consts: - if isinstance(const, six.string_types): - yield const - elif isinstance(const, CodeType): - for name in iter_symbols(const): - yield name - - -def can_scan(): - if not sys.platform.startswith('java') and sys.platform != 'cli': - # CPython, PyPy, etc. - return True - log.warn("Unable to analyze compiled code on this platform.") - log.warn("Please ask the author to include a 'zip_safe'" - " setting (either True or False) in the package's setup.py") - - -# Attribute names of options for commands that might need to be convinced to -# install to the egg build directory - -INSTALL_DIRECTORY_ATTRS = [ - 'install_lib', 'install_dir', 'install_data', 'install_base' -] - - -def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True, - mode='w'): - """Create a zip file from all the files under 'base_dir'. The output - zip file will be named 'base_dir' + ".zip". Uses either the "zipfile" - Python module (if available) or the InfoZIP "zip" utility (if installed - and found on the default search path). If neither tool is available, - raises DistutilsExecError. Returns the name of the output zip file. - """ - import zipfile - - mkpath(os.path.dirname(zip_filename), dry_run=dry_run) - log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir) - - def visit(z, dirname, names): - for name in names: - path = os.path.normpath(os.path.join(dirname, name)) - if os.path.isfile(path): - p = path[len(base_dir) + 1:] - if not dry_run: - z.write(path, p) - log.debug("adding '%s'", p) - - compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED - if not dry_run: - z = zipfile.ZipFile(zip_filename, mode, compression=compression) - for dirname, dirs, files in sorted_walk(base_dir): - visit(z, dirname, files) - z.close() - else: - for dirname, dirs, files in sorted_walk(base_dir): - visit(None, dirname, files) - return zip_filename diff --git a/lib/setuptools/command/bdist_rpm.py b/lib/setuptools/command/bdist_rpm.py deleted file mode 100644 index 7073092..0000000 --- a/lib/setuptools/command/bdist_rpm.py +++ /dev/null @@ -1,43 +0,0 @@ -import distutils.command.bdist_rpm as orig - - -class bdist_rpm(orig.bdist_rpm): - """ - Override the default bdist_rpm behavior to do the following: - - 1. Run egg_info to ensure the name and version are properly calculated. - 2. Always run 'install' using --single-version-externally-managed to - disable eggs in RPM distributions. - 3. Replace dash with underscore in the version numbers for better RPM - compatibility. - """ - - def run(self): - # ensure distro name is up-to-date - self.run_command('egg_info') - - orig.bdist_rpm.run(self) - - def _make_spec_file(self): - version = self.distribution.get_version() - rpmversion = version.replace('-', '_') - spec = orig.bdist_rpm._make_spec_file(self) - line23 = '%define version ' + version - line24 = '%define version ' + rpmversion - spec = [ - line.replace( - "Source0: %{name}-%{version}.tar", - "Source0: %{name}-%{unmangled_version}.tar" - ).replace( - "setup.py install ", - "setup.py install --single-version-externally-managed " - ).replace( - "%setup", - "%setup -n %{name}-%{unmangled_version}" - ).replace(line23, line24) - for line in spec - ] - insert_loc = spec.index(line24) + 1 - unmangled_version = "%define unmangled_version " + version - spec.insert(insert_loc, unmangled_version) - return spec diff --git a/lib/setuptools/command/bdist_wininst.py b/lib/setuptools/command/bdist_wininst.py deleted file mode 100644 index 073de97..0000000 --- a/lib/setuptools/command/bdist_wininst.py +++ /dev/null @@ -1,21 +0,0 @@ -import distutils.command.bdist_wininst as orig - - -class bdist_wininst(orig.bdist_wininst): - def reinitialize_command(self, command, reinit_subcommands=0): - """ - Supplement reinitialize_command to work around - http://bugs.python.org/issue20819 - """ - cmd = self.distribution.reinitialize_command( - command, reinit_subcommands) - if command in ('install', 'install_lib'): - cmd.install_lib = None - return cmd - - def run(self): - self._is_running = True - try: - orig.bdist_wininst.run(self) - finally: - self._is_running = False diff --git a/lib/setuptools/command/build_clib.py b/lib/setuptools/command/build_clib.py deleted file mode 100644 index 09caff6..0000000 --- a/lib/setuptools/command/build_clib.py +++ /dev/null @@ -1,98 +0,0 @@ -import distutils.command.build_clib as orig -from distutils.errors import DistutilsSetupError -from distutils import log -from setuptools.dep_util import newer_pairwise_group - - -class build_clib(orig.build_clib): - """ - Override the default build_clib behaviour to do the following: - - 1. Implement a rudimentary timestamp-based dependency system - so 'compile()' doesn't run every time. - 2. Add more keys to the 'build_info' dictionary: - * obj_deps - specify dependencies for each object compiled. - this should be a dictionary mapping a key - with the source filename to a list of - dependencies. Use an empty string for global - dependencies. - * cflags - specify a list of additional flags to pass to - the compiler. - """ - - def build_libraries(self, libraries): - for (lib_name, build_info) in libraries: - sources = build_info.get('sources') - if sources is None or not isinstance(sources, (list, tuple)): - raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'sources' must be present and must be " - "a list of source filenames" % lib_name) - sources = list(sources) - - log.info("building '%s' library", lib_name) - - # Make sure everything is the correct type. - # obj_deps should be a dictionary of keys as sources - # and a list/tuple of files that are its dependencies. - obj_deps = build_info.get('obj_deps', dict()) - if not isinstance(obj_deps, dict): - raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'obj_deps' must be a dictionary of " - "type 'source: list'" % lib_name) - dependencies = [] - - # Get the global dependencies that are specified by the '' key. - # These will go into every source's dependency list. - global_deps = obj_deps.get('', list()) - if not isinstance(global_deps, (list, tuple)): - raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'obj_deps' must be a dictionary of " - "type 'source: list'" % lib_name) - - # Build the list to be used by newer_pairwise_group - # each source will be auto-added to its dependencies. - for source in sources: - src_deps = [source] - src_deps.extend(global_deps) - extra_deps = obj_deps.get(source, list()) - if not isinstance(extra_deps, (list, tuple)): - raise DistutilsSetupError( - "in 'libraries' option (library '%s'), " - "'obj_deps' must be a dictionary of " - "type 'source: list'" % lib_name) - src_deps.extend(extra_deps) - dependencies.append(src_deps) - - expected_objects = self.compiler.object_filenames( - sources, - output_dir=self.build_temp - ) - - if newer_pairwise_group(dependencies, expected_objects) != ([], []): - # First, compile the source code to object files in the library - # directory. (This should probably change to putting object - # files in a temporary build directory.) - macros = build_info.get('macros') - include_dirs = build_info.get('include_dirs') - cflags = build_info.get('cflags') - objects = self.compiler.compile( - sources, - output_dir=self.build_temp, - macros=macros, - include_dirs=include_dirs, - extra_postargs=cflags, - debug=self.debug - ) - - # Now "link" the object files together into a static library. - # (On Unix at least, this isn't really linking -- it just - # builds an archive. Whatever.) - self.compiler.create_static_lib( - expected_objects, - lib_name, - output_dir=self.build_clib, - debug=self.debug - ) diff --git a/lib/setuptools/command/build_ext.py b/lib/setuptools/command/build_ext.py deleted file mode 100644 index 60a8a32..0000000 --- a/lib/setuptools/command/build_ext.py +++ /dev/null @@ -1,321 +0,0 @@ -import os -import sys -import itertools -import imp -from distutils.command.build_ext import build_ext as _du_build_ext -from distutils.file_util import copy_file -from distutils.ccompiler import new_compiler -from distutils.sysconfig import customize_compiler, get_config_var -from distutils.errors import DistutilsError -from distutils import log - -from setuptools.extension import Library -from setuptools.extern import six - -try: - # Attempt to use Cython for building extensions, if available - from Cython.Distutils.build_ext import build_ext as _build_ext - # Additionally, assert that the compiler module will load - # also. Ref #1229. - __import__('Cython.Compiler.Main') -except ImportError: - _build_ext = _du_build_ext - -# make sure _config_vars is initialized -get_config_var("LDSHARED") -from distutils.sysconfig import _config_vars as _CONFIG_VARS - - -def _customize_compiler_for_shlib(compiler): - if sys.platform == "darwin": - # building .dylib requires additional compiler flags on OSX; here we - # temporarily substitute the pyconfig.h variables so that distutils' - # 'customize_compiler' uses them before we build the shared libraries. - tmp = _CONFIG_VARS.copy() - try: - # XXX Help! I don't have any idea whether these are right... - _CONFIG_VARS['LDSHARED'] = ( - "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup") - _CONFIG_VARS['CCSHARED'] = " -dynamiclib" - _CONFIG_VARS['SO'] = ".dylib" - customize_compiler(compiler) - finally: - _CONFIG_VARS.clear() - _CONFIG_VARS.update(tmp) - else: - customize_compiler(compiler) - - -have_rtld = False -use_stubs = False -libtype = 'shared' - -if sys.platform == "darwin": - use_stubs = True -elif os.name != 'nt': - try: - import dl - use_stubs = have_rtld = hasattr(dl, 'RTLD_NOW') - except ImportError: - pass - -if_dl = lambda s: s if have_rtld else '' - - -def get_abi3_suffix(): - """Return the file extension for an abi3-compliant Extension()""" - for suffix, _, _ in (s for s in imp.get_suffixes() if s[2] == imp.C_EXTENSION): - if '.abi3' in suffix: # Unix - return suffix - elif suffix == '.pyd': # Windows - return suffix - - -class build_ext(_build_ext): - def run(self): - """Build extensions in build directory, then copy if --inplace""" - old_inplace, self.inplace = self.inplace, 0 - _build_ext.run(self) - self.inplace = old_inplace - if old_inplace: - self.copy_extensions_to_source() - - def copy_extensions_to_source(self): - build_py = self.get_finalized_command('build_py') - for ext in self.extensions: - fullname = self.get_ext_fullname(ext.name) - filename = self.get_ext_filename(fullname) - modpath = fullname.split('.') - package = '.'.join(modpath[:-1]) - package_dir = build_py.get_package_dir(package) - dest_filename = os.path.join(package_dir, - os.path.basename(filename)) - src_filename = os.path.join(self.build_lib, filename) - - # Always copy, even if source is older than destination, to ensure - # that the right extensions for the current Python/platform are - # used. - copy_file( - src_filename, dest_filename, verbose=self.verbose, - dry_run=self.dry_run - ) - if ext._needs_stub: - self.write_stub(package_dir or os.curdir, ext, True) - - def get_ext_filename(self, fullname): - filename = _build_ext.get_ext_filename(self, fullname) - if fullname in self.ext_map: - ext = self.ext_map[fullname] - use_abi3 = ( - six.PY3 - and getattr(ext, 'py_limited_api') - and get_abi3_suffix() - ) - if use_abi3: - so_ext = get_config_var('EXT_SUFFIX') - filename = filename[:-len(so_ext)] - filename = filename + get_abi3_suffix() - if isinstance(ext, Library): - fn, ext = os.path.splitext(filename) - return self.shlib_compiler.library_filename(fn, libtype) - elif use_stubs and ext._links_to_dynamic: - d, fn = os.path.split(filename) - return os.path.join(d, 'dl-' + fn) - return filename - - def initialize_options(self): - _build_ext.initialize_options(self) - self.shlib_compiler = None - self.shlibs = [] - self.ext_map = {} - - def finalize_options(self): - _build_ext.finalize_options(self) - self.extensions = self.extensions or [] - self.check_extensions_list(self.extensions) - self.shlibs = [ext for ext in self.extensions - if isinstance(ext, Library)] - if self.shlibs: - self.setup_shlib_compiler() - for ext in self.extensions: - ext._full_name = self.get_ext_fullname(ext.name) - for ext in self.extensions: - fullname = ext._full_name - self.ext_map[fullname] = ext - - # distutils 3.1 will also ask for module names - # XXX what to do with conflicts? - self.ext_map[fullname.split('.')[-1]] = ext - - ltd = self.shlibs and self.links_to_dynamic(ext) or False - ns = ltd and use_stubs and not isinstance(ext, Library) - ext._links_to_dynamic = ltd - ext._needs_stub = ns - filename = ext._file_name = self.get_ext_filename(fullname) - libdir = os.path.dirname(os.path.join(self.build_lib, filename)) - if ltd and libdir not in ext.library_dirs: - ext.library_dirs.append(libdir) - if ltd and use_stubs and os.curdir not in ext.runtime_library_dirs: - ext.runtime_library_dirs.append(os.curdir) - - def setup_shlib_compiler(self): - compiler = self.shlib_compiler = new_compiler( - compiler=self.compiler, dry_run=self.dry_run, force=self.force - ) - _customize_compiler_for_shlib(compiler) - - if self.include_dirs is not None: - compiler.set_include_dirs(self.include_dirs) - if self.define is not None: - # 'define' option is a list of (name,value) tuples - for (name, value) in self.define: - compiler.define_macro(name, value) - if self.undef is not None: - for macro in self.undef: - compiler.undefine_macro(macro) - if self.libraries is not None: - compiler.set_libraries(self.libraries) - if self.library_dirs is not None: - compiler.set_library_dirs(self.library_dirs) - if self.rpath is not None: - compiler.set_runtime_library_dirs(self.rpath) - if self.link_objects is not None: - compiler.set_link_objects(self.link_objects) - - # hack so distutils' build_extension() builds a library instead - compiler.link_shared_object = link_shared_object.__get__(compiler) - - def get_export_symbols(self, ext): - if isinstance(ext, Library): - return ext.export_symbols - return _build_ext.get_export_symbols(self, ext) - - def build_extension(self, ext): - ext._convert_pyx_sources_to_lang() - _compiler = self.compiler - try: - if isinstance(ext, Library): - self.compiler = self.shlib_compiler - _build_ext.build_extension(self, ext) - if ext._needs_stub: - cmd = self.get_finalized_command('build_py').build_lib - self.write_stub(cmd, ext) - finally: - self.compiler = _compiler - - def links_to_dynamic(self, ext): - """Return true if 'ext' links to a dynamic lib in the same package""" - # XXX this should check to ensure the lib is actually being built - # XXX as dynamic, and not just using a locally-found version or a - # XXX static-compiled version - libnames = dict.fromkeys([lib._full_name for lib in self.shlibs]) - pkg = '.'.join(ext._full_name.split('.')[:-1] + ['']) - return any(pkg + libname in libnames for libname in ext.libraries) - - def get_outputs(self): - return _build_ext.get_outputs(self) + self.__get_stubs_outputs() - - def __get_stubs_outputs(self): - # assemble the base name for each extension that needs a stub - ns_ext_bases = ( - os.path.join(self.build_lib, *ext._full_name.split('.')) - for ext in self.extensions - if ext._needs_stub - ) - # pair each base with the extension - pairs = itertools.product(ns_ext_bases, self.__get_output_extensions()) - return list(base + fnext for base, fnext in pairs) - - def __get_output_extensions(self): - yield '.py' - yield '.pyc' - if self.get_finalized_command('build_py').optimize: - yield '.pyo' - - def write_stub(self, output_dir, ext, compile=False): - log.info("writing stub loader for %s to %s", ext._full_name, - output_dir) - stub_file = (os.path.join(output_dir, *ext._full_name.split('.')) + - '.py') - if compile and os.path.exists(stub_file): - raise DistutilsError(stub_file + " already exists! Please delete.") - if not self.dry_run: - f = open(stub_file, 'w') - f.write( - '\n'.join([ - "def __bootstrap__():", - " global __bootstrap__, __file__, __loader__", - " import sys, os, pkg_resources, imp" + if_dl(", dl"), - " __file__ = pkg_resources.resource_filename" - "(__name__,%r)" - % os.path.basename(ext._file_name), - " del __bootstrap__", - " if '__loader__' in globals():", - " del __loader__", - if_dl(" old_flags = sys.getdlopenflags()"), - " old_dir = os.getcwd()", - " try:", - " os.chdir(os.path.dirname(__file__))", - if_dl(" sys.setdlopenflags(dl.RTLD_NOW)"), - " imp.load_dynamic(__name__,__file__)", - " finally:", - if_dl(" sys.setdlopenflags(old_flags)"), - " os.chdir(old_dir)", - "__bootstrap__()", - "" # terminal \n - ]) - ) - f.close() - if compile: - from distutils.util import byte_compile - - byte_compile([stub_file], optimize=0, - force=True, dry_run=self.dry_run) - optimize = self.get_finalized_command('install_lib').optimize - if optimize > 0: - byte_compile([stub_file], optimize=optimize, - force=True, dry_run=self.dry_run) - if os.path.exists(stub_file) and not self.dry_run: - os.unlink(stub_file) - - -if use_stubs or os.name == 'nt': - # Build shared libraries - # - def link_shared_object( - self, objects, output_libname, output_dir=None, libraries=None, - library_dirs=None, runtime_library_dirs=None, export_symbols=None, - debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, - target_lang=None): - self.link( - self.SHARED_LIBRARY, objects, output_libname, - output_dir, libraries, library_dirs, runtime_library_dirs, - export_symbols, debug, extra_preargs, extra_postargs, - build_temp, target_lang - ) -else: - # Build static libraries everywhere else - libtype = 'static' - - def link_shared_object( - self, objects, output_libname, output_dir=None, libraries=None, - library_dirs=None, runtime_library_dirs=None, export_symbols=None, - debug=0, extra_preargs=None, extra_postargs=None, build_temp=None, - target_lang=None): - # XXX we need to either disallow these attrs on Library instances, - # or warn/abort here if set, or something... - # libraries=None, library_dirs=None, runtime_library_dirs=None, - # export_symbols=None, extra_preargs=None, extra_postargs=None, - # build_temp=None - - assert output_dir is None # distutils build_ext doesn't pass this - output_dir, filename = os.path.split(output_libname) - basename, ext = os.path.splitext(filename) - if self.library_filename("x").startswith('lib'): - # strip 'lib' prefix; this is kludgy if some platform uses - # a different prefix - basename = basename[3:] - - self.create_static_lib( - objects, basename, output_dir, debug, target_lang - ) diff --git a/lib/setuptools/command/build_py.py b/lib/setuptools/command/build_py.py deleted file mode 100644 index b0314fd..0000000 --- a/lib/setuptools/command/build_py.py +++ /dev/null @@ -1,270 +0,0 @@ -from glob import glob -from distutils.util import convert_path -import distutils.command.build_py as orig -import os -import fnmatch -import textwrap -import io -import distutils.errors -import itertools - -from setuptools.extern import six -from setuptools.extern.six.moves import map, filter, filterfalse - -try: - from setuptools.lib2to3_ex import Mixin2to3 -except ImportError: - - class Mixin2to3: - def run_2to3(self, files, doctests=True): - "do nothing" - - -class build_py(orig.build_py, Mixin2to3): - """Enhanced 'build_py' command that includes data files with packages - - The data files are specified via a 'package_data' argument to 'setup()'. - See 'setuptools.dist.Distribution' for more details. - - Also, this version of the 'build_py' command allows you to specify both - 'py_modules' and 'packages' in the same setup operation. - """ - - def finalize_options(self): - orig.build_py.finalize_options(self) - self.package_data = self.distribution.package_data - self.exclude_package_data = (self.distribution.exclude_package_data or - {}) - if 'data_files' in self.__dict__: - del self.__dict__['data_files'] - self.__updated_files = [] - self.__doctests_2to3 = [] - - def run(self): - """Build modules, packages, and copy data files to build directory""" - if not self.py_modules and not self.packages: - return - - if self.py_modules: - self.build_modules() - - if self.packages: - self.build_packages() - self.build_package_data() - - self.run_2to3(self.__updated_files, False) - self.run_2to3(self.__updated_files, True) - self.run_2to3(self.__doctests_2to3, True) - - # Only compile actual .py files, using our base class' idea of what our - # output files are. - self.byte_compile(orig.build_py.get_outputs(self, include_bytecode=0)) - - def __getattr__(self, attr): - "lazily compute data files" - if attr == 'data_files': - self.data_files = self._get_data_files() - return self.data_files - return orig.build_py.__getattr__(self, attr) - - def build_module(self, module, module_file, package): - if six.PY2 and isinstance(package, six.string_types): - # avoid errors on Python 2 when unicode is passed (#190) - package = package.split('.') - outfile, copied = orig.build_py.build_module(self, module, module_file, - package) - if copied: - self.__updated_files.append(outfile) - return outfile, copied - - def _get_data_files(self): - """Generate list of '(package,src_dir,build_dir,filenames)' tuples""" - self.analyze_manifest() - return list(map(self._get_pkg_data_files, self.packages or ())) - - def _get_pkg_data_files(self, package): - # Locate package source directory - src_dir = self.get_package_dir(package) - - # Compute package build directory - build_dir = os.path.join(*([self.build_lib] + package.split('.'))) - - # Strip directory from globbed filenames - filenames = [ - os.path.relpath(file, src_dir) - for file in self.find_data_files(package, src_dir) - ] - return package, src_dir, build_dir, filenames - - def find_data_files(self, package, src_dir): - """Return filenames for package's data files in 'src_dir'""" - patterns = self._get_platform_patterns( - self.package_data, - package, - src_dir, - ) - globs_expanded = map(glob, patterns) - # flatten the expanded globs into an iterable of matches - globs_matches = itertools.chain.from_iterable(globs_expanded) - glob_files = filter(os.path.isfile, globs_matches) - files = itertools.chain( - self.manifest_files.get(package, []), - glob_files, - ) - return self.exclude_data_files(package, src_dir, files) - - def build_package_data(self): - """Copy data files into build directory""" - for package, src_dir, build_dir, filenames in self.data_files: - for filename in filenames: - target = os.path.join(build_dir, filename) - self.mkpath(os.path.dirname(target)) - srcfile = os.path.join(src_dir, filename) - outf, copied = self.copy_file(srcfile, target) - srcfile = os.path.abspath(srcfile) - if (copied and - srcfile in self.distribution.convert_2to3_doctests): - self.__doctests_2to3.append(outf) - - def analyze_manifest(self): - self.manifest_files = mf = {} - if not self.distribution.include_package_data: - return - src_dirs = {} - for package in self.packages or (): - # Locate package source directory - src_dirs[assert_relative(self.get_package_dir(package))] = package - - self.run_command('egg_info') - ei_cmd = self.get_finalized_command('egg_info') - for path in ei_cmd.filelist.files: - d, f = os.path.split(assert_relative(path)) - prev = None - oldf = f - while d and d != prev and d not in src_dirs: - prev = d - d, df = os.path.split(d) - f = os.path.join(df, f) - if d in src_dirs: - if path.endswith('.py') and f == oldf: - continue # it's a module, not data - mf.setdefault(src_dirs[d], []).append(path) - - def get_data_files(self): - pass # Lazily compute data files in _get_data_files() function. - - def check_package(self, package, package_dir): - """Check namespace packages' __init__ for declare_namespace""" - try: - return self.packages_checked[package] - except KeyError: - pass - - init_py = orig.build_py.check_package(self, package, package_dir) - self.packages_checked[package] = init_py - - if not init_py or not self.distribution.namespace_packages: - return init_py - - for pkg in self.distribution.namespace_packages: - if pkg == package or pkg.startswith(package + '.'): - break - else: - return init_py - - with io.open(init_py, 'rb') as f: - contents = f.read() - if b'declare_namespace' not in contents: - raise distutils.errors.DistutilsError( - "Namespace package problem: %s is a namespace package, but " - "its\n__init__.py does not call declare_namespace()! Please " - 'fix it.\n(See the setuptools manual under ' - '"Namespace Packages" for details.)\n"' % (package,) - ) - return init_py - - def initialize_options(self): - self.packages_checked = {} - orig.build_py.initialize_options(self) - - def get_package_dir(self, package): - res = orig.build_py.get_package_dir(self, package) - if self.distribution.src_root is not None: - return os.path.join(self.distribution.src_root, res) - return res - - def exclude_data_files(self, package, src_dir, files): - """Filter filenames for package's data files in 'src_dir'""" - files = list(files) - patterns = self._get_platform_patterns( - self.exclude_package_data, - package, - src_dir, - ) - match_groups = ( - fnmatch.filter(files, pattern) - for pattern in patterns - ) - # flatten the groups of matches into an iterable of matches - matches = itertools.chain.from_iterable(match_groups) - bad = set(matches) - keepers = ( - fn - for fn in files - if fn not in bad - ) - # ditch dupes - return list(_unique_everseen(keepers)) - - @staticmethod - def _get_platform_patterns(spec, package, src_dir): - """ - yield platform-specific path patterns (suitable for glob - or fn_match) from a glob-based spec (such as - self.package_data or self.exclude_package_data) - matching package in src_dir. - """ - raw_patterns = itertools.chain( - spec.get('', []), - spec.get(package, []), - ) - return ( - # Each pattern has to be converted to a platform-specific path - os.path.join(src_dir, convert_path(pattern)) - for pattern in raw_patterns - ) - - -# from Python docs -def _unique_everseen(iterable, key=None): - "List unique elements, preserving order. Remember all elements ever seen." - # unique_everseen('AAAABBBCCDAABBB') --> A B C D - # unique_everseen('ABBCcAD', str.lower) --> A B C D - seen = set() - seen_add = seen.add - if key is None: - for element in filterfalse(seen.__contains__, iterable): - seen_add(element) - yield element - else: - for element in iterable: - k = key(element) - if k not in seen: - seen_add(k) - yield element - - -def assert_relative(path): - if not os.path.isabs(path): - return path - from distutils.errors import DistutilsSetupError - - msg = textwrap.dedent(""" - Error: setup script specifies an absolute path: - - %s - - setup() arguments must *always* be /-separated paths relative to the - setup.py directory, *never* absolute paths. - """).lstrip() % path - raise DistutilsSetupError(msg) diff --git a/lib/setuptools/command/develop.py b/lib/setuptools/command/develop.py deleted file mode 100644 index fdc9fc4..0000000 --- a/lib/setuptools/command/develop.py +++ /dev/null @@ -1,218 +0,0 @@ -from distutils.util import convert_path -from distutils import log -from distutils.errors import DistutilsError, DistutilsOptionError -import os -import glob -import io - -from setuptools.extern import six - -from pkg_resources import Distribution, PathMetadata, normalize_path -from setuptools.command.easy_install import easy_install -from setuptools import namespaces -import setuptools - -__metaclass__ = type - - -class develop(namespaces.DevelopInstaller, easy_install): - """Set up package for development""" - - description = "install package in 'development mode'" - - user_options = easy_install.user_options + [ - ("uninstall", "u", "Uninstall this source package"), - ("egg-path=", None, "Set the path to be used in the .egg-link file"), - ] - - boolean_options = easy_install.boolean_options + ['uninstall'] - - command_consumes_arguments = False # override base - - def run(self): - if self.uninstall: - self.multi_version = True - self.uninstall_link() - self.uninstall_namespaces() - else: - self.install_for_development() - self.warn_deprecated_options() - - def initialize_options(self): - self.uninstall = None - self.egg_path = None - easy_install.initialize_options(self) - self.setup_path = None - self.always_copy_from = '.' # always copy eggs installed in curdir - - def finalize_options(self): - ei = self.get_finalized_command("egg_info") - if ei.broken_egg_info: - template = "Please rename %r to %r before using 'develop'" - args = ei.egg_info, ei.broken_egg_info - raise DistutilsError(template % args) - self.args = [ei.egg_name] - - easy_install.finalize_options(self) - self.expand_basedirs() - self.expand_dirs() - # pick up setup-dir .egg files only: no .egg-info - self.package_index.scan(glob.glob('*.egg')) - - egg_link_fn = ei.egg_name + '.egg-link' - self.egg_link = os.path.join(self.install_dir, egg_link_fn) - self.egg_base = ei.egg_base - if self.egg_path is None: - self.egg_path = os.path.abspath(ei.egg_base) - - target = normalize_path(self.egg_base) - egg_path = normalize_path(os.path.join(self.install_dir, - self.egg_path)) - if egg_path != target: - raise DistutilsOptionError( - "--egg-path must be a relative path from the install" - " directory to " + target - ) - - # Make a distribution for the package's source - self.dist = Distribution( - target, - PathMetadata(target, os.path.abspath(ei.egg_info)), - project_name=ei.egg_name - ) - - self.setup_path = self._resolve_setup_path( - self.egg_base, - self.install_dir, - self.egg_path, - ) - - @staticmethod - def _resolve_setup_path(egg_base, install_dir, egg_path): - """ - Generate a path from egg_base back to '.' where the - setup script resides and ensure that path points to the - setup path from $install_dir/$egg_path. - """ - path_to_setup = egg_base.replace(os.sep, '/').rstrip('/') - if path_to_setup != os.curdir: - path_to_setup = '../' * (path_to_setup.count('/') + 1) - resolved = normalize_path( - os.path.join(install_dir, egg_path, path_to_setup) - ) - if resolved != normalize_path(os.curdir): - raise DistutilsOptionError( - "Can't get a consistent path to setup script from" - " installation directory", resolved, normalize_path(os.curdir)) - return path_to_setup - - def install_for_development(self): - if six.PY3 and getattr(self.distribution, 'use_2to3', False): - # If we run 2to3 we can not do this inplace: - - # Ensure metadata is up-to-date - self.reinitialize_command('build_py', inplace=0) - self.run_command('build_py') - bpy_cmd = self.get_finalized_command("build_py") - build_path = normalize_path(bpy_cmd.build_lib) - - # Build extensions - self.reinitialize_command('egg_info', egg_base=build_path) - self.run_command('egg_info') - - self.reinitialize_command('build_ext', inplace=0) - self.run_command('build_ext') - - # Fixup egg-link and easy-install.pth - ei_cmd = self.get_finalized_command("egg_info") - self.egg_path = build_path - self.dist.location = build_path - # XXX - self.dist._provider = PathMetadata(build_path, ei_cmd.egg_info) - else: - # Without 2to3 inplace works fine: - self.run_command('egg_info') - - # Build extensions in-place - self.reinitialize_command('build_ext', inplace=1) - self.run_command('build_ext') - - self.install_site_py() # ensure that target dir is site-safe - if setuptools.bootstrap_install_from: - self.easy_install(setuptools.bootstrap_install_from) - setuptools.bootstrap_install_from = None - - self.install_namespaces() - - # create an .egg-link in the installation dir, pointing to our egg - log.info("Creating %s (link to %s)", self.egg_link, self.egg_base) - if not self.dry_run: - with open(self.egg_link, "w") as f: - f.write(self.egg_path + "\n" + self.setup_path) - # postprocess the installed distro, fixing up .pth, installing scripts, - # and handling requirements - self.process_distribution(None, self.dist, not self.no_deps) - - def uninstall_link(self): - if os.path.exists(self.egg_link): - log.info("Removing %s (link to %s)", self.egg_link, self.egg_base) - egg_link_file = open(self.egg_link) - contents = [line.rstrip() for line in egg_link_file] - egg_link_file.close() - if contents not in ([self.egg_path], - [self.egg_path, self.setup_path]): - log.warn("Link points to %s: uninstall aborted", contents) - return - if not self.dry_run: - os.unlink(self.egg_link) - if not self.dry_run: - self.update_pth(self.dist) # remove any .pth link to us - if self.distribution.scripts: - # XXX should also check for entry point scripts! - log.warn("Note: you must uninstall or replace scripts manually!") - - def install_egg_scripts(self, dist): - if dist is not self.dist: - # Installing a dependency, so fall back to normal behavior - return easy_install.install_egg_scripts(self, dist) - - # create wrapper scripts in the script dir, pointing to dist.scripts - - # new-style... - self.install_wrapper_scripts(dist) - - # ...and old-style - for script_name in self.distribution.scripts or []: - script_path = os.path.abspath(convert_path(script_name)) - script_name = os.path.basename(script_path) - with io.open(script_path) as strm: - script_text = strm.read() - self.install_script(dist, script_name, script_text, script_path) - - def install_wrapper_scripts(self, dist): - dist = VersionlessRequirement(dist) - return easy_install.install_wrapper_scripts(self, dist) - - -class VersionlessRequirement: - """ - Adapt a pkg_resources.Distribution to simply return the project - name as the 'requirement' so that scripts will work across - multiple versions. - - >>> dist = Distribution(project_name='foo', version='1.0') - >>> str(dist.as_requirement()) - 'foo==1.0' - >>> adapted_dist = VersionlessRequirement(dist) - >>> str(adapted_dist.as_requirement()) - 'foo' - """ - - def __init__(self, dist): - self.__dist = dist - - def __getattr__(self, name): - return getattr(self.__dist, name) - - def as_requirement(self): - return self.project_name diff --git a/lib/setuptools/command/dist_info.py b/lib/setuptools/command/dist_info.py deleted file mode 100644 index c45258f..0000000 --- a/lib/setuptools/command/dist_info.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Create a dist_info directory -As defined in the wheel specification -""" - -import os - -from distutils.core import Command -from distutils import log - - -class dist_info(Command): - - description = 'create a .dist-info directory' - - user_options = [ - ('egg-base=', 'e', "directory containing .egg-info directories" - " (default: top of the source tree)"), - ] - - def initialize_options(self): - self.egg_base = None - - def finalize_options(self): - pass - - def run(self): - egg_info = self.get_finalized_command('egg_info') - egg_info.egg_base = self.egg_base - egg_info.finalize_options() - egg_info.run() - dist_info_dir = egg_info.egg_info[:-len('.egg-info')] + '.dist-info' - log.info("creating '{}'".format(os.path.abspath(dist_info_dir))) - - bdist_wheel = self.get_finalized_command('bdist_wheel') - bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir) diff --git a/lib/setuptools/command/easy_install.py b/lib/setuptools/command/easy_install.py deleted file mode 100644 index 06c9827..0000000 --- a/lib/setuptools/command/easy_install.py +++ /dev/null @@ -1,2342 +0,0 @@ -#!/usr/bin/env python -""" -Easy Install ------------- - -A tool for doing automatic download/extract/build of distutils-based Python -packages. For detailed documentation, see the accompanying EasyInstall.txt -file, or visit the `EasyInstall home page`__. - -__ https://setuptools.readthedocs.io/en/latest/easy_install.html - -""" - -from glob import glob -from distutils.util import get_platform -from distutils.util import convert_path, subst_vars -from distutils.errors import ( - DistutilsArgError, DistutilsOptionError, - DistutilsError, DistutilsPlatformError, -) -from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS -from distutils import log, dir_util -from distutils.command.build_scripts import first_line_re -from distutils.spawn import find_executable -import sys -import os -import zipimport -import shutil -import tempfile -import zipfile -import re -import stat -import random -import textwrap -import warnings -import site -import struct -import contextlib -import subprocess -import shlex -import io - - -from sysconfig import get_config_vars, get_path - -from setuptools import SetuptoolsDeprecationWarning - -from setuptools.extern import six -from setuptools.extern.six.moves import configparser, map - -from setuptools import Command -from setuptools.sandbox import run_setup -from setuptools.py27compat import rmtree_safe -from setuptools.command import setopt -from setuptools.archive_util import unpack_archive -from setuptools.package_index import ( - PackageIndex, parse_requirement_arg, URL_SCHEME, -) -from setuptools.command import bdist_egg, egg_info -from setuptools.wheel import Wheel -from pkg_resources import ( - yield_lines, normalize_path, resource_string, ensure_directory, - get_distribution, find_distributions, Environment, Requirement, - Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound, - VersionConflict, DEVELOP_DIST, -) -import pkg_resources.py31compat - -__metaclass__ = type - -# Turn on PEP440Warnings -warnings.filterwarnings("default", category=pkg_resources.PEP440Warning) - -__all__ = [ - 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg', - 'main', 'get_exe_prefixes', -] - - -def is_64bit(): - return struct.calcsize("P") == 8 - - -def samefile(p1, p2): - """ - Determine if two paths reference the same file. - - Augments os.path.samefile to work on Windows and - suppresses errors if the path doesn't exist. - """ - both_exist = os.path.exists(p1) and os.path.exists(p2) - use_samefile = hasattr(os.path, 'samefile') and both_exist - if use_samefile: - return os.path.samefile(p1, p2) - norm_p1 = os.path.normpath(os.path.normcase(p1)) - norm_p2 = os.path.normpath(os.path.normcase(p2)) - return norm_p1 == norm_p2 - - -if six.PY2: - - def _to_bytes(s): - return s - - def isascii(s): - try: - six.text_type(s, 'ascii') - return True - except UnicodeError: - return False -else: - - def _to_bytes(s): - return s.encode('utf8') - - def isascii(s): - try: - s.encode('ascii') - return True - except UnicodeError: - return False - - -_one_liner = lambda text: textwrap.dedent(text).strip().replace('\n', '; ') - - -class easy_install(Command): - """Manage a download/build/install process""" - description = "Find/get/install Python packages" - command_consumes_arguments = True - - user_options = [ - ('prefix=', None, "installation prefix"), - ("zip-ok", "z", "install package as a zipfile"), - ("multi-version", "m", "make apps have to require() a version"), - ("upgrade", "U", "force upgrade (searches PyPI for latest versions)"), - ("install-dir=", "d", "install package to DIR"), - ("script-dir=", "s", "install scripts to DIR"), - ("exclude-scripts", "x", "Don't install scripts"), - ("always-copy", "a", "Copy all needed packages to install dir"), - ("index-url=", "i", "base URL of Python Package Index"), - ("find-links=", "f", "additional URL(s) to search for packages"), - ("build-directory=", "b", - "download/extract/build in DIR; keep the results"), - ('optimize=', 'O', - "also compile with optimization: -O1 for \"python -O\", " - "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), - ('record=', None, - "filename in which to record list of installed files"), - ('always-unzip', 'Z', "don't install as a zipfile, no matter what"), - ('site-dirs=', 'S', "list of directories where .pth files work"), - ('editable', 'e', "Install specified packages in editable form"), - ('no-deps', 'N', "don't install dependencies"), - ('allow-hosts=', 'H', "pattern(s) that hostnames must match"), - ('local-snapshots-ok', 'l', - "allow building eggs from local checkouts"), - ('version', None, "print version information and exit"), - ('no-find-links', None, - "Don't load find-links defined in packages being installed") - ] - boolean_options = [ - 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', - 'editable', - 'no-deps', 'local-snapshots-ok', 'version' - ] - - if site.ENABLE_USER_SITE: - help_msg = "install in user site-package '%s'" % site.USER_SITE - user_options.append(('user', None, help_msg)) - boolean_options.append('user') - - negative_opt = {'always-unzip': 'zip-ok'} - create_index = PackageIndex - - def initialize_options(self): - # the --user option seems to be an opt-in one, - # so the default should be False. - self.user = 0 - self.zip_ok = self.local_snapshots_ok = None - self.install_dir = self.script_dir = self.exclude_scripts = None - self.index_url = None - self.find_links = None - self.build_directory = None - self.args = None - self.optimize = self.record = None - self.upgrade = self.always_copy = self.multi_version = None - self.editable = self.no_deps = self.allow_hosts = None - self.root = self.prefix = self.no_report = None - self.version = None - self.install_purelib = None # for pure module distributions - self.install_platlib = None # non-pure (dists w/ extensions) - self.install_headers = None # for C/C++ headers - self.install_lib = None # set to either purelib or platlib - self.install_scripts = None - self.install_data = None - self.install_base = None - self.install_platbase = None - if site.ENABLE_USER_SITE: - self.install_userbase = site.USER_BASE - self.install_usersite = site.USER_SITE - else: - self.install_userbase = None - self.install_usersite = None - self.no_find_links = None - - # Options not specifiable via command line - self.package_index = None - self.pth_file = self.always_copy_from = None - self.site_dirs = None - self.installed_projects = {} - self.sitepy_installed = False - # Always read easy_install options, even if we are subclassed, or have - # an independent instance created. This ensures that defaults will - # always come from the standard configuration file(s)' "easy_install" - # section, even if this is a "develop" or "install" command, or some - # other embedding. - self._dry_run = None - self.verbose = self.distribution.verbose - self.distribution._set_command_options( - self, self.distribution.get_option_dict('easy_install') - ) - - def delete_blockers(self, blockers): - extant_blockers = ( - filename for filename in blockers - if os.path.exists(filename) or os.path.islink(filename) - ) - list(map(self._delete_path, extant_blockers)) - - def _delete_path(self, path): - log.info("Deleting %s", path) - if self.dry_run: - return - - is_tree = os.path.isdir(path) and not os.path.islink(path) - remover = rmtree if is_tree else os.unlink - remover(path) - - @staticmethod - def _render_version(): - """ - Render the Setuptools version and installation details, then exit. - """ - ver = sys.version[:3] - dist = get_distribution('setuptools') - tmpl = 'setuptools {dist.version} from {dist.location} (Python {ver})' - print(tmpl.format(**locals())) - raise SystemExit() - - def finalize_options(self): - self.version and self._render_version() - - py_version = sys.version.split()[0] - prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix') - - self.config_vars = { - 'dist_name': self.distribution.get_name(), - 'dist_version': self.distribution.get_version(), - 'dist_fullname': self.distribution.get_fullname(), - 'py_version': py_version, - 'py_version_short': py_version[0:3], - 'py_version_nodot': py_version[0] + py_version[2], - 'sys_prefix': prefix, - 'prefix': prefix, - 'sys_exec_prefix': exec_prefix, - 'exec_prefix': exec_prefix, - # Only python 3.2+ has abiflags - 'abiflags': getattr(sys, 'abiflags', ''), - } - - if site.ENABLE_USER_SITE: - self.config_vars['userbase'] = self.install_userbase - self.config_vars['usersite'] = self.install_usersite - - self._fix_install_dir_for_user_site() - - self.expand_basedirs() - self.expand_dirs() - - self._expand( - 'install_dir', 'script_dir', 'build_directory', - 'site_dirs', - ) - # If a non-default installation directory was specified, default the - # script directory to match it. - if self.script_dir is None: - self.script_dir = self.install_dir - - if self.no_find_links is None: - self.no_find_links = False - - # Let install_dir get set by install_lib command, which in turn - # gets its info from the install command, and takes into account - # --prefix and --home and all that other crud. - self.set_undefined_options( - 'install_lib', ('install_dir', 'install_dir') - ) - # Likewise, set default script_dir from 'install_scripts.install_dir' - self.set_undefined_options( - 'install_scripts', ('install_dir', 'script_dir') - ) - - if self.user and self.install_purelib: - self.install_dir = self.install_purelib - self.script_dir = self.install_scripts - # default --record from the install command - self.set_undefined_options('install', ('record', 'record')) - # Should this be moved to the if statement below? It's not used - # elsewhere - normpath = map(normalize_path, sys.path) - self.all_site_dirs = get_site_dirs() - if self.site_dirs is not None: - site_dirs = [ - os.path.expanduser(s.strip()) for s in - self.site_dirs.split(',') - ] - for d in site_dirs: - if not os.path.isdir(d): - log.warn("%s (in --site-dirs) does not exist", d) - elif normalize_path(d) not in normpath: - raise DistutilsOptionError( - d + " (in --site-dirs) is not on sys.path" - ) - else: - self.all_site_dirs.append(normalize_path(d)) - if not self.editable: - self.check_site_dir() - self.index_url = self.index_url or "https://pypi.org/simple/" - self.shadow_path = self.all_site_dirs[:] - for path_item in self.install_dir, normalize_path(self.script_dir): - if path_item not in self.shadow_path: - self.shadow_path.insert(0, path_item) - - if self.allow_hosts is not None: - hosts = [s.strip() for s in self.allow_hosts.split(',')] - else: - hosts = ['*'] - if self.package_index is None: - self.package_index = self.create_index( - self.index_url, search_path=self.shadow_path, hosts=hosts, - ) - self.local_index = Environment(self.shadow_path + sys.path) - - if self.find_links is not None: - if isinstance(self.find_links, six.string_types): - self.find_links = self.find_links.split() - else: - self.find_links = [] - if self.local_snapshots_ok: - self.package_index.scan_egg_links(self.shadow_path + sys.path) - if not self.no_find_links: - self.package_index.add_find_links(self.find_links) - self.set_undefined_options('install_lib', ('optimize', 'optimize')) - if not isinstance(self.optimize, int): - try: - self.optimize = int(self.optimize) - if not (0 <= self.optimize <= 2): - raise ValueError - except ValueError: - raise DistutilsOptionError("--optimize must be 0, 1, or 2") - - if self.editable and not self.build_directory: - raise DistutilsArgError( - "Must specify a build directory (-b) when using --editable" - ) - if not self.args: - raise DistutilsArgError( - "No urls, filenames, or requirements specified (see --help)") - - self.outputs = [] - - def _fix_install_dir_for_user_site(self): - """ - Fix the install_dir if "--user" was used. - """ - if not self.user or not site.ENABLE_USER_SITE: - return - - self.create_home_path() - if self.install_userbase is None: - msg = "User base directory is not specified" - raise DistutilsPlatformError(msg) - self.install_base = self.install_platbase = self.install_userbase - scheme_name = os.name.replace('posix', 'unix') + '_user' - self.select_scheme(scheme_name) - - def _expand_attrs(self, attrs): - for attr in attrs: - val = getattr(self, attr) - if val is not None: - if os.name == 'posix' or os.name == 'nt': - val = os.path.expanduser(val) - val = subst_vars(val, self.config_vars) - setattr(self, attr, val) - - def expand_basedirs(self): - """Calls `os.path.expanduser` on install_base, install_platbase and - root.""" - self._expand_attrs(['install_base', 'install_platbase', 'root']) - - def expand_dirs(self): - """Calls `os.path.expanduser` on install dirs.""" - dirs = [ - 'install_purelib', - 'install_platlib', - 'install_lib', - 'install_headers', - 'install_scripts', - 'install_data', - ] - self._expand_attrs(dirs) - - def run(self): - if self.verbose != self.distribution.verbose: - log.set_verbosity(self.verbose) - try: - for spec in self.args: - self.easy_install(spec, not self.no_deps) - if self.record: - outputs = self.outputs - if self.root: # strip any package prefix - root_len = len(self.root) - for counter in range(len(outputs)): - outputs[counter] = outputs[counter][root_len:] - from distutils import file_util - - self.execute( - file_util.write_file, (self.record, outputs), - "writing list of installed files to '%s'" % - self.record - ) - self.warn_deprecated_options() - finally: - log.set_verbosity(self.distribution.verbose) - - def pseudo_tempname(self): - """Return a pseudo-tempname base in the install directory. - This code is intentionally naive; if a malicious party can write to - the target directory you're already in deep doodoo. - """ - try: - pid = os.getpid() - except Exception: - pid = random.randint(0, sys.maxsize) - return os.path.join(self.install_dir, "test-easy-install-%s" % pid) - - def warn_deprecated_options(self): - pass - - def check_site_dir(self): - """Verify that self.install_dir is .pth-capable dir, if needed""" - - instdir = normalize_path(self.install_dir) - pth_file = os.path.join(instdir, 'easy-install.pth') - - # Is it a configured, PYTHONPATH, implicit, or explicit site dir? - is_site_dir = instdir in self.all_site_dirs - - if not is_site_dir and not self.multi_version: - # No? Then directly test whether it does .pth file processing - is_site_dir = self.check_pth_processing() - else: - # make sure we can write to target dir - testfile = self.pseudo_tempname() + '.write-test' - test_exists = os.path.exists(testfile) - try: - if test_exists: - os.unlink(testfile) - open(testfile, 'w').close() - os.unlink(testfile) - except (OSError, IOError): - self.cant_write_to_target() - - if not is_site_dir and not self.multi_version: - # Can't install non-multi to non-site dir - raise DistutilsError(self.no_default_version_msg()) - - if is_site_dir: - if self.pth_file is None: - self.pth_file = PthDistributions(pth_file, self.all_site_dirs) - else: - self.pth_file = None - - if instdir not in map(normalize_path, _pythonpath()): - # only PYTHONPATH dirs need a site.py, so pretend it's there - self.sitepy_installed = True - elif self.multi_version and not os.path.exists(pth_file): - self.sitepy_installed = True # don't need site.py in this case - self.pth_file = None # and don't create a .pth file - self.install_dir = instdir - - __cant_write_msg = textwrap.dedent(""" - can't create or remove files in install directory - - The following error occurred while trying to add or remove files in the - installation directory: - - %s - - The installation directory you specified (via --install-dir, --prefix, or - the distutils default setting) was: - - %s - """).lstrip() - - __not_exists_id = textwrap.dedent(""" - This directory does not currently exist. Please create it and try again, or - choose a different installation directory (using the -d or --install-dir - option). - """).lstrip() - - __access_msg = textwrap.dedent(""" - Perhaps your account does not have write access to this directory? If the - installation directory is a system-owned directory, you may need to sign in - as the administrator or "root" account. If you do not have administrative - access to this machine, you may wish to choose a different installation - directory, preferably one that is listed in your PYTHONPATH environment - variable. - - For information on other options, you may wish to consult the - documentation at: - - https://setuptools.readthedocs.io/en/latest/easy_install.html - - Please make the appropriate changes for your system and try again. - """).lstrip() - - def cant_write_to_target(self): - msg = self.__cant_write_msg % (sys.exc_info()[1], self.install_dir,) - - if not os.path.exists(self.install_dir): - msg += '\n' + self.__not_exists_id - else: - msg += '\n' + self.__access_msg - raise DistutilsError(msg) - - def check_pth_processing(self): - """Empirically verify whether .pth files are supported in inst. dir""" - instdir = self.install_dir - log.info("Checking .pth file support in %s", instdir) - pth_file = self.pseudo_tempname() + ".pth" - ok_file = pth_file + '.ok' - ok_exists = os.path.exists(ok_file) - tmpl = _one_liner(""" - import os - f = open({ok_file!r}, 'w') - f.write('OK') - f.close() - """) + '\n' - try: - if ok_exists: - os.unlink(ok_file) - dirname = os.path.dirname(ok_file) - pkg_resources.py31compat.makedirs(dirname, exist_ok=True) - f = open(pth_file, 'w') - except (OSError, IOError): - self.cant_write_to_target() - else: - try: - f.write(tmpl.format(**locals())) - f.close() - f = None - executable = sys.executable - if os.name == 'nt': - dirname, basename = os.path.split(executable) - alt = os.path.join(dirname, 'pythonw.exe') - use_alt = ( - basename.lower() == 'python.exe' and - os.path.exists(alt) - ) - if use_alt: - # use pythonw.exe to avoid opening a console window - executable = alt - - from distutils.spawn import spawn - - spawn([executable, '-E', '-c', 'pass'], 0) - - if os.path.exists(ok_file): - log.info( - "TEST PASSED: %s appears to support .pth files", - instdir - ) - return True - finally: - if f: - f.close() - if os.path.exists(ok_file): - os.unlink(ok_file) - if os.path.exists(pth_file): - os.unlink(pth_file) - if not self.multi_version: - log.warn("TEST FAILED: %s does NOT support .pth files", instdir) - return False - - def install_egg_scripts(self, dist): - """Write all the scripts for `dist`, unless scripts are excluded""" - if not self.exclude_scripts and dist.metadata_isdir('scripts'): - for script_name in dist.metadata_listdir('scripts'): - if dist.metadata_isdir('scripts/' + script_name): - # The "script" is a directory, likely a Python 3 - # __pycache__ directory, so skip it. - continue - self.install_script( - dist, script_name, - dist.get_metadata('scripts/' + script_name) - ) - self.install_wrapper_scripts(dist) - - def add_output(self, path): - if os.path.isdir(path): - for base, dirs, files in os.walk(path): - for filename in files: - self.outputs.append(os.path.join(base, filename)) - else: - self.outputs.append(path) - - def not_editable(self, spec): - if self.editable: - raise DistutilsArgError( - "Invalid argument %r: you can't use filenames or URLs " - "with --editable (except via the --find-links option)." - % (spec,) - ) - - def check_editable(self, spec): - if not self.editable: - return - - if os.path.exists(os.path.join(self.build_directory, spec.key)): - raise DistutilsArgError( - "%r already exists in %s; can't do a checkout there" % - (spec.key, self.build_directory) - ) - - @contextlib.contextmanager - def _tmpdir(self): - tmpdir = tempfile.mkdtemp(prefix=u"easy_install-") - try: - # cast to str as workaround for #709 and #710 and #712 - yield str(tmpdir) - finally: - os.path.exists(tmpdir) and rmtree(rmtree_safe(tmpdir)) - - def easy_install(self, spec, deps=False): - if not self.editable: - self.install_site_py() - - with self._tmpdir() as tmpdir: - if not isinstance(spec, Requirement): - if URL_SCHEME(spec): - # It's a url, download it to tmpdir and process - self.not_editable(spec) - dl = self.package_index.download(spec, tmpdir) - return self.install_item(None, dl, tmpdir, deps, True) - - elif os.path.exists(spec): - # Existing file or directory, just process it directly - self.not_editable(spec) - return self.install_item(None, spec, tmpdir, deps, True) - else: - spec = parse_requirement_arg(spec) - - self.check_editable(spec) - dist = self.package_index.fetch_distribution( - spec, tmpdir, self.upgrade, self.editable, - not self.always_copy, self.local_index - ) - if dist is None: - msg = "Could not find suitable distribution for %r" % spec - if self.always_copy: - msg += " (--always-copy skips system and development eggs)" - raise DistutilsError(msg) - elif dist.precedence == DEVELOP_DIST: - # .egg-info dists don't need installing, just process deps - self.process_distribution(spec, dist, deps, "Using") - return dist - else: - return self.install_item(spec, dist.location, tmpdir, deps) - - def install_item(self, spec, download, tmpdir, deps, install_needed=False): - - # Installation is also needed if file in tmpdir or is not an egg - install_needed = install_needed or self.always_copy - install_needed = install_needed or os.path.dirname(download) == tmpdir - install_needed = install_needed or not download.endswith('.egg') - install_needed = install_needed or ( - self.always_copy_from is not None and - os.path.dirname(normalize_path(download)) == - normalize_path(self.always_copy_from) - ) - - if spec and not install_needed: - # at this point, we know it's a local .egg, we just don't know if - # it's already installed. - for dist in self.local_index[spec.project_name]: - if dist.location == download: - break - else: - install_needed = True # it's not in the local index - - log.info("Processing %s", os.path.basename(download)) - - if install_needed: - dists = self.install_eggs(spec, download, tmpdir) - for dist in dists: - self.process_distribution(spec, dist, deps) - else: - dists = [self.egg_distribution(download)] - self.process_distribution(spec, dists[0], deps, "Using") - - if spec is not None: - for dist in dists: - if dist in spec: - return dist - - def select_scheme(self, name): - """Sets the install directories by applying the install schemes.""" - # it's the caller's problem if they supply a bad name! - scheme = INSTALL_SCHEMES[name] - for key in SCHEME_KEYS: - attrname = 'install_' + key - if getattr(self, attrname) is None: - setattr(self, attrname, scheme[key]) - - def process_distribution(self, requirement, dist, deps=True, *info): - self.update_pth(dist) - self.package_index.add(dist) - if dist in self.local_index[dist.key]: - self.local_index.remove(dist) - self.local_index.add(dist) - self.install_egg_scripts(dist) - self.installed_projects[dist.key] = dist - log.info(self.installation_report(requirement, dist, *info)) - if (dist.has_metadata('dependency_links.txt') and - not self.no_find_links): - self.package_index.add_find_links( - dist.get_metadata_lines('dependency_links.txt') - ) - if not deps and not self.always_copy: - return - elif requirement is not None and dist.key != requirement.key: - log.warn("Skipping dependencies for %s", dist) - return # XXX this is not the distribution we were looking for - elif requirement is None or dist not in requirement: - # if we wound up with a different version, resolve what we've got - distreq = dist.as_requirement() - requirement = Requirement(str(distreq)) - log.info("Processing dependencies for %s", requirement) - try: - distros = WorkingSet([]).resolve( - [requirement], self.local_index, self.easy_install - ) - except DistributionNotFound as e: - raise DistutilsError(str(e)) - except VersionConflict as e: - raise DistutilsError(e.report()) - if self.always_copy or self.always_copy_from: - # Force all the relevant distros to be copied or activated - for dist in distros: - if dist.key not in self.installed_projects: - self.easy_install(dist.as_requirement()) - log.info("Finished processing dependencies for %s", requirement) - - def should_unzip(self, dist): - if self.zip_ok is not None: - return not self.zip_ok - if dist.has_metadata('not-zip-safe'): - return True - if not dist.has_metadata('zip-safe'): - return True - return False - - def maybe_move(self, spec, dist_filename, setup_base): - dst = os.path.join(self.build_directory, spec.key) - if os.path.exists(dst): - msg = ( - "%r already exists in %s; build directory %s will not be kept" - ) - log.warn(msg, spec.key, self.build_directory, setup_base) - return setup_base - if os.path.isdir(dist_filename): - setup_base = dist_filename - else: - if os.path.dirname(dist_filename) == setup_base: - os.unlink(dist_filename) # get it out of the tmp dir - contents = os.listdir(setup_base) - if len(contents) == 1: - dist_filename = os.path.join(setup_base, contents[0]) - if os.path.isdir(dist_filename): - # if the only thing there is a directory, move it instead - setup_base = dist_filename - ensure_directory(dst) - shutil.move(setup_base, dst) - return dst - - def install_wrapper_scripts(self, dist): - if self.exclude_scripts: - return - for args in ScriptWriter.best().get_args(dist): - self.write_script(*args) - - def install_script(self, dist, script_name, script_text, dev_path=None): - """Generate a legacy script wrapper and install it""" - spec = str(dist.as_requirement()) - is_script = is_python_script(script_text, script_name) - - if is_script: - body = self._load_template(dev_path) % locals() - script_text = ScriptWriter.get_header(script_text) + body - self.write_script(script_name, _to_bytes(script_text), 'b') - - @staticmethod - def _load_template(dev_path): - """ - There are a couple of template scripts in the package. This - function loads one of them and prepares it for use. - """ - # See https://github.com/pypa/setuptools/issues/134 for info - # on script file naming and downstream issues with SVR4 - name = 'script.tmpl' - if dev_path: - name = name.replace('.tmpl', ' (dev).tmpl') - - raw_bytes = resource_string('setuptools', name) - return raw_bytes.decode('utf-8') - - def write_script(self, script_name, contents, mode="t", blockers=()): - """Write an executable file to the scripts directory""" - self.delete_blockers( # clean up old .py/.pyw w/o a script - [os.path.join(self.script_dir, x) for x in blockers] - ) - log.info("Installing %s script to %s", script_name, self.script_dir) - target = os.path.join(self.script_dir, script_name) - self.add_output(target) - - if self.dry_run: - return - - mask = current_umask() - ensure_directory(target) - if os.path.exists(target): - os.unlink(target) - with open(target, "w" + mode) as f: - f.write(contents) - chmod(target, 0o777 - mask) - - def install_eggs(self, spec, dist_filename, tmpdir): - # .egg dirs or files are already built, so just return them - if dist_filename.lower().endswith('.egg'): - return [self.install_egg(dist_filename, tmpdir)] - elif dist_filename.lower().endswith('.exe'): - return [self.install_exe(dist_filename, tmpdir)] - elif dist_filename.lower().endswith('.whl'): - return [self.install_wheel(dist_filename, tmpdir)] - - # Anything else, try to extract and build - setup_base = tmpdir - if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'): - unpack_archive(dist_filename, tmpdir, self.unpack_progress) - elif os.path.isdir(dist_filename): - setup_base = os.path.abspath(dist_filename) - - if (setup_base.startswith(tmpdir) # something we downloaded - and self.build_directory and spec is not None): - setup_base = self.maybe_move(spec, dist_filename, setup_base) - - # Find the setup.py file - setup_script = os.path.join(setup_base, 'setup.py') - - if not os.path.exists(setup_script): - setups = glob(os.path.join(setup_base, '*', 'setup.py')) - if not setups: - raise DistutilsError( - "Couldn't find a setup script in %s" % - os.path.abspath(dist_filename) - ) - if len(setups) > 1: - raise DistutilsError( - "Multiple setup scripts in %s" % - os.path.abspath(dist_filename) - ) - setup_script = setups[0] - - # Now run it, and return the result - if self.editable: - log.info(self.report_editable(spec, setup_script)) - return [] - else: - return self.build_and_install(setup_script, setup_base) - - def egg_distribution(self, egg_path): - if os.path.isdir(egg_path): - metadata = PathMetadata(egg_path, os.path.join(egg_path, - 'EGG-INFO')) - else: - metadata = EggMetadata(zipimport.zipimporter(egg_path)) - return Distribution.from_filename(egg_path, metadata=metadata) - - def install_egg(self, egg_path, tmpdir): - destination = os.path.join( - self.install_dir, - os.path.basename(egg_path), - ) - destination = os.path.abspath(destination) - if not self.dry_run: - ensure_directory(destination) - - dist = self.egg_distribution(egg_path) - if not samefile(egg_path, destination): - if os.path.isdir(destination) and not os.path.islink(destination): - dir_util.remove_tree(destination, dry_run=self.dry_run) - elif os.path.exists(destination): - self.execute( - os.unlink, - (destination,), - "Removing " + destination, - ) - try: - new_dist_is_zipped = False - if os.path.isdir(egg_path): - if egg_path.startswith(tmpdir): - f, m = shutil.move, "Moving" - else: - f, m = shutil.copytree, "Copying" - elif self.should_unzip(dist): - self.mkpath(destination) - f, m = self.unpack_and_compile, "Extracting" - else: - new_dist_is_zipped = True - if egg_path.startswith(tmpdir): - f, m = shutil.move, "Moving" - else: - f, m = shutil.copy2, "Copying" - self.execute( - f, - (egg_path, destination), - (m + " %s to %s") % ( - os.path.basename(egg_path), - os.path.dirname(destination) - ), - ) - update_dist_caches( - destination, - fix_zipimporter_caches=new_dist_is_zipped, - ) - except Exception: - update_dist_caches(destination, fix_zipimporter_caches=False) - raise - - self.add_output(destination) - return self.egg_distribution(destination) - - def install_exe(self, dist_filename, tmpdir): - # See if it's valid, get data - cfg = extract_wininst_cfg(dist_filename) - if cfg is None: - raise DistutilsError( - "%s is not a valid distutils Windows .exe" % dist_filename - ) - # Create a dummy distribution object until we build the real distro - dist = Distribution( - None, - project_name=cfg.get('metadata', 'name'), - version=cfg.get('metadata', 'version'), platform=get_platform(), - ) - - # Convert the .exe to an unpacked egg - egg_path = os.path.join(tmpdir, dist.egg_name() + '.egg') - dist.location = egg_path - egg_tmp = egg_path + '.tmp' - _egg_info = os.path.join(egg_tmp, 'EGG-INFO') - pkg_inf = os.path.join(_egg_info, 'PKG-INFO') - ensure_directory(pkg_inf) # make sure EGG-INFO dir exists - dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX - self.exe_to_egg(dist_filename, egg_tmp) - - # Write EGG-INFO/PKG-INFO - if not os.path.exists(pkg_inf): - f = open(pkg_inf, 'w') - f.write('Metadata-Version: 1.0\n') - for k, v in cfg.items('metadata'): - if k != 'target_version': - f.write('%s: %s\n' % (k.replace('_', '-').title(), v)) - f.close() - script_dir = os.path.join(_egg_info, 'scripts') - # delete entry-point scripts to avoid duping - self.delete_blockers([ - os.path.join(script_dir, args[0]) - for args in ScriptWriter.get_args(dist) - ]) - # Build .egg file from tmpdir - bdist_egg.make_zipfile( - egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run, - ) - # install the .egg - return self.install_egg(egg_path, tmpdir) - - def exe_to_egg(self, dist_filename, egg_tmp): - """Extract a bdist_wininst to the directories an egg would use""" - # Check for .pth file and set up prefix translations - prefixes = get_exe_prefixes(dist_filename) - to_compile = [] - native_libs = [] - top_level = {} - - def process(src, dst): - s = src.lower() - for old, new in prefixes: - if s.startswith(old): - src = new + src[len(old):] - parts = src.split('/') - dst = os.path.join(egg_tmp, *parts) - dl = dst.lower() - if dl.endswith('.pyd') or dl.endswith('.dll'): - parts[-1] = bdist_egg.strip_module(parts[-1]) - top_level[os.path.splitext(parts[0])[0]] = 1 - native_libs.append(src) - elif dl.endswith('.py') and old != 'SCRIPTS/': - top_level[os.path.splitext(parts[0])[0]] = 1 - to_compile.append(dst) - return dst - if not src.endswith('.pth'): - log.warn("WARNING: can't process %s", src) - return None - - # extract, tracking .pyd/.dll->native_libs and .py -> to_compile - unpack_archive(dist_filename, egg_tmp, process) - stubs = [] - for res in native_libs: - if res.lower().endswith('.pyd'): # create stubs for .pyd's - parts = res.split('/') - resource = parts[-1] - parts[-1] = bdist_egg.strip_module(parts[-1]) + '.py' - pyfile = os.path.join(egg_tmp, *parts) - to_compile.append(pyfile) - stubs.append(pyfile) - bdist_egg.write_stub(resource, pyfile) - self.byte_compile(to_compile) # compile .py's - bdist_egg.write_safety_flag( - os.path.join(egg_tmp, 'EGG-INFO'), - bdist_egg.analyze_egg(egg_tmp, stubs)) # write zip-safety flag - - for name in 'top_level', 'native_libs': - if locals()[name]: - txt = os.path.join(egg_tmp, 'EGG-INFO', name + '.txt') - if not os.path.exists(txt): - f = open(txt, 'w') - f.write('\n'.join(locals()[name]) + '\n') - f.close() - - def install_wheel(self, wheel_path, tmpdir): - wheel = Wheel(wheel_path) - assert wheel.is_compatible() - destination = os.path.join(self.install_dir, wheel.egg_name()) - destination = os.path.abspath(destination) - if not self.dry_run: - ensure_directory(destination) - if os.path.isdir(destination) and not os.path.islink(destination): - dir_util.remove_tree(destination, dry_run=self.dry_run) - elif os.path.exists(destination): - self.execute( - os.unlink, - (destination,), - "Removing " + destination, - ) - try: - self.execute( - wheel.install_as_egg, - (destination,), - ("Installing %s to %s") % ( - os.path.basename(wheel_path), - os.path.dirname(destination) - ), - ) - finally: - update_dist_caches(destination, fix_zipimporter_caches=False) - self.add_output(destination) - return self.egg_distribution(destination) - - __mv_warning = textwrap.dedent(""" - Because this distribution was installed --multi-version, before you can - import modules from this package in an application, you will need to - 'import pkg_resources' and then use a 'require()' call similar to one of - these examples, in order to select the desired version: - - pkg_resources.require("%(name)s") # latest installed version - pkg_resources.require("%(name)s==%(version)s") # this exact version - pkg_resources.require("%(name)s>=%(version)s") # this version or higher - """).lstrip() - - __id_warning = textwrap.dedent(""" - Note also that the installation directory must be on sys.path at runtime for - this to work. (e.g. by being the application's script directory, by being on - PYTHONPATH, or by being added to sys.path by your code.) - """) - - def installation_report(self, req, dist, what="Installed"): - """Helpful installation message for display to package users""" - msg = "\n%(what)s %(eggloc)s%(extras)s" - if self.multi_version and not self.no_report: - msg += '\n' + self.__mv_warning - if self.install_dir not in map(normalize_path, sys.path): - msg += '\n' + self.__id_warning - - eggloc = dist.location - name = dist.project_name - version = dist.version - extras = '' # TODO: self.report_extras(req, dist) - return msg % locals() - - __editable_msg = textwrap.dedent(""" - Extracted editable version of %(spec)s to %(dirname)s - - If it uses setuptools in its setup script, you can activate it in - "development" mode by going to that directory and running:: - - %(python)s setup.py develop - - See the setuptools documentation for the "develop" command for more info. - """).lstrip() - - def report_editable(self, spec, setup_script): - dirname = os.path.dirname(setup_script) - python = sys.executable - return '\n' + self.__editable_msg % locals() - - def run_setup(self, setup_script, setup_base, args): - sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg) - sys.modules.setdefault('distutils.command.egg_info', egg_info) - - args = list(args) - if self.verbose > 2: - v = 'v' * (self.verbose - 1) - args.insert(0, '-' + v) - elif self.verbose < 2: - args.insert(0, '-q') - if self.dry_run: - args.insert(0, '-n') - log.info( - "Running %s %s", setup_script[len(setup_base) + 1:], ' '.join(args) - ) - try: - run_setup(setup_script, args) - except SystemExit as v: - raise DistutilsError("Setup script exited with %s" % (v.args[0],)) - - def build_and_install(self, setup_script, setup_base): - args = ['bdist_egg', '--dist-dir'] - - dist_dir = tempfile.mkdtemp( - prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script) - ) - try: - self._set_fetcher_options(os.path.dirname(setup_script)) - args.append(dist_dir) - - self.run_setup(setup_script, setup_base, args) - all_eggs = Environment([dist_dir]) - eggs = [] - for key in all_eggs: - for dist in all_eggs[key]: - eggs.append(self.install_egg(dist.location, setup_base)) - if not eggs and not self.dry_run: - log.warn("No eggs found in %s (setup script problem?)", - dist_dir) - return eggs - finally: - rmtree(dist_dir) - log.set_verbosity(self.verbose) # restore our log verbosity - - def _set_fetcher_options(self, base): - """ - When easy_install is about to run bdist_egg on a source dist, that - source dist might have 'setup_requires' directives, requiring - additional fetching. Ensure the fetcher options given to easy_install - are available to that command as well. - """ - # find the fetch options from easy_install and write them out - # to the setup.cfg file. - ei_opts = self.distribution.get_option_dict('easy_install').copy() - fetch_directives = ( - 'find_links', 'site_dirs', 'index_url', 'optimize', - 'site_dirs', 'allow_hosts', - ) - fetch_options = {} - for key, val in ei_opts.items(): - if key not in fetch_directives: - continue - fetch_options[key.replace('_', '-')] = val[1] - # create a settings dictionary suitable for `edit_config` - settings = dict(easy_install=fetch_options) - cfg_filename = os.path.join(base, 'setup.cfg') - setopt.edit_config(cfg_filename, settings) - - def update_pth(self, dist): - if self.pth_file is None: - return - - for d in self.pth_file[dist.key]: # drop old entries - if self.multi_version or d.location != dist.location: - log.info("Removing %s from easy-install.pth file", d) - self.pth_file.remove(d) - if d.location in self.shadow_path: - self.shadow_path.remove(d.location) - - if not self.multi_version: - if dist.location in self.pth_file.paths: - log.info( - "%s is already the active version in easy-install.pth", - dist, - ) - else: - log.info("Adding %s to easy-install.pth file", dist) - self.pth_file.add(dist) # add new entry - if dist.location not in self.shadow_path: - self.shadow_path.append(dist.location) - - if not self.dry_run: - - self.pth_file.save() - - if dist.key == 'setuptools': - # Ensure that setuptools itself never becomes unavailable! - # XXX should this check for latest version? - filename = os.path.join(self.install_dir, 'setuptools.pth') - if os.path.islink(filename): - os.unlink(filename) - f = open(filename, 'wt') - f.write(self.pth_file.make_relative(dist.location) + '\n') - f.close() - - def unpack_progress(self, src, dst): - # Progress filter for unpacking - log.debug("Unpacking %s to %s", src, dst) - return dst # only unpack-and-compile skips files for dry run - - def unpack_and_compile(self, egg_path, destination): - to_compile = [] - to_chmod = [] - - def pf(src, dst): - if dst.endswith('.py') and not src.startswith('EGG-INFO/'): - to_compile.append(dst) - elif dst.endswith('.dll') or dst.endswith('.so'): - to_chmod.append(dst) - self.unpack_progress(src, dst) - return not self.dry_run and dst or None - - unpack_archive(egg_path, destination, pf) - self.byte_compile(to_compile) - if not self.dry_run: - for f in to_chmod: - mode = ((os.stat(f)[stat.ST_MODE]) | 0o555) & 0o7755 - chmod(f, mode) - - def byte_compile(self, to_compile): - if sys.dont_write_bytecode: - return - - from distutils.util import byte_compile - - try: - # try to make the byte compile messages quieter - log.set_verbosity(self.verbose - 1) - - byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run) - if self.optimize: - byte_compile( - to_compile, optimize=self.optimize, force=1, - dry_run=self.dry_run, - ) - finally: - log.set_verbosity(self.verbose) # restore original verbosity - - __no_default_msg = textwrap.dedent(""" - bad install directory or PYTHONPATH - - You are attempting to install a package to a directory that is not - on PYTHONPATH and which Python does not read ".pth" files from. The - installation directory you specified (via --install-dir, --prefix, or - the distutils default setting) was: - - %s - - and your PYTHONPATH environment variable currently contains: - - %r - - Here are some of your options for correcting the problem: - - * You can choose a different installation directory, i.e., one that is - on PYTHONPATH or supports .pth files - - * You can add the installation directory to the PYTHONPATH environment - variable. (It must then also be on PYTHONPATH whenever you run - Python and want to use the package(s) you are installing.) - - * You can set up the installation directory to support ".pth" files by - using one of the approaches described here: - - https://setuptools.readthedocs.io/en/latest/easy_install.html#custom-installation-locations - - - Please make the appropriate changes for your system and try again.""").lstrip() - - def no_default_version_msg(self): - template = self.__no_default_msg - return template % (self.install_dir, os.environ.get('PYTHONPATH', '')) - - def install_site_py(self): - """Make sure there's a site.py in the target dir, if needed""" - - if self.sitepy_installed: - return # already did it, or don't need to - - sitepy = os.path.join(self.install_dir, "site.py") - source = resource_string("setuptools", "site-patch.py") - source = source.decode('utf-8') - current = "" - - if os.path.exists(sitepy): - log.debug("Checking existing site.py in %s", self.install_dir) - with io.open(sitepy) as strm: - current = strm.read() - - if not current.startswith('def __boot():'): - raise DistutilsError( - "%s is not a setuptools-generated site.py; please" - " remove it." % sitepy - ) - - if current != source: - log.info("Creating %s", sitepy) - if not self.dry_run: - ensure_directory(sitepy) - with io.open(sitepy, 'w', encoding='utf-8') as strm: - strm.write(source) - self.byte_compile([sitepy]) - - self.sitepy_installed = True - - def create_home_path(self): - """Create directories under ~.""" - if not self.user: - return - home = convert_path(os.path.expanduser("~")) - for name, path in six.iteritems(self.config_vars): - if path.startswith(home) and not os.path.isdir(path): - self.debug_print("os.makedirs('%s', 0o700)" % path) - os.makedirs(path, 0o700) - - INSTALL_SCHEMES = dict( - posix=dict( - install_dir='$base/lib/python$py_version_short/site-packages', - script_dir='$base/bin', - ), - ) - - DEFAULT_SCHEME = dict( - install_dir='$base/Lib/site-packages', - script_dir='$base/Scripts', - ) - - def _expand(self, *attrs): - config_vars = self.get_finalized_command('install').config_vars - - if self.prefix: - # Set default install_dir/scripts from --prefix - config_vars = config_vars.copy() - config_vars['base'] = self.prefix - scheme = self.INSTALL_SCHEMES.get(os.name, self.DEFAULT_SCHEME) - for attr, val in scheme.items(): - if getattr(self, attr, None) is None: - setattr(self, attr, val) - - from distutils.util import subst_vars - - for attr in attrs: - val = getattr(self, attr) - if val is not None: - val = subst_vars(val, config_vars) - if os.name == 'posix': - val = os.path.expanduser(val) - setattr(self, attr, val) - - -def _pythonpath(): - items = os.environ.get('PYTHONPATH', '').split(os.pathsep) - return filter(None, items) - - -def get_site_dirs(): - """ - Return a list of 'site' dirs - """ - - sitedirs = [] - - # start with PYTHONPATH - sitedirs.extend(_pythonpath()) - - prefixes = [sys.prefix] - if sys.exec_prefix != sys.prefix: - prefixes.append(sys.exec_prefix) - for prefix in prefixes: - if prefix: - if sys.platform in ('os2emx', 'riscos'): - sitedirs.append(os.path.join(prefix, "Lib", "site-packages")) - elif os.sep == '/': - sitedirs.extend([ - os.path.join( - prefix, - "lib", - "python" + sys.version[:3], - "site-packages", - ), - os.path.join(prefix, "lib", "site-python"), - ]) - else: - sitedirs.extend([ - prefix, - os.path.join(prefix, "lib", "site-packages"), - ]) - if sys.platform == 'darwin': - # for framework builds *only* we add the standard Apple - # locations. Currently only per-user, but /Library and - # /Network/Library could be added too - if 'Python.framework' in prefix: - home = os.environ.get('HOME') - if home: - home_sp = os.path.join( - home, - 'Library', - 'Python', - sys.version[:3], - 'site-packages', - ) - sitedirs.append(home_sp) - lib_paths = get_path('purelib'), get_path('platlib') - for site_lib in lib_paths: - if site_lib not in sitedirs: - sitedirs.append(site_lib) - - if site.ENABLE_USER_SITE: - sitedirs.append(site.USER_SITE) - - try: - sitedirs.extend(site.getsitepackages()) - except AttributeError: - pass - - sitedirs = list(map(normalize_path, sitedirs)) - - return sitedirs - - -def expand_paths(inputs): - """Yield sys.path directories that might contain "old-style" packages""" - - seen = {} - - for dirname in inputs: - dirname = normalize_path(dirname) - if dirname in seen: - continue - - seen[dirname] = 1 - if not os.path.isdir(dirname): - continue - - files = os.listdir(dirname) - yield dirname, files - - for name in files: - if not name.endswith('.pth'): - # We only care about the .pth files - continue - if name in ('easy-install.pth', 'setuptools.pth'): - # Ignore .pth files that we control - continue - - # Read the .pth file - f = open(os.path.join(dirname, name)) - lines = list(yield_lines(f)) - f.close() - - # Yield existing non-dupe, non-import directory lines from it - for line in lines: - if not line.startswith("import"): - line = normalize_path(line.rstrip()) - if line not in seen: - seen[line] = 1 - if not os.path.isdir(line): - continue - yield line, os.listdir(line) - - -def extract_wininst_cfg(dist_filename): - """Extract configuration data from a bdist_wininst .exe - - Returns a configparser.RawConfigParser, or None - """ - f = open(dist_filename, 'rb') - try: - endrec = zipfile._EndRecData(f) - if endrec is None: - return None - - prepended = (endrec[9] - endrec[5]) - endrec[6] - if prepended < 12: # no wininst data here - return None - f.seek(prepended - 12) - - tag, cfglen, bmlen = struct.unpack("<iii", f.read(12)) - if tag not in (0x1234567A, 0x1234567B): - return None # not a valid tag - - f.seek(prepended - (12 + cfglen)) - init = {'version': '', 'target_version': ''} - cfg = configparser.RawConfigParser(init) - try: - part = f.read(cfglen) - # Read up to the first null byte. - config = part.split(b'\0', 1)[0] - # Now the config is in bytes, but for RawConfigParser, it should - # be text, so decode it. - config = config.decode(sys.getfilesystemencoding()) - cfg.readfp(six.StringIO(config)) - except configparser.Error: - return None - if not cfg.has_section('metadata') or not cfg.has_section('Setup'): - return None - return cfg - - finally: - f.close() - - -def get_exe_prefixes(exe_filename): - """Get exe->egg path translations for a given .exe file""" - - prefixes = [ - ('PURELIB/', ''), - ('PLATLIB/pywin32_system32', ''), - ('PLATLIB/', ''), - ('SCRIPTS/', 'EGG-INFO/scripts/'), - ('DATA/lib/site-packages', ''), - ] - z = zipfile.ZipFile(exe_filename) - try: - for info in z.infolist(): - name = info.filename - parts = name.split('/') - if len(parts) == 3 and parts[2] == 'PKG-INFO': - if parts[1].endswith('.egg-info'): - prefixes.insert(0, ('/'.join(parts[:2]), 'EGG-INFO/')) - break - if len(parts) != 2 or not name.endswith('.pth'): - continue - if name.endswith('-nspkg.pth'): - continue - if parts[0].upper() in ('PURELIB', 'PLATLIB'): - contents = z.read(name) - if six.PY3: - contents = contents.decode() - for pth in yield_lines(contents): - pth = pth.strip().replace('\\', '/') - if not pth.startswith('import'): - prefixes.append((('%s/%s/' % (parts[0], pth)), '')) - finally: - z.close() - prefixes = [(x.lower(), y) for x, y in prefixes] - prefixes.sort() - prefixes.reverse() - return prefixes - - -class PthDistributions(Environment): - """A .pth file with Distribution paths in it""" - - dirty = False - - def __init__(self, filename, sitedirs=()): - self.filename = filename - self.sitedirs = list(map(normalize_path, sitedirs)) - self.basedir = normalize_path(os.path.dirname(self.filename)) - self._load() - Environment.__init__(self, [], None, None) - for path in yield_lines(self.paths): - list(map(self.add, find_distributions(path, True))) - - def _load(self): - self.paths = [] - saw_import = False - seen = dict.fromkeys(self.sitedirs) - if os.path.isfile(self.filename): - f = open(self.filename, 'rt') - for line in f: - if line.startswith('import'): - saw_import = True - continue - path = line.rstrip() - self.paths.append(path) - if not path.strip() or path.strip().startswith('#'): - continue - # skip non-existent paths, in case somebody deleted a package - # manually, and duplicate paths as well - path = self.paths[-1] = normalize_path( - os.path.join(self.basedir, path) - ) - if not os.path.exists(path) or path in seen: - self.paths.pop() # skip it - self.dirty = True # we cleaned up, so we're dirty now :) - continue - seen[path] = 1 - f.close() - - if self.paths and not saw_import: - self.dirty = True # ensure anything we touch has import wrappers - while self.paths and not self.paths[-1].strip(): - self.paths.pop() - - def save(self): - """Write changed .pth file back to disk""" - if not self.dirty: - return - - rel_paths = list(map(self.make_relative, self.paths)) - if rel_paths: - log.debug("Saving %s", self.filename) - lines = self._wrap_lines(rel_paths) - data = '\n'.join(lines) + '\n' - - if os.path.islink(self.filename): - os.unlink(self.filename) - with open(self.filename, 'wt') as f: - f.write(data) - - elif os.path.exists(self.filename): - log.debug("Deleting empty %s", self.filename) - os.unlink(self.filename) - - self.dirty = False - - @staticmethod - def _wrap_lines(lines): - return lines - - def add(self, dist): - """Add `dist` to the distribution map""" - new_path = ( - dist.location not in self.paths and ( - dist.location not in self.sitedirs or - # account for '.' being in PYTHONPATH - dist.location == os.getcwd() - ) - ) - if new_path: - self.paths.append(dist.location) - self.dirty = True - Environment.add(self, dist) - - def remove(self, dist): - """Remove `dist` from the distribution map""" - while dist.location in self.paths: - self.paths.remove(dist.location) - self.dirty = True - Environment.remove(self, dist) - - def make_relative(self, path): - npath, last = os.path.split(normalize_path(path)) - baselen = len(self.basedir) - parts = [last] - sep = os.altsep == '/' and '/' or os.sep - while len(npath) >= baselen: - if npath == self.basedir: - parts.append(os.curdir) - parts.reverse() - return sep.join(parts) - npath, last = os.path.split(npath) - parts.append(last) - else: - return path - - -class RewritePthDistributions(PthDistributions): - @classmethod - def _wrap_lines(cls, lines): - yield cls.prelude - for line in lines: - yield line - yield cls.postlude - - prelude = _one_liner(""" - import sys - sys.__plen = len(sys.path) - """) - postlude = _one_liner(""" - import sys - new = sys.path[sys.__plen:] - del sys.path[sys.__plen:] - p = getattr(sys, '__egginsert', 0) - sys.path[p:p] = new - sys.__egginsert = p + len(new) - """) - - -if os.environ.get('SETUPTOOLS_SYS_PATH_TECHNIQUE', 'raw') == 'rewrite': - PthDistributions = RewritePthDistributions - - -def _first_line_re(): - """ - Return a regular expression based on first_line_re suitable for matching - strings. - """ - if isinstance(first_line_re.pattern, str): - return first_line_re - - # first_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern. - return re.compile(first_line_re.pattern.decode()) - - -def auto_chmod(func, arg, exc): - if func in [os.unlink, os.remove] and os.name == 'nt': - chmod(arg, stat.S_IWRITE) - return func(arg) - et, ev, _ = sys.exc_info() - six.reraise(et, (ev[0], ev[1] + (" %s %s" % (func, arg)))) - - -def update_dist_caches(dist_path, fix_zipimporter_caches): - """ - Fix any globally cached `dist_path` related data - - `dist_path` should be a path of a newly installed egg distribution (zipped - or unzipped). - - sys.path_importer_cache contains finder objects that have been cached when - importing data from the original distribution. Any such finders need to be - cleared since the replacement distribution might be packaged differently, - e.g. a zipped egg distribution might get replaced with an unzipped egg - folder or vice versa. Having the old finders cached may then cause Python - to attempt loading modules from the replacement distribution using an - incorrect loader. - - zipimport.zipimporter objects are Python loaders charged with importing - data packaged inside zip archives. If stale loaders referencing the - original distribution, are left behind, they can fail to load modules from - the replacement distribution. E.g. if an old zipimport.zipimporter instance - is used to load data from a new zipped egg archive, it may cause the - operation to attempt to locate the requested data in the wrong location - - one indicated by the original distribution's zip archive directory - information. Such an operation may then fail outright, e.g. report having - read a 'bad local file header', or even worse, it may fail silently & - return invalid data. - - zipimport._zip_directory_cache contains cached zip archive directory - information for all existing zipimport.zipimporter instances and all such - instances connected to the same archive share the same cached directory - information. - - If asked, and the underlying Python implementation allows it, we can fix - all existing zipimport.zipimporter instances instead of having to track - them down and remove them one by one, by updating their shared cached zip - archive directory information. This, of course, assumes that the - replacement distribution is packaged as a zipped egg. - - If not asked to fix existing zipimport.zipimporter instances, we still do - our best to clear any remaining zipimport.zipimporter related cached data - that might somehow later get used when attempting to load data from the new - distribution and thus cause such load operations to fail. Note that when - tracking down such remaining stale data, we can not catch every conceivable - usage from here, and we clear only those that we know of and have found to - cause problems if left alive. Any remaining caches should be updated by - whomever is in charge of maintaining them, i.e. they should be ready to - handle us replacing their zip archives with new distributions at runtime. - - """ - # There are several other known sources of stale zipimport.zipimporter - # instances that we do not clear here, but might if ever given a reason to - # do so: - # * Global setuptools pkg_resources.working_set (a.k.a. 'master working - # set') may contain distributions which may in turn contain their - # zipimport.zipimporter loaders. - # * Several zipimport.zipimporter loaders held by local variables further - # up the function call stack when running the setuptools installation. - # * Already loaded modules may have their __loader__ attribute set to the - # exact loader instance used when importing them. Python 3.4 docs state - # that this information is intended mostly for introspection and so is - # not expected to cause us problems. - normalized_path = normalize_path(dist_path) - _uncache(normalized_path, sys.path_importer_cache) - if fix_zipimporter_caches: - _replace_zip_directory_cache_data(normalized_path) - else: - # Here, even though we do not want to fix existing and now stale - # zipimporter cache information, we still want to remove it. Related to - # Python's zip archive directory information cache, we clear each of - # its stale entries in two phases: - # 1. Clear the entry so attempting to access zip archive information - # via any existing stale zipimport.zipimporter instances fails. - # 2. Remove the entry from the cache so any newly constructed - # zipimport.zipimporter instances do not end up using old stale - # zip archive directory information. - # This whole stale data removal step does not seem strictly necessary, - # but has been left in because it was done before we started replacing - # the zip archive directory information cache content if possible, and - # there are no relevant unit tests that we can depend on to tell us if - # this is really needed. - _remove_and_clear_zip_directory_cache_data(normalized_path) - - -def _collect_zipimporter_cache_entries(normalized_path, cache): - """ - Return zipimporter cache entry keys related to a given normalized path. - - Alternative path spellings (e.g. those using different character case or - those using alternative path separators) related to the same path are - included. Any sub-path entries are included as well, i.e. those - corresponding to zip archives embedded in other zip archives. - - """ - result = [] - prefix_len = len(normalized_path) - for p in cache: - np = normalize_path(p) - if (np.startswith(normalized_path) and - np[prefix_len:prefix_len + 1] in (os.sep, '')): - result.append(p) - return result - - -def _update_zipimporter_cache(normalized_path, cache, updater=None): - """ - Update zipimporter cache data for a given normalized path. - - Any sub-path entries are processed as well, i.e. those corresponding to zip - archives embedded in other zip archives. - - Given updater is a callable taking a cache entry key and the original entry - (after already removing the entry from the cache), and expected to update - the entry and possibly return a new one to be inserted in its place. - Returning None indicates that the entry should not be replaced with a new - one. If no updater is given, the cache entries are simply removed without - any additional processing, the same as if the updater simply returned None. - - """ - for p in _collect_zipimporter_cache_entries(normalized_path, cache): - # N.B. pypy's custom zipimport._zip_directory_cache implementation does - # not support the complete dict interface: - # * Does not support item assignment, thus not allowing this function - # to be used only for removing existing cache entries. - # * Does not support the dict.pop() method, forcing us to use the - # get/del patterns instead. For more detailed information see the - # following links: - # https://github.com/pypa/setuptools/issues/202#issuecomment-202913420 - # http://bit.ly/2h9itJX - old_entry = cache[p] - del cache[p] - new_entry = updater and updater(p, old_entry) - if new_entry is not None: - cache[p] = new_entry - - -def _uncache(normalized_path, cache): - _update_zipimporter_cache(normalized_path, cache) - - -def _remove_and_clear_zip_directory_cache_data(normalized_path): - def clear_and_remove_cached_zip_archive_directory_data(path, old_entry): - old_entry.clear() - - _update_zipimporter_cache( - normalized_path, zipimport._zip_directory_cache, - updater=clear_and_remove_cached_zip_archive_directory_data) - - -# PyPy Python implementation does not allow directly writing to the -# zipimport._zip_directory_cache and so prevents us from attempting to correct -# its content. The best we can do there is clear the problematic cache content -# and have PyPy repopulate it as needed. The downside is that if there are any -# stale zipimport.zipimporter instances laying around, attempting to use them -# will fail due to not having its zip archive directory information available -# instead of being automatically corrected to use the new correct zip archive -# directory information. -if '__pypy__' in sys.builtin_module_names: - _replace_zip_directory_cache_data = \ - _remove_and_clear_zip_directory_cache_data -else: - - def _replace_zip_directory_cache_data(normalized_path): - def replace_cached_zip_archive_directory_data(path, old_entry): - # N.B. In theory, we could load the zip directory information just - # once for all updated path spellings, and then copy it locally and - # update its contained path strings to contain the correct - # spelling, but that seems like a way too invasive move (this cache - # structure is not officially documented anywhere and could in - # theory change with new Python releases) for no significant - # benefit. - old_entry.clear() - zipimport.zipimporter(path) - old_entry.update(zipimport._zip_directory_cache[path]) - return old_entry - - _update_zipimporter_cache( - normalized_path, zipimport._zip_directory_cache, - updater=replace_cached_zip_archive_directory_data) - - -def is_python(text, filename='<string>'): - "Is this string a valid Python script?" - try: - compile(text, filename, 'exec') - except (SyntaxError, TypeError): - return False - else: - return True - - -def is_sh(executable): - """Determine if the specified executable is a .sh (contains a #! line)""" - try: - with io.open(executable, encoding='latin-1') as fp: - magic = fp.read(2) - except (OSError, IOError): - return executable - return magic == '#!' - - -def nt_quote_arg(arg): - """Quote a command line argument according to Windows parsing rules""" - return subprocess.list2cmdline([arg]) - - -def is_python_script(script_text, filename): - """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc. - """ - if filename.endswith('.py') or filename.endswith('.pyw'): - return True # extension says it's Python - if is_python(script_text, filename): - return True # it's syntactically valid Python - if script_text.startswith('#!'): - # It begins with a '#!' line, so check if 'python' is in it somewhere - return 'python' in script_text.splitlines()[0].lower() - - return False # Not any Python I can recognize - - -try: - from os import chmod as _chmod -except ImportError: - # Jython compatibility - def _chmod(*args): - pass - - -def chmod(path, mode): - log.debug("changing mode of %s to %o", path, mode) - try: - _chmod(path, mode) - except os.error as e: - log.debug("chmod failed: %s", e) - - -class CommandSpec(list): - """ - A command spec for a #! header, specified as a list of arguments akin to - those passed to Popen. - """ - - options = [] - split_args = dict() - - @classmethod - def best(cls): - """ - Choose the best CommandSpec class based on environmental conditions. - """ - return cls - - @classmethod - def _sys_executable(cls): - _default = os.path.normpath(sys.executable) - return os.environ.get('__PYVENV_LAUNCHER__', _default) - - @classmethod - def from_param(cls, param): - """ - Construct a CommandSpec from a parameter to build_scripts, which may - be None. - """ - if isinstance(param, cls): - return param - if isinstance(param, list): - return cls(param) - if param is None: - return cls.from_environment() - # otherwise, assume it's a string. - return cls.from_string(param) - - @classmethod - def from_environment(cls): - return cls([cls._sys_executable()]) - - @classmethod - def from_string(cls, string): - """ - Construct a command spec from a simple string representing a command - line parseable by shlex.split. - """ - items = shlex.split(string, **cls.split_args) - return cls(items) - - def install_options(self, script_text): - self.options = shlex.split(self._extract_options(script_text)) - cmdline = subprocess.list2cmdline(self) - if not isascii(cmdline): - self.options[:0] = ['-x'] - - @staticmethod - def _extract_options(orig_script): - """ - Extract any options from the first line of the script. - """ - first = (orig_script + '\n').splitlines()[0] - match = _first_line_re().match(first) - options = match.group(1) or '' if match else '' - return options.strip() - - def as_header(self): - return self._render(self + list(self.options)) - - @staticmethod - def _strip_quotes(item): - _QUOTES = '"\'' - for q in _QUOTES: - if item.startswith(q) and item.endswith(q): - return item[1:-1] - return item - - @staticmethod - def _render(items): - cmdline = subprocess.list2cmdline( - CommandSpec._strip_quotes(item.strip()) for item in items) - return '#!' + cmdline + '\n' - - -# For pbr compat; will be removed in a future version. -sys_executable = CommandSpec._sys_executable() - - -class WindowsCommandSpec(CommandSpec): - split_args = dict(posix=False) - - -class ScriptWriter: - """ - Encapsulates behavior around writing entry point scripts for console and - gui apps. - """ - - template = textwrap.dedent(r""" - # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r - __requires__ = %(spec)r - import re - import sys - from pkg_resources import load_entry_point - - if __name__ == '__main__': - sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) - sys.exit( - load_entry_point(%(spec)r, %(group)r, %(name)r)() - ) - """).lstrip() - - command_spec_class = CommandSpec - - @classmethod - def get_script_args(cls, dist, executable=None, wininst=False): - # for backward compatibility - warnings.warn("Use get_args", EasyInstallDeprecationWarning) - writer = (WindowsScriptWriter if wininst else ScriptWriter).best() - header = cls.get_script_header("", executable, wininst) - return writer.get_args(dist, header) - - @classmethod - def get_script_header(cls, script_text, executable=None, wininst=False): - # for backward compatibility - warnings.warn("Use get_header", EasyInstallDeprecationWarning, stacklevel=2) - if wininst: - executable = "python.exe" - return cls.get_header(script_text, executable) - - @classmethod - def get_args(cls, dist, header=None): - """ - Yield write_script() argument tuples for a distribution's - console_scripts and gui_scripts entry points. - """ - if header is None: - header = cls.get_header() - spec = str(dist.as_requirement()) - for type_ in 'console', 'gui': - group = type_ + '_scripts' - for name, ep in dist.get_entry_map(group).items(): - cls._ensure_safe_name(name) - script_text = cls.template % locals() - args = cls._get_script_args(type_, name, header, script_text) - for res in args: - yield res - - @staticmethod - def _ensure_safe_name(name): - """ - Prevent paths in *_scripts entry point names. - """ - has_path_sep = re.search(r'[\\/]', name) - if has_path_sep: - raise ValueError("Path separators not allowed in script names") - - @classmethod - def get_writer(cls, force_windows): - # for backward compatibility - warnings.warn("Use best", EasyInstallDeprecationWarning) - return WindowsScriptWriter.best() if force_windows else cls.best() - - @classmethod - def best(cls): - """ - Select the best ScriptWriter for this environment. - """ - if sys.platform == 'win32' or (os.name == 'java' and os._name == 'nt'): - return WindowsScriptWriter.best() - else: - return cls - - @classmethod - def _get_script_args(cls, type_, name, header, script_text): - # Simply write the stub with no extension. - yield (name, header + script_text) - - @classmethod - def get_header(cls, script_text="", executable=None): - """Create a #! line, getting options (if any) from script_text""" - cmd = cls.command_spec_class.best().from_param(executable) - cmd.install_options(script_text) - return cmd.as_header() - - -class WindowsScriptWriter(ScriptWriter): - command_spec_class = WindowsCommandSpec - - @classmethod - def get_writer(cls): - # for backward compatibility - warnings.warn("Use best", EasyInstallDeprecationWarning) - return cls.best() - - @classmethod - def best(cls): - """ - Select the best ScriptWriter suitable for Windows - """ - writer_lookup = dict( - executable=WindowsExecutableLauncherWriter, - natural=cls, - ) - # for compatibility, use the executable launcher by default - launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable') - return writer_lookup[launcher] - - @classmethod - def _get_script_args(cls, type_, name, header, script_text): - "For Windows, add a .py extension" - ext = dict(console='.pya', gui='.pyw')[type_] - if ext not in os.environ['PATHEXT'].lower().split(';'): - msg = ( - "{ext} not listed in PATHEXT; scripts will not be " - "recognized as executables." - ).format(**locals()) - warnings.warn(msg, UserWarning) - old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe'] - old.remove(ext) - header = cls._adjust_header(type_, header) - blockers = [name + x for x in old] - yield name + ext, header + script_text, 't', blockers - - @classmethod - def _adjust_header(cls, type_, orig_header): - """ - Make sure 'pythonw' is used for gui and and 'python' is used for - console (regardless of what sys.executable is). - """ - pattern = 'pythonw.exe' - repl = 'python.exe' - if type_ == 'gui': - pattern, repl = repl, pattern - pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE) - new_header = pattern_ob.sub(string=orig_header, repl=repl) - return new_header if cls._use_header(new_header) else orig_header - - @staticmethod - def _use_header(new_header): - """ - Should _adjust_header use the replaced header? - - On non-windows systems, always use. On - Windows systems, only use the replaced header if it resolves - to an executable on the system. - """ - clean_header = new_header[2:-1].strip('"') - return sys.platform != 'win32' or find_executable(clean_header) - - -class WindowsExecutableLauncherWriter(WindowsScriptWriter): - @classmethod - def _get_script_args(cls, type_, name, header, script_text): - """ - For Windows, add a .py extension and an .exe launcher - """ - if type_ == 'gui': - launcher_type = 'gui' - ext = '-script.pyw' - old = ['.pyw'] - else: - launcher_type = 'cli' - ext = '-script.py' - old = ['.py', '.pyc', '.pyo'] - hdr = cls._adjust_header(type_, header) - blockers = [name + x for x in old] - yield (name + ext, hdr + script_text, 't', blockers) - yield ( - name + '.exe', get_win_launcher(launcher_type), - 'b' # write in binary mode - ) - if not is_64bit(): - # install a manifest for the launcher to prevent Windows - # from detecting it as an installer (which it will for - # launchers like easy_install.exe). Consider only - # adding a manifest for launchers detected as installers. - # See Distribute #143 for details. - m_name = name + '.exe.manifest' - yield (m_name, load_launcher_manifest(name), 't') - - -# for backward-compatibility -get_script_args = ScriptWriter.get_script_args -get_script_header = ScriptWriter.get_script_header - - -def get_win_launcher(type): - """ - Load the Windows launcher (executable) suitable for launching a script. - - `type` should be either 'cli' or 'gui' - - Returns the executable as a byte string. - """ - launcher_fn = '%s.exe' % type - if is_64bit(): - launcher_fn = launcher_fn.replace(".", "-64.") - else: - launcher_fn = launcher_fn.replace(".", "-32.") - return resource_string('setuptools', launcher_fn) - - -def load_launcher_manifest(name): - manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml') - if six.PY2: - return manifest % vars() - else: - return manifest.decode('utf-8') % vars() - - -def rmtree(path, ignore_errors=False, onerror=auto_chmod): - return shutil.rmtree(path, ignore_errors, onerror) - - -def current_umask(): - tmp = os.umask(0o022) - os.umask(tmp) - return tmp - - -def bootstrap(): - # This function is called when setuptools*.egg is run using /bin/sh - import setuptools - - argv0 = os.path.dirname(setuptools.__path__[0]) - sys.argv[0] = argv0 - sys.argv.append(argv0) - main() - - -def main(argv=None, **kw): - from setuptools import setup - from setuptools.dist import Distribution - - class DistributionWithoutHelpCommands(Distribution): - common_usage = "" - - def _show_help(self, *args, **kw): - with _patch_usage(): - Distribution._show_help(self, *args, **kw) - - if argv is None: - argv = sys.argv[1:] - - with _patch_usage(): - setup( - script_args=['-q', 'easy_install', '-v'] + argv, - script_name=sys.argv[0] or 'easy_install', - distclass=DistributionWithoutHelpCommands, - **kw - ) - - -@contextlib.contextmanager -def _patch_usage(): - import distutils.core - USAGE = textwrap.dedent(""" - usage: %(script)s [options] requirement_or_url ... - or: %(script)s --help - """).lstrip() - - def gen_usage(script_name): - return USAGE % dict( - script=os.path.basename(script_name), - ) - - saved = distutils.core.gen_usage - distutils.core.gen_usage = gen_usage - try: - yield - finally: - distutils.core.gen_usage = saved - -class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning): - """Class for warning about deprecations in EasyInstall in SetupTools. Not ignored by default, unlike DeprecationWarning.""" - diff --git a/lib/setuptools/command/egg_info.py b/lib/setuptools/command/egg_info.py deleted file mode 100644 index d9fe3da..0000000 --- a/lib/setuptools/command/egg_info.py +++ /dev/null @@ -1,716 +0,0 @@ -"""setuptools.command.egg_info - -Create a distribution's .egg-info directory and contents""" - -from distutils.filelist import FileList as _FileList -from distutils.errors import DistutilsInternalError -from distutils.util import convert_path -from distutils import log -import distutils.errors -import distutils.filelist -import os -import re -import sys -import io -import warnings -import time -import collections - -from setuptools.extern import six -from setuptools.extern.six.moves import map - -from setuptools import Command -from setuptools.command.sdist import sdist -from setuptools.command.sdist import walk_revctrl -from setuptools.command.setopt import edit_config -from setuptools.command import bdist_egg -from pkg_resources import ( - parse_requirements, safe_name, parse_version, - safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename) -import setuptools.unicode_utils as unicode_utils -from setuptools.glob import glob - -from setuptools.extern import packaging -from setuptools import SetuptoolsDeprecationWarning - -def translate_pattern(glob): - """ - Translate a file path glob like '*.txt' in to a regular expression. - This differs from fnmatch.translate which allows wildcards to match - directory separators. It also knows about '**/' which matches any number of - directories. - """ - pat = '' - - # This will split on '/' within [character classes]. This is deliberate. - chunks = glob.split(os.path.sep) - - sep = re.escape(os.sep) - valid_char = '[^%s]' % (sep,) - - for c, chunk in enumerate(chunks): - last_chunk = c == len(chunks) - 1 - - # Chunks that are a literal ** are globstars. They match anything. - if chunk == '**': - if last_chunk: - # Match anything if this is the last component - pat += '.*' - else: - # Match '(name/)*' - pat += '(?:%s+%s)*' % (valid_char, sep) - continue # Break here as the whole path component has been handled - - # Find any special characters in the remainder - i = 0 - chunk_len = len(chunk) - while i < chunk_len: - char = chunk[i] - if char == '*': - # Match any number of name characters - pat += valid_char + '*' - elif char == '?': - # Match a name character - pat += valid_char - elif char == '[': - # Character class - inner_i = i + 1 - # Skip initial !/] chars - if inner_i < chunk_len and chunk[inner_i] == '!': - inner_i = inner_i + 1 - if inner_i < chunk_len and chunk[inner_i] == ']': - inner_i = inner_i + 1 - - # Loop till the closing ] is found - while inner_i < chunk_len and chunk[inner_i] != ']': - inner_i = inner_i + 1 - - if inner_i >= chunk_len: - # Got to the end of the string without finding a closing ] - # Do not treat this as a matching group, but as a literal [ - pat += re.escape(char) - else: - # Grab the insides of the [brackets] - inner = chunk[i + 1:inner_i] - char_class = '' - - # Class negation - if inner[0] == '!': - char_class = '^' - inner = inner[1:] - - char_class += re.escape(inner) - pat += '[%s]' % (char_class,) - - # Skip to the end ] - i = inner_i - else: - pat += re.escape(char) - i += 1 - - # Join each chunk with the dir separator - if not last_chunk: - pat += sep - - pat += r'\Z' - return re.compile(pat, flags=re.MULTILINE|re.DOTALL) - - -class InfoCommon: - tag_build = None - tag_date = None - - @property - def name(self): - return safe_name(self.distribution.get_name()) - - def tagged_version(self): - version = self.distribution.get_version() - # egg_info may be called more than once for a distribution, - # in which case the version string already contains all tags. - if self.vtags and version.endswith(self.vtags): - return safe_version(version) - return safe_version(version + self.vtags) - - def tags(self): - version = '' - if self.tag_build: - version += self.tag_build - if self.tag_date: - version += time.strftime("-%Y%m%d") - return version - vtags = property(tags) - - -class egg_info(InfoCommon, Command): - description = "create a distribution's .egg-info directory" - - user_options = [ - ('egg-base=', 'e', "directory containing .egg-info directories" - " (default: top of the source tree)"), - ('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"), - ('tag-build=', 'b', "Specify explicit tag to add to version number"), - ('no-date', 'D', "Don't include date stamp [default]"), - ] - - boolean_options = ['tag-date'] - negative_opt = { - 'no-date': 'tag-date', - } - - def initialize_options(self): - self.egg_base = None - self.egg_name = None - self.egg_info = None - self.egg_version = None - self.broken_egg_info = False - - #################################### - # allow the 'tag_svn_revision' to be detected and - # set, supporting sdists built on older Setuptools. - @property - def tag_svn_revision(self): - pass - - @tag_svn_revision.setter - def tag_svn_revision(self, value): - pass - #################################### - - def save_version_info(self, filename): - """ - Materialize the value of date into the - build tag. Install build keys in a deterministic order - to avoid arbitrary reordering on subsequent builds. - """ - egg_info = collections.OrderedDict() - # follow the order these keys would have been added - # when PYTHONHASHSEED=0 - egg_info['tag_build'] = self.tags() - egg_info['tag_date'] = 0 - edit_config(filename, dict(egg_info=egg_info)) - - def finalize_options(self): - # Note: we need to capture the current value returned - # by `self.tagged_version()`, so we can later update - # `self.distribution.metadata.version` without - # repercussions. - self.egg_name = self.name - self.egg_version = self.tagged_version() - parsed_version = parse_version(self.egg_version) - - try: - is_version = isinstance(parsed_version, packaging.version.Version) - spec = ( - "%s==%s" if is_version else "%s===%s" - ) - list( - parse_requirements(spec % (self.egg_name, self.egg_version)) - ) - except ValueError: - raise distutils.errors.DistutilsOptionError( - "Invalid distribution name or version syntax: %s-%s" % - (self.egg_name, self.egg_version) - ) - - if self.egg_base is None: - dirs = self.distribution.package_dir - self.egg_base = (dirs or {}).get('', os.curdir) - - self.ensure_dirname('egg_base') - self.egg_info = to_filename(self.egg_name) + '.egg-info' - if self.egg_base != os.curdir: - self.egg_info = os.path.join(self.egg_base, self.egg_info) - if '-' in self.egg_name: - self.check_broken_egg_info() - - # Set package version for the benefit of dumber commands - # (e.g. sdist, bdist_wininst, etc.) - # - self.distribution.metadata.version = self.egg_version - - # If we bootstrapped around the lack of a PKG-INFO, as might be the - # case in a fresh checkout, make sure that any special tags get added - # to the version info - # - pd = self.distribution._patched_dist - if pd is not None and pd.key == self.egg_name.lower(): - pd._version = self.egg_version - pd._parsed_version = parse_version(self.egg_version) - self.distribution._patched_dist = None - - def write_or_delete_file(self, what, filename, data, force=False): - """Write `data` to `filename` or delete if empty - - If `data` is non-empty, this routine is the same as ``write_file()``. - If `data` is empty but not ``None``, this is the same as calling - ``delete_file(filename)`. If `data` is ``None``, then this is a no-op - unless `filename` exists, in which case a warning is issued about the - orphaned file (if `force` is false), or deleted (if `force` is true). - """ - if data: - self.write_file(what, filename, data) - elif os.path.exists(filename): - if data is None and not force: - log.warn( - "%s not set in setup(), but %s exists", what, filename - ) - return - else: - self.delete_file(filename) - - def write_file(self, what, filename, data): - """Write `data` to `filename` (if not a dry run) after announcing it - - `what` is used in a log message to identify what is being written - to the file. - """ - log.info("writing %s to %s", what, filename) - if six.PY3: - data = data.encode("utf-8") - if not self.dry_run: - f = open(filename, 'wb') - f.write(data) - f.close() - - def delete_file(self, filename): - """Delete `filename` (if not a dry run) after announcing it""" - log.info("deleting %s", filename) - if not self.dry_run: - os.unlink(filename) - - def run(self): - self.mkpath(self.egg_info) - os.utime(self.egg_info, None) - installer = self.distribution.fetch_build_egg - for ep in iter_entry_points('egg_info.writers'): - ep.require(installer=installer) - writer = ep.resolve() - writer(self, ep.name, os.path.join(self.egg_info, ep.name)) - - # Get rid of native_libs.txt if it was put there by older bdist_egg - nl = os.path.join(self.egg_info, "native_libs.txt") - if os.path.exists(nl): - self.delete_file(nl) - - self.find_sources() - - def find_sources(self): - """Generate SOURCES.txt manifest file""" - manifest_filename = os.path.join(self.egg_info, "SOURCES.txt") - mm = manifest_maker(self.distribution) - mm.manifest = manifest_filename - mm.run() - self.filelist = mm.filelist - - def check_broken_egg_info(self): - bei = self.egg_name + '.egg-info' - if self.egg_base != os.curdir: - bei = os.path.join(self.egg_base, bei) - if os.path.exists(bei): - log.warn( - "-" * 78 + '\n' - "Note: Your current .egg-info directory has a '-' in its name;" - '\nthis will not work correctly with "setup.py develop".\n\n' - 'Please rename %s to %s to correct this problem.\n' + '-' * 78, - bei, self.egg_info - ) - self.broken_egg_info = self.egg_info - self.egg_info = bei # make it work for now - - -class FileList(_FileList): - # Implementations of the various MANIFEST.in commands - - def process_template_line(self, line): - # Parse the line: split it up, make sure the right number of words - # is there, and return the relevant words. 'action' is always - # defined: it's the first word of the line. Which of the other - # three are defined depends on the action; it'll be either - # patterns, (dir and patterns), or (dir_pattern). - (action, patterns, dir, dir_pattern) = self._parse_template_line(line) - - # OK, now we know that the action is valid and we have the - # right number of words on the line for that action -- so we - # can proceed with minimal error-checking. - if action == 'include': - self.debug_print("include " + ' '.join(patterns)) - for pattern in patterns: - if not self.include(pattern): - log.warn("warning: no files found matching '%s'", pattern) - - elif action == 'exclude': - self.debug_print("exclude " + ' '.join(patterns)) - for pattern in patterns: - if not self.exclude(pattern): - log.warn(("warning: no previously-included files " - "found matching '%s'"), pattern) - - elif action == 'global-include': - self.debug_print("global-include " + ' '.join(patterns)) - for pattern in patterns: - if not self.global_include(pattern): - log.warn(("warning: no files found matching '%s' " - "anywhere in distribution"), pattern) - - elif action == 'global-exclude': - self.debug_print("global-exclude " + ' '.join(patterns)) - for pattern in patterns: - if not self.global_exclude(pattern): - log.warn(("warning: no previously-included files matching " - "'%s' found anywhere in distribution"), - pattern) - - elif action == 'recursive-include': - self.debug_print("recursive-include %s %s" % - (dir, ' '.join(patterns))) - for pattern in patterns: - if not self.recursive_include(dir, pattern): - log.warn(("warning: no files found matching '%s' " - "under directory '%s'"), - pattern, dir) - - elif action == 'recursive-exclude': - self.debug_print("recursive-exclude %s %s" % - (dir, ' '.join(patterns))) - for pattern in patterns: - if not self.recursive_exclude(dir, pattern): - log.warn(("warning: no previously-included files matching " - "'%s' found under directory '%s'"), - pattern, dir) - - elif action == 'graft': - self.debug_print("graft " + dir_pattern) - if not self.graft(dir_pattern): - log.warn("warning: no directories found matching '%s'", - dir_pattern) - - elif action == 'prune': - self.debug_print("prune " + dir_pattern) - if not self.prune(dir_pattern): - log.warn(("no previously-included directories found " - "matching '%s'"), dir_pattern) - - else: - raise DistutilsInternalError( - "this cannot happen: invalid action '%s'" % action) - - def _remove_files(self, predicate): - """ - Remove all files from the file list that match the predicate. - Return True if any matching files were removed - """ - found = False - for i in range(len(self.files) - 1, -1, -1): - if predicate(self.files[i]): - self.debug_print(" removing " + self.files[i]) - del self.files[i] - found = True - return found - - def include(self, pattern): - """Include files that match 'pattern'.""" - found = [f for f in glob(pattern) if not os.path.isdir(f)] - self.extend(found) - return bool(found) - - def exclude(self, pattern): - """Exclude files that match 'pattern'.""" - match = translate_pattern(pattern) - return self._remove_files(match.match) - - def recursive_include(self, dir, pattern): - """ - Include all files anywhere in 'dir/' that match the pattern. - """ - full_pattern = os.path.join(dir, '**', pattern) - found = [f for f in glob(full_pattern, recursive=True) - if not os.path.isdir(f)] - self.extend(found) - return bool(found) - - def recursive_exclude(self, dir, pattern): - """ - Exclude any file anywhere in 'dir/' that match the pattern. - """ - match = translate_pattern(os.path.join(dir, '**', pattern)) - return self._remove_files(match.match) - - def graft(self, dir): - """Include all files from 'dir/'.""" - found = [ - item - for match_dir in glob(dir) - for item in distutils.filelist.findall(match_dir) - ] - self.extend(found) - return bool(found) - - def prune(self, dir): - """Filter out files from 'dir/'.""" - match = translate_pattern(os.path.join(dir, '**')) - return self._remove_files(match.match) - - def global_include(self, pattern): - """ - Include all files anywhere in the current directory that match the - pattern. This is very inefficient on large file trees. - """ - if self.allfiles is None: - self.findall() - match = translate_pattern(os.path.join('**', pattern)) - found = [f for f in self.allfiles if match.match(f)] - self.extend(found) - return bool(found) - - def global_exclude(self, pattern): - """ - Exclude all files anywhere that match the pattern. - """ - match = translate_pattern(os.path.join('**', pattern)) - return self._remove_files(match.match) - - def append(self, item): - if item.endswith('\r'): # Fix older sdists built on Windows - item = item[:-1] - path = convert_path(item) - - if self._safe_path(path): - self.files.append(path) - - def extend(self, paths): - self.files.extend(filter(self._safe_path, paths)) - - def _repair(self): - """ - Replace self.files with only safe paths - - Because some owners of FileList manipulate the underlying - ``files`` attribute directly, this method must be called to - repair those paths. - """ - self.files = list(filter(self._safe_path, self.files)) - - def _safe_path(self, path): - enc_warn = "'%s' not %s encodable -- skipping" - - # To avoid accidental trans-codings errors, first to unicode - u_path = unicode_utils.filesys_decode(path) - if u_path is None: - log.warn("'%s' in unexpected encoding -- skipping" % path) - return False - - # Must ensure utf-8 encodability - utf8_path = unicode_utils.try_encode(u_path, "utf-8") - if utf8_path is None: - log.warn(enc_warn, path, 'utf-8') - return False - - try: - # accept is either way checks out - if os.path.exists(u_path) or os.path.exists(utf8_path): - return True - # this will catch any encode errors decoding u_path - except UnicodeEncodeError: - log.warn(enc_warn, path, sys.getfilesystemencoding()) - - -class manifest_maker(sdist): - template = "MANIFEST.in" - - def initialize_options(self): - self.use_defaults = 1 - self.prune = 1 - self.manifest_only = 1 - self.force_manifest = 1 - - def finalize_options(self): - pass - - def run(self): - self.filelist = FileList() - if not os.path.exists(self.manifest): - self.write_manifest() # it must exist so it'll get in the list - self.add_defaults() - if os.path.exists(self.template): - self.read_template() - self.prune_file_list() - self.filelist.sort() - self.filelist.remove_duplicates() - self.write_manifest() - - def _manifest_normalize(self, path): - path = unicode_utils.filesys_decode(path) - return path.replace(os.sep, '/') - - def write_manifest(self): - """ - Write the file list in 'self.filelist' to the manifest file - named by 'self.manifest'. - """ - self.filelist._repair() - - # Now _repairs should encodability, but not unicode - files = [self._manifest_normalize(f) for f in self.filelist.files] - msg = "writing manifest file '%s'" % self.manifest - self.execute(write_file, (self.manifest, files), msg) - - def warn(self, msg): - if not self._should_suppress_warning(msg): - sdist.warn(self, msg) - - @staticmethod - def _should_suppress_warning(msg): - """ - suppress missing-file warnings from sdist - """ - return re.match(r"standard file .*not found", msg) - - def add_defaults(self): - sdist.add_defaults(self) - self.filelist.append(self.template) - self.filelist.append(self.manifest) - rcfiles = list(walk_revctrl()) - if rcfiles: - self.filelist.extend(rcfiles) - elif os.path.exists(self.manifest): - self.read_manifest() - - if os.path.exists("setup.py"): - # setup.py should be included by default, even if it's not - # the script called to create the sdist - self.filelist.append("setup.py") - - ei_cmd = self.get_finalized_command('egg_info') - self.filelist.graft(ei_cmd.egg_info) - - def prune_file_list(self): - build = self.get_finalized_command('build') - base_dir = self.distribution.get_fullname() - self.filelist.prune(build.build_base) - self.filelist.prune(base_dir) - sep = re.escape(os.sep) - self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep, - is_regex=1) - - -def write_file(filename, contents): - """Create a file with the specified name and write 'contents' (a - sequence of strings without line terminators) to it. - """ - contents = "\n".join(contents) - - # assuming the contents has been vetted for utf-8 encoding - contents = contents.encode("utf-8") - - with open(filename, "wb") as f: # always write POSIX-style manifest - f.write(contents) - - -def write_pkg_info(cmd, basename, filename): - log.info("writing %s", filename) - if not cmd.dry_run: - metadata = cmd.distribution.metadata - metadata.version, oldver = cmd.egg_version, metadata.version - metadata.name, oldname = cmd.egg_name, metadata.name - - try: - # write unescaped data to PKG-INFO, so older pkg_resources - # can still parse it - metadata.write_pkg_info(cmd.egg_info) - finally: - metadata.name, metadata.version = oldname, oldver - - safe = getattr(cmd.distribution, 'zip_safe', None) - - bdist_egg.write_safety_flag(cmd.egg_info, safe) - - -def warn_depends_obsolete(cmd, basename, filename): - if os.path.exists(filename): - log.warn( - "WARNING: 'depends.txt' is not used by setuptools 0.6!\n" - "Use the install_requires/extras_require setup() args instead." - ) - - -def _write_requirements(stream, reqs): - lines = yield_lines(reqs or ()) - append_cr = lambda line: line + '\n' - lines = map(append_cr, lines) - stream.writelines(lines) - - -def write_requirements(cmd, basename, filename): - dist = cmd.distribution - data = six.StringIO() - _write_requirements(data, dist.install_requires) - extras_require = dist.extras_require or {} - for extra in sorted(extras_require): - data.write('\n[{extra}]\n'.format(**vars())) - _write_requirements(data, extras_require[extra]) - cmd.write_or_delete_file("requirements", filename, data.getvalue()) - - -def write_setup_requirements(cmd, basename, filename): - data = io.StringIO() - _write_requirements(data, cmd.distribution.setup_requires) - cmd.write_or_delete_file("setup-requirements", filename, data.getvalue()) - - -def write_toplevel_names(cmd, basename, filename): - pkgs = dict.fromkeys( - [ - k.split('.', 1)[0] - for k in cmd.distribution.iter_distribution_names() - ] - ) - cmd.write_file("top-level names", filename, '\n'.join(sorted(pkgs)) + '\n') - - -def overwrite_arg(cmd, basename, filename): - write_arg(cmd, basename, filename, True) - - -def write_arg(cmd, basename, filename, force=False): - argname = os.path.splitext(basename)[0] - value = getattr(cmd.distribution, argname, None) - if value is not None: - value = '\n'.join(value) + '\n' - cmd.write_or_delete_file(argname, filename, value, force) - - -def write_entries(cmd, basename, filename): - ep = cmd.distribution.entry_points - - if isinstance(ep, six.string_types) or ep is None: - data = ep - elif ep is not None: - data = [] - for section, contents in sorted(ep.items()): - if not isinstance(contents, six.string_types): - contents = EntryPoint.parse_group(section, contents) - contents = '\n'.join(sorted(map(str, contents.values()))) - data.append('[%s]\n%s\n\n' % (section, contents)) - data = ''.join(data) - - cmd.write_or_delete_file('entry points', filename, data, True) - - -def get_pkg_info_revision(): - """ - Get a -r### off of PKG-INFO Version in case this is an sdist of - a subversion revision. - """ - warnings.warn("get_pkg_info_revision is deprecated.", EggInfoDeprecationWarning) - if os.path.exists('PKG-INFO'): - with io.open('PKG-INFO') as f: - for line in f: - match = re.match(r"Version:.*-r(\d+)\s*$", line) - if match: - return int(match.group(1)) - return 0 - - -class EggInfoDeprecationWarning(SetuptoolsDeprecationWarning): - """Class for warning about deprecations in eggInfo in setupTools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/lib/setuptools/command/install.py b/lib/setuptools/command/install.py deleted file mode 100644 index 31a5ddb..0000000 --- a/lib/setuptools/command/install.py +++ /dev/null @@ -1,125 +0,0 @@ -from distutils.errors import DistutilsArgError -import inspect -import glob -import warnings -import platform -import distutils.command.install as orig - -import setuptools - -# Prior to numpy 1.9, NumPy relies on the '_install' name, so provide it for -# now. See https://github.com/pypa/setuptools/issues/199/ -_install = orig.install - - -class install(orig.install): - """Use easy_install to install the package, w/dependencies""" - - user_options = orig.install.user_options + [ - ('old-and-unmanageable', None, "Try not to use this!"), - ('single-version-externally-managed', None, - "used by system package builders to create 'flat' eggs"), - ] - boolean_options = orig.install.boolean_options + [ - 'old-and-unmanageable', 'single-version-externally-managed', - ] - new_commands = [ - ('install_egg_info', lambda self: True), - ('install_scripts', lambda self: True), - ] - _nc = dict(new_commands) - - def initialize_options(self): - orig.install.initialize_options(self) - self.old_and_unmanageable = None - self.single_version_externally_managed = None - - def finalize_options(self): - orig.install.finalize_options(self) - if self.root: - self.single_version_externally_managed = True - elif self.single_version_externally_managed: - if not self.root and not self.record: - raise DistutilsArgError( - "You must specify --record or --root when building system" - " packages" - ) - - def handle_extra_path(self): - if self.root or self.single_version_externally_managed: - # explicit backward-compatibility mode, allow extra_path to work - return orig.install.handle_extra_path(self) - - # Ignore extra_path when installing an egg (or being run by another - # command without --root or --single-version-externally-managed - self.path_file = None - self.extra_dirs = '' - - def run(self): - # Explicit request for old-style install? Just do it - if self.old_and_unmanageable or self.single_version_externally_managed: - return orig.install.run(self) - - if not self._called_from_setup(inspect.currentframe()): - # Run in backward-compatibility mode to support bdist_* commands. - orig.install.run(self) - else: - self.do_egg_install() - - @staticmethod - def _called_from_setup(run_frame): - """ - Attempt to detect whether run() was called from setup() or by another - command. If called by setup(), the parent caller will be the - 'run_command' method in 'distutils.dist', and *its* caller will be - the 'run_commands' method. If called any other way, the - immediate caller *might* be 'run_command', but it won't have been - called by 'run_commands'. Return True in that case or if a call stack - is unavailable. Return False otherwise. - """ - if run_frame is None: - msg = "Call stack not available. bdist_* commands may fail." - warnings.warn(msg) - if platform.python_implementation() == 'IronPython': - msg = "For best results, pass -X:Frames to enable call stack." - warnings.warn(msg) - return True - res = inspect.getouterframes(run_frame)[2] - caller, = res[:1] - info = inspect.getframeinfo(caller) - caller_module = caller.f_globals.get('__name__', '') - return ( - caller_module == 'distutils.dist' - and info.function == 'run_commands' - ) - - def do_egg_install(self): - - easy_install = self.distribution.get_command_class('easy_install') - - cmd = easy_install( - self.distribution, args="x", root=self.root, record=self.record, - ) - cmd.ensure_finalized() # finalize before bdist_egg munges install cmd - cmd.always_copy_from = '.' # make sure local-dir eggs get installed - - # pick up setup-dir .egg files only: no .egg-info - cmd.package_index.scan(glob.glob('*.egg')) - - self.run_command('bdist_egg') - args = [self.distribution.get_command_obj('bdist_egg').egg_output] - - if setuptools.bootstrap_install_from: - # Bootstrap self-installation of setuptools - args.insert(0, setuptools.bootstrap_install_from) - - cmd.args = args - cmd.run() - setuptools.bootstrap_install_from = None - - -# XXX Python 3.1 doesn't see _nc if this is inside the class -install.sub_commands = ( - [cmd for cmd in orig.install.sub_commands if cmd[0] not in install._nc] + - install.new_commands -) diff --git a/lib/setuptools/command/install_egg_info.py b/lib/setuptools/command/install_egg_info.py deleted file mode 100644 index edc4718..0000000 --- a/lib/setuptools/command/install_egg_info.py +++ /dev/null @@ -1,62 +0,0 @@ -from distutils import log, dir_util -import os - -from setuptools import Command -from setuptools import namespaces -from setuptools.archive_util import unpack_archive -import pkg_resources - - -class install_egg_info(namespaces.Installer, Command): - """Install an .egg-info directory for the package""" - - description = "Install an .egg-info directory for the package" - - user_options = [ - ('install-dir=', 'd', "directory to install to"), - ] - - def initialize_options(self): - self.install_dir = None - - def finalize_options(self): - self.set_undefined_options('install_lib', - ('install_dir', 'install_dir')) - ei_cmd = self.get_finalized_command("egg_info") - basename = pkg_resources.Distribution( - None, None, ei_cmd.egg_name, ei_cmd.egg_version - ).egg_name() + '.egg-info' - self.source = ei_cmd.egg_info - self.target = os.path.join(self.install_dir, basename) - self.outputs = [] - - def run(self): - self.run_command('egg_info') - if os.path.isdir(self.target) and not os.path.islink(self.target): - dir_util.remove_tree(self.target, dry_run=self.dry_run) - elif os.path.exists(self.target): - self.execute(os.unlink, (self.target,), "Removing " + self.target) - if not self.dry_run: - pkg_resources.ensure_directory(self.target) - self.execute( - self.copytree, (), "Copying %s to %s" % (self.source, self.target) - ) - self.install_namespaces() - - def get_outputs(self): - return self.outputs - - def copytree(self): - # Copy the .egg-info tree to site-packages - def skimmer(src, dst): - # filter out source-control directories; note that 'src' is always - # a '/'-separated path, regardless of platform. 'dst' is a - # platform-specific path. - for skip in '.svn/', 'CVS/': - if src.startswith(skip) or '/' + skip in src: - return None - self.outputs.append(dst) - log.debug("Copying %s to %s", src, dst) - return dst - - unpack_archive(self.source, self.target, skimmer) diff --git a/lib/setuptools/command/install_lib.py b/lib/setuptools/command/install_lib.py deleted file mode 100644 index 2b31c3e..0000000 --- a/lib/setuptools/command/install_lib.py +++ /dev/null @@ -1,121 +0,0 @@ -import os -import imp -from itertools import product, starmap -import distutils.command.install_lib as orig - - -class install_lib(orig.install_lib): - """Don't add compiled flags to filenames of non-Python files""" - - def run(self): - self.build() - outfiles = self.install() - if outfiles is not None: - # always compile, in case we have any extension stubs to deal with - self.byte_compile(outfiles) - - def get_exclusions(self): - """ - Return a collections.Sized collections.Container of paths to be - excluded for single_version_externally_managed installations. - """ - all_packages = ( - pkg - for ns_pkg in self._get_SVEM_NSPs() - for pkg in self._all_packages(ns_pkg) - ) - - excl_specs = product(all_packages, self._gen_exclusion_paths()) - return set(starmap(self._exclude_pkg_path, excl_specs)) - - def _exclude_pkg_path(self, pkg, exclusion_path): - """ - Given a package name and exclusion path within that package, - compute the full exclusion path. - """ - parts = pkg.split('.') + [exclusion_path] - return os.path.join(self.install_dir, *parts) - - @staticmethod - def _all_packages(pkg_name): - """ - >>> list(install_lib._all_packages('foo.bar.baz')) - ['foo.bar.baz', 'foo.bar', 'foo'] - """ - while pkg_name: - yield pkg_name - pkg_name, sep, child = pkg_name.rpartition('.') - - def _get_SVEM_NSPs(self): - """ - Get namespace packages (list) but only for - single_version_externally_managed installations and empty otherwise. - """ - # TODO: is it necessary to short-circuit here? i.e. what's the cost - # if get_finalized_command is called even when namespace_packages is - # False? - if not self.distribution.namespace_packages: - return [] - - install_cmd = self.get_finalized_command('install') - svem = install_cmd.single_version_externally_managed - - return self.distribution.namespace_packages if svem else [] - - @staticmethod - def _gen_exclusion_paths(): - """ - Generate file paths to be excluded for namespace packages (bytecode - cache files). - """ - # always exclude the package module itself - yield '__init__.py' - - yield '__init__.pyc' - yield '__init__.pyo' - - if not hasattr(imp, 'get_tag'): - return - - base = os.path.join('__pycache__', '__init__.' + imp.get_tag()) - yield base + '.pyc' - yield base + '.pyo' - yield base + '.opt-1.pyc' - yield base + '.opt-2.pyc' - - def copy_tree( - self, infile, outfile, - preserve_mode=1, preserve_times=1, preserve_symlinks=0, level=1 - ): - assert preserve_mode and preserve_times and not preserve_symlinks - exclude = self.get_exclusions() - - if not exclude: - return orig.install_lib.copy_tree(self, infile, outfile) - - # Exclude namespace package __init__.py* files from the output - - from setuptools.archive_util import unpack_directory - from distutils import log - - outfiles = [] - - def pf(src, dst): - if dst in exclude: - log.warn("Skipping installation of %s (namespace package)", - dst) - return False - - log.info("copying %s -> %s", src, os.path.dirname(dst)) - outfiles.append(dst) - return dst - - unpack_directory(infile, outfile, pf) - return outfiles - - def get_outputs(self): - outputs = orig.install_lib.get_outputs(self) - exclude = self.get_exclusions() - if exclude: - return [f for f in outputs if f not in exclude] - return outputs diff --git a/lib/setuptools/command/install_scripts.py b/lib/setuptools/command/install_scripts.py deleted file mode 100644 index 1623427..0000000 --- a/lib/setuptools/command/install_scripts.py +++ /dev/null @@ -1,65 +0,0 @@ -from distutils import log -import distutils.command.install_scripts as orig -import os -import sys - -from pkg_resources import Distribution, PathMetadata, ensure_directory - - -class install_scripts(orig.install_scripts): - """Do normal script install, plus any egg_info wrapper scripts""" - - def initialize_options(self): - orig.install_scripts.initialize_options(self) - self.no_ep = False - - def run(self): - import setuptools.command.easy_install as ei - - self.run_command("egg_info") - if self.distribution.scripts: - orig.install_scripts.run(self) # run first to set up self.outfiles - else: - self.outfiles = [] - if self.no_ep: - # don't install entry point scripts into .egg file! - return - - ei_cmd = self.get_finalized_command("egg_info") - dist = Distribution( - ei_cmd.egg_base, PathMetadata(ei_cmd.egg_base, ei_cmd.egg_info), - ei_cmd.egg_name, ei_cmd.egg_version, - ) - bs_cmd = self.get_finalized_command('build_scripts') - exec_param = getattr(bs_cmd, 'executable', None) - bw_cmd = self.get_finalized_command("bdist_wininst") - is_wininst = getattr(bw_cmd, '_is_running', False) - writer = ei.ScriptWriter - if is_wininst: - exec_param = "python.exe" - writer = ei.WindowsScriptWriter - if exec_param == sys.executable: - # In case the path to the Python executable contains a space, wrap - # it so it's not split up. - exec_param = [exec_param] - # resolve the writer to the environment - writer = writer.best() - cmd = writer.command_spec_class.best().from_param(exec_param) - for args in writer.get_args(dist, cmd.as_header()): - self.write_script(*args) - - def write_script(self, script_name, contents, mode="t", *ignored): - """Write an executable file to the scripts directory""" - from setuptools.command.easy_install import chmod, current_umask - - log.info("Installing %s script to %s", script_name, self.install_dir) - target = os.path.join(self.install_dir, script_name) - self.outfiles.append(target) - - mask = current_umask() - if not self.dry_run: - ensure_directory(target) - f = open(target, "w" + mode) - f.write(contents) - f.close() - chmod(target, 0o777 - mask) diff --git a/lib/setuptools/command/launcher manifest.xml b/lib/setuptools/command/launcher manifest.xml deleted file mode 100644 index 5972a96..0000000 --- a/lib/setuptools/command/launcher manifest.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> - <assemblyIdentity version="1.0.0.0" - processorArchitecture="X86" - name="%(name)s" - type="win32"/> - <!-- Identify the application security requirements. --> - <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> - <security> - <requestedPrivileges> - <requestedExecutionLevel level="asInvoker" uiAccess="false"/> - </requestedPrivileges> - </security> - </trustInfo> -</assembly> diff --git a/lib/setuptools/command/py36compat.py b/lib/setuptools/command/py36compat.py deleted file mode 100644 index 61063e7..0000000 --- a/lib/setuptools/command/py36compat.py +++ /dev/null @@ -1,136 +0,0 @@ -import os -from glob import glob -from distutils.util import convert_path -from distutils.command import sdist - -from setuptools.extern.six.moves import filter - - -class sdist_add_defaults: - """ - Mix-in providing forward-compatibility for functionality as found in - distutils on Python 3.7. - - Do not edit the code in this class except to update functionality - as implemented in distutils. Instead, override in the subclass. - """ - - def add_defaults(self): - """Add all the default files to self.filelist: - - README or README.txt - - setup.py - - test/test*.py - - all pure Python modules mentioned in setup script - - all files pointed by package_data (build_py) - - all files defined in data_files. - - all files defined as scripts. - - all C sources listed as part of extensions or C libraries - in the setup script (doesn't catch C headers!) - Warns if (README or README.txt) or setup.py are missing; everything - else is optional. - """ - self._add_defaults_standards() - self._add_defaults_optional() - self._add_defaults_python() - self._add_defaults_data_files() - self._add_defaults_ext() - self._add_defaults_c_libs() - self._add_defaults_scripts() - - @staticmethod - def _cs_path_exists(fspath): - """ - Case-sensitive path existence check - - >>> sdist_add_defaults._cs_path_exists(__file__) - True - >>> sdist_add_defaults._cs_path_exists(__file__.upper()) - False - """ - if not os.path.exists(fspath): - return False - # make absolute so we always have a directory - abspath = os.path.abspath(fspath) - directory, filename = os.path.split(abspath) - return filename in os.listdir(directory) - - def _add_defaults_standards(self): - standards = [self.READMES, self.distribution.script_name] - for fn in standards: - if isinstance(fn, tuple): - alts = fn - got_it = False - for fn in alts: - if self._cs_path_exists(fn): - got_it = True - self.filelist.append(fn) - break - - if not got_it: - self.warn("standard file not found: should have one of " + - ', '.join(alts)) - else: - if self._cs_path_exists(fn): - self.filelist.append(fn) - else: - self.warn("standard file '%s' not found" % fn) - - def _add_defaults_optional(self): - optional = ['test/test*.py', 'setup.cfg'] - for pattern in optional: - files = filter(os.path.isfile, glob(pattern)) - self.filelist.extend(files) - - def _add_defaults_python(self): - # build_py is used to get: - # - python modules - # - files defined in package_data - build_py = self.get_finalized_command('build_py') - - # getting python files - if self.distribution.has_pure_modules(): - self.filelist.extend(build_py.get_source_files()) - - # getting package_data files - # (computed in build_py.data_files by build_py.finalize_options) - for pkg, src_dir, build_dir, filenames in build_py.data_files: - for filename in filenames: - self.filelist.append(os.path.join(src_dir, filename)) - - def _add_defaults_data_files(self): - # getting distribution.data_files - if self.distribution.has_data_files(): - for item in self.distribution.data_files: - if isinstance(item, str): - # plain file - item = convert_path(item) - if os.path.isfile(item): - self.filelist.append(item) - else: - # a (dirname, filenames) tuple - dirname, filenames = item - for f in filenames: - f = convert_path(f) - if os.path.isfile(f): - self.filelist.append(f) - - def _add_defaults_ext(self): - if self.distribution.has_ext_modules(): - build_ext = self.get_finalized_command('build_ext') - self.filelist.extend(build_ext.get_source_files()) - - def _add_defaults_c_libs(self): - if self.distribution.has_c_libraries(): - build_clib = self.get_finalized_command('build_clib') - self.filelist.extend(build_clib.get_source_files()) - - def _add_defaults_scripts(self): - if self.distribution.has_scripts(): - build_scripts = self.get_finalized_command('build_scripts') - self.filelist.extend(build_scripts.get_source_files()) - - -if hasattr(sdist.sdist, '_add_defaults_standards'): - # disable the functionality already available upstream - class sdist_add_defaults: - pass diff --git a/lib/setuptools/command/register.py b/lib/setuptools/command/register.py deleted file mode 100644 index 98bc015..0000000 --- a/lib/setuptools/command/register.py +++ /dev/null @@ -1,18 +0,0 @@ -from distutils import log -import distutils.command.register as orig - - -class register(orig.register): - __doc__ = orig.register.__doc__ - - def run(self): - try: - # Make sure that we are using valid current name/version info - self.run_command('egg_info') - orig.register.run(self) - finally: - self.announce( - "WARNING: Registering is deprecated, use twine to " - "upload instead (https://pypi.org/p/twine/)", - log.WARN - ) diff --git a/lib/setuptools/command/rotate.py b/lib/setuptools/command/rotate.py deleted file mode 100644 index b89353f..0000000 --- a/lib/setuptools/command/rotate.py +++ /dev/null @@ -1,66 +0,0 @@ -from distutils.util import convert_path -from distutils import log -from distutils.errors import DistutilsOptionError -import os -import shutil - -from setuptools.extern import six - -from setuptools import Command - - -class rotate(Command): - """Delete older distributions""" - - description = "delete older distributions, keeping N newest files" - user_options = [ - ('match=', 'm', "patterns to match (required)"), - ('dist-dir=', 'd', "directory where the distributions are"), - ('keep=', 'k', "number of matching distributions to keep"), - ] - - boolean_options = [] - - def initialize_options(self): - self.match = None - self.dist_dir = None - self.keep = None - - def finalize_options(self): - if self.match is None: - raise DistutilsOptionError( - "Must specify one or more (comma-separated) match patterns " - "(e.g. '.zip' or '.egg')" - ) - if self.keep is None: - raise DistutilsOptionError("Must specify number of files to keep") - try: - self.keep = int(self.keep) - except ValueError: - raise DistutilsOptionError("--keep must be an integer") - if isinstance(self.match, six.string_types): - self.match = [ - convert_path(p.strip()) for p in self.match.split(',') - ] - self.set_undefined_options('bdist', ('dist_dir', 'dist_dir')) - - def run(self): - self.run_command("egg_info") - from glob import glob - - for pattern in self.match: - pattern = self.distribution.get_name() + '*' + pattern - files = glob(os.path.join(self.dist_dir, pattern)) - files = [(os.path.getmtime(f), f) for f in files] - files.sort() - files.reverse() - - log.info("%d file(s) matching %s", len(files), pattern) - files = files[self.keep:] - for (t, f) in files: - log.info("Deleting %s", f) - if not self.dry_run: - if os.path.isdir(f): - shutil.rmtree(f) - else: - os.unlink(f) diff --git a/lib/setuptools/command/saveopts.py b/lib/setuptools/command/saveopts.py deleted file mode 100644 index 611cec5..0000000 --- a/lib/setuptools/command/saveopts.py +++ /dev/null @@ -1,22 +0,0 @@ -from setuptools.command.setopt import edit_config, option_base - - -class saveopts(option_base): - """Save command-line options to a file""" - - description = "save supplied options to setup.cfg or other config file" - - def run(self): - dist = self.distribution - settings = {} - - for cmd in dist.command_options: - - if cmd == 'saveopts': - continue # don't save our own options! - - for opt, (src, val) in dist.get_option_dict(cmd).items(): - if src == "command line": - settings.setdefault(cmd, {})[opt] = val - - edit_config(self.filename, settings, self.dry_run) diff --git a/lib/setuptools/command/sdist.py b/lib/setuptools/command/sdist.py deleted file mode 100644 index bcfae4d..0000000 --- a/lib/setuptools/command/sdist.py +++ /dev/null @@ -1,200 +0,0 @@ -from distutils import log -import distutils.command.sdist as orig -import os -import sys -import io -import contextlib - -from setuptools.extern import six - -from .py36compat import sdist_add_defaults - -import pkg_resources - -_default_revctrl = list - - -def walk_revctrl(dirname=''): - """Find all files under revision control""" - for ep in pkg_resources.iter_entry_points('setuptools.file_finders'): - for item in ep.load()(dirname): - yield item - - -class sdist(sdist_add_defaults, orig.sdist): - """Smart sdist that finds anything supported by revision control""" - - user_options = [ - ('formats=', None, - "formats for source distribution (comma-separated list)"), - ('keep-temp', 'k', - "keep the distribution tree around after creating " + - "archive file(s)"), - ('dist-dir=', 'd', - "directory to put the source distribution archive(s) in " - "[default: dist]"), - ] - - negative_opt = {} - - README_EXTENSIONS = ['', '.rst', '.txt', '.md'] - READMES = tuple('README{0}'.format(ext) for ext in README_EXTENSIONS) - - def run(self): - self.run_command('egg_info') - ei_cmd = self.get_finalized_command('egg_info') - self.filelist = ei_cmd.filelist - self.filelist.append(os.path.join(ei_cmd.egg_info, 'SOURCES.txt')) - self.check_readme() - - # Run sub commands - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - - self.make_distribution() - - dist_files = getattr(self.distribution, 'dist_files', []) - for file in self.archive_files: - data = ('sdist', '', file) - if data not in dist_files: - dist_files.append(data) - - def initialize_options(self): - orig.sdist.initialize_options(self) - - self._default_to_gztar() - - def _default_to_gztar(self): - # only needed on Python prior to 3.6. - if sys.version_info >= (3, 6, 0, 'beta', 1): - return - self.formats = ['gztar'] - - def make_distribution(self): - """ - Workaround for #516 - """ - with self._remove_os_link(): - orig.sdist.make_distribution(self) - - @staticmethod - @contextlib.contextmanager - def _remove_os_link(): - """ - In a context, remove and restore os.link if it exists - """ - - class NoValue: - pass - - orig_val = getattr(os, 'link', NoValue) - try: - del os.link - except Exception: - pass - try: - yield - finally: - if orig_val is not NoValue: - setattr(os, 'link', orig_val) - - def __read_template_hack(self): - # This grody hack closes the template file (MANIFEST.in) if an - # exception occurs during read_template. - # Doing so prevents an error when easy_install attempts to delete the - # file. - try: - orig.sdist.read_template(self) - except Exception: - _, _, tb = sys.exc_info() - tb.tb_next.tb_frame.f_locals['template'].close() - raise - - # Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle - # has been fixed, so only override the method if we're using an earlier - # Python. - has_leaky_handle = ( - sys.version_info < (2, 7, 2) - or (3, 0) <= sys.version_info < (3, 1, 4) - or (3, 2) <= sys.version_info < (3, 2, 1) - ) - if has_leaky_handle: - read_template = __read_template_hack - - def _add_defaults_python(self): - """getting python files""" - if self.distribution.has_pure_modules(): - build_py = self.get_finalized_command('build_py') - self.filelist.extend(build_py.get_source_files()) - # This functionality is incompatible with include_package_data, and - # will in fact create an infinite recursion if include_package_data - # is True. Use of include_package_data will imply that - # distutils-style automatic handling of package_data is disabled - if not self.distribution.include_package_data: - for _, src_dir, _, filenames in build_py.data_files: - self.filelist.extend([os.path.join(src_dir, filename) - for filename in filenames]) - - def _add_defaults_data_files(self): - try: - if six.PY2: - sdist_add_defaults._add_defaults_data_files(self) - else: - super()._add_defaults_data_files() - except TypeError: - log.warn("data_files contains unexpected objects") - - def check_readme(self): - for f in self.READMES: - if os.path.exists(f): - return - else: - self.warn( - "standard file not found: should have one of " + - ', '.join(self.READMES) - ) - - def make_release_tree(self, base_dir, files): - orig.sdist.make_release_tree(self, base_dir, files) - - # Save any egg_info command line options used to create this sdist - dest = os.path.join(base_dir, 'setup.cfg') - if hasattr(os, 'link') and os.path.exists(dest): - # unlink and re-copy, since it might be hard-linked, and - # we don't want to change the source version - os.unlink(dest) - self.copy_file('setup.cfg', dest) - - self.get_finalized_command('egg_info').save_version_info(dest) - - def _manifest_is_not_generated(self): - # check for special comment used in 2.7.1 and higher - if not os.path.isfile(self.manifest): - return False - - with io.open(self.manifest, 'rb') as fp: - first_line = fp.readline() - return (first_line != - '# file GENERATED by distutils, do NOT edit\n'.encode()) - - def read_manifest(self): - """Read the manifest file (named by 'self.manifest') and use it to - fill in 'self.filelist', the list of files to include in the source - distribution. - """ - log.info("reading manifest file '%s'", self.manifest) - manifest = open(self.manifest, 'rb') - for line in manifest: - # The manifest must contain UTF-8. See #303. - if six.PY3: - try: - line = line.decode('UTF-8') - except UnicodeDecodeError: - log.warn("%r not UTF-8 decodable -- skipping" % line) - continue - # ignore comments and blank lines - line = line.strip() - if line.startswith('#') or not line: - continue - self.filelist.append(line) - manifest.close() diff --git a/lib/setuptools/command/setopt.py b/lib/setuptools/command/setopt.py deleted file mode 100644 index 7e57cc0..0000000 --- a/lib/setuptools/command/setopt.py +++ /dev/null @@ -1,149 +0,0 @@ -from distutils.util import convert_path -from distutils import log -from distutils.errors import DistutilsOptionError -import distutils -import os - -from setuptools.extern.six.moves import configparser - -from setuptools import Command - -__all__ = ['config_file', 'edit_config', 'option_base', 'setopt'] - - -def config_file(kind="local"): - """Get the filename of the distutils, local, global, or per-user config - - `kind` must be one of "local", "global", or "user" - """ - if kind == 'local': - return 'setup.cfg' - if kind == 'global': - return os.path.join( - os.path.dirname(distutils.__file__), 'distutils.cfg' - ) - if kind == 'user': - dot = os.name == 'posix' and '.' or '' - return os.path.expanduser(convert_path("~/%spydistutils.cfg" % dot)) - raise ValueError( - "config_file() type must be 'local', 'global', or 'user'", kind - ) - - -def edit_config(filename, settings, dry_run=False): - """Edit a configuration file to include `settings` - - `settings` is a dictionary of dictionaries or ``None`` values, keyed by - command/section name. A ``None`` value means to delete the entire section, - while a dictionary lists settings to be changed or deleted in that section. - A setting of ``None`` means to delete that setting. - """ - log.debug("Reading configuration from %s", filename) - opts = configparser.RawConfigParser() - opts.read([filename]) - for section, options in settings.items(): - if options is None: - log.info("Deleting section [%s] from %s", section, filename) - opts.remove_section(section) - else: - if not opts.has_section(section): - log.debug("Adding new section [%s] to %s", section, filename) - opts.add_section(section) - for option, value in options.items(): - if value is None: - log.debug( - "Deleting %s.%s from %s", - section, option, filename - ) - opts.remove_option(section, option) - if not opts.options(section): - log.info("Deleting empty [%s] section from %s", - section, filename) - opts.remove_section(section) - else: - log.debug( - "Setting %s.%s to %r in %s", - section, option, value, filename - ) - opts.set(section, option, value) - - log.info("Writing %s", filename) - if not dry_run: - with open(filename, 'w') as f: - opts.write(f) - - -class option_base(Command): - """Abstract base class for commands that mess with config files""" - - user_options = [ - ('global-config', 'g', - "save options to the site-wide distutils.cfg file"), - ('user-config', 'u', - "save options to the current user's pydistutils.cfg file"), - ('filename=', 'f', - "configuration file to use (default=setup.cfg)"), - ] - - boolean_options = [ - 'global-config', 'user-config', - ] - - def initialize_options(self): - self.global_config = None - self.user_config = None - self.filename = None - - def finalize_options(self): - filenames = [] - if self.global_config: - filenames.append(config_file('global')) - if self.user_config: - filenames.append(config_file('user')) - if self.filename is not None: - filenames.append(self.filename) - if not filenames: - filenames.append(config_file('local')) - if len(filenames) > 1: - raise DistutilsOptionError( - "Must specify only one configuration file option", - filenames - ) - self.filename, = filenames - - -class setopt(option_base): - """Save command-line options to a file""" - - description = "set an option in setup.cfg or another config file" - - user_options = [ - ('command=', 'c', 'command to set an option for'), - ('option=', 'o', 'option to set'), - ('set-value=', 's', 'value of the option'), - ('remove', 'r', 'remove (unset) the value'), - ] + option_base.user_options - - boolean_options = option_base.boolean_options + ['remove'] - - def initialize_options(self): - option_base.initialize_options(self) - self.command = None - self.option = None - self.set_value = None - self.remove = None - - def finalize_options(self): - option_base.finalize_options(self) - if self.command is None or self.option is None: - raise DistutilsOptionError("Must specify --command *and* --option") - if self.set_value is None and not self.remove: - raise DistutilsOptionError("Must specify --set-value or --remove") - - def run(self): - edit_config( - self.filename, { - self.command: {self.option.replace('-', '_'): self.set_value} - }, - self.dry_run - ) diff --git a/lib/setuptools/command/test.py b/lib/setuptools/command/test.py deleted file mode 100644 index dde0118..0000000 --- a/lib/setuptools/command/test.py +++ /dev/null @@ -1,270 +0,0 @@ -import os -import operator -import sys -import contextlib -import itertools -import unittest -from distutils.errors import DistutilsError, DistutilsOptionError -from distutils import log -from unittest import TestLoader - -from setuptools.extern import six -from setuptools.extern.six.moves import map, filter - -from pkg_resources import (resource_listdir, resource_exists, normalize_path, - working_set, _namespace_packages, evaluate_marker, - add_activation_listener, require, EntryPoint) -from setuptools import Command - -__metaclass__ = type - - -class ScanningLoader(TestLoader): - - def __init__(self): - TestLoader.__init__(self) - self._visited = set() - - def loadTestsFromModule(self, module, pattern=None): - """Return a suite of all tests cases contained in the given module - - If the module is a package, load tests from all the modules in it. - If the module has an ``additional_tests`` function, call it and add - the return value to the tests. - """ - if module in self._visited: - return None - self._visited.add(module) - - tests = [] - tests.append(TestLoader.loadTestsFromModule(self, module)) - - if hasattr(module, "additional_tests"): - tests.append(module.additional_tests()) - - if hasattr(module, '__path__'): - for file in resource_listdir(module.__name__, ''): - if file.endswith('.py') and file != '__init__.py': - submodule = module.__name__ + '.' + file[:-3] - else: - if resource_exists(module.__name__, file + '/__init__.py'): - submodule = module.__name__ + '.' + file - else: - continue - tests.append(self.loadTestsFromName(submodule)) - - if len(tests) != 1: - return self.suiteClass(tests) - else: - return tests[0] # don't create a nested suite for only one return - - -# adapted from jaraco.classes.properties:NonDataProperty -class NonDataProperty: - def __init__(self, fget): - self.fget = fget - - def __get__(self, obj, objtype=None): - if obj is None: - return self - return self.fget(obj) - - -class test(Command): - """Command to run unit tests after in-place build""" - - description = "run unit tests after in-place build" - - user_options = [ - ('test-module=', 'm', "Run 'test_suite' in specified module"), - ('test-suite=', 's', - "Run single test, case or suite (e.g. 'module.test_suite')"), - ('test-runner=', 'r', "Test runner to use"), - ] - - def initialize_options(self): - self.test_suite = None - self.test_module = None - self.test_loader = None - self.test_runner = None - - def finalize_options(self): - - if self.test_suite and self.test_module: - msg = "You may specify a module or a suite, but not both" - raise DistutilsOptionError(msg) - - if self.test_suite is None: - if self.test_module is None: - self.test_suite = self.distribution.test_suite - else: - self.test_suite = self.test_module + ".test_suite" - - if self.test_loader is None: - self.test_loader = getattr(self.distribution, 'test_loader', None) - if self.test_loader is None: - self.test_loader = "setuptools.command.test:ScanningLoader" - if self.test_runner is None: - self.test_runner = getattr(self.distribution, 'test_runner', None) - - @NonDataProperty - def test_args(self): - return list(self._test_args()) - - def _test_args(self): - if not self.test_suite and sys.version_info >= (2, 7): - yield 'discover' - if self.verbose: - yield '--verbose' - if self.test_suite: - yield self.test_suite - - def with_project_on_sys_path(self, func): - """ - Backward compatibility for project_on_sys_path context. - """ - with self.project_on_sys_path(): - func() - - @contextlib.contextmanager - def project_on_sys_path(self, include_dists=[]): - with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False) - - if with_2to3: - # If we run 2to3 we can not do this inplace: - - # Ensure metadata is up-to-date - self.reinitialize_command('build_py', inplace=0) - self.run_command('build_py') - bpy_cmd = self.get_finalized_command("build_py") - build_path = normalize_path(bpy_cmd.build_lib) - - # Build extensions - self.reinitialize_command('egg_info', egg_base=build_path) - self.run_command('egg_info') - - self.reinitialize_command('build_ext', inplace=0) - self.run_command('build_ext') - else: - # Without 2to3 inplace works fine: - self.run_command('egg_info') - - # Build extensions in-place - self.reinitialize_command('build_ext', inplace=1) - self.run_command('build_ext') - - ei_cmd = self.get_finalized_command("egg_info") - - old_path = sys.path[:] - old_modules = sys.modules.copy() - - try: - project_path = normalize_path(ei_cmd.egg_base) - sys.path.insert(0, project_path) - working_set.__init__() - add_activation_listener(lambda dist: dist.activate()) - require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version)) - with self.paths_on_pythonpath([project_path]): - yield - finally: - sys.path[:] = old_path - sys.modules.clear() - sys.modules.update(old_modules) - working_set.__init__() - - @staticmethod - @contextlib.contextmanager - def paths_on_pythonpath(paths): - """ - Add the indicated paths to the head of the PYTHONPATH environment - variable so that subprocesses will also see the packages at - these paths. - - Do this in a context that restores the value on exit. - """ - nothing = object() - orig_pythonpath = os.environ.get('PYTHONPATH', nothing) - current_pythonpath = os.environ.get('PYTHONPATH', '') - try: - prefix = os.pathsep.join(paths) - to_join = filter(None, [prefix, current_pythonpath]) - new_path = os.pathsep.join(to_join) - if new_path: - os.environ['PYTHONPATH'] = new_path - yield - finally: - if orig_pythonpath is nothing: - os.environ.pop('PYTHONPATH', None) - else: - os.environ['PYTHONPATH'] = orig_pythonpath - - @staticmethod - def install_dists(dist): - """ - Install the requirements indicated by self.distribution and - return an iterable of the dists that were built. - """ - ir_d = dist.fetch_build_eggs(dist.install_requires) - tr_d = dist.fetch_build_eggs(dist.tests_require or []) - er_d = dist.fetch_build_eggs( - v for k, v in dist.extras_require.items() - if k.startswith(':') and evaluate_marker(k[1:]) - ) - return itertools.chain(ir_d, tr_d, er_d) - - def run(self): - installed_dists = self.install_dists(self.distribution) - - cmd = ' '.join(self._argv) - if self.dry_run: - self.announce('skipping "%s" (dry run)' % cmd) - return - - self.announce('running "%s"' % cmd) - - paths = map(operator.attrgetter('location'), installed_dists) - with self.paths_on_pythonpath(paths): - with self.project_on_sys_path(): - self.run_tests() - - def run_tests(self): - # Purge modules under test from sys.modules. The test loader will - # re-import them from the build location. Required when 2to3 is used - # with namespace packages. - if six.PY3 and getattr(self.distribution, 'use_2to3', False): - module = self.test_suite.split('.')[0] - if module in _namespace_packages: - del_modules = [] - if module in sys.modules: - del_modules.append(module) - module += '.' - for name in sys.modules: - if name.startswith(module): - del_modules.append(name) - list(map(sys.modules.__delitem__, del_modules)) - - test = unittest.main( - None, None, self._argv, - testLoader=self._resolve_as_ep(self.test_loader), - testRunner=self._resolve_as_ep(self.test_runner), - exit=False, - ) - if not test.result.wasSuccessful(): - msg = 'Test failed: %s' % test.result - self.announce(msg, log.ERROR) - raise DistutilsError(msg) - - @property - def _argv(self): - return ['unittest'] + self.test_args - - @staticmethod - def _resolve_as_ep(val): - """ - Load the indicated attribute value, called, as a as if it were - specified as an entry point. - """ - if val is None: - return - parsed = EntryPoint.parse("x=" + val) - return parsed.resolve()() diff --git a/lib/setuptools/command/upload.py b/lib/setuptools/command/upload.py deleted file mode 100644 index dd17f7a..0000000 --- a/lib/setuptools/command/upload.py +++ /dev/null @@ -1,196 +0,0 @@ -import io -import os -import hashlib -import getpass -import platform - -from base64 import standard_b64encode - -from distutils import log -from distutils.command import upload as orig -from distutils.spawn import spawn - -from distutils.errors import DistutilsError - -from setuptools.extern.six.moves.urllib.request import urlopen, Request -from setuptools.extern.six.moves.urllib.error import HTTPError -from setuptools.extern.six.moves.urllib.parse import urlparse - -class upload(orig.upload): - """ - Override default upload behavior to obtain password - in a variety of different ways. - """ - def run(self): - try: - orig.upload.run(self) - finally: - self.announce( - "WARNING: Uploading via this command is deprecated, use twine " - "to upload instead (https://pypi.org/p/twine/)", - log.WARN - ) - - def finalize_options(self): - orig.upload.finalize_options(self) - self.username = ( - self.username or - getpass.getuser() - ) - # Attempt to obtain password. Short circuit evaluation at the first - # sign of success. - self.password = ( - self.password or - self._load_password_from_keyring() or - self._prompt_for_password() - ) - - def upload_file(self, command, pyversion, filename): - # Makes sure the repository URL is compliant - schema, netloc, url, params, query, fragments = \ - urlparse(self.repository) - if params or query or fragments: - raise AssertionError("Incompatible url %s" % self.repository) - - if schema not in ('http', 'https'): - raise AssertionError("unsupported schema " + schema) - - # Sign if requested - if self.sign: - gpg_args = ["gpg", "--detach-sign", "-a", filename] - if self.identity: - gpg_args[2:2] = ["--local-user", self.identity] - spawn(gpg_args, - dry_run=self.dry_run) - - # Fill in the data - send all the meta-data in case we need to - # register a new release - with open(filename, 'rb') as f: - content = f.read() - - meta = self.distribution.metadata - - data = { - # action - ':action': 'file_upload', - 'protocol_version': '1', - - # identify release - 'name': meta.get_name(), - 'version': meta.get_version(), - - # file content - 'content': (os.path.basename(filename),content), - 'filetype': command, - 'pyversion': pyversion, - 'md5_digest': hashlib.md5(content).hexdigest(), - - # additional meta-data - 'metadata_version': str(meta.get_metadata_version()), - 'summary': meta.get_description(), - 'home_page': meta.get_url(), - 'author': meta.get_contact(), - 'author_email': meta.get_contact_email(), - 'license': meta.get_licence(), - 'description': meta.get_long_description(), - 'keywords': meta.get_keywords(), - 'platform': meta.get_platforms(), - 'classifiers': meta.get_classifiers(), - 'download_url': meta.get_download_url(), - # PEP 314 - 'provides': meta.get_provides(), - 'requires': meta.get_requires(), - 'obsoletes': meta.get_obsoletes(), - } - - data['comment'] = '' - - if self.sign: - data['gpg_signature'] = (os.path.basename(filename) + ".asc", - open(filename+".asc", "rb").read()) - - # set up the authentication - user_pass = (self.username + ":" + self.password).encode('ascii') - # The exact encoding of the authentication string is debated. - # Anyway PyPI only accepts ascii for both username or password. - auth = "Basic " + standard_b64encode(user_pass).decode('ascii') - - # Build up the MIME payload for the POST data - boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - sep_boundary = b'\r\n--' + boundary.encode('ascii') - end_boundary = sep_boundary + b'--\r\n' - body = io.BytesIO() - for key, value in data.items(): - title = '\r\nContent-Disposition: form-data; name="%s"' % key - # handle multiple entries for the same name - if not isinstance(value, list): - value = [value] - for value in value: - if type(value) is tuple: - title += '; filename="%s"' % value[0] - value = value[1] - else: - value = str(value).encode('utf-8') - body.write(sep_boundary) - body.write(title.encode('utf-8')) - body.write(b"\r\n\r\n") - body.write(value) - body.write(end_boundary) - body = body.getvalue() - - msg = "Submitting %s to %s" % (filename, self.repository) - self.announce(msg, log.INFO) - - # build the Request - headers = { - 'Content-type': 'multipart/form-data; boundary=%s' % boundary, - 'Content-length': str(len(body)), - 'Authorization': auth, - } - - request = Request(self.repository, data=body, - headers=headers) - # send the data - try: - result = urlopen(request) - status = result.getcode() - reason = result.msg - except HTTPError as e: - status = e.code - reason = e.msg - except OSError as e: - self.announce(str(e), log.ERROR) - raise - - if status == 200: - self.announce('Server response (%s): %s' % (status, reason), - log.INFO) - if self.show_response: - text = getattr(self, '_read_pypi_response', - lambda x: None)(result) - if text is not None: - msg = '\n'.join(('-' * 75, text, '-' * 75)) - self.announce(msg, log.INFO) - else: - msg = 'Upload failed (%s): %s' % (status, reason) - self.announce(msg, log.ERROR) - raise DistutilsError(msg) - - def _load_password_from_keyring(self): - """ - Attempt to load password from keyring. Suppress Exceptions. - """ - try: - keyring = __import__('keyring') - return keyring.get_password(self.repository, self.username) - except Exception: - pass - - def _prompt_for_password(self): - """ - Prompt for a password on the tty. Suppress Exceptions. - """ - try: - return getpass.getpass() - except (Exception, KeyboardInterrupt): - pass diff --git a/lib/setuptools/command/upload_docs.py b/lib/setuptools/command/upload_docs.py deleted file mode 100644 index 07aa564..0000000 --- a/lib/setuptools/command/upload_docs.py +++ /dev/null @@ -1,206 +0,0 @@ -# -*- coding: utf-8 -*- -"""upload_docs - -Implements a Distutils 'upload_docs' subcommand (upload documentation to -PyPI's pythonhosted.org). -""" - -from base64 import standard_b64encode -from distutils import log -from distutils.errors import DistutilsOptionError -import os -import socket -import zipfile -import tempfile -import shutil -import itertools -import functools - -from setuptools.extern import six -from setuptools.extern.six.moves import http_client, urllib - -from pkg_resources import iter_entry_points -from .upload import upload - - -def _encode(s): - errors = 'surrogateescape' if six.PY3 else 'strict' - return s.encode('utf-8', errors) - - -class upload_docs(upload): - # override the default repository as upload_docs isn't - # supported by Warehouse (and won't be). - DEFAULT_REPOSITORY = 'https://pypi.python.org/pypi/' - - description = 'Upload documentation to PyPI' - - user_options = [ - ('repository=', 'r', - "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY), - ('show-response', None, - 'display full response text from server'), - ('upload-dir=', None, 'directory to upload'), - ] - boolean_options = upload.boolean_options - - def has_sphinx(self): - if self.upload_dir is None: - for ep in iter_entry_points('distutils.commands', 'build_sphinx'): - return True - - sub_commands = [('build_sphinx', has_sphinx)] - - def initialize_options(self): - upload.initialize_options(self) - self.upload_dir = None - self.target_dir = None - - def finalize_options(self): - upload.finalize_options(self) - if self.upload_dir is None: - if self.has_sphinx(): - build_sphinx = self.get_finalized_command('build_sphinx') - self.target_dir = build_sphinx.builder_target_dir - else: - build = self.get_finalized_command('build') - self.target_dir = os.path.join(build.build_base, 'docs') - else: - self.ensure_dirname('upload_dir') - self.target_dir = self.upload_dir - if 'pypi.python.org' in self.repository: - log.warn("Upload_docs command is deprecated. Use RTD instead.") - self.announce('Using upload directory %s' % self.target_dir) - - def create_zipfile(self, filename): - zip_file = zipfile.ZipFile(filename, "w") - try: - self.mkpath(self.target_dir) # just in case - for root, dirs, files in os.walk(self.target_dir): - if root == self.target_dir and not files: - tmpl = "no files found in upload directory '%s'" - raise DistutilsOptionError(tmpl % self.target_dir) - for name in files: - full = os.path.join(root, name) - relative = root[len(self.target_dir):].lstrip(os.path.sep) - dest = os.path.join(relative, name) - zip_file.write(full, dest) - finally: - zip_file.close() - - def run(self): - # Run sub commands - for cmd_name in self.get_sub_commands(): - self.run_command(cmd_name) - - tmp_dir = tempfile.mkdtemp() - name = self.distribution.metadata.get_name() - zip_file = os.path.join(tmp_dir, "%s.zip" % name) - try: - self.create_zipfile(zip_file) - self.upload_file(zip_file) - finally: - shutil.rmtree(tmp_dir) - - @staticmethod - def _build_part(item, sep_boundary): - key, values = item - title = '\nContent-Disposition: form-data; name="%s"' % key - # handle multiple entries for the same name - if not isinstance(values, list): - values = [values] - for value in values: - if isinstance(value, tuple): - title += '; filename="%s"' % value[0] - value = value[1] - else: - value = _encode(value) - yield sep_boundary - yield _encode(title) - yield b"\n\n" - yield value - if value and value[-1:] == b'\r': - yield b'\n' # write an extra newline (lurve Macs) - - @classmethod - def _build_multipart(cls, data): - """ - Build up the MIME payload for the POST data - """ - boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' - sep_boundary = b'\n--' + boundary - end_boundary = sep_boundary + b'--' - end_items = end_boundary, b"\n", - builder = functools.partial( - cls._build_part, - sep_boundary=sep_boundary, - ) - part_groups = map(builder, data.items()) - parts = itertools.chain.from_iterable(part_groups) - body_items = itertools.chain(parts, end_items) - content_type = 'multipart/form-data; boundary=%s' % boundary.decode('ascii') - return b''.join(body_items), content_type - - def upload_file(self, filename): - with open(filename, 'rb') as f: - content = f.read() - meta = self.distribution.metadata - data = { - ':action': 'doc_upload', - 'name': meta.get_name(), - 'content': (os.path.basename(filename), content), - } - # set up the authentication - credentials = _encode(self.username + ':' + self.password) - credentials = standard_b64encode(credentials) - if six.PY3: - credentials = credentials.decode('ascii') - auth = "Basic " + credentials - - body, ct = self._build_multipart(data) - - msg = "Submitting documentation to %s" % (self.repository) - self.announce(msg, log.INFO) - - # build the Request - # We can't use urllib2 since we need to send the Basic - # auth right with the first request - schema, netloc, url, params, query, fragments = \ - urllib.parse.urlparse(self.repository) - assert not params and not query and not fragments - if schema == 'http': - conn = http_client.HTTPConnection(netloc) - elif schema == 'https': - conn = http_client.HTTPSConnection(netloc) - else: - raise AssertionError("unsupported schema " + schema) - - data = '' - try: - conn.connect() - conn.putrequest("POST", url) - content_type = ct - conn.putheader('Content-type', content_type) - conn.putheader('Content-length', str(len(body))) - conn.putheader('Authorization', auth) - conn.endheaders() - conn.send(body) - except socket.error as e: - self.announce(str(e), log.ERROR) - return - - r = conn.getresponse() - if r.status == 200: - msg = 'Server response (%s): %s' % (r.status, r.reason) - self.announce(msg, log.INFO) - elif r.status == 301: - location = r.getheader('Location') - if location is None: - location = 'https://pythonhosted.org/%s/' % meta.get_name() - msg = 'Upload successful. Visit %s' % location - self.announce(msg, log.INFO) - else: - msg = 'Upload failed (%s): %s' % (r.status, r.reason) - self.announce(msg, log.ERROR) - if self.show_response: - print('-' * 75, r.read(), '-' * 75) diff --git a/lib/setuptools/config.py b/lib/setuptools/config.py deleted file mode 100644 index d1ac673..0000000 --- a/lib/setuptools/config.py +++ /dev/null @@ -1,635 +0,0 @@ -from __future__ import absolute_import, unicode_literals -import io -import os -import sys - -import warnings -import functools -from collections import defaultdict -from functools import partial -from functools import wraps -from importlib import import_module - -from distutils.errors import DistutilsOptionError, DistutilsFileError -from setuptools.extern.packaging.version import LegacyVersion, parse -from setuptools.extern.six import string_types, PY3 - - -__metaclass__ = type - - -def read_configuration( - filepath, find_others=False, ignore_option_errors=False): - """Read given configuration file and returns options from it as a dict. - - :param str|unicode filepath: Path to configuration file - to get options from. - - :param bool find_others: Whether to search for other configuration files - which could be on in various places. - - :param bool ignore_option_errors: Whether to silently ignore - options, values of which could not be resolved (e.g. due to exceptions - in directives such as file:, attr:, etc.). - If False exceptions are propagated as expected. - - :rtype: dict - """ - from setuptools.dist import Distribution, _Distribution - - filepath = os.path.abspath(filepath) - - if not os.path.isfile(filepath): - raise DistutilsFileError( - 'Configuration file %s does not exist.' % filepath) - - current_directory = os.getcwd() - os.chdir(os.path.dirname(filepath)) - - try: - dist = Distribution() - - filenames = dist.find_config_files() if find_others else [] - if filepath not in filenames: - filenames.append(filepath) - - _Distribution.parse_config_files(dist, filenames=filenames) - - handlers = parse_configuration( - dist, dist.command_options, - ignore_option_errors=ignore_option_errors) - - finally: - os.chdir(current_directory) - - return configuration_to_dict(handlers) - - -def _get_option(target_obj, key): - """ - Given a target object and option key, get that option from - the target object, either through a get_{key} method or - from an attribute directly. - """ - getter_name = 'get_{key}'.format(**locals()) - by_attribute = functools.partial(getattr, target_obj, key) - getter = getattr(target_obj, getter_name, by_attribute) - return getter() - - -def configuration_to_dict(handlers): - """Returns configuration data gathered by given handlers as a dict. - - :param list[ConfigHandler] handlers: Handlers list, - usually from parse_configuration() - - :rtype: dict - """ - config_dict = defaultdict(dict) - - for handler in handlers: - for option in handler.set_options: - value = _get_option(handler.target_obj, option) - config_dict[handler.section_prefix][option] = value - - return config_dict - - -def parse_configuration( - distribution, command_options, ignore_option_errors=False): - """Performs additional parsing of configuration options - for a distribution. - - Returns a list of used option handlers. - - :param Distribution distribution: - :param dict command_options: - :param bool ignore_option_errors: Whether to silently ignore - options, values of which could not be resolved (e.g. due to exceptions - in directives such as file:, attr:, etc.). - If False exceptions are propagated as expected. - :rtype: list - """ - options = ConfigOptionsHandler( - distribution, command_options, ignore_option_errors) - options.parse() - - meta = ConfigMetadataHandler( - distribution.metadata, command_options, ignore_option_errors, - distribution.package_dir) - meta.parse() - - return meta, options - - -class ConfigHandler: - """Handles metadata supplied in configuration files.""" - - section_prefix = None - """Prefix for config sections handled by this handler. - Must be provided by class heirs. - - """ - - aliases = {} - """Options aliases. - For compatibility with various packages. E.g.: d2to1 and pbr. - Note: `-` in keys is replaced with `_` by config parser. - - """ - - def __init__(self, target_obj, options, ignore_option_errors=False): - sections = {} - - section_prefix = self.section_prefix - for section_name, section_options in options.items(): - if not section_name.startswith(section_prefix): - continue - - section_name = section_name.replace(section_prefix, '').strip('.') - sections[section_name] = section_options - - self.ignore_option_errors = ignore_option_errors - self.target_obj = target_obj - self.sections = sections - self.set_options = [] - - @property - def parsers(self): - """Metadata item name to parser function mapping.""" - raise NotImplementedError( - '%s must provide .parsers property' % self.__class__.__name__) - - def __setitem__(self, option_name, value): - unknown = tuple() - target_obj = self.target_obj - - # Translate alias into real name. - option_name = self.aliases.get(option_name, option_name) - - current_value = getattr(target_obj, option_name, unknown) - - if current_value is unknown: - raise KeyError(option_name) - - if current_value: - # Already inhabited. Skipping. - return - - skip_option = False - parser = self.parsers.get(option_name) - if parser: - try: - value = parser(value) - - except Exception: - skip_option = True - if not self.ignore_option_errors: - raise - - if skip_option: - return - - setter = getattr(target_obj, 'set_%s' % option_name, None) - if setter is None: - setattr(target_obj, option_name, value) - else: - setter(value) - - self.set_options.append(option_name) - - @classmethod - def _parse_list(cls, value, separator=','): - """Represents value as a list. - - Value is split either by separator (defaults to comma) or by lines. - - :param value: - :param separator: List items separator character. - :rtype: list - """ - if isinstance(value, list): # _get_parser_compound case - return value - - if '\n' in value: - value = value.splitlines() - else: - value = value.split(separator) - - return [chunk.strip() for chunk in value if chunk.strip()] - - @classmethod - def _parse_dict(cls, value): - """Represents value as a dict. - - :param value: - :rtype: dict - """ - separator = '=' - result = {} - for line in cls._parse_list(value): - key, sep, val = line.partition(separator) - if sep != separator: - raise DistutilsOptionError( - 'Unable to parse option value to dict: %s' % value) - result[key.strip()] = val.strip() - - return result - - @classmethod - def _parse_bool(cls, value): - """Represents value as boolean. - - :param value: - :rtype: bool - """ - value = value.lower() - return value in ('1', 'true', 'yes') - - @classmethod - def _parse_file(cls, value): - """Represents value as a string, allowing including text - from nearest files using `file:` directive. - - Directive is sandboxed and won't reach anything outside - directory with setup.py. - - Examples: - file: LICENSE - file: README.rst, CHANGELOG.md, src/file.txt - - :param str value: - :rtype: str - """ - include_directive = 'file:' - - if not isinstance(value, string_types): - return value - - if not value.startswith(include_directive): - return value - - spec = value[len(include_directive):] - filepaths = (os.path.abspath(path.strip()) for path in spec.split(',')) - return '\n'.join( - cls._read_file(path) - for path in filepaths - if (cls._assert_local(path) or True) - and os.path.isfile(path) - ) - - @staticmethod - def _assert_local(filepath): - if not filepath.startswith(os.getcwd()): - raise DistutilsOptionError( - '`file:` directive can not access %s' % filepath) - - @staticmethod - def _read_file(filepath): - with io.open(filepath, encoding='utf-8') as f: - return f.read() - - @classmethod - def _parse_attr(cls, value, package_dir=None): - """Represents value as a module attribute. - - Examples: - attr: package.attr - attr: package.module.attr - - :param str value: - :rtype: str - """ - attr_directive = 'attr:' - if not value.startswith(attr_directive): - return value - - attrs_path = value.replace(attr_directive, '').strip().split('.') - attr_name = attrs_path.pop() - - module_name = '.'.join(attrs_path) - module_name = module_name or '__init__' - - parent_path = os.getcwd() - if package_dir: - if attrs_path[0] in package_dir: - # A custom path was specified for the module we want to import - custom_path = package_dir[attrs_path[0]] - parts = custom_path.rsplit('/', 1) - if len(parts) > 1: - parent_path = os.path.join(os.getcwd(), parts[0]) - module_name = parts[1] - else: - module_name = custom_path - elif '' in package_dir: - # A custom parent directory was specified for all root modules - parent_path = os.path.join(os.getcwd(), package_dir['']) - sys.path.insert(0, parent_path) - try: - module = import_module(module_name) - value = getattr(module, attr_name) - - finally: - sys.path = sys.path[1:] - - return value - - @classmethod - def _get_parser_compound(cls, *parse_methods): - """Returns parser function to represents value as a list. - - Parses a value applying given methods one after another. - - :param parse_methods: - :rtype: callable - """ - def parse(value): - parsed = value - - for method in parse_methods: - parsed = method(parsed) - - return parsed - - return parse - - @classmethod - def _parse_section_to_dict(cls, section_options, values_parser=None): - """Parses section options into a dictionary. - - Optionally applies a given parser to values. - - :param dict section_options: - :param callable values_parser: - :rtype: dict - """ - value = {} - values_parser = values_parser or (lambda val: val) - for key, (_, val) in section_options.items(): - value[key] = values_parser(val) - return value - - def parse_section(self, section_options): - """Parses configuration file section. - - :param dict section_options: - """ - for (name, (_, value)) in section_options.items(): - try: - self[name] = value - - except KeyError: - pass # Keep silent for a new option may appear anytime. - - def parse(self): - """Parses configuration file items from one - or more related sections. - - """ - for section_name, section_options in self.sections.items(): - - method_postfix = '' - if section_name: # [section.option] variant - method_postfix = '_%s' % section_name - - section_parser_method = getattr( - self, - # Dots in section names are tranlsated into dunderscores. - ('parse_section%s' % method_postfix).replace('.', '__'), - None) - - if section_parser_method is None: - raise DistutilsOptionError( - 'Unsupported distribution option section: [%s.%s]' % ( - self.section_prefix, section_name)) - - section_parser_method(section_options) - - def _deprecated_config_handler(self, func, msg, warning_class): - """ this function will wrap around parameters that are deprecated - - :param msg: deprecation message - :param warning_class: class of warning exception to be raised - :param func: function to be wrapped around - """ - @wraps(func) - def config_handler(*args, **kwargs): - warnings.warn(msg, warning_class) - return func(*args, **kwargs) - - return config_handler - - -class ConfigMetadataHandler(ConfigHandler): - - section_prefix = 'metadata' - - aliases = { - 'home_page': 'url', - 'summary': 'description', - 'classifier': 'classifiers', - 'platform': 'platforms', - } - - strict_mode = False - """We need to keep it loose, to be partially compatible with - `pbr` and `d2to1` packages which also uses `metadata` section. - - """ - - def __init__(self, target_obj, options, ignore_option_errors=False, - package_dir=None): - super(ConfigMetadataHandler, self).__init__(target_obj, options, - ignore_option_errors) - self.package_dir = package_dir - - @property - def parsers(self): - """Metadata item name to parser function mapping.""" - parse_list = self._parse_list - parse_file = self._parse_file - parse_dict = self._parse_dict - - return { - 'platforms': parse_list, - 'keywords': parse_list, - 'provides': parse_list, - 'requires': self._deprecated_config_handler(parse_list, - "The requires parameter is deprecated, please use " + - "install_requires for runtime dependencies.", - DeprecationWarning), - 'obsoletes': parse_list, - 'classifiers': self._get_parser_compound(parse_file, parse_list), - 'license': parse_file, - 'description': parse_file, - 'long_description': parse_file, - 'version': self._parse_version, - 'project_urls': parse_dict, - } - - def _parse_version(self, value): - """Parses `version` option value. - - :param value: - :rtype: str - - """ - version = self._parse_file(value) - - if version != value: - version = version.strip() - # Be strict about versions loaded from file because it's easy to - # accidentally include newlines and other unintended content - if isinstance(parse(version), LegacyVersion): - tmpl = ( - 'Version loaded from {value} does not ' - 'comply with PEP 440: {version}' - ) - raise DistutilsOptionError(tmpl.format(**locals())) - - return version - - version = self._parse_attr(value, self.package_dir) - - if callable(version): - version = version() - - if not isinstance(version, string_types): - if hasattr(version, '__iter__'): - version = '.'.join(map(str, version)) - else: - version = '%s' % version - - return version - - -class ConfigOptionsHandler(ConfigHandler): - - section_prefix = 'options' - - @property - def parsers(self): - """Metadata item name to parser function mapping.""" - parse_list = self._parse_list - parse_list_semicolon = partial(self._parse_list, separator=';') - parse_bool = self._parse_bool - parse_dict = self._parse_dict - - return { - 'zip_safe': parse_bool, - 'use_2to3': parse_bool, - 'include_package_data': parse_bool, - 'package_dir': parse_dict, - 'use_2to3_fixers': parse_list, - 'use_2to3_exclude_fixers': parse_list, - 'convert_2to3_doctests': parse_list, - 'scripts': parse_list, - 'eager_resources': parse_list, - 'dependency_links': parse_list, - 'namespace_packages': parse_list, - 'install_requires': parse_list_semicolon, - 'setup_requires': parse_list_semicolon, - 'tests_require': parse_list_semicolon, - 'packages': self._parse_packages, - 'entry_points': self._parse_file, - 'py_modules': parse_list, - } - - def _parse_packages(self, value): - """Parses `packages` option value. - - :param value: - :rtype: list - """ - find_directives = ['find:', 'find_namespace:'] - trimmed_value = value.strip() - - if trimmed_value not in find_directives: - return self._parse_list(value) - - findns = trimmed_value == find_directives[1] - if findns and not PY3: - raise DistutilsOptionError( - 'find_namespace: directive is unsupported on Python < 3.3') - - # Read function arguments from a dedicated section. - find_kwargs = self.parse_section_packages__find( - self.sections.get('packages.find', {})) - - if findns: - from setuptools import find_namespace_packages as find_packages - else: - from setuptools import find_packages - - return find_packages(**find_kwargs) - - def parse_section_packages__find(self, section_options): - """Parses `packages.find` configuration file section. - - To be used in conjunction with _parse_packages(). - - :param dict section_options: - """ - section_data = self._parse_section_to_dict( - section_options, self._parse_list) - - valid_keys = ['where', 'include', 'exclude'] - - find_kwargs = dict( - [(k, v) for k, v in section_data.items() if k in valid_keys and v]) - - where = find_kwargs.get('where') - if where is not None: - find_kwargs['where'] = where[0] # cast list to single val - - return find_kwargs - - def parse_section_entry_points(self, section_options): - """Parses `entry_points` configuration file section. - - :param dict section_options: - """ - parsed = self._parse_section_to_dict(section_options, self._parse_list) - self['entry_points'] = parsed - - def _parse_package_data(self, section_options): - parsed = self._parse_section_to_dict(section_options, self._parse_list) - - root = parsed.get('*') - if root: - parsed[''] = root - del parsed['*'] - - return parsed - - def parse_section_package_data(self, section_options): - """Parses `package_data` configuration file section. - - :param dict section_options: - """ - self['package_data'] = self._parse_package_data(section_options) - - def parse_section_exclude_package_data(self, section_options): - """Parses `exclude_package_data` configuration file section. - - :param dict section_options: - """ - self['exclude_package_data'] = self._parse_package_data( - section_options) - - def parse_section_extras_require(self, section_options): - """Parses `extras_require` configuration file section. - - :param dict section_options: - """ - parse_list = partial(self._parse_list, separator=';') - self['extras_require'] = self._parse_section_to_dict( - section_options, parse_list) - - def parse_section_data_files(self, section_options): - """Parses `data_files` configuration file section. - - :param dict section_options: - """ - parsed = self._parse_section_to_dict(section_options, self._parse_list) - self['data_files'] = [(k, v) for k, v in parsed.items()] diff --git a/lib/setuptools/dep_util.py b/lib/setuptools/dep_util.py deleted file mode 100644 index 2931c13..0000000 --- a/lib/setuptools/dep_util.py +++ /dev/null @@ -1,23 +0,0 @@ -from distutils.dep_util import newer_group - -# yes, this is was almost entirely copy-pasted from -# 'newer_pairwise()', this is just another convenience -# function. -def newer_pairwise_group(sources_groups, targets): - """Walk both arguments in parallel, testing if each source group is newer - than its corresponding target. Returns a pair of lists (sources_groups, - targets) where sources is newer than target, according to the semantics - of 'newer_group()'. - """ - if len(sources_groups) != len(targets): - raise ValueError("'sources_group' and 'targets' must be the same length") - - # build a pair of lists (sources_groups, targets) where source is newer - n_sources = [] - n_targets = [] - for i in range(len(sources_groups)): - if newer_group(sources_groups[i], targets[i]): - n_sources.append(sources_groups[i]) - n_targets.append(targets[i]) - - return n_sources, n_targets diff --git a/lib/setuptools/depends.py b/lib/setuptools/depends.py deleted file mode 100644 index 45e7052..0000000 --- a/lib/setuptools/depends.py +++ /dev/null @@ -1,186 +0,0 @@ -import sys -import imp -import marshal -from distutils.version import StrictVersion -from imp import PKG_DIRECTORY, PY_COMPILED, PY_SOURCE, PY_FROZEN - -from .py33compat import Bytecode - - -__all__ = [ - 'Require', 'find_module', 'get_module_constant', 'extract_constant' -] - - -class Require: - """A prerequisite to building or installing a distribution""" - - def __init__(self, name, requested_version, module, homepage='', - attribute=None, format=None): - - if format is None and requested_version is not None: - format = StrictVersion - - if format is not None: - requested_version = format(requested_version) - if attribute is None: - attribute = '__version__' - - self.__dict__.update(locals()) - del self.self - - def full_name(self): - """Return full package/distribution name, w/version""" - if self.requested_version is not None: - return '%s-%s' % (self.name, self.requested_version) - return self.name - - def version_ok(self, version): - """Is 'version' sufficiently up-to-date?""" - return self.attribute is None or self.format is None or \ - str(version) != "unknown" and version >= self.requested_version - - def get_version(self, paths=None, default="unknown"): - """Get version number of installed module, 'None', or 'default' - - Search 'paths' for module. If not found, return 'None'. If found, - return the extracted version attribute, or 'default' if no version - attribute was specified, or the value cannot be determined without - importing the module. The version is formatted according to the - requirement's version format (if any), unless it is 'None' or the - supplied 'default'. - """ - - if self.attribute is None: - try: - f, p, i = find_module(self.module, paths) - if f: - f.close() - return default - except ImportError: - return None - - v = get_module_constant(self.module, self.attribute, default, paths) - - if v is not None and v is not default and self.format is not None: - return self.format(v) - - return v - - def is_present(self, paths=None): - """Return true if dependency is present on 'paths'""" - return self.get_version(paths) is not None - - def is_current(self, paths=None): - """Return true if dependency is present and up-to-date on 'paths'""" - version = self.get_version(paths) - if version is None: - return False - return self.version_ok(version) - - -def find_module(module, paths=None): - """Just like 'imp.find_module()', but with package support""" - - parts = module.split('.') - - while parts: - part = parts.pop(0) - f, path, (suffix, mode, kind) = info = imp.find_module(part, paths) - - if kind == PKG_DIRECTORY: - parts = parts or ['__init__'] - paths = [path] - - elif parts: - raise ImportError("Can't find %r in %s" % (parts, module)) - - return info - - -def get_module_constant(module, symbol, default=-1, paths=None): - """Find 'module' by searching 'paths', and extract 'symbol' - - Return 'None' if 'module' does not exist on 'paths', or it does not define - 'symbol'. If the module defines 'symbol' as a constant, return the - constant. Otherwise, return 'default'.""" - - try: - f, path, (suffix, mode, kind) = find_module(module, paths) - except ImportError: - # Module doesn't exist - return None - - try: - if kind == PY_COMPILED: - f.read(8) # skip magic & date - code = marshal.load(f) - elif kind == PY_FROZEN: - code = imp.get_frozen_object(module) - elif kind == PY_SOURCE: - code = compile(f.read(), path, 'exec') - else: - # Not something we can parse; we'll have to import it. :( - if module not in sys.modules: - imp.load_module(module, f, path, (suffix, mode, kind)) - return getattr(sys.modules[module], symbol, None) - - finally: - if f: - f.close() - - return extract_constant(code, symbol, default) - - -def extract_constant(code, symbol, default=-1): - """Extract the constant value of 'symbol' from 'code' - - If the name 'symbol' is bound to a constant value by the Python code - object 'code', return that value. If 'symbol' is bound to an expression, - return 'default'. Otherwise, return 'None'. - - Return value is based on the first assignment to 'symbol'. 'symbol' must - be a global, or at least a non-"fast" local in the code block. That is, - only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol' - must be present in 'code.co_names'. - """ - if symbol not in code.co_names: - # name's not there, can't possibly be an assignment - return None - - name_idx = list(code.co_names).index(symbol) - - STORE_NAME = 90 - STORE_GLOBAL = 97 - LOAD_CONST = 100 - - const = default - - for byte_code in Bytecode(code): - op = byte_code.opcode - arg = byte_code.arg - - if op == LOAD_CONST: - const = code.co_consts[arg] - elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL): - return const - else: - const = default - - -def _update_globals(): - """ - Patch the globals to remove the objects not available on some platforms. - - XXX it'd be better to test assertions about bytecode instead. - """ - - if not sys.platform.startswith('java') and sys.platform != 'cli': - return - incompatible = 'extract_constant', 'get_module_constant' - for name in incompatible: - del globals()[name] - __all__.remove(name) - - -_update_globals() diff --git a/lib/setuptools/dist.py b/lib/setuptools/dist.py deleted file mode 100644 index 7062ae8..0000000 --- a/lib/setuptools/dist.py +++ /dev/null @@ -1,1147 +0,0 @@ -# -*- coding: utf-8 -*- -__all__ = ['Distribution'] - -import re -import os -import warnings -import numbers -import distutils.log -import distutils.core -import distutils.cmd -import distutils.dist -import itertools - - -from collections import defaultdict -from email import message_from_file - -from distutils.errors import ( - DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError, -) -from distutils.util import rfc822_escape -from distutils.version import StrictVersion - -from setuptools.extern import six -from setuptools.extern import packaging -from setuptools.extern.six.moves import map, filter, filterfalse - -from . import SetuptoolsDeprecationWarning - -from setuptools.depends import Require -from setuptools import windows_support -from setuptools.monkey import get_unpatched -from setuptools.config import parse_configuration -import pkg_resources -from .py36compat import Distribution_parse_config_files - -__import__('setuptools.extern.packaging.specifiers') -__import__('setuptools.extern.packaging.version') - - -def _get_unpatched(cls): - warnings.warn("Do not call this function", DistDeprecationWarning) - return get_unpatched(cls) - - -def get_metadata_version(self): - mv = getattr(self, 'metadata_version', None) - - if mv is None: - if self.long_description_content_type or self.provides_extras: - mv = StrictVersion('2.1') - elif (self.maintainer is not None or - self.maintainer_email is not None or - getattr(self, 'python_requires', None) is not None): - mv = StrictVersion('1.2') - elif (self.provides or self.requires or self.obsoletes or - self.classifiers or self.download_url): - mv = StrictVersion('1.1') - else: - mv = StrictVersion('1.0') - - self.metadata_version = mv - - return mv - - -def read_pkg_file(self, file): - """Reads the metadata values from a file object.""" - msg = message_from_file(file) - - def _read_field(name): - value = msg[name] - if value == 'UNKNOWN': - return None - return value - - def _read_list(name): - values = msg.get_all(name, None) - if values == []: - return None - return values - - self.metadata_version = StrictVersion(msg['metadata-version']) - self.name = _read_field('name') - self.version = _read_field('version') - self.description = _read_field('summary') - # we are filling author only. - self.author = _read_field('author') - self.maintainer = None - self.author_email = _read_field('author-email') - self.maintainer_email = None - self.url = _read_field('home-page') - self.license = _read_field('license') - - if 'download-url' in msg: - self.download_url = _read_field('download-url') - else: - self.download_url = None - - self.long_description = _read_field('description') - self.description = _read_field('summary') - - if 'keywords' in msg: - self.keywords = _read_field('keywords').split(',') - - self.platforms = _read_list('platform') - self.classifiers = _read_list('classifier') - - # PEP 314 - these fields only exist in 1.1 - if self.metadata_version == StrictVersion('1.1'): - self.requires = _read_list('requires') - self.provides = _read_list('provides') - self.obsoletes = _read_list('obsoletes') - else: - self.requires = None - self.provides = None - self.obsoletes = None - - -# Based on Python 3.5 version -def write_pkg_file(self, file): - """Write the PKG-INFO format data to a file object. - """ - version = self.get_metadata_version() - - if six.PY2: - def write_field(key, value): - file.write("%s: %s\n" % (key, self._encode_field(value))) - else: - def write_field(key, value): - file.write("%s: %s\n" % (key, value)) - - - write_field('Metadata-Version', str(version)) - write_field('Name', self.get_name()) - write_field('Version', self.get_version()) - write_field('Summary', self.get_description()) - write_field('Home-page', self.get_url()) - - if version < StrictVersion('1.2'): - write_field('Author', self.get_contact()) - write_field('Author-email', self.get_contact_email()) - else: - optional_fields = ( - ('Author', 'author'), - ('Author-email', 'author_email'), - ('Maintainer', 'maintainer'), - ('Maintainer-email', 'maintainer_email'), - ) - - for field, attr in optional_fields: - attr_val = getattr(self, attr) - - if attr_val is not None: - write_field(field, attr_val) - - write_field('License', self.get_license()) - if self.download_url: - write_field('Download-URL', self.download_url) - for project_url in self.project_urls.items(): - write_field('Project-URL', '%s, %s' % project_url) - - long_desc = rfc822_escape(self.get_long_description()) - write_field('Description', long_desc) - - keywords = ','.join(self.get_keywords()) - if keywords: - write_field('Keywords', keywords) - - if version >= StrictVersion('1.2'): - for platform in self.get_platforms(): - write_field('Platform', platform) - else: - self._write_list(file, 'Platform', self.get_platforms()) - - self._write_list(file, 'Classifier', self.get_classifiers()) - - # PEP 314 - self._write_list(file, 'Requires', self.get_requires()) - self._write_list(file, 'Provides', self.get_provides()) - self._write_list(file, 'Obsoletes', self.get_obsoletes()) - - # Setuptools specific for PEP 345 - if hasattr(self, 'python_requires'): - write_field('Requires-Python', self.python_requires) - - # PEP 566 - if self.long_description_content_type: - write_field( - 'Description-Content-Type', - self.long_description_content_type - ) - if self.provides_extras: - for extra in self.provides_extras: - write_field('Provides-Extra', extra) - - -sequence = tuple, list - - -def check_importable(dist, attr, value): - try: - ep = pkg_resources.EntryPoint.parse('x=' + value) - assert not ep.extras - except (TypeError, ValueError, AttributeError, AssertionError): - raise DistutilsSetupError( - "%r must be importable 'module:attrs' string (got %r)" - % (attr, value) - ) - - -def assert_string_list(dist, attr, value): - """Verify that value is a string list or None""" - try: - assert ''.join(value) != value - except (TypeError, ValueError, AttributeError, AssertionError): - raise DistutilsSetupError( - "%r must be a list of strings (got %r)" % (attr, value) - ) - - -def check_nsp(dist, attr, value): - """Verify that namespace packages are valid""" - ns_packages = value - assert_string_list(dist, attr, ns_packages) - for nsp in ns_packages: - if not dist.has_contents_for(nsp): - raise DistutilsSetupError( - "Distribution contains no modules or packages for " + - "namespace package %r" % nsp - ) - parent, sep, child = nsp.rpartition('.') - if parent and parent not in ns_packages: - distutils.log.warn( - "WARNING: %r is declared as a package namespace, but %r" - " is not: please correct this in setup.py", nsp, parent - ) - - -def check_extras(dist, attr, value): - """Verify that extras_require mapping is valid""" - try: - list(itertools.starmap(_check_extra, value.items())) - except (TypeError, ValueError, AttributeError): - raise DistutilsSetupError( - "'extras_require' must be a dictionary whose values are " - "strings or lists of strings containing valid project/version " - "requirement specifiers." - ) - - -def _check_extra(extra, reqs): - name, sep, marker = extra.partition(':') - if marker and pkg_resources.invalid_marker(marker): - raise DistutilsSetupError("Invalid environment marker: " + marker) - list(pkg_resources.parse_requirements(reqs)) - - -def assert_bool(dist, attr, value): - """Verify that value is True, False, 0, or 1""" - if bool(value) != value: - tmpl = "{attr!r} must be a boolean value (got {value!r})" - raise DistutilsSetupError(tmpl.format(attr=attr, value=value)) - - -def check_requirements(dist, attr, value): - """Verify that install_requires is a valid requirements list""" - try: - list(pkg_resources.parse_requirements(value)) - if isinstance(value, (dict, set)): - raise TypeError("Unordered types are not allowed") - except (TypeError, ValueError) as error: - tmpl = ( - "{attr!r} must be a string or list of strings " - "containing valid project/version requirement specifiers; {error}" - ) - raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) - - -def check_specifier(dist, attr, value): - """Verify that value is a valid version specifier""" - try: - packaging.specifiers.SpecifierSet(value) - except packaging.specifiers.InvalidSpecifier as error: - tmpl = ( - "{attr!r} must be a string " - "containing valid version specifiers; {error}" - ) - raise DistutilsSetupError(tmpl.format(attr=attr, error=error)) - - -def check_entry_points(dist, attr, value): - """Verify that entry_points map is parseable""" - try: - pkg_resources.EntryPoint.parse_map(value) - except ValueError as e: - raise DistutilsSetupError(e) - - -def check_test_suite(dist, attr, value): - if not isinstance(value, six.string_types): - raise DistutilsSetupError("test_suite must be a string") - - -def check_package_data(dist, attr, value): - """Verify that value is a dictionary of package names to glob lists""" - if isinstance(value, dict): - for k, v in value.items(): - if not isinstance(k, str): - break - try: - iter(v) - except TypeError: - break - else: - return - raise DistutilsSetupError( - attr + " must be a dictionary mapping package names to lists of " - "wildcard patterns" - ) - - -def check_packages(dist, attr, value): - for pkgname in value: - if not re.match(r'\w+(\.\w+)*', pkgname): - distutils.log.warn( - "WARNING: %r not a valid package name; please use only " - ".-separated package names in setup.py", pkgname - ) - - -_Distribution = get_unpatched(distutils.core.Distribution) - - -class Distribution(Distribution_parse_config_files, _Distribution): - """Distribution with support for features, tests, and package data - - This is an enhanced version of 'distutils.dist.Distribution' that - effectively adds the following new optional keyword arguments to 'setup()': - - 'install_requires' -- a string or sequence of strings specifying project - versions that the distribution requires when installed, in the format - used by 'pkg_resources.require()'. They will be installed - automatically when the package is installed. If you wish to use - packages that are not available in PyPI, or want to give your users an - alternate download location, you can add a 'find_links' option to the - '[easy_install]' section of your project's 'setup.cfg' file, and then - setuptools will scan the listed web pages for links that satisfy the - requirements. - - 'extras_require' -- a dictionary mapping names of optional "extras" to the - additional requirement(s) that using those extras incurs. For example, - this:: - - extras_require = dict(reST = ["docutils>=0.3", "reSTedit"]) - - indicates that the distribution can optionally provide an extra - capability called "reST", but it can only be used if docutils and - reSTedit are installed. If the user installs your package using - EasyInstall and requests one of your extras, the corresponding - additional requirements will be installed if needed. - - 'features' **deprecated** -- a dictionary mapping option names to - 'setuptools.Feature' - objects. Features are a portion of the distribution that can be - included or excluded based on user options, inter-feature dependencies, - and availability on the current system. Excluded features are omitted - from all setup commands, including source and binary distributions, so - you can create multiple distributions from the same source tree. - Feature names should be valid Python identifiers, except that they may - contain the '-' (minus) sign. Features can be included or excluded - via the command line options '--with-X' and '--without-X', where 'X' is - the name of the feature. Whether a feature is included by default, and - whether you are allowed to control this from the command line, is - determined by the Feature object. See the 'Feature' class for more - information. - - 'test_suite' -- the name of a test suite to run for the 'test' command. - If the user runs 'python setup.py test', the package will be installed, - and the named test suite will be run. The format is the same as - would be used on a 'unittest.py' command line. That is, it is the - dotted name of an object to import and call to generate a test suite. - - 'package_data' -- a dictionary mapping package names to lists of filenames - or globs to use to find data files contained in the named packages. - If the dictionary has filenames or globs listed under '""' (the empty - string), those names will be searched for in every package, in addition - to any names for the specific package. Data files found using these - names/globs will be installed along with the package, in the same - location as the package. Note that globs are allowed to reference - the contents of non-package subdirectories, as long as you use '/' as - a path separator. (Globs are automatically converted to - platform-specific paths at runtime.) - - In addition to these new keywords, this class also has several new methods - for manipulating the distribution's contents. For example, the 'include()' - and 'exclude()' methods can be thought of as in-place add and subtract - commands that add or remove packages, modules, extensions, and so on from - the distribution. They are used by the feature subsystem to configure the - distribution for the included and excluded features. - """ - - _DISTUTILS_UNSUPPORTED_METADATA = { - 'long_description_content_type': None, - 'project_urls': dict, - 'provides_extras': set, - } - - _patched_dist = None - - def patch_missing_pkg_info(self, attrs): - # Fake up a replacement for the data that would normally come from - # PKG-INFO, but which might not yet be built if this is a fresh - # checkout. - # - if not attrs or 'name' not in attrs or 'version' not in attrs: - return - key = pkg_resources.safe_name(str(attrs['name'])).lower() - dist = pkg_resources.working_set.by_key.get(key) - if dist is not None and not dist.has_metadata('PKG-INFO'): - dist._version = pkg_resources.safe_version(str(attrs['version'])) - self._patched_dist = dist - - def __init__(self, attrs=None): - have_package_data = hasattr(self, "package_data") - if not have_package_data: - self.package_data = {} - attrs = attrs or {} - if 'features' in attrs or 'require_features' in attrs: - Feature.warn_deprecated() - self.require_features = [] - self.features = {} - self.dist_files = [] - # Filter-out setuptools' specific options. - self.src_root = attrs.pop("src_root", None) - self.patch_missing_pkg_info(attrs) - self.dependency_links = attrs.pop('dependency_links', []) - self.setup_requires = attrs.pop('setup_requires', []) - for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): - vars(self).setdefault(ep.name, None) - _Distribution.__init__(self, { - k: v for k, v in attrs.items() - if k not in self._DISTUTILS_UNSUPPORTED_METADATA - }) - - # Fill-in missing metadata fields not supported by distutils. - # Note some fields may have been set by other tools (e.g. pbr) - # above; they are taken preferrentially to setup() arguments - for option, default in self._DISTUTILS_UNSUPPORTED_METADATA.items(): - for source in self.metadata.__dict__, attrs: - if option in source: - value = source[option] - break - else: - value = default() if default else None - setattr(self.metadata, option, value) - - if isinstance(self.metadata.version, numbers.Number): - # Some people apparently take "version number" too literally :) - self.metadata.version = str(self.metadata.version) - - if self.metadata.version is not None: - try: - ver = packaging.version.Version(self.metadata.version) - normalized_version = str(ver) - if self.metadata.version != normalized_version: - warnings.warn( - "Normalizing '%s' to '%s'" % ( - self.metadata.version, - normalized_version, - ) - ) - self.metadata.version = normalized_version - except (packaging.version.InvalidVersion, TypeError): - warnings.warn( - "The version specified (%r) is an invalid version, this " - "may not work as expected with newer versions of " - "setuptools, pip, and PyPI. Please see PEP 440 for more " - "details." % self.metadata.version - ) - self._finalize_requires() - - def _finalize_requires(self): - """ - Set `metadata.python_requires` and fix environment markers - in `install_requires` and `extras_require`. - """ - if getattr(self, 'python_requires', None): - self.metadata.python_requires = self.python_requires - - if getattr(self, 'extras_require', None): - for extra in self.extras_require.keys(): - # Since this gets called multiple times at points where the - # keys have become 'converted' extras, ensure that we are only - # truly adding extras we haven't seen before here. - extra = extra.split(':')[0] - if extra: - self.metadata.provides_extras.add(extra) - - self._convert_extras_requirements() - self._move_install_requirements_markers() - - def _convert_extras_requirements(self): - """ - Convert requirements in `extras_require` of the form - `"extra": ["barbazquux; {marker}"]` to - `"extra:{marker}": ["barbazquux"]`. - """ - spec_ext_reqs = getattr(self, 'extras_require', None) or {} - self._tmp_extras_require = defaultdict(list) - for section, v in spec_ext_reqs.items(): - # Do not strip empty sections. - self._tmp_extras_require[section] - for r in pkg_resources.parse_requirements(v): - suffix = self._suffix_for(r) - self._tmp_extras_require[section + suffix].append(r) - - @staticmethod - def _suffix_for(req): - """ - For a requirement, return the 'extras_require' suffix for - that requirement. - """ - return ':' + str(req.marker) if req.marker else '' - - def _move_install_requirements_markers(self): - """ - Move requirements in `install_requires` that are using environment - markers `extras_require`. - """ - - # divide the install_requires into two sets, simple ones still - # handled by install_requires and more complex ones handled - # by extras_require. - - def is_simple_req(req): - return not req.marker - - spec_inst_reqs = getattr(self, 'install_requires', None) or () - inst_reqs = list(pkg_resources.parse_requirements(spec_inst_reqs)) - simple_reqs = filter(is_simple_req, inst_reqs) - complex_reqs = filterfalse(is_simple_req, inst_reqs) - self.install_requires = list(map(str, simple_reqs)) - - for r in complex_reqs: - self._tmp_extras_require[':' + str(r.marker)].append(r) - self.extras_require = dict( - (k, [str(r) for r in map(self._clean_req, v)]) - for k, v in self._tmp_extras_require.items() - ) - - def _clean_req(self, req): - """ - Given a Requirement, remove environment markers and return it. - """ - req.marker = None - return req - - def parse_config_files(self, filenames=None, ignore_option_errors=False): - """Parses configuration files from various levels - and loads configuration. - - """ - _Distribution.parse_config_files(self, filenames=filenames) - - parse_configuration(self, self.command_options, - ignore_option_errors=ignore_option_errors) - self._finalize_requires() - - def parse_command_line(self): - """Process features after parsing command line options""" - result = _Distribution.parse_command_line(self) - if self.features: - self._finalize_features() - return result - - def _feature_attrname(self, name): - """Convert feature name to corresponding option attribute name""" - return 'with_' + name.replace('-', '_') - - def fetch_build_eggs(self, requires): - """Resolve pre-setup requirements""" - resolved_dists = pkg_resources.working_set.resolve( - pkg_resources.parse_requirements(requires), - installer=self.fetch_build_egg, - replace_conflicting=True, - ) - for dist in resolved_dists: - pkg_resources.working_set.add(dist, replace=True) - return resolved_dists - - def finalize_options(self): - _Distribution.finalize_options(self) - if self.features: - self._set_global_opts_from_features() - - for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'): - value = getattr(self, ep.name, None) - if value is not None: - ep.require(installer=self.fetch_build_egg) - ep.load()(self, ep.name, value) - if getattr(self, 'convert_2to3_doctests', None): - # XXX may convert to set here when we can rely on set being builtin - self.convert_2to3_doctests = [ - os.path.abspath(p) - for p in self.convert_2to3_doctests - ] - else: - self.convert_2to3_doctests = [] - - def get_egg_cache_dir(self): - egg_cache_dir = os.path.join(os.curdir, '.eggs') - if not os.path.exists(egg_cache_dir): - os.mkdir(egg_cache_dir) - windows_support.hide_file(egg_cache_dir) - readme_txt_filename = os.path.join(egg_cache_dir, 'README.txt') - with open(readme_txt_filename, 'w') as f: - f.write('This directory contains eggs that were downloaded ' - 'by setuptools to build, test, and run plug-ins.\n\n') - f.write('This directory caches those eggs to prevent ' - 'repeated downloads.\n\n') - f.write('However, it is safe to delete this directory.\n\n') - - return egg_cache_dir - - def fetch_build_egg(self, req): - """Fetch an egg needed for building""" - from setuptools.command.easy_install import easy_install - dist = self.__class__({'script_args': ['easy_install']}) - opts = dist.get_option_dict('easy_install') - opts.clear() - opts.update( - (k, v) - for k, v in self.get_option_dict('easy_install').items() - if k in ( - # don't use any other settings - 'find_links', 'site_dirs', 'index_url', - 'optimize', 'site_dirs', 'allow_hosts', - )) - if self.dependency_links: - links = self.dependency_links[:] - if 'find_links' in opts: - links = opts['find_links'][1] + links - opts['find_links'] = ('setup', links) - install_dir = self.get_egg_cache_dir() - cmd = easy_install( - dist, args=["x"], install_dir=install_dir, - exclude_scripts=True, - always_copy=False, build_directory=None, editable=False, - upgrade=False, multi_version=True, no_report=True, user=False - ) - cmd.ensure_finalized() - return cmd.easy_install(req) - - def _set_global_opts_from_features(self): - """Add --with-X/--without-X options based on optional features""" - - go = [] - no = self.negative_opt.copy() - - for name, feature in self.features.items(): - self._set_feature(name, None) - feature.validate(self) - - if feature.optional: - descr = feature.description - incdef = ' (default)' - excdef = '' - if not feature.include_by_default(): - excdef, incdef = incdef, excdef - - new = ( - ('with-' + name, None, 'include ' + descr + incdef), - ('without-' + name, None, 'exclude ' + descr + excdef), - ) - go.extend(new) - no['without-' + name] = 'with-' + name - - self.global_options = self.feature_options = go + self.global_options - self.negative_opt = self.feature_negopt = no - - def _finalize_features(self): - """Add/remove features and resolve dependencies between them""" - - # First, flag all the enabled items (and thus their dependencies) - for name, feature in self.features.items(): - enabled = self.feature_is_included(name) - if enabled or (enabled is None and feature.include_by_default()): - feature.include_in(self) - self._set_feature(name, 1) - - # Then disable the rest, so that off-by-default features don't - # get flagged as errors when they're required by an enabled feature - for name, feature in self.features.items(): - if not self.feature_is_included(name): - feature.exclude_from(self) - self._set_feature(name, 0) - - def get_command_class(self, command): - """Pluggable version of get_command_class()""" - if command in self.cmdclass: - return self.cmdclass[command] - - eps = pkg_resources.iter_entry_points('distutils.commands', command) - for ep in eps: - ep.require(installer=self.fetch_build_egg) - self.cmdclass[command] = cmdclass = ep.load() - return cmdclass - else: - return _Distribution.get_command_class(self, command) - - def print_commands(self): - for ep in pkg_resources.iter_entry_points('distutils.commands'): - if ep.name not in self.cmdclass: - # don't require extras as the commands won't be invoked - cmdclass = ep.resolve() - self.cmdclass[ep.name] = cmdclass - return _Distribution.print_commands(self) - - def get_command_list(self): - for ep in pkg_resources.iter_entry_points('distutils.commands'): - if ep.name not in self.cmdclass: - # don't require extras as the commands won't be invoked - cmdclass = ep.resolve() - self.cmdclass[ep.name] = cmdclass - return _Distribution.get_command_list(self) - - def _set_feature(self, name, status): - """Set feature's inclusion status""" - setattr(self, self._feature_attrname(name), status) - - def feature_is_included(self, name): - """Return 1 if feature is included, 0 if excluded, 'None' if unknown""" - return getattr(self, self._feature_attrname(name)) - - def include_feature(self, name): - """Request inclusion of feature named 'name'""" - - if self.feature_is_included(name) == 0: - descr = self.features[name].description - raise DistutilsOptionError( - descr + " is required, but was excluded or is not available" - ) - self.features[name].include_in(self) - self._set_feature(name, 1) - - def include(self, **attrs): - """Add items to distribution that are named in keyword arguments - - For example, 'dist.exclude(py_modules=["x"])' would add 'x' to - the distribution's 'py_modules' attribute, if it was not already - there. - - Currently, this method only supports inclusion for attributes that are - lists or tuples. If you need to add support for adding to other - attributes in this or a subclass, you can add an '_include_X' method, - where 'X' is the name of the attribute. The method will be called with - the value passed to 'include()'. So, 'dist.include(foo={"bar":"baz"})' - will try to call 'dist._include_foo({"bar":"baz"})', which can then - handle whatever special inclusion logic is needed. - """ - for k, v in attrs.items(): - include = getattr(self, '_include_' + k, None) - if include: - include(v) - else: - self._include_misc(k, v) - - def exclude_package(self, package): - """Remove packages, modules, and extensions in named package""" - - pfx = package + '.' - if self.packages: - self.packages = [ - p for p in self.packages - if p != package and not p.startswith(pfx) - ] - - if self.py_modules: - self.py_modules = [ - p for p in self.py_modules - if p != package and not p.startswith(pfx) - ] - - if self.ext_modules: - self.ext_modules = [ - p for p in self.ext_modules - if p.name != package and not p.name.startswith(pfx) - ] - - def has_contents_for(self, package): - """Return true if 'exclude_package(package)' would do something""" - - pfx = package + '.' - - for p in self.iter_distribution_names(): - if p == package or p.startswith(pfx): - return True - - def _exclude_misc(self, name, value): - """Handle 'exclude()' for list/tuple attrs without a special handler""" - if not isinstance(value, sequence): - raise DistutilsSetupError( - "%s: setting must be a list or tuple (%r)" % (name, value) - ) - try: - old = getattr(self, name) - except AttributeError: - raise DistutilsSetupError( - "%s: No such distribution setting" % name - ) - if old is not None and not isinstance(old, sequence): - raise DistutilsSetupError( - name + ": this setting cannot be changed via include/exclude" - ) - elif old: - setattr(self, name, [item for item in old if item not in value]) - - def _include_misc(self, name, value): - """Handle 'include()' for list/tuple attrs without a special handler""" - - if not isinstance(value, sequence): - raise DistutilsSetupError( - "%s: setting must be a list (%r)" % (name, value) - ) - try: - old = getattr(self, name) - except AttributeError: - raise DistutilsSetupError( - "%s: No such distribution setting" % name - ) - if old is None: - setattr(self, name, value) - elif not isinstance(old, sequence): - raise DistutilsSetupError( - name + ": this setting cannot be changed via include/exclude" - ) - else: - new = [item for item in value if item not in old] - setattr(self, name, old + new) - - def exclude(self, **attrs): - """Remove items from distribution that are named in keyword arguments - - For example, 'dist.exclude(py_modules=["x"])' would remove 'x' from - the distribution's 'py_modules' attribute. Excluding packages uses - the 'exclude_package()' method, so all of the package's contained - packages, modules, and extensions are also excluded. - - Currently, this method only supports exclusion from attributes that are - lists or tuples. If you need to add support for excluding from other - attributes in this or a subclass, you can add an '_exclude_X' method, - where 'X' is the name of the attribute. The method will be called with - the value passed to 'exclude()'. So, 'dist.exclude(foo={"bar":"baz"})' - will try to call 'dist._exclude_foo({"bar":"baz"})', which can then - handle whatever special exclusion logic is needed. - """ - for k, v in attrs.items(): - exclude = getattr(self, '_exclude_' + k, None) - if exclude: - exclude(v) - else: - self._exclude_misc(k, v) - - def _exclude_packages(self, packages): - if not isinstance(packages, sequence): - raise DistutilsSetupError( - "packages: setting must be a list or tuple (%r)" % (packages,) - ) - list(map(self.exclude_package, packages)) - - def _parse_command_opts(self, parser, args): - # Remove --with-X/--without-X options when processing command args - self.global_options = self.__class__.global_options - self.negative_opt = self.__class__.negative_opt - - # First, expand any aliases - command = args[0] - aliases = self.get_option_dict('aliases') - while command in aliases: - src, alias = aliases[command] - del aliases[command] # ensure each alias can expand only once! - import shlex - args[:1] = shlex.split(alias, True) - command = args[0] - - nargs = _Distribution._parse_command_opts(self, parser, args) - - # Handle commands that want to consume all remaining arguments - cmd_class = self.get_command_class(command) - if getattr(cmd_class, 'command_consumes_arguments', None): - self.get_option_dict(command)['args'] = ("command line", nargs) - if nargs is not None: - return [] - - return nargs - - def get_cmdline_options(self): - """Return a '{cmd: {opt:val}}' map of all command-line options - - Option names are all long, but do not include the leading '--', and - contain dashes rather than underscores. If the option doesn't take - an argument (e.g. '--quiet'), the 'val' is 'None'. - - Note that options provided by config files are intentionally excluded. - """ - - d = {} - - for cmd, opts in self.command_options.items(): - - for opt, (src, val) in opts.items(): - - if src != "command line": - continue - - opt = opt.replace('_', '-') - - if val == 0: - cmdobj = self.get_command_obj(cmd) - neg_opt = self.negative_opt.copy() - neg_opt.update(getattr(cmdobj, 'negative_opt', {})) - for neg, pos in neg_opt.items(): - if pos == opt: - opt = neg - val = None - break - else: - raise AssertionError("Shouldn't be able to get here") - - elif val == 1: - val = None - - d.setdefault(cmd, {})[opt] = val - - return d - - def iter_distribution_names(self): - """Yield all packages, modules, and extension names in distribution""" - - for pkg in self.packages or (): - yield pkg - - for module in self.py_modules or (): - yield module - - for ext in self.ext_modules or (): - if isinstance(ext, tuple): - name, buildinfo = ext - else: - name = ext.name - if name.endswith('module'): - name = name[:-6] - yield name - - def handle_display_options(self, option_order): - """If there were any non-global "display-only" options - (--help-commands or the metadata display options) on the command - line, display the requested info and return true; else return - false. - """ - import sys - - if six.PY2 or self.help_commands: - return _Distribution.handle_display_options(self, option_order) - - # Stdout may be StringIO (e.g. in tests) - import io - if not isinstance(sys.stdout, io.TextIOWrapper): - return _Distribution.handle_display_options(self, option_order) - - # Don't wrap stdout if utf-8 is already the encoding. Provides - # workaround for #334. - if sys.stdout.encoding.lower() in ('utf-8', 'utf8'): - return _Distribution.handle_display_options(self, option_order) - - # Print metadata in UTF-8 no matter the platform - encoding = sys.stdout.encoding - errors = sys.stdout.errors - newline = sys.platform != 'win32' and '\n' or None - line_buffering = sys.stdout.line_buffering - - sys.stdout = io.TextIOWrapper( - sys.stdout.detach(), 'utf-8', errors, newline, line_buffering) - try: - return _Distribution.handle_display_options(self, option_order) - finally: - sys.stdout = io.TextIOWrapper( - sys.stdout.detach(), encoding, errors, newline, line_buffering) - - -class Feature: - """ - **deprecated** -- The `Feature` facility was never completely implemented - or supported, `has reported issues - <https://github.com/pypa/setuptools/issues/58>`_ and will be removed in - a future version. - - A subset of the distribution that can be excluded if unneeded/wanted - - Features are created using these keyword arguments: - - 'description' -- a short, human readable description of the feature, to - be used in error messages, and option help messages. - - 'standard' -- if true, the feature is included by default if it is - available on the current system. Otherwise, the feature is only - included if requested via a command line '--with-X' option, or if - another included feature requires it. The default setting is 'False'. - - 'available' -- if true, the feature is available for installation on the - current system. The default setting is 'True'. - - 'optional' -- if true, the feature's inclusion can be controlled from the - command line, using the '--with-X' or '--without-X' options. If - false, the feature's inclusion status is determined automatically, - based on 'availabile', 'standard', and whether any other feature - requires it. The default setting is 'True'. - - 'require_features' -- a string or sequence of strings naming features - that should also be included if this feature is included. Defaults to - empty list. May also contain 'Require' objects that should be - added/removed from the distribution. - - 'remove' -- a string or list of strings naming packages to be removed - from the distribution if this feature is *not* included. If the - feature *is* included, this argument is ignored. This argument exists - to support removing features that "crosscut" a distribution, such as - defining a 'tests' feature that removes all the 'tests' subpackages - provided by other features. The default for this argument is an empty - list. (Note: the named package(s) or modules must exist in the base - distribution when the 'setup()' function is initially called.) - - other keywords -- any other keyword arguments are saved, and passed to - the distribution's 'include()' and 'exclude()' methods when the - feature is included or excluded, respectively. So, for example, you - could pass 'packages=["a","b"]' to cause packages 'a' and 'b' to be - added or removed from the distribution as appropriate. - - A feature must include at least one 'requires', 'remove', or other - keyword argument. Otherwise, it can't affect the distribution in any way. - Note also that you can subclass 'Feature' to create your own specialized - feature types that modify the distribution in other ways when included or - excluded. See the docstrings for the various methods here for more detail. - Aside from the methods, the only feature attributes that distributions look - at are 'description' and 'optional'. - """ - - @staticmethod - def warn_deprecated(): - msg = ( - "Features are deprecated and will be removed in a future " - "version. See https://github.com/pypa/setuptools/issues/65." - ) - warnings.warn(msg, DistDeprecationWarning, stacklevel=3) - - def __init__( - self, description, standard=False, available=True, - optional=True, require_features=(), remove=(), **extras): - self.warn_deprecated() - - self.description = description - self.standard = standard - self.available = available - self.optional = optional - if isinstance(require_features, (str, Require)): - require_features = require_features, - - self.require_features = [ - r for r in require_features if isinstance(r, str) - ] - er = [r for r in require_features if not isinstance(r, str)] - if er: - extras['require_features'] = er - - if isinstance(remove, str): - remove = remove, - self.remove = remove - self.extras = extras - - if not remove and not require_features and not extras: - raise DistutilsSetupError( - "Feature %s: must define 'require_features', 'remove', or " - "at least one of 'packages', 'py_modules', etc." - ) - - def include_by_default(self): - """Should this feature be included by default?""" - return self.available and self.standard - - def include_in(self, dist): - """Ensure feature and its requirements are included in distribution - - You may override this in a subclass to perform additional operations on - the distribution. Note that this method may be called more than once - per feature, and so should be idempotent. - - """ - - if not self.available: - raise DistutilsPlatformError( - self.description + " is required, " - "but is not available on this platform" - ) - - dist.include(**self.extras) - - for f in self.require_features: - dist.include_feature(f) - - def exclude_from(self, dist): - """Ensure feature is excluded from distribution - - You may override this in a subclass to perform additional operations on - the distribution. This method will be called at most once per - feature, and only after all included features have been asked to - include themselves. - """ - - dist.exclude(**self.extras) - - if self.remove: - for item in self.remove: - dist.exclude_package(item) - - def validate(self, dist): - """Verify that feature makes sense in context of distribution - - This method is called by the distribution just before it parses its - command line. It checks to ensure that the 'remove' attribute, if any, - contains only valid package/module names that are present in the base - distribution when 'setup()' is called. You may override it in a - subclass to perform any other required validation of the feature - against a target distribution. - """ - - for item in self.remove: - if not dist.has_contents_for(item): - raise DistutilsSetupError( - "%s wants to be able to remove %s, but the distribution" - " doesn't contain any packages or modules under %s" - % (self.description, item, item) - ) - - -class DistDeprecationWarning(SetuptoolsDeprecationWarning): - """Class for warning about deprecations in dist in setuptools. Not ignored by default, unlike DeprecationWarning.""" diff --git a/lib/setuptools/extension.py b/lib/setuptools/extension.py deleted file mode 100644 index 2946889..0000000 --- a/lib/setuptools/extension.py +++ /dev/null @@ -1,57 +0,0 @@ -import re -import functools -import distutils.core -import distutils.errors -import distutils.extension - -from setuptools.extern.six.moves import map - -from .monkey import get_unpatched - - -def _have_cython(): - """ - Return True if Cython can be imported. - """ - cython_impl = 'Cython.Distutils.build_ext' - try: - # from (cython_impl) import build_ext - __import__(cython_impl, fromlist=['build_ext']).build_ext - return True - except Exception: - pass - return False - - -# for compatibility -have_pyrex = _have_cython - -_Extension = get_unpatched(distutils.core.Extension) - - -class Extension(_Extension): - """Extension that uses '.c' files in place of '.pyx' files""" - - def __init__(self, name, sources, *args, **kw): - # The *args is needed for compatibility as calls may use positional - # arguments. py_limited_api may be set only via keyword. - self.py_limited_api = kw.pop("py_limited_api", False) - _Extension.__init__(self, name, sources, *args, **kw) - - def _convert_pyx_sources_to_lang(self): - """ - Replace sources with .pyx extensions to sources with the target - language extension. This mechanism allows language authors to supply - pre-converted sources but to prefer the .pyx sources. - """ - if _have_cython(): - # the build has Cython, so allow it to compile the .pyx files - return - lang = self.language or '' - target_ext = '.cpp' if lang.lower() == 'c++' else '.c' - sub = functools.partial(re.sub, '.pyx$', target_ext) - self.sources = list(map(sub, self.sources)) - - -class Library(Extension): - """Just like a regular Extension, but built as a library instead""" diff --git a/lib/setuptools/extern/__init__.py b/lib/setuptools/extern/__init__.py deleted file mode 100644 index cb2fa32..0000000 --- a/lib/setuptools/extern/__init__.py +++ /dev/null @@ -1,73 +0,0 @@ -import sys - - -class VendorImporter: - """ - A PEP 302 meta path importer for finding optionally-vendored - or otherwise naturally-installed packages from root_name. - """ - - def __init__(self, root_name, vendored_names=(), vendor_pkg=None): - self.root_name = root_name - self.vendored_names = set(vendored_names) - self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor') - - @property - def search_path(self): - """ - Search first the vendor package then as a natural package. - """ - yield self.vendor_pkg + '.' - yield '' - - def find_module(self, fullname, path=None): - """ - Return self when fullname starts with root_name and the - target module is one vendored through this importer. - """ - root, base, target = fullname.partition(self.root_name + '.') - if root: - return - if not any(map(target.startswith, self.vendored_names)): - return - return self - - def load_module(self, fullname): - """ - Iterate over the search path to locate and load fullname. - """ - root, base, target = fullname.partition(self.root_name + '.') - for prefix in self.search_path: - try: - extant = prefix + target - __import__(extant) - mod = sys.modules[extant] - sys.modules[fullname] = mod - # mysterious hack: - # Remove the reference to the extant package/module - # on later Python versions to cause relative imports - # in the vendor package to resolve the same modules - # as those going through this importer. - if sys.version_info >= (3, ): - del sys.modules[extant] - return mod - except ImportError: - pass - else: - raise ImportError( - "The '{target}' package is required; " - "normally this is bundled with this package so if you get " - "this warning, consult the packager of your " - "distribution.".format(**locals()) - ) - - def install(self): - """ - Install this importer into sys.meta_path if not already present. - """ - if self not in sys.meta_path: - sys.meta_path.append(self) - - -names = 'six', 'packaging', 'pyparsing', -VendorImporter(__name__, names, 'setuptools._vendor').install() diff --git a/lib/setuptools/glibc.py b/lib/setuptools/glibc.py deleted file mode 100644 index a134591..0000000 --- a/lib/setuptools/glibc.py +++ /dev/null @@ -1,86 +0,0 @@ -# This file originally from pip: -# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/utils/glibc.py -from __future__ import absolute_import - -import ctypes -import re -import warnings - - -def glibc_version_string(): - "Returns glibc version string, or None if not using glibc." - - # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen - # manpage says, "If filename is NULL, then the returned handle is for the - # main program". This way we can let the linker do the work to figure out - # which libc our process is actually using. - process_namespace = ctypes.CDLL(None) - try: - gnu_get_libc_version = process_namespace.gnu_get_libc_version - except AttributeError: - # Symbol doesn't exist -> therefore, we are not linked to - # glibc. - return None - - # Call gnu_get_libc_version, which returns a string like "2.5" - gnu_get_libc_version.restype = ctypes.c_char_p - version_str = gnu_get_libc_version() - # py2 / py3 compatibility: - if not isinstance(version_str, str): - version_str = version_str.decode("ascii") - - return version_str - - -# Separated out from have_compatible_glibc for easier unit testing -def check_glibc_version(version_str, required_major, minimum_minor): - # Parse string and check against requested version. - # - # We use a regexp instead of str.split because we want to discard any - # random junk that might come after the minor version -- this might happen - # in patched/forked versions of glibc (e.g. Linaro's version of glibc - # uses version strings like "2.20-2014.11"). See gh-3588. - m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str) - if not m: - warnings.warn("Expected glibc version with 2 components major.minor," - " got: %s" % version_str, RuntimeWarning) - return False - return (int(m.group("major")) == required_major and - int(m.group("minor")) >= minimum_minor) - - -def have_compatible_glibc(required_major, minimum_minor): - version_str = glibc_version_string() - if version_str is None: - return False - return check_glibc_version(version_str, required_major, minimum_minor) - - -# platform.libc_ver regularly returns completely nonsensical glibc -# versions. E.g. on my computer, platform says: -# -# ~$ python2.7 -c 'import platform; print(platform.libc_ver())' -# ('glibc', '2.7') -# ~$ python3.5 -c 'import platform; print(platform.libc_ver())' -# ('glibc', '2.9') -# -# But the truth is: -# -# ~$ ldd --version -# ldd (Debian GLIBC 2.22-11) 2.22 -# -# This is unfortunate, because it means that the linehaul data on libc -# versions that was generated by pip 8.1.2 and earlier is useless and -# misleading. Solution: instead of using platform, use our code that actually -# works. -def libc_ver(): - """Try to determine the glibc version - - Returns a tuple of strings (lib, version) which default to empty strings - in case the lookup fails. - """ - glibc_version = glibc_version_string() - if glibc_version is None: - return ("", "") - else: - return ("glibc", glibc_version) diff --git a/lib/setuptools/glob.py b/lib/setuptools/glob.py deleted file mode 100644 index 9d7cbc5..0000000 --- a/lib/setuptools/glob.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -Filename globbing utility. Mostly a copy of `glob` from Python 3.5. - -Changes include: - * `yield from` and PEP3102 `*` removed. - * Hidden files are not ignored. -""" - -import os -import re -import fnmatch - -__all__ = ["glob", "iglob", "escape"] - - -def glob(pathname, recursive=False): - """Return a list of paths matching a pathname pattern. - - The pattern may contain simple shell-style wildcards a la - fnmatch. However, unlike fnmatch, filenames starting with a - dot are special cases that are not matched by '*' and '?' - patterns. - - If recursive is true, the pattern '**' will match any files and - zero or more directories and subdirectories. - """ - return list(iglob(pathname, recursive=recursive)) - - -def iglob(pathname, recursive=False): - """Return an iterator which yields the paths matching a pathname pattern. - - The pattern may contain simple shell-style wildcards a la - fnmatch. However, unlike fnmatch, filenames starting with a - dot are special cases that are not matched by '*' and '?' - patterns. - - If recursive is true, the pattern '**' will match any files and - zero or more directories and subdirectories. - """ - it = _iglob(pathname, recursive) - if recursive and _isrecursive(pathname): - s = next(it) # skip empty string - assert not s - return it - - -def _iglob(pathname, recursive): - dirname, basename = os.path.split(pathname) - if not has_magic(pathname): - if basename: - if os.path.lexists(pathname): - yield pathname - else: - # Patterns ending with a slash should match only directories - if os.path.isdir(dirname): - yield pathname - return - if not dirname: - if recursive and _isrecursive(basename): - for x in glob2(dirname, basename): - yield x - else: - for x in glob1(dirname, basename): - yield x - return - # `os.path.split()` returns the argument itself as a dirname if it is a - # drive or UNC path. Prevent an infinite recursion if a drive or UNC path - # contains magic characters (i.e. r'\\?\C:'). - if dirname != pathname and has_magic(dirname): - dirs = _iglob(dirname, recursive) - else: - dirs = [dirname] - if has_magic(basename): - if recursive and _isrecursive(basename): - glob_in_dir = glob2 - else: - glob_in_dir = glob1 - else: - glob_in_dir = glob0 - for dirname in dirs: - for name in glob_in_dir(dirname, basename): - yield os.path.join(dirname, name) - - -# These 2 helper functions non-recursively glob inside a literal directory. -# They return a list of basenames. `glob1` accepts a pattern while `glob0` -# takes a literal basename (so it only has to check for its existence). - - -def glob1(dirname, pattern): - if not dirname: - if isinstance(pattern, bytes): - dirname = os.curdir.encode('ASCII') - else: - dirname = os.curdir - try: - names = os.listdir(dirname) - except OSError: - return [] - return fnmatch.filter(names, pattern) - - -def glob0(dirname, basename): - if not basename: - # `os.path.split()` returns an empty basename for paths ending with a - # directory separator. 'q*x/' should match only directories. - if os.path.isdir(dirname): - return [basename] - else: - if os.path.lexists(os.path.join(dirname, basename)): - return [basename] - return [] - - -# This helper function recursively yields relative pathnames inside a literal -# directory. - - -def glob2(dirname, pattern): - assert _isrecursive(pattern) - yield pattern[:0] - for x in _rlistdir(dirname): - yield x - - -# Recursively yields relative pathnames inside a literal directory. -def _rlistdir(dirname): - if not dirname: - if isinstance(dirname, bytes): - dirname = os.curdir.encode('ASCII') - else: - dirname = os.curdir - try: - names = os.listdir(dirname) - except os.error: - return - for x in names: - yield x - path = os.path.join(dirname, x) if dirname else x - for y in _rlistdir(path): - yield os.path.join(x, y) - - -magic_check = re.compile('([*?[])') -magic_check_bytes = re.compile(b'([*?[])') - - -def has_magic(s): - if isinstance(s, bytes): - match = magic_check_bytes.search(s) - else: - match = magic_check.search(s) - return match is not None - - -def _isrecursive(pattern): - if isinstance(pattern, bytes): - return pattern == b'**' - else: - return pattern == '**' - - -def escape(pathname): - """Escape all special characters. - """ - # Escaping is done by wrapping any of "*?[" between square brackets. - # Metacharacters do not work in the drive part and shouldn't be escaped. - drive, pathname = os.path.splitdrive(pathname) - if isinstance(pathname, bytes): - pathname = magic_check_bytes.sub(br'[\1]', pathname) - else: - pathname = magic_check.sub(r'[\1]', pathname) - return drive + pathname diff --git a/lib/setuptools/gui-32.exe b/lib/setuptools/gui-32.exe deleted file mode 100644 index f8d3509653ba8f80ca7f3aa7f95616142ba83a94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65536 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 diff --git a/lib/setuptools/gui-64.exe b/lib/setuptools/gui-64.exe deleted file mode 100644 index 330c51a5dde15a0bb610a48cd0ca11770c914dae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75264 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 diff --git a/lib/setuptools/gui.exe b/lib/setuptools/gui.exe deleted file mode 100644 index f8d3509653ba8f80ca7f3aa7f95616142ba83a94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65536 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 diff --git a/lib/setuptools/launch.py b/lib/setuptools/launch.py deleted file mode 100644 index 308283e..0000000 --- a/lib/setuptools/launch.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Launch the Python script on the command line after -setuptools is bootstrapped via import. -""" - -# Note that setuptools gets imported implicitly by the -# invocation of this script using python -m setuptools.launch - -import tokenize -import sys - - -def run(): - """ - Run the script in sys.argv[1] as if it had - been invoked naturally. - """ - __builtins__ - script_name = sys.argv[1] - namespace = dict( - __file__=script_name, - __name__='__main__', - __doc__=None, - ) - sys.argv[:] = sys.argv[1:] - - open_ = getattr(tokenize, 'open', open) - script = open_(script_name).read() - norm_script = script.replace('\\r\\n', '\\n') - code = compile(norm_script, script_name, 'exec') - exec(code, namespace) - - -if __name__ == '__main__': - run() diff --git a/lib/setuptools/lib2to3_ex.py b/lib/setuptools/lib2to3_ex.py deleted file mode 100644 index 4b1a73f..0000000 --- a/lib/setuptools/lib2to3_ex.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -Customized Mixin2to3 support: - - - adds support for converting doctests - - -This module raises an ImportError on Python 2. -""" - -from distutils.util import Mixin2to3 as _Mixin2to3 -from distutils import log -from lib2to3.refactor import RefactoringTool, get_fixers_from_package - -import setuptools - - -class DistutilsRefactoringTool(RefactoringTool): - def log_error(self, msg, *args, **kw): - log.error(msg, *args) - - def log_message(self, msg, *args): - log.info(msg, *args) - - def log_debug(self, msg, *args): - log.debug(msg, *args) - - -class Mixin2to3(_Mixin2to3): - def run_2to3(self, files, doctests=False): - # See of the distribution option has been set, otherwise check the - # setuptools default. - if self.distribution.use_2to3 is not True: - return - if not files: - return - log.info("Fixing " + " ".join(files)) - self.__build_fixer_names() - self.__exclude_fixers() - if doctests: - if setuptools.run_2to3_on_doctests: - r = DistutilsRefactoringTool(self.fixer_names) - r.refactor(files, write=True, doctests_only=True) - else: - _Mixin2to3.run_2to3(self, files) - - def __build_fixer_names(self): - if self.fixer_names: - return - self.fixer_names = [] - for p in setuptools.lib2to3_fixer_packages: - self.fixer_names.extend(get_fixers_from_package(p)) - if self.distribution.use_2to3_fixers is not None: - for p in self.distribution.use_2to3_fixers: - self.fixer_names.extend(get_fixers_from_package(p)) - - def __exclude_fixers(self): - excluded_fixers = getattr(self, 'exclude_fixers', []) - if self.distribution.use_2to3_exclude_fixers is not None: - excluded_fixers.extend(self.distribution.use_2to3_exclude_fixers) - for fixer_name in excluded_fixers: - if fixer_name in self.fixer_names: - self.fixer_names.remove(fixer_name) diff --git a/lib/setuptools/monkey.py b/lib/setuptools/monkey.py deleted file mode 100644 index 3c77f8c..0000000 --- a/lib/setuptools/monkey.py +++ /dev/null @@ -1,179 +0,0 @@ -""" -Monkey patching of distutils. -""" - -import sys -import distutils.filelist -import platform -import types -import functools -from importlib import import_module -import inspect - -from setuptools.extern import six - -import setuptools - -__all__ = [] -""" -Everything is private. Contact the project team -if you think you need this functionality. -""" - - -def _get_mro(cls): - """ - Returns the bases classes for cls sorted by the MRO. - - Works around an issue on Jython where inspect.getmro will not return all - base classes if multiple classes share the same name. Instead, this - function will return a tuple containing the class itself, and the contents - of cls.__bases__. See https://github.com/pypa/setuptools/issues/1024. - """ - if platform.python_implementation() == "Jython": - return (cls,) + cls.__bases__ - return inspect.getmro(cls) - - -def get_unpatched(item): - lookup = ( - get_unpatched_class if isinstance(item, six.class_types) else - get_unpatched_function if isinstance(item, types.FunctionType) else - lambda item: None - ) - return lookup(item) - - -def get_unpatched_class(cls): - """Protect against re-patching the distutils if reloaded - - Also ensures that no other distutils extension monkeypatched the distutils - first. - """ - external_bases = ( - cls - for cls in _get_mro(cls) - if not cls.__module__.startswith('setuptools') - ) - base = next(external_bases) - if not base.__module__.startswith('distutils'): - msg = "distutils has already been patched by %r" % cls - raise AssertionError(msg) - return base - - -def patch_all(): - # we can't patch distutils.cmd, alas - distutils.core.Command = setuptools.Command - - has_issue_12885 = sys.version_info <= (3, 5, 3) - - if has_issue_12885: - # fix findall bug in distutils (http://bugs.python.org/issue12885) - distutils.filelist.findall = setuptools.findall - - needs_warehouse = ( - sys.version_info < (2, 7, 13) - or - (3, 4) < sys.version_info < (3, 4, 6) - or - (3, 5) < sys.version_info <= (3, 5, 3) - ) - - if needs_warehouse: - warehouse = 'https://upload.pypi.org/legacy/' - distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse - - _patch_distribution_metadata() - - # Install Distribution throughout the distutils - for module in distutils.dist, distutils.core, distutils.cmd: - module.Distribution = setuptools.dist.Distribution - - # Install the patched Extension - distutils.core.Extension = setuptools.extension.Extension - distutils.extension.Extension = setuptools.extension.Extension - if 'distutils.command.build_ext' in sys.modules: - sys.modules['distutils.command.build_ext'].Extension = ( - setuptools.extension.Extension - ) - - patch_for_msvc_specialized_compiler() - - -def _patch_distribution_metadata(): - """Patch write_pkg_file and read_pkg_file for higher metadata standards""" - for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'): - new_val = getattr(setuptools.dist, attr) - setattr(distutils.dist.DistributionMetadata, attr, new_val) - - -def patch_func(replacement, target_mod, func_name): - """ - Patch func_name in target_mod with replacement - - Important - original must be resolved by name to avoid - patching an already patched function. - """ - original = getattr(target_mod, func_name) - - # set the 'unpatched' attribute on the replacement to - # point to the original. - vars(replacement).setdefault('unpatched', original) - - # replace the function in the original module - setattr(target_mod, func_name, replacement) - - -def get_unpatched_function(candidate): - return getattr(candidate, 'unpatched') - - -def patch_for_msvc_specialized_compiler(): - """ - Patch functions in distutils to use standalone Microsoft Visual C++ - compilers. - """ - # import late to avoid circular imports on Python < 3.5 - msvc = import_module('setuptools.msvc') - - if platform.system() != 'Windows': - # Compilers only availables on Microsoft Windows - return - - def patch_params(mod_name, func_name): - """ - Prepare the parameters for patch_func to patch indicated function. - """ - repl_prefix = 'msvc9_' if 'msvc9' in mod_name else 'msvc14_' - repl_name = repl_prefix + func_name.lstrip('_') - repl = getattr(msvc, repl_name) - mod = import_module(mod_name) - if not hasattr(mod, func_name): - raise ImportError(func_name) - return repl, mod, func_name - - # Python 2.7 to 3.4 - msvc9 = functools.partial(patch_params, 'distutils.msvc9compiler') - - # Python 3.5+ - msvc14 = functools.partial(patch_params, 'distutils._msvccompiler') - - try: - # Patch distutils.msvc9compiler - patch_func(*msvc9('find_vcvarsall')) - patch_func(*msvc9('query_vcvarsall')) - except ImportError: - pass - - try: - # Patch distutils._msvccompiler._get_vc_env - patch_func(*msvc14('_get_vc_env')) - except ImportError: - pass - - try: - # Patch distutils._msvccompiler.gen_lib_options for Numpy - patch_func(*msvc14('gen_lib_options')) - except ImportError: - pass diff --git a/lib/setuptools/msvc.py b/lib/setuptools/msvc.py deleted file mode 100644 index b9c472f..0000000 --- a/lib/setuptools/msvc.py +++ /dev/null @@ -1,1301 +0,0 @@ -""" -Improved support for Microsoft Visual C++ compilers. - -Known supported compilers: --------------------------- -Microsoft Visual C++ 9.0: - Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) - Microsoft Windows SDK 6.1 (x86, x64, ia64) - Microsoft Windows SDK 7.0 (x86, x64, ia64) - -Microsoft Visual C++ 10.0: - Microsoft Windows SDK 7.1 (x86, x64, ia64) - -Microsoft Visual C++ 14.0: - Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) - Microsoft Visual Studio 2017 (x86, x64, arm, arm64) - Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) -""" - -import os -import sys -import platform -import itertools -import distutils.errors -from setuptools.extern.packaging.version import LegacyVersion - -from setuptools.extern.six.moves import filterfalse - -from .monkey import get_unpatched - -if platform.system() == 'Windows': - from setuptools.extern.six.moves import winreg - safe_env = os.environ -else: - """ - Mock winreg and environ so the module can be imported - on this platform. - """ - - class winreg: - HKEY_USERS = None - HKEY_CURRENT_USER = None - HKEY_LOCAL_MACHINE = None - HKEY_CLASSES_ROOT = None - - safe_env = dict() - -_msvc9_suppress_errors = ( - # msvc9compiler isn't available on some platforms - ImportError, - - # msvc9compiler raises DistutilsPlatformError in some - # environments. See #1118. - distutils.errors.DistutilsPlatformError, -) - -try: - from distutils.msvc9compiler import Reg -except _msvc9_suppress_errors: - pass - - -def msvc9_find_vcvarsall(version): - """ - Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone - compiler build for Python (VCForPython). Fall back to original behavior - when the standalone compiler is not available. - - Redirect the path of "vcvarsall.bat". - - Known supported compilers - ------------------------- - Microsoft Visual C++ 9.0: - Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) - - Parameters - ---------- - version: float - Required Microsoft Visual C++ version. - - Return - ------ - vcvarsall.bat path: str - """ - VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f' - key = VC_BASE % ('', version) - try: - # Per-user installs register the compiler path here - productdir = Reg.get_value(key, "installdir") - except KeyError: - try: - # All-user installs on a 64-bit system register here - key = VC_BASE % ('Wow6432Node\\', version) - productdir = Reg.get_value(key, "installdir") - except KeyError: - productdir = None - - if productdir: - vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat") - if os.path.isfile(vcvarsall): - return vcvarsall - - return get_unpatched(msvc9_find_vcvarsall)(version) - - -def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs): - """ - Patched "distutils.msvc9compiler.query_vcvarsall" for support extra - compilers. - - Set environment without use of "vcvarsall.bat". - - Known supported compilers - ------------------------- - Microsoft Visual C++ 9.0: - Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) - Microsoft Windows SDK 6.1 (x86, x64, ia64) - Microsoft Windows SDK 7.0 (x86, x64, ia64) - - Microsoft Visual C++ 10.0: - Microsoft Windows SDK 7.1 (x86, x64, ia64) - - Parameters - ---------- - ver: float - Required Microsoft Visual C++ version. - arch: str - Target architecture. - - Return - ------ - environment: dict - """ - # Try to get environement from vcvarsall.bat (Classical way) - try: - orig = get_unpatched(msvc9_query_vcvarsall) - return orig(ver, arch, *args, **kwargs) - except distutils.errors.DistutilsPlatformError: - # Pass error if Vcvarsall.bat is missing - pass - except ValueError: - # Pass error if environment not set after executing vcvarsall.bat - pass - - # If error, try to set environment directly - try: - return EnvironmentInfo(arch, ver).return_env() - except distutils.errors.DistutilsPlatformError as exc: - _augment_exception(exc, ver, arch) - raise - - -def msvc14_get_vc_env(plat_spec): - """ - Patched "distutils._msvccompiler._get_vc_env" for support extra - compilers. - - Set environment without use of "vcvarsall.bat". - - Known supported compilers - ------------------------- - Microsoft Visual C++ 14.0: - Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) - Microsoft Visual Studio 2017 (x86, x64, arm, arm64) - Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) - - Parameters - ---------- - plat_spec: str - Target architecture. - - Return - ------ - environment: dict - """ - # Try to get environment from vcvarsall.bat (Classical way) - try: - return get_unpatched(msvc14_get_vc_env)(plat_spec) - except distutils.errors.DistutilsPlatformError: - # Pass error Vcvarsall.bat is missing - pass - - # If error, try to set environment directly - try: - return EnvironmentInfo(plat_spec, vc_min_ver=14.0).return_env() - except distutils.errors.DistutilsPlatformError as exc: - _augment_exception(exc, 14.0) - raise - - -def msvc14_gen_lib_options(*args, **kwargs): - """ - Patched "distutils._msvccompiler.gen_lib_options" for fix - compatibility between "numpy.distutils" and "distutils._msvccompiler" - (for Numpy < 1.11.2) - """ - if "numpy.distutils" in sys.modules: - import numpy as np - if LegacyVersion(np.__version__) < LegacyVersion('1.11.2'): - return np.distutils.ccompiler.gen_lib_options(*args, **kwargs) - return get_unpatched(msvc14_gen_lib_options)(*args, **kwargs) - - -def _augment_exception(exc, version, arch=''): - """ - Add details to the exception message to help guide the user - as to what action will resolve it. - """ - # Error if MSVC++ directory not found or environment not set - message = exc.args[0] - - if "vcvarsall" in message.lower() or "visual c" in message.lower(): - # Special error message if MSVC++ not installed - tmpl = 'Microsoft Visual C++ {version:0.1f} is required.' - message = tmpl.format(**locals()) - msdownload = 'www.microsoft.com/download/details.aspx?id=%d' - if version == 9.0: - if arch.lower().find('ia64') > -1: - # For VC++ 9.0, if IA64 support is needed, redirect user - # to Windows SDK 7.0 - message += ' Get it with "Microsoft Windows SDK 7.0": ' - message += msdownload % 3138 - else: - # For VC++ 9.0 redirect user to Vc++ for Python 2.7 : - # This redirection link is maintained by Microsoft. - # Contact vspython@microsoft.com if it needs updating. - message += ' Get it from http://aka.ms/vcpython27' - elif version == 10.0: - # For VC++ 10.0 Redirect user to Windows SDK 7.1 - message += ' Get it with "Microsoft Windows SDK 7.1": ' - message += msdownload % 8279 - elif version >= 14.0: - # For VC++ 14.0 Redirect user to Visual C++ Build Tools - message += (' Get it with "Microsoft Visual C++ Build Tools": ' - r'https://visualstudio.microsoft.com/downloads/') - - exc.args = (message, ) - - -class PlatformInfo: - """ - Current and Target Architectures informations. - - Parameters - ---------- - arch: str - Target architecture. - """ - current_cpu = safe_env.get('processor_architecture', '').lower() - - def __init__(self, arch): - self.arch = arch.lower().replace('x64', 'amd64') - - @property - def target_cpu(self): - return self.arch[self.arch.find('_') + 1:] - - def target_is_x86(self): - return self.target_cpu == 'x86' - - def current_is_x86(self): - return self.current_cpu == 'x86' - - def current_dir(self, hidex86=False, x64=False): - """ - Current platform specific subfolder. - - Parameters - ---------- - hidex86: bool - return '' and not '\x86' if architecture is x86. - x64: bool - return '\x64' and not '\amd64' if architecture is amd64. - - Return - ------ - subfolder: str - '\target', or '' (see hidex86 parameter) - """ - return ( - '' if (self.current_cpu == 'x86' and hidex86) else - r'\x64' if (self.current_cpu == 'amd64' and x64) else - r'\%s' % self.current_cpu - ) - - def target_dir(self, hidex86=False, x64=False): - r""" - Target platform specific subfolder. - - Parameters - ---------- - hidex86: bool - return '' and not '\x86' if architecture is x86. - x64: bool - return '\x64' and not '\amd64' if architecture is amd64. - - Return - ------ - subfolder: str - '\current', or '' (see hidex86 parameter) - """ - return ( - '' if (self.target_cpu == 'x86' and hidex86) else - r'\x64' if (self.target_cpu == 'amd64' and x64) else - r'\%s' % self.target_cpu - ) - - def cross_dir(self, forcex86=False): - r""" - Cross platform specific subfolder. - - Parameters - ---------- - forcex86: bool - Use 'x86' as current architecture even if current acritecture is - not x86. - - Return - ------ - subfolder: str - '' if target architecture is current architecture, - '\current_target' if not. - """ - current = 'x86' if forcex86 else self.current_cpu - return ( - '' if self.target_cpu == current else - self.target_dir().replace('\\', '\\%s_' % current) - ) - - -class RegistryInfo: - """ - Microsoft Visual Studio related registry informations. - - Parameters - ---------- - platform_info: PlatformInfo - "PlatformInfo" instance. - """ - HKEYS = (winreg.HKEY_USERS, - winreg.HKEY_CURRENT_USER, - winreg.HKEY_LOCAL_MACHINE, - winreg.HKEY_CLASSES_ROOT) - - def __init__(self, platform_info): - self.pi = platform_info - - @property - def visualstudio(self): - """ - Microsoft Visual Studio root registry key. - """ - return 'VisualStudio' - - @property - def sxs(self): - """ - Microsoft Visual Studio SxS registry key. - """ - return os.path.join(self.visualstudio, 'SxS') - - @property - def vc(self): - """ - Microsoft Visual C++ VC7 registry key. - """ - return os.path.join(self.sxs, 'VC7') - - @property - def vs(self): - """ - Microsoft Visual Studio VS7 registry key. - """ - return os.path.join(self.sxs, 'VS7') - - @property - def vc_for_python(self): - """ - Microsoft Visual C++ for Python registry key. - """ - return r'DevDiv\VCForPython' - - @property - def microsoft_sdk(self): - """ - Microsoft SDK registry key. - """ - return 'Microsoft SDKs' - - @property - def windows_sdk(self): - """ - Microsoft Windows/Platform SDK registry key. - """ - return os.path.join(self.microsoft_sdk, 'Windows') - - @property - def netfx_sdk(self): - """ - Microsoft .NET Framework SDK registry key. - """ - return os.path.join(self.microsoft_sdk, 'NETFXSDK') - - @property - def windows_kits_roots(self): - """ - Microsoft Windows Kits Roots registry key. - """ - return r'Windows Kits\Installed Roots' - - def microsoft(self, key, x86=False): - """ - Return key in Microsoft software registry. - - Parameters - ---------- - key: str - Registry key path where look. - x86: str - Force x86 software registry. - - Return - ------ - str: value - """ - node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node' - return os.path.join('Software', node64, 'Microsoft', key) - - def lookup(self, key, name): - """ - Look for values in registry in Microsoft software registry. - - Parameters - ---------- - key: str - Registry key path where look. - name: str - Value name to find. - - Return - ------ - str: value - """ - KEY_READ = winreg.KEY_READ - openkey = winreg.OpenKey - ms = self.microsoft - for hkey in self.HKEYS: - try: - bkey = openkey(hkey, ms(key), 0, KEY_READ) - except (OSError, IOError): - if not self.pi.current_is_x86(): - try: - bkey = openkey(hkey, ms(key, True), 0, KEY_READ) - except (OSError, IOError): - continue - else: - continue - try: - return winreg.QueryValueEx(bkey, name)[0] - except (OSError, IOError): - pass - - -class SystemInfo: - """ - Microsoft Windows and Visual Studio related system inormations. - - Parameters - ---------- - registry_info: RegistryInfo - "RegistryInfo" instance. - vc_ver: float - Required Microsoft Visual C++ version. - """ - - # Variables and properties in this class use originals CamelCase variables - # names from Microsoft source files for more easy comparaison. - WinDir = safe_env.get('WinDir', '') - ProgramFiles = safe_env.get('ProgramFiles', '') - ProgramFilesx86 = safe_env.get('ProgramFiles(x86)', ProgramFiles) - - def __init__(self, registry_info, vc_ver=None): - self.ri = registry_info - self.pi = self.ri.pi - self.vc_ver = vc_ver or self._find_latest_available_vc_ver() - - def _find_latest_available_vc_ver(self): - try: - return self.find_available_vc_vers()[-1] - except IndexError: - err = 'No Microsoft Visual C++ version found' - raise distutils.errors.DistutilsPlatformError(err) - - def find_available_vc_vers(self): - """ - Find all available Microsoft Visual C++ versions. - """ - ms = self.ri.microsoft - vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs) - vc_vers = [] - for hkey in self.ri.HKEYS: - for key in vckeys: - try: - bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ) - except (OSError, IOError): - continue - subkeys, values, _ = winreg.QueryInfoKey(bkey) - for i in range(values): - try: - ver = float(winreg.EnumValue(bkey, i)[0]) - if ver not in vc_vers: - vc_vers.append(ver) - except ValueError: - pass - for i in range(subkeys): - try: - ver = float(winreg.EnumKey(bkey, i)) - if ver not in vc_vers: - vc_vers.append(ver) - except ValueError: - pass - return sorted(vc_vers) - - @property - def VSInstallDir(self): - """ - Microsoft Visual Studio directory. - """ - # Default path - name = 'Microsoft Visual Studio %0.1f' % self.vc_ver - default = os.path.join(self.ProgramFilesx86, name) - - # Try to get path from registry, if fail use default path - return self.ri.lookup(self.ri.vs, '%0.1f' % self.vc_ver) or default - - @property - def VCInstallDir(self): - """ - Microsoft Visual C++ directory. - """ - self.VSInstallDir - - guess_vc = self._guess_vc() or self._guess_vc_legacy() - - # Try to get "VC++ for Python" path from registry as default path - reg_path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) - python_vc = self.ri.lookup(reg_path, 'installdir') - default_vc = os.path.join(python_vc, 'VC') if python_vc else guess_vc - - # Try to get path from registry, if fail use default path - path = self.ri.lookup(self.ri.vc, '%0.1f' % self.vc_ver) or default_vc - - if not os.path.isdir(path): - msg = 'Microsoft Visual C++ directory not found' - raise distutils.errors.DistutilsPlatformError(msg) - - return path - - def _guess_vc(self): - """ - Locate Visual C for 2017 - """ - if self.vc_ver <= 14.0: - return - - default = r'VC\Tools\MSVC' - guess_vc = os.path.join(self.VSInstallDir, default) - # Subdir with VC exact version as name - try: - vc_exact_ver = os.listdir(guess_vc)[-1] - return os.path.join(guess_vc, vc_exact_ver) - except (OSError, IOError, IndexError): - pass - - def _guess_vc_legacy(self): - """ - Locate Visual C for versions prior to 2017 - """ - default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver - return os.path.join(self.ProgramFilesx86, default) - - @property - def WindowsSdkVersion(self): - """ - Microsoft Windows SDK versions for specified MSVC++ version. - """ - if self.vc_ver <= 9.0: - return ('7.0', '6.1', '6.0a') - elif self.vc_ver == 10.0: - return ('7.1', '7.0a') - elif self.vc_ver == 11.0: - return ('8.0', '8.0a') - elif self.vc_ver == 12.0: - return ('8.1', '8.1a') - elif self.vc_ver >= 14.0: - return ('10.0', '8.1') - - @property - def WindowsSdkLastVersion(self): - """ - Microsoft Windows SDK last version - """ - return self._use_last_dir_name(os.path.join( - self.WindowsSdkDir, 'lib')) - - @property - def WindowsSdkDir(self): - """ - Microsoft Windows SDK directory. - """ - sdkdir = '' - for ver in self.WindowsSdkVersion: - # Try to get it from registry - loc = os.path.join(self.ri.windows_sdk, 'v%s' % ver) - sdkdir = self.ri.lookup(loc, 'installationfolder') - if sdkdir: - break - if not sdkdir or not os.path.isdir(sdkdir): - # Try to get "VC++ for Python" version from registry - path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) - install_base = self.ri.lookup(path, 'installdir') - if install_base: - sdkdir = os.path.join(install_base, 'WinSDK') - if not sdkdir or not os.path.isdir(sdkdir): - # If fail, use default new path - for ver in self.WindowsSdkVersion: - intver = ver[:ver.rfind('.')] - path = r'Microsoft SDKs\Windows Kits\%s' % (intver) - d = os.path.join(self.ProgramFiles, path) - if os.path.isdir(d): - sdkdir = d - if not sdkdir or not os.path.isdir(sdkdir): - # If fail, use default old path - for ver in self.WindowsSdkVersion: - path = r'Microsoft SDKs\Windows\v%s' % ver - d = os.path.join(self.ProgramFiles, path) - if os.path.isdir(d): - sdkdir = d - if not sdkdir: - # If fail, use Platform SDK - sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK') - return sdkdir - - @property - def WindowsSDKExecutablePath(self): - """ - Microsoft Windows SDK executable directory. - """ - # Find WinSDK NetFx Tools registry dir name - if self.vc_ver <= 11.0: - netfxver = 35 - arch = '' - else: - netfxver = 40 - hidex86 = True if self.vc_ver <= 12.0 else False - arch = self.pi.current_dir(x64=True, hidex86=hidex86) - fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-')) - - # liste all possibles registry paths - regpaths = [] - if self.vc_ver >= 14.0: - for ver in self.NetFxSdkVersion: - regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)] - - for ver in self.WindowsSdkVersion: - regpaths += [os.path.join(self.ri.windows_sdk, 'v%sA' % ver, fx)] - - # Return installation folder from the more recent path - for path in regpaths: - execpath = self.ri.lookup(path, 'installationfolder') - if execpath: - break - return execpath - - @property - def FSharpInstallDir(self): - """ - Microsoft Visual F# directory. - """ - path = r'%0.1f\Setup\F#' % self.vc_ver - path = os.path.join(self.ri.visualstudio, path) - return self.ri.lookup(path, 'productdir') or '' - - @property - def UniversalCRTSdkDir(self): - """ - Microsoft Universal CRT SDK directory. - """ - # Set Kit Roots versions for specified MSVC++ version - if self.vc_ver >= 14.0: - vers = ('10', '81') - else: - vers = () - - # Find path of the more recent Kit - for ver in vers: - sdkdir = self.ri.lookup(self.ri.windows_kits_roots, - 'kitsroot%s' % ver) - if sdkdir: - break - return sdkdir or '' - - @property - def UniversalCRTSdkLastVersion(self): - """ - Microsoft Universal C Runtime SDK last version - """ - return self._use_last_dir_name(os.path.join( - self.UniversalCRTSdkDir, 'lib')) - - @property - def NetFxSdkVersion(self): - """ - Microsoft .NET Framework SDK versions. - """ - # Set FxSdk versions for specified MSVC++ version - if self.vc_ver >= 14.0: - return ('4.6.1', '4.6') - else: - return () - - @property - def NetFxSdkDir(self): - """ - Microsoft .NET Framework SDK directory. - """ - for ver in self.NetFxSdkVersion: - loc = os.path.join(self.ri.netfx_sdk, ver) - sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder') - if sdkdir: - break - return sdkdir or '' - - @property - def FrameworkDir32(self): - """ - Microsoft .NET Framework 32bit directory. - """ - # Default path - guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework') - - # Try to get path from registry, if fail use default path - return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw - - @property - def FrameworkDir64(self): - """ - Microsoft .NET Framework 64bit directory. - """ - # Default path - guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64') - - # Try to get path from registry, if fail use default path - return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw - - @property - def FrameworkVersion32(self): - """ - Microsoft .NET Framework 32bit versions. - """ - return self._find_dot_net_versions(32) - - @property - def FrameworkVersion64(self): - """ - Microsoft .NET Framework 64bit versions. - """ - return self._find_dot_net_versions(64) - - def _find_dot_net_versions(self, bits): - """ - Find Microsoft .NET Framework versions. - - Parameters - ---------- - bits: int - Platform number of bits: 32 or 64. - """ - # Find actual .NET version in registry - reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits) - dot_net_dir = getattr(self, 'FrameworkDir%d' % bits) - ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or '' - - # Set .NET versions for specified MSVC++ version - if self.vc_ver >= 12.0: - frameworkver = (ver, 'v4.0') - elif self.vc_ver >= 10.0: - frameworkver = ('v4.0.30319' if ver.lower()[:2] != 'v4' else ver, - 'v3.5') - elif self.vc_ver == 9.0: - frameworkver = ('v3.5', 'v2.0.50727') - if self.vc_ver == 8.0: - frameworkver = ('v3.0', 'v2.0.50727') - return frameworkver - - def _use_last_dir_name(self, path, prefix=''): - """ - Return name of the last dir in path or '' if no dir found. - - Parameters - ---------- - path: str - Use dirs in this path - prefix: str - Use only dirs startings by this prefix - """ - matching_dirs = ( - dir_name - for dir_name in reversed(os.listdir(path)) - if os.path.isdir(os.path.join(path, dir_name)) and - dir_name.startswith(prefix) - ) - return next(matching_dirs, None) or '' - - -class EnvironmentInfo: - """ - Return environment variables for specified Microsoft Visual C++ version - and platform : Lib, Include, Path and libpath. - - This function is compatible with Microsoft Visual C++ 9.0 to 14.0. - - Script created by analysing Microsoft environment configuration files like - "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ... - - Parameters - ---------- - arch: str - Target architecture. - vc_ver: float - Required Microsoft Visual C++ version. If not set, autodetect the last - version. - vc_min_ver: float - Minimum Microsoft Visual C++ version. - """ - - # Variables and properties in this class use originals CamelCase variables - # names from Microsoft source files for more easy comparaison. - - def __init__(self, arch, vc_ver=None, vc_min_ver=0): - self.pi = PlatformInfo(arch) - self.ri = RegistryInfo(self.pi) - self.si = SystemInfo(self.ri, vc_ver) - - if self.vc_ver < vc_min_ver: - err = 'No suitable Microsoft Visual C++ version found' - raise distutils.errors.DistutilsPlatformError(err) - - @property - def vc_ver(self): - """ - Microsoft Visual C++ version. - """ - return self.si.vc_ver - - @property - def VSTools(self): - """ - Microsoft Visual Studio Tools - """ - paths = [r'Common7\IDE', r'Common7\Tools'] - - if self.vc_ver >= 14.0: - arch_subdir = self.pi.current_dir(hidex86=True, x64=True) - paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow'] - paths += [r'Team Tools\Performance Tools'] - paths += [r'Team Tools\Performance Tools%s' % arch_subdir] - - return [os.path.join(self.si.VSInstallDir, path) for path in paths] - - @property - def VCIncludes(self): - """ - Microsoft Visual C++ & Microsoft Foundation Class Includes - """ - return [os.path.join(self.si.VCInstallDir, 'Include'), - os.path.join(self.si.VCInstallDir, r'ATLMFC\Include')] - - @property - def VCLibraries(self): - """ - Microsoft Visual C++ & Microsoft Foundation Class Libraries - """ - if self.vc_ver >= 15.0: - arch_subdir = self.pi.target_dir(x64=True) - else: - arch_subdir = self.pi.target_dir(hidex86=True) - paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir] - - if self.vc_ver >= 14.0: - paths += [r'Lib\store%s' % arch_subdir] - - return [os.path.join(self.si.VCInstallDir, path) for path in paths] - - @property - def VCStoreRefs(self): - """ - Microsoft Visual C++ store references Libraries - """ - if self.vc_ver < 14.0: - return [] - return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')] - - @property - def VCTools(self): - """ - Microsoft Visual C++ Tools - """ - si = self.si - tools = [os.path.join(si.VCInstallDir, 'VCPackages')] - - forcex86 = True if self.vc_ver <= 10.0 else False - arch_subdir = self.pi.cross_dir(forcex86) - if arch_subdir: - tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)] - - if self.vc_ver == 14.0: - path = 'Bin%s' % self.pi.current_dir(hidex86=True) - tools += [os.path.join(si.VCInstallDir, path)] - - elif self.vc_ver >= 15.0: - host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else - r'bin\HostX64%s') - tools += [os.path.join( - si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))] - - if self.pi.current_cpu != self.pi.target_cpu: - tools += [os.path.join( - si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))] - - else: - tools += [os.path.join(si.VCInstallDir, 'Bin')] - - return tools - - @property - def OSLibraries(self): - """ - Microsoft Windows SDK Libraries - """ - if self.vc_ver <= 10.0: - arch_subdir = self.pi.target_dir(hidex86=True, x64=True) - return [os.path.join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)] - - else: - arch_subdir = self.pi.target_dir(x64=True) - lib = os.path.join(self.si.WindowsSdkDir, 'lib') - libver = self._sdk_subdir - return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))] - - @property - def OSIncludes(self): - """ - Microsoft Windows SDK Include - """ - include = os.path.join(self.si.WindowsSdkDir, 'include') - - if self.vc_ver <= 10.0: - return [include, os.path.join(include, 'gl')] - - else: - if self.vc_ver >= 14.0: - sdkver = self._sdk_subdir - else: - sdkver = '' - return [os.path.join(include, '%sshared' % sdkver), - os.path.join(include, '%sum' % sdkver), - os.path.join(include, '%swinrt' % sdkver)] - - @property - def OSLibpath(self): - """ - Microsoft Windows SDK Libraries Paths - """ - ref = os.path.join(self.si.WindowsSdkDir, 'References') - libpath = [] - - if self.vc_ver <= 9.0: - libpath += self.OSLibraries - - if self.vc_ver >= 11.0: - libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')] - - if self.vc_ver >= 14.0: - libpath += [ - ref, - os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'), - os.path.join( - ref, - 'Windows.Foundation.UniversalApiContract', - '1.0.0.0', - ), - os.path.join( - ref, - 'Windows.Foundation.FoundationContract', - '1.0.0.0', - ), - os.path.join( - ref, - 'Windows.Networking.Connectivity.WwanContract', - '1.0.0.0', - ), - os.path.join( - self.si.WindowsSdkDir, - 'ExtensionSDKs', - 'Microsoft.VCLibs', - '%0.1f' % self.vc_ver, - 'References', - 'CommonConfiguration', - 'neutral', - ), - ] - return libpath - - @property - def SdkTools(self): - """ - Microsoft Windows SDK Tools - """ - return list(self._sdk_tools()) - - def _sdk_tools(self): - """ - Microsoft Windows SDK Tools paths generator - """ - if self.vc_ver < 15.0: - bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86' - yield os.path.join(self.si.WindowsSdkDir, bin_dir) - - if not self.pi.current_is_x86(): - arch_subdir = self.pi.current_dir(x64=True) - path = 'Bin%s' % arch_subdir - yield os.path.join(self.si.WindowsSdkDir, path) - - if self.vc_ver == 10.0 or self.vc_ver == 11.0: - if self.pi.target_is_x86(): - arch_subdir = '' - else: - arch_subdir = self.pi.current_dir(hidex86=True, x64=True) - path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir - yield os.path.join(self.si.WindowsSdkDir, path) - - elif self.vc_ver >= 15.0: - path = os.path.join(self.si.WindowsSdkDir, 'Bin') - arch_subdir = self.pi.current_dir(x64=True) - sdkver = self.si.WindowsSdkLastVersion - yield os.path.join(path, '%s%s' % (sdkver, arch_subdir)) - - if self.si.WindowsSDKExecutablePath: - yield self.si.WindowsSDKExecutablePath - - @property - def _sdk_subdir(self): - """ - Microsoft Windows SDK version subdir - """ - ucrtver = self.si.WindowsSdkLastVersion - return ('%s\\' % ucrtver) if ucrtver else '' - - @property - def SdkSetup(self): - """ - Microsoft Windows SDK Setup - """ - if self.vc_ver > 9.0: - return [] - - return [os.path.join(self.si.WindowsSdkDir, 'Setup')] - - @property - def FxTools(self): - """ - Microsoft .NET Framework Tools - """ - pi = self.pi - si = self.si - - if self.vc_ver <= 10.0: - include32 = True - include64 = not pi.target_is_x86() and not pi.current_is_x86() - else: - include32 = pi.target_is_x86() or pi.current_is_x86() - include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64' - - tools = [] - if include32: - tools += [os.path.join(si.FrameworkDir32, ver) - for ver in si.FrameworkVersion32] - if include64: - tools += [os.path.join(si.FrameworkDir64, ver) - for ver in si.FrameworkVersion64] - return tools - - @property - def NetFxSDKLibraries(self): - """ - Microsoft .Net Framework SDK Libraries - """ - if self.vc_ver < 14.0 or not self.si.NetFxSdkDir: - return [] - - arch_subdir = self.pi.target_dir(x64=True) - return [os.path.join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)] - - @property - def NetFxSDKIncludes(self): - """ - Microsoft .Net Framework SDK Includes - """ - if self.vc_ver < 14.0 or not self.si.NetFxSdkDir: - return [] - - return [os.path.join(self.si.NetFxSdkDir, r'include\um')] - - @property - def VsTDb(self): - """ - Microsoft Visual Studio Team System Database - """ - return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')] - - @property - def MSBuild(self): - """ - Microsoft Build Engine - """ - if self.vc_ver < 12.0: - return [] - elif self.vc_ver < 15.0: - base_path = self.si.ProgramFilesx86 - arch_subdir = self.pi.current_dir(hidex86=True) - else: - base_path = self.si.VSInstallDir - arch_subdir = '' - - path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir) - build = [os.path.join(base_path, path)] - - if self.vc_ver >= 15.0: - # Add Roslyn C# & Visual Basic Compiler - build += [os.path.join(base_path, path, 'Roslyn')] - - return build - - @property - def HTMLHelpWorkshop(self): - """ - Microsoft HTML Help Workshop - """ - if self.vc_ver < 11.0: - return [] - - return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')] - - @property - def UCRTLibraries(self): - """ - Microsoft Universal C Runtime SDK Libraries - """ - if self.vc_ver < 14.0: - return [] - - arch_subdir = self.pi.target_dir(x64=True) - lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib') - ucrtver = self._ucrt_subdir - return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))] - - @property - def UCRTIncludes(self): - """ - Microsoft Universal C Runtime SDK Include - """ - if self.vc_ver < 14.0: - return [] - - include = os.path.join(self.si.UniversalCRTSdkDir, 'include') - return [os.path.join(include, '%sucrt' % self._ucrt_subdir)] - - @property - def _ucrt_subdir(self): - """ - Microsoft Universal C Runtime SDK version subdir - """ - ucrtver = self.si.UniversalCRTSdkLastVersion - return ('%s\\' % ucrtver) if ucrtver else '' - - @property - def FSharp(self): - """ - Microsoft Visual F# - """ - if self.vc_ver < 11.0 and self.vc_ver > 12.0: - return [] - - return self.si.FSharpInstallDir - - @property - def VCRuntimeRedist(self): - """ - Microsoft Visual C++ runtime redistribuable dll - """ - arch_subdir = self.pi.target_dir(x64=True) - if self.vc_ver < 15: - redist_path = self.si.VCInstallDir - vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll' - else: - redist_path = self.si.VCInstallDir.replace('\\Tools', '\\Redist') - vcruntime = 'onecore%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll' - - # Visual Studio 2017 is still Visual C++ 14.0 - dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver - - vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver) - return os.path.join(redist_path, vcruntime) - - def return_env(self, exists=True): - """ - Return environment dict. - - Parameters - ---------- - exists: bool - It True, only return existing paths. - """ - env = dict( - include=self._build_paths('include', - [self.VCIncludes, - self.OSIncludes, - self.UCRTIncludes, - self.NetFxSDKIncludes], - exists), - lib=self._build_paths('lib', - [self.VCLibraries, - self.OSLibraries, - self.FxTools, - self.UCRTLibraries, - self.NetFxSDKLibraries], - exists), - libpath=self._build_paths('libpath', - [self.VCLibraries, - self.FxTools, - self.VCStoreRefs, - self.OSLibpath], - exists), - path=self._build_paths('path', - [self.VCTools, - self.VSTools, - self.VsTDb, - self.SdkTools, - self.SdkSetup, - self.FxTools, - self.MSBuild, - self.HTMLHelpWorkshop, - self.FSharp], - exists), - ) - if self.vc_ver >= 14 and os.path.isfile(self.VCRuntimeRedist): - env['py_vcruntime_redist'] = self.VCRuntimeRedist - return env - - def _build_paths(self, name, spec_path_lists, exists): - """ - Given an environment variable name and specified paths, - return a pathsep-separated string of paths containing - unique, extant, directories from those paths and from - the environment variable. Raise an error if no paths - are resolved. - """ - # flatten spec_path_lists - spec_paths = itertools.chain.from_iterable(spec_path_lists) - env_paths = safe_env.get(name, '').split(os.pathsep) - paths = itertools.chain(spec_paths, env_paths) - extant_paths = list(filter(os.path.isdir, paths)) if exists else paths - if not extant_paths: - msg = "%s environment variable is empty" % name.upper() - raise distutils.errors.DistutilsPlatformError(msg) - unique_paths = self._unique_everseen(extant_paths) - return os.pathsep.join(unique_paths) - - # from Python docs - def _unique_everseen(self, iterable, key=None): - """ - List unique elements, preserving order. - Remember all elements ever seen. - - _unique_everseen('AAAABBBCCDAABBB') --> A B C D - - _unique_everseen('ABBCcAD', str.lower) --> A B C D - """ - seen = set() - seen_add = seen.add - if key is None: - for element in filterfalse(seen.__contains__, iterable): - seen_add(element) - yield element - else: - for element in iterable: - k = key(element) - if k not in seen: - seen_add(k) - yield element diff --git a/lib/setuptools/namespaces.py b/lib/setuptools/namespaces.py deleted file mode 100644 index dc16106..0000000 --- a/lib/setuptools/namespaces.py +++ /dev/null @@ -1,107 +0,0 @@ -import os -from distutils import log -import itertools - -from setuptools.extern.six.moves import map - - -flatten = itertools.chain.from_iterable - - -class Installer: - - nspkg_ext = '-nspkg.pth' - - def install_namespaces(self): - nsp = self._get_all_ns_packages() - if not nsp: - return - filename, ext = os.path.splitext(self._get_target()) - filename += self.nspkg_ext - self.outputs.append(filename) - log.info("Installing %s", filename) - lines = map(self._gen_nspkg_line, nsp) - - if self.dry_run: - # always generate the lines, even in dry run - list(lines) - return - - with open(filename, 'wt') as f: - f.writelines(lines) - - def uninstall_namespaces(self): - filename, ext = os.path.splitext(self._get_target()) - filename += self.nspkg_ext - if not os.path.exists(filename): - return - log.info("Removing %s", filename) - os.remove(filename) - - def _get_target(self): - return self.target - - _nspkg_tmpl = ( - "import sys, types, os", - "has_mfs = sys.version_info > (3, 5)", - "p = os.path.join(%(root)s, *%(pth)r)", - "importlib = has_mfs and __import__('importlib.util')", - "has_mfs and __import__('importlib.machinery')", - "m = has_mfs and " - "sys.modules.setdefault(%(pkg)r, " - "importlib.util.module_from_spec(" - "importlib.machinery.PathFinder.find_spec(%(pkg)r, " - "[os.path.dirname(p)])))", - "m = m or " - "sys.modules.setdefault(%(pkg)r, types.ModuleType(%(pkg)r))", - "mp = (m or []) and m.__dict__.setdefault('__path__',[])", - "(p not in mp) and mp.append(p)", - ) - "lines for the namespace installer" - - _nspkg_tmpl_multi = ( - 'm and setattr(sys.modules[%(parent)r], %(child)r, m)', - ) - "additional line(s) when a parent package is indicated" - - def _get_root(self): - return "sys._getframe(1).f_locals['sitedir']" - - def _gen_nspkg_line(self, pkg): - # ensure pkg is not a unicode string under Python 2.7 - pkg = str(pkg) - pth = tuple(pkg.split('.')) - root = self._get_root() - tmpl_lines = self._nspkg_tmpl - parent, sep, child = pkg.rpartition('.') - if parent: - tmpl_lines += self._nspkg_tmpl_multi - return ';'.join(tmpl_lines) % locals() + '\n' - - def _get_all_ns_packages(self): - """Return sorted list of all package namespaces""" - pkgs = self.distribution.namespace_packages or [] - return sorted(flatten(map(self._pkg_names, pkgs))) - - @staticmethod - def _pkg_names(pkg): - """ - Given a namespace package, yield the components of that - package. - - >>> names = Installer._pkg_names('a.b.c') - >>> set(names) == set(['a', 'a.b', 'a.b.c']) - True - """ - parts = pkg.split('.') - while parts: - yield '.'.join(parts) - parts.pop() - - -class DevelopInstaller(Installer): - def _get_root(self): - return repr(str(self.egg_path)) - - def _get_target(self): - return self.egg_link diff --git a/lib/setuptools/package_index.py b/lib/setuptools/package_index.py deleted file mode 100644 index 1608b91..0000000 --- a/lib/setuptools/package_index.py +++ /dev/null @@ -1,1128 +0,0 @@ -"""PyPI and direct package downloading""" -import sys -import os -import re -import shutil -import socket -import base64 -import hashlib -import itertools -import warnings -from functools import wraps - -from setuptools.extern import six -from setuptools.extern.six.moves import urllib, http_client, configparser, map - -import setuptools -from pkg_resources import ( - CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST, - Environment, find_distributions, safe_name, safe_version, - to_filename, Requirement, DEVELOP_DIST, EGG_DIST, -) -from setuptools import ssl_support -from distutils import log -from distutils.errors import DistutilsError -from fnmatch import translate -from setuptools.py27compat import get_all_headers -from setuptools.py33compat import unescape -from setuptools.wheel import Wheel - -__metaclass__ = type - -EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.+!]+)$') -HREF = re.compile(r"""href\s*=\s*['"]?([^'"> ]+)""", re.I) -PYPI_MD5 = re.compile( - r'<a href="([^"#]+)">([^<]+)</a>\n\s+\(<a (?:title="MD5 hash"\n\s+)' - r'href="[^?]+\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\)' -) -URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):', re.I).match -EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split() - -__all__ = [ - 'PackageIndex', 'distros_for_url', 'parse_bdist_wininst', - 'interpret_distro_name', -] - -_SOCKET_TIMEOUT = 15 - -_tmpl = "setuptools/{setuptools.__version__} Python-urllib/{py_major}" -user_agent = _tmpl.format(py_major=sys.version[:3], setuptools=setuptools) - - -def parse_requirement_arg(spec): - try: - return Requirement.parse(spec) - except ValueError: - raise DistutilsError( - "Not a URL, existing file, or requirement spec: %r" % (spec,) - ) - - -def parse_bdist_wininst(name): - """Return (base,pyversion) or (None,None) for possible .exe name""" - - lower = name.lower() - base, py_ver, plat = None, None, None - - if lower.endswith('.exe'): - if lower.endswith('.win32.exe'): - base = name[:-10] - plat = 'win32' - elif lower.startswith('.win32-py', -16): - py_ver = name[-7:-4] - base = name[:-16] - plat = 'win32' - elif lower.endswith('.win-amd64.exe'): - base = name[:-14] - plat = 'win-amd64' - elif lower.startswith('.win-amd64-py', -20): - py_ver = name[-7:-4] - base = name[:-20] - plat = 'win-amd64' - return base, py_ver, plat - - -def egg_info_for_url(url): - parts = urllib.parse.urlparse(url) - scheme, server, path, parameters, query, fragment = parts - base = urllib.parse.unquote(path.split('/')[-1]) - if server == 'sourceforge.net' and base == 'download': # XXX Yuck - base = urllib.parse.unquote(path.split('/')[-2]) - if '#' in base: - base, fragment = base.split('#', 1) - return base, fragment - - -def distros_for_url(url, metadata=None): - """Yield egg or source distribution objects that might be found at a URL""" - base, fragment = egg_info_for_url(url) - for dist in distros_for_location(url, base, metadata): - yield dist - if fragment: - match = EGG_FRAGMENT.match(fragment) - if match: - for dist in interpret_distro_name( - url, match.group(1), metadata, precedence=CHECKOUT_DIST - ): - yield dist - - -def distros_for_location(location, basename, metadata=None): - """Yield egg or source distribution objects based on basename""" - if basename.endswith('.egg.zip'): - basename = basename[:-4] # strip the .zip - if basename.endswith('.egg') and '-' in basename: - # only one, unambiguous interpretation - return [Distribution.from_location(location, basename, metadata)] - if basename.endswith('.whl') and '-' in basename: - wheel = Wheel(basename) - if not wheel.is_compatible(): - return [] - return [Distribution( - location=location, - project_name=wheel.project_name, - version=wheel.version, - # Increase priority over eggs. - precedence=EGG_DIST + 1, - )] - if basename.endswith('.exe'): - win_base, py_ver, platform = parse_bdist_wininst(basename) - if win_base is not None: - return interpret_distro_name( - location, win_base, metadata, py_ver, BINARY_DIST, platform - ) - # Try source distro extensions (.zip, .tgz, etc.) - # - for ext in EXTENSIONS: - if basename.endswith(ext): - basename = basename[:-len(ext)] - return interpret_distro_name(location, basename, metadata) - return [] # no extension matched - - -def distros_for_filename(filename, metadata=None): - """Yield possible egg or source distribution objects based on a filename""" - return distros_for_location( - normalize_path(filename), os.path.basename(filename), metadata - ) - - -def interpret_distro_name( - location, basename, metadata, py_version=None, precedence=SOURCE_DIST, - platform=None -): - """Generate alternative interpretations of a source distro name - - Note: if `location` is a filesystem filename, you should call - ``pkg_resources.normalize_path()`` on it before passing it to this - routine! - """ - # Generate alternative interpretations of a source distro name - # Because some packages are ambiguous as to name/versions split - # e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc. - # So, we generate each possible interepretation (e.g. "adns, python-1.1.0" - # "adns-python, 1.1.0", and "adns-python-1.1.0, no version"). In practice, - # the spurious interpretations should be ignored, because in the event - # there's also an "adns" package, the spurious "python-1.1.0" version will - # compare lower than any numeric version number, and is therefore unlikely - # to match a request for it. It's still a potential problem, though, and - # in the long run PyPI and the distutils should go for "safe" names and - # versions in distribution archive names (sdist and bdist). - - parts = basename.split('-') - if not py_version and any(re.match(r'py\d\.\d$', p) for p in parts[2:]): - # it is a bdist_dumb, not an sdist -- bail out - return - - for p in range(1, len(parts) + 1): - yield Distribution( - location, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]), - py_version=py_version, precedence=precedence, - platform=platform - ) - - -# From Python 2.7 docs -def unique_everseen(iterable, key=None): - "List unique elements, preserving order. Remember all elements ever seen." - # unique_everseen('AAAABBBCCDAABBB') --> A B C D - # unique_everseen('ABBCcAD', str.lower) --> A B C D - seen = set() - seen_add = seen.add - if key is None: - for element in six.moves.filterfalse(seen.__contains__, iterable): - seen_add(element) - yield element - else: - for element in iterable: - k = key(element) - if k not in seen: - seen_add(k) - yield element - - -def unique_values(func): - """ - Wrap a function returning an iterable such that the resulting iterable - only ever yields unique items. - """ - - @wraps(func) - def wrapper(*args, **kwargs): - return unique_everseen(func(*args, **kwargs)) - - return wrapper - - -REL = re.compile(r"""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I) -# this line is here to fix emacs' cruddy broken syntax highlighting - - -@unique_values -def find_external_links(url, page): - """Find rel="homepage" and rel="download" links in `page`, yielding URLs""" - - for match in REL.finditer(page): - tag, rel = match.groups() - rels = set(map(str.strip, rel.lower().split(','))) - if 'homepage' in rels or 'download' in rels: - for match in HREF.finditer(tag): - yield urllib.parse.urljoin(url, htmldecode(match.group(1))) - - for tag in ("<th>Home Page", "<th>Download URL"): - pos = page.find(tag) - if pos != -1: - match = HREF.search(page, pos) - if match: - yield urllib.parse.urljoin(url, htmldecode(match.group(1))) - - -class ContentChecker: - """ - A null content checker that defines the interface for checking content - """ - - def feed(self, block): - """ - Feed a block of data to the hash. - """ - return - - def is_valid(self): - """ - Check the hash. Return False if validation fails. - """ - return True - - def report(self, reporter, template): - """ - Call reporter with information about the checker (hash name) - substituted into the template. - """ - return - - -class HashChecker(ContentChecker): - pattern = re.compile( - r'(?P<hash_name>sha1|sha224|sha384|sha256|sha512|md5)=' - r'(?P<expected>[a-f0-9]+)' - ) - - def __init__(self, hash_name, expected): - self.hash_name = hash_name - self.hash = hashlib.new(hash_name) - self.expected = expected - - @classmethod - def from_url(cls, url): - "Construct a (possibly null) ContentChecker from a URL" - fragment = urllib.parse.urlparse(url)[-1] - if not fragment: - return ContentChecker() - match = cls.pattern.search(fragment) - if not match: - return ContentChecker() - return cls(**match.groupdict()) - - def feed(self, block): - self.hash.update(block) - - def is_valid(self): - return self.hash.hexdigest() == self.expected - - def report(self, reporter, template): - msg = template % self.hash_name - return reporter(msg) - - -class PackageIndex(Environment): - """A distribution index that scans web pages for download URLs""" - - def __init__( - self, index_url="https://pypi.org/simple/", hosts=('*',), - ca_bundle=None, verify_ssl=True, *args, **kw - ): - Environment.__init__(self, *args, **kw) - self.index_url = index_url + "/" [:not index_url.endswith('/')] - self.scanned_urls = {} - self.fetched_urls = {} - self.package_pages = {} - self.allows = re.compile('|'.join(map(translate, hosts))).match - self.to_scan = [] - use_ssl = ( - verify_ssl - and ssl_support.is_available - and (ca_bundle or ssl_support.find_ca_bundle()) - ) - if use_ssl: - self.opener = ssl_support.opener_for(ca_bundle) - else: - self.opener = urllib.request.urlopen - - def process_url(self, url, retrieve=False): - """Evaluate a URL as a possible download, and maybe retrieve it""" - if url in self.scanned_urls and not retrieve: - return - self.scanned_urls[url] = True - if not URL_SCHEME(url): - self.process_filename(url) - return - else: - dists = list(distros_for_url(url)) - if dists: - if not self.url_ok(url): - return - self.debug("Found link: %s", url) - - if dists or not retrieve or url in self.fetched_urls: - list(map(self.add, dists)) - return # don't need the actual page - - if not self.url_ok(url): - self.fetched_urls[url] = True - return - - self.info("Reading %s", url) - self.fetched_urls[url] = True # prevent multiple fetch attempts - tmpl = "Download error on %s: %%s -- Some packages may not be found!" - f = self.open_url(url, tmpl % url) - if f is None: - return - self.fetched_urls[f.url] = True - if 'html' not in f.headers.get('content-type', '').lower(): - f.close() # not html, we can't process it - return - - base = f.url # handle redirects - page = f.read() - if not isinstance(page, str): - # In Python 3 and got bytes but want str. - if isinstance(f, urllib.error.HTTPError): - # Errors have no charset, assume latin1: - charset = 'latin-1' - else: - charset = f.headers.get_param('charset') or 'latin-1' - page = page.decode(charset, "ignore") - f.close() - for match in HREF.finditer(page): - link = urllib.parse.urljoin(base, htmldecode(match.group(1))) - self.process_url(link) - if url.startswith(self.index_url) and getattr(f, 'code', None) != 404: - page = self.process_index(url, page) - - def process_filename(self, fn, nested=False): - # process filenames or directories - if not os.path.exists(fn): - self.warn("Not found: %s", fn) - return - - if os.path.isdir(fn) and not nested: - path = os.path.realpath(fn) - for item in os.listdir(path): - self.process_filename(os.path.join(path, item), True) - - dists = distros_for_filename(fn) - if dists: - self.debug("Found: %s", fn) - list(map(self.add, dists)) - - def url_ok(self, url, fatal=False): - s = URL_SCHEME(url) - is_file = s and s.group(1).lower() == 'file' - if is_file or self.allows(urllib.parse.urlparse(url)[1]): - return True - msg = ( - "\nNote: Bypassing %s (disallowed host; see " - "http://bit.ly/2hrImnY for details).\n") - if fatal: - raise DistutilsError(msg % url) - else: - self.warn(msg, url) - - def scan_egg_links(self, search_path): - dirs = filter(os.path.isdir, search_path) - egg_links = ( - (path, entry) - for path in dirs - for entry in os.listdir(path) - if entry.endswith('.egg-link') - ) - list(itertools.starmap(self.scan_egg_link, egg_links)) - - def scan_egg_link(self, path, entry): - with open(os.path.join(path, entry)) as raw_lines: - # filter non-empty lines - lines = list(filter(None, map(str.strip, raw_lines))) - - if len(lines) != 2: - # format is not recognized; punt - return - - egg_path, setup_path = lines - - for dist in find_distributions(os.path.join(path, egg_path)): - dist.location = os.path.join(path, *lines) - dist.precedence = SOURCE_DIST - self.add(dist) - - def process_index(self, url, page): - """Process the contents of a PyPI page""" - - def scan(link): - # Process a URL to see if it's for a package page - if link.startswith(self.index_url): - parts = list(map( - urllib.parse.unquote, link[len(self.index_url):].split('/') - )) - if len(parts) == 2 and '#' not in parts[1]: - # it's a package page, sanitize and index it - pkg = safe_name(parts[0]) - ver = safe_version(parts[1]) - self.package_pages.setdefault(pkg.lower(), {})[link] = True - return to_filename(pkg), to_filename(ver) - return None, None - - # process an index page into the package-page index - for match in HREF.finditer(page): - try: - scan(urllib.parse.urljoin(url, htmldecode(match.group(1)))) - except ValueError: - pass - - pkg, ver = scan(url) # ensure this page is in the page index - if pkg: - # process individual package page - for new_url in find_external_links(url, page): - # Process the found URL - base, frag = egg_info_for_url(new_url) - if base.endswith('.py') and not frag: - if ver: - new_url += '#egg=%s-%s' % (pkg, ver) - else: - self.need_version_info(url) - self.scan_url(new_url) - - return PYPI_MD5.sub( - lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1, 3, 2), page - ) - else: - return "" # no sense double-scanning non-package pages - - def need_version_info(self, url): - self.scan_all( - "Page at %s links to .py file(s) without version info; an index " - "scan is required.", url - ) - - def scan_all(self, msg=None, *args): - if self.index_url not in self.fetched_urls: - if msg: - self.warn(msg, *args) - self.info( - "Scanning index of all packages (this may take a while)" - ) - self.scan_url(self.index_url) - - def find_packages(self, requirement): - self.scan_url(self.index_url + requirement.unsafe_name + '/') - - if not self.package_pages.get(requirement.key): - # Fall back to safe version of the name - self.scan_url(self.index_url + requirement.project_name + '/') - - if not self.package_pages.get(requirement.key): - # We couldn't find the target package, so search the index page too - self.not_found_in_index(requirement) - - for url in list(self.package_pages.get(requirement.key, ())): - # scan each page that might be related to the desired package - self.scan_url(url) - - def obtain(self, requirement, installer=None): - self.prescan() - self.find_packages(requirement) - for dist in self[requirement.key]: - if dist in requirement: - return dist - self.debug("%s does not match %s", requirement, dist) - return super(PackageIndex, self).obtain(requirement, installer) - - def check_hash(self, checker, filename, tfp): - """ - checker is a ContentChecker - """ - checker.report( - self.debug, - "Validating %%s checksum for %s" % filename) - if not checker.is_valid(): - tfp.close() - os.unlink(filename) - raise DistutilsError( - "%s validation failed for %s; " - "possible download problem?" - % (checker.hash.name, os.path.basename(filename)) - ) - - def add_find_links(self, urls): - """Add `urls` to the list that will be prescanned for searches""" - for url in urls: - if ( - self.to_scan is None # if we have already "gone online" - or not URL_SCHEME(url) # or it's a local file/directory - or url.startswith('file:') - or list(distros_for_url(url)) # or a direct package link - ): - # then go ahead and process it now - self.scan_url(url) - else: - # otherwise, defer retrieval till later - self.to_scan.append(url) - - def prescan(self): - """Scan urls scheduled for prescanning (e.g. --find-links)""" - if self.to_scan: - list(map(self.scan_url, self.to_scan)) - self.to_scan = None # from now on, go ahead and process immediately - - def not_found_in_index(self, requirement): - if self[requirement.key]: # we've seen at least one distro - meth, msg = self.info, "Couldn't retrieve index page for %r" - else: # no distros seen for this name, might be misspelled - meth, msg = ( - self.warn, - "Couldn't find index page for %r (maybe misspelled?)") - meth(msg, requirement.unsafe_name) - self.scan_all() - - def download(self, spec, tmpdir): - """Locate and/or download `spec` to `tmpdir`, returning a local path - - `spec` may be a ``Requirement`` object, or a string containing a URL, - an existing local filename, or a project/version requirement spec - (i.e. the string form of a ``Requirement`` object). If it is the URL - of a .py file with an unambiguous ``#egg=name-version`` tag (i.e., one - that escapes ``-`` as ``_`` throughout), a trivial ``setup.py`` is - automatically created alongside the downloaded file. - - If `spec` is a ``Requirement`` object or a string containing a - project/version requirement spec, this method returns the location of - a matching distribution (possibly after downloading it to `tmpdir`). - If `spec` is a locally existing file or directory name, it is simply - returned unchanged. If `spec` is a URL, it is downloaded to a subpath - of `tmpdir`, and the local filename is returned. Various errors may be - raised if a problem occurs during downloading. - """ - if not isinstance(spec, Requirement): - scheme = URL_SCHEME(spec) - if scheme: - # It's a url, download it to tmpdir - found = self._download_url(scheme.group(1), spec, tmpdir) - base, fragment = egg_info_for_url(spec) - if base.endswith('.py'): - found = self.gen_setup(found, fragment, tmpdir) - return found - elif os.path.exists(spec): - # Existing file or directory, just return it - return spec - else: - spec = parse_requirement_arg(spec) - return getattr(self.fetch_distribution(spec, tmpdir), 'location', None) - - def fetch_distribution( - self, requirement, tmpdir, force_scan=False, source=False, - develop_ok=False, local_index=None): - """Obtain a distribution suitable for fulfilling `requirement` - - `requirement` must be a ``pkg_resources.Requirement`` instance. - If necessary, or if the `force_scan` flag is set, the requirement is - searched for in the (online) package index as well as the locally - installed packages. If a distribution matching `requirement` is found, - the returned distribution's ``location`` is the value you would have - gotten from calling the ``download()`` method with the matching - distribution's URL or filename. If no matching distribution is found, - ``None`` is returned. - - If the `source` flag is set, only source distributions and source - checkout links will be considered. Unless the `develop_ok` flag is - set, development and system eggs (i.e., those using the ``.egg-info`` - format) will be ignored. - """ - # process a Requirement - self.info("Searching for %s", requirement) - skipped = {} - dist = None - - def find(req, env=None): - if env is None: - env = self - # Find a matching distribution; may be called more than once - - for dist in env[req.key]: - - if dist.precedence == DEVELOP_DIST and not develop_ok: - if dist not in skipped: - self.warn( - "Skipping development or system egg: %s", dist, - ) - skipped[dist] = 1 - continue - - test = ( - dist in req - and (dist.precedence <= SOURCE_DIST or not source) - ) - if test: - loc = self.download(dist.location, tmpdir) - dist.download_location = loc - if os.path.exists(dist.download_location): - return dist - - if force_scan: - self.prescan() - self.find_packages(requirement) - dist = find(requirement) - - if not dist and local_index is not None: - dist = find(requirement, local_index) - - if dist is None: - if self.to_scan is not None: - self.prescan() - dist = find(requirement) - - if dist is None and not force_scan: - self.find_packages(requirement) - dist = find(requirement) - - if dist is None: - self.warn( - "No local packages or working download links found for %s%s", - (source and "a source distribution of " or ""), - requirement, - ) - else: - self.info("Best match: %s", dist) - return dist.clone(location=dist.download_location) - - def fetch(self, requirement, tmpdir, force_scan=False, source=False): - """Obtain a file suitable for fulfilling `requirement` - - DEPRECATED; use the ``fetch_distribution()`` method now instead. For - backward compatibility, this routine is identical but returns the - ``location`` of the downloaded distribution instead of a distribution - object. - """ - dist = self.fetch_distribution(requirement, tmpdir, force_scan, source) - if dist is not None: - return dist.location - return None - - def gen_setup(self, filename, fragment, tmpdir): - match = EGG_FRAGMENT.match(fragment) - dists = match and [ - d for d in - interpret_distro_name(filename, match.group(1), None) if d.version - ] or [] - - if len(dists) == 1: # unambiguous ``#egg`` fragment - basename = os.path.basename(filename) - - # Make sure the file has been downloaded to the temp dir. - if os.path.dirname(filename) != tmpdir: - dst = os.path.join(tmpdir, basename) - from setuptools.command.easy_install import samefile - if not samefile(filename, dst): - shutil.copy2(filename, dst) - filename = dst - - with open(os.path.join(tmpdir, 'setup.py'), 'w') as file: - file.write( - "from setuptools import setup\n" - "setup(name=%r, version=%r, py_modules=[%r])\n" - % ( - dists[0].project_name, dists[0].version, - os.path.splitext(basename)[0] - ) - ) - return filename - - elif match: - raise DistutilsError( - "Can't unambiguously interpret project/version identifier %r; " - "any dashes in the name or version should be escaped using " - "underscores. %r" % (fragment, dists) - ) - else: - raise DistutilsError( - "Can't process plain .py files without an '#egg=name-version'" - " suffix to enable automatic setup script generation." - ) - - dl_blocksize = 8192 - - def _download_to(self, url, filename): - self.info("Downloading %s", url) - # Download the file - fp = None - try: - checker = HashChecker.from_url(url) - fp = self.open_url(url) - if isinstance(fp, urllib.error.HTTPError): - raise DistutilsError( - "Can't download %s: %s %s" % (url, fp.code, fp.msg) - ) - headers = fp.info() - blocknum = 0 - bs = self.dl_blocksize - size = -1 - if "content-length" in headers: - # Some servers return multiple Content-Length headers :( - sizes = get_all_headers(headers, 'Content-Length') - size = max(map(int, sizes)) - self.reporthook(url, filename, blocknum, bs, size) - with open(filename, 'wb') as tfp: - while True: - block = fp.read(bs) - if block: - checker.feed(block) - tfp.write(block) - blocknum += 1 - self.reporthook(url, filename, blocknum, bs, size) - else: - break - self.check_hash(checker, filename, tfp) - return headers - finally: - if fp: - fp.close() - - def reporthook(self, url, filename, blocknum, blksize, size): - pass # no-op - - def open_url(self, url, warning=None): - if url.startswith('file:'): - return local_open(url) - try: - return open_with_auth(url, self.opener) - except (ValueError, http_client.InvalidURL) as v: - msg = ' '.join([str(arg) for arg in v.args]) - if warning: - self.warn(warning, msg) - else: - raise DistutilsError('%s %s' % (url, msg)) - except urllib.error.HTTPError as v: - return v - except urllib.error.URLError as v: - if warning: - self.warn(warning, v.reason) - else: - raise DistutilsError("Download error for %s: %s" - % (url, v.reason)) - except http_client.BadStatusLine as v: - if warning: - self.warn(warning, v.line) - else: - raise DistutilsError( - '%s returned a bad status line. The server might be ' - 'down, %s' % - (url, v.line) - ) - except (http_client.HTTPException, socket.error) as v: - if warning: - self.warn(warning, v) - else: - raise DistutilsError("Download error for %s: %s" - % (url, v)) - - def _download_url(self, scheme, url, tmpdir): - # Determine download filename - # - name, fragment = egg_info_for_url(url) - if name: - while '..' in name: - name = name.replace('..', '.').replace('\\', '_') - else: - name = "__downloaded__" # default if URL has no path contents - - if name.endswith('.egg.zip'): - name = name[:-4] # strip the extra .zip before download - - filename = os.path.join(tmpdir, name) - - # Download the file - # - if scheme == 'svn' or scheme.startswith('svn+'): - return self._download_svn(url, filename) - elif scheme == 'git' or scheme.startswith('git+'): - return self._download_git(url, filename) - elif scheme.startswith('hg+'): - return self._download_hg(url, filename) - elif scheme == 'file': - return urllib.request.url2pathname(urllib.parse.urlparse(url)[2]) - else: - self.url_ok(url, True) # raises error if not allowed - return self._attempt_download(url, filename) - - def scan_url(self, url): - self.process_url(url, True) - - def _attempt_download(self, url, filename): - headers = self._download_to(url, filename) - if 'html' in headers.get('content-type', '').lower(): - return self._download_html(url, headers, filename) - else: - return filename - - def _download_html(self, url, headers, filename): - file = open(filename) - for line in file: - if line.strip(): - # Check for a subversion index page - if re.search(r'<title>([^- ]+ - )?Revision \d+:', line): - # it's a subversion index page: - file.close() - os.unlink(filename) - return self._download_svn(url, filename) - break # not an index page - file.close() - os.unlink(filename) - raise DistutilsError("Unexpected HTML page found at " + url) - - def _download_svn(self, url, filename): - warnings.warn("SVN download support is deprecated", UserWarning) - url = url.split('#', 1)[0] # remove any fragment for svn's sake - creds = '' - if url.lower().startswith('svn:') and '@' in url: - scheme, netloc, path, p, q, f = urllib.parse.urlparse(url) - if not netloc and path.startswith('//') and '/' in path[2:]: - netloc, path = path[2:].split('/', 1) - auth, host = urllib.parse.splituser(netloc) - if auth: - if ':' in auth: - user, pw = auth.split(':', 1) - creds = " --username=%s --password=%s" % (user, pw) - else: - creds = " --username=" + auth - netloc = host - parts = scheme, netloc, url, p, q, f - url = urllib.parse.urlunparse(parts) - self.info("Doing subversion checkout from %s to %s", url, filename) - os.system("svn checkout%s -q %s %s" % (creds, url, filename)) - return filename - - @staticmethod - def _vcs_split_rev_from_url(url, pop_prefix=False): - scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) - - scheme = scheme.split('+', 1)[-1] - - # Some fragment identification fails - path = path.split('#', 1)[0] - - rev = None - if '@' in path: - path, rev = path.rsplit('@', 1) - - # Also, discard fragment - url = urllib.parse.urlunsplit((scheme, netloc, path, query, '')) - - return url, rev - - def _download_git(self, url, filename): - filename = filename.split('#', 1)[0] - url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) - - self.info("Doing git clone from %s to %s", url, filename) - os.system("git clone --quiet %s %s" % (url, filename)) - - if rev is not None: - self.info("Checking out %s", rev) - os.system("(cd %s && git checkout --quiet %s)" % ( - filename, - rev, - )) - - return filename - - def _download_hg(self, url, filename): - filename = filename.split('#', 1)[0] - url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) - - self.info("Doing hg clone from %s to %s", url, filename) - os.system("hg clone --quiet %s %s" % (url, filename)) - - if rev is not None: - self.info("Updating to %s", rev) - os.system("(cd %s && hg up -C -r %s -q)" % ( - filename, - rev, - )) - - return filename - - def debug(self, msg, *args): - log.debug(msg, *args) - - def info(self, msg, *args): - log.info(msg, *args) - - def warn(self, msg, *args): - log.warn(msg, *args) - - -# This pattern matches a character entity reference (a decimal numeric -# references, a hexadecimal numeric reference, or a named reference). -entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub - - -def decode_entity(match): - what = match.group(0) - return unescape(what) - - -def htmldecode(text): - """ - Decode HTML entities in the given text. - - >>> htmldecode( - ... 'https://../package_name-0.1.2.tar.gz' - ... '?tokena=A&amp;tokenb=B">package_name-0.1.2.tar.gz') - 'https://../package_name-0.1.2.tar.gz?tokena=A&tokenb=B">package_name-0.1.2.tar.gz' - """ - return entity_sub(decode_entity, text) - - -def socket_timeout(timeout=15): - def _socket_timeout(func): - def _socket_timeout(*args, **kwargs): - old_timeout = socket.getdefaulttimeout() - socket.setdefaulttimeout(timeout) - try: - return func(*args, **kwargs) - finally: - socket.setdefaulttimeout(old_timeout) - - return _socket_timeout - - return _socket_timeout - - -def _encode_auth(auth): - """ - A function compatible with Python 2.3-3.3 that will encode - auth from a URL suitable for an HTTP header. - >>> str(_encode_auth('username%3Apassword')) - 'dXNlcm5hbWU6cGFzc3dvcmQ=' - - Long auth strings should not cause a newline to be inserted. - >>> long_auth = 'username:' + 'password'*10 - >>> chr(10) in str(_encode_auth(long_auth)) - False - """ - auth_s = urllib.parse.unquote(auth) - # convert to bytes - auth_bytes = auth_s.encode() - encoded_bytes = base64.b64encode(auth_bytes) - # convert back to a string - encoded = encoded_bytes.decode() - # strip the trailing carriage return - return encoded.replace('\n', '') - - -class Credential: - """ - A username/password pair. Use like a namedtuple. - """ - - def __init__(self, username, password): - self.username = username - self.password = password - - def __iter__(self): - yield self.username - yield self.password - - def __str__(self): - return '%(username)s:%(password)s' % vars(self) - - -class PyPIConfig(configparser.RawConfigParser): - def __init__(self): - """ - Load from ~/.pypirc - """ - defaults = dict.fromkeys(['username', 'password', 'repository'], '') - configparser.RawConfigParser.__init__(self, defaults) - - rc = os.path.join(os.path.expanduser('~'), '.pypirc') - if os.path.exists(rc): - self.read(rc) - - @property - def creds_by_repository(self): - sections_with_repositories = [ - section for section in self.sections() - if self.get(section, 'repository').strip() - ] - - return dict(map(self._get_repo_cred, sections_with_repositories)) - - def _get_repo_cred(self, section): - repo = self.get(section, 'repository').strip() - return repo, Credential( - self.get(section, 'username').strip(), - self.get(section, 'password').strip(), - ) - - def find_credential(self, url): - """ - If the URL indicated appears to be a repository defined in this - config, return the credential for that repository. - """ - for repository, cred in self.creds_by_repository.items(): - if url.startswith(repository): - return cred - - -def open_with_auth(url, opener=urllib.request.urlopen): - """Open a urllib2 request, handling HTTP authentication""" - - scheme, netloc, path, params, query, frag = urllib.parse.urlparse(url) - - # Double scheme does not raise on Mac OS X as revealed by a - # failing test. We would expect "nonnumeric port". Refs #20. - if netloc.endswith(':'): - raise http_client.InvalidURL("nonnumeric port: ''") - - if scheme in ('http', 'https'): - auth, host = urllib.parse.splituser(netloc) - else: - auth = None - - if not auth: - cred = PyPIConfig().find_credential(url) - if cred: - auth = str(cred) - info = cred.username, url - log.info('Authenticating as %s for %s (from .pypirc)', *info) - - if auth: - auth = "Basic " + _encode_auth(auth) - parts = scheme, host, path, params, query, frag - new_url = urllib.parse.urlunparse(parts) - request = urllib.request.Request(new_url) - request.add_header("Authorization", auth) - else: - request = urllib.request.Request(url) - - request.add_header('User-Agent', user_agent) - fp = opener(request) - - if auth: - # Put authentication info back into request URL if same host, - # so that links found on the page will work - s2, h2, path2, param2, query2, frag2 = urllib.parse.urlparse(fp.url) - if s2 == scheme and h2 == host: - parts = s2, netloc, path2, param2, query2, frag2 - fp.url = urllib.parse.urlunparse(parts) - - return fp - - -# adding a timeout to avoid freezing package_index -open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth) - - -def fix_sf_url(url): - return url # backward compatibility - - -def local_open(url): - """Read a local path, with special support for directories""" - scheme, server, path, param, query, frag = urllib.parse.urlparse(url) - filename = urllib.request.url2pathname(path) - if os.path.isfile(filename): - return urllib.request.urlopen(url) - elif path.endswith('/') and os.path.isdir(filename): - files = [] - for f in os.listdir(filename): - filepath = os.path.join(filename, f) - if f == 'index.html': - with open(filepath, 'r') as fp: - body = fp.read() - break - elif os.path.isdir(filepath): - f += '/' - files.append('<a href="{name}">{name}</a>'.format(name=f)) - else: - tmpl = ( - "<html><head><title>{url}</title>" - "</head><body>{files}</body></html>") - body = tmpl.format(url=url, files='\n'.join(files)) - status, message = 200, "OK" - else: - status, message, body = 404, "Path not found", "Not found" - - headers = {'content-type': 'text/html'} - body_stream = six.StringIO(body) - return urllib.error.HTTPError(url, status, message, headers, body_stream) diff --git a/lib/setuptools/pep425tags.py b/lib/setuptools/pep425tags.py deleted file mode 100644 index 8bf4277..0000000 --- a/lib/setuptools/pep425tags.py +++ /dev/null @@ -1,319 +0,0 @@ -# This file originally from pip: -# https://github.com/pypa/pip/blob/8f4f15a5a95d7d5b511ceaee9ed261176c181970/src/pip/_internal/pep425tags.py -"""Generate and work with PEP 425 Compatibility Tags.""" -from __future__ import absolute_import - -import distutils.util -from distutils import log -import platform -import re -import sys -import sysconfig -import warnings -from collections import OrderedDict - -from .extern import six - -from . import glibc - -_osx_arch_pat = re.compile(r'(.+)_(\d+)_(\d+)_(.+)') - - -def get_config_var(var): - try: - return sysconfig.get_config_var(var) - except IOError as e: # Issue #1074 - warnings.warn("{}".format(e), RuntimeWarning) - return None - - -def get_abbr_impl(): - """Return abbreviated implementation name.""" - if hasattr(sys, 'pypy_version_info'): - pyimpl = 'pp' - elif sys.platform.startswith('java'): - pyimpl = 'jy' - elif sys.platform == 'cli': - pyimpl = 'ip' - else: - pyimpl = 'cp' - return pyimpl - - -def get_impl_ver(): - """Return implementation version.""" - impl_ver = get_config_var("py_version_nodot") - if not impl_ver or get_abbr_impl() == 'pp': - impl_ver = ''.join(map(str, get_impl_version_info())) - return impl_ver - - -def get_impl_version_info(): - """Return sys.version_info-like tuple for use in decrementing the minor - version.""" - if get_abbr_impl() == 'pp': - # as per https://github.com/pypa/pip/issues/2882 - return (sys.version_info[0], sys.pypy_version_info.major, - sys.pypy_version_info.minor) - else: - return sys.version_info[0], sys.version_info[1] - - -def get_impl_tag(): - """ - Returns the Tag for this specific implementation. - """ - return "{}{}".format(get_abbr_impl(), get_impl_ver()) - - -def get_flag(var, fallback, expected=True, warn=True): - """Use a fallback method for determining SOABI flags if the needed config - var is unset or unavailable.""" - val = get_config_var(var) - if val is None: - if warn: - log.debug("Config variable '%s' is unset, Python ABI tag may " - "be incorrect", var) - return fallback() - return val == expected - - -def get_abi_tag(): - """Return the ABI tag based on SOABI (if available) or emulate SOABI - (CPython 2, PyPy).""" - soabi = get_config_var('SOABI') - impl = get_abbr_impl() - if not soabi and impl in {'cp', 'pp'} and hasattr(sys, 'maxunicode'): - d = '' - m = '' - u = '' - if get_flag('Py_DEBUG', - lambda: hasattr(sys, 'gettotalrefcount'), - warn=(impl == 'cp')): - d = 'd' - if get_flag('WITH_PYMALLOC', - lambda: impl == 'cp', - warn=(impl == 'cp')): - m = 'm' - if get_flag('Py_UNICODE_SIZE', - lambda: sys.maxunicode == 0x10ffff, - expected=4, - warn=(impl == 'cp' and - six.PY2)) \ - and six.PY2: - u = 'u' - abi = '%s%s%s%s%s' % (impl, get_impl_ver(), d, m, u) - elif soabi and soabi.startswith('cpython-'): - abi = 'cp' + soabi.split('-')[1] - elif soabi: - abi = soabi.replace('.', '_').replace('-', '_') - else: - abi = None - return abi - - -def _is_running_32bit(): - return sys.maxsize == 2147483647 - - -def get_platform(): - """Return our platform name 'win32', 'linux_x86_64'""" - if sys.platform == 'darwin': - # distutils.util.get_platform() returns the release based on the value - # of MACOSX_DEPLOYMENT_TARGET on which Python was built, which may - # be significantly older than the user's current machine. - release, _, machine = platform.mac_ver() - split_ver = release.split('.') - - if machine == "x86_64" and _is_running_32bit(): - machine = "i386" - elif machine == "ppc64" and _is_running_32bit(): - machine = "ppc" - - return 'macosx_{}_{}_{}'.format(split_ver[0], split_ver[1], machine) - - # XXX remove distutils dependency - result = distutils.util.get_platform().replace('.', '_').replace('-', '_') - if result == "linux_x86_64" and _is_running_32bit(): - # 32 bit Python program (running on a 64 bit Linux): pip should only - # install and run 32 bit compiled extensions in that case. - result = "linux_i686" - - return result - - -def is_manylinux1_compatible(): - # Only Linux, and only x86-64 / i686 - if get_platform() not in {"linux_x86_64", "linux_i686"}: - return False - - # Check for presence of _manylinux module - try: - import _manylinux - return bool(_manylinux.manylinux1_compatible) - except (ImportError, AttributeError): - # Fall through to heuristic check below - pass - - # Check glibc version. CentOS 5 uses glibc 2.5. - return glibc.have_compatible_glibc(2, 5) - - -def get_darwin_arches(major, minor, machine): - """Return a list of supported arches (including group arches) for - the given major, minor and machine architecture of an macOS machine. - """ - arches = [] - - def _supports_arch(major, minor, arch): - # Looking at the application support for macOS versions in the chart - # provided by https://en.wikipedia.org/wiki/OS_X#Versions it appears - # our timeline looks roughly like: - # - # 10.0 - Introduces ppc support. - # 10.4 - Introduces ppc64, i386, and x86_64 support, however the ppc64 - # and x86_64 support is CLI only, and cannot be used for GUI - # applications. - # 10.5 - Extends ppc64 and x86_64 support to cover GUI applications. - # 10.6 - Drops support for ppc64 - # 10.7 - Drops support for ppc - # - # Given that we do not know if we're installing a CLI or a GUI - # application, we must be conservative and assume it might be a GUI - # application and behave as if ppc64 and x86_64 support did not occur - # until 10.5. - # - # Note: The above information is taken from the "Application support" - # column in the chart not the "Processor support" since I believe - # that we care about what instruction sets an application can use - # not which processors the OS supports. - if arch == 'ppc': - return (major, minor) <= (10, 5) - if arch == 'ppc64': - return (major, minor) == (10, 5) - if arch == 'i386': - return (major, minor) >= (10, 4) - if arch == 'x86_64': - return (major, minor) >= (10, 5) - if arch in groups: - for garch in groups[arch]: - if _supports_arch(major, minor, garch): - return True - return False - - groups = OrderedDict([ - ("fat", ("i386", "ppc")), - ("intel", ("x86_64", "i386")), - ("fat64", ("x86_64", "ppc64")), - ("fat32", ("x86_64", "i386", "ppc")), - ]) - - if _supports_arch(major, minor, machine): - arches.append(machine) - - for garch in groups: - if machine in groups[garch] and _supports_arch(major, minor, garch): - arches.append(garch) - - arches.append('universal') - - return arches - - -def get_supported(versions=None, noarch=False, platform=None, - impl=None, abi=None): - """Return a list of supported tags for each version specified in - `versions`. - - :param versions: a list of string versions, of the form ["33", "32"], - or None. The first version will be assumed to support our ABI. - :param platform: specify the exact platform you want valid - tags for, or None. If None, use the local system platform. - :param impl: specify the exact implementation you want valid - tags for, or None. If None, use the local interpreter impl. - :param abi: specify the exact abi you want valid - tags for, or None. If None, use the local interpreter abi. - """ - supported = [] - - # Versions must be given with respect to the preference - if versions is None: - versions = [] - version_info = get_impl_version_info() - major = version_info[:-1] - # Support all previous minor Python versions. - for minor in range(version_info[-1], -1, -1): - versions.append(''.join(map(str, major + (minor,)))) - - impl = impl or get_abbr_impl() - - abis = [] - - abi = abi or get_abi_tag() - if abi: - abis[0:0] = [abi] - - abi3s = set() - import imp - for suffix in imp.get_suffixes(): - if suffix[0].startswith('.abi'): - abi3s.add(suffix[0].split('.', 2)[1]) - - abis.extend(sorted(list(abi3s))) - - abis.append('none') - - if not noarch: - arch = platform or get_platform() - if arch.startswith('macosx'): - # support macosx-10.6-intel on macosx-10.9-x86_64 - match = _osx_arch_pat.match(arch) - if match: - name, major, minor, actual_arch = match.groups() - tpl = '{}_{}_%i_%s'.format(name, major) - arches = [] - for m in reversed(range(int(minor) + 1)): - for a in get_darwin_arches(int(major), m, actual_arch): - arches.append(tpl % (m, a)) - else: - # arch pattern didn't match (?!) - arches = [arch] - elif platform is None and is_manylinux1_compatible(): - arches = [arch.replace('linux', 'manylinux1'), arch] - else: - arches = [arch] - - # Current version, current API (built specifically for our Python): - for abi in abis: - for arch in arches: - supported.append(('%s%s' % (impl, versions[0]), abi, arch)) - - # abi3 modules compatible with older version of Python - for version in versions[1:]: - # abi3 was introduced in Python 3.2 - if version in {'31', '30'}: - break - for abi in abi3s: # empty set if not Python 3 - for arch in arches: - supported.append(("%s%s" % (impl, version), abi, arch)) - - # Has binaries, does not use the Python API: - for arch in arches: - supported.append(('py%s' % (versions[0][0]), 'none', arch)) - - # No abi / arch, but requires our implementation: - supported.append(('%s%s' % (impl, versions[0]), 'none', 'any')) - # Tagged specifically as being cross-version compatible - # (with just the major version specified) - supported.append(('%s%s' % (impl, versions[0][0]), 'none', 'any')) - - # No abi / arch, generic Python - for i, version in enumerate(versions): - supported.append(('py%s' % (version,), 'none', 'any')) - if i == 0: - supported.append(('py%s' % (version[0]), 'none', 'any')) - - return supported - - -implementation_tag = get_impl_tag() diff --git a/lib/setuptools/py27compat.py b/lib/setuptools/py27compat.py deleted file mode 100644 index 2985011..0000000 --- a/lib/setuptools/py27compat.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Compatibility Support for Python 2.7 and earlier -""" - -import platform - -from setuptools.extern import six - - -def get_all_headers(message, key): - """ - Given an HTTPMessage, return all headers matching a given key. - """ - return message.get_all(key) - - -if six.PY2: - def get_all_headers(message, key): - return message.getheaders(key) - - -linux_py2_ascii = ( - platform.system() == 'Linux' and - six.PY2 -) - -rmtree_safe = str if linux_py2_ascii else lambda x: x -"""Workaround for http://bugs.python.org/issue24672""" diff --git a/lib/setuptools/py31compat.py b/lib/setuptools/py31compat.py deleted file mode 100644 index 1a0705e..0000000 --- a/lib/setuptools/py31compat.py +++ /dev/null @@ -1,32 +0,0 @@ -__all__ = [] - -__metaclass__ = type - - -try: - # Python >=3.2 - from tempfile import TemporaryDirectory -except ImportError: - import shutil - import tempfile - - class TemporaryDirectory: - """ - Very simple temporary directory context manager. - Will try to delete afterward, but will also ignore OS and similar - errors on deletion. - """ - - def __init__(self): - self.name = None # Handle mkdtemp raising an exception - self.name = tempfile.mkdtemp() - - def __enter__(self): - return self.name - - def __exit__(self, exctype, excvalue, exctrace): - try: - shutil.rmtree(self.name, True) - except OSError: # removal errors are not the only possible - pass - self.name = None diff --git a/lib/setuptools/py33compat.py b/lib/setuptools/py33compat.py deleted file mode 100644 index 87cf539..0000000 --- a/lib/setuptools/py33compat.py +++ /dev/null @@ -1,55 +0,0 @@ -import dis -import array -import collections - -try: - import html -except ImportError: - html = None - -from setuptools.extern import six -from setuptools.extern.six.moves import html_parser - -__metaclass__ = type - -OpArg = collections.namedtuple('OpArg', 'opcode arg') - - -class Bytecode_compat: - def __init__(self, code): - self.code = code - - def __iter__(self): - """Yield '(op,arg)' pair for each operation in code object 'code'""" - - bytes = array.array('b', self.code.co_code) - eof = len(self.code.co_code) - - ptr = 0 - extended_arg = 0 - - while ptr < eof: - - op = bytes[ptr] - - if op >= dis.HAVE_ARGUMENT: - - arg = bytes[ptr + 1] + bytes[ptr + 2] * 256 + extended_arg - ptr += 3 - - if op == dis.EXTENDED_ARG: - long_type = six.integer_types[-1] - extended_arg = arg * long_type(65536) - continue - - else: - arg = None - ptr += 1 - - yield OpArg(op, arg) - - -Bytecode = getattr(dis, 'Bytecode', Bytecode_compat) - - -unescape = getattr(html, 'unescape', html_parser.HTMLParser().unescape) diff --git a/lib/setuptools/py36compat.py b/lib/setuptools/py36compat.py deleted file mode 100644 index f527969..0000000 --- a/lib/setuptools/py36compat.py +++ /dev/null @@ -1,82 +0,0 @@ -import sys -from distutils.errors import DistutilsOptionError -from distutils.util import strtobool -from distutils.debug import DEBUG - - -class Distribution_parse_config_files: - """ - Mix-in providing forward-compatibility for functionality to be - included by default on Python 3.7. - - Do not edit the code in this class except to update functionality - as implemented in distutils. - """ - def parse_config_files(self, filenames=None): - from configparser import ConfigParser - - # Ignore install directory options if we have a venv - if sys.prefix != sys.base_prefix: - ignore_options = [ - 'install-base', 'install-platbase', 'install-lib', - 'install-platlib', 'install-purelib', 'install-headers', - 'install-scripts', 'install-data', 'prefix', 'exec-prefix', - 'home', 'user', 'root'] - else: - ignore_options = [] - - ignore_options = frozenset(ignore_options) - - if filenames is None: - filenames = self.find_config_files() - - if DEBUG: - self.announce("Distribution.parse_config_files():") - - parser = ConfigParser(interpolation=None) - for filename in filenames: - if DEBUG: - self.announce(" reading %s" % filename) - parser.read(filename) - for section in parser.sections(): - options = parser.options(section) - opt_dict = self.get_option_dict(section) - - for opt in options: - if opt != '__name__' and opt not in ignore_options: - val = parser.get(section,opt) - opt = opt.replace('-', '_') - opt_dict[opt] = (filename, val) - - # Make the ConfigParser forget everything (so we retain - # the original filenames that options come from) - parser.__init__() - - # If there was a "global" section in the config file, use it - # to set Distribution options. - - if 'global' in self.command_options: - for (opt, (src, val)) in self.command_options['global'].items(): - alias = self.negative_opt.get(opt) - try: - if alias: - setattr(self, alias, not strtobool(val)) - elif opt in ('verbose', 'dry_run'): # ugh! - setattr(self, opt, strtobool(val)) - else: - setattr(self, opt, val) - except ValueError as msg: - raise DistutilsOptionError(msg) - - -if sys.version_info < (3,): - # Python 2 behavior is sufficient - class Distribution_parse_config_files: - pass - - -if False: - # When updated behavior is available upstream, - # disable override here. - class Distribution_parse_config_files: - pass diff --git a/lib/setuptools/sandbox.py b/lib/setuptools/sandbox.py deleted file mode 100644 index 685f3f7..0000000 --- a/lib/setuptools/sandbox.py +++ /dev/null @@ -1,491 +0,0 @@ -import os -import sys -import tempfile -import operator -import functools -import itertools -import re -import contextlib -import pickle -import textwrap - -from setuptools.extern import six -from setuptools.extern.six.moves import builtins, map - -import pkg_resources.py31compat - -if sys.platform.startswith('java'): - import org.python.modules.posix.PosixModule as _os -else: - _os = sys.modules[os.name] -try: - _file = file -except NameError: - _file = None -_open = open -from distutils.errors import DistutilsError -from pkg_resources import working_set - - -__all__ = [ - "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup", -] - - -def _execfile(filename, globals, locals=None): - """ - Python 3 implementation of execfile. - """ - mode = 'rb' - with open(filename, mode) as stream: - script = stream.read() - if locals is None: - locals = globals - code = compile(script, filename, 'exec') - exec(code, globals, locals) - - -@contextlib.contextmanager -def save_argv(repl=None): - saved = sys.argv[:] - if repl is not None: - sys.argv[:] = repl - try: - yield saved - finally: - sys.argv[:] = saved - - -@contextlib.contextmanager -def save_path(): - saved = sys.path[:] - try: - yield saved - finally: - sys.path[:] = saved - - -@contextlib.contextmanager -def override_temp(replacement): - """ - Monkey-patch tempfile.tempdir with replacement, ensuring it exists - """ - pkg_resources.py31compat.makedirs(replacement, exist_ok=True) - - saved = tempfile.tempdir - - tempfile.tempdir = replacement - - try: - yield - finally: - tempfile.tempdir = saved - - -@contextlib.contextmanager -def pushd(target): - saved = os.getcwd() - os.chdir(target) - try: - yield saved - finally: - os.chdir(saved) - - -class UnpickleableException(Exception): - """ - An exception representing another Exception that could not be pickled. - """ - - @staticmethod - def dump(type, exc): - """ - Always return a dumped (pickled) type and exc. If exc can't be pickled, - wrap it in UnpickleableException first. - """ - try: - return pickle.dumps(type), pickle.dumps(exc) - except Exception: - # get UnpickleableException inside the sandbox - from setuptools.sandbox import UnpickleableException as cls - return cls.dump(cls, cls(repr(exc))) - - -class ExceptionSaver: - """ - A Context Manager that will save an exception, serialized, and restore it - later. - """ - - def __enter__(self): - return self - - def __exit__(self, type, exc, tb): - if not exc: - return - - # dump the exception - self._saved = UnpickleableException.dump(type, exc) - self._tb = tb - - # suppress the exception - return True - - def resume(self): - "restore and re-raise any exception" - - if '_saved' not in vars(self): - return - - type, exc = map(pickle.loads, self._saved) - six.reraise(type, exc, self._tb) - - -@contextlib.contextmanager -def save_modules(): - """ - Context in which imported modules are saved. - - Translates exceptions internal to the context into the equivalent exception - outside the context. - """ - saved = sys.modules.copy() - with ExceptionSaver() as saved_exc: - yield saved - - sys.modules.update(saved) - # remove any modules imported since - del_modules = ( - mod_name for mod_name in sys.modules - if mod_name not in saved - # exclude any encodings modules. See #285 - and not mod_name.startswith('encodings.') - ) - _clear_modules(del_modules) - - saved_exc.resume() - - -def _clear_modules(module_names): - for mod_name in list(module_names): - del sys.modules[mod_name] - - -@contextlib.contextmanager -def save_pkg_resources_state(): - saved = pkg_resources.__getstate__() - try: - yield saved - finally: - pkg_resources.__setstate__(saved) - - -@contextlib.contextmanager -def setup_context(setup_dir): - temp_dir = os.path.join(setup_dir, 'temp') - with save_pkg_resources_state(): - with save_modules(): - hide_setuptools() - with save_path(): - with save_argv(): - with override_temp(temp_dir): - with pushd(setup_dir): - # ensure setuptools commands are available - __import__('setuptools') - yield - - -def _needs_hiding(mod_name): - """ - >>> _needs_hiding('setuptools') - True - >>> _needs_hiding('pkg_resources') - True - >>> _needs_hiding('setuptools_plugin') - False - >>> _needs_hiding('setuptools.__init__') - True - >>> _needs_hiding('distutils') - True - >>> _needs_hiding('os') - False - >>> _needs_hiding('Cython') - True - """ - pattern = re.compile(r'(setuptools|pkg_resources|distutils|Cython)(\.|$)') - return bool(pattern.match(mod_name)) - - -def hide_setuptools(): - """ - Remove references to setuptools' modules from sys.modules to allow the - invocation to import the most appropriate setuptools. This technique is - necessary to avoid issues such as #315 where setuptools upgrading itself - would fail to find a function declared in the metadata. - """ - modules = filter(_needs_hiding, sys.modules) - _clear_modules(modules) - - -def run_setup(setup_script, args): - """Run a distutils setup script, sandboxed in its directory""" - setup_dir = os.path.abspath(os.path.dirname(setup_script)) - with setup_context(setup_dir): - try: - sys.argv[:] = [setup_script] + list(args) - sys.path.insert(0, setup_dir) - # reset to include setup dir, w/clean callback list - working_set.__init__() - working_set.callbacks.append(lambda dist: dist.activate()) - - # __file__ should be a byte string on Python 2 (#712) - dunder_file = ( - setup_script - if isinstance(setup_script, str) else - setup_script.encode(sys.getfilesystemencoding()) - ) - - with DirectorySandbox(setup_dir): - ns = dict(__file__=dunder_file, __name__='__main__') - _execfile(setup_script, ns) - except SystemExit as v: - if v.args and v.args[0]: - raise - # Normal exit, just return - - -class AbstractSandbox: - """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts""" - - _active = False - - def __init__(self): - self._attrs = [ - name for name in dir(_os) - if not name.startswith('_') and hasattr(self, name) - ] - - def _copy(self, source): - for name in self._attrs: - setattr(os, name, getattr(source, name)) - - def __enter__(self): - self._copy(self) - if _file: - builtins.file = self._file - builtins.open = self._open - self._active = True - - def __exit__(self, exc_type, exc_value, traceback): - self._active = False - if _file: - builtins.file = _file - builtins.open = _open - self._copy(_os) - - def run(self, func): - """Run 'func' under os sandboxing""" - with self: - return func() - - def _mk_dual_path_wrapper(name): - original = getattr(_os, name) - - def wrap(self, src, dst, *args, **kw): - if self._active: - src, dst = self._remap_pair(name, src, dst, *args, **kw) - return original(src, dst, *args, **kw) - - return wrap - - for name in ["rename", "link", "symlink"]: - if hasattr(_os, name): - locals()[name] = _mk_dual_path_wrapper(name) - - def _mk_single_path_wrapper(name, original=None): - original = original or getattr(_os, name) - - def wrap(self, path, *args, **kw): - if self._active: - path = self._remap_input(name, path, *args, **kw) - return original(path, *args, **kw) - - return wrap - - if _file: - _file = _mk_single_path_wrapper('file', _file) - _open = _mk_single_path_wrapper('open', _open) - for name in [ - "stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir", - "remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat", - "startfile", "mkfifo", "mknod", "pathconf", "access" - ]: - if hasattr(_os, name): - locals()[name] = _mk_single_path_wrapper(name) - - def _mk_single_with_return(name): - original = getattr(_os, name) - - def wrap(self, path, *args, **kw): - if self._active: - path = self._remap_input(name, path, *args, **kw) - return self._remap_output(name, original(path, *args, **kw)) - return original(path, *args, **kw) - - return wrap - - for name in ['readlink', 'tempnam']: - if hasattr(_os, name): - locals()[name] = _mk_single_with_return(name) - - def _mk_query(name): - original = getattr(_os, name) - - def wrap(self, *args, **kw): - retval = original(*args, **kw) - if self._active: - return self._remap_output(name, retval) - return retval - - return wrap - - for name in ['getcwd', 'tmpnam']: - if hasattr(_os, name): - locals()[name] = _mk_query(name) - - def _validate_path(self, path): - """Called to remap or validate any path, whether input or output""" - return path - - def _remap_input(self, operation, path, *args, **kw): - """Called for path inputs""" - return self._validate_path(path) - - def _remap_output(self, operation, path): - """Called for path outputs""" - return self._validate_path(path) - - def _remap_pair(self, operation, src, dst, *args, **kw): - """Called for path pairs like rename, link, and symlink operations""" - return ( - self._remap_input(operation + '-from', src, *args, **kw), - self._remap_input(operation + '-to', dst, *args, **kw) - ) - - -if hasattr(os, 'devnull'): - _EXCEPTIONS = [os.devnull,] -else: - _EXCEPTIONS = [] - - -class DirectorySandbox(AbstractSandbox): - """Restrict operations to a single subdirectory - pseudo-chroot""" - - write_ops = dict.fromkeys([ - "open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir", - "utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam", - ]) - - _exception_patterns = [ - # Allow lib2to3 to attempt to save a pickled grammar object (#121) - r'.*lib2to3.*\.pickle$', - ] - "exempt writing to paths that match the pattern" - - def __init__(self, sandbox, exceptions=_EXCEPTIONS): - self._sandbox = os.path.normcase(os.path.realpath(sandbox)) - self._prefix = os.path.join(self._sandbox, '') - self._exceptions = [ - os.path.normcase(os.path.realpath(path)) - for path in exceptions - ] - AbstractSandbox.__init__(self) - - def _violation(self, operation, *args, **kw): - from setuptools.sandbox import SandboxViolation - raise SandboxViolation(operation, args, kw) - - if _file: - - def _file(self, path, mode='r', *args, **kw): - if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): - self._violation("file", path, mode, *args, **kw) - return _file(path, mode, *args, **kw) - - def _open(self, path, mode='r', *args, **kw): - if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): - self._violation("open", path, mode, *args, **kw) - return _open(path, mode, *args, **kw) - - def tmpnam(self): - self._violation("tmpnam") - - def _ok(self, path): - active = self._active - try: - self._active = False - realpath = os.path.normcase(os.path.realpath(path)) - return ( - self._exempted(realpath) - or realpath == self._sandbox - or realpath.startswith(self._prefix) - ) - finally: - self._active = active - - def _exempted(self, filepath): - start_matches = ( - filepath.startswith(exception) - for exception in self._exceptions - ) - pattern_matches = ( - re.match(pattern, filepath) - for pattern in self._exception_patterns - ) - candidates = itertools.chain(start_matches, pattern_matches) - return any(candidates) - - def _remap_input(self, operation, path, *args, **kw): - """Called for path inputs""" - if operation in self.write_ops and not self._ok(path): - self._violation(operation, os.path.realpath(path), *args, **kw) - return path - - def _remap_pair(self, operation, src, dst, *args, **kw): - """Called for path pairs like rename, link, and symlink operations""" - if not self._ok(src) or not self._ok(dst): - self._violation(operation, src, dst, *args, **kw) - return (src, dst) - - def open(self, file, flags, mode=0o777, *args, **kw): - """Called for low-level os.open()""" - if flags & WRITE_FLAGS and not self._ok(file): - self._violation("os.open", file, flags, mode, *args, **kw) - return _os.open(file, flags, mode, *args, **kw) - - -WRITE_FLAGS = functools.reduce( - operator.or_, [getattr(_os, a, 0) for a in - "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()] -) - - -class SandboxViolation(DistutilsError): - """A setup script attempted to modify the filesystem outside the sandbox""" - - tmpl = textwrap.dedent(""" - SandboxViolation: {cmd}{args!r} {kwargs} - - The package setup script has attempted to modify files on your system - that are not within the EasyInstall build area, and has been aborted. - - This package cannot be safely installed by EasyInstall, and may not - support alternate installation locations even if you run its setup - script by hand. Please inform the package's author and the EasyInstall - maintainers to find out if a fix or workaround is available. - """).lstrip() - - def __str__(self): - cmd, args, kwargs = self.args - return self.tmpl.format(**locals()) diff --git a/lib/setuptools/script (dev).tmpl b/lib/setuptools/script (dev).tmpl deleted file mode 100644 index 39a24b0..0000000 --- a/lib/setuptools/script (dev).tmpl +++ /dev/null @@ -1,6 +0,0 @@ -# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r -__requires__ = %(spec)r -__import__('pkg_resources').require(%(spec)r) -__file__ = %(dev_path)r -with open(__file__) as f: - exec(compile(f.read(), __file__, 'exec')) diff --git a/lib/setuptools/script.tmpl b/lib/setuptools/script.tmpl deleted file mode 100644 index ff5efbc..0000000 --- a/lib/setuptools/script.tmpl +++ /dev/null @@ -1,3 +0,0 @@ -# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r -__requires__ = %(spec)r -__import__('pkg_resources').run_script(%(spec)r, %(script_name)r) diff --git a/lib/setuptools/site-patch.py b/lib/setuptools/site-patch.py deleted file mode 100644 index 40b00de..0000000 --- a/lib/setuptools/site-patch.py +++ /dev/null @@ -1,74 +0,0 @@ -def __boot(): - import sys - import os - PYTHONPATH = os.environ.get('PYTHONPATH') - if PYTHONPATH is None or (sys.platform == 'win32' and not PYTHONPATH): - PYTHONPATH = [] - else: - PYTHONPATH = PYTHONPATH.split(os.pathsep) - - pic = getattr(sys, 'path_importer_cache', {}) - stdpath = sys.path[len(PYTHONPATH):] - mydir = os.path.dirname(__file__) - - for item in stdpath: - if item == mydir or not item: - continue # skip if current dir. on Windows, or my own directory - importer = pic.get(item) - if importer is not None: - loader = importer.find_module('site') - if loader is not None: - # This should actually reload the current module - loader.load_module('site') - break - else: - try: - import imp # Avoid import loop in Python 3 - stream, path, descr = imp.find_module('site', [item]) - except ImportError: - continue - if stream is None: - continue - try: - # This should actually reload the current module - imp.load_module('site', stream, path, descr) - finally: - stream.close() - break - else: - raise ImportError("Couldn't find the real 'site' module") - - known_paths = dict([(makepath(item)[1], 1) for item in sys.path]) # 2.2 comp - - oldpos = getattr(sys, '__egginsert', 0) # save old insertion position - sys.__egginsert = 0 # and reset the current one - - for item in PYTHONPATH: - addsitedir(item) - - sys.__egginsert += oldpos # restore effective old position - - d, nd = makepath(stdpath[0]) - insert_at = None - new_path = [] - - for item in sys.path: - p, np = makepath(item) - - if np == nd and insert_at is None: - # We've hit the first 'system' path entry, so added entries go here - insert_at = len(new_path) - - if np in known_paths or insert_at is None: - new_path.append(item) - else: - # new path after the insert point, back-insert it - new_path.insert(insert_at, item) - insert_at += 1 - - sys.path[:] = new_path - - -if __name__ == 'site': - __boot() - del __boot diff --git a/lib/setuptools/ssl_support.py b/lib/setuptools/ssl_support.py deleted file mode 100644 index 6362f1f..0000000 --- a/lib/setuptools/ssl_support.py +++ /dev/null @@ -1,260 +0,0 @@ -import os -import socket -import atexit -import re -import functools - -from setuptools.extern.six.moves import urllib, http_client, map, filter - -from pkg_resources import ResolutionError, ExtractionError - -try: - import ssl -except ImportError: - ssl = None - -__all__ = [ - 'VerifyingHTTPSHandler', 'find_ca_bundle', 'is_available', 'cert_paths', - 'opener_for' -] - -cert_paths = """ -/etc/pki/tls/certs/ca-bundle.crt -/etc/ssl/certs/ca-certificates.crt -/usr/share/ssl/certs/ca-bundle.crt -/usr/local/share/certs/ca-root.crt -/etc/ssl/cert.pem -/System/Library/OpenSSL/certs/cert.pem -/usr/local/share/certs/ca-root-nss.crt -/etc/ssl/ca-bundle.pem -""".strip().split() - -try: - HTTPSHandler = urllib.request.HTTPSHandler - HTTPSConnection = http_client.HTTPSConnection -except AttributeError: - HTTPSHandler = HTTPSConnection = object - -is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection) - - -try: - from ssl import CertificateError, match_hostname -except ImportError: - try: - from backports.ssl_match_hostname import CertificateError - from backports.ssl_match_hostname import match_hostname - except ImportError: - CertificateError = None - match_hostname = None - -if not CertificateError: - - class CertificateError(ValueError): - pass - - -if not match_hostname: - - def _dnsname_match(dn, hostname, max_wildcards=1): - """Matching according to RFC 6125, section 6.4.3 - - http://tools.ietf.org/html/rfc6125#section-6.4.3 - """ - pats = [] - if not dn: - return False - - # Ported from python3-syntax: - # leftmost, *remainder = dn.split(r'.') - parts = dn.split(r'.') - leftmost = parts[0] - remainder = parts[1:] - - wildcards = leftmost.count('*') - if wildcards > max_wildcards: - # Issue #17980: avoid denials of service by refusing more - # than one wildcard per fragment. A survey of established - # policy among SSL implementations showed it to be a - # reasonable choice. - raise CertificateError( - "too many wildcards in certificate DNS name: " + repr(dn)) - - # speed up common case w/o wildcards - if not wildcards: - return dn.lower() == hostname.lower() - - # RFC 6125, section 6.4.3, subitem 1. - # The client SHOULD NOT attempt to match a presented identifier in which - # the wildcard character comprises a label other than the left-most label. - if leftmost == '*': - # When '*' is a fragment by itself, it matches a non-empty dotless - # fragment. - pats.append('[^.]+') - elif leftmost.startswith('xn--') or hostname.startswith('xn--'): - # RFC 6125, section 6.4.3, subitem 3. - # The client SHOULD NOT attempt to match a presented identifier - # where the wildcard character is embedded within an A-label or - # U-label of an internationalized domain name. - pats.append(re.escape(leftmost)) - else: - # Otherwise, '*' matches any dotless string, e.g. www* - pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) - - # add the remaining fragments, ignore any wildcards - for frag in remainder: - pats.append(re.escape(frag)) - - pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) - return pat.match(hostname) - - def match_hostname(cert, hostname): - """Verify that *cert* (in decoded format as returned by - SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 - rules are followed, but IP addresses are not accepted for *hostname*. - - CertificateError is raised on failure. On success, the function - returns nothing. - """ - if not cert: - raise ValueError("empty or no certificate") - dnsnames = [] - san = cert.get('subjectAltName', ()) - for key, value in san: - if key == 'DNS': - if _dnsname_match(value, hostname): - return - dnsnames.append(value) - if not dnsnames: - # The subject is only checked when there is no dNSName entry - # in subjectAltName - for sub in cert.get('subject', ()): - for key, value in sub: - # XXX according to RFC 2818, the most specific Common Name - # must be used. - if key == 'commonName': - if _dnsname_match(value, hostname): - return - dnsnames.append(value) - if len(dnsnames) > 1: - raise CertificateError("hostname %r " - "doesn't match either of %s" - % (hostname, ', '.join(map(repr, dnsnames)))) - elif len(dnsnames) == 1: - raise CertificateError("hostname %r " - "doesn't match %r" - % (hostname, dnsnames[0])) - else: - raise CertificateError("no appropriate commonName or " - "subjectAltName fields were found") - - -class VerifyingHTTPSHandler(HTTPSHandler): - """Simple verifying handler: no auth, subclasses, timeouts, etc.""" - - def __init__(self, ca_bundle): - self.ca_bundle = ca_bundle - HTTPSHandler.__init__(self) - - def https_open(self, req): - return self.do_open( - lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req - ) - - -class VerifyingHTTPSConn(HTTPSConnection): - """Simple verifying connection: no auth, subclasses, timeouts, etc.""" - - def __init__(self, host, ca_bundle, **kw): - HTTPSConnection.__init__(self, host, **kw) - self.ca_bundle = ca_bundle - - def connect(self): - sock = socket.create_connection( - (self.host, self.port), getattr(self, 'source_address', None) - ) - - # Handle the socket if a (proxy) tunnel is present - if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None): - self.sock = sock - self._tunnel() - # http://bugs.python.org/issue7776: Python>=3.4.1 and >=2.7.7 - # change self.host to mean the proxy server host when tunneling is - # being used. Adapt, since we are interested in the destination - # host for the match_hostname() comparison. - actual_host = self._tunnel_host - else: - actual_host = self.host - - if hasattr(ssl, 'create_default_context'): - ctx = ssl.create_default_context(cafile=self.ca_bundle) - self.sock = ctx.wrap_socket(sock, server_hostname=actual_host) - else: - # This is for python < 2.7.9 and < 3.4? - self.sock = ssl.wrap_socket( - sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle - ) - try: - match_hostname(self.sock.getpeercert(), actual_host) - except CertificateError: - self.sock.shutdown(socket.SHUT_RDWR) - self.sock.close() - raise - - -def opener_for(ca_bundle=None): - """Get a urlopen() replacement that uses ca_bundle for verification""" - return urllib.request.build_opener( - VerifyingHTTPSHandler(ca_bundle or find_ca_bundle()) - ).open - - -# from jaraco.functools -def once(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - if not hasattr(func, 'always_returns'): - func.always_returns = func(*args, **kwargs) - return func.always_returns - return wrapper - - -@once -def get_win_certfile(): - try: - import wincertstore - except ImportError: - return None - - class CertFile(wincertstore.CertFile): - def __init__(self): - super(CertFile, self).__init__() - atexit.register(self.close) - - def close(self): - try: - super(CertFile, self).close() - except OSError: - pass - - _wincerts = CertFile() - _wincerts.addstore('CA') - _wincerts.addstore('ROOT') - return _wincerts.name - - -def find_ca_bundle(): - """Return an existing CA bundle path, or None""" - extant_cert_paths = filter(os.path.isfile, cert_paths) - return ( - get_win_certfile() - or next(extant_cert_paths, None) - or _certifi_where() - ) - - -def _certifi_where(): - try: - return __import__('certifi').where() - except (ImportError, ResolutionError, ExtractionError): - pass diff --git a/lib/setuptools/unicode_utils.py b/lib/setuptools/unicode_utils.py deleted file mode 100644 index 7c63efd..0000000 --- a/lib/setuptools/unicode_utils.py +++ /dev/null @@ -1,44 +0,0 @@ -import unicodedata -import sys - -from setuptools.extern import six - - -# HFS Plus uses decomposed UTF-8 -def decompose(path): - if isinstance(path, six.text_type): - return unicodedata.normalize('NFD', path) - try: - path = path.decode('utf-8') - path = unicodedata.normalize('NFD', path) - path = path.encode('utf-8') - except UnicodeError: - pass # Not UTF-8 - return path - - -def filesys_decode(path): - """ - Ensure that the given path is decoded, - NONE when no expected encoding works - """ - - if isinstance(path, six.text_type): - return path - - fs_enc = sys.getfilesystemencoding() or 'utf-8' - candidates = fs_enc, 'utf-8' - - for enc in candidates: - try: - return path.decode(enc) - except UnicodeDecodeError: - continue - - -def try_encode(string, enc): - "turn unicode encoding into a functional routine" - try: - return string.encode(enc) - except UnicodeEncodeError: - return None diff --git a/lib/setuptools/version.py b/lib/setuptools/version.py deleted file mode 100644 index 95e1869..0000000 --- a/lib/setuptools/version.py +++ /dev/null @@ -1,6 +0,0 @@ -import pkg_resources - -try: - __version__ = pkg_resources.get_distribution('setuptools').version -except Exception: - __version__ = 'unknown' diff --git a/lib/setuptools/wheel.py b/lib/setuptools/wheel.py deleted file mode 100644 index 95a794a..0000000 --- a/lib/setuptools/wheel.py +++ /dev/null @@ -1,210 +0,0 @@ -"""Wheels support.""" - -from distutils.util import get_platform -import email -import itertools -import os -import posixpath -import re -import zipfile - -from pkg_resources import Distribution, PathMetadata, parse_version -from setuptools.extern.packaging.utils import canonicalize_name -from setuptools.extern.six import PY3 -from setuptools import Distribution as SetuptoolsDistribution -from setuptools import pep425tags -from setuptools.command.egg_info import write_requirements - - -__metaclass__ = type - - -WHEEL_NAME = re.compile( - r"""^(?P<project_name>.+?)-(?P<version>\d.*?) - ((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?) - )\.whl$""", - re.VERBOSE).match - -NAMESPACE_PACKAGE_INIT = '''\ -try: - __import__('pkg_resources').declare_namespace(__name__) -except ImportError: - __path__ = __import__('pkgutil').extend_path(__path__, __name__) -''' - - -def unpack(src_dir, dst_dir): - '''Move everything under `src_dir` to `dst_dir`, and delete the former.''' - for dirpath, dirnames, filenames in os.walk(src_dir): - subdir = os.path.relpath(dirpath, src_dir) - for f in filenames: - src = os.path.join(dirpath, f) - dst = os.path.join(dst_dir, subdir, f) - os.renames(src, dst) - for n, d in reversed(list(enumerate(dirnames))): - src = os.path.join(dirpath, d) - dst = os.path.join(dst_dir, subdir, d) - if not os.path.exists(dst): - # Directory does not exist in destination, - # rename it and prune it from os.walk list. - os.renames(src, dst) - del dirnames[n] - # Cleanup. - for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True): - assert not filenames - os.rmdir(dirpath) - - -class Wheel: - - def __init__(self, filename): - match = WHEEL_NAME(os.path.basename(filename)) - if match is None: - raise ValueError('invalid wheel name: %r' % filename) - self.filename = filename - for k, v in match.groupdict().items(): - setattr(self, k, v) - - def tags(self): - '''List tags (py_version, abi, platform) supported by this wheel.''' - return itertools.product( - self.py_version.split('.'), - self.abi.split('.'), - self.platform.split('.'), - ) - - def is_compatible(self): - '''Is the wheel is compatible with the current platform?''' - supported_tags = pep425tags.get_supported() - return next((True for t in self.tags() if t in supported_tags), False) - - def egg_name(self): - return Distribution( - project_name=self.project_name, version=self.version, - platform=(None if self.platform == 'any' else get_platform()), - ).egg_name() + '.egg' - - def get_dist_info(self, zf): - # find the correct name of the .dist-info dir in the wheel file - for member in zf.namelist(): - dirname = posixpath.dirname(member) - if (dirname.endswith('.dist-info') and - canonicalize_name(dirname).startswith( - canonicalize_name(self.project_name))): - return dirname - raise ValueError("unsupported wheel format. .dist-info not found") - - def install_as_egg(self, destination_eggdir): - '''Install wheel as an egg directory.''' - with zipfile.ZipFile(self.filename) as zf: - self._install_as_egg(destination_eggdir, zf) - - def _install_as_egg(self, destination_eggdir, zf): - dist_basename = '%s-%s' % (self.project_name, self.version) - dist_info = self.get_dist_info(zf) - dist_data = '%s.data' % dist_basename - egg_info = os.path.join(destination_eggdir, 'EGG-INFO') - - self._convert_metadata(zf, destination_eggdir, dist_info, egg_info) - self._move_data_entries(destination_eggdir, dist_data) - self._fix_namespace_packages(egg_info, destination_eggdir) - - @staticmethod - def _convert_metadata(zf, destination_eggdir, dist_info, egg_info): - def get_metadata(name): - with zf.open(posixpath.join(dist_info, name)) as fp: - value = fp.read().decode('utf-8') if PY3 else fp.read() - return email.parser.Parser().parsestr(value) - - wheel_metadata = get_metadata('WHEEL') - # Check wheel format version is supported. - wheel_version = parse_version(wheel_metadata.get('Wheel-Version')) - wheel_v1 = ( - parse_version('1.0') <= wheel_version < parse_version('2.0dev0') - ) - if not wheel_v1: - raise ValueError( - 'unsupported wheel format version: %s' % wheel_version) - # Extract to target directory. - os.mkdir(destination_eggdir) - zf.extractall(destination_eggdir) - # Convert metadata. - dist_info = os.path.join(destination_eggdir, dist_info) - dist = Distribution.from_location( - destination_eggdir, dist_info, - metadata=PathMetadata(destination_eggdir, dist_info), - ) - - # Note: Evaluate and strip markers now, - # as it's difficult to convert back from the syntax: - # foobar; "linux" in sys_platform and extra == 'test' - def raw_req(req): - req.marker = None - return str(req) - install_requires = list(sorted(map(raw_req, dist.requires()))) - extras_require = { - extra: sorted( - req - for req in map(raw_req, dist.requires((extra,))) - if req not in install_requires - ) - for extra in dist.extras - } - os.rename(dist_info, egg_info) - os.rename( - os.path.join(egg_info, 'METADATA'), - os.path.join(egg_info, 'PKG-INFO'), - ) - setup_dist = SetuptoolsDistribution( - attrs=dict( - install_requires=install_requires, - extras_require=extras_require, - ), - ) - write_requirements( - setup_dist.get_command_obj('egg_info'), - None, - os.path.join(egg_info, 'requires.txt'), - ) - - @staticmethod - def _move_data_entries(destination_eggdir, dist_data): - """Move data entries to their correct location.""" - dist_data = os.path.join(destination_eggdir, dist_data) - dist_data_scripts = os.path.join(dist_data, 'scripts') - if os.path.exists(dist_data_scripts): - egg_info_scripts = os.path.join( - destination_eggdir, 'EGG-INFO', 'scripts') - os.mkdir(egg_info_scripts) - for entry in os.listdir(dist_data_scripts): - # Remove bytecode, as it's not properly handled - # during easy_install scripts install phase. - if entry.endswith('.pyc'): - os.unlink(os.path.join(dist_data_scripts, entry)) - else: - os.rename( - os.path.join(dist_data_scripts, entry), - os.path.join(egg_info_scripts, entry), - ) - os.rmdir(dist_data_scripts) - for subdir in filter(os.path.exists, ( - os.path.join(dist_data, d) - for d in ('data', 'headers', 'purelib', 'platlib') - )): - unpack(subdir, destination_eggdir) - if os.path.exists(dist_data): - os.rmdir(dist_data) - - @staticmethod - def _fix_namespace_packages(egg_info, destination_eggdir): - namespace_packages = os.path.join( - egg_info, 'namespace_packages.txt') - if os.path.exists(namespace_packages): - with open(namespace_packages) as fp: - namespace_packages = fp.read().split() - for mod in namespace_packages: - mod_dir = os.path.join(destination_eggdir, *mod.split('.')) - mod_init = os.path.join(mod_dir, '__init__.py') - if os.path.exists(mod_dir) and not os.path.exists(mod_init): - with open(mod_init, 'w') as fp: - fp.write(NAMESPACE_PACKAGE_INIT) diff --git a/lib/setuptools/windows_support.py b/lib/setuptools/windows_support.py deleted file mode 100644 index cb977cf..0000000 --- a/lib/setuptools/windows_support.py +++ /dev/null @@ -1,29 +0,0 @@ -import platform -import ctypes - - -def windows_only(func): - if platform.system() != 'Windows': - return lambda *args, **kwargs: None - return func - - -@windows_only -def hide_file(path): - """ - Set the hidden attribute on a file or directory. - - From http://stackoverflow.com/questions/19622133/ - - `path` must be text. - """ - __import__('ctypes.wintypes') - SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW - SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD - SetFileAttributes.restype = ctypes.wintypes.BOOL - - FILE_ATTRIBUTE_HIDDEN = 0x02 - - ret = SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN) - if not ret: - raise ctypes.WinError() diff --git a/lib/six.py b/lib/six.py deleted file mode 100644 index 6bf4fd3..0000000 --- a/lib/six.py +++ /dev/null @@ -1,891 +0,0 @@ -# Copyright (c) 2010-2017 Benjamin Peterson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -"""Utilities for writing code that runs on Python 2 and 3""" - -from __future__ import absolute_import - -import functools -import itertools -import operator -import sys -import types - -__author__ = "Benjamin Peterson <benjamin@python.org>" -__version__ = "1.11.0" - - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -PY34 = sys.version_info[0:2] >= (3, 4) - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) # Invokes __set__. - try: - # This is a bit ugly, but it avoids running this again by - # removing this descriptor. - delattr(obj.__class__, self.name) - except AttributeError: - pass - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - def __getattr__(self, attr): - _module = self._resolve() - value = getattr(_module, attr) - setattr(self, attr, value) - return value - - -class _LazyModule(types.ModuleType): - - def __init__(self, name): - super(_LazyModule, self).__init__(name) - self.__doc__ = self.__class__.__doc__ - - def __dir__(self): - attrs = ["__doc__", "__name__"] - attrs += [attr.name for attr in self._moved_attributes] - return attrs - - # Subclasses should override this - _moved_attributes = [] - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _SixMetaPathImporter(object): - - """ - A meta path importer to import six.moves and its submodules. - - This class implements a PEP302 finder and loader. It should be compatible - with Python 2.5 and all existing versions of Python3 - """ - - def __init__(self, six_module_name): - self.name = six_module_name - self.known_modules = {} - - def _add_module(self, mod, *fullnames): - for fullname in fullnames: - self.known_modules[self.name + "." + fullname] = mod - - def _get_module(self, fullname): - return self.known_modules[self.name + "." + fullname] - - def find_module(self, fullname, path=None): - if fullname in self.known_modules: - return self - return None - - def __get_module(self, fullname): - try: - return self.known_modules[fullname] - except KeyError: - raise ImportError("This loader does not know module " + fullname) - - def load_module(self, fullname): - try: - # in case of a reload - return sys.modules[fullname] - except KeyError: - pass - mod = self.__get_module(fullname) - if isinstance(mod, MovedModule): - mod = mod._resolve() - else: - mod.__loader__ = self - sys.modules[fullname] = mod - return mod - - def is_package(self, fullname): - """ - Return true, if the named module is a package. - - We need this method to get correct spec objects with - Python 3.4 (see PEP451) - """ - return hasattr(self.__get_module(fullname), "__path__") - - def get_code(self, fullname): - """Return None - - Required, if is_package is implemented""" - self.__get_module(fullname) # eventually raises ImportError - return None - get_source = get_code # same as get_code - -_importer = _SixMetaPathImporter(__name__) - - -class _MovedItems(_LazyModule): - - """Lazy loading of moved objects""" - __path__ = [] # mark as package - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("intern", "__builtin__", "sys"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), - MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), - MovedAttribute("getoutput", "commands", "subprocess"), - MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("UserDict", "UserDict", "collections"), - MovedAttribute("UserList", "UserList", "collections"), - MovedAttribute("UserString", "UserString", "collections"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("copyreg", "copy_reg"), - MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("_thread", "thread", "_thread"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), - MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), - MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), - MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), -] -# Add windows specific modules. -if sys.platform == "win32": - _moved_attributes += [ - MovedModule("winreg", "_winreg"), - ] - -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) - if isinstance(attr, MovedModule): - _importer._add_module(attr, "moves." + attr.name) -del attr - -_MovedItems._moved_attributes = _moved_attributes - -moves = _MovedItems(__name__ + ".moves") -_importer._add_module(moves, "moves") - - -class Module_six_moves_urllib_parse(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_parse""" - - -_urllib_parse_moved_attributes = [ - MovedAttribute("ParseResult", "urlparse", "urllib.parse"), - MovedAttribute("SplitResult", "urlparse", "urllib.parse"), - MovedAttribute("parse_qs", "urlparse", "urllib.parse"), - MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), - MovedAttribute("urldefrag", "urlparse", "urllib.parse"), - MovedAttribute("urljoin", "urlparse", "urllib.parse"), - MovedAttribute("urlparse", "urlparse", "urllib.parse"), - MovedAttribute("urlsplit", "urlparse", "urllib.parse"), - MovedAttribute("urlunparse", "urlparse", "urllib.parse"), - MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), - MovedAttribute("quote", "urllib", "urllib.parse"), - MovedAttribute("quote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote", "urllib", "urllib.parse"), - MovedAttribute("unquote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), - MovedAttribute("urlencode", "urllib", "urllib.parse"), - MovedAttribute("splitquery", "urllib", "urllib.parse"), - MovedAttribute("splittag", "urllib", "urllib.parse"), - MovedAttribute("splituser", "urllib", "urllib.parse"), - MovedAttribute("splitvalue", "urllib", "urllib.parse"), - MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), - MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), - MovedAttribute("uses_params", "urlparse", "urllib.parse"), - MovedAttribute("uses_query", "urlparse", "urllib.parse"), - MovedAttribute("uses_relative", "urlparse", "urllib.parse"), -] -for attr in _urllib_parse_moved_attributes: - setattr(Module_six_moves_urllib_parse, attr.name, attr) -del attr - -Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes - -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") - - -class Module_six_moves_urllib_error(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_error""" - - -_urllib_error_moved_attributes = [ - MovedAttribute("URLError", "urllib2", "urllib.error"), - MovedAttribute("HTTPError", "urllib2", "urllib.error"), - MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), -] -for attr in _urllib_error_moved_attributes: - setattr(Module_six_moves_urllib_error, attr.name, attr) -del attr - -Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes - -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") - - -class Module_six_moves_urllib_request(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_request""" - - -_urllib_request_moved_attributes = [ - MovedAttribute("urlopen", "urllib2", "urllib.request"), - MovedAttribute("install_opener", "urllib2", "urllib.request"), - MovedAttribute("build_opener", "urllib2", "urllib.request"), - MovedAttribute("pathname2url", "urllib", "urllib.request"), - MovedAttribute("url2pathname", "urllib", "urllib.request"), - MovedAttribute("getproxies", "urllib", "urllib.request"), - MovedAttribute("Request", "urllib2", "urllib.request"), - MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), - MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), - MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), - MovedAttribute("BaseHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), - MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), - MovedAttribute("FileHandler", "urllib2", "urllib.request"), - MovedAttribute("FTPHandler", "urllib2", "urllib.request"), - MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), - MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), - MovedAttribute("urlretrieve", "urllib", "urllib.request"), - MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), - MovedAttribute("proxy_bypass", "urllib", "urllib.request"), - MovedAttribute("parse_http_list", "urllib2", "urllib.request"), - MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), -] -for attr in _urllib_request_moved_attributes: - setattr(Module_six_moves_urllib_request, attr.name, attr) -del attr - -Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes - -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") - - -class Module_six_moves_urllib_response(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_response""" - - -_urllib_response_moved_attributes = [ - MovedAttribute("addbase", "urllib", "urllib.response"), - MovedAttribute("addclosehook", "urllib", "urllib.response"), - MovedAttribute("addinfo", "urllib", "urllib.response"), - MovedAttribute("addinfourl", "urllib", "urllib.response"), -] -for attr in _urllib_response_moved_attributes: - setattr(Module_six_moves_urllib_response, attr.name, attr) -del attr - -Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes - -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") - - -class Module_six_moves_urllib_robotparser(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_robotparser""" - - -_urllib_robotparser_moved_attributes = [ - MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), -] -for attr in _urllib_robotparser_moved_attributes: - setattr(Module_six_moves_urllib_robotparser, attr.name, attr) -del attr - -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes - -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") - - -class Module_six_moves_urllib(types.ModuleType): - - """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" - __path__ = [] # mark as package - parse = _importer._get_module("moves.urllib_parse") - error = _importer._get_module("moves.urllib_error") - request = _importer._get_module("moves.urllib_request") - response = _importer._get_module("moves.urllib_response") - robotparser = _importer._get_module("moves.urllib_robotparser") - - def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] - -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - - -if PY3: - def get_unbound_function(unbound): - return unbound - - create_bound_method = types.MethodType - - def create_unbound_method(func, cls): - return func - - Iterator = object -else: - def get_unbound_function(unbound): - return unbound.im_func - - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - - def create_unbound_method(func, cls): - return types.MethodType(func, None, cls) - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -if PY3: - def iterkeys(d, **kw): - return iter(d.keys(**kw)) - - def itervalues(d, **kw): - return iter(d.values(**kw)) - - def iteritems(d, **kw): - return iter(d.items(**kw)) - - def iterlists(d, **kw): - return iter(d.lists(**kw)) - - viewkeys = operator.methodcaller("keys") - - viewvalues = operator.methodcaller("values") - - viewitems = operator.methodcaller("items") -else: - def iterkeys(d, **kw): - return d.iterkeys(**kw) - - def itervalues(d, **kw): - return d.itervalues(**kw) - - def iteritems(d, **kw): - return d.iteritems(**kw) - - def iterlists(d, **kw): - return d.iterlists(**kw) - - viewkeys = operator.methodcaller("viewkeys") - - viewvalues = operator.methodcaller("viewvalues") - - viewitems = operator.methodcaller("viewitems") - -_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") -_add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - unichr = chr - import struct - int2byte = struct.Struct(">B").pack - del struct - byte2int = operator.itemgetter(0) - indexbytes = operator.getitem - iterbytes = iter - import io - StringIO = io.StringIO - BytesIO = io.BytesIO - _assertCountEqual = "assertCountEqual" - if sys.version_info[1] <= 1: - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - else: - _assertRaisesRegex = "assertRaisesRegex" - _assertRegex = "assertRegex" -else: - def b(s): - return s - # Workaround for standalone backslash - - def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") - unichr = unichr - int2byte = chr - - def byte2int(bs): - return ord(bs[0]) - - def indexbytes(buf, i): - return ord(buf[i]) - iterbytes = functools.partial(itertools.imap, ord) - import StringIO - StringIO = BytesIO = StringIO.StringIO - _assertCountEqual = "assertItemsEqual" - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -def assertCountEqual(self, *args, **kwargs): - return getattr(self, _assertCountEqual)(*args, **kwargs) - - -def assertRaisesRegex(self, *args, **kwargs): - return getattr(self, _assertRaisesRegex)(*args, **kwargs) - - -def assertRegex(self, *args, **kwargs): - return getattr(self, _assertRegex)(*args, **kwargs) - - -if PY3: - exec_ = getattr(moves.builtins, "exec") - - def reraise(tp, value, tb=None): - try: - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - finally: - value = None - tb = None - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - try: - raise tp, value, tb - finally: - tb = None -""") - - -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - try: - if from_value is None: - raise value - raise value from from_value - finally: - value = None -""") -elif sys.version_info[:2] > (3, 2): - exec_("""def raise_from(value, from_value): - try: - raise value from from_value - finally: - value = None -""") -else: - def raise_from(value, from_value): - raise value - - -print_ = getattr(moves.builtins, "print", None) -if print_ is None: - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(fp.encoding, errors) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) -if sys.version_info[:2] < (3, 3): - _print = print_ - - def print_(*args, **kwargs): - fp = kwargs.get("file", sys.stdout) - flush = kwargs.pop("flush", False) - _print(*args, **kwargs) - if flush and fp is not None: - fp.flush() - -_add_doc(reraise, """Reraise an exception.""") - -if sys.version_info[0:2] < (3, 4): - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f - return wrapper -else: - wraps = functools.wraps - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(type): - - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - - @classmethod - def __prepare__(cls, name, this_bases): - return meta.__prepare__(name, bases) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -def python_2_unicode_compatible(klass): - """ - A decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - """ - if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass - - -# Complete the moves implementation. -# This code is at the end of this module to speed up module loading. -# Turn this module into a package. -__path__ = [] # required for PEP 302 and PEP 451 -__package__ = __name__ # see PEP 366 @ReservedAssignment -if globals().get("__spec__") is not None: - __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable -# Remove other six meta path importers, since they cause problems. This can -# happen if six is removed from sys.modules and then reloaded. (Setuptools does -# this for some reason.) -if sys.meta_path: - for i, importer in enumerate(sys.meta_path): - # Here's some real nastiness: Another "instance" of the six module might - # be floating around. Therefore, we can't use isinstance() to check for - # the six meta path importer, since the other six instance will have - # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): - del sys.meta_path[i] - break - del i, importer -# Finally, add the importer to the meta path import hook. -sys.meta_path.append(_importer) diff --git a/lib/typing.py b/lib/typing.py deleted file mode 100644 index 2189cd4..0000000 --- a/lib/typing.py +++ /dev/null @@ -1,2413 +0,0 @@ -import abc -from abc import abstractmethod, abstractproperty -import collections -import contextlib -import functools -import re as stdlib_re # Avoid confusion with the re we export. -import sys -import types -try: - import collections.abc as collections_abc -except ImportError: - import collections as collections_abc # Fallback for PY3.2. -if sys.version_info[:2] >= (3, 6): - import _collections_abc # Needed for private function _check_methods # noqa -try: - from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType -except ImportError: - WrapperDescriptorType = type(object.__init__) - MethodWrapperType = type(object().__str__) - MethodDescriptorType = type(str.join) - - -# Please keep __all__ alphabetized within each category. -__all__ = [ - # Super-special typing primitives. - 'Any', - 'Callable', - 'ClassVar', - 'Generic', - 'Optional', - 'Tuple', - 'Type', - 'TypeVar', - 'Union', - - # ABCs (from collections.abc). - 'AbstractSet', # collections.abc.Set. - 'GenericMeta', # subclass of abc.ABCMeta and a metaclass - # for 'Generic' and ABCs below. - 'ByteString', - 'Container', - 'ContextManager', - 'Hashable', - 'ItemsView', - 'Iterable', - 'Iterator', - 'KeysView', - 'Mapping', - 'MappingView', - 'MutableMapping', - 'MutableSequence', - 'MutableSet', - 'Sequence', - 'Sized', - 'ValuesView', - # The following are added depending on presence - # of their non-generic counterparts in stdlib: - # Awaitable, - # AsyncIterator, - # AsyncIterable, - # Coroutine, - # Collection, - # AsyncGenerator, - # AsyncContextManager - - # Structural checks, a.k.a. protocols. - 'Reversible', - 'SupportsAbs', - 'SupportsBytes', - 'SupportsComplex', - 'SupportsFloat', - 'SupportsInt', - 'SupportsRound', - - # Concrete collection types. - 'Counter', - 'Deque', - 'Dict', - 'DefaultDict', - 'List', - 'Set', - 'FrozenSet', - 'NamedTuple', # Not really a type. - 'Generator', - - # One-off things. - 'AnyStr', - 'cast', - 'get_type_hints', - 'NewType', - 'no_type_check', - 'no_type_check_decorator', - 'NoReturn', - 'overload', - 'Text', - 'TYPE_CHECKING', -] - -# The pseudo-submodules 're' and 'io' are part of the public -# namespace, but excluded from __all__ because they might stomp on -# legitimate imports of those modules. - - -def _qualname(x): - if sys.version_info[:2] >= (3, 3): - return x.__qualname__ - else: - # Fall back to just name. - return x.__name__ - - -def _trim_name(nm): - whitelist = ('_TypeAlias', '_ForwardRef', '_TypingBase', '_FinalTypingBase') - if nm.startswith('_') and nm not in whitelist: - nm = nm[1:] - return nm - - -class TypingMeta(type): - """Metaclass for most types defined in typing module - (not a part of public API). - - This overrides __new__() to require an extra keyword parameter - '_root', which serves as a guard against naive subclassing of the - typing classes. Any legitimate class defined using a metaclass - derived from TypingMeta must pass _root=True. - - This also defines a dummy constructor (all the work for most typing - constructs is done in __new__) and a nicer repr(). - """ - - _is_protocol = False - - def __new__(cls, name, bases, namespace, *, _root=False): - if not _root: - raise TypeError("Cannot subclass %s" % - (', '.join(map(_type_repr, bases)) or '()')) - return super().__new__(cls, name, bases, namespace) - - def __init__(self, *args, **kwds): - pass - - def _eval_type(self, globalns, localns): - """Override this in subclasses to interpret forward references. - - For example, List['C'] is internally stored as - List[_ForwardRef('C')], which should evaluate to List[C], - where C is an object found in globalns or localns (searching - localns first, of course). - """ - return self - - def _get_type_vars(self, tvars): - pass - - def __repr__(self): - qname = _trim_name(_qualname(self)) - return '%s.%s' % (self.__module__, qname) - - -class _TypingBase(metaclass=TypingMeta, _root=True): - """Internal indicator of special typing constructs.""" - - __slots__ = ('__weakref__',) - - def __init__(self, *args, **kwds): - pass - - def __new__(cls, *args, **kwds): - """Constructor. - - This only exists to give a better error message in case - someone tries to subclass a special typing object (not a good idea). - """ - if (len(args) == 3 and - isinstance(args[0], str) and - isinstance(args[1], tuple)): - # Close enough. - raise TypeError("Cannot subclass %r" % cls) - return super().__new__(cls) - - # Things that are not classes also need these. - def _eval_type(self, globalns, localns): - return self - - def _get_type_vars(self, tvars): - pass - - def __repr__(self): - cls = type(self) - qname = _trim_name(_qualname(cls)) - return '%s.%s' % (cls.__module__, qname) - - def __call__(self, *args, **kwds): - raise TypeError("Cannot instantiate %r" % type(self)) - - -class _FinalTypingBase(_TypingBase, _root=True): - """Internal mix-in class to prevent instantiation. - - Prevents instantiation unless _root=True is given in class call. - It is used to create pseudo-singleton instances Any, Union, Optional, etc. - """ - - __slots__ = () - - def __new__(cls, *args, _root=False, **kwds): - self = super().__new__(cls, *args, **kwds) - if _root is True: - return self - raise TypeError("Cannot instantiate %r" % cls) - - def __reduce__(self): - return _trim_name(type(self).__name__) - - -class _ForwardRef(_TypingBase, _root=True): - """Internal wrapper to hold a forward reference.""" - - __slots__ = ('__forward_arg__', '__forward_code__', - '__forward_evaluated__', '__forward_value__') - - def __init__(self, arg): - super().__init__(arg) - if not isinstance(arg, str): - raise TypeError('Forward reference must be a string -- got %r' % (arg,)) - try: - code = compile(arg, '<string>', 'eval') - except SyntaxError: - raise SyntaxError('Forward reference must be an expression -- got %r' % - (arg,)) - self.__forward_arg__ = arg - self.__forward_code__ = code - self.__forward_evaluated__ = False - self.__forward_value__ = None - - def _eval_type(self, globalns, localns): - if not self.__forward_evaluated__ or localns is not globalns: - if globalns is None and localns is None: - globalns = localns = {} - elif globalns is None: - globalns = localns - elif localns is None: - localns = globalns - self.__forward_value__ = _type_check( - eval(self.__forward_code__, globalns, localns), - "Forward references must evaluate to types.") - self.__forward_evaluated__ = True - return self.__forward_value__ - - def __eq__(self, other): - if not isinstance(other, _ForwardRef): - return NotImplemented - return (self.__forward_arg__ == other.__forward_arg__ and - self.__forward_value__ == other.__forward_value__) - - def __hash__(self): - return hash((self.__forward_arg__, self.__forward_value__)) - - def __instancecheck__(self, obj): - raise TypeError("Forward references cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Forward references cannot be used with issubclass().") - - def __repr__(self): - return '_ForwardRef(%r)' % (self.__forward_arg__,) - - -class _TypeAlias(_TypingBase, _root=True): - """Internal helper class for defining generic variants of concrete types. - - Note that this is not a type; let's call it a pseudo-type. It cannot - be used in instance and subclass checks in parameterized form, i.e. - ``isinstance(42, Match[str])`` raises ``TypeError`` instead of returning - ``False``. - """ - - __slots__ = ('name', 'type_var', 'impl_type', 'type_checker') - - def __init__(self, name, type_var, impl_type, type_checker): - """Initializer. - - Args: - name: The name, e.g. 'Pattern'. - type_var: The type parameter, e.g. AnyStr, or the - specific type, e.g. str. - impl_type: The implementation type. - type_checker: Function that takes an impl_type instance. - and returns a value that should be a type_var instance. - """ - assert isinstance(name, str), repr(name) - assert isinstance(impl_type, type), repr(impl_type) - assert not isinstance(impl_type, TypingMeta), repr(impl_type) - assert isinstance(type_var, (type, _TypingBase)), repr(type_var) - self.name = name - self.type_var = type_var - self.impl_type = impl_type - self.type_checker = type_checker - - def __repr__(self): - return "%s[%s]" % (self.name, _type_repr(self.type_var)) - - def __getitem__(self, parameter): - if not isinstance(self.type_var, TypeVar): - raise TypeError("%s cannot be further parameterized." % self) - if self.type_var.__constraints__ and isinstance(parameter, type): - if not issubclass(parameter, self.type_var.__constraints__): - raise TypeError("%s is not a valid substitution for %s." % - (parameter, self.type_var)) - if isinstance(parameter, TypeVar) and parameter is not self.type_var: - raise TypeError("%s cannot be re-parameterized." % self) - return self.__class__(self.name, parameter, - self.impl_type, self.type_checker) - - def __eq__(self, other): - if not isinstance(other, _TypeAlias): - return NotImplemented - return self.name == other.name and self.type_var == other.type_var - - def __hash__(self): - return hash((self.name, self.type_var)) - - def __instancecheck__(self, obj): - if not isinstance(self.type_var, TypeVar): - raise TypeError("Parameterized type aliases cannot be used " - "with isinstance().") - return isinstance(obj, self.impl_type) - - def __subclasscheck__(self, cls): - if not isinstance(self.type_var, TypeVar): - raise TypeError("Parameterized type aliases cannot be used " - "with issubclass().") - return issubclass(cls, self.impl_type) - - -def _get_type_vars(types, tvars): - for t in types: - if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): - t._get_type_vars(tvars) - - -def _type_vars(types): - tvars = [] - _get_type_vars(types, tvars) - return tuple(tvars) - - -def _eval_type(t, globalns, localns): - if isinstance(t, TypingMeta) or isinstance(t, _TypingBase): - return t._eval_type(globalns, localns) - return t - - -def _type_check(arg, msg): - """Check that the argument is a type, and return it (internal helper). - - As a special case, accept None and return type(None) instead. - Also, _TypeAlias instances (e.g. Match, Pattern) are acceptable. - - The msg argument is a human-readable error message, e.g. - - "Union[arg, ...]: arg should be a type." - - We append the repr() of the actual value (truncated to 100 chars). - """ - if arg is None: - return type(None) - if isinstance(arg, str): - arg = _ForwardRef(arg) - if ( - isinstance(arg, _TypingBase) and type(arg).__name__ == '_ClassVar' or - not isinstance(arg, (type, _TypingBase)) and not callable(arg) - ): - raise TypeError(msg + " Got %.100r." % (arg,)) - # Bare Union etc. are not valid as type arguments - if ( - type(arg).__name__ in ('_Union', '_Optional') and - not getattr(arg, '__origin__', None) or - isinstance(arg, TypingMeta) and arg._gorg in (Generic, _Protocol) - ): - raise TypeError("Plain %s is not valid as type argument" % arg) - return arg - - -def _type_repr(obj): - """Return the repr() of an object, special-casing types (internal helper). - - If obj is a type, we return a shorter version than the default - type.__repr__, based on the module and qualified name, which is - typically enough to uniquely identify a type. For everything - else, we fall back on repr(obj). - """ - if isinstance(obj, type) and not isinstance(obj, TypingMeta): - if obj.__module__ == 'builtins': - return _qualname(obj) - return '%s.%s' % (obj.__module__, _qualname(obj)) - if obj is ...: - return('...') - if isinstance(obj, types.FunctionType): - return obj.__name__ - return repr(obj) - - -class _Any(_FinalTypingBase, _root=True): - """Special type indicating an unconstrained type. - - - Any is compatible with every type. - - Any assumed to have all methods. - - All values assumed to be instances of Any. - - Note that all the above statements are true from the point of view of - static type checkers. At runtime, Any should not be used with instance - or class checks. - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("Any cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Any cannot be used with issubclass().") - - -Any = _Any(_root=True) - - -class _NoReturn(_FinalTypingBase, _root=True): - """Special type indicating functions that never return. - Example:: - - from typing import NoReturn - - def stop() -> NoReturn: - raise Exception('no way') - - This type is invalid in other positions, e.g., ``List[NoReturn]`` - will fail in static type checkers. - """ - - __slots__ = () - - def __instancecheck__(self, obj): - raise TypeError("NoReturn cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("NoReturn cannot be used with issubclass().") - - -NoReturn = _NoReturn(_root=True) - - -class TypeVar(_TypingBase, _root=True): - """Type variable. - - Usage:: - - T = TypeVar('T') # Can be anything - A = TypeVar('A', str, bytes) # Must be str or bytes - - Type variables exist primarily for the benefit of static type - checkers. They serve as the parameters for generic types as well - as for generic function definitions. See class Generic for more - information on generic types. Generic functions work as follows: - - def repeat(x: T, n: int) -> List[T]: - '''Return a list containing n references to x.''' - return [x]*n - - def longest(x: A, y: A) -> A: - '''Return the longest of two strings.''' - return x if len(x) >= len(y) else y - - The latter example's signature is essentially the overloading - of (str, str) -> str and (bytes, bytes) -> bytes. Also note - that if the arguments are instances of some subclass of str, - the return type is still plain str. - - At runtime, isinstance(x, T) and issubclass(C, T) will raise TypeError. - - Type variables defined with covariant=True or contravariant=True - can be used do declare covariant or contravariant generic types. - See PEP 484 for more details. By default generic types are invariant - in all type variables. - - Type variables can be introspected. e.g.: - - T.__name__ == 'T' - T.__constraints__ == () - T.__covariant__ == False - T.__contravariant__ = False - A.__constraints__ == (str, bytes) - """ - - __slots__ = ('__name__', '__bound__', '__constraints__', - '__covariant__', '__contravariant__') - - def __init__(self, name, *constraints, bound=None, - covariant=False, contravariant=False): - super().__init__(name, *constraints, bound=bound, - covariant=covariant, contravariant=contravariant) - self.__name__ = name - if covariant and contravariant: - raise ValueError("Bivariant types are not supported.") - self.__covariant__ = bool(covariant) - self.__contravariant__ = bool(contravariant) - if constraints and bound is not None: - raise TypeError("Constraints cannot be combined with bound=...") - if constraints and len(constraints) == 1: - raise TypeError("A single constraint is not allowed") - msg = "TypeVar(name, constraint, ...): constraints must be types." - self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) - if bound: - self.__bound__ = _type_check(bound, "Bound must be a type.") - else: - self.__bound__ = None - - def _get_type_vars(self, tvars): - if self not in tvars: - tvars.append(self) - - def __repr__(self): - if self.__covariant__: - prefix = '+' - elif self.__contravariant__: - prefix = '-' - else: - prefix = '~' - return prefix + self.__name__ - - def __instancecheck__(self, instance): - raise TypeError("Type variables cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Type variables cannot be used with issubclass().") - - -# Some unconstrained type variables. These are used by the container types. -# (These are not for export.) -T = TypeVar('T') # Any type. -KT = TypeVar('KT') # Key type. -VT = TypeVar('VT') # Value type. -T_co = TypeVar('T_co', covariant=True) # Any type covariant containers. -V_co = TypeVar('V_co', covariant=True) # Any type covariant containers. -VT_co = TypeVar('VT_co', covariant=True) # Value type covariant containers. -T_contra = TypeVar('T_contra', contravariant=True) # Ditto contravariant. - -# A useful type variable with constraints. This represents string types. -# (This one *is* for export!) -AnyStr = TypeVar('AnyStr', bytes, str) - - -def _replace_arg(arg, tvars, args): - """An internal helper function: replace arg if it is a type variable - found in tvars with corresponding substitution from args or - with corresponding substitution sub-tree if arg is a generic type. - """ - - if tvars is None: - tvars = [] - if hasattr(arg, '_subs_tree') and isinstance(arg, (GenericMeta, _TypingBase)): - return arg._subs_tree(tvars, args) - if isinstance(arg, TypeVar): - for i, tvar in enumerate(tvars): - if arg == tvar: - return args[i] - return arg - - -# Special typing constructs Union, Optional, Generic, Callable and Tuple -# use three special attributes for internal bookkeeping of generic types: -# * __parameters__ is a tuple of unique free type parameters of a generic -# type, for example, Dict[T, T].__parameters__ == (T,); -# * __origin__ keeps a reference to a type that was subscripted, -# e.g., Union[T, int].__origin__ == Union; -# * __args__ is a tuple of all arguments used in subscripting, -# e.g., Dict[T, int].__args__ == (T, int). - - -def _subs_tree(cls, tvars=None, args=None): - """An internal helper function: calculate substitution tree - for generic cls after replacing its type parameters with - substitutions in tvars -> args (if any). - Repeat the same following __origin__'s. - - Return a list of arguments with all possible substitutions - performed. Arguments that are generic classes themselves are represented - as tuples (so that no new classes are created by this function). - For example: _subs_tree(List[Tuple[int, T]][str]) == [(Tuple, int, str)] - """ - - if cls.__origin__ is None: - return cls - # Make of chain of origins (i.e. cls -> cls.__origin__) - current = cls.__origin__ - orig_chain = [] - while current.__origin__ is not None: - orig_chain.append(current) - current = current.__origin__ - # Replace type variables in __args__ if asked ... - tree_args = [] - for arg in cls.__args__: - tree_args.append(_replace_arg(arg, tvars, args)) - # ... then continue replacing down the origin chain. - for ocls in orig_chain: - new_tree_args = [] - for arg in ocls.__args__: - new_tree_args.append(_replace_arg(arg, ocls.__parameters__, tree_args)) - tree_args = new_tree_args - return tree_args - - -def _remove_dups_flatten(parameters): - """An internal helper for Union creation and substitution: flatten Union's - among parameters, then remove duplicates and strict subclasses. - """ - - # Flatten out Union[Union[...], ...]. - params = [] - for p in parameters: - if isinstance(p, _Union) and p.__origin__ is Union: - params.extend(p.__args__) - elif isinstance(p, tuple) and len(p) > 0 and p[0] is Union: - params.extend(p[1:]) - else: - params.append(p) - # Weed out strict duplicates, preserving the first of each occurrence. - all_params = set(params) - if len(all_params) < len(params): - new_params = [] - for t in params: - if t in all_params: - new_params.append(t) - all_params.remove(t) - params = new_params - assert not all_params, all_params - # Weed out subclasses. - # E.g. Union[int, Employee, Manager] == Union[int, Employee]. - # If object is present it will be sole survivor among proper classes. - # Never discard type variables. - # (In particular, Union[str, AnyStr] != AnyStr.) - all_params = set(params) - for t1 in params: - if not isinstance(t1, type): - continue - if any(isinstance(t2, type) and issubclass(t1, t2) - for t2 in all_params - {t1} - if not (isinstance(t2, GenericMeta) and - t2.__origin__ is not None)): - all_params.remove(t1) - return tuple(t for t in params if t in all_params) - - -def _check_generic(cls, parameters): - # Check correct count for parameters of a generic cls (internal helper). - if not cls.__parameters__: - raise TypeError("%s is not a generic class" % repr(cls)) - alen = len(parameters) - elen = len(cls.__parameters__) - if alen != elen: - raise TypeError("Too %s parameters for %s; actual %s, expected %s" % - ("many" if alen > elen else "few", repr(cls), alen, elen)) - - -_cleanups = [] - - -def _tp_cache(func): - """Internal wrapper caching __getitem__ of generic types with a fallback to - original function for non-hashable arguments. - """ - - cached = functools.lru_cache()(func) - _cleanups.append(cached.cache_clear) - - @functools.wraps(func) - def inner(*args, **kwds): - try: - return cached(*args, **kwds) - except TypeError: - pass # All real errors (not unhashable args) are raised below. - return func(*args, **kwds) - return inner - - -class _Union(_FinalTypingBase, _root=True): - """Union type; Union[X, Y] means either X or Y. - - To define a union, use e.g. Union[int, str]. Details: - - - The arguments must be types and there must be at least one. - - - None as an argument is a special case and is replaced by - type(None). - - - Unions of unions are flattened, e.g.:: - - Union[Union[int, str], float] == Union[int, str, float] - - - Unions of a single argument vanish, e.g.:: - - Union[int] == int # The constructor actually returns int - - - Redundant arguments are skipped, e.g.:: - - Union[int, str, int] == Union[int, str] - - - When comparing unions, the argument order is ignored, e.g.:: - - Union[int, str] == Union[str, int] - - - When two arguments have a subclass relationship, the least - derived argument is kept, e.g.:: - - class Employee: pass - class Manager(Employee): pass - Union[int, Employee, Manager] == Union[int, Employee] - Union[Manager, int, Employee] == Union[int, Employee] - Union[Employee, Manager] == Employee - - - Similar for object:: - - Union[int, object] == object - - - You cannot subclass or instantiate a union. - - - You can use Optional[X] as a shorthand for Union[X, None]. - """ - - __slots__ = ('__parameters__', '__args__', '__origin__', '__tree_hash__') - - def __new__(cls, parameters=None, origin=None, *args, _root=False): - self = super().__new__(cls, parameters, origin, *args, _root=_root) - if origin is None: - self.__parameters__ = None - self.__args__ = None - self.__origin__ = None - self.__tree_hash__ = hash(frozenset(('Union',))) - return self - if not isinstance(parameters, tuple): - raise TypeError("Expected parameters=<tuple>") - if origin is Union: - parameters = _remove_dups_flatten(parameters) - # It's not a union if there's only one type left. - if len(parameters) == 1: - return parameters[0] - self.__parameters__ = _type_vars(parameters) - self.__args__ = parameters - self.__origin__ = origin - # Pre-calculate the __hash__ on instantiation. - # This improves speed for complex substitutions. - subs_tree = self._subs_tree() - if isinstance(subs_tree, tuple): - self.__tree_hash__ = hash(frozenset(subs_tree)) - else: - self.__tree_hash__ = hash(subs_tree) - return self - - def _eval_type(self, globalns, localns): - if self.__args__ is None: - return self - ev_args = tuple(_eval_type(t, globalns, localns) for t in self.__args__) - ev_origin = _eval_type(self.__origin__, globalns, localns) - if ev_args == self.__args__ and ev_origin == self.__origin__: - # Everything is already evaluated. - return self - return self.__class__(ev_args, ev_origin, _root=True) - - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - _get_type_vars(self.__parameters__, tvars) - - def __repr__(self): - if self.__origin__ is None: - return super().__repr__() - tree = self._subs_tree() - if not isinstance(tree, tuple): - return repr(tree) - return tree[0]._tree_repr(tree) - - def _tree_repr(self, tree): - arg_list = [] - for arg in tree[1:]: - if not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - return super().__repr__() + '[%s]' % ', '.join(arg_list) - - @_tp_cache - def __getitem__(self, parameters): - if parameters == (): - raise TypeError("Cannot take a Union of no types.") - if not isinstance(parameters, tuple): - parameters = (parameters,) - if self.__origin__ is None: - msg = "Union[arg, ...]: each arg must be a type." - else: - msg = "Parameters to generic types must be types." - parameters = tuple(_type_check(p, msg) for p in parameters) - if self is not Union: - _check_generic(self, parameters) - return self.__class__(parameters, origin=self, _root=True) - - def _subs_tree(self, tvars=None, args=None): - if self is Union: - return Union # Nothing to substitute - tree_args = _subs_tree(self, tvars, args) - tree_args = _remove_dups_flatten(tree_args) - if len(tree_args) == 1: - return tree_args[0] # Union of a single type is that type - return (Union,) + tree_args - - def __eq__(self, other): - if isinstance(other, _Union): - return self.__tree_hash__ == other.__tree_hash__ - elif self is not Union: - return self._subs_tree() == other - else: - return self is other - - def __hash__(self): - return self.__tree_hash__ - - def __instancecheck__(self, obj): - raise TypeError("Unions cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - raise TypeError("Unions cannot be used with issubclass().") - - -Union = _Union(_root=True) - - -class _Optional(_FinalTypingBase, _root=True): - """Optional type. - - Optional[X] is equivalent to Union[X, None]. - """ - - __slots__ = () - - @_tp_cache - def __getitem__(self, arg): - arg = _type_check(arg, "Optional[t] requires a single type.") - return Union[arg, type(None)] - - -Optional = _Optional(_root=True) - - -def _next_in_mro(cls): - """Helper for Generic.__new__. - - Returns the class after the last occurrence of Generic or - Generic[...] in cls.__mro__. - """ - next_in_mro = object - # Look for the last occurrence of Generic or Generic[...]. - for i, c in enumerate(cls.__mro__[:-1]): - if isinstance(c, GenericMeta) and c._gorg is Generic: - next_in_mro = cls.__mro__[i + 1] - return next_in_mro - - -def _make_subclasshook(cls): - """Construct a __subclasshook__ callable that incorporates - the associated __extra__ class in subclass checks performed - against cls. - """ - if isinstance(cls.__extra__, abc.ABCMeta): - # The logic mirrors that of ABCMeta.__subclasscheck__. - # Registered classes need not be checked here because - # cls and its extra share the same _abc_registry. - def __extrahook__(subclass): - res = cls.__extra__.__subclasshook__(subclass) - if res is not NotImplemented: - return res - if cls.__extra__ in subclass.__mro__: - return True - for scls in cls.__extra__.__subclasses__(): - if isinstance(scls, GenericMeta): - continue - if issubclass(subclass, scls): - return True - return NotImplemented - else: - # For non-ABC extras we'll just call issubclass(). - def __extrahook__(subclass): - if cls.__extra__ and issubclass(subclass, cls.__extra__): - return True - return NotImplemented - return __extrahook__ - - -def _no_slots_copy(dct): - """Internal helper: copy class __dict__ and clean slots class variables. - (They will be re-created if necessary by normal class machinery.) - """ - dict_copy = dict(dct) - if '__slots__' in dict_copy: - for slot in dict_copy['__slots__']: - dict_copy.pop(slot, None) - return dict_copy - - -class GenericMeta(TypingMeta, abc.ABCMeta): - """Metaclass for generic types. - - This is a metaclass for typing.Generic and generic ABCs defined in - typing module. User defined subclasses of GenericMeta can override - __new__ and invoke super().__new__. Note that GenericMeta.__new__ - has strict rules on what is allowed in its bases argument: - * plain Generic is disallowed in bases; - * Generic[...] should appear in bases at most once; - * if Generic[...] is present, then it should list all type variables - that appear in other bases. - In addition, type of all generic bases is erased, e.g., C[int] is - stripped to plain C. - """ - - def __new__(cls, name, bases, namespace, - tvars=None, args=None, origin=None, extra=None, orig_bases=None): - """Create a new generic class. GenericMeta.__new__ accepts - keyword arguments that are used for internal bookkeeping, therefore - an override should pass unused keyword arguments to super(). - """ - if tvars is not None: - # Called from __getitem__() below. - assert origin is not None - assert all(isinstance(t, TypeVar) for t in tvars), tvars - else: - # Called from class statement. - assert tvars is None, tvars - assert args is None, args - assert origin is None, origin - - # Get the full set of tvars from the bases. - tvars = _type_vars(bases) - # Look for Generic[T1, ..., Tn]. - # If found, tvars must be a subset of it. - # If not found, tvars is it. - # Also check for and reject plain Generic, - # and reject multiple Generic[...]. - gvars = None - for base in bases: - if base is Generic: - raise TypeError("Cannot inherit from plain Generic") - if (isinstance(base, GenericMeta) and - base.__origin__ is Generic): - if gvars is not None: - raise TypeError( - "Cannot inherit from Generic[...] multiple types.") - gvars = base.__parameters__ - if gvars is None: - gvars = tvars - else: - tvarset = set(tvars) - gvarset = set(gvars) - if not tvarset <= gvarset: - raise TypeError( - "Some type variables (%s) " - "are not listed in Generic[%s]" % - (", ".join(str(t) for t in tvars if t not in gvarset), - ", ".join(str(g) for g in gvars))) - tvars = gvars - - initial_bases = bases - if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: - bases = (extra,) + bases - bases = tuple(b._gorg if isinstance(b, GenericMeta) else b for b in bases) - - # remove bare Generic from bases if there are other generic bases - if any(isinstance(b, GenericMeta) and b is not Generic for b in bases): - bases = tuple(b for b in bases if b is not Generic) - namespace.update({'__origin__': origin, '__extra__': extra, - '_gorg': None if not origin else origin._gorg}) - self = super().__new__(cls, name, bases, namespace, _root=True) - super(GenericMeta, self).__setattr__('_gorg', - self if not origin else origin._gorg) - self.__parameters__ = tvars - # Be prepared that GenericMeta will be subclassed by TupleMeta - # and CallableMeta, those two allow ..., (), or [] in __args___. - self.__args__ = tuple(... if a is _TypingEllipsis else - () if a is _TypingEmpty else - a for a in args) if args else None - # Speed hack (https://github.com/python/typing/issues/196). - self.__next_in_mro__ = _next_in_mro(self) - # Preserve base classes on subclassing (__bases__ are type erased now). - if orig_bases is None: - self.__orig_bases__ = initial_bases - - # This allows unparameterized generic collections to be used - # with issubclass() and isinstance() in the same way as their - # collections.abc counterparts (e.g., isinstance([], Iterable)). - if ( - '__subclasshook__' not in namespace and extra or - # allow overriding - getattr(self.__subclasshook__, '__name__', '') == '__extrahook__' - ): - self.__subclasshook__ = _make_subclasshook(self) - if isinstance(extra, abc.ABCMeta): - self._abc_registry = extra._abc_registry - self._abc_cache = extra._abc_cache - elif origin is not None: - self._abc_registry = origin._abc_registry - self._abc_cache = origin._abc_cache - - if origin and hasattr(origin, '__qualname__'): # Fix for Python 3.2. - self.__qualname__ = origin.__qualname__ - self.__tree_hash__ = (hash(self._subs_tree()) if origin else - super(GenericMeta, self).__hash__()) - return self - - # _abc_negative_cache and _abc_negative_cache_version - # realised as descriptors, since GenClass[t1, t2, ...] always - # share subclass info with GenClass. - # This is an important memory optimization. - @property - def _abc_negative_cache(self): - if isinstance(self.__extra__, abc.ABCMeta): - return self.__extra__._abc_negative_cache - return self._gorg._abc_generic_negative_cache - - @_abc_negative_cache.setter - def _abc_negative_cache(self, value): - if self.__origin__ is None: - if isinstance(self.__extra__, abc.ABCMeta): - self.__extra__._abc_negative_cache = value - else: - self._abc_generic_negative_cache = value - - @property - def _abc_negative_cache_version(self): - if isinstance(self.__extra__, abc.ABCMeta): - return self.__extra__._abc_negative_cache_version - return self._gorg._abc_generic_negative_cache_version - - @_abc_negative_cache_version.setter - def _abc_negative_cache_version(self, value): - if self.__origin__ is None: - if isinstance(self.__extra__, abc.ABCMeta): - self.__extra__._abc_negative_cache_version = value - else: - self._abc_generic_negative_cache_version = value - - def _get_type_vars(self, tvars): - if self.__origin__ and self.__parameters__: - _get_type_vars(self.__parameters__, tvars) - - def _eval_type(self, globalns, localns): - ev_origin = (self.__origin__._eval_type(globalns, localns) - if self.__origin__ else None) - ev_args = tuple(_eval_type(a, globalns, localns) for a - in self.__args__) if self.__args__ else None - if ev_origin == self.__origin__ and ev_args == self.__args__: - return self - return self.__class__(self.__name__, - self.__bases__, - _no_slots_copy(self.__dict__), - tvars=_type_vars(ev_args) if ev_args else None, - args=ev_args, - origin=ev_origin, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - def __repr__(self): - if self.__origin__ is None: - return super().__repr__() - return self._tree_repr(self._subs_tree()) - - def _tree_repr(self, tree): - arg_list = [] - for arg in tree[1:]: - if arg == (): - arg_list.append('()') - elif not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - return super().__repr__() + '[%s]' % ', '.join(arg_list) - - def _subs_tree(self, tvars=None, args=None): - if self.__origin__ is None: - return self - tree_args = _subs_tree(self, tvars, args) - return (self._gorg,) + tuple(tree_args) - - def __eq__(self, other): - if not isinstance(other, GenericMeta): - return NotImplemented - if self.__origin__ is None or other.__origin__ is None: - return self is other - return self.__tree_hash__ == other.__tree_hash__ - - def __hash__(self): - return self.__tree_hash__ - - @_tp_cache - def __getitem__(self, params): - if not isinstance(params, tuple): - params = (params,) - if not params and self._gorg is not Tuple: - raise TypeError( - "Parameter list to %s[...] cannot be empty" % _qualname(self)) - msg = "Parameters to generic types must be types." - params = tuple(_type_check(p, msg) for p in params) - if self is Generic: - # Generic can only be subscripted with unique type variables. - if not all(isinstance(p, TypeVar) for p in params): - raise TypeError( - "Parameters to Generic[...] must all be type variables") - if len(set(params)) != len(params): - raise TypeError( - "Parameters to Generic[...] must all be unique") - tvars = params - args = params - elif self in (Tuple, Callable): - tvars = _type_vars(params) - args = params - elif self is _Protocol: - # _Protocol is internal, don't check anything. - tvars = params - args = params - elif self.__origin__ in (Generic, _Protocol): - # Can't subscript Generic[...] or _Protocol[...]. - raise TypeError("Cannot subscript already-subscripted %s" % - repr(self)) - else: - # Subscripting a regular Generic subclass. - _check_generic(self, params) - tvars = _type_vars(params) - args = params - - prepend = (self,) if self.__origin__ is None else () - return self.__class__(self.__name__, - prepend + self.__bases__, - _no_slots_copy(self.__dict__), - tvars=tvars, - args=args, - origin=self, - extra=self.__extra__, - orig_bases=self.__orig_bases__) - - def __subclasscheck__(self, cls): - if self.__origin__ is not None: - if sys._getframe(1).f_globals['__name__'] not in ['abc', 'functools']: - raise TypeError("Parameterized generics cannot be used with class " - "or instance checks") - return False - if self is Generic: - raise TypeError("Class %r cannot be used with class " - "or instance checks" % self) - return super().__subclasscheck__(cls) - - def __instancecheck__(self, instance): - # Since we extend ABC.__subclasscheck__ and - # ABC.__instancecheck__ inlines the cache checking done by the - # latter, we must extend __instancecheck__ too. For simplicity - # we just skip the cache check -- instance checks for generic - # classes are supposed to be rare anyways. - return issubclass(instance.__class__, self) - - def __setattr__(self, attr, value): - # We consider all the subscripted generics as proxies for original class - if ( - attr.startswith('__') and attr.endswith('__') or - attr.startswith('_abc_') or - self._gorg is None # The class is not fully created, see #typing/506 - ): - super(GenericMeta, self).__setattr__(attr, value) - else: - super(GenericMeta, self._gorg).__setattr__(attr, value) - - -# Prevent checks for Generic to crash when defining Generic. -Generic = None - - -def _generic_new(base_cls, cls, *args, **kwds): - # Assure type is erased on instantiation, - # but attempt to store it in __orig_class__ - if cls.__origin__ is None: - if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): - return base_cls.__new__(cls) - else: - return base_cls.__new__(cls, *args, **kwds) - else: - origin = cls._gorg - if (base_cls.__new__ is object.__new__ and - cls.__init__ is not object.__init__): - obj = base_cls.__new__(origin) - else: - obj = base_cls.__new__(origin, *args, **kwds) - try: - obj.__orig_class__ = cls - except AttributeError: - pass - obj.__init__(*args, **kwds) - return obj - - -class Generic(metaclass=GenericMeta): - """Abstract base class for generic types. - - A generic type is typically declared by inheriting from - this class parameterized with one or more type variables. - For example, a generic mapping type might be defined as:: - - class Mapping(Generic[KT, VT]): - def __getitem__(self, key: KT) -> VT: - ... - # Etc. - - This class can then be used as follows:: - - def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: - try: - return mapping[key] - except KeyError: - return default - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Generic: - raise TypeError("Type Generic cannot be instantiated; " - "it can be used only as a base class") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -class _TypingEmpty: - """Internal placeholder for () or []. Used by TupleMeta and CallableMeta - to allow empty list/tuple in specific places, without allowing them - to sneak in where prohibited. - """ - - -class _TypingEllipsis: - """Internal placeholder for ... (ellipsis).""" - - -class TupleMeta(GenericMeta): - """Metaclass for Tuple (internal).""" - - @_tp_cache - def __getitem__(self, parameters): - if self.__origin__ is not None or self._gorg is not Tuple: - # Normal generic rules apply if this is not the first subscription - # or a subscription of a subclass. - return super().__getitem__(parameters) - if parameters == (): - return super().__getitem__((_TypingEmpty,)) - if not isinstance(parameters, tuple): - parameters = (parameters,) - if len(parameters) == 2 and parameters[1] is ...: - msg = "Tuple[t, ...]: t must be a type." - p = _type_check(parameters[0], msg) - return super().__getitem__((p, _TypingEllipsis)) - msg = "Tuple[t0, t1, ...]: each t must be a type." - parameters = tuple(_type_check(p, msg) for p in parameters) - return super().__getitem__(parameters) - - def __instancecheck__(self, obj): - if self.__args__ is None: - return isinstance(obj, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with isinstance().") - - def __subclasscheck__(self, cls): - if self.__args__ is None: - return issubclass(cls, tuple) - raise TypeError("Parameterized Tuple cannot be used " - "with issubclass().") - - -class Tuple(tuple, extra=tuple, metaclass=TupleMeta): - """Tuple type; Tuple[X, Y] is the cross-product type of X and Y. - - Example: Tuple[T1, T2] is a tuple of two elements corresponding - to type variables T1 and T2. Tuple[int, float, str] is a tuple - of an int, a float and a string. - - To specify a variable-length tuple of homogeneous type, use Tuple[T, ...]. - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Tuple: - raise TypeError("Type Tuple cannot be instantiated; " - "use tuple() instead") - return _generic_new(tuple, cls, *args, **kwds) - - -class CallableMeta(GenericMeta): - """Metaclass for Callable (internal).""" - - def __repr__(self): - if self.__origin__ is None: - return super().__repr__() - return self._tree_repr(self._subs_tree()) - - def _tree_repr(self, tree): - if self._gorg is not Callable: - return super()._tree_repr(tree) - # For actual Callable (not its subclass) we override - # super()._tree_repr() for nice formatting. - arg_list = [] - for arg in tree[1:]: - if not isinstance(arg, tuple): - arg_list.append(_type_repr(arg)) - else: - arg_list.append(arg[0]._tree_repr(arg)) - if arg_list[0] == '...': - return repr(tree[0]) + '[..., %s]' % arg_list[1] - return (repr(tree[0]) + - '[[%s], %s]' % (', '.join(arg_list[:-1]), arg_list[-1])) - - def __getitem__(self, parameters): - """A thin wrapper around __getitem_inner__ to provide the latter - with hashable arguments to improve speed. - """ - - if self.__origin__ is not None or self._gorg is not Callable: - return super().__getitem__(parameters) - if not isinstance(parameters, tuple) or len(parameters) != 2: - raise TypeError("Callable must be used as " - "Callable[[arg, ...], result].") - args, result = parameters - if args is Ellipsis: - parameters = (Ellipsis, result) - else: - if not isinstance(args, list): - raise TypeError("Callable[args, result]: args must be a list." - " Got %.100r." % (args,)) - parameters = (tuple(args), result) - return self.__getitem_inner__(parameters) - - @_tp_cache - def __getitem_inner__(self, parameters): - args, result = parameters - msg = "Callable[args, result]: result must be a type." - result = _type_check(result, msg) - if args is Ellipsis: - return super().__getitem__((_TypingEllipsis, result)) - msg = "Callable[[arg, ...], result]: each arg must be a type." - args = tuple(_type_check(arg, msg) for arg in args) - parameters = args + (result,) - return super().__getitem__(parameters) - - -class Callable(extra=collections_abc.Callable, metaclass=CallableMeta): - """Callable type; Callable[[int], str] is a function of (int) -> str. - - The subscription syntax must always be used with exactly two - values: the argument list and the return type. The argument list - must be a list of types or ellipsis; the return type must be a single type. - - There is no syntax to indicate optional or keyword arguments, - such function types are rarely used as callback types. - """ - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Callable: - raise TypeError("Type Callable cannot be instantiated; " - "use a non-abstract subclass instead") - return _generic_new(cls.__next_in_mro__, cls, *args, **kwds) - - -class _ClassVar(_FinalTypingBase, _root=True): - """Special type construct to mark class variables. - - An annotation wrapped in ClassVar indicates that a given - attribute is intended to be used as a class variable and - should not be set on instances of that class. Usage:: - - class Starship: - stats: ClassVar[Dict[str, int]] = {} # class variable - damage: int = 10 # instance variable - - ClassVar accepts only types and cannot be further subscribed. - - Note that ClassVar is not a class itself, and should not - be used with isinstance() or issubclass(). - """ - - __slots__ = ('__type__',) - - def __init__(self, tp=None, **kwds): - self.__type__ = tp - - def __getitem__(self, item): - cls = type(self) - if self.__type__ is None: - return cls(_type_check(item, - '{} accepts only single type.'.format(cls.__name__[1:])), - _root=True) - raise TypeError('{} cannot be further subscripted' - .format(cls.__name__[1:])) - - def _eval_type(self, globalns, localns): - new_tp = _eval_type(self.__type__, globalns, localns) - if new_tp == self.__type__: - return self - return type(self)(new_tp, _root=True) - - def __repr__(self): - r = super().__repr__() - if self.__type__ is not None: - r += '[{}]'.format(_type_repr(self.__type__)) - return r - - def __hash__(self): - return hash((type(self).__name__, self.__type__)) - - def __eq__(self, other): - if not isinstance(other, _ClassVar): - return NotImplemented - if self.__type__ is not None: - return self.__type__ == other.__type__ - return self is other - - -ClassVar = _ClassVar(_root=True) - - -def cast(typ, val): - """Cast a value to a type. - - This returns the value unchanged. To the type checker this - signals that the return value has the designated type, but at - runtime we intentionally don't check anything (we want this - to be as fast as possible). - """ - return val - - -def _get_defaults(func): - """Internal helper to extract the default arguments, by name.""" - try: - code = func.__code__ - except AttributeError: - # Some built-in functions don't have __code__, __defaults__, etc. - return {} - pos_count = code.co_argcount - arg_names = code.co_varnames - arg_names = arg_names[:pos_count] - defaults = func.__defaults__ or () - kwdefaults = func.__kwdefaults__ - res = dict(kwdefaults) if kwdefaults else {} - pos_offset = pos_count - len(defaults) - for name, value in zip(arg_names[pos_offset:], defaults): - assert name not in res - res[name] = value - return res - - -_allowed_types = (types.FunctionType, types.BuiltinFunctionType, - types.MethodType, types.ModuleType, - WrapperDescriptorType, MethodWrapperType, MethodDescriptorType) - - -def get_type_hints(obj, globalns=None, localns=None): - """Return type hints for an object. - - This is often the same as obj.__annotations__, but it handles - forward references encoded as string literals, and if necessary - adds Optional[t] if a default value equal to None is set. - - The argument may be a module, class, method, or function. The annotations - are returned as a dictionary. For classes, annotations include also - inherited members. - - TypeError is raised if the argument is not of a type that can contain - annotations, and an empty dictionary is returned if no annotations are - present. - - BEWARE -- the behavior of globalns and localns is counterintuitive - (unless you are familiar with how eval() and exec() work). The - search order is locals first, then globals. - - - If no dict arguments are passed, an attempt is made to use the - globals from obj (or the respective module's globals for classes), - and these are also used as the locals. If the object does not appear - to have globals, an empty dictionary is used. - - - If one dict argument is passed, it is used for both globals and - locals. - - - If two dict arguments are passed, they specify globals and - locals, respectively. - """ - - if getattr(obj, '__no_type_check__', None): - return {} - # Classes require a special treatment. - if isinstance(obj, type): - hints = {} - for base in reversed(obj.__mro__): - if globalns is None: - base_globals = sys.modules[base.__module__].__dict__ - else: - base_globals = globalns - ann = base.__dict__.get('__annotations__', {}) - for name, value in ann.items(): - if value is None: - value = type(None) - if isinstance(value, str): - value = _ForwardRef(value) - value = _eval_type(value, base_globals, localns) - hints[name] = value - return hints - - if globalns is None: - if isinstance(obj, types.ModuleType): - globalns = obj.__dict__ - else: - globalns = getattr(obj, '__globals__', {}) - if localns is None: - localns = globalns - elif localns is None: - localns = globalns - hints = getattr(obj, '__annotations__', None) - if hints is None: - # Return empty annotations for something that _could_ have them. - if isinstance(obj, _allowed_types): - return {} - else: - raise TypeError('{!r} is not a module, class, method, ' - 'or function.'.format(obj)) - defaults = _get_defaults(obj) - hints = dict(hints) - for name, value in hints.items(): - if value is None: - value = type(None) - if isinstance(value, str): - value = _ForwardRef(value) - value = _eval_type(value, globalns, localns) - if name in defaults and defaults[name] is None: - value = Optional[value] - hints[name] = value - return hints - - -def no_type_check(arg): - """Decorator to indicate that annotations are not type hints. - - The argument must be a class or function; if it is a class, it - applies recursively to all methods and classes defined in that class - (but not to methods defined in its superclasses or subclasses). - - This mutates the function(s) or class(es) in place. - """ - if isinstance(arg, type): - arg_attrs = arg.__dict__.copy() - for attr, val in arg.__dict__.items(): - if val in arg.__bases__ + (arg,): - arg_attrs.pop(attr) - for obj in arg_attrs.values(): - if isinstance(obj, types.FunctionType): - obj.__no_type_check__ = True - if isinstance(obj, type): - no_type_check(obj) - try: - arg.__no_type_check__ = True - except TypeError: # built-in classes - pass - return arg - - -def no_type_check_decorator(decorator): - """Decorator to give another decorator the @no_type_check effect. - - This wraps the decorator with something that wraps the decorated - function in @no_type_check. - """ - - @functools.wraps(decorator) - def wrapped_decorator(*args, **kwds): - func = decorator(*args, **kwds) - func = no_type_check(func) - return func - - return wrapped_decorator - - -def _overload_dummy(*args, **kwds): - """Helper for @overload to raise when called.""" - raise NotImplementedError( - "You should not call an overloaded function. " - "A series of @overload-decorated functions " - "outside a stub module should always be followed " - "by an implementation that is not @overload-ed.") - - -def overload(func): - """Decorator for overloaded functions/methods. - - In a stub file, place two or more stub definitions for the same - function in a row, each decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - - In a non-stub file (i.e. a regular .py file), do the same but - follow it with an implementation. The implementation should *not* - be decorated with @overload. For example: - - @overload - def utf8(value: None) -> None: ... - @overload - def utf8(value: bytes) -> bytes: ... - @overload - def utf8(value: str) -> bytes: ... - def utf8(value): - # implementation goes here - """ - return _overload_dummy - - -class _ProtocolMeta(GenericMeta): - """Internal metaclass for _Protocol. - - This exists so _Protocol classes can be generic without deriving - from Generic. - """ - - def __instancecheck__(self, obj): - if _Protocol not in self.__bases__: - return super().__instancecheck__(obj) - raise TypeError("Protocols cannot be used with isinstance().") - - def __subclasscheck__(self, cls): - if not self._is_protocol: - # No structural checks since this isn't a protocol. - return NotImplemented - - if self is _Protocol: - # Every class is a subclass of the empty protocol. - return True - - # Find all attributes defined in the protocol. - attrs = self._get_protocol_attrs() - - for attr in attrs: - if not any(attr in d.__dict__ for d in cls.__mro__): - return False - return True - - def _get_protocol_attrs(self): - # Get all Protocol base classes. - protocol_bases = [] - for c in self.__mro__: - if getattr(c, '_is_protocol', False) and c.__name__ != '_Protocol': - protocol_bases.append(c) - - # Get attributes included in protocol. - attrs = set() - for base in protocol_bases: - for attr in base.__dict__.keys(): - # Include attributes not defined in any non-protocol bases. - for c in self.__mro__: - if (c is not base and attr in c.__dict__ and - not getattr(c, '_is_protocol', False)): - break - else: - if (not attr.startswith('_abc_') and - attr != '__abstractmethods__' and - attr != '__annotations__' and - attr != '__weakref__' and - attr != '_is_protocol' and - attr != '_gorg' and - attr != '__dict__' and - attr != '__args__' and - attr != '__slots__' and - attr != '_get_protocol_attrs' and - attr != '__next_in_mro__' and - attr != '__parameters__' and - attr != '__origin__' and - attr != '__orig_bases__' and - attr != '__extra__' and - attr != '__tree_hash__' and - attr != '__module__'): - attrs.add(attr) - - return attrs - - -class _Protocol(metaclass=_ProtocolMeta): - """Internal base class for protocol classes. - - This implements a simple-minded structural issubclass check - (similar but more general than the one-offs in collections.abc - such as Hashable). - """ - - __slots__ = () - - _is_protocol = True - - -# Various ABCs mimicking those in collections.abc. -# A few are simply re-exported for completeness. - -Hashable = collections_abc.Hashable # Not generic. - - -if hasattr(collections_abc, 'Awaitable'): - class Awaitable(Generic[T_co], extra=collections_abc.Awaitable): - __slots__ = () - - __all__.append('Awaitable') - - -if hasattr(collections_abc, 'Coroutine'): - class Coroutine(Awaitable[V_co], Generic[T_co, T_contra, V_co], - extra=collections_abc.Coroutine): - __slots__ = () - - __all__.append('Coroutine') - - -if hasattr(collections_abc, 'AsyncIterable'): - - class AsyncIterable(Generic[T_co], extra=collections_abc.AsyncIterable): - __slots__ = () - - class AsyncIterator(AsyncIterable[T_co], - extra=collections_abc.AsyncIterator): - __slots__ = () - - __all__.append('AsyncIterable') - __all__.append('AsyncIterator') - - -class Iterable(Generic[T_co], extra=collections_abc.Iterable): - __slots__ = () - - -class Iterator(Iterable[T_co], extra=collections_abc.Iterator): - __slots__ = () - - -class SupportsInt(_Protocol): - __slots__ = () - - @abstractmethod - def __int__(self) -> int: - pass - - -class SupportsFloat(_Protocol): - __slots__ = () - - @abstractmethod - def __float__(self) -> float: - pass - - -class SupportsComplex(_Protocol): - __slots__ = () - - @abstractmethod - def __complex__(self) -> complex: - pass - - -class SupportsBytes(_Protocol): - __slots__ = () - - @abstractmethod - def __bytes__(self) -> bytes: - pass - - -class SupportsAbs(_Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __abs__(self) -> T_co: - pass - - -class SupportsRound(_Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __round__(self, ndigits: int = 0) -> T_co: - pass - - -if hasattr(collections_abc, 'Reversible'): - class Reversible(Iterable[T_co], extra=collections_abc.Reversible): - __slots__ = () -else: - class Reversible(_Protocol[T_co]): - __slots__ = () - - @abstractmethod - def __reversed__(self) -> 'Iterator[T_co]': - pass - - -Sized = collections_abc.Sized # Not generic. - - -class Container(Generic[T_co], extra=collections_abc.Container): - __slots__ = () - - -if hasattr(collections_abc, 'Collection'): - class Collection(Sized, Iterable[T_co], Container[T_co], - extra=collections_abc.Collection): - __slots__ = () - - __all__.append('Collection') - - -# Callable was defined earlier. - -if hasattr(collections_abc, 'Collection'): - class AbstractSet(Collection[T_co], - extra=collections_abc.Set): - __slots__ = () -else: - class AbstractSet(Sized, Iterable[T_co], Container[T_co], - extra=collections_abc.Set): - __slots__ = () - - -class MutableSet(AbstractSet[T], extra=collections_abc.MutableSet): - __slots__ = () - - -# NOTE: It is only covariant in the value type. -if hasattr(collections_abc, 'Collection'): - class Mapping(Collection[KT], Generic[KT, VT_co], - extra=collections_abc.Mapping): - __slots__ = () -else: - class Mapping(Sized, Iterable[KT], Container[KT], Generic[KT, VT_co], - extra=collections_abc.Mapping): - __slots__ = () - - -class MutableMapping(Mapping[KT, VT], extra=collections_abc.MutableMapping): - __slots__ = () - - -if hasattr(collections_abc, 'Reversible'): - if hasattr(collections_abc, 'Collection'): - class Sequence(Reversible[T_co], Collection[T_co], - extra=collections_abc.Sequence): - __slots__ = () - else: - class Sequence(Sized, Reversible[T_co], Container[T_co], - extra=collections_abc.Sequence): - __slots__ = () -else: - class Sequence(Sized, Iterable[T_co], Container[T_co], - extra=collections_abc.Sequence): - __slots__ = () - - -class MutableSequence(Sequence[T], extra=collections_abc.MutableSequence): - __slots__ = () - - -class ByteString(Sequence[int], extra=collections_abc.ByteString): - __slots__ = () - - -class List(list, MutableSequence[T], extra=list): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is List: - raise TypeError("Type List cannot be instantiated; " - "use list() instead") - return _generic_new(list, cls, *args, **kwds) - - -class Deque(collections.deque, MutableSequence[T], extra=collections.deque): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Deque: - return collections.deque(*args, **kwds) - return _generic_new(collections.deque, cls, *args, **kwds) - - -class Set(set, MutableSet[T], extra=set): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Set: - raise TypeError("Type Set cannot be instantiated; " - "use set() instead") - return _generic_new(set, cls, *args, **kwds) - - -class FrozenSet(frozenset, AbstractSet[T_co], extra=frozenset): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is FrozenSet: - raise TypeError("Type FrozenSet cannot be instantiated; " - "use frozenset() instead") - return _generic_new(frozenset, cls, *args, **kwds) - - -class MappingView(Sized, Iterable[T_co], extra=collections_abc.MappingView): - __slots__ = () - - -class KeysView(MappingView[KT], AbstractSet[KT], - extra=collections_abc.KeysView): - __slots__ = () - - -class ItemsView(MappingView[Tuple[KT, VT_co]], - AbstractSet[Tuple[KT, VT_co]], - Generic[KT, VT_co], - extra=collections_abc.ItemsView): - __slots__ = () - - -class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): - __slots__ = () - - -if hasattr(contextlib, 'AbstractContextManager'): - class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager): - __slots__ = () -else: - class ContextManager(Generic[T_co]): - __slots__ = () - - def __enter__(self): - return self - - @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is ContextManager: - # In Python 3.6+, it is possible to set a method to None to - # explicitly indicate that the class does not implement an ABC - # (https://bugs.python.org/issue25958), but we do not support - # that pattern here because this fallback class is only used - # in Python 3.5 and earlier. - if (any("__enter__" in B.__dict__ for B in C.__mro__) and - any("__exit__" in B.__dict__ for B in C.__mro__)): - return True - return NotImplemented - - -if hasattr(contextlib, 'AbstractAsyncContextManager'): - class AsyncContextManager(Generic[T_co], - extra=contextlib.AbstractAsyncContextManager): - __slots__ = () - - __all__.append('AsyncContextManager') -elif sys.version_info[:2] >= (3, 5): - exec(""" -class AsyncContextManager(Generic[T_co]): - __slots__ = () - - async def __aenter__(self): - return self - - @abc.abstractmethod - async def __aexit__(self, exc_type, exc_value, traceback): - return None - - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncContextManager: - if sys.version_info[:2] >= (3, 6): - return _collections_abc._check_methods(C, "__aenter__", "__aexit__") - if (any("__aenter__" in B.__dict__ for B in C.__mro__) and - any("__aexit__" in B.__dict__ for B in C.__mro__)): - return True - return NotImplemented - -__all__.append('AsyncContextManager') -""") - - -class Dict(dict, MutableMapping[KT, VT], extra=dict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Dict: - raise TypeError("Type Dict cannot be instantiated; " - "use dict() instead") - return _generic_new(dict, cls, *args, **kwds) - - -class DefaultDict(collections.defaultdict, MutableMapping[KT, VT], - extra=collections.defaultdict): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is DefaultDict: - return collections.defaultdict(*args, **kwds) - return _generic_new(collections.defaultdict, cls, *args, **kwds) - - -class Counter(collections.Counter, Dict[T, int], extra=collections.Counter): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Counter: - return collections.Counter(*args, **kwds) - return _generic_new(collections.Counter, cls, *args, **kwds) - - -if hasattr(collections, 'ChainMap'): - # ChainMap only exists in 3.3+ - __all__.append('ChainMap') - - class ChainMap(collections.ChainMap, MutableMapping[KT, VT], - extra=collections.ChainMap): - - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is ChainMap: - return collections.ChainMap(*args, **kwds) - return _generic_new(collections.ChainMap, cls, *args, **kwds) - - -# Determine what base class to use for Generator. -if hasattr(collections_abc, 'Generator'): - # Sufficiently recent versions of 3.5 have a Generator ABC. - _G_base = collections_abc.Generator -else: - # Fall back on the exact type. - _G_base = types.GeneratorType - - -class Generator(Iterator[T_co], Generic[T_co, T_contra, V_co], - extra=_G_base): - __slots__ = () - - def __new__(cls, *args, **kwds): - if cls._gorg is Generator: - raise TypeError("Type Generator cannot be instantiated; " - "create a subclass instead") - return _generic_new(_G_base, cls, *args, **kwds) - - -if hasattr(collections_abc, 'AsyncGenerator'): - class AsyncGenerator(AsyncIterator[T_co], Generic[T_co, T_contra], - extra=collections_abc.AsyncGenerator): - __slots__ = () - - __all__.append('AsyncGenerator') - - -# Internal type variable used for Type[]. -CT_co = TypeVar('CT_co', covariant=True, bound=type) - - -# This is not a real generic class. Don't use outside annotations. -class Type(Generic[CT_co], extra=type): - """A special construct usable to annotate class objects. - - For example, suppose we have the following classes:: - - class User: ... # Abstract base for User classes - class BasicUser(User): ... - class ProUser(User): ... - class TeamUser(User): ... - - And a function that takes a class argument that's a subclass of - User and returns an instance of the corresponding class:: - - U = TypeVar('U', bound=User) - def new_user(user_class: Type[U]) -> U: - user = user_class() - # (Here we could write the user object to a database) - return user - - joe = new_user(BasicUser) - - At this point the type checker knows that joe has type BasicUser. - """ - - __slots__ = () - - -def _make_nmtuple(name, types): - msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" - types = [(n, _type_check(t, msg)) for n, t in types] - nm_tpl = collections.namedtuple(name, [n for n, t in types]) - # Prior to PEP 526, only _field_types attribute was assigned. - # Now, both __annotations__ and _field_types are used to maintain compatibility. - nm_tpl.__annotations__ = nm_tpl._field_types = collections.OrderedDict(types) - try: - nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') - except (AttributeError, ValueError): - pass - return nm_tpl - - -_PY36 = sys.version_info[:2] >= (3, 6) - -# attributes prohibited to set in NamedTuple class syntax -_prohibited = ('__new__', '__init__', '__slots__', '__getnewargs__', - '_fields', '_field_defaults', '_field_types', - '_make', '_replace', '_asdict', '_source') - -_special = ('__module__', '__name__', '__qualname__', '__annotations__') - - -class NamedTupleMeta(type): - - def __new__(cls, typename, bases, ns): - if ns.get('_root', False): - return super().__new__(cls, typename, bases, ns) - if not _PY36: - raise TypeError("Class syntax for NamedTuple is only supported" - " in Python 3.6+") - types = ns.get('__annotations__', {}) - nm_tpl = _make_nmtuple(typename, types.items()) - defaults = [] - defaults_dict = {} - for field_name in types: - if field_name in ns: - default_value = ns[field_name] - defaults.append(default_value) - defaults_dict[field_name] = default_value - elif defaults: - raise TypeError("Non-default namedtuple field {field_name} cannot " - "follow default field(s) {default_names}" - .format(field_name=field_name, - default_names=', '.join(defaults_dict.keys()))) - nm_tpl.__new__.__annotations__ = collections.OrderedDict(types) - nm_tpl.__new__.__defaults__ = tuple(defaults) - nm_tpl._field_defaults = defaults_dict - # update from user namespace without overriding special namedtuple attributes - for key in ns: - if key in _prohibited: - raise AttributeError("Cannot overwrite NamedTuple attribute " + key) - elif key not in _special and key not in nm_tpl._fields: - setattr(nm_tpl, key, ns[key]) - return nm_tpl - - -class NamedTuple(metaclass=NamedTupleMeta): - """Typed version of namedtuple. - - Usage in Python versions >= 3.6:: - - class Employee(NamedTuple): - name: str - id: int - - This is equivalent to:: - - Employee = collections.namedtuple('Employee', ['name', 'id']) - - The resulting class has extra __annotations__ and _field_types - attributes, giving an ordered dict mapping field names to types. - __annotations__ should be preferred, while _field_types - is kept to maintain pre PEP 526 compatibility. (The field names - are in the _fields attribute, which is part of the namedtuple - API.) Alternative equivalent keyword syntax is also accepted:: - - Employee = NamedTuple('Employee', name=str, id=int) - - In Python versions <= 3.5 use:: - - Employee = NamedTuple('Employee', [('name', str), ('id', int)]) - """ - _root = True - - def __new__(self, typename, fields=None, **kwargs): - if kwargs and not _PY36: - raise TypeError("Keyword syntax for NamedTuple is only supported" - " in Python 3.6+") - if fields is None: - fields = kwargs.items() - elif kwargs: - raise TypeError("Either list of fields or keywords" - " can be provided to NamedTuple, not both") - return _make_nmtuple(typename, fields) - - -def NewType(name, tp): - """NewType creates simple unique types with almost zero - runtime overhead. NewType(name, tp) is considered a subtype of tp - by static type checkers. At runtime, NewType(name, tp) returns - a dummy function that simply returns its argument. Usage:: - - UserId = NewType('UserId', int) - - def name_by_id(user_id: UserId) -> str: - ... - - UserId('user') # Fails type check - - name_by_id(42) # Fails type check - name_by_id(UserId(42)) # OK - - num = UserId(5) + 1 # type: int - """ - - def new_type(x): - return x - - new_type.__name__ = name - new_type.__supertype__ = tp - return new_type - - -# Python-version-specific alias (Python 2: unicode; Python 3: str) -Text = str - - -# Constant that's True when type checking, but False here. -TYPE_CHECKING = False - - -class IO(Generic[AnyStr]): - """Generic base class for TextIO and BinaryIO. - - This is an abstract, generic version of the return of open(). - - NOTE: This does not distinguish between the different possible - classes (text vs. binary, read vs. write vs. read/write, - append-only, unbuffered). The TextIO and BinaryIO subclasses - below capture the distinctions between text vs. binary, which is - pervasive in the interface; however we currently do not offer a - way to track the other distinctions in the type system. - """ - - __slots__ = () - - @abstractproperty - def mode(self) -> str: - pass - - @abstractproperty - def name(self) -> str: - pass - - @abstractmethod - def close(self) -> None: - pass - - @abstractproperty - def closed(self) -> bool: - pass - - @abstractmethod - def fileno(self) -> int: - pass - - @abstractmethod - def flush(self) -> None: - pass - - @abstractmethod - def isatty(self) -> bool: - pass - - @abstractmethod - def read(self, n: int = -1) -> AnyStr: - pass - - @abstractmethod - def readable(self) -> bool: - pass - - @abstractmethod - def readline(self, limit: int = -1) -> AnyStr: - pass - - @abstractmethod - def readlines(self, hint: int = -1) -> List[AnyStr]: - pass - - @abstractmethod - def seek(self, offset: int, whence: int = 0) -> int: - pass - - @abstractmethod - def seekable(self) -> bool: - pass - - @abstractmethod - def tell(self) -> int: - pass - - @abstractmethod - def truncate(self, size: int = None) -> int: - pass - - @abstractmethod - def writable(self) -> bool: - pass - - @abstractmethod - def write(self, s: AnyStr) -> int: - pass - - @abstractmethod - def writelines(self, lines: List[AnyStr]) -> None: - pass - - @abstractmethod - def __enter__(self) -> 'IO[AnyStr]': - pass - - @abstractmethod - def __exit__(self, type, value, traceback) -> None: - pass - - -class BinaryIO(IO[bytes]): - """Typed version of the return of open() in binary mode.""" - - __slots__ = () - - @abstractmethod - def write(self, s: Union[bytes, bytearray]) -> int: - pass - - @abstractmethod - def __enter__(self) -> 'BinaryIO': - pass - - -class TextIO(IO[str]): - """Typed version of the return of open() in text mode.""" - - __slots__ = () - - @abstractproperty - def buffer(self) -> BinaryIO: - pass - - @abstractproperty - def encoding(self) -> str: - pass - - @abstractproperty - def errors(self) -> Optional[str]: - pass - - @abstractproperty - def line_buffering(self) -> bool: - pass - - @abstractproperty - def newlines(self) -> Any: - pass - - @abstractmethod - def __enter__(self) -> 'TextIO': - pass - - -class io: - """Wrapper namespace for IO generic classes.""" - - __all__ = ['IO', 'TextIO', 'BinaryIO'] - IO = IO - TextIO = TextIO - BinaryIO = BinaryIO - - -io.__name__ = __name__ + '.io' -sys.modules[io.__name__] = io - - -Pattern = _TypeAlias('Pattern', AnyStr, type(stdlib_re.compile('')), - lambda p: p.pattern) -Match = _TypeAlias('Match', AnyStr, type(stdlib_re.match('', '')), - lambda m: m.re.pattern) - - -class re: - """Wrapper namespace for re type aliases.""" - - __all__ = ['Pattern', 'Match'] - Pattern = Pattern - Match = Match - - -re.__name__ = __name__ + '.re' -sys.modules[re.__name__] = re diff --git a/lib/urllib3/__init__.py b/lib/urllib3/__init__.py deleted file mode 100644 index 148a9c3..0000000 --- a/lib/urllib3/__init__.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -urllib3 - Thread-safe connection pooling and re-using. -""" - -from __future__ import absolute_import -import warnings - -from .connectionpool import ( - HTTPConnectionPool, - HTTPSConnectionPool, - connection_from_url -) - -from . import exceptions -from .filepost import encode_multipart_formdata -from .poolmanager import PoolManager, ProxyManager, proxy_from_url -from .response import HTTPResponse -from .util.request import make_headers -from .util.url import get_host -from .util.timeout import Timeout -from .util.retry import Retry - - -# Set default logging handler to avoid "No handler found" warnings. -import logging -from logging import NullHandler - -__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' -__license__ = 'MIT' -__version__ = '1.24.1' - -__all__ = ( - 'HTTPConnectionPool', - 'HTTPSConnectionPool', - 'PoolManager', - 'ProxyManager', - 'HTTPResponse', - 'Retry', - 'Timeout', - 'add_stderr_logger', - 'connection_from_url', - 'disable_warnings', - 'encode_multipart_formdata', - 'get_host', - 'make_headers', - 'proxy_from_url', -) - -logging.getLogger(__name__).addHandler(NullHandler()) - - -def add_stderr_logger(level=logging.DEBUG): - """ - Helper for quickly adding a StreamHandler to the logger. Useful for - debugging. - - Returns the handler after adding it. - """ - # This method needs to be in this __init__.py to get the __name__ correct - # even if urllib3 is vendored within another package. - logger = logging.getLogger(__name__) - handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) - logger.addHandler(handler) - logger.setLevel(level) - logger.debug('Added a stderr logging handler to logger: %s', __name__) - return handler - - -# ... Clean up. -del NullHandler - - -# All warning filters *must* be appended unless you're really certain that they -# shouldn't be: otherwise, it's very hard for users to use most Python -# mechanisms to silence them. -# SecurityWarning's always go off by default. -warnings.simplefilter('always', exceptions.SecurityWarning, append=True) -# SubjectAltNameWarning's should go off once per host -warnings.simplefilter('default', exceptions.SubjectAltNameWarning, append=True) -# InsecurePlatformWarning's don't vary between requests, so we keep it default. -warnings.simplefilter('default', exceptions.InsecurePlatformWarning, - append=True) -# SNIMissingWarnings should go off only once. -warnings.simplefilter('default', exceptions.SNIMissingWarning, append=True) - - -def disable_warnings(category=exceptions.HTTPWarning): - """ - Helper for quickly disabling all urllib3 warnings. - """ - warnings.simplefilter('ignore', category) diff --git a/lib/urllib3/_collections.py b/lib/urllib3/_collections.py deleted file mode 100644 index 34f2381..0000000 --- a/lib/urllib3/_collections.py +++ /dev/null @@ -1,329 +0,0 @@ -from __future__ import absolute_import -try: - from collections.abc import Mapping, MutableMapping -except ImportError: - from collections import Mapping, MutableMapping -try: - from threading import RLock -except ImportError: # Platform-specific: No threads available - class RLock: - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_value, traceback): - pass - - -from collections import OrderedDict -from .exceptions import InvalidHeader -from .packages.six import iterkeys, itervalues, PY3 - - -__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] - - -_Null = object() - - -class RecentlyUsedContainer(MutableMapping): - """ - Provides a thread-safe dict-like container which maintains up to - ``maxsize`` keys while throwing away the least-recently-used keys beyond - ``maxsize``. - - :param maxsize: - Maximum number of recent elements to retain. - - :param dispose_func: - Every time an item is evicted from the container, - ``dispose_func(value)`` is called. Callback which will get called - """ - - ContainerCls = OrderedDict - - def __init__(self, maxsize=10, dispose_func=None): - self._maxsize = maxsize - self.dispose_func = dispose_func - - self._container = self.ContainerCls() - self.lock = RLock() - - def __getitem__(self, key): - # Re-insert the item, moving it to the end of the eviction line. - with self.lock: - item = self._container.pop(key) - self._container[key] = item - return item - - def __setitem__(self, key, value): - evicted_value = _Null - with self.lock: - # Possibly evict the existing value of 'key' - evicted_value = self._container.get(key, _Null) - self._container[key] = value - - # If we didn't evict an existing value, we might have to evict the - # least recently used item from the beginning of the container. - if len(self._container) > self._maxsize: - _key, evicted_value = self._container.popitem(last=False) - - if self.dispose_func and evicted_value is not _Null: - self.dispose_func(evicted_value) - - def __delitem__(self, key): - with self.lock: - value = self._container.pop(key) - - if self.dispose_func: - self.dispose_func(value) - - def __len__(self): - with self.lock: - return len(self._container) - - def __iter__(self): - raise NotImplementedError('Iteration over this class is unlikely to be threadsafe.') - - def clear(self): - with self.lock: - # Copy pointers to all values, then wipe the mapping - values = list(itervalues(self._container)) - self._container.clear() - - if self.dispose_func: - for value in values: - self.dispose_func(value) - - def keys(self): - with self.lock: - return list(iterkeys(self._container)) - - -class HTTPHeaderDict(MutableMapping): - """ - :param headers: - An iterable of field-value pairs. Must not contain multiple field names - when compared case-insensitively. - - :param kwargs: - Additional field-value pairs to pass in to ``dict.update``. - - A ``dict`` like container for storing HTTP Headers. - - Field names are stored and compared case-insensitively in compliance with - RFC 7230. Iteration provides the first case-sensitive key seen for each - case-insensitive pair. - - Using ``__setitem__`` syntax overwrites fields that compare equal - case-insensitively in order to maintain ``dict``'s api. For fields that - compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` - in a loop. - - If multiple fields that are equal case-insensitively are passed to the - constructor or ``.update``, the behavior is undefined and some will be - lost. - - >>> headers = HTTPHeaderDict() - >>> headers.add('Set-Cookie', 'foo=bar') - >>> headers.add('set-cookie', 'baz=quxx') - >>> headers['content-length'] = '7' - >>> headers['SET-cookie'] - 'foo=bar, baz=quxx' - >>> headers['Content-Length'] - '7' - """ - - def __init__(self, headers=None, **kwargs): - super(HTTPHeaderDict, self).__init__() - self._container = OrderedDict() - if headers is not None: - if isinstance(headers, HTTPHeaderDict): - self._copy_from(headers) - else: - self.extend(headers) - if kwargs: - self.extend(kwargs) - - def __setitem__(self, key, val): - self._container[key.lower()] = [key, val] - return self._container[key.lower()] - - def __getitem__(self, key): - val = self._container[key.lower()] - return ', '.join(val[1:]) - - def __delitem__(self, key): - del self._container[key.lower()] - - def __contains__(self, key): - return key.lower() in self._container - - def __eq__(self, other): - if not isinstance(other, Mapping) and not hasattr(other, 'keys'): - return False - if not isinstance(other, type(self)): - other = type(self)(other) - return (dict((k.lower(), v) for k, v in self.itermerged()) == - dict((k.lower(), v) for k, v in other.itermerged())) - - def __ne__(self, other): - return not self.__eq__(other) - - if not PY3: # Python 2 - iterkeys = MutableMapping.iterkeys - itervalues = MutableMapping.itervalues - - __marker = object() - - def __len__(self): - return len(self._container) - - def __iter__(self): - # Only provide the originally cased names - for vals in self._container.values(): - yield vals[0] - - def pop(self, key, default=__marker): - '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. - If key is not found, d is returned if given, otherwise KeyError is raised. - ''' - # Using the MutableMapping function directly fails due to the private marker. - # Using ordinary dict.pop would expose the internal structures. - # So let's reinvent the wheel. - try: - value = self[key] - except KeyError: - if default is self.__marker: - raise - return default - else: - del self[key] - return value - - def discard(self, key): - try: - del self[key] - except KeyError: - pass - - def add(self, key, val): - """Adds a (name, value) pair, doesn't overwrite the value if it already - exists. - - >>> headers = HTTPHeaderDict(foo='bar') - >>> headers.add('Foo', 'baz') - >>> headers['foo'] - 'bar, baz' - """ - key_lower = key.lower() - new_vals = [key, val] - # Keep the common case aka no item present as fast as possible - vals = self._container.setdefault(key_lower, new_vals) - if new_vals is not vals: - vals.append(val) - - def extend(self, *args, **kwargs): - """Generic import function for any type of header-like object. - Adapted version of MutableMapping.update in order to insert items - with self.add instead of self.__setitem__ - """ - if len(args) > 1: - raise TypeError("extend() takes at most 1 positional " - "arguments ({0} given)".format(len(args))) - other = args[0] if len(args) >= 1 else () - - if isinstance(other, HTTPHeaderDict): - for key, val in other.iteritems(): - self.add(key, val) - elif isinstance(other, Mapping): - for key in other: - self.add(key, other[key]) - elif hasattr(other, "keys"): - for key in other.keys(): - self.add(key, other[key]) - else: - for key, value in other: - self.add(key, value) - - for key, value in kwargs.items(): - self.add(key, value) - - def getlist(self, key, default=__marker): - """Returns a list of all the values for the named field. Returns an - empty list if the key doesn't exist.""" - try: - vals = self._container[key.lower()] - except KeyError: - if default is self.__marker: - return [] - return default - else: - return vals[1:] - - # Backwards compatibility for httplib - getheaders = getlist - getallmatchingheaders = getlist - iget = getlist - - # Backwards compatibility for http.cookiejar - get_all = getlist - - def __repr__(self): - return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) - - def _copy_from(self, other): - for key in other: - val = other.getlist(key) - if isinstance(val, list): - # Don't need to convert tuples - val = list(val) - self._container[key.lower()] = [key] + val - - def copy(self): - clone = type(self)() - clone._copy_from(self) - return clone - - def iteritems(self): - """Iterate over all header lines, including duplicate ones.""" - for key in self: - vals = self._container[key.lower()] - for val in vals[1:]: - yield vals[0], val - - def itermerged(self): - """Iterate over all headers, merging duplicate ones together.""" - for key in self: - val = self._container[key.lower()] - yield val[0], ', '.join(val[1:]) - - def items(self): - return list(self.iteritems()) - - @classmethod - def from_httplib(cls, message): # Python 2 - """Read headers from a Python 2 httplib message object.""" - # python2.7 does not expose a proper API for exporting multiheaders - # efficiently. This function re-reads raw lines from the message - # object and extracts the multiheaders properly. - obs_fold_continued_leaders = (' ', '\t') - headers = [] - - for line in message.headers: - if line.startswith(obs_fold_continued_leaders): - if not headers: - # We received a header line that starts with OWS as described - # in RFC-7230 S3.2.4. This indicates a multiline header, but - # there exists no previous header to which we can attach it. - raise InvalidHeader( - 'Header continuation with no previous header: %s' % line - ) - else: - key, value = headers[-1] - headers[-1] = (key, value + ' ' + line.strip()) - continue - - key, value = line.split(':', 1) - headers.append((key, value.strip())) - - return cls(headers) diff --git a/lib/urllib3/connection.py b/lib/urllib3/connection.py deleted file mode 100644 index 02b3665..0000000 --- a/lib/urllib3/connection.py +++ /dev/null @@ -1,391 +0,0 @@ -from __future__ import absolute_import -import datetime -import logging -import os -import socket -from socket import error as SocketError, timeout as SocketTimeout -import warnings -from .packages import six -from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection -from .packages.six.moves.http_client import HTTPException # noqa: F401 - -try: # Compiled with SSL? - import ssl - BaseSSLError = ssl.SSLError -except (ImportError, AttributeError): # Platform-specific: No SSL. - ssl = None - - class BaseSSLError(BaseException): - pass - - -try: # Python 3: - # Not a no-op, we're adding this to the namespace so it can be imported. - ConnectionError = ConnectionError -except NameError: # Python 2: - class ConnectionError(Exception): - pass - - -from .exceptions import ( - NewConnectionError, - ConnectTimeoutError, - SubjectAltNameWarning, - SystemTimeWarning, -) -from .packages.ssl_match_hostname import match_hostname, CertificateError - -from .util.ssl_ import ( - resolve_cert_reqs, - resolve_ssl_version, - assert_fingerprint, - create_urllib3_context, - ssl_wrap_socket -) - - -from .util import connection - -from ._collections import HTTPHeaderDict - -log = logging.getLogger(__name__) - -port_by_scheme = { - 'http': 80, - 'https': 443, -} - -# When updating RECENT_DATE, move it to within two years of the current date, -# and not less than 6 months ago. -# Example: if Today is 2018-01-01, then RECENT_DATE should be any date on or -# after 2016-01-01 (today - 2 years) AND before 2017-07-01 (today - 6 months) -RECENT_DATE = datetime.date(2017, 6, 30) - - -class DummyConnection(object): - """Used to detect a failed ConnectionCls import.""" - pass - - -class HTTPConnection(_HTTPConnection, object): - """ - Based on httplib.HTTPConnection but provides an extra constructor - backwards-compatibility layer between older and newer Pythons. - - Additional keyword parameters are used to configure attributes of the connection. - Accepted parameters include: - - - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` - - ``source_address``: Set the source address for the current connection. - - ``socket_options``: Set specific options on the underlying socket. If not specified, then - defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling - Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. - - For example, if you wish to enable TCP Keep Alive in addition to the defaults, - you might pass:: - - HTTPConnection.default_socket_options + [ - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - ] - - Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). - """ - - default_port = port_by_scheme['http'] - - #: Disable Nagle's algorithm by default. - #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` - default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] - - #: Whether this connection verifies the host's certificate. - is_verified = False - - def __init__(self, *args, **kw): - if six.PY3: # Python 3 - kw.pop('strict', None) - - # Pre-set source_address. - self.source_address = kw.get('source_address') - - #: The socket options provided by the user. If no options are - #: provided, we use the default options. - self.socket_options = kw.pop('socket_options', self.default_socket_options) - - _HTTPConnection.__init__(self, *args, **kw) - - @property - def host(self): - """ - Getter method to remove any trailing dots that indicate the hostname is an FQDN. - - In general, SSL certificates don't include the trailing dot indicating a - fully-qualified domain name, and thus, they don't validate properly when - checked against a domain name that includes the dot. In addition, some - servers may not expect to receive the trailing dot when provided. - - However, the hostname with trailing dot is critical to DNS resolution; doing a - lookup with the trailing dot will properly only resolve the appropriate FQDN, - whereas a lookup without a trailing dot will search the system's search domain - list. Thus, it's important to keep the original host around for use only in - those cases where it's appropriate (i.e., when doing DNS lookup to establish the - actual TCP connection across which we're going to send HTTP requests). - """ - return self._dns_host.rstrip('.') - - @host.setter - def host(self, value): - """ - Setter for the `host` property. - - We assume that only urllib3 uses the _dns_host attribute; httplib itself - only uses `host`, and it seems reasonable that other libraries follow suit. - """ - self._dns_host = value - - def _new_conn(self): - """ Establish a socket connection and set nodelay settings on it. - - :return: New socket connection. - """ - extra_kw = {} - if self.source_address: - extra_kw['source_address'] = self.source_address - - if self.socket_options: - extra_kw['socket_options'] = self.socket_options - - try: - conn = connection.create_connection( - (self._dns_host, self.port), self.timeout, **extra_kw) - - except SocketTimeout as e: - raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) - - except SocketError as e: - raise NewConnectionError( - self, "Failed to establish a new connection: %s" % e) - - return conn - - def _prepare_conn(self, conn): - self.sock = conn - if self._tunnel_host: - # TODO: Fix tunnel so it doesn't depend on self.sock state. - self._tunnel() - # Mark this connection as not reusable - self.auto_open = 0 - - def connect(self): - conn = self._new_conn() - self._prepare_conn(conn) - - def request_chunked(self, method, url, body=None, headers=None): - """ - Alternative to the common request method, which sends the - body with chunked encoding and not as one block - """ - headers = HTTPHeaderDict(headers if headers is not None else {}) - skip_accept_encoding = 'accept-encoding' in headers - skip_host = 'host' in headers - self.putrequest( - method, - url, - skip_accept_encoding=skip_accept_encoding, - skip_host=skip_host - ) - for header, value in headers.items(): - self.putheader(header, value) - if 'transfer-encoding' not in headers: - self.putheader('Transfer-Encoding', 'chunked') - self.endheaders() - - if body is not None: - stringish_types = six.string_types + (bytes,) - if isinstance(body, stringish_types): - body = (body,) - for chunk in body: - if not chunk: - continue - if not isinstance(chunk, bytes): - chunk = chunk.encode('utf8') - len_str = hex(len(chunk))[2:] - self.send(len_str.encode('utf-8')) - self.send(b'\r\n') - self.send(chunk) - self.send(b'\r\n') - - # After the if clause, to always have a closed body - self.send(b'0\r\n\r\n') - - -class HTTPSConnection(HTTPConnection): - default_port = port_by_scheme['https'] - - ssl_version = None - - def __init__(self, host, port=None, key_file=None, cert_file=None, - strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - ssl_context=None, server_hostname=None, **kw): - - HTTPConnection.__init__(self, host, port, strict=strict, - timeout=timeout, **kw) - - self.key_file = key_file - self.cert_file = cert_file - self.ssl_context = ssl_context - self.server_hostname = server_hostname - - # Required property for Google AppEngine 1.9.0 which otherwise causes - # HTTPS requests to go out as HTTP. (See Issue #356) - self._protocol = 'https' - - def connect(self): - conn = self._new_conn() - self._prepare_conn(conn) - - if self.ssl_context is None: - self.ssl_context = create_urllib3_context( - ssl_version=resolve_ssl_version(None), - cert_reqs=resolve_cert_reqs(None), - ) - - self.sock = ssl_wrap_socket( - sock=conn, - keyfile=self.key_file, - certfile=self.cert_file, - ssl_context=self.ssl_context, - server_hostname=self.server_hostname - ) - - -class VerifiedHTTPSConnection(HTTPSConnection): - """ - Based on httplib.HTTPSConnection but wraps the socket with - SSL certification. - """ - cert_reqs = None - ca_certs = None - ca_cert_dir = None - ssl_version = None - assert_fingerprint = None - - def set_cert(self, key_file=None, cert_file=None, - cert_reqs=None, ca_certs=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None): - """ - This method should only be called once, before the connection is used. - """ - # If cert_reqs is not provided, we can try to guess. If the user gave - # us a cert database, we assume they want to use it: otherwise, if - # they gave us an SSL Context object we should use whatever is set for - # it. - if cert_reqs is None: - if ca_certs or ca_cert_dir: - cert_reqs = 'CERT_REQUIRED' - elif self.ssl_context is not None: - cert_reqs = self.ssl_context.verify_mode - - self.key_file = key_file - self.cert_file = cert_file - self.cert_reqs = cert_reqs - self.assert_hostname = assert_hostname - self.assert_fingerprint = assert_fingerprint - self.ca_certs = ca_certs and os.path.expanduser(ca_certs) - self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) - - def connect(self): - # Add certificate verification - conn = self._new_conn() - hostname = self.host - - if self._tunnel_host: - self.sock = conn - # Calls self._set_hostport(), so self.host is - # self._tunnel_host below. - self._tunnel() - # Mark this connection as not reusable - self.auto_open = 0 - - # Override the host with the one we're requesting data from. - hostname = self._tunnel_host - - server_hostname = hostname - if self.server_hostname is not None: - server_hostname = self.server_hostname - - is_time_off = datetime.date.today() < RECENT_DATE - if is_time_off: - warnings.warn(( - 'System time is way off (before {0}). This will probably ' - 'lead to SSL verification errors').format(RECENT_DATE), - SystemTimeWarning - ) - - # Wrap socket using verification with the root certs in - # trusted_root_certs - if self.ssl_context is None: - self.ssl_context = create_urllib3_context( - ssl_version=resolve_ssl_version(self.ssl_version), - cert_reqs=resolve_cert_reqs(self.cert_reqs), - ) - - context = self.ssl_context - context.verify_mode = resolve_cert_reqs(self.cert_reqs) - self.sock = ssl_wrap_socket( - sock=conn, - keyfile=self.key_file, - certfile=self.cert_file, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - server_hostname=server_hostname, - ssl_context=context) - - if self.assert_fingerprint: - assert_fingerprint(self.sock.getpeercert(binary_form=True), - self.assert_fingerprint) - elif context.verify_mode != ssl.CERT_NONE \ - and not getattr(context, 'check_hostname', False) \ - and self.assert_hostname is not False: - # While urllib3 attempts to always turn off hostname matching from - # the TLS library, this cannot always be done. So we check whether - # the TLS Library still thinks it's matching hostnames. - cert = self.sock.getpeercert() - if not cert.get('subjectAltName', ()): - warnings.warn(( - 'Certificate for {0} has no `subjectAltName`, falling back to check for a ' - '`commonName` for now. This feature is being removed by major browsers and ' - 'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 ' - 'for details.)'.format(hostname)), - SubjectAltNameWarning - ) - _match_hostname(cert, self.assert_hostname or server_hostname) - - self.is_verified = ( - context.verify_mode == ssl.CERT_REQUIRED or - self.assert_fingerprint is not None - ) - - -def _match_hostname(cert, asserted_hostname): - try: - match_hostname(cert, asserted_hostname) - except CertificateError as e: - log.error( - 'Certificate did not match expected hostname: %s. ' - 'Certificate: %s', asserted_hostname, cert - ) - # Add cert to exception and reraise so client code can inspect - # the cert when catching the exception, if they want to - e._peer_cert = cert - raise - - -if ssl: - # Make a copy for testing. - UnverifiedHTTPSConnection = HTTPSConnection - HTTPSConnection = VerifiedHTTPSConnection -else: - HTTPSConnection = DummyConnection diff --git a/lib/urllib3/connectionpool.py b/lib/urllib3/connectionpool.py deleted file mode 100644 index f7a8f19..0000000 --- a/lib/urllib3/connectionpool.py +++ /dev/null @@ -1,896 +0,0 @@ -from __future__ import absolute_import -import errno -import logging -import sys -import warnings - -from socket import error as SocketError, timeout as SocketTimeout -import socket - - -from .exceptions import ( - ClosedPoolError, - ProtocolError, - EmptyPoolError, - HeaderParsingError, - HostChangedError, - LocationValueError, - MaxRetryError, - ProxyError, - ReadTimeoutError, - SSLError, - TimeoutError, - InsecureRequestWarning, - NewConnectionError, -) -from .packages.ssl_match_hostname import CertificateError -from .packages import six -from .packages.six.moves import queue -from .connection import ( - port_by_scheme, - DummyConnection, - HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, - HTTPException, BaseSSLError, -) -from .request import RequestMethods -from .response import HTTPResponse - -from .util.connection import is_connection_dropped -from .util.request import set_file_position -from .util.response import assert_header_parsing -from .util.retry import Retry -from .util.timeout import Timeout -from .util.url import get_host, Url, NORMALIZABLE_SCHEMES -from .util.queue import LifoQueue - - -xrange = six.moves.xrange - -log = logging.getLogger(__name__) - -_Default = object() - - -# Pool objects -class ConnectionPool(object): - """ - Base class for all connection pools, such as - :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. - """ - - scheme = None - QueueCls = LifoQueue - - def __init__(self, host, port=None): - if not host: - raise LocationValueError("No host specified.") - - self.host = _ipv6_host(host, self.scheme) - self._proxy_host = host.lower() - self.port = port - - def __str__(self): - return '%s(host=%r, port=%r)' % (type(self).__name__, - self.host, self.port) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() - # Return False to re-raise any potential exceptions - return False - - def close(self): - """ - Close all pooled connections and disable the pool. - """ - pass - - -# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 -_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} - - -class HTTPConnectionPool(ConnectionPool, RequestMethods): - """ - Thread-safe connection pool for one host. - - :param host: - Host used for this HTTP Connection (e.g. "localhost"), passed into - :class:`httplib.HTTPConnection`. - - :param port: - Port used for this HTTP Connection (None is equivalent to 80), passed - into :class:`httplib.HTTPConnection`. - - :param strict: - Causes BadStatusLine to be raised if the status line can't be parsed - as a valid HTTP/1.0 or 1.1 status line, passed into - :class:`httplib.HTTPConnection`. - - .. note:: - Only works in Python 2. This parameter is ignored in Python 3. - - :param timeout: - Socket timeout in seconds for each individual connection. This can - be a float or integer, which sets the timeout for the HTTP request, - or an instance of :class:`urllib3.util.Timeout` which gives you more - fine-grained control over request timeouts. After the constructor has - been parsed, this is always a `urllib3.util.Timeout` object. - - :param maxsize: - Number of connections to save that can be reused. More than 1 is useful - in multithreaded situations. If ``block`` is set to False, more - connections will be created but they will not be saved once they've - been used. - - :param block: - If set to True, no more than ``maxsize`` connections will be used at - a time. When no free connections are available, the call will block - until a connection has been released. This is a useful side effect for - particular multithreaded situations where one does not want to use more - than maxsize connections per host to prevent flooding. - - :param headers: - Headers to include with all requests, unless other headers are given - explicitly. - - :param retries: - Retry configuration to use by default with requests in this pool. - - :param _proxy: - Parsed proxy URL, should not be used directly, instead, see - :class:`urllib3.connectionpool.ProxyManager`" - - :param _proxy_headers: - A dictionary with proxy headers, should not be used directly, - instead, see :class:`urllib3.connectionpool.ProxyManager`" - - :param \\**conn_kw: - Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, - :class:`urllib3.connection.HTTPSConnection` instances. - """ - - scheme = 'http' - ConnectionCls = HTTPConnection - ResponseCls = HTTPResponse - - def __init__(self, host, port=None, strict=False, - timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, - headers=None, retries=None, - _proxy=None, _proxy_headers=None, - **conn_kw): - ConnectionPool.__init__(self, host, port) - RequestMethods.__init__(self, headers) - - self.strict = strict - - if not isinstance(timeout, Timeout): - timeout = Timeout.from_float(timeout) - - if retries is None: - retries = Retry.DEFAULT - - self.timeout = timeout - self.retries = retries - - self.pool = self.QueueCls(maxsize) - self.block = block - - self.proxy = _proxy - self.proxy_headers = _proxy_headers or {} - - # Fill the queue up so that doing get() on it will block properly - for _ in xrange(maxsize): - self.pool.put(None) - - # These are mostly for testing and debugging purposes. - self.num_connections = 0 - self.num_requests = 0 - self.conn_kw = conn_kw - - if self.proxy: - # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. - # We cannot know if the user has added default socket options, so we cannot replace the - # list. - self.conn_kw.setdefault('socket_options', []) - - def _new_conn(self): - """ - Return a fresh :class:`HTTPConnection`. - """ - self.num_connections += 1 - log.debug("Starting new HTTP connection (%d): %s:%s", - self.num_connections, self.host, self.port or "80") - - conn = self.ConnectionCls(host=self.host, port=self.port, - timeout=self.timeout.connect_timeout, - strict=self.strict, **self.conn_kw) - return conn - - def _get_conn(self, timeout=None): - """ - Get a connection. Will return a pooled connection if one is available. - - If no connections are available and :prop:`.block` is ``False``, then a - fresh connection is returned. - - :param timeout: - Seconds to wait before giving up and raising - :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and - :prop:`.block` is ``True``. - """ - conn = None - try: - conn = self.pool.get(block=self.block, timeout=timeout) - - except AttributeError: # self.pool is None - raise ClosedPoolError(self, "Pool is closed.") - - except queue.Empty: - if self.block: - raise EmptyPoolError(self, - "Pool reached maximum size and no more " - "connections are allowed.") - pass # Oh well, we'll create a new connection then - - # If this is a persistent connection, check if it got disconnected - if conn and is_connection_dropped(conn): - log.debug("Resetting dropped connection: %s", self.host) - conn.close() - if getattr(conn, 'auto_open', 1) == 0: - # This is a proxied connection that has been mutated by - # httplib._tunnel() and cannot be reused (since it would - # attempt to bypass the proxy) - conn = None - - return conn or self._new_conn() - - def _put_conn(self, conn): - """ - Put a connection back into the pool. - - :param conn: - Connection object for the current host and port as returned by - :meth:`._new_conn` or :meth:`._get_conn`. - - If the pool is already full, the connection is closed and discarded - because we exceeded maxsize. If connections are discarded frequently, - then maxsize should be increased. - - If the pool is closed, then the connection will be closed and discarded. - """ - try: - self.pool.put(conn, block=False) - return # Everything is dandy, done. - except AttributeError: - # self.pool is None. - pass - except queue.Full: - # This should never happen if self.block == True - log.warning( - "Connection pool is full, discarding connection: %s", - self.host) - - # Connection never got put back into the pool, close it. - if conn: - conn.close() - - def _validate_conn(self, conn): - """ - Called right before a request is made, after the socket is created. - """ - pass - - def _prepare_proxy(self, conn): - # Nothing to do for HTTP connections. - pass - - def _get_timeout(self, timeout): - """ Helper that always returns a :class:`urllib3.util.Timeout` """ - if timeout is _Default: - return self.timeout.clone() - - if isinstance(timeout, Timeout): - return timeout.clone() - else: - # User passed us an int/float. This is for backwards compatibility, - # can be removed later - return Timeout.from_float(timeout) - - def _raise_timeout(self, err, url, timeout_value): - """Is the error actually a timeout? Will raise a ReadTimeout or pass""" - - if isinstance(err, SocketTimeout): - raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) - - # See the above comment about EAGAIN in Python 3. In Python 2 we have - # to specifically catch it and throw the timeout error - if hasattr(err, 'errno') and err.errno in _blocking_errnos: - raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) - - # Catch possible read timeouts thrown as SSL errors. If not the - # case, rethrow the original. We need to do this because of: - # http://bugs.python.org/issue10272 - if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python < 2.7.4 - raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) - - def _make_request(self, conn, method, url, timeout=_Default, chunked=False, - **httplib_request_kw): - """ - Perform a request on a given urllib connection object taken from our - pool. - - :param conn: - a connection from one of our connection pools - - :param timeout: - Socket timeout in seconds for the request. This can be a - float or integer, which will set the same timeout value for - the socket connect and the socket read, or an instance of - :class:`urllib3.util.Timeout`, which gives you more fine-grained - control over your timeouts. - """ - self.num_requests += 1 - - timeout_obj = self._get_timeout(timeout) - timeout_obj.start_connect() - conn.timeout = timeout_obj.connect_timeout - - # Trigger any extra validation we need to do. - try: - self._validate_conn(conn) - except (SocketTimeout, BaseSSLError) as e: - # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. - self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) - raise - - # conn.request() calls httplib.*.request, not the method in - # urllib3.request. It also calls makefile (recv) on the socket. - if chunked: - conn.request_chunked(method, url, **httplib_request_kw) - else: - conn.request(method, url, **httplib_request_kw) - - # Reset the timeout for the recv() on the socket - read_timeout = timeout_obj.read_timeout - - # App Engine doesn't have a sock attr - if getattr(conn, 'sock', None): - # In Python 3 socket.py will catch EAGAIN and return None when you - # try and read into the file pointer created by http.client, which - # instead raises a BadStatusLine exception. Instead of catching - # the exception and assuming all BadStatusLine exceptions are read - # timeouts, check for a zero timeout before making the request. - if read_timeout == 0: - raise ReadTimeoutError( - self, url, "Read timed out. (read timeout=%s)" % read_timeout) - if read_timeout is Timeout.DEFAULT_TIMEOUT: - conn.sock.settimeout(socket.getdefaulttimeout()) - else: # None or a value - conn.sock.settimeout(read_timeout) - - # Receive the response from the server - try: - try: # Python 2.7, use buffering of HTTP responses - httplib_response = conn.getresponse(buffering=True) - except TypeError: # Python 3 - try: - httplib_response = conn.getresponse() - except Exception as e: - # Remove the TypeError from the exception chain in Python 3; - # otherwise it looks like a programming error was the cause. - six.raise_from(e, None) - except (SocketTimeout, BaseSSLError, SocketError) as e: - self._raise_timeout(err=e, url=url, timeout_value=read_timeout) - raise - - # AppEngine doesn't have a version attr. - http_version = getattr(conn, '_http_vsn_str', 'HTTP/?') - log.debug("%s://%s:%s \"%s %s %s\" %s %s", self.scheme, self.host, self.port, - method, url, http_version, httplib_response.status, - httplib_response.length) - - try: - assert_header_parsing(httplib_response.msg) - except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 - log.warning( - 'Failed to parse headers (url=%s): %s', - self._absolute_url(url), hpe, exc_info=True) - - return httplib_response - - def _absolute_url(self, path): - return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url - - def close(self): - """ - Close all pooled connections and disable the pool. - """ - if self.pool is None: - return - # Disable access to the pool - old_pool, self.pool = self.pool, None - - try: - while True: - conn = old_pool.get(block=False) - if conn: - conn.close() - - except queue.Empty: - pass # Done. - - def is_same_host(self, url): - """ - Check if the given ``url`` is a member of the same host as this - connection pool. - """ - if url.startswith('/'): - return True - - # TODO: Add optional support for socket.gethostbyname checking. - scheme, host, port = get_host(url) - - host = _ipv6_host(host, self.scheme) - - # Use explicit default port for comparison when none is given - if self.port and not port: - port = port_by_scheme.get(scheme) - elif not self.port and port == port_by_scheme.get(scheme): - port = None - - return (scheme, host, port) == (self.scheme, self.host, self.port) - - def urlopen(self, method, url, body=None, headers=None, retries=None, - redirect=True, assert_same_host=True, timeout=_Default, - pool_timeout=None, release_conn=None, chunked=False, - body_pos=None, **response_kw): - """ - Get a connection from the pool and perform an HTTP request. This is the - lowest level call for making a request, so you'll need to specify all - the raw details. - - .. note:: - - More commonly, it's appropriate to use a convenience method provided - by :class:`.RequestMethods`, such as :meth:`request`. - - .. note:: - - `release_conn` will only behave as expected if - `preload_content=False` because we want to make - `preload_content=False` the default behaviour someday soon without - breaking backwards compatibility. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param body: - Data to send in the request body (useful for creating - POST requests, see HTTPConnectionPool.post_url for - more convenience). - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. - - :param retries: - Configure the number of retries to allow before raising a - :class:`~urllib3.exceptions.MaxRetryError` exception. - - Pass ``None`` to retry until you receive a response. Pass a - :class:`~urllib3.util.retry.Retry` object for fine-grained control - over different types of retries. - Pass an integer number to retry connection errors that many times, - but no other types of errors. Pass zero to never retry. - - If ``False``, then retries are disabled and any exception is raised - immediately. Also, instead of raising a MaxRetryError on redirects, - the redirect response will be returned. - - :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. - - :param redirect: - If True, automatically handle redirects (status codes 301, 302, - 303, 307, 308). Each redirect counts as a retry. Disabling retries - will disable redirect, too. - - :param assert_same_host: - If ``True``, will make sure that the host of the pool requests is - consistent else will raise HostChangedError. When False, you can - use the pool on an HTTP proxy and request foreign hosts. - - :param timeout: - If specified, overrides the default timeout for this one - request. It may be a float (in seconds) or an instance of - :class:`urllib3.util.Timeout`. - - :param pool_timeout: - If set and the pool is set to block=True, then this method will - block for ``pool_timeout`` seconds and raise EmptyPoolError if no - connection is available within the time period. - - :param release_conn: - If False, then the urlopen call will not release the connection - back into the pool once a response is received (but will release if - you read the entire contents of the response such as when - `preload_content=True`). This is useful if you're not preloading - the response's content immediately. You will need to call - ``r.release_conn()`` on the response ``r`` to return the connection - back into the pool. If None, it takes the value of - ``response_kw.get('preload_content', True)``. - - :param chunked: - If True, urllib3 will send the body using chunked transfer - encoding. Otherwise, urllib3 will send the body using the standard - content-length form. Defaults to False. - - :param int body_pos: - Position to seek to in file-like body in the event of a retry or - redirect. Typically this won't need to be set because urllib3 will - auto-populate the value when needed. - - :param \\**response_kw: - Additional parameters are passed to - :meth:`urllib3.response.HTTPResponse.from_httplib` - """ - if headers is None: - headers = self.headers - - if not isinstance(retries, Retry): - retries = Retry.from_int(retries, redirect=redirect, default=self.retries) - - if release_conn is None: - release_conn = response_kw.get('preload_content', True) - - # Check host - if assert_same_host and not self.is_same_host(url): - raise HostChangedError(self, url, retries) - - conn = None - - # Track whether `conn` needs to be released before - # returning/raising/recursing. Update this variable if necessary, and - # leave `release_conn` constant throughout the function. That way, if - # the function recurses, the original value of `release_conn` will be - # passed down into the recursive call, and its value will be respected. - # - # See issue #651 [1] for details. - # - # [1] <https://github.com/shazow/urllib3/issues/651> - release_this_conn = release_conn - - # Merge the proxy headers. Only do this in HTTP. We have to copy the - # headers dict so we can safely change it without those changes being - # reflected in anyone else's copy. - if self.scheme == 'http': - headers = headers.copy() - headers.update(self.proxy_headers) - - # Must keep the exception bound to a separate variable or else Python 3 - # complains about UnboundLocalError. - err = None - - # Keep track of whether we cleanly exited the except block. This - # ensures we do proper cleanup in finally. - clean_exit = False - - # Rewind body position, if needed. Record current position - # for future rewinds in the event of a redirect/retry. - body_pos = set_file_position(body, body_pos) - - try: - # Request a connection from the queue. - timeout_obj = self._get_timeout(timeout) - conn = self._get_conn(timeout=pool_timeout) - - conn.timeout = timeout_obj.connect_timeout - - is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None) - if is_new_proxy_conn: - self._prepare_proxy(conn) - - # Make the request on the httplib connection object. - httplib_response = self._make_request(conn, method, url, - timeout=timeout_obj, - body=body, headers=headers, - chunked=chunked) - - # If we're going to release the connection in ``finally:``, then - # the response doesn't need to know about the connection. Otherwise - # it will also try to release it and we'll have a double-release - # mess. - response_conn = conn if not release_conn else None - - # Pass method to Response for length checking - response_kw['request_method'] = method - - # Import httplib's response into our own wrapper object - response = self.ResponseCls.from_httplib(httplib_response, - pool=self, - connection=response_conn, - retries=retries, - **response_kw) - - # Everything went great! - clean_exit = True - - except queue.Empty: - # Timed out by queue. - raise EmptyPoolError(self, "No pool connections are available.") - - except (TimeoutError, HTTPException, SocketError, ProtocolError, - BaseSSLError, SSLError, CertificateError) as e: - # Discard the connection for these exceptions. It will be - # replaced during the next _get_conn() call. - clean_exit = False - if isinstance(e, (BaseSSLError, CertificateError)): - e = SSLError(e) - elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: - e = ProxyError('Cannot connect to proxy.', e) - elif isinstance(e, (SocketError, HTTPException)): - e = ProtocolError('Connection aborted.', e) - - retries = retries.increment(method, url, error=e, _pool=self, - _stacktrace=sys.exc_info()[2]) - retries.sleep() - - # Keep track of the error for the retry warning. - err = e - - finally: - if not clean_exit: - # We hit some kind of exception, handled or otherwise. We need - # to throw the connection away unless explicitly told not to. - # Close the connection, set the variable to None, and make sure - # we put the None back in the pool to avoid leaking it. - conn = conn and conn.close() - release_this_conn = True - - if release_this_conn: - # Put the connection back to be reused. If the connection is - # expired then it will be None, which will get replaced with a - # fresh connection during _get_conn. - self._put_conn(conn) - - if not conn: - # Try again - log.warning("Retrying (%r) after connection " - "broken by '%r': %s", retries, err, url) - return self.urlopen(method, url, body, headers, retries, - redirect, assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, body_pos=body_pos, - **response_kw) - - def drain_and_release_conn(response): - try: - # discard any remaining response body, the connection will be - # released back to the pool once the entire response is read - response.read() - except (TimeoutError, HTTPException, SocketError, ProtocolError, - BaseSSLError, SSLError) as e: - pass - - # Handle redirect? - redirect_location = redirect and response.get_redirect_location() - if redirect_location: - if response.status == 303: - method = 'GET' - - try: - retries = retries.increment(method, url, response=response, _pool=self) - except MaxRetryError: - if retries.raise_on_redirect: - # Drain and release the connection for this response, since - # we're not returning it to be released manually. - drain_and_release_conn(response) - raise - return response - - # drain and return the connection to the pool before recursing - drain_and_release_conn(response) - - retries.sleep_for_retry(response) - log.debug("Redirecting %s -> %s", url, redirect_location) - return self.urlopen( - method, redirect_location, body, headers, - retries=retries, redirect=redirect, - assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, body_pos=body_pos, - **response_kw) - - # Check if we should retry the HTTP response. - has_retry_after = bool(response.getheader('Retry-After')) - if retries.is_retry(method, response.status, has_retry_after): - try: - retries = retries.increment(method, url, response=response, _pool=self) - except MaxRetryError: - if retries.raise_on_status: - # Drain and release the connection for this response, since - # we're not returning it to be released manually. - drain_and_release_conn(response) - raise - return response - - # drain and return the connection to the pool before recursing - drain_and_release_conn(response) - - retries.sleep(response) - log.debug("Retry: %s", url) - return self.urlopen( - method, url, body, headers, - retries=retries, redirect=redirect, - assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, - body_pos=body_pos, **response_kw) - - return response - - -class HTTPSConnectionPool(HTTPConnectionPool): - """ - Same as :class:`.HTTPConnectionPool`, but HTTPS. - - When Python is compiled with the :mod:`ssl` module, then - :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates, - instead of :class:`.HTTPSConnection`. - - :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``, - ``assert_hostname`` and ``host`` in this order to verify connections. - If ``assert_hostname`` is False, no verification is done. - - The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, - ``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is - available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade - the connection socket into an SSL socket. - """ - - scheme = 'https' - ConnectionCls = HTTPSConnection - - def __init__(self, host, port=None, - strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, - block=False, headers=None, retries=None, - _proxy=None, _proxy_headers=None, - key_file=None, cert_file=None, cert_reqs=None, - ca_certs=None, ssl_version=None, - assert_hostname=None, assert_fingerprint=None, - ca_cert_dir=None, **conn_kw): - - HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, - block, headers, retries, _proxy, _proxy_headers, - **conn_kw) - - if ca_certs and cert_reqs is None: - cert_reqs = 'CERT_REQUIRED' - - self.key_file = key_file - self.cert_file = cert_file - self.cert_reqs = cert_reqs - self.ca_certs = ca_certs - self.ca_cert_dir = ca_cert_dir - self.ssl_version = ssl_version - self.assert_hostname = assert_hostname - self.assert_fingerprint = assert_fingerprint - - def _prepare_conn(self, conn): - """ - Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` - and establish the tunnel if proxy is used. - """ - - if isinstance(conn, VerifiedHTTPSConnection): - conn.set_cert(key_file=self.key_file, - cert_file=self.cert_file, - cert_reqs=self.cert_reqs, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - assert_hostname=self.assert_hostname, - assert_fingerprint=self.assert_fingerprint) - conn.ssl_version = self.ssl_version - return conn - - def _prepare_proxy(self, conn): - """ - Establish tunnel connection early, because otherwise httplib - would improperly set Host: header to proxy's IP:port. - """ - conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) - conn.connect() - - def _new_conn(self): - """ - Return a fresh :class:`httplib.HTTPSConnection`. - """ - self.num_connections += 1 - log.debug("Starting new HTTPS connection (%d): %s:%s", - self.num_connections, self.host, self.port or "443") - - if not self.ConnectionCls or self.ConnectionCls is DummyConnection: - raise SSLError("Can't connect to HTTPS URL because the SSL " - "module is not available.") - - actual_host = self.host - actual_port = self.port - if self.proxy is not None: - actual_host = self.proxy.host - actual_port = self.proxy.port - - conn = self.ConnectionCls(host=actual_host, port=actual_port, - timeout=self.timeout.connect_timeout, - strict=self.strict, **self.conn_kw) - - return self._prepare_conn(conn) - - def _validate_conn(self, conn): - """ - Called right before a request is made, after the socket is created. - """ - super(HTTPSConnectionPool, self)._validate_conn(conn) - - # Force connect early to allow us to validate the connection. - if not getattr(conn, 'sock', None): # AppEngine might not have `.sock` - conn.connect() - - if not conn.is_verified: - warnings.warn(( - 'Unverified HTTPS request is being made. ' - 'Adding certificate verification is strongly advised. See: ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings'), - InsecureRequestWarning) - - -def connection_from_url(url, **kw): - """ - Given a url, return an :class:`.ConnectionPool` instance of its host. - - This is a shortcut for not having to parse out the scheme, host, and port - of the url before creating an :class:`.ConnectionPool` instance. - - :param url: - Absolute URL string that must include the scheme. Port is optional. - - :param \\**kw: - Passes additional parameters to the constructor of the appropriate - :class:`.ConnectionPool`. Useful for specifying things like - timeout, maxsize, headers, etc. - - Example:: - - >>> conn = connection_from_url('http://google.com/') - >>> r = conn.request('GET', '/') - """ - scheme, host, port = get_host(url) - port = port or port_by_scheme.get(scheme, 80) - if scheme == 'https': - return HTTPSConnectionPool(host, port=port, **kw) - else: - return HTTPConnectionPool(host, port=port, **kw) - - -def _ipv6_host(host, scheme): - """ - Process IPv6 address literals - """ - - # httplib doesn't like it when we include brackets in IPv6 addresses - # Specifically, if we include brackets but also pass the port then - # httplib crazily doubles up the square brackets on the Host header. - # Instead, we need to make sure we never pass ``None`` as the port. - # However, for backward compatibility reasons we can't actually - # *assert* that. See http://bugs.python.org/issue28539 - # - # Also if an IPv6 address literal has a zone identifier, the - # percent sign might be URIencoded, convert it back into ASCII - if host.startswith('[') and host.endswith(']'): - host = host.replace('%25', '%').strip('[]') - if scheme in NORMALIZABLE_SCHEMES: - host = host.lower() - return host diff --git a/lib/urllib3/contrib/__init__.py b/lib/urllib3/contrib/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/urllib3/contrib/_appengine_environ.py b/lib/urllib3/contrib/_appengine_environ.py deleted file mode 100644 index f3e0094..0000000 --- a/lib/urllib3/contrib/_appengine_environ.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -This module provides means to detect the App Engine environment. -""" - -import os - - -def is_appengine(): - return (is_local_appengine() or - is_prod_appengine() or - is_prod_appengine_mvms()) - - -def is_appengine_sandbox(): - return is_appengine() and not is_prod_appengine_mvms() - - -def is_local_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Development/' in os.environ['SERVER_SOFTWARE']) - - -def is_prod_appengine(): - return ('APPENGINE_RUNTIME' in os.environ and - 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and - not is_prod_appengine_mvms()) - - -def is_prod_appengine_mvms(): - return os.environ.get('GAE_VM', False) == 'true' diff --git a/lib/urllib3/contrib/_securetransport/__init__.py b/lib/urllib3/contrib/_securetransport/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/urllib3/contrib/_securetransport/bindings.py b/lib/urllib3/contrib/_securetransport/bindings.py deleted file mode 100644 index bcf41c0..0000000 --- a/lib/urllib3/contrib/_securetransport/bindings.py +++ /dev/null @@ -1,593 +0,0 @@ -""" -This module uses ctypes to bind a whole bunch of functions and constants from -SecureTransport. The goal here is to provide the low-level API to -SecureTransport. These are essentially the C-level functions and constants, and -they're pretty gross to work with. - -This code is a bastardised version of the code found in Will Bond's oscrypto -library. An enormous debt is owed to him for blazing this trail for us. For -that reason, this code should be considered to be covered both by urllib3's -license and by oscrypto's: - - Copyright (c) 2015-2016 Will Bond <will@wbond.net> - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. -""" -from __future__ import absolute_import - -import platform -from ctypes.util import find_library -from ctypes import ( - c_void_p, c_int32, c_char_p, c_size_t, c_byte, c_uint32, c_ulong, c_long, - c_bool -) -from ctypes import CDLL, POINTER, CFUNCTYPE - - -security_path = find_library('Security') -if not security_path: - raise ImportError('The library Security could not be found') - - -core_foundation_path = find_library('CoreFoundation') -if not core_foundation_path: - raise ImportError('The library CoreFoundation could not be found') - - -version = platform.mac_ver()[0] -version_info = tuple(map(int, version.split('.'))) -if version_info < (10, 8): - raise OSError( - 'Only OS X 10.8 and newer are supported, not %s.%s' % ( - version_info[0], version_info[1] - ) - ) - -Security = CDLL(security_path, use_errno=True) -CoreFoundation = CDLL(core_foundation_path, use_errno=True) - -Boolean = c_bool -CFIndex = c_long -CFStringEncoding = c_uint32 -CFData = c_void_p -CFString = c_void_p -CFArray = c_void_p -CFMutableArray = c_void_p -CFDictionary = c_void_p -CFError = c_void_p -CFType = c_void_p -CFTypeID = c_ulong - -CFTypeRef = POINTER(CFType) -CFAllocatorRef = c_void_p - -OSStatus = c_int32 - -CFDataRef = POINTER(CFData) -CFStringRef = POINTER(CFString) -CFArrayRef = POINTER(CFArray) -CFMutableArrayRef = POINTER(CFMutableArray) -CFDictionaryRef = POINTER(CFDictionary) -CFArrayCallBacks = c_void_p -CFDictionaryKeyCallBacks = c_void_p -CFDictionaryValueCallBacks = c_void_p - -SecCertificateRef = POINTER(c_void_p) -SecExternalFormat = c_uint32 -SecExternalItemType = c_uint32 -SecIdentityRef = POINTER(c_void_p) -SecItemImportExportFlags = c_uint32 -SecItemImportExportKeyParameters = c_void_p -SecKeychainRef = POINTER(c_void_p) -SSLProtocol = c_uint32 -SSLCipherSuite = c_uint32 -SSLContextRef = POINTER(c_void_p) -SecTrustRef = POINTER(c_void_p) -SSLConnectionRef = c_uint32 -SecTrustResultType = c_uint32 -SecTrustOptionFlags = c_uint32 -SSLProtocolSide = c_uint32 -SSLConnectionType = c_uint32 -SSLSessionOption = c_uint32 - - -try: - Security.SecItemImport.argtypes = [ - CFDataRef, - CFStringRef, - POINTER(SecExternalFormat), - POINTER(SecExternalItemType), - SecItemImportExportFlags, - POINTER(SecItemImportExportKeyParameters), - SecKeychainRef, - POINTER(CFArrayRef), - ] - Security.SecItemImport.restype = OSStatus - - Security.SecCertificateGetTypeID.argtypes = [] - Security.SecCertificateGetTypeID.restype = CFTypeID - - Security.SecIdentityGetTypeID.argtypes = [] - Security.SecIdentityGetTypeID.restype = CFTypeID - - Security.SecKeyGetTypeID.argtypes = [] - Security.SecKeyGetTypeID.restype = CFTypeID - - Security.SecCertificateCreateWithData.argtypes = [ - CFAllocatorRef, - CFDataRef - ] - Security.SecCertificateCreateWithData.restype = SecCertificateRef - - Security.SecCertificateCopyData.argtypes = [ - SecCertificateRef - ] - Security.SecCertificateCopyData.restype = CFDataRef - - Security.SecCopyErrorMessageString.argtypes = [ - OSStatus, - c_void_p - ] - Security.SecCopyErrorMessageString.restype = CFStringRef - - Security.SecIdentityCreateWithCertificate.argtypes = [ - CFTypeRef, - SecCertificateRef, - POINTER(SecIdentityRef) - ] - Security.SecIdentityCreateWithCertificate.restype = OSStatus - - Security.SecKeychainCreate.argtypes = [ - c_char_p, - c_uint32, - c_void_p, - Boolean, - c_void_p, - POINTER(SecKeychainRef) - ] - Security.SecKeychainCreate.restype = OSStatus - - Security.SecKeychainDelete.argtypes = [ - SecKeychainRef - ] - Security.SecKeychainDelete.restype = OSStatus - - Security.SecPKCS12Import.argtypes = [ - CFDataRef, - CFDictionaryRef, - POINTER(CFArrayRef) - ] - Security.SecPKCS12Import.restype = OSStatus - - SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) - SSLWriteFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t)) - - Security.SSLSetIOFuncs.argtypes = [ - SSLContextRef, - SSLReadFunc, - SSLWriteFunc - ] - Security.SSLSetIOFuncs.restype = OSStatus - - Security.SSLSetPeerID.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t - ] - Security.SSLSetPeerID.restype = OSStatus - - Security.SSLSetCertificate.argtypes = [ - SSLContextRef, - CFArrayRef - ] - Security.SSLSetCertificate.restype = OSStatus - - Security.SSLSetCertificateAuthorities.argtypes = [ - SSLContextRef, - CFTypeRef, - Boolean - ] - Security.SSLSetCertificateAuthorities.restype = OSStatus - - Security.SSLSetConnection.argtypes = [ - SSLContextRef, - SSLConnectionRef - ] - Security.SSLSetConnection.restype = OSStatus - - Security.SSLSetPeerDomainName.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t - ] - Security.SSLSetPeerDomainName.restype = OSStatus - - Security.SSLHandshake.argtypes = [ - SSLContextRef - ] - Security.SSLHandshake.restype = OSStatus - - Security.SSLRead.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t, - POINTER(c_size_t) - ] - Security.SSLRead.restype = OSStatus - - Security.SSLWrite.argtypes = [ - SSLContextRef, - c_char_p, - c_size_t, - POINTER(c_size_t) - ] - Security.SSLWrite.restype = OSStatus - - Security.SSLClose.argtypes = [ - SSLContextRef - ] - Security.SSLClose.restype = OSStatus - - Security.SSLGetNumberSupportedCiphers.argtypes = [ - SSLContextRef, - POINTER(c_size_t) - ] - Security.SSLGetNumberSupportedCiphers.restype = OSStatus - - Security.SSLGetSupportedCiphers.argtypes = [ - SSLContextRef, - POINTER(SSLCipherSuite), - POINTER(c_size_t) - ] - Security.SSLGetSupportedCiphers.restype = OSStatus - - Security.SSLSetEnabledCiphers.argtypes = [ - SSLContextRef, - POINTER(SSLCipherSuite), - c_size_t - ] - Security.SSLSetEnabledCiphers.restype = OSStatus - - Security.SSLGetNumberEnabledCiphers.argtype = [ - SSLContextRef, - POINTER(c_size_t) - ] - Security.SSLGetNumberEnabledCiphers.restype = OSStatus - - Security.SSLGetEnabledCiphers.argtypes = [ - SSLContextRef, - POINTER(SSLCipherSuite), - POINTER(c_size_t) - ] - Security.SSLGetEnabledCiphers.restype = OSStatus - - Security.SSLGetNegotiatedCipher.argtypes = [ - SSLContextRef, - POINTER(SSLCipherSuite) - ] - Security.SSLGetNegotiatedCipher.restype = OSStatus - - Security.SSLGetNegotiatedProtocolVersion.argtypes = [ - SSLContextRef, - POINTER(SSLProtocol) - ] - Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus - - Security.SSLCopyPeerTrust.argtypes = [ - SSLContextRef, - POINTER(SecTrustRef) - ] - Security.SSLCopyPeerTrust.restype = OSStatus - - Security.SecTrustSetAnchorCertificates.argtypes = [ - SecTrustRef, - CFArrayRef - ] - Security.SecTrustSetAnchorCertificates.restype = OSStatus - - Security.SecTrustSetAnchorCertificatesOnly.argstypes = [ - SecTrustRef, - Boolean - ] - Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus - - Security.SecTrustEvaluate.argtypes = [ - SecTrustRef, - POINTER(SecTrustResultType) - ] - Security.SecTrustEvaluate.restype = OSStatus - - Security.SecTrustGetCertificateCount.argtypes = [ - SecTrustRef - ] - Security.SecTrustGetCertificateCount.restype = CFIndex - - Security.SecTrustGetCertificateAtIndex.argtypes = [ - SecTrustRef, - CFIndex - ] - Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef - - Security.SSLCreateContext.argtypes = [ - CFAllocatorRef, - SSLProtocolSide, - SSLConnectionType - ] - Security.SSLCreateContext.restype = SSLContextRef - - Security.SSLSetSessionOption.argtypes = [ - SSLContextRef, - SSLSessionOption, - Boolean - ] - Security.SSLSetSessionOption.restype = OSStatus - - Security.SSLSetProtocolVersionMin.argtypes = [ - SSLContextRef, - SSLProtocol - ] - Security.SSLSetProtocolVersionMin.restype = OSStatus - - Security.SSLSetProtocolVersionMax.argtypes = [ - SSLContextRef, - SSLProtocol - ] - Security.SSLSetProtocolVersionMax.restype = OSStatus - - Security.SecCopyErrorMessageString.argtypes = [ - OSStatus, - c_void_p - ] - Security.SecCopyErrorMessageString.restype = CFStringRef - - Security.SSLReadFunc = SSLReadFunc - Security.SSLWriteFunc = SSLWriteFunc - Security.SSLContextRef = SSLContextRef - Security.SSLProtocol = SSLProtocol - Security.SSLCipherSuite = SSLCipherSuite - Security.SecIdentityRef = SecIdentityRef - Security.SecKeychainRef = SecKeychainRef - Security.SecTrustRef = SecTrustRef - Security.SecTrustResultType = SecTrustResultType - Security.SecExternalFormat = SecExternalFormat - Security.OSStatus = OSStatus - - Security.kSecImportExportPassphrase = CFStringRef.in_dll( - Security, 'kSecImportExportPassphrase' - ) - Security.kSecImportItemIdentity = CFStringRef.in_dll( - Security, 'kSecImportItemIdentity' - ) - - # CoreFoundation time! - CoreFoundation.CFRetain.argtypes = [ - CFTypeRef - ] - CoreFoundation.CFRetain.restype = CFTypeRef - - CoreFoundation.CFRelease.argtypes = [ - CFTypeRef - ] - CoreFoundation.CFRelease.restype = None - - CoreFoundation.CFGetTypeID.argtypes = [ - CFTypeRef - ] - CoreFoundation.CFGetTypeID.restype = CFTypeID - - CoreFoundation.CFStringCreateWithCString.argtypes = [ - CFAllocatorRef, - c_char_p, - CFStringEncoding - ] - CoreFoundation.CFStringCreateWithCString.restype = CFStringRef - - CoreFoundation.CFStringGetCStringPtr.argtypes = [ - CFStringRef, - CFStringEncoding - ] - CoreFoundation.CFStringGetCStringPtr.restype = c_char_p - - CoreFoundation.CFStringGetCString.argtypes = [ - CFStringRef, - c_char_p, - CFIndex, - CFStringEncoding - ] - CoreFoundation.CFStringGetCString.restype = c_bool - - CoreFoundation.CFDataCreate.argtypes = [ - CFAllocatorRef, - c_char_p, - CFIndex - ] - CoreFoundation.CFDataCreate.restype = CFDataRef - - CoreFoundation.CFDataGetLength.argtypes = [ - CFDataRef - ] - CoreFoundation.CFDataGetLength.restype = CFIndex - - CoreFoundation.CFDataGetBytePtr.argtypes = [ - CFDataRef - ] - CoreFoundation.CFDataGetBytePtr.restype = c_void_p - - CoreFoundation.CFDictionaryCreate.argtypes = [ - CFAllocatorRef, - POINTER(CFTypeRef), - POINTER(CFTypeRef), - CFIndex, - CFDictionaryKeyCallBacks, - CFDictionaryValueCallBacks - ] - CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef - - CoreFoundation.CFDictionaryGetValue.argtypes = [ - CFDictionaryRef, - CFTypeRef - ] - CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef - - CoreFoundation.CFArrayCreate.argtypes = [ - CFAllocatorRef, - POINTER(CFTypeRef), - CFIndex, - CFArrayCallBacks, - ] - CoreFoundation.CFArrayCreate.restype = CFArrayRef - - CoreFoundation.CFArrayCreateMutable.argtypes = [ - CFAllocatorRef, - CFIndex, - CFArrayCallBacks - ] - CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef - - CoreFoundation.CFArrayAppendValue.argtypes = [ - CFMutableArrayRef, - c_void_p - ] - CoreFoundation.CFArrayAppendValue.restype = None - - CoreFoundation.CFArrayGetCount.argtypes = [ - CFArrayRef - ] - CoreFoundation.CFArrayGetCount.restype = CFIndex - - CoreFoundation.CFArrayGetValueAtIndex.argtypes = [ - CFArrayRef, - CFIndex - ] - CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p - - CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( - CoreFoundation, 'kCFAllocatorDefault' - ) - CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll(CoreFoundation, 'kCFTypeArrayCallBacks') - CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( - CoreFoundation, 'kCFTypeDictionaryKeyCallBacks' - ) - CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( - CoreFoundation, 'kCFTypeDictionaryValueCallBacks' - ) - - CoreFoundation.CFTypeRef = CFTypeRef - CoreFoundation.CFArrayRef = CFArrayRef - CoreFoundation.CFStringRef = CFStringRef - CoreFoundation.CFDictionaryRef = CFDictionaryRef - -except (AttributeError): - raise ImportError('Error initializing ctypes') - - -class CFConst(object): - """ - A class object that acts as essentially a namespace for CoreFoundation - constants. - """ - kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) - - -class SecurityConst(object): - """ - A class object that acts as essentially a namespace for Security constants. - """ - kSSLSessionOptionBreakOnServerAuth = 0 - - kSSLProtocol2 = 1 - kSSLProtocol3 = 2 - kTLSProtocol1 = 4 - kTLSProtocol11 = 7 - kTLSProtocol12 = 8 - - kSSLClientSide = 1 - kSSLStreamType = 0 - - kSecFormatPEMSequence = 10 - - kSecTrustResultInvalid = 0 - kSecTrustResultProceed = 1 - # This gap is present on purpose: this was kSecTrustResultConfirm, which - # is deprecated. - kSecTrustResultDeny = 3 - kSecTrustResultUnspecified = 4 - kSecTrustResultRecoverableTrustFailure = 5 - kSecTrustResultFatalTrustFailure = 6 - kSecTrustResultOtherError = 7 - - errSSLProtocol = -9800 - errSSLWouldBlock = -9803 - errSSLClosedGraceful = -9805 - errSSLClosedNoNotify = -9816 - errSSLClosedAbort = -9806 - - errSSLXCertChainInvalid = -9807 - errSSLCrypto = -9809 - errSSLInternal = -9810 - errSSLCertExpired = -9814 - errSSLCertNotYetValid = -9815 - errSSLUnknownRootCert = -9812 - errSSLNoRootCert = -9813 - errSSLHostNameMismatch = -9843 - errSSLPeerHandshakeFail = -9824 - errSSLPeerUserCancelled = -9839 - errSSLWeakPeerEphemeralDHKey = -9850 - errSSLServerAuthCompleted = -9841 - errSSLRecordOverflow = -9847 - - errSecVerifyFailed = -67808 - errSecNoTrustSettings = -25263 - errSecItemNotFound = -25300 - errSecInvalidTrustSettings = -25262 - - # Cipher suites. We only pick the ones our default cipher string allows. - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F - TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3 - TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F - TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2 - TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 - TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A - TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 - TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B - TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A - TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 - TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038 - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 - TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 - TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 - TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040 - TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 - TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032 - TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D - TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C - TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D - TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C - TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 - TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F - TLS_AES_128_GCM_SHA256 = 0x1301 - TLS_AES_256_GCM_SHA384 = 0x1302 - TLS_CHACHA20_POLY1305_SHA256 = 0x1303 diff --git a/lib/urllib3/contrib/_securetransport/low_level.py b/lib/urllib3/contrib/_securetransport/low_level.py deleted file mode 100644 index b13cd9e..0000000 --- a/lib/urllib3/contrib/_securetransport/low_level.py +++ /dev/null @@ -1,346 +0,0 @@ -""" -Low-level helpers for the SecureTransport bindings. - -These are Python functions that are not directly related to the high-level APIs -but are necessary to get them to work. They include a whole bunch of low-level -CoreFoundation messing about and memory management. The concerns in this module -are almost entirely about trying to avoid memory leaks and providing -appropriate and useful assistance to the higher-level code. -""" -import base64 -import ctypes -import itertools -import re -import os -import ssl -import tempfile - -from .bindings import Security, CoreFoundation, CFConst - - -# This regular expression is used to grab PEM data out of a PEM bundle. -_PEM_CERTS_RE = re.compile( - b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL -) - - -def _cf_data_from_bytes(bytestring): - """ - Given a bytestring, create a CFData object from it. This CFData object must - be CFReleased by the caller. - """ - return CoreFoundation.CFDataCreate( - CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring) - ) - - -def _cf_dictionary_from_tuples(tuples): - """ - Given a list of Python tuples, create an associated CFDictionary. - """ - dictionary_size = len(tuples) - - # We need to get the dictionary keys and values out in the same order. - keys = (t[0] for t in tuples) - values = (t[1] for t in tuples) - cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys) - cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values) - - return CoreFoundation.CFDictionaryCreate( - CoreFoundation.kCFAllocatorDefault, - cf_keys, - cf_values, - dictionary_size, - CoreFoundation.kCFTypeDictionaryKeyCallBacks, - CoreFoundation.kCFTypeDictionaryValueCallBacks, - ) - - -def _cf_string_to_unicode(value): - """ - Creates a Unicode string from a CFString object. Used entirely for error - reporting. - - Yes, it annoys me quite a lot that this function is this complex. - """ - value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) - - string = CoreFoundation.CFStringGetCStringPtr( - value_as_void_p, - CFConst.kCFStringEncodingUTF8 - ) - if string is None: - buffer = ctypes.create_string_buffer(1024) - result = CoreFoundation.CFStringGetCString( - value_as_void_p, - buffer, - 1024, - CFConst.kCFStringEncodingUTF8 - ) - if not result: - raise OSError('Error copying C string from CFStringRef') - string = buffer.value - if string is not None: - string = string.decode('utf-8') - return string - - -def _assert_no_error(error, exception_class=None): - """ - Checks the return code and throws an exception if there is an error to - report - """ - if error == 0: - return - - cf_error_string = Security.SecCopyErrorMessageString(error, None) - output = _cf_string_to_unicode(cf_error_string) - CoreFoundation.CFRelease(cf_error_string) - - if output is None or output == u'': - output = u'OSStatus %s' % error - - if exception_class is None: - exception_class = ssl.SSLError - - raise exception_class(output) - - -def _cert_array_from_pem(pem_bundle): - """ - Given a bundle of certs in PEM format, turns them into a CFArray of certs - that can be used to validate a cert chain. - """ - # Normalize the PEM bundle's line endings. - pem_bundle = pem_bundle.replace(b"\r\n", b"\n") - - der_certs = [ - base64.b64decode(match.group(1)) - for match in _PEM_CERTS_RE.finditer(pem_bundle) - ] - if not der_certs: - raise ssl.SSLError("No root certificates specified") - - cert_array = CoreFoundation.CFArrayCreateMutable( - CoreFoundation.kCFAllocatorDefault, - 0, - ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks) - ) - if not cert_array: - raise ssl.SSLError("Unable to allocate memory!") - - try: - for der_bytes in der_certs: - certdata = _cf_data_from_bytes(der_bytes) - if not certdata: - raise ssl.SSLError("Unable to allocate memory!") - cert = Security.SecCertificateCreateWithData( - CoreFoundation.kCFAllocatorDefault, certdata - ) - CoreFoundation.CFRelease(certdata) - if not cert: - raise ssl.SSLError("Unable to build cert object!") - - CoreFoundation.CFArrayAppendValue(cert_array, cert) - CoreFoundation.CFRelease(cert) - except Exception: - # We need to free the array before the exception bubbles further. - # We only want to do that if an error occurs: otherwise, the caller - # should free. - CoreFoundation.CFRelease(cert_array) - - return cert_array - - -def _is_cert(item): - """ - Returns True if a given CFTypeRef is a certificate. - """ - expected = Security.SecCertificateGetTypeID() - return CoreFoundation.CFGetTypeID(item) == expected - - -def _is_identity(item): - """ - Returns True if a given CFTypeRef is an identity. - """ - expected = Security.SecIdentityGetTypeID() - return CoreFoundation.CFGetTypeID(item) == expected - - -def _temporary_keychain(): - """ - This function creates a temporary Mac keychain that we can use to work with - credentials. This keychain uses a one-time password and a temporary file to - store the data. We expect to have one keychain per socket. The returned - SecKeychainRef must be freed by the caller, including calling - SecKeychainDelete. - - Returns a tuple of the SecKeychainRef and the path to the temporary - directory that contains it. - """ - # Unfortunately, SecKeychainCreate requires a path to a keychain. This - # means we cannot use mkstemp to use a generic temporary file. Instead, - # we're going to create a temporary directory and a filename to use there. - # This filename will be 8 random bytes expanded into base64. We also need - # some random bytes to password-protect the keychain we're creating, so we - # ask for 40 random bytes. - random_bytes = os.urandom(40) - filename = base64.b16encode(random_bytes[:8]).decode('utf-8') - password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 - tempdirectory = tempfile.mkdtemp() - - keychain_path = os.path.join(tempdirectory, filename).encode('utf-8') - - # We now want to create the keychain itself. - keychain = Security.SecKeychainRef() - status = Security.SecKeychainCreate( - keychain_path, - len(password), - password, - False, - None, - ctypes.byref(keychain) - ) - _assert_no_error(status) - - # Having created the keychain, we want to pass it off to the caller. - return keychain, tempdirectory - - -def _load_items_from_file(keychain, path): - """ - Given a single file, loads all the trust objects from it into arrays and - the keychain. - Returns a tuple of lists: the first list is a list of identities, the - second a list of certs. - """ - certificates = [] - identities = [] - result_array = None - - with open(path, 'rb') as f: - raw_filedata = f.read() - - try: - filedata = CoreFoundation.CFDataCreate( - CoreFoundation.kCFAllocatorDefault, - raw_filedata, - len(raw_filedata) - ) - result_array = CoreFoundation.CFArrayRef() - result = Security.SecItemImport( - filedata, # cert data - None, # Filename, leaving it out for now - None, # What the type of the file is, we don't care - None, # what's in the file, we don't care - 0, # import flags - None, # key params, can include passphrase in the future - keychain, # The keychain to insert into - ctypes.byref(result_array) # Results - ) - _assert_no_error(result) - - # A CFArray is not very useful to us as an intermediary - # representation, so we are going to extract the objects we want - # and then free the array. We don't need to keep hold of keys: the - # keychain already has them! - result_count = CoreFoundation.CFArrayGetCount(result_array) - for index in range(result_count): - item = CoreFoundation.CFArrayGetValueAtIndex( - result_array, index - ) - item = ctypes.cast(item, CoreFoundation.CFTypeRef) - - if _is_cert(item): - CoreFoundation.CFRetain(item) - certificates.append(item) - elif _is_identity(item): - CoreFoundation.CFRetain(item) - identities.append(item) - finally: - if result_array: - CoreFoundation.CFRelease(result_array) - - CoreFoundation.CFRelease(filedata) - - return (identities, certificates) - - -def _load_client_cert_chain(keychain, *paths): - """ - Load certificates and maybe keys from a number of files. Has the end goal - of returning a CFArray containing one SecIdentityRef, and then zero or more - SecCertificateRef objects, suitable for use as a client certificate trust - chain. - """ - # Ok, the strategy. - # - # This relies on knowing that macOS will not give you a SecIdentityRef - # unless you have imported a key into a keychain. This is a somewhat - # artificial limitation of macOS (for example, it doesn't necessarily - # affect iOS), but there is nothing inside Security.framework that lets you - # get a SecIdentityRef without having a key in a keychain. - # - # So the policy here is we take all the files and iterate them in order. - # Each one will use SecItemImport to have one or more objects loaded from - # it. We will also point at a keychain that macOS can use to work with the - # private key. - # - # Once we have all the objects, we'll check what we actually have. If we - # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise, - # we'll take the first certificate (which we assume to be our leaf) and - # ask the keychain to give us a SecIdentityRef with that cert's associated - # key. - # - # We'll then return a CFArray containing the trust chain: one - # SecIdentityRef and then zero-or-more SecCertificateRef objects. The - # responsibility for freeing this CFArray will be with the caller. This - # CFArray must remain alive for the entire connection, so in practice it - # will be stored with a single SSLSocket, along with the reference to the - # keychain. - certificates = [] - identities = [] - - # Filter out bad paths. - paths = (path for path in paths if path) - - try: - for file_path in paths: - new_identities, new_certs = _load_items_from_file( - keychain, file_path - ) - identities.extend(new_identities) - certificates.extend(new_certs) - - # Ok, we have everything. The question is: do we have an identity? If - # not, we want to grab one from the first cert we have. - if not identities: - new_identity = Security.SecIdentityRef() - status = Security.SecIdentityCreateWithCertificate( - keychain, - certificates[0], - ctypes.byref(new_identity) - ) - _assert_no_error(status) - identities.append(new_identity) - - # We now want to release the original certificate, as we no longer - # need it. - CoreFoundation.CFRelease(certificates.pop(0)) - - # We now need to build a new CFArray that holds the trust chain. - trust_chain = CoreFoundation.CFArrayCreateMutable( - CoreFoundation.kCFAllocatorDefault, - 0, - ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), - ) - for item in itertools.chain(identities, certificates): - # ArrayAppendValue does a CFRetain on the item. That's fine, - # because the finally block will release our other refs to them. - CoreFoundation.CFArrayAppendValue(trust_chain, item) - - return trust_chain - finally: - for obj in itertools.chain(identities, certificates): - CoreFoundation.CFRelease(obj) diff --git a/lib/urllib3/contrib/appengine.py b/lib/urllib3/contrib/appengine.py deleted file mode 100644 index 2952f11..0000000 --- a/lib/urllib3/contrib/appengine.py +++ /dev/null @@ -1,289 +0,0 @@ -""" -This module provides a pool manager that uses Google App Engine's -`URLFetch Service <https://cloud.google.com/appengine/docs/python/urlfetch>`_. - -Example usage:: - - from urllib3 import PoolManager - from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox - - if is_appengine_sandbox(): - # AppEngineManager uses AppEngine's URLFetch API behind the scenes - http = AppEngineManager() - else: - # PoolManager uses a socket-level API behind the scenes - http = PoolManager() - - r = http.request('GET', 'https://google.com/') - -There are `limitations <https://cloud.google.com/appengine/docs/python/\ -urlfetch/#Python_Quotas_and_limits>`_ to the URLFetch service and it may not be -the best choice for your application. There are three options for using -urllib3 on Google App Engine: - -1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is - cost-effective in many circumstances as long as your usage is within the - limitations. -2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. - Sockets also have `limitations and restrictions - <https://cloud.google.com/appengine/docs/python/sockets/\ - #limitations-and-restrictions>`_ and have a lower free quota than URLFetch. - To use sockets, be sure to specify the following in your ``app.yaml``:: - - env_variables: - GAE_USE_SOCKETS_HTTPLIB : 'true' - -3. If you are using `App Engine Flexible -<https://cloud.google.com/appengine/docs/flexible/>`_, you can use the standard -:class:`PoolManager` without any configuration or special environment variables. -""" - -from __future__ import absolute_import -import io -import logging -import warnings -from ..packages.six.moves.urllib.parse import urljoin - -from ..exceptions import ( - HTTPError, - HTTPWarning, - MaxRetryError, - ProtocolError, - TimeoutError, - SSLError -) - -from ..request import RequestMethods -from ..response import HTTPResponse -from ..util.timeout import Timeout -from ..util.retry import Retry -from . import _appengine_environ - -try: - from google.appengine.api import urlfetch -except ImportError: - urlfetch = None - - -log = logging.getLogger(__name__) - - -class AppEnginePlatformWarning(HTTPWarning): - pass - - -class AppEnginePlatformError(HTTPError): - pass - - -class AppEngineManager(RequestMethods): - """ - Connection manager for Google App Engine sandbox applications. - - This manager uses the URLFetch service directly instead of using the - emulated httplib, and is subject to URLFetch limitations as described in - the App Engine documentation `here - <https://cloud.google.com/appengine/docs/python/urlfetch>`_. - - Notably it will raise an :class:`AppEnginePlatformError` if: - * URLFetch is not available. - * If you attempt to use this on App Engine Flexible, as full socket - support is available. - * If a request size is more than 10 megabytes. - * If a response size is more than 32 megabtyes. - * If you use an unsupported request method such as OPTIONS. - - Beyond those cases, it will raise normal urllib3 errors. - """ - - def __init__(self, headers=None, retries=None, validate_certificate=True, - urlfetch_retries=True): - if not urlfetch: - raise AppEnginePlatformError( - "URLFetch is not available in this environment.") - - if is_prod_appengine_mvms(): - raise AppEnginePlatformError( - "Use normal urllib3.PoolManager instead of AppEngineManager" - "on Managed VMs, as using URLFetch is not necessary in " - "this environment.") - - warnings.warn( - "urllib3 is using URLFetch on Google App Engine sandbox instead " - "of sockets. To use sockets directly instead of URLFetch see " - "https://urllib3.readthedocs.io/en/latest/reference/urllib3.contrib.html.", - AppEnginePlatformWarning) - - RequestMethods.__init__(self, headers) - self.validate_certificate = validate_certificate - self.urlfetch_retries = urlfetch_retries - - self.retries = retries or Retry.DEFAULT - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - # Return False to re-raise any potential exceptions - return False - - def urlopen(self, method, url, body=None, headers=None, - retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT, - **response_kw): - - retries = self._get_retries(retries, redirect) - - try: - follow_redirects = ( - redirect and - retries.redirect != 0 and - retries.total) - response = urlfetch.fetch( - url, - payload=body, - method=method, - headers=headers or {}, - allow_truncated=False, - follow_redirects=self.urlfetch_retries and follow_redirects, - deadline=self._get_absolute_timeout(timeout), - validate_certificate=self.validate_certificate, - ) - except urlfetch.DeadlineExceededError as e: - raise TimeoutError(self, e) - - except urlfetch.InvalidURLError as e: - if 'too large' in str(e): - raise AppEnginePlatformError( - "URLFetch request too large, URLFetch only " - "supports requests up to 10mb in size.", e) - raise ProtocolError(e) - - except urlfetch.DownloadError as e: - if 'Too many redirects' in str(e): - raise MaxRetryError(self, url, reason=e) - raise ProtocolError(e) - - except urlfetch.ResponseTooLargeError as e: - raise AppEnginePlatformError( - "URLFetch response too large, URLFetch only supports" - "responses up to 32mb in size.", e) - - except urlfetch.SSLCertificateError as e: - raise SSLError(e) - - except urlfetch.InvalidMethodError as e: - raise AppEnginePlatformError( - "URLFetch does not support method: %s" % method, e) - - http_response = self._urlfetch_response_to_http_response( - response, retries=retries, **response_kw) - - # Handle redirect? - redirect_location = redirect and http_response.get_redirect_location() - if redirect_location: - # Check for redirect response - if (self.urlfetch_retries and retries.raise_on_redirect): - raise MaxRetryError(self, url, "too many redirects") - else: - if http_response.status == 303: - method = 'GET' - - try: - retries = retries.increment(method, url, response=http_response, _pool=self) - except MaxRetryError: - if retries.raise_on_redirect: - raise MaxRetryError(self, url, "too many redirects") - return http_response - - retries.sleep_for_retry(http_response) - log.debug("Redirecting %s -> %s", url, redirect_location) - redirect_url = urljoin(url, redirect_location) - return self.urlopen( - method, redirect_url, body, headers, - retries=retries, redirect=redirect, - timeout=timeout, **response_kw) - - # Check if we should retry the HTTP response. - has_retry_after = bool(http_response.getheader('Retry-After')) - if retries.is_retry(method, http_response.status, has_retry_after): - retries = retries.increment( - method, url, response=http_response, _pool=self) - log.debug("Retry: %s", url) - retries.sleep(http_response) - return self.urlopen( - method, url, - body=body, headers=headers, - retries=retries, redirect=redirect, - timeout=timeout, **response_kw) - - return http_response - - def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): - - if is_prod_appengine(): - # Production GAE handles deflate encoding automatically, but does - # not remove the encoding header. - content_encoding = urlfetch_resp.headers.get('content-encoding') - - if content_encoding == 'deflate': - del urlfetch_resp.headers['content-encoding'] - - transfer_encoding = urlfetch_resp.headers.get('transfer-encoding') - # We have a full response's content, - # so let's make sure we don't report ourselves as chunked data. - if transfer_encoding == 'chunked': - encodings = transfer_encoding.split(",") - encodings.remove('chunked') - urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings) - - original_response = HTTPResponse( - # In order for decoding to work, we must present the content as - # a file-like object. - body=io.BytesIO(urlfetch_resp.content), - msg=urlfetch_resp.header_msg, - headers=urlfetch_resp.headers, - status=urlfetch_resp.status_code, - **response_kw - ) - - return HTTPResponse( - body=io.BytesIO(urlfetch_resp.content), - headers=urlfetch_resp.headers, - status=urlfetch_resp.status_code, - original_response=original_response, - **response_kw - ) - - def _get_absolute_timeout(self, timeout): - if timeout is Timeout.DEFAULT_TIMEOUT: - return None # Defer to URLFetch's default. - if isinstance(timeout, Timeout): - if timeout._read is not None or timeout._connect is not None: - warnings.warn( - "URLFetch does not support granular timeout settings, " - "reverting to total or default URLFetch timeout.", - AppEnginePlatformWarning) - return timeout.total - return timeout - - def _get_retries(self, retries, redirect): - if not isinstance(retries, Retry): - retries = Retry.from_int( - retries, redirect=redirect, default=self.retries) - - if retries.connect or retries.read or retries.redirect: - warnings.warn( - "URLFetch only supports total retries and does not " - "recognize connect, read, or redirect retry parameters.", - AppEnginePlatformWarning) - - return retries - - -# Alias methods from _appengine_environ to maintain public API interface. - -is_appengine = _appengine_environ.is_appengine -is_appengine_sandbox = _appengine_environ.is_appengine_sandbox -is_local_appengine = _appengine_environ.is_local_appengine -is_prod_appengine = _appengine_environ.is_prod_appengine -is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms diff --git a/lib/urllib3/contrib/ntlmpool.py b/lib/urllib3/contrib/ntlmpool.py deleted file mode 100644 index 8ea127c..0000000 --- a/lib/urllib3/contrib/ntlmpool.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -NTLM authenticating pool, contributed by erikcederstran - -Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 -""" -from __future__ import absolute_import - -from logging import getLogger -from ntlm import ntlm - -from .. import HTTPSConnectionPool -from ..packages.six.moves.http_client import HTTPSConnection - - -log = getLogger(__name__) - - -class NTLMConnectionPool(HTTPSConnectionPool): - """ - Implements an NTLM authentication version of an urllib3 connection pool - """ - - scheme = 'https' - - def __init__(self, user, pw, authurl, *args, **kwargs): - """ - authurl is a random URL on the server that is protected by NTLM. - user is the Windows user, probably in the DOMAIN\\username format. - pw is the password for the user. - """ - super(NTLMConnectionPool, self).__init__(*args, **kwargs) - self.authurl = authurl - self.rawuser = user - user_parts = user.split('\\', 1) - self.domain = user_parts[0].upper() - self.user = user_parts[1] - self.pw = pw - - def _new_conn(self): - # Performs the NTLM handshake that secures the connection. The socket - # must be kept open while requests are performed. - self.num_connections += 1 - log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s', - self.num_connections, self.host, self.authurl) - - headers = {'Connection': 'Keep-Alive'} - req_header = 'Authorization' - resp_header = 'www-authenticate' - - conn = HTTPSConnection(host=self.host, port=self.port) - - # Send negotiation message - headers[req_header] = ( - 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser)) - log.debug('Request headers: %s', headers) - conn.request('GET', self.authurl, None, headers) - res = conn.getresponse() - reshdr = dict(res.getheaders()) - log.debug('Response status: %s %s', res.status, res.reason) - log.debug('Response headers: %s', reshdr) - log.debug('Response data: %s [...]', res.read(100)) - - # Remove the reference to the socket, so that it can not be closed by - # the response object (we want to keep the socket open) - res.fp = None - - # Server should respond with a challenge message - auth_header_values = reshdr[resp_header].split(', ') - auth_header_value = None - for s in auth_header_values: - if s[:5] == 'NTLM ': - auth_header_value = s[5:] - if auth_header_value is None: - raise Exception('Unexpected %s response header: %s' % - (resp_header, reshdr[resp_header])) - - # Send authentication message - ServerChallenge, NegotiateFlags = \ - ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value) - auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, - self.user, - self.domain, - self.pw, - NegotiateFlags) - headers[req_header] = 'NTLM %s' % auth_msg - log.debug('Request headers: %s', headers) - conn.request('GET', self.authurl, None, headers) - res = conn.getresponse() - log.debug('Response status: %s %s', res.status, res.reason) - log.debug('Response headers: %s', dict(res.getheaders())) - log.debug('Response data: %s [...]', res.read()[:100]) - if res.status != 200: - if res.status == 401: - raise Exception('Server rejected request: wrong ' - 'username or password') - raise Exception('Wrong server response: %s %s' % - (res.status, res.reason)) - - res.fp = None - log.debug('Connection established') - return conn - - def urlopen(self, method, url, body=None, headers=None, retries=3, - redirect=True, assert_same_host=True): - if headers is None: - headers = {} - headers['Connection'] = 'Keep-Alive' - return super(NTLMConnectionPool, self).urlopen(method, url, body, - headers, retries, - redirect, - assert_same_host) diff --git a/lib/urllib3/contrib/pyopenssl.py b/lib/urllib3/contrib/pyopenssl.py deleted file mode 100644 index 7c0e946..0000000 --- a/lib/urllib3/contrib/pyopenssl.py +++ /dev/null @@ -1,466 +0,0 @@ -""" -SSL with SNI_-support for Python 2. Follow these instructions if you would -like to verify SSL certificates in Python 2. Note, the default libraries do -*not* do certificate checking; you need to do additional work to validate -certificates yourself. - -This needs the following packages installed: - -* pyOpenSSL (tested with 16.0.0) -* cryptography (minimum 1.3.4, from pyopenssl) -* idna (minimum 2.0, from cryptography) - -However, pyopenssl depends on cryptography, which depends on idna, so while we -use all three directly here we end up having relatively few packages required. - -You can install them with the following command: - - pip install pyopenssl cryptography idna - -To activate certificate checking, call -:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code -before you begin making HTTP requests. This can be done in a ``sitecustomize`` -module, or at any other time before your application begins using ``urllib3``, -like this:: - - try: - import urllib3.contrib.pyopenssl - urllib3.contrib.pyopenssl.inject_into_urllib3() - except ImportError: - pass - -Now you can use :mod:`urllib3` as you normally would, and it will support SNI -when the required modules are installed. - -Activating this module also has the positive side effect of disabling SSL/TLS -compression in Python 2 (see `CRIME attack`_). - -If you want to configure the default list of supported cipher suites, you can -set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. - -.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication -.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) -""" -from __future__ import absolute_import - -import OpenSSL.SSL -from cryptography import x509 -from cryptography.hazmat.backends.openssl import backend as openssl_backend -from cryptography.hazmat.backends.openssl.x509 import _Certificate -try: - from cryptography.x509 import UnsupportedExtension -except ImportError: - # UnsupportedExtension is gone in cryptography >= 2.1.0 - class UnsupportedExtension(Exception): - pass - -from socket import timeout, error as SocketError -from io import BytesIO - -try: # Platform-specific: Python 2 - from socket import _fileobject -except ImportError: # Platform-specific: Python 3 - _fileobject = None - from ..packages.backports.makefile import backport_makefile - -import logging -import ssl -from ..packages import six -import sys - -from .. import util - -__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] - -# SNI always works. -HAS_SNI = True - -# Map from urllib3 to PyOpenSSL compatible parameter-values. -_openssl_versions = { - ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, - ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, -} - -if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'): - _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD - -if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'): - _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD - -try: - _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) -except AttributeError: - pass - -_stdlib_to_openssl_verify = { - ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, - ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, - ssl.CERT_REQUIRED: - OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, -} -_openssl_to_stdlib_verify = dict( - (v, k) for k, v in _stdlib_to_openssl_verify.items() -) - -# OpenSSL will only write 16K at a time -SSL_WRITE_BLOCKSIZE = 16384 - -orig_util_HAS_SNI = util.HAS_SNI -orig_util_SSLContext = util.ssl_.SSLContext - - -log = logging.getLogger(__name__) - - -def inject_into_urllib3(): - 'Monkey-patch urllib3 with PyOpenSSL-backed SSL-support.' - - _validate_dependencies_met() - - util.ssl_.SSLContext = PyOpenSSLContext - util.HAS_SNI = HAS_SNI - util.ssl_.HAS_SNI = HAS_SNI - util.IS_PYOPENSSL = True - util.ssl_.IS_PYOPENSSL = True - - -def extract_from_urllib3(): - 'Undo monkey-patching by :func:`inject_into_urllib3`.' - - util.ssl_.SSLContext = orig_util_SSLContext - util.HAS_SNI = orig_util_HAS_SNI - util.ssl_.HAS_SNI = orig_util_HAS_SNI - util.IS_PYOPENSSL = False - util.ssl_.IS_PYOPENSSL = False - - -def _validate_dependencies_met(): - """ - Verifies that PyOpenSSL's package-level dependencies have been met. - Throws `ImportError` if they are not met. - """ - # Method added in `cryptography==1.1`; not available in older versions - from cryptography.x509.extensions import Extensions - if getattr(Extensions, "get_extension_for_class", None) is None: - raise ImportError("'cryptography' module missing required functionality. " - "Try upgrading to v1.3.4 or newer.") - - # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 - # attribute is only present on those versions. - from OpenSSL.crypto import X509 - x509 = X509() - if getattr(x509, "_x509", None) is None: - raise ImportError("'pyOpenSSL' module missing required functionality. " - "Try upgrading to v0.14 or newer.") - - -def _dnsname_to_stdlib(name): - """ - Converts a dNSName SubjectAlternativeName field to the form used by the - standard library on the given Python version. - - Cryptography produces a dNSName as a unicode string that was idna-decoded - from ASCII bytes. We need to idna-encode that string to get it back, and - then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib - uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). - - If the name cannot be idna-encoded then we return None signalling that - the name given should be skipped. - """ - def idna_encode(name): - """ - Borrowed wholesale from the Python Cryptography Project. It turns out - that we can't just safely call `idna.encode`: it can explode for - wildcard names. This avoids that problem. - """ - import idna - - try: - for prefix in [u'*.', u'.']: - if name.startswith(prefix): - name = name[len(prefix):] - return prefix.encode('ascii') + idna.encode(name) - return idna.encode(name) - except idna.core.IDNAError: - return None - - name = idna_encode(name) - if name is None: - return None - elif sys.version_info >= (3, 0): - name = name.decode('utf-8') - return name - - -def get_subj_alt_name(peer_cert): - """ - Given an PyOpenSSL certificate, provides all the subject alternative names. - """ - # Pass the cert to cryptography, which has much better APIs for this. - if hasattr(peer_cert, "to_cryptography"): - cert = peer_cert.to_cryptography() - else: - # This is technically using private APIs, but should work across all - # relevant versions before PyOpenSSL got a proper API for this. - cert = _Certificate(openssl_backend, peer_cert._x509) - - # We want to find the SAN extension. Ask Cryptography to locate it (it's - # faster than looping in Python) - try: - ext = cert.extensions.get_extension_for_class( - x509.SubjectAlternativeName - ).value - except x509.ExtensionNotFound: - # No such extension, return the empty list. - return [] - except (x509.DuplicateExtension, UnsupportedExtension, - x509.UnsupportedGeneralNameType, UnicodeError) as e: - # A problem has been found with the quality of the certificate. Assume - # no SAN field is present. - log.warning( - "A problem was encountered with the certificate that prevented " - "urllib3 from finding the SubjectAlternativeName field. This can " - "affect certificate validation. The error was %s", - e, - ) - return [] - - # We want to return dNSName and iPAddress fields. We need to cast the IPs - # back to strings because the match_hostname function wants them as - # strings. - # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 - # decoded. This is pretty frustrating, but that's what the standard library - # does with certificates, and so we need to attempt to do the same. - # We also want to skip over names which cannot be idna encoded. - names = [ - ('DNS', name) for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) - if name is not None - ] - names.extend( - ('IP Address', str(name)) - for name in ext.get_values_for_type(x509.IPAddress) - ) - - return names - - -class WrappedSocket(object): - '''API-compatibility wrapper for Python OpenSSL's Connection-class. - - Note: _makefile_refs, _drop() and _reuse() are needed for the garbage - collector of pypy. - ''' - - def __init__(self, connection, socket, suppress_ragged_eofs=True): - self.connection = connection - self.socket = socket - self.suppress_ragged_eofs = suppress_ragged_eofs - self._makefile_refs = 0 - self._closed = False - - def fileno(self): - return self.socket.fileno() - - # Copy-pasted from Python 3.5 source code - def _decref_socketios(self): - if self._makefile_refs > 0: - self._makefile_refs -= 1 - if self._closed: - self.close() - - def recv(self, *args, **kwargs): - try: - data = self.connection.recv(*args, **kwargs) - except OpenSSL.SSL.SysCallError as e: - if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): - return b'' - else: - raise SocketError(str(e)) - except OpenSSL.SSL.ZeroReturnError as e: - if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: - return b'' - else: - raise - except OpenSSL.SSL.WantReadError: - if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise timeout('The read operation timed out') - else: - return self.recv(*args, **kwargs) - else: - return data - - def recv_into(self, *args, **kwargs): - try: - return self.connection.recv_into(*args, **kwargs) - except OpenSSL.SSL.SysCallError as e: - if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): - return 0 - else: - raise SocketError(str(e)) - except OpenSSL.SSL.ZeroReturnError as e: - if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: - return 0 - else: - raise - except OpenSSL.SSL.WantReadError: - if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise timeout('The read operation timed out') - else: - return self.recv_into(*args, **kwargs) - - def settimeout(self, timeout): - return self.socket.settimeout(timeout) - - def _send_until_done(self, data): - while True: - try: - return self.connection.send(data) - except OpenSSL.SSL.WantWriteError: - if not util.wait_for_write(self.socket, self.socket.gettimeout()): - raise timeout() - continue - except OpenSSL.SSL.SysCallError as e: - raise SocketError(str(e)) - - def sendall(self, data): - total_sent = 0 - while total_sent < len(data): - sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) - total_sent += sent - - def shutdown(self): - # FIXME rethrow compatible exceptions should we ever use this - self.connection.shutdown() - - def close(self): - if self._makefile_refs < 1: - try: - self._closed = True - return self.connection.close() - except OpenSSL.SSL.Error: - return - else: - self._makefile_refs -= 1 - - def getpeercert(self, binary_form=False): - x509 = self.connection.get_peer_certificate() - - if not x509: - return x509 - - if binary_form: - return OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_ASN1, - x509) - - return { - 'subject': ( - (('commonName', x509.get_subject().CN),), - ), - 'subjectAltName': get_subj_alt_name(x509) - } - - def _reuse(self): - self._makefile_refs += 1 - - def _drop(self): - if self._makefile_refs < 1: - self.close() - else: - self._makefile_refs -= 1 - - -if _fileobject: # Platform-specific: Python 2 - def makefile(self, mode, bufsize=-1): - self._makefile_refs += 1 - return _fileobject(self, mode, bufsize, close=True) -else: # Platform-specific: Python 3 - makefile = backport_makefile - -WrappedSocket.makefile = makefile - - -class PyOpenSSLContext(object): - """ - I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible - for translating the interface of the standard library ``SSLContext`` object - to calls into PyOpenSSL. - """ - def __init__(self, protocol): - self.protocol = _openssl_versions[protocol] - self._ctx = OpenSSL.SSL.Context(self.protocol) - self._options = 0 - self.check_hostname = False - - @property - def options(self): - return self._options - - @options.setter - def options(self, value): - self._options = value - self._ctx.set_options(value) - - @property - def verify_mode(self): - return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] - - @verify_mode.setter - def verify_mode(self, value): - self._ctx.set_verify( - _stdlib_to_openssl_verify[value], - _verify_callback - ) - - def set_default_verify_paths(self): - self._ctx.set_default_verify_paths() - - def set_ciphers(self, ciphers): - if isinstance(ciphers, six.text_type): - ciphers = ciphers.encode('utf-8') - self._ctx.set_cipher_list(ciphers) - - def load_verify_locations(self, cafile=None, capath=None, cadata=None): - if cafile is not None: - cafile = cafile.encode('utf-8') - if capath is not None: - capath = capath.encode('utf-8') - self._ctx.load_verify_locations(cafile, capath) - if cadata is not None: - self._ctx.load_verify_locations(BytesIO(cadata)) - - def load_cert_chain(self, certfile, keyfile=None, password=None): - self._ctx.use_certificate_chain_file(certfile) - if password is not None: - self._ctx.set_passwd_cb(lambda max_length, prompt_twice, userdata: password) - self._ctx.use_privatekey_file(keyfile or certfile) - - def wrap_socket(self, sock, server_side=False, - do_handshake_on_connect=True, suppress_ragged_eofs=True, - server_hostname=None): - cnx = OpenSSL.SSL.Connection(self._ctx, sock) - - if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 - server_hostname = server_hostname.encode('utf-8') - - if server_hostname is not None: - cnx.set_tlsext_host_name(server_hostname) - - cnx.set_connect_state() - - while True: - try: - cnx.do_handshake() - except OpenSSL.SSL.WantReadError: - if not util.wait_for_read(sock, sock.gettimeout()): - raise timeout('select timed out') - continue - except OpenSSL.SSL.Error as e: - raise ssl.SSLError('bad handshake: %r' % e) - break - - return WrappedSocket(cnx, sock) - - -def _verify_callback(cnx, x509, err_no, err_depth, return_code): - return err_no == 0 diff --git a/lib/urllib3/contrib/securetransport.py b/lib/urllib3/contrib/securetransport.py deleted file mode 100644 index 77cb59e..0000000 --- a/lib/urllib3/contrib/securetransport.py +++ /dev/null @@ -1,804 +0,0 @@ -""" -SecureTranport support for urllib3 via ctypes. - -This makes platform-native TLS available to urllib3 users on macOS without the -use of a compiler. This is an important feature because the Python Package -Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL -that ships with macOS is not capable of doing TLSv1.2. The only way to resolve -this is to give macOS users an alternative solution to the problem, and that -solution is to use SecureTransport. - -We use ctypes here because this solution must not require a compiler. That's -because pip is not allowed to require a compiler either. - -This is not intended to be a seriously long-term solution to this problem. -The hope is that PEP 543 will eventually solve this issue for us, at which -point we can retire this contrib module. But in the short term, we need to -solve the impending tire fire that is Python on Mac without this kind of -contrib module. So...here we are. - -To use this module, simply import and inject it:: - - import urllib3.contrib.securetransport - urllib3.contrib.securetransport.inject_into_urllib3() - -Happy TLSing! -""" -from __future__ import absolute_import - -import contextlib -import ctypes -import errno -import os.path -import shutil -import socket -import ssl -import threading -import weakref - -from .. import util -from ._securetransport.bindings import ( - Security, SecurityConst, CoreFoundation -) -from ._securetransport.low_level import ( - _assert_no_error, _cert_array_from_pem, _temporary_keychain, - _load_client_cert_chain -) - -try: # Platform-specific: Python 2 - from socket import _fileobject -except ImportError: # Platform-specific: Python 3 - _fileobject = None - from ..packages.backports.makefile import backport_makefile - -__all__ = ['inject_into_urllib3', 'extract_from_urllib3'] - -# SNI always works -HAS_SNI = True - -orig_util_HAS_SNI = util.HAS_SNI -orig_util_SSLContext = util.ssl_.SSLContext - -# This dictionary is used by the read callback to obtain a handle to the -# calling wrapped socket. This is a pretty silly approach, but for now it'll -# do. I feel like I should be able to smuggle a handle to the wrapped socket -# directly in the SSLConnectionRef, but for now this approach will work I -# guess. -# -# We need to lock around this structure for inserts, but we don't do it for -# reads/writes in the callbacks. The reasoning here goes as follows: -# -# 1. It is not possible to call into the callbacks before the dictionary is -# populated, so once in the callback the id must be in the dictionary. -# 2. The callbacks don't mutate the dictionary, they only read from it, and -# so cannot conflict with any of the insertions. -# -# This is good: if we had to lock in the callbacks we'd drastically slow down -# the performance of this code. -_connection_refs = weakref.WeakValueDictionary() -_connection_ref_lock = threading.Lock() - -# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over -# for no better reason than we need *a* limit, and this one is right there. -SSL_WRITE_BLOCKSIZE = 16384 - -# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to -# individual cipher suites. We need to do this because this is how -# SecureTransport wants them. -CIPHER_SUITES = [ - SecurityConst.TLS_AES_256_GCM_SHA384, - SecurityConst.TLS_CHACHA20_POLY1305_SHA256, - SecurityConst.TLS_AES_128_GCM_SHA256, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - SecurityConst.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, - SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - SecurityConst.TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, - SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, - SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, - SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, - SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, - SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, -] - -# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of -# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. -_protocol_to_min_max = { - ssl.PROTOCOL_SSLv23: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), -} - -if hasattr(ssl, "PROTOCOL_SSLv2"): - _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( - SecurityConst.kSSLProtocol2, SecurityConst.kSSLProtocol2 - ) -if hasattr(ssl, "PROTOCOL_SSLv3"): - _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( - SecurityConst.kSSLProtocol3, SecurityConst.kSSLProtocol3 - ) -if hasattr(ssl, "PROTOCOL_TLSv1"): - _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( - SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol1 - ) -if hasattr(ssl, "PROTOCOL_TLSv1_1"): - _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( - SecurityConst.kTLSProtocol11, SecurityConst.kTLSProtocol11 - ) -if hasattr(ssl, "PROTOCOL_TLSv1_2"): - _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( - SecurityConst.kTLSProtocol12, SecurityConst.kTLSProtocol12 - ) -if hasattr(ssl, "PROTOCOL_TLS"): - _protocol_to_min_max[ssl.PROTOCOL_TLS] = _protocol_to_min_max[ssl.PROTOCOL_SSLv23] - - -def inject_into_urllib3(): - """ - Monkey-patch urllib3 with SecureTransport-backed SSL-support. - """ - util.ssl_.SSLContext = SecureTransportContext - util.HAS_SNI = HAS_SNI - util.ssl_.HAS_SNI = HAS_SNI - util.IS_SECURETRANSPORT = True - util.ssl_.IS_SECURETRANSPORT = True - - -def extract_from_urllib3(): - """ - Undo monkey-patching by :func:`inject_into_urllib3`. - """ - util.ssl_.SSLContext = orig_util_SSLContext - util.HAS_SNI = orig_util_HAS_SNI - util.ssl_.HAS_SNI = orig_util_HAS_SNI - util.IS_SECURETRANSPORT = False - util.ssl_.IS_SECURETRANSPORT = False - - -def _read_callback(connection_id, data_buffer, data_length_pointer): - """ - SecureTransport read callback. This is called by ST to request that data - be returned from the socket. - """ - wrapped_socket = None - try: - wrapped_socket = _connection_refs.get(connection_id) - if wrapped_socket is None: - return SecurityConst.errSSLInternal - base_socket = wrapped_socket.socket - - requested_length = data_length_pointer[0] - - timeout = wrapped_socket.gettimeout() - error = None - read_count = 0 - - try: - while read_count < requested_length: - if timeout is None or timeout >= 0: - if not util.wait_for_read(base_socket, timeout): - raise socket.error(errno.EAGAIN, 'timed out') - - remaining = requested_length - read_count - buffer = (ctypes.c_char * remaining).from_address( - data_buffer + read_count - ) - chunk_size = base_socket.recv_into(buffer, remaining) - read_count += chunk_size - if not chunk_size: - if not read_count: - return SecurityConst.errSSLClosedGraceful - break - except (socket.error) as e: - error = e.errno - - if error is not None and error != errno.EAGAIN: - data_length_pointer[0] = read_count - if error == errno.ECONNRESET or error == errno.EPIPE: - return SecurityConst.errSSLClosedAbort - raise - - data_length_pointer[0] = read_count - - if read_count != requested_length: - return SecurityConst.errSSLWouldBlock - - return 0 - except Exception as e: - if wrapped_socket is not None: - wrapped_socket._exception = e - return SecurityConst.errSSLInternal - - -def _write_callback(connection_id, data_buffer, data_length_pointer): - """ - SecureTransport write callback. This is called by ST to request that data - actually be sent on the network. - """ - wrapped_socket = None - try: - wrapped_socket = _connection_refs.get(connection_id) - if wrapped_socket is None: - return SecurityConst.errSSLInternal - base_socket = wrapped_socket.socket - - bytes_to_write = data_length_pointer[0] - data = ctypes.string_at(data_buffer, bytes_to_write) - - timeout = wrapped_socket.gettimeout() - error = None - sent = 0 - - try: - while sent < bytes_to_write: - if timeout is None or timeout >= 0: - if not util.wait_for_write(base_socket, timeout): - raise socket.error(errno.EAGAIN, 'timed out') - chunk_sent = base_socket.send(data) - sent += chunk_sent - - # This has some needless copying here, but I'm not sure there's - # much value in optimising this data path. - data = data[chunk_sent:] - except (socket.error) as e: - error = e.errno - - if error is not None and error != errno.EAGAIN: - data_length_pointer[0] = sent - if error == errno.ECONNRESET or error == errno.EPIPE: - return SecurityConst.errSSLClosedAbort - raise - - data_length_pointer[0] = sent - - if sent != bytes_to_write: - return SecurityConst.errSSLWouldBlock - - return 0 - except Exception as e: - if wrapped_socket is not None: - wrapped_socket._exception = e - return SecurityConst.errSSLInternal - - -# We need to keep these two objects references alive: if they get GC'd while -# in use then SecureTransport could attempt to call a function that is in freed -# memory. That would be...uh...bad. Yeah, that's the word. Bad. -_read_callback_pointer = Security.SSLReadFunc(_read_callback) -_write_callback_pointer = Security.SSLWriteFunc(_write_callback) - - -class WrappedSocket(object): - """ - API-compatibility wrapper for Python's OpenSSL wrapped socket object. - - Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage - collector of PyPy. - """ - def __init__(self, socket): - self.socket = socket - self.context = None - self._makefile_refs = 0 - self._closed = False - self._exception = None - self._keychain = None - self._keychain_dir = None - self._client_cert_chain = None - - # We save off the previously-configured timeout and then set it to - # zero. This is done because we use select and friends to handle the - # timeouts, but if we leave the timeout set on the lower socket then - # Python will "kindly" call select on that socket again for us. Avoid - # that by forcing the timeout to zero. - self._timeout = self.socket.gettimeout() - self.socket.settimeout(0) - - @contextlib.contextmanager - def _raise_on_error(self): - """ - A context manager that can be used to wrap calls that do I/O from - SecureTransport. If any of the I/O callbacks hit an exception, this - context manager will correctly propagate the exception after the fact. - This avoids silently swallowing those exceptions. - - It also correctly forces the socket closed. - """ - self._exception = None - - # We explicitly don't catch around this yield because in the unlikely - # event that an exception was hit in the block we don't want to swallow - # it. - yield - if self._exception is not None: - exception, self._exception = self._exception, None - self.close() - raise exception - - def _set_ciphers(self): - """ - Sets up the allowed ciphers. By default this matches the set in - util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done - custom and doesn't allow changing at this time, mostly because parsing - OpenSSL cipher strings is going to be a freaking nightmare. - """ - ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) - result = Security.SSLSetEnabledCiphers( - self.context, ciphers, len(CIPHER_SUITES) - ) - _assert_no_error(result) - - def _custom_validate(self, verify, trust_bundle): - """ - Called when we have set custom validation. We do this in two cases: - first, when cert validation is entirely disabled; and second, when - using a custom trust DB. - """ - # If we disabled cert validation, just say: cool. - if not verify: - return - - # We want data in memory, so load it up. - if os.path.isfile(trust_bundle): - with open(trust_bundle, 'rb') as f: - trust_bundle = f.read() - - cert_array = None - trust = Security.SecTrustRef() - - try: - # Get a CFArray that contains the certs we want. - cert_array = _cert_array_from_pem(trust_bundle) - - # Ok, now the hard part. We want to get the SecTrustRef that ST has - # created for this connection, shove our CAs into it, tell ST to - # ignore everything else it knows, and then ask if it can build a - # chain. This is a buuuunch of code. - result = Security.SSLCopyPeerTrust( - self.context, ctypes.byref(trust) - ) - _assert_no_error(result) - if not trust: - raise ssl.SSLError("Failed to copy trust reference") - - result = Security.SecTrustSetAnchorCertificates(trust, cert_array) - _assert_no_error(result) - - result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) - _assert_no_error(result) - - trust_result = Security.SecTrustResultType() - result = Security.SecTrustEvaluate( - trust, ctypes.byref(trust_result) - ) - _assert_no_error(result) - finally: - if trust: - CoreFoundation.CFRelease(trust) - - if cert_array is not None: - CoreFoundation.CFRelease(cert_array) - - # Ok, now we can look at what the result was. - successes = ( - SecurityConst.kSecTrustResultUnspecified, - SecurityConst.kSecTrustResultProceed - ) - if trust_result.value not in successes: - raise ssl.SSLError( - "certificate verify failed, error code: %d" % - trust_result.value - ) - - def handshake(self, - server_hostname, - verify, - trust_bundle, - min_version, - max_version, - client_cert, - client_key, - client_key_passphrase): - """ - Actually performs the TLS handshake. This is run automatically by - wrapped socket, and shouldn't be needed in user code. - """ - # First, we do the initial bits of connection setup. We need to create - # a context, set its I/O funcs, and set the connection reference. - self.context = Security.SSLCreateContext( - None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType - ) - result = Security.SSLSetIOFuncs( - self.context, _read_callback_pointer, _write_callback_pointer - ) - _assert_no_error(result) - - # Here we need to compute the handle to use. We do this by taking the - # id of self modulo 2**31 - 1. If this is already in the dictionary, we - # just keep incrementing by one until we find a free space. - with _connection_ref_lock: - handle = id(self) % 2147483647 - while handle in _connection_refs: - handle = (handle + 1) % 2147483647 - _connection_refs[handle] = self - - result = Security.SSLSetConnection(self.context, handle) - _assert_no_error(result) - - # If we have a server hostname, we should set that too. - if server_hostname: - if not isinstance(server_hostname, bytes): - server_hostname = server_hostname.encode('utf-8') - - result = Security.SSLSetPeerDomainName( - self.context, server_hostname, len(server_hostname) - ) - _assert_no_error(result) - - # Setup the ciphers. - self._set_ciphers() - - # Set the minimum and maximum TLS versions. - result = Security.SSLSetProtocolVersionMin(self.context, min_version) - _assert_no_error(result) - result = Security.SSLSetProtocolVersionMax(self.context, max_version) - _assert_no_error(result) - - # If there's a trust DB, we need to use it. We do that by telling - # SecureTransport to break on server auth. We also do that if we don't - # want to validate the certs at all: we just won't actually do any - # authing in that case. - if not verify or trust_bundle is not None: - result = Security.SSLSetSessionOption( - self.context, - SecurityConst.kSSLSessionOptionBreakOnServerAuth, - True - ) - _assert_no_error(result) - - # If there's a client cert, we need to use it. - if client_cert: - self._keychain, self._keychain_dir = _temporary_keychain() - self._client_cert_chain = _load_client_cert_chain( - self._keychain, client_cert, client_key - ) - result = Security.SSLSetCertificate( - self.context, self._client_cert_chain - ) - _assert_no_error(result) - - while True: - with self._raise_on_error(): - result = Security.SSLHandshake(self.context) - - if result == SecurityConst.errSSLWouldBlock: - raise socket.timeout("handshake timed out") - elif result == SecurityConst.errSSLServerAuthCompleted: - self._custom_validate(verify, trust_bundle) - continue - else: - _assert_no_error(result) - break - - def fileno(self): - return self.socket.fileno() - - # Copy-pasted from Python 3.5 source code - def _decref_socketios(self): - if self._makefile_refs > 0: - self._makefile_refs -= 1 - if self._closed: - self.close() - - def recv(self, bufsiz): - buffer = ctypes.create_string_buffer(bufsiz) - bytes_read = self.recv_into(buffer, bufsiz) - data = buffer[:bytes_read] - return data - - def recv_into(self, buffer, nbytes=None): - # Read short on EOF. - if self._closed: - return 0 - - if nbytes is None: - nbytes = len(buffer) - - buffer = (ctypes.c_char * nbytes).from_buffer(buffer) - processed_bytes = ctypes.c_size_t(0) - - with self._raise_on_error(): - result = Security.SSLRead( - self.context, buffer, nbytes, ctypes.byref(processed_bytes) - ) - - # There are some result codes that we want to treat as "not always - # errors". Specifically, those are errSSLWouldBlock, - # errSSLClosedGraceful, and errSSLClosedNoNotify. - if (result == SecurityConst.errSSLWouldBlock): - # If we didn't process any bytes, then this was just a time out. - # However, we can get errSSLWouldBlock in situations when we *did* - # read some data, and in those cases we should just read "short" - # and return. - if processed_bytes.value == 0: - # Timed out, no data read. - raise socket.timeout("recv timed out") - elif result in (SecurityConst.errSSLClosedGraceful, SecurityConst.errSSLClosedNoNotify): - # The remote peer has closed this connection. We should do so as - # well. Note that we don't actually return here because in - # principle this could actually be fired along with return data. - # It's unlikely though. - self.close() - else: - _assert_no_error(result) - - # Ok, we read and probably succeeded. We should return whatever data - # was actually read. - return processed_bytes.value - - def settimeout(self, timeout): - self._timeout = timeout - - def gettimeout(self): - return self._timeout - - def send(self, data): - processed_bytes = ctypes.c_size_t(0) - - with self._raise_on_error(): - result = Security.SSLWrite( - self.context, data, len(data), ctypes.byref(processed_bytes) - ) - - if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: - # Timed out - raise socket.timeout("send timed out") - else: - _assert_no_error(result) - - # We sent, and probably succeeded. Tell them how much we sent. - return processed_bytes.value - - def sendall(self, data): - total_sent = 0 - while total_sent < len(data): - sent = self.send(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) - total_sent += sent - - def shutdown(self): - with self._raise_on_error(): - Security.SSLClose(self.context) - - def close(self): - # TODO: should I do clean shutdown here? Do I have to? - if self._makefile_refs < 1: - self._closed = True - if self.context: - CoreFoundation.CFRelease(self.context) - self.context = None - if self._client_cert_chain: - CoreFoundation.CFRelease(self._client_cert_chain) - self._client_cert_chain = None - if self._keychain: - Security.SecKeychainDelete(self._keychain) - CoreFoundation.CFRelease(self._keychain) - shutil.rmtree(self._keychain_dir) - self._keychain = self._keychain_dir = None - return self.socket.close() - else: - self._makefile_refs -= 1 - - def getpeercert(self, binary_form=False): - # Urgh, annoying. - # - # Here's how we do this: - # - # 1. Call SSLCopyPeerTrust to get hold of the trust object for this - # connection. - # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. - # 3. To get the CN, call SecCertificateCopyCommonName and process that - # string so that it's of the appropriate type. - # 4. To get the SAN, we need to do something a bit more complex: - # a. Call SecCertificateCopyValues to get the data, requesting - # kSecOIDSubjectAltName. - # b. Mess about with this dictionary to try to get the SANs out. - # - # This is gross. Really gross. It's going to be a few hundred LoC extra - # just to repeat something that SecureTransport can *already do*. So my - # operating assumption at this time is that what we want to do is - # instead to just flag to urllib3 that it shouldn't do its own hostname - # validation when using SecureTransport. - if not binary_form: - raise ValueError( - "SecureTransport only supports dumping binary certs" - ) - trust = Security.SecTrustRef() - certdata = None - der_bytes = None - - try: - # Grab the trust store. - result = Security.SSLCopyPeerTrust( - self.context, ctypes.byref(trust) - ) - _assert_no_error(result) - if not trust: - # Probably we haven't done the handshake yet. No biggie. - return None - - cert_count = Security.SecTrustGetCertificateCount(trust) - if not cert_count: - # Also a case that might happen if we haven't handshaked. - # Handshook? Handshaken? - return None - - leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) - assert leaf - - # Ok, now we want the DER bytes. - certdata = Security.SecCertificateCopyData(leaf) - assert certdata - - data_length = CoreFoundation.CFDataGetLength(certdata) - data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) - der_bytes = ctypes.string_at(data_buffer, data_length) - finally: - if certdata: - CoreFoundation.CFRelease(certdata) - if trust: - CoreFoundation.CFRelease(trust) - - return der_bytes - - def _reuse(self): - self._makefile_refs += 1 - - def _drop(self): - if self._makefile_refs < 1: - self.close() - else: - self._makefile_refs -= 1 - - -if _fileobject: # Platform-specific: Python 2 - def makefile(self, mode, bufsize=-1): - self._makefile_refs += 1 - return _fileobject(self, mode, bufsize, close=True) -else: # Platform-specific: Python 3 - def makefile(self, mode="r", buffering=None, *args, **kwargs): - # We disable buffering with SecureTransport because it conflicts with - # the buffering that ST does internally (see issue #1153 for more). - buffering = 0 - return backport_makefile(self, mode, buffering, *args, **kwargs) - -WrappedSocket.makefile = makefile - - -class SecureTransportContext(object): - """ - I am a wrapper class for the SecureTransport library, to translate the - interface of the standard library ``SSLContext`` object to calls into - SecureTransport. - """ - def __init__(self, protocol): - self._min_version, self._max_version = _protocol_to_min_max[protocol] - self._options = 0 - self._verify = False - self._trust_bundle = None - self._client_cert = None - self._client_key = None - self._client_key_passphrase = None - - @property - def check_hostname(self): - """ - SecureTransport cannot have its hostname checking disabled. For more, - see the comment on getpeercert() in this file. - """ - return True - - @check_hostname.setter - def check_hostname(self, value): - """ - SecureTransport cannot have its hostname checking disabled. For more, - see the comment on getpeercert() in this file. - """ - pass - - @property - def options(self): - # TODO: Well, crap. - # - # So this is the bit of the code that is the most likely to cause us - # trouble. Essentially we need to enumerate all of the SSL options that - # users might want to use and try to see if we can sensibly translate - # them, or whether we should just ignore them. - return self._options - - @options.setter - def options(self, value): - # TODO: Update in line with above. - self._options = value - - @property - def verify_mode(self): - return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE - - @verify_mode.setter - def verify_mode(self, value): - self._verify = True if value == ssl.CERT_REQUIRED else False - - def set_default_verify_paths(self): - # So, this has to do something a bit weird. Specifically, what it does - # is nothing. - # - # This means that, if we had previously had load_verify_locations - # called, this does not undo that. We need to do that because it turns - # out that the rest of the urllib3 code will attempt to load the - # default verify paths if it hasn't been told about any paths, even if - # the context itself was sometime earlier. We resolve that by just - # ignoring it. - pass - - def load_default_certs(self): - return self.set_default_verify_paths() - - def set_ciphers(self, ciphers): - # For now, we just require the default cipher string. - if ciphers != util.ssl_.DEFAULT_CIPHERS: - raise ValueError( - "SecureTransport doesn't support custom cipher strings" - ) - - def load_verify_locations(self, cafile=None, capath=None, cadata=None): - # OK, we only really support cadata and cafile. - if capath is not None: - raise ValueError( - "SecureTransport does not support cert directories" - ) - - self._trust_bundle = cafile or cadata - - def load_cert_chain(self, certfile, keyfile=None, password=None): - self._client_cert = certfile - self._client_key = keyfile - self._client_cert_passphrase = password - - def wrap_socket(self, sock, server_side=False, - do_handshake_on_connect=True, suppress_ragged_eofs=True, - server_hostname=None): - # So, what do we do here? Firstly, we assert some properties. This is a - # stripped down shim, so there is some functionality we don't support. - # See PEP 543 for the real deal. - assert not server_side - assert do_handshake_on_connect - assert suppress_ragged_eofs - - # Ok, we're good to go. Now we want to create the wrapped socket object - # and store it in the appropriate place. - wrapped_socket = WrappedSocket(sock) - - # Now we can handshake - wrapped_socket.handshake( - server_hostname, self._verify, self._trust_bundle, - self._min_version, self._max_version, self._client_cert, - self._client_key, self._client_key_passphrase - ) - return wrapped_socket diff --git a/lib/urllib3/contrib/socks.py b/lib/urllib3/contrib/socks.py deleted file mode 100644 index 811e312..0000000 --- a/lib/urllib3/contrib/socks.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This module contains provisional support for SOCKS proxies from within -urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and -SOCKS5. To enable its functionality, either install PySocks or install this -module with the ``socks`` extra. - -The SOCKS implementation supports the full range of urllib3 features. It also -supports the following SOCKS features: - -- SOCKS4 -- SOCKS4a -- SOCKS5 -- Usernames and passwords for the SOCKS proxy - -Known Limitations: - -- Currently PySocks does not support contacting remote websites via literal - IPv6 addresses. Any such connection attempt will fail. You must use a domain - name. -- Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any - such connection attempt will fail. -""" -from __future__ import absolute_import - -try: - import socks -except ImportError: - import warnings - from ..exceptions import DependencyWarning - - warnings.warn(( - 'SOCKS support in urllib3 requires the installation of optional ' - 'dependencies: specifically, PySocks. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/contrib.html#socks-proxies' - ), - DependencyWarning - ) - raise - -from socket import error as SocketError, timeout as SocketTimeout - -from ..connection import ( - HTTPConnection, HTTPSConnection -) -from ..connectionpool import ( - HTTPConnectionPool, HTTPSConnectionPool -) -from ..exceptions import ConnectTimeoutError, NewConnectionError -from ..poolmanager import PoolManager -from ..util.url import parse_url - -try: - import ssl -except ImportError: - ssl = None - - -class SOCKSConnection(HTTPConnection): - """ - A plain-text HTTP connection that connects via a SOCKS proxy. - """ - def __init__(self, *args, **kwargs): - self._socks_options = kwargs.pop('_socks_options') - super(SOCKSConnection, self).__init__(*args, **kwargs) - - def _new_conn(self): - """ - Establish a new connection via the SOCKS proxy. - """ - extra_kw = {} - if self.source_address: - extra_kw['source_address'] = self.source_address - - if self.socket_options: - extra_kw['socket_options'] = self.socket_options - - try: - conn = socks.create_connection( - (self.host, self.port), - proxy_type=self._socks_options['socks_version'], - proxy_addr=self._socks_options['proxy_host'], - proxy_port=self._socks_options['proxy_port'], - proxy_username=self._socks_options['username'], - proxy_password=self._socks_options['password'], - proxy_rdns=self._socks_options['rdns'], - timeout=self.timeout, - **extra_kw - ) - - except SocketTimeout as e: - raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) - - except socks.ProxyError as e: - # This is fragile as hell, but it seems to be the only way to raise - # useful errors here. - if e.socket_err: - error = e.socket_err - if isinstance(error, SocketTimeout): - raise ConnectTimeoutError( - self, - "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout) - ) - else: - raise NewConnectionError( - self, - "Failed to establish a new connection: %s" % error - ) - else: - raise NewConnectionError( - self, - "Failed to establish a new connection: %s" % e - ) - - except SocketError as e: # Defensive: PySocks should catch all these. - raise NewConnectionError( - self, "Failed to establish a new connection: %s" % e) - - return conn - - -# We don't need to duplicate the Verified/Unverified distinction from -# urllib3/connection.py here because the HTTPSConnection will already have been -# correctly set to either the Verified or Unverified form by that module. This -# means the SOCKSHTTPSConnection will automatically be the correct type. -class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): - pass - - -class SOCKSHTTPConnectionPool(HTTPConnectionPool): - ConnectionCls = SOCKSConnection - - -class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): - ConnectionCls = SOCKSHTTPSConnection - - -class SOCKSProxyManager(PoolManager): - """ - A version of the urllib3 ProxyManager that routes connections via the - defined SOCKS proxy. - """ - pool_classes_by_scheme = { - 'http': SOCKSHTTPConnectionPool, - 'https': SOCKSHTTPSConnectionPool, - } - - def __init__(self, proxy_url, username=None, password=None, - num_pools=10, headers=None, **connection_pool_kw): - parsed = parse_url(proxy_url) - - if username is None and password is None and parsed.auth is not None: - split = parsed.auth.split(':') - if len(split) == 2: - username, password = split - if parsed.scheme == 'socks5': - socks_version = socks.PROXY_TYPE_SOCKS5 - rdns = False - elif parsed.scheme == 'socks5h': - socks_version = socks.PROXY_TYPE_SOCKS5 - rdns = True - elif parsed.scheme == 'socks4': - socks_version = socks.PROXY_TYPE_SOCKS4 - rdns = False - elif parsed.scheme == 'socks4a': - socks_version = socks.PROXY_TYPE_SOCKS4 - rdns = True - else: - raise ValueError( - "Unable to determine SOCKS version from %s" % proxy_url - ) - - self.proxy_url = proxy_url - - socks_options = { - 'socks_version': socks_version, - 'proxy_host': parsed.host, - 'proxy_port': parsed.port, - 'username': username, - 'password': password, - 'rdns': rdns - } - connection_pool_kw['_socks_options'] = socks_options - - super(SOCKSProxyManager, self).__init__( - num_pools, headers, **connection_pool_kw - ) - - self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/lib/urllib3/exceptions.py b/lib/urllib3/exceptions.py deleted file mode 100644 index 7bbaa98..0000000 --- a/lib/urllib3/exceptions.py +++ /dev/null @@ -1,246 +0,0 @@ -from __future__ import absolute_import -from .packages.six.moves.http_client import ( - IncompleteRead as httplib_IncompleteRead -) -# Base Exceptions - - -class HTTPError(Exception): - "Base exception used by this module." - pass - - -class HTTPWarning(Warning): - "Base warning used by this module." - pass - - -class PoolError(HTTPError): - "Base exception for errors caused within a pool." - def __init__(self, pool, message): - self.pool = pool - HTTPError.__init__(self, "%s: %s" % (pool, message)) - - def __reduce__(self): - # For pickling purposes. - return self.__class__, (None, None) - - -class RequestError(PoolError): - "Base exception for PoolErrors that have associated URLs." - def __init__(self, pool, url, message): - self.url = url - PoolError.__init__(self, pool, message) - - def __reduce__(self): - # For pickling purposes. - return self.__class__, (None, self.url, None) - - -class SSLError(HTTPError): - "Raised when SSL certificate fails in an HTTPS connection." - pass - - -class ProxyError(HTTPError): - "Raised when the connection to a proxy fails." - pass - - -class DecodeError(HTTPError): - "Raised when automatic decoding based on Content-Type fails." - pass - - -class ProtocolError(HTTPError): - "Raised when something unexpected happens mid-request/response." - pass - - -#: Renamed to ProtocolError but aliased for backwards compatibility. -ConnectionError = ProtocolError - - -# Leaf Exceptions - -class MaxRetryError(RequestError): - """Raised when the maximum number of retries is exceeded. - - :param pool: The connection pool - :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` - :param string url: The requested Url - :param exceptions.Exception reason: The underlying error - - """ - - def __init__(self, pool, url, reason=None): - self.reason = reason - - message = "Max retries exceeded with url: %s (Caused by %r)" % ( - url, reason) - - RequestError.__init__(self, pool, url, message) - - -class HostChangedError(RequestError): - "Raised when an existing pool gets a request for a foreign host." - - def __init__(self, pool, url, retries=3): - message = "Tried to open a foreign host with url: %s" % url - RequestError.__init__(self, pool, url, message) - self.retries = retries - - -class TimeoutStateError(HTTPError): - """ Raised when passing an invalid state to a timeout """ - pass - - -class TimeoutError(HTTPError): - """ Raised when a socket timeout error occurs. - - Catching this error will catch both :exc:`ReadTimeoutErrors - <ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`. - """ - pass - - -class ReadTimeoutError(TimeoutError, RequestError): - "Raised when a socket timeout occurs while receiving data from a server" - pass - - -# This timeout error does not have a URL attached and needs to inherit from the -# base HTTPError -class ConnectTimeoutError(TimeoutError): - "Raised when a socket timeout occurs while connecting to a server" - pass - - -class NewConnectionError(ConnectTimeoutError, PoolError): - "Raised when we fail to establish a new connection. Usually ECONNREFUSED." - pass - - -class EmptyPoolError(PoolError): - "Raised when a pool runs out of connections and no more are allowed." - pass - - -class ClosedPoolError(PoolError): - "Raised when a request enters a pool after the pool has been closed." - pass - - -class LocationValueError(ValueError, HTTPError): - "Raised when there is something wrong with a given URL input." - pass - - -class LocationParseError(LocationValueError): - "Raised when get_host or similar fails to parse the URL input." - - def __init__(self, location): - message = "Failed to parse: %s" % location - HTTPError.__init__(self, message) - - self.location = location - - -class ResponseError(HTTPError): - "Used as a container for an error reason supplied in a MaxRetryError." - GENERIC_ERROR = 'too many error responses' - SPECIFIC_ERROR = 'too many {status_code} error responses' - - -class SecurityWarning(HTTPWarning): - "Warned when performing security reducing actions" - pass - - -class SubjectAltNameWarning(SecurityWarning): - "Warned when connecting to a host with a certificate missing a SAN." - pass - - -class InsecureRequestWarning(SecurityWarning): - "Warned when making an unverified HTTPS request." - pass - - -class SystemTimeWarning(SecurityWarning): - "Warned when system time is suspected to be wrong" - pass - - -class InsecurePlatformWarning(SecurityWarning): - "Warned when certain SSL configuration is not available on a platform." - pass - - -class SNIMissingWarning(HTTPWarning): - "Warned when making a HTTPS request without SNI available." - pass - - -class DependencyWarning(HTTPWarning): - """ - Warned when an attempt is made to import a module with missing optional - dependencies. - """ - pass - - -class ResponseNotChunked(ProtocolError, ValueError): - "Response needs to be chunked in order to read it as chunks." - pass - - -class BodyNotHttplibCompatible(HTTPError): - """ - Body should be httplib.HTTPResponse like (have an fp attribute which - returns raw chunks) for read_chunked(). - """ - pass - - -class IncompleteRead(HTTPError, httplib_IncompleteRead): - """ - Response length doesn't match expected Content-Length - - Subclass of http_client.IncompleteRead to allow int value - for `partial` to avoid creating large objects on streamed - reads. - """ - def __init__(self, partial, expected): - super(IncompleteRead, self).__init__(partial, expected) - - def __repr__(self): - return ('IncompleteRead(%i bytes read, ' - '%i more expected)' % (self.partial, self.expected)) - - -class InvalidHeader(HTTPError): - "The header provided was somehow invalid." - pass - - -class ProxySchemeUnknown(AssertionError, ValueError): - "ProxyManager does not support the supplied scheme" - # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. - - def __init__(self, scheme): - message = "Not supported proxy scheme %s" % scheme - super(ProxySchemeUnknown, self).__init__(message) - - -class HeaderParsingError(HTTPError): - "Raised by assert_header_parsing, but we convert it to a log.warning statement." - def __init__(self, defects, unparsed_data): - message = '%s, unparsed data: %r' % (defects or 'Unknown', unparsed_data) - super(HeaderParsingError, self).__init__(message) - - -class UnrewindableBodyError(HTTPError): - "urllib3 encountered an error when trying to rewind a body" - pass diff --git a/lib/urllib3/fields.py b/lib/urllib3/fields.py deleted file mode 100644 index 37fe64a..0000000 --- a/lib/urllib3/fields.py +++ /dev/null @@ -1,178 +0,0 @@ -from __future__ import absolute_import -import email.utils -import mimetypes - -from .packages import six - - -def guess_content_type(filename, default='application/octet-stream'): - """ - Guess the "Content-Type" of a file. - - :param filename: - The filename to guess the "Content-Type" of using :mod:`mimetypes`. - :param default: - If no "Content-Type" can be guessed, default to `default`. - """ - if filename: - return mimetypes.guess_type(filename)[0] or default - return default - - -def format_header_param(name, value): - """ - Helper function to format and quote a single header parameter. - - Particularly useful for header parameters which might contain - non-ASCII values, like file names. This follows RFC 2231, as - suggested by RFC 2388 Section 4.4. - - :param name: - The name of the parameter, a string expected to be ASCII only. - :param value: - The value of the parameter, provided as a unicode string. - """ - if not any(ch in value for ch in '"\\\r\n'): - result = '%s="%s"' % (name, value) - try: - result.encode('ascii') - except (UnicodeEncodeError, UnicodeDecodeError): - pass - else: - return result - if not six.PY3 and isinstance(value, six.text_type): # Python 2: - value = value.encode('utf-8') - value = email.utils.encode_rfc2231(value, 'utf-8') - value = '%s*=%s' % (name, value) - return value - - -class RequestField(object): - """ - A data container for request body parameters. - - :param name: - The name of this request field. - :param data: - The data/value body. - :param filename: - An optional filename of the request field. - :param headers: - An optional dict-like object of headers to initially use for the field. - """ - def __init__(self, name, data, filename=None, headers=None): - self._name = name - self._filename = filename - self.data = data - self.headers = {} - if headers: - self.headers = dict(headers) - - @classmethod - def from_tuples(cls, fieldname, value): - """ - A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. - - Supports constructing :class:`~urllib3.fields.RequestField` from - parameter of key/value strings AND key/filetuple. A filetuple is a - (filename, data, MIME type) tuple where the MIME type is optional. - For example:: - - 'foo': 'bar', - 'fakefile': ('foofile.txt', 'contents of foofile'), - 'realfile': ('barfile.txt', open('realfile').read()), - 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), - 'nonamefile': 'contents of nonamefile field', - - Field names and filenames must be unicode. - """ - if isinstance(value, tuple): - if len(value) == 3: - filename, data, content_type = value - else: - filename, data = value - content_type = guess_content_type(filename) - else: - filename = None - content_type = None - data = value - - request_param = cls(fieldname, data, filename=filename) - request_param.make_multipart(content_type=content_type) - - return request_param - - def _render_part(self, name, value): - """ - Overridable helper function to format a single header parameter. - - :param name: - The name of the parameter, a string expected to be ASCII only. - :param value: - The value of the parameter, provided as a unicode string. - """ - return format_header_param(name, value) - - def _render_parts(self, header_parts): - """ - Helper function to format and quote a single header. - - Useful for single headers that are composed of multiple items. E.g., - 'Content-Disposition' fields. - - :param header_parts: - A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format - as `k1="v1"; k2="v2"; ...`. - """ - parts = [] - iterable = header_parts - if isinstance(header_parts, dict): - iterable = header_parts.items() - - for name, value in iterable: - if value is not None: - parts.append(self._render_part(name, value)) - - return '; '.join(parts) - - def render_headers(self): - """ - Renders the headers for this request field. - """ - lines = [] - - sort_keys = ['Content-Disposition', 'Content-Type', 'Content-Location'] - for sort_key in sort_keys: - if self.headers.get(sort_key, False): - lines.append('%s: %s' % (sort_key, self.headers[sort_key])) - - for header_name, header_value in self.headers.items(): - if header_name not in sort_keys: - if header_value: - lines.append('%s: %s' % (header_name, header_value)) - - lines.append('\r\n') - return '\r\n'.join(lines) - - def make_multipart(self, content_disposition=None, content_type=None, - content_location=None): - """ - Makes this request field into a multipart request field. - - This method overrides "Content-Disposition", "Content-Type" and - "Content-Location" headers to the request parameter. - - :param content_type: - The 'Content-Type' of the request body. - :param content_location: - The 'Content-Location' of the request body. - - """ - self.headers['Content-Disposition'] = content_disposition or 'form-data' - self.headers['Content-Disposition'] += '; '.join([ - '', self._render_parts( - (('name', self._name), ('filename', self._filename)) - ) - ]) - self.headers['Content-Type'] = content_type - self.headers['Content-Location'] = content_location diff --git a/lib/urllib3/filepost.py b/lib/urllib3/filepost.py deleted file mode 100644 index 78f1e19..0000000 --- a/lib/urllib3/filepost.py +++ /dev/null @@ -1,98 +0,0 @@ -from __future__ import absolute_import -import binascii -import codecs -import os - -from io import BytesIO - -from .packages import six -from .packages.six import b -from .fields import RequestField - -writer = codecs.lookup('utf-8')[3] - - -def choose_boundary(): - """ - Our embarrassingly-simple replacement for mimetools.choose_boundary. - """ - boundary = binascii.hexlify(os.urandom(16)) - if six.PY3: - boundary = boundary.decode('ascii') - return boundary - - -def iter_field_objects(fields): - """ - Iterate over fields. - - Supports list of (k, v) tuples and dicts, and lists of - :class:`~urllib3.fields.RequestField`. - - """ - if isinstance(fields, dict): - i = six.iteritems(fields) - else: - i = iter(fields) - - for field in i: - if isinstance(field, RequestField): - yield field - else: - yield RequestField.from_tuples(*field) - - -def iter_fields(fields): - """ - .. deprecated:: 1.6 - - Iterate over fields. - - The addition of :class:`~urllib3.fields.RequestField` makes this function - obsolete. Instead, use :func:`iter_field_objects`, which returns - :class:`~urllib3.fields.RequestField` objects. - - Supports list of (k, v) tuples and dicts. - """ - if isinstance(fields, dict): - return ((k, v) for k, v in six.iteritems(fields)) - - return ((k, v) for k, v in fields) - - -def encode_multipart_formdata(fields, boundary=None): - """ - Encode a dictionary of ``fields`` using the multipart/form-data MIME format. - - :param fields: - Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). - - :param boundary: - If not specified, then a random boundary will be generated using - :func:`urllib3.filepost.choose_boundary`. - """ - body = BytesIO() - if boundary is None: - boundary = choose_boundary() - - for field in iter_field_objects(fields): - body.write(b('--%s\r\n' % (boundary))) - - writer(body).write(field.render_headers()) - data = field.data - - if isinstance(data, int): - data = str(data) # Backwards compatibility - - if isinstance(data, six.text_type): - writer(body).write(data) - else: - body.write(data) - - body.write(b'\r\n') - - body.write(b('--%s--\r\n' % (boundary))) - - content_type = str('multipart/form-data; boundary=%s' % boundary) - - return body.getvalue(), content_type diff --git a/lib/urllib3/packages/__init__.py b/lib/urllib3/packages/__init__.py deleted file mode 100644 index 170e974..0000000 --- a/lib/urllib3/packages/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import absolute_import - -from . import ssl_match_hostname - -__all__ = ('ssl_match_hostname', ) diff --git a/lib/urllib3/packages/backports/__init__.py b/lib/urllib3/packages/backports/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lib/urllib3/packages/backports/makefile.py b/lib/urllib3/packages/backports/makefile.py deleted file mode 100644 index 740db37..0000000 --- a/lib/urllib3/packages/backports/makefile.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -""" -backports.makefile -~~~~~~~~~~~~~~~~~~ - -Backports the Python 3 ``socket.makefile`` method for use with anything that -wants to create a "fake" socket object. -""" -import io - -from socket import SocketIO - - -def backport_makefile(self, mode="r", buffering=None, encoding=None, - errors=None, newline=None): - """ - Backport of ``socket.makefile`` from Python 3.5. - """ - if not set(mode) <= {"r", "w", "b"}: - raise ValueError( - "invalid mode %r (only r, w, b allowed)" % (mode,) - ) - writing = "w" in mode - reading = "r" in mode or not writing - assert reading or writing - binary = "b" in mode - rawmode = "" - if reading: - rawmode += "r" - if writing: - rawmode += "w" - raw = SocketIO(self, rawmode) - self._makefile_refs += 1 - if buffering is None: - buffering = -1 - if buffering < 0: - buffering = io.DEFAULT_BUFFER_SIZE - if buffering == 0: - if not binary: - raise ValueError("unbuffered streams must be binary") - return raw - if reading and writing: - buffer = io.BufferedRWPair(raw, raw, buffering) - elif reading: - buffer = io.BufferedReader(raw, buffering) - else: - assert writing - buffer = io.BufferedWriter(raw, buffering) - if binary: - return buffer - text = io.TextIOWrapper(buffer, encoding, errors, newline) - text.mode = mode - return text diff --git a/lib/urllib3/packages/six.py b/lib/urllib3/packages/six.py deleted file mode 100644 index 190c023..0000000 --- a/lib/urllib3/packages/six.py +++ /dev/null @@ -1,868 +0,0 @@ -"""Utilities for writing code that runs on Python 2 and 3""" - -# Copyright (c) 2010-2015 Benjamin Peterson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -from __future__ import absolute_import - -import functools -import itertools -import operator -import sys -import types - -__author__ = "Benjamin Peterson <benjamin@python.org>" -__version__ = "1.10.0" - - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -PY34 = sys.version_info[0:2] >= (3, 4) - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) # Invokes __set__. - try: - # This is a bit ugly, but it avoids running this again by - # removing this descriptor. - delattr(obj.__class__, self.name) - except AttributeError: - pass - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - def __getattr__(self, attr): - _module = self._resolve() - value = getattr(_module, attr) - setattr(self, attr, value) - return value - - -class _LazyModule(types.ModuleType): - - def __init__(self, name): - super(_LazyModule, self).__init__(name) - self.__doc__ = self.__class__.__doc__ - - def __dir__(self): - attrs = ["__doc__", "__name__"] - attrs += [attr.name for attr in self._moved_attributes] - return attrs - - # Subclasses should override this - _moved_attributes = [] - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _SixMetaPathImporter(object): - - """ - A meta path importer to import six.moves and its submodules. - - This class implements a PEP302 finder and loader. It should be compatible - with Python 2.5 and all existing versions of Python3 - """ - - def __init__(self, six_module_name): - self.name = six_module_name - self.known_modules = {} - - def _add_module(self, mod, *fullnames): - for fullname in fullnames: - self.known_modules[self.name + "." + fullname] = mod - - def _get_module(self, fullname): - return self.known_modules[self.name + "." + fullname] - - def find_module(self, fullname, path=None): - if fullname in self.known_modules: - return self - return None - - def __get_module(self, fullname): - try: - return self.known_modules[fullname] - except KeyError: - raise ImportError("This loader does not know module " + fullname) - - def load_module(self, fullname): - try: - # in case of a reload - return sys.modules[fullname] - except KeyError: - pass - mod = self.__get_module(fullname) - if isinstance(mod, MovedModule): - mod = mod._resolve() - else: - mod.__loader__ = self - sys.modules[fullname] = mod - return mod - - def is_package(self, fullname): - """ - Return true, if the named module is a package. - - We need this method to get correct spec objects with - Python 3.4 (see PEP451) - """ - return hasattr(self.__get_module(fullname), "__path__") - - def get_code(self, fullname): - """Return None - - Required, if is_package is implemented""" - self.__get_module(fullname) # eventually raises ImportError - return None - get_source = get_code # same as get_code - -_importer = _SixMetaPathImporter(__name__) - - -class _MovedItems(_LazyModule): - - """Lazy loading of moved objects""" - __path__ = [] # mark as package - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("intern", "__builtin__", "sys"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), - MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), - MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("UserDict", "UserDict", "collections"), - MovedAttribute("UserList", "UserList", "collections"), - MovedAttribute("UserString", "UserString", "collections"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("copyreg", "copy_reg"), - MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("_thread", "thread", "_thread"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), - MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), - MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), - MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), -] -# Add windows specific modules. -if sys.platform == "win32": - _moved_attributes += [ - MovedModule("winreg", "_winreg"), - ] - -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) - if isinstance(attr, MovedModule): - _importer._add_module(attr, "moves." + attr.name) -del attr - -_MovedItems._moved_attributes = _moved_attributes - -moves = _MovedItems(__name__ + ".moves") -_importer._add_module(moves, "moves") - - -class Module_six_moves_urllib_parse(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_parse""" - - -_urllib_parse_moved_attributes = [ - MovedAttribute("ParseResult", "urlparse", "urllib.parse"), - MovedAttribute("SplitResult", "urlparse", "urllib.parse"), - MovedAttribute("parse_qs", "urlparse", "urllib.parse"), - MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), - MovedAttribute("urldefrag", "urlparse", "urllib.parse"), - MovedAttribute("urljoin", "urlparse", "urllib.parse"), - MovedAttribute("urlparse", "urlparse", "urllib.parse"), - MovedAttribute("urlsplit", "urlparse", "urllib.parse"), - MovedAttribute("urlunparse", "urlparse", "urllib.parse"), - MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), - MovedAttribute("quote", "urllib", "urllib.parse"), - MovedAttribute("quote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote", "urllib", "urllib.parse"), - MovedAttribute("unquote_plus", "urllib", "urllib.parse"), - MovedAttribute("urlencode", "urllib", "urllib.parse"), - MovedAttribute("splitquery", "urllib", "urllib.parse"), - MovedAttribute("splittag", "urllib", "urllib.parse"), - MovedAttribute("splituser", "urllib", "urllib.parse"), - MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), - MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), - MovedAttribute("uses_params", "urlparse", "urllib.parse"), - MovedAttribute("uses_query", "urlparse", "urllib.parse"), - MovedAttribute("uses_relative", "urlparse", "urllib.parse"), -] -for attr in _urllib_parse_moved_attributes: - setattr(Module_six_moves_urllib_parse, attr.name, attr) -del attr - -Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes - -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") - - -class Module_six_moves_urllib_error(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_error""" - - -_urllib_error_moved_attributes = [ - MovedAttribute("URLError", "urllib2", "urllib.error"), - MovedAttribute("HTTPError", "urllib2", "urllib.error"), - MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), -] -for attr in _urllib_error_moved_attributes: - setattr(Module_six_moves_urllib_error, attr.name, attr) -del attr - -Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes - -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") - - -class Module_six_moves_urllib_request(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_request""" - - -_urllib_request_moved_attributes = [ - MovedAttribute("urlopen", "urllib2", "urllib.request"), - MovedAttribute("install_opener", "urllib2", "urllib.request"), - MovedAttribute("build_opener", "urllib2", "urllib.request"), - MovedAttribute("pathname2url", "urllib", "urllib.request"), - MovedAttribute("url2pathname", "urllib", "urllib.request"), - MovedAttribute("getproxies", "urllib", "urllib.request"), - MovedAttribute("Request", "urllib2", "urllib.request"), - MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), - MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), - MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), - MovedAttribute("BaseHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), - MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), - MovedAttribute("FileHandler", "urllib2", "urllib.request"), - MovedAttribute("FTPHandler", "urllib2", "urllib.request"), - MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), - MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), - MovedAttribute("urlretrieve", "urllib", "urllib.request"), - MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), - MovedAttribute("proxy_bypass", "urllib", "urllib.request"), -] -for attr in _urllib_request_moved_attributes: - setattr(Module_six_moves_urllib_request, attr.name, attr) -del attr - -Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes - -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") - - -class Module_six_moves_urllib_response(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_response""" - - -_urllib_response_moved_attributes = [ - MovedAttribute("addbase", "urllib", "urllib.response"), - MovedAttribute("addclosehook", "urllib", "urllib.response"), - MovedAttribute("addinfo", "urllib", "urllib.response"), - MovedAttribute("addinfourl", "urllib", "urllib.response"), -] -for attr in _urllib_response_moved_attributes: - setattr(Module_six_moves_urllib_response, attr.name, attr) -del attr - -Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes - -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") - - -class Module_six_moves_urllib_robotparser(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_robotparser""" - - -_urllib_robotparser_moved_attributes = [ - MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), -] -for attr in _urllib_robotparser_moved_attributes: - setattr(Module_six_moves_urllib_robotparser, attr.name, attr) -del attr - -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes - -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") - - -class Module_six_moves_urllib(types.ModuleType): - - """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" - __path__ = [] # mark as package - parse = _importer._get_module("moves.urllib_parse") - error = _importer._get_module("moves.urllib_error") - request = _importer._get_module("moves.urllib_request") - response = _importer._get_module("moves.urllib_response") - robotparser = _importer._get_module("moves.urllib_robotparser") - - def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] - -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - - -if PY3: - def get_unbound_function(unbound): - return unbound - - create_bound_method = types.MethodType - - def create_unbound_method(func, cls): - return func - - Iterator = object -else: - def get_unbound_function(unbound): - return unbound.im_func - - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - - def create_unbound_method(func, cls): - return types.MethodType(func, None, cls) - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -if PY3: - def iterkeys(d, **kw): - return iter(d.keys(**kw)) - - def itervalues(d, **kw): - return iter(d.values(**kw)) - - def iteritems(d, **kw): - return iter(d.items(**kw)) - - def iterlists(d, **kw): - return iter(d.lists(**kw)) - - viewkeys = operator.methodcaller("keys") - - viewvalues = operator.methodcaller("values") - - viewitems = operator.methodcaller("items") -else: - def iterkeys(d, **kw): - return d.iterkeys(**kw) - - def itervalues(d, **kw): - return d.itervalues(**kw) - - def iteritems(d, **kw): - return d.iteritems(**kw) - - def iterlists(d, **kw): - return d.iterlists(**kw) - - viewkeys = operator.methodcaller("viewkeys") - - viewvalues = operator.methodcaller("viewvalues") - - viewitems = operator.methodcaller("viewitems") - -_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") -_add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - unichr = chr - import struct - int2byte = struct.Struct(">B").pack - del struct - byte2int = operator.itemgetter(0) - indexbytes = operator.getitem - iterbytes = iter - import io - StringIO = io.StringIO - BytesIO = io.BytesIO - _assertCountEqual = "assertCountEqual" - if sys.version_info[1] <= 1: - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - else: - _assertRaisesRegex = "assertRaisesRegex" - _assertRegex = "assertRegex" -else: - def b(s): - return s - # Workaround for standalone backslash - - def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") - unichr = unichr - int2byte = chr - - def byte2int(bs): - return ord(bs[0]) - - def indexbytes(buf, i): - return ord(buf[i]) - iterbytes = functools.partial(itertools.imap, ord) - import StringIO - StringIO = BytesIO = StringIO.StringIO - _assertCountEqual = "assertItemsEqual" - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -def assertCountEqual(self, *args, **kwargs): - return getattr(self, _assertCountEqual)(*args, **kwargs) - - -def assertRaisesRegex(self, *args, **kwargs): - return getattr(self, _assertRaisesRegex)(*args, **kwargs) - - -def assertRegex(self, *args, **kwargs): - return getattr(self, _assertRegex)(*args, **kwargs) - - -if PY3: - exec_ = getattr(moves.builtins, "exec") - - def reraise(tp, value, tb=None): - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - raise tp, value, tb -""") - - -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - if from_value is None: - raise value - raise value from from_value -""") -elif sys.version_info[:2] > (3, 2): - exec_("""def raise_from(value, from_value): - raise value from from_value -""") -else: - def raise_from(value, from_value): - raise value - - -print_ = getattr(moves.builtins, "print", None) -if print_ is None: - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(fp.encoding, errors) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) -if sys.version_info[:2] < (3, 3): - _print = print_ - - def print_(*args, **kwargs): - fp = kwargs.get("file", sys.stdout) - flush = kwargs.pop("flush", False) - _print(*args, **kwargs) - if flush and fp is not None: - fp.flush() - -_add_doc(reraise, """Reraise an exception.""") - -if sys.version_info[0:2] < (3, 4): - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f - return wrapper -else: - wraps = functools.wraps - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(meta): - - def __new__(cls, name, this_bases, d): - return meta(name, bases, d) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -def python_2_unicode_compatible(klass): - """ - A decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - """ - if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass - - -# Complete the moves implementation. -# This code is at the end of this module to speed up module loading. -# Turn this module into a package. -__path__ = [] # required for PEP 302 and PEP 451 -__package__ = __name__ # see PEP 366 @ReservedAssignment -if globals().get("__spec__") is not None: - __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable -# Remove other six meta path importers, since they cause problems. This can -# happen if six is removed from sys.modules and then reloaded. (Setuptools does -# this for some reason.) -if sys.meta_path: - for i, importer in enumerate(sys.meta_path): - # Here's some real nastiness: Another "instance" of the six module might - # be floating around. Therefore, we can't use isinstance() to check for - # the six meta path importer, since the other six instance will have - # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): - del sys.meta_path[i] - break - del i, importer -# Finally, add the importer to the meta path import hook. -sys.meta_path.append(_importer) diff --git a/lib/urllib3/packages/ssl_match_hostname/__init__.py b/lib/urllib3/packages/ssl_match_hostname/__init__.py deleted file mode 100644 index d6594eb..0000000 --- a/lib/urllib3/packages/ssl_match_hostname/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -import sys - -try: - # Our match_hostname function is the same as 3.5's, so we only want to - # import the match_hostname function if it's at least that good. - if sys.version_info < (3, 5): - raise ImportError("Fallback to vendored code") - - from ssl import CertificateError, match_hostname -except ImportError: - try: - # Backport of the function from a pypi module - from backports.ssl_match_hostname import CertificateError, match_hostname - except ImportError: - # Our vendored copy - from ._implementation import CertificateError, match_hostname - -# Not needed, but documenting what we provide. -__all__ = ('CertificateError', 'match_hostname') diff --git a/lib/urllib3/packages/ssl_match_hostname/_implementation.py b/lib/urllib3/packages/ssl_match_hostname/_implementation.py deleted file mode 100644 index d6e66c0..0000000 --- a/lib/urllib3/packages/ssl_match_hostname/_implementation.py +++ /dev/null @@ -1,156 +0,0 @@ -"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" - -# Note: This file is under the PSF license as the code comes from the python -# stdlib. http://docs.python.org/3/license.html - -import re -import sys - -# ipaddress has been backported to 2.6+ in pypi. If it is installed on the -# system, use it to handle IPAddress ServerAltnames (this was added in -# python-3.5) otherwise only do DNS matching. This allows -# backports.ssl_match_hostname to continue to be used in Python 2.7. -try: - import ipaddress -except ImportError: - ipaddress = None - -__version__ = '3.5.0.1' - - -class CertificateError(ValueError): - pass - - -def _dnsname_match(dn, hostname, max_wildcards=1): - """Matching according to RFC 6125, section 6.4.3 - - http://tools.ietf.org/html/rfc6125#section-6.4.3 - """ - pats = [] - if not dn: - return False - - # Ported from python3-syntax: - # leftmost, *remainder = dn.split(r'.') - parts = dn.split(r'.') - leftmost = parts[0] - remainder = parts[1:] - - wildcards = leftmost.count('*') - if wildcards > max_wildcards: - # Issue #17980: avoid denials of service by refusing more - # than one wildcard per fragment. A survey of established - # policy among SSL implementations showed it to be a - # reasonable choice. - raise CertificateError( - "too many wildcards in certificate DNS name: " + repr(dn)) - - # speed up common case w/o wildcards - if not wildcards: - return dn.lower() == hostname.lower() - - # RFC 6125, section 6.4.3, subitem 1. - # The client SHOULD NOT attempt to match a presented identifier in which - # the wildcard character comprises a label other than the left-most label. - if leftmost == '*': - # When '*' is a fragment by itself, it matches a non-empty dotless - # fragment. - pats.append('[^.]+') - elif leftmost.startswith('xn--') or hostname.startswith('xn--'): - # RFC 6125, section 6.4.3, subitem 3. - # The client SHOULD NOT attempt to match a presented identifier - # where the wildcard character is embedded within an A-label or - # U-label of an internationalized domain name. - pats.append(re.escape(leftmost)) - else: - # Otherwise, '*' matches any dotless string, e.g. www* - pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) - - # add the remaining fragments, ignore any wildcards - for frag in remainder: - pats.append(re.escape(frag)) - - pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) - return pat.match(hostname) - - -def _to_unicode(obj): - if isinstance(obj, str) and sys.version_info < (3,): - obj = unicode(obj, encoding='ascii', errors='strict') - return obj - -def _ipaddress_match(ipname, host_ip): - """Exact matching of IP addresses. - - RFC 6125 explicitly doesn't define an algorithm for this - (section 1.7.2 - "Out of Scope"). - """ - # OpenSSL may add a trailing newline to a subjectAltName's IP address - # Divergence from upstream: ipaddress can't handle byte str - ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) - return ip == host_ip - - -def match_hostname(cert, hostname): - """Verify that *cert* (in decoded format as returned by - SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 - rules are followed, but IP addresses are not accepted for *hostname*. - - CertificateError is raised on failure. On success, the function - returns nothing. - """ - if not cert: - raise ValueError("empty or no certificate, match_hostname needs a " - "SSL socket or SSL context with either " - "CERT_OPTIONAL or CERT_REQUIRED") - try: - # Divergence from upstream: ipaddress can't handle byte str - host_ip = ipaddress.ip_address(_to_unicode(hostname)) - except ValueError: - # Not an IP address (common case) - host_ip = None - except UnicodeError: - # Divergence from upstream: Have to deal with ipaddress not taking - # byte strings. addresses should be all ascii, so we consider it not - # an ipaddress in this case - host_ip = None - except AttributeError: - # Divergence from upstream: Make ipaddress library optional - if ipaddress is None: - host_ip = None - else: - raise - dnsnames = [] - san = cert.get('subjectAltName', ()) - for key, value in san: - if key == 'DNS': - if host_ip is None and _dnsname_match(value, hostname): - return - dnsnames.append(value) - elif key == 'IP Address': - if host_ip is not None and _ipaddress_match(value, host_ip): - return - dnsnames.append(value) - if not dnsnames: - # The subject is only checked when there is no dNSName entry - # in subjectAltName - for sub in cert.get('subject', ()): - for key, value in sub: - # XXX according to RFC 2818, the most specific Common Name - # must be used. - if key == 'commonName': - if _dnsname_match(value, hostname): - return - dnsnames.append(value) - if len(dnsnames) > 1: - raise CertificateError("hostname %r " - "doesn't match either of %s" - % (hostname, ', '.join(map(repr, dnsnames)))) - elif len(dnsnames) == 1: - raise CertificateError("hostname %r " - "doesn't match %r" - % (hostname, dnsnames[0])) - else: - raise CertificateError("no appropriate commonName or " - "subjectAltName fields were found") diff --git a/lib/urllib3/poolmanager.py b/lib/urllib3/poolmanager.py deleted file mode 100644 index fe5491c..0000000 --- a/lib/urllib3/poolmanager.py +++ /dev/null @@ -1,450 +0,0 @@ -from __future__ import absolute_import -import collections -import functools -import logging - -from ._collections import RecentlyUsedContainer -from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool -from .connectionpool import port_by_scheme -from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown -from .packages.six.moves.urllib.parse import urljoin -from .request import RequestMethods -from .util.url import parse_url -from .util.retry import Retry - - -__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url'] - - -log = logging.getLogger(__name__) - -SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', - 'ssl_version', 'ca_cert_dir', 'ssl_context') - -# All known keyword arguments that could be provided to the pool manager, its -# pools, or the underlying connections. This is used to construct a pool key. -_key_fields = ( - 'key_scheme', # str - 'key_host', # str - 'key_port', # int - 'key_timeout', # int or float or Timeout - 'key_retries', # int or Retry - 'key_strict', # bool - 'key_block', # bool - 'key_source_address', # str - 'key_key_file', # str - 'key_cert_file', # str - 'key_cert_reqs', # str - 'key_ca_certs', # str - 'key_ssl_version', # str - 'key_ca_cert_dir', # str - 'key_ssl_context', # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext - 'key_maxsize', # int - 'key_headers', # dict - 'key__proxy', # parsed proxy url - 'key__proxy_headers', # dict - 'key_socket_options', # list of (level (int), optname (int), value (int or str)) tuples - 'key__socks_options', # dict - 'key_assert_hostname', # bool or string - 'key_assert_fingerprint', # str - 'key_server_hostname', #str -) - -#: The namedtuple class used to construct keys for the connection pool. -#: All custom key schemes should include the fields in this key at a minimum. -PoolKey = collections.namedtuple('PoolKey', _key_fields) - - -def _default_key_normalizer(key_class, request_context): - """ - Create a pool key out of a request context dictionary. - - According to RFC 3986, both the scheme and host are case-insensitive. - Therefore, this function normalizes both before constructing the pool - key for an HTTPS request. If you wish to change this behaviour, provide - alternate callables to ``key_fn_by_scheme``. - - :param key_class: - The class to use when constructing the key. This should be a namedtuple - with the ``scheme`` and ``host`` keys at a minimum. - :type key_class: namedtuple - :param request_context: - A dictionary-like object that contain the context for a request. - :type request_context: dict - - :return: A namedtuple that can be used as a connection pool key. - :rtype: PoolKey - """ - # Since we mutate the dictionary, make a copy first - context = request_context.copy() - context['scheme'] = context['scheme'].lower() - context['host'] = context['host'].lower() - - # These are both dictionaries and need to be transformed into frozensets - for key in ('headers', '_proxy_headers', '_socks_options'): - if key in context and context[key] is not None: - context[key] = frozenset(context[key].items()) - - # The socket_options key may be a list and needs to be transformed into a - # tuple. - socket_opts = context.get('socket_options') - if socket_opts is not None: - context['socket_options'] = tuple(socket_opts) - - # Map the kwargs to the names in the namedtuple - this is necessary since - # namedtuples can't have fields starting with '_'. - for key in list(context.keys()): - context['key_' + key] = context.pop(key) - - # Default to ``None`` for keys missing from the context - for field in key_class._fields: - if field not in context: - context[field] = None - - return key_class(**context) - - -#: A dictionary that maps a scheme to a callable that creates a pool key. -#: This can be used to alter the way pool keys are constructed, if desired. -#: Each PoolManager makes a copy of this dictionary so they can be configured -#: globally here, or individually on the instance. -key_fn_by_scheme = { - 'http': functools.partial(_default_key_normalizer, PoolKey), - 'https': functools.partial(_default_key_normalizer, PoolKey), -} - -pool_classes_by_scheme = { - 'http': HTTPConnectionPool, - 'https': HTTPSConnectionPool, -} - - -class PoolManager(RequestMethods): - """ - Allows for arbitrary requests while transparently keeping track of - necessary connection pools for you. - - :param num_pools: - Number of connection pools to cache before discarding the least - recently used pool. - - :param headers: - Headers to include with all requests, unless other headers are given - explicitly. - - :param \\**connection_pool_kw: - Additional parameters are used to create fresh - :class:`urllib3.connectionpool.ConnectionPool` instances. - - Example:: - - >>> manager = PoolManager(num_pools=2) - >>> r = manager.request('GET', 'http://google.com/') - >>> r = manager.request('GET', 'http://google.com/mail') - >>> r = manager.request('GET', 'http://yahoo.com/') - >>> len(manager.pools) - 2 - - """ - - proxy = None - - def __init__(self, num_pools=10, headers=None, **connection_pool_kw): - RequestMethods.__init__(self, headers) - self.connection_pool_kw = connection_pool_kw - self.pools = RecentlyUsedContainer(num_pools, - dispose_func=lambda p: p.close()) - - # Locally set the pool classes and keys so other PoolManagers can - # override them. - self.pool_classes_by_scheme = pool_classes_by_scheme - self.key_fn_by_scheme = key_fn_by_scheme.copy() - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - self.clear() - # Return False to re-raise any potential exceptions - return False - - def _new_pool(self, scheme, host, port, request_context=None): - """ - Create a new :class:`ConnectionPool` based on host, port, scheme, and - any additional pool keyword arguments. - - If ``request_context`` is provided, it is provided as keyword arguments - to the pool class used. This method is used to actually create the - connection pools handed out by :meth:`connection_from_url` and - companion methods. It is intended to be overridden for customization. - """ - pool_cls = self.pool_classes_by_scheme[scheme] - if request_context is None: - request_context = self.connection_pool_kw.copy() - - # Although the context has everything necessary to create the pool, - # this function has historically only used the scheme, host, and port - # in the positional args. When an API change is acceptable these can - # be removed. - for key in ('scheme', 'host', 'port'): - request_context.pop(key, None) - - if scheme == 'http': - for kw in SSL_KEYWORDS: - request_context.pop(kw, None) - - return pool_cls(host, port, **request_context) - - def clear(self): - """ - Empty our store of pools and direct them all to close. - - This will not affect in-flight connections, but they will not be - re-used after completion. - """ - self.pools.clear() - - def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): - """ - Get a :class:`ConnectionPool` based on the host, port, and scheme. - - If ``port`` isn't given, it will be derived from the ``scheme`` using - ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is - provided, it is merged with the instance's ``connection_pool_kw`` - variable and used to create the new connection pool, if one is - needed. - """ - - if not host: - raise LocationValueError("No host specified.") - - request_context = self._merge_pool_kwargs(pool_kwargs) - request_context['scheme'] = scheme or 'http' - if not port: - port = port_by_scheme.get(request_context['scheme'].lower(), 80) - request_context['port'] = port - request_context['host'] = host - - return self.connection_from_context(request_context) - - def connection_from_context(self, request_context): - """ - Get a :class:`ConnectionPool` based on the request context. - - ``request_context`` must at least contain the ``scheme`` key and its - value must be a key in ``key_fn_by_scheme`` instance variable. - """ - scheme = request_context['scheme'].lower() - pool_key_constructor = self.key_fn_by_scheme[scheme] - pool_key = pool_key_constructor(request_context) - - return self.connection_from_pool_key(pool_key, request_context=request_context) - - def connection_from_pool_key(self, pool_key, request_context=None): - """ - Get a :class:`ConnectionPool` based on the provided pool key. - - ``pool_key`` should be a namedtuple that only contains immutable - objects. At a minimum it must have the ``scheme``, ``host``, and - ``port`` fields. - """ - with self.pools.lock: - # If the scheme, host, or port doesn't match existing open - # connections, open a new ConnectionPool. - pool = self.pools.get(pool_key) - if pool: - return pool - - # Make a fresh ConnectionPool of the desired type - scheme = request_context['scheme'] - host = request_context['host'] - port = request_context['port'] - pool = self._new_pool(scheme, host, port, request_context=request_context) - self.pools[pool_key] = pool - - return pool - - def connection_from_url(self, url, pool_kwargs=None): - """ - Similar to :func:`urllib3.connectionpool.connection_from_url`. - - If ``pool_kwargs`` is not provided and a new pool needs to be - constructed, ``self.connection_pool_kw`` is used to initialize - the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` - is provided, it is used instead. Note that if a new pool does not - need to be created for the request, the provided ``pool_kwargs`` are - not used. - """ - u = parse_url(url) - return self.connection_from_host(u.host, port=u.port, scheme=u.scheme, - pool_kwargs=pool_kwargs) - - def _merge_pool_kwargs(self, override): - """ - Merge a dictionary of override values for self.connection_pool_kw. - - This does not modify self.connection_pool_kw and returns a new dict. - Any keys in the override dictionary with a value of ``None`` are - removed from the merged dictionary. - """ - base_pool_kwargs = self.connection_pool_kw.copy() - if override: - for key, value in override.items(): - if value is None: - try: - del base_pool_kwargs[key] - except KeyError: - pass - else: - base_pool_kwargs[key] = value - return base_pool_kwargs - - def urlopen(self, method, url, redirect=True, **kw): - """ - Same as :meth:`urllib3.connectionpool.HTTPConnectionPool.urlopen` - with custom cross-host redirect logic and only sends the request-uri - portion of the ``url``. - - The given ``url`` parameter must be absolute, such that an appropriate - :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. - """ - u = parse_url(url) - conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) - - kw['assert_same_host'] = False - kw['redirect'] = False - - if 'headers' not in kw: - kw['headers'] = self.headers.copy() - - if self.proxy is not None and u.scheme == "http": - response = conn.urlopen(method, url, **kw) - else: - response = conn.urlopen(method, u.request_uri, **kw) - - redirect_location = redirect and response.get_redirect_location() - if not redirect_location: - return response - - # Support relative URLs for redirecting. - redirect_location = urljoin(url, redirect_location) - - # RFC 7231, Section 6.4.4 - if response.status == 303: - method = 'GET' - - retries = kw.get('retries') - if not isinstance(retries, Retry): - retries = Retry.from_int(retries, redirect=redirect) - - # Strip headers marked as unsafe to forward to the redirected location. - # Check remove_headers_on_redirect to avoid a potential network call within - # conn.is_same_host() which may use socket.gethostbyname() in the future. - if (retries.remove_headers_on_redirect - and not conn.is_same_host(redirect_location)): - for header in retries.remove_headers_on_redirect: - kw['headers'].pop(header, None) - - try: - retries = retries.increment(method, url, response=response, _pool=conn) - except MaxRetryError: - if retries.raise_on_redirect: - raise - return response - - kw['retries'] = retries - kw['redirect'] = redirect - - log.info("Redirecting %s -> %s", url, redirect_location) - return self.urlopen(method, redirect_location, **kw) - - -class ProxyManager(PoolManager): - """ - Behaves just like :class:`PoolManager`, but sends all requests through - the defined proxy, using the CONNECT method for HTTPS URLs. - - :param proxy_url: - The URL of the proxy to be used. - - :param proxy_headers: - A dictionary containing headers that will be sent to the proxy. In case - of HTTP they are being sent with each request, while in the - HTTPS/CONNECT case they are sent only once. Could be used for proxy - authentication. - - Example: - >>> proxy = urllib3.ProxyManager('http://localhost:3128/') - >>> r1 = proxy.request('GET', 'http://google.com/') - >>> r2 = proxy.request('GET', 'http://httpbin.org/') - >>> len(proxy.pools) - 1 - >>> r3 = proxy.request('GET', 'https://httpbin.org/') - >>> r4 = proxy.request('GET', 'https://twitter.com/') - >>> len(proxy.pools) - 3 - - """ - - def __init__(self, proxy_url, num_pools=10, headers=None, - proxy_headers=None, **connection_pool_kw): - - if isinstance(proxy_url, HTTPConnectionPool): - proxy_url = '%s://%s:%i' % (proxy_url.scheme, proxy_url.host, - proxy_url.port) - proxy = parse_url(proxy_url) - if not proxy.port: - port = port_by_scheme.get(proxy.scheme, 80) - proxy = proxy._replace(port=port) - - if proxy.scheme not in ("http", "https"): - raise ProxySchemeUnknown(proxy.scheme) - - self.proxy = proxy - self.proxy_headers = proxy_headers or {} - - connection_pool_kw['_proxy'] = self.proxy - connection_pool_kw['_proxy_headers'] = self.proxy_headers - - super(ProxyManager, self).__init__( - num_pools, headers, **connection_pool_kw) - - def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None): - if scheme == "https": - return super(ProxyManager, self).connection_from_host( - host, port, scheme, pool_kwargs=pool_kwargs) - - return super(ProxyManager, self).connection_from_host( - self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs) - - def _set_proxy_headers(self, url, headers=None): - """ - Sets headers needed by proxies: specifically, the Accept and Host - headers. Only sets headers not provided by the user. - """ - headers_ = {'Accept': '*/*'} - - netloc = parse_url(url).netloc - if netloc: - headers_['Host'] = netloc - - if headers: - headers_.update(headers) - return headers_ - - def urlopen(self, method, url, redirect=True, **kw): - "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." - u = parse_url(url) - - if u.scheme == "http": - # For proxied HTTPS requests, httplib sets the necessary headers - # on the CONNECT to the proxy. For HTTP, we'll definitely - # need to set 'Host' at the very least. - headers = kw.get('headers', self.headers) - kw['headers'] = self._set_proxy_headers(url, headers) - - return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) - - -def proxy_from_url(url, **kw): - return ProxyManager(proxy_url=url, **kw) diff --git a/lib/urllib3/request.py b/lib/urllib3/request.py deleted file mode 100644 index 8f2f44b..0000000 --- a/lib/urllib3/request.py +++ /dev/null @@ -1,150 +0,0 @@ -from __future__ import absolute_import - -from .filepost import encode_multipart_formdata -from .packages.six.moves.urllib.parse import urlencode - - -__all__ = ['RequestMethods'] - - -class RequestMethods(object): - """ - Convenience mixin for classes who implement a :meth:`urlopen` method, such - as :class:`~urllib3.connectionpool.HTTPConnectionPool` and - :class:`~urllib3.poolmanager.PoolManager`. - - Provides behavior for making common types of HTTP request methods and - decides which type of request field encoding to use. - - Specifically, - - :meth:`.request_encode_url` is for sending requests whose fields are - encoded in the URL (such as GET, HEAD, DELETE). - - :meth:`.request_encode_body` is for sending requests whose fields are - encoded in the *body* of the request using multipart or www-form-urlencoded - (such as for POST, PUT, PATCH). - - :meth:`.request` is for making any kind of request, it will look up the - appropriate encoding format and use one of the above two methods to make - the request. - - Initializer parameters: - - :param headers: - Headers to include with all requests, unless other headers are given - explicitly. - """ - - _encode_url_methods = {'DELETE', 'GET', 'HEAD', 'OPTIONS'} - - def __init__(self, headers=None): - self.headers = headers or {} - - def urlopen(self, method, url, body=None, headers=None, - encode_multipart=True, multipart_boundary=None, - **kw): # Abstract - raise NotImplementedError("Classes extending RequestMethods must implement " - "their own ``urlopen`` method.") - - def request(self, method, url, fields=None, headers=None, **urlopen_kw): - """ - Make a request using :meth:`urlopen` with the appropriate encoding of - ``fields`` based on the ``method`` used. - - This is a convenience method that requires the least amount of manual - effort. It can be used in most situations, while still having the - option to drop down to more specific methods when necessary, such as - :meth:`request_encode_url`, :meth:`request_encode_body`, - or even the lowest level :meth:`urlopen`. - """ - method = method.upper() - - urlopen_kw['request_url'] = url - - if method in self._encode_url_methods: - return self.request_encode_url(method, url, fields=fields, - headers=headers, - **urlopen_kw) - else: - return self.request_encode_body(method, url, fields=fields, - headers=headers, - **urlopen_kw) - - def request_encode_url(self, method, url, fields=None, headers=None, - **urlopen_kw): - """ - Make a request using :meth:`urlopen` with the ``fields`` encoded in - the url. This is useful for request methods like GET, HEAD, DELETE, etc. - """ - if headers is None: - headers = self.headers - - extra_kw = {'headers': headers} - extra_kw.update(urlopen_kw) - - if fields: - url += '?' + urlencode(fields) - - return self.urlopen(method, url, **extra_kw) - - def request_encode_body(self, method, url, fields=None, headers=None, - encode_multipart=True, multipart_boundary=None, - **urlopen_kw): - """ - Make a request using :meth:`urlopen` with the ``fields`` encoded in - the body. This is useful for request methods like POST, PUT, PATCH, etc. - - When ``encode_multipart=True`` (default), then - :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode - the payload with the appropriate content type. Otherwise - :meth:`urllib.urlencode` is used with the - 'application/x-www-form-urlencoded' content type. - - Multipart encoding must be used when posting files, and it's reasonably - safe to use it in other times too. However, it may break request - signing, such as with OAuth. - - Supports an optional ``fields`` parameter of key/value strings AND - key/filetuple. A filetuple is a (filename, data, MIME type) tuple where - the MIME type is optional. For example:: - - fields = { - 'foo': 'bar', - 'fakefile': ('foofile.txt', 'contents of foofile'), - 'realfile': ('barfile.txt', open('realfile').read()), - 'typedfile': ('bazfile.bin', open('bazfile').read(), - 'image/jpeg'), - 'nonamefile': 'contents of nonamefile field', - } - - When uploading a file, providing a filename (the first parameter of the - tuple) is optional but recommended to best mimic behavior of browsers. - - Note that if ``headers`` are supplied, the 'Content-Type' header will - be overwritten because it depends on the dynamic random boundary string - which is used to compose the body of the request. The random boundary - string can be explicitly set with the ``multipart_boundary`` parameter. - """ - if headers is None: - headers = self.headers - - extra_kw = {'headers': {}} - - if fields: - if 'body' in urlopen_kw: - raise TypeError( - "request got values for both 'fields' and 'body', can only specify one.") - - if encode_multipart: - body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) - else: - body, content_type = urlencode(fields), 'application/x-www-form-urlencoded' - - extra_kw['body'] = body - extra_kw['headers'] = {'Content-Type': content_type} - - extra_kw['headers'].update(headers) - extra_kw.update(urlopen_kw) - - return self.urlopen(method, url, **extra_kw) diff --git a/lib/urllib3/response.py b/lib/urllib3/response.py deleted file mode 100644 index c112690..0000000 --- a/lib/urllib3/response.py +++ /dev/null @@ -1,705 +0,0 @@ -from __future__ import absolute_import -from contextlib import contextmanager -import zlib -import io -import logging -from socket import timeout as SocketTimeout -from socket import error as SocketError - -from ._collections import HTTPHeaderDict -from .exceptions import ( - BodyNotHttplibCompatible, ProtocolError, DecodeError, ReadTimeoutError, - ResponseNotChunked, IncompleteRead, InvalidHeader -) -from .packages.six import string_types as basestring, PY3 -from .packages.six.moves import http_client as httplib -from .connection import HTTPException, BaseSSLError -from .util.response import is_fp_closed, is_response_to_head - -log = logging.getLogger(__name__) - - -class DeflateDecoder(object): - - def __init__(self): - self._first_try = True - self._data = b'' - self._obj = zlib.decompressobj() - - def __getattr__(self, name): - return getattr(self._obj, name) - - def decompress(self, data): - if not data: - return data - - if not self._first_try: - return self._obj.decompress(data) - - self._data += data - try: - decompressed = self._obj.decompress(data) - if decompressed: - self._first_try = False - self._data = None - return decompressed - except zlib.error: - self._first_try = False - self._obj = zlib.decompressobj(-zlib.MAX_WBITS) - try: - return self.decompress(self._data) - finally: - self._data = None - - -class GzipDecoderState(object): - - FIRST_MEMBER = 0 - OTHER_MEMBERS = 1 - SWALLOW_DATA = 2 - - -class GzipDecoder(object): - - def __init__(self): - self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) - self._state = GzipDecoderState.FIRST_MEMBER - - def __getattr__(self, name): - return getattr(self._obj, name) - - def decompress(self, data): - ret = bytearray() - if self._state == GzipDecoderState.SWALLOW_DATA or not data: - return bytes(ret) - while True: - try: - ret += self._obj.decompress(data) - except zlib.error: - previous_state = self._state - # Ignore data after the first error - self._state = GzipDecoderState.SWALLOW_DATA - if previous_state == GzipDecoderState.OTHER_MEMBERS: - # Allow trailing garbage acceptable in other gzip clients - return bytes(ret) - raise - data = self._obj.unused_data - if not data: - return bytes(ret) - self._state = GzipDecoderState.OTHER_MEMBERS - self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) - - -class MultiDecoder(object): - """ - From RFC7231: - If one or more encodings have been applied to a representation, the - sender that applied the encodings MUST generate a Content-Encoding - header field that lists the content codings in the order in which - they were applied. - """ - - def __init__(self, modes): - self._decoders = [_get_decoder(m.strip()) for m in modes.split(',')] - - def flush(self): - return self._decoders[0].flush() - - def decompress(self, data): - for d in reversed(self._decoders): - data = d.decompress(data) - return data - - -def _get_decoder(mode): - if ',' in mode: - return MultiDecoder(mode) - - if mode == 'gzip': - return GzipDecoder() - - return DeflateDecoder() - - -class HTTPResponse(io.IOBase): - """ - HTTP Response container. - - Backwards-compatible to httplib's HTTPResponse but the response ``body`` is - loaded and decoded on-demand when the ``data`` property is accessed. This - class is also compatible with the Python standard library's :mod:`io` - module, and can hence be treated as a readable object in the context of that - framework. - - Extra parameters for behaviour not present in httplib.HTTPResponse: - - :param preload_content: - If True, the response's body will be preloaded during construction. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - - :param original_response: - When this HTTPResponse wrapper is generated from an httplib.HTTPResponse - object, it's convenient to include the original for debug purposes. It's - otherwise unused. - - :param retries: - The retries contains the last :class:`~urllib3.util.retry.Retry` that - was used during the request. - - :param enforce_content_length: - Enforce content length checking. Body returned by server must match - value of Content-Length header, if present. Otherwise, raise error. - """ - - CONTENT_DECODERS = ['gzip', 'deflate'] - REDIRECT_STATUSES = [301, 302, 303, 307, 308] - - def __init__(self, body='', headers=None, status=0, version=0, reason=None, - strict=0, preload_content=True, decode_content=True, - original_response=None, pool=None, connection=None, msg=None, - retries=None, enforce_content_length=False, - request_method=None, request_url=None): - - if isinstance(headers, HTTPHeaderDict): - self.headers = headers - else: - self.headers = HTTPHeaderDict(headers) - self.status = status - self.version = version - self.reason = reason - self.strict = strict - self.decode_content = decode_content - self.retries = retries - self.enforce_content_length = enforce_content_length - - self._decoder = None - self._body = None - self._fp = None - self._original_response = original_response - self._fp_bytes_read = 0 - self.msg = msg - self._request_url = request_url - - if body and isinstance(body, (basestring, bytes)): - self._body = body - - self._pool = pool - self._connection = connection - - if hasattr(body, 'read'): - self._fp = body - - # Are we using the chunked-style of transfer encoding? - self.chunked = False - self.chunk_left = None - tr_enc = self.headers.get('transfer-encoding', '').lower() - # Don't incur the penalty of creating a list and then discarding it - encodings = (enc.strip() for enc in tr_enc.split(",")) - if "chunked" in encodings: - self.chunked = True - - # Determine length of response - self.length_remaining = self._init_length(request_method) - - # If requested, preload the body. - if preload_content and not self._body: - self._body = self.read(decode_content=decode_content) - - def get_redirect_location(self): - """ - Should we redirect and where to? - - :returns: Truthy redirect location string if we got a redirect status - code and valid location. ``None`` if redirect status and no - location. ``False`` if not a redirect status code. - """ - if self.status in self.REDIRECT_STATUSES: - return self.headers.get('location') - - return False - - def release_conn(self): - if not self._pool or not self._connection: - return - - self._pool._put_conn(self._connection) - self._connection = None - - @property - def data(self): - # For backwords-compat with earlier urllib3 0.4 and earlier. - if self._body: - return self._body - - if self._fp: - return self.read(cache_content=True) - - @property - def connection(self): - return self._connection - - def isclosed(self): - return is_fp_closed(self._fp) - - def tell(self): - """ - Obtain the number of bytes pulled over the wire so far. May differ from - the amount of content returned by :meth:``HTTPResponse.read`` if bytes - are encoded on the wire (e.g, compressed). - """ - return self._fp_bytes_read - - def _init_length(self, request_method): - """ - Set initial length value for Response content if available. - """ - length = self.headers.get('content-length') - - if length is not None: - if self.chunked: - # This Response will fail with an IncompleteRead if it can't be - # received as chunked. This method falls back to attempt reading - # the response before raising an exception. - log.warning("Received response with both Content-Length and " - "Transfer-Encoding set. This is expressly forbidden " - "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " - "attempting to process response as Transfer-Encoding: " - "chunked.") - return None - - try: - # RFC 7230 section 3.3.2 specifies multiple content lengths can - # be sent in a single Content-Length header - # (e.g. Content-Length: 42, 42). This line ensures the values - # are all valid ints and that as long as the `set` length is 1, - # all values are the same. Otherwise, the header is invalid. - lengths = set([int(val) for val in length.split(',')]) - if len(lengths) > 1: - raise InvalidHeader("Content-Length contained multiple " - "unmatching values (%s)" % length) - length = lengths.pop() - except ValueError: - length = None - else: - if length < 0: - length = None - - # Convert status to int for comparison - # In some cases, httplib returns a status of "_UNKNOWN" - try: - status = int(self.status) - except ValueError: - status = 0 - - # Check for responses that shouldn't include a body - if status in (204, 304) or 100 <= status < 200 or request_method == 'HEAD': - length = 0 - - return length - - def _init_decoder(self): - """ - Set-up the _decoder attribute if necessary. - """ - # Note: content-encoding value should be case-insensitive, per RFC 7230 - # Section 3.2 - content_encoding = self.headers.get('content-encoding', '').lower() - if self._decoder is None: - if content_encoding in self.CONTENT_DECODERS: - self._decoder = _get_decoder(content_encoding) - elif ',' in content_encoding: - encodings = [e.strip() for e in content_encoding.split(',') if e.strip() in self.CONTENT_DECODERS] - if len(encodings): - self._decoder = _get_decoder(content_encoding) - - def _decode(self, data, decode_content, flush_decoder): - """ - Decode the data passed in and potentially flush the decoder. - """ - try: - if decode_content and self._decoder: - data = self._decoder.decompress(data) - except (IOError, zlib.error) as e: - content_encoding = self.headers.get('content-encoding', '').lower() - raise DecodeError( - "Received response with content-encoding: %s, but " - "failed to decode it." % content_encoding, e) - - if flush_decoder and decode_content: - data += self._flush_decoder() - - return data - - def _flush_decoder(self): - """ - Flushes the decoder. Should only be called if the decoder is actually - being used. - """ - if self._decoder: - buf = self._decoder.decompress(b'') - return buf + self._decoder.flush() - - return b'' - - @contextmanager - def _error_catcher(self): - """ - Catch low-level python exceptions, instead re-raising urllib3 - variants, so that low-level exceptions are not leaked in the - high-level api. - - On exit, release the connection back to the pool. - """ - clean_exit = False - - try: - try: - yield - - except SocketTimeout: - # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but - # there is yet no clean way to get at it from this context. - raise ReadTimeoutError(self._pool, None, 'Read timed out.') - - except BaseSSLError as e: - # FIXME: Is there a better way to differentiate between SSLErrors? - if 'read operation timed out' not in str(e): # Defensive: - # This shouldn't happen but just in case we're missing an edge - # case, let's avoid swallowing SSL errors. - raise - - raise ReadTimeoutError(self._pool, None, 'Read timed out.') - - except (HTTPException, SocketError) as e: - # This includes IncompleteRead. - raise ProtocolError('Connection broken: %r' % e, e) - - # If no exception is thrown, we should avoid cleaning up - # unnecessarily. - clean_exit = True - finally: - # If we didn't terminate cleanly, we need to throw away our - # connection. - if not clean_exit: - # The response may not be closed but we're not going to use it - # anymore so close it now to ensure that the connection is - # released back to the pool. - if self._original_response: - self._original_response.close() - - # Closing the response may not actually be sufficient to close - # everything, so if we have a hold of the connection close that - # too. - if self._connection: - self._connection.close() - - # If we hold the original response but it's closed now, we should - # return the connection back to the pool. - if self._original_response and self._original_response.isclosed(): - self.release_conn() - - def read(self, amt=None, decode_content=None, cache_content=False): - """ - Similar to :meth:`httplib.HTTPResponse.read`, but with two additional - parameters: ``decode_content`` and ``cache_content``. - - :param amt: - How much of the content to read. If specified, caching is skipped - because it doesn't make sense to cache partial content as the full - response. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - - :param cache_content: - If True, will save the returned data such that the same result is - returned despite of the state of the underlying file object. This - is useful if you want the ``.data`` property to continue working - after having ``.read()`` the file object. (Overridden if ``amt`` is - set.) - """ - self._init_decoder() - if decode_content is None: - decode_content = self.decode_content - - if self._fp is None: - return - - flush_decoder = False - data = None - - with self._error_catcher(): - if amt is None: - # cStringIO doesn't like amt=None - data = self._fp.read() - flush_decoder = True - else: - cache_content = False - data = self._fp.read(amt) - if amt != 0 and not data: # Platform-specific: Buggy versions of Python. - # Close the connection when no data is returned - # - # This is redundant to what httplib/http.client _should_ - # already do. However, versions of python released before - # December 15, 2012 (http://bugs.python.org/issue16298) do - # not properly close the connection in all cases. There is - # no harm in redundantly calling close. - self._fp.close() - flush_decoder = True - if self.enforce_content_length and self.length_remaining not in (0, None): - # This is an edge case that httplib failed to cover due - # to concerns of backward compatibility. We're - # addressing it here to make sure IncompleteRead is - # raised during streaming, so all calls with incorrect - # Content-Length are caught. - raise IncompleteRead(self._fp_bytes_read, self.length_remaining) - - if data: - self._fp_bytes_read += len(data) - if self.length_remaining is not None: - self.length_remaining -= len(data) - - data = self._decode(data, decode_content, flush_decoder) - - if cache_content: - self._body = data - - return data - - def stream(self, amt=2**16, decode_content=None): - """ - A generator wrapper for the read() method. A call will block until - ``amt`` bytes have been read from the connection or until the - connection is closed. - - :param amt: - How much of the content to read. The generator will return up to - much data per iteration, but may return less. This is particularly - likely when using compressed data. However, the empty string will - never be returned. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - """ - if self.chunked and self.supports_chunked_reads(): - for line in self.read_chunked(amt, decode_content=decode_content): - yield line - else: - while not is_fp_closed(self._fp): - data = self.read(amt=amt, decode_content=decode_content) - - if data: - yield data - - @classmethod - def from_httplib(ResponseCls, r, **response_kw): - """ - Given an :class:`httplib.HTTPResponse` instance ``r``, return a - corresponding :class:`urllib3.response.HTTPResponse` object. - - Remaining parameters are passed to the HTTPResponse constructor, along - with ``original_response=r``. - """ - headers = r.msg - - if not isinstance(headers, HTTPHeaderDict): - if PY3: # Python 3 - headers = HTTPHeaderDict(headers.items()) - else: # Python 2 - headers = HTTPHeaderDict.from_httplib(headers) - - # HTTPResponse objects in Python 3 don't have a .strict attribute - strict = getattr(r, 'strict', 0) - resp = ResponseCls(body=r, - headers=headers, - status=r.status, - version=r.version, - reason=r.reason, - strict=strict, - original_response=r, - **response_kw) - return resp - - # Backwards-compatibility methods for httplib.HTTPResponse - def getheaders(self): - return self.headers - - def getheader(self, name, default=None): - return self.headers.get(name, default) - - # Backwards compatibility for http.cookiejar - def info(self): - return self.headers - - # Overrides from io.IOBase - def close(self): - if not self.closed: - self._fp.close() - - if self._connection: - self._connection.close() - - @property - def closed(self): - if self._fp is None: - return True - elif hasattr(self._fp, 'isclosed'): - return self._fp.isclosed() - elif hasattr(self._fp, 'closed'): - return self._fp.closed - else: - return True - - def fileno(self): - if self._fp is None: - raise IOError("HTTPResponse has no file to get a fileno from") - elif hasattr(self._fp, "fileno"): - return self._fp.fileno() - else: - raise IOError("The file-like object this HTTPResponse is wrapped " - "around has no file descriptor") - - def flush(self): - if self._fp is not None and hasattr(self._fp, 'flush'): - return self._fp.flush() - - def readable(self): - # This method is required for `io` module compatibility. - return True - - def readinto(self, b): - # This method is required for `io` module compatibility. - temp = self.read(len(b)) - if len(temp) == 0: - return 0 - else: - b[:len(temp)] = temp - return len(temp) - - def supports_chunked_reads(self): - """ - Checks if the underlying file-like object looks like a - httplib.HTTPResponse object. We do this by testing for the fp - attribute. If it is present we assume it returns raw chunks as - processed by read_chunked(). - """ - return hasattr(self._fp, 'fp') - - def _update_chunk_length(self): - # First, we'll figure out length of a chunk and then - # we'll try to read it from socket. - if self.chunk_left is not None: - return - line = self._fp.fp.readline() - line = line.split(b';', 1)[0] - try: - self.chunk_left = int(line, 16) - except ValueError: - # Invalid chunked protocol response, abort. - self.close() - raise httplib.IncompleteRead(line) - - def _handle_chunk(self, amt): - returned_chunk = None - if amt is None: - chunk = self._fp._safe_read(self.chunk_left) - returned_chunk = chunk - self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. - self.chunk_left = None - elif amt < self.chunk_left: - value = self._fp._safe_read(amt) - self.chunk_left = self.chunk_left - amt - returned_chunk = value - elif amt == self.chunk_left: - value = self._fp._safe_read(amt) - self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. - self.chunk_left = None - returned_chunk = value - else: # amt > self.chunk_left - returned_chunk = self._fp._safe_read(self.chunk_left) - self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. - self.chunk_left = None - return returned_chunk - - def read_chunked(self, amt=None, decode_content=None): - """ - Similar to :meth:`HTTPResponse.read`, but with an additional - parameter: ``decode_content``. - - :param amt: - How much of the content to read. If specified, caching is skipped - because it doesn't make sense to cache partial content as the full - response. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - """ - self._init_decoder() - # FIXME: Rewrite this method and make it a class with a better structured logic. - if not self.chunked: - raise ResponseNotChunked( - "Response is not chunked. " - "Header 'transfer-encoding: chunked' is missing.") - if not self.supports_chunked_reads(): - raise BodyNotHttplibCompatible( - "Body should be httplib.HTTPResponse like. " - "It should have have an fp attribute which returns raw chunks.") - - with self._error_catcher(): - # Don't bother reading the body of a HEAD request. - if self._original_response and is_response_to_head(self._original_response): - self._original_response.close() - return - - # If a response is already read and closed - # then return immediately. - if self._fp.fp is None: - return - - while True: - self._update_chunk_length() - if self.chunk_left == 0: - break - chunk = self._handle_chunk(amt) - decoded = self._decode(chunk, decode_content=decode_content, - flush_decoder=False) - if decoded: - yield decoded - - if decode_content: - # On CPython and PyPy, we should never need to flush the - # decoder. However, on Jython we *might* need to, so - # lets defensively do it anyway. - decoded = self._flush_decoder() - if decoded: # Platform-specific: Jython. - yield decoded - - # Chunk content ends with \r\n: discard it. - while True: - line = self._fp.fp.readline() - if not line: - # Some sites may not end with '\r\n'. - break - if line == b'\r\n': - break - - # We read everything; close the "file". - if self._original_response: - self._original_response.close() - - def geturl(self): - """ - Returns the URL that was the source of this response. - If the request that generated this response redirected, this method - will return the final redirect location. - """ - if self.retries is not None and len(self.retries.history): - return self.retries.history[-1].redirect_location - else: - return self._request_url diff --git a/lib/urllib3/util/__init__.py b/lib/urllib3/util/__init__.py deleted file mode 100644 index 2f2770b..0000000 --- a/lib/urllib3/util/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import absolute_import -# For backwards compatibility, provide imports that used to be here. -from .connection import is_connection_dropped -from .request import make_headers -from .response import is_fp_closed -from .ssl_ import ( - SSLContext, - HAS_SNI, - IS_PYOPENSSL, - IS_SECURETRANSPORT, - assert_fingerprint, - resolve_cert_reqs, - resolve_ssl_version, - ssl_wrap_socket, -) -from .timeout import ( - current_time, - Timeout, -) - -from .retry import Retry -from .url import ( - get_host, - parse_url, - split_first, - Url, -) -from .wait import ( - wait_for_read, - wait_for_write -) - -__all__ = ( - 'HAS_SNI', - 'IS_PYOPENSSL', - 'IS_SECURETRANSPORT', - 'SSLContext', - 'Retry', - 'Timeout', - 'Url', - 'assert_fingerprint', - 'current_time', - 'is_connection_dropped', - 'is_fp_closed', - 'get_host', - 'parse_url', - 'make_headers', - 'resolve_cert_reqs', - 'resolve_ssl_version', - 'split_first', - 'ssl_wrap_socket', - 'wait_for_read', - 'wait_for_write' -) diff --git a/lib/urllib3/util/connection.py b/lib/urllib3/util/connection.py deleted file mode 100644 index 5ad70b2..0000000 --- a/lib/urllib3/util/connection.py +++ /dev/null @@ -1,134 +0,0 @@ -from __future__ import absolute_import -import socket -from .wait import NoWayToWaitForSocketError, wait_for_read -from ..contrib import _appengine_environ - - -def is_connection_dropped(conn): # Platform-specific - """ - Returns True if the connection is dropped and should be closed. - - :param conn: - :class:`httplib.HTTPConnection` object. - - Note: For platforms like AppEngine, this will always return ``False`` to - let the platform handle connection recycling transparently for us. - """ - sock = getattr(conn, 'sock', False) - if sock is False: # Platform-specific: AppEngine - return False - if sock is None: # Connection already closed (such as by httplib). - return True - try: - # Returns True if readable, which here means it's been dropped - return wait_for_read(sock, timeout=0.0) - except NoWayToWaitForSocketError: # Platform-specific: AppEngine - return False - - -# This function is copied from socket.py in the Python 2.7 standard -# library test suite. Added to its signature is only `socket_options`. -# One additional modification is that we avoid binding to IPv6 servers -# discovered in DNS if the system doesn't have IPv6 functionality. -def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - source_address=None, socket_options=None): - """Connect to *address* and return the socket object. - - Convenience function. Connect to *address* (a 2-tuple ``(host, - port)``) and return the socket object. Passing the optional - *timeout* parameter will set the timeout on the socket instance - before attempting to connect. If no *timeout* is supplied, the - global default timeout setting returned by :func:`getdefaulttimeout` - is used. If *source_address* is set it must be a tuple of (host, port) - for the socket to bind as a source address before making the connection. - An host of '' or port 0 tells the OS to use the default. - """ - - host, port = address - if host.startswith('['): - host = host.strip('[]') - err = None - - # Using the value from allowed_gai_family() in the context of getaddrinfo lets - # us select whether to work with IPv4 DNS records, IPv6 records, or both. - # The original create_connection function always returns all records. - family = allowed_gai_family() - - for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - sock = None - try: - sock = socket.socket(af, socktype, proto) - - # If provided, set socket level options before connecting. - _set_socket_options(sock, socket_options) - - if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: - sock.settimeout(timeout) - if source_address: - sock.bind(source_address) - sock.connect(sa) - return sock - - except socket.error as e: - err = e - if sock is not None: - sock.close() - sock = None - - if err is not None: - raise err - - raise socket.error("getaddrinfo returns an empty list") - - -def _set_socket_options(sock, options): - if options is None: - return - - for opt in options: - sock.setsockopt(*opt) - - -def allowed_gai_family(): - """This function is designed to work in the context of - getaddrinfo, where family=socket.AF_UNSPEC is the default and - will perform a DNS search for both IPv6 and IPv4 records.""" - - family = socket.AF_INET - if HAS_IPV6: - family = socket.AF_UNSPEC - return family - - -def _has_ipv6(host): - """ Returns True if the system can bind an IPv6 address. """ - sock = None - has_ipv6 = False - - # App Engine doesn't support IPV6 sockets and actually has a quota on the - # number of sockets that can be used, so just early out here instead of - # creating a socket needlessly. - # See https://github.com/urllib3/urllib3/issues/1446 - if _appengine_environ.is_appengine_sandbox(): - return False - - if socket.has_ipv6: - # has_ipv6 returns true if cPython was compiled with IPv6 support. - # It does not tell us if the system has IPv6 support enabled. To - # determine that we must bind to an IPv6 address. - # https://github.com/shazow/urllib3/pull/611 - # https://bugs.python.org/issue658327 - try: - sock = socket.socket(socket.AF_INET6) - sock.bind((host, 0)) - has_ipv6 = True - except Exception: - pass - - if sock: - sock.close() - return has_ipv6 - - -HAS_IPV6 = _has_ipv6('::1') diff --git a/lib/urllib3/util/queue.py b/lib/urllib3/util/queue.py deleted file mode 100644 index d3d379a..0000000 --- a/lib/urllib3/util/queue.py +++ /dev/null @@ -1,21 +0,0 @@ -import collections -from ..packages import six -from ..packages.six.moves import queue - -if six.PY2: - # Queue is imported for side effects on MS Windows. See issue #229. - import Queue as _unused_module_Queue # noqa: F401 - - -class LifoQueue(queue.Queue): - def _init(self, _): - self.queue = collections.deque() - - def _qsize(self, len=len): - return len(self.queue) - - def _put(self, item): - self.queue.append(item) - - def _get(self): - return self.queue.pop() diff --git a/lib/urllib3/util/request.py b/lib/urllib3/util/request.py deleted file mode 100644 index 3ddfcd5..0000000 --- a/lib/urllib3/util/request.py +++ /dev/null @@ -1,118 +0,0 @@ -from __future__ import absolute_import -from base64 import b64encode - -from ..packages.six import b, integer_types -from ..exceptions import UnrewindableBodyError - -ACCEPT_ENCODING = 'gzip,deflate' -_FAILEDTELL = object() - - -def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, - basic_auth=None, proxy_basic_auth=None, disable_cache=None): - """ - Shortcuts for generating request headers. - - :param keep_alive: - If ``True``, adds 'connection: keep-alive' header. - - :param accept_encoding: - Can be a boolean, list, or string. - ``True`` translates to 'gzip,deflate'. - List will get joined by comma. - String will be used as provided. - - :param user_agent: - String representing the user-agent you want, such as - "python-urllib3/0.6" - - :param basic_auth: - Colon-separated username:password string for 'authorization: basic ...' - auth header. - - :param proxy_basic_auth: - Colon-separated username:password string for 'proxy-authorization: basic ...' - auth header. - - :param disable_cache: - If ``True``, adds 'cache-control: no-cache' header. - - Example:: - - >>> make_headers(keep_alive=True, user_agent="Batman/1.0") - {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} - >>> make_headers(accept_encoding=True) - {'accept-encoding': 'gzip,deflate'} - """ - headers = {} - if accept_encoding: - if isinstance(accept_encoding, str): - pass - elif isinstance(accept_encoding, list): - accept_encoding = ','.join(accept_encoding) - else: - accept_encoding = ACCEPT_ENCODING - headers['accept-encoding'] = accept_encoding - - if user_agent: - headers['user-agent'] = user_agent - - if keep_alive: - headers['connection'] = 'keep-alive' - - if basic_auth: - headers['authorization'] = 'Basic ' + \ - b64encode(b(basic_auth)).decode('utf-8') - - if proxy_basic_auth: - headers['proxy-authorization'] = 'Basic ' + \ - b64encode(b(proxy_basic_auth)).decode('utf-8') - - if disable_cache: - headers['cache-control'] = 'no-cache' - - return headers - - -def set_file_position(body, pos): - """ - If a position is provided, move file to that point. - Otherwise, we'll attempt to record a position for future use. - """ - if pos is not None: - rewind_body(body, pos) - elif getattr(body, 'tell', None) is not None: - try: - pos = body.tell() - except (IOError, OSError): - # This differentiates from None, allowing us to catch - # a failed `tell()` later when trying to rewind the body. - pos = _FAILEDTELL - - return pos - - -def rewind_body(body, body_pos): - """ - Attempt to rewind body to a certain position. - Primarily used for request redirects and retries. - - :param body: - File-like object that supports seek. - - :param int pos: - Position to seek to in file. - """ - body_seek = getattr(body, 'seek', None) - if body_seek is not None and isinstance(body_pos, integer_types): - try: - body_seek(body_pos) - except (IOError, OSError): - raise UnrewindableBodyError("An error occurred when rewinding request " - "body for redirect/retry.") - elif body_pos is _FAILEDTELL: - raise UnrewindableBodyError("Unable to record file position for rewinding " - "request body during a redirect/retry.") - else: - raise ValueError("body_pos must be of type integer, " - "instead it was %s." % type(body_pos)) diff --git a/lib/urllib3/util/response.py b/lib/urllib3/util/response.py deleted file mode 100644 index 3d54864..0000000 --- a/lib/urllib3/util/response.py +++ /dev/null @@ -1,87 +0,0 @@ -from __future__ import absolute_import -from ..packages.six.moves import http_client as httplib - -from ..exceptions import HeaderParsingError - - -def is_fp_closed(obj): - """ - Checks whether a given file-like object is closed. - - :param obj: - The file-like object to check. - """ - - try: - # Check `isclosed()` first, in case Python3 doesn't set `closed`. - # GH Issue #928 - return obj.isclosed() - except AttributeError: - pass - - try: - # Check via the official file-like-object way. - return obj.closed - except AttributeError: - pass - - try: - # Check if the object is a container for another file-like object that - # gets released on exhaustion (e.g. HTTPResponse). - return obj.fp is None - except AttributeError: - pass - - raise ValueError("Unable to determine whether fp is closed.") - - -def assert_header_parsing(headers): - """ - Asserts whether all headers have been successfully parsed. - Extracts encountered errors from the result of parsing headers. - - Only works on Python 3. - - :param headers: Headers to verify. - :type headers: `httplib.HTTPMessage`. - - :raises urllib3.exceptions.HeaderParsingError: - If parsing errors are found. - """ - - # This will fail silently if we pass in the wrong kind of parameter. - # To make debugging easier add an explicit check. - if not isinstance(headers, httplib.HTTPMessage): - raise TypeError('expected httplib.Message, got {0}.'.format( - type(headers))) - - defects = getattr(headers, 'defects', None) - get_payload = getattr(headers, 'get_payload', None) - - unparsed_data = None - if get_payload: - # get_payload is actually email.message.Message.get_payload; - # we're only interested in the result if it's not a multipart message - if not headers.is_multipart(): - payload = get_payload() - - if isinstance(payload, (bytes, str)): - unparsed_data = payload - - if defects or unparsed_data: - raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) - - -def is_response_to_head(response): - """ - Checks whether the request of a response has been a HEAD-request. - Handles the quirks of AppEngine. - - :param conn: - :type conn: :class:`httplib.HTTPResponse` - """ - # FIXME: Can we do this somehow without accessing private httplib _method? - method = response._method - if isinstance(method, int): # Platform-specific: Appengine - return method == 3 - return method.upper() == 'HEAD' diff --git a/lib/urllib3/util/retry.py b/lib/urllib3/util/retry.py deleted file mode 100644 index e7d0abd..0000000 --- a/lib/urllib3/util/retry.py +++ /dev/null @@ -1,411 +0,0 @@ -from __future__ import absolute_import -import time -import logging -from collections import namedtuple -from itertools import takewhile -import email -import re - -from ..exceptions import ( - ConnectTimeoutError, - MaxRetryError, - ProtocolError, - ReadTimeoutError, - ResponseError, - InvalidHeader, -) -from ..packages import six - - -log = logging.getLogger(__name__) - - -# Data structure for representing the metadata of requests that result in a retry. -RequestHistory = namedtuple('RequestHistory', ["method", "url", "error", - "status", "redirect_location"]) - - -class Retry(object): - """ Retry configuration. - - Each retry attempt will create a new Retry object with updated values, so - they can be safely reused. - - Retries can be defined as a default for a pool:: - - retries = Retry(connect=5, read=2, redirect=5) - http = PoolManager(retries=retries) - response = http.request('GET', 'http://example.com/') - - Or per-request (which overrides the default for the pool):: - - response = http.request('GET', 'http://example.com/', retries=Retry(10)) - - Retries can be disabled by passing ``False``:: - - response = http.request('GET', 'http://example.com/', retries=False) - - Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless - retries are disabled, in which case the causing exception will be raised. - - :param int total: - Total number of retries to allow. Takes precedence over other counts. - - Set to ``None`` to remove this constraint and fall back on other - counts. It's a good idea to set this to some sensibly-high value to - account for unexpected edge cases and avoid infinite retry loops. - - Set to ``0`` to fail on the first retry. - - Set to ``False`` to disable and imply ``raise_on_redirect=False``. - - :param int connect: - How many connection-related errors to retry on. - - These are errors raised before the request is sent to the remote server, - which we assume has not triggered the server to process the request. - - Set to ``0`` to fail on the first retry of this type. - - :param int read: - How many times to retry on read errors. - - These errors are raised after the request was sent to the server, so the - request may have side-effects. - - Set to ``0`` to fail on the first retry of this type. - - :param int redirect: - How many redirects to perform. Limit this to avoid infinite redirect - loops. - - A redirect is a HTTP response with a status code 301, 302, 303, 307 or - 308. - - Set to ``0`` to fail on the first retry of this type. - - Set to ``False`` to disable and imply ``raise_on_redirect=False``. - - :param int status: - How many times to retry on bad status codes. - - These are retries made on responses, where status code matches - ``status_forcelist``. - - Set to ``0`` to fail on the first retry of this type. - - :param iterable method_whitelist: - Set of uppercased HTTP method verbs that we should retry on. - - By default, we only retry on methods which are considered to be - idempotent (multiple requests with the same parameters end with the - same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. - - Set to a ``False`` value to retry on any verb. - - :param iterable status_forcelist: - A set of integer HTTP status codes that we should force a retry on. - A retry is initiated if the request method is in ``method_whitelist`` - and the response status code is in ``status_forcelist``. - - By default, this is disabled with ``None``. - - :param float backoff_factor: - A backoff factor to apply between attempts after the second try - (most errors are resolved immediately by a second try without a - delay). urllib3 will sleep for:: - - {backoff factor} * (2 ** ({number of total retries} - 1)) - - seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep - for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer - than :attr:`Retry.BACKOFF_MAX`. - - By default, backoff is disabled (set to 0). - - :param bool raise_on_redirect: Whether, if the number of redirects is - exhausted, to raise a MaxRetryError, or to return a response with a - response code in the 3xx range. - - :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: - whether we should raise an exception, or return a response, - if status falls in ``status_forcelist`` range and retries have - been exhausted. - - :param tuple history: The history of the request encountered during - each call to :meth:`~Retry.increment`. The list is in the order - the requests occurred. Each list item is of class :class:`RequestHistory`. - - :param bool respect_retry_after_header: - Whether to respect Retry-After header on status codes defined as - :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. - - :param iterable remove_headers_on_redirect: - Sequence of headers to remove from the request when a response - indicating a redirect is returned before firing off the redirected - request. - """ - - DEFAULT_METHOD_WHITELIST = frozenset([ - 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) - - RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) - - DEFAULT_REDIRECT_HEADERS_BLACKLIST = frozenset(['Authorization']) - - #: Maximum backoff time. - BACKOFF_MAX = 120 - - def __init__(self, total=10, connect=None, read=None, redirect=None, status=None, - method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, - backoff_factor=0, raise_on_redirect=True, raise_on_status=True, - history=None, respect_retry_after_header=True, - remove_headers_on_redirect=DEFAULT_REDIRECT_HEADERS_BLACKLIST): - - self.total = total - self.connect = connect - self.read = read - self.status = status - - if redirect is False or total is False: - redirect = 0 - raise_on_redirect = False - - self.redirect = redirect - self.status_forcelist = status_forcelist or set() - self.method_whitelist = method_whitelist - self.backoff_factor = backoff_factor - self.raise_on_redirect = raise_on_redirect - self.raise_on_status = raise_on_status - self.history = history or tuple() - self.respect_retry_after_header = respect_retry_after_header - self.remove_headers_on_redirect = remove_headers_on_redirect - - def new(self, **kw): - params = dict( - total=self.total, - connect=self.connect, read=self.read, redirect=self.redirect, status=self.status, - method_whitelist=self.method_whitelist, - status_forcelist=self.status_forcelist, - backoff_factor=self.backoff_factor, - raise_on_redirect=self.raise_on_redirect, - raise_on_status=self.raise_on_status, - history=self.history, - remove_headers_on_redirect=self.remove_headers_on_redirect - ) - params.update(kw) - return type(self)(**params) - - @classmethod - def from_int(cls, retries, redirect=True, default=None): - """ Backwards-compatibility for the old retries format.""" - if retries is None: - retries = default if default is not None else cls.DEFAULT - - if isinstance(retries, Retry): - return retries - - redirect = bool(redirect) and None - new_retries = cls(retries, redirect=redirect) - log.debug("Converted retries value: %r -> %r", retries, new_retries) - return new_retries - - def get_backoff_time(self): - """ Formula for computing the current backoff - - :rtype: float - """ - # We want to consider only the last consecutive errors sequence (Ignore redirects). - consecutive_errors_len = len(list(takewhile(lambda x: x.redirect_location is None, - reversed(self.history)))) - if consecutive_errors_len <= 1: - return 0 - - backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) - return min(self.BACKOFF_MAX, backoff_value) - - def parse_retry_after(self, retry_after): - # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 - if re.match(r"^\s*[0-9]+\s*$", retry_after): - seconds = int(retry_after) - else: - retry_date_tuple = email.utils.parsedate(retry_after) - if retry_date_tuple is None: - raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) - retry_date = time.mktime(retry_date_tuple) - seconds = retry_date - time.time() - - if seconds < 0: - seconds = 0 - - return seconds - - def get_retry_after(self, response): - """ Get the value of Retry-After in seconds. """ - - retry_after = response.getheader("Retry-After") - - if retry_after is None: - return None - - return self.parse_retry_after(retry_after) - - def sleep_for_retry(self, response=None): - retry_after = self.get_retry_after(response) - if retry_after: - time.sleep(retry_after) - return True - - return False - - def _sleep_backoff(self): - backoff = self.get_backoff_time() - if backoff <= 0: - return - time.sleep(backoff) - - def sleep(self, response=None): - """ Sleep between retry attempts. - - This method will respect a server's ``Retry-After`` response header - and sleep the duration of the time requested. If that is not present, it - will use an exponential backoff. By default, the backoff factor is 0 and - this method will return immediately. - """ - - if response: - slept = self.sleep_for_retry(response) - if slept: - return - - self._sleep_backoff() - - def _is_connection_error(self, err): - """ Errors when we're fairly sure that the server did not receive the - request, so it should be safe to retry. - """ - return isinstance(err, ConnectTimeoutError) - - def _is_read_error(self, err): - """ Errors that occur after the request has been started, so we should - assume that the server began processing it. - """ - return isinstance(err, (ReadTimeoutError, ProtocolError)) - - def _is_method_retryable(self, method): - """ Checks if a given HTTP method should be retried upon, depending if - it is included on the method whitelist. - """ - if self.method_whitelist and method.upper() not in self.method_whitelist: - return False - - return True - - def is_retry(self, method, status_code, has_retry_after=False): - """ Is this method/status code retryable? (Based on whitelists and control - variables such as the number of total retries to allow, whether to - respect the Retry-After header, whether this header is present, and - whether the returned status code is on the list of status codes to - be retried upon on the presence of the aforementioned header) - """ - if not self._is_method_retryable(method): - return False - - if self.status_forcelist and status_code in self.status_forcelist: - return True - - return (self.total and self.respect_retry_after_header and - has_retry_after and (status_code in self.RETRY_AFTER_STATUS_CODES)) - - def is_exhausted(self): - """ Are we out of retries? """ - retry_counts = (self.total, self.connect, self.read, self.redirect, self.status) - retry_counts = list(filter(None, retry_counts)) - if not retry_counts: - return False - - return min(retry_counts) < 0 - - def increment(self, method=None, url=None, response=None, error=None, - _pool=None, _stacktrace=None): - """ Return a new Retry object with incremented retry counters. - - :param response: A response object, or None, if the server did not - return a response. - :type response: :class:`~urllib3.response.HTTPResponse` - :param Exception error: An error encountered during the request, or - None if the response was received successfully. - - :return: A new ``Retry`` object. - """ - if self.total is False and error: - # Disabled, indicate to re-raise the error. - raise six.reraise(type(error), error, _stacktrace) - - total = self.total - if total is not None: - total -= 1 - - connect = self.connect - read = self.read - redirect = self.redirect - status_count = self.status - cause = 'unknown' - status = None - redirect_location = None - - if error and self._is_connection_error(error): - # Connect retry? - if connect is False: - raise six.reraise(type(error), error, _stacktrace) - elif connect is not None: - connect -= 1 - - elif error and self._is_read_error(error): - # Read retry? - if read is False or not self._is_method_retryable(method): - raise six.reraise(type(error), error, _stacktrace) - elif read is not None: - read -= 1 - - elif response and response.get_redirect_location(): - # Redirect retry? - if redirect is not None: - redirect -= 1 - cause = 'too many redirects' - redirect_location = response.get_redirect_location() - status = response.status - - else: - # Incrementing because of a server error like a 500 in - # status_forcelist and a the given method is in the whitelist - cause = ResponseError.GENERIC_ERROR - if response and response.status: - if status_count is not None: - status_count -= 1 - cause = ResponseError.SPECIFIC_ERROR.format( - status_code=response.status) - status = response.status - - history = self.history + (RequestHistory(method, url, error, status, redirect_location),) - - new_retry = self.new( - total=total, - connect=connect, read=read, redirect=redirect, status=status_count, - history=history) - - if new_retry.is_exhausted(): - raise MaxRetryError(_pool, url, error or ResponseError(cause)) - - log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) - - return new_retry - - def __repr__(self): - return ('{cls.__name__}(total={self.total}, connect={self.connect}, ' - 'read={self.read}, redirect={self.redirect}, status={self.status})').format( - cls=type(self), self=self) - - -# For backwards compatibility (equivalent to pre-v1.9): -Retry.DEFAULT = Retry(3) diff --git a/lib/urllib3/util/ssl_.py b/lib/urllib3/util/ssl_.py deleted file mode 100644 index 64ea192..0000000 --- a/lib/urllib3/util/ssl_.py +++ /dev/null @@ -1,381 +0,0 @@ -from __future__ import absolute_import -import errno -import warnings -import hmac -import socket - -from binascii import hexlify, unhexlify -from hashlib import md5, sha1, sha256 - -from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning -from ..packages import six - - -SSLContext = None -HAS_SNI = False -IS_PYOPENSSL = False -IS_SECURETRANSPORT = False - -# Maps the length of a digest to a possible hash function producing this digest -HASHFUNC_MAP = { - 32: md5, - 40: sha1, - 64: sha256, -} - - -def _const_compare_digest_backport(a, b): - """ - Compare two digests of equal length in constant time. - - The digests must be of type str/bytes. - Returns True if the digests match, and False otherwise. - """ - result = abs(len(a) - len(b)) - for l, r in zip(bytearray(a), bytearray(b)): - result |= l ^ r - return result == 0 - - -_const_compare_digest = getattr(hmac, 'compare_digest', - _const_compare_digest_backport) - - -try: # Test for SSL features - import ssl - from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 - from ssl import HAS_SNI # Has SNI? -except ImportError: - pass - - -try: - from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION -except ImportError: - OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 - OP_NO_COMPRESSION = 0x20000 - - -# Python 2.7 doesn't have inet_pton on non-Linux so we fallback on inet_aton in -# those cases. This means that we can only detect IPv4 addresses in this case. -if hasattr(socket, 'inet_pton'): - inet_pton = socket.inet_pton -else: - # Maybe we can use ipaddress if the user has urllib3[secure]? - try: - import ipaddress - - def inet_pton(_, host): - if isinstance(host, bytes): - host = host.decode('ascii') - return ipaddress.ip_address(host) - - except ImportError: # Platform-specific: Non-Linux - def inet_pton(_, host): - return socket.inet_aton(host) - - -# A secure default. -# Sources for more information on TLS ciphers: -# -# - https://wiki.mozilla.org/Security/Server_Side_TLS -# - https://www.ssllabs.com/projects/best-practices/index.html -# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ -# -# The general intent is: -# - Prefer TLS 1.3 cipher suites -# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), -# - prefer ECDHE over DHE for better performance, -# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and -# security, -# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, -# - disable NULL authentication, MD5 MACs and DSS for security reasons. -DEFAULT_CIPHERS = ':'.join([ - 'TLS13-AES-256-GCM-SHA384', - 'TLS13-CHACHA20-POLY1305-SHA256', - 'TLS13-AES-128-GCM-SHA256', - 'ECDH+AESGCM', - 'ECDH+CHACHA20', - 'DH+AESGCM', - 'DH+CHACHA20', - 'ECDH+AES256', - 'DH+AES256', - 'ECDH+AES128', - 'DH+AES', - 'RSA+AESGCM', - 'RSA+AES', - '!aNULL', - '!eNULL', - '!MD5', -]) - -try: - from ssl import SSLContext # Modern SSL? -except ImportError: - import sys - - class SSLContext(object): # Platform-specific: Python 2 - def __init__(self, protocol_version): - self.protocol = protocol_version - # Use default values from a real SSLContext - self.check_hostname = False - self.verify_mode = ssl.CERT_NONE - self.ca_certs = None - self.options = 0 - self.certfile = None - self.keyfile = None - self.ciphers = None - - def load_cert_chain(self, certfile, keyfile): - self.certfile = certfile - self.keyfile = keyfile - - def load_verify_locations(self, cafile=None, capath=None): - self.ca_certs = cafile - - if capath is not None: - raise SSLError("CA directories not supported in older Pythons") - - def set_ciphers(self, cipher_suite): - self.ciphers = cipher_suite - - def wrap_socket(self, socket, server_hostname=None, server_side=False): - warnings.warn( - 'A true SSLContext object is not available. This prevents ' - 'urllib3 from configuring SSL appropriately and may cause ' - 'certain SSL connections to fail. You can upgrade to a newer ' - 'version of Python to solve this. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings', - InsecurePlatformWarning - ) - kwargs = { - 'keyfile': self.keyfile, - 'certfile': self.certfile, - 'ca_certs': self.ca_certs, - 'cert_reqs': self.verify_mode, - 'ssl_version': self.protocol, - 'server_side': server_side, - } - return wrap_socket(socket, ciphers=self.ciphers, **kwargs) - - -def assert_fingerprint(cert, fingerprint): - """ - Checks if given fingerprint matches the supplied certificate. - - :param cert: - Certificate as bytes object. - :param fingerprint: - Fingerprint as string of hexdigits, can be interspersed by colons. - """ - - fingerprint = fingerprint.replace(':', '').lower() - digest_length = len(fingerprint) - hashfunc = HASHFUNC_MAP.get(digest_length) - if not hashfunc: - raise SSLError( - 'Fingerprint of invalid length: {0}'.format(fingerprint)) - - # We need encode() here for py32; works on py2 and p33. - fingerprint_bytes = unhexlify(fingerprint.encode()) - - cert_digest = hashfunc(cert).digest() - - if not _const_compare_digest(cert_digest, fingerprint_bytes): - raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' - .format(fingerprint, hexlify(cert_digest))) - - -def resolve_cert_reqs(candidate): - """ - Resolves the argument to a numeric constant, which can be passed to - the wrap_socket function/method from the ssl module. - Defaults to :data:`ssl.CERT_NONE`. - If given a string it is assumed to be the name of the constant in the - :mod:`ssl` module or its abbreviation. - (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. - If it's neither `None` nor a string we assume it is already the numeric - constant which can directly be passed to wrap_socket. - """ - if candidate is None: - return CERT_NONE - - if isinstance(candidate, str): - res = getattr(ssl, candidate, None) - if res is None: - res = getattr(ssl, 'CERT_' + candidate) - return res - - return candidate - - -def resolve_ssl_version(candidate): - """ - like resolve_cert_reqs - """ - if candidate is None: - return PROTOCOL_SSLv23 - - if isinstance(candidate, str): - res = getattr(ssl, candidate, None) - if res is None: - res = getattr(ssl, 'PROTOCOL_' + candidate) - return res - - return candidate - - -def create_urllib3_context(ssl_version=None, cert_reqs=None, - options=None, ciphers=None): - """All arguments have the same meaning as ``ssl_wrap_socket``. - - By default, this function does a lot of the same work that - ``ssl.create_default_context`` does on Python 3.4+. It: - - - Disables SSLv2, SSLv3, and compression - - Sets a restricted set of server ciphers - - If you wish to enable SSLv3, you can do:: - - from urllib3.util import ssl_ - context = ssl_.create_urllib3_context() - context.options &= ~ssl_.OP_NO_SSLv3 - - You can do the same to enable compression (substituting ``COMPRESSION`` - for ``SSLv3`` in the last line above). - - :param ssl_version: - The desired protocol version to use. This will default to - PROTOCOL_SSLv23 which will negotiate the highest protocol that both - the server and your installation of OpenSSL support. - :param cert_reqs: - Whether to require the certificate verification. This defaults to - ``ssl.CERT_REQUIRED``. - :param options: - Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, - ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. - :param ciphers: - Which cipher suites to allow the server to select. - :returns: - Constructed SSLContext object with specified options - :rtype: SSLContext - """ - context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) - - context.set_ciphers(ciphers or DEFAULT_CIPHERS) - - # Setting the default here, as we may have no ssl module on import - cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs - - if options is None: - options = 0 - # SSLv2 is easily broken and is considered harmful and dangerous - options |= OP_NO_SSLv2 - # SSLv3 has several problems and is now dangerous - options |= OP_NO_SSLv3 - # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ - # (issue #309) - options |= OP_NO_COMPRESSION - - context.options |= options - - context.verify_mode = cert_reqs - if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 - # We do our own verification, including fingerprints and alternative - # hostnames. So disable it here - context.check_hostname = False - return context - - -def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - ca_certs=None, server_hostname=None, - ssl_version=None, ciphers=None, ssl_context=None, - ca_cert_dir=None): - """ - All arguments except for server_hostname, ssl_context, and ca_cert_dir have - the same meaning as they do when using :func:`ssl.wrap_socket`. - - :param server_hostname: - When SNI is supported, the expected hostname of the certificate - :param ssl_context: - A pre-made :class:`SSLContext` object. If none is provided, one will - be created using :func:`create_urllib3_context`. - :param ciphers: - A string of ciphers we wish the client to support. - :param ca_cert_dir: - A directory containing CA certificates in multiple separate files, as - supported by OpenSSL's -CApath flag or the capath argument to - SSLContext.load_verify_locations(). - """ - context = ssl_context - if context is None: - # Note: This branch of code and all the variables in it are no longer - # used by urllib3 itself. We should consider deprecating and removing - # this code. - context = create_urllib3_context(ssl_version, cert_reqs, - ciphers=ciphers) - - if ca_certs or ca_cert_dir: - try: - context.load_verify_locations(ca_certs, ca_cert_dir) - except IOError as e: # Platform-specific: Python 2.7 - raise SSLError(e) - # Py33 raises FileNotFoundError which subclasses OSError - # These are not equivalent unless we check the errno attribute - except OSError as e: # Platform-specific: Python 3.3 and beyond - if e.errno == errno.ENOENT: - raise SSLError(e) - raise - elif getattr(context, 'load_default_certs', None) is not None: - # try to load OS default certs; works well on Windows (require Python3.4+) - context.load_default_certs() - - if certfile: - context.load_cert_chain(certfile, keyfile) - - # If we detect server_hostname is an IP address then the SNI - # extension should not be used according to RFC3546 Section 3.1 - # We shouldn't warn the user if SNI isn't available but we would - # not be using SNI anyways due to IP address for server_hostname. - if ((server_hostname is not None and not is_ipaddress(server_hostname)) - or IS_SECURETRANSPORT): - if HAS_SNI and server_hostname is not None: - return context.wrap_socket(sock, server_hostname=server_hostname) - - warnings.warn( - 'An HTTPS request has been made, but the SNI (Server Name ' - 'Indication) extension to TLS is not available on this platform. ' - 'This may cause the server to present an incorrect TLS ' - 'certificate, which can cause validation failures. You can upgrade to ' - 'a newer version of Python to solve this. For more information, see ' - 'https://urllib3.readthedocs.io/en/latest/advanced-usage.html' - '#ssl-warnings', - SNIMissingWarning - ) - - return context.wrap_socket(sock) - - -def is_ipaddress(hostname): - """Detects whether the hostname given is an IP address. - - :param str hostname: Hostname to examine. - :return: True if the hostname is an IP address, False otherwise. - """ - if six.PY3 and isinstance(hostname, bytes): - # IDN A-label bytes are ASCII compatible. - hostname = hostname.decode('ascii') - - families = [socket.AF_INET] - if hasattr(socket, 'AF_INET6'): - families.append(socket.AF_INET6) - - for af in families: - try: - inet_pton(af, hostname) - except (socket.error, ValueError, OSError): - pass - else: - return True - return False diff --git a/lib/urllib3/util/timeout.py b/lib/urllib3/util/timeout.py deleted file mode 100644 index cec817e..0000000 --- a/lib/urllib3/util/timeout.py +++ /dev/null @@ -1,242 +0,0 @@ -from __future__ import absolute_import -# The default socket timeout, used by httplib to indicate that no timeout was -# specified by the user -from socket import _GLOBAL_DEFAULT_TIMEOUT -import time - -from ..exceptions import TimeoutStateError - -# A sentinel value to indicate that no timeout was specified by the user in -# urllib3 -_Default = object() - - -# Use time.monotonic if available. -current_time = getattr(time, "monotonic", time.time) - - -class Timeout(object): - """ Timeout configuration. - - Timeouts can be defined as a default for a pool:: - - timeout = Timeout(connect=2.0, read=7.0) - http = PoolManager(timeout=timeout) - response = http.request('GET', 'http://example.com/') - - Or per-request (which overrides the default for the pool):: - - response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) - - Timeouts can be disabled by setting all the parameters to ``None``:: - - no_timeout = Timeout(connect=None, read=None) - response = http.request('GET', 'http://example.com/, timeout=no_timeout) - - - :param total: - This combines the connect and read timeouts into one; the read timeout - will be set to the time leftover from the connect attempt. In the - event that both a connect timeout and a total are specified, or a read - timeout and a total are specified, the shorter timeout will be applied. - - Defaults to None. - - :type total: integer, float, or None - - :param connect: - The maximum amount of time to wait for a connection attempt to a server - to succeed. Omitting the parameter will default the connect timeout to - the system default, probably `the global default timeout in socket.py - <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. - None will set an infinite timeout for connection attempts. - - :type connect: integer, float, or None - - :param read: - The maximum amount of time to wait between consecutive - read operations for a response from the server. Omitting - the parameter will default the read timeout to the system - default, probably `the global default timeout in socket.py - <http://hg.python.org/cpython/file/603b4d593758/Lib/socket.py#l535>`_. - None will set an infinite timeout. - - :type read: integer, float, or None - - .. note:: - - Many factors can affect the total amount of time for urllib3 to return - an HTTP response. - - For example, Python's DNS resolver does not obey the timeout specified - on the socket. Other factors that can affect total request time include - high CPU load, high swap, the program running at a low priority level, - or other behaviors. - - In addition, the read and total timeouts only measure the time between - read operations on the socket connecting the client and the server, - not the total amount of time for the request to return a complete - response. For most requests, the timeout is raised because the server - has not sent the first byte in the specified time. This is not always - the case; if a server streams one byte every fifteen seconds, a timeout - of 20 seconds will not trigger, even though the request will take - several minutes to complete. - - If your goal is to cut off any request after a set amount of wall clock - time, consider having a second "watcher" thread to cut off a slow - request. - """ - - #: A sentinel object representing the default timeout value - DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT - - def __init__(self, total=None, connect=_Default, read=_Default): - self._connect = self._validate_timeout(connect, 'connect') - self._read = self._validate_timeout(read, 'read') - self.total = self._validate_timeout(total, 'total') - self._start_connect = None - - def __str__(self): - return '%s(connect=%r, read=%r, total=%r)' % ( - type(self).__name__, self._connect, self._read, self.total) - - @classmethod - def _validate_timeout(cls, value, name): - """ Check that a timeout attribute is valid. - - :param value: The timeout value to validate - :param name: The name of the timeout attribute to validate. This is - used to specify in error messages. - :return: The validated and casted version of the given value. - :raises ValueError: If it is a numeric value less than or equal to - zero, or the type is not an integer, float, or None. - """ - if value is _Default: - return cls.DEFAULT_TIMEOUT - - if value is None or value is cls.DEFAULT_TIMEOUT: - return value - - if isinstance(value, bool): - raise ValueError("Timeout cannot be a boolean value. It must " - "be an int, float or None.") - try: - float(value) - except (TypeError, ValueError): - raise ValueError("Timeout value %s was %s, but it must be an " - "int, float or None." % (name, value)) - - try: - if value <= 0: - raise ValueError("Attempted to set %s timeout to %s, but the " - "timeout cannot be set to a value less " - "than or equal to 0." % (name, value)) - except TypeError: # Python 3 - raise ValueError("Timeout value %s was %s, but it must be an " - "int, float or None." % (name, value)) - - return value - - @classmethod - def from_float(cls, timeout): - """ Create a new Timeout from a legacy timeout value. - - The timeout value used by httplib.py sets the same timeout on the - connect(), and recv() socket requests. This creates a :class:`Timeout` - object that sets the individual timeouts to the ``timeout`` value - passed to this function. - - :param timeout: The legacy timeout value. - :type timeout: integer, float, sentinel default object, or None - :return: Timeout object - :rtype: :class:`Timeout` - """ - return Timeout(read=timeout, connect=timeout) - - def clone(self): - """ Create a copy of the timeout object - - Timeout properties are stored per-pool but each request needs a fresh - Timeout object to ensure each one has its own start/stop configured. - - :return: a copy of the timeout object - :rtype: :class:`Timeout` - """ - # We can't use copy.deepcopy because that will also create a new object - # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to - # detect the user default. - return Timeout(connect=self._connect, read=self._read, - total=self.total) - - def start_connect(self): - """ Start the timeout clock, used during a connect() attempt - - :raises urllib3.exceptions.TimeoutStateError: if you attempt - to start a timer that has been started already. - """ - if self._start_connect is not None: - raise TimeoutStateError("Timeout timer has already been started.") - self._start_connect = current_time() - return self._start_connect - - def get_connect_duration(self): - """ Gets the time elapsed since the call to :meth:`start_connect`. - - :return: Elapsed time. - :rtype: float - :raises urllib3.exceptions.TimeoutStateError: if you attempt - to get duration for a timer that hasn't been started. - """ - if self._start_connect is None: - raise TimeoutStateError("Can't get connect duration for timer " - "that has not started.") - return current_time() - self._start_connect - - @property - def connect_timeout(self): - """ Get the value to use when setting a connection timeout. - - This will be a positive float or integer, the value None - (never timeout), or the default system timeout. - - :return: Connect timeout. - :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None - """ - if self.total is None: - return self._connect - - if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: - return self.total - - return min(self._connect, self.total) - - @property - def read_timeout(self): - """ Get the value for the read timeout. - - This assumes some time has elapsed in the connection timeout and - computes the read timeout appropriately. - - If self.total is set, the read timeout is dependent on the amount of - time taken by the connect timeout. If the connection time has not been - established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be - raised. - - :return: Value to use for the read timeout. - :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None - :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` - has not yet been called on this object. - """ - if (self.total is not None and - self.total is not self.DEFAULT_TIMEOUT and - self._read is not None and - self._read is not self.DEFAULT_TIMEOUT): - # In case the connect timeout has not yet been established. - if self._start_connect is None: - return self._read - return max(0, min(self.total - self.get_connect_duration(), - self._read)) - elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: - return max(0, self.total - self.get_connect_duration()) - else: - return self._read diff --git a/lib/urllib3/util/url.py b/lib/urllib3/util/url.py deleted file mode 100644 index 6b6f996..0000000 --- a/lib/urllib3/util/url.py +++ /dev/null @@ -1,230 +0,0 @@ -from __future__ import absolute_import -from collections import namedtuple - -from ..exceptions import LocationParseError - - -url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] - -# We only want to normalize urls with an HTTP(S) scheme. -# urllib3 infers URLs without a scheme (None) to be http. -NORMALIZABLE_SCHEMES = ('http', 'https', None) - - -class Url(namedtuple('Url', url_attrs)): - """ - Datastructure for representing an HTTP URL. Used as a return value for - :func:`parse_url`. Both the scheme and host are normalized as they are - both case-insensitive according to RFC 3986. - """ - __slots__ = () - - def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, - query=None, fragment=None): - if path and not path.startswith('/'): - path = '/' + path - if scheme: - scheme = scheme.lower() - if host and scheme in NORMALIZABLE_SCHEMES: - host = host.lower() - return super(Url, cls).__new__(cls, scheme, auth, host, port, path, - query, fragment) - - @property - def hostname(self): - """For backwards-compatibility with urlparse. We're nice like that.""" - return self.host - - @property - def request_uri(self): - """Absolute path including the query string.""" - uri = self.path or '/' - - if self.query is not None: - uri += '?' + self.query - - return uri - - @property - def netloc(self): - """Network location including host and port""" - if self.port: - return '%s:%d' % (self.host, self.port) - return self.host - - @property - def url(self): - """ - Convert self into a url - - This function should more or less round-trip with :func:`.parse_url`. The - returned url may not be exactly the same as the url inputted to - :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls - with a blank port will have : removed). - - Example: :: - - >>> U = parse_url('http://google.com/mail/') - >>> U.url - 'http://google.com/mail/' - >>> Url('http', 'username:password', 'host.com', 80, - ... '/path', 'query', 'fragment').url - 'http://username:password@host.com:80/path?query#fragment' - """ - scheme, auth, host, port, path, query, fragment = self - url = '' - - # We use "is not None" we want things to happen with empty strings (or 0 port) - if scheme is not None: - url += scheme + '://' - if auth is not None: - url += auth + '@' - if host is not None: - url += host - if port is not None: - url += ':' + str(port) - if path is not None: - url += path - if query is not None: - url += '?' + query - if fragment is not None: - url += '#' + fragment - - return url - - def __str__(self): - return self.url - - -def split_first(s, delims): - """ - Given a string and an iterable of delimiters, split on the first found - delimiter. Return two split parts and the matched delimiter. - - If not found, then the first part is the full input string. - - Example:: - - >>> split_first('foo/bar?baz', '?/=') - ('foo', 'bar?baz', '/') - >>> split_first('foo/bar?baz', '123') - ('foo/bar?baz', '', None) - - Scales linearly with number of delims. Not ideal for large number of delims. - """ - min_idx = None - min_delim = None - for d in delims: - idx = s.find(d) - if idx < 0: - continue - - if min_idx is None or idx < min_idx: - min_idx = idx - min_delim = d - - if min_idx is None or min_idx < 0: - return s, '', None - - return s[:min_idx], s[min_idx + 1:], min_delim - - -def parse_url(url): - """ - Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is - performed to parse incomplete urls. Fields not provided will be None. - - Partly backwards-compatible with :mod:`urlparse`. - - Example:: - - >>> parse_url('http://google.com/mail/') - Url(scheme='http', host='google.com', port=None, path='/mail/', ...) - >>> parse_url('google.com:80') - Url(scheme=None, host='google.com', port=80, path=None, ...) - >>> parse_url('/foo?bar') - Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) - """ - - # While this code has overlap with stdlib's urlparse, it is much - # simplified for our needs and less annoying. - # Additionally, this implementations does silly things to be optimal - # on CPython. - - if not url: - # Empty - return Url() - - scheme = None - auth = None - host = None - port = None - path = None - fragment = None - query = None - - # Scheme - if '://' in url: - scheme, url = url.split('://', 1) - - # Find the earliest Authority Terminator - # (http://tools.ietf.org/html/rfc3986#section-3.2) - url, path_, delim = split_first(url, ['/', '?', '#']) - - if delim: - # Reassemble the path - path = delim + path_ - - # Auth - if '@' in url: - # Last '@' denotes end of auth part - auth, url = url.rsplit('@', 1) - - # IPv6 - if url and url[0] == '[': - host, url = url.split(']', 1) - host += ']' - - # Port - if ':' in url: - _host, port = url.split(':', 1) - - if not host: - host = _host - - if port: - # If given, ports must be integers. No whitespace, no plus or - # minus prefixes, no non-integer digits such as ^2 (superscript). - if not port.isdigit(): - raise LocationParseError(url) - try: - port = int(port) - except ValueError: - raise LocationParseError(url) - else: - # Blank ports are cool, too. (rfc3986#section-3.2.3) - port = None - - elif not host and url: - host = url - - if not path: - return Url(scheme, auth, host, port, path, query, fragment) - - # Fragment - if '#' in path: - path, fragment = path.split('#', 1) - - # Query - if '?' in path: - path, query = path.split('?', 1) - - return Url(scheme, auth, host, port, path, query, fragment) - - -def get_host(url): - """ - Deprecated. Use :func:`parse_url` instead. - """ - p = parse_url(url) - return p.scheme or 'http', p.hostname, p.port diff --git a/lib/urllib3/util/wait.py b/lib/urllib3/util/wait.py deleted file mode 100644 index 4db71ba..0000000 --- a/lib/urllib3/util/wait.py +++ /dev/null @@ -1,150 +0,0 @@ -import errno -from functools import partial -import select -import sys -try: - from time import monotonic -except ImportError: - from time import time as monotonic - -__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] - - -class NoWayToWaitForSocketError(Exception): - pass - - -# How should we wait on sockets? -# -# There are two types of APIs you can use for waiting on sockets: the fancy -# modern stateful APIs like epoll/kqueue, and the older stateless APIs like -# select/poll. The stateful APIs are more efficient when you have a lots of -# sockets to keep track of, because you can set them up once and then use them -# lots of times. But we only ever want to wait on a single socket at a time -# and don't want to keep track of state, so the stateless APIs are actually -# more efficient. So we want to use select() or poll(). -# -# Now, how do we choose between select() and poll()? On traditional Unixes, -# select() has a strange calling convention that makes it slow, or fail -# altogether, for high-numbered file descriptors. The point of poll() is to fix -# that, so on Unixes, we prefer poll(). -# -# On Windows, there is no poll() (or at least Python doesn't provide a wrapper -# for it), but that's OK, because on Windows, select() doesn't have this -# strange calling convention; plain select() works fine. -# -# So: on Windows we use select(), and everywhere else we use poll(). We also -# fall back to select() in case poll() is somehow broken or missing. - -if sys.version_info >= (3, 5): - # Modern Python, that retries syscalls by default - def _retry_on_intr(fn, timeout): - return fn(timeout) -else: - # Old and broken Pythons. - def _retry_on_intr(fn, timeout): - if timeout is None: - deadline = float("inf") - else: - deadline = monotonic() + timeout - - while True: - try: - return fn(timeout) - # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 - except (OSError, select.error) as e: - # 'e.args[0]' incantation works for both OSError and select.error - if e.args[0] != errno.EINTR: - raise - else: - timeout = deadline - monotonic() - if timeout < 0: - timeout = 0 - if timeout == float("inf"): - timeout = None - continue - - -def select_wait_for_socket(sock, read=False, write=False, timeout=None): - if not read and not write: - raise RuntimeError("must specify at least one of read=True, write=True") - rcheck = [] - wcheck = [] - if read: - rcheck.append(sock) - if write: - wcheck.append(sock) - # When doing a non-blocking connect, most systems signal success by - # marking the socket writable. Windows, though, signals success by marked - # it as "exceptional". We paper over the difference by checking the write - # sockets for both conditions. (The stdlib selectors module does the same - # thing.) - fn = partial(select.select, rcheck, wcheck, wcheck) - rready, wready, xready = _retry_on_intr(fn, timeout) - return bool(rready or wready or xready) - - -def poll_wait_for_socket(sock, read=False, write=False, timeout=None): - if not read and not write: - raise RuntimeError("must specify at least one of read=True, write=True") - mask = 0 - if read: - mask |= select.POLLIN - if write: - mask |= select.POLLOUT - poll_obj = select.poll() - poll_obj.register(sock, mask) - - # For some reason, poll() takes timeout in milliseconds - def do_poll(t): - if t is not None: - t *= 1000 - return poll_obj.poll(t) - - return bool(_retry_on_intr(do_poll, timeout)) - - -def null_wait_for_socket(*args, **kwargs): - raise NoWayToWaitForSocketError("no select-equivalent available") - - -def _have_working_poll(): - # Apparently some systems have a select.poll that fails as soon as you try - # to use it, either due to strange configuration or broken monkeypatching - # from libraries like eventlet/greenlet. - try: - poll_obj = select.poll() - _retry_on_intr(poll_obj.poll, 0) - except (AttributeError, OSError): - return False - else: - return True - - -def wait_for_socket(*args, **kwargs): - # We delay choosing which implementation to use until the first time we're - # called. We could do it at import time, but then we might make the wrong - # decision if someone goes wild with monkeypatching select.poll after - # we're imported. - global wait_for_socket - if _have_working_poll(): - wait_for_socket = poll_wait_for_socket - elif hasattr(select, "select"): - wait_for_socket = select_wait_for_socket - else: # Platform-specific: Appengine. - wait_for_socket = null_wait_for_socket - return wait_for_socket(*args, **kwargs) - - -def wait_for_read(sock, timeout=None): - """ Waits for reading to be available on a given socket. - Returns True if the socket is readable, or False if the timeout expired. - """ - return wait_for_socket(sock, read=True, timeout=timeout) - - -def wait_for_write(sock, timeout=None): - """ Waits for writing to be available on a given socket. - Returns True if the socket is readable, or False if the timeout expired. - """ - return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/lib/zope.interface-4.6.0-py3.7-nspkg.pth b/lib/zope.interface-4.6.0-py3.7-nspkg.pth deleted file mode 100644 index 4fa827e..0000000 --- a/lib/zope.interface-4.6.0-py3.7-nspkg.pth +++ /dev/null @@ -1 +0,0 @@ -import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('zope',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('zope', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('zope', [os.path.dirname(p)])));m = m or sys.modules.setdefault('zope', types.ModuleType('zope'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p) diff --git a/lib/zope/interface/__init__.py b/lib/zope/interface/__init__.py deleted file mode 100644 index 605b706..0000000 --- a/lib/zope/interface/__init__.py +++ /dev/null @@ -1,90 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Interfaces - -This package implements the Python "scarecrow" proposal. - -The package exports two objects, `Interface` and `Attribute` directly. It also -exports several helper methods. Interface is used to create an interface with -a class statement, as in: - - class IMyInterface(Interface): - '''Interface documentation - ''' - - def meth(arg1, arg2): - '''Documentation for meth - ''' - - # Note that there is no self argument - -To find out what you can do with interfaces, see the interface -interface, `IInterface` in the `interfaces` module. - -The package has several public modules: - - o `declarations` provides utilities to declare interfaces on objects. It - also provides a wide range of helpful utilities that aid in managing - declared interfaces. Most of its public names are however imported here. - - o `document` has a utility for documenting an interface as structured text. - - o `exceptions` has the interface-defined exceptions - - o `interfaces` contains a list of all public interfaces for this package. - - o `verify` has utilities for verifying implementations of interfaces. - -See the module doc strings for more information. -""" -__docformat__ = 'restructuredtext' - -from zope.interface.interface import Interface -from zope.interface.interface import _wire - -# Need to actually get the interface elements to implement the right interfaces -_wire() -del _wire - -from zope.interface.declarations import Declaration -from zope.interface.declarations import alsoProvides -from zope.interface.declarations import classImplements -from zope.interface.declarations import classImplementsOnly -from zope.interface.declarations import classProvides -from zope.interface.declarations import directlyProvidedBy -from zope.interface.declarations import directlyProvides -from zope.interface.declarations import implementedBy -from zope.interface.declarations import implementer -from zope.interface.declarations import implementer_only -from zope.interface.declarations import implements -from zope.interface.declarations import implementsOnly -from zope.interface.declarations import moduleProvides -from zope.interface.declarations import named -from zope.interface.declarations import noLongerProvides -from zope.interface.declarations import providedBy -from zope.interface.declarations import provider -from zope.interface.exceptions import Invalid -from zope.interface.interface import Attribute -from zope.interface.interface import invariant -from zope.interface.interface import taggedValue - -# The following are to make spec pickles cleaner -from zope.interface.declarations import Provides - - -from zope.interface.interfaces import IInterfaceDeclaration - -moduleProvides(IInterfaceDeclaration) - -__all__ = ('Interface', 'Attribute') + tuple(IInterfaceDeclaration) diff --git a/lib/zope/interface/_compat.py b/lib/zope/interface/_compat.py deleted file mode 100644 index fb61e13..0000000 --- a/lib/zope/interface/_compat.py +++ /dev/null @@ -1,58 +0,0 @@ -############################################################################## -# -# Copyright (c) 2006 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Basic components support -""" -import sys -import types - -if sys.version_info[0] < 3: - - def _normalize_name(name): - if isinstance(name, basestring): - return unicode(name) - raise TypeError("name must be a regular or unicode string") - - CLASS_TYPES = (type, types.ClassType) - STRING_TYPES = (basestring,) - - _BUILTINS = '__builtin__' - - PYTHON3 = False - PYTHON2 = True - -else: - - def _normalize_name(name): - if isinstance(name, bytes): - name = str(name, 'ascii') - if isinstance(name, str): - return name - raise TypeError("name must be a string or ASCII-only bytes") - - CLASS_TYPES = (type,) - STRING_TYPES = (str,) - - _BUILTINS = 'builtins' - - PYTHON3 = True - PYTHON2 = False - -def _skip_under_py3k(test_method): - import unittest - return unittest.skipIf(sys.version_info[0] >= 3, "Only on Python 2")(test_method) - - -def _skip_under_py2(test_method): - import unittest - return unittest.skipIf(sys.version_info[0] < 3, "Only on Python 3")(test_method) diff --git a/lib/zope/interface/_flatten.py b/lib/zope/interface/_flatten.py deleted file mode 100644 index a80c2de..0000000 --- a/lib/zope/interface/_flatten.py +++ /dev/null @@ -1,35 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Adapter-style interface registry - -See Adapter class. -""" -from zope.interface import Declaration - -def _flatten(implements, include_None=0): - - try: - r = implements.flattened() - except AttributeError: - if implements is None: - r=() - else: - r = Declaration(implements).flattened() - - if not include_None: - return r - - r = list(r) - r.append(None) - return r diff --git a/lib/zope/interface/_zope_interface_coptimizations.c b/lib/zope/interface/_zope_interface_coptimizations.c deleted file mode 100644 index b1e955e..0000000 --- a/lib/zope/interface/_zope_interface_coptimizations.c +++ /dev/null @@ -1,1726 +0,0 @@ -/*########################################################################### - # - # Copyright (c) 2003 Zope Foundation and Contributors. - # All Rights Reserved. - # - # This software is subject to the provisions of the Zope Public License, - # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. - # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED - # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS - # FOR A PARTICULAR PURPOSE. - # - ############################################################################*/ - -#include "Python.h" -#include "structmember.h" - -#define TYPE(O) ((PyTypeObject*)(O)) -#define OBJECT(O) ((PyObject*)(O)) -#define CLASSIC(O) ((PyClassObject*)(O)) -#ifndef PyVarObject_HEAD_INIT -#define PyVarObject_HEAD_INIT(a, b) PyObject_HEAD_INIT(a) b, -#endif -#ifndef Py_TYPE -#define Py_TYPE(o) ((o)->ob_type) -#endif - -#if PY_MAJOR_VERSION >= 3 -#define PY3K -#endif - -static PyObject *str__dict__, *str__implemented__, *strextends; -static PyObject *BuiltinImplementationSpecifications, *str__provides__; -static PyObject *str__class__, *str__providedBy__; -static PyObject *empty, *fallback, *str_implied, *str_cls, *str_implements; -static PyObject *str__conform__, *str_call_conform, *adapter_hooks; -static PyObject *str_uncached_lookup, *str_uncached_lookupAll; -static PyObject *str_uncached_subscriptions; -static PyObject *str_registry, *strro, *str_generation, *strchanged; - -static PyTypeObject *Implements; - -static int imported_declarations = 0; - -static int -import_declarations(void) -{ - PyObject *declarations, *i; - - declarations = PyImport_ImportModule("zope.interface.declarations"); - if (declarations == NULL) - return -1; - - BuiltinImplementationSpecifications = PyObject_GetAttrString( - declarations, "BuiltinImplementationSpecifications"); - if (BuiltinImplementationSpecifications == NULL) - return -1; - - empty = PyObject_GetAttrString(declarations, "_empty"); - if (empty == NULL) - return -1; - - fallback = PyObject_GetAttrString(declarations, "implementedByFallback"); - if (fallback == NULL) - return -1; - - - - i = PyObject_GetAttrString(declarations, "Implements"); - if (i == NULL) - return -1; - - if (! PyType_Check(i)) - { - PyErr_SetString(PyExc_TypeError, - "zope.interface.declarations.Implements is not a type"); - return -1; - } - - Implements = (PyTypeObject *)i; - - Py_DECREF(declarations); - - imported_declarations = 1; - return 0; -} - -static PyTypeObject SpecType; /* Forward */ - -static PyObject * -implementedByFallback(PyObject *cls) -{ - if (imported_declarations == 0 && import_declarations() < 0) - return NULL; - - return PyObject_CallFunctionObjArgs(fallback, cls, NULL); -} - -static PyObject * -implementedBy(PyObject *ignored, PyObject *cls) -{ - /* Fast retrieval of implements spec, if possible, to optimize - common case. Use fallback code if we get stuck. - */ - - PyObject *dict = NULL, *spec; - - if (PyType_Check(cls)) - { - dict = TYPE(cls)->tp_dict; - Py_XINCREF(dict); - } - - if (dict == NULL) - dict = PyObject_GetAttr(cls, str__dict__); - - if (dict == NULL) - { - /* Probably a security proxied class, use more expensive fallback code */ - PyErr_Clear(); - return implementedByFallback(cls); - } - - spec = PyObject_GetItem(dict, str__implemented__); - Py_DECREF(dict); - if (spec) - { - if (imported_declarations == 0 && import_declarations() < 0) - return NULL; - - if (PyObject_TypeCheck(spec, Implements)) - return spec; - - /* Old-style declaration, use more expensive fallback code */ - Py_DECREF(spec); - return implementedByFallback(cls); - } - - PyErr_Clear(); - - /* Maybe we have a builtin */ - if (imported_declarations == 0 && import_declarations() < 0) - return NULL; - - spec = PyDict_GetItem(BuiltinImplementationSpecifications, cls); - if (spec != NULL) - { - Py_INCREF(spec); - return spec; - } - - /* We're stuck, use fallback */ - return implementedByFallback(cls); -} - -static PyObject * -getObjectSpecification(PyObject *ignored, PyObject *ob) -{ - PyObject *cls, *result; - - result = PyObject_GetAttr(ob, str__provides__); - if (result != NULL && PyObject_TypeCheck(result, &SpecType)) - return result; - - PyErr_Clear(); - - /* We do a getattr here so as not to be defeated by proxies */ - cls = PyObject_GetAttr(ob, str__class__); - if (cls == NULL) - { - PyErr_Clear(); - if (imported_declarations == 0 && import_declarations() < 0) - return NULL; - Py_INCREF(empty); - return empty; - } - - result = implementedBy(NULL, cls); - Py_DECREF(cls); - - return result; -} - -static PyObject * -providedBy(PyObject *ignored, PyObject *ob) -{ - PyObject *result, *cls, *cp; - - result = PyObject_GetAttr(ob, str__providedBy__); - if (result == NULL) - { - PyErr_Clear(); - return getObjectSpecification(NULL, ob); - } - - - /* We want to make sure we have a spec. We can't do a type check - because we may have a proxy, so we'll just try to get the - only attribute. - */ - if (PyObject_TypeCheck(result, &SpecType) - || - PyObject_HasAttr(result, strextends) - ) - return result; - - /* - The object's class doesn't understand descriptors. - Sigh. We need to get an object descriptor, but we have to be - careful. We want to use the instance's __provides__,l if - there is one, but only if it didn't come from the class. - */ - Py_DECREF(result); - - cls = PyObject_GetAttr(ob, str__class__); - if (cls == NULL) - return NULL; - - result = PyObject_GetAttr(ob, str__provides__); - if (result == NULL) - { - /* No __provides__, so just fall back to implementedBy */ - PyErr_Clear(); - result = implementedBy(NULL, cls); - Py_DECREF(cls); - return result; - } - - cp = PyObject_GetAttr(cls, str__provides__); - if (cp == NULL) - { - /* The the class has no provides, assume we're done: */ - PyErr_Clear(); - Py_DECREF(cls); - return result; - } - - if (cp == result) - { - /* - Oops, we got the provides from the class. This means - the object doesn't have it's own. We should use implementedBy - */ - Py_DECREF(result); - result = implementedBy(NULL, cls); - } - - Py_DECREF(cls); - Py_DECREF(cp); - - return result; -} - -/* - Get an attribute from an inst dict. Return a borrowed reference. - - This has a number of advantages: - - - It avoids layers of Python api - - - It doesn't waste time looking for descriptors - - - It fails wo raising an exception, although that shouldn't really - matter. - -*/ -static PyObject * -inst_attr(PyObject *self, PyObject *name) -{ - PyObject **dictp, *v; - - dictp = _PyObject_GetDictPtr(self); - if (dictp && *dictp && (v = PyDict_GetItem(*dictp, name))) - return v; - PyErr_SetObject(PyExc_AttributeError, name); - return NULL; -} - - -static PyObject * -Spec_extends(PyObject *self, PyObject *other) -{ - PyObject *implied; - - implied = inst_attr(self, str_implied); - if (implied == NULL) - return NULL; - -#ifdef Py_True - if (PyDict_GetItem(implied, other) != NULL) - { - Py_INCREF(Py_True); - return Py_True; - } - Py_INCREF(Py_False); - return Py_False; -#else - return PyInt_FromLong(PyDict_GetItem(implied, other) != NULL); -#endif -} - -static char Spec_extends__doc__[] = -"Test whether a specification is or extends another" -; - -static char Spec_providedBy__doc__[] = -"Test whether an interface is implemented by the specification" -; - -static PyObject * -Spec_call(PyObject *self, PyObject *args, PyObject *kw) -{ - PyObject *spec; - - if (! PyArg_ParseTuple(args, "O", &spec)) - return NULL; - return Spec_extends(self, spec); -} - -static PyObject * -Spec_providedBy(PyObject *self, PyObject *ob) -{ - PyObject *decl, *item; - - decl = providedBy(NULL, ob); - if (decl == NULL) - return NULL; - - if (PyObject_TypeCheck(decl, &SpecType)) - item = Spec_extends(decl, self); - else - /* decl is probably a security proxy. We have to go the long way - around. - */ - item = PyObject_CallFunctionObjArgs(decl, self, NULL); - - Py_DECREF(decl); - return item; -} - - -static char Spec_implementedBy__doc__[] = -"Test whether the specification is implemented by a class or factory.\n" -"Raise TypeError if argument is neither a class nor a callable." -; - -static PyObject * -Spec_implementedBy(PyObject *self, PyObject *cls) -{ - PyObject *decl, *item; - - decl = implementedBy(NULL, cls); - if (decl == NULL) - return NULL; - - if (PyObject_TypeCheck(decl, &SpecType)) - item = Spec_extends(decl, self); - else - item = PyObject_CallFunctionObjArgs(decl, self, NULL); - - Py_DECREF(decl); - return item; -} - -static struct PyMethodDef Spec_methods[] = { - {"providedBy", - (PyCFunction)Spec_providedBy, METH_O, - Spec_providedBy__doc__}, - {"implementedBy", - (PyCFunction)Spec_implementedBy, METH_O, - Spec_implementedBy__doc__}, - {"isOrExtends", (PyCFunction)Spec_extends, METH_O, - Spec_extends__doc__}, - - {NULL, NULL} /* sentinel */ -}; - -static PyTypeObject SpecType = { - PyVarObject_HEAD_INIT(NULL, 0) - /* tp_name */ "_interface_coptimizations." - "SpecificationBase", - /* tp_basicsize */ 0, - /* tp_itemsize */ 0, - /* tp_dealloc */ (destructor)0, - /* tp_print */ (printfunc)0, - /* tp_getattr */ (getattrfunc)0, - /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ 0, - /* tp_repr */ (reprfunc)0, - /* tp_as_number */ 0, - /* tp_as_sequence */ 0, - /* tp_as_mapping */ 0, - /* tp_hash */ (hashfunc)0, - /* tp_call */ (ternaryfunc)Spec_call, - /* tp_str */ (reprfunc)0, - /* tp_getattro */ (getattrofunc)0, - /* tp_setattro */ (setattrofunc)0, - /* tp_as_buffer */ 0, - /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - "Base type for Specification objects", - /* tp_traverse */ (traverseproc)0, - /* tp_clear */ (inquiry)0, - /* tp_richcompare */ (richcmpfunc)0, - /* tp_weaklistoffset */ (long)0, - /* tp_iter */ (getiterfunc)0, - /* tp_iternext */ (iternextfunc)0, - /* tp_methods */ Spec_methods, -}; - -static PyObject * -OSD_descr_get(PyObject *self, PyObject *inst, PyObject *cls) -{ - PyObject *provides; - - if (inst == NULL) - return getObjectSpecification(NULL, cls); - - provides = PyObject_GetAttr(inst, str__provides__); - if (provides != NULL) - return provides; - PyErr_Clear(); - return implementedBy(NULL, cls); -} - -static PyTypeObject OSDType = { - PyVarObject_HEAD_INIT(NULL, 0) - /* tp_name */ "_interface_coptimizations." - "ObjectSpecificationDescriptor", - /* tp_basicsize */ 0, - /* tp_itemsize */ 0, - /* tp_dealloc */ (destructor)0, - /* tp_print */ (printfunc)0, - /* tp_getattr */ (getattrfunc)0, - /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ 0, - /* tp_repr */ (reprfunc)0, - /* tp_as_number */ 0, - /* tp_as_sequence */ 0, - /* tp_as_mapping */ 0, - /* tp_hash */ (hashfunc)0, - /* tp_call */ (ternaryfunc)0, - /* tp_str */ (reprfunc)0, - /* tp_getattro */ (getattrofunc)0, - /* tp_setattro */ (setattrofunc)0, - /* tp_as_buffer */ 0, - /* tp_flags */ Py_TPFLAGS_DEFAULT - | Py_TPFLAGS_BASETYPE , - "Object Specification Descriptor", - /* tp_traverse */ (traverseproc)0, - /* tp_clear */ (inquiry)0, - /* tp_richcompare */ (richcmpfunc)0, - /* tp_weaklistoffset */ (long)0, - /* tp_iter */ (getiterfunc)0, - /* tp_iternext */ (iternextfunc)0, - /* tp_methods */ 0, - /* tp_members */ 0, - /* tp_getset */ 0, - /* tp_base */ 0, - /* tp_dict */ 0, /* internal use */ - /* tp_descr_get */ (descrgetfunc)OSD_descr_get, -}; - -static PyObject * -CPB_descr_get(PyObject *self, PyObject *inst, PyObject *cls) -{ - PyObject *mycls, *implements; - - mycls = inst_attr(self, str_cls); - if (mycls == NULL) - return NULL; - - if (cls == mycls) - { - if (inst == NULL) - { - Py_INCREF(self); - return OBJECT(self); - } - - implements = inst_attr(self, str_implements); - Py_XINCREF(implements); - return implements; - } - - PyErr_SetObject(PyExc_AttributeError, str__provides__); - return NULL; -} - -static PyTypeObject CPBType = { - PyVarObject_HEAD_INIT(NULL, 0) - /* tp_name */ "_interface_coptimizations." - "ClassProvidesBase", - /* tp_basicsize */ 0, - /* tp_itemsize */ 0, - /* tp_dealloc */ (destructor)0, - /* tp_print */ (printfunc)0, - /* tp_getattr */ (getattrfunc)0, - /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ 0, - /* tp_repr */ (reprfunc)0, - /* tp_as_number */ 0, - /* tp_as_sequence */ 0, - /* tp_as_mapping */ 0, - /* tp_hash */ (hashfunc)0, - /* tp_call */ (ternaryfunc)0, - /* tp_str */ (reprfunc)0, - /* tp_getattro */ (getattrofunc)0, - /* tp_setattro */ (setattrofunc)0, - /* tp_as_buffer */ 0, - /* tp_flags */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - "C Base class for ClassProvides", - /* tp_traverse */ (traverseproc)0, - /* tp_clear */ (inquiry)0, - /* tp_richcompare */ (richcmpfunc)0, - /* tp_weaklistoffset */ (long)0, - /* tp_iter */ (getiterfunc)0, - /* tp_iternext */ (iternextfunc)0, - /* tp_methods */ 0, - /* tp_members */ 0, - /* tp_getset */ 0, - /* tp_base */ &SpecType, - /* tp_dict */ 0, /* internal use */ - /* tp_descr_get */ (descrgetfunc)CPB_descr_get, -}; - -/* ==================================================================== */ -/* ========== Begin: __call__ and __adapt__ =========================== */ - -/* - def __adapt__(self, obj): - """Adapt an object to the reciever - """ - if self.providedBy(obj): - return obj - - for hook in adapter_hooks: - adapter = hook(self, obj) - if adapter is not None: - return adapter - - -*/ -static PyObject * -__adapt__(PyObject *self, PyObject *obj) -{ - PyObject *decl, *args, *adapter; - int implements, i, l; - - decl = providedBy(NULL, obj); - if (decl == NULL) - return NULL; - - if (PyObject_TypeCheck(decl, &SpecType)) - { - PyObject *implied; - - implied = inst_attr(decl, str_implied); - if (implied == NULL) - { - Py_DECREF(decl); - return NULL; - } - - implements = PyDict_GetItem(implied, self) != NULL; - Py_DECREF(decl); - } - else - { - /* decl is probably a security proxy. We have to go the long way - around. - */ - PyObject *r; - r = PyObject_CallFunctionObjArgs(decl, self, NULL); - Py_DECREF(decl); - if (r == NULL) - return NULL; - implements = PyObject_IsTrue(r); - Py_DECREF(r); - } - - if (implements) - { - Py_INCREF(obj); - return obj; - } - - l = PyList_GET_SIZE(adapter_hooks); - args = PyTuple_New(2); - if (args == NULL) - return NULL; - Py_INCREF(self); - PyTuple_SET_ITEM(args, 0, self); - Py_INCREF(obj); - PyTuple_SET_ITEM(args, 1, obj); - for (i = 0; i < l; i++) - { - adapter = PyObject_CallObject(PyList_GET_ITEM(adapter_hooks, i), args); - if (adapter == NULL || adapter != Py_None) - { - Py_DECREF(args); - return adapter; - } - Py_DECREF(adapter); - } - - Py_DECREF(args); - - Py_INCREF(Py_None); - return Py_None; -} - -static struct PyMethodDef ib_methods[] = { - {"__adapt__", (PyCFunction)__adapt__, METH_O, - "Adapt an object to the reciever"}, - {NULL, NULL} /* sentinel */ -}; - -/* - def __call__(self, obj, alternate=_marker): - conform = getattr(obj, '__conform__', None) - if conform is not None: - adapter = self._call_conform(conform) - if adapter is not None: - return adapter - - adapter = self.__adapt__(obj) - - if adapter is not None: - return adapter - elif alternate is not _marker: - return alternate - else: - raise TypeError("Could not adapt", obj, self) -*/ -static PyObject * -ib_call(PyObject *self, PyObject *args, PyObject *kwargs) -{ - PyObject *conform, *obj, *alternate=NULL, *adapter; - - static char *kwlist[] = {"obj", "alternate", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|O", kwlist, - &obj, &alternate)) - return NULL; - - conform = PyObject_GetAttr(obj, str__conform__); - if (conform != NULL) - { - adapter = PyObject_CallMethodObjArgs(self, str_call_conform, - conform, NULL); - Py_DECREF(conform); - if (adapter == NULL || adapter != Py_None) - return adapter; - Py_DECREF(adapter); - } - else - PyErr_Clear(); - - adapter = __adapt__(self, obj); - if (adapter == NULL || adapter != Py_None) - return adapter; - Py_DECREF(adapter); - - if (alternate != NULL) - { - Py_INCREF(alternate); - return alternate; - } - - adapter = Py_BuildValue("sOO", "Could not adapt", obj, self); - if (adapter != NULL) - { - PyErr_SetObject(PyExc_TypeError, adapter); - Py_DECREF(adapter); - } - return NULL; -} - -static PyTypeObject InterfaceBase = { - PyVarObject_HEAD_INIT(NULL, 0) - /* tp_name */ "_zope_interface_coptimizations." - "InterfaceBase", - /* tp_basicsize */ 0, - /* tp_itemsize */ 0, - /* tp_dealloc */ (destructor)0, - /* tp_print */ (printfunc)0, - /* tp_getattr */ (getattrfunc)0, - /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ 0, - /* tp_repr */ (reprfunc)0, - /* tp_as_number */ 0, - /* tp_as_sequence */ 0, - /* tp_as_mapping */ 0, - /* tp_hash */ (hashfunc)0, - /* tp_call */ (ternaryfunc)ib_call, - /* tp_str */ (reprfunc)0, - /* tp_getattro */ (getattrofunc)0, - /* tp_setattro */ (setattrofunc)0, - /* tp_as_buffer */ 0, - /* tp_flags */ Py_TPFLAGS_DEFAULT - | Py_TPFLAGS_BASETYPE , - /* tp_doc */ "Interface base type providing __call__ and __adapt__", - /* tp_traverse */ (traverseproc)0, - /* tp_clear */ (inquiry)0, - /* tp_richcompare */ (richcmpfunc)0, - /* tp_weaklistoffset */ (long)0, - /* tp_iter */ (getiterfunc)0, - /* tp_iternext */ (iternextfunc)0, - /* tp_methods */ ib_methods, -}; - -/* =================== End: __call__ and __adapt__ ==================== */ -/* ==================================================================== */ - -/* ==================================================================== */ -/* ========================== Begin: Lookup Bases ===================== */ - -typedef struct { - PyObject_HEAD - PyObject *_cache; - PyObject *_mcache; - PyObject *_scache; -} lookup; - -typedef struct { - PyObject_HEAD - PyObject *_cache; - PyObject *_mcache; - PyObject *_scache; - PyObject *_verify_ro; - PyObject *_verify_generations; -} verify; - -static int -lookup_traverse(lookup *self, visitproc visit, void *arg) -{ - int vret; - - if (self->_cache) { - vret = visit(self->_cache, arg); - if (vret != 0) - return vret; - } - - if (self->_mcache) { - vret = visit(self->_mcache, arg); - if (vret != 0) - return vret; - } - - if (self->_scache) { - vret = visit(self->_scache, arg); - if (vret != 0) - return vret; - } - - return 0; -} - -static int -lookup_clear(lookup *self) -{ - Py_CLEAR(self->_cache); - Py_CLEAR(self->_mcache); - Py_CLEAR(self->_scache); - return 0; -} - -static void -lookup_dealloc(lookup *self) -{ - PyObject_GC_UnTrack((PyObject *)self); - lookup_clear(self); - Py_TYPE(self)->tp_free((PyObject*)self); -} - -/* - def changed(self, ignored=None): - self._cache.clear() - self._mcache.clear() - self._scache.clear() -*/ -static PyObject * -lookup_changed(lookup *self, PyObject *ignored) -{ - lookup_clear(self); - Py_INCREF(Py_None); - return Py_None; -} - -#define ASSURE_DICT(N) if (N == NULL) { N = PyDict_New(); \ - if (N == NULL) return NULL; \ - } - -/* - def _getcache(self, provided, name): - cache = self._cache.get(provided) - if cache is None: - cache = {} - self._cache[provided] = cache - if name: - c = cache.get(name) - if c is None: - c = {} - cache[name] = c - cache = c - return cache -*/ -static PyObject * -_subcache(PyObject *cache, PyObject *key) -{ - PyObject *subcache; - - subcache = PyDict_GetItem(cache, key); - if (subcache == NULL) - { - int status; - - subcache = PyDict_New(); - if (subcache == NULL) - return NULL; - status = PyDict_SetItem(cache, key, subcache); - Py_DECREF(subcache); - if (status < 0) - return NULL; - } - - return subcache; -} -static PyObject * -_getcache(lookup *self, PyObject *provided, PyObject *name) -{ - PyObject *cache; - - ASSURE_DICT(self->_cache); - cache = _subcache(self->_cache, provided); - if (cache == NULL) - return NULL; - - if (name != NULL && PyObject_IsTrue(name)) - cache = _subcache(cache, name); - - return cache; -} - - -/* - def lookup(self, required, provided, name=u'', default=None): - cache = self._getcache(provided, name) - if len(required) == 1: - result = cache.get(required[0], _not_in_mapping) - else: - result = cache.get(tuple(required), _not_in_mapping) - - if result is _not_in_mapping: - result = self._uncached_lookup(required, provided, name) - if len(required) == 1: - cache[required[0]] = result - else: - cache[tuple(required)] = result - - if result is None: - return default - - return result -*/ -static PyObject * -tuplefy(PyObject *v) -{ - if (! PyTuple_Check(v)) - { - v = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), v, NULL); - if (v == NULL) - return NULL; - } - else - Py_INCREF(v); - - return v; -} -static PyObject * -_lookup(lookup *self, - PyObject *required, PyObject *provided, PyObject *name, - PyObject *default_) -{ - PyObject *result, *key, *cache; - -#ifdef PY3K - if ( name && !PyUnicode_Check(name) ) -#else - if ( name && !PyString_Check(name) && !PyUnicode_Check(name) ) -#endif - { - PyErr_SetString(PyExc_ValueError, - "name is not a string or unicode"); - return NULL; - } - cache = _getcache(self, provided, name); - if (cache == NULL) - return NULL; - - required = tuplefy(required); - if (required == NULL) - return NULL; - - if (PyTuple_GET_SIZE(required) == 1) - key = PyTuple_GET_ITEM(required, 0); - else - key = required; - - result = PyDict_GetItem(cache, key); - if (result == NULL) - { - int status; - - result = PyObject_CallMethodObjArgs(OBJECT(self), str_uncached_lookup, - required, provided, name, NULL); - if (result == NULL) - { - Py_DECREF(required); - return NULL; - } - status = PyDict_SetItem(cache, key, result); - Py_DECREF(required); - if (status < 0) - { - Py_DECREF(result); - return NULL; - } - } - else - { - Py_INCREF(result); - Py_DECREF(required); - } - - if (result == Py_None && default_ != NULL) - { - Py_DECREF(Py_None); - Py_INCREF(default_); - return default_; - } - - return result; -} -static PyObject * -lookup_lookup(lookup *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"required", "provided", "name", "default", NULL}; - PyObject *required, *provided, *name=NULL, *default_=NULL; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, - &required, &provided, &name, &default_)) - return NULL; - - return _lookup(self, required, provided, name, default_); -} - - -/* - def lookup1(self, required, provided, name=u'', default=None): - cache = self._getcache(provided, name) - result = cache.get(required, _not_in_mapping) - if result is _not_in_mapping: - return self.lookup((required, ), provided, name, default) - - if result is None: - return default - - return result -*/ -static PyObject * -_lookup1(lookup *self, - PyObject *required, PyObject *provided, PyObject *name, - PyObject *default_) -{ - PyObject *result, *cache; - -#ifdef PY3K - if ( name && !PyUnicode_Check(name) ) -#else - if ( name && !PyString_Check(name) && !PyUnicode_Check(name) ) -#endif - { - PyErr_SetString(PyExc_ValueError, - "name is not a string or unicode"); - return NULL; - } - - cache = _getcache(self, provided, name); - if (cache == NULL) - return NULL; - - result = PyDict_GetItem(cache, required); - if (result == NULL) - { - PyObject *tup; - - tup = PyTuple_New(1); - if (tup == NULL) - return NULL; - Py_INCREF(required); - PyTuple_SET_ITEM(tup, 0, required); - result = _lookup(self, tup, provided, name, default_); - Py_DECREF(tup); - } - else - { - if (result == Py_None && default_ != NULL) - { - result = default_; - } - Py_INCREF(result); - } - - return result; -} -static PyObject * -lookup_lookup1(lookup *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"required", "provided", "name", "default", NULL}; - PyObject *required, *provided, *name=NULL, *default_=NULL; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, - &required, &provided, &name, &default_)) - return NULL; - - return _lookup1(self, required, provided, name, default_); -} - -/* - def adapter_hook(self, provided, object, name=u'', default=None): - required = providedBy(object) - cache = self._getcache(provided, name) - factory = cache.get(required, _not_in_mapping) - if factory is _not_in_mapping: - factory = self.lookup((required, ), provided, name) - - if factory is not None: - result = factory(object) - if result is not None: - return result - - return default -*/ -static PyObject * -_adapter_hook(lookup *self, - PyObject *provided, PyObject *object, PyObject *name, - PyObject *default_) -{ - PyObject *required, *factory, *result; - -#ifdef PY3K - if ( name && !PyUnicode_Check(name) ) -#else - if ( name && !PyString_Check(name) && !PyUnicode_Check(name) ) -#endif - { - PyErr_SetString(PyExc_ValueError, - "name is not a string or unicode"); - return NULL; - } - - required = providedBy(NULL, object); - if (required == NULL) - return NULL; - - factory = _lookup1(self, required, provided, name, Py_None); - Py_DECREF(required); - if (factory == NULL) - return NULL; - - if (factory != Py_None) - { - result = PyObject_CallFunctionObjArgs(factory, object, NULL); - Py_DECREF(factory); - if (result == NULL || result != Py_None) - return result; - } - else - result = factory; /* None */ - - if (default_ == NULL || default_ == result) /* No default specified, */ - return result; /* Return None. result is owned None */ - - Py_DECREF(result); - Py_INCREF(default_); - - return default_; -} -static PyObject * -lookup_adapter_hook(lookup *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"provided", "object", "name", "default", NULL}; - PyObject *object, *provided, *name=NULL, *default_=NULL; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, - &provided, &object, &name, &default_)) - return NULL; - - return _adapter_hook(self, provided, object, name, default_); -} - -static PyObject * -lookup_queryAdapter(lookup *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"object", "provided", "name", "default", NULL}; - PyObject *object, *provided, *name=NULL, *default_=NULL; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, - &object, &provided, &name, &default_)) - return NULL; - - return _adapter_hook(self, provided, object, name, default_); -} - -/* - def lookupAll(self, required, provided): - cache = self._mcache.get(provided) - if cache is None: - cache = {} - self._mcache[provided] = cache - - required = tuple(required) - result = cache.get(required, _not_in_mapping) - if result is _not_in_mapping: - result = self._uncached_lookupAll(required, provided) - cache[required] = result - - return result -*/ -static PyObject * -_lookupAll(lookup *self, PyObject *required, PyObject *provided) -{ - PyObject *cache, *result; - - ASSURE_DICT(self->_mcache); - cache = _subcache(self->_mcache, provided); - if (cache == NULL) - return NULL; - - required = tuplefy(required); - if (required == NULL) - return NULL; - - result = PyDict_GetItem(cache, required); - if (result == NULL) - { - int status; - - result = PyObject_CallMethodObjArgs(OBJECT(self), str_uncached_lookupAll, - required, provided, NULL); - if (result == NULL) - { - Py_DECREF(required); - return NULL; - } - status = PyDict_SetItem(cache, required, result); - Py_DECREF(required); - if (status < 0) - { - Py_DECREF(result); - return NULL; - } - } - else - { - Py_INCREF(result); - Py_DECREF(required); - } - - return result; -} -static PyObject * -lookup_lookupAll(lookup *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"required", "provided", NULL}; - PyObject *required, *provided; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, - &required, &provided)) - return NULL; - - return _lookupAll(self, required, provided); -} - -/* - def subscriptions(self, required, provided): - cache = self._scache.get(provided) - if cache is None: - cache = {} - self._scache[provided] = cache - - required = tuple(required) - result = cache.get(required, _not_in_mapping) - if result is _not_in_mapping: - result = self._uncached_subscriptions(required, provided) - cache[required] = result - - return result -*/ -static PyObject * -_subscriptions(lookup *self, PyObject *required, PyObject *provided) -{ - PyObject *cache, *result; - - ASSURE_DICT(self->_scache); - cache = _subcache(self->_scache, provided); - if (cache == NULL) - return NULL; - - required = tuplefy(required); - if (required == NULL) - return NULL; - - result = PyDict_GetItem(cache, required); - if (result == NULL) - { - int status; - - result = PyObject_CallMethodObjArgs( - OBJECT(self), str_uncached_subscriptions, - required, provided, NULL); - if (result == NULL) - { - Py_DECREF(required); - return NULL; - } - status = PyDict_SetItem(cache, required, result); - Py_DECREF(required); - if (status < 0) - { - Py_DECREF(result); - return NULL; - } - } - else - { - Py_INCREF(result); - Py_DECREF(required); - } - - return result; -} -static PyObject * -lookup_subscriptions(lookup *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"required", "provided", NULL}; - PyObject *required, *provided; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, - &required, &provided)) - return NULL; - - return _subscriptions(self, required, provided); -} - -static struct PyMethodDef lookup_methods[] = { - {"changed", (PyCFunction)lookup_changed, METH_O, ""}, - {"lookup", (PyCFunction)lookup_lookup, METH_KEYWORDS | METH_VARARGS, ""}, - {"lookup1", (PyCFunction)lookup_lookup1, METH_KEYWORDS | METH_VARARGS, ""}, - {"queryAdapter", (PyCFunction)lookup_queryAdapter, METH_KEYWORDS | METH_VARARGS, ""}, - {"adapter_hook", (PyCFunction)lookup_adapter_hook, METH_KEYWORDS | METH_VARARGS, ""}, - {"lookupAll", (PyCFunction)lookup_lookupAll, METH_KEYWORDS | METH_VARARGS, ""}, - {"subscriptions", (PyCFunction)lookup_subscriptions, METH_KEYWORDS | METH_VARARGS, ""}, - {NULL, NULL} /* sentinel */ -}; - -static PyTypeObject LookupBase = { - PyVarObject_HEAD_INIT(NULL, 0) - /* tp_name */ "_zope_interface_coptimizations." - "LookupBase", - /* tp_basicsize */ sizeof(lookup), - /* tp_itemsize */ 0, - /* tp_dealloc */ (destructor)&lookup_dealloc, - /* tp_print */ (printfunc)0, - /* tp_getattr */ (getattrfunc)0, - /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ 0, - /* tp_repr */ (reprfunc)0, - /* tp_as_number */ 0, - /* tp_as_sequence */ 0, - /* tp_as_mapping */ 0, - /* tp_hash */ (hashfunc)0, - /* tp_call */ (ternaryfunc)0, - /* tp_str */ (reprfunc)0, - /* tp_getattro */ (getattrofunc)0, - /* tp_setattro */ (setattrofunc)0, - /* tp_as_buffer */ 0, - /* tp_flags */ Py_TPFLAGS_DEFAULT - | Py_TPFLAGS_BASETYPE - | Py_TPFLAGS_HAVE_GC, - /* tp_doc */ "", - /* tp_traverse */ (traverseproc)lookup_traverse, - /* tp_clear */ (inquiry)lookup_clear, - /* tp_richcompare */ (richcmpfunc)0, - /* tp_weaklistoffset */ (long)0, - /* tp_iter */ (getiterfunc)0, - /* tp_iternext */ (iternextfunc)0, - /* tp_methods */ lookup_methods, -}; - -static int -verifying_traverse(verify *self, visitproc visit, void *arg) -{ - int vret; - - vret = lookup_traverse((lookup *)self, visit, arg); - if (vret != 0) - return vret; - - if (self->_verify_ro) { - vret = visit(self->_verify_ro, arg); - if (vret != 0) - return vret; - } - if (self->_verify_generations) { - vret = visit(self->_verify_generations, arg); - if (vret != 0) - return vret; - } - - return 0; -} - -static int -verifying_clear(verify *self) -{ - lookup_clear((lookup *)self); - Py_CLEAR(self->_verify_generations); - Py_CLEAR(self->_verify_ro); - return 0; -} - - -static void -verifying_dealloc(verify *self) -{ - PyObject_GC_UnTrack((PyObject *)self); - verifying_clear(self); - Py_TYPE(self)->tp_free((PyObject*)self); -} - -/* - def changed(self, originally_changed): - super(VerifyingBasePy, self).changed(originally_changed) - self._verify_ro = self._registry.ro[1:] - self._verify_generations = [r._generation for r in self._verify_ro] -*/ -static PyObject * -_generations_tuple(PyObject *ro) -{ - int i, l; - PyObject *generations; - - l = PyTuple_GET_SIZE(ro); - generations = PyTuple_New(l); - for (i=0; i < l; i++) - { - PyObject *generation; - - generation = PyObject_GetAttr(PyTuple_GET_ITEM(ro, i), str_generation); - if (generation == NULL) - { - Py_DECREF(generations); - return NULL; - } - PyTuple_SET_ITEM(generations, i, generation); - } - - return generations; -} -static PyObject * -verifying_changed(verify *self, PyObject *ignored) -{ - PyObject *t, *ro; - - verifying_clear(self); - - t = PyObject_GetAttr(OBJECT(self), str_registry); - if (t == NULL) - return NULL; - ro = PyObject_GetAttr(t, strro); - Py_DECREF(t); - if (ro == NULL) - return NULL; - - t = PyObject_CallFunctionObjArgs(OBJECT(&PyTuple_Type), ro, NULL); - Py_DECREF(ro); - if (t == NULL) - return NULL; - - ro = PyTuple_GetSlice(t, 1, PyTuple_GET_SIZE(t)); - Py_DECREF(t); - if (ro == NULL) - return NULL; - - self->_verify_generations = _generations_tuple(ro); - if (self->_verify_generations == NULL) - { - Py_DECREF(ro); - return NULL; - } - - self->_verify_ro = ro; - - Py_INCREF(Py_None); - return Py_None; -} - -/* - def _verify(self): - if ([r._generation for r in self._verify_ro] - != self._verify_generations): - self.changed(None) -*/ -static int -_verify(verify *self) -{ - PyObject *changed_result; - - if (self->_verify_ro != NULL && self->_verify_generations != NULL) - { - PyObject *generations; - int changed; - - generations = _generations_tuple(self->_verify_ro); - if (generations == NULL) - return -1; - - changed = PyObject_RichCompareBool(self->_verify_generations, - generations, Py_NE); - Py_DECREF(generations); - if (changed == -1) - return -1; - - if (changed == 0) - return 0; - } - - changed_result = PyObject_CallMethodObjArgs(OBJECT(self), strchanged, - Py_None, NULL); - if (changed_result == NULL) - return -1; - - Py_DECREF(changed_result); - return 0; -} - -static PyObject * -verifying_lookup(verify *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"required", "provided", "name", "default", NULL}; - PyObject *required, *provided, *name=NULL, *default_=NULL; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, - &required, &provided, &name, &default_)) - return NULL; - - if (_verify(self) < 0) - return NULL; - - return _lookup((lookup *)self, required, provided, name, default_); -} - -static PyObject * -verifying_lookup1(verify *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"required", "provided", "name", "default", NULL}; - PyObject *required, *provided, *name=NULL, *default_=NULL; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, - &required, &provided, &name, &default_)) - return NULL; - - if (_verify(self) < 0) - return NULL; - - return _lookup1((lookup *)self, required, provided, name, default_); -} - -static PyObject * -verifying_adapter_hook(verify *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"provided", "object", "name", "default", NULL}; - PyObject *object, *provided, *name=NULL, *default_=NULL; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, - &provided, &object, &name, &default_)) - return NULL; - - if (_verify(self) < 0) - return NULL; - - return _adapter_hook((lookup *)self, provided, object, name, default_); -} - -static PyObject * -verifying_queryAdapter(verify *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"object", "provided", "name", "default", NULL}; - PyObject *object, *provided, *name=NULL, *default_=NULL; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, - &object, &provided, &name, &default_)) - return NULL; - - if (_verify(self) < 0) - return NULL; - - return _adapter_hook((lookup *)self, provided, object, name, default_); -} - -static PyObject * -verifying_lookupAll(verify *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"required", "provided", NULL}; - PyObject *required, *provided; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, - &required, &provided)) - return NULL; - - if (_verify(self) < 0) - return NULL; - - return _lookupAll((lookup *)self, required, provided); -} - -static PyObject * -verifying_subscriptions(verify *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = {"required", "provided", NULL}; - PyObject *required, *provided; - - if (! PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, - &required, &provided)) - return NULL; - - if (_verify(self) < 0) - return NULL; - - return _subscriptions((lookup *)self, required, provided); -} - -static struct PyMethodDef verifying_methods[] = { - {"changed", (PyCFunction)verifying_changed, METH_O, ""}, - {"lookup", (PyCFunction)verifying_lookup, METH_KEYWORDS | METH_VARARGS, ""}, - {"lookup1", (PyCFunction)verifying_lookup1, METH_KEYWORDS | METH_VARARGS, ""}, - {"queryAdapter", (PyCFunction)verifying_queryAdapter, METH_KEYWORDS | METH_VARARGS, ""}, - {"adapter_hook", (PyCFunction)verifying_adapter_hook, METH_KEYWORDS | METH_VARARGS, ""}, - {"lookupAll", (PyCFunction)verifying_lookupAll, METH_KEYWORDS | METH_VARARGS, ""}, - {"subscriptions", (PyCFunction)verifying_subscriptions, METH_KEYWORDS | METH_VARARGS, ""}, - {NULL, NULL} /* sentinel */ -}; - -static PyTypeObject VerifyingBase = { - PyVarObject_HEAD_INIT(NULL, 0) - /* tp_name */ "_zope_interface_coptimizations." - "VerifyingBase", - /* tp_basicsize */ sizeof(verify), - /* tp_itemsize */ 0, - /* tp_dealloc */ (destructor)&verifying_dealloc, - /* tp_print */ (printfunc)0, - /* tp_getattr */ (getattrfunc)0, - /* tp_setattr */ (setattrfunc)0, - /* tp_compare */ 0, - /* tp_repr */ (reprfunc)0, - /* tp_as_number */ 0, - /* tp_as_sequence */ 0, - /* tp_as_mapping */ 0, - /* tp_hash */ (hashfunc)0, - /* tp_call */ (ternaryfunc)0, - /* tp_str */ (reprfunc)0, - /* tp_getattro */ (getattrofunc)0, - /* tp_setattro */ (setattrofunc)0, - /* tp_as_buffer */ 0, - /* tp_flags */ Py_TPFLAGS_DEFAULT - | Py_TPFLAGS_BASETYPE - | Py_TPFLAGS_HAVE_GC, - /* tp_doc */ "", - /* tp_traverse */ (traverseproc)verifying_traverse, - /* tp_clear */ (inquiry)verifying_clear, - /* tp_richcompare */ (richcmpfunc)0, - /* tp_weaklistoffset */ (long)0, - /* tp_iter */ (getiterfunc)0, - /* tp_iternext */ (iternextfunc)0, - /* tp_methods */ verifying_methods, - /* tp_members */ 0, - /* tp_getset */ 0, - /* tp_base */ &LookupBase, -}; - -/* ========================== End: Lookup Bases ======================= */ -/* ==================================================================== */ - - - -static struct PyMethodDef m_methods[] = { - {"implementedBy", (PyCFunction)implementedBy, METH_O, - "Interfaces implemented by a class or factory.\n" - "Raises TypeError if argument is neither a class nor a callable."}, - {"getObjectSpecification", (PyCFunction)getObjectSpecification, METH_O, - "Get an object's interfaces (internal api)"}, - {"providedBy", (PyCFunction)providedBy, METH_O, - "Get an object's interfaces"}, - - {NULL, (PyCFunction)NULL, 0, NULL} /* sentinel */ -}; - -#if PY_MAJOR_VERSION >= 3 -static char module_doc[] = "C optimizations for zope.interface\n\n"; - -static struct PyModuleDef _zic_module = { - PyModuleDef_HEAD_INIT, - "_zope_interface_coptimizations", - module_doc, - -1, - m_methods, - NULL, - NULL, - NULL, - NULL -}; -#endif - -static PyObject * -init(void) -{ - PyObject *m; - -#if PY_MAJOR_VERSION < 3 -#define DEFINE_STRING(S) \ - if(! (str ## S = PyString_FromString(# S))) return NULL -#else -#define DEFINE_STRING(S) \ - if(! (str ## S = PyUnicode_FromString(# S))) return NULL -#endif - - DEFINE_STRING(__dict__); - DEFINE_STRING(__implemented__); - DEFINE_STRING(__provides__); - DEFINE_STRING(__class__); - DEFINE_STRING(__providedBy__); - DEFINE_STRING(extends); - DEFINE_STRING(_implied); - DEFINE_STRING(_implements); - DEFINE_STRING(_cls); - DEFINE_STRING(__conform__); - DEFINE_STRING(_call_conform); - DEFINE_STRING(_uncached_lookup); - DEFINE_STRING(_uncached_lookupAll); - DEFINE_STRING(_uncached_subscriptions); - DEFINE_STRING(_registry); - DEFINE_STRING(_generation); - DEFINE_STRING(ro); - DEFINE_STRING(changed); -#undef DEFINE_STRING - adapter_hooks = PyList_New(0); - if (adapter_hooks == NULL) - return NULL; - - /* Initialize types: */ - SpecType.tp_new = PyBaseObject_Type.tp_new; - if (PyType_Ready(&SpecType) < 0) - return NULL; - OSDType.tp_new = PyBaseObject_Type.tp_new; - if (PyType_Ready(&OSDType) < 0) - return NULL; - CPBType.tp_new = PyBaseObject_Type.tp_new; - if (PyType_Ready(&CPBType) < 0) - return NULL; - - InterfaceBase.tp_new = PyBaseObject_Type.tp_new; - if (PyType_Ready(&InterfaceBase) < 0) - return NULL; - - LookupBase.tp_new = PyBaseObject_Type.tp_new; - if (PyType_Ready(&LookupBase) < 0) - return NULL; - - VerifyingBase.tp_new = PyBaseObject_Type.tp_new; - if (PyType_Ready(&VerifyingBase) < 0) - return NULL; - - #if PY_MAJOR_VERSION < 3 - /* Create the module and add the functions */ - m = Py_InitModule3("_zope_interface_coptimizations", m_methods, - "C optimizations for zope.interface\n\n"); - #else - m = PyModule_Create(&_zic_module); - #endif - if (m == NULL) - return NULL; - - /* Add types: */ - if (PyModule_AddObject(m, "SpecificationBase", OBJECT(&SpecType)) < 0) - return NULL; - if (PyModule_AddObject(m, "ObjectSpecificationDescriptor", - (PyObject *)&OSDType) < 0) - return NULL; - if (PyModule_AddObject(m, "ClassProvidesBase", OBJECT(&CPBType)) < 0) - return NULL; - if (PyModule_AddObject(m, "InterfaceBase", OBJECT(&InterfaceBase)) < 0) - return NULL; - if (PyModule_AddObject(m, "LookupBase", OBJECT(&LookupBase)) < 0) - return NULL; - if (PyModule_AddObject(m, "VerifyingBase", OBJECT(&VerifyingBase)) < 0) - return NULL; - if (PyModule_AddObject(m, "adapter_hooks", adapter_hooks) < 0) - return NULL; - return m; -} - -PyMODINIT_FUNC -#if PY_MAJOR_VERSION < 3 -init_zope_interface_coptimizations(void) -{ - init(); -} -#else -PyInit__zope_interface_coptimizations(void) -{ - return init(); -} -#endif diff --git a/lib/zope/interface/_zope_interface_coptimizations.cp37-win32.pyd b/lib/zope/interface/_zope_interface_coptimizations.cp37-win32.pyd deleted file mode 100644 index c959bc07d6f835e5cd25e677833482217181dc4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22528 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 WjbY=E@tE<r@dR|})9aIQ;Qs;?k$~3# diff --git a/lib/zope/interface/adapter.py b/lib/zope/interface/adapter.py deleted file mode 100644 index aae3155..0000000 --- a/lib/zope/interface/adapter.py +++ /dev/null @@ -1,712 +0,0 @@ -############################################################################## -# -# Copyright (c) 2004 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Adapter management -""" -import weakref - -from zope.interface import implementer -from zope.interface import providedBy -from zope.interface import Interface -from zope.interface import ro -from zope.interface.interfaces import IAdapterRegistry - -from zope.interface._compat import _normalize_name -from zope.interface._compat import STRING_TYPES - -_BLANK = u'' - -class BaseAdapterRegistry(object): - - # List of methods copied from lookup sub-objects: - _delegated = ('lookup', 'queryMultiAdapter', 'lookup1', 'queryAdapter', - 'adapter_hook', 'lookupAll', 'names', - 'subscriptions', 'subscribers') - - # All registries maintain a generation that can be used by verifying - # registries - _generation = 0 - - def __init__(self, bases=()): - - # The comments here could be improved. Possibly this bit needs - # explaining in a separate document, as the comments here can - # be quite confusing. /regebro - - # {order -> {required -> {provided -> {name -> value}}}} - # Here "order" is actually an index in a list, "required" and - # "provided" are interfaces, and "required" is really a nested - # key. So, for example: - # for order == 0 (that is, self._adapters[0]), we have: - # {provided -> {name -> value}} - # but for order == 2 (that is, self._adapters[2]), we have: - # {r1 -> {r2 -> {provided -> {name -> value}}}} - # - self._adapters = [] - - # {order -> {required -> {provided -> {name -> [value]}}}} - # where the remarks about adapters above apply - self._subscribers = [] - - # Set, with a reference count, keeping track of the interfaces - # for which we have provided components: - self._provided = {} - - # Create ``_v_lookup`` object to perform lookup. We make this a - # separate object to to make it easier to implement just the - # lookup functionality in C. This object keeps track of cache - # invalidation data in two kinds of registries. - - # Invalidating registries have caches that are invalidated - # when they or their base registies change. An invalidating - # registry can only have invalidating registries as bases. - # See LookupBaseFallback below for the pertinent logic. - - # Verifying registies can't rely on getting invalidation messages, - # so have to check the generations of base registries to determine - # if their cache data are current. See VerifyingBasePy below - # for the pertinent object. - self._createLookup() - - # Setting the bases causes the registries described above - # to be initialized (self._setBases -> self.changed -> - # self._v_lookup.changed). - - self.__bases__ = bases - - def _setBases(self, bases): - self.__dict__['__bases__'] = bases - self.ro = ro.ro(self) - self.changed(self) - - __bases__ = property(lambda self: self.__dict__['__bases__'], - lambda self, bases: self._setBases(bases), - ) - - def _createLookup(self): - self._v_lookup = self.LookupClass(self) - for name in self._delegated: - self.__dict__[name] = getattr(self._v_lookup, name) - - def changed(self, originally_changed): - self._generation += 1 - self._v_lookup.changed(originally_changed) - - def register(self, required, provided, name, value): - if not isinstance(name, STRING_TYPES): - raise ValueError('name is not a string') - if value is None: - self.unregister(required, provided, name, value) - return - - required = tuple(map(_convert_None_to_Interface, required)) - name = _normalize_name(name) - order = len(required) - byorder = self._adapters - while len(byorder) <= order: - byorder.append({}) - components = byorder[order] - key = required + (provided,) - - for k in key: - d = components.get(k) - if d is None: - d = {} - components[k] = d - components = d - - if components.get(name) is value: - return - - components[name] = value - - n = self._provided.get(provided, 0) + 1 - self._provided[provided] = n - if n == 1: - self._v_lookup.add_extendor(provided) - - self.changed(self) - - def registered(self, required, provided, name=_BLANK): - required = tuple(map(_convert_None_to_Interface, required)) - name = _normalize_name(name) - order = len(required) - byorder = self._adapters - if len(byorder) <= order: - return None - - components = byorder[order] - key = required + (provided,) - - for k in key: - d = components.get(k) - if d is None: - return None - components = d - - return components.get(name) - - def unregister(self, required, provided, name, value=None): - required = tuple(map(_convert_None_to_Interface, required)) - order = len(required) - byorder = self._adapters - if order >= len(byorder): - return False - components = byorder[order] - key = required + (provided,) - - # Keep track of how we got to `components`: - lookups = [] - for k in key: - d = components.get(k) - if d is None: - return - lookups.append((components, k)) - components = d - - old = components.get(name) - if old is None: - return - if (value is not None) and (old is not value): - return - - del components[name] - if not components: - # Clean out empty containers, since we don't want our keys - # to reference global objects (interfaces) unnecessarily. - # This is often a problem when an interface is slated for - # removal; a hold-over entry in the registry can make it - # difficult to remove such interfaces. - for comp, k in reversed(lookups): - d = comp[k] - if d: - break - else: - del comp[k] - while byorder and not byorder[-1]: - del byorder[-1] - n = self._provided[provided] - 1 - if n == 0: - del self._provided[provided] - self._v_lookup.remove_extendor(provided) - else: - self._provided[provided] = n - - self.changed(self) - - def subscribe(self, required, provided, value): - required = tuple(map(_convert_None_to_Interface, required)) - name = _BLANK - order = len(required) - byorder = self._subscribers - while len(byorder) <= order: - byorder.append({}) - components = byorder[order] - key = required + (provided,) - - for k in key: - d = components.get(k) - if d is None: - d = {} - components[k] = d - components = d - - components[name] = components.get(name, ()) + (value, ) - - if provided is not None: - n = self._provided.get(provided, 0) + 1 - self._provided[provided] = n - if n == 1: - self._v_lookup.add_extendor(provided) - - self.changed(self) - - def unsubscribe(self, required, provided, value=None): - required = tuple(map(_convert_None_to_Interface, required)) - order = len(required) - byorder = self._subscribers - if order >= len(byorder): - return - components = byorder[order] - key = required + (provided,) - - # Keep track of how we got to `components`: - lookups = [] - for k in key: - d = components.get(k) - if d is None: - return - lookups.append((components, k)) - components = d - - old = components.get(_BLANK) - if not old: - # this is belt-and-suspenders against the failure of cleanup below - return # pragma: no cover - - if value is None: - new = () - else: - new = tuple([v for v in old if v != value]) - - if new == old: - return - - if new: - components[_BLANK] = new - else: - # Instead of setting components[_BLANK] = new, we clean out - # empty containers, since we don't want our keys to - # reference global objects (interfaces) unnecessarily. This - # is often a problem when an interface is slated for - # removal; a hold-over entry in the registry can make it - # difficult to remove such interfaces. - del components[_BLANK] - for comp, k in reversed(lookups): - d = comp[k] - if d: - break - else: - del comp[k] - while byorder and not byorder[-1]: - del byorder[-1] - - if provided is not None: - n = self._provided[provided] + len(new) - len(old) - if n == 0: - del self._provided[provided] - self._v_lookup.remove_extendor(provided) - - self.changed(self) - - # XXX hack to fake out twisted's use of a private api. We need to get them - # to use the new registed method. - def get(self, _): # pragma: no cover - class XXXTwistedFakeOut: - selfImplied = {} - return XXXTwistedFakeOut - - -_not_in_mapping = object() -class LookupBaseFallback(object): - - def __init__(self): - self._cache = {} - self._mcache = {} - self._scache = {} - - def changed(self, ignored=None): - self._cache.clear() - self._mcache.clear() - self._scache.clear() - - def _getcache(self, provided, name): - cache = self._cache.get(provided) - if cache is None: - cache = {} - self._cache[provided] = cache - if name: - c = cache.get(name) - if c is None: - c = {} - cache[name] = c - cache = c - return cache - - def lookup(self, required, provided, name=_BLANK, default=None): - if not isinstance(name, STRING_TYPES): - raise ValueError('name is not a string') - cache = self._getcache(provided, name) - required = tuple(required) - if len(required) == 1: - result = cache.get(required[0], _not_in_mapping) - else: - result = cache.get(tuple(required), _not_in_mapping) - - if result is _not_in_mapping: - result = self._uncached_lookup(required, provided, name) - if len(required) == 1: - cache[required[0]] = result - else: - cache[tuple(required)] = result - - if result is None: - return default - - return result - - def lookup1(self, required, provided, name=_BLANK, default=None): - if not isinstance(name, STRING_TYPES): - raise ValueError('name is not a string') - cache = self._getcache(provided, name) - result = cache.get(required, _not_in_mapping) - if result is _not_in_mapping: - return self.lookup((required, ), provided, name, default) - - if result is None: - return default - - return result - - def queryAdapter(self, object, provided, name=_BLANK, default=None): - return self.adapter_hook(provided, object, name, default) - - def adapter_hook(self, provided, object, name=_BLANK, default=None): - if not isinstance(name, STRING_TYPES): - raise ValueError('name is not a string') - required = providedBy(object) - cache = self._getcache(provided, name) - factory = cache.get(required, _not_in_mapping) - if factory is _not_in_mapping: - factory = self.lookup((required, ), provided, name) - - if factory is not None: - result = factory(object) - if result is not None: - return result - - return default - - def lookupAll(self, required, provided): - cache = self._mcache.get(provided) - if cache is None: - cache = {} - self._mcache[provided] = cache - - required = tuple(required) - result = cache.get(required, _not_in_mapping) - if result is _not_in_mapping: - result = self._uncached_lookupAll(required, provided) - cache[required] = result - - return result - - - def subscriptions(self, required, provided): - cache = self._scache.get(provided) - if cache is None: - cache = {} - self._scache[provided] = cache - - required = tuple(required) - result = cache.get(required, _not_in_mapping) - if result is _not_in_mapping: - result = self._uncached_subscriptions(required, provided) - cache[required] = result - - return result - -LookupBasePy = LookupBaseFallback # BBB - -try: - from zope.interface._zope_interface_coptimizations import LookupBase -except ImportError: - LookupBase = LookupBaseFallback - - -class VerifyingBaseFallback(LookupBaseFallback): - # Mixin for lookups against registries which "chain" upwards, and - # whose lookups invalidate their own caches whenever a parent registry - # bumps its own '_generation' counter. E.g., used by - # zope.component.persistentregistry - - def changed(self, originally_changed): - LookupBaseFallback.changed(self, originally_changed) - self._verify_ro = self._registry.ro[1:] - self._verify_generations = [r._generation for r in self._verify_ro] - - def _verify(self): - if ([r._generation for r in self._verify_ro] - != self._verify_generations): - self.changed(None) - - def _getcache(self, provided, name): - self._verify() - return LookupBaseFallback._getcache(self, provided, name) - - def lookupAll(self, required, provided): - self._verify() - return LookupBaseFallback.lookupAll(self, required, provided) - - def subscriptions(self, required, provided): - self._verify() - return LookupBaseFallback.subscriptions(self, required, provided) - -VerifyingBasePy = VerifyingBaseFallback #BBB - -try: - from zope.interface._zope_interface_coptimizations import VerifyingBase -except ImportError: - VerifyingBase = VerifyingBaseFallback - - -class AdapterLookupBase(object): - - def __init__(self, registry): - self._registry = registry - self._required = {} - self.init_extendors() - super(AdapterLookupBase, self).__init__() - - def changed(self, ignored=None): - super(AdapterLookupBase, self).changed(None) - for r in self._required.keys(): - r = r() - if r is not None: - r.unsubscribe(self) - self._required.clear() - - - # Extendors - # --------- - - # When given an target interface for an adapter lookup, we need to consider - # adapters for interfaces that extend the target interface. This is - # what the extendors dictionary is about. It tells us all of the - # interfaces that extend an interface for which there are adapters - # registered. - - # We could separate this by order and name, thus reducing the - # number of provided interfaces to search at run time. The tradeoff, - # however, is that we have to store more information. For example, - # if the same interface is provided for multiple names and if the - # interface extends many interfaces, we'll have to keep track of - # a fair bit of information for each name. It's better to - # be space efficient here and be time efficient in the cache - # implementation. - - # TODO: add invalidation when a provided interface changes, in case - # the interface's __iro__ has changed. This is unlikely enough that - # we'll take our chances for now. - - def init_extendors(self): - self._extendors = {} - for p in self._registry._provided: - self.add_extendor(p) - - def add_extendor(self, provided): - _extendors = self._extendors - for i in provided.__iro__: - extendors = _extendors.get(i, ()) - _extendors[i] = ( - [e for e in extendors if provided.isOrExtends(e)] - + - [provided] - + - [e for e in extendors if not provided.isOrExtends(e)] - ) - - def remove_extendor(self, provided): - _extendors = self._extendors - for i in provided.__iro__: - _extendors[i] = [e for e in _extendors.get(i, ()) - if e != provided] - - - def _subscribe(self, *required): - _refs = self._required - for r in required: - ref = r.weakref() - if ref not in _refs: - r.subscribe(self) - _refs[ref] = 1 - - def _uncached_lookup(self, required, provided, name=_BLANK): - required = tuple(required) - result = None - order = len(required) - for registry in self._registry.ro: - byorder = registry._adapters - if order >= len(byorder): - continue - - extendors = registry._v_lookup._extendors.get(provided) - if not extendors: - continue - - components = byorder[order] - result = _lookup(components, required, extendors, name, 0, - order) - if result is not None: - break - - self._subscribe(*required) - - return result - - def queryMultiAdapter(self, objects, provided, name=_BLANK, default=None): - factory = self.lookup(map(providedBy, objects), provided, name) - if factory is None: - return default - - result = factory(*objects) - if result is None: - return default - - return result - - def _uncached_lookupAll(self, required, provided): - required = tuple(required) - order = len(required) - result = {} - for registry in reversed(self._registry.ro): - byorder = registry._adapters - if order >= len(byorder): - continue - extendors = registry._v_lookup._extendors.get(provided) - if not extendors: - continue - components = byorder[order] - _lookupAll(components, required, extendors, result, 0, order) - - self._subscribe(*required) - - return tuple(result.items()) - - def names(self, required, provided): - return [c[0] for c in self.lookupAll(required, provided)] - - def _uncached_subscriptions(self, required, provided): - required = tuple(required) - order = len(required) - result = [] - for registry in reversed(self._registry.ro): - byorder = registry._subscribers - if order >= len(byorder): - continue - - if provided is None: - extendors = (provided, ) - else: - extendors = registry._v_lookup._extendors.get(provided) - if extendors is None: - continue - - _subscriptions(byorder[order], required, extendors, _BLANK, - result, 0, order) - - self._subscribe(*required) - - return result - - def subscribers(self, objects, provided): - subscriptions = self.subscriptions(map(providedBy, objects), provided) - if provided is None: - result = () - for subscription in subscriptions: - subscription(*objects) - else: - result = [] - for subscription in subscriptions: - subscriber = subscription(*objects) - if subscriber is not None: - result.append(subscriber) - return result - -class AdapterLookup(AdapterLookupBase, LookupBase): - pass - -@implementer(IAdapterRegistry) -class AdapterRegistry(BaseAdapterRegistry): - - LookupClass = AdapterLookup - - def __init__(self, bases=()): - # AdapterRegisties are invalidating registries, so - # we need to keep track of out invalidating subregistries. - self._v_subregistries = weakref.WeakKeyDictionary() - - super(AdapterRegistry, self).__init__(bases) - - def _addSubregistry(self, r): - self._v_subregistries[r] = 1 - - def _removeSubregistry(self, r): - if r in self._v_subregistries: - del self._v_subregistries[r] - - def _setBases(self, bases): - old = self.__dict__.get('__bases__', ()) - for r in old: - if r not in bases: - r._removeSubregistry(self) - for r in bases: - if r not in old: - r._addSubregistry(self) - - super(AdapterRegistry, self)._setBases(bases) - - def changed(self, originally_changed): - super(AdapterRegistry, self).changed(originally_changed) - - for sub in self._v_subregistries.keys(): - sub.changed(originally_changed) - - -class VerifyingAdapterLookup(AdapterLookupBase, VerifyingBase): - pass - -@implementer(IAdapterRegistry) -class VerifyingAdapterRegistry(BaseAdapterRegistry): - - LookupClass = VerifyingAdapterLookup - -def _convert_None_to_Interface(x): - if x is None: - return Interface - else: - return x - -def _lookup(components, specs, provided, name, i, l): - if i < l: - for spec in specs[i].__sro__: - comps = components.get(spec) - if comps: - r = _lookup(comps, specs, provided, name, i+1, l) - if r is not None: - return r - else: - for iface in provided: - comps = components.get(iface) - if comps: - r = comps.get(name) - if r is not None: - return r - - return None - -def _lookupAll(components, specs, provided, result, i, l): - if i < l: - for spec in reversed(specs[i].__sro__): - comps = components.get(spec) - if comps: - _lookupAll(comps, specs, provided, result, i+1, l) - else: - for iface in reversed(provided): - comps = components.get(iface) - if comps: - result.update(comps) - -def _subscriptions(components, specs, provided, name, result, i, l): - if i < l: - for spec in reversed(specs[i].__sro__): - comps = components.get(spec) - if comps: - _subscriptions(comps, specs, provided, name, result, i+1, l) - else: - for iface in reversed(provided): - comps = components.get(iface) - if comps: - comps = comps.get(name) - if comps: - result.extend(comps) diff --git a/lib/zope/interface/advice.py b/lib/zope/interface/advice.py deleted file mode 100644 index e55930d..0000000 --- a/lib/zope/interface/advice.py +++ /dev/null @@ -1,205 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Class advice. - -This module was adapted from 'protocols.advice', part of the Python -Enterprise Application Kit (PEAK). Please notify the PEAK authors -(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or -Zope-specific changes are required, so that the PEAK version of this module -can be kept in sync. - -PEAK is a Python application framework that interoperates with (but does -not require) Zope 3 and Twisted. It provides tools for manipulating UML -models, object-relational persistence, aspect-oriented programming, and more. -Visit the PEAK home page at http://peak.telecommunity.com for more information. -""" - -from types import FunctionType -try: - from types import ClassType -except ImportError: - __python3 = True -else: - __python3 = False - -import sys - -def getFrameInfo(frame): - """Return (kind,module,locals,globals) for a frame - - 'kind' is one of "exec", "module", "class", "function call", or "unknown". - """ - - f_locals = frame.f_locals - f_globals = frame.f_globals - - sameNamespace = f_locals is f_globals - hasModule = '__module__' in f_locals - hasName = '__name__' in f_globals - - sameName = hasModule and hasName - sameName = sameName and f_globals['__name__']==f_locals['__module__'] - - module = hasName and sys.modules.get(f_globals['__name__']) or None - - namespaceIsModule = module and module.__dict__ is f_globals - - if not namespaceIsModule: - # some kind of funky exec - kind = "exec" - elif sameNamespace and not hasModule: - kind = "module" - elif sameName and not sameNamespace: - kind = "class" - elif not sameNamespace: - kind = "function call" - else: # pragma: no cover - # How can you have f_locals is f_globals, and have '__module__' set? - # This is probably module-level code, but with a '__module__' variable. - kind = "unknown" - return kind, module, f_locals, f_globals - - -def addClassAdvisor(callback, depth=2): - """Set up 'callback' to be passed the containing class upon creation - - This function is designed to be called by an "advising" function executed - in a class suite. The "advising" function supplies a callback that it - wishes to have executed when the containing class is created. The - callback will be given one argument: the newly created containing class. - The return value of the callback will be used in place of the class, so - the callback should return the input if it does not wish to replace the - class. - - The optional 'depth' argument to this function determines the number of - frames between this function and the targeted class suite. 'depth' - defaults to 2, since this skips this function's frame and one calling - function frame. If you use this function from a function called directly - in the class suite, the default will be correct, otherwise you will need - to determine the correct depth yourself. - - This function works by installing a special class factory function in - place of the '__metaclass__' of the containing class. Therefore, only - callbacks *after* the last '__metaclass__' assignment in the containing - class will be executed. Be sure that classes using "advising" functions - declare any '__metaclass__' *first*, to ensure all callbacks are run.""" - # This entire approach is invalid under Py3K. Don't even try to fix - # the coverage for this block there. :( - if __python3: # pragma: no cover - raise TypeError('Class advice impossible in Python3') - - frame = sys._getframe(depth) - kind, module, caller_locals, caller_globals = getFrameInfo(frame) - - # This causes a problem when zope interfaces are used from doctest. - # In these cases, kind == "exec". - # - #if kind != "class": - # raise SyntaxError( - # "Advice must be in the body of a class statement" - # ) - - previousMetaclass = caller_locals.get('__metaclass__') - if __python3: # pragma: no cover - defaultMetaclass = caller_globals.get('__metaclass__', type) - else: - defaultMetaclass = caller_globals.get('__metaclass__', ClassType) - - - def advise(name, bases, cdict): - - if '__metaclass__' in cdict: - del cdict['__metaclass__'] - - if previousMetaclass is None: - if bases: - # find best metaclass or use global __metaclass__ if no bases - meta = determineMetaclass(bases) - else: - meta = defaultMetaclass - - elif isClassAdvisor(previousMetaclass): - # special case: we can't compute the "true" metaclass here, - # so we need to invoke the previous metaclass and let it - # figure it out for us (and apply its own advice in the process) - meta = previousMetaclass - - else: - meta = determineMetaclass(bases, previousMetaclass) - - newClass = meta(name,bases,cdict) - - # this lets the callback replace the class completely, if it wants to - return callback(newClass) - - # introspection data only, not used by inner function - advise.previousMetaclass = previousMetaclass - advise.callback = callback - - # install the advisor - caller_locals['__metaclass__'] = advise - - -def isClassAdvisor(ob): - """True if 'ob' is a class advisor function""" - return isinstance(ob,FunctionType) and hasattr(ob,'previousMetaclass') - - -def determineMetaclass(bases, explicit_mc=None): - """Determine metaclass from 1+ bases and optional explicit __metaclass__""" - - meta = [getattr(b,'__class__',type(b)) for b in bases] - - if explicit_mc is not None: - # The explicit metaclass needs to be verified for compatibility - # as well, and allowed to resolve the incompatible bases, if any - meta.append(explicit_mc) - - if len(meta)==1: - # easy case - return meta[0] - - candidates = minimalBases(meta) # minimal set of metaclasses - - if not candidates: # pragma: no cover - # they're all "classic" classes - assert(not __python3) # This should not happen under Python 3 - return ClassType - - elif len(candidates)>1: - # We could auto-combine, but for now we won't... - raise TypeError("Incompatible metatypes",bases) - - # Just one, return it - return candidates[0] - - -def minimalBases(classes): - """Reduce a list of base classes to its ordered minimum equivalent""" - - if not __python3: # pragma: no cover - classes = [c for c in classes if c is not ClassType] - candidates = [] - - for m in classes: - for n in classes: - if issubclass(n,m) and m is not n: - break - else: - # m has no subclasses in 'classes' - if m in candidates: - candidates.remove(m) # ensure that we're later in the list - candidates.append(m) - - return candidates diff --git a/lib/zope/interface/common/__init__.py b/lib/zope/interface/common/__init__.py deleted file mode 100644 index b711d36..0000000 --- a/lib/zope/interface/common/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -# This file is necessary to make this directory a package. diff --git a/lib/zope/interface/common/idatetime.py b/lib/zope/interface/common/idatetime.py deleted file mode 100644 index 82f0059..0000000 --- a/lib/zope/interface/common/idatetime.py +++ /dev/null @@ -1,606 +0,0 @@ -############################################################################## -# Copyright (c) 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -############################################################################## -"""Datetime interfaces. - -This module is called idatetime because if it were called datetime the import -of the real datetime would fail. -""" -from datetime import timedelta, date, datetime, time, tzinfo - -from zope.interface import Interface, Attribute -from zope.interface import classImplements - - -class ITimeDeltaClass(Interface): - """This is the timedelta class interface. - - This is symbolic; this module does **not** make - `datetime.timedelta` provide this interface. - """ - - min = Attribute("The most negative timedelta object") - - max = Attribute("The most positive timedelta object") - - resolution = Attribute( - "The smallest difference between non-equal timedelta objects") - - -class ITimeDelta(ITimeDeltaClass): - """Represent the difference between two datetime objects. - - Implemented by `datetime.timedelta`. - - Supported operators: - - - add, subtract timedelta - - unary plus, minus, abs - - compare to timedelta - - multiply, divide by int/long - - In addition, `.datetime` supports subtraction of two `.datetime` objects - returning a `.timedelta`, and addition or subtraction of a `.datetime` - and a `.timedelta` giving a `.datetime`. - - Representation: (days, seconds, microseconds). - """ - - days = Attribute("Days between -999999999 and 999999999 inclusive") - - seconds = Attribute("Seconds between 0 and 86399 inclusive") - - microseconds = Attribute("Microseconds between 0 and 999999 inclusive") - - -class IDateClass(Interface): - """This is the date class interface. - - This is symbolic; this module does **not** make - `datetime.date` provide this interface. - """ - - min = Attribute("The earliest representable date") - - max = Attribute("The latest representable date") - - resolution = Attribute( - "The smallest difference between non-equal date objects") - - def today(): - """Return the current local time. - - This is equivalent to ``date.fromtimestamp(time.time())``""" - - def fromtimestamp(timestamp): - """Return the local date from a POSIX timestamp (like time.time()) - - This may raise `ValueError`, if the timestamp is out of the range of - values supported by the platform C ``localtime()`` function. It's common - for this to be restricted to years from 1970 through 2038. Note that - on non-POSIX systems that include leap seconds in their notion of a - timestamp, leap seconds are ignored by `fromtimestamp`. - """ - - def fromordinal(ordinal): - """Return the date corresponding to the proleptic Gregorian ordinal. - - January 1 of year 1 has ordinal 1. `ValueError` is raised unless - 1 <= ordinal <= date.max.toordinal(). - - For any date *d*, ``date.fromordinal(d.toordinal()) == d``. - """ - - -class IDate(IDateClass): - """Represents a date (year, month and day) in an idealized calendar. - - Implemented by `datetime.date`. - - Operators: - - __repr__, __str__ - __cmp__, __hash__ - __add__, __radd__, __sub__ (add/radd only with timedelta arg) - """ - - year = Attribute("Between MINYEAR and MAXYEAR inclusive.") - - month = Attribute("Between 1 and 12 inclusive") - - day = Attribute( - "Between 1 and the number of days in the given month of the given year.") - - def replace(year, month, day): - """Return a date with the same value. - - Except for those members given new values by whichever keyword - arguments are specified. For example, if ``d == date(2002, 12, 31)``, then - ``d.replace(day=26) == date(2000, 12, 26)``. - """ - - def timetuple(): - """Return a 9-element tuple of the form returned by `time.localtime`. - - The hours, minutes and seconds are 0, and the DST flag is -1. - ``d.timetuple()`` is equivalent to - ``(d.year, d.month, d.day, 0, 0, 0, d.weekday(), d.toordinal() - - date(d.year, 1, 1).toordinal() + 1, -1)`` - """ - - def toordinal(): - """Return the proleptic Gregorian ordinal of the date - - January 1 of year 1 has ordinal 1. For any date object *d*, - ``date.fromordinal(d.toordinal()) == d``. - """ - - def weekday(): - """Return the day of the week as an integer. - - Monday is 0 and Sunday is 6. For example, - ``date(2002, 12, 4).weekday() == 2``, a Wednesday. - - .. seealso:: `isoweekday`. - """ - - def isoweekday(): - """Return the day of the week as an integer. - - Monday is 1 and Sunday is 7. For example, - date(2002, 12, 4).isoweekday() == 3, a Wednesday. - - .. seealso:: `weekday`, `isocalendar`. - """ - - def isocalendar(): - """Return a 3-tuple, (ISO year, ISO week number, ISO weekday). - - The ISO calendar is a widely used variant of the Gregorian calendar. - See http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm for a good - explanation. - - The ISO year consists of 52 or 53 full weeks, and where a week starts - on a Monday and ends on a Sunday. The first week of an ISO year is the - first (Gregorian) calendar week of a year containing a Thursday. This - is called week number 1, and the ISO year of that Thursday is the same - as its Gregorian year. - - For example, 2004 begins on a Thursday, so the first week of ISO year - 2004 begins on Monday, 29 Dec 2003 and ends on Sunday, 4 Jan 2004, so - that ``date(2003, 12, 29).isocalendar() == (2004, 1, 1)`` and - ``date(2004, 1, 4).isocalendar() == (2004, 1, 7)``. - """ - - def isoformat(): - """Return a string representing the date in ISO 8601 format. - - This is 'YYYY-MM-DD'. - For example, ``date(2002, 12, 4).isoformat() == '2002-12-04'``. - """ - - def __str__(): - """For a date *d*, ``str(d)`` is equivalent to ``d.isoformat()``.""" - - def ctime(): - """Return a string representing the date. - - For example date(2002, 12, 4).ctime() == 'Wed Dec 4 00:00:00 2002'. - d.ctime() is equivalent to time.ctime(time.mktime(d.timetuple())) - on platforms where the native C ctime() function - (which `time.ctime` invokes, but which date.ctime() does not invoke) - conforms to the C standard. - """ - - def strftime(format): - """Return a string representing the date. - - Controlled by an explicit format string. Format codes referring to - hours, minutes or seconds will see 0 values. - """ - - -class IDateTimeClass(Interface): - """This is the datetime class interface. - - This is symbolic; this module does **not** make - `datetime.datetime` provide this interface. - """ - - min = Attribute("The earliest representable datetime") - - max = Attribute("The latest representable datetime") - - resolution = Attribute( - "The smallest possible difference between non-equal datetime objects") - - def today(): - """Return the current local datetime, with tzinfo None. - - This is equivalent to ``datetime.fromtimestamp(time.time())``. - - .. seealso:: `now`, `fromtimestamp`. - """ - - def now(tz=None): - """Return the current local date and time. - - If optional argument *tz* is None or not specified, this is like `today`, - but, if possible, supplies more precision than can be gotten from going - through a `time.time` timestamp (for example, this may be possible on - platforms supplying the C ``gettimeofday()`` function). - - Else tz must be an instance of a class tzinfo subclass, and the current - date and time are converted to tz's time zone. In this case the result - is equivalent to tz.fromutc(datetime.utcnow().replace(tzinfo=tz)). - - .. seealso:: `today`, `utcnow`. - """ - - def utcnow(): - """Return the current UTC date and time, with tzinfo None. - - This is like `now`, but returns the current UTC date and time, as a - naive datetime object. - - .. seealso:: `now`. - """ - - def fromtimestamp(timestamp, tz=None): - """Return the local date and time corresponding to the POSIX timestamp. - - Same as is returned by time.time(). If optional argument tz is None or - not specified, the timestamp is converted to the platform's local date - and time, and the returned datetime object is naive. - - Else tz must be an instance of a class tzinfo subclass, and the - timestamp is converted to tz's time zone. In this case the result is - equivalent to - ``tz.fromutc(datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz))``. - - fromtimestamp() may raise `ValueError`, if the timestamp is out of the - range of values supported by the platform C localtime() or gmtime() - functions. It's common for this to be restricted to years in 1970 - through 2038. Note that on non-POSIX systems that include leap seconds - in their notion of a timestamp, leap seconds are ignored by - fromtimestamp(), and then it's possible to have two timestamps - differing by a second that yield identical datetime objects. - - .. seealso:: `utcfromtimestamp`. - """ - - def utcfromtimestamp(timestamp): - """Return the UTC datetime from the POSIX timestamp with tzinfo None. - - This may raise `ValueError`, if the timestamp is out of the range of - values supported by the platform C ``gmtime()`` function. It's common for - this to be restricted to years in 1970 through 2038. - - .. seealso:: `fromtimestamp`. - """ - - def fromordinal(ordinal): - """Return the datetime from the proleptic Gregorian ordinal. - - January 1 of year 1 has ordinal 1. `ValueError` is raised unless - 1 <= ordinal <= datetime.max.toordinal(). - The hour, minute, second and microsecond of the result are all 0, and - tzinfo is None. - """ - - def combine(date, time): - """Return a new datetime object. - - Its date members are equal to the given date object's, and whose time - and tzinfo members are equal to the given time object's. For any - datetime object *d*, ``d == datetime.combine(d.date(), d.timetz())``. - If date is a datetime object, its time and tzinfo members are ignored. - """ - - -class IDateTime(IDate, IDateTimeClass): - """Object contains all the information from a date object and a time object. - - Implemented by `datetime.datetime`. - """ - - year = Attribute("Year between MINYEAR and MAXYEAR inclusive") - - month = Attribute("Month between 1 and 12 inclusive") - - day = Attribute( - "Day between 1 and the number of days in the given month of the year") - - hour = Attribute("Hour in range(24)") - - minute = Attribute("Minute in range(60)") - - second = Attribute("Second in range(60)") - - microsecond = Attribute("Microsecond in range(1000000)") - - tzinfo = Attribute( - """The object passed as the tzinfo argument to the datetime constructor - or None if none was passed""") - - def date(): - """Return date object with same year, month and day.""" - - def time(): - """Return time object with same hour, minute, second, microsecond. - - tzinfo is None. - - .. seealso:: Method :meth:`timetz`. - """ - - def timetz(): - """Return time object with same hour, minute, second, microsecond, - and tzinfo. - - .. seealso:: Method :meth:`time`. - """ - - def replace(year, month, day, hour, minute, second, microsecond, tzinfo): - """Return a datetime with the same members, except for those members - given new values by whichever keyword arguments are specified. - - Note that ``tzinfo=None`` can be specified to create a naive datetime from - an aware datetime with no conversion of date and time members. - """ - - def astimezone(tz): - """Return a datetime object with new tzinfo member tz, adjusting the - date and time members so the result is the same UTC time as self, but - in tz's local time. - - tz must be an instance of a tzinfo subclass, and its utcoffset() and - dst() methods must not return None. self must be aware (self.tzinfo - must not be None, and self.utcoffset() must not return None). - - If self.tzinfo is tz, self.astimezone(tz) is equal to self: no - adjustment of date or time members is performed. Else the result is - local time in time zone tz, representing the same UTC time as self: - - after astz = dt.astimezone(tz), astz - astz.utcoffset() - - will usually have the same date and time members as dt - dt.utcoffset(). - The discussion of class `datetime.tzinfo` explains the cases at Daylight Saving - Time transition boundaries where this cannot be achieved (an issue only - if tz models both standard and daylight time). - - If you merely want to attach a time zone object *tz* to a datetime *dt* - without adjustment of date and time members, use ``dt.replace(tzinfo=tz)``. - If you merely want to remove the time zone object from an aware - datetime dt without conversion of date and time members, use - ``dt.replace(tzinfo=None)``. - - Note that the default `tzinfo.fromutc` method can be overridden in a - tzinfo subclass to effect the result returned by `astimezone`. - """ - - def utcoffset(): - """Return the timezone offset in minutes east of UTC (negative west of - UTC).""" - - def dst(): - """Return 0 if DST is not in effect, or the DST offset (in minutes - eastward) if DST is in effect. - """ - - def tzname(): - """Return the timezone name.""" - - def timetuple(): - """Return a 9-element tuple of the form returned by `time.localtime`.""" - - def utctimetuple(): - """Return UTC time tuple compatilble with `time.gmtime`.""" - - def toordinal(): - """Return the proleptic Gregorian ordinal of the date. - - The same as self.date().toordinal(). - """ - - def weekday(): - """Return the day of the week as an integer. - - Monday is 0 and Sunday is 6. The same as self.date().weekday(). - See also isoweekday(). - """ - - def isoweekday(): - """Return the day of the week as an integer. - - Monday is 1 and Sunday is 7. The same as self.date().isoweekday. - - .. seealso:: `weekday`, `isocalendar`. - """ - - def isocalendar(): - """Return a 3-tuple, (ISO year, ISO week number, ISO weekday). - - The same as self.date().isocalendar(). - """ - - def isoformat(sep='T'): - """Return a string representing the date and time in ISO 8601 format. - - YYYY-MM-DDTHH:MM:SS.mmmmmm or YYYY-MM-DDTHH:MM:SS if microsecond is 0 - - If `utcoffset` does not return None, a 6-character string is appended, - giving the UTC offset in (signed) hours and minutes: - - YYYY-MM-DDTHH:MM:SS.mmmmmm+HH:MM or YYYY-MM-DDTHH:MM:SS+HH:MM - if microsecond is 0. - - The optional argument sep (default 'T') is a one-character separator, - placed between the date and time portions of the result. - """ - - def __str__(): - """For a datetime instance *d*, ``str(d)`` is equivalent to ``d.isoformat(' ')``. - """ - - def ctime(): - """Return a string representing the date and time. - - ``datetime(2002, 12, 4, 20, 30, 40).ctime() == 'Wed Dec 4 20:30:40 2002'``. - ``d.ctime()`` is equivalent to ``time.ctime(time.mktime(d.timetuple()))`` on - platforms where the native C ``ctime()`` function (which `time.ctime` - invokes, but which `datetime.ctime` does not invoke) conforms to the - C standard. - """ - - def strftime(format): - """Return a string representing the date and time. - - This is controlled by an explicit format string. - """ - - -class ITimeClass(Interface): - """This is the time class interface. - - This is symbolic; this module does **not** make - `datetime.time` provide this interface. - - """ - - min = Attribute("The earliest representable time") - - max = Attribute("The latest representable time") - - resolution = Attribute( - "The smallest possible difference between non-equal time objects") - - -class ITime(ITimeClass): - """Represent time with time zone. - - Implemented by `datetime.time`. - - Operators: - - __repr__, __str__ - __cmp__, __hash__ - """ - - hour = Attribute("Hour in range(24)") - - minute = Attribute("Minute in range(60)") - - second = Attribute("Second in range(60)") - - microsecond = Attribute("Microsecond in range(1000000)") - - tzinfo = Attribute( - """The object passed as the tzinfo argument to the time constructor - or None if none was passed.""") - - def replace(hour, minute, second, microsecond, tzinfo): - """Return a time with the same value. - - Except for those members given new values by whichever keyword - arguments are specified. Note that tzinfo=None can be specified - to create a naive time from an aware time, without conversion of the - time members. - """ - - def isoformat(): - """Return a string representing the time in ISO 8601 format. - - That is HH:MM:SS.mmmmmm or, if self.microsecond is 0, HH:MM:SS - If utcoffset() does not return None, a 6-character string is appended, - giving the UTC offset in (signed) hours and minutes: - HH:MM:SS.mmmmmm+HH:MM or, if self.microsecond is 0, HH:MM:SS+HH:MM - """ - - def __str__(): - """For a time t, str(t) is equivalent to t.isoformat().""" - - def strftime(format): - """Return a string representing the time. - - This is controlled by an explicit format string. - """ - - def utcoffset(): - """Return the timezone offset in minutes east of UTC (negative west of - UTC). - - If tzinfo is None, returns None, else returns - self.tzinfo.utcoffset(None), and raises an exception if the latter - doesn't return None or a timedelta object representing a whole number - of minutes with magnitude less than one day. - """ - - def dst(): - """Return 0 if DST is not in effect, or the DST offset (in minutes - eastward) if DST is in effect. - - If tzinfo is None, returns None, else returns self.tzinfo.dst(None), - and raises an exception if the latter doesn't return None, or a - timedelta object representing a whole number of minutes with - magnitude less than one day. - """ - - def tzname(): - """Return the timezone name. - - If tzinfo is None, returns None, else returns self.tzinfo.tzname(None), - or raises an exception if the latter doesn't return None or a string - object. - """ - - -class ITZInfo(Interface): - """Time zone info class. - """ - - def utcoffset(dt): - """Return offset of local time from UTC, in minutes east of UTC. - - If local time is west of UTC, this should be negative. - Note that this is intended to be the total offset from UTC; - for example, if a tzinfo object represents both time zone and DST - adjustments, utcoffset() should return their sum. If the UTC offset - isn't known, return None. Else the value returned must be a timedelta - object specifying a whole number of minutes in the range -1439 to 1439 - inclusive (1440 = 24*60; the magnitude of the offset must be less - than one day). - """ - - def dst(dt): - """Return the daylight saving time (DST) adjustment, in minutes east - of UTC, or None if DST information isn't known. - """ - - def tzname(dt): - """Return the time zone name corresponding to the datetime object as - a string. - """ - - def fromutc(dt): - """Return an equivalent datetime in self's local time.""" - - -classImplements(timedelta, ITimeDelta) -classImplements(date, IDate) -classImplements(datetime, IDateTime) -classImplements(time, ITime) -classImplements(tzinfo, ITZInfo) - -## directlyProvides(timedelta, ITimeDeltaClass) -## directlyProvides(date, IDateClass) -## directlyProvides(datetime, IDateTimeClass) -## directlyProvides(time, ITimeClass) diff --git a/lib/zope/interface/common/interfaces.py b/lib/zope/interface/common/interfaces.py deleted file mode 100644 index 4308e0a..0000000 --- a/lib/zope/interface/common/interfaces.py +++ /dev/null @@ -1,212 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Interfaces for standard python exceptions -""" -from zope.interface import Interface -from zope.interface import classImplements - -class IException(Interface): - "Interface for `Exception`" -classImplements(Exception, IException) - - -class IStandardError(IException): - "Interface for `StandardError` (Python 2 only.)" -try: - classImplements(StandardError, IStandardError) -except NameError: #pragma NO COVER - pass # StandardError does not exist in Python 3 - - -class IWarning(IException): - "Interface for `Warning`" -classImplements(Warning, IWarning) - - -class ISyntaxError(IStandardError): - "Interface for `SyntaxError`" -classImplements(SyntaxError, ISyntaxError) - - -class ILookupError(IStandardError): - "Interface for `LookupError`" -classImplements(LookupError, ILookupError) - - -class IValueError(IStandardError): - "Interface for `ValueError`" -classImplements(ValueError, IValueError) - - -class IRuntimeError(IStandardError): - "Interface for `RuntimeError`" -classImplements(RuntimeError, IRuntimeError) - - -class IArithmeticError(IStandardError): - "Interface for `ArithmeticError`" -classImplements(ArithmeticError, IArithmeticError) - - -class IAssertionError(IStandardError): - "Interface for `AssertionError`" -classImplements(AssertionError, IAssertionError) - - -class IAttributeError(IStandardError): - "Interface for `AttributeError`" -classImplements(AttributeError, IAttributeError) - - -class IDeprecationWarning(IWarning): - "Interface for `DeprecationWarning`" -classImplements(DeprecationWarning, IDeprecationWarning) - - -class IEOFError(IStandardError): - "Interface for `EOFError`" -classImplements(EOFError, IEOFError) - - -class IEnvironmentError(IStandardError): - "Interface for `EnvironmentError`" -classImplements(EnvironmentError, IEnvironmentError) - - -class IFloatingPointError(IArithmeticError): - "Interface for `FloatingPointError`" -classImplements(FloatingPointError, IFloatingPointError) - - -class IIOError(IEnvironmentError): - "Interface for `IOError`" -classImplements(IOError, IIOError) - - -class IImportError(IStandardError): - "Interface for `ImportError`" -classImplements(ImportError, IImportError) - - -class IIndentationError(ISyntaxError): - "Interface for `IndentationError`" -classImplements(IndentationError, IIndentationError) - - -class IIndexError(ILookupError): - "Interface for `IndexError`" -classImplements(IndexError, IIndexError) - - -class IKeyError(ILookupError): - "Interface for `KeyError`" -classImplements(KeyError, IKeyError) - - -class IKeyboardInterrupt(IStandardError): - "Interface for `KeyboardInterrupt`" -classImplements(KeyboardInterrupt, IKeyboardInterrupt) - - -class IMemoryError(IStandardError): - "Interface for `MemoryError`" -classImplements(MemoryError, IMemoryError) - - -class INameError(IStandardError): - "Interface for `NameError`" -classImplements(NameError, INameError) - - -class INotImplementedError(IRuntimeError): - "Interface for `NotImplementedError`" -classImplements(NotImplementedError, INotImplementedError) - - -class IOSError(IEnvironmentError): - "Interface for `OSError`" -classImplements(OSError, IOSError) - - -class IOverflowError(IArithmeticError): - "Interface for `ArithmeticError`" -classImplements(OverflowError, IOverflowError) - - -class IOverflowWarning(IWarning): - """Deprecated, no standard class implements this. - - This was the interface for ``OverflowWarning`` prior to Python 2.5, - but that class was removed for all versions after that. - """ - - -class IReferenceError(IStandardError): - "Interface for `ReferenceError`" -classImplements(ReferenceError, IReferenceError) - - -class IRuntimeWarning(IWarning): - "Interface for `RuntimeWarning`" -classImplements(RuntimeWarning, IRuntimeWarning) - - -class IStopIteration(IException): - "Interface for `StopIteration`" -classImplements(StopIteration, IStopIteration) - - -class ISyntaxWarning(IWarning): - "Interface for `SyntaxWarning`" -classImplements(SyntaxWarning, ISyntaxWarning) - - -class ISystemError(IStandardError): - "Interface for `SystemError`" -classImplements(SystemError, ISystemError) - - -class ISystemExit(IException): - "Interface for `SystemExit`" -classImplements(SystemExit, ISystemExit) - - -class ITabError(IIndentationError): - "Interface for `TabError`" -classImplements(TabError, ITabError) - - -class ITypeError(IStandardError): - "Interface for `TypeError`" -classImplements(TypeError, ITypeError) - - -class IUnboundLocalError(INameError): - "Interface for `UnboundLocalError`" -classImplements(UnboundLocalError, IUnboundLocalError) - - -class IUnicodeError(IValueError): - "Interface for `UnicodeError`" -classImplements(UnicodeError, IUnicodeError) - - -class IUserWarning(IWarning): - "Interface for `UserWarning`" -classImplements(UserWarning, IUserWarning) - - -class IZeroDivisionError(IArithmeticError): - "Interface for `ZeroDivisionError`" -classImplements(ZeroDivisionError, IZeroDivisionError) diff --git a/lib/zope/interface/common/mapping.py b/lib/zope/interface/common/mapping.py deleted file mode 100644 index 1c5661a..0000000 --- a/lib/zope/interface/common/mapping.py +++ /dev/null @@ -1,150 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Mapping Interfaces. - -Importing this module does *not* mark any standard classes -as implementing any of these interfaces. -""" -from zope.interface import Interface - -class IItemMapping(Interface): - """Simplest readable mapping object - """ - - def __getitem__(key): - """Get a value for a key - - A `KeyError` is raised if there is no value for the key. - """ - - -class IReadMapping(IItemMapping): - """Basic mapping interface - """ - - def get(key, default=None): - """Get a value for a key - - The default is returned if there is no value for the key. - """ - - def __contains__(key): - """Tell if a key exists in the mapping.""" - - -class IWriteMapping(Interface): - """Mapping methods for changing data""" - - def __delitem__(key): - """Delete a value from the mapping using the key.""" - - def __setitem__(key, value): - """Set a new item in the mapping.""" - - -class IEnumerableMapping(IReadMapping): - """Mapping objects whose items can be enumerated. - """ - - def keys(): - """Return the keys of the mapping object. - """ - - def __iter__(): - """Return an iterator for the keys of the mapping object. - """ - - def values(): - """Return the values of the mapping object. - """ - - def items(): - """Return the items of the mapping object. - """ - - def __len__(): - """Return the number of items. - """ - -class IMapping(IWriteMapping, IEnumerableMapping): - ''' Simple mapping interface ''' - -class IIterableMapping(IEnumerableMapping): - """A mapping that has distinct methods for iterating - without copying. - - On Python 2, a `dict` has these methods, but on Python 3 - the methods defined in `IEnumerableMapping` already iterate - without copying. - """ - - def iterkeys(): - "iterate over keys; equivalent to ``__iter__``" - - def itervalues(): - "iterate over values" - - def iteritems(): - "iterate over items" - -class IClonableMapping(Interface): - """Something that can produce a copy of itself. - - This is available in `dict`. - """ - - def copy(): - "return copy of dict" - -class IExtendedReadMapping(IIterableMapping): - """ - Something with a particular method equivalent to ``__contains__``. - - On Python 2, `dict` provides this method, but it was removed - in Python 3. - """ - - def has_key(key): - """Tell if a key exists in the mapping; equivalent to ``__contains__``""" - -class IExtendedWriteMapping(IWriteMapping): - """Additional mutation methods. - - These are all provided by `dict`. - """ - - def clear(): - "delete all items" - - def update(d): - " Update D from E: for k in E.keys(): D[k] = E[k]" - - def setdefault(key, default=None): - "D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D" - - def pop(k, *args): - """Remove specified key and return the corresponding value. - - ``*args`` may contain a single default value, or may not be supplied. - If key is not found, default is returned if given, otherwise - `KeyError` is raised""" - - def popitem(): - """remove and return some (key, value) pair as a - 2-tuple; but raise KeyError if mapping is empty""" - -class IFullMapping( - IExtendedReadMapping, IExtendedWriteMapping, IClonableMapping, IMapping): - ''' Full mapping interface ''' # IMapping included so tests for IMapping - # succeed with IFullMapping diff --git a/lib/zope/interface/common/sequence.py b/lib/zope/interface/common/sequence.py deleted file mode 100644 index 393918e..0000000 --- a/lib/zope/interface/common/sequence.py +++ /dev/null @@ -1,165 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Sequence Interfaces - -Importing this module does *not* mark any standard classes -as implementing any of these interfaces. -""" - -__docformat__ = 'restructuredtext' -from zope.interface import Interface - -class IMinimalSequence(Interface): - """Most basic sequence interface. - - All sequences are iterable. This requires at least one of the - following: - - - a `__getitem__()` method that takes a single argument; integer - values starting at 0 must be supported, and `IndexError` should - be raised for the first index for which there is no value, or - - - an `__iter__()` method that returns an iterator as defined in - the Python documentation (http://docs.python.org/lib/typeiter.html). - - """ - - def __getitem__(index): - """``x.__getitem__(index) <==> x[index]`` - - Declaring this interface does not specify whether `__getitem__` - supports slice objects.""" - -class IFiniteSequence(IMinimalSequence): - - def __len__(): - """``x.__len__() <==> len(x)``""" - -class IReadSequence(IFiniteSequence): - """read interface shared by tuple and list""" - - def __contains__(item): - """``x.__contains__(item) <==> item in x``""" - - def __lt__(other): - """``x.__lt__(other) <==> x < other``""" - - def __le__(other): - """``x.__le__(other) <==> x <= other``""" - - def __eq__(other): - """``x.__eq__(other) <==> x == other``""" - - def __ne__(other): - """``x.__ne__(other) <==> x != other``""" - - def __gt__(other): - """``x.__gt__(other) <==> x > other``""" - - def __ge__(other): - """``x.__ge__(other) <==> x >= other``""" - - def __add__(other): - """``x.__add__(other) <==> x + other``""" - - def __mul__(n): - """``x.__mul__(n) <==> x * n``""" - - def __rmul__(n): - """``x.__rmul__(n) <==> n * x``""" - - def __getslice__(i, j): - """``x.__getslice__(i, j) <==> x[i:j]`` - - Use of negative indices is not supported. - - Deprecated since Python 2.0 but still a part of `UserList`. - """ - -class IExtendedReadSequence(IReadSequence): - """Full read interface for lists""" - - def count(item): - """Return number of occurrences of value""" - - def index(item, *args): - """index(value, [start, [stop]]) -> int - - Return first index of *value* - """ - -class IUniqueMemberWriteSequence(Interface): - """The write contract for a sequence that may enforce unique members""" - - def __setitem__(index, item): - """``x.__setitem__(index, item) <==> x[index] = item`` - - Declaring this interface does not specify whether `__setitem__` - supports slice objects. - """ - - def __delitem__(index): - """``x.__delitem__(index) <==> del x[index]`` - - Declaring this interface does not specify whether `__delitem__` - supports slice objects. - """ - - def __setslice__(i, j, other): - """``x.__setslice__(i, j, other) <==> x[i:j] = other`` - - Use of negative indices is not supported. - - Deprecated since Python 2.0 but still a part of `UserList`. - """ - - def __delslice__(i, j): - """``x.__delslice__(i, j) <==> del x[i:j]`` - - Use of negative indices is not supported. - - Deprecated since Python 2.0 but still a part of `UserList`. - """ - def __iadd__(y): - """``x.__iadd__(y) <==> x += y``""" - - def append(item): - """Append item to end""" - - def insert(index, item): - """Insert item before index""" - - def pop(index=-1): - """Remove and return item at index (default last)""" - - def remove(item): - """Remove first occurrence of value""" - - def reverse(): - """Reverse *IN PLACE*""" - - def sort(cmpfunc=None): - """Stable sort *IN PLACE*; `cmpfunc(x, y)` -> -1, 0, 1""" - - def extend(iterable): - """Extend list by appending elements from the iterable""" - -class IWriteSequence(IUniqueMemberWriteSequence): - """Full write contract for sequences""" - - def __imul__(n): - """``x.__imul__(n) <==> x *= n``""" - -class ISequence(IReadSequence, IWriteSequence): - """Full sequence contract""" diff --git a/lib/zope/interface/common/tests/__init__.py b/lib/zope/interface/common/tests/__init__.py deleted file mode 100644 index b711d36..0000000 --- a/lib/zope/interface/common/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -# This file is necessary to make this directory a package. diff --git a/lib/zope/interface/common/tests/basemapping.py b/lib/zope/interface/common/tests/basemapping.py deleted file mode 100644 index b756dca..0000000 --- a/lib/zope/interface/common/tests/basemapping.py +++ /dev/null @@ -1,107 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Base Mapping tests -""" -from operator import __getitem__ - -def testIReadMapping(self, inst, state, absent): - for key in state: - self.assertEqual(inst[key], state[key]) - self.assertEqual(inst.get(key, None), state[key]) - self.assertTrue(key in inst) - - for key in absent: - self.assertEqual(inst.get(key, None), None) - self.assertEqual(inst.get(key), None) - self.assertEqual(inst.get(key, self), self) - self.assertRaises(KeyError, __getitem__, inst, key) - - -def test_keys(self, inst, state): - # Return the keys of the mapping object - inst_keys = list(inst.keys()); inst_keys.sort() - state_keys = list(state.keys()) ; state_keys.sort() - self.assertEqual(inst_keys, state_keys) - -def test_iter(self, inst, state): - # Return the keys of the mapping object - inst_keys = list(inst); inst_keys.sort() - state_keys = list(state.keys()) ; state_keys.sort() - self.assertEqual(inst_keys, state_keys) - -def test_values(self, inst, state): - # Return the values of the mapping object - inst_values = list(inst.values()); inst_values.sort() - state_values = list(state.values()) ; state_values.sort() - self.assertEqual(inst_values, state_values) - -def test_items(self, inst, state): - # Return the items of the mapping object - inst_items = list(inst.items()); inst_items.sort() - state_items = list(state.items()) ; state_items.sort() - self.assertEqual(inst_items, state_items) - -def test___len__(self, inst, state): - # Return the number of items - self.assertEqual(len(inst), len(state)) - -def testIEnumerableMapping(self, inst, state): - test_keys(self, inst, state) - test_items(self, inst, state) - test_values(self, inst, state) - test___len__(self, inst, state) - - -class BaseTestIReadMapping(object): - def testIReadMapping(self): - inst = self._IReadMapping__sample() - state = self._IReadMapping__stateDict() - absent = self._IReadMapping__absentKeys() - testIReadMapping(self, inst, state, absent) - - -class BaseTestIEnumerableMapping(BaseTestIReadMapping): - # Mapping objects whose items can be enumerated - def test_keys(self): - # Return the keys of the mapping object - inst = self._IEnumerableMapping__sample() - state = self._IEnumerableMapping__stateDict() - test_keys(self, inst, state) - - def test_values(self): - # Return the values of the mapping object - inst = self._IEnumerableMapping__sample() - state = self._IEnumerableMapping__stateDict() - test_values(self, inst, state) - - def test_items(self): - # Return the items of the mapping object - inst = self._IEnumerableMapping__sample() - state = self._IEnumerableMapping__stateDict() - test_items(self, inst, state) - - def test___len__(self): - # Return the number of items - inst = self._IEnumerableMapping__sample() - state = self._IEnumerableMapping__stateDict() - test___len__(self, inst, state) - - def _IReadMapping__stateDict(self): - return self._IEnumerableMapping__stateDict() - - def _IReadMapping__sample(self): - return self._IEnumerableMapping__sample() - - def _IReadMapping__absentKeys(self): - return self._IEnumerableMapping__absentKeys() diff --git a/lib/zope/interface/common/tests/test_idatetime.py b/lib/zope/interface/common/tests/test_idatetime.py deleted file mode 100644 index 496a5c9..0000000 --- a/lib/zope/interface/common/tests/test_idatetime.py +++ /dev/null @@ -1,37 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Test for datetime interfaces -""" - -import unittest - -from zope.interface.verify import verifyObject, verifyClass -from zope.interface.common.idatetime import ITimeDelta, ITimeDeltaClass -from zope.interface.common.idatetime import IDate, IDateClass -from zope.interface.common.idatetime import IDateTime, IDateTimeClass -from zope.interface.common.idatetime import ITime, ITimeClass, ITZInfo -from datetime import timedelta, date, datetime, time, tzinfo - -class TestDateTimeInterfaces(unittest.TestCase): - - def test_interfaces(self): - verifyObject(ITimeDelta, timedelta(minutes=20)) - verifyObject(IDate, date(2000, 1, 2)) - verifyObject(IDateTime, datetime(2000, 1, 2, 10, 20)) - verifyObject(ITime, time(20, 30, 15, 1234)) - verifyObject(ITZInfo, tzinfo()) - verifyClass(ITimeDeltaClass, timedelta) - verifyClass(IDateClass, date) - verifyClass(IDateTimeClass, datetime) - verifyClass(ITimeClass, time) diff --git a/lib/zope/interface/common/tests/test_import_interfaces.py b/lib/zope/interface/common/tests/test_import_interfaces.py deleted file mode 100644 index fe3766f..0000000 --- a/lib/zope/interface/common/tests/test_import_interfaces.py +++ /dev/null @@ -1,20 +0,0 @@ -############################################################################## -# -# Copyright (c) 2006 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -import unittest - -class TestInterfaceImport(unittest.TestCase): - - def test_import(self): - import zope.interface.common.interfaces as x - self.assertIsNotNone(x) diff --git a/lib/zope/interface/declarations.py b/lib/zope/interface/declarations.py deleted file mode 100644 index b80245f..0000000 --- a/lib/zope/interface/declarations.py +++ /dev/null @@ -1,929 +0,0 @@ -############################################################################## -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -############################################################################## -"""Implementation of interface declarations - -There are three flavors of declarations: - - - Declarations are used to simply name declared interfaces. - - - ImplementsDeclarations are used to express the interfaces that a - class implements (that instances of the class provides). - - Implements specifications support inheriting interfaces. - - - ProvidesDeclarations are used to express interfaces directly - provided by objects. - -""" -__docformat__ = 'restructuredtext' - -import sys -from types import FunctionType -from types import MethodType -from types import ModuleType -import weakref - -from zope.interface.advice import addClassAdvisor -from zope.interface.interface import InterfaceClass -from zope.interface.interface import SpecificationBase -from zope.interface.interface import Specification -from zope.interface._compat import CLASS_TYPES as DescriptorAwareMetaClasses -from zope.interface._compat import PYTHON3 - -# Registry of class-implementation specifications -BuiltinImplementationSpecifications = {} - -_ADVICE_ERROR = ('Class advice impossible in Python3. ' - 'Use the @%s class decorator instead.') - -_ADVICE_WARNING = ('The %s API is deprecated, and will not work in Python3 ' - 'Use the @%s class decorator instead.') - -class named(object): - - def __init__(self, name): - self.name = name - - def __call__(self, ob): - ob.__component_name__ = self.name - return ob - -class Declaration(Specification): - """Interface declarations""" - - def __init__(self, *interfaces): - Specification.__init__(self, _normalizeargs(interfaces)) - - def changed(self, originally_changed): - Specification.changed(self, originally_changed) - try: - del self._v_attrs - except AttributeError: - pass - - def __contains__(self, interface): - """Test whether an interface is in the specification - """ - - return self.extends(interface) and interface in self.interfaces() - - def __iter__(self): - """Return an iterator for the interfaces in the specification - """ - return self.interfaces() - - def flattened(self): - """Return an iterator of all included and extended interfaces - """ - return iter(self.__iro__) - - def __sub__(self, other): - """Remove interfaces from a specification - """ - return Declaration( - *[i for i in self.interfaces() - if not [j for j in other.interfaces() - if i.extends(j, 0)] - ] - ) - - def __add__(self, other): - """Add two specifications or a specification and an interface - """ - seen = {} - result = [] - for i in self.interfaces(): - seen[i] = 1 - result.append(i) - for i in other.interfaces(): - if i not in seen: - seen[i] = 1 - result.append(i) - - return Declaration(*result) - - __radd__ = __add__ - - -############################################################################## -# -# Implementation specifications -# -# These specify interfaces implemented by instances of classes - -class Implements(Declaration): - - # class whose specification should be used as additional base - inherit = None - - # interfaces actually declared for a class - declared = () - - __name__ = '?' - - @classmethod - def named(cls, name, *interfaces): - # Implementation method: Produce an Implements interface with - # a fully fleshed out __name__ before calling the constructor, which - # sets bases to the given interfaces and which may pass this object to - # other objects (e.g., to adjust dependents). If they're sorting or comparing - # by name, this needs to be set. - inst = cls.__new__(cls) - inst.__name__ = name - inst.__init__(*interfaces) - return inst - - def __repr__(self): - return '<implementedBy %s>' % (self.__name__) - - def __reduce__(self): - return implementedBy, (self.inherit, ) - - def __cmp(self, other): - # Yes, I did mean to name this __cmp, rather than __cmp__. - # It is a private method used by __lt__ and __gt__. - # This is based on, and compatible with, InterfaceClass. - # (The two must be mutually comparable to be able to work in e.g., BTrees.) - # Instances of this class generally don't have a __module__ other than - # `zope.interface.declarations`, whereas they *do* have a __name__ that is the - # fully qualified name of the object they are representing. - - # Note, though, that equality and hashing are still identity based. This - # accounts for things like nested objects that have the same name (typically - # only in tests) and is consistent with pickling. As far as comparisons to InterfaceClass - # goes, we'll never have equal name and module to those, so we're still consistent there. - # Instances of this class are essentially intended to be unique and are - # heavily cached (note how our __reduce__ handles this) so having identity - # based hash and eq should also work. - if other is None: - return -1 - - n1 = (self.__name__, self.__module__) - n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', '')) - - # This spelling works under Python3, which doesn't have cmp(). - return (n1 > n2) - (n1 < n2) - - def __hash__(self): - return Declaration.__hash__(self) - - # We want equality to be based on identity. However, we can't actually - # implement __eq__/__ne__ to do this because sometimes we get wrapped in a proxy. - # We need to let the proxy types implement these methods so they can handle unwrapping - # and then rely on: (1) the interpreter automatically changing `implements == proxy` into - # `proxy == implements` (which will call proxy.__eq__ to do the unwrapping) and then - # (2) the default equality semantics being identity based. - - def __lt__(self, other): - c = self.__cmp(other) - return c < 0 - - def __le__(self, other): - c = self.__cmp(other) - return c <= 0 - - def __gt__(self, other): - c = self.__cmp(other) - return c > 0 - - def __ge__(self, other): - c = self.__cmp(other) - return c >= 0 - -def _implements_name(ob): - # Return the __name__ attribute to be used by its __implemented__ - # property. - # This must be stable for the "same" object across processes - # because it is used for sorting. It needn't be unique, though, in cases - # like nested classes named Foo created by different functions, because - # equality and hashing is still based on identity. - # It might be nice to use __qualname__ on Python 3, but that would produce - # different values between Py2 and Py3. - return (getattr(ob, '__module__', '?') or '?') + \ - '.' + (getattr(ob, '__name__', '?') or '?') - -def implementedByFallback(cls): - """Return the interfaces implemented for a class' instances - - The value returned is an `~zope.interface.interfaces.IDeclaration`. - """ - try: - spec = cls.__dict__.get('__implemented__') - except AttributeError: - - # we can't get the class dict. This is probably due to a - # security proxy. If this is the case, then probably no - # descriptor was installed for the class. - - # We don't want to depend directly on zope.security in - # zope.interface, but we'll try to make reasonable - # accommodations in an indirect way. - - # We'll check to see if there's an implements: - - spec = getattr(cls, '__implemented__', None) - if spec is None: - # There's no spec stred in the class. Maybe its a builtin: - spec = BuiltinImplementationSpecifications.get(cls) - if spec is not None: - return spec - return _empty - - if spec.__class__ == Implements: - # we defaulted to _empty or there was a spec. Good enough. - # Return it. - return spec - - # TODO: need old style __implements__ compatibility? - # Hm, there's an __implemented__, but it's not a spec. Must be - # an old-style declaration. Just compute a spec for it - return Declaration(*_normalizeargs((spec, ))) - - if isinstance(spec, Implements): - return spec - - if spec is None: - spec = BuiltinImplementationSpecifications.get(cls) - if spec is not None: - return spec - - # TODO: need old style __implements__ compatibility? - spec_name = _implements_name(cls) - if spec is not None: - # old-style __implemented__ = foo declaration - spec = (spec, ) # tuplefy, as it might be just an int - spec = Implements.named(spec_name, *_normalizeargs(spec)) - spec.inherit = None # old-style implies no inherit - del cls.__implemented__ # get rid of the old-style declaration - else: - try: - bases = cls.__bases__ - except AttributeError: - if not callable(cls): - raise TypeError("ImplementedBy called for non-factory", cls) - bases = () - - spec = Implements.named(spec_name, *[implementedBy(c) for c in bases]) - spec.inherit = cls - - try: - cls.__implemented__ = spec - if not hasattr(cls, '__providedBy__'): - cls.__providedBy__ = objectSpecificationDescriptor - - if (isinstance(cls, DescriptorAwareMetaClasses) - and - '__provides__' not in cls.__dict__): - # Make sure we get a __provides__ descriptor - cls.__provides__ = ClassProvides( - cls, - getattr(cls, '__class__', type(cls)), - ) - - except TypeError: - if not isinstance(cls, type): - raise TypeError("ImplementedBy called for non-type", cls) - BuiltinImplementationSpecifications[cls] = spec - - return spec - -implementedBy = implementedByFallback - -def classImplementsOnly(cls, *interfaces): - """Declare the only interfaces implemented by instances of a class - - The arguments after the class are one or more interfaces or interface - specifications (`~zope.interface.interfaces.IDeclaration` objects). - - The interfaces given (including the interfaces in the specifications) - replace any previous declarations. - """ - spec = implementedBy(cls) - spec.declared = () - spec.inherit = None - classImplements(cls, *interfaces) - -def classImplements(cls, *interfaces): - """Declare additional interfaces implemented for instances of a class - - The arguments after the class are one or more interfaces or - interface specifications (`~zope.interface.interfaces.IDeclaration` objects). - - The interfaces given (including the interfaces in the specifications) - are added to any interfaces previously declared. - """ - spec = implementedBy(cls) - spec.declared += tuple(_normalizeargs(interfaces)) - - # compute the bases - bases = [] - seen = {} - for b in spec.declared: - if b not in seen: - seen[b] = 1 - bases.append(b) - - if spec.inherit is not None: - - for c in spec.inherit.__bases__: - b = implementedBy(c) - if b not in seen: - seen[b] = 1 - bases.append(b) - - spec.__bases__ = tuple(bases) - -def _implements_advice(cls): - interfaces, classImplements = cls.__dict__['__implements_advice_data__'] - del cls.__implements_advice_data__ - classImplements(cls, *interfaces) - return cls - - -class implementer(object): - """Declare the interfaces implemented by instances of a class. - - This function is called as a class decorator. - - The arguments are one or more interfaces or interface - specifications (`~zope.interface.interfaces.IDeclaration` objects). - - The interfaces given (including the interfaces in the - specifications) are added to any interfaces previously - declared. - - Previous declarations include declarations for base classes - unless implementsOnly was used. - - This function is provided for convenience. It provides a more - convenient way to call `classImplements`. For example:: - - @implementer(I1) - class C(object): - pass - - is equivalent to calling:: - - classImplements(C, I1) - - after the class has been created. - """ - - def __init__(self, *interfaces): - self.interfaces = interfaces - - def __call__(self, ob): - if isinstance(ob, DescriptorAwareMetaClasses): - classImplements(ob, *self.interfaces) - return ob - - spec_name = _implements_name(ob) - spec = Implements.named(spec_name, *self.interfaces) - try: - ob.__implemented__ = spec - except AttributeError: - raise TypeError("Can't declare implements", ob) - return ob - -class implementer_only(object): - """Declare the only interfaces implemented by instances of a class - - This function is called as a class decorator. - - The arguments are one or more interfaces or interface - specifications (`~zope.interface.interfaces.IDeclaration` objects). - - Previous declarations including declarations for base classes - are overridden. - - This function is provided for convenience. It provides a more - convenient way to call `classImplementsOnly`. For example:: - - @implementer_only(I1) - class C(object): pass - - is equivalent to calling:: - - classImplementsOnly(I1) - - after the class has been created. - """ - - def __init__(self, *interfaces): - self.interfaces = interfaces - - def __call__(self, ob): - if isinstance(ob, (FunctionType, MethodType)): - # XXX Does this decorator make sense for anything but classes? - # I don't think so. There can be no inheritance of interfaces - # on a method pr function.... - raise ValueError('The implementer_only decorator is not ' - 'supported for methods or functions.') - else: - # Assume it's a class: - classImplementsOnly(ob, *self.interfaces) - return ob - -def _implements(name, interfaces, classImplements): - # This entire approach is invalid under Py3K. Don't even try to fix - # the coverage for this block there. :( - frame = sys._getframe(2) - locals = frame.f_locals - - # Try to make sure we were called from a class def. In 2.2.0 we can't - # check for __module__ since it doesn't seem to be added to the locals - # until later on. - if locals is frame.f_globals or '__module__' not in locals: - raise TypeError(name+" can be used only from a class definition.") - - if '__implements_advice_data__' in locals: - raise TypeError(name+" can be used only once in a class definition.") - - locals['__implements_advice_data__'] = interfaces, classImplements - addClassAdvisor(_implements_advice, depth=3) - -def implements(*interfaces): - """Declare interfaces implemented by instances of a class - - This function is called in a class definition. - - The arguments are one or more interfaces or interface - specifications (`~zope.interface.interfaces.IDeclaration` objects). - - The interfaces given (including the interfaces in the - specifications) are added to any interfaces previously - declared. - - Previous declarations include declarations for base classes - unless `implementsOnly` was used. - - This function is provided for convenience. It provides a more - convenient way to call `classImplements`. For example:: - - implements(I1) - - is equivalent to calling:: - - classImplements(C, I1) - - after the class has been created. - """ - # This entire approach is invalid under Py3K. Don't even try to fix - # the coverage for this block there. :( - if PYTHON3: - raise TypeError(_ADVICE_ERROR % 'implementer') - _implements("implements", interfaces, classImplements) - -def implementsOnly(*interfaces): - """Declare the only interfaces implemented by instances of a class - - This function is called in a class definition. - - The arguments are one or more interfaces or interface - specifications (`~zope.interface.interfaces.IDeclaration` objects). - - Previous declarations including declarations for base classes - are overridden. - - This function is provided for convenience. It provides a more - convenient way to call `classImplementsOnly`. For example:: - - implementsOnly(I1) - - is equivalent to calling:: - - classImplementsOnly(I1) - - after the class has been created. - """ - # This entire approach is invalid under Py3K. Don't even try to fix - # the coverage for this block there. :( - if PYTHON3: - raise TypeError(_ADVICE_ERROR % 'implementer_only') - _implements("implementsOnly", interfaces, classImplementsOnly) - -############################################################################## -# -# Instance declarations - -class Provides(Declaration): # Really named ProvidesClass - """Implement ``__provides__``, the instance-specific specification - - When an object is pickled, we pickle the interfaces that it implements. - """ - - def __init__(self, cls, *interfaces): - self.__args = (cls, ) + interfaces - self._cls = cls - Declaration.__init__(self, *(interfaces + (implementedBy(cls), ))) - - def __reduce__(self): - return Provides, self.__args - - __module__ = 'zope.interface' - - def __get__(self, inst, cls): - """Make sure that a class __provides__ doesn't leak to an instance - """ - if inst is None and cls is self._cls: - # We were accessed through a class, so we are the class' - # provides spec. Just return this object, but only if we are - # being called on the same class that we were defined for: - return self - - raise AttributeError('__provides__') - -ProvidesClass = Provides - -# Registry of instance declarations -# This is a memory optimization to allow objects to share specifications. -InstanceDeclarations = weakref.WeakValueDictionary() - -def Provides(*interfaces): - """Cache instance declarations - - Instance declarations are shared among instances that have the same - declaration. The declarations are cached in a weak value dictionary. - """ - spec = InstanceDeclarations.get(interfaces) - if spec is None: - spec = ProvidesClass(*interfaces) - InstanceDeclarations[interfaces] = spec - - return spec - -Provides.__safe_for_unpickling__ = True - - -def directlyProvides(object, *interfaces): - """Declare interfaces declared directly for an object - - The arguments after the object are one or more interfaces or interface - specifications (`~zope.interface.interfaces.IDeclaration` objects). - - The interfaces given (including the interfaces in the specifications) - replace interfaces previously declared for the object. - """ - cls = getattr(object, '__class__', None) - if cls is not None and getattr(cls, '__class__', None) is cls: - # It's a meta class (well, at least it it could be an extension class) - # Note that we can't get here from Py3k tests: there is no normal - # class which isn't descriptor aware. - if not isinstance(object, - DescriptorAwareMetaClasses): - raise TypeError("Attempt to make an interface declaration on a " - "non-descriptor-aware class") - - interfaces = _normalizeargs(interfaces) - if cls is None: - cls = type(object) - - issub = False - for damc in DescriptorAwareMetaClasses: - if issubclass(cls, damc): - issub = True - break - if issub: - # we have a class or type. We'll use a special descriptor - # that provides some extra caching - object.__provides__ = ClassProvides(object, cls, *interfaces) - else: - object.__provides__ = Provides(cls, *interfaces) - - -def alsoProvides(object, *interfaces): - """Declare interfaces declared directly for an object - - The arguments after the object are one or more interfaces or interface - specifications (`~zope.interface.interfaces.IDeclaration` objects). - - The interfaces given (including the interfaces in the specifications) are - added to the interfaces previously declared for the object. - """ - directlyProvides(object, directlyProvidedBy(object), *interfaces) - -def noLongerProvides(object, interface): - """ Removes a directly provided interface from an object. - """ - directlyProvides(object, directlyProvidedBy(object) - interface) - if interface.providedBy(object): - raise ValueError("Can only remove directly provided interfaces.") - -class ClassProvidesBaseFallback(object): - - def __get__(self, inst, cls): - if cls is self._cls: - # We only work if called on the class we were defined for - - if inst is None: - # We were accessed through a class, so we are the class' - # provides spec. Just return this object as is: - return self - - return self._implements - - raise AttributeError('__provides__') - -ClassProvidesBasePy = ClassProvidesBaseFallback # BBB -ClassProvidesBase = ClassProvidesBaseFallback - -# Try to get C base: -try: - import zope.interface._zope_interface_coptimizations -except ImportError: - pass -else: - from zope.interface._zope_interface_coptimizations import ClassProvidesBase - - -class ClassProvides(Declaration, ClassProvidesBase): - """Special descriptor for class ``__provides__`` - - The descriptor caches the implementedBy info, so that - we can get declarations for objects without instance-specific - interfaces a bit quicker. - """ - def __init__(self, cls, metacls, *interfaces): - self._cls = cls - self._implements = implementedBy(cls) - self.__args = (cls, metacls, ) + interfaces - Declaration.__init__(self, *(interfaces + (implementedBy(metacls), ))) - - def __reduce__(self): - return self.__class__, self.__args - - # Copy base-class method for speed - __get__ = ClassProvidesBase.__get__ - -def directlyProvidedBy(object): - """Return the interfaces directly provided by the given object - - The value returned is an `~zope.interface.interfaces.IDeclaration`. - """ - provides = getattr(object, "__provides__", None) - if (provides is None # no spec - or - # We might have gotten the implements spec, as an - # optimization. If so, it's like having only one base, that we - # lop off to exclude class-supplied declarations: - isinstance(provides, Implements) - ): - return _empty - - # Strip off the class part of the spec: - return Declaration(provides.__bases__[:-1]) - -def classProvides(*interfaces): - """Declare interfaces provided directly by a class - - This function is called in a class definition. - - The arguments are one or more interfaces or interface specifications - (`~zope.interface.interfaces.IDeclaration` objects). - - The given interfaces (including the interfaces in the specifications) - are used to create the class's direct-object interface specification. - An error will be raised if the module class has an direct interface - specification. In other words, it is an error to call this function more - than once in a class definition. - - Note that the given interfaces have nothing to do with the interfaces - implemented by instances of the class. - - This function is provided for convenience. It provides a more convenient - way to call `directlyProvides` for a class. For example:: - - classProvides(I1) - - is equivalent to calling:: - - directlyProvides(theclass, I1) - - after the class has been created. - """ - # This entire approach is invalid under Py3K. Don't even try to fix - # the coverage for this block there. :( - - if PYTHON3: - raise TypeError(_ADVICE_ERROR % 'provider') - - frame = sys._getframe(1) - locals = frame.f_locals - - # Try to make sure we were called from a class def - if (locals is frame.f_globals) or ('__module__' not in locals): - raise TypeError("classProvides can be used only from a " - "class definition.") - - if '__provides__' in locals: - raise TypeError( - "classProvides can only be used once in a class definition.") - - locals["__provides__"] = _normalizeargs(interfaces) - - addClassAdvisor(_classProvides_advice, depth=2) - -def _classProvides_advice(cls): - # This entire approach is invalid under Py3K. Don't even try to fix - # the coverage for this block there. :( - interfaces = cls.__dict__['__provides__'] - del cls.__provides__ - directlyProvides(cls, *interfaces) - return cls - -class provider(object): - """Class decorator version of classProvides""" - - def __init__(self, *interfaces): - self.interfaces = interfaces - - def __call__(self, ob): - directlyProvides(ob, *self.interfaces) - return ob - -def moduleProvides(*interfaces): - """Declare interfaces provided by a module - - This function is used in a module definition. - - The arguments are one or more interfaces or interface specifications - (`~zope.interface.interfaces.IDeclaration` objects). - - The given interfaces (including the interfaces in the specifications) are - used to create the module's direct-object interface specification. An - error will be raised if the module already has an interface specification. - In other words, it is an error to call this function more than once in a - module definition. - - This function is provided for convenience. It provides a more convenient - way to call directlyProvides. For example:: - - moduleImplements(I1) - - is equivalent to:: - - directlyProvides(sys.modules[__name__], I1) - """ - frame = sys._getframe(1) - locals = frame.f_locals - - # Try to make sure we were called from a class def - if (locals is not frame.f_globals) or ('__name__' not in locals): - raise TypeError( - "moduleProvides can only be used from a module definition.") - - if '__provides__' in locals: - raise TypeError( - "moduleProvides can only be used once in a module definition.") - - locals["__provides__"] = Provides(ModuleType, - *_normalizeargs(interfaces)) - -############################################################################## -# -# Declaration querying support - -# XXX: is this a fossil? Nobody calls it, no unit tests exercise it, no -# doctests import it, and the package __init__ doesn't import it. -def ObjectSpecification(direct, cls): - """Provide object specifications - - These combine information for the object and for it's classes. - """ - return Provides(cls, direct) # pragma: no cover fossil - -def getObjectSpecificationFallback(ob): - - provides = getattr(ob, '__provides__', None) - if provides is not None: - if isinstance(provides, SpecificationBase): - return provides - - try: - cls = ob.__class__ - except AttributeError: - # We can't get the class, so just consider provides - return _empty - - return implementedBy(cls) - -getObjectSpecification = getObjectSpecificationFallback - -def providedByFallback(ob): - - # Here we have either a special object, an old-style declaration - # or a descriptor - - # Try to get __providedBy__ - try: - r = ob.__providedBy__ - except AttributeError: - # Not set yet. Fall back to lower-level thing that computes it - return getObjectSpecification(ob) - - try: - # We might have gotten a descriptor from an instance of a - # class (like an ExtensionClass) that doesn't support - # descriptors. We'll make sure we got one by trying to get - # the only attribute, which all specs have. - r.extends - - except AttributeError: - - # The object's class doesn't understand descriptors. - # Sigh. We need to get an object descriptor, but we have to be - # careful. We want to use the instance's __provides__, if - # there is one, but only if it didn't come from the class. - - try: - r = ob.__provides__ - except AttributeError: - # No __provides__, so just fall back to implementedBy - return implementedBy(ob.__class__) - - # We need to make sure we got the __provides__ from the - # instance. We'll do this by making sure we don't get the same - # thing from the class: - - try: - cp = ob.__class__.__provides__ - except AttributeError: - # The ob doesn't have a class or the class has no - # provides, assume we're done: - return r - - if r is cp: - # Oops, we got the provides from the class. This means - # the object doesn't have it's own. We should use implementedBy - return implementedBy(ob.__class__) - - return r -providedBy = providedByFallback - -class ObjectSpecificationDescriptorFallback(object): - """Implement the `__providedBy__` attribute - - The `__providedBy__` attribute computes the interfaces peovided by - an object. - """ - - def __get__(self, inst, cls): - """Get an object specification for an object - """ - if inst is None: - return getObjectSpecification(cls) - - provides = getattr(inst, '__provides__', None) - if provides is not None: - return provides - - return implementedBy(cls) - -ObjectSpecificationDescriptor = ObjectSpecificationDescriptorFallback - -############################################################################## - -def _normalizeargs(sequence, output = None): - """Normalize declaration arguments - - Normalization arguments might contain Declarions, tuples, or single - interfaces. - - Anything but individial interfaces or implements specs will be expanded. - """ - if output is None: - output = [] - - cls = sequence.__class__ - if InterfaceClass in cls.__mro__ or Implements in cls.__mro__: - output.append(sequence) - else: - for v in sequence: - _normalizeargs(v, output) - - return output - -_empty = Declaration() - -try: - import zope.interface._zope_interface_coptimizations -except ImportError: - pass -else: - from zope.interface._zope_interface_coptimizations import implementedBy - from zope.interface._zope_interface_coptimizations import providedBy - from zope.interface._zope_interface_coptimizations import ( - getObjectSpecification) - from zope.interface._zope_interface_coptimizations import ( - ObjectSpecificationDescriptor) - -objectSpecificationDescriptor = ObjectSpecificationDescriptor() diff --git a/lib/zope/interface/document.py b/lib/zope/interface/document.py deleted file mode 100644 index e6d6e88..0000000 --- a/lib/zope/interface/document.py +++ /dev/null @@ -1,120 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -""" Pretty-Print an Interface object as structured text (Yum) - -This module provides a function, asStructuredText, for rendering an -interface as structured text. -""" -import zope.interface - - -def asStructuredText(I, munge=0, rst=False): - """ Output structured text format. Note, this will whack any existing - 'structured' format of the text. - - If `rst=True`, then the output will quote all code as inline literals in - accordance with 'reStructuredText' markup principles. - """ - - if rst: - inline_literal = lambda s: "``%s``" % (s,) - else: - inline_literal = lambda s: s - - r = [inline_literal(I.getName())] - outp = r.append - level = 1 - - if I.getDoc(): - outp(_justify_and_indent(_trim_doc_string(I.getDoc()), level)) - - bases = [base - for base in I.__bases__ - if base is not zope.interface.Interface - ] - if bases: - outp(_justify_and_indent("This interface extends:", level, munge)) - level += 1 - for b in bases: - item = "o %s" % inline_literal(b.getName()) - outp(_justify_and_indent(_trim_doc_string(item), level, munge)) - level -= 1 - - namesAndDescriptions = sorted(I.namesAndDescriptions()) - - outp(_justify_and_indent("Attributes:", level, munge)) - level += 1 - for name, desc in namesAndDescriptions: - if not hasattr(desc, 'getSignatureString'): # ugh... - item = "%s -- %s" % (inline_literal(desc.getName()), - desc.getDoc() or 'no documentation') - outp(_justify_and_indent(_trim_doc_string(item), level, munge)) - level -= 1 - - outp(_justify_and_indent("Methods:", level, munge)) - level += 1 - for name, desc in namesAndDescriptions: - if hasattr(desc, 'getSignatureString'): # ugh... - _call = "%s%s" % (desc.getName(), desc.getSignatureString()) - item = "%s -- %s" % (inline_literal(_call), - desc.getDoc() or 'no documentation') - outp(_justify_and_indent(_trim_doc_string(item), level, munge)) - - return "\n\n".join(r) + "\n\n" - - -def asReStructuredText(I, munge=0): - """ Output reStructuredText format. Note, this will whack any existing - 'structured' format of the text.""" - return asStructuredText(I, munge=munge, rst=True) - - -def _trim_doc_string(text): - """ Trims a doc string to make it format - correctly with structured text. """ - - lines = text.replace('\r\n', '\n').split('\n') - nlines = [lines.pop(0)] - if lines: - min_indent = min([len(line) - len(line.lstrip()) - for line in lines]) - for line in lines: - nlines.append(line[min_indent:]) - - return '\n'.join(nlines) - - -def _justify_and_indent(text, level, munge=0, width=72): - """ indent and justify text, rejustify (munge) if specified """ - - indent = " " * level - - if munge: - lines = [] - line = indent - text = text.split() - - for word in text: - line = ' '.join([line, word]) - if len(line) > width: - lines.append(line) - line = indent - else: - lines.append(line) - - return '\n'.join(lines) - - else: - return indent + \ - text.strip().replace("\r\n", "\n") .replace("\n", "\n" + indent) diff --git a/lib/zope/interface/exceptions.py b/lib/zope/interface/exceptions.py deleted file mode 100644 index e9a4788..0000000 --- a/lib/zope/interface/exceptions.py +++ /dev/null @@ -1,67 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Interface-specific exceptions -""" - -class Invalid(Exception): - """A specification is violated - """ - -class DoesNotImplement(Invalid): - """ This object does not implement """ - def __init__(self, interface): - self.interface = interface - - def __str__(self): - return """An object does not implement interface %(interface)s - - """ % self.__dict__ - -class BrokenImplementation(Invalid): - """An attribute is not completely implemented. - """ - - def __init__(self, interface, name): - self.interface=interface - self.name=name - - def __str__(self): - return """An object has failed to implement interface %(interface)s - - The %(name)s attribute was not provided. - """ % self.__dict__ - -class BrokenMethodImplementation(Invalid): - """An method is not completely implemented. - """ - - def __init__(self, method, mess): - self.method=method - self.mess=mess - - def __str__(self): - return """The implementation of %(method)s violates its contract - because %(mess)s. - """ % self.__dict__ - -class InvalidInterface(Exception): - """The interface has invalid contents - """ - -class BadImplements(TypeError): - """An implementation assertion is invalid - - because it doesn't contain an interface or a sequence of valid - implementation assertions. - """ diff --git a/lib/zope/interface/interface.py b/lib/zope/interface/interface.py deleted file mode 100644 index d4e5a94..0000000 --- a/lib/zope/interface/interface.py +++ /dev/null @@ -1,687 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Interface object implementation -""" -from __future__ import generators - -import sys -from types import MethodType -from types import FunctionType -import warnings -import weakref - -from zope.interface.exceptions import Invalid -from zope.interface.ro import ro - - -CO_VARARGS = 4 -CO_VARKEYWORDS = 8 -TAGGED_DATA = '__interface_tagged_values__' - -_decorator_non_return = object() - -def invariant(call): - f_locals = sys._getframe(1).f_locals - tags = f_locals.setdefault(TAGGED_DATA, {}) - invariants = tags.setdefault('invariants', []) - invariants.append(call) - return _decorator_non_return - - -def taggedValue(key, value): - """Attaches a tagged value to an interface at definition time.""" - f_locals = sys._getframe(1).f_locals - tagged_values = f_locals.setdefault(TAGGED_DATA, {}) - tagged_values[key] = value - return _decorator_non_return - - -class Element(object): - """ - Default implementation of `zope.interface.interfaces.IElement`. - """ - - # We can't say this yet because we don't have enough - # infrastructure in place. - # - #implements(IElement) - - def __init__(self, __name__, __doc__=''): - if not __doc__ and __name__.find(' ') >= 0: - __doc__ = __name__ - __name__ = None - - self.__name__=__name__ - self.__doc__=__doc__ - self.__tagged_values = {} - - def getName(self): - """ Returns the name of the object. """ - return self.__name__ - - def getDoc(self): - """ Returns the documentation for the object. """ - return self.__doc__ - - def getTaggedValue(self, tag): - """ Returns the value associated with 'tag'. """ - return self.__tagged_values[tag] - - def queryTaggedValue(self, tag, default=None): - """ Returns the value associated with 'tag'. """ - return self.__tagged_values.get(tag, default) - - def getTaggedValueTags(self): - """ Returns a list of all tags. """ - return self.__tagged_values.keys() - - def setTaggedValue(self, tag, value): - """ Associates 'value' with 'key'. """ - self.__tagged_values[tag] = value - -class SpecificationBasePy(object): - - def providedBy(self, ob): - """Is the interface implemented by an object - """ - spec = providedBy(ob) - return self in spec._implied - - def implementedBy(self, cls): - """Test whether the specification is implemented by a class or factory. - - Raise TypeError if argument is neither a class nor a callable. - """ - spec = implementedBy(cls) - return self in spec._implied - - def isOrExtends(self, interface): - """Is the interface the same as or extend the given interface - """ - return interface in self._implied - - __call__ = isOrExtends - -SpecificationBase = SpecificationBasePy -try: - from zope.interface._zope_interface_coptimizations import SpecificationBase -except ImportError: - pass - -_marker = object() -class InterfaceBasePy(object): - """Base class that wants to be replaced with a C base :) - """ - - def __call__(self, obj, alternate=_marker): - """Adapt an object to the interface - """ - conform = getattr(obj, '__conform__', None) - if conform is not None: - adapter = self._call_conform(conform) - if adapter is not None: - return adapter - - adapter = self.__adapt__(obj) - - if adapter is not None: - return adapter - elif alternate is not _marker: - return alternate - else: - raise TypeError("Could not adapt", obj, self) - - def __adapt__(self, obj): - """Adapt an object to the reciever - """ - if self.providedBy(obj): - return obj - - for hook in adapter_hooks: - adapter = hook(self, obj) - if adapter is not None: - return adapter - - -InterfaceBase = InterfaceBasePy -try: - from zope.interface._zope_interface_coptimizations import InterfaceBase -except ImportError: - pass - - -adapter_hooks = [] -try: - from zope.interface._zope_interface_coptimizations import adapter_hooks -except ImportError: - pass - - -class Specification(SpecificationBase): - """Specifications - - An interface specification is used to track interface declarations - and component registrations. - - This class is a base class for both interfaces themselves and for - interface specifications (declarations). - - Specifications are mutable. If you reassign their bases, their - relations with other specifications are adjusted accordingly. - """ - - # Copy some base class methods for speed - isOrExtends = SpecificationBase.isOrExtends - providedBy = SpecificationBase.providedBy - - def __init__(self, bases=()): - self._implied = {} - self.dependents = weakref.WeakKeyDictionary() - self.__bases__ = tuple(bases) - - def subscribe(self, dependent): - self.dependents[dependent] = self.dependents.get(dependent, 0) + 1 - - def unsubscribe(self, dependent): - n = self.dependents.get(dependent, 0) - 1 - if not n: - del self.dependents[dependent] - elif n > 0: - self.dependents[dependent] = n - else: - raise KeyError(dependent) - - def __setBases(self, bases): - # Register ourselves as a dependent of our old bases - for b in self.__bases__: - b.unsubscribe(self) - - # Register ourselves as a dependent of our bases - self.__dict__['__bases__'] = bases - for b in bases: - b.subscribe(self) - - self.changed(self) - - __bases__ = property( - - lambda self: self.__dict__.get('__bases__', ()), - __setBases, - ) - - def changed(self, originally_changed): - """We, or something we depend on, have changed - """ - try: - del self._v_attrs - except AttributeError: - pass - - implied = self._implied - implied.clear() - - ancestors = ro(self) - - try: - if Interface not in ancestors: - ancestors.append(Interface) - except NameError: - pass # defining Interface itself - - self.__sro__ = tuple(ancestors) - self.__iro__ = tuple([ancestor for ancestor in ancestors - if isinstance(ancestor, InterfaceClass) - ]) - - for ancestor in ancestors: - # We directly imply our ancestors: - implied[ancestor] = () - - # Now, advise our dependents of change: - for dependent in tuple(self.dependents.keys()): - dependent.changed(originally_changed) - - - def interfaces(self): - """Return an iterator for the interfaces in the specification. - """ - seen = {} - for base in self.__bases__: - for interface in base.interfaces(): - if interface not in seen: - seen[interface] = 1 - yield interface - - - def extends(self, interface, strict=True): - """Does the specification extend the given interface? - - Test whether an interface in the specification extends the - given interface - """ - return ((interface in self._implied) - and - ((not strict) or (self != interface)) - ) - - def weakref(self, callback=None): - return weakref.ref(self, callback) - - def get(self, name, default=None): - """Query for an attribute description - """ - try: - attrs = self._v_attrs - except AttributeError: - attrs = self._v_attrs = {} - attr = attrs.get(name) - if attr is None: - for iface in self.__iro__: - attr = iface.direct(name) - if attr is not None: - attrs[name] = attr - break - - if attr is None: - return default - else: - return attr - -class InterfaceClass(Element, InterfaceBase, Specification): - """Prototype (scarecrow) Interfaces Implementation.""" - - # We can't say this yet because we don't have enough - # infrastructure in place. - # - #implements(IInterface) - - def __init__(self, name, bases=(), attrs=None, __doc__=None, - __module__=None): - - if attrs is None: - attrs = {} - - if __module__ is None: - __module__ = attrs.get('__module__') - if isinstance(__module__, str): - del attrs['__module__'] - else: - try: - # Figure out what module defined the interface. - # This is how cPython figures out the module of - # a class, but of course it does it in C. :-/ - __module__ = sys._getframe(1).f_globals['__name__'] - except (AttributeError, KeyError): # pragma: no cover - pass - - self.__module__ = __module__ - - d = attrs.get('__doc__') - if d is not None: - if not isinstance(d, Attribute): - if __doc__ is None: - __doc__ = d - del attrs['__doc__'] - - if __doc__ is None: - __doc__ = '' - - Element.__init__(self, name, __doc__) - - tagged_data = attrs.pop(TAGGED_DATA, None) - if tagged_data is not None: - for key, val in tagged_data.items(): - self.setTaggedValue(key, val) - - for base in bases: - if not isinstance(base, InterfaceClass): - raise TypeError('Expected base interfaces') - - Specification.__init__(self, bases) - - # Make sure that all recorded attributes (and methods) are of type - # `Attribute` and `Method` - for name, attr in list(attrs.items()): - if name in ('__locals__', '__qualname__', '__annotations__'): - # __locals__: Python 3 sometimes adds this. - # __qualname__: PEP 3155 (Python 3.3+) - # __annotations__: PEP 3107 (Python 3.0+) - del attrs[name] - continue - if isinstance(attr, Attribute): - attr.interface = self - if not attr.__name__: - attr.__name__ = name - elif isinstance(attr, FunctionType): - attrs[name] = fromFunction(attr, self, name=name) - elif attr is _decorator_non_return: - del attrs[name] - else: - raise InvalidInterface("Concrete attribute, " + name) - - self.__attrs = attrs - - self.__identifier__ = "%s.%s" % (self.__module__, self.__name__) - - def interfaces(self): - """Return an iterator for the interfaces in the specification. - """ - yield self - - def getBases(self): - return self.__bases__ - - def isEqualOrExtendedBy(self, other): - """Same interface or extends?""" - return self == other or other.extends(self) - - def names(self, all=False): - """Return the attribute names defined by the interface.""" - if not all: - return self.__attrs.keys() - - r = self.__attrs.copy() - - for base in self.__bases__: - r.update(dict.fromkeys(base.names(all))) - - return r.keys() - - def __iter__(self): - return iter(self.names(all=True)) - - def namesAndDescriptions(self, all=False): - """Return attribute names and descriptions defined by interface.""" - if not all: - return self.__attrs.items() - - r = {} - for base in self.__bases__[::-1]: - r.update(dict(base.namesAndDescriptions(all))) - - r.update(self.__attrs) - - return r.items() - - def getDescriptionFor(self, name): - """Return the attribute description for the given name.""" - r = self.get(name) - if r is not None: - return r - - raise KeyError(name) - - __getitem__ = getDescriptionFor - - def __contains__(self, name): - return self.get(name) is not None - - def direct(self, name): - return self.__attrs.get(name) - - def queryDescriptionFor(self, name, default=None): - return self.get(name, default) - - def validateInvariants(self, obj, errors=None): - """validate object to defined invariants.""" - for call in self.queryTaggedValue('invariants', []): - try: - call(obj) - except Invalid as e: - if errors is None: - raise - else: - errors.append(e) - for base in self.__bases__: - try: - base.validateInvariants(obj, errors) - except Invalid: - if errors is None: - raise - if errors: - raise Invalid(errors) - - def __repr__(self): # pragma: no cover - try: - return self._v_repr - except AttributeError: - name = self.__name__ - m = self.__module__ - if m: - name = '%s.%s' % (m, name) - r = "<%s %s>" % (self.__class__.__name__, name) - self._v_repr = r - return r - - def _call_conform(self, conform): - try: - return conform(self) - except TypeError: # pragma: no cover - # We got a TypeError. It might be an error raised by - # the __conform__ implementation, or *we* may have - # made the TypeError by calling an unbound method - # (object is a class). In the later case, we behave - # as though there is no __conform__ method. We can - # detect this case by checking whether there is more - # than one traceback object in the traceback chain: - if sys.exc_info()[2].tb_next is not None: - # There is more than one entry in the chain, so - # reraise the error: - raise - # This clever trick is from Phillip Eby - - return None # pragma: no cover - - def __reduce__(self): - return self.__name__ - - def __cmp(self, other): - # Yes, I did mean to name this __cmp, rather than __cmp__. - # It is a private method used by __lt__ and __gt__. - # I don't want to override __eq__ because I want the default - # __eq__, which is really fast. - """Make interfaces sortable - - TODO: It would ne nice if: - - More specific interfaces should sort before less specific ones. - Otherwise, sort on name and module. - - But this is too complicated, and we're going to punt on it - for now. - - For now, sort on interface and module name. - - None is treated as a pseudo interface that implies the loosest - contact possible, no contract. For that reason, all interfaces - sort before None. - - """ - if other is None: - return -1 - - n1 = (getattr(self, '__name__', ''), getattr(self, '__module__', '')) - n2 = (getattr(other, '__name__', ''), getattr(other, '__module__', '')) - - # This spelling works under Python3, which doesn't have cmp(). - return (n1 > n2) - (n1 < n2) - - def __hash__(self): - d = self.__dict__ - if '__module__' not in d or '__name__' not in d: # pragma: no cover - warnings.warn('Hashing uninitialized InterfaceClass instance') - return 1 - return hash((self.__name__, self.__module__)) - - def __eq__(self, other): - c = self.__cmp(other) - return c == 0 - - def __ne__(self, other): - c = self.__cmp(other) - return c != 0 - - def __lt__(self, other): - c = self.__cmp(other) - return c < 0 - - def __le__(self, other): - c = self.__cmp(other) - return c <= 0 - - def __gt__(self, other): - c = self.__cmp(other) - return c > 0 - - def __ge__(self, other): - c = self.__cmp(other) - return c >= 0 - - -Interface = InterfaceClass("Interface", __module__ = 'zope.interface') - -class Attribute(Element): - """Attribute descriptions - """ - - # We can't say this yet because we don't have enough - # infrastructure in place. - # - # implements(IAttribute) - - interface = None - - -class Method(Attribute): - """Method interfaces - - The idea here is that you have objects that describe methods. - This provides an opportunity for rich meta-data. - """ - - # We can't say this yet because we don't have enough - # infrastructure in place. - # - # implements(IMethod) - - positional = required = () - _optional = varargs = kwargs = None - def _get_optional(self): - if self._optional is None: - return {} - return self._optional - def _set_optional(self, opt): - self._optional = opt - def _del_optional(self): - self._optional = None - optional = property(_get_optional, _set_optional, _del_optional) - - def __call__(self, *args, **kw): - raise BrokenImplementation(self.interface, self.__name__) - - def getSignatureInfo(self): - return {'positional': self.positional, - 'required': self.required, - 'optional': self.optional, - 'varargs': self.varargs, - 'kwargs': self.kwargs, - } - - def getSignatureString(self): - sig = [] - for v in self.positional: - sig.append(v) - if v in self.optional.keys(): - sig[-1] += "=" + repr(self.optional[v]) - if self.varargs: - sig.append("*" + self.varargs) - if self.kwargs: - sig.append("**" + self.kwargs) - - return "(%s)" % ", ".join(sig) - -def fromFunction(func, interface=None, imlevel=0, name=None): - name = name or func.__name__ - method = Method(name, func.__doc__) - defaults = getattr(func, '__defaults__', None) or () - code = func.__code__ - # Number of positional arguments - na = code.co_argcount-imlevel - names = code.co_varnames[imlevel:] - opt = {} - # Number of required arguments - nr = na-len(defaults) - if nr < 0: - defaults=defaults[-nr:] - nr = 0 - - # Determine the optional arguments. - opt.update(dict(zip(names[nr:], defaults))) - - method.positional = names[:na] - method.required = names[:nr] - method.optional = opt - - argno = na - - # Determine the function's variable argument's name (i.e. *args) - if code.co_flags & CO_VARARGS: - method.varargs = names[argno] - argno = argno + 1 - else: - method.varargs = None - - # Determine the function's keyword argument's name (i.e. **kw) - if code.co_flags & CO_VARKEYWORDS: - method.kwargs = names[argno] - else: - method.kwargs = None - - method.interface = interface - - for key, value in func.__dict__.items(): - method.setTaggedValue(key, value) - - return method - - -def fromMethod(meth, interface=None, name=None): - if isinstance(meth, MethodType): - func = meth.__func__ - else: - func = meth - return fromFunction(func, interface, imlevel=1, name=name) - - -# Now we can create the interesting interfaces and wire them up: -def _wire(): - from zope.interface.declarations import classImplements - - from zope.interface.interfaces import IAttribute - classImplements(Attribute, IAttribute) - - from zope.interface.interfaces import IMethod - classImplements(Method, IMethod) - - from zope.interface.interfaces import IInterface - classImplements(InterfaceClass, IInterface) - - from zope.interface.interfaces import ISpecification - classImplements(Specification, ISpecification) - -# We import this here to deal with module dependencies. -from zope.interface.declarations import implementedBy -from zope.interface.declarations import providedBy -from zope.interface.exceptions import InvalidInterface -from zope.interface.exceptions import BrokenImplementation diff --git a/lib/zope/interface/interfaces.py b/lib/zope/interface/interfaces.py deleted file mode 100644 index 27e64e9..0000000 --- a/lib/zope/interface/interfaces.py +++ /dev/null @@ -1,1282 +0,0 @@ -############################################################################## -# -# Copyright (c) 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Interface Package Interfaces -""" -__docformat__ = 'restructuredtext' - -from zope.interface.interface import Attribute -from zope.interface.interface import Interface -from zope.interface.declarations import implementer - - -_BLANK = u'' - -class IElement(Interface): - """Objects that have basic documentation and tagged values. - """ - - __name__ = Attribute('__name__', 'The object name') - __doc__ = Attribute('__doc__', 'The object doc string') - - def getTaggedValue(tag): - """Returns the value associated with `tag`. - - Raise a `KeyError` of the tag isn't set. - """ - - def queryTaggedValue(tag, default=None): - """Returns the value associated with `tag`. - - Return the default value of the tag isn't set. - """ - - def getTaggedValueTags(): - """Returns a list of all tags.""" - - def setTaggedValue(tag, value): - """Associates `value` with `key`.""" - - -class IAttribute(IElement): - """Attribute descriptors""" - - interface = Attribute('interface', - 'Stores the interface instance in which the ' - 'attribute is located.') - - -class IMethod(IAttribute): - """Method attributes""" - - def getSignatureInfo(): - """Returns the signature information. - - This method returns a dictionary with the following keys: - - o `positional` - All positional arguments. - - o `required` - A list of all required arguments. - - o `optional` - A list of all optional arguments. - - o `varargs` - The name of the varargs argument. - - o `kwargs` - The name of the kwargs argument. - """ - - def getSignatureString(): - """Return a signature string suitable for inclusion in documentation. - - This method returns the function signature string. For example, if you - have `func(a, b, c=1, d='f')`, then the signature string is `(a, b, - c=1, d='f')`. - """ - -class ISpecification(Interface): - """Object Behavioral specifications""" - - def providedBy(object): - """Test whether the interface is implemented by the object - - Return true of the object asserts that it implements the - interface, including asserting that it implements an extended - interface. - """ - - def implementedBy(class_): - """Test whether the interface is implemented by instances of the class - - Return true of the class asserts that its instances implement the - interface, including asserting that they implement an extended - interface. - """ - - def isOrExtends(other): - """Test whether the specification is or extends another - """ - - def extends(other, strict=True): - """Test whether a specification extends another - - The specification extends other if it has other as a base - interface or if one of it's bases extends other. - - If strict is false, then the specification extends itself. - """ - - def weakref(callback=None): - """Return a weakref to the specification - - This method is, regrettably, needed to allow weakrefs to be - computed to security-proxied specifications. While the - zope.interface package does not require zope.security or - zope.proxy, it has to be able to coexist with it. - - """ - - __bases__ = Attribute("""Base specifications - - A tuple if specifications from which this specification is - directly derived. - - """) - - __sro__ = Attribute("""Specification-resolution order - - A tuple of the specification and all of it's ancestor - specifications from most specific to least specific. - - (This is similar to the method-resolution order for new-style classes.) - """) - - __iro__ = Attribute("""Interface-resolution order - - A tuple of the of the specification's ancestor interfaces from - most specific to least specific. The specification itself is - included if it is an interface. - - (This is similar to the method-resolution order for new-style classes.) - """) - - def get(name, default=None): - """Look up the description for a name - - If the named attribute is not defined, the default is - returned. - """ - - -class IInterface(ISpecification, IElement): - """Interface objects - - Interface objects describe the behavior of an object by containing - useful information about the object. This information includes: - - - Prose documentation about the object. In Python terms, this - is called the "doc string" of the interface. In this element, - you describe how the object works in prose language and any - other useful information about the object. - - - Descriptions of attributes. Attribute descriptions include - the name of the attribute and prose documentation describing - the attributes usage. - - - Descriptions of methods. Method descriptions can include: - - - Prose "doc string" documentation about the method and its - usage. - - - A description of the methods arguments; how many arguments - are expected, optional arguments and their default values, - the position or arguments in the signature, whether the - method accepts arbitrary arguments and whether the method - accepts arbitrary keyword arguments. - - - Optional tagged data. Interface objects (and their attributes and - methods) can have optional, application specific tagged data - associated with them. Examples uses for this are examples, - security assertions, pre/post conditions, and other possible - information you may want to associate with an Interface or its - attributes. - - Not all of this information is mandatory. For example, you may - only want the methods of your interface to have prose - documentation and not describe the arguments of the method in - exact detail. Interface objects are flexible and let you give or - take any of these components. - - Interfaces are created with the Python class statement using - either `zope.interface.Interface` or another interface, as in:: - - from zope.interface import Interface - - class IMyInterface(Interface): - '''Interface documentation''' - - def meth(arg1, arg2): - '''Documentation for meth''' - - # Note that there is no self argument - - class IMySubInterface(IMyInterface): - '''Interface documentation''' - - def meth2(): - '''Documentation for meth2''' - - You use interfaces in two ways: - - - You assert that your object implement the interfaces. - - There are several ways that you can assert that an object - implements an interface: - - 1. Call `zope.interface.implements` in your class definition. - - 2. Call `zope.interfaces.directlyProvides` on your object. - - 3. Call `zope.interface.classImplements` to assert that instances - of a class implement an interface. - - For example:: - - from zope.interface import classImplements - - classImplements(some_class, some_interface) - - This approach is useful when it is not an option to modify - the class source. Note that this doesn't affect what the - class itself implements, but only what its instances - implement. - - - You query interface meta-data. See the IInterface methods and - attributes for details. - - """ - - def names(all=False): - """Get the interface attribute names - - Return a sequence of the names of the attributes, including - methods, included in the interface definition. - - Normally, only directly defined attributes are included. If - a true positional or keyword argument is given, then - attributes defined by base classes will be included. - """ - - def namesAndDescriptions(all=False): - """Get the interface attribute names and descriptions - - Return a sequence of the names and descriptions of the - attributes, including methods, as name-value pairs, included - in the interface definition. - - Normally, only directly defined attributes are included. If - a true positional or keyword argument is given, then - attributes defined by base classes will be included. - """ - - def __getitem__(name): - """Get the description for a name - - If the named attribute is not defined, a `KeyError` is raised. - """ - - def direct(name): - """Get the description for the name if it was defined by the interface - - If the interface doesn't define the name, returns None. - """ - - def validateInvariants(obj, errors=None): - """Validate invariants - - Validate object to defined invariants. If errors is None, - raises first Invalid error; if errors is a list, appends all errors - to list, then raises Invalid with the errors as the first element - of the "args" tuple.""" - - def __contains__(name): - """Test whether the name is defined by the interface""" - - def __iter__(): - """Return an iterator over the names defined by the interface - - The names iterated include all of the names defined by the - interface directly and indirectly by base interfaces. - """ - - __module__ = Attribute("""The name of the module defining the interface""") - -class IDeclaration(ISpecification): - """Interface declaration - - Declarations are used to express the interfaces implemented by - classes or provided by objects. - """ - - def __contains__(interface): - """Test whether an interface is in the specification - - Return true if the given interface is one of the interfaces in - the specification and false otherwise. - """ - - def __iter__(): - """Return an iterator for the interfaces in the specification - """ - - def flattened(): - """Return an iterator of all included and extended interfaces - - An iterator is returned for all interfaces either included in - or extended by interfaces included in the specifications - without duplicates. The interfaces are in "interface - resolution order". The interface resolution order is such that - base interfaces are listed after interfaces that extend them - and, otherwise, interfaces are included in the order that they - were defined in the specification. - """ - - def __sub__(interfaces): - """Create an interface specification with some interfaces excluded - - The argument can be an interface or an interface - specifications. The interface or interfaces given in a - specification are subtracted from the interface specification. - - Removing an interface that is not in the specification does - not raise an error. Doing so has no effect. - - Removing an interface also removes sub-interfaces of the interface. - - """ - - def __add__(interfaces): - """Create an interface specification with some interfaces added - - The argument can be an interface or an interface - specifications. The interface or interfaces given in a - specification are added to the interface specification. - - Adding an interface that is already in the specification does - not raise an error. Doing so has no effect. - """ - - def __nonzero__(): - """Return a true value of the interface specification is non-empty - """ - -class IInterfaceDeclaration(Interface): - """Declare and check the interfaces of objects - - The functions defined in this interface are used to declare the - interfaces that objects provide and to query the interfaces that have - been declared. - - Interfaces can be declared for objects in two ways: - - - Interfaces are declared for instances of the object's class - - - Interfaces are declared for the object directly. - - The interfaces declared for an object are, therefore, the union of - interfaces declared for the object directly and the interfaces - declared for instances of the object's class. - - Note that we say that a class implements the interfaces provided - by it's instances. An instance can also provide interfaces - directly. The interfaces provided by an object are the union of - the interfaces provided directly and the interfaces implemented by - the class. - """ - - def providedBy(ob): - """Return the interfaces provided by an object - - This is the union of the interfaces directly provided by an - object and interfaces implemented by it's class. - - The value returned is an `IDeclaration`. - """ - - def implementedBy(class_): - """Return the interfaces implemented for a class' instances - - The value returned is an `IDeclaration`. - """ - - def classImplements(class_, *interfaces): - """Declare additional interfaces implemented for instances of a class - - The arguments after the class are one or more interfaces or - interface specifications (`IDeclaration` objects). - - The interfaces given (including the interfaces in the - specifications) are added to any interfaces previously - declared. - - Consider the following example:: - - class C(A, B): - ... - - classImplements(C, I1, I2) - - - Instances of ``C`` provide ``I1``, ``I2``, and whatever interfaces - instances of ``A`` and ``B`` provide. - """ - - def implementer(*interfaces): - """Create a decorator for declaring interfaces implemented by a factory. - - A callable is returned that makes an implements declaration on - objects passed to it. - """ - - def classImplementsOnly(class_, *interfaces): - """Declare the only interfaces implemented by instances of a class - - The arguments after the class are one or more interfaces or - interface specifications (`IDeclaration` objects). - - The interfaces given (including the interfaces in the - specifications) replace any previous declarations. - - Consider the following example:: - - class C(A, B): - ... - - classImplements(C, IA, IB. IC) - classImplementsOnly(C. I1, I2) - - Instances of ``C`` provide only ``I1``, ``I2``, and regardless of - whatever interfaces instances of ``A`` and ``B`` implement. - """ - - def implementer_only(*interfaces): - """Create a decorator for declaring the only interfaces implemented - - A callable is returned that makes an implements declaration on - objects passed to it. - """ - - def directlyProvidedBy(object): - """Return the interfaces directly provided by the given object - - The value returned is an `IDeclaration`. - """ - - def directlyProvides(object, *interfaces): - """Declare interfaces declared directly for an object - - The arguments after the object are one or more interfaces or - interface specifications (`IDeclaration` objects). - - The interfaces given (including the interfaces in the - specifications) replace interfaces previously - declared for the object. - - Consider the following example:: - - class C(A, B): - ... - - ob = C() - directlyProvides(ob, I1, I2) - - The object, ``ob`` provides ``I1``, ``I2``, and whatever interfaces - instances have been declared for instances of ``C``. - - To remove directly provided interfaces, use `directlyProvidedBy` and - subtract the unwanted interfaces. For example:: - - directlyProvides(ob, directlyProvidedBy(ob)-I2) - - removes I2 from the interfaces directly provided by - ``ob``. The object, ``ob`` no longer directly provides ``I2``, - although it might still provide ``I2`` if it's class - implements ``I2``. - - To add directly provided interfaces, use `directlyProvidedBy` and - include additional interfaces. For example:: - - directlyProvides(ob, directlyProvidedBy(ob), I2) - - adds I2 to the interfaces directly provided by ob. - """ - - def alsoProvides(object, *interfaces): - """Declare additional interfaces directly for an object:: - - alsoProvides(ob, I1) - - is equivalent to:: - - directlyProvides(ob, directlyProvidedBy(ob), I1) - """ - - def noLongerProvides(object, interface): - """Remove an interface from the list of an object's directly - provided interfaces:: - - noLongerProvides(ob, I1) - - is equivalent to:: - - directlyProvides(ob, directlyProvidedBy(ob) - I1) - - with the exception that if ``I1`` is an interface that is - provided by ``ob`` through the class's implementation, - `ValueError` is raised. - """ - - def implements(*interfaces): - """Declare interfaces implemented by instances of a class - - This function is called in a class definition (Python 2.x only). - - The arguments are one or more interfaces or interface - specifications (`IDeclaration` objects). - - The interfaces given (including the interfaces in the - specifications) are added to any interfaces previously - declared. - - Previous declarations include declarations for base classes - unless implementsOnly was used. - - This function is provided for convenience. It provides a more - convenient way to call `classImplements`. For example:: - - implements(I1) - - is equivalent to calling:: - - classImplements(C, I1) - - after the class has been created. - - Consider the following example (Python 2.x only):: - - class C(A, B): - implements(I1, I2) - - - Instances of ``C`` implement ``I1``, ``I2``, and whatever interfaces - instances of ``A`` and ``B`` implement. - """ - - def implementsOnly(*interfaces): - """Declare the only interfaces implemented by instances of a class - - This function is called in a class definition (Python 2.x only). - - The arguments are one or more interfaces or interface - specifications (`IDeclaration` objects). - - Previous declarations including declarations for base classes - are overridden. - - This function is provided for convenience. It provides a more - convenient way to call `classImplementsOnly`. For example:: - - implementsOnly(I1) - - is equivalent to calling:: - - classImplementsOnly(I1) - - after the class has been created. - - Consider the following example (Python 2.x only):: - - class C(A, B): - implementsOnly(I1, I2) - - - Instances of ``C`` implement ``I1``, ``I2``, regardless of what - instances of ``A`` and ``B`` implement. - """ - - def classProvides(*interfaces): - """Declare interfaces provided directly by a class - - This function is called in a class definition. - - The arguments are one or more interfaces or interface - specifications (`IDeclaration` objects). - - The given interfaces (including the interfaces in the - specifications) are used to create the class's direct-object - interface specification. An error will be raised if the module - class has an direct interface specification. In other words, it is - an error to call this function more than once in a class - definition. - - Note that the given interfaces have nothing to do with the - interfaces implemented by instances of the class. - - This function is provided for convenience. It provides a more - convenient way to call `directlyProvides` for a class. For example:: - - classProvides(I1) - - is equivalent to calling:: - - directlyProvides(theclass, I1) - - after the class has been created. - """ - def provider(*interfaces): - """A class decorator version of `classProvides`""" - - def moduleProvides(*interfaces): - """Declare interfaces provided by a module - - This function is used in a module definition. - - The arguments are one or more interfaces or interface - specifications (`IDeclaration` objects). - - The given interfaces (including the interfaces in the - specifications) are used to create the module's direct-object - interface specification. An error will be raised if the module - already has an interface specification. In other words, it is - an error to call this function more than once in a module - definition. - - This function is provided for convenience. It provides a more - convenient way to call `directlyProvides` for a module. For example:: - - moduleImplements(I1) - - is equivalent to:: - - directlyProvides(sys.modules[__name__], I1) - """ - - def Declaration(*interfaces): - """Create an interface specification - - The arguments are one or more interfaces or interface - specifications (`IDeclaration` objects). - - A new interface specification (`IDeclaration`) with - the given interfaces is returned. - """ - -class IAdapterRegistry(Interface): - """Provide an interface-based registry for adapters - - This registry registers objects that are in some sense "from" a - sequence of specification to an interface and a name. - - No specific semantics are assumed for the registered objects, - however, the most common application will be to register factories - that adapt objects providing required specifications to a provided - interface. - """ - - def register(required, provided, name, value): - """Register a value - - A value is registered for a *sequence* of required specifications, a - provided interface, and a name, which must be text. - """ - - def registered(required, provided, name=_BLANK): - """Return the component registered for the given interfaces and name - - name must be text. - - Unlike the lookup method, this methods won't retrieve - components registered for more specific required interfaces or - less specific provided interfaces. - - If no component was registered exactly for the given - interfaces and name, then None is returned. - - """ - - def lookup(required, provided, name='', default=None): - """Lookup a value - - A value is looked up based on a *sequence* of required - specifications, a provided interface, and a name, which must be - text. - """ - - def queryMultiAdapter(objects, provided, name=_BLANK, default=None): - """Adapt a sequence of objects to a named, provided, interface - """ - - def lookup1(required, provided, name=_BLANK, default=None): - """Lookup a value using a single required interface - - A value is looked up based on a single required - specifications, a provided interface, and a name, which must be - text. - """ - - def queryAdapter(object, provided, name=_BLANK, default=None): - """Adapt an object using a registered adapter factory. - """ - - def adapter_hook(provided, object, name=_BLANK, default=None): - """Adapt an object using a registered adapter factory. - - name must be text. - """ - - def lookupAll(required, provided): - """Find all adapters from the required to the provided interfaces - - An iterable object is returned that provides name-value two-tuples. - """ - - def names(required, provided): - """Return the names for which there are registered objects - """ - - def subscribe(required, provided, subscriber, name=_BLANK): - """Register a subscriber - - A subscriber is registered for a *sequence* of required - specifications, a provided interface, and a name. - - Multiple subscribers may be registered for the same (or - equivalent) interfaces. - """ - - def subscriptions(required, provided, name=_BLANK): - """Get a sequence of subscribers - - Subscribers for a *sequence* of required interfaces, and a provided - interface are returned. - """ - - def subscribers(objects, provided, name=_BLANK): - """Get a sequence of subscription adapters - """ - -# begin formerly in zope.component - -class ComponentLookupError(LookupError): - """A component could not be found.""" - -class Invalid(Exception): - """A component doesn't satisfy a promise.""" - -class IObjectEvent(Interface): - """An event related to an object. - - The object that generated this event is not necessarily the object - refered to by location. - """ - - object = Attribute("The subject of the event.") - - -@implementer(IObjectEvent) -class ObjectEvent(object): - - def __init__(self, object): - self.object = object - -class IComponentLookup(Interface): - """Component Manager for a Site - - This object manages the components registered at a particular site. The - definition of a site is intentionally vague. - """ - - adapters = Attribute( - "Adapter Registry to manage all registered adapters.") - - utilities = Attribute( - "Adapter Registry to manage all registered utilities.") - - def queryAdapter(object, interface, name=_BLANK, default=None): - """Look for a named adapter to an interface for an object - - If a matching adapter cannot be found, returns the default. - """ - - def getAdapter(object, interface, name=_BLANK): - """Look for a named adapter to an interface for an object - - If a matching adapter cannot be found, a `ComponentLookupError` - is raised. - """ - - def queryMultiAdapter(objects, interface, name=_BLANK, default=None): - """Look for a multi-adapter to an interface for multiple objects - - If a matching adapter cannot be found, returns the default. - """ - - def getMultiAdapter(objects, interface, name=_BLANK): - """Look for a multi-adapter to an interface for multiple objects - - If a matching adapter cannot be found, a `ComponentLookupError` - is raised. - """ - - def getAdapters(objects, provided): - """Look for all matching adapters to a provided interface for objects - - Return an iterable of name-adapter pairs for adapters that - provide the given interface. - """ - - def subscribers(objects, provided): - """Get subscribers - - Subscribers are returned that provide the provided interface - and that depend on and are comuted from the sequence of - required objects. - """ - - def handle(*objects): - """Call handlers for the given objects - - Handlers registered for the given objects are called. - """ - - def queryUtility(interface, name='', default=None): - """Look up a utility that provides an interface. - - If one is not found, returns default. - """ - - def getUtilitiesFor(interface): - """Look up the registered utilities that provide an interface. - - Returns an iterable of name-utility pairs. - """ - - def getAllUtilitiesRegisteredFor(interface): - """Return all registered utilities for an interface - - This includes overridden utilities. - - An iterable of utility instances is returned. No names are - returned. - """ - -class IRegistration(Interface): - """A registration-information object - """ - - registry = Attribute("The registry having the registration") - - name = Attribute("The registration name") - - info = Attribute("""Information about the registration - - This is information deemed useful to people browsing the - configuration of a system. It could, for example, include - commentary or information about the source of the configuration. - """) - -class IUtilityRegistration(IRegistration): - """Information about the registration of a utility - """ - - factory = Attribute("The factory used to create the utility. Optional.") - component = Attribute("The object registered") - provided = Attribute("The interface provided by the component") - -class _IBaseAdapterRegistration(IRegistration): - """Information about the registration of an adapter - """ - - factory = Attribute("The factory used to create adapters") - - required = Attribute("""The adapted interfaces - - This is a sequence of interfaces adapters by the registered - factory. The factory will be caled with a sequence of objects, as - positional arguments, that provide these interfaces. - """) - - provided = Attribute("""The interface provided by the adapters. - - This interface is implemented by the factory - """) - -class IAdapterRegistration(_IBaseAdapterRegistration): - """Information about the registration of an adapter - """ - -class ISubscriptionAdapterRegistration(_IBaseAdapterRegistration): - """Information about the registration of a subscription adapter - """ - -class IHandlerRegistration(IRegistration): - - handler = Attribute("An object called used to handle an event") - - required = Attribute("""The handled interfaces - - This is a sequence of interfaces handled by the registered - handler. The handler will be caled with a sequence of objects, as - positional arguments, that provide these interfaces. - """) - -class IRegistrationEvent(IObjectEvent): - """An event that involves a registration""" - - -@implementer(IRegistrationEvent) -class RegistrationEvent(ObjectEvent): - """There has been a change in a registration - """ - def __repr__(self): - return "%s event:\n%r" % (self.__class__.__name__, self.object) - -class IRegistered(IRegistrationEvent): - """A component or factory was registered - """ - -@implementer(IRegistered) -class Registered(RegistrationEvent): - pass - -class IUnregistered(IRegistrationEvent): - """A component or factory was unregistered - """ - -@implementer(IUnregistered) -class Unregistered(RegistrationEvent): - """A component or factory was unregistered - """ - pass - -class IComponentRegistry(Interface): - """Register components - """ - - def registerUtility(component=None, provided=None, name=_BLANK, - info=_BLANK, factory=None): - """Register a utility - - :param factory: - Factory for the component to be registered. - - :param component: - The registered component - - :param provided: - This is the interface provided by the utility. If the - component provides a single interface, then this - argument is optional and the component-implemented - interface will be used. - - :param name: - The utility name. - - :param info: - An object that can be converted to a string to provide - information about the registration. - - Only one of *component* and *factory* can be used. - - A `IRegistered` event is generated with an `IUtilityRegistration`. - """ - - def unregisterUtility(component=None, provided=None, name=_BLANK, - factory=None): - """Unregister a utility - - :returns: - A boolean is returned indicating whether the registry was - changed. If the given *component* is None and there is no - component registered, or if the given *component* is not - None and is not registered, then the function returns - False, otherwise it returns True. - - :param factory: - Factory for the component to be unregistered. - - :param component: - The registered component The given component can be - None, in which case any component registered to provide - the given provided interface with the given name is - unregistered. - - :param provided: - This is the interface provided by the utility. If the - component is not None and provides a single interface, - then this argument is optional and the - component-implemented interface will be used. - - :param name: - The utility name. - - Only one of *component* and *factory* can be used. - An `IUnregistered` event is generated with an `IUtilityRegistration`. - """ - - def registeredUtilities(): - """Return an iterable of `IUtilityRegistration` instances. - - These registrations describe the current utility registrations - in the object. - """ - - def registerAdapter(factory, required=None, provided=None, name=_BLANK, - info=_BLANK): - """Register an adapter factory - - :param factory: - The object used to compute the adapter - - :param required: - This is a sequence of specifications for objects to be - adapted. If omitted, then the value of the factory's - ``__component_adapts__`` attribute will be used. The - ``__component_adapts__`` attribute is - normally set in class definitions using - the `.adapter` - decorator. If the factory doesn't have a - ``__component_adapts__`` adapts attribute, then this - argument is required. - - :param provided: - This is the interface provided by the adapter and - implemented by the factory. If the factory - implements a single interface, then this argument is - optional and the factory-implemented interface will be - used. - - :param name: - The adapter name. - - :param info: - An object that can be converted to a string to provide - information about the registration. - - A `IRegistered` event is generated with an `IAdapterRegistration`. - """ - - def unregisterAdapter(factory=None, required=None, - provided=None, name=_BLANK): - """Unregister an adapter factory - - :returns: - A boolean is returned indicating whether the registry was - changed. If the given component is None and there is no - component registered, or if the given component is not - None and is not registered, then the function returns - False, otherwise it returns True. - - :param factory: - This is the object used to compute the adapter. The - factory can be None, in which case any factory - registered to implement the given provided interface - for the given required specifications with the given - name is unregistered. - - :param required: - This is a sequence of specifications for objects to be - adapted. If the factory is not None and the required - arguments is omitted, then the value of the factory's - __component_adapts__ attribute will be used. The - __component_adapts__ attribute attribute is normally - set in class definitions using adapts function, or for - callables using the adapter decorator. If the factory - is None or doesn't have a __component_adapts__ adapts - attribute, then this argument is required. - - :param provided: - This is the interface provided by the adapter and - implemented by the factory. If the factory is not - None and implements a single interface, then this - argument is optional and the factory-implemented - interface will be used. - - :param name: - The adapter name. - - An `IUnregistered` event is generated with an `IAdapterRegistration`. - """ - - def registeredAdapters(): - """Return an iterable of `IAdapterRegistration` instances. - - These registrations describe the current adapter registrations - in the object. - """ - - def registerSubscriptionAdapter(factory, required=None, provides=None, - name=_BLANK, info=''): - """Register a subscriber factory - - :param factory: - The object used to compute the adapter - - :param required: - This is a sequence of specifications for objects to be - adapted. If omitted, then the value of the factory's - ``__component_adapts__`` attribute will be used. The - ``__component_adapts__`` attribute is - normally set using the adapter - decorator. If the factory doesn't have a - ``__component_adapts__`` adapts attribute, then this - argument is required. - - :param provided: - This is the interface provided by the adapter and - implemented by the factory. If the factory implements - a single interface, then this argument is optional and - the factory-implemented interface will be used. - - :param name: - The adapter name. - - Currently, only the empty string is accepted. Other - strings will be accepted in the future when support for - named subscribers is added. - - :param info: - An object that can be converted to a string to provide - information about the registration. - - A `IRegistered` event is generated with an - `ISubscriptionAdapterRegistration`. - """ - - def unregisterSubscriptionAdapter(factory=None, required=None, - provides=None, name=_BLANK): - """Unregister a subscriber factory. - - :returns: - A boolean is returned indicating whether the registry was - changed. If the given component is None and there is no - component registered, or if the given component is not - None and is not registered, then the function returns - False, otherwise it returns True. - - :param factory: - This is the object used to compute the adapter. The - factory can be None, in which case any factories - registered to implement the given provided interface - for the given required specifications with the given - name are unregistered. - - :param required: - This is a sequence of specifications for objects to be - adapted. If omitted, then the value of the factory's - ``__component_adapts__`` attribute will be used. The - ``__component_adapts__`` attribute is - normally set using the adapter - decorator. If the factory doesn't have a - ``__component_adapts__`` adapts attribute, then this - argument is required. - - :param provided: - This is the interface provided by the adapter and - implemented by the factory. If the factory is not - None implements a single interface, then this argument - is optional and the factory-implemented interface will - be used. - - :param name: - The adapter name. - - Currently, only the empty string is accepted. Other - strings will be accepted in the future when support for - named subscribers is added. - - An `IUnregistered` event is generated with an - `ISubscriptionAdapterRegistration`. - """ - - def registeredSubscriptionAdapters(): - """Return an iterable of `ISubscriptionAdapterRegistration` instances. - - These registrations describe the current subscription adapter - registrations in the object. - """ - - def registerHandler(handler, required=None, name=_BLANK, info=''): - """Register a handler. - - A handler is a subscriber that doesn't compute an adapter - but performs some function when called. - - :param handler: - The object used to handle some event represented by - the objects passed to it. - - :param required: - This is a sequence of specifications for objects to be - adapted. If omitted, then the value of the factory's - ``__component_adapts__`` attribute will be used. The - ``__component_adapts__`` attribute is - normally set using the adapter - decorator. If the factory doesn't have a - ``__component_adapts__`` adapts attribute, then this - argument is required. - - :param name: - The handler name. - - Currently, only the empty string is accepted. Other - strings will be accepted in the future when support for - named handlers is added. - - :param info: - An object that can be converted to a string to provide - information about the registration. - - - A `IRegistered` event is generated with an `IHandlerRegistration`. - """ - - def unregisterHandler(handler=None, required=None, name=_BLANK): - """Unregister a handler. - - A handler is a subscriber that doesn't compute an adapter - but performs some function when called. - - :returns: A boolean is returned indicating whether the registry was - changed. - - :param handler: - This is the object used to handle some event - represented by the objects passed to it. The handler - can be None, in which case any handlers registered for - the given required specifications with the given are - unregistered. - - :param required: - This is a sequence of specifications for objects to be - adapted. If omitted, then the value of the factory's - ``__component_adapts__`` attribute will be used. The - ``__component_adapts__`` attribute is - normally set using the adapter - decorator. If the factory doesn't have a - ``__component_adapts__`` adapts attribute, then this - argument is required. - - :param name: - The handler name. - - Currently, only the empty string is accepted. Other - strings will be accepted in the future when support for - named handlers is added. - - An `IUnregistered` event is generated with an `IHandlerRegistration`. - """ - - def registeredHandlers(): - """Return an iterable of `IHandlerRegistration` instances. - - These registrations describe the current handler registrations - in the object. - """ - - -class IComponents(IComponentLookup, IComponentRegistry): - """Component registration and access - """ - - -# end formerly in zope.component diff --git a/lib/zope/interface/registry.py b/lib/zope/interface/registry.py deleted file mode 100644 index bba0267..0000000 --- a/lib/zope/interface/registry.py +++ /dev/null @@ -1,654 +0,0 @@ -############################################################################## -# -# Copyright (c) 2006 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Basic components support -""" -from collections import defaultdict - -try: - from zope.event import notify -except ImportError: # pragma: no cover - def notify(*arg, **kw): pass - -from zope.interface.interfaces import ISpecification -from zope.interface.interfaces import ComponentLookupError -from zope.interface.interfaces import IAdapterRegistration -from zope.interface.interfaces import IComponents -from zope.interface.interfaces import IHandlerRegistration -from zope.interface.interfaces import ISubscriptionAdapterRegistration -from zope.interface.interfaces import IUtilityRegistration -from zope.interface.interfaces import Registered -from zope.interface.interfaces import Unregistered - -from zope.interface.interface import Interface -from zope.interface.declarations import implementedBy -from zope.interface.declarations import implementer -from zope.interface.declarations import implementer_only -from zope.interface.declarations import providedBy -from zope.interface.adapter import AdapterRegistry -from zope.interface._compat import CLASS_TYPES -from zope.interface._compat import STRING_TYPES - - -class _UnhashableComponentCounter(object): - # defaultdict(int)-like object for unhashable components - - def __init__(self, otherdict): - # [(component, count)] - self._data = [item for item in otherdict.items()] - - def __getitem__(self, key): - for component, count in self._data: - if component == key: - return count - return 0 - - def __setitem__(self, component, count): - for i, data in enumerate(self._data): - if data[0] == component: - self._data[i] = component, count - return - self._data.append((component, count)) - - def __delitem__(self, component): - for i, data in enumerate(self._data): - if data[0] == component: - del self._data[i] - return - raise KeyError(component) # pragma: no cover - -def _defaultdict_int(): - return defaultdict(int) - -class _UtilityRegistrations(object): - - def __init__(self, utilities, utility_registrations): - # {provided -> {component: count}} - self._cache = defaultdict(_defaultdict_int) - self._utilities = utilities - self._utility_registrations = utility_registrations - - self.__populate_cache() - - def __populate_cache(self): - for ((p, _), data) in iter(self._utility_registrations.items()): - component = data[0] - self.__cache_utility(p, component) - - def __cache_utility(self, provided, component): - try: - self._cache[provided][component] += 1 - except TypeError: - # The component is not hashable, and we have a dict. Switch to a strategy - # that doesn't use hashing. - prov = self._cache[provided] = _UnhashableComponentCounter(self._cache[provided]) - prov[component] += 1 - - def __uncache_utility(self, provided, component): - provided = self._cache[provided] - # It seems like this line could raise a TypeError if component isn't - # hashable and we haven't yet switched to _UnhashableComponentCounter. However, - # we can't actually get in that situation. In order to get here, we would - # have had to cache the utility already which would have switched - # the datastructure if needed. - count = provided[component] - count -= 1 - if count == 0: - del provided[component] - else: - provided[component] = count - return count > 0 - - def _is_utility_subscribed(self, provided, component): - try: - return self._cache[provided][component] > 0 - except TypeError: - # Not hashable and we're still using a dict - return False - - def registerUtility(self, provided, name, component, info, factory): - subscribed = self._is_utility_subscribed(provided, component) - - self._utility_registrations[(provided, name)] = component, info, factory - self._utilities.register((), provided, name, component) - - if not subscribed: - self._utilities.subscribe((), provided, component) - - self.__cache_utility(provided, component) - - def unregisterUtility(self, provided, name, component): - del self._utility_registrations[(provided, name)] - self._utilities.unregister((), provided, name) - - subscribed = self.__uncache_utility(provided, component) - - if not subscribed: - self._utilities.unsubscribe((), provided, component) - - -@implementer(IComponents) -class Components(object): - - _v_utility_registrations_cache = None - - def __init__(self, name='', bases=()): - # __init__ is used for test cleanup as well as initialization. - # XXX add a separate API for test cleanup. - assert isinstance(name, STRING_TYPES) - self.__name__ = name - self._init_registries() - self._init_registrations() - self.__bases__ = tuple(bases) - self._v_utility_registrations_cache = None - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.__name__) - - def __reduce__(self): - # Mimic what a persistent.Persistent object does and elide - # _v_ attributes so that they don't get saved in ZODB. - # This allows us to store things that cannot be pickled in such - # attributes. - reduction = super(Components, self).__reduce__() - # (callable, args, state, listiter, dictiter) - # We assume the state is always a dict; the last three items - # are technically optional and can be missing or None. - filtered_state = {k: v for k, v in reduction[2].items() - if not k.startswith('_v_')} - reduction = list(reduction) - reduction[2] = filtered_state - return tuple(reduction) - - def _init_registries(self): - # Subclasses have never been required to call this method - # if they override it, merely to fill in these two attributes. - self.adapters = AdapterRegistry() - self.utilities = AdapterRegistry() - - def _init_registrations(self): - self._utility_registrations = {} - self._adapter_registrations = {} - self._subscription_registrations = [] - self._handler_registrations = [] - - @property - def _utility_registrations_cache(self): - # We use a _v_ attribute internally so that data aren't saved in ZODB, - # because this object cannot be pickled. - cache = self._v_utility_registrations_cache - if (cache is None - or cache._utilities is not self.utilities - or cache._utility_registrations is not self._utility_registrations): - cache = self._v_utility_registrations_cache = _UtilityRegistrations( - self.utilities, - self._utility_registrations) - return cache - - def _getBases(self): - # Subclasses might override - return self.__dict__.get('__bases__', ()) - - def _setBases(self, bases): - # Subclasses might override - self.adapters.__bases__ = tuple([ - base.adapters for base in bases]) - self.utilities.__bases__ = tuple([ - base.utilities for base in bases]) - self.__dict__['__bases__'] = tuple(bases) - - __bases__ = property( - lambda self: self._getBases(), - lambda self, bases: self._setBases(bases), - ) - - def registerUtility(self, component=None, provided=None, name=u'', - info=u'', event=True, factory=None): - if factory: - if component: - raise TypeError("Can't specify factory and component.") - component = factory() - - if provided is None: - provided = _getUtilityProvided(component) - - if name == u'': - name = _getName(component) - - reg = self._utility_registrations.get((provided, name)) - if reg is not None: - if reg[:2] == (component, info): - # already registered - return - self.unregisterUtility(reg[0], provided, name) - - self._utility_registrations_cache.registerUtility( - provided, name, component, info, factory) - - if event: - notify(Registered( - UtilityRegistration(self, provided, name, component, info, - factory) - )) - - def unregisterUtility(self, component=None, provided=None, name=u'', - factory=None): - if factory: - if component: - raise TypeError("Can't specify factory and component.") - component = factory() - - if provided is None: - if component is None: - raise TypeError("Must specify one of component, factory and " - "provided") - provided = _getUtilityProvided(component) - - old = self._utility_registrations.get((provided, name)) - if (old is None) or ((component is not None) and - (component != old[0])): - return False - - if component is None: - component = old[0] - - # Note that component is now the old thing registered - self._utility_registrations_cache.unregisterUtility( - provided, name, component) - - notify(Unregistered( - UtilityRegistration(self, provided, name, component, *old[1:]) - )) - - return True - - def registeredUtilities(self): - for ((provided, name), data - ) in iter(self._utility_registrations.items()): - yield UtilityRegistration(self, provided, name, *data) - - def queryUtility(self, provided, name=u'', default=None): - return self.utilities.lookup((), provided, name, default) - - def getUtility(self, provided, name=u''): - utility = self.utilities.lookup((), provided, name) - if utility is None: - raise ComponentLookupError(provided, name) - return utility - - def getUtilitiesFor(self, interface): - for name, utility in self.utilities.lookupAll((), interface): - yield name, utility - - def getAllUtilitiesRegisteredFor(self, interface): - return self.utilities.subscriptions((), interface) - - def registerAdapter(self, factory, required=None, provided=None, - name=u'', info=u'', event=True): - if provided is None: - provided = _getAdapterProvided(factory) - required = _getAdapterRequired(factory, required) - if name == u'': - name = _getName(factory) - self._adapter_registrations[(required, provided, name) - ] = factory, info - self.adapters.register(required, provided, name, factory) - - if event: - notify(Registered( - AdapterRegistration(self, required, provided, name, - factory, info) - )) - - - def unregisterAdapter(self, factory=None, - required=None, provided=None, name=u'', - ): - if provided is None: - if factory is None: - raise TypeError("Must specify one of factory and provided") - provided = _getAdapterProvided(factory) - - if (required is None) and (factory is None): - raise TypeError("Must specify one of factory and required") - - required = _getAdapterRequired(factory, required) - old = self._adapter_registrations.get((required, provided, name)) - if (old is None) or ((factory is not None) and - (factory != old[0])): - return False - - del self._adapter_registrations[(required, provided, name)] - self.adapters.unregister(required, provided, name) - - notify(Unregistered( - AdapterRegistration(self, required, provided, name, - *old) - )) - - return True - - def registeredAdapters(self): - for ((required, provided, name), (component, info) - ) in iter(self._adapter_registrations.items()): - yield AdapterRegistration(self, required, provided, name, - component, info) - - def queryAdapter(self, object, interface, name=u'', default=None): - return self.adapters.queryAdapter(object, interface, name, default) - - def getAdapter(self, object, interface, name=u''): - adapter = self.adapters.queryAdapter(object, interface, name) - if adapter is None: - raise ComponentLookupError(object, interface, name) - return adapter - - def queryMultiAdapter(self, objects, interface, name=u'', - default=None): - return self.adapters.queryMultiAdapter( - objects, interface, name, default) - - def getMultiAdapter(self, objects, interface, name=u''): - adapter = self.adapters.queryMultiAdapter(objects, interface, name) - if adapter is None: - raise ComponentLookupError(objects, interface, name) - return adapter - - def getAdapters(self, objects, provided): - for name, factory in self.adapters.lookupAll( - list(map(providedBy, objects)), - provided): - adapter = factory(*objects) - if adapter is not None: - yield name, adapter - - def registerSubscriptionAdapter(self, - factory, required=None, provided=None, - name=u'', info=u'', - event=True): - if name: - raise TypeError("Named subscribers are not yet supported") - if provided is None: - provided = _getAdapterProvided(factory) - required = _getAdapterRequired(factory, required) - self._subscription_registrations.append( - (required, provided, name, factory, info) - ) - self.adapters.subscribe(required, provided, factory) - - if event: - notify(Registered( - SubscriptionRegistration(self, required, provided, name, - factory, info) - )) - - def registeredSubscriptionAdapters(self): - for data in self._subscription_registrations: - yield SubscriptionRegistration(self, *data) - - def unregisterSubscriptionAdapter(self, factory=None, - required=None, provided=None, name=u'', - ): - if name: - raise TypeError("Named subscribers are not yet supported") - if provided is None: - if factory is None: - raise TypeError("Must specify one of factory and provided") - provided = _getAdapterProvided(factory) - - if (required is None) and (factory is None): - raise TypeError("Must specify one of factory and required") - - required = _getAdapterRequired(factory, required) - - if factory is None: - new = [(r, p, n, f, i) - for (r, p, n, f, i) - in self._subscription_registrations - if not (r == required and p == provided) - ] - else: - new = [(r, p, n, f, i) - for (r, p, n, f, i) - in self._subscription_registrations - if not (r == required and p == provided and f == factory) - ] - - if len(new) == len(self._subscription_registrations): - return False - - - self._subscription_registrations[:] = new - self.adapters.unsubscribe(required, provided, factory) - - notify(Unregistered( - SubscriptionRegistration(self, required, provided, name, - factory, '') - )) - - return True - - def subscribers(self, objects, provided): - return self.adapters.subscribers(objects, provided) - - def registerHandler(self, - factory, required=None, - name=u'', info=u'', - event=True): - if name: - raise TypeError("Named handlers are not yet supported") - required = _getAdapterRequired(factory, required) - self._handler_registrations.append( - (required, name, factory, info) - ) - self.adapters.subscribe(required, None, factory) - - if event: - notify(Registered( - HandlerRegistration(self, required, name, factory, info) - )) - - def registeredHandlers(self): - for data in self._handler_registrations: - yield HandlerRegistration(self, *data) - - def unregisterHandler(self, factory=None, required=None, name=u''): - if name: - raise TypeError("Named subscribers are not yet supported") - - if (required is None) and (factory is None): - raise TypeError("Must specify one of factory and required") - - required = _getAdapterRequired(factory, required) - - if factory is None: - new = [(r, n, f, i) - for (r, n, f, i) - in self._handler_registrations - if r != required - ] - else: - new = [(r, n, f, i) - for (r, n, f, i) - in self._handler_registrations - if not (r == required and f == factory) - ] - - if len(new) == len(self._handler_registrations): - return False - - self._handler_registrations[:] = new - self.adapters.unsubscribe(required, None, factory) - - notify(Unregistered( - HandlerRegistration(self, required, name, factory, '') - )) - - return True - - def handle(self, *objects): - self.adapters.subscribers(objects, None) - - -def _getName(component): - try: - return component.__component_name__ - except AttributeError: - return u'' - -def _getUtilityProvided(component): - provided = list(providedBy(component)) - if len(provided) == 1: - return provided[0] - raise TypeError( - "The utility doesn't provide a single interface " - "and no provided interface was specified.") - -def _getAdapterProvided(factory): - provided = list(implementedBy(factory)) - if len(provided) == 1: - return provided[0] - raise TypeError( - "The adapter factory doesn't implement a single interface " - "and no provided interface was specified.") - -def _getAdapterRequired(factory, required): - if required is None: - try: - required = factory.__component_adapts__ - except AttributeError: - raise TypeError( - "The adapter factory doesn't have a __component_adapts__ " - "attribute and no required specifications were specified" - ) - elif ISpecification.providedBy(required): - raise TypeError("the required argument should be a list of " - "interfaces, not a single interface") - - result = [] - for r in required: - if r is None: - r = Interface - elif not ISpecification.providedBy(r): - if isinstance(r, CLASS_TYPES): - r = implementedBy(r) - else: - raise TypeError("Required specification must be a " - "specification or class." - ) - result.append(r) - return tuple(result) - - -@implementer(IUtilityRegistration) -class UtilityRegistration(object): - - def __init__(self, registry, provided, name, component, doc, factory=None): - (self.registry, self.provided, self.name, self.component, self.info, - self.factory - ) = registry, provided, name, component, doc, factory - - def __repr__(self): - return '%s(%r, %s, %r, %s, %r, %r)' % ( - self.__class__.__name__, - self.registry, - getattr(self.provided, '__name__', None), self.name, - getattr(self.component, '__name__', repr(self.component)), - self.factory, self.info, - ) - - def __hash__(self): - return id(self) - - def __eq__(self, other): - return repr(self) == repr(other) - - def __ne__(self, other): - return repr(self) != repr(other) - - def __lt__(self, other): - return repr(self) < repr(other) - - def __le__(self, other): - return repr(self) <= repr(other) - - def __gt__(self, other): - return repr(self) > repr(other) - - def __ge__(self, other): - return repr(self) >= repr(other) - -@implementer(IAdapterRegistration) -class AdapterRegistration(object): - - def __init__(self, registry, required, provided, name, component, doc): - (self.registry, self.required, self.provided, self.name, - self.factory, self.info - ) = registry, required, provided, name, component, doc - - def __repr__(self): - return '%s(%r, %s, %s, %r, %s, %r)' % ( - self.__class__.__name__, - self.registry, - '[' + ", ".join([r.__name__ for r in self.required]) + ']', - getattr(self.provided, '__name__', None), self.name, - getattr(self.factory, '__name__', repr(self.factory)), self.info, - ) - - def __hash__(self): - return id(self) - - def __eq__(self, other): - return repr(self) == repr(other) - - def __ne__(self, other): - return repr(self) != repr(other) - - def __lt__(self, other): - return repr(self) < repr(other) - - def __le__(self, other): - return repr(self) <= repr(other) - - def __gt__(self, other): - return repr(self) > repr(other) - - def __ge__(self, other): - return repr(self) >= repr(other) - -@implementer_only(ISubscriptionAdapterRegistration) -class SubscriptionRegistration(AdapterRegistration): - pass - - -@implementer_only(IHandlerRegistration) -class HandlerRegistration(AdapterRegistration): - - def __init__(self, registry, required, name, handler, doc): - (self.registry, self.required, self.name, self.handler, self.info - ) = registry, required, name, handler, doc - - @property - def factory(self): - return self.handler - - provided = None - - def __repr__(self): - return '%s(%r, %s, %r, %s, %r)' % ( - self.__class__.__name__, - self.registry, - '[' + ", ".join([r.__name__ for r in self.required]) + ']', - self.name, - getattr(self.factory, '__name__', repr(self.factory)), self.info, - ) diff --git a/lib/zope/interface/ro.py b/lib/zope/interface/ro.py deleted file mode 100644 index 84f10dc..0000000 --- a/lib/zope/interface/ro.py +++ /dev/null @@ -1,64 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Compute a resolution order for an object and its bases -""" -__docformat__ = 'restructuredtext' - -def _mergeOrderings(orderings): - """Merge multiple orderings so that within-ordering order is preserved - - Orderings are constrained in such a way that if an object appears - in two or more orderings, then the suffix that begins with the - object must be in both orderings. - - For example: - - >>> _mergeOrderings([ - ... ['x', 'y', 'z'], - ... ['q', 'z'], - ... [1, 3, 5], - ... ['z'] - ... ]) - ['x', 'y', 'q', 1, 3, 5, 'z'] - - """ - - seen = {} - result = [] - for ordering in reversed(orderings): - for o in reversed(ordering): - if o not in seen: - seen[o] = 1 - result.insert(0, o) - - return result - -def _flatten(ob): - result = [ob] - i = 0 - for ob in iter(result): - i += 1 - # The recursive calls can be avoided by inserting the base classes - # into the dynamically growing list directly after the currently - # considered object; the iterator makes sure this will keep working - # in the future, since it cannot rely on the length of the list - # by definition. - result[i:i] = ob.__bases__ - return result - - -def ro(object): - """Compute a "resolution order" for an object - """ - return _mergeOrderings([_flatten(object)]) diff --git a/lib/zope/interface/tests/__init__.py b/lib/zope/interface/tests/__init__.py deleted file mode 100644 index 15259c1..0000000 --- a/lib/zope/interface/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Make this directory a package. diff --git a/lib/zope/interface/tests/advisory_testing.py b/lib/zope/interface/tests/advisory_testing.py deleted file mode 100644 index b159e93..0000000 --- a/lib/zope/interface/tests/advisory_testing.py +++ /dev/null @@ -1,42 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -import sys - -from zope.interface.advice import addClassAdvisor -from zope.interface.advice import getFrameInfo - -my_globals = globals() - -def ping(log, value): - - def pong(klass): - log.append((value,klass)) - return [klass] - - addClassAdvisor(pong) - -try: - from types import ClassType - - class ClassicClass: - __metaclass__ = ClassType - classLevelFrameInfo = getFrameInfo(sys._getframe()) -except ImportError: - ClassicClass = None - -class NewStyleClass: - __metaclass__ = type - classLevelFrameInfo = getFrameInfo(sys._getframe()) - -moduleLevelFrameInfo = getFrameInfo(sys._getframe()) diff --git a/lib/zope/interface/tests/dummy.py b/lib/zope/interface/tests/dummy.py deleted file mode 100644 index 6b142ff..0000000 --- a/lib/zope/interface/tests/dummy.py +++ /dev/null @@ -1,23 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -""" Dummy Module -""" -from zope.interface import moduleProvides -from zope.interface.tests.idummy import IDummyModule - -moduleProvides(IDummyModule) - -def bar(baz): - # Note: no 'self', because the module provides the interface directly. - raise NotImplementedError() diff --git a/lib/zope/interface/tests/idummy.py b/lib/zope/interface/tests/idummy.py deleted file mode 100644 index 1e34fe0..0000000 --- a/lib/zope/interface/tests/idummy.py +++ /dev/null @@ -1,23 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -""" Interface describing API of zope.interface.tests.dummy test module -""" -from zope.interface import Interface - -class IDummyModule(Interface): - """ Dummy interface for unit tests. - """ - def bar(baz): - """ Just a note. - """ diff --git a/lib/zope/interface/tests/ifoo.py b/lib/zope/interface/tests/ifoo.py deleted file mode 100644 index 29a7877..0000000 --- a/lib/zope/interface/tests/ifoo.py +++ /dev/null @@ -1,26 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""IFoo test module -""" -from zope.interface import Interface - -class IFoo(Interface): - """ - Dummy interface for unit tests. - """ - - def bar(baz): - """ - Just a note. - """ diff --git a/lib/zope/interface/tests/ifoo_other.py b/lib/zope/interface/tests/ifoo_other.py deleted file mode 100644 index 29a7877..0000000 --- a/lib/zope/interface/tests/ifoo_other.py +++ /dev/null @@ -1,26 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""IFoo test module -""" -from zope.interface import Interface - -class IFoo(Interface): - """ - Dummy interface for unit tests. - """ - - def bar(baz): - """ - Just a note. - """ diff --git a/lib/zope/interface/tests/m1.py b/lib/zope/interface/tests/m1.py deleted file mode 100644 index d311fb4..0000000 --- a/lib/zope/interface/tests/m1.py +++ /dev/null @@ -1,21 +0,0 @@ -############################################################################## -# -# Copyright (c) 2004 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Test module that declares an interface -""" -from zope.interface import Interface, moduleProvides - -class I1(Interface): pass -class I2(Interface): pass - -moduleProvides(I1, I2) diff --git a/lib/zope/interface/tests/m2.py b/lib/zope/interface/tests/m2.py deleted file mode 100644 index 511cd9c..0000000 --- a/lib/zope/interface/tests/m2.py +++ /dev/null @@ -1,15 +0,0 @@ -############################################################################## -# -# Copyright (c) 2004 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Test module that doesn't declare an interface -""" diff --git a/lib/zope/interface/tests/odd.py b/lib/zope/interface/tests/odd.py deleted file mode 100644 index 74c6158..0000000 --- a/lib/zope/interface/tests/odd.py +++ /dev/null @@ -1,128 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Odd meta class that doesn't subclass type. - -This is used for testing support for ExtensionClass in new interfaces. - - >>> class A(object): - ... __metaclass__ = MetaClass - ... a = 1 - ... - >>> A.__name__ - 'A' - >>> A.__bases__ == (object,) - True - >>> class B(object): - ... __metaclass__ = MetaClass - ... b = 1 - ... - >>> class C(A, B): pass - ... - >>> C.__name__ - 'C' - >>> int(C.__bases__ == (A, B)) - 1 - >>> a = A() - >>> aa = A() - >>> a.a - 1 - >>> aa.a - 1 - >>> aa.a = 2 - >>> a.a - 1 - >>> aa.a - 2 - >>> c = C() - >>> c.a - 1 - >>> c.b - 1 - >>> c.b = 2 - >>> c.b - 2 - >>> C.c = 1 - >>> c.c - 1 - >>> import sys - >>> if sys.version[0] == '2': # This test only makes sense under Python 2.x - ... from types import ClassType - ... assert not isinstance(C, (type, ClassType)) - - >>> int(C.__class__.__class__ is C.__class__) - 1 -""" - -# class OddClass is an odd meta class - -class MetaMetaClass(type): - - def __getattribute__(cls, name): - if name == '__class__': - return cls - # Under Python 3.6, __prepare__ gets requested - return type.__getattribute__(cls, name) - - -class MetaClass(object): - """Odd classes - """ - - def __init__(self, name, bases, dict): - self.__name__ = name - self.__bases__ = bases - self.__dict__.update(dict) - - def __call__(self): - return OddInstance(self) - - def __getattr__(self, name): - for b in self.__bases__: - v = getattr(b, name, self) - if v is not self: - return v - raise AttributeError(name) - - def __repr__(self): # pragma: no cover - return "<odd class %s at %s>" % (self.__name__, hex(id(self))) - - -MetaClass = MetaMetaClass('MetaClass', - MetaClass.__bases__, - {k: v for k, v in MetaClass.__dict__.items() - if k not in ('__dict__',)}) - -class OddInstance(object): - - def __init__(self, cls): - self.__dict__['__class__'] = cls - - def __getattribute__(self, name): - dict = object.__getattribute__(self, '__dict__') - if name == '__dict__': - return dict - v = dict.get(name, self) - if v is not self: - return v - return getattr(dict['__class__'], name) - - def __setattr__(self, name, v): - self.__dict__[name] = v - - def __delattr__(self, name): - raise NotImplementedError() - - def __repr__(self): # pragma: no cover - return "<odd %s instance at %s>" % ( - self.__class__.__name__, hex(id(self))) diff --git a/lib/zope/interface/tests/test_adapter.py b/lib/zope/interface/tests/test_adapter.py deleted file mode 100644 index 41c618c..0000000 --- a/lib/zope/interface/tests/test_adapter.py +++ /dev/null @@ -1,1419 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Adapter registry tests -""" -import unittest - - -def _makeInterfaces(): - from zope.interface import Interface - - class IB0(Interface): pass - class IB1(IB0): pass - class IB2(IB0): pass - class IB3(IB2, IB1): pass - class IB4(IB1, IB2): pass - - class IF0(Interface): pass - class IF1(IF0): pass - - class IR0(Interface): pass - class IR1(IR0): pass - - return IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 - - -class BaseAdapterRegistryTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.adapter import BaseAdapterRegistry - class _CUT(BaseAdapterRegistry): - class LookupClass(object): - _changed = _extendors = () - def __init__(self, reg): - pass - def changed(self, orig): - self._changed += (orig,) - def add_extendor(self, provided): - self._extendors += (provided,) - def remove_extendor(self, provided): - self._extendors = tuple([x for x in self._extendors - if x != provided]) - for name in BaseAdapterRegistry._delegated: - setattr(_CUT.LookupClass, name, object()) - return _CUT - - def _makeOne(self): - return self._getTargetClass()() - - def test_lookup_delegation(self): - CUT = self._getTargetClass() - registry = CUT() - for name in CUT._delegated: - self.assertTrue( - getattr(registry, name) is getattr(registry._v_lookup, name)) - - def test__generation_on_first_creation(self): - registry = self._makeOne() - # Bumped to 1 in BaseAdapterRegistry.__init__ - self.assertEqual(registry._generation, 1) - - def test__generation_after_calling_changed(self): - registry = self._makeOne() - orig = object() - registry.changed(orig) - # Bumped to 1 in BaseAdapterRegistry.__init__ - self.assertEqual(registry._generation, 2) - self.assertEqual(registry._v_lookup._changed, (registry, orig,)) - - def test__generation_after_changing___bases__(self): - class _Base(object): pass - registry = self._makeOne() - registry.__bases__ = (_Base,) - self.assertEqual(registry._generation, 2) - - def test_register(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - registry.register([IB0], IR0, '', 'A1') - self.assertEqual(registry.registered([IB0], IR0, ''), 'A1') - self.assertEqual(len(registry._adapters), 2) #order 0 and order 1 - self.assertEqual(registry._generation, 2) - - def test_register_with_invalid_name(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - with self.assertRaises(ValueError): - registry.register([IB0], IR0, object(), 'A1') - - def test_register_with_value_None_unregisters(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - registry.register([None], IR0, '', 'A1') - registry.register([None], IR0, '', None) - self.assertEqual(len(registry._adapters), 0) - - def test_register_with_same_value(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - _value = object() - registry.register([None], IR0, '', _value) - _before = registry._generation - registry.register([None], IR0, '', _value) - self.assertEqual(registry._generation, _before) # skipped changed() - - def test_registered_empty(self): - registry = self._makeOne() - self.assertEqual(registry.registered([None], None, ''), None) - - def test_registered_non_empty_miss(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - registry.register([IB1], None, '', 'A1') - self.assertEqual(registry.registered([IB2], None, ''), None) - - def test_registered_non_empty_hit(self): - registry = self._makeOne() - registry.register([None], None, '', 'A1') - self.assertEqual(registry.registered([None], None, ''), 'A1') - - def test_unregister_empty(self): - registry = self._makeOne() - registry.unregister([None], None, '') #doesn't raise - self.assertEqual(registry.registered([None], None, ''), None) - - def test_unregister_non_empty_miss_on_required(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - registry.register([IB1], None, '', 'A1') - registry.unregister([IB2], None, '') #doesn't raise - self.assertEqual(registry.registered([IB1], None, ''), 'A1') - - def test_unregister_non_empty_miss_on_name(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - registry.register([IB1], None, '', 'A1') - registry.unregister([IB1], None, 'nonesuch') #doesn't raise - self.assertEqual(registry.registered([IB1], None, ''), 'A1') - - def test_unregister_with_value_not_None_miss(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - orig = object() - nomatch = object() - registry.register([IB1], None, '', orig) - registry.unregister([IB1], None, '', nomatch) #doesn't raise - self.assertTrue(registry.registered([IB1], None, '') is orig) - - def test_unregister_hit_clears_empty_subcomponents(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - one = object() - another = object() - registry.register([IB1, IB2], None, '', one) - registry.register([IB1, IB3], None, '', another) - self.assertTrue(IB2 in registry._adapters[2][IB1]) - self.assertTrue(IB3 in registry._adapters[2][IB1]) - registry.unregister([IB1, IB3], None, '', another) - self.assertTrue(IB2 in registry._adapters[2][IB1]) - self.assertFalse(IB3 in registry._adapters[2][IB1]) - - def test_unsubscribe_empty(self): - registry = self._makeOne() - registry.unsubscribe([None], None, '') #doesn't raise - self.assertEqual(registry.registered([None], None, ''), None) - - def test_unsubscribe_hit(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - orig = object() - registry.subscribe([IB1], None, orig) - registry.unsubscribe([IB1], None, orig) #doesn't raise - self.assertEqual(len(registry._subscribers), 0) - - def test_unsubscribe_after_multiple(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - first = object() - second = object() - third = object() - fourth = object() - registry.subscribe([IB1], None, first) - registry.subscribe([IB1], None, second) - registry.subscribe([IB1], IR0, third) - registry.subscribe([IB1], IR0, fourth) - registry.unsubscribe([IB1], IR0, fourth) - registry.unsubscribe([IB1], IR0, third) - registry.unsubscribe([IB1], None, second) - registry.unsubscribe([IB1], None, first) - self.assertEqual(len(registry._subscribers), 0) - - def test_unsubscribe_w_None_after_multiple(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - first = object() - second = object() - third = object() - registry.subscribe([IB1], None, first) - registry.subscribe([IB1], None, second) - registry.unsubscribe([IB1], None) - self.assertEqual(len(registry._subscribers), 0) - - def test_unsubscribe_non_empty_miss_on_required(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - registry.subscribe([IB1], None, 'A1') - self.assertEqual(len(registry._subscribers), 2) - registry.unsubscribe([IB2], None, '') #doesn't raise - self.assertEqual(len(registry._subscribers), 2) - - def test_unsubscribe_non_empty_miss_on_value(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - registry.subscribe([IB1], None, 'A1') - self.assertEqual(len(registry._subscribers), 2) - registry.unsubscribe([IB1], None, 'A2') #doesn't raise - self.assertEqual(len(registry._subscribers), 2) - - def test_unsubscribe_with_value_not_None_miss(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - orig = object() - nomatch = object() - registry.subscribe([IB1], None, orig) - registry.unsubscribe([IB1], None, nomatch) #doesn't raise - self.assertEqual(len(registry._subscribers), 2) - - def _instance_method_notify_target(self): - self.fail("Example method, not intended to be called.") - - def test_unsubscribe_instance_method(self): - IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() - registry = self._makeOne() - self.assertEqual(len(registry._subscribers), 0) - registry.subscribe([IB1], None, self._instance_method_notify_target) - registry.unsubscribe([IB1], None, self._instance_method_notify_target) - self.assertEqual(len(registry._subscribers), 0) - - -class LookupBaseFallbackTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.adapter import LookupBaseFallback - return LookupBaseFallback - - def _makeOne(self, uc_lookup=None, uc_lookupAll=None, - uc_subscriptions=None): - if uc_lookup is None: - def uc_lookup(self, required, provided, name): - pass - if uc_lookupAll is None: - def uc_lookupAll(self, required, provided): - raise NotImplementedError() - if uc_subscriptions is None: - def uc_subscriptions(self, required, provided): - raise NotImplementedError() - class Derived(self._getTargetClass()): - _uncached_lookup = uc_lookup - _uncached_lookupAll = uc_lookupAll - _uncached_subscriptions = uc_subscriptions - return Derived() - - def test_lookup_w_invalid_name(self): - def _lookup(self, required, provided, name): - self.fail("This should never be called") - lb = self._makeOne(uc_lookup=_lookup) - with self.assertRaises(ValueError): - lb.lookup(('A',), 'B', object()) - - def test_lookup_miss_no_default(self): - _called_with = [] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return None - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup(('A',), 'B', 'C') - self.assertTrue(found is None) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - - def test_lookup_miss_w_default(self): - _called_with = [] - _default = object() - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return None - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup(('A',), 'B', 'C', _default) - self.assertTrue(found is _default) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - - def test_lookup_not_cached(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup(('A',), 'B', 'C') - self.assertTrue(found is a) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - self.assertEqual(_results, [b, c]) - - def test_lookup_cached(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup(('A',), 'B', 'C') - found = lb.lookup(('A',), 'B', 'C') - self.assertTrue(found is a) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - self.assertEqual(_results, [b, c]) - - def test_lookup_not_cached_multi_required(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup(('A', 'D'), 'B', 'C') - self.assertTrue(found is a) - self.assertEqual(_called_with, [(('A', 'D'), 'B', 'C')]) - self.assertEqual(_results, [b, c]) - - def test_lookup_cached_multi_required(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup(('A', 'D'), 'B', 'C') - found = lb.lookup(('A', 'D'), 'B', 'C') - self.assertTrue(found is a) - self.assertEqual(_called_with, [(('A', 'D'), 'B', 'C')]) - self.assertEqual(_results, [b, c]) - - def test_lookup_not_cached_after_changed(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup(('A',), 'B', 'C') - lb.changed(lb) - found = lb.lookup(('A',), 'B', 'C') - self.assertTrue(found is b) - self.assertEqual(_called_with, - [(('A',), 'B', 'C'), (('A',), 'B', 'C')]) - self.assertEqual(_results, [c]) - - def test_lookup1_w_invalid_name(self): - def _lookup(self, required, provided, name): - self.fail("This should never be called") - - lb = self._makeOne(uc_lookup=_lookup) - with self.assertRaises(ValueError): - lb.lookup1('A', 'B', object()) - - def test_lookup1_miss_no_default(self): - _called_with = [] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return None - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup1('A', 'B', 'C') - self.assertTrue(found is None) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - - def test_lookup1_miss_w_default(self): - _called_with = [] - _default = object() - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return None - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup1('A', 'B', 'C', _default) - self.assertTrue(found is _default) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - - def test_lookup1_miss_w_default_negative_cache(self): - _called_with = [] - _default = object() - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return None - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup1('A', 'B', 'C', _default) - self.assertTrue(found is _default) - found = lb.lookup1('A', 'B', 'C', _default) - self.assertTrue(found is _default) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - - def test_lookup1_not_cached(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup1('A', 'B', 'C') - self.assertTrue(found is a) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - self.assertEqual(_results, [b, c]) - - def test_lookup1_cached(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup1('A', 'B', 'C') - found = lb.lookup1('A', 'B', 'C') - self.assertTrue(found is a) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - self.assertEqual(_results, [b, c]) - - def test_lookup1_not_cached_after_changed(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - lb = self._makeOne(uc_lookup=_lookup) - found = lb.lookup1('A', 'B', 'C') - lb.changed(lb) - found = lb.lookup1('A', 'B', 'C') - self.assertTrue(found is b) - self.assertEqual(_called_with, - [(('A',), 'B', 'C'), (('A',), 'B', 'C')]) - self.assertEqual(_results, [c]) - - def test_adapter_hook_w_invalid_name(self): - req, prv = object(), object() - lb = self._makeOne() - with self.assertRaises(ValueError): - lb.adapter_hook(prv, req, object()) - - def test_adapter_hook_miss_no_default(self): - req, prv = object(), object() - lb = self._makeOne() - found = lb.adapter_hook(prv, req, '') - self.assertTrue(found is None) - - def test_adapter_hook_miss_w_default(self): - req, prv, _default = object(), object(), object() - lb = self._makeOne() - found = lb.adapter_hook(prv, req, '', _default) - self.assertTrue(found is _default) - - def test_adapter_hook_hit_factory_returns_None(self): - _f_called_with = [] - def _factory(context): - _f_called_with.append(context) - return None - def _lookup(self, required, provided, name): - return _factory - req, prv, _default = object(), object(), object() - lb = self._makeOne(uc_lookup=_lookup) - adapted = lb.adapter_hook(prv, req, 'C', _default) - self.assertTrue(adapted is _default) - self.assertEqual(_f_called_with, [req]) - - def test_adapter_hook_hit_factory_returns_adapter(self): - _f_called_with = [] - _adapter = object() - def _factory(context): - _f_called_with.append(context) - return _adapter - def _lookup(self, required, provided, name): - return _factory - req, prv, _default = object(), object(), object() - lb = self._makeOne(uc_lookup=_lookup) - adapted = lb.adapter_hook(prv, req, 'C', _default) - self.assertTrue(adapted is _adapter) - self.assertEqual(_f_called_with, [req]) - - def test_queryAdapter(self): - _f_called_with = [] - _adapter = object() - def _factory(context): - _f_called_with.append(context) - return _adapter - def _lookup(self, required, provided, name): - return _factory - req, prv, _default = object(), object(), object() - lb = self._makeOne(uc_lookup=_lookup) - adapted = lb.queryAdapter(req, prv, 'C', _default) - self.assertTrue(adapted is _adapter) - self.assertEqual(_f_called_with, [req]) - - def test_lookupAll_uncached(self): - _called_with = [] - _results = [object(), object(), object()] - def _lookupAll(self, required, provided): - _called_with.append((required, provided)) - return tuple(_results) - lb = self._makeOne(uc_lookupAll=_lookupAll) - found = lb.lookupAll('A', 'B') - self.assertEqual(found, tuple(_results)) - self.assertEqual(_called_with, [(('A',), 'B')]) - - def test_lookupAll_cached(self): - _called_with = [] - _results = [object(), object(), object()] - def _lookupAll(self, required, provided): - _called_with.append((required, provided)) - return tuple(_results) - lb = self._makeOne(uc_lookupAll=_lookupAll) - found = lb.lookupAll('A', 'B') - found = lb.lookupAll('A', 'B') - self.assertEqual(found, tuple(_results)) - self.assertEqual(_called_with, [(('A',), 'B')]) - - def test_subscriptions_uncached(self): - _called_with = [] - _results = [object(), object(), object()] - def _subscriptions(self, required, provided): - _called_with.append((required, provided)) - return tuple(_results) - lb = self._makeOne(uc_subscriptions=_subscriptions) - found = lb.subscriptions('A', 'B') - self.assertEqual(found, tuple(_results)) - self.assertEqual(_called_with, [(('A',), 'B')]) - - def test_subscriptions_cached(self): - _called_with = [] - _results = [object(), object(), object()] - def _subscriptions(self, required, provided): - _called_with.append((required, provided)) - return tuple(_results) - lb = self._makeOne(uc_subscriptions=_subscriptions) - found = lb.subscriptions('A', 'B') - found = lb.subscriptions('A', 'B') - self.assertEqual(found, tuple(_results)) - self.assertEqual(_called_with, [(('A',), 'B')]) - - -class LookupBaseTests(LookupBaseFallbackTests): - - def _getTargetClass(self): - from zope.interface.adapter import LookupBase - return LookupBase - - def test_optimizations(self): - from zope.interface.adapter import LookupBaseFallback - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), LookupBaseFallback) - else: - self.assertIsNot(self._getTargetClass(), LookupBaseFallback) - - -class VerifyingBaseFallbackTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.adapter import VerifyingBaseFallback - return VerifyingBaseFallback - - def _makeOne(self, registry, uc_lookup=None, uc_lookupAll=None, - uc_subscriptions=None): - if uc_lookup is None: - def uc_lookup(self, required, provided, name): - raise NotImplementedError() - if uc_lookupAll is None: - def uc_lookupAll(self, required, provided): - raise NotImplementedError() - if uc_subscriptions is None: - def uc_subscriptions(self, required, provided): - raise NotImplementedError() - class Derived(self._getTargetClass()): - _uncached_lookup = uc_lookup - _uncached_lookupAll = uc_lookupAll - _uncached_subscriptions = uc_subscriptions - def __init__(self, registry): - super(Derived, self).__init__() - self._registry = registry - derived = Derived(registry) - derived.changed(derived) # init. '_verify_ro' / '_verify_generations' - return derived - - def _makeRegistry(self, depth): - class WithGeneration(object): - _generation = 1 - class Registry: - def __init__(self, depth): - self.ro = [WithGeneration() for i in range(depth)] - return Registry(depth) - - def test_lookup(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - reg = self._makeRegistry(3) - lb = self._makeOne(reg, uc_lookup=_lookup) - found = lb.lookup(('A',), 'B', 'C') - found = lb.lookup(('A',), 'B', 'C') - self.assertTrue(found is a) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - self.assertEqual(_results, [b, c]) - reg.ro[1]._generation += 1 - found = lb.lookup(('A',), 'B', 'C') - self.assertTrue(found is b) - self.assertEqual(_called_with, - [(('A',), 'B', 'C'), (('A',), 'B', 'C')]) - self.assertEqual(_results, [c]) - - def test_lookup1(self): - _called_with = [] - a, b, c = object(), object(), object() - _results = [a, b, c] - def _lookup(self, required, provided, name): - _called_with.append((required, provided, name)) - return _results.pop(0) - reg = self._makeRegistry(3) - lb = self._makeOne(reg, uc_lookup=_lookup) - found = lb.lookup1('A', 'B', 'C') - found = lb.lookup1('A', 'B', 'C') - self.assertTrue(found is a) - self.assertEqual(_called_with, [(('A',), 'B', 'C')]) - self.assertEqual(_results, [b, c]) - reg.ro[1]._generation += 1 - found = lb.lookup1('A', 'B', 'C') - self.assertTrue(found is b) - self.assertEqual(_called_with, - [(('A',), 'B', 'C'), (('A',), 'B', 'C')]) - self.assertEqual(_results, [c]) - - def test_adapter_hook(self): - a, b, _c = [object(), object(), object()] - def _factory1(context): - return a - def _factory2(context): - return b - def _factory3(context): - self.fail("This should never be called") - _factories = [_factory1, _factory2, _factory3] - def _lookup(self, required, provided, name): - return _factories.pop(0) - req, prv, _default = object(), object(), object() - reg = self._makeRegistry(3) - lb = self._makeOne(reg, uc_lookup=_lookup) - adapted = lb.adapter_hook(prv, req, 'C', _default) - self.assertTrue(adapted is a) - adapted = lb.adapter_hook(prv, req, 'C', _default) - self.assertTrue(adapted is a) - reg.ro[1]._generation += 1 - adapted = lb.adapter_hook(prv, req, 'C', _default) - self.assertTrue(adapted is b) - - def test_queryAdapter(self): - a, b, _c = [object(), object(), object()] - def _factory1(context): - return a - def _factory2(context): - return b - def _factory3(context): - self.fail("This should never be called") - _factories = [_factory1, _factory2, _factory3] - def _lookup(self, required, provided, name): - return _factories.pop(0) - req, prv, _default = object(), object(), object() - reg = self._makeRegistry(3) - lb = self._makeOne(reg, uc_lookup=_lookup) - adapted = lb.queryAdapter(req, prv, 'C', _default) - self.assertTrue(adapted is a) - adapted = lb.queryAdapter(req, prv, 'C', _default) - self.assertTrue(adapted is a) - reg.ro[1]._generation += 1 - adapted = lb.adapter_hook(prv, req, 'C', _default) - self.assertTrue(adapted is b) - - def test_lookupAll(self): - _results_1 = [object(), object(), object()] - _results_2 = [object(), object(), object()] - _results = [_results_1, _results_2] - def _lookupAll(self, required, provided): - return tuple(_results.pop(0)) - reg = self._makeRegistry(3) - lb = self._makeOne(reg, uc_lookupAll=_lookupAll) - found = lb.lookupAll('A', 'B') - self.assertEqual(found, tuple(_results_1)) - found = lb.lookupAll('A', 'B') - self.assertEqual(found, tuple(_results_1)) - reg.ro[1]._generation += 1 - found = lb.lookupAll('A', 'B') - self.assertEqual(found, tuple(_results_2)) - - def test_subscriptions(self): - _results_1 = [object(), object(), object()] - _results_2 = [object(), object(), object()] - _results = [_results_1, _results_2] - def _subscriptions(self, required, provided): - return tuple(_results.pop(0)) - reg = self._makeRegistry(3) - lb = self._makeOne(reg, uc_subscriptions=_subscriptions) - found = lb.subscriptions('A', 'B') - self.assertEqual(found, tuple(_results_1)) - found = lb.subscriptions('A', 'B') - self.assertEqual(found, tuple(_results_1)) - reg.ro[1]._generation += 1 - found = lb.subscriptions('A', 'B') - self.assertEqual(found, tuple(_results_2)) - - -class VerifyingBaseTests(VerifyingBaseFallbackTests): - - def _getTargetClass(self): - from zope.interface.adapter import VerifyingBase - return VerifyingBase - - def test_optimizations(self): - from zope.interface.adapter import VerifyingBaseFallback - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), VerifyingBaseFallback) - else: - self.assertIsNot(self._getTargetClass(), VerifyingBaseFallback) - - -class AdapterLookupBaseTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.adapter import AdapterLookupBase - return AdapterLookupBase - - def _makeOne(self, registry): - return self._getTargetClass()(registry) - - def _makeSubregistry(self, *provided): - class Subregistry: - def __init__(self): - self._adapters = [] - self._subscribers = [] - return Subregistry() - - def _makeRegistry(self, *provided): - class Registry: - def __init__(self, provided): - self._provided = provided - self.ro = [] - return Registry(provided) - - def test_ctor_empty_registry(self): - registry = self._makeRegistry() - alb = self._makeOne(registry) - self.assertEqual(alb._extendors, {}) - - def test_ctor_w_registry_provided(self): - from zope.interface import Interface - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - alb = self._makeOne(registry) - self.assertEqual(sorted(alb._extendors.keys()), - sorted([IBar, IFoo, Interface])) - self.assertEqual(alb._extendors[IFoo], [IFoo]) - self.assertEqual(alb._extendors[IBar], [IBar]) - self.assertEqual(sorted(alb._extendors[Interface]), - sorted([IFoo, IBar])) - - def test_changed_empty_required(self): - # ALB.changed expects to call a mixed in changed. - class Mixin(object): - def changed(self, *other): - pass - class Derived(self._getTargetClass(), Mixin): - pass - registry = self._makeRegistry() - alb = Derived(registry) - alb.changed(alb) - - def test_changed_w_required(self): - # ALB.changed expects to call a mixed in changed. - class Mixin(object): - def changed(self, *other): - pass - class Derived(self._getTargetClass(), Mixin): - pass - class FauxWeakref(object): - _unsub = None - def __init__(self, here): - self._here = here - def __call__(self): - if self._here: - return self - def unsubscribe(self, target): - self._unsub = target - gone = FauxWeakref(False) - here = FauxWeakref(True) - registry = self._makeRegistry() - alb = Derived(registry) - alb._required[gone] = 1 - alb._required[here] = 1 - alb.changed(alb) - self.assertEqual(len(alb._required), 0) - self.assertEqual(gone._unsub, None) - self.assertEqual(here._unsub, alb) - - def test_init_extendors_after_registry_update(self): - from zope.interface import Interface - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry() - alb = self._makeOne(registry) - registry._provided = [IFoo, IBar] - alb.init_extendors() - self.assertEqual(sorted(alb._extendors.keys()), - sorted([IBar, IFoo, Interface])) - self.assertEqual(alb._extendors[IFoo], [IFoo]) - self.assertEqual(alb._extendors[IBar], [IBar]) - self.assertEqual(sorted(alb._extendors[Interface]), - sorted([IFoo, IBar])) - - def test_add_extendor(self): - from zope.interface import Interface - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry() - alb = self._makeOne(registry) - alb.add_extendor(IFoo) - alb.add_extendor(IBar) - self.assertEqual(sorted(alb._extendors.keys()), - sorted([IBar, IFoo, Interface])) - self.assertEqual(alb._extendors[IFoo], [IFoo]) - self.assertEqual(alb._extendors[IBar], [IBar]) - self.assertEqual(sorted(alb._extendors[Interface]), - sorted([IFoo, IBar])) - - def test_remove_extendor(self): - from zope.interface import Interface - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - alb = self._makeOne(registry) - alb.remove_extendor(IFoo) - self.assertEqual(sorted(alb._extendors.keys()), - sorted([IFoo, IBar, Interface])) - self.assertEqual(alb._extendors[IFoo], []) - self.assertEqual(alb._extendors[IBar], [IBar]) - self.assertEqual(sorted(alb._extendors[Interface]), - sorted([IBar])) - - # test '_subscribe' via its callers, '_uncached_lookup', etc. - - def test__uncached_lookup_empty_ro(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry() - alb = self._makeOne(registry) - result = alb._uncached_lookup((IFoo,), IBar) - self.assertEqual(result, None) - self.assertEqual(len(alb._required), 1) - self.assertTrue(IFoo.weakref() in alb._required) - - def test__uncached_lookup_order_miss(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - registry.ro.append(subr) - alb = self._makeOne(registry) - result = alb._uncached_lookup((IFoo,), IBar) - self.assertEqual(result, None) - - def test__uncached_lookup_extendors_miss(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry() - subr = self._makeSubregistry() - subr._adapters = [{}, {}] #utilities, single adapters - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_lookup((IFoo,), IBar) - self.assertEqual(result, None) - - def test__uncached_lookup_components_miss_wrong_iface(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - IQux = InterfaceClass('IQux') - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - irrelevant = object() - subr._adapters = [ #utilities, single adapters - {}, - {IFoo: {IQux: {'': irrelevant}, - }}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_lookup((IFoo,), IBar) - self.assertEqual(result, None) - - def test__uncached_lookup_components_miss_wrong_name(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - irrelevant = object() - wrongname = object() - subr._adapters = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'wrongname': wrongname}, - }}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_lookup((IFoo,), IBar) - self.assertEqual(result, None) - - def test__uncached_lookup_simple_hit(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - _expected = object() - subr._adapters = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'': _expected}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_lookup((IFoo,), IBar) - self.assertTrue(result is _expected) - - def test__uncached_lookup_repeated_hit(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - _expected = object() - subr._adapters = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'': _expected}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_lookup((IFoo,), IBar) - result2 = alb._uncached_lookup((IFoo,), IBar) - self.assertTrue(result is _expected) - self.assertTrue(result2 is _expected) - - def test_queryMultiAdaptor_lookup_miss(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - registry = self._makeRegistry() - subr = self._makeSubregistry() - subr._adapters = [ #utilities, single adapters - {}, - {}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - alb.lookup = alb._uncached_lookup # provided by derived - subr._v_lookup = alb - _default = object() - result = alb.queryMultiAdapter((foo,), IBar, default=_default) - self.assertTrue(result is _default) - - def test_queryMultiAdaptor_factory_miss(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - _expected = object() - _called_with = [] - def _factory(context): - _called_with.append(context) - return None - subr._adapters = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'': _factory}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - alb.lookup = alb._uncached_lookup # provided by derived - subr._v_lookup = alb - _default = object() - result = alb.queryMultiAdapter((foo,), IBar, default=_default) - self.assertTrue(result is _default) - self.assertEqual(_called_with, [foo]) - - def test_queryMultiAdaptor_factory_hit(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - _expected = object() - _called_with = [] - def _factory(context): - _called_with.append(context) - return _expected - subr._adapters = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'': _factory}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - alb.lookup = alb._uncached_lookup # provided by derived - subr._v_lookup = alb - _default = object() - result = alb.queryMultiAdapter((foo,), IBar, default=_default) - self.assertTrue(result is _expected) - self.assertEqual(_called_with, [foo]) - - def test__uncached_lookupAll_empty_ro(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry() - alb = self._makeOne(registry) - result = alb._uncached_lookupAll((IFoo,), IBar) - self.assertEqual(result, ()) - self.assertEqual(len(alb._required), 1) - self.assertTrue(IFoo.weakref() in alb._required) - - def test__uncached_lookupAll_order_miss(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_lookupAll((IFoo,), IBar) - self.assertEqual(result, ()) - - def test__uncached_lookupAll_extendors_miss(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry() - subr = self._makeSubregistry() - subr._adapters = [{}, {}] #utilities, single adapters - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_lookupAll((IFoo,), IBar) - self.assertEqual(result, ()) - - def test__uncached_lookupAll_components_miss(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - IQux = InterfaceClass('IQux') - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - irrelevant = object() - subr._adapters = [ #utilities, single adapters - {}, - {IFoo: {IQux: {'': irrelevant}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_lookupAll((IFoo,), IBar) - self.assertEqual(result, ()) - - def test__uncached_lookupAll_simple_hit(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - _expected = object() - _named = object() - subr._adapters = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'': _expected, 'named': _named}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_lookupAll((IFoo,), IBar) - self.assertEqual(sorted(result), [('', _expected), ('named', _named)]) - - def test_names(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - _expected = object() - _named = object() - subr._adapters = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'': _expected, 'named': _named}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - alb.lookupAll = alb._uncached_lookupAll - subr._v_lookup = alb - result = alb.names((IFoo,), IBar) - self.assertEqual(sorted(result), ['', 'named']) - - def test__uncached_subscriptions_empty_ro(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry() - alb = self._makeOne(registry) - result = alb._uncached_subscriptions((IFoo,), IBar) - self.assertEqual(result, []) - self.assertEqual(len(alb._required), 1) - self.assertTrue(IFoo.weakref() in alb._required) - - def test__uncached_subscriptions_order_miss(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_subscriptions((IFoo,), IBar) - self.assertEqual(result, []) - - def test__uncached_subscriptions_extendors_miss(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry() - subr = self._makeSubregistry() - subr._subscribers = [{}, {}] #utilities, single adapters - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_subscriptions((IFoo,), IBar) - self.assertEqual(result, []) - - def test__uncached_subscriptions_components_miss_wrong_iface(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - IQux = InterfaceClass('IQux') - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - irrelevant = object() - subr._subscribers = [ #utilities, single adapters - {}, - {IFoo: {IQux: {'': irrelevant}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_subscriptions((IFoo,), IBar) - self.assertEqual(result, []) - - def test__uncached_subscriptions_components_miss_wrong_name(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - wrongname = object() - subr._subscribers = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'wrongname': wrongname}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_subscriptions((IFoo,), IBar) - self.assertEqual(result, []) - - def test__uncached_subscriptions_simple_hit(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - class Foo(object): - def __lt__(self, other): - return True - _exp1, _exp2 = Foo(), Foo() - subr._subscribers = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'': (_exp1, _exp2)}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - subr._v_lookup = alb - result = alb._uncached_subscriptions((IFoo,), IBar) - self.assertEqual(sorted(result), sorted([_exp1, _exp2])) - - def test_subscribers_wo_provided(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - registry = self._makeRegistry(IFoo, IBar) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - _called = {} - def _factory1(context): - _called.setdefault('_factory1', []).append(context) - def _factory2(context): - _called.setdefault('_factory2', []).append(context) - subr._subscribers = [ #utilities, single adapters - {}, - {IFoo: {None: {'': (_factory1, _factory2)}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - alb.subscriptions = alb._uncached_subscriptions - subr._v_lookup = alb - result = alb.subscribers((foo,), None) - self.assertEqual(result, ()) - self.assertEqual(_called, {'_factory1': [foo], '_factory2': [foo]}) - - def test_subscribers_w_provided(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', IFoo) - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - registry = self._makeRegistry(IFoo, IBar) - registry = self._makeRegistry(IFoo, IBar) - subr = self._makeSubregistry() - _called = {} - _exp1, _exp2 = object(), object() - def _factory1(context): - _called.setdefault('_factory1', []).append(context) - return _exp1 - def _factory2(context): - _called.setdefault('_factory2', []).append(context) - return _exp2 - def _side_effect_only(context): - _called.setdefault('_side_effect_only', []).append(context) - return None - subr._subscribers = [ #utilities, single adapters - {}, - {IFoo: {IBar: {'': (_factory1, _factory2, _side_effect_only)}}}, - ] - registry.ro.append(subr) - alb = self._makeOne(registry) - alb.subscriptions = alb._uncached_subscriptions - subr._v_lookup = alb - result = alb.subscribers((foo,), IBar) - self.assertEqual(result, [_exp1, _exp2]) - self.assertEqual(_called, - {'_factory1': [foo], - '_factory2': [foo], - '_side_effect_only': [foo], - }) - - -class AdapterRegistryTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.adapter import AdapterRegistry - return AdapterRegistry - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor_no_bases(self): - ar = self._makeOne() - self.assertEqual(len(ar._v_subregistries), 0) - - def test_ctor_w_bases(self): - base = self._makeOne() - sub = self._makeOne([base]) - self.assertEqual(len(sub._v_subregistries), 0) - self.assertEqual(len(base._v_subregistries), 1) - self.assertTrue(sub in base._v_subregistries) - - # test _addSubregistry / _removeSubregistry via only caller, _setBases - - def test__setBases_removing_existing_subregistry(self): - before = self._makeOne() - after = self._makeOne() - sub = self._makeOne([before]) - sub.__bases__ = [after] - self.assertEqual(len(before._v_subregistries), 0) - self.assertEqual(len(after._v_subregistries), 1) - self.assertTrue(sub in after._v_subregistries) - - def test__setBases_wo_stray_entry(self): - before = self._makeOne() - stray = self._makeOne() - after = self._makeOne() - sub = self._makeOne([before]) - sub.__dict__['__bases__'].append(stray) - sub.__bases__ = [after] - self.assertEqual(len(before._v_subregistries), 0) - self.assertEqual(len(after._v_subregistries), 1) - self.assertTrue(sub in after._v_subregistries) - - def test__setBases_w_existing_entry_continuing(self): - before = self._makeOne() - after = self._makeOne() - sub = self._makeOne([before]) - sub.__bases__ = [before, after] - self.assertEqual(len(before._v_subregistries), 1) - self.assertEqual(len(after._v_subregistries), 1) - self.assertTrue(sub in before._v_subregistries) - self.assertTrue(sub in after._v_subregistries) - - def test_changed_w_subregistries(self): - base = self._makeOne() - class Derived(object): - _changed = None - def changed(self, originally_changed): - self._changed = originally_changed - derived1, derived2 = Derived(), Derived() - base._addSubregistry(derived1) - base._addSubregistry(derived2) - orig = object() - base.changed(orig) - self.assertTrue(derived1._changed is orig) - self.assertTrue(derived2._changed is orig) - - -class Test_utils(unittest.TestCase): - - def test__convert_None_to_Interface_w_None(self): - from zope.interface.adapter import _convert_None_to_Interface - from zope.interface.interface import Interface - self.assertTrue(_convert_None_to_Interface(None) is Interface) - - def test__convert_None_to_Interface_w_other(self): - from zope.interface.adapter import _convert_None_to_Interface - other = object() - self.assertTrue(_convert_None_to_Interface(other) is other) - - def test__normalize_name_str(self): - import sys - from zope.interface.adapter import _normalize_name - STR = b'str' - if sys.version_info[0] < 3: - self.assertEqual(_normalize_name(STR), unicode(STR)) - else: - self.assertEqual(_normalize_name(STR), str(STR, 'ascii')) - - def test__normalize_name_unicode(self): - from zope.interface.adapter import _normalize_name - - USTR = u'ustr' - self.assertEqual(_normalize_name(USTR), USTR) - - def test__normalize_name_other(self): - from zope.interface.adapter import _normalize_name - for other in 1, 1.0, (), [], {}, object(): - self.assertRaises(TypeError, _normalize_name, other) - - # _lookup, _lookupAll, and _subscriptions tested via their callers - # (AdapterLookupBase.{lookup,lookupAll,subscriptions}). diff --git a/lib/zope/interface/tests/test_advice.py b/lib/zope/interface/tests/test_advice.py deleted file mode 100644 index 0739ac1..0000000 --- a/lib/zope/interface/tests/test_advice.py +++ /dev/null @@ -1,355 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Tests for advice - -This module was adapted from 'protocols.tests.advice', part of the Python -Enterprise Application Kit (PEAK). Please notify the PEAK authors -(pje@telecommunity.com and tsarna@sarna.org) if bugs are found or -Zope-specific changes are required, so that the PEAK version of this module -can be kept in sync. - -PEAK is a Python application framework that interoperates with (but does -not require) Zope 3 and Twisted. It provides tools for manipulating UML -models, object-relational persistence, aspect-oriented programming, and more. -Visit the PEAK home page at http://peak.telecommunity.com for more information. -""" - -import unittest -import sys - -from zope.interface._compat import _skip_under_py2 -from zope.interface._compat import _skip_under_py3k - - -class FrameInfoTest(unittest.TestCase): - - def test_w_module(self): - from zope.interface.tests import advisory_testing - (kind, module, - f_locals, f_globals) = advisory_testing.moduleLevelFrameInfo - self.assertEqual(kind, "module") - for d in module.__dict__, f_locals, f_globals: - self.assertTrue(d is advisory_testing.my_globals) - - @_skip_under_py3k - def test_w_ClassicClass(self): - from zope.interface.tests import advisory_testing - (kind, - module, - f_locals, - f_globals) = advisory_testing.ClassicClass.classLevelFrameInfo - self.assertEqual(kind, "class") - - self.assertTrue( - f_locals is advisory_testing.ClassicClass.__dict__) # ??? - for d in module.__dict__, f_globals: - self.assertTrue(d is advisory_testing.my_globals) - - def test_w_NewStyleClass(self): - from zope.interface.tests import advisory_testing - (kind, - module, - f_locals, - f_globals) = advisory_testing.NewStyleClass.classLevelFrameInfo - self.assertEqual(kind, "class") - - for d in module.__dict__, f_globals: - self.assertTrue(d is advisory_testing.my_globals) - - def test_inside_function_call(self): - from zope.interface.advice import getFrameInfo - kind, module, f_locals, f_globals = getFrameInfo(sys._getframe()) - self.assertEqual(kind, "function call") - self.assertTrue(f_locals is locals()) # ??? - for d in module.__dict__, f_globals: - self.assertTrue(d is globals()) - - def test_inside_exec(self): - from zope.interface.advice import getFrameInfo - _globals = {'getFrameInfo': getFrameInfo} - _locals = {} - exec(_FUNKY_EXEC, _globals, _locals) - self.assertEqual(_locals['kind'], "exec") - self.assertTrue(_locals['f_locals'] is _locals) - self.assertTrue(_locals['module'] is None) - self.assertTrue(_locals['f_globals'] is _globals) - - -_FUNKY_EXEC = """\ -import sys -kind, module, f_locals, f_globals = getFrameInfo(sys._getframe()) -""" - -class AdviceTests(unittest.TestCase): - - @_skip_under_py3k - def test_order(self): - from zope.interface.tests.advisory_testing import ping - log = [] - class Foo(object): - ping(log, 1) - ping(log, 2) - ping(log, 3) - - # Strip the list nesting - for i in 1, 2, 3: - self.assertTrue(isinstance(Foo, list)) - Foo, = Foo - - self.assertEqual(log, [(1, Foo), (2, [Foo]), (3, [[Foo]])]) - - @_skip_under_py3k - def test_single_explicit_meta(self): - from zope.interface.tests.advisory_testing import ping - - class Metaclass(type): - pass - - class Concrete(Metaclass): - __metaclass__ = Metaclass - ping([],1) - - Concrete, = Concrete - self.assertTrue(Concrete.__class__ is Metaclass) - - - @_skip_under_py3k - def test_mixed_metas(self): - from zope.interface.tests.advisory_testing import ping - - class Metaclass1(type): - pass - - class Metaclass2(type): - pass - - class Base1: - __metaclass__ = Metaclass1 - - class Base2: - __metaclass__ = Metaclass2 - - try: - class Derived(Base1, Base2): - ping([], 1) - self.fail("Should have gotten incompatibility error") - except TypeError: - pass - - class Metaclass3(Metaclass1, Metaclass2): - pass - - class Derived(Base1, Base2): - __metaclass__ = Metaclass3 - ping([], 1) - - self.assertTrue(isinstance(Derived, list)) - Derived, = Derived - self.assertTrue(isinstance(Derived, Metaclass3)) - - @_skip_under_py3k - def test_meta_no_bases(self): - from zope.interface.tests.advisory_testing import ping - from types import ClassType - class Thing: - ping([], 1) - klass, = Thing # unpack list created by pong - self.assertEqual(type(klass), ClassType) - - -class Test_isClassAdvisor(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.advice import isClassAdvisor - return isClassAdvisor(*args, **kw) - - def test_w_non_function(self): - self.assertEqual(self._callFUT(self), False) - - def test_w_normal_function(self): - def foo(): - raise NotImplementedError() - self.assertEqual(self._callFUT(foo), False) - - def test_w_advisor_function(self): - def bar(): - raise NotImplementedError() - bar.previousMetaclass = object() - self.assertEqual(self._callFUT(bar), True) - - -class Test_determineMetaclass(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.advice import determineMetaclass - return determineMetaclass(*args, **kw) - - @_skip_under_py3k - def test_empty(self): - from types import ClassType - self.assertEqual(self._callFUT(()), ClassType) - - def test_empty_w_explicit_metatype(self): - class Meta(type): - pass - self.assertEqual(self._callFUT((), Meta), Meta) - - def test_single(self): - class Meta(type): - pass - self.assertEqual(self._callFUT((Meta,)), type) - - @_skip_under_py3k - def test_meta_of_class(self): - class Metameta(type): - pass - - class Meta(type): - __metaclass__ = Metameta - - self.assertEqual(self._callFUT((Meta, type)), Metameta) - - @_skip_under_py2 - def test_meta_of_class_py3k(self): - # Work around SyntaxError under Python2. - EXEC = '\n'.join([ - 'class Metameta(type):', - ' pass', - 'class Meta(type, metaclass=Metameta):', - ' pass', - ]) - globs = {} - exec(EXEC, globs) - Meta = globs['Meta'] - Metameta = globs['Metameta'] - - self.assertEqual(self._callFUT((Meta, type)), Metameta) - - @_skip_under_py3k - def test_multiple_in_hierarchy(self): - class Meta_A(type): - pass - class Meta_B(Meta_A): - pass - class A(type): - __metaclass__ = Meta_A - class B(type): - __metaclass__ = Meta_B - self.assertEqual(self._callFUT((A, B,)), Meta_B) - - @_skip_under_py2 - def test_multiple_in_hierarchy_py3k(self): - # Work around SyntaxError under Python2. - EXEC = '\n'.join([ - 'class Meta_A(type):', - ' pass', - 'class Meta_B(Meta_A):', - ' pass', - 'class A(type, metaclass=Meta_A):', - ' pass', - 'class B(type, metaclass=Meta_B):', - ' pass', - ]) - globs = {} - exec(EXEC, globs) - Meta_A = globs['Meta_A'] - Meta_B = globs['Meta_B'] - A = globs['A'] - B = globs['B'] - self.assertEqual(self._callFUT((A, B)), Meta_B) - - @_skip_under_py3k - def test_multiple_not_in_hierarchy(self): - class Meta_A(type): - pass - class Meta_B(type): - pass - class A(type): - __metaclass__ = Meta_A - class B(type): - __metaclass__ = Meta_B - self.assertRaises(TypeError, self._callFUT, (A, B,)) - - @_skip_under_py2 - def test_multiple_not_in_hierarchy_py3k(self): - # Work around SyntaxError under Python2. - EXEC = '\n'.join([ - 'class Meta_A(type):', - ' pass', - 'class Meta_B(type):', - ' pass', - 'class A(type, metaclass=Meta_A):', - ' pass', - 'class B(type, metaclass=Meta_B):', - ' pass', - ]) - globs = {} - exec(EXEC, globs) - Meta_A = globs['Meta_A'] - Meta_B = globs['Meta_B'] - A = globs['A'] - B = globs['B'] - self.assertRaises(TypeError, self._callFUT, (A, B)) - - -class Test_minimalBases(unittest.TestCase): - - def _callFUT(self, klasses): - from zope.interface.advice import minimalBases - return minimalBases(klasses) - - def test_empty(self): - self.assertEqual(self._callFUT([]), []) - - @_skip_under_py3k - def test_w_oldstyle_meta(self): - class C: - pass - self.assertEqual(self._callFUT([type(C)]), []) - - @_skip_under_py3k - def test_w_oldstyle_class(self): - class C: - pass - self.assertEqual(self._callFUT([C]), [C]) - - def test_w_newstyle_meta(self): - self.assertEqual(self._callFUT([type]), [type]) - - def test_w_newstyle_class(self): - class C(object): - pass - self.assertEqual(self._callFUT([C]), [C]) - - def test_simple_hierarchy_skips_implied(self): - class A(object): - pass - class B(A): - pass - class C(B): - pass - class D(object): - pass - self.assertEqual(self._callFUT([A, B, C]), [C]) - self.assertEqual(self._callFUT([A, C]), [C]) - self.assertEqual(self._callFUT([B, C]), [C]) - self.assertEqual(self._callFUT([A, B]), [B]) - self.assertEqual(self._callFUT([D, B, D]), [B, D]) - - def test_repeats_kicked_to_end_of_queue(self): - class A(object): - pass - class B(object): - pass - self.assertEqual(self._callFUT([A, B, A]), [B, A]) diff --git a/lib/zope/interface/tests/test_declarations.py b/lib/zope/interface/tests/test_declarations.py deleted file mode 100644 index 43f95c8..0000000 --- a/lib/zope/interface/tests/test_declarations.py +++ /dev/null @@ -1,1658 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Test the new API for making and checking interface declarations -""" -import unittest - -from zope.interface._compat import _skip_under_py3k - - -class _Py3ClassAdvice(object): - - def _run_generated_code(self, code, globs, locs, - fails_under_py3k=True, - ): - import warnings - from zope.interface._compat import PYTHON3 - with warnings.catch_warnings(record=True) as log: - warnings.resetwarnings() - if not PYTHON3: - exec(code, globs, locs) - self.assertEqual(len(log), 0) # no longer warn - return True - else: - try: - exec(code, globs, locs) - except TypeError: - return False - else: - if fails_under_py3k: - self.fail("Didn't raise TypeError") - - -class NamedTests(unittest.TestCase): - - def test_class(self): - from zope.interface.declarations import named - - @named(u'foo') - class Foo(object): - pass - - self.assertEqual(Foo.__component_name__, u'foo') - - def test_function(self): - from zope.interface.declarations import named - - @named(u'foo') - def doFoo(o): - raise NotImplementedError() - - self.assertEqual(doFoo.__component_name__, u'foo') - - def test_instance(self): - from zope.interface.declarations import named - - class Foo(object): - pass - foo = Foo() - named(u'foo')(foo) - - self.assertEqual(foo.__component_name__, u'foo') - - -class DeclarationTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.declarations import Declaration - return Declaration - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor_no_bases(self): - decl = self._makeOne() - self.assertEqual(list(decl.__bases__), []) - - def test_ctor_w_interface_in_bases(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - decl = self._makeOne(IFoo) - self.assertEqual(list(decl.__bases__), [IFoo]) - - def test_ctor_w_implements_in_bases(self): - from zope.interface.declarations import Implements - impl = Implements() - decl = self._makeOne(impl) - self.assertEqual(list(decl.__bases__), [impl]) - - def test_changed_wo_existing__v_attrs(self): - decl = self._makeOne() - decl.changed(decl) # doesn't raise - self.assertFalse('_v_attrs' in decl.__dict__) - - def test_changed_w_existing__v_attrs(self): - decl = self._makeOne() - decl._v_attrs = object() - decl.changed(decl) - self.assertFalse('_v_attrs' in decl.__dict__) - - def test___contains__w_self(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - decl = self._makeOne() - self.assertFalse(decl in decl) - - def test___contains__w_unrelated_iface(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - decl = self._makeOne() - self.assertFalse(IFoo in decl) - - def test___contains__w_base_interface(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - decl = self._makeOne(IFoo) - self.assertTrue(IFoo in decl) - - def test___iter___empty(self): - decl = self._makeOne() - self.assertEqual(list(decl), []) - - def test___iter___single_base(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - decl = self._makeOne(IFoo) - self.assertEqual(list(decl), [IFoo]) - - def test___iter___multiple_bases(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - decl = self._makeOne(IFoo, IBar) - self.assertEqual(list(decl), [IFoo, IBar]) - - def test___iter___inheritance(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', (IFoo,)) - decl = self._makeOne(IBar) - self.assertEqual(list(decl), [IBar]) #IBar.interfaces() omits bases - - def test___iter___w_nested_sequence_overlap(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - decl = self._makeOne(IBar, (IFoo, IBar)) - self.assertEqual(list(decl), [IBar, IFoo]) - - def test_flattened_empty(self): - from zope.interface.interface import Interface - decl = self._makeOne() - self.assertEqual(list(decl.flattened()), [Interface]) - - def test_flattened_single_base(self): - from zope.interface.interface import Interface - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - decl = self._makeOne(IFoo) - self.assertEqual(list(decl.flattened()), [IFoo, Interface]) - - def test_flattened_multiple_bases(self): - from zope.interface.interface import Interface - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - decl = self._makeOne(IFoo, IBar) - self.assertEqual(list(decl.flattened()), [IFoo, IBar, Interface]) - - def test_flattened_inheritance(self): - from zope.interface.interface import Interface - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', (IFoo,)) - decl = self._makeOne(IBar) - self.assertEqual(list(decl.flattened()), [IBar, IFoo, Interface]) - - def test_flattened_w_nested_sequence_overlap(self): - from zope.interface.interface import Interface - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - decl = self._makeOne(IBar, (IFoo, IBar)) - # Note that decl.__iro__ has IFoo first. - self.assertEqual(list(decl.flattened()), [IFoo, IBar, Interface]) - - def test___sub___unrelated_interface(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - before = self._makeOne(IFoo) - after = before - IBar - self.assertTrue(isinstance(after, self._getTargetClass())) - self.assertEqual(list(after), [IFoo]) - - def test___sub___related_interface(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - before = self._makeOne(IFoo) - after = before - IFoo - self.assertEqual(list(after), []) - - def test___sub___related_interface_by_inheritance(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar', (IFoo,)) - before = self._makeOne(IBar) - after = before - IBar - self.assertEqual(list(after), []) - - def test___add___unrelated_interface(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - before = self._makeOne(IFoo) - after = before + IBar - self.assertTrue(isinstance(after, self._getTargetClass())) - self.assertEqual(list(after), [IFoo, IBar]) - - def test___add___related_interface(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - IBaz = InterfaceClass('IBaz') - before = self._makeOne(IFoo, IBar) - other = self._makeOne(IBar, IBaz) - after = before + other - self.assertEqual(list(after), [IFoo, IBar, IBaz]) - - -class TestImplements(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.declarations import Implements - return Implements - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor_no_bases(self): - impl = self._makeOne() - self.assertEqual(impl.inherit, None) - self.assertEqual(impl.declared, ()) - self.assertEqual(impl.__name__, '?') - self.assertEqual(list(impl.__bases__), []) - - def test___repr__(self): - impl = self._makeOne() - impl.__name__ = 'Testing' - self.assertEqual(repr(impl), '<implementedBy Testing>') - - def test___reduce__(self): - from zope.interface.declarations import implementedBy - impl = self._makeOne() - self.assertEqual(impl.__reduce__(), (implementedBy, (None,))) - - def test_sort(self): - from zope.interface.declarations import implementedBy - class A(object): - pass - class B(object): - pass - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - - self.assertEqual(implementedBy(A), implementedBy(A)) - self.assertEqual(hash(implementedBy(A)), hash(implementedBy(A))) - self.assertTrue(implementedBy(A) < None) - self.assertTrue(None > implementedBy(A)) - self.assertTrue(implementedBy(A) < implementedBy(B)) - self.assertTrue(implementedBy(A) > IFoo) - self.assertTrue(implementedBy(A) <= implementedBy(B)) - self.assertTrue(implementedBy(A) >= IFoo) - self.assertTrue(implementedBy(A) != IFoo) - - def test_proxy_equality(self): - # https://github.com/zopefoundation/zope.interface/issues/55 - class Proxy(object): - def __init__(self, wrapped): - self._wrapped = wrapped - - def __getattr__(self, name): - raise NotImplementedError() - - def __eq__(self, other): - return self._wrapped == other - - def __ne__(self, other): - return self._wrapped != other - - from zope.interface.declarations import implementedBy - class A(object): - pass - - class B(object): - pass - - implementedByA = implementedBy(A) - implementedByB = implementedBy(B) - proxy = Proxy(implementedByA) - - # The order of arguments to the operators matters, - # test both - self.assertTrue(implementedByA == implementedByA) - self.assertTrue(implementedByA != implementedByB) - self.assertTrue(implementedByB != implementedByA) - - self.assertTrue(proxy == implementedByA) - self.assertTrue(implementedByA == proxy) - self.assertFalse(proxy != implementedByA) - self.assertFalse(implementedByA != proxy) - - self.assertTrue(proxy != implementedByB) - self.assertTrue(implementedByB != proxy) - - -class Test_implementedByFallback(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import implementedByFallback - return implementedByFallback(*args, **kw) - - def test_dictless_wo_existing_Implements_wo_registrations(self): - class Foo(object): - __slots__ = ('__implemented__',) - foo = Foo() - foo.__implemented__ = None - self.assertEqual(list(self._callFUT(foo)), []) - - def test_dictless_wo_existing_Implements_cant_assign___implemented__(self): - class Foo(object): - def _get_impl(self): - raise NotImplementedError() - def _set_impl(self, val): - raise TypeError - __implemented__ = property(_get_impl, _set_impl) - def __call__(self): - # act like a factory - raise NotImplementedError() - foo = Foo() - self.assertRaises(TypeError, self._callFUT, foo) - - def test_dictless_wo_existing_Implements_w_registrations(self): - from zope.interface import declarations - class Foo(object): - __slots__ = ('__implemented__',) - foo = Foo() - foo.__implemented__ = None - reg = object() - with _MonkeyDict(declarations, - 'BuiltinImplementationSpecifications') as specs: - specs[foo] = reg - self.assertTrue(self._callFUT(foo) is reg) - - def test_dictless_w_existing_Implements(self): - from zope.interface.declarations import Implements - impl = Implements() - class Foo(object): - __slots__ = ('__implemented__',) - foo = Foo() - foo.__implemented__ = impl - self.assertTrue(self._callFUT(foo) is impl) - - def test_dictless_w_existing_not_Implements(self): - from zope.interface.interface import InterfaceClass - class Foo(object): - __slots__ = ('__implemented__',) - foo = Foo() - IFoo = InterfaceClass('IFoo') - foo.__implemented__ = (IFoo,) - self.assertEqual(list(self._callFUT(foo)), [IFoo]) - - def test_w_existing_attr_as_Implements(self): - from zope.interface.declarations import Implements - impl = Implements() - class Foo(object): - __implemented__ = impl - self.assertTrue(self._callFUT(Foo) is impl) - - def test_builtins_added_to_cache(self): - from zope.interface import declarations - from zope.interface.declarations import Implements - from zope.interface._compat import _BUILTINS - with _MonkeyDict(declarations, - 'BuiltinImplementationSpecifications') as specs: - self.assertEqual(list(self._callFUT(tuple)), []) - self.assertEqual(list(self._callFUT(list)), []) - self.assertEqual(list(self._callFUT(dict)), []) - for typ in (tuple, list, dict): - spec = specs[typ] - self.assertTrue(isinstance(spec, Implements)) - self.assertEqual(repr(spec), - '<implementedBy %s.%s>' - % (_BUILTINS, typ.__name__)) - - def test_builtins_w_existing_cache(self): - from zope.interface import declarations - t_spec, l_spec, d_spec = object(), object(), object() - with _MonkeyDict(declarations, - 'BuiltinImplementationSpecifications') as specs: - specs[tuple] = t_spec - specs[list] = l_spec - specs[dict] = d_spec - self.assertTrue(self._callFUT(tuple) is t_spec) - self.assertTrue(self._callFUT(list) is l_spec) - self.assertTrue(self._callFUT(dict) is d_spec) - - def test_oldstyle_class_no_assertions(self): - # TODO: Figure out P3 story - class Foo: - pass - self.assertEqual(list(self._callFUT(Foo)), []) - - def test_no_assertions(self): - # TODO: Figure out P3 story - class Foo(object): - pass - self.assertEqual(list(self._callFUT(Foo)), []) - - def test_w_None_no_bases_not_factory(self): - class Foo(object): - __implemented__ = None - foo = Foo() - self.assertRaises(TypeError, self._callFUT, foo) - - def test_w_None_no_bases_w_factory(self): - from zope.interface.declarations import objectSpecificationDescriptor - class Foo(object): - __implemented__ = None - def __call__(self): - raise NotImplementedError() - - foo = Foo() - foo.__name__ = 'foo' - spec = self._callFUT(foo) - self.assertEqual(spec.__name__, - 'zope.interface.tests.test_declarations.foo') - self.assertTrue(spec.inherit is foo) - self.assertTrue(foo.__implemented__ is spec) - self.assertTrue(foo.__providedBy__ is objectSpecificationDescriptor) - self.assertFalse('__provides__' in foo.__dict__) - - def test_w_None_no_bases_w_class(self): - from zope.interface.declarations import ClassProvides - class Foo(object): - __implemented__ = None - spec = self._callFUT(Foo) - self.assertEqual(spec.__name__, - 'zope.interface.tests.test_declarations.Foo') - self.assertTrue(spec.inherit is Foo) - self.assertTrue(Foo.__implemented__ is spec) - self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) - self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) - self.assertEqual(Foo.__provides__, Foo.__providedBy__) - - def test_w_existing_Implements(self): - from zope.interface.declarations import Implements - impl = Implements() - class Foo(object): - __implemented__ = impl - self.assertTrue(self._callFUT(Foo) is impl) - - -class Test_implementedBy(Test_implementedByFallback): - # Repeat tests for C optimizations - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import implementedBy - return implementedBy(*args, **kw) - - def test_optimizations(self): - from zope.interface.declarations import implementedByFallback - from zope.interface.declarations import implementedBy - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(implementedBy, implementedByFallback) - else: - self.assertIsNot(implementedBy, implementedByFallback) - - -class Test_classImplementsOnly(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import classImplementsOnly - return classImplementsOnly(*args, **kw) - - def test_no_existing(self): - from zope.interface.declarations import ClassProvides - from zope.interface.interface import InterfaceClass - class Foo(object): - pass - ifoo = InterfaceClass('IFoo') - self._callFUT(Foo, ifoo) - spec = Foo.__implemented__ - self.assertEqual(spec.__name__, - 'zope.interface.tests.test_declarations.Foo') - self.assertTrue(spec.inherit is None) - self.assertTrue(Foo.__implemented__ is spec) - self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) - self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) - self.assertEqual(Foo.__provides__, Foo.__providedBy__) - - def test_w_existing_Implements(self): - from zope.interface.declarations import Implements - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - impl = Implements(IFoo) - impl.declared = (IFoo,) - class Foo(object): - __implemented__ = impl - impl.inherit = Foo - self._callFUT(Foo, IBar) - # Same spec, now different values - self.assertTrue(Foo.__implemented__ is impl) - self.assertEqual(impl.inherit, None) - self.assertEqual(impl.declared, (IBar,)) - - -class Test_classImplements(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import classImplements - return classImplements(*args, **kw) - - def test_no_existing(self): - from zope.interface.declarations import ClassProvides - from zope.interface.interface import InterfaceClass - class Foo(object): - pass - IFoo = InterfaceClass('IFoo') - self._callFUT(Foo, IFoo) - spec = Foo.__implemented__ - self.assertEqual(spec.__name__, - 'zope.interface.tests.test_declarations.Foo') - self.assertTrue(spec.inherit is Foo) - self.assertTrue(Foo.__implemented__ is spec) - self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) - self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) - self.assertEqual(Foo.__provides__, Foo.__providedBy__) - - def test_w_existing_Implements(self): - from zope.interface.declarations import Implements - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - impl = Implements(IFoo) - impl.declared = (IFoo,) - class Foo(object): - __implemented__ = impl - impl.inherit = Foo - self._callFUT(Foo, IBar) - # Same spec, now different values - self.assertTrue(Foo.__implemented__ is impl) - self.assertEqual(impl.inherit, Foo) - self.assertEqual(impl.declared, (IFoo, IBar,)) - - def test_w_existing_Implements_w_bases(self): - from zope.interface.declarations import Implements - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - IBaz = InterfaceClass('IBaz', IFoo) - b_impl = Implements(IBaz) - impl = Implements(IFoo) - impl.declared = (IFoo,) - class Base1(object): - __implemented__ = b_impl - class Base2(object): - __implemented__ = b_impl - class Foo(Base1, Base2): - __implemented__ = impl - impl.inherit = Foo - self._callFUT(Foo, IBar) - # Same spec, now different values - self.assertTrue(Foo.__implemented__ is impl) - self.assertEqual(impl.inherit, Foo) - self.assertEqual(impl.declared, (IFoo, IBar,)) - self.assertEqual(impl.__bases__, (IFoo, IBar, b_impl)) - - -class Test__implements_advice(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import _implements_advice - return _implements_advice(*args, **kw) - - def test_no_existing_implements(self): - from zope.interface.declarations import classImplements - from zope.interface.declarations import Implements - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - class Foo(object): - __implements_advice_data__ = ((IFoo,), classImplements) - self._callFUT(Foo) - self.assertFalse('__implements_advice_data__' in Foo.__dict__) - self.assertTrue(isinstance(Foo.__implemented__, Implements)) - self.assertEqual(list(Foo.__implemented__), [IFoo]) - - -class Test_implementer(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.declarations import implementer - return implementer - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_oldstyle_class(self): - # TODO Py3 story - from zope.interface.declarations import ClassProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - class Foo: - pass - decorator = self._makeOne(IFoo) - returned = decorator(Foo) - self.assertTrue(returned is Foo) - spec = Foo.__implemented__ - self.assertEqual(spec.__name__, - 'zope.interface.tests.test_declarations.Foo') - self.assertTrue(spec.inherit is Foo) - self.assertTrue(Foo.__implemented__ is spec) - self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) - self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) - self.assertEqual(Foo.__provides__, Foo.__providedBy__) - - def test_newstyle_class(self): - from zope.interface.declarations import ClassProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - class Foo(object): - pass - decorator = self._makeOne(IFoo) - returned = decorator(Foo) - self.assertTrue(returned is Foo) - spec = Foo.__implemented__ - self.assertEqual(spec.__name__, - 'zope.interface.tests.test_declarations.Foo') - self.assertTrue(spec.inherit is Foo) - self.assertTrue(Foo.__implemented__ is spec) - self.assertTrue(isinstance(Foo.__providedBy__, ClassProvides)) - self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) - self.assertEqual(Foo.__provides__, Foo.__providedBy__) - - def test_nonclass_cannot_assign_attr(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - decorator = self._makeOne(IFoo) - self.assertRaises(TypeError, decorator, object()) - - def test_nonclass_can_assign_attr(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - class Foo(object): - pass - foo = Foo() - decorator = self._makeOne(IFoo) - returned = decorator(foo) - self.assertTrue(returned is foo) - spec = foo.__implemented__ - self.assertEqual(spec.__name__, 'zope.interface.tests.test_declarations.?') - self.assertTrue(spec.inherit is None) - self.assertTrue(foo.__implemented__ is spec) - - -class Test_implementer_only(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.declarations import implementer_only - return implementer_only - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_function(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - decorator = self._makeOne(IFoo) - def _function(): - raise NotImplementedError() - self.assertRaises(ValueError, decorator, _function) - - def test_method(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - decorator = self._makeOne(IFoo) - class Bar: - def _method(): - raise NotImplementedError() - self.assertRaises(ValueError, decorator, Bar._method) - - def test_oldstyle_class(self): - # TODO Py3 story - from zope.interface.declarations import Implements - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - old_spec = Implements(IBar) - class Foo: - __implemented__ = old_spec - decorator = self._makeOne(IFoo) - returned = decorator(Foo) - self.assertTrue(returned is Foo) - spec = Foo.__implemented__ - self.assertEqual(spec.__name__, '?') - self.assertTrue(spec.inherit is None) - self.assertTrue(Foo.__implemented__ is spec) - - def test_newstyle_class(self): - from zope.interface.declarations import Implements - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass('IFoo') - IBar = InterfaceClass('IBar') - old_spec = Implements(IBar) - class Foo(object): - __implemented__ = old_spec - decorator = self._makeOne(IFoo) - returned = decorator(Foo) - self.assertTrue(returned is Foo) - spec = Foo.__implemented__ - self.assertEqual(spec.__name__, '?') - self.assertTrue(spec.inherit is None) - self.assertTrue(Foo.__implemented__ is spec) - - -# Test '_implements' by way of 'implements{,Only}', its only callers. - -class Test_implementsOnly(unittest.TestCase, _Py3ClassAdvice): - - def test_simple(self): - import warnings - from zope.interface.declarations import implementsOnly - from zope.interface._compat import PYTHON3 - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - globs = {'implementsOnly': implementsOnly, - 'IFoo': IFoo, - } - locs = {} - CODE = "\n".join([ - 'class Foo(object):' - ' implementsOnly(IFoo)', - ]) - with warnings.catch_warnings(record=True) as log: - warnings.resetwarnings() - try: - exec(CODE, globs, locs) - except TypeError: - self.assertTrue(PYTHON3, "Must be Python 3") - else: - if PYTHON3: - self.fail("Didn't raise TypeError") - Foo = locs['Foo'] - spec = Foo.__implemented__ - self.assertEqual(list(spec), [IFoo]) - self.assertEqual(len(log), 0) # no longer warn - - def test_called_once_from_class_w_bases(self): - from zope.interface.declarations import implements - from zope.interface.declarations import implementsOnly - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - globs = {'implements': implements, - 'implementsOnly': implementsOnly, - 'IFoo': IFoo, - 'IBar': IBar, - } - locs = {} - CODE = "\n".join([ - 'class Foo(object):', - ' implements(IFoo)', - 'class Bar(Foo):' - ' implementsOnly(IBar)', - ]) - if self._run_generated_code(CODE, globs, locs): - Bar = locs['Bar'] - spec = Bar.__implemented__ - self.assertEqual(list(spec), [IBar]) - - -class Test_implements(unittest.TestCase, _Py3ClassAdvice): - - def test_called_from_function(self): - import warnings - from zope.interface.declarations import implements - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - globs = {'implements': implements, 'IFoo': IFoo} - locs = {} - CODE = "\n".join([ - 'def foo():', - ' implements(IFoo)' - ]) - if self._run_generated_code(CODE, globs, locs, False): - foo = locs['foo'] - with warnings.catch_warnings(record=True) as log: - warnings.resetwarnings() - self.assertRaises(TypeError, foo) - self.assertEqual(len(log), 0) # no longer warn - - def test_called_twice_from_class(self): - import warnings - from zope.interface.declarations import implements - from zope.interface.interface import InterfaceClass - from zope.interface._compat import PYTHON3 - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - globs = {'implements': implements, 'IFoo': IFoo, 'IBar': IBar} - locs = {} - CODE = "\n".join([ - 'class Foo(object):', - ' implements(IFoo)', - ' implements(IBar)', - ]) - with warnings.catch_warnings(record=True) as log: - warnings.resetwarnings() - try: - exec(CODE, globs, locs) - except TypeError: - if not PYTHON3: - self.assertEqual(len(log), 0) # no longer warn - else: - self.fail("Didn't raise TypeError") - - def test_called_once_from_class(self): - from zope.interface.declarations import implements - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - globs = {'implements': implements, 'IFoo': IFoo} - locs = {} - CODE = "\n".join([ - 'class Foo(object):', - ' implements(IFoo)', - ]) - if self._run_generated_code(CODE, globs, locs): - Foo = locs['Foo'] - spec = Foo.__implemented__ - self.assertEqual(list(spec), [IFoo]) - - -class ProvidesClassTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.declarations import ProvidesClass - return ProvidesClass - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_simple_class_one_interface(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - spec = self._makeOne(Foo, IFoo) - self.assertEqual(list(spec), [IFoo]) - - def test___reduce__(self): - from zope.interface.declarations import Provides # the function - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - spec = self._makeOne(Foo, IFoo) - klass, args = spec.__reduce__() - self.assertTrue(klass is Provides) - self.assertEqual(args, (Foo, IFoo)) - - def test___get___class(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - spec = self._makeOne(Foo, IFoo) - Foo.__provides__ = spec - self.assertTrue(Foo.__provides__ is spec) - - def test___get___instance(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - spec = self._makeOne(Foo, IFoo) - Foo.__provides__ = spec - def _test(): - foo = Foo() - return foo.__provides__ - self.assertRaises(AttributeError, _test) - - -class Test_Provides(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import Provides - return Provides(*args, **kw) - - def test_no_cached_spec(self): - from zope.interface import declarations - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - cache = {} - class Foo(object): - pass - with _Monkey(declarations, InstanceDeclarations=cache): - spec = self._callFUT(Foo, IFoo) - self.assertEqual(list(spec), [IFoo]) - self.assertTrue(cache[(Foo, IFoo)] is spec) - - def test_w_cached_spec(self): - from zope.interface import declarations - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - prior = object() - class Foo(object): - pass - cache = {(Foo, IFoo): prior} - with _Monkey(declarations, InstanceDeclarations=cache): - spec = self._callFUT(Foo, IFoo) - self.assertTrue(spec is prior) - - -class Test_directlyProvides(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import directlyProvides - return directlyProvides(*args, **kw) - - def test_w_normal_object(self): - from zope.interface.declarations import ProvidesClass - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - obj = Foo() - self._callFUT(obj, IFoo) - self.assertTrue(isinstance(obj.__provides__, ProvidesClass)) - self.assertEqual(list(obj.__provides__), [IFoo]) - - def test_w_class(self): - from zope.interface.declarations import ClassProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - self._callFUT(Foo, IFoo) - self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) - self.assertEqual(list(Foo.__provides__), [IFoo]) - - @_skip_under_py3k - def test_w_non_descriptor_aware_metaclass(self): - # There are no non-descriptor-aware types in Py3k - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class MetaClass(type): - def __getattribute__(cls, name): - # Emulate metaclass whose base is not the type object. - if name == '__class__': - return cls - # Under certain circumstances, the implementedByFallback - # can get here for __dict__ - return type.__getattribute__(cls, name) # pragma: no cover - - class Foo(object): - __metaclass__ = MetaClass - obj = Foo() - self.assertRaises(TypeError, self._callFUT, obj, IFoo) - - def test_w_classless_object(self): - from zope.interface.declarations import ProvidesClass - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - the_dict = {} - class Foo(object): - def __getattribute__(self, name): - # Emulate object w/o any class - if name == '__class__': - return None - raise NotImplementedError(name) - def __setattr__(self, name, value): - the_dict[name] = value - obj = Foo() - self._callFUT(obj, IFoo) - self.assertTrue(isinstance(the_dict['__provides__'], ProvidesClass)) - self.assertEqual(list(the_dict['__provides__']), [IFoo]) - - -class Test_alsoProvides(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import alsoProvides - return alsoProvides(*args, **kw) - - def test_wo_existing_provides(self): - from zope.interface.declarations import ProvidesClass - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - obj = Foo() - self._callFUT(obj, IFoo) - self.assertTrue(isinstance(obj.__provides__, ProvidesClass)) - self.assertEqual(list(obj.__provides__), [IFoo]) - - def test_w_existing_provides(self): - from zope.interface.declarations import directlyProvides - from zope.interface.declarations import ProvidesClass - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - class Foo(object): - pass - obj = Foo() - directlyProvides(obj, IFoo) - self._callFUT(obj, IBar) - self.assertTrue(isinstance(obj.__provides__, ProvidesClass)) - self.assertEqual(list(obj.__provides__), [IFoo, IBar]) - - -class Test_noLongerProvides(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import noLongerProvides - return noLongerProvides(*args, **kw) - - def test_wo_existing_provides(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - obj = Foo() - self._callFUT(obj, IFoo) - self.assertEqual(list(obj.__provides__), []) - - def test_w_existing_provides_hit(self): - from zope.interface.declarations import directlyProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - obj = Foo() - directlyProvides(obj, IFoo) - self._callFUT(obj, IFoo) - self.assertEqual(list(obj.__provides__), []) - - def test_w_existing_provides_miss(self): - from zope.interface.declarations import directlyProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - class Foo(object): - pass - obj = Foo() - directlyProvides(obj, IFoo) - self._callFUT(obj, IBar) - self.assertEqual(list(obj.__provides__), [IFoo]) - - def test_w_iface_implemented_by_class(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - @implementer(IFoo) - class Foo(object): - pass - obj = Foo() - self.assertRaises(ValueError, self._callFUT, obj, IFoo) - - -class ClassProvidesBaseFallbackTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.declarations import ClassProvidesBaseFallback - return ClassProvidesBaseFallback - - def _makeOne(self, klass, implements): - # Don't instantiate directly: the C version can't have attributes - # assigned. - class Derived(self._getTargetClass()): - def __init__(self, k, i): - self._cls = k - self._implements = i - return Derived(klass, implements) - - def test_w_same_class_via_class(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - cpbp = Foo.__provides__ = self._makeOne(Foo, IFoo) - self.assertTrue(Foo.__provides__ is cpbp) - - def test_w_same_class_via_instance(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - foo = Foo() - cpbp = Foo.__provides__ = self._makeOne(Foo, IFoo) - self.assertTrue(foo.__provides__ is IFoo) - - def test_w_different_class(self): - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - class Bar(Foo): - pass - bar = Bar() - cpbp = Foo.__provides__ = self._makeOne(Foo, IFoo) - self.assertRaises(AttributeError, getattr, Bar, '__provides__') - self.assertRaises(AttributeError, getattr, bar, '__provides__') - - -class ClassProvidesBaseTests(ClassProvidesBaseFallbackTests): - # Repeat tests for C optimizations - - def _getTargetClass(self): - from zope.interface.declarations import ClassProvidesBase - return ClassProvidesBase - - def test_optimizations(self): - from zope.interface.declarations import ClassProvidesBaseFallback - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), ClassProvidesBaseFallback) - else: - self.assertIsNot(self._getTargetClass(), ClassProvidesBaseFallback) - - -class ClassProvidesTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.declarations import ClassProvides - return ClassProvides - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_w_simple_metaclass(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - @implementer(IFoo) - class Foo(object): - pass - cp = Foo.__provides__ = self._makeOne(Foo, type(Foo), IBar) - self.assertTrue(Foo.__provides__ is cp) - self.assertEqual(list(Foo().__provides__), [IFoo]) - - def test___reduce__(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - @implementer(IFoo) - class Foo(object): - pass - cp = Foo.__provides__ = self._makeOne(Foo, type(Foo), IBar) - self.assertEqual(cp.__reduce__(), - (self._getTargetClass(), (Foo, type(Foo), IBar))) - - -class Test_directlyProvidedBy(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import directlyProvidedBy - return directlyProvidedBy(*args, **kw) - - def test_wo_declarations_in_class_or_instance(self): - class Foo(object): - pass - foo = Foo() - self.assertEqual(list(self._callFUT(foo)), []) - - def test_w_declarations_in_class_but_not_instance(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - self.assertEqual(list(self._callFUT(foo)), []) - - def test_w_declarations_in_instance_but_not_class(self): - from zope.interface.declarations import directlyProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - foo = Foo() - directlyProvides(foo, IFoo) - self.assertEqual(list(self._callFUT(foo)), [IFoo]) - - def test_w_declarations_in_instance_and_class(self): - from zope.interface.declarations import directlyProvides - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - directlyProvides(foo, IBar) - self.assertEqual(list(self._callFUT(foo)), [IBar]) - - -class Test_classProvides(unittest.TestCase, _Py3ClassAdvice): - - def test_called_from_function(self): - import warnings - from zope.interface.declarations import classProvides - from zope.interface.interface import InterfaceClass - from zope.interface._compat import PYTHON3 - IFoo = InterfaceClass("IFoo") - globs = {'classProvides': classProvides, 'IFoo': IFoo} - locs = {} - CODE = "\n".join([ - 'def foo():', - ' classProvides(IFoo)' - ]) - exec(CODE, globs, locs) - foo = locs['foo'] - with warnings.catch_warnings(record=True) as log: - warnings.resetwarnings() - self.assertRaises(TypeError, foo) - if not PYTHON3: - self.assertEqual(len(log), 0) # no longer warn - - def test_called_twice_from_class(self): - import warnings - from zope.interface.declarations import classProvides - from zope.interface.interface import InterfaceClass - from zope.interface._compat import PYTHON3 - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - globs = {'classProvides': classProvides, 'IFoo': IFoo, 'IBar': IBar} - locs = {} - CODE = "\n".join([ - 'class Foo(object):', - ' classProvides(IFoo)', - ' classProvides(IBar)', - ]) - with warnings.catch_warnings(record=True) as log: - warnings.resetwarnings() - try: - exec(CODE, globs, locs) - except TypeError: - if not PYTHON3: - self.assertEqual(len(log), 0) # no longer warn - else: - self.fail("Didn't raise TypeError") - - def test_called_once_from_class(self): - from zope.interface.declarations import classProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - globs = {'classProvides': classProvides, 'IFoo': IFoo} - locs = {} - CODE = "\n".join([ - 'class Foo(object):', - ' classProvides(IFoo)', - ]) - if self._run_generated_code(CODE, globs, locs): - Foo = locs['Foo'] - spec = Foo.__providedBy__ - self.assertEqual(list(spec), [IFoo]) - -# Test _classProvides_advice through classProvides, its only caller. - - -class Test_provider(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.declarations import provider - return provider - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_w_class(self): - from zope.interface.declarations import ClassProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - @self._makeOne(IFoo) - class Foo(object): - pass - self.assertTrue(isinstance(Foo.__provides__, ClassProvides)) - self.assertEqual(list(Foo.__provides__), [IFoo]) - - -class Test_moduleProvides(unittest.TestCase): - - def test_called_from_function(self): - from zope.interface.declarations import moduleProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - globs = {'__name__': 'zope.interface.tests.foo', - 'moduleProvides': moduleProvides, 'IFoo': IFoo} - locs = {} - CODE = "\n".join([ - 'def foo():', - ' moduleProvides(IFoo)' - ]) - exec(CODE, globs, locs) - foo = locs['foo'] - self.assertRaises(TypeError, foo) - - def test_called_from_class(self): - from zope.interface.declarations import moduleProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - globs = {'__name__': 'zope.interface.tests.foo', - 'moduleProvides': moduleProvides, 'IFoo': IFoo} - locs = {} - CODE = "\n".join([ - 'class Foo(object):', - ' moduleProvides(IFoo)', - ]) - with self.assertRaises(TypeError): - exec(CODE, globs, locs) - - def test_called_once_from_module_scope(self): - from zope.interface.declarations import moduleProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - globs = {'__name__': 'zope.interface.tests.foo', - 'moduleProvides': moduleProvides, 'IFoo': IFoo} - CODE = "\n".join([ - 'moduleProvides(IFoo)', - ]) - exec(CODE, globs) - spec = globs['__provides__'] - self.assertEqual(list(spec), [IFoo]) - - def test_called_twice_from_module_scope(self): - from zope.interface.declarations import moduleProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - globs = {'__name__': 'zope.interface.tests.foo', - 'moduleProvides': moduleProvides, 'IFoo': IFoo} - locs = {} - CODE = "\n".join([ - 'moduleProvides(IFoo)', - 'moduleProvides(IFoo)', - ]) - with self.assertRaises(TypeError): - exec(CODE, globs) - - -class Test_getObjectSpecificationFallback(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import getObjectSpecificationFallback - return getObjectSpecificationFallback(*args, **kw) - - def test_wo_existing_provides_classless(self): - the_dict = {} - class Foo(object): - def __getattribute__(self, name): - # Emulate object w/o any class - if name == '__class__': - raise AttributeError(name) - try: - return the_dict[name] - except KeyError: - raise AttributeError(name) - def __setattr__(self, name, value): - raise NotImplementedError() - foo = Foo() - spec = self._callFUT(foo) - self.assertEqual(list(spec), []) - - def test_existing_provides_is_spec(self): - from zope.interface.declarations import directlyProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - def foo(): - raise NotImplementedError() - directlyProvides(foo, IFoo) - spec = self._callFUT(foo) - self.assertTrue(spec is foo.__provides__) - - def test_existing_provides_is_not_spec(self): - def foo(): - raise NotImplementedError() - foo.__provides__ = object() # not a valid spec - spec = self._callFUT(foo) - self.assertEqual(list(spec), []) - - def test_existing_provides(self): - from zope.interface.declarations import directlyProvides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - foo = Foo() - directlyProvides(foo, IFoo) - spec = self._callFUT(foo) - self.assertEqual(list(spec), [IFoo]) - - def test_wo_provides_on_class_w_implements(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - spec = self._callFUT(foo) - self.assertEqual(list(spec), [IFoo]) - - def test_wo_provides_on_class_wo_implements(self): - class Foo(object): - pass - foo = Foo() - spec = self._callFUT(foo) - self.assertEqual(list(spec), []) - - -class Test_getObjectSpecification(Test_getObjectSpecificationFallback): - # Repeat tests for C optimizations - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import getObjectSpecification - return getObjectSpecification(*args, **kw) - - def test_optimizations(self): - from zope.interface.declarations import getObjectSpecificationFallback - from zope.interface.declarations import getObjectSpecification - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(getObjectSpecification, - getObjectSpecificationFallback) - else: - self.assertIsNot(getObjectSpecification, - getObjectSpecificationFallback) - - -class Test_providedByFallback(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import providedByFallback - return providedByFallback(*args, **kw) - - def test_wo_providedBy_on_class_wo_implements(self): - class Foo(object): - pass - foo = Foo() - spec = self._callFUT(foo) - self.assertEqual(list(spec), []) - - def test_w_providedBy_valid_spec(self): - from zope.interface.declarations import Provides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - foo = Foo() - foo.__providedBy__ = Provides(Foo, IFoo) - spec = self._callFUT(foo) - self.assertEqual(list(spec), [IFoo]) - - def test_w_providedBy_invalid_spec(self): - class Foo(object): - pass - foo = Foo() - foo.__providedBy__ = object() - spec = self._callFUT(foo) - self.assertEqual(list(spec), []) - - def test_w_providedBy_invalid_spec_class_w_implements(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - foo.__providedBy__ = object() - spec = self._callFUT(foo) - self.assertEqual(list(spec), [IFoo]) - - def test_w_providedBy_invalid_spec_w_provides_no_provides_on_class(self): - class Foo(object): - pass - foo = Foo() - foo.__providedBy__ = object() - expected = foo.__provides__ = object() - spec = self._callFUT(foo) - self.assertTrue(spec is expected) - - def test_w_providedBy_invalid_spec_w_provides_diff_provides_on_class(self): - class Foo(object): - pass - foo = Foo() - foo.__providedBy__ = object() - expected = foo.__provides__ = object() - Foo.__provides__ = object() - spec = self._callFUT(foo) - self.assertTrue(spec is expected) - - def test_w_providedBy_invalid_spec_w_provides_same_provides_on_class(self): - from zope.interface.declarations import implementer - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - @implementer(IFoo) - class Foo(object): - pass - foo = Foo() - foo.__providedBy__ = object() - foo.__provides__ = Foo.__provides__ = object() - spec = self._callFUT(foo) - self.assertEqual(list(spec), [IFoo]) - - -class Test_providedBy(Test_providedByFallback): - # Repeat tests for C optimizations - - def _callFUT(self, *args, **kw): - from zope.interface.declarations import providedBy - return providedBy(*args, **kw) - - def test_optimizations(self): - from zope.interface.declarations import providedByFallback - from zope.interface.declarations import providedBy - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(providedBy, providedByFallback) - else: - self.assertIsNot(providedBy, providedByFallback) - - -class ObjectSpecificationDescriptorFallbackTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.declarations \ - import ObjectSpecificationDescriptorFallback - return ObjectSpecificationDescriptorFallback - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_accessed_via_class(self): - from zope.interface.declarations import Provides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - class Foo(object): - pass - Foo.__provides__ = Provides(Foo, IFoo) - Foo.__providedBy__ = self._makeOne() - self.assertEqual(list(Foo.__providedBy__), [IFoo]) - - def test_accessed_via_inst_wo_provides(self): - from zope.interface.declarations import implementer - from zope.interface.declarations import Provides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - @implementer(IFoo) - class Foo(object): - pass - Foo.__provides__ = Provides(Foo, IBar) - Foo.__providedBy__ = self._makeOne() - foo = Foo() - self.assertEqual(list(foo.__providedBy__), [IFoo]) - - def test_accessed_via_inst_w_provides(self): - from zope.interface.declarations import directlyProvides - from zope.interface.declarations import implementer - from zope.interface.declarations import Provides - from zope.interface.interface import InterfaceClass - IFoo = InterfaceClass("IFoo") - IBar = InterfaceClass("IBar") - IBaz = InterfaceClass("IBaz") - @implementer(IFoo) - class Foo(object): - pass - Foo.__provides__ = Provides(Foo, IBar) - Foo.__providedBy__ = self._makeOne() - foo = Foo() - directlyProvides(foo, IBaz) - self.assertEqual(list(foo.__providedBy__), [IBaz, IFoo]) - - -class ObjectSpecificationDescriptorTests( - ObjectSpecificationDescriptorFallbackTests): - # Repeat tests for C optimizations - - def _getTargetClass(self): - from zope.interface.declarations import ObjectSpecificationDescriptor - return ObjectSpecificationDescriptor - - def test_optimizations(self): - from zope.interface.declarations import ( - ObjectSpecificationDescriptorFallback) - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), - ObjectSpecificationDescriptorFallback) - else: - self.assertIsNot(self._getTargetClass(), - ObjectSpecificationDescriptorFallback) - - -# Test _normalizeargs through its callers. - - -class _Monkey(object): - # context-manager for replacing module names in the scope of a test. - def __init__(self, module, **kw): - self.module = module - self.to_restore = dict([(key, getattr(module, key)) for key in kw]) - for key, value in kw.items(): - setattr(module, key, value) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - for key, value in self.to_restore.items(): - setattr(self.module, key, value) - - -class _MonkeyDict(object): - # context-manager for restoring a dict w/in a module in the scope of a test. - def __init__(self, module, attrname, **kw): - self.module = module - self.target = getattr(module, attrname) - self.to_restore = self.target.copy() - self.target.clear() - self.target.update(kw) - - def __enter__(self): - return self.target - - def __exit__(self, exc_type, exc_val, exc_tb): - self.target.clear() - self.target.update(self.to_restore) diff --git a/lib/zope/interface/tests/test_document.py b/lib/zope/interface/tests/test_document.py deleted file mode 100644 index bffe6a2..0000000 --- a/lib/zope/interface/tests/test_document.py +++ /dev/null @@ -1,505 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Documentation tests. -""" -import unittest - - -class Test_asStructuredText(unittest.TestCase): - - def _callFUT(self, iface): - from zope.interface.document import asStructuredText - return asStructuredText(iface) - - def test_asStructuredText_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "INoDocstring", - " Attributes:", - " Methods:", - "" - ]) - class INoDocstring(Interface): - pass - self.assertEqual(self._callFUT(INoDocstring), EXPECTED) - - def test_asStructuredText_empty_with_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "IEmpty", - " This is an empty interface.", - " Attributes:", - " Methods:", - "" - ]) - class IEmpty(Interface): - """ This is an empty interface. - """ - self.assertEqual(self._callFUT(IEmpty), EXPECTED) - - def test_asStructuredText_empty_with_multiline_docstring(self): - from zope.interface import Interface - EXPECTED = '\n'.join([ - "IEmpty", - "", - " This is an empty interface.", - " ", - (" It can be used to annotate any class or object, " - "because it promises"), - " nothing.", - "", - " Attributes:", - "", - " Methods:", - "", - "" - ]) - class IEmpty(Interface): - """ This is an empty interface. - - It can be used to annotate any class or object, because it promises - nothing. - """ - self.assertEqual(self._callFUT(IEmpty), EXPECTED) - - def test_asStructuredText_with_attribute_no_docstring(self): - from zope.interface import Attribute - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "IHasAttribute", - " This interface has an attribute.", - " Attributes:", - " an_attribute -- no documentation", - " Methods:", - "" - ]) - class IHasAttribute(Interface): - """ This interface has an attribute. - """ - an_attribute = Attribute('an_attribute') - - self.assertEqual(self._callFUT(IHasAttribute), EXPECTED) - - def test_asStructuredText_with_attribute_with_docstring(self): - from zope.interface import Attribute - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "IHasAttribute", - " This interface has an attribute.", - " Attributes:", - " an_attribute -- This attribute is documented.", - " Methods:", - "" - ]) - class IHasAttribute(Interface): - """ This interface has an attribute. - """ - an_attribute = Attribute('an_attribute', - 'This attribute is documented.') - - self.assertEqual(self._callFUT(IHasAttribute), EXPECTED) - - def test_asStructuredText_with_method_no_args_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "IHasMethod", - " This interface has a method.", - " Attributes:", - " Methods:", - " aMethod() -- no documentation", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(): - pass - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asStructuredText_with_method_positional_args_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "IHasMethod", - " This interface has a method.", - " Attributes:", - " Methods:", - " aMethod(first, second) -- no documentation", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(first, second): - pass - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asStructuredText_with_method_starargs_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "IHasMethod", - " This interface has a method.", - " Attributes:", - " Methods:", - " aMethod(first, second, *rest) -- no documentation", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(first, second, *rest): - pass - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asStructuredText_with_method_kwargs_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "IHasMethod", - " This interface has a method.", - " Attributes:", - " Methods:", - " aMethod(first, second, **kw) -- no documentation", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(first, second, **kw): - pass - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asStructuredText_with_method_with_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "IHasMethod", - " This interface has a method.", - " Attributes:", - " Methods:", - " aMethod() -- This method is documented.", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(): - """This method is documented. - """ - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asStructuredText_derived_ignores_base(self): - from zope.interface import Attribute - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "IDerived", - " IDerived doc", - " This interface extends:", - " o IBase", - " Attributes:", - " attr1 -- no documentation", - " attr2 -- attr2 doc", - " Methods:", - " method3() -- method3 doc", - " method4() -- no documentation", - " method5() -- method5 doc", - "", - ]) - - class IBase(Interface): - def method1(): - pass - def method2(): - pass - - class IDerived(IBase): - "IDerived doc" - attr1 = Attribute('attr1') - attr2 = Attribute('attr2', 'attr2 doc') - - def method3(): - "method3 doc" - def method4(): - pass - def method5(): - "method5 doc" - - self.assertEqual(self._callFUT(IDerived), EXPECTED) - - -class Test_asReStructuredText(unittest.TestCase): - - def _callFUT(self, iface): - from zope.interface.document import asReStructuredText - return asReStructuredText(iface) - - def test_asReStructuredText_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``INoDocstring``", - " Attributes:", - " Methods:", - "" - ]) - class INoDocstring(Interface): - pass - self.assertEqual(self._callFUT(INoDocstring), EXPECTED) - - def test_asReStructuredText_empty_with_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``IEmpty``", - " This is an empty interface.", - " Attributes:", - " Methods:", - "" - ]) - class IEmpty(Interface): - """ This is an empty interface. - """ - self.assertEqual(self._callFUT(IEmpty), EXPECTED) - - def test_asReStructuredText_empty_with_multiline_docstring(self): - from zope.interface import Interface - EXPECTED = '\n'.join([ - "``IEmpty``", - "", - " This is an empty interface.", - " ", - (" It can be used to annotate any class or object, " - "because it promises"), - " nothing.", - "", - " Attributes:", - "", - " Methods:", - "", - "" - ]) - class IEmpty(Interface): - """ This is an empty interface. - - It can be used to annotate any class or object, because it promises - nothing. - """ - self.assertEqual(self._callFUT(IEmpty), EXPECTED) - - def test_asReStructuredText_with_attribute_no_docstring(self): - from zope.interface import Attribute - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``IHasAttribute``", - " This interface has an attribute.", - " Attributes:", - " ``an_attribute`` -- no documentation", - " Methods:", - "" - ]) - class IHasAttribute(Interface): - """ This interface has an attribute. - """ - an_attribute = Attribute('an_attribute') - - self.assertEqual(self._callFUT(IHasAttribute), EXPECTED) - - def test_asReStructuredText_with_attribute_with_docstring(self): - from zope.interface import Attribute - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``IHasAttribute``", - " This interface has an attribute.", - " Attributes:", - " ``an_attribute`` -- This attribute is documented.", - " Methods:", - "" - ]) - class IHasAttribute(Interface): - """ This interface has an attribute. - """ - an_attribute = Attribute('an_attribute', - 'This attribute is documented.') - - self.assertEqual(self._callFUT(IHasAttribute), EXPECTED) - - def test_asReStructuredText_with_method_no_args_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``IHasMethod``", - " This interface has a method.", - " Attributes:", - " Methods:", - " ``aMethod()`` -- no documentation", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(): - pass - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asReStructuredText_with_method_positional_args_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``IHasMethod``", - " This interface has a method.", - " Attributes:", - " Methods:", - " ``aMethod(first, second)`` -- no documentation", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(first, second): - pass - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asReStructuredText_with_method_starargs_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``IHasMethod``", - " This interface has a method.", - " Attributes:", - " Methods:", - " ``aMethod(first, second, *rest)`` -- no documentation", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(first, second, *rest): - pass - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asReStructuredText_with_method_kwargs_no_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``IHasMethod``", - " This interface has a method.", - " Attributes:", - " Methods:", - " ``aMethod(first, second, **kw)`` -- no documentation", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(first, second, **kw): - pass - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asReStructuredText_with_method_with_docstring(self): - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``IHasMethod``", - " This interface has a method.", - " Attributes:", - " Methods:", - " ``aMethod()`` -- This method is documented.", - "" - ]) - class IHasMethod(Interface): - """ This interface has a method. - """ - def aMethod(): - """This method is documented. - """ - - self.assertEqual(self._callFUT(IHasMethod), EXPECTED) - - def test_asReStructuredText_derived_ignores_base(self): - from zope.interface import Attribute - from zope.interface import Interface - EXPECTED = '\n\n'.join([ - "``IDerived``", - " IDerived doc", - " This interface extends:", - " o ``IBase``", - " Attributes:", - " ``attr1`` -- no documentation", - " ``attr2`` -- attr2 doc", - " Methods:", - " ``method3()`` -- method3 doc", - " ``method4()`` -- no documentation", - " ``method5()`` -- method5 doc", - "", - ]) - - class IBase(Interface): - def method1(): - pass - def method2(): - pass - - class IDerived(IBase): - "IDerived doc" - attr1 = Attribute('attr1') - attr2 = Attribute('attr2', 'attr2 doc') - - def method3(): - "method3 doc" - def method4(): - pass - def method5(): - "method5 doc" - - self.assertEqual(self._callFUT(IDerived), EXPECTED) - - -class Test__justify_and_indent(unittest.TestCase): - - def _callFUT(self, text, level, **kw): - from zope.interface.document import _justify_and_indent - return _justify_and_indent(text, level, **kw) - - def test_simple_level_0(self): - LINES = ['Three blind mice', 'See how they run'] - text = '\n'.join(LINES) - self.assertEqual(self._callFUT(text, 0), text) - - def test_simple_level_1(self): - LINES = ['Three blind mice', 'See how they run'] - text = '\n'.join(LINES) - self.assertEqual(self._callFUT(text, 1), - '\n'.join([' ' + line for line in LINES])) - - def test_simple_level_2(self): - LINES = ['Three blind mice', 'See how they run'] - text = '\n'.join(LINES) - self.assertEqual(self._callFUT(text, 1), - '\n'.join([' ' + line for line in LINES])) - - def test_simple_w_CRLF(self): - LINES = ['Three blind mice', 'See how they run'] - text = '\r\n'.join(LINES) - self.assertEqual(self._callFUT(text, 1), - '\n'.join([' ' + line for line in LINES])) - - def test_with_munge(self): - TEXT = ("This is a piece of text longer than 15 characters, \n" - "and split across multiple lines.") - EXPECTED = (" This is a piece\n" - " of text longer\n" - " than 15 characters,\n" - " and split across\n" - " multiple lines.\n" - " ") - self.assertEqual(self._callFUT(TEXT, 1, munge=1, width=15), EXPECTED) diff --git a/lib/zope/interface/tests/test_element.py b/lib/zope/interface/tests/test_element.py deleted file mode 100644 index eb003cd..0000000 --- a/lib/zope/interface/tests/test_element.py +++ /dev/null @@ -1,31 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Test Element meta-class. -""" - -import unittest -from zope.interface.interface import Element - -class TestElement(unittest.TestCase): - - def test_taggedValues(self): - """Test that we can update tagged values of more than one element - """ - - e1 = Element("foo") - e2 = Element("bar") - e1.setTaggedValue("x", 1) - e2.setTaggedValue("x", 2) - self.assertEqual(e1.getTaggedValue("x"), 1) - self.assertEqual(e2.getTaggedValue("x"), 2) diff --git a/lib/zope/interface/tests/test_exceptions.py b/lib/zope/interface/tests/test_exceptions.py deleted file mode 100644 index ae73f9c..0000000 --- a/lib/zope/interface/tests/test_exceptions.py +++ /dev/null @@ -1,72 +0,0 @@ -############################################################################## -# -# Copyright (c) 2010 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -""" zope.interface.exceptions unit tests -""" -import unittest - -def _makeIface(): - from zope.interface import Interface - class IDummy(Interface): - pass - return IDummy - -class DoesNotImplementTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.exceptions import DoesNotImplement - return DoesNotImplement - - def _makeOne(self): - iface = _makeIface() - return self._getTargetClass()(iface) - - def test___str__(self): - dni = self._makeOne() - # XXX The trailing newlines and blank spaces are a stupid artifact. - self.assertEqual(str(dni), - 'An object does not implement interface <InterfaceClass ' - 'zope.interface.tests.test_exceptions.IDummy>\n\n ') - -class BrokenImplementationTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.exceptions import BrokenImplementation - return BrokenImplementation - - def _makeOne(self, name='missing'): - iface = _makeIface() - return self._getTargetClass()(iface, name) - - def test___str__(self): - dni = self._makeOne() - # XXX The trailing newlines and blank spaces are a stupid artifact. - self.assertEqual(str(dni), - 'An object has failed to implement interface <InterfaceClass ' - 'zope.interface.tests.test_exceptions.IDummy>\n\n' - ' The missing attribute was not provided.\n ') - -class BrokenMethodImplementationTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.exceptions import BrokenMethodImplementation - return BrokenMethodImplementation - - def _makeOne(self, method='aMethod', mess='I said so'): - return self._getTargetClass()(method, mess) - - def test___str__(self): - dni = self._makeOne() - self.assertEqual(str(dni), - 'The implementation of aMethod violates its contract\n' - ' because I said so.\n ') diff --git a/lib/zope/interface/tests/test_interface.py b/lib/zope/interface/tests/test_interface.py deleted file mode 100644 index 2bb3d1c..0000000 --- a/lib/zope/interface/tests/test_interface.py +++ /dev/null @@ -1,2123 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Test Interface implementation -""" -# pylint:disable=protected-access -import unittest - -from zope.interface._compat import _skip_under_py3k - -_marker = object() - - -class Test_invariant(unittest.TestCase): - - def test_w_single(self): - from zope.interface.interface import invariant - from zope.interface.interface import TAGGED_DATA - - def _check(*args, **kw): - raise NotImplementedError() - - class Foo(object): - invariant(_check) - - self.assertEqual(getattr(Foo, TAGGED_DATA, None), - {'invariants': [_check]}) - - def test_w_multiple(self): - from zope.interface.interface import invariant - from zope.interface.interface import TAGGED_DATA - - def _check(*args, **kw): - raise NotImplementedError() - - def _another_check(*args, **kw): - raise NotImplementedError() - - class Foo(object): - invariant(_check) - invariant(_another_check) - - self.assertEqual(getattr(Foo, TAGGED_DATA, None), - {'invariants': [_check, _another_check]}) - - -class Test_taggedValue(unittest.TestCase): - - def test_w_single(self): - from zope.interface.interface import taggedValue - from zope.interface.interface import TAGGED_DATA - - class Foo(object): - taggedValue('bar', ['baz']) - - self.assertEqual(getattr(Foo, TAGGED_DATA, None), - {'bar': ['baz']}) - - def test_w_multiple(self): - from zope.interface.interface import taggedValue - from zope.interface.interface import TAGGED_DATA - - class Foo(object): - taggedValue('bar', ['baz']) - taggedValue('qux', 'spam') - - self.assertEqual(getattr(Foo, TAGGED_DATA, None), - {'bar': ['baz'], 'qux': 'spam'}) - - def test_w_multiple_overwriting(self): - from zope.interface.interface import taggedValue - from zope.interface.interface import TAGGED_DATA - - class Foo(object): - taggedValue('bar', ['baz']) - taggedValue('qux', 'spam') - taggedValue('bar', 'frob') - - self.assertEqual(getattr(Foo, TAGGED_DATA, None), - {'bar': 'frob', 'qux': 'spam'}) - - -class ElementTests(unittest.TestCase): - - DEFAULT_NAME = 'AnElement' - - def _getTargetClass(self): - from zope.interface.interface import Element - return Element - - def _makeOne(self, name=None): - if name is None: - name = self.DEFAULT_NAME - return self._getTargetClass()(name) - - def test_ctor_defaults(self): - element = self._makeOne() - self.assertEqual(element.__name__, self.DEFAULT_NAME) - self.assertEqual(element.getName(), self.DEFAULT_NAME) - self.assertEqual(element.__doc__, '') - self.assertEqual(element.getDoc(), '') - self.assertEqual(list(element.getTaggedValueTags()), []) - - def test_ctor_no_doc_space_in_name(self): - element = self._makeOne('An Element') - self.assertEqual(element.__name__, None) - self.assertEqual(element.__doc__, 'An Element') - - def test_getTaggedValue_miss(self): - element = self._makeOne() - self.assertRaises(KeyError, element.getTaggedValue, 'nonesuch') - - def test_queryTaggedValue_miss(self): - element = self._makeOne() - self.assertEqual(element.queryTaggedValue('nonesuch'), None) - - def test_queryTaggedValue_miss_w_default(self): - element = self._makeOne() - self.assertEqual(element.queryTaggedValue('nonesuch', 'bar'), 'bar') - - def test_setTaggedValue(self): - element = self._makeOne() - element.setTaggedValue('foo', 'bar') - self.assertEqual(list(element.getTaggedValueTags()), ['foo']) - self.assertEqual(element.getTaggedValue('foo'), 'bar') - self.assertEqual(element.queryTaggedValue('foo'), 'bar') - - -class SpecificationBasePyTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.interface import SpecificationBasePy - return SpecificationBasePy - - def _makeOne(self): - return self._getTargetClass()() - - def test_providedBy_miss(self): - from zope.interface import interface - from zope.interface.declarations import _empty - sb = self._makeOne() - def _providedBy(obj): - return _empty - with _Monkey(interface, providedBy=_providedBy): - self.assertFalse(sb.providedBy(object())) - - def test_providedBy_hit(self): - from zope.interface import interface - sb = self._makeOne() - class _Decl(object): - _implied = {sb: {},} - def _providedBy(obj): - return _Decl() - with _Monkey(interface, providedBy=_providedBy): - self.assertTrue(sb.providedBy(object())) - - def test_implementedBy_miss(self): - from zope.interface import interface - from zope.interface.declarations import _empty - sb = self._makeOne() - def _implementedBy(obj): - return _empty - with _Monkey(interface, implementedBy=_implementedBy): - self.assertFalse(sb.implementedBy(object())) - - def test_implementedBy_hit(self): - from zope.interface import interface - sb = self._makeOne() - class _Decl(object): - _implied = {sb: {},} - def _implementedBy(obj): - return _Decl() - with _Monkey(interface, implementedBy=_implementedBy): - self.assertTrue(sb.implementedBy(object())) - - def test_isOrExtends_miss(self): - sb = self._makeOne() - sb._implied = {} # not defined by SpecificationBasePy - self.assertFalse(sb.isOrExtends(object())) - - def test_isOrExtends_hit(self): - sb = self._makeOne() - testing = object() - sb._implied = {testing: {}} # not defined by SpecificationBasePy - self.assertTrue(sb(testing)) - - def test___call___miss(self): - sb = self._makeOne() - sb._implied = {} # not defined by SpecificationBasePy - self.assertFalse(sb.isOrExtends(object())) - - def test___call___hit(self): - sb = self._makeOne() - testing = object() - sb._implied = {testing: {}} # not defined by SpecificationBasePy - self.assertTrue(sb(testing)) - - -class SpecificationBaseTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.interface import SpecificationBase - return SpecificationBase - - def test_optimizations(self): - from zope.interface.interface import SpecificationBasePy - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), SpecificationBasePy) - else: - self.assertIsNot(self._getTargetClass(), SpecificationBasePy) - - -class InterfaceBasePyTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.interface import InterfaceBasePy - return InterfaceBasePy - - def _makeOne(self, object_should_provide): - class IB(self._getTargetClass()): - def _call_conform(self, conform): - return conform(self) - def providedBy(self, obj): - return object_should_provide - return IB() - - def test___call___w___conform___returning_value(self): - ib = self._makeOne(False) - conformed = object() - class _Adapted(object): - def __conform__(self, iface): - return conformed - self.assertTrue(ib(_Adapted()) is conformed) - - def test___call___w___conform___miss_ob_provides(self): - ib = self._makeOne(True) - class _Adapted(object): - def __conform__(self, iface): - return None - adapted = _Adapted() - self.assertTrue(ib(adapted) is adapted) - - def test___call___wo___conform___ob_no_provides_w_alternate(self): - ib = self._makeOne(False) - adapted = object() - alternate = object() - self.assertTrue(ib(adapted, alternate) is alternate) - - def test___call___w___conform___ob_no_provides_wo_alternate(self): - ib = self._makeOne(False) - adapted = object() - self.assertRaises(TypeError, ib, adapted) - - def test___adapt___ob_provides(self): - ib = self._makeOne(True) - adapted = object() - self.assertTrue(ib.__adapt__(adapted) is adapted) - - def test___adapt___ob_no_provides_uses_hooks(self): - from zope.interface import interface - ib = self._makeOne(False) - adapted = object() - _missed = [] - def _hook_miss(iface, obj): - _missed.append((iface, obj)) - return None - def _hook_hit(iface, obj): - return obj - with _Monkey(interface, adapter_hooks=[_hook_miss, _hook_hit]): - self.assertTrue(ib.__adapt__(adapted) is adapted) - self.assertEqual(_missed, [(ib, adapted)]) - - -class InterfaceBaseTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.interface import InterfaceBase - return InterfaceBase - - def test_optimizations(self): - from zope.interface.interface import InterfaceBasePy - try: - import zope.interface._zope_interface_coptimizations - except ImportError: - self.assertIs(self._getTargetClass(), InterfaceBasePy) - else: - self.assertIsNot(self._getTargetClass(), InterfaceBasePy) - - -class SpecificationTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.interface import Specification - return Specification - - def _makeOne(self, bases=_marker): - if bases is _marker: - return self._getTargetClass()() - return self._getTargetClass()(bases) - - def test_ctor(self): - from zope.interface.interface import Interface - spec = self._makeOne() - self.assertEqual(spec.__bases__, ()) - self.assertEqual(len(spec._implied), 2) - self.assertTrue(spec in spec._implied) - self.assertTrue(Interface in spec._implied) - self.assertEqual(len(spec.dependents), 0) - - def test_subscribe_first_time(self): - spec = self._makeOne() - dep = DummyDependent() - spec.subscribe(dep) - self.assertEqual(len(spec.dependents), 1) - self.assertEqual(spec.dependents[dep], 1) - - def test_subscribe_again(self): - spec = self._makeOne() - dep = DummyDependent() - spec.subscribe(dep) - spec.subscribe(dep) - self.assertEqual(spec.dependents[dep], 2) - - def test_unsubscribe_miss(self): - spec = self._makeOne() - dep = DummyDependent() - self.assertRaises(KeyError, spec.unsubscribe, dep) - - def test_unsubscribe(self): - spec = self._makeOne() - dep = DummyDependent() - spec.subscribe(dep) - spec.subscribe(dep) - spec.unsubscribe(dep) - self.assertEqual(spec.dependents[dep], 1) - spec.unsubscribe(dep) - self.assertFalse(dep in spec.dependents) - - def test___setBases_subscribes_bases_and_notifies_dependents(self): - from zope.interface.interface import Interface - spec = self._makeOne() - dep = DummyDependent() - spec.subscribe(dep) - class I(Interface): - pass - class J(Interface): - pass - spec.__bases__ = (I,) - self.assertEqual(dep._changed, [spec]) - self.assertEqual(I.dependents[spec], 1) - spec.__bases__ = (J,) - self.assertEqual(I.dependents.get(spec), None) - self.assertEqual(J.dependents[spec], 1) - - def test_changed_clears_volatiles_and_implied(self): - from zope.interface.interface import Interface - class I(Interface): - pass - spec = self._makeOne() - spec._v_attrs = 'Foo' - spec._implied[I] = () - spec.changed(spec) - self.assertTrue(getattr(spec, '_v_attrs', self) is self) - self.assertFalse(I in spec._implied) - - def test_interfaces_skips_already_seen(self): - from zope.interface.interface import Interface - class IFoo(Interface): - pass - spec = self._makeOne([IFoo, IFoo]) - self.assertEqual(list(spec.interfaces()), [IFoo]) - - def test_extends_strict_wo_self(self): - from zope.interface.interface import Interface - class IFoo(Interface): - pass - spec = self._makeOne(IFoo) - self.assertFalse(spec.extends(IFoo, strict=True)) - - def test_extends_strict_w_self(self): - spec = self._makeOne() - self.assertFalse(spec.extends(spec, strict=True)) - - def test_extends_non_strict_w_self(self): - spec = self._makeOne() - self.assertTrue(spec.extends(spec, strict=False)) - - def test_get_hit_w__v_attrs(self): - spec = self._makeOne() - foo = object() - spec._v_attrs = {'foo': foo} - self.assertTrue(spec.get('foo') is foo) - - def test_get_hit_from_base_wo__v_attrs(self): - from zope.interface.interface import Attribute - from zope.interface.interface import Interface - class IFoo(Interface): - foo = Attribute('foo') - class IBar(Interface): - bar = Attribute('bar') - spec = self._makeOne([IFoo, IBar]) - self.assertTrue(spec.get('foo') is IFoo.get('foo')) - self.assertTrue(spec.get('bar') is IBar.get('bar')) - -class InterfaceClassTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.interface import InterfaceClass - return InterfaceClass - - def _makeOne(self, name='ITest', bases=(), attrs=None, __doc__=None, - __module__=None): - return self._getTargetClass()(name, bases, attrs, __doc__, __module__) - - def test_ctor_defaults(self): - klass = self._getTargetClass() - inst = klass('ITesting') - self.assertEqual(inst.__name__, 'ITesting') - self.assertEqual(inst.__doc__, '') - self.assertEqual(inst.__bases__, ()) - self.assertEqual(inst.getBases(), ()) - - def test_ctor_bad_bases(self): - klass = self._getTargetClass() - self.assertRaises(TypeError, klass, 'ITesting', (object(),)) - - def test_ctor_w_attrs_attrib_methods(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - klass = self._getTargetClass() - inst = klass('ITesting', attrs=ATTRS) - self.assertEqual(inst.__name__, 'ITesting') - self.assertEqual(inst.__doc__, '') - self.assertEqual(inst.__bases__, ()) - self.assertEqual(inst.names(), ATTRS.keys()) - - def test_ctor_attrs_w___locals__(self): - ATTRS = {'__locals__': {}} - klass = self._getTargetClass() - inst = klass('ITesting', attrs=ATTRS) - self.assertEqual(inst.__name__, 'ITesting') - self.assertEqual(inst.__doc__, '') - self.assertEqual(inst.__bases__, ()) - self.assertEqual(inst.names(), ATTRS.keys()) - - def test_ctor_attrs_w___annotations__(self): - ATTRS = {'__annotations__': {}} - klass = self._getTargetClass() - inst = klass('ITesting', attrs=ATTRS) - self.assertEqual(inst.__name__, 'ITesting') - self.assertEqual(inst.__doc__, '') - self.assertEqual(inst.__bases__, ()) - self.assertEqual(inst.names(), ATTRS.keys()) - - def test_ctor_attrs_w__decorator_non_return(self): - from zope.interface.interface import _decorator_non_return - ATTRS = {'dropme': _decorator_non_return} - klass = self._getTargetClass() - inst = klass('ITesting', attrs=ATTRS) - self.assertEqual(inst.__name__, 'ITesting') - self.assertEqual(inst.__doc__, '') - self.assertEqual(inst.__bases__, ()) - self.assertEqual(list(inst.names()), []) - - def test_ctor_attrs_w_invalid_attr_type(self): - from zope.interface.exceptions import InvalidInterface - ATTRS = {'invalid': object()} - klass = self._getTargetClass() - self.assertRaises(InvalidInterface, klass, 'ITesting', attrs=ATTRS) - - def test_ctor_w_explicit___doc__(self): - ATTRS = {'__doc__': 'ATTR'} - klass = self._getTargetClass() - inst = klass('ITesting', attrs=ATTRS, __doc__='EXPLICIT') - self.assertEqual(inst.__doc__, 'EXPLICIT') - - def test_interfaces(self): - iface = self._makeOne() - self.assertEqual(list(iface.interfaces()), [iface]) - - def test_getBases(self): - iface = self._makeOne() - sub = self._makeOne('ISub', bases=(iface,)) - self.assertEqual(sub.getBases(), (iface,)) - - def test_isEqualOrExtendedBy_identity(self): - iface = self._makeOne() - self.assertTrue(iface.isEqualOrExtendedBy(iface)) - - def test_isEqualOrExtendedBy_subiface(self): - iface = self._makeOne() - sub = self._makeOne('ISub', bases=(iface,)) - self.assertTrue(iface.isEqualOrExtendedBy(sub)) - self.assertFalse(sub.isEqualOrExtendedBy(iface)) - - def test_isEqualOrExtendedBy_unrelated(self): - one = self._makeOne('One') - another = self._makeOne('Another') - self.assertFalse(one.isEqualOrExtendedBy(another)) - self.assertFalse(another.isEqualOrExtendedBy(one)) - - def test_names_w_all_False_ignores_bases(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - BASE_ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - DERIVED_ATTRS = {'baz': Attribute('Baz', ''), - } - base = self._makeOne('IBase', attrs=BASE_ATTRS) - derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) - self.assertEqual(sorted(derived.names(all=False)), ['baz']) - - def test_names_w_all_True_no_bases(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - one = self._makeOne(attrs=ATTRS) - self.assertEqual(sorted(one.names(all=True)), ['bar', 'foo']) - - def test_names_w_all_True_w_bases_simple(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - BASE_ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - DERIVED_ATTRS = {'baz': Attribute('Baz', ''), - } - base = self._makeOne('IBase', attrs=BASE_ATTRS) - derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) - self.assertEqual(sorted(derived.names(all=True)), ['bar', 'baz', 'foo']) - - def test_names_w_all_True_bases_w_same_names(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - def _foo(): - """DOCSTRING""" - BASE_ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - DERIVED_ATTRS = {'foo': fromFunction(_foo), - 'baz': Attribute('Baz', ''), - } - base = self._makeOne('IBase', attrs=BASE_ATTRS) - derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) - self.assertEqual(sorted(derived.names(all=True)), ['bar', 'baz', 'foo']) - - def test___iter__(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - def _foo(): - """DOCSTRING""" - BASE_ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - DERIVED_ATTRS = {'foo': fromFunction(_foo), - 'baz': Attribute('Baz', ''), - } - base = self._makeOne('IBase', attrs=BASE_ATTRS) - derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) - self.assertEqual(sorted(derived), ['bar', 'baz', 'foo']) - - def test_namesAndDescriptions_w_all_False_ignores_bases(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - BASE_ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - DERIVED_ATTRS = {'baz': Attribute('Baz', ''), - } - base = self._makeOne('IBase', attrs=BASE_ATTRS) - derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) - self.assertEqual(sorted(derived.namesAndDescriptions(all=False)), - [('baz', DERIVED_ATTRS['baz']), - ]) - - def test_namesAndDescriptions_w_all_True_no_bases(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - one = self._makeOne(attrs=ATTRS) - self.assertEqual(sorted(one.namesAndDescriptions(all=False)), - [('bar', ATTRS['bar']), - ('foo', ATTRS['foo']), - ]) - - def test_namesAndDescriptions_w_all_True_simple(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - BASE_ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - DERIVED_ATTRS = {'baz': Attribute('Baz', ''), - } - base = self._makeOne('IBase', attrs=BASE_ATTRS) - derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) - self.assertEqual(sorted(derived.namesAndDescriptions(all=True)), - [('bar', BASE_ATTRS['bar']), - ('baz', DERIVED_ATTRS['baz']), - ('foo', BASE_ATTRS['foo']), - ]) - - def test_namesAndDescriptions_w_all_True_bases_w_same_names(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - def _foo(): - """DOCSTRING""" - BASE_ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - DERIVED_ATTRS = {'foo': fromFunction(_foo), - 'baz': Attribute('Baz', ''), - } - base = self._makeOne('IBase', attrs=BASE_ATTRS) - derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) - self.assertEqual(sorted(derived.namesAndDescriptions(all=True)), - [('bar', BASE_ATTRS['bar']), - ('baz', DERIVED_ATTRS['baz']), - ('foo', DERIVED_ATTRS['foo']), - ]) - - def test_getDescriptionFor_miss(self): - one = self._makeOne() - self.assertRaises(KeyError, one.getDescriptionFor, 'nonesuch') - - def test_getDescriptionFor_hit(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - one = self._makeOne(attrs=ATTRS) - self.assertEqual(one.getDescriptionFor('foo'), ATTRS['foo']) - self.assertEqual(one.getDescriptionFor('bar'), ATTRS['bar']) - - def test___getitem___miss(self): - one = self._makeOne() - def _test(): - return one['nonesuch'] - self.assertRaises(KeyError, _test) - - def test___getitem___hit(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - one = self._makeOne(attrs=ATTRS) - self.assertEqual(one['foo'], ATTRS['foo']) - self.assertEqual(one['bar'], ATTRS['bar']) - - def test___contains___miss(self): - one = self._makeOne() - self.assertFalse('nonesuch' in one) - - def test___contains___hit(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - one = self._makeOne(attrs=ATTRS) - self.assertTrue('foo' in one) - self.assertTrue('bar' in one) - - def test_direct_miss(self): - one = self._makeOne() - self.assertEqual(one.direct('nonesuch'), None) - - def test_direct_hit_local_miss_bases(self): - from zope.interface.interface import Attribute - from zope.interface.interface import fromFunction - def _bar(): - """DOCSTRING""" - def _foo(): - """DOCSTRING""" - BASE_ATTRS = {'foo': Attribute('Foo', ''), - 'bar': fromFunction(_bar), - } - DERIVED_ATTRS = {'foo': fromFunction(_foo), - 'baz': Attribute('Baz', ''), - } - base = self._makeOne('IBase', attrs=BASE_ATTRS) - derived = self._makeOne('IDerived', bases=(base,), attrs=DERIVED_ATTRS) - self.assertEqual(derived.direct('foo'), DERIVED_ATTRS['foo']) - self.assertEqual(derived.direct('baz'), DERIVED_ATTRS['baz']) - self.assertEqual(derived.direct('bar'), None) - - def test_queryDescriptionFor_miss(self): - iface = self._makeOne() - self.assertEqual(iface.queryDescriptionFor('nonesuch'), None) - - def test_queryDescriptionFor_hit(self): - from zope.interface import Attribute - ATTRS = {'attr': Attribute('Title', 'Description')} - iface = self._makeOne(attrs=ATTRS) - self.assertEqual(iface.queryDescriptionFor('attr'), ATTRS['attr']) - - def test_validateInvariants_pass(self): - _called_with = [] - def _passable(*args, **kw): - _called_with.append((args, kw)) - return True - iface = self._makeOne() - obj = object() - iface.setTaggedValue('invariants', [_passable]) - self.assertEqual(iface.validateInvariants(obj), None) - self.assertEqual(_called_with, [((obj,), {})]) - - def test_validateInvariants_fail_wo_errors_passed(self): - from zope.interface.exceptions import Invalid - _passable_called_with = [] - def _passable(*args, **kw): - _passable_called_with.append((args, kw)) - return True - _fail_called_with = [] - def _fail(*args, **kw): - _fail_called_with.append((args, kw)) - raise Invalid - iface = self._makeOne() - obj = object() - iface.setTaggedValue('invariants', [_passable, _fail]) - self.assertRaises(Invalid, iface.validateInvariants, obj) - self.assertEqual(_passable_called_with, [((obj,), {})]) - self.assertEqual(_fail_called_with, [((obj,), {})]) - - def test_validateInvariants_fail_w_errors_passed(self): - from zope.interface.exceptions import Invalid - _errors = [] - _fail_called_with = [] - def _fail(*args, **kw): - _fail_called_with.append((args, kw)) - raise Invalid - iface = self._makeOne() - obj = object() - iface.setTaggedValue('invariants', [_fail]) - self.assertRaises(Invalid, iface.validateInvariants, obj, _errors) - self.assertEqual(_fail_called_with, [((obj,), {})]) - self.assertEqual(len(_errors), 1) - self.assertTrue(isinstance(_errors[0], Invalid)) - - def test_validateInvariants_fail_in_base_wo_errors_passed(self): - from zope.interface.exceptions import Invalid - _passable_called_with = [] - def _passable(*args, **kw): - _passable_called_with.append((args, kw)) - return True - _fail_called_with = [] - def _fail(*args, **kw): - _fail_called_with.append((args, kw)) - raise Invalid - base = self._makeOne('IBase') - derived = self._makeOne('IDerived', (base,)) - obj = object() - base.setTaggedValue('invariants', [_fail]) - derived.setTaggedValue('invariants', [_passable]) - self.assertRaises(Invalid, derived.validateInvariants, obj) - self.assertEqual(_passable_called_with, [((obj,), {})]) - self.assertEqual(_fail_called_with, [((obj,), {})]) - - def test_validateInvariants_fail_in_base_w_errors_passed(self): - from zope.interface.exceptions import Invalid - _errors = [] - _passable_called_with = [] - def _passable(*args, **kw): - _passable_called_with.append((args, kw)) - return True - _fail_called_with = [] - def _fail(*args, **kw): - _fail_called_with.append((args, kw)) - raise Invalid - base = self._makeOne('IBase') - derived = self._makeOne('IDerived', (base,)) - obj = object() - base.setTaggedValue('invariants', [_fail]) - derived.setTaggedValue('invariants', [_passable]) - self.assertRaises(Invalid, derived.validateInvariants, obj, _errors) - self.assertEqual(_passable_called_with, [((obj,), {})]) - self.assertEqual(_fail_called_with, [((obj,), {})]) - self.assertEqual(len(_errors), 1) - self.assertTrue(isinstance(_errors[0], Invalid)) - - def test___reduce__(self): - iface = self._makeOne('PickleMe') - self.assertEqual(iface.__reduce__(), 'PickleMe') - - def test___hash___normal(self): - iface = self._makeOne('HashMe') - self.assertEqual(hash(iface), - hash((('HashMe', - 'zope.interface.tests.test_interface')))) - - def test___hash___missing_required_attrs(self): - import warnings - from warnings import catch_warnings - - class Derived(self._getTargetClass()): - def __init__(self): - pass # Don't call base class. - derived = Derived() - with catch_warnings(record=True) as warned: - warnings.simplefilter('always') # see LP #825249 - self.assertEqual(hash(derived), 1) - self.assertEqual(len(warned), 1) - self.assertTrue(warned[0].category is UserWarning) - self.assertEqual(str(warned[0].message), - 'Hashing uninitialized InterfaceClass instance') - - def test_comparison_with_None(self): - iface = self._makeOne() - self.assertTrue(iface < None) - self.assertTrue(iface <= None) - self.assertFalse(iface == None) - self.assertTrue(iface != None) - self.assertFalse(iface >= None) - self.assertFalse(iface > None) - - self.assertFalse(None < iface) - self.assertFalse(None <= iface) - self.assertFalse(None == iface) - self.assertTrue(None != iface) - self.assertTrue(None >= iface) - self.assertTrue(None > iface) - - def test_comparison_with_same_instance(self): - iface = self._makeOne() - - self.assertFalse(iface < iface) - self.assertTrue(iface <= iface) - self.assertTrue(iface == iface) - self.assertFalse(iface != iface) - self.assertTrue(iface >= iface) - self.assertFalse(iface > iface) - - def test_comparison_with_same_named_instance_in_other_module(self): - - one = self._makeOne('IName', __module__='zope.interface.tests.one') - other = self._makeOne('IName', __module__='zope.interface.tests.other') - - self.assertTrue(one < other) - self.assertFalse(other < one) - self.assertTrue(one <= other) - self.assertFalse(other <= one) - self.assertFalse(one == other) - self.assertFalse(other == one) - self.assertTrue(one != other) - self.assertTrue(other != one) - self.assertFalse(one >= other) - self.assertTrue(other >= one) - self.assertFalse(one > other) - self.assertTrue(other > one) - - -class InterfaceTests(unittest.TestCase): - - def test_attributes_link_to_interface(self): - from zope.interface import Interface - from zope.interface import Attribute - - class I1(Interface): - attr = Attribute("My attr") - - self.assertTrue(I1['attr'].interface is I1) - - def test_methods_link_to_interface(self): - from zope.interface import Interface - - class I1(Interface): - - def method(foo, bar, bingo): - "A method" - - self.assertTrue(I1['method'].interface is I1) - - def test_classImplements_simple(self): - from zope.interface import Interface - from zope.interface import implementedBy - from zope.interface import providedBy - - class ICurrent(Interface): - def method1(a, b): - pass - def method2(a, b): - pass - - class IOther(Interface): - pass - - class Current(object): - __implemented__ = ICurrent - def method1(self, a, b): - raise NotImplementedError() - def method2(self, a, b): - raise NotImplementedError() - - current = Current() - - self.assertTrue(ICurrent.implementedBy(Current)) - self.assertFalse(IOther.implementedBy(Current)) - self.assertTrue(ICurrent in implementedBy(Current)) - self.assertFalse(IOther in implementedBy(Current)) - self.assertTrue(ICurrent in providedBy(current)) - self.assertFalse(IOther in providedBy(current)) - - def test_classImplements_base_not_derived(self): - from zope.interface import Interface - from zope.interface import implementedBy - from zope.interface import providedBy - class IBase(Interface): - def method(): - pass - class IDerived(IBase): - pass - class Current(): - __implemented__ = IBase - def method(self): - raise NotImplementedError() - current = Current() - - self.assertTrue(IBase.implementedBy(Current)) - self.assertFalse(IDerived.implementedBy(Current)) - self.assertTrue(IBase in implementedBy(Current)) - self.assertFalse(IDerived in implementedBy(Current)) - self.assertTrue(IBase in providedBy(current)) - self.assertFalse(IDerived in providedBy(current)) - - def test_classImplements_base_and_derived(self): - from zope.interface import Interface - from zope.interface import implementedBy - from zope.interface import providedBy - - class IBase(Interface): - def method(): - pass - - class IDerived(IBase): - pass - - class Current(object): - __implemented__ = IDerived - def method(self): - raise NotImplementedError() - - current = Current() - - self.assertTrue(IBase.implementedBy(Current)) - self.assertTrue(IDerived.implementedBy(Current)) - self.assertFalse(IBase in implementedBy(Current)) - self.assertTrue(IBase in implementedBy(Current).flattened()) - self.assertTrue(IDerived in implementedBy(Current)) - self.assertFalse(IBase in providedBy(current)) - self.assertTrue(IBase in providedBy(current).flattened()) - self.assertTrue(IDerived in providedBy(current)) - - def test_classImplements_multiple(self): - from zope.interface import Interface - from zope.interface import implementedBy - from zope.interface import providedBy - - class ILeft(Interface): - def method(): - pass - - class IRight(ILeft): - pass - - class Left(object): - __implemented__ = ILeft - - def method(self): - raise NotImplementedError() - - class Right(object): - __implemented__ = IRight - - class Ambi(Left, Right): - pass - - ambi = Ambi() - - self.assertTrue(ILeft.implementedBy(Ambi)) - self.assertTrue(IRight.implementedBy(Ambi)) - self.assertTrue(ILeft in implementedBy(Ambi)) - self.assertTrue(IRight in implementedBy(Ambi)) - self.assertTrue(ILeft in providedBy(ambi)) - self.assertTrue(IRight in providedBy(ambi)) - - def test_classImplements_multiple_w_explict_implements(self): - from zope.interface import Interface - from zope.interface import implementedBy - from zope.interface import providedBy - - class ILeft(Interface): - - def method(): - pass - - class IRight(ILeft): - pass - - class IOther(Interface): - pass - - class Left(): - __implemented__ = ILeft - - def method(self): - raise NotImplementedError() - - class Right(object): - __implemented__ = IRight - - class Other(object): - __implemented__ = IOther - - class Mixed(Left, Right): - __implemented__ = Left.__implemented__, Other.__implemented__ - - mixed = Mixed() - - self.assertTrue(ILeft.implementedBy(Mixed)) - self.assertFalse(IRight.implementedBy(Mixed)) - self.assertTrue(IOther.implementedBy(Mixed)) - self.assertTrue(ILeft in implementedBy(Mixed)) - self.assertFalse(IRight in implementedBy(Mixed)) - self.assertTrue(IOther in implementedBy(Mixed)) - self.assertTrue(ILeft in providedBy(mixed)) - self.assertFalse(IRight in providedBy(mixed)) - self.assertTrue(IOther in providedBy(mixed)) - - def testInterfaceExtendsInterface(self): - from zope.interface import Interface - - new = Interface.__class__ - FunInterface = new('FunInterface') - BarInterface = new('BarInterface', [FunInterface]) - BobInterface = new('BobInterface') - BazInterface = new('BazInterface', [BobInterface, BarInterface]) - - self.assertTrue(BazInterface.extends(BobInterface)) - self.assertTrue(BazInterface.extends(BarInterface)) - self.assertTrue(BazInterface.extends(FunInterface)) - self.assertFalse(BobInterface.extends(FunInterface)) - self.assertFalse(BobInterface.extends(BarInterface)) - self.assertTrue(BarInterface.extends(FunInterface)) - self.assertFalse(BarInterface.extends(BazInterface)) - - def test_verifyClass(self): - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface.verify import verifyClass - - - class ICheckMe(Interface): - attr = Attribute(u'My attr') - - def method(): - "A method" - - class CheckMe(object): - __implemented__ = ICheckMe - attr = 'value' - - def method(self): - raise NotImplementedError() - - self.assertTrue(verifyClass(ICheckMe, CheckMe)) - - def test_verifyObject(self): - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface.verify import verifyObject - - - class ICheckMe(Interface): - attr = Attribute(u'My attr') - - def method(): - "A method" - - class CheckMe(object): - __implemented__ = ICheckMe - attr = 'value' - - def method(self): - raise NotImplementedError() - - check_me = CheckMe() - - self.assertTrue(verifyObject(ICheckMe, check_me)) - - def test_interface_object_provides_Interface(self): - from zope.interface import Interface - - class AnInterface(Interface): - pass - - self.assertTrue(Interface.providedBy(AnInterface)) - - def test_names_simple(self): - from zope.interface import Attribute - from zope.interface import Interface - - - class ISimple(Interface): - attr = Attribute(u'My attr') - - def method(): - pass - - self.assertEqual(sorted(ISimple.names()), ['attr', 'method']) - - def test_names_derived(self): - from zope.interface import Attribute - from zope.interface import Interface - - - class IBase(Interface): - attr = Attribute(u'My attr') - - def method(): - pass - - class IDerived(IBase): - attr2 = Attribute(u'My attr2') - - def method(): - pass - - def method2(): - pass - - self.assertEqual(sorted(IDerived.names()), - ['attr2', 'method', 'method2']) - self.assertEqual(sorted(IDerived.names(all=True)), - ['attr', 'attr2', 'method', 'method2']) - - def test_namesAndDescriptions_simple(self): - from zope.interface import Attribute - from zope.interface.interface import Method - from zope.interface import Interface - - - class ISimple(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - name_values = sorted(ISimple.namesAndDescriptions()) - - self.assertEqual(len(name_values), 2) - self.assertEqual(name_values[0][0], 'attr') - self.assertTrue(isinstance(name_values[0][1], Attribute)) - self.assertEqual(name_values[0][1].__name__, 'attr') - self.assertEqual(name_values[0][1].__doc__, 'My attr') - self.assertEqual(name_values[1][0], 'method') - self.assertTrue(isinstance(name_values[1][1], Method)) - self.assertEqual(name_values[1][1].__name__, 'method') - self.assertEqual(name_values[1][1].__doc__, 'My method') - - def test_namesAndDescriptions_derived(self): - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface.interface import Method - - - class IBase(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - class IDerived(IBase): - attr2 = Attribute(u'My attr2') - - def method(): - "My method, overridden" - - def method2(): - "My method2" - - name_values = sorted(IDerived.namesAndDescriptions()) - - self.assertEqual(len(name_values), 3) - self.assertEqual(name_values[0][0], 'attr2') - self.assertTrue(isinstance(name_values[0][1], Attribute)) - self.assertEqual(name_values[0][1].__name__, 'attr2') - self.assertEqual(name_values[0][1].__doc__, 'My attr2') - self.assertEqual(name_values[1][0], 'method') - self.assertTrue(isinstance(name_values[1][1], Method)) - self.assertEqual(name_values[1][1].__name__, 'method') - self.assertEqual(name_values[1][1].__doc__, 'My method, overridden') - self.assertEqual(name_values[2][0], 'method2') - self.assertTrue(isinstance(name_values[2][1], Method)) - self.assertEqual(name_values[2][1].__name__, 'method2') - self.assertEqual(name_values[2][1].__doc__, 'My method2') - - name_values = sorted(IDerived.namesAndDescriptions(all=True)) - - self.assertEqual(len(name_values), 4) - self.assertEqual(name_values[0][0], 'attr') - self.assertTrue(isinstance(name_values[0][1], Attribute)) - self.assertEqual(name_values[0][1].__name__, 'attr') - self.assertEqual(name_values[0][1].__doc__, 'My attr') - self.assertEqual(name_values[1][0], 'attr2') - self.assertTrue(isinstance(name_values[1][1], Attribute)) - self.assertEqual(name_values[1][1].__name__, 'attr2') - self.assertEqual(name_values[1][1].__doc__, 'My attr2') - self.assertEqual(name_values[2][0], 'method') - self.assertTrue(isinstance(name_values[2][1], Method)) - self.assertEqual(name_values[2][1].__name__, 'method') - self.assertEqual(name_values[2][1].__doc__, 'My method, overridden') - self.assertEqual(name_values[3][0], 'method2') - self.assertTrue(isinstance(name_values[3][1], Method)) - self.assertEqual(name_values[3][1].__name__, 'method2') - self.assertEqual(name_values[3][1].__doc__, 'My method2') - - def test_getDescriptionFor_nonesuch_no_default(self): - from zope.interface import Interface - - class IEmpty(Interface): - pass - - self.assertRaises(KeyError, IEmpty.getDescriptionFor, 'nonesuch') - - def test_getDescriptionFor_simple(self): - from zope.interface import Attribute - from zope.interface.interface import Method - from zope.interface import Interface - - - class ISimple(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - a_desc = ISimple.getDescriptionFor('attr') - self.assertTrue(isinstance(a_desc, Attribute)) - self.assertEqual(a_desc.__name__, 'attr') - self.assertEqual(a_desc.__doc__, 'My attr') - - m_desc = ISimple.getDescriptionFor('method') - self.assertTrue(isinstance(m_desc, Method)) - self.assertEqual(m_desc.__name__, 'method') - self.assertEqual(m_desc.__doc__, 'My method') - - def test_getDescriptionFor_derived(self): - from zope.interface import Attribute - from zope.interface.interface import Method - from zope.interface import Interface - - - class IBase(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - class IDerived(IBase): - attr2 = Attribute(u'My attr2') - - def method(): - "My method, overridden" - - def method2(): - "My method2" - - a_desc = IDerived.getDescriptionFor('attr') - self.assertTrue(isinstance(a_desc, Attribute)) - self.assertEqual(a_desc.__name__, 'attr') - self.assertEqual(a_desc.__doc__, 'My attr') - - m_desc = IDerived.getDescriptionFor('method') - self.assertTrue(isinstance(m_desc, Method)) - self.assertEqual(m_desc.__name__, 'method') - self.assertEqual(m_desc.__doc__, 'My method, overridden') - - a2_desc = IDerived.getDescriptionFor('attr2') - self.assertTrue(isinstance(a2_desc, Attribute)) - self.assertEqual(a2_desc.__name__, 'attr2') - self.assertEqual(a2_desc.__doc__, 'My attr2') - - m2_desc = IDerived.getDescriptionFor('method2') - self.assertTrue(isinstance(m2_desc, Method)) - self.assertEqual(m2_desc.__name__, 'method2') - self.assertEqual(m2_desc.__doc__, 'My method2') - - def test___getitem__nonesuch(self): - from zope.interface import Interface - - class IEmpty(Interface): - pass - - self.assertRaises(KeyError, IEmpty.__getitem__, 'nonesuch') - - def test___getitem__simple(self): - from zope.interface import Attribute - from zope.interface.interface import Method - from zope.interface import Interface - - - class ISimple(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - a_desc = ISimple['attr'] - self.assertTrue(isinstance(a_desc, Attribute)) - self.assertEqual(a_desc.__name__, 'attr') - self.assertEqual(a_desc.__doc__, 'My attr') - - m_desc = ISimple['method'] - self.assertTrue(isinstance(m_desc, Method)) - self.assertEqual(m_desc.__name__, 'method') - self.assertEqual(m_desc.__doc__, 'My method') - - def test___getitem___derived(self): - from zope.interface import Attribute - from zope.interface.interface import Method - from zope.interface import Interface - - - class IBase(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - class IDerived(IBase): - attr2 = Attribute(u'My attr2') - - def method(): - "My method, overridden" - - def method2(): - "My method2" - - a_desc = IDerived['attr'] - self.assertTrue(isinstance(a_desc, Attribute)) - self.assertEqual(a_desc.__name__, 'attr') - self.assertEqual(a_desc.__doc__, 'My attr') - - m_desc = IDerived['method'] - self.assertTrue(isinstance(m_desc, Method)) - self.assertEqual(m_desc.__name__, 'method') - self.assertEqual(m_desc.__doc__, 'My method, overridden') - - a2_desc = IDerived['attr2'] - self.assertTrue(isinstance(a2_desc, Attribute)) - self.assertEqual(a2_desc.__name__, 'attr2') - self.assertEqual(a2_desc.__doc__, 'My attr2') - - m2_desc = IDerived['method2'] - self.assertTrue(isinstance(m2_desc, Method)) - self.assertEqual(m2_desc.__name__, 'method2') - self.assertEqual(m2_desc.__doc__, 'My method2') - - def test___contains__nonesuch(self): - from zope.interface import Interface - - class IEmpty(Interface): - pass - - self.assertFalse('nonesuch' in IEmpty) - - def test___contains__simple(self): - from zope.interface import Attribute - from zope.interface import Interface - - - class ISimple(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - self.assertTrue('attr' in ISimple) - self.assertTrue('method' in ISimple) - - def test___contains__derived(self): - from zope.interface import Attribute - from zope.interface import Interface - - - class IBase(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - class IDerived(IBase): - attr2 = Attribute(u'My attr2') - - def method(): - "My method, overridden" - - def method2(): - "My method2" - - self.assertTrue('attr' in IDerived) - self.assertTrue('method' in IDerived) - self.assertTrue('attr2' in IDerived) - self.assertTrue('method2' in IDerived) - - def test___iter__empty(self): - from zope.interface import Interface - - class IEmpty(Interface): - pass - - self.assertEqual(list(IEmpty), []) - - def test___iter__simple(self): - from zope.interface import Attribute - from zope.interface import Interface - - - class ISimple(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - self.assertEqual(sorted(list(ISimple)), ['attr', 'method']) - - def test___iter__derived(self): - from zope.interface import Attribute - from zope.interface import Interface - - - class IBase(Interface): - attr = Attribute(u'My attr') - - def method(): - "My method" - - class IDerived(IBase): - attr2 = Attribute(u'My attr2') - - def method(): - "My method, overridden" - - def method2(): - "My method2" - - self.assertEqual(sorted(list(IDerived)), - ['attr', 'attr2', 'method', 'method2']) - - def test_function_attributes_become_tagged_values(self): - from zope.interface import Interface - - class ITagMe(Interface): - def method(): - pass - method.optional = 1 - - method = ITagMe['method'] - self.assertEqual(method.getTaggedValue('optional'), 1) - - def test___doc___non_element(self): - from zope.interface import Interface - - class IHaveADocString(Interface): - "xxx" - - self.assertEqual(IHaveADocString.__doc__, "xxx") - self.assertEqual(list(IHaveADocString), []) - - def test___doc___as_element(self): - from zope.interface import Attribute - from zope.interface import Interface - - class IHaveADocString(Interface): - "xxx" - __doc__ = Attribute('the doc') - - self.assertEqual(IHaveADocString.__doc__, "") - self.assertEqual(list(IHaveADocString), ['__doc__']) - - def _errorsEqual(self, has_invariant, error_len, error_msgs, iface): - from zope.interface.exceptions import Invalid - self.assertRaises(Invalid, iface.validateInvariants, has_invariant) - e = [] - try: - iface.validateInvariants(has_invariant, e) - self.fail("validateInvariants should always raise") - except Invalid as error: - self.assertEqual(error.args[0], e) - - self.assertEqual(len(e), error_len) - msgs = [error.args[0] for error in e] - msgs.sort() - for msg in msgs: - self.assertEqual(msg, error_msgs.pop(0)) - - def test_invariant_simple(self): - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface import directlyProvides - from zope.interface import invariant - - class IInvariant(Interface): - foo = Attribute('foo') - bar = Attribute('bar; must eval to Boolean True if foo does') - invariant(_ifFooThenBar) - - class HasInvariant(object): - pass - - # set up - has_invariant = HasInvariant() - directlyProvides(has_invariant, IInvariant) - - # the tests - self.assertEqual(IInvariant.getTaggedValue('invariants'), - [_ifFooThenBar]) - self.assertEqual(IInvariant.validateInvariants(has_invariant), None) - has_invariant.bar = 27 - self.assertEqual(IInvariant.validateInvariants(has_invariant), None) - has_invariant.foo = 42 - self.assertEqual(IInvariant.validateInvariants(has_invariant), None) - del has_invariant.bar - self._errorsEqual(has_invariant, 1, ['If Foo, then Bar!'], - IInvariant) - - def test_invariant_nested(self): - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface import directlyProvides - from zope.interface import invariant - - class IInvariant(Interface): - foo = Attribute('foo') - bar = Attribute('bar; must eval to Boolean True if foo does') - invariant(_ifFooThenBar) - - class ISubInvariant(IInvariant): - invariant(_barGreaterThanFoo) - - class HasInvariant(object): - pass - - # nested interfaces with invariants: - self.assertEqual(ISubInvariant.getTaggedValue('invariants'), - [_barGreaterThanFoo]) - has_invariant = HasInvariant() - directlyProvides(has_invariant, ISubInvariant) - has_invariant.foo = 42 - # even though the interface has changed, we should still only have one - # error. - self._errorsEqual(has_invariant, 1, ['If Foo, then Bar!'], - ISubInvariant) - # however, if we set foo to 0 (Boolean False) and bar to a negative - # number then we'll get the new error - has_invariant.foo = 2 - has_invariant.bar = 1 - self._errorsEqual(has_invariant, 1, - ['Please, Boo MUST be greater than Foo!'], - ISubInvariant) - # and if we set foo to a positive number and boo to 0, we'll - # get both errors! - has_invariant.foo = 1 - has_invariant.bar = 0 - self._errorsEqual(has_invariant, 2, - ['If Foo, then Bar!', - 'Please, Boo MUST be greater than Foo!'], - ISubInvariant) - # for a happy ending, we'll make the invariants happy - has_invariant.foo = 1 - has_invariant.bar = 2 - self.assertEqual(IInvariant.validateInvariants(has_invariant), None) - - def test_invariant_mutandis(self): - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface import directlyProvides - from zope.interface import invariant - - class IInvariant(Interface): - foo = Attribute('foo') - bar = Attribute('bar; must eval to Boolean True if foo does') - invariant(_ifFooThenBar) - - class HasInvariant(object): - pass - - # now we'll do two invariants on the same interface, - # just to make sure that a small - # multi-invariant interface is at least minimally tested. - has_invariant = HasInvariant() - directlyProvides(has_invariant, IInvariant) - has_invariant.foo = 42 - - # if you really need to mutate, then this would be the way to do it. - # Probably a bad idea, though. :-) - old_invariants = IInvariant.getTaggedValue('invariants') - invariants = old_invariants[:] - invariants.append(_barGreaterThanFoo) - IInvariant.setTaggedValue('invariants', invariants) - - # even though the interface has changed, we should still only have one - # error. - self._errorsEqual(has_invariant, 1, ['If Foo, then Bar!'], - IInvariant) - # however, if we set foo to 0 (Boolean False) and bar to a negative - # number then we'll get the new error - has_invariant.foo = 2 - has_invariant.bar = 1 - self._errorsEqual(has_invariant, 1, - ['Please, Boo MUST be greater than Foo!'], IInvariant) - # and if we set foo to a positive number and boo to 0, we'll - # get both errors! - has_invariant.foo = 1 - has_invariant.bar = 0 - self._errorsEqual(has_invariant, 2, - ['If Foo, then Bar!', - 'Please, Boo MUST be greater than Foo!'], - IInvariant) - # for another happy ending, we'll make the invariants happy again - has_invariant.foo = 1 - has_invariant.bar = 2 - self.assertEqual(IInvariant.validateInvariants(has_invariant), None) - # clean up - IInvariant.setTaggedValue('invariants', old_invariants) - - def test___doc___element(self): - from zope.interface import Interface - from zope.interface import Attribute - class I(Interface): - "xxx" - - self.assertEqual(I.__doc__, "xxx") - self.assertEqual(list(I), []) - - class I(Interface): - "xxx" - - __doc__ = Attribute('the doc') - - self.assertEqual(I.__doc__, "") - self.assertEqual(list(I), ['__doc__']) - - @_skip_under_py3k - def testIssue228(self): - # Test for http://collector.zope.org/Zope3-dev/228 - # Old style classes don't have a '__class__' attribute - # No old style classes in Python 3, so the test becomes moot. - import sys - - from zope.interface import Interface - - class I(Interface): - "xxx" - - class OldStyle: - __providedBy__ = None - - self.assertRaises(AttributeError, I.providedBy, OldStyle) - - def test_invariant_as_decorator(self): - from zope.interface import Interface - from zope.interface import Attribute - from zope.interface import implementer - from zope.interface import invariant - from zope.interface.exceptions import Invalid - - class IRange(Interface): - min = Attribute("Lower bound") - max = Attribute("Upper bound") - - @invariant - def range_invariant(ob): - if ob.max < ob.min: - raise Invalid('max < min') - - @implementer(IRange) - class Range(object): - - def __init__(self, min, max): - self.min, self.max = min, max - - IRange.validateInvariants(Range(1,2)) - IRange.validateInvariants(Range(1,1)) - try: - IRange.validateInvariants(Range(2,1)) - except Invalid as e: - self.assertEqual(str(e), 'max < min') - - def test_taggedValue(self): - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface import taggedValue - - class ITagged(Interface): - foo = Attribute('foo') - bar = Attribute('bar; must eval to Boolean True if foo does') - taggedValue('qux', 'Spam') - - class HasInvariant(object): - pass - - self.assertEqual(ITagged.getTaggedValue('qux'), 'Spam') - self.assertTrue('qux' in ITagged.getTaggedValueTags()) - - def test_description_cache_management(self): - # See https://bugs.launchpad.net/zope.interface/+bug/185974 - # There was a bug where the cache used by Specification.get() was not - # cleared when the bases were changed. - from zope.interface import Interface - from zope.interface import Attribute - - class I1(Interface): - a = Attribute('a') - - class I2(I1): - pass - - class I3(I2): - pass - - self.assertTrue(I3.get('a') is I1.get('a')) - - I2.__bases__ = (Interface,) - self.assertTrue(I3.get('a') is None) - - def test___call___defers_to___conform___(self): - from zope.interface import Interface - from zope.interface import implementer - - class I(Interface): - pass - - @implementer(I) - class C(object): - def __conform__(self, proto): - return 0 - - self.assertEqual(I(C()), 0) - - def test___call___object_implements(self): - from zope.interface import Interface - from zope.interface import implementer - - class I(Interface): - pass - - @implementer(I) - class C(object): - pass - - c = C() - self.assertTrue(I(c) is c) - - def test___call___miss_wo_alternate(self): - from zope.interface import Interface - - class I(Interface): - pass - - class C(object): - pass - - c = C() - self.assertRaises(TypeError, I, c) - - def test___call___miss_w_alternate(self): - from zope.interface import Interface - - class I(Interface): - pass - - class C(object): - pass - - c = C() - self.assertTrue(I(c, self) is self) - - def test___call___w_adapter_hook(self): - from zope.interface import Interface - from zope.interface.interface import adapter_hooks - old_hooks = adapter_hooks[:] - - def _miss(iface, obj): - pass - - def _hit(iface, obj): - return self - - class I(Interface): - pass - - class C(object): - pass - - c = C() - - old_adapter_hooks = adapter_hooks[:] - adapter_hooks[:] = [_miss, _hit] - try: - self.assertTrue(I(c) is self) - finally: - adapter_hooks[:] = old_adapter_hooks - - -class AttributeTests(ElementTests): - - DEFAULT_NAME = 'TestAttribute' - - def _getTargetClass(self): - from zope.interface.interface import Attribute - return Attribute - - -class MethodTests(AttributeTests): - - DEFAULT_NAME = 'TestMethod' - - def _getTargetClass(self): - from zope.interface.interface import Method - return Method - - def test_optional_as_property(self): - method = self._makeOne() - self.assertEqual(method.optional, {}) - method.optional = {'foo': 'bar'} - self.assertEqual(method.optional, {'foo': 'bar'}) - del method.optional - self.assertEqual(method.optional, {}) - - def test___call___raises_BrokenImplementation(self): - from zope.interface.exceptions import BrokenImplementation - method = self._makeOne() - try: - method() - except BrokenImplementation as e: - self.assertEqual(e.interface, None) - self.assertEqual(e.name, self.DEFAULT_NAME) - else: - self.fail('__call__ should raise BrokenImplementation') - - def test_getSignatureInfo_bare(self): - method = self._makeOne() - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), []) - self.assertEqual(list(info['required']), []) - self.assertEqual(info['optional'], {}) - self.assertEqual(info['varargs'], None) - self.assertEqual(info['kwargs'], None) - - def test_getSignatureString_bare(self): - method = self._makeOne() - self.assertEqual(method.getSignatureString(), '()') - - def test_getSignatureString_w_only_required(self): - method = self._makeOne() - method.positional = method.required = ['foo'] - self.assertEqual(method.getSignatureString(), '(foo)') - - def test_getSignatureString_w_optional(self): - method = self._makeOne() - method.positional = method.required = ['foo'] - method.optional = {'foo': 'bar'} - self.assertEqual(method.getSignatureString(), "(foo='bar')") - - def test_getSignatureString_w_varargs(self): - method = self._makeOne() - method.varargs = 'args' - self.assertEqual(method.getSignatureString(), "(*args)") - - def test_getSignatureString_w_kwargs(self): - method = self._makeOne() - method.kwargs = 'kw' - self.assertEqual(method.getSignatureString(), "(**kw)") - - -class Test_fromFunction(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.interface import fromFunction - return fromFunction(*args, **kw) - - def test_bare(self): - def _func(): - "DOCSTRING" - method = self._callFUT(_func) - self.assertEqual(method.getName(), '_func') - self.assertEqual(method.getDoc(), 'DOCSTRING') - self.assertEqual(method.interface, None) - self.assertEqual(list(method.getTaggedValueTags()), []) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), []) - self.assertEqual(list(info['required']), []) - self.assertEqual(info['optional'], {}) - self.assertEqual(info['varargs'], None) - self.assertEqual(info['kwargs'], None) - - def test_w_interface(self): - from zope.interface.interface import InterfaceClass - class IFoo(InterfaceClass): - pass - def _func(): - "DOCSTRING" - method = self._callFUT(_func, interface=IFoo) - self.assertEqual(method.interface, IFoo) - - def test_w_name(self): - def _func(): - "DOCSTRING" - method = self._callFUT(_func, name='anotherName') - self.assertEqual(method.getName(), 'anotherName') - - def test_w_only_required(self): - def _func(foo): - "DOCSTRING" - method = self._callFUT(_func) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), ['foo']) - self.assertEqual(list(info['required']), ['foo']) - self.assertEqual(info['optional'], {}) - self.assertEqual(info['varargs'], None) - self.assertEqual(info['kwargs'], None) - - def test_w_optional(self): - def _func(foo='bar'): - "DOCSTRING" - method = self._callFUT(_func) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), ['foo']) - self.assertEqual(list(info['required']), []) - self.assertEqual(info['optional'], {'foo': 'bar'}) - self.assertEqual(info['varargs'], None) - self.assertEqual(info['kwargs'], None) - - def test_w_optional_self(self): - # XXX This is a weird case, trying to cover the following code in - # FUT:: - # - # nr = na-len(defaults) - # if nr < 0: - # defaults=defaults[-nr:] - # nr = 0 - def _func(self='bar'): - "DOCSTRING" - method = self._callFUT(_func, imlevel=1) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), []) - self.assertEqual(list(info['required']), []) - self.assertEqual(info['optional'], {}) - self.assertEqual(info['varargs'], None) - self.assertEqual(info['kwargs'], None) - - def test_w_varargs(self): - def _func(*args): - "DOCSTRING" - method = self._callFUT(_func) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), []) - self.assertEqual(list(info['required']), []) - self.assertEqual(info['optional'], {}) - self.assertEqual(info['varargs'], 'args') - self.assertEqual(info['kwargs'], None) - - def test_w_kwargs(self): - def _func(**kw): - "DOCSTRING" - method = self._callFUT(_func) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), []) - self.assertEqual(list(info['required']), []) - self.assertEqual(info['optional'], {}) - self.assertEqual(info['varargs'], None) - self.assertEqual(info['kwargs'], 'kw') - - def test_full_spectrum(self): - def _func(foo, bar='baz', *args, **kw): - "DOCSTRING" - method = self._callFUT(_func) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), ['foo', 'bar']) - self.assertEqual(list(info['required']), ['foo']) - self.assertEqual(info['optional'], {'bar': 'baz'}) - self.assertEqual(info['varargs'], 'args') - self.assertEqual(info['kwargs'], 'kw') - - -class Test_fromMethod(unittest.TestCase): - - def _callFUT(self, *args, **kw): - from zope.interface.interface import fromMethod - return fromMethod(*args, **kw) - - def test_no_args(self): - class Foo(object): - def bar(self): - "DOCSTRING" - method = self._callFUT(Foo.bar) - self.assertEqual(method.getName(), 'bar') - self.assertEqual(method.getDoc(), 'DOCSTRING') - self.assertEqual(method.interface, None) - self.assertEqual(list(method.getTaggedValueTags()), []) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), []) - self.assertEqual(list(info['required']), []) - self.assertEqual(info['optional'], {}) - self.assertEqual(info['varargs'], None) - self.assertEqual(info['kwargs'], None) - - def test_full_spectrum(self): - class Foo(object): - def bar(self, foo, bar='baz', *args, **kw): - "DOCSTRING" - method = self._callFUT(Foo.bar) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), ['foo', 'bar']) - self.assertEqual(list(info['required']), ['foo']) - self.assertEqual(info['optional'], {'bar': 'baz'}) - self.assertEqual(info['varargs'], 'args') - self.assertEqual(info['kwargs'], 'kw') - - def test_w_non_method(self): - def foo(): - "DOCSTRING" - method = self._callFUT(foo) - self.assertEqual(method.getName(), 'foo') - self.assertEqual(method.getDoc(), 'DOCSTRING') - self.assertEqual(method.interface, None) - self.assertEqual(list(method.getTaggedValueTags()), []) - info = method.getSignatureInfo() - self.assertEqual(list(info['positional']), []) - self.assertEqual(list(info['required']), []) - self.assertEqual(info['optional'], {}) - self.assertEqual(info['varargs'], None) - self.assertEqual(info['kwargs'], None) - -class DummyDependent(object): - - def __init__(self): - self._changed = [] - - def changed(self, originally_changed): - self._changed.append(originally_changed) - - -def _barGreaterThanFoo(obj): - from zope.interface.exceptions import Invalid - foo = getattr(obj, 'foo', None) - bar = getattr(obj, 'bar', None) - if foo is not None and isinstance(foo, type(bar)): - # type checking should be handled elsewhere (like, say, - # schema); these invariants should be intra-interface - # constraints. This is a hacky way to do it, maybe, but you - # get the idea - if not bar > foo: - raise Invalid('Please, Boo MUST be greater than Foo!') - -def _ifFooThenBar(obj): - from zope.interface.exceptions import Invalid - if getattr(obj, 'foo', None) and not getattr(obj, 'bar', None): - raise Invalid('If Foo, then Bar!') - - -class _Monkey(object): - # context-manager for replacing module names in the scope of a test. - def __init__(self, module, **kw): - self.module = module - self.to_restore = dict([(key, getattr(module, key)) for key in kw]) - for key, value in kw.items(): - setattr(module, key, value) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - for key, value in self.to_restore.items(): - setattr(self.module, key, value) diff --git a/lib/zope/interface/tests/test_interfaces.py b/lib/zope/interface/tests/test_interfaces.py deleted file mode 100644 index 285d857..0000000 --- a/lib/zope/interface/tests/test_interfaces.py +++ /dev/null @@ -1,95 +0,0 @@ -import unittest - - -class _ConformsToIObjectEvent(object): - - def _makeOne(self, target=None): - if target is None: - target = object() - return self._getTargetClass()(target) - - def test_class_conforms_to_IObjectEvent(self): - from zope.interface.interfaces import IObjectEvent - from zope.interface.verify import verifyClass - verifyClass(IObjectEvent, self._getTargetClass()) - - def test_instance_conforms_to_IObjectEvent(self): - from zope.interface.interfaces import IObjectEvent - from zope.interface.verify import verifyObject - verifyObject(IObjectEvent, self._makeOne()) - - -class _ConformsToIRegistrationEvent(_ConformsToIObjectEvent): - - def test_class_conforms_to_IRegistrationEvent(self): - from zope.interface.interfaces import IRegistrationEvent - from zope.interface.verify import verifyClass - verifyClass(IRegistrationEvent, self._getTargetClass()) - - def test_instance_conforms_to_IRegistrationEvent(self): - from zope.interface.interfaces import IRegistrationEvent - from zope.interface.verify import verifyObject - verifyObject(IRegistrationEvent, self._makeOne()) - - -class ObjectEventTests(unittest.TestCase, _ConformsToIObjectEvent): - - def _getTargetClass(self): - from zope.interface.interfaces import ObjectEvent - return ObjectEvent - - def test_ctor(self): - target = object() - event = self._makeOne(target) - self.assertTrue(event.object is target) - - -class RegistrationEventTests(unittest.TestCase, - _ConformsToIRegistrationEvent): - - def _getTargetClass(self): - from zope.interface.interfaces import RegistrationEvent - return RegistrationEvent - - def test___repr__(self): - target = object() - event = self._makeOne(target) - r = repr(event) - self.assertEqual(r.splitlines(), - ['RegistrationEvent event:', repr(target)]) - - -class RegisteredTests(unittest.TestCase, - _ConformsToIRegistrationEvent): - - def _getTargetClass(self): - from zope.interface.interfaces import Registered - return Registered - - def test_class_conforms_to_IRegistered(self): - from zope.interface.interfaces import IRegistered - from zope.interface.verify import verifyClass - verifyClass(IRegistered, self._getTargetClass()) - - def test_instance_conforms_to_IRegistered(self): - from zope.interface.interfaces import IRegistered - from zope.interface.verify import verifyObject - verifyObject(IRegistered, self._makeOne()) - - -class UnregisteredTests(unittest.TestCase, - _ConformsToIRegistrationEvent): - - def _getTargetClass(self): - from zope.interface.interfaces import Unregistered - return Unregistered - - def test_class_conforms_to_IUnregistered(self): - from zope.interface.interfaces import IUnregistered - from zope.interface.verify import verifyClass - verifyClass(IUnregistered, self._getTargetClass()) - - def test_instance_conforms_to_IUnregistered(self): - from zope.interface.interfaces import IUnregistered - from zope.interface.verify import verifyObject - verifyObject(IUnregistered, self._makeOne()) diff --git a/lib/zope/interface/tests/test_odd_declarations.py b/lib/zope/interface/tests/test_odd_declarations.py deleted file mode 100644 index 46e7675..0000000 --- a/lib/zope/interface/tests/test_odd_declarations.py +++ /dev/null @@ -1,268 +0,0 @@ -############################################################################## -# -# Copyright (c) 2003 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Test interface declarations against ExtensionClass-like classes. - -These tests are to make sure we do something sane in the presence of -classic ExtensionClass classes and instances. -""" -import unittest - -from zope.interface.tests import odd -from zope.interface import Interface -from zope.interface import implementer -from zope.interface import directlyProvides -from zope.interface import providedBy -from zope.interface import directlyProvidedBy -from zope.interface import classImplements -from zope.interface import classImplementsOnly -from zope.interface import implementedBy -from zope.interface._compat import _skip_under_py3k - -class I1(Interface): pass -class I2(Interface): pass -class I3(Interface): pass -class I31(I3): pass -class I4(Interface): pass -class I5(Interface): pass - -class Odd(object): - pass -Odd = odd.MetaClass('Odd', Odd.__bases__, {}) - - -class B(Odd): __implemented__ = I2 - - -# TODO: We are going to need more magic to make classProvides work with odd -# classes. This will work in the next iteration. For now, we'll use -# a different mechanism. - -# from zope.interface import classProvides -class A(Odd): - pass -classImplements(A, I1) - -class C(A, B): - pass -classImplements(C, I31) - - -class Test(unittest.TestCase): - - def test_ObjectSpecification(self): - c = C() - directlyProvides(c, I4) - self.assertEqual([i.getName() for i in providedBy(c)], - ['I4', 'I31', 'I1', 'I2'] - ) - self.assertEqual([i.getName() for i in providedBy(c).flattened()], - ['I4', 'I31', 'I3', 'I1', 'I2', 'Interface'] - ) - self.assertTrue(I1 in providedBy(c)) - self.assertFalse(I3 in providedBy(c)) - self.assertTrue(providedBy(c).extends(I3)) - self.assertTrue(providedBy(c).extends(I31)) - self.assertFalse(providedBy(c).extends(I5)) - - class COnly(A, B): - pass - classImplementsOnly(COnly, I31) - - class D(COnly): - pass - classImplements(D, I5) - - classImplements(D, I5) - - c = D() - directlyProvides(c, I4) - self.assertEqual([i.getName() for i in providedBy(c)], - ['I4', 'I5', 'I31']) - self.assertEqual([i.getName() for i in providedBy(c).flattened()], - ['I4', 'I5', 'I31', 'I3', 'Interface']) - self.assertFalse(I1 in providedBy(c)) - self.assertFalse(I3 in providedBy(c)) - self.assertTrue(providedBy(c).extends(I3)) - self.assertFalse(providedBy(c).extends(I1)) - self.assertTrue(providedBy(c).extends(I31)) - self.assertTrue(providedBy(c).extends(I5)) - - class COnly(A, B): __implemented__ = I31 - class D(COnly): - pass - classImplements(D, I5) - - classImplements(D, I5) - c = D() - directlyProvides(c, I4) - self.assertEqual([i.getName() for i in providedBy(c)], - ['I4', 'I5', 'I31']) - self.assertEqual([i.getName() for i in providedBy(c).flattened()], - ['I4', 'I5', 'I31', 'I3', 'Interface']) - self.assertFalse(I1 in providedBy(c)) - self.assertFalse(I3 in providedBy(c)) - self.assertTrue(providedBy(c).extends(I3)) - self.assertFalse(providedBy(c).extends(I1)) - self.assertTrue(providedBy(c).extends(I31)) - self.assertTrue(providedBy(c).extends(I5)) - - def test_classImplements(self): - - @implementer(I3) - class A(Odd): - pass - - @implementer(I4) - class B(Odd): - pass - - class C(A, B): - pass - classImplements(C, I1, I2) - self.assertEqual([i.getName() for i in implementedBy(C)], - ['I1', 'I2', 'I3', 'I4']) - classImplements(C, I5) - self.assertEqual([i.getName() for i in implementedBy(C)], - ['I1', 'I2', 'I5', 'I3', 'I4']) - - def test_classImplementsOnly(self): - @implementer(I3) - class A(Odd): - pass - - @implementer(I4) - class B(Odd): - pass - - class C(A, B): - pass - classImplementsOnly(C, I1, I2) - self.assertEqual([i.__name__ for i in implementedBy(C)], - ['I1', 'I2']) - - - def test_directlyProvides(self): - class IA1(Interface): pass - class IA2(Interface): pass - class IB(Interface): pass - class IC(Interface): pass - class A(Odd): - pass - classImplements(A, IA1, IA2) - - class B(Odd): - pass - classImplements(B, IB) - - class C(A, B): - pass - classImplements(C, IC) - - - ob = C() - directlyProvides(ob, I1, I2) - self.assertTrue(I1 in providedBy(ob)) - self.assertTrue(I2 in providedBy(ob)) - self.assertTrue(IA1 in providedBy(ob)) - self.assertTrue(IA2 in providedBy(ob)) - self.assertTrue(IB in providedBy(ob)) - self.assertTrue(IC in providedBy(ob)) - - directlyProvides(ob, directlyProvidedBy(ob)-I2) - self.assertTrue(I1 in providedBy(ob)) - self.assertFalse(I2 in providedBy(ob)) - self.assertFalse(I2 in providedBy(ob)) - directlyProvides(ob, directlyProvidedBy(ob), I2) - self.assertTrue(I2 in providedBy(ob)) - - @_skip_under_py3k - def test_directlyProvides_fails_for_odd_class(self): - self.assertRaises(TypeError, directlyProvides, C, I5) - - # see above - #def TODO_test_classProvides_fails_for_odd_class(self): - # try: - # class A(Odd): - # classProvides(I1) - # except TypeError: - # pass # Sucess - # self.assert_(False, - # "Shouldn't be able to use directlyProvides on odd class." - # ) - - def test_implementedBy(self): - class I2(I1): pass - - class C1(Odd): - pass - classImplements(C1, I2) - - class C2(C1): - pass - classImplements(C2, I3) - - self.assertEqual([i.getName() for i in implementedBy(C2)], - ['I3', 'I2']) - - def test_odd_metaclass_that_doesnt_subclass_type(self): - # This was originally a doctest in odd.py. - # It verifies that the metaclass the rest of these tests use - # works as expected. - - # This is used for testing support for ExtensionClass in new interfaces. - - class A(object): - a = 1 - - A = odd.MetaClass('A', A.__bases__, A.__dict__) - - class B(object): - b = 1 - - B = odd.MetaClass('B', B.__bases__, B.__dict__) - - class C(A, B): - pass - - self.assertEqual(C.__bases__, (A, B)) - - a = A() - aa = A() - self.assertEqual(a.a, 1) - self.assertEqual(aa.a, 1) - - aa.a = 2 - self.assertEqual(a.a, 1) - self.assertEqual(aa.a, 2) - - c = C() - self.assertEqual(c.a, 1) - self.assertEqual(c.b, 1) - - c.b = 2 - self.assertEqual(c.b, 2) - - C.c = 1 - self.assertEqual(c.c, 1) - c.c - - try: - from types import ClassType - except ImportError: - pass - else: - # This test only makes sense under Python 2.x - assert not isinstance(C, (type, ClassType)) - - self.assertIs(C.__class__.__class__, C.__class__) diff --git a/lib/zope/interface/tests/test_registry.py b/lib/zope/interface/tests/test_registry.py deleted file mode 100644 index e5a8eb0..0000000 --- a/lib/zope/interface/tests/test_registry.py +++ /dev/null @@ -1,2788 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002, 2009 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Component Registry Tests""" -# pylint:disable=protected-access -import unittest - -from zope.interface import Interface -from zope.interface.adapter import VerifyingAdapterRegistry - -from zope.interface.registry import Components - -class ComponentsTests(unittest.TestCase): - - def _getTargetClass(self): - return Components - - def _makeOne(self, name='test', *args, **kw): - return self._getTargetClass()(name, *args, **kw) - - def _wrapEvents(self): - from zope.interface import registry - _events = [] - def _notify(*args, **kw): - _events.append((args, kw)) - _monkey = _Monkey(registry, notify=_notify) - return _monkey, _events - - def test_ctor_no_bases(self): - from zope.interface.adapter import AdapterRegistry - comp = self._makeOne('testing') - self.assertEqual(comp.__name__, 'testing') - self.assertEqual(comp.__bases__, ()) - self.assertTrue(isinstance(comp.adapters, AdapterRegistry)) - self.assertTrue(isinstance(comp.utilities, AdapterRegistry)) - self.assertEqual(comp.adapters.__bases__, ()) - self.assertEqual(comp.utilities.__bases__, ()) - self.assertEqual(comp._utility_registrations, {}) - self.assertEqual(comp._adapter_registrations, {}) - self.assertEqual(comp._subscription_registrations, []) - self.assertEqual(comp._handler_registrations, []) - - def test_ctor_w_base(self): - base = self._makeOne('base') - comp = self._makeOne('testing', (base,)) - self.assertEqual(comp.__name__, 'testing') - self.assertEqual(comp.__bases__, (base,)) - self.assertEqual(comp.adapters.__bases__, (base.adapters,)) - self.assertEqual(comp.utilities.__bases__, (base.utilities,)) - - def test___repr__(self): - comp = self._makeOne('testing') - self.assertEqual(repr(comp), '<Components testing>') - - # test _init_registries / _init_registrations via only caller, __init__. - - def test_assign_to___bases__(self): - base1 = self._makeOne('base1') - base2 = self._makeOne('base2') - comp = self._makeOne() - comp.__bases__ = (base1, base2) - self.assertEqual(comp.__bases__, (base1, base2)) - self.assertEqual(comp.adapters.__bases__, - (base1.adapters, base2.adapters)) - self.assertEqual(comp.utilities.__bases__, - (base1.utilities, base2.utilities)) - - def test_registerUtility_with_component_name(self): - from zope.interface.declarations import named, InterfaceClass - - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - - @named(u'foo') - class Foo(object): - pass - foo = Foo() - _info = u'info' - - comp = self._makeOne() - comp.registerUtility(foo, ifoo, info=_info) - self.assertEqual( - comp._utility_registrations[ifoo, u'foo'], - (foo, _info, None)) - - def test_registerUtility_both_factory_and_component(self): - def _factory(): - raise NotImplementedError() - _to_reg = object() - comp = self._makeOne() - self.assertRaises(TypeError, comp.registerUtility, - component=_to_reg, factory=_factory) - - def test_registerUtility_w_component(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Registered - from zope.interface.registry import UtilityRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - _to_reg = object() - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerUtility(_to_reg, ifoo, _name, _info) - self.assertTrue(comp.utilities._adapters[0][ifoo][_name] is _to_reg) - self.assertEqual(comp._utility_registrations[ifoo, _name], - (_to_reg, _info, None)) - self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, UtilityRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.component is _to_reg) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is None) - - def test_registerUtility_w_factory(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Registered - from zope.interface.registry import UtilityRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - _to_reg = object() - def _factory(): - return _to_reg - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerUtility(None, ifoo, _name, _info, factory=_factory) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, UtilityRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.component is _to_reg) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _factory) - - def test_registerUtility_no_provided_available(self): - class Foo(object): - pass - - _info = u'info' - _name = u'name' - _to_reg = Foo() - comp = self._makeOne() - self.assertRaises(TypeError, - comp.registerUtility, _to_reg, None, _name, _info) - - def test_registerUtility_wo_provided(self): - from zope.interface.declarations import directlyProvides - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Registered - from zope.interface.registry import UtilityRegistration - - class IFoo(InterfaceClass): - pass - class Foo(object): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - _to_reg = Foo() - directlyProvides(_to_reg, ifoo) - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerUtility(_to_reg, None, _name, _info) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, UtilityRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.component is _to_reg) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is None) - - def test_registerUtility_duplicates_existing_reg(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerUtility(_to_reg, ifoo, _name, _info) - self.assertEqual(len(_events), 0) - - def test_registerUtility_w_different_info(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info1 = u'info1' - _info2 = u'info2' - _name = u'name' - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name, _info1) - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerUtility(_to_reg, ifoo, _name, _info2) - self.assertEqual(len(_events), 2) # unreg, reg - self.assertEqual(comp._utility_registrations[(ifoo, _name)], - (_to_reg, _info2, None)) # replaced - self.assertEqual(comp.utilities._subscribers[0][ifoo][u''], - (_to_reg,)) - - def test_registerUtility_w_different_names_same_component(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - _other_reg = object() - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_other_reg, ifoo, _name1, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerUtility(_to_reg, ifoo, _name2, _info) - self.assertEqual(len(_events), 1) # reg - self.assertEqual(comp._utility_registrations[(ifoo, _name1)], - (_other_reg, _info, None)) - self.assertEqual(comp._utility_registrations[(ifoo, _name2)], - (_to_reg, _info, None)) - self.assertEqual(comp.utilities._subscribers[0][ifoo][u''], - (_other_reg, _to_reg,)) - - def test_registerUtility_replaces_existing_reg(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.interfaces import Registered - from zope.interface.registry import UtilityRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - _before, _after = object(), object() - comp = self._makeOne() - comp.registerUtility(_before, ifoo, _name, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerUtility(_after, ifoo, _name, _info) - self.assertEqual(len(_events), 2) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, UtilityRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.component is _before) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is None) - args, kw = _events[1] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, UtilityRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.component is _after) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is None) - - def test_registerUtility_w_existing_subscr(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name1, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerUtility(_to_reg, ifoo, _name2, _info) - self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) - - def test_registerUtility_wo_event(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - _to_reg = object() - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerUtility(_to_reg, ifoo, _name, _info, False) - self.assertEqual(len(_events), 0) - - def test_registerUtility_changes_object_identity_after(self): - # If a subclass changes the identity of the _utility_registrations, - # the cache is updated and the right thing still happens. - class CompThatChangesAfter1Reg(self._getTargetClass()): - reg_count = 0 - def registerUtility(self, *args): - self.reg_count += 1 - super(CompThatChangesAfter1Reg, self).registerUtility(*args) - if self.reg_count == 1: - self._utility_registrations = dict(self._utility_registrations) - - comp = CompThatChangesAfter1Reg() - comp.registerUtility(object(), Interface) - - self.assertEqual(len(list(comp.registeredUtilities())), 1) - - class IFoo(Interface): - pass - - comp.registerUtility(object(), IFoo) - self.assertEqual(len(list(comp.registeredUtilities())), 2) - - def test_registerUtility_changes_object_identity_before(self): - # If a subclass changes the identity of the _utility_registrations, - # the cache is updated and the right thing still happens. - class CompThatChangesAfter2Reg(self._getTargetClass()): - reg_count = 0 - def registerUtility(self, *args): - self.reg_count += 1 - if self.reg_count == 2: - self._utility_registrations = dict(self._utility_registrations) - - super(CompThatChangesAfter2Reg, self).registerUtility(*args) - - comp = CompThatChangesAfter2Reg() - comp.registerUtility(object(), Interface) - - self.assertEqual(len(list(comp.registeredUtilities())), 1) - - class IFoo(Interface): - pass - - comp.registerUtility(object(), IFoo) - self.assertEqual(len(list(comp.registeredUtilities())), 2) - - - class IBar(Interface): - pass - - comp.registerUtility(object(), IBar) - self.assertEqual(len(list(comp.registeredUtilities())), 3) - - - def test_unregisterUtility_neither_factory_nor_component_nor_provided(self): - comp = self._makeOne() - self.assertRaises(TypeError, comp.unregisterUtility, - component=None, provided=None, factory=None) - - def test_unregisterUtility_both_factory_and_component(self): - def _factory(): - raise NotImplementedError() - _to_reg = object() - comp = self._makeOne() - self.assertRaises(TypeError, comp.unregisterUtility, - component=_to_reg, factory=_factory) - - def test_unregisterUtility_w_component_miss(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _name = u'name' - _to_reg = object() - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterUtility(_to_reg, ifoo, _name) - self.assertFalse(unreg) - self.assertFalse(_events) - - def test_unregisterUtility_w_component(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import UtilityRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _name = u'name' - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterUtility(_to_reg, ifoo, _name) - self.assertTrue(unreg) - self.assertFalse(comp.utilities._adapters) # all erased - self.assertFalse((ifoo, _name) in comp._utility_registrations) - self.assertFalse(comp.utilities._subscribers) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, UtilityRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.component is _to_reg) - self.assertTrue(event.object.factory is None) - - def test_unregisterUtility_w_factory(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import UtilityRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - _to_reg = object() - def _factory(): - return _to_reg - comp = self._makeOne() - comp.registerUtility(None, ifoo, _name, _info, factory=_factory) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterUtility(None, ifoo, _name, factory=_factory) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, UtilityRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.component is _to_reg) - self.assertTrue(event.object.factory is _factory) - - def test_unregisterUtility_wo_explicit_provided(self): - from zope.interface.declarations import directlyProvides - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import UtilityRegistration - - class IFoo(InterfaceClass): - pass - class Foo(object): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - _to_reg = Foo() - directlyProvides(_to_reg, ifoo) - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterUtility(_to_reg, None, _name) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, UtilityRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.component is _to_reg) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is None) - - def test_unregisterUtility_wo_component_or_factory(self): - from zope.interface.declarations import directlyProvides - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import UtilityRegistration - - class IFoo(InterfaceClass): - pass - class Foo(object): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - _to_reg = Foo() - directlyProvides(_to_reg, ifoo) - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - # Just pass the interface / name - unreg = comp.unregisterUtility(provided=ifoo, name=_name) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, UtilityRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.component is _to_reg) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is None) - - def test_unregisterUtility_w_existing_subscr(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name1, _info) - comp.registerUtility(_to_reg, ifoo, _name2, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - comp.unregisterUtility(_to_reg, ifoo, _name2) - self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) - - def test_unregisterUtility_w_existing_subscr_non_hashable(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - _to_reg = dict() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name1, _info) - comp.registerUtility(_to_reg, ifoo, _name2, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - comp.unregisterUtility(_to_reg, ifoo, _name2) - self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) - - def test_unregisterUtility_w_existing_subscr_non_hashable_fresh_cache(self): - # We correctly populate the cache of registrations if it has gone away - # (for example, the Components was unpickled) - from zope.interface.declarations import InterfaceClass - from zope.interface.registry import _UtilityRegistrations - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - _to_reg = dict() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name1, _info) - comp.registerUtility(_to_reg, ifoo, _name2, _info) - - _monkey, _events = self._wrapEvents() - with _monkey: - comp.unregisterUtility(_to_reg, ifoo, _name2) - self.assertEqual(comp.utilities._subscribers[0][ifoo][''], (_to_reg,)) - - def test_unregisterUtility_w_existing_subscr_non_hashable_reinitted(self): - # We correctly populate the cache of registrations if the base objects change - # out from under us - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - _to_reg = dict() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name1, _info) - comp.registerUtility(_to_reg, ifoo, _name2, _info) - - # zope.component.testing does this - comp.__init__('base') - - comp.registerUtility(_to_reg, ifoo, _name2, _info) - - _monkey, _events = self._wrapEvents() - with _monkey: - # Nothing to do, but we don't break either - comp.unregisterUtility(_to_reg, ifoo, _name2) - self.assertEqual(0, len(comp.utilities._subscribers)) - - def test_unregisterUtility_w_existing_subscr_other_component(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - _other_reg = object() - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_other_reg, ifoo, _name1, _info) - comp.registerUtility(_to_reg, ifoo, _name2, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - comp.unregisterUtility(_to_reg, ifoo, _name2) - self.assertEqual(comp.utilities._subscribers[0][ifoo][''], - (_other_reg,)) - - def test_unregisterUtility_w_existing_subscr_other_component_mixed_hash(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - # First register something hashable - _other_reg = object() - # Then it transfers to something unhashable - _to_reg = dict() - comp = self._makeOne() - comp.registerUtility(_other_reg, ifoo, _name1, _info) - comp.registerUtility(_to_reg, ifoo, _name2, _info) - _monkey, _events = self._wrapEvents() - with _monkey: - comp.unregisterUtility(_to_reg, ifoo, _name2) - self.assertEqual(comp.utilities._subscribers[0][ifoo][''], - (_other_reg,)) - - def test_registeredUtilities_empty(self): - comp = self._makeOne() - self.assertEqual(list(comp.registeredUtilities()), []) - - def test_registeredUtilities_notempty(self): - from zope.interface.declarations import InterfaceClass - - from zope.interface.registry import UtilityRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, _name1, _info) - comp.registerUtility(_to_reg, ifoo, _name2, _info) - reg = sorted(comp.registeredUtilities(), key=lambda r: r.name) - self.assertEqual(len(reg), 2) - self.assertTrue(isinstance(reg[0], UtilityRegistration)) - self.assertTrue(reg[0].registry is comp) - self.assertTrue(reg[0].provided is ifoo) - self.assertTrue(reg[0].name is _name1) - self.assertTrue(reg[0].component is _to_reg) - self.assertTrue(reg[0].info is _info) - self.assertTrue(reg[0].factory is None) - self.assertTrue(isinstance(reg[1], UtilityRegistration)) - self.assertTrue(reg[1].registry is comp) - self.assertTrue(reg[1].provided is ifoo) - self.assertTrue(reg[1].name is _name2) - self.assertTrue(reg[1].component is _to_reg) - self.assertTrue(reg[1].info is _info) - self.assertTrue(reg[1].factory is None) - - def test_queryUtility_miss_no_default(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - self.assertTrue(comp.queryUtility(ifoo) is None) - - def test_queryUtility_miss_w_default(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - _default = object() - self.assertTrue(comp.queryUtility(ifoo, default=_default) is _default) - - def test_queryUtility_hit(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo) - self.assertTrue(comp.queryUtility(ifoo) is _to_reg) - - def test_getUtility_miss(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import ComponentLookupError - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - self.assertRaises(ComponentLookupError, comp.getUtility, ifoo) - - def test_getUtility_hit(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo) - self.assertTrue(comp.getUtility(ifoo) is _to_reg) - - def test_getUtilitiesFor_miss(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - self.assertEqual(list(comp.getUtilitiesFor(ifoo)), []) - - def test_getUtilitiesFor_hit(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _name1 = u'name1' - _name2 = u'name2' - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, name=_name1) - comp.registerUtility(_to_reg, ifoo, name=_name2) - self.assertEqual(sorted(comp.getUtilitiesFor(ifoo)), - [(_name1, _to_reg), (_name2, _to_reg)]) - - def test_getAllUtilitiesRegisteredFor_miss(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - self.assertEqual(list(comp.getAllUtilitiesRegisteredFor(ifoo)), []) - - def test_getAllUtilitiesRegisteredFor_hit(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _name1 = u'name1' - _name2 = u'name2' - _to_reg = object() - comp = self._makeOne() - comp.registerUtility(_to_reg, ifoo, name=_name1) - comp.registerUtility(_to_reg, ifoo, name=_name2) - self.assertEqual(list(comp.getAllUtilitiesRegisteredFor(ifoo)), - [_to_reg]) - - def test_registerAdapter_with_component_name(self): - from zope.interface.declarations import named, InterfaceClass - - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - - @named(u'foo') - class Foo(object): - pass - _info = u'info' - - comp = self._makeOne() - comp.registerAdapter(Foo, (ibar,), ifoo, info=_info) - - self.assertEqual( - comp._adapter_registrations[(ibar,), ifoo, u'foo'], - (Foo, _info)) - - def test_registerAdapter_w_explicit_provided_and_required(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Registered - from zope.interface.registry import AdapterRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _info = u'info' - _name = u'name' - - def _factory(context): - raise NotImplementedError() - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerAdapter(_factory, (ibar,), ifoo, _name, _info) - self.assertTrue(comp.adapters._adapters[1][ibar][ifoo][_name] - is _factory) - self.assertEqual(comp._adapter_registrations[(ibar,), ifoo, _name], - (_factory, _info)) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, AdapterRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _factory) - - def test_registerAdapter_no_provided_available(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - - ibar = IFoo('IBar') - _info = u'info' - _name = u'name' - _to_reg = object() - class _Factory(object): - pass - - comp = self._makeOne() - self.assertRaises(TypeError, comp.registerAdapter, _Factory, (ibar,), - name=_name, info=_info) - - def test_registerAdapter_wo_explicit_provided(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - from zope.interface.interfaces import Registered - from zope.interface.registry import AdapterRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _info = u'info' - _name = u'name' - _to_reg = object() - - @implementer(ifoo) - class _Factory(object): - pass - - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerAdapter(_Factory, (ibar,), name=_name, info=_info) - self.assertTrue(comp.adapters._adapters[1][ibar][ifoo][_name] - is _Factory) - self.assertEqual(comp._adapter_registrations[(ibar,), ifoo, _name], - (_Factory, _info)) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, AdapterRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _Factory) - - def test_registerAdapter_no_required_available(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - - _info = u'info' - _name = u'name' - class _Factory(object): - pass - - comp = self._makeOne() - self.assertRaises(TypeError, comp.registerAdapter, _Factory, - provided=ifoo, name=_name, info=_info) - - def test_registerAdapter_w_invalid_required(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _info = u'info' - _name = u'name' - class _Factory(object): - pass - comp = self._makeOne() - self.assertRaises(TypeError, comp.registerAdapter, _Factory, - ibar, provided=ifoo, name=_name, info=_info) - - def test_registerAdapter_w_required_containing_None(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interface import Interface - from zope.interface.interfaces import Registered - from zope.interface.registry import AdapterRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _name = u'name' - class _Factory(object): - pass - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerAdapter(_Factory, [None], provided=ifoo, - name=_name, info=_info) - self.assertTrue(comp.adapters._adapters[1][Interface][ifoo][_name] - is _Factory) - self.assertEqual(comp._adapter_registrations[(Interface,), ifoo, _name], - (_Factory, _info)) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, AdapterRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (Interface,)) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _Factory) - - def test_registerAdapter_w_required_containing_class(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - from zope.interface.declarations import implementedBy - from zope.interface.interfaces import Registered - from zope.interface.registry import AdapterRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _info = u'info' - _name = u'name' - class _Factory(object): - pass - - @implementer(ibar) - class _Context(object): - pass - _ctx_impl = implementedBy(_Context) - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerAdapter(_Factory, [_Context], provided=ifoo, - name=_name, info=_info) - self.assertTrue(comp.adapters._adapters[1][_ctx_impl][ifoo][_name] - is _Factory) - self.assertEqual(comp._adapter_registrations[(_ctx_impl,), ifoo, _name], - (_Factory, _info)) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, AdapterRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (_ctx_impl,)) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _Factory) - - def test_registerAdapter_w_required_containing_junk(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - - _info = u'info' - _name = u'name' - class _Factory(object): - pass - comp = self._makeOne() - self.assertRaises(TypeError, comp.registerAdapter, _Factory, [object()], - provided=ifoo, name=_name, info=_info) - - def test_registerAdapter_wo_explicit_required(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Registered - from zope.interface.registry import AdapterRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _info = u'info' - _name = u'name' - class _Factory(object): - __component_adapts__ = (ibar,) - - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerAdapter(_Factory, provided=ifoo, name=_name, - info=_info) - self.assertTrue(comp.adapters._adapters[1][ibar][ifoo][_name] - is _Factory) - self.assertEqual(comp._adapter_registrations[(ibar,), ifoo, _name], - (_Factory, _info)) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, AdapterRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertTrue(event.object.name is _name) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _Factory) - - def test_registerAdapter_wo_event(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _info = u'info' - _name = u'name' - - def _factory(context): - raise NotImplementedError() - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerAdapter(_factory, (ibar,), ifoo, _name, _info, - event=False) - self.assertEqual(len(_events), 0) - - def test_unregisterAdapter_neither_factory_nor_provided(self): - comp = self._makeOne() - self.assertRaises(TypeError, comp.unregisterAdapter, - factory=None, provided=None) - - def test_unregisterAdapter_neither_factory_nor_required(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - self.assertRaises(TypeError, comp.unregisterAdapter, - factory=None, provided=ifoo, required=None) - - def test_unregisterAdapter_miss(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - pass - - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterAdapter(_Factory, (ibar,), ifoo) - self.assertFalse(unreg) - - def test_unregisterAdapter_hit_w_explicit_provided_and_required(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import AdapterRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - pass - - comp = self._makeOne() - comp.registerAdapter(_Factory, (ibar,), ifoo) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterAdapter(_Factory, (ibar,), ifoo) - self.assertTrue(unreg) - self.assertFalse(comp.adapters._adapters) - self.assertFalse(comp._adapter_registrations) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, AdapterRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, '') - self.assertEqual(event.object.info, '') - self.assertTrue(event.object.factory is _Factory) - - def test_unregisterAdapter_wo_explicit_provided(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - from zope.interface.interfaces import Unregistered - from zope.interface.registry import AdapterRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - @implementer(ifoo) - class _Factory(object): - pass - - comp = self._makeOne() - comp.registerAdapter(_Factory, (ibar,), ifoo) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterAdapter(_Factory, (ibar,)) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, AdapterRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, '') - self.assertEqual(event.object.info, '') - self.assertTrue(event.object.factory is _Factory) - - def test_unregisterAdapter_wo_explicit_required(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import AdapterRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - __component_adapts__ = (ibar,) - - comp = self._makeOne() - comp.registerAdapter(_Factory, (ibar,), ifoo) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterAdapter(_Factory, provided=ifoo) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, AdapterRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, '') - self.assertEqual(event.object.info, '') - self.assertTrue(event.object.factory is _Factory) - - def test_registeredAdapters_empty(self): - comp = self._makeOne() - self.assertEqual(list(comp.registeredAdapters()), []) - - def test_registeredAdapters_notempty(self): - from zope.interface.declarations import InterfaceClass - - from zope.interface.registry import AdapterRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IFoo') - _info = u'info' - _name1 = u'name1' - _name2 = u'name2' - class _Factory(object): - pass - - comp = self._makeOne() - comp.registerAdapter(_Factory, (ibar,), ifoo, _name1, _info) - comp.registerAdapter(_Factory, (ibar,), ifoo, _name2, _info) - reg = sorted(comp.registeredAdapters(), key=lambda r: r.name) - self.assertEqual(len(reg), 2) - self.assertTrue(isinstance(reg[0], AdapterRegistration)) - self.assertTrue(reg[0].registry is comp) - self.assertTrue(reg[0].provided is ifoo) - self.assertEqual(reg[0].required, (ibar,)) - self.assertTrue(reg[0].name is _name1) - self.assertTrue(reg[0].info is _info) - self.assertTrue(reg[0].factory is _Factory) - self.assertTrue(isinstance(reg[1], AdapterRegistration)) - self.assertTrue(reg[1].registry is comp) - self.assertTrue(reg[1].provided is ifoo) - self.assertEqual(reg[1].required, (ibar,)) - self.assertTrue(reg[1].name is _name2) - self.assertTrue(reg[1].info is _info) - self.assertTrue(reg[1].factory is _Factory) - - def test_queryAdapter_miss_no_default(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - _context = object() - self.assertTrue(comp.queryAdapter(_context, ifoo) is None) - - def test_queryAdapter_miss_w_default(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - _context = object() - _default = object() - self.assertTrue( - comp.queryAdapter(_context, ifoo, default=_default) is _default) - - def test_queryAdapter_hit(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - def __init__(self, context): - self.context = context - @implementer(ibar) - class _Context(object): - pass - _context = _Context() - comp = self._makeOne() - comp.registerAdapter(_Factory, (ibar,), ifoo) - adapter = comp.queryAdapter(_context, ifoo) - self.assertTrue(isinstance(adapter, _Factory)) - self.assertTrue(adapter.context is _context) - - def test_getAdapter_miss(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - from zope.interface.interfaces import ComponentLookupError - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - @implementer(ibar) - class _Context(object): - pass - _context = _Context() - comp = self._makeOne() - self.assertRaises(ComponentLookupError, - comp.getAdapter, _context, ifoo) - - def test_getAdapter_hit(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - def __init__(self, context): - self.context = context - @implementer(ibar) - class _Context(object): - pass - _context = _Context() - comp = self._makeOne() - comp.registerAdapter(_Factory, (ibar,), ifoo) - adapter = comp.getAdapter(_context, ifoo) - self.assertTrue(isinstance(adapter, _Factory)) - self.assertTrue(adapter.context is _context) - - def test_queryMultiAdapter_miss(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - ibaz = IFoo('IBaz') - @implementer(ibar) - class _Context1(object): - pass - @implementer(ibaz) - class _Context2(object): - pass - _context1 = _Context1() - _context2 = _Context2() - comp = self._makeOne() - self.assertEqual(comp.queryMultiAdapter((_context1, _context2), ifoo), - None) - - def test_queryMultiAdapter_miss_w_default(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - ibaz = IFoo('IBaz') - @implementer(ibar) - class _Context1(object): - pass - @implementer(ibaz) - class _Context2(object): - pass - _context1 = _Context1() - _context2 = _Context2() - _default = object() - comp = self._makeOne() - self.assertTrue( - comp.queryMultiAdapter((_context1, _context2), ifoo, - default=_default) is _default) - - def test_queryMultiAdapter_hit(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - ibaz = IFoo('IBaz') - @implementer(ibar) - class _Context1(object): - pass - @implementer(ibaz) - class _Context2(object): - pass - _context1 = _Context1() - _context2 = _Context2() - class _Factory(object): - def __init__(self, context1, context2): - self.context = context1, context2 - comp = self._makeOne() - comp.registerAdapter(_Factory, (ibar, ibaz), ifoo) - adapter = comp.queryMultiAdapter((_context1, _context2), ifoo) - self.assertTrue(isinstance(adapter, _Factory)) - self.assertEqual(adapter.context, (_context1, _context2)) - - def test_getMultiAdapter_miss(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - from zope.interface.interfaces import ComponentLookupError - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - ibaz = IFoo('IBaz') - @implementer(ibar) - class _Context1(object): - pass - @implementer(ibaz) - class _Context2(object): - pass - _context1 = _Context1() - _context2 = _Context2() - comp = self._makeOne() - self.assertRaises(ComponentLookupError, - comp.getMultiAdapter, (_context1, _context2), ifoo) - - def test_getMultiAdapter_hit(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - ibaz = IFoo('IBaz') - @implementer(ibar) - class _Context1(object): - pass - @implementer(ibaz) - class _Context2(object): - pass - _context1 = _Context1() - _context2 = _Context2() - class _Factory(object): - def __init__(self, context1, context2): - self.context = context1, context2 - comp = self._makeOne() - comp.registerAdapter(_Factory, (ibar, ibaz), ifoo) - adapter = comp.getMultiAdapter((_context1, _context2), ifoo) - self.assertTrue(isinstance(adapter, _Factory)) - self.assertEqual(adapter.context, (_context1, _context2)) - - def test_getAdapters_empty(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - ibaz = IFoo('IBaz') - @implementer(ibar) - class _Context1(object): - pass - @implementer(ibaz) - class _Context2(object): - pass - _context1 = _Context1() - _context2 = _Context2() - comp = self._makeOne() - self.assertEqual( - list(comp.getAdapters((_context1, _context2), ifoo)), []) - - def test_getAdapters_factory_returns_None(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - ibaz = IFoo('IBaz') - @implementer(ibar) - class _Context1(object): - pass - @implementer(ibaz) - class _Context2(object): - pass - _context1 = _Context1() - _context2 = _Context2() - comp = self._makeOne() - _called_with = [] - def _side_effect_only(context1, context2): - _called_with.append((context1, context2)) - return None - comp.registerAdapter(_side_effect_only, (ibar, ibaz), ifoo) - self.assertEqual( - list(comp.getAdapters((_context1, _context2), ifoo)), []) - self.assertEqual(_called_with, [(_context1, _context2)]) - - def test_getAdapters_non_empty(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - ibaz = IFoo('IBaz') - @implementer(ibar) - class _Context1(object): - pass - @implementer(ibaz) - class _Context2(object): - pass - _context1 = _Context1() - _context2 = _Context2() - class _Factory1(object): - def __init__(self, context1, context2): - self.context = context1, context2 - class _Factory2(object): - def __init__(self, context1, context2): - self.context = context1, context2 - _name1 = u'name1' - _name2 = u'name2' - comp = self._makeOne() - comp.registerAdapter(_Factory1, (ibar, ibaz), ifoo, name=_name1) - comp.registerAdapter(_Factory2, (ibar, ibaz), ifoo, name=_name2) - found = sorted(comp.getAdapters((_context1, _context2), ifoo)) - self.assertEqual(len(found), 2) - self.assertEqual(found[0][0], _name1) - self.assertTrue(isinstance(found[0][1], _Factory1)) - self.assertEqual(found[1][0], _name2) - self.assertTrue(isinstance(found[1][1], _Factory2)) - - def test_registerSubscriptionAdapter_w_nonblank_name(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _name = u'name' - _info = u'info' - def _factory(context): - raise NotImplementedError() - - comp = self._makeOne() - self.assertRaises(TypeError, comp.registerSubscriptionAdapter, - _factory, (ibar,), ifoo, _name, _info) - - def test_registerSubscriptionAdapter_w_explicit_provided_and_required(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Registered - from zope.interface.registry import SubscriptionRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _blank = u'' - _info = u'info' - def _factory(context): - raise NotImplementedError() - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerSubscriptionAdapter(_factory, (ibar,), ifoo, - info=_info) - reg = comp.adapters._subscribers[1][ibar][ifoo][_blank] - self.assertEqual(len(reg), 1) - self.assertTrue(reg[0] is _factory) - self.assertEqual(comp._subscription_registrations, - [((ibar,), ifoo, _blank, _factory, _info)]) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, SubscriptionRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, _blank) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _factory) - - def test_registerSubscriptionAdapter_wo_explicit_provided(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - from zope.interface.interfaces import Registered - from zope.interface.registry import SubscriptionRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _info = u'info' - _blank = u'' - - @implementer(ifoo) - class _Factory(object): - pass - - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerSubscriptionAdapter(_Factory, (ibar,), info=_info) - reg = comp.adapters._subscribers[1][ibar][ifoo][_blank] - self.assertEqual(len(reg), 1) - self.assertTrue(reg[0] is _Factory) - self.assertEqual(comp._subscription_registrations, - [((ibar,), ifoo, _blank, _Factory, _info)]) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, SubscriptionRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, _blank) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _Factory) - - def test_registerSubscriptionAdapter_wo_explicit_required(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Registered - from zope.interface.registry import SubscriptionRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _info = u'info' - _blank = u'' - class _Factory(object): - __component_adapts__ = (ibar,) - - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerSubscriptionAdapter( - _Factory, provided=ifoo, info=_info) - reg = comp.adapters._subscribers[1][ibar][ifoo][_blank] - self.assertEqual(len(reg), 1) - self.assertTrue(reg[0] is _Factory) - self.assertEqual(comp._subscription_registrations, - [((ibar,), ifoo, _blank, _Factory, _info)]) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, SubscriptionRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, _blank) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _Factory) - - def test_registerSubscriptionAdapter_wo_event(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _blank = u'' - _info = u'info' - - def _factory(context): - raise NotImplementedError() - - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerSubscriptionAdapter(_factory, (ibar,), ifoo, - info=_info, event=False) - self.assertEqual(len(_events), 0) - - def test_registeredSubscriptionAdapters_empty(self): - comp = self._makeOne() - self.assertEqual(list(comp.registeredSubscriptionAdapters()), []) - - def test_registeredSubscriptionAdapters_notempty(self): - from zope.interface.declarations import InterfaceClass - - from zope.interface.registry import SubscriptionRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IFoo') - _info = u'info' - _blank = u'' - class _Factory(object): - pass - - comp = self._makeOne() - comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo, info=_info) - comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo, info=_info) - reg = list(comp.registeredSubscriptionAdapters()) - self.assertEqual(len(reg), 2) - self.assertTrue(isinstance(reg[0], SubscriptionRegistration)) - self.assertTrue(reg[0].registry is comp) - self.assertTrue(reg[0].provided is ifoo) - self.assertEqual(reg[0].required, (ibar,)) - self.assertEqual(reg[0].name, _blank) - self.assertTrue(reg[0].info is _info) - self.assertTrue(reg[0].factory is _Factory) - self.assertTrue(isinstance(reg[1], SubscriptionRegistration)) - self.assertTrue(reg[1].registry is comp) - self.assertTrue(reg[1].provided is ifoo) - self.assertEqual(reg[1].required, (ibar,)) - self.assertEqual(reg[1].name, _blank) - self.assertTrue(reg[1].info is _info) - self.assertTrue(reg[1].factory is _Factory) - - def test_unregisterSubscriptionAdapter_w_nonblank_name(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - _nonblank = u'nonblank' - comp = self._makeOne() - self.assertRaises(TypeError, comp.unregisterSubscriptionAdapter, - required=ifoo, provided=ibar, name=_nonblank) - - def test_unregisterSubscriptionAdapter_neither_factory_nor_provided(self): - comp = self._makeOne() - self.assertRaises(TypeError, comp.unregisterSubscriptionAdapter, - factory=None, provided=None) - - def test_unregisterSubscriptionAdapter_neither_factory_nor_required(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - self.assertRaises(TypeError, comp.unregisterSubscriptionAdapter, - factory=None, provided=ifoo, required=None) - - def test_unregisterSubscriptionAdapter_miss(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - pass - - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterSubscriptionAdapter(_Factory, (ibar,), ifoo) - self.assertFalse(unreg) - self.assertFalse(_events) - - def test_unregisterSubscriptionAdapter_hit_wo_factory(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import SubscriptionRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - pass - - comp = self._makeOne() - comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterSubscriptionAdapter(None, (ibar,), ifoo) - self.assertTrue(unreg) - self.assertFalse(comp.adapters._subscribers) - self.assertFalse(comp._subscription_registrations) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, SubscriptionRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, '') - self.assertEqual(event.object.info, '') - self.assertTrue(event.object.factory is None) - - def test_unregisterSubscriptionAdapter_hit_w_factory(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import SubscriptionRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - pass - - comp = self._makeOne() - comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterSubscriptionAdapter(_Factory, (ibar,), ifoo) - self.assertTrue(unreg) - self.assertFalse(comp.adapters._subscribers) - self.assertFalse(comp._subscription_registrations) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, SubscriptionRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, '') - self.assertEqual(event.object.info, '') - self.assertTrue(event.object.factory is _Factory) - - def test_unregisterSubscriptionAdapter_wo_explicit_provided(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - from zope.interface.interfaces import Unregistered - from zope.interface.registry import SubscriptionRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - @implementer(ifoo) - class _Factory(object): - pass - - comp = self._makeOne() - comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterSubscriptionAdapter(_Factory, (ibar,)) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, SubscriptionRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, '') - self.assertEqual(event.object.info, '') - self.assertTrue(event.object.factory is _Factory) - - def test_unregisterSubscriptionAdapter_wo_explicit_required(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import SubscriptionRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - __component_adapts__ = (ibar,) - - comp = self._makeOne() - comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterSubscriptionAdapter(_Factory, provided=ifoo) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, SubscriptionRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertTrue(event.object.provided is ifoo) - self.assertEqual(event.object.required, (ibar,)) - self.assertEqual(event.object.name, '') - self.assertEqual(event.object.info, '') - self.assertTrue(event.object.factory is _Factory) - - def test_subscribers_empty(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - comp = self._makeOne() - @implementer(ibar) - class Bar(object): - pass - bar = Bar() - self.assertEqual(list(comp.subscribers((bar,), ifoo)), []) - - def test_subscribers_non_empty(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Factory(object): - __component_adapts__ = (ibar,) - def __init__(self, context): - self._context = context - class _Derived(_Factory): - pass - comp = self._makeOne() - comp.registerSubscriptionAdapter(_Factory, (ibar,), ifoo) - comp.registerSubscriptionAdapter(_Derived, (ibar,), ifoo) - @implementer(ibar) - class Bar(object): - pass - bar = Bar() - subscribers = comp.subscribers((bar,), ifoo) - def _klassname(x): - return x.__class__.__name__ - subscribers = sorted(subscribers, key=_klassname) - self.assertEqual(len(subscribers), 2) - self.assertTrue(isinstance(subscribers[0], _Derived)) - self.assertTrue(isinstance(subscribers[1], _Factory)) - - def test_registerHandler_w_nonblank_name(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _nonblank = u'nonblank' - comp = self._makeOne() - def _factory(context): - raise NotImplementedError() - - self.assertRaises(TypeError, comp.registerHandler, _factory, - required=ifoo, name=_nonblank) - - def test_registerHandler_w_explicit_required(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Registered - from zope.interface.registry import HandlerRegistration - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _blank = u'' - _info = u'info' - def _factory(context): - raise NotImplementedError() - - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerHandler(_factory, (ifoo,), info=_info) - reg = comp.adapters._subscribers[1][ifoo][None][_blank] - self.assertEqual(len(reg), 1) - self.assertTrue(reg[0] is _factory) - self.assertEqual(comp._handler_registrations, - [((ifoo,), _blank, _factory, _info)]) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Registered)) - self.assertTrue(isinstance(event.object, HandlerRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertEqual(event.object.required, (ifoo,)) - self.assertEqual(event.object.name, _blank) - self.assertTrue(event.object.info is _info) - self.assertTrue(event.object.factory is _factory) - - def test_registerHandler_wo_explicit_required_no_event(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _info = u'info' - _blank = u'' - class _Factory(object): - __component_adapts__ = (ifoo,) - pass - - comp = self._makeOne() - _monkey, _events = self._wrapEvents() - with _monkey: - comp.registerHandler(_Factory, info=_info, event=False) - reg = comp.adapters._subscribers[1][ifoo][None][_blank] - self.assertEqual(len(reg), 1) - self.assertTrue(reg[0] is _Factory) - self.assertEqual(comp._handler_registrations, - [((ifoo,), _blank, _Factory, _info)]) - self.assertEqual(len(_events), 0) - - def test_registeredHandlers_empty(self): - comp = self._makeOne() - self.assertFalse(list(comp.registeredHandlers())) - - def test_registeredHandlers_non_empty(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.registry import HandlerRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - def _factory1(context): - raise NotImplementedError() - def _factory2(context): - raise NotImplementedError() - comp = self._makeOne() - comp.registerHandler(_factory1, (ifoo,)) - comp.registerHandler(_factory2, (ifoo,)) - def _factory_name(x): - return x.factory.__code__.co_name - subscribers = sorted(comp.registeredHandlers(), key=_factory_name) - self.assertEqual(len(subscribers), 2) - self.assertTrue(isinstance(subscribers[0], HandlerRegistration)) - self.assertEqual(subscribers[0].required, (ifoo,)) - self.assertEqual(subscribers[0].name, '') - self.assertEqual(subscribers[0].factory, _factory1) - self.assertEqual(subscribers[0].info, '') - self.assertTrue(isinstance(subscribers[1], HandlerRegistration)) - self.assertEqual(subscribers[1].required, (ifoo,)) - self.assertEqual(subscribers[1].name, '') - self.assertEqual(subscribers[1].factory, _factory2) - self.assertEqual(subscribers[1].info, '') - - def test_unregisterHandler_w_nonblank_name(self): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _nonblank = u'nonblank' - comp = self._makeOne() - self.assertRaises(TypeError, comp.unregisterHandler, - required=(ifoo,), name=_nonblank) - - def test_unregisterHandler_neither_factory_nor_required(self): - comp = self._makeOne() - self.assertRaises(TypeError, comp.unregisterHandler) - - def test_unregisterHandler_miss(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - unreg = comp.unregisterHandler(required=(ifoo,)) - self.assertFalse(unreg) - - def test_unregisterHandler_hit_w_factory_and_explicit_provided(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import HandlerRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - def _factory(context): - raise NotImplementedError() - comp = self._makeOne() - comp.registerHandler(_factory, (ifoo,)) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterHandler(_factory, (ifoo,)) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, HandlerRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertEqual(event.object.required, (ifoo,)) - self.assertEqual(event.object.name, '') - self.assertTrue(event.object.factory is _factory) - - def test_unregisterHandler_hit_w_only_explicit_provided(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import HandlerRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - def _factory(context): - raise NotImplementedError() - comp = self._makeOne() - comp.registerHandler(_factory, (ifoo,)) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterHandler(required=(ifoo,)) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, HandlerRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertEqual(event.object.required, (ifoo,)) - self.assertEqual(event.object.name, '') - self.assertTrue(event.object.factory is None) - - def test_unregisterHandler_wo_explicit_required(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.interfaces import Unregistered - from zope.interface.registry import HandlerRegistration - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - class _Factory(object): - __component_adapts__ = (ifoo,) - - comp = self._makeOne() - comp.registerHandler(_Factory) - _monkey, _events = self._wrapEvents() - with _monkey: - unreg = comp.unregisterHandler(_Factory) - self.assertTrue(unreg) - self.assertEqual(len(_events), 1) - args, kw = _events[0] - event, = args - self.assertEqual(kw, {}) - self.assertTrue(isinstance(event, Unregistered)) - self.assertTrue(isinstance(event.object, HandlerRegistration)) - self.assertTrue(event.object.registry is comp) - self.assertEqual(event.object.required, (ifoo,)) - self.assertEqual(event.object.name, '') - self.assertEqual(event.object.info, '') - self.assertTrue(event.object.factory is _Factory) - - def test_handle_empty(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - comp = self._makeOne() - @implementer(ifoo) - class Bar(object): - pass - bar = Bar() - comp.handle((bar,)) # doesn't raise - - def test_handle_non_empty(self): - from zope.interface.declarations import InterfaceClass - from zope.interface.declarations import implementer - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - _called_1 = [] - def _factory_1(context): - _called_1.append(context) - _called_2 = [] - def _factory_2(context): - _called_2.append(context) - comp = self._makeOne() - comp.registerHandler(_factory_1, (ifoo,)) - comp.registerHandler(_factory_2, (ifoo,)) - @implementer(ifoo) - class Bar(object): - pass - bar = Bar() - comp.handle(bar) - self.assertEqual(_called_1, [bar]) - self.assertEqual(_called_2, [bar]) - - -class UnhashableComponentsTests(ComponentsTests): - - def _getTargetClass(self): - # Mimic what pyramid does to create an unhashable - # registry - class Components(super(UnhashableComponentsTests, self)._getTargetClass(), dict): - pass - return Components - -# Test _getUtilityProvided, _getAdapterProvided, _getAdapterRequired via their -# callers (Component.registerUtility, Component.registerAdapter). - - -class UtilityRegistrationTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.registry import UtilityRegistration - return UtilityRegistration - - def _makeOne(self, component=None, factory=None): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - class _Registry(object): - def __repr__(self): - return '_REGISTRY' - registry = _Registry() - name = u'name' - doc = 'DOCSTRING' - klass = self._getTargetClass() - return (klass(registry, ifoo, name, component, doc, factory), - registry, - name, - ) - - def test_class_conforms_to_IUtilityRegistration(self): - from zope.interface.verify import verifyClass - from zope.interface.interfaces import IUtilityRegistration - verifyClass(IUtilityRegistration, self._getTargetClass()) - - def test_instance_conforms_to_IUtilityRegistration(self): - from zope.interface.verify import verifyObject - from zope.interface.interfaces import IUtilityRegistration - ur, _, _ = self._makeOne() - verifyObject(IUtilityRegistration, ur) - - def test___repr__(self): - class _Component(object): - __name__ = 'TEST' - _component = _Component() - ur, _registry, _name = self._makeOne(_component) - self.assertEqual(repr(ur), - "UtilityRegistration(_REGISTRY, IFoo, %r, TEST, None, 'DOCSTRING')" - % (_name)) - - def test___repr___provided_wo_name(self): - class _Component(object): - def __repr__(self): - return 'TEST' - _component = _Component() - ur, _registry, _name = self._makeOne(_component) - ur.provided = object() - self.assertEqual(repr(ur), - "UtilityRegistration(_REGISTRY, None, %r, TEST, None, 'DOCSTRING')" - % (_name)) - - def test___repr___component_wo_name(self): - class _Component(object): - def __repr__(self): - return 'TEST' - _component = _Component() - ur, _registry, _name = self._makeOne(_component) - ur.provided = object() - self.assertEqual(repr(ur), - "UtilityRegistration(_REGISTRY, None, %r, TEST, None, 'DOCSTRING')" - % (_name)) - - def test___hash__(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - self.assertEqual(ur.__hash__(), id(ur)) - - def test___eq___identity(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - self.assertTrue(ur == ur) - - def test___eq___hit(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component) - self.assertTrue(ur == ur2) - - def test___eq___miss(self): - _component = object() - _component2 = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component2) - self.assertFalse(ur == ur2) - - def test___ne___identity(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - self.assertFalse(ur != ur) - - def test___ne___hit(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component) - self.assertFalse(ur != ur2) - - def test___ne___miss(self): - _component = object() - _component2 = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component2) - self.assertTrue(ur != ur2) - - def test___lt___identity(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - self.assertFalse(ur < ur) - - def test___lt___hit(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component) - self.assertFalse(ur < ur2) - - def test___lt___miss(self): - _component = object() - _component2 = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component2) - ur2.name = _name + '2' - self.assertTrue(ur < ur2) - - def test___le___identity(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - self.assertTrue(ur <= ur) - - def test___le___hit(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component) - self.assertTrue(ur <= ur2) - - def test___le___miss(self): - _component = object() - _component2 = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component2) - ur2.name = _name + '2' - self.assertTrue(ur <= ur2) - - def test___gt___identity(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - self.assertFalse(ur > ur) - - def test___gt___hit(self): - _component = object() - _component2 = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component2) - ur2.name = _name + '2' - self.assertTrue(ur2 > ur) - - def test___gt___miss(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component) - self.assertFalse(ur2 > ur) - - def test___ge___identity(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - self.assertTrue(ur >= ur) - - def test___ge___miss(self): - _component = object() - _component2 = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component2) - ur2.name = _name + '2' - self.assertFalse(ur >= ur2) - - def test___ge___hit(self): - _component = object() - ur, _registry, _name = self._makeOne(_component) - ur2, _, _ = self._makeOne(_component) - ur2.name = _name + '2' - self.assertTrue(ur2 >= ur) - - -class AdapterRegistrationTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.registry import AdapterRegistration - return AdapterRegistration - - def _makeOne(self, component=None): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Registry(object): - def __repr__(self): - return '_REGISTRY' - registry = _Registry() - name = u'name' - doc = 'DOCSTRING' - klass = self._getTargetClass() - return (klass(registry, (ibar,), ifoo, name, component, doc), - registry, - name, - ) - - def test_class_conforms_to_IAdapterRegistration(self): - from zope.interface.verify import verifyClass - from zope.interface.interfaces import IAdapterRegistration - verifyClass(IAdapterRegistration, self._getTargetClass()) - - def test_instance_conforms_to_IAdapterRegistration(self): - from zope.interface.verify import verifyObject - from zope.interface.interfaces import IAdapterRegistration - ar, _, _ = self._makeOne() - verifyObject(IAdapterRegistration, ar) - - def test___repr__(self): - class _Component(object): - __name__ = 'TEST' - _component = _Component() - ar, _registry, _name = self._makeOne(_component) - self.assertEqual(repr(ar), - ("AdapterRegistration(_REGISTRY, [IBar], IFoo, %r, TEST, " - + "'DOCSTRING')") % (_name)) - - def test___repr___provided_wo_name(self): - class _Component(object): - def __repr__(self): - return 'TEST' - _component = _Component() - ar, _registry, _name = self._makeOne(_component) - ar.provided = object() - self.assertEqual(repr(ar), - ("AdapterRegistration(_REGISTRY, [IBar], None, %r, TEST, " - + "'DOCSTRING')") % (_name)) - - def test___repr___component_wo_name(self): - class _Component(object): - def __repr__(self): - return 'TEST' - _component = _Component() - ar, _registry, _name = self._makeOne(_component) - ar.provided = object() - self.assertEqual(repr(ar), - ("AdapterRegistration(_REGISTRY, [IBar], None, %r, TEST, " - + "'DOCSTRING')") % (_name)) - - def test___hash__(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - self.assertEqual(ar.__hash__(), id(ar)) - - def test___eq___identity(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - self.assertTrue(ar == ar) - - def test___eq___hit(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component) - self.assertTrue(ar == ar2) - - def test___eq___miss(self): - _component = object() - _component2 = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component2) - self.assertFalse(ar == ar2) - - def test___ne___identity(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - self.assertFalse(ar != ar) - - def test___ne___miss(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component) - self.assertFalse(ar != ar2) - - def test___ne___hit_component(self): - _component = object() - _component2 = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component2) - self.assertTrue(ar != ar2) - - def test___ne___hit_provided(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ibaz = IFoo('IBaz') - _component = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component) - ar2.provided = ibaz - self.assertTrue(ar != ar2) - - def test___ne___hit_required(self): - from zope.interface.declarations import InterfaceClass - class IFoo(InterfaceClass): - pass - ibaz = IFoo('IBaz') - _component = object() - _component2 = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component2) - ar2.required = (ibaz,) - self.assertTrue(ar != ar2) - - def test___lt___identity(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - self.assertFalse(ar < ar) - - def test___lt___hit(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component) - self.assertFalse(ar < ar2) - - def test___lt___miss(self): - _component = object() - _component2 = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component2) - ar2.name = _name + '2' - self.assertTrue(ar < ar2) - - def test___le___identity(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - self.assertTrue(ar <= ar) - - def test___le___hit(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component) - self.assertTrue(ar <= ar2) - - def test___le___miss(self): - _component = object() - _component2 = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component2) - ar2.name = _name + '2' - self.assertTrue(ar <= ar2) - - def test___gt___identity(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - self.assertFalse(ar > ar) - - def test___gt___hit(self): - _component = object() - _component2 = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component2) - ar2.name = _name + '2' - self.assertTrue(ar2 > ar) - - def test___gt___miss(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component) - self.assertFalse(ar2 > ar) - - def test___ge___identity(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - self.assertTrue(ar >= ar) - - def test___ge___miss(self): - _component = object() - _component2 = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component2) - ar2.name = _name + '2' - self.assertFalse(ar >= ar2) - - def test___ge___hit(self): - _component = object() - ar, _registry, _name = self._makeOne(_component) - ar2, _, _ = self._makeOne(_component) - ar2.name = _name + '2' - self.assertTrue(ar2 >= ar) - - -class SubscriptionRegistrationTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.registry import SubscriptionRegistration - return SubscriptionRegistration - - def _makeOne(self, component=None): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - ibar = IFoo('IBar') - class _Registry(object): - def __repr__(self): # pragma: no cover - return '_REGISTRY' - registry = _Registry() - name = u'name' - doc = 'DOCSTRING' - klass = self._getTargetClass() - return (klass(registry, (ibar,), ifoo, name, component, doc), - registry, - name, - ) - - def test_class_conforms_to_ISubscriptionAdapterRegistration(self): - from zope.interface.verify import verifyClass - from zope.interface.interfaces import ISubscriptionAdapterRegistration - verifyClass(ISubscriptionAdapterRegistration, self._getTargetClass()) - - def test_instance_conforms_to_ISubscriptionAdapterRegistration(self): - from zope.interface.verify import verifyObject - from zope.interface.interfaces import ISubscriptionAdapterRegistration - sar, _, _ = self._makeOne() - verifyObject(ISubscriptionAdapterRegistration, sar) - - -class HandlerRegistrationTests(unittest.TestCase): - - def _getTargetClass(self): - from zope.interface.registry import HandlerRegistration - return HandlerRegistration - - def _makeOne(self, component=None): - from zope.interface.declarations import InterfaceClass - - class IFoo(InterfaceClass): - pass - ifoo = IFoo('IFoo') - class _Registry(object): - def __repr__(self): - return '_REGISTRY' - registry = _Registry() - name = u'name' - doc = 'DOCSTRING' - klass = self._getTargetClass() - return (klass(registry, (ifoo,), name, component, doc), - registry, - name, - ) - - def test_class_conforms_to_IHandlerRegistration(self): - from zope.interface.verify import verifyClass - from zope.interface.interfaces import IHandlerRegistration - verifyClass(IHandlerRegistration, self._getTargetClass()) - - def test_instance_conforms_to_IHandlerRegistration(self): - from zope.interface.verify import verifyObject - from zope.interface.interfaces import IHandlerRegistration - hr, _, _ = self._makeOne() - verifyObject(IHandlerRegistration, hr) - - def test_properties(self): - def _factory(context): - raise NotImplementedError() - hr, _, _ = self._makeOne(_factory) - self.assertTrue(hr.handler is _factory) - self.assertTrue(hr.factory is hr.handler) - self.assertTrue(hr.provided is None) - - def test___repr___factory_w_name(self): - class _Factory(object): - __name__ = 'TEST' - hr, _registry, _name = self._makeOne(_Factory()) - self.assertEqual(repr(hr), - ("HandlerRegistration(_REGISTRY, [IFoo], %r, TEST, " - + "'DOCSTRING')") % (_name)) - - def test___repr___factory_wo_name(self): - class _Factory(object): - def __repr__(self): - return 'TEST' - hr, _registry, _name = self._makeOne(_Factory()) - self.assertEqual(repr(hr), - ("HandlerRegistration(_REGISTRY, [IFoo], %r, TEST, " - + "'DOCSTRING')") % (_name)) - -class PersistentAdapterRegistry(VerifyingAdapterRegistry): - - def __getstate__(self): - state = self.__dict__.copy() - for k in list(state): - if k in self._delegated or k.startswith('_v'): - state.pop(k) - state.pop('ro', None) - return state - - def __setstate__(self, state): - bases = state.pop('__bases__', ()) - self.__dict__.update(state) - self._createLookup() - self.__bases__ = bases - self._v_lookup.changed(self) - -class PersistentComponents(Components): - # Mimic zope.component.persistentregistry.PersistentComponents: - # we should be picklalable, but not persistent.Persistent ourself. - - def _init_registries(self): - self.adapters = PersistentAdapterRegistry() - self.utilities = PersistentAdapterRegistry() - -class PersistentDictComponents(PersistentComponents, dict): - # Like Pyramid's Registry, we subclass Components and dict - pass - - -class PersistentComponentsDict(dict, PersistentComponents): - # Like the above, but inheritance is flipped - def __init__(self, name): - dict.__init__(self) - PersistentComponents.__init__(self, name) - -class TestPersistentComponents(unittest.TestCase): - - def _makeOne(self): - return PersistentComponents('test') - - def _check_equality_after_pickle(self, made): - pass - - def test_pickles_empty(self): - import pickle - comp = self._makeOne() - pickle.dumps(comp) - comp2 = pickle.loads(pickle.dumps(comp)) - - self.assertEqual(comp2.__name__, 'test') - - def test_pickles_with_utility_registration(self): - import pickle - comp = self._makeOne() - utility = object() - comp.registerUtility( - utility, - Interface) - - self.assertIs(utility, - comp.getUtility(Interface)) - - comp2 = pickle.loads(pickle.dumps(comp)) - self.assertEqual(comp2.__name__, 'test') - - # The utility is still registered - self.assertIsNotNone(comp2.getUtility(Interface)) - - # We can register another one - comp2.registerUtility( - utility, - Interface) - self.assertIs(utility, - comp2.getUtility(Interface)) - - self._check_equality_after_pickle(comp2) - - -class TestPersistentDictComponents(TestPersistentComponents): - - def _getTargetClass(self): - return PersistentDictComponents - - def _makeOne(self): - comp = self._getTargetClass()(name='test') - comp['key'] = 42 - return comp - - def _check_equality_after_pickle(self, made): - self.assertIn('key', made) - self.assertEqual(made['key'], 42) - -class TestPersistentComponentsDict(TestPersistentDictComponents): - - def _getTargetClass(self): - return PersistentComponentsDict - -class _Monkey(object): - # context-manager for replacing module names in the scope of a test. - def __init__(self, module, **kw): - self.module = module - self.to_restore = dict([(key, getattr(module, key)) for key in kw]) - for key, value in kw.items(): - setattr(module, key, value) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - for key, value in self.to_restore.items(): - setattr(self.module, key, value) diff --git a/lib/zope/interface/tests/test_ro.py b/lib/zope/interface/tests/test_ro.py deleted file mode 100644 index 0756c6d..0000000 --- a/lib/zope/interface/tests/test_ro.py +++ /dev/null @@ -1,115 +0,0 @@ -############################################################################## -# -# Copyright (c) 2014 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Resolution ordering utility tests""" -import unittest - - -class Test__mergeOrderings(unittest.TestCase): - - def _callFUT(self, orderings): - from zope.interface.ro import _mergeOrderings - return _mergeOrderings(orderings) - - def test_empty(self): - self.assertEqual(self._callFUT([]), []) - - def test_single(self): - self.assertEqual(self._callFUT(['a', 'b', 'c']), ['a', 'b', 'c']) - - def test_w_duplicates(self): - self.assertEqual(self._callFUT([['a'], ['b', 'a']]), ['b', 'a']) - - def test_suffix_across_multiple_duplicats(self): - O1 = ['x', 'y', 'z'] - O2 = ['q', 'z'] - O3 = [1, 3, 5] - O4 = ['z'] - self.assertEqual(self._callFUT([O1, O2, O3, O4]), - ['x', 'y', 'q', 1, 3, 5, 'z']) - - -class Test__flatten(unittest.TestCase): - - def _callFUT(self, ob): - from zope.interface.ro import _flatten - return _flatten(ob) - - def test_w_empty_bases(self): - class Foo(object): - pass - foo = Foo() - foo.__bases__ = () - self.assertEqual(self._callFUT(foo), [foo]) - - def test_w_single_base(self): - class Foo(object): - pass - self.assertEqual(self._callFUT(Foo), [Foo, object]) - - def test_w_bases(self): - class Foo(object): - pass - class Bar(Foo): - pass - self.assertEqual(self._callFUT(Bar), [Bar, Foo, object]) - - def test_w_diamond(self): - class Foo(object): - pass - class Bar(Foo): - pass - class Baz(Foo): - pass - class Qux(Bar, Baz): - pass - self.assertEqual(self._callFUT(Qux), - [Qux, Bar, Foo, object, Baz, Foo, object]) - - -class Test_ro(unittest.TestCase): - - def _callFUT(self, ob): - from zope.interface.ro import ro - return ro(ob) - - def test_w_empty_bases(self): - class Foo(object): - pass - foo = Foo() - foo.__bases__ = () - self.assertEqual(self._callFUT(foo), [foo]) - - def test_w_single_base(self): - class Foo(object): - pass - self.assertEqual(self._callFUT(Foo), [Foo, object]) - - def test_w_bases(self): - class Foo(object): - pass - class Bar(Foo): - pass - self.assertEqual(self._callFUT(Bar), [Bar, Foo, object]) - - def test_w_diamond(self): - class Foo(object): - pass - class Bar(Foo): - pass - class Baz(Foo): - pass - class Qux(Bar, Baz): - pass - self.assertEqual(self._callFUT(Qux), - [Qux, Bar, Baz, Foo, object]) diff --git a/lib/zope/interface/tests/test_sorting.py b/lib/zope/interface/tests/test_sorting.py deleted file mode 100644 index 73613d0..0000000 --- a/lib/zope/interface/tests/test_sorting.py +++ /dev/null @@ -1,47 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Test interface sorting -""" - -import unittest - -from zope.interface import Interface - -class I1(Interface): pass -class I2(I1): pass -class I3(I1): pass -class I4(Interface): pass -class I5(I4): pass -class I6(I2): pass - - -class Test(unittest.TestCase): - - def test(self): - l = [I1, I3, I5, I6, I4, I2] - l.sort() - self.assertEqual(l, [I1, I2, I3, I4, I5, I6]) - - def test_w_None(self): - l = [I1, None, I3, I5, I6, I4, I2] - l.sort() - self.assertEqual(l, [I1, I2, I3, I4, I5, I6, None]) - - def test_w_equal_names(self): - # interfaces with equal names but different modules should sort by - # module name - from zope.interface.tests.m1 import I1 as m1_I1 - l = [I1, m1_I1] - l.sort() - self.assertEqual(l, [m1_I1, I1]) diff --git a/lib/zope/interface/tests/test_verify.py b/lib/zope/interface/tests/test_verify.py deleted file mode 100644 index 5ad8bff..0000000 --- a/lib/zope/interface/tests/test_verify.py +++ /dev/null @@ -1,582 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -""" zope.interface.verify unit tests -""" -import unittest - - -class Test_verifyClass(unittest.TestCase): - - def _callFUT(self, iface, klass): - from zope.interface.verify import verifyClass - return verifyClass(iface, klass) - - def test_class_doesnt_implement(self): - from zope.interface import Interface - from zope.interface.exceptions import DoesNotImplement - - class ICurrent(Interface): - pass - - class Current(object): - pass - - self.assertRaises(DoesNotImplement, self._callFUT, ICurrent, Current) - - def test_class_doesnt_implement_but_classImplements_later(self): - from zope.interface import Interface - from zope.interface import classImplements - - class ICurrent(Interface): - pass - - class Current(object): - pass - - classImplements(Current, ICurrent) - - self._callFUT(ICurrent, Current) - - def test_class_doesnt_have_required_method_simple(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenImplementation - - class ICurrent(Interface): - def method(): pass - - @implementer(ICurrent) - class Current(object): - pass - - self.assertRaises(BrokenImplementation, - self._callFUT, ICurrent, Current) - - def test_class_has_required_method_simple(self): - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - def method(): pass - - @implementer(ICurrent) - class Current(object): - - def method(self): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_class_doesnt_have_required_method_derived(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenImplementation - - class IBase(Interface): - def method(): - pass - - class IDerived(IBase): - pass - - @implementer(IDerived) - class Current(object): - pass - - self.assertRaises(BrokenImplementation, - self._callFUT, IDerived, Current) - - def test_class_has_required_method_derived(self): - from zope.interface import Interface - from zope.interface import implementer - - class IBase(Interface): - def method(): - pass - - class IDerived(IBase): - pass - - @implementer(IDerived) - class Current(object): - - def method(self): - raise NotImplementedError() - - self._callFUT(IDerived, Current) - - def test_method_takes_wrong_arg_names_but_OK(self): - # We no longer require names to match. - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - - def method(a): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, b): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_method_takes_not_enough_args(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenMethodImplementation - - class ICurrent(Interface): - - def method(a): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self): - raise NotImplementedError() - - self.assertRaises(BrokenMethodImplementation, - self._callFUT, ICurrent, Current) - - def test_method_doesnt_take_required_starargs(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenMethodImplementation - - class ICurrent(Interface): - - def method(*args): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self): - raise NotImplementedError() - - self.assertRaises(BrokenMethodImplementation, - self._callFUT, ICurrent, Current) - - def test_method_doesnt_take_required_only_kwargs(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenMethodImplementation - - class ICurrent(Interface): - - def method(**kw): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self): - raise NotImplementedError() - - self.assertRaises(BrokenMethodImplementation, - self._callFUT, ICurrent, Current) - - def test_method_takes_extra_arg(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenMethodImplementation - - class ICurrent(Interface): - - def method(a): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, a, b): - raise NotImplementedError() - - self.assertRaises(BrokenMethodImplementation, - self._callFUT, ICurrent, Current) - - def test_method_takes_extra_arg_with_default(self): - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - - def method(a): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, a, b=None): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_method_takes_only_positional_args(self): - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - - def method(a): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, *args): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_method_takes_only_kwargs(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenMethodImplementation - - class ICurrent(Interface): - - def method(a): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, **kw): - raise NotImplementedError() - - self.assertRaises(BrokenMethodImplementation, - self._callFUT, ICurrent, Current) - - def test_method_takes_extra_starargs(self): - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - - def method(a): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, a, *args): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_method_takes_extra_starargs_and_kwargs(self): - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - - def method(a): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, a, *args, **kw): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_method_doesnt_take_required_positional_and_starargs(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenMethodImplementation - - class ICurrent(Interface): - - def method(a, *args): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, a): - raise NotImplementedError() - - self.assertRaises(BrokenMethodImplementation, - self._callFUT, ICurrent, Current) - - def test_method_takes_required_positional_and_starargs(self): - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - - def method(a, *args): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, a, *args): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_method_takes_only_starargs(self): - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - - def method(a, *args): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, *args): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_method_takes_required_kwargs(self): - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - - def method(**kwargs): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, **kw): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_method_takes_positional_plus_required_starargs(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenMethodImplementation - - class ICurrent(Interface): - - def method(*args): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, a, *args): - raise NotImplementedError() - - self.assertRaises(BrokenMethodImplementation, - self._callFUT, ICurrent, Current) - - - def test_method_doesnt_take_required_kwargs(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenMethodImplementation - - class ICurrent(Interface): - - def method(**kwargs): - pass - - @implementer(ICurrent) - class Current(object): - - def method(self, a): - raise NotImplementedError() - - self.assertRaises(BrokenMethodImplementation, - self._callFUT, ICurrent, Current) - - - def test_class_has_method_for_iface_attr(self): - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - attr = Attribute("The foo Attribute") - - @implementer(ICurrent) - class Current: - - def attr(self): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - - def test_class_has_nonmethod_for_method(self): - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenMethodImplementation - - class ICurrent(Interface): - def method(): - pass - - @implementer(ICurrent) - class Current: - method = 1 - - self.assertRaises(BrokenMethodImplementation, - self._callFUT, ICurrent, Current) - - def test_class_has_attribute_for_attribute(self): - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - attr = Attribute("The foo Attribute") - - @implementer(ICurrent) - class Current: - - attr = 1 - - self._callFUT(ICurrent, Current) - - def test_class_misses_attribute_for_attribute(self): - # This check *passes* for verifyClass - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface import implementer - - class ICurrent(Interface): - attr = Attribute("The foo Attribute") - - @implementer(ICurrent) - class Current: - pass - - self._callFUT(ICurrent, Current) - - def test_w_callable_non_func_method(self): - from zope.interface.interface import Method - from zope.interface import Interface - from zope.interface import implementer - - class QuasiMethod(Method): - def __call__(self, *args, **kw): - raise NotImplementedError() - - class QuasiCallable(object): - def __call__(self, *args, **kw): - raise NotImplementedError() - - class ICurrent(Interface): - attr = QuasiMethod('This is callable') - - @implementer(ICurrent) - class Current: - attr = QuasiCallable() - - self._callFUT(ICurrent, Current) - - - def test_w_decorated_method(self): - from zope.interface import Interface - from zope.interface import implementer - - def decorator(func): - # this is, in fact, zope.proxy.non_overridable - return property(lambda self: func.__get__(self)) - - class ICurrent(Interface): - - def method(a): - pass - - @implementer(ICurrent) - class Current(object): - - @decorator - def method(self, a): - raise NotImplementedError() - - self._callFUT(ICurrent, Current) - -class Test_verifyObject(Test_verifyClass): - - def _callFUT(self, iface, target): - from zope.interface.verify import verifyObject - if isinstance(target, (type, type(OldSkool))): - target = target() - return verifyObject(iface, target) - - def test_class_misses_attribute_for_attribute(self): - # This check *fails* for verifyObject - from zope.interface import Attribute - from zope.interface import Interface - from zope.interface import implementer - from zope.interface.exceptions import BrokenImplementation - - class ICurrent(Interface): - attr = Attribute("The foo Attribute") - - @implementer(ICurrent) - class Current: - pass - - self.assertRaises(BrokenImplementation, - self._callFUT, ICurrent, Current) - - def test_module_hit(self): - from zope.interface.tests.idummy import IDummyModule - from zope.interface.tests import dummy - - self._callFUT(IDummyModule, dummy) - - def test_module_miss(self): - from zope.interface import Interface - from zope.interface.tests import dummy - from zope.interface.exceptions import DoesNotImplement - - # same name, different object - class IDummyModule(Interface): - pass - - self.assertRaises(DoesNotImplement, - self._callFUT, IDummyModule, dummy) - - def test_staticmethod_hit_on_class(self): - from zope.interface import Interface - from zope.interface import provider - from zope.interface.verify import verifyObject - - class IFoo(Interface): - - def bar(a, b): - "The bar method" - - @provider(IFoo) - class Foo(object): - - @staticmethod - def bar(a, b): - raise AssertionError("We're never actually called") - - # Don't use self._callFUT, we don't want to instantiate the - # class. - verifyObject(IFoo, Foo) - -class OldSkool: - pass diff --git a/lib/zope/interface/verify.py b/lib/zope/interface/verify.py deleted file mode 100644 index 62bb64c..0000000 --- a/lib/zope/interface/verify.py +++ /dev/null @@ -1,123 +0,0 @@ -############################################################################## -# -# Copyright (c) 2001, 2002 Zope Foundation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Verify interface implementations -""" -from zope.interface.exceptions import BrokenImplementation, DoesNotImplement -from zope.interface.exceptions import BrokenMethodImplementation -from types import FunctionType, MethodType -from zope.interface.interface import fromMethod, fromFunction, Method -import sys - -# This will be monkey-patched when running under Zope 2, so leave this -# here: -MethodTypes = (MethodType, ) - - -def _verify(iface, candidate, tentative=0, vtype=None): - """Verify that 'candidate' might correctly implements 'iface'. - - This involves: - - o Making sure the candidate defines all the necessary methods - - o Making sure the methods have the correct signature - - o Making sure the candidate asserts that it implements the interface - - Note that this isn't the same as verifying that the class does - implement the interface. - - If optional tentative is true, suppress the "is implemented by" test. - """ - - if vtype == 'c': - tester = iface.implementedBy - else: - tester = iface.providedBy - - if not tentative and not tester(candidate): - raise DoesNotImplement(iface) - - # Here the `desc` is either an `Attribute` or `Method` instance - for name, desc in iface.namesAndDescriptions(1): - try: - attr = getattr(candidate, name) - except AttributeError: - if (not isinstance(desc, Method)) and vtype == 'c': - # We can't verify non-methods on classes, since the - # class may provide attrs in it's __init__. - continue - - raise BrokenImplementation(iface, name) - - if not isinstance(desc, Method): - # If it's not a method, there's nothing else we can test - continue - - if isinstance(attr, FunctionType): - if sys.version_info[0] >= 3 and isinstance(candidate, type) and vtype == 'c': - # This is an "unbound method" in Python 3. - # Only unwrap this if we're verifying implementedBy; - # otherwise we can unwrap @staticmethod on classes that directly - # provide an interface. - meth = fromFunction(attr, iface, name=name, - imlevel=1) - else: - # Nope, just a normal function - meth = fromFunction(attr, iface, name=name) - elif (isinstance(attr, MethodTypes) - and type(attr.__func__) is FunctionType): - meth = fromMethod(attr, iface, name) - elif isinstance(attr, property) and vtype == 'c': - # We without an instance we cannot be sure it's not a - # callable. - continue - else: - if not callable(attr): - raise BrokenMethodImplementation(name, "Not a method") - # sigh, it's callable, but we don't know how to introspect it, so - # we have to give it a pass. - continue - - # Make sure that the required and implemented method signatures are - # the same. - desc = desc.getSignatureInfo() - meth = meth.getSignatureInfo() - - mess = _incompat(desc, meth) - if mess: - raise BrokenMethodImplementation(name, mess) - - return True - -def verifyClass(iface, candidate, tentative=0): - return _verify(iface, candidate, tentative, vtype='c') - -def verifyObject(iface, candidate, tentative=0): - return _verify(iface, candidate, tentative, vtype='o') - -def _incompat(required, implemented): - #if (required['positional'] != - # implemented['positional'][:len(required['positional'])] - # and implemented['kwargs'] is None): - # return 'imlementation has different argument names' - if len(implemented['required']) > len(required['required']): - return 'implementation requires too many arguments' - if ((len(implemented['positional']) < len(required['positional'])) - and not implemented['varargs']): - return "implementation doesn't allow enough arguments" - if required['kwargs'] and not implemented['kwargs']: - return "implementation doesn't support keyword arguments" - if required['varargs'] and not implemented['varargs']: - return "implementation doesn't support variable arguments" diff --git a/requirements.txt b/requirements.txt index 401cf01..760382e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,4 @@ requests>=2.20.1 geoip2>=2.9.0 influxdb>=5.2.0 -schedule>=0.5.0 -configparser>=3.5.0 -datetime>=4.3 -typing>=3.6.6 +schedule>=0.5.0 \ No newline at end of file diff --git a/varken.py b/varken.py index ecf0461..2d68b86 100644 --- a/varken.py +++ b/varken.py @@ -1,7 +1,3 @@ -from sys import path -from os.path import abspath, dirname, join -path.insert(0, abspath(join(dirname(__file__), '..', 'lib'))) - import schedule import threading from time import sleep From ea122ddb2bcf73f0b655916cfe58b64771e84661 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 2 Dec 2018 14:52:55 -0600 Subject: [PATCH 058/120] modified for venv --- README.md | 13 ++++++++----- varken.service | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 359e01b..3321a9b 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,17 @@ Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), <center><img width="800" src="https://i.imgur.com/av8e0HP.png"></center> ## Quick Setup (Varken Alpha) -1. Clone the repository `git clone https://github.com/DirtyCajunRice/grafana-scripts.git /opt/Varken` +1. Clone the repository `sudo git clone https://github.com/DirtyCajunRice/grafana-scripts.git /opt/Varken` +2. Change ownership to current user `sudo chown $USER -R /opt/Varken/` 1. Switch to the testing branch `cd /opt/Varken && git checkout refactor-project` -1. Install requirements `/usr/bin/python -m pip install -r requirements.txt` +1. Create venv in project `/usr/bin/python3 -m venv varken-venv` +1. Install requirements `/opt/Varken/varken-venv/bin/python -m pip install -r requirements.txt` 2. Make a copy of `varken.example.ini` to `varken.ini` in the `data` folder - `cp data/varken.example.ini data/varken.ini` + `cp /opt/Varken/data/varken.example.ini /opt/Varken/data/varken.ini` 3. Make the appropriate changes to `varken.ini` - `nano data/varken.ini` -4. Copy the systemd file `cp varken.service /etc/systemd/system/` + `nano /opt/Varken/data/varken.ini` +4. Copy the systemd file `sudo cp /opt/Varken/varken.service /etc/systemd/system/` +1. Edit the username of the systemd file `sudo sed -i "s/username/$USER" /etc/systemd/system/varken.service` 5. start the service and enable it `systemctl start varken && systemctl enable varken` 5. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. At a minimum, you will need the plex database. 6. Install `grafana-cli plugins install grafana-worldmap-panel` diff --git a/varken.service b/varken.service index 0b181a2..5e9bfec 100644 --- a/varken.service +++ b/varken.service @@ -4,9 +4,9 @@ After=network-online.target [Service] Type=simple -User=root +User=username WorkingDirectory=/opt/Varken -ExecStart=/usr/bin/python3 /opt/Varken/varken.py +ExecStart=/opt/Varken/varken-venv/bin/python /opt/Varken/varken.py Restart=always [Install] From 4c4955e47430ca849c9aea67dd21c4867c000e00 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 2 Dec 2018 21:59:35 -0600 Subject: [PATCH 059/120] fixed file path search --- Varken/iniparser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 9a21e11..0b71629 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,6 +1,6 @@ import sys import configparser -from os.path import abspath, join +from os.path import abspath, dirname, join from Varken.helpers import OmbiServer, TautulliServer, SonarrServer, InfluxServer, RadarrServer @@ -28,7 +28,7 @@ class INIParser(object): self.parse_opts() def read_file(self): - file_path = abspath(join('.', 'data', 'varken.ini')) + file_path = abspath(join(dirname(__file__), '..', 'data', 'varken.ini')) with open(file_path) as config_ini: self.config.read_file(config_ini) From 4f1ec17538001f1057dd5b8382dd0eda5e4d898f Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 2 Dec 2018 21:59:46 -0600 Subject: [PATCH 060/120] testing hashit --- Varken/helpers.py | 11 +++++++++++ Varken/sonarr.py | 34 ++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Varken/helpers.py b/Varken/helpers.py index e464b89..b441e3b 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -1,6 +1,7 @@ import os import time import tarfile +import hashlib import geoip2.database from typing import NamedTuple from os.path import abspath, join @@ -65,6 +66,7 @@ class Movie(NamedTuple): website: str = None id: int = None + class Queue(NamedTuple): movie: dict = None series: dict = None @@ -95,6 +97,7 @@ class SonarrServer(NamedTuple): queue: bool = False queue_run_seconds: int = 30 + class RadarrServer(NamedTuple): id: int = None url: str = None @@ -343,6 +346,7 @@ def geoip_download(): tar.extract(files, abspath(join('.', 'data'))) os.remove(tar_dbfile) + def geo_lookup(ipaddress): dbfile = abspath(join('.', 'data', 'GeoLite2-City.mmdb')) @@ -360,3 +364,10 @@ def geo_lookup(ipaddress): reader = geoip2.database.Reader(dbfile) return reader.city(ipaddress) + + +def hashit(string): + encoded = string.encode() + hashed = hashlib.md5(encoded).hexdigest() + + return hashed diff --git a/Varken/sonarr.py b/Varken/sonarr.py index ae09c2e..6409af5 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -2,7 +2,7 @@ from requests import Session from datetime import datetime, timezone, date, timedelta from Varken.logger import logging -from Varken.helpers import TVShow, Queue +from Varken.helpers import TVShow, Queue, hashit class SonarrAPI(object): @@ -38,20 +38,23 @@ class SonarrAPI(object): missing.append((show.series['title'], sxe, show.airDate, show.title, show.id)) for series_title, sxe, air_date, episode_title, sonarr_id in missing: + hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) influx_payload.append( { "measurement": "Sonarr", "tags": { "type": "Missing", "sonarrId": sonarr_id, - "server": self.server.id - }, - "time": self.now, - "fields": { + "server": self.server.id, "name": series_title, "epname": episode_title, "sxe": sxe, "airs": air_date + }, + "time": self.now, + "fields": { + "hash": hash_id + } } ) @@ -77,21 +80,23 @@ class SonarrAPI(object): air_days.append((show.series['title'], show.hasFile, sxe, show.title, show.airDate, show.id)) for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: + hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) influx_payload.append( { "measurement": "Sonarr", "tags": { "type": "Future", "sonarrId": sonarr_id, - "server": self.server.id - }, - "time": self.now, - "fields": { + "server": self.server.id, "name": series_title, "epname": episode_title, "sxe": sxe, "airs": air_date, "downloaded": dl_status + }, + "time": self.now, + "fields": { + "hash": hash_id } } ) @@ -120,22 +125,23 @@ class SonarrAPI(object): protocol_id, sxe, show.id)) for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue: + hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) influx_payload.append( { "measurement": "Sonarr", "tags": { "type": "Queue", "sonarrId": sonarr_id, - "server": self.server.id - - }, - "time": self.now, - "fields": { + "server": self.server.id, "name": series_title, "epname": episode_title, "sxe": sxe, "protocol": protocol, "protocol_id": protocol_id + }, + "time": self.now, + "fields": { + "hash": hash_id } } ) From 8ef4b7fd0f167f2721dd03eb1de93ebd40182c87 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 2 Dec 2018 22:19:05 -0600 Subject: [PATCH 061/120] added hashing to radarr --- Varken/radarr.py | 18 +++++++++++------- Varken/sonarr.py | 6 +++++- Varken/tautulli.py | 1 + 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Varken/radarr.py b/Varken/radarr.py index 091bb77..2e170a1 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -2,7 +2,7 @@ from requests import Session from datetime import datetime, timezone from Varken.logger import logging -from Varken.helpers import Movie, Queue +from Varken.helpers import Movie, Queue, hashit class RadarrAPI(object): @@ -34,6 +34,7 @@ class RadarrAPI(object): missing.append((movie_name, ma, movie.tmdbId)) for title, ma, mid in missing: + hash_id = hashit('{}{}{}'.format(self.server.id, title, mid)) influx_payload.append( { "measurement": "Radarr", @@ -41,11 +42,12 @@ class RadarrAPI(object): "Missing": True, "Missing_Available": ma, "tmdbId": mid, - "server": self.server.id + "server": self.server.id, + "name": title }, "time": self.now, "fields": { - "name": title + "hash": hash_id } } ) @@ -77,20 +79,22 @@ class RadarrAPI(object): protocol_id, queue_item.id)) for movie, quality, protocol, protocol_id, qid in queue: + hash_id = hashit('{}{}{}'.format(self.server.id, movie, quality)) influx_payload.append( { "measurement": "Radarr", "tags": { "type": "Queue", "tmdbId": qid, - "server": self.server.id - }, - "time": self.now, - "fields": { + "server": self.server.id, "name": movie, "quality": quality, "protocol": protocol, "protocol_id": protocol_id + }, + "time": self.now, + "fields": { + "hash": hash_id } } ) diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 6409af5..70277fc 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -77,7 +77,11 @@ class SonarrAPI(object): for show in tv_shows: sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) - air_days.append((show.series['title'], show.hasFile, sxe, show.title, show.airDate, show.id)) + if show.hasFile: + downloaded = 1 + else: + downloaded = 0 + air_days.append((show.series['title'], downloaded, sxe, show.title, show.airDate, show.id)) for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) diff --git a/Varken/tautulli.py b/Varken/tautulli.py index 62f42a1..6f9cd97 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -1,6 +1,7 @@ from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError from requests import Session + from Varken.helpers import TautulliStream, geo_lookup from Varken.logger import logging From 76427e582ef7ac964f13fe7b94399578d1f7ea09 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 2 Dec 2018 22:25:14 -0600 Subject: [PATCH 062/120] added hashing to tautulli --- Varken/tautulli.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Varken/tautulli.py b/Varken/tautulli.py index 6f9cd97..b001ebc 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError from requests import Session -from Varken.helpers import TautulliStream, geo_lookup +from Varken.helpers import TautulliStream, geo_lookup, hashit from Varken.logger import logging @@ -103,6 +103,8 @@ class TautulliAPI(object): if session.platform == 'Roku': product_version = session.product_version.split('-')[0] + hash_id = hashit('{}{}{}{}'.format(session.session_id, session.session_key, session.username, + session.full_title)) influx_payload.append( { "measurement": "Tautulli", @@ -135,8 +137,7 @@ class TautulliAPI(object): }, "time": self.now, "fields": { - "session_id": session.session_id, - "session_key": session.session_key + "hash": hash_id } } ) From d7d4535dc31e6833acf65240d203b4c27c0bad20 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Mon, 3 Dec 2018 12:38:10 -0600 Subject: [PATCH 063/120] since init uses only one server, moved headers/params to init --- Varken/dbmanager.py | 1 + Varken/ombi.py | 1 + Varken/radarr.py | 10 ++++------ Varken/sonarr.py | 12 ++++-------- Varken/tautulli.py | 3 +-- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/Varken/dbmanager.py b/Varken/dbmanager.py index de5dc1d..aa73534 100644 --- a/Varken/dbmanager.py +++ b/Varken/dbmanager.py @@ -1,5 +1,6 @@ from influxdb import InfluxDBClient + class DBManager(object): def __init__(self, server): self.server = server diff --git a/Varken/ombi.py b/Varken/ombi.py index 3981250..5ca3d7f 100644 --- a/Varken/ombi.py +++ b/Varken/ombi.py @@ -4,6 +4,7 @@ from datetime import datetime, timezone from Varken.helpers import OmbiRequestCounts from Varken.logger import logging + class OmbiAPI(object): def __init__(self, server, dbmanager): self.now = datetime.now(timezone.utc).astimezone().isoformat() diff --git a/Varken/radarr.py b/Varken/radarr.py index 2e170a1..9bff5c9 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -12,16 +12,16 @@ class RadarrAPI(object): self.server = server # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = Session() + self.session.headers = {'X-Api-Key': self.server.api_key} @logging def get_missing(self): endpoint = '/api/movie' self.now = datetime.now(timezone.utc).astimezone().isoformat() influx_payload = [] - missing = [] - headers = {'X-Api-Key': self.server.api_key} - get = self.session.get(self.server.url + endpoint, headers=headers, verify=self.server.verify_ssl).json() + + get = self.session.get(self.server.url + endpoint, verify=self.server.verify_ssl).json() movies = [Movie(**movie) for movie in get] for movie in movies: @@ -59,10 +59,8 @@ class RadarrAPI(object): endpoint = '/api/queue' self.now = datetime.now(timezone.utc).astimezone().isoformat() influx_payload = [] - queue = [] - headers = {'X-Api-Key': self.server.api_key} - get = self.session.get(self.server.url + endpoint, headers=headers, verify=self.server.verify_ssl).json() + get = self.session.get(self.server.url + endpoint, verify=self.server.verify_ssl).json() for movie in get: movie['movie'] = Movie(**movie['movie']) download_queue = [Queue(**movie) for movie in get] diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 70277fc..60e73fb 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -14,6 +14,7 @@ class SonarrAPI(object): self.server = server # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = Session() + self.session.headers = {'X-Api-Key': self.server.api_key} self.session.params = {'pageSize': 1000} @logging @@ -24,10 +25,8 @@ class SonarrAPI(object): params = {'start': last_days, 'end': self.today} influx_payload = [] missing = [] - headers = {'X-Api-Key': self.server.api_key} - get = self.session.get(self.server.url + endpoint, params=params, headers=headers, - verify=self.server.verify_ssl).json() + get = self.session.get(self.server.url + endpoint, params=params, verify=self.server.verify_ssl).json() # Iteratively create a list of TVShow Objects from response json tv_shows = [TVShow(**show) for show in get] @@ -68,11 +67,9 @@ class SonarrAPI(object): future = str(date.today() + timedelta(days=self.server.future_days)) influx_payload = [] air_days = [] - headers = {'X-Api-Key': self.server.api_key} params = {'start': self.today, 'end': future} - get = self.session.get(self.server.url + endpoint, params=params, headers=headers, - verify=self.server.verify_ssl).json() + get = self.session.get(self.server.url + endpoint, params=params, verify=self.server.verify_ssl).json() tv_shows = [TVShow(**show) for show in get] for show in tv_shows: @@ -113,9 +110,8 @@ class SonarrAPI(object): endpoint = '/api/queue' self.now = datetime.now(timezone.utc).astimezone().isoformat() queue = [] - headers = {'X-Api-Key': self.server.api_key} - get = self.session.get(self.server.url + endpoint, headers=headers, verify=self.server.verify_ssl).json() + get = self.session.get(self.server.url + endpoint, verify=self.server.verify_ssl).json() download_queue = [Queue(**show) for show in get] for show in download_queue: diff --git a/Varken/tautulli.py b/Varken/tautulli.py index b001ebc..149599c 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -13,6 +13,7 @@ class TautulliAPI(object): self.dbmanager = dbmanager self.server = server self.session = Session() + self.session.params['apikey'] = self.server.api_key self.endpoint = '/api/v2' @logging @@ -20,7 +21,6 @@ class TautulliAPI(object): self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} influx_payload = [] - params['apikey'] = self.server.api_key g = self.session.get(self.server.url + self.endpoint, params=params, verify=self.server.verify_ssl) get = g.json()['response']['data'] @@ -51,7 +51,6 @@ class TautulliAPI(object): self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} influx_payload = [] - params['apikey'] = self.server.api_key g = self.session.get(self.server.url + self.endpoint, params=params, verify=self.server.verify_ssl) get = g.json()['response']['data']['sessions'] sessions = [TautulliStream(**session) for session in get] From 15a757d2e6537eea5d005bf8cc8d4a027b9d89ed Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Mon, 3 Dec 2018 13:43:41 -0600 Subject: [PATCH 064/120] changed default verify to false --- data/varken.example.ini | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/varken.example.ini b/data/varken.example.ini index c51272b..27aa60f 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -23,7 +23,7 @@ url = tautulli.domain.tld fallback_ip = 0.0.0.0 apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = true +verify_ssl = false get_activity = true get_activity_run_seconds = 30 get_sessions = true @@ -33,7 +33,7 @@ get_sessions_run_seconds = 30 url = sonarr1.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = true +verify_ssl = false missing_days = 7 missing_days_run_seconds = 300 future_days = 1 @@ -45,7 +45,7 @@ queue_run_seconds = 300 url = sonarr2.domain.tld apikey = yyyyyyyyyyyyyyyy ssl = false -verify_ssl = true +verify_ssl = false missing_days = 7 missing_days_run_seconds = 300 future_days = 1 @@ -57,7 +57,7 @@ queue_run_seconds = 300 url = radarr1.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = true +verify_ssl = false queue = true queue_run_seconds = 300 get_missing = true @@ -67,7 +67,7 @@ get_missing_run_seconds = 300 url = radarr2.domain.tld apikey = yyyyyyyyyyyyyyyy ssl = false -verify_ssl = true +verify_ssl = false queue = true queue_run_seconds = 300 get_missing = true @@ -77,7 +77,7 @@ get_missing_run_seconds = 300 url = ombi.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = true +verify_ssl = false get_request_type_counts = true request_type_run_seconds = 300 get_request_total_counts = true From 1148f986bfd9ad3516208c99e3fd149dfe123634 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Mon, 3 Dec 2018 14:28:15 -0600 Subject: [PATCH 065/120] added initial run --- varken.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/varken.py b/varken.py index 2d68b86..db95e0f 100644 --- a/varken.py +++ b/varken.py @@ -52,6 +52,9 @@ if __name__ == "__main__": if server.request_total_counts: schedule.every(server.request_total_run_seconds).seconds.do(threaded, OMBI.get_total_requests) + # Run all on startup + schedule.run_all() + while True: schedule.run_pending() sleep(1) From 62a2134833ff2e556102ec4342e2c83165ed4222 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Mon, 3 Dec 2018 14:37:29 -0600 Subject: [PATCH 066/120] added docker config to readme --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 3321a9b..b259edd 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,20 @@ Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), 6. Install `grafana-cli plugins install grafana-worldmap-panel` 7. TODO:: Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. +### Docker + +Repo is included in [si0972/grafana-scripts-docker](https://github.com/si0972/grafana-scripts-docker/tree/varken) + +<details><summary>Example</summary> +<p> + +``` +docker create \ + --name=grafana-scripts \ + -v <path to data>:/Scripts \ + -e plex=true \ + -e PGID=<gid> -e PUID=<uid> \ + si0972/grafana-scripts:varken +``` +</p> +</details> \ No newline at end of file From 8907e9c8edda50b37008b27bf3a63d15509e7e30 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Mon, 3 Dec 2018 14:43:24 -0600 Subject: [PATCH 067/120] added docker config to readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index b259edd..2cd18a9 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ Repo is included in [si0972/grafana-scripts-docker](https://github.com/si0972/gr docker create \ --name=grafana-scripts \ -v <path to data>:/Scripts \ - -e plex=true \ -e PGID=<gid> -e PUID=<uid> \ si0972/grafana-scripts:varken ``` From 7277ee14f91936d0bb98b439e236d415df3a77c1 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Mon, 3 Dec 2018 22:56:12 -0600 Subject: [PATCH 068/120] added connection_handler for bad requests --- Varken/helpers.py | 30 ++++++++++++++++++++++++++++++ Varken/iniparser.py | 10 ++++++++++ Varken/ombi.py | 22 +++++++++++++++++----- Varken/radarr.py | 19 +++++++++++++++---- Varken/sonarr.py | 25 ++++++++++++++++++++----- Varken/tautulli.py | 25 +++++++++++++++++++------ 6 files changed, 111 insertions(+), 20 deletions(-) diff --git a/Varken/helpers.py b/Varken/helpers.py index b441e3b..57afbab 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -4,7 +4,9 @@ import tarfile import hashlib import geoip2.database from typing import NamedTuple +from json.decoder import JSONDecodeError from os.path import abspath, join +from requests.exceptions import InvalidSchema, SSLError from urllib.request import urlretrieve @@ -371,3 +373,31 @@ def hashit(string): hashed = hashlib.md5(encoded).hexdigest() return hashed + + +def connection_handler(session, request, verify): + s = session + r = request + v = verify + return_json = False + + try: + get = s.send(r, verify=v) + if get.status_code == 401: + print("Your api key is incorrect for {}".format(r.url)) + elif get.status_code == 404: + print("This url doesnt even resolve: {}".format(r.url)) + elif get.status_code == 200: + try: + return_json = get.json() + except JSONDecodeError: + print("No JSON response... BORKED! Let us know in discord") + + except InvalidSchema: + print("You added http(s):// in the config file. Don't do that.") + + except SSLError as e: + print("Either your host is unreachable or you have an ssl issue.") + print("The issue was: {}".format(e)) + + return return_json diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 0b71629..be390e4 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -60,6 +60,8 @@ class INIParser(object): apikey = self.config.get(sonarr_section, 'apikey') scheme = 'https://' if self.config.getboolean(sonarr_section, 'ssl') else 'http://' verify_ssl = self.config.getboolean(sonarr_section, 'verify_ssl') + if scheme != 'https://': + verify_ssl = False queue = self.config.getboolean(sonarr_section, 'queue') missing_days = self.config.getint(sonarr_section, 'missing_days') future_days = self.config.getint(sonarr_section, 'future_days') @@ -90,6 +92,8 @@ class INIParser(object): apikey = self.config.get(radarr_section, 'apikey') scheme = 'https://' if self.config.getboolean(radarr_section, 'ssl') else 'http://' verify_ssl = self.config.getboolean(radarr_section, 'verify_ssl') + if scheme != 'https://': + verify_ssl = False queue = self.config.getboolean(radarr_section, 'queue') queue_run_seconds = self.config.getint(radarr_section, 'queue_run_seconds') get_missing = self.config.getboolean(radarr_section, 'get_missing') @@ -118,6 +122,8 @@ class INIParser(object): apikey = self.config.get(tautulli_section, 'apikey') scheme = 'https://' if self.config.getboolean(tautulli_section, 'ssl') else 'http://' verify_ssl = self.config.getboolean(tautulli_section, 'verify_ssl') + if scheme != 'https://': + verify_ssl = False get_activity = self.config.getboolean(tautulli_section, 'get_activity') get_activity_run_seconds = self.config.getint(tautulli_section, 'get_activity_run_seconds') get_sessions = self.config.getboolean(tautulli_section, 'get_sessions') @@ -144,6 +150,8 @@ class INIParser(object): apikey = self.config.get(ombi_section, 'apikey') scheme = 'https://' if self.config.getboolean(ombi_section, 'ssl') else 'http://' verify_ssl = self.config.getboolean(ombi_section, 'verify_ssl') + if scheme != 'https://': + verify_ssl = False request_type_counts = self.config.getboolean(ombi_section, 'get_request_type_counts') request_type_run_seconds = self.config.getint(ombi_section, 'request_type_run_seconds') request_total_counts = self.config.getboolean(ombi_section, 'get_request_total_counts') @@ -161,6 +169,8 @@ class INIParser(object): password = self.config.get('asa', 'password') scheme = 'https://' if self.config.getboolean('asa', 'ssl') else 'http://' verify_ssl = self.config.getboolean('asa', 'verify_ssl') + if scheme != 'https://': + verify_ssl = False db_name = self.config.get('asa', 'influx_db') self.asa = (scheme + url, username, password, verify_ssl, db_name) diff --git a/Varken/ombi.py b/Varken/ombi.py index 5ca3d7f..50cbca5 100644 --- a/Varken/ombi.py +++ b/Varken/ombi.py @@ -1,8 +1,8 @@ -from requests import Session +from requests import Session, Request from datetime import datetime, timezone -from Varken.helpers import OmbiRequestCounts from Varken.logger import logging +from Varken.helpers import OmbiRequestCounts, connection_handler class OmbiAPI(object): @@ -19,8 +19,14 @@ class OmbiAPI(object): self.now = datetime.now(timezone.utc).astimezone().isoformat() tv_endpoint = '/api/v1/Request/tv' movie_endpoint = "/api/v1/Request/movie" - get_tv = self.session.get(self.server.url + tv_endpoint, verify=self.server.verify_ssl).json() - get_movie = self.session.get(self.server.url + movie_endpoint, verify=self.server.verify_ssl).json() + + tv_req = self.session.prepare_request(Request('GET', self.server.url + tv_endpoint)) + movie_req = self.session.prepare_request(Request('GET', self.server.url + movie_endpoint)) + get_tv = connection_handler(self.session, tv_req, self.server.verify_ssl) + get_movie = connection_handler(self.session, movie_req, self.server.verify_ssl) + + if not all([get_tv, get_movie]): + return movie_requests = len(get_movie) tv_requests = len(get_tv) @@ -46,7 +52,13 @@ class OmbiAPI(object): def get_request_counts(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() endpoint = '/api/v1/Request/count' - get = self.session.get(self.server.url + endpoint, verify=self.server.verify_ssl).json() + + req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + requests = OmbiRequestCounts(**get) influx_payload = [ { diff --git a/Varken/radarr.py b/Varken/radarr.py index 9bff5c9..1ec3e1e 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -1,8 +1,8 @@ -from requests import Session +from requests import Session, Request from datetime import datetime, timezone from Varken.logger import logging -from Varken.helpers import Movie, Queue, hashit +from Varken.helpers import Movie, Queue, hashit, connection_handler class RadarrAPI(object): @@ -21,7 +21,12 @@ class RadarrAPI(object): influx_payload = [] missing = [] - get = self.session.get(self.server.url + endpoint, verify=self.server.verify_ssl).json() + req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + movies = [Movie(**movie) for movie in get] for movie in movies: @@ -60,7 +65,13 @@ class RadarrAPI(object): self.now = datetime.now(timezone.utc).astimezone().isoformat() influx_payload = [] queue = [] - get = self.session.get(self.server.url + endpoint, verify=self.server.verify_ssl).json() + + req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + for movie in get: movie['movie'] = Movie(**movie['movie']) download_queue = [Queue(**movie) for movie in get] diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 60e73fb..cbcab0f 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -1,8 +1,8 @@ -from requests import Session +from requests import Session, Request from datetime import datetime, timezone, date, timedelta from Varken.logger import logging -from Varken.helpers import TVShow, Queue, hashit +from Varken.helpers import TVShow, Queue, hashit, connection_handler class SonarrAPI(object): @@ -26,7 +26,12 @@ class SonarrAPI(object): influx_payload = [] missing = [] - get = self.session.get(self.server.url + endpoint, params=params, verify=self.server.verify_ssl).json() + req = self.session.prepare_request(Request('GET', self.server.url + endpoint, params=params)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + # Iteratively create a list of TVShow Objects from response json tv_shows = [TVShow(**show) for show in get] @@ -69,7 +74,12 @@ class SonarrAPI(object): air_days = [] params = {'start': self.today, 'end': future} - get = self.session.get(self.server.url + endpoint, params=params, verify=self.server.verify_ssl).json() + req = self.session.prepare_request(Request('GET', self.server.url + endpoint, params=params)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + tv_shows = [TVShow(**show) for show in get] for show in tv_shows: @@ -111,7 +121,12 @@ class SonarrAPI(object): self.now = datetime.now(timezone.utc).astimezone().isoformat() queue = [] - get = self.session.get(self.server.url + endpoint, verify=self.server.verify_ssl).json() + req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + download_queue = [Queue(**show) for show in get] for show in download_queue: diff --git a/Varken/tautulli.py b/Varken/tautulli.py index 149599c..fe2629a 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -1,9 +1,9 @@ from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError -from requests import Session +from requests import Session, Request -from Varken.helpers import TautulliStream, geo_lookup, hashit from Varken.logger import logging +from Varken.helpers import TautulliStream, geo_lookup, hashit, connection_handler class TautulliAPI(object): @@ -21,8 +21,14 @@ class TautulliAPI(object): self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} influx_payload = [] - g = self.session.get(self.server.url + self.endpoint, params=params, verify=self.server.verify_ssl) - get = g.json()['response']['data'] + + req = self.session.prepare_request(Request('GET', self.server.url + self.endpoint, params=params)) + g = connection_handler(self.session, req, self.server.verify_ssl) + + if not g: + return + else: + get = g['response']['data'] influx_payload.append( { @@ -51,8 +57,15 @@ class TautulliAPI(object): self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} influx_payload = [] - g = self.session.get(self.server.url + self.endpoint, params=params, verify=self.server.verify_ssl) - get = g.json()['response']['data']['sessions'] + + req = self.session.prepare_request(Request('GET', self.server.url + self.endpoint, params=params)) + g = connection_handler(self.session, req, self.server.verify_ssl) + + if not g: + return + else: + get = g['response']['data']['sessions'] + sessions = [TautulliStream(**session) for session in get] for session in sessions: From 73d92e1e1bd8e248601e81c052c435ef56b174e6 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Mon, 3 Dec 2018 23:47:46 -0600 Subject: [PATCH 069/120] added ability to define data folder --- README.md | 5 +++-- Varken/iniparser.py | 15 ++++++++++----- varken.py | 28 +++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2cd18a9..1201638 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Varken Dutch for PIG. PIG is an Acronym for Plex/InfluxDB/Grafana -Varken is a standalone commmand-line utility that will aggregate date -from the plex ecosystem into influxdb to be displayed in grafana +Varken is a standalone command-line utility to aggregate data +from the plex ecosystem into InfluxDB. Examples use Grafana for a +frontend Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), [Python3](https://www.python.org/downloads/), [InfluxDB](https://docs.influxdata.com/influxdb/v1.5/introduction/installation/) diff --git a/Varken/iniparser.py b/Varken/iniparser.py index be390e4..dad64e5 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,12 +1,14 @@ import sys import configparser -from os.path import abspath, dirname, join +from sys import exit +from os.path import join, exists from Varken.helpers import OmbiServer, TautulliServer, SonarrServer, InfluxServer, RadarrServer class INIParser(object): - def __init__(self): + def __init__(self, data_folder): self.config = configparser.ConfigParser() + self.data_folder = data_folder self.influx_server = InfluxServer() @@ -28,9 +30,12 @@ class INIParser(object): self.parse_opts() def read_file(self): - file_path = abspath(join(dirname(__file__), '..', 'data', 'varken.ini')) - with open(file_path) as config_ini: - self.config.read_file(config_ini) + file_path = join(self.data_folder, 'varken.ini') + if exists(file_path): + with open(file_path) as config_ini: + self.config.read_file(config_ini) + else: + exit("You do not have a varken.ini file in {}".format(self.data_folder)) def parse_opts(self): self.read_file() diff --git a/varken.py b/varken.py index db95e0f..d0d7c8c 100644 --- a/varken.py +++ b/varken.py @@ -1,6 +1,10 @@ import schedule import threading +from sys import exit from time import sleep +from os import access, R_OK +from os.path import isdir, abspath, dirname, join +from argparse import ArgumentParser, RawTextHelpFormatter from Varken.iniparser import INIParser from Varken.sonarr import SonarrAPI @@ -15,7 +19,29 @@ def threaded(job): if __name__ == "__main__": - CONFIG = INIParser() + parser = ArgumentParser(prog='Varken', + description='Command-line utility to aggregate data from the plex ecosystem into InfluxDB', + formatter_class=RawTextHelpFormatter) + + parser.add_argument("-d", "--data-folder", help='Define an alternate data folder location') + parser.add_argument("-l", "--log-level", choices=['info', 'error', 'debug'], help='Not yet implemented') + + opts = parser.parse_args() + + DATA_FOLDER = abspath(join(dirname(__file__), 'data')) + + if opts.data_folder: + ARG_FOLDER = opts.data_folder + + if isdir(ARG_FOLDER): + DATA_FOLDER = ARG_FOLDER + if not access(ARG_FOLDER, R_OK): + exit("Read permission error for {}".format(ARG_FOLDER)) + else: + exit("{} does not exist".format(ARG_FOLDER)) + + + CONFIG = INIParser(DATA_FOLDER) DBMANAGER = DBManager(CONFIG.influx_server) if CONFIG.sonarr_enabled: From 3993f6e9e086c196e860a6b1a134b21acb09e91e Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Tue, 4 Dec 2018 00:10:36 -0600 Subject: [PATCH 070/120] split helpers and structures --- Varken/helpers.py | 328 ------------------------------------------- Varken/iniparser.py | 2 +- Varken/ombi.py | 3 +- Varken/radarr.py | 3 +- Varken/sonarr.py | 3 +- Varken/structures.py | 328 +++++++++++++++++++++++++++++++++++++++++++ Varken/tautulli.py | 3 +- 7 files changed, 337 insertions(+), 333 deletions(-) create mode 100644 Varken/structures.py diff --git a/Varken/helpers.py b/Varken/helpers.py index 57afbab..eeda689 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -3,340 +3,12 @@ import time import tarfile import hashlib import geoip2.database -from typing import NamedTuple from json.decoder import JSONDecodeError from os.path import abspath, join from requests.exceptions import InvalidSchema, SSLError from urllib.request import urlretrieve -class TVShow(NamedTuple): - seriesId: int = None - episodeFileId: int = None - seasonNumber: int = None - episodeNumber: int = None - title: str = None - airDate: str = None - airDateUtc: str = None - overview: str = None - episodeFile: dict = None - hasFile: bool = None - monitored: bool = None - unverifiedSceneNumbering: bool = None - absoluteEpisodeNumber: int = None - series: dict = None - id: int = None - - -class Movie(NamedTuple): - title: str = None - alternativeTitles: list = None - secondaryYearSourceId: int = None - sortTitle: str = None - sizeOnDisk: int = None - status: str = None - overview: str = None - inCinemas: str = None - images: list = None - downloaded: bool = None - year: int = None - secondaryYear: str = None - hasFile: bool = None - youTubeTrailerId: str = None - studio: str = None - path: str = None - profileId: int = None - pathState: str = None - monitored: bool = None - minimumAvailability: str = None - isAvailable: bool = None - folderName: str = None - runtime: int = None - lastInfoSync: str = None - cleanTitle: str = None - imdbId: str = None - tmdbId: int = None - titleSlug: str = None - genres: list = None - tags: list = None - added: str = None - ratings: dict = None - movieFile: dict = None - qualityProfileId: int = None - physicalRelease: str = None - physicalReleaseNote: str = None - website: str = None - id: int = None - - -class Queue(NamedTuple): - movie: dict = None - series: dict = None - episode: dict = None - quality: dict = None - size: float = None - title: str = None - sizeleft: float = None - timeleft: str = None - estimatedCompletionTime: str = None - status: str = None - trackedDownloadStatus: str = None - statusMessages: list = None - downloadId: str = None - protocol: str = None - id: int = None - - -class SonarrServer(NamedTuple): - id: int = None - url: str = None - api_key: str = None - verify_ssl: bool = False - missing_days: int = 0 - missing_days_run_seconds: int = 30 - future_days: int = 0 - future_days_run_seconds: int = 30 - queue: bool = False - queue_run_seconds: int = 30 - - -class RadarrServer(NamedTuple): - id: int = None - url: str = None - api_key: str = None - verify_ssl: bool = False - queue: bool = False - queue_run_seconds: int = 30 - get_missing: bool = False - get_missing_run_seconds: int = 30 - - -class OmbiServer(NamedTuple): - id: int = None - url: str = None - api_key: str = None - verify_ssl: bool = False - request_type_counts: bool = False - request_type_run_seconds: int = 30 - request_total_counts: bool = False - request_total_run_seconds: int = 30 - - -class TautulliServer(NamedTuple): - id: int = None - url: str = None - fallback_ip: str = None - api_key: str = None - verify_ssl: bool = None - get_activity: bool = False - get_activity_run_seconds: int = 30 - get_sessions: bool = False - get_sessions_run_seconds: int = 30 - - -class InfluxServer(NamedTuple): - url: str = 'localhost' - port: int = 8086 - username: str = 'root' - password: str = 'root' - - -class OmbiRequestCounts(NamedTuple): - pending: int = 0 - approved: int = 0 - available: int = 0 - - -class TautulliStream(NamedTuple): - rating: str = None - transcode_width: str = None - labels: list = None - stream_bitrate: str = None - bandwidth: str = None - optimized_version: int = None - video_language: str = None - parent_rating_key: str = None - rating_key: str = None - platform_version: str = None - transcode_hw_decoding: int = None - thumb: str = None - title: str = None - video_codec_level: str = None - tagline: str = None - last_viewed_at: str = None - audio_sample_rate: str = None - user_rating: str = None - platform: str = None - collections: list = None - location: str = None - transcode_container: str = None - audio_channel_layout: str = None - local: str = None - stream_subtitle_format: str = None - stream_video_ref_frames: str = None - transcode_hw_encode_title: str = None - stream_container_decision: str = None - audience_rating: str = None - full_title: str = None - ip_address: str = None - subtitles: int = None - stream_subtitle_language: str = None - channel_stream: int = None - video_bitrate: str = None - is_allow_sync: int = None - stream_video_bitrate: str = None - summary: str = None - stream_audio_decision: str = None - aspect_ratio: str = None - audio_bitrate_mode: str = None - transcode_hw_decode_title: str = None - stream_audio_channel_layout: str = None - deleted_user: int = None - library_name: str = None - art: str = None - stream_video_resolution: str = None - video_profile: str = None - sort_title: str = None - stream_video_codec_level: str = None - stream_video_height: str = None - year: str = None - stream_duration: str = None - stream_audio_channels: str = None - video_language_code: str = None - transcode_key: str = None - transcode_throttled: int = None - container: str = None - stream_audio_bitrate: str = None - user: str = None - selected: int = None - product_version: str = None - subtitle_location: str = None - transcode_hw_requested: int = None - video_height: str = None - state: str = None - is_restricted: int = None - email: str = None - stream_container: str = None - transcode_speed: str = None - video_bit_depth: str = None - stream_audio_sample_rate: str = None - grandparent_title: str = None - studio: str = None - transcode_decision: str = None - video_width: str = None - bitrate: str = None - machine_id: str = None - originally_available_at: str = None - video_frame_rate: str = None - synced_version_profile: str = None - friendly_name: str = None - audio_profile: str = None - optimized_version_title: str = None - platform_name: str = None - stream_video_language: str = None - keep_history: int = None - stream_audio_codec: str = None - stream_video_codec: str = None - grandparent_thumb: str = None - synced_version: int = None - transcode_hw_decode: str = None - user_thumb: str = None - stream_video_width: str = None - height: str = None - stream_subtitle_decision: str = None - audio_codec: str = None - parent_title: str = None - guid: str = None - audio_language_code: str = None - transcode_video_codec: str = None - transcode_audio_codec: str = None - stream_video_decision: str = None - user_id: int = None - transcode_height: str = None - transcode_hw_full_pipeline: int = None - throttled: str = None - quality_profile: str = None - width: str = None - live: int = None - stream_subtitle_forced: int = None - media_type: str = None - video_resolution: str = None - stream_subtitle_location: str = None - do_notify: int = None - video_ref_frames: str = None - stream_subtitle_language_code: str = None - audio_channels: str = None - stream_audio_language_code: str = None - optimized_version_profile: str = None - relay: int = None - duration: str = None - rating_image: str = None - is_home_user: int = None - is_admin: int = None - ip_address_public: str = None - allow_guest: int = None - transcode_audio_channels: str = None - stream_audio_channel_layout_: str = None - media_index: str = None - stream_video_framerate: str = None - transcode_hw_encode: str = None - grandparent_rating_key: str = None - original_title: str = None - added_at: str = None - banner: str = None - bif_thumb: str = None - parent_media_index: str = None - live_uuid: str = None - audio_language: str = None - stream_audio_bitrate_mode: str = None - username: str = None - subtitle_decision: str = None - children_count: str = None - updated_at: str = None - player: str = None - subtitle_format: str = None - file: str = None - file_size: str = None - session_key: str = None - id: str = None - subtitle_container: str = None - genres: list = None - stream_video_language_code: str = None - indexes: int = None - video_decision: str = None - stream_audio_language: str = None - writers: list = None - actors: list = None - progress_percent: str = None - audio_decision: str = None - subtitle_forced: int = None - profile: str = None - product: str = None - view_offset: str = None - type: str = None - audience_rating_image: str = None - audio_bitrate: str = None - section_id: str = None - stream_subtitle_codec: str = None - subtitle_codec: str = None - video_codec: str = None - device: str = None - stream_video_bit_depth: str = None - video_framerate: str = None - transcode_hw_encoding: int = None - transcode_protocol: str = None - shared_libraries: list = None - stream_aspect_ratio: str = None - content_rating: str = None - session_id: str = None - directors: list = None - parent_thumb: str = None - subtitle_language_code: str = None - transcode_progress: int = None - subtitle_language: str = None - stream_subtitle_container: str = None - - def geoip_download(): tar_dbfile = abspath(join('.', 'data', 'GeoLite2-City.tar.gz')) url = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz' diff --git a/Varken/iniparser.py b/Varken/iniparser.py index dad64e5..6820bd2 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -2,7 +2,7 @@ import sys import configparser from sys import exit from os.path import join, exists -from Varken.helpers import OmbiServer, TautulliServer, SonarrServer, InfluxServer, RadarrServer +from Varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer class INIParser(object): diff --git a/Varken/ombi.py b/Varken/ombi.py index 50cbca5..87de969 100644 --- a/Varken/ombi.py +++ b/Varken/ombi.py @@ -2,7 +2,8 @@ from requests import Session, Request from datetime import datetime, timezone from Varken.logger import logging -from Varken.helpers import OmbiRequestCounts, connection_handler +from Varken.helpers import connection_handler +from Varken.structures import OmbiRequestCounts class OmbiAPI(object): diff --git a/Varken/radarr.py b/Varken/radarr.py index 1ec3e1e..ba7f050 100644 --- a/Varken/radarr.py +++ b/Varken/radarr.py @@ -2,7 +2,8 @@ from requests import Session, Request from datetime import datetime, timezone from Varken.logger import logging -from Varken.helpers import Movie, Queue, hashit, connection_handler +from Varken.helpers import hashit, connection_handler +from Varken.structures import Movie, Queue class RadarrAPI(object): diff --git a/Varken/sonarr.py b/Varken/sonarr.py index cbcab0f..bcda5e5 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -2,7 +2,8 @@ from requests import Session, Request from datetime import datetime, timezone, date, timedelta from Varken.logger import logging -from Varken.helpers import TVShow, Queue, hashit, connection_handler +from Varken.helpers import hashit, connection_handler +from Varken.structures import Queue, TVShow class SonarrAPI(object): diff --git a/Varken/structures.py b/Varken/structures.py new file mode 100644 index 0000000..d089c35 --- /dev/null +++ b/Varken/structures.py @@ -0,0 +1,328 @@ +from typing import NamedTuple + + +class Queue(NamedTuple): + movie: dict = None + series: dict = None + episode: dict = None + quality: dict = None + size: float = None + title: str = None + sizeleft: float = None + timeleft: str = None + estimatedCompletionTime: str = None + status: str = None + trackedDownloadStatus: str = None + statusMessages: list = None + downloadId: str = None + protocol: str = None + id: int = None + + +class SonarrServer(NamedTuple): + id: int = None + url: str = None + api_key: str = None + verify_ssl: bool = False + missing_days: int = 0 + missing_days_run_seconds: int = 30 + future_days: int = 0 + future_days_run_seconds: int = 30 + queue: bool = False + queue_run_seconds: int = 30 + + +class RadarrServer(NamedTuple): + id: int = None + url: str = None + api_key: str = None + verify_ssl: bool = False + queue: bool = False + queue_run_seconds: int = 30 + get_missing: bool = False + get_missing_run_seconds: int = 30 + + +class OmbiServer(NamedTuple): + id: int = None + url: str = None + api_key: str = None + verify_ssl: bool = False + request_type_counts: bool = False + request_type_run_seconds: int = 30 + request_total_counts: bool = False + request_total_run_seconds: int = 30 + + +class TautulliServer(NamedTuple): + id: int = None + url: str = None + fallback_ip: str = None + api_key: str = None + verify_ssl: bool = None + get_activity: bool = False + get_activity_run_seconds: int = 30 + get_sessions: bool = False + get_sessions_run_seconds: int = 30 + + +class InfluxServer(NamedTuple): + url: str = 'localhost' + port: int = 8086 + username: str = 'root' + password: str = 'root' + + +class OmbiRequestCounts(NamedTuple): + pending: int = 0 + approved: int = 0 + available: int = 0 + + +class TautulliStream(NamedTuple): + rating: str = None + transcode_width: str = None + labels: list = None + stream_bitrate: str = None + bandwidth: str = None + optimized_version: int = None + video_language: str = None + parent_rating_key: str = None + rating_key: str = None + platform_version: str = None + transcode_hw_decoding: int = None + thumb: str = None + title: str = None + video_codec_level: str = None + tagline: str = None + last_viewed_at: str = None + audio_sample_rate: str = None + user_rating: str = None + platform: str = None + collections: list = None + location: str = None + transcode_container: str = None + audio_channel_layout: str = None + local: str = None + stream_subtitle_format: str = None + stream_video_ref_frames: str = None + transcode_hw_encode_title: str = None + stream_container_decision: str = None + audience_rating: str = None + full_title: str = None + ip_address: str = None + subtitles: int = None + stream_subtitle_language: str = None + channel_stream: int = None + video_bitrate: str = None + is_allow_sync: int = None + stream_video_bitrate: str = None + summary: str = None + stream_audio_decision: str = None + aspect_ratio: str = None + audio_bitrate_mode: str = None + transcode_hw_decode_title: str = None + stream_audio_channel_layout: str = None + deleted_user: int = None + library_name: str = None + art: str = None + stream_video_resolution: str = None + video_profile: str = None + sort_title: str = None + stream_video_codec_level: str = None + stream_video_height: str = None + year: str = None + stream_duration: str = None + stream_audio_channels: str = None + video_language_code: str = None + transcode_key: str = None + transcode_throttled: int = None + container: str = None + stream_audio_bitrate: str = None + user: str = None + selected: int = None + product_version: str = None + subtitle_location: str = None + transcode_hw_requested: int = None + video_height: str = None + state: str = None + is_restricted: int = None + email: str = None + stream_container: str = None + transcode_speed: str = None + video_bit_depth: str = None + stream_audio_sample_rate: str = None + grandparent_title: str = None + studio: str = None + transcode_decision: str = None + video_width: str = None + bitrate: str = None + machine_id: str = None + originally_available_at: str = None + video_frame_rate: str = None + synced_version_profile: str = None + friendly_name: str = None + audio_profile: str = None + optimized_version_title: str = None + platform_name: str = None + stream_video_language: str = None + keep_history: int = None + stream_audio_codec: str = None + stream_video_codec: str = None + grandparent_thumb: str = None + synced_version: int = None + transcode_hw_decode: str = None + user_thumb: str = None + stream_video_width: str = None + height: str = None + stream_subtitle_decision: str = None + audio_codec: str = None + parent_title: str = None + guid: str = None + audio_language_code: str = None + transcode_video_codec: str = None + transcode_audio_codec: str = None + stream_video_decision: str = None + user_id: int = None + transcode_height: str = None + transcode_hw_full_pipeline: int = None + throttled: str = None + quality_profile: str = None + width: str = None + live: int = None + stream_subtitle_forced: int = None + media_type: str = None + video_resolution: str = None + stream_subtitle_location: str = None + do_notify: int = None + video_ref_frames: str = None + stream_subtitle_language_code: str = None + audio_channels: str = None + stream_audio_language_code: str = None + optimized_version_profile: str = None + relay: int = None + duration: str = None + rating_image: str = None + is_home_user: int = None + is_admin: int = None + ip_address_public: str = None + allow_guest: int = None + transcode_audio_channels: str = None + stream_audio_channel_layout_: str = None + media_index: str = None + stream_video_framerate: str = None + transcode_hw_encode: str = None + grandparent_rating_key: str = None + original_title: str = None + added_at: str = None + banner: str = None + bif_thumb: str = None + parent_media_index: str = None + live_uuid: str = None + audio_language: str = None + stream_audio_bitrate_mode: str = None + username: str = None + subtitle_decision: str = None + children_count: str = None + updated_at: str = None + player: str = None + subtitle_format: str = None + file: str = None + file_size: str = None + session_key: str = None + id: str = None + subtitle_container: str = None + genres: list = None + stream_video_language_code: str = None + indexes: int = None + video_decision: str = None + stream_audio_language: str = None + writers: list = None + actors: list = None + progress_percent: str = None + audio_decision: str = None + subtitle_forced: int = None + profile: str = None + product: str = None + view_offset: str = None + type: str = None + audience_rating_image: str = None + audio_bitrate: str = None + section_id: str = None + stream_subtitle_codec: str = None + subtitle_codec: str = None + video_codec: str = None + device: str = None + stream_video_bit_depth: str = None + video_framerate: str = None + transcode_hw_encoding: int = None + transcode_protocol: str = None + shared_libraries: list = None + stream_aspect_ratio: str = None + content_rating: str = None + session_id: str = None + directors: list = None + parent_thumb: str = None + subtitle_language_code: str = None + transcode_progress: int = None + subtitle_language: str = None + stream_subtitle_container: str = None + + +class TVShow(NamedTuple): + seriesId: int = None + episodeFileId: int = None + seasonNumber: int = None + episodeNumber: int = None + title: str = None + airDate: str = None + airDateUtc: str = None + overview: str = None + episodeFile: dict = None + hasFile: bool = None + monitored: bool = None + unverifiedSceneNumbering: bool = None + absoluteEpisodeNumber: int = None + series: dict = None + id: int = None + + +class Movie(NamedTuple): + title: str = None + alternativeTitles: list = None + secondaryYearSourceId: int = None + sortTitle: str = None + sizeOnDisk: int = None + status: str = None + overview: str = None + inCinemas: str = None + images: list = None + downloaded: bool = None + year: int = None + secondaryYear: str = None + hasFile: bool = None + youTubeTrailerId: str = None + studio: str = None + path: str = None + profileId: int = None + pathState: str = None + monitored: bool = None + minimumAvailability: str = None + isAvailable: bool = None + folderName: str = None + runtime: int = None + lastInfoSync: str = None + cleanTitle: str = None + imdbId: str = None + tmdbId: int = None + titleSlug: str = None + genres: list = None + tags: list = None + added: str = None + ratings: dict = None + movieFile: dict = None + qualityProfileId: int = None + physicalRelease: str = None + physicalReleaseNote: str = None + website: str = None + id: int = None \ No newline at end of file diff --git a/Varken/tautulli.py b/Varken/tautulli.py index fe2629a..b04a019 100644 --- a/Varken/tautulli.py +++ b/Varken/tautulli.py @@ -3,7 +3,8 @@ from geoip2.errors import AddressNotFoundError from requests import Session, Request from Varken.logger import logging -from Varken.helpers import TautulliStream, geo_lookup, hashit, connection_handler +from Varken.helpers import geo_lookup, hashit, connection_handler +from Varken.structures import TautulliStream class TautulliAPI(object): From cfb532794074350be481560b7947f129fa892ed4 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 11:03:39 -0500 Subject: [PATCH 071/120] Update README to simplify instructions --- README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 1201638..5291bf0 100644 --- a/README.md +++ b/README.md @@ -7,24 +7,22 @@ frontend Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), [Python3](https://www.python.org/downloads/), [InfluxDB](https://docs.influxdata.com/influxdb/v1.5/introduction/installation/) -<center><img width="800" src="https://i.imgur.com/av8e0HP.png"></center> +<p align="center"> +<img width="800" src="https://i.imgur.com/av8e0HP.png"> +</p> ## Quick Setup (Varken Alpha) 1. Clone the repository `sudo git clone https://github.com/DirtyCajunRice/grafana-scripts.git /opt/Varken` -2. Change ownership to current user `sudo chown $USER -R /opt/Varken/` -1. Switch to the testing branch `cd /opt/Varken && git checkout refactor-project` +1. Follow the systemd install instructions located in `varken.systemd` 1. Create venv in project `/usr/bin/python3 -m venv varken-venv` 1. Install requirements `/opt/Varken/varken-venv/bin/python -m pip install -r requirements.txt` -2. Make a copy of `varken.example.ini` to `varken.ini` in the `data` folder +1. Make a copy of `varken.example.ini` to `varken.ini` in the `data` folder `cp /opt/Varken/data/varken.example.ini /opt/Varken/data/varken.ini` -3. Make the appropriate changes to `varken.ini` - `nano /opt/Varken/data/varken.ini` -4. Copy the systemd file `sudo cp /opt/Varken/varken.service /etc/systemd/system/` -1. Edit the username of the systemd file `sudo sed -i "s/username/$USER" /etc/systemd/system/varken.service` -5. start the service and enable it `systemctl start varken && systemctl enable varken` -5. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. At a minimum, you will need the plex database. -6. Install `grafana-cli plugins install grafana-worldmap-panel` -7. TODO:: Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. +1. Make the appropriate changes to `varken.ini` + ie.`nano /opt/Varken/data/varken.ini` +1. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. +1. Install `grafana-cli plugins install grafana-worldmap-panel` +1. TODO:: Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. ### Docker @@ -41,4 +39,4 @@ docker create \ si0972/grafana-scripts:varken ``` </p> -</details> \ No newline at end of file +</details> From 55bde30aec9fced226589caee3c01dae63b22565 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 11:03:58 -0500 Subject: [PATCH 072/120] Rename and update systemd to have more info --- varken.service | 13 ------------- varken.systemd | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 13 deletions(-) delete mode 100644 varken.service create mode 100644 varken.systemd diff --git a/varken.service b/varken.service deleted file mode 100644 index 5e9bfec..0000000 --- a/varken.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=Varken - A data collection and graphing tool -After=network-online.target - -[Service] -Type=simple -User=username -WorkingDirectory=/opt/Varken -ExecStart=/opt/Varken/varken-venv/bin/python /opt/Varken/varken.py -Restart=always - -[Install] -WantedBy=multi-user.target diff --git a/varken.systemd b/varken.systemd new file mode 100644 index 0000000..a082ebb --- /dev/null +++ b/varken.systemd @@ -0,0 +1,48 @@ +# Varken - Command-line utility to aggregate data from the Plex ecosystem into InfluxDB. +# +# Service Unit file for systemd system manager +# +# INSTALLATION NOTES +# +# 1. Copy this file into your systemd service unit directory (often '/lib/systemd/system') +# and name it 'varken.service' with the following command: +# cp /opt/Varken/Varken/varken.systemd /lib/systemd/system/varken.service +# +# 2. Edit the new varken.service file with configuration settings as required. +# More details in the "CONFIGURATION NOTES" section shown below. +# +# 3. Enable boot-time autostart with the following commands: +# systemctl daemon-reload +# systemctl enable varken.service +# +# 4. Start now with the following command: +# systemctl start varken.service +# +# CONFIGURATION NOTES +# +# - The example settings in this file assume that you will run varken as user: varken +# - The example settings in this file assume that varken is installed to: /opt/Varken +# +# - To create this user and give it ownership of the Varken directory: +# Ubuntu/Debian: sudo addgroup varken && sudo adduser --system --no-create-home varken --ingroup varken +# CentOS/Fedora: sudo adduser --system --no-create-home varken +# sudo chown varken:varken -R /opt/Varken +# +# - Adjust User= and Group= to the user/group you want Varken to run as. +# +# - WantedBy= specifies which target (i.e. runlevel) to start Varken for. +# multi-user.target equates to runlevel 3 (multi-user text mode) +# graphical.target equates to runlevel 5 (multi-user X11 graphical mode) + +[Unit] +Description=Varken - A data collection and graphing tool +After=network-online.target + +[Service] +Type=simple +User=username +WorkingDirectory=/opt/Varken +ExecStart=/opt/Varken/varken-venv/bin/python /opt/Varken/varken.py + +[Install] +WantedBy=multi-user.target From 2e158212d24bac3fa2e4d59dc64b6103a97865b4 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 11:32:55 -0500 Subject: [PATCH 073/120] Change User/group and restart interval --- varken.systemd | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/varken.systemd b/varken.systemd index a082ebb..29e8606 100644 --- a/varken.systemd +++ b/varken.systemd @@ -37,12 +37,17 @@ [Unit] Description=Varken - A data collection and graphing tool After=network-online.target +StartLimitInterval=200 +StartLimitBurst=3 [Service] Type=simple -User=username +User=varken +Group=varken WorkingDirectory=/opt/Varken ExecStart=/opt/Varken/varken-venv/bin/python /opt/Varken/varken.py +Restart=always +RestartSec=30 [Install] WantedBy=multi-user.target From 8b2056ad66d85260af834debb4a3f7645012958f Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Tue, 4 Dec 2018 10:45:18 -0600 Subject: [PATCH 074/120] swapped capitalization --- varken.py => Varken.py | 14 +-- {Varken => varken}/__init__.py | 0 varken/dbmanager.py | 16 +++ varken/iniparser.py | 181 +++++++++++++++++++++++++++++++++ {Varken => varken}/ombi.py | 6 +- varken/radarr.py | 112 ++++++++++++++++++++ varken/sonarr.py | 165 ++++++++++++++++++++++++++++++ {Varken => varken}/tautulli.py | 6 +- 8 files changed, 487 insertions(+), 13 deletions(-) rename varken.py => Varken.py (93%) rename {Varken => varken}/__init__.py (100%) create mode 100644 varken/dbmanager.py create mode 100644 varken/iniparser.py rename {Varken => varken}/ombi.py (95%) create mode 100644 varken/radarr.py create mode 100644 varken/sonarr.py rename {Varken => varken}/tautulli.py (97%) diff --git a/varken.py b/Varken.py similarity index 93% rename from varken.py rename to Varken.py index d0d7c8c..c0059c1 100644 --- a/varken.py +++ b/Varken.py @@ -6,12 +6,12 @@ from os import access, R_OK from os.path import isdir, abspath, dirname, join from argparse import ArgumentParser, RawTextHelpFormatter -from Varken.iniparser import INIParser -from Varken.sonarr import SonarrAPI -from Varken.tautulli import TautulliAPI -from Varken.radarr import RadarrAPI -from Varken.ombi import OmbiAPI -from Varken.dbmanager import DBManager +from varken.iniparser import INIParser +from varken.sonarr import SonarrAPI +from varken.tautulli import TautulliAPI +from varken.radarr import RadarrAPI +from varken.ombi import OmbiAPI +from varken.dbmanager import DBManager def threaded(job): thread = threading.Thread(target=job) @@ -19,7 +19,7 @@ def threaded(job): if __name__ == "__main__": - parser = ArgumentParser(prog='Varken', + parser = ArgumentParser(prog='varken', description='Command-line utility to aggregate data from the plex ecosystem into InfluxDB', formatter_class=RawTextHelpFormatter) diff --git a/Varken/__init__.py b/varken/__init__.py similarity index 100% rename from Varken/__init__.py rename to varken/__init__.py diff --git a/varken/dbmanager.py b/varken/dbmanager.py new file mode 100644 index 0000000..8da3ced --- /dev/null +++ b/varken/dbmanager.py @@ -0,0 +1,16 @@ +from influxdb import InfluxDBClient + + +class DBManager(object): + def __init__(self, server): + self.server = server + self.influx = InfluxDBClient(self.server.url, self.server.port, self.server.username, self.server.password, + 'varken') + databases = [db['name'] for db in self.influx.get_list_database()] + + if 'varken' not in databases: + self.influx.create_database('varken') + self.influx.create_retention_policy('varken 30d/1h', '30d', '1', 'varken', False, '1h') + + def write_points(self, data): + self.influx.write_points(data) \ No newline at end of file diff --git a/varken/iniparser.py b/varken/iniparser.py new file mode 100644 index 0000000..f0ddf2d --- /dev/null +++ b/varken/iniparser.py @@ -0,0 +1,181 @@ +import sys +import configparser +from sys import exit +from os.path import join, exists +from varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer + + +class INIParser(object): + def __init__(self, data_folder): + self.config = configparser.ConfigParser() + self.data_folder = data_folder + + self.influx_server = InfluxServer() + + self.sonarr_enabled = False + self.sonarr_servers = [] + + self.radarr_enabled = False + self.radarr_servers = [] + + self.ombi_enabled = False + self.ombi_servers = [] + + self.tautulli_enabled = False + self.tautulli_servers = [] + + self.asa_enabled = False + self.asa = None + + self.parse_opts() + + def read_file(self): + file_path = join(self.data_folder, 'varken.ini') + if exists(file_path): + with open(file_path) as config_ini: + self.config.read_file(config_ini) + else: + exit("You do not have a varken.ini file in {}".format(self.data_folder)) + + def parse_opts(self): + self.read_file() + # Parse InfluxDB options + url = self.config.get('influxdb', 'url') + port = self.config.getint('influxdb', 'port') + username = self.config.get('influxdb', 'username') + password = self.config.get('influxdb', 'password') + + self.influx_server = InfluxServer(url, port, username, password) + + # Parse Sonarr options + try: + if not self.config.getboolean('global', 'sonarr_server_ids'): + sys.exit('server_ids must be either false, or a comma-separated list of server ids') + elif self.config.getint('global', 'sonarr_server_ids'): + self.sonarr_enabled = True + except ValueError: + self.sonarr_enabled = True + + if self.sonarr_enabled: + sids = self.config.get('global', 'sonarr_server_ids').strip(' ').split(',') + + for server_id in sids: + sonarr_section = 'sonarr-' + server_id + url = self.config.get(sonarr_section, 'url') + apikey = self.config.get(sonarr_section, 'apikey') + scheme = 'https://' if self.config.getboolean(sonarr_section, 'ssl') else 'http://' + verify_ssl = self.config.getboolean(sonarr_section, 'verify_ssl') + if scheme != 'https://': + verify_ssl = False + queue = self.config.getboolean(sonarr_section, 'queue') + missing_days = self.config.getint(sonarr_section, 'missing_days') + future_days = self.config.getint(sonarr_section, 'future_days') + missing_days_run_seconds = self.config.getint(sonarr_section, 'missing_days_run_seconds') + future_days_run_seconds = self.config.getint(sonarr_section, 'future_days_run_seconds') + queue_run_seconds = self.config.getint(sonarr_section, 'queue_run_seconds') + + server = SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days, + missing_days_run_seconds, future_days, future_days_run_seconds, + queue, queue_run_seconds) + self.sonarr_servers.append(server) + + # Parse Radarr options + try: + if not self.config.getboolean('global', 'radarr_server_ids'): + sys.exit('server_ids must be either false, or a comma-separated list of server ids') + elif self.config.getint('global', 'radarr_server_ids'): + self.radarr_enabled = True + except ValueError: + self.radarr_enabled = True + + if self.radarr_enabled: + sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') + + for server_id in sids: + radarr_section = 'radarr-' + server_id + url = self.config.get(radarr_section, 'url') + apikey = self.config.get(radarr_section, 'apikey') + scheme = 'https://' if self.config.getboolean(radarr_section, 'ssl') else 'http://' + verify_ssl = self.config.getboolean(radarr_section, 'verify_ssl') + if scheme != 'https://': + verify_ssl = False + queue = self.config.getboolean(radarr_section, 'queue') + queue_run_seconds = self.config.getint(radarr_section, 'queue_run_seconds') + get_missing = self.config.getboolean(radarr_section, 'get_missing') + get_missing_run_seconds = self.config.getint(radarr_section, 'get_missing_run_seconds') + + server = RadarrServer(server_id, scheme + url, apikey, verify_ssl, queue, queue_run_seconds, + get_missing, get_missing_run_seconds) + self.radarr_servers.append(server) + + # Parse Tautulli options + try: + if not self.config.getboolean('global', 'tautulli_server_ids'): + sys.exit('server_ids must be either false, or a comma-separated list of server ids') + elif self.config.getint('global', 'tautulli_server_ids'): + self.tautulli_enabled = True + except ValueError: + self.tautulli_enabled = True + + if self.tautulli_enabled: + sids = self.config.get('global', 'tautulli_server_ids').strip(' ').split(',') + + for server_id in sids: + tautulli_section = 'tautulli-' + server_id + url = self.config.get(tautulli_section, 'url') + fallback_ip = self.config.get(tautulli_section, 'fallback_ip') + apikey = self.config.get(tautulli_section, 'apikey') + scheme = 'https://' if self.config.getboolean(tautulli_section, 'ssl') else 'http://' + verify_ssl = self.config.getboolean(tautulli_section, 'verify_ssl') + if scheme != 'https://': + verify_ssl = False + get_activity = self.config.getboolean(tautulli_section, 'get_activity') + get_activity_run_seconds = self.config.getint(tautulli_section, 'get_activity_run_seconds') + get_sessions = self.config.getboolean(tautulli_section, 'get_sessions') + get_sessions_run_seconds = self.config.getint(tautulli_section, 'get_sessions_run_seconds') + + server = TautulliServer(server_id, scheme + url, fallback_ip, apikey, verify_ssl, get_activity, + get_activity_run_seconds, get_sessions, get_sessions_run_seconds) + self.tautulli_servers.append(server) + + # Parse Ombi Options + try: + if not self.config.getboolean('global', 'ombi_server_ids'): + sys.exit('server_ids must be either false, or a comma-separated list of server ids') + elif self.config.getint('global', 'ombi_server_ids'): + self.ombi_enabled = True + except ValueError: + self.ombi_enabled = True + + if self.ombi_enabled: + sids = self.config.get('global', 'ombi_server_ids').strip(' ').split(',') + for server_id in sids: + ombi_section = 'ombi-' + server_id + url = self.config.get(ombi_section, 'url') + apikey = self.config.get(ombi_section, 'apikey') + scheme = 'https://' if self.config.getboolean(ombi_section, 'ssl') else 'http://' + verify_ssl = self.config.getboolean(ombi_section, 'verify_ssl') + if scheme != 'https://': + verify_ssl = False + request_type_counts = self.config.getboolean(ombi_section, 'get_request_type_counts') + request_type_run_seconds = self.config.getint(ombi_section, 'request_type_run_seconds') + request_total_counts = self.config.getboolean(ombi_section, 'get_request_total_counts') + request_total_run_seconds = self.config.getint(ombi_section, 'request_total_run_seconds') + + server = OmbiServer(server_id, scheme + url, apikey, verify_ssl, request_type_counts, + request_type_run_seconds, request_total_counts, request_total_run_seconds) + self.ombi_servers.append(server) + + # Parse ASA opts + if self.config.getboolean('global', 'asa'): + self.asa_enabled = True + url = self.config.get('asa', 'url') + username = self.config.get('asa', 'username') + password = self.config.get('asa', 'password') + scheme = 'https://' if self.config.getboolean('asa', 'ssl') else 'http://' + verify_ssl = self.config.getboolean('asa', 'verify_ssl') + if scheme != 'https://': + verify_ssl = False + db_name = self.config.get('asa', 'influx_db') + + self.asa = (scheme + url, username, password, verify_ssl, db_name) diff --git a/Varken/ombi.py b/varken/ombi.py similarity index 95% rename from Varken/ombi.py rename to varken/ombi.py index 87de969..8d8fe03 100644 --- a/Varken/ombi.py +++ b/varken/ombi.py @@ -1,9 +1,9 @@ from requests import Session, Request from datetime import datetime, timezone -from Varken.logger import logging -from Varken.helpers import connection_handler -from Varken.structures import OmbiRequestCounts +from varken.logger import logging +from varken.helpers import connection_handler +from varken.structures import OmbiRequestCounts class OmbiAPI(object): diff --git a/varken/radarr.py b/varken/radarr.py new file mode 100644 index 0000000..acc1166 --- /dev/null +++ b/varken/radarr.py @@ -0,0 +1,112 @@ +from requests import Session, Request +from datetime import datetime, timezone + +from varken.logger import logging +from varken.helpers import hashit, connection_handler +from varken.structures import Movie, Queue + + +class RadarrAPI(object): + def __init__(self, server, dbmanager): + self.now = datetime.now(timezone.utc).astimezone().isoformat() + self.dbmanager = dbmanager + self.server = server + # Create session to reduce server web thread load, and globally define pageSize for all requests + self.session = Session() + self.session.headers = {'X-Api-Key': self.server.api_key} + + @logging + def get_missing(self): + endpoint = '/api/movie' + self.now = datetime.now(timezone.utc).astimezone().isoformat() + influx_payload = [] + missing = [] + + req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + + movies = [Movie(**movie) for movie in get] + + for movie in movies: + if self.server.get_missing: + if not movie.downloaded and movie.isAvailable: + ma = True + else: + ma = False + movie_name = '{} ({})'.format(movie.title, movie.year) + missing.append((movie_name, ma, movie.tmdbId)) + + for title, ma, mid in missing: + hash_id = hashit('{}{}{}'.format(self.server.id, title, mid)) + influx_payload.append( + { + "measurement": "Radarr", + "tags": { + "Missing": True, + "Missing_Available": ma, + "tmdbId": mid, + "server": self.server.id, + "name": title + }, + "time": self.now, + "fields": { + "hash": hash_id + } + } + ) + + self.dbmanager.write_points(influx_payload) + + @logging + def get_queue(self): + endpoint = '/api/queue' + self.now = datetime.now(timezone.utc).astimezone().isoformat() + influx_payload = [] + queue = [] + + req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + + for movie in get: + movie['movie'] = Movie(**movie['movie']) + download_queue = [Queue(**movie) for movie in get] + + for queue_item in download_queue: + name = '{} ({})'.format(queue_item.movie.title, queue_item.movie.year) + + if queue_item.protocol.upper() == 'USENET': + protocol_id = 1 + else: + protocol_id = 0 + + queue.append((name, queue_item.quality['quality']['name'], queue_item.protocol.upper(), + protocol_id, queue_item.id)) + + for movie, quality, protocol, protocol_id, qid in queue: + hash_id = hashit('{}{}{}'.format(self.server.id, movie, quality)) + influx_payload.append( + { + "measurement": "Radarr", + "tags": { + "type": "Queue", + "tmdbId": qid, + "server": self.server.id, + "name": movie, + "quality": quality, + "protocol": protocol, + "protocol_id": protocol_id + }, + "time": self.now, + "fields": { + "hash": hash_id + } + } + ) + + self.dbmanager.write_points(influx_payload) diff --git a/varken/sonarr.py b/varken/sonarr.py new file mode 100644 index 0000000..ecde50e --- /dev/null +++ b/varken/sonarr.py @@ -0,0 +1,165 @@ +from requests import Session, Request +from datetime import datetime, timezone, date, timedelta + +from varken.logger import logging +from varken.helpers import hashit, connection_handler +from varken.structures import Queue, TVShow + + +class SonarrAPI(object): + def __init__(self, server, dbmanager): + # Set Time of initialization + self.now = datetime.now(timezone.utc).astimezone().isoformat() + self.dbmanager = dbmanager + self.today = str(date.today()) + self.server = server + # Create session to reduce server web thread load, and globally define pageSize for all requests + self.session = Session() + self.session.headers = {'X-Api-Key': self.server.api_key} + self.session.params = {'pageSize': 1000} + + @logging + def get_missing(self): + endpoint = '/api/calendar' + last_days = str(date.today() + timedelta(days=-self.server.missing_days)) + self.now = datetime.now(timezone.utc).astimezone().isoformat() + params = {'start': last_days, 'end': self.today} + influx_payload = [] + missing = [] + + req = self.session.prepare_request(Request('GET', self.server.url + endpoint, params=params)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + + # Iteratively create a list of TVShow Objects from response json + tv_shows = [TVShow(**show) for show in get] + + # Add show to missing list if file does not exist + for show in tv_shows: + if not show.hasFile: + sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) + missing.append((show.series['title'], sxe, show.airDate, show.title, show.id)) + + for series_title, sxe, air_date, episode_title, sonarr_id in missing: + hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) + influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Missing", + "sonarrId": sonarr_id, + "server": self.server.id, + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "airs": air_date + }, + "time": self.now, + "fields": { + "hash": hash_id + + } + } + ) + + self.dbmanager.write_points(influx_payload) + + @logging + def get_future(self): + endpoint = '/api/calendar/' + self.now = datetime.now(timezone.utc).astimezone().isoformat() + future = str(date.today() + timedelta(days=self.server.future_days)) + influx_payload = [] + air_days = [] + params = {'start': self.today, 'end': future} + + req = self.session.prepare_request(Request('GET', self.server.url + endpoint, params=params)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + + tv_shows = [TVShow(**show) for show in get] + + for show in tv_shows: + sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) + if show.hasFile: + downloaded = 1 + else: + downloaded = 0 + air_days.append((show.series['title'], downloaded, sxe, show.title, show.airDate, show.id)) + + for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: + hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) + influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Future", + "sonarrId": sonarr_id, + "server": self.server.id, + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "airs": air_date, + "downloaded": dl_status + }, + "time": self.now, + "fields": { + "hash": hash_id + } + } + ) + + self.dbmanager.write_points(influx_payload) + + @logging + def get_queue(self): + influx_payload = [] + endpoint = '/api/queue' + self.now = datetime.now(timezone.utc).astimezone().isoformat() + queue = [] + + req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) + get = connection_handler(self.session, req, self.server.verify_ssl) + + if not get: + return + + download_queue = [Queue(**show) for show in get] + + for show in download_queue: + sxe = 'S{:0>2}E{:0>2}'.format(show.episode['seasonNumber'], show.episode['episodeNumber']) + if show.protocol.upper() == 'USENET': + protocol_id = 1 + else: + protocol_id = 0 + + queue.append((show.series['title'], show.episode['title'], show.protocol.upper(), + protocol_id, sxe, show.id)) + + for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue: + hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) + influx_payload.append( + { + "measurement": "Sonarr", + "tags": { + "type": "Queue", + "sonarrId": sonarr_id, + "server": self.server.id, + "name": series_title, + "epname": episode_title, + "sxe": sxe, + "protocol": protocol, + "protocol_id": protocol_id + }, + "time": self.now, + "fields": { + "hash": hash_id + } + } + ) + + self.dbmanager.write_points(influx_payload) diff --git a/Varken/tautulli.py b/varken/tautulli.py similarity index 97% rename from Varken/tautulli.py rename to varken/tautulli.py index b04a019..e2b0b5c 100644 --- a/Varken/tautulli.py +++ b/varken/tautulli.py @@ -2,9 +2,9 @@ from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError from requests import Session, Request -from Varken.logger import logging -from Varken.helpers import geo_lookup, hashit, connection_handler -from Varken.structures import TautulliStream +from varken.logger import logging +from varken.helpers import geo_lookup, hashit, connection_handler +from varken.structures import TautulliStream class TautulliAPI(object): From 967a2efc1eef6a4822823fc38a8a9138fca48ab8 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Tue, 4 Dec 2018 10:50:02 -0600 Subject: [PATCH 075/120] test push --- README.md | 2 +- Varken/dbmanager.py | 16 --- Varken/iniparser.py | 181 ------------------------------- Varken/radarr.py | 112 ------------------- Varken/sonarr.py | 165 ---------------------------- {Varken => varken}/helpers.py | 0 {Varken => varken}/logger.py | 0 {Varken => varken}/structures.py | 0 8 files changed, 1 insertion(+), 475 deletions(-) delete mode 100644 Varken/dbmanager.py delete mode 100644 Varken/iniparser.py delete mode 100644 Varken/radarr.py delete mode 100644 Varken/sonarr.py rename {Varken => varken}/helpers.py (100%) rename {Varken => varken}/logger.py (100%) rename {Varken => varken}/structures.py (100%) diff --git a/README.md b/README.md index 5291bf0..9b8370d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Dutch for PIG. PIG is an Acronym for Plex/InfluxDB/Grafana Varken is a standalone command-line utility to aggregate data -from the plex ecosystem into InfluxDB. Examples use Grafana for a +from the Plex ecosystem into InfluxDB. Examples use Grafana for a frontend Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), [Python3](https://www.python.org/downloads/), [InfluxDB](https://docs.influxdata.com/influxdb/v1.5/introduction/installation/) diff --git a/Varken/dbmanager.py b/Varken/dbmanager.py deleted file mode 100644 index aa73534..0000000 --- a/Varken/dbmanager.py +++ /dev/null @@ -1,16 +0,0 @@ -from influxdb import InfluxDBClient - - -class DBManager(object): - def __init__(self, server): - self.server = server - self.influx = InfluxDBClient(self.server.url, self.server.port, self.server.username, self.server.password, - 'varken') - databases = [db['name'] for db in self.influx.get_list_database()] - - if 'varken' not in databases: - self.influx.create_database('varken') - self.influx.create_retention_policy('Varken 30d/1h', '30d', '1', 'varken', False, '1h') - - def write_points(self, data): - self.influx.write_points(data) \ No newline at end of file diff --git a/Varken/iniparser.py b/Varken/iniparser.py deleted file mode 100644 index 6820bd2..0000000 --- a/Varken/iniparser.py +++ /dev/null @@ -1,181 +0,0 @@ -import sys -import configparser -from sys import exit -from os.path import join, exists -from Varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer - - -class INIParser(object): - def __init__(self, data_folder): - self.config = configparser.ConfigParser() - self.data_folder = data_folder - - self.influx_server = InfluxServer() - - self.sonarr_enabled = False - self.sonarr_servers = [] - - self.radarr_enabled = False - self.radarr_servers = [] - - self.ombi_enabled = False - self.ombi_servers = [] - - self.tautulli_enabled = False - self.tautulli_servers = [] - - self.asa_enabled = False - self.asa = None - - self.parse_opts() - - def read_file(self): - file_path = join(self.data_folder, 'varken.ini') - if exists(file_path): - with open(file_path) as config_ini: - self.config.read_file(config_ini) - else: - exit("You do not have a varken.ini file in {}".format(self.data_folder)) - - def parse_opts(self): - self.read_file() - # Parse InfluxDB options - url = self.config.get('influxdb', 'url') - port = self.config.getint('influxdb', 'port') - username = self.config.get('influxdb', 'username') - password = self.config.get('influxdb', 'password') - - self.influx_server = InfluxServer(url, port, username, password) - - # Parse Sonarr options - try: - if not self.config.getboolean('global', 'sonarr_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') - elif self.config.getint('global', 'sonarr_server_ids'): - self.sonarr_enabled = True - except ValueError: - self.sonarr_enabled = True - - if self.sonarr_enabled: - sids = self.config.get('global', 'sonarr_server_ids').strip(' ').split(',') - - for server_id in sids: - sonarr_section = 'sonarr-' + server_id - url = self.config.get(sonarr_section, 'url') - apikey = self.config.get(sonarr_section, 'apikey') - scheme = 'https://' if self.config.getboolean(sonarr_section, 'ssl') else 'http://' - verify_ssl = self.config.getboolean(sonarr_section, 'verify_ssl') - if scheme != 'https://': - verify_ssl = False - queue = self.config.getboolean(sonarr_section, 'queue') - missing_days = self.config.getint(sonarr_section, 'missing_days') - future_days = self.config.getint(sonarr_section, 'future_days') - missing_days_run_seconds = self.config.getint(sonarr_section, 'missing_days_run_seconds') - future_days_run_seconds = self.config.getint(sonarr_section, 'future_days_run_seconds') - queue_run_seconds = self.config.getint(sonarr_section, 'queue_run_seconds') - - server = SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days, - missing_days_run_seconds, future_days, future_days_run_seconds, - queue, queue_run_seconds) - self.sonarr_servers.append(server) - - # Parse Radarr options - try: - if not self.config.getboolean('global', 'radarr_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') - elif self.config.getint('global', 'radarr_server_ids'): - self.radarr_enabled = True - except ValueError: - self.radarr_enabled = True - - if self.radarr_enabled: - sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') - - for server_id in sids: - radarr_section = 'radarr-' + server_id - url = self.config.get(radarr_section, 'url') - apikey = self.config.get(radarr_section, 'apikey') - scheme = 'https://' if self.config.getboolean(radarr_section, 'ssl') else 'http://' - verify_ssl = self.config.getboolean(radarr_section, 'verify_ssl') - if scheme != 'https://': - verify_ssl = False - queue = self.config.getboolean(radarr_section, 'queue') - queue_run_seconds = self.config.getint(radarr_section, 'queue_run_seconds') - get_missing = self.config.getboolean(radarr_section, 'get_missing') - get_missing_run_seconds = self.config.getint(radarr_section, 'get_missing_run_seconds') - - server = RadarrServer(server_id, scheme + url, apikey, verify_ssl, queue, queue_run_seconds, - get_missing, get_missing_run_seconds) - self.radarr_servers.append(server) - - # Parse Tautulli options - try: - if not self.config.getboolean('global', 'tautulli_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') - elif self.config.getint('global', 'tautulli_server_ids'): - self.tautulli_enabled = True - except ValueError: - self.tautulli_enabled = True - - if self.tautulli_enabled: - sids = self.config.get('global', 'tautulli_server_ids').strip(' ').split(',') - - for server_id in sids: - tautulli_section = 'tautulli-' + server_id - url = self.config.get(tautulli_section, 'url') - fallback_ip = self.config.get(tautulli_section, 'fallback_ip') - apikey = self.config.get(tautulli_section, 'apikey') - scheme = 'https://' if self.config.getboolean(tautulli_section, 'ssl') else 'http://' - verify_ssl = self.config.getboolean(tautulli_section, 'verify_ssl') - if scheme != 'https://': - verify_ssl = False - get_activity = self.config.getboolean(tautulli_section, 'get_activity') - get_activity_run_seconds = self.config.getint(tautulli_section, 'get_activity_run_seconds') - get_sessions = self.config.getboolean(tautulli_section, 'get_sessions') - get_sessions_run_seconds = self.config.getint(tautulli_section, 'get_sessions_run_seconds') - - server = TautulliServer(server_id, scheme + url, fallback_ip, apikey, verify_ssl, get_activity, - get_activity_run_seconds, get_sessions, get_sessions_run_seconds) - self.tautulli_servers.append(server) - - # Parse Ombi Options - try: - if not self.config.getboolean('global', 'ombi_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') - elif self.config.getint('global', 'ombi_server_ids'): - self.ombi_enabled = True - except ValueError: - self.ombi_enabled = True - - if self.ombi_enabled: - sids = self.config.get('global', 'ombi_server_ids').strip(' ').split(',') - for server_id in sids: - ombi_section = 'ombi-' + server_id - url = self.config.get(ombi_section, 'url') - apikey = self.config.get(ombi_section, 'apikey') - scheme = 'https://' if self.config.getboolean(ombi_section, 'ssl') else 'http://' - verify_ssl = self.config.getboolean(ombi_section, 'verify_ssl') - if scheme != 'https://': - verify_ssl = False - request_type_counts = self.config.getboolean(ombi_section, 'get_request_type_counts') - request_type_run_seconds = self.config.getint(ombi_section, 'request_type_run_seconds') - request_total_counts = self.config.getboolean(ombi_section, 'get_request_total_counts') - request_total_run_seconds = self.config.getint(ombi_section, 'request_total_run_seconds') - - server = OmbiServer(server_id, scheme + url, apikey, verify_ssl, request_type_counts, - request_type_run_seconds, request_total_counts, request_total_run_seconds) - self.ombi_servers.append(server) - - # Parse ASA opts - if self.config.getboolean('global', 'asa'): - self.asa_enabled = True - url = self.config.get('asa', 'url') - username = self.config.get('asa', 'username') - password = self.config.get('asa', 'password') - scheme = 'https://' if self.config.getboolean('asa', 'ssl') else 'http://' - verify_ssl = self.config.getboolean('asa', 'verify_ssl') - if scheme != 'https://': - verify_ssl = False - db_name = self.config.get('asa', 'influx_db') - - self.asa = (scheme + url, username, password, verify_ssl, db_name) diff --git a/Varken/radarr.py b/Varken/radarr.py deleted file mode 100644 index ba7f050..0000000 --- a/Varken/radarr.py +++ /dev/null @@ -1,112 +0,0 @@ -from requests import Session, Request -from datetime import datetime, timezone - -from Varken.logger import logging -from Varken.helpers import hashit, connection_handler -from Varken.structures import Movie, Queue - - -class RadarrAPI(object): - def __init__(self, server, dbmanager): - self.now = datetime.now(timezone.utc).astimezone().isoformat() - self.dbmanager = dbmanager - self.server = server - # Create session to reduce server web thread load, and globally define pageSize for all requests - self.session = Session() - self.session.headers = {'X-Api-Key': self.server.api_key} - - @logging - def get_missing(self): - endpoint = '/api/movie' - self.now = datetime.now(timezone.utc).astimezone().isoformat() - influx_payload = [] - missing = [] - - req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) - get = connection_handler(self.session, req, self.server.verify_ssl) - - if not get: - return - - movies = [Movie(**movie) for movie in get] - - for movie in movies: - if self.server.get_missing: - if not movie.downloaded and movie.isAvailable: - ma = True - else: - ma = False - movie_name = '{} ({})'.format(movie.title, movie.year) - missing.append((movie_name, ma, movie.tmdbId)) - - for title, ma, mid in missing: - hash_id = hashit('{}{}{}'.format(self.server.id, title, mid)) - influx_payload.append( - { - "measurement": "Radarr", - "tags": { - "Missing": True, - "Missing_Available": ma, - "tmdbId": mid, - "server": self.server.id, - "name": title - }, - "time": self.now, - "fields": { - "hash": hash_id - } - } - ) - - self.dbmanager.write_points(influx_payload) - - @logging - def get_queue(self): - endpoint = '/api/queue' - self.now = datetime.now(timezone.utc).astimezone().isoformat() - influx_payload = [] - queue = [] - - req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) - get = connection_handler(self.session, req, self.server.verify_ssl) - - if not get: - return - - for movie in get: - movie['movie'] = Movie(**movie['movie']) - download_queue = [Queue(**movie) for movie in get] - - for queue_item in download_queue: - name = '{} ({})'.format(queue_item.movie.title, queue_item.movie.year) - - if queue_item.protocol.upper() == 'USENET': - protocol_id = 1 - else: - protocol_id = 0 - - queue.append((name, queue_item.quality['quality']['name'], queue_item.protocol.upper(), - protocol_id, queue_item.id)) - - for movie, quality, protocol, protocol_id, qid in queue: - hash_id = hashit('{}{}{}'.format(self.server.id, movie, quality)) - influx_payload.append( - { - "measurement": "Radarr", - "tags": { - "type": "Queue", - "tmdbId": qid, - "server": self.server.id, - "name": movie, - "quality": quality, - "protocol": protocol, - "protocol_id": protocol_id - }, - "time": self.now, - "fields": { - "hash": hash_id - } - } - ) - - self.dbmanager.write_points(influx_payload) diff --git a/Varken/sonarr.py b/Varken/sonarr.py deleted file mode 100644 index bcda5e5..0000000 --- a/Varken/sonarr.py +++ /dev/null @@ -1,165 +0,0 @@ -from requests import Session, Request -from datetime import datetime, timezone, date, timedelta - -from Varken.logger import logging -from Varken.helpers import hashit, connection_handler -from Varken.structures import Queue, TVShow - - -class SonarrAPI(object): - def __init__(self, server, dbmanager): - # Set Time of initialization - self.now = datetime.now(timezone.utc).astimezone().isoformat() - self.dbmanager = dbmanager - self.today = str(date.today()) - self.server = server - # Create session to reduce server web thread load, and globally define pageSize for all requests - self.session = Session() - self.session.headers = {'X-Api-Key': self.server.api_key} - self.session.params = {'pageSize': 1000} - - @logging - def get_missing(self): - endpoint = '/api/calendar' - last_days = str(date.today() + timedelta(days=-self.server.missing_days)) - self.now = datetime.now(timezone.utc).astimezone().isoformat() - params = {'start': last_days, 'end': self.today} - influx_payload = [] - missing = [] - - req = self.session.prepare_request(Request('GET', self.server.url + endpoint, params=params)) - get = connection_handler(self.session, req, self.server.verify_ssl) - - if not get: - return - - # Iteratively create a list of TVShow Objects from response json - tv_shows = [TVShow(**show) for show in get] - - # Add show to missing list if file does not exist - for show in tv_shows: - if not show.hasFile: - sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) - missing.append((show.series['title'], sxe, show.airDate, show.title, show.id)) - - for series_title, sxe, air_date, episode_title, sonarr_id in missing: - hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Missing", - "sonarrId": sonarr_id, - "server": self.server.id, - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "airs": air_date - }, - "time": self.now, - "fields": { - "hash": hash_id - - } - } - ) - - self.dbmanager.write_points(influx_payload) - - @logging - def get_future(self): - endpoint = '/api/calendar/' - self.now = datetime.now(timezone.utc).astimezone().isoformat() - future = str(date.today() + timedelta(days=self.server.future_days)) - influx_payload = [] - air_days = [] - params = {'start': self.today, 'end': future} - - req = self.session.prepare_request(Request('GET', self.server.url + endpoint, params=params)) - get = connection_handler(self.session, req, self.server.verify_ssl) - - if not get: - return - - tv_shows = [TVShow(**show) for show in get] - - for show in tv_shows: - sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) - if show.hasFile: - downloaded = 1 - else: - downloaded = 0 - air_days.append((show.series['title'], downloaded, sxe, show.title, show.airDate, show.id)) - - for series_title, dl_status, sxe, episode_title, air_date, sonarr_id in air_days: - hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Future", - "sonarrId": sonarr_id, - "server": self.server.id, - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "airs": air_date, - "downloaded": dl_status - }, - "time": self.now, - "fields": { - "hash": hash_id - } - } - ) - - self.dbmanager.write_points(influx_payload) - - @logging - def get_queue(self): - influx_payload = [] - endpoint = '/api/queue' - self.now = datetime.now(timezone.utc).astimezone().isoformat() - queue = [] - - req = self.session.prepare_request(Request('GET', self.server.url + endpoint)) - get = connection_handler(self.session, req, self.server.verify_ssl) - - if not get: - return - - download_queue = [Queue(**show) for show in get] - - for show in download_queue: - sxe = 'S{:0>2}E{:0>2}'.format(show.episode['seasonNumber'], show.episode['episodeNumber']) - if show.protocol.upper() == 'USENET': - protocol_id = 1 - else: - protocol_id = 0 - - queue.append((show.series['title'], show.episode['title'], show.protocol.upper(), - protocol_id, sxe, show.id)) - - for series_title, episode_title, protocol, protocol_id, sxe, sonarr_id in queue: - hash_id = hashit('{}{}{}'.format(self.server.id, series_title, sxe)) - influx_payload.append( - { - "measurement": "Sonarr", - "tags": { - "type": "Queue", - "sonarrId": sonarr_id, - "server": self.server.id, - "name": series_title, - "epname": episode_title, - "sxe": sxe, - "protocol": protocol, - "protocol_id": protocol_id - }, - "time": self.now, - "fields": { - "hash": hash_id - } - } - ) - - self.dbmanager.write_points(influx_payload) diff --git a/Varken/helpers.py b/varken/helpers.py similarity index 100% rename from Varken/helpers.py rename to varken/helpers.py diff --git a/Varken/logger.py b/varken/logger.py similarity index 100% rename from Varken/logger.py rename to varken/logger.py diff --git a/Varken/structures.py b/varken/structures.py similarity index 100% rename from Varken/structures.py rename to varken/structures.py From f6483e0d4dd2817e5feab6a2536c1b1cdfaff11f Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 11:57:36 -0500 Subject: [PATCH 076/120] Update systemd with uppercase --- varken.systemd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/varken.systemd b/varken.systemd index 29e8606..ddc576c 100644 --- a/varken.systemd +++ b/varken.systemd @@ -45,7 +45,7 @@ Type=simple User=varken Group=varken WorkingDirectory=/opt/Varken -ExecStart=/opt/Varken/varken-venv/bin/python /opt/Varken/varken.py +ExecStart=/opt/Varken/varken-venv/bin/python /opt/Varken/Varken.py Restart=always RestartSec=30 From 4ab1c42abc0054d96ac7399bf50efe36dcf0d01b Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 11:57:53 -0500 Subject: [PATCH 077/120] Update gitignore to exclude varken-venv --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f238ac1..f97a8aa 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ GeoLite2-City.tar.gz data/varken.ini .idea/ Legacy/configuration.py +varken-venv/ From b80d51ba881e4c15385a3a7a01082e752746b3c2 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 14:46:28 -0500 Subject: [PATCH 078/120] Fix parsing for disabled servers --- varken/iniparser.py | 41 ++++++++++++----------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/varken/iniparser.py b/varken/iniparser.py index f0ddf2d..aa6b942 100644 --- a/varken/iniparser.py +++ b/varken/iniparser.py @@ -29,6 +29,13 @@ class INIParser(object): self.parse_opts() + def enable_check(self, server_ids): + global_server_ids = self.config.get('global', server_ids) + if global_server_ids.lower() in ['false', 'no']: + return False + else: + return global_server_ids + def read_file(self): file_path = join(self.data_folder, 'varken.ini') if exists(file_path): @@ -48,13 +55,7 @@ class INIParser(object): self.influx_server = InfluxServer(url, port, username, password) # Parse Sonarr options - try: - if not self.config.getboolean('global', 'sonarr_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') - elif self.config.getint('global', 'sonarr_server_ids'): - self.sonarr_enabled = True - except ValueError: - self.sonarr_enabled = True + self.sonarr_enabled = self.enable_check('sonarr_server_ids') if self.sonarr_enabled: sids = self.config.get('global', 'sonarr_server_ids').strip(' ').split(',') @@ -80,13 +81,7 @@ class INIParser(object): self.sonarr_servers.append(server) # Parse Radarr options - try: - if not self.config.getboolean('global', 'radarr_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') - elif self.config.getint('global', 'radarr_server_ids'): - self.radarr_enabled = True - except ValueError: - self.radarr_enabled = True + self.radarr_enabled = self.enable_check('radarr_server_ids') if self.radarr_enabled: sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') @@ -109,13 +104,7 @@ class INIParser(object): self.radarr_servers.append(server) # Parse Tautulli options - try: - if not self.config.getboolean('global', 'tautulli_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') - elif self.config.getint('global', 'tautulli_server_ids'): - self.tautulli_enabled = True - except ValueError: - self.tautulli_enabled = True + self.tautulli_enabled = self.enable_check('tautulli_server_ids') if self.tautulli_enabled: sids = self.config.get('global', 'tautulli_server_ids').strip(' ').split(',') @@ -138,14 +127,8 @@ class INIParser(object): get_activity_run_seconds, get_sessions, get_sessions_run_seconds) self.tautulli_servers.append(server) - # Parse Ombi Options - try: - if not self.config.getboolean('global', 'ombi_server_ids'): - sys.exit('server_ids must be either false, or a comma-separated list of server ids') - elif self.config.getint('global', 'ombi_server_ids'): - self.ombi_enabled = True - except ValueError: - self.ombi_enabled = True + # Parse Ombi options + self.ombi_enabled = self.enable_check('ombi_server_ids') if self.ombi_enabled: sids = self.config.get('global', 'ombi_server_ids').strip(' ').split(',') From b8b4bad23ce8550263cc60c7865c10233a8f663d Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 14:49:30 -0500 Subject: [PATCH 079/120] Make 0 explicit --- varken/iniparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/varken/iniparser.py b/varken/iniparser.py index aa6b942..613035d 100644 --- a/varken/iniparser.py +++ b/varken/iniparser.py @@ -31,7 +31,7 @@ class INIParser(object): def enable_check(self, server_ids): global_server_ids = self.config.get('global', server_ids) - if global_server_ids.lower() in ['false', 'no']: + if global_server_ids.lower() in ['false', 'no', '0']: return False else: return global_server_ids From 91dd3975b7cc24826509a489bbacf655c402f980 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 19:12:11 -0500 Subject: [PATCH 080/120] Add new logger --- varken/logger.py | 11 ----------- varken/varkenlogger.py | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) delete mode 100644 varken/logger.py create mode 100644 varken/varkenlogger.py diff --git a/varken/logger.py b/varken/logger.py deleted file mode 100644 index 689dd37..0000000 --- a/varken/logger.py +++ /dev/null @@ -1,11 +0,0 @@ -import functools - -def logging(function): - @functools.wraps(function) - def wrapper(*args, **kwargs): - print('LOG: Running job "%s"' % function.__name__) - result = function(*args, **kwargs) - print('LOG: Job "%s" completed' % function.__name__) - return result - - return wrapper \ No newline at end of file diff --git a/varken/varkenlogger.py b/varken/varkenlogger.py new file mode 100644 index 0000000..bbdf4c8 --- /dev/null +++ b/varken/varkenlogger.py @@ -0,0 +1,27 @@ +import logging + +class VarkenLogger(object): + """docstring for .""" + def __init__(self, log_path=None, log_level=None): + # Create the Logger + self.logger = logging.getLogger() + self.logger.setLevel(logging.DEBUG) + + # Create a Formatter for formatting the log messages + logger_formatter = logging.Formatter('%(asctime)s : %(levelname)s : %(module)s : %(message)s', '%Y-%m-%d %H:%M:%S') + + # Create the Handler for logging data to a file + file_logger = logging.FileHandler('varken.log') + file_logger.setLevel(logging.DEBUG) + + # Add the Formatter to the Handler + file_logger.setFormatter(logger_formatter) + + # Add the console logger + console_logger = logging.StreamHandler() + console_logger.setFormatter(logger_formatter) + console_logger.setLevel(logging.INFO) + + # Add the Handler to the Logger + self.logger.addHandler(file_logger) + self.logger.addHandler(console_logger) From 50c1be7e41531059e65c0215badfe33f6f86ea07 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 19:14:08 -0500 Subject: [PATCH 081/120] Changes to logging tautulli --- varken/tautulli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/varken/tautulli.py b/varken/tautulli.py index e2b0b5c..e5779f5 100644 --- a/varken/tautulli.py +++ b/varken/tautulli.py @@ -1,11 +1,13 @@ +import logging from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError from requests import Session, Request -from varken.logger import logging from varken.helpers import geo_lookup, hashit, connection_handler from varken.structures import TautulliStream +logger = logging.getLogger() + class TautulliAPI(object): def __init__(self, server, dbmanager): @@ -17,7 +19,6 @@ class TautulliAPI(object): self.session.params['apikey'] = self.server.api_key self.endpoint = '/api/v2' - @logging def get_activity(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} @@ -53,7 +54,6 @@ class TautulliAPI(object): self.dbmanager.write_points(influx_payload) - @logging def get_sessions(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} From aa04ea84b7b84df8769344cebef8671b646fef6b Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 19:16:00 -0500 Subject: [PATCH 082/120] Removing old logging --- Varken.py | 5 ++++- varken/dbmanager.py | 6 +++++- varken/helpers.py | 17 ++++++++++------- varken/ombi.py | 3 --- varken/radarr.py | 3 --- varken/sonarr.py | 5 ----- 6 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Varken.py b/Varken.py index c0059c1..914eafe 100644 --- a/Varken.py +++ b/Varken.py @@ -12,6 +12,7 @@ from varken.tautulli import TautulliAPI from varken.radarr import RadarrAPI from varken.ombi import OmbiAPI from varken.dbmanager import DBManager +from varken.varkenlogger import VarkenLogger def threaded(job): thread = threading.Thread(target=job) @@ -19,6 +20,9 @@ def threaded(job): if __name__ == "__main__": + vl = VarkenLogger() + vl.logger.info('Starting Varken...') + parser = ArgumentParser(prog='varken', description='Command-line utility to aggregate data from the plex ecosystem into InfluxDB', formatter_class=RawTextHelpFormatter) @@ -84,4 +88,3 @@ if __name__ == "__main__": while True: schedule.run_pending() sleep(1) - diff --git a/varken/dbmanager.py b/varken/dbmanager.py index 8da3ced..581cb0f 100644 --- a/varken/dbmanager.py +++ b/varken/dbmanager.py @@ -1,5 +1,8 @@ +import logging + from influxdb import InfluxDBClient +logger = logging.getLogger('Varken') class DBManager(object): def __init__(self, server): @@ -13,4 +16,5 @@ class DBManager(object): self.influx.create_retention_policy('varken 30d/1h', '30d', '1', 'varken', False, '1h') def write_points(self, data): - self.influx.write_points(data) \ No newline at end of file + logger.debug('Writing Data to InfluxDB {}'.format(data)) + self.influx.write_points(data) diff --git a/varken/helpers.py b/varken/helpers.py index eeda689..772f70f 100644 --- a/varken/helpers.py +++ b/varken/helpers.py @@ -3,17 +3,20 @@ import time import tarfile import hashlib import geoip2.database +import logging + from json.decoder import JSONDecodeError from os.path import abspath, join from requests.exceptions import InvalidSchema, SSLError from urllib.request import urlretrieve +logger = logging.getLogger('Varken') def geoip_download(): tar_dbfile = abspath(join('.', 'data', 'GeoLite2-City.tar.gz')) url = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz' urlretrieve(url, tar_dbfile) - tar = tarfile.open(tar_dbfile, "r:gz") + tar = tarfile.open(tar_dbfile, 'r:gz') for files in tar.getmembers(): if 'GeoLite2-City.mmdb' in files.name: files.name = os.path.basename(files.name) @@ -56,20 +59,20 @@ def connection_handler(session, request, verify): try: get = s.send(r, verify=v) if get.status_code == 401: - print("Your api key is incorrect for {}".format(r.url)) + logger.info('Your api key is incorrect for {}'.format(r.url)) elif get.status_code == 404: - print("This url doesnt even resolve: {}".format(r.url)) + logger.info('This url doesnt even resolve: {}'.format(r.url)) elif get.status_code == 200: try: return_json = get.json() except JSONDecodeError: - print("No JSON response... BORKED! Let us know in discord") + logger.info('No JSON response... BORKED! Let us know in discord') except InvalidSchema: - print("You added http(s):// in the config file. Don't do that.") + logger.info('You added http(s):// in the config file. Don't do that.') except SSLError as e: - print("Either your host is unreachable or you have an ssl issue.") - print("The issue was: {}".format(e)) + logger.info('Either your host is unreachable or you have an ssl issue.') + logger.info('The issue was: {}'.format(e)) return return_json diff --git a/varken/ombi.py b/varken/ombi.py index 8d8fe03..6c71fe0 100644 --- a/varken/ombi.py +++ b/varken/ombi.py @@ -1,7 +1,6 @@ from requests import Session, Request from datetime import datetime, timezone -from varken.logger import logging from varken.helpers import connection_handler from varken.structures import OmbiRequestCounts @@ -15,7 +14,6 @@ class OmbiAPI(object): self.session = Session() self.session.headers = {'Apikey': self.server.api_key} - @logging def get_total_requests(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() tv_endpoint = '/api/v1/Request/tv' @@ -49,7 +47,6 @@ class OmbiAPI(object): self.dbmanager.write_points(influx_payload) - @logging def get_request_counts(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() endpoint = '/api/v1/Request/count' diff --git a/varken/radarr.py b/varken/radarr.py index acc1166..2dddacb 100644 --- a/varken/radarr.py +++ b/varken/radarr.py @@ -1,7 +1,6 @@ from requests import Session, Request from datetime import datetime, timezone -from varken.logger import logging from varken.helpers import hashit, connection_handler from varken.structures import Movie, Queue @@ -15,7 +14,6 @@ class RadarrAPI(object): self.session = Session() self.session.headers = {'X-Api-Key': self.server.api_key} - @logging def get_missing(self): endpoint = '/api/movie' self.now = datetime.now(timezone.utc).astimezone().isoformat() @@ -60,7 +58,6 @@ class RadarrAPI(object): self.dbmanager.write_points(influx_payload) - @logging def get_queue(self): endpoint = '/api/queue' self.now = datetime.now(timezone.utc).astimezone().isoformat() diff --git a/varken/sonarr.py b/varken/sonarr.py index ecde50e..81a4d38 100644 --- a/varken/sonarr.py +++ b/varken/sonarr.py @@ -1,11 +1,9 @@ from requests import Session, Request from datetime import datetime, timezone, date, timedelta -from varken.logger import logging from varken.helpers import hashit, connection_handler from varken.structures import Queue, TVShow - class SonarrAPI(object): def __init__(self, server, dbmanager): # Set Time of initialization @@ -18,7 +16,6 @@ class SonarrAPI(object): self.session.headers = {'X-Api-Key': self.server.api_key} self.session.params = {'pageSize': 1000} - @logging def get_missing(self): endpoint = '/api/calendar' last_days = str(date.today() + timedelta(days=-self.server.missing_days)) @@ -66,7 +63,6 @@ class SonarrAPI(object): self.dbmanager.write_points(influx_payload) - @logging def get_future(self): endpoint = '/api/calendar/' self.now = datetime.now(timezone.utc).astimezone().isoformat() @@ -115,7 +111,6 @@ class SonarrAPI(object): self.dbmanager.write_points(influx_payload) - @logging def get_queue(self): influx_payload = [] endpoint = '/api/queue' From 0a0c1b8cefe804fa29330aeee708eaa3775eea1d Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 19:58:07 -0500 Subject: [PATCH 083/120] Fix what I broke when converting to single quotes --- varken/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/varken/helpers.py b/varken/helpers.py index 772f70f..6643fd1 100644 --- a/varken/helpers.py +++ b/varken/helpers.py @@ -69,7 +69,7 @@ def connection_handler(session, request, verify): logger.info('No JSON response... BORKED! Let us know in discord') except InvalidSchema: - logger.info('You added http(s):// in the config file. Don't do that.') + logger.info('You added http(s):// in the config file. Don\'t do that.') except SSLError as e: logger.info('Either your host is unreachable or you have an ssl issue.') From b0497afa209c054dced4f90d37ab3e5107e04aab Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 19:58:21 -0500 Subject: [PATCH 084/120] Add more logging to intparser --- varken/iniparser.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/varken/iniparser.py b/varken/iniparser.py index 613035d..d234fd2 100644 --- a/varken/iniparser.py +++ b/varken/iniparser.py @@ -1,9 +1,12 @@ import sys import configparser +import logging from sys import exit from os.path import join, exists from varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer +logger = logging.getLogger() + class INIParser(object): def __init__(self, data_folder): @@ -29,11 +32,13 @@ class INIParser(object): self.parse_opts() - def enable_check(self, server_ids): - global_server_ids = self.config.get('global', server_ids) + def enable_check(self, type=None): + global_server_ids = self.config.get('global', type) if global_server_ids.lower() in ['false', 'no', '0']: + logger.info('{} disabled.'.format(type.upper())) return False else: + logger.info('{} : ({})'.format(type.upper(), global_server_ids)) return global_server_ids def read_file(self): From d807cf1b07ba81bf9d2f5348da9660e6a51d981d Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 19:58:37 -0500 Subject: [PATCH 085/120] Add platform info and python info --- Varken.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Varken.py b/Varken.py index 914eafe..22ed962 100644 --- a/Varken.py +++ b/Varken.py @@ -1,5 +1,8 @@ import schedule import threading +import sys +import platform + from sys import exit from time import sleep from os import access, R_OK @@ -14,14 +17,23 @@ from varken.ombi import OmbiAPI from varken.dbmanager import DBManager from varken.varkenlogger import VarkenLogger +PLATFORM_LINUX_DISTRO = ' '.join(x for x in platform.linux_distribution() if x) + def threaded(job): thread = threading.Thread(target=job) thread.start() if __name__ == "__main__": + # Initiate the logger vl = VarkenLogger() vl.logger.info('Starting Varken...') + vl.logger.info(u"{} {} ({}{})".format( + platform.system(), platform.release(), platform.version(), + ' - {}'.format(PLATFORM_LINUX_DISTRO) if PLATFORM_LINUX_DISTRO else '' + )) + vl.logger.info(u"Python {}".format(sys.version)) + parser = ArgumentParser(prog='varken', description='Command-line utility to aggregate data from the plex ecosystem into InfluxDB', From 87fea6db065e18235f15f92f7ebf2e618de5be6f Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 20:14:55 -0500 Subject: [PATCH 086/120] Add distro package --- Varken.py | 3 ++- requirements.txt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Varken.py b/Varken.py index 22ed962..cbfe3da 100644 --- a/Varken.py +++ b/Varken.py @@ -2,6 +2,7 @@ import schedule import threading import sys import platform +import distro from sys import exit from time import sleep @@ -17,7 +18,7 @@ from varken.ombi import OmbiAPI from varken.dbmanager import DBManager from varken.varkenlogger import VarkenLogger -PLATFORM_LINUX_DISTRO = ' '.join(x for x in platform.linux_distribution() if x) +PLATFORM_LINUX_DISTRO = ' '.join(x for x in distro.linux_distribution() if x) def threaded(job): thread = threading.Thread(target=job) diff --git a/requirements.txt b/requirements.txt index 760382e..8fdd006 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ requests>=2.20.1 geoip2>=2.9.0 influxdb>=5.2.0 -schedule>=0.5.0 \ No newline at end of file +schedule>=0.5.0 +distro>=1.3.0 From 18a5fdacbaaacda02a65999de3a58958996c0801 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Tue, 4 Dec 2018 21:17:33 -0600 Subject: [PATCH 087/120] fixed clean_check of server_ids, fixed under-indented radarr get_movie, and added __repr__ for cleaner logging --- README.md | 10 +++++----- varken/dbmanager.py | 7 ++++--- varken/helpers.py | 5 +++-- varken/iniparser.py | 29 ++++++++++++++++++++++++----- varken/ombi.py | 3 +++ varken/radarr.py | 7 +++++-- varken/sonarr.py | 3 +++ varken/tautulli.py | 3 +++ 8 files changed, 50 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 9b8370d..f4967ee 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Varken Dutch for PIG. PIG is an Acronym for Plex/InfluxDB/Grafana -Varken is a standalone command-line utility to aggregate data +varken is a standalone command-line utility to aggregate data from the Plex ecosystem into InfluxDB. Examples use Grafana for a frontend @@ -12,14 +12,14 @@ Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), </p> ## Quick Setup (Varken Alpha) -1. Clone the repository `sudo git clone https://github.com/DirtyCajunRice/grafana-scripts.git /opt/Varken` +1. Clone the repository `sudo git clone https://github.com/DirtyCajunRice/grafana-scripts.git /opt/varken` 1. Follow the systemd install instructions located in `varken.systemd` 1. Create venv in project `/usr/bin/python3 -m venv varken-venv` -1. Install requirements `/opt/Varken/varken-venv/bin/python -m pip install -r requirements.txt` +1. Install requirements `/opt/varken/varken-venv/bin/python -m pip install -r requirements.txt` 1. Make a copy of `varken.example.ini` to `varken.ini` in the `data` folder - `cp /opt/Varken/data/varken.example.ini /opt/Varken/data/varken.ini` + `cp /opt/varken/data/varken.example.ini /opt/varken/data/varken.ini` 1. Make the appropriate changes to `varken.ini` - ie.`nano /opt/Varken/data/varken.ini` + ie.`nano /opt/varken/data/varken.ini` 1. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. 1. Install `grafana-cli plugins install grafana-worldmap-panel` 1. TODO:: Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. diff --git a/varken/dbmanager.py b/varken/dbmanager.py index 581cb0f..4eee803 100644 --- a/varken/dbmanager.py +++ b/varken/dbmanager.py @@ -2,7 +2,7 @@ import logging from influxdb import InfluxDBClient -logger = logging.getLogger('Varken') +logger = logging.getLogger('varken') class DBManager(object): def __init__(self, server): @@ -16,5 +16,6 @@ class DBManager(object): self.influx.create_retention_policy('varken 30d/1h', '30d', '1', 'varken', False, '1h') def write_points(self, data): - logger.debug('Writing Data to InfluxDB {}'.format(data)) - self.influx.write_points(data) + d = data + logger.debug('Writing Data to InfluxDB {}'.format(d)) + self.influx.write_points(d) diff --git a/varken/helpers.py b/varken/helpers.py index 6643fd1..8806f5b 100644 --- a/varken/helpers.py +++ b/varken/helpers.py @@ -4,13 +4,14 @@ import tarfile import hashlib import geoip2.database import logging - +from functools import update_wrapper from json.decoder import JSONDecodeError from os.path import abspath, join from requests.exceptions import InvalidSchema, SSLError from urllib.request import urlretrieve -logger = logging.getLogger('Varken') +logger = logging.getLogger('varken') + def geoip_download(): tar_dbfile = abspath(join('.', 'data', 'GeoLite2-City.tar.gz')) diff --git a/varken/iniparser.py b/varken/iniparser.py index d234fd2..daadac0 100644 --- a/varken/iniparser.py +++ b/varken/iniparser.py @@ -32,14 +32,33 @@ class INIParser(object): self.parse_opts() - def enable_check(self, type=None): - global_server_ids = self.config.get('global', type) + def enable_check(self, server_type=None): + t = server_type + global_server_ids = self.config.get('global', t) if global_server_ids.lower() in ['false', 'no', '0']: - logger.info('{} disabled.'.format(type.upper())) + logger.info('{} disabled.'.format(t.upper())) return False else: - logger.info('{} : ({})'.format(type.upper(), global_server_ids)) - return global_server_ids + sids = self.clean_check(global_server_ids, t) + return sids + + def clean_check(self, server_id_list, server_type=None): + t = server_type + sid_list = server_id_list + cleaned_list = sid_list.replace(' ', '').split(',') + valid_sids = [] + for sid in cleaned_list: + try: + valid_sids.append(int(sid)) + except ValueError: + logger.error("{} is not a valid server id number".format(sid)) + + if valid_sids: + logger.info('{} : {}'.format(t.upper(), valid_sids)) + return valid_sids + else: + logger.error("No valid {}".format(t.upper())) + return False def read_file(self): file_path = join(self.data_folder, 'varken.ini') diff --git a/varken/ombi.py b/varken/ombi.py index 6c71fe0..6e0fc6e 100644 --- a/varken/ombi.py +++ b/varken/ombi.py @@ -14,6 +14,9 @@ class OmbiAPI(object): self.session = Session() self.session.headers = {'Apikey': self.server.api_key} + def __repr__(self): + return "<ombi-{}>".format(self.server.id) + def get_total_requests(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() tv_endpoint = '/api/v1/Request/tv' diff --git a/varken/radarr.py b/varken/radarr.py index 2dddacb..62ad2bf 100644 --- a/varken/radarr.py +++ b/varken/radarr.py @@ -14,6 +14,9 @@ class RadarrAPI(object): self.session = Session() self.session.headers = {'X-Api-Key': self.server.api_key} + def __repr__(self): + return "<radarr-{}>".format(self.server.id) + def get_missing(self): endpoint = '/api/movie' self.now = datetime.now(timezone.utc).astimezone().isoformat() @@ -29,8 +32,8 @@ class RadarrAPI(object): movies = [Movie(**movie) for movie in get] for movie in movies: - if self.server.get_missing: - if not movie.downloaded and movie.isAvailable: + if not movie.downloaded: + if movie.isAvailable: ma = True else: ma = False diff --git a/varken/sonarr.py b/varken/sonarr.py index 81a4d38..335ea84 100644 --- a/varken/sonarr.py +++ b/varken/sonarr.py @@ -16,6 +16,9 @@ class SonarrAPI(object): self.session.headers = {'X-Api-Key': self.server.api_key} self.session.params = {'pageSize': 1000} + def __repr__(self): + return "<sonarr-{}>".format(self.server.id) + def get_missing(self): endpoint = '/api/calendar' last_days = str(date.today() + timedelta(days=-self.server.missing_days)) diff --git a/varken/tautulli.py b/varken/tautulli.py index e5779f5..b3f4be7 100644 --- a/varken/tautulli.py +++ b/varken/tautulli.py @@ -19,6 +19,9 @@ class TautulliAPI(object): self.session.params['apikey'] = self.server.api_key self.endpoint = '/api/v2' + def __repr__(self): + return "<tautulli-{}>".format(self.server.id) + def get_activity(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() params = {'cmd': 'get_activity'} From dce59eb28a858592242fbfd523e230b57eb44ece Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Tue, 4 Dec 2018 21:45:58 -0600 Subject: [PATCH 088/120] kill script if no services enabled --- Varken.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Varken.py b/Varken.py index cbfe3da..b2d6c17 100644 --- a/Varken.py +++ b/Varken.py @@ -96,6 +96,9 @@ if __name__ == "__main__": schedule.every(server.request_total_run_seconds).seconds.do(threaded, OMBI.get_total_requests) # Run all on startup + SERVICES_ENABLED = [CONFIG.ombi_enabled, CONFIG.radarr_enabled, CONFIG.tautulli_enabled, CONFIG.sonarr_enabled] + if not [enabled for enabled in SERVICES_ENABLED if enabled]: + exit("All services disabled. Exiting") schedule.run_all() while True: From 002e7b65c1cbbd4b8238425de31f8157eeabef39 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Tue, 4 Dec 2018 21:52:24 -0600 Subject: [PATCH 089/120] removed all traces of get_sessions and consolidated to get_activity --- Varken.py | 2 -- varken/iniparser.py | 4 +-- varken/structures.py | 2 -- varken/tautulli.py | 65 ++++++++++++++++---------------------------- 4 files changed, 25 insertions(+), 48 deletions(-) diff --git a/Varken.py b/Varken.py index b2d6c17..dd6de9a 100644 --- a/Varken.py +++ b/Varken.py @@ -76,8 +76,6 @@ if __name__ == "__main__": TAUTULLI = TautulliAPI(server, DBMANAGER) if server.get_activity: schedule.every(server.get_activity_run_seconds).seconds.do(threaded, TAUTULLI.get_activity) - if server.get_sessions: - schedule.every(server.get_sessions_run_seconds).seconds.do(threaded, TAUTULLI.get_sessions) if CONFIG.radarr_enabled: for server in CONFIG.radarr_servers: diff --git a/varken/iniparser.py b/varken/iniparser.py index daadac0..4a2c962 100644 --- a/varken/iniparser.py +++ b/varken/iniparser.py @@ -144,11 +144,9 @@ class INIParser(object): verify_ssl = False get_activity = self.config.getboolean(tautulli_section, 'get_activity') get_activity_run_seconds = self.config.getint(tautulli_section, 'get_activity_run_seconds') - get_sessions = self.config.getboolean(tautulli_section, 'get_sessions') - get_sessions_run_seconds = self.config.getint(tautulli_section, 'get_sessions_run_seconds') server = TautulliServer(server_id, scheme + url, fallback_ip, apikey, verify_ssl, get_activity, - get_activity_run_seconds, get_sessions, get_sessions_run_seconds) + get_activity_run_seconds) self.tautulli_servers.append(server) # Parse Ombi options diff --git a/varken/structures.py b/varken/structures.py index d089c35..3c977d4 100644 --- a/varken/structures.py +++ b/varken/structures.py @@ -62,8 +62,6 @@ class TautulliServer(NamedTuple): verify_ssl: bool = None get_activity: bool = False get_activity_run_seconds: int = 30 - get_sessions: bool = False - get_sessions_run_seconds: int = 30 class InfluxServer(NamedTuple): diff --git a/varken/tautulli.py b/varken/tautulli.py index b3f4be7..9592636 100644 --- a/varken/tautulli.py +++ b/varken/tautulli.py @@ -16,7 +16,7 @@ class TautulliAPI(object): self.dbmanager = dbmanager self.server = server self.session = Session() - self.session.params['apikey'] = self.server.api_key + self.session.params = {'apikey': self.server.api_key, 'cmd': 'get_activity'} self.endpoint = '/api/v2' def __repr__(self): @@ -24,53 +24,16 @@ class TautulliAPI(object): def get_activity(self): self.now = datetime.now(timezone.utc).astimezone().isoformat() - params = {'cmd': 'get_activity'} influx_payload = [] - req = self.session.prepare_request(Request('GET', self.server.url + self.endpoint, params=params)) + req = self.session.prepare_request(Request('GET', self.server.url + self.endpoint)) g = connection_handler(self.session, req, self.server.verify_ssl) if not g: return - else: - get = g['response']['data'] - influx_payload.append( - { - "measurement": "Tautulli", - "tags": { - "type": "current_stream_stats", - "server": self.server.id - }, - "time": self.now, - "fields": { - "stream_count": int(get['stream_count']), - "total_bandwidth": int(get['total_bandwidth']), - "wan_bandwidth": int(get['wan_bandwidth']), - "lan_bandwidth": int(get['lan_bandwidth']), - "transcode_streams": int(get['stream_count_transcode']), - "direct_play_streams": int(get['stream_count_direct_play']), - "direct_streams": int(get['stream_count_direct_stream']) - } - } - ) - - self.dbmanager.write_points(influx_payload) - - def get_sessions(self): - self.now = datetime.now(timezone.utc).astimezone().isoformat() - params = {'cmd': 'get_activity'} - influx_payload = [] - - req = self.session.prepare_request(Request('GET', self.server.url + self.endpoint, params=params)) - g = connection_handler(self.session, req, self.server.verify_ssl) - - if not g: - return - else: - get = g['response']['data']['sessions'] - - sessions = [TautulliStream(**session) for session in get] + get = g['response']['data'] + sessions = [TautulliStream(**session) for session in get['sessions']] for session in sessions: try: @@ -158,4 +121,24 @@ class TautulliAPI(object): } ) + influx_payload.append( + { + "measurement": "Tautulli", + "tags": { + "type": "current_stream_stats", + "server": self.server.id + }, + "time": self.now, + "fields": { + "stream_count": int(get['stream_count']), + "total_bandwidth": int(get['total_bandwidth']), + "wan_bandwidth": int(get['wan_bandwidth']), + "lan_bandwidth": int(get['lan_bandwidth']), + "transcode_streams": int(get['stream_count_transcode']), + "direct_play_streams": int(get['stream_count_direct_play']), + "direct_streams": int(get['stream_count_direct_stream']) + } + } + ) + self.dbmanager.write_points(influx_payload) From f71a7a0c2888c4c6366758abc00aa1099a405cc3 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 23:06:35 -0500 Subject: [PATCH 090/120] Thou shall not lie... --- data/varken.example.ini | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/data/varken.example.ini b/data/varken.example.ini index 27aa60f..aab1924 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -26,8 +26,6 @@ ssl = false verify_ssl = false get_activity = true get_activity_run_seconds = 30 -get_sessions = true -get_sessions_run_seconds = 30 [sonarr-1] url = sonarr1.domain.tld @@ -89,4 +87,4 @@ username = cisco password = cisco influx_db = asa ssl = false -verify_ssl = true \ No newline at end of file +verify_ssl = true From 773d081ce200e5757554849348874a653635eef8 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Tue, 4 Dec 2018 23:17:21 -0500 Subject: [PATCH 091/120] Remove username and pass from default --- data/varken.example.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/varken.example.ini b/data/varken.example.ini index aab1924..380b9d6 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -15,8 +15,8 @@ asa = false [influxdb] url = influxdb.domain.tld port = 8086 -username = root -password = root +username = +password = [tautulli-1] url = tautulli.domain.tld From 87687110e77f143667bb777a1e1d2cea322ef8ea Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Wed, 5 Dec 2018 00:41:56 -0500 Subject: [PATCH 092/120] Update note --- varken.systemd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/varken.systemd b/varken.systemd index ddc576c..78657ff 100644 --- a/varken.systemd +++ b/varken.systemd @@ -35,7 +35,7 @@ # graphical.target equates to runlevel 5 (multi-user X11 graphical mode) [Unit] -Description=Varken - A data collection and graphing tool +Description=Varken - Command-line utility to aggregate data from the Plex ecosystem into InfluxDB. After=network-online.target StartLimitInterval=200 StartLimitBurst=3 From 8289a938308da77b6754edc0fce8e06ae2c49c7a Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Wed, 5 Dec 2018 00:42:15 -0500 Subject: [PATCH 093/120] Update logger --- varken/varkenlogger.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/varken/varkenlogger.py b/varken/varkenlogger.py index bbdf4c8..92d097b 100644 --- a/varken/varkenlogger.py +++ b/varken/varkenlogger.py @@ -1,8 +1,31 @@ import logging +from logging.handlers import RotatingFileHandler +from varken.helpers import mkdir_p + +FILENAME = "varken.log" +MAX_SIZE = 5000000 # 5 MB +MAX_FILES = 5 +LOG_FOLDER = 'logs' + + class VarkenLogger(object): """docstring for .""" - def __init__(self, log_path=None, log_level=None): + def __init__(self, log_path=None, debug=None, data_folder=None): + self.data_folder = data_folder + self.log_level = debug + + # Set log level + if self.log_level: + self.log_level = logging.DEBUG + + else: + self.log_level = logging.INFO + + + # Make the log directory if it does not exist + mkdir_p('{}/{}'.format(self.data_folder, LOG_FOLDER)) + # Create the Logger self.logger = logging.getLogger() self.logger.setLevel(logging.DEBUG) @@ -11,8 +34,13 @@ class VarkenLogger(object): logger_formatter = logging.Formatter('%(asctime)s : %(levelname)s : %(module)s : %(message)s', '%Y-%m-%d %H:%M:%S') # Create the Handler for logging data to a file - file_logger = logging.FileHandler('varken.log') - file_logger.setLevel(logging.DEBUG) + file_logger = RotatingFileHandler('{}/{}/{}'.format(self.data_folder, LOG_FOLDER, FILENAME), + mode='a', maxBytes=MAX_SIZE, + backupCount=MAX_FILES, + encoding=None, delay=0 + ) + + file_logger.setLevel(self.log_level) # Add the Formatter to the Handler file_logger.setFormatter(logger_formatter) @@ -20,7 +48,7 @@ class VarkenLogger(object): # Add the console logger console_logger = logging.StreamHandler() console_logger.setFormatter(logger_formatter) - console_logger.setLevel(logging.INFO) + console_logger.setLevel(self.log_level) # Add the Handler to the Logger self.logger.addHandler(file_logger) From 98f6d4516e7d8519d89cfcd62ce6de6a5cbe9e67 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Wed, 5 Dec 2018 00:42:43 -0500 Subject: [PATCH 094/120] Fix logging and add new make dir function --- varken/helpers.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/varken/helpers.py b/varken/helpers.py index 8806f5b..00ca318 100644 --- a/varken/helpers.py +++ b/varken/helpers.py @@ -4,6 +4,7 @@ import tarfile import hashlib import geoip2.database import logging + from functools import update_wrapper from json.decoder import JSONDecodeError from os.path import abspath, join @@ -67,13 +68,21 @@ def connection_handler(session, request, verify): try: return_json = get.json() except JSONDecodeError: - logger.info('No JSON response... BORKED! Let us know in discord') + logger.error('No JSON response... BORKED! Let us know in discord') except InvalidSchema: - logger.info('You added http(s):// in the config file. Don\'t do that.') + logger.error('You added http(s):// in the config file. Don\'t do that.') except SSLError as e: - logger.info('Either your host is unreachable or you have an ssl issue.') - logger.info('The issue was: {}'.format(e)) + logger.error('Either your host is unreachable or you have an SSL issue. : %s', e) return return_json + + +def mkdir_p(path): + """http://stackoverflow.com/a/600612/190597 (tzot)""" + try: + logger.info('Creating folder %s ', path) + os.makedirs(path, exist_ok=True) + except Exception as e: + logger.error('Could not create folder %s : %s ', path, e) From 8245dfd69c0437d92a5090b514edfd873766dfb2 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Wed, 5 Dec 2018 00:43:15 -0500 Subject: [PATCH 095/120] Move logger and add --debug --- Varken.py | 22 +++++++++++----------- varken/tautulli.py | 1 + 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Varken.py b/Varken.py index dd6de9a..1e1e94c 100644 --- a/Varken.py +++ b/Varken.py @@ -26,22 +26,12 @@ def threaded(job): if __name__ == "__main__": - # Initiate the logger - vl = VarkenLogger() - vl.logger.info('Starting Varken...') - vl.logger.info(u"{} {} ({}{})".format( - platform.system(), platform.release(), platform.version(), - ' - {}'.format(PLATFORM_LINUX_DISTRO) if PLATFORM_LINUX_DISTRO else '' - )) - vl.logger.info(u"Python {}".format(sys.version)) - - parser = ArgumentParser(prog='varken', description='Command-line utility to aggregate data from the plex ecosystem into InfluxDB', formatter_class=RawTextHelpFormatter) parser.add_argument("-d", "--data-folder", help='Define an alternate data folder location') - parser.add_argument("-l", "--log-level", choices=['info', 'error', 'debug'], help='Not yet implemented') + parser.add_argument("-D", "--debug", action='store_true', help='Use to enable DEBUG logging') opts = parser.parse_args() @@ -57,6 +47,16 @@ if __name__ == "__main__": else: exit("{} does not exist".format(ARG_FOLDER)) + # Initiate the logger + vl = VarkenLogger(data_folder=DATA_FOLDER, debug=opts.debug) + vl.logger.info('Starting Varken...') + + vl.logger.info(u"{} {} ({}{})".format( + platform.system(), platform.release(), platform.version(), + ' - {}'.format(PLATFORM_LINUX_DISTRO) if PLATFORM_LINUX_DISTRO else '' + )) + vl.logger.info(u"Python {}".format(sys.version)) + CONFIG = INIParser(DATA_FOLDER) DBMANAGER = DBManager(CONFIG.influx_server) diff --git a/varken/tautulli.py b/varken/tautulli.py index 9592636..e6f183f 100644 --- a/varken/tautulli.py +++ b/varken/tautulli.py @@ -1,4 +1,5 @@ import logging + from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError from requests import Session, Request From f5b44f3c05499db35311bef6a66d0b2d9b3bc724 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Wed, 5 Dec 2018 10:02:44 -0500 Subject: [PATCH 096/120] Verify SSL by default --- data/varken.example.ini | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/varken.example.ini b/data/varken.example.ini index 380b9d6..f38217f 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -16,14 +16,14 @@ asa = false url = influxdb.domain.tld port = 8086 username = -password = +password = [tautulli-1] url = tautulli.domain.tld fallback_ip = 0.0.0.0 apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = false +verify_ssl = true get_activity = true get_activity_run_seconds = 30 @@ -31,7 +31,7 @@ get_activity_run_seconds = 30 url = sonarr1.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = false +verify_ssl = true missing_days = 7 missing_days_run_seconds = 300 future_days = 1 @@ -43,7 +43,7 @@ queue_run_seconds = 300 url = sonarr2.domain.tld apikey = yyyyyyyyyyyyyyyy ssl = false -verify_ssl = false +verify_ssl = true missing_days = 7 missing_days_run_seconds = 300 future_days = 1 @@ -55,7 +55,7 @@ queue_run_seconds = 300 url = radarr1.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = false +verify_ssl = true queue = true queue_run_seconds = 300 get_missing = true @@ -65,7 +65,7 @@ get_missing_run_seconds = 300 url = radarr2.domain.tld apikey = yyyyyyyyyyyyyyyy ssl = false -verify_ssl = false +verify_ssl = true queue = true queue_run_seconds = 300 get_missing = true @@ -75,7 +75,7 @@ get_missing_run_seconds = 300 url = ombi.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false -verify_ssl = false +verify_ssl = true get_request_type_counts = true request_type_run_seconds = 300 get_request_total_counts = true From a4d44f8727a0a25b8d9e415b87a8c21928fb8f25 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Wed, 5 Dec 2018 10:33:36 -0500 Subject: [PATCH 097/120] Fix links and typos --- README.md | 8 ++++---- varken.systemd | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f4967ee..7ba149e 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,14 @@ Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), </p> ## Quick Setup (Varken Alpha) -1. Clone the repository `sudo git clone https://github.com/DirtyCajunRice/grafana-scripts.git /opt/varken` +1. Clone the repository `sudo git clone https://github.com/Boerderij/Varken.git /opt/Varken` 1. Follow the systemd install instructions located in `varken.systemd` 1. Create venv in project `/usr/bin/python3 -m venv varken-venv` -1. Install requirements `/opt/varken/varken-venv/bin/python -m pip install -r requirements.txt` +1. Install requirements `/opt/Varken/varken-venv/bin/python -m pip install -r requirements.txt` 1. Make a copy of `varken.example.ini` to `varken.ini` in the `data` folder - `cp /opt/varken/data/varken.example.ini /opt/varken/data/varken.ini` + `cp /opt/Varken/data/varken.example.ini /opt/Varken/data/varken.ini` 1. Make the appropriate changes to `varken.ini` - ie.`nano /opt/varken/data/varken.ini` + ie.`nano /opt/Varken/data/varken.ini` 1. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. 1. Install `grafana-cli plugins install grafana-worldmap-panel` 1. TODO:: Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. diff --git a/varken.systemd b/varken.systemd index 78657ff..f8aefe6 100644 --- a/varken.systemd +++ b/varken.systemd @@ -6,7 +6,7 @@ # # 1. Copy this file into your systemd service unit directory (often '/lib/systemd/system') # and name it 'varken.service' with the following command: -# cp /opt/Varken/Varken/varken.systemd /lib/systemd/system/varken.service +# cp /opt/Varken/varken.systemd /lib/systemd/system/varken.service # # 2. Edit the new varken.service file with configuration settings as required. # More details in the "CONFIGURATION NOTES" section shown below. From 79ddbf68be1b5f8a14f3dee851c11eff66b4e8ed Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Wed, 5 Dec 2018 11:42:21 -0500 Subject: [PATCH 098/120] Add sub_type to tautulli tuple --- varken/structures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/varken/structures.py b/varken/structures.py index 3c977d4..4c5ca58 100644 --- a/varken/structures.py +++ b/varken/structures.py @@ -265,6 +265,7 @@ class TautulliStream(NamedTuple): transcode_progress: int = None subtitle_language: str = None stream_subtitle_container: str = None + sub_type: str = None class TVShow(NamedTuple): @@ -323,4 +324,4 @@ class Movie(NamedTuple): physicalRelease: str = None physicalReleaseNote: str = None website: str = None - id: int = None \ No newline at end of file + id: int = None From 308cca4c1bf147323bbe14e34483289dd2eab803 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Wed, 5 Dec 2018 11:52:42 -0500 Subject: [PATCH 099/120] Add exception logging for tautulli --- varken/tautulli.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/varken/tautulli.py b/varken/tautulli.py index e6f183f..5931179 100644 --- a/varken/tautulli.py +++ b/varken/tautulli.py @@ -34,7 +34,11 @@ class TautulliAPI(object): return get = g['response']['data'] - sessions = [TautulliStream(**session) for session in get['sessions']] + try: + sessions = [TautulliStream(**session) for session in get['sessions']] + except TypeError as e: + logger.error('TypeError has occured : %s', e) + return for session in sessions: try: From 4907f9c4061a7480d6d263e1597b6f76cb5d54c4 Mon Sep 17 00:00:00 2001 From: samwiseg0 <samwiseg00@gmail.com> Date: Wed, 5 Dec 2018 13:59:22 -0500 Subject: [PATCH 100/120] Update README and log messgaes --- README.md | 3 ++- varken/iniparser.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7ba149e..5ddecfe 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,13 @@ Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), ## Quick Setup (Varken Alpha) 1. Clone the repository `sudo git clone https://github.com/Boerderij/Varken.git /opt/Varken` 1. Follow the systemd install instructions located in `varken.systemd` -1. Create venv in project `/usr/bin/python3 -m venv varken-venv` +1. Create venv in project `cd /opt/Varken && /usr/bin/python3 -m venv varken-venv` 1. Install requirements `/opt/Varken/varken-venv/bin/python -m pip install -r requirements.txt` 1. Make a copy of `varken.example.ini` to `varken.ini` in the `data` folder `cp /opt/Varken/data/varken.example.ini /opt/Varken/data/varken.ini` 1. Make the appropriate changes to `varken.ini` ie.`nano /opt/Varken/data/varken.ini` +1. Make sure all the files have the appropriate permissions `sudo chown varken:varken -R /opt/Varken` 1. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. 1. Install `grafana-cli plugins install grafana-worldmap-panel` 1. TODO:: Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. diff --git a/varken/iniparser.py b/varken/iniparser.py index 4a2c962..7c12d2f 100644 --- a/varken/iniparser.py +++ b/varken/iniparser.py @@ -36,7 +36,7 @@ class INIParser(object): t = server_type global_server_ids = self.config.get('global', t) if global_server_ids.lower() in ['false', 'no', '0']: - logger.info('{} disabled.'.format(t.upper())) + logger.info('%s disabled.', t.upper()) return False else: sids = self.clean_check(global_server_ids, t) @@ -54,10 +54,10 @@ class INIParser(object): logger.error("{} is not a valid server id number".format(sid)) if valid_sids: - logger.info('{} : {}'.format(t.upper(), valid_sids)) + logger.info('%s : %s', t.upper(), valid_sids) return valid_sids else: - logger.error("No valid {}".format(t.upper())) + logger.error('No valid %s', t.upper()) return False def read_file(self): @@ -66,7 +66,7 @@ class INIParser(object): with open(file_path) as config_ini: self.config.read_file(config_ini) else: - exit("You do not have a varken.ini file in {}".format(self.data_folder)) + exit('Config file missing (varken.ini) in {}'.format(self.data_folder)) def parse_opts(self): self.read_file() From b27066a2133db350e30b0ce87169e9c0d9cd2128 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 11:17:57 -0600 Subject: [PATCH 101/120] enhancement: moved logger to class as it should be. also fixed a typo --- varken/sonarr.py | 7 ++++++- varken/tautulli.py | 5 ++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/varken/sonarr.py b/varken/sonarr.py index 335ea84..1ff0263 100644 --- a/varken/sonarr.py +++ b/varken/sonarr.py @@ -4,6 +4,7 @@ from datetime import datetime, timezone, date, timedelta from varken.helpers import hashit, connection_handler from varken.structures import Queue, TVShow + class SonarrAPI(object): def __init__(self, server, dbmanager): # Set Time of initialization @@ -34,7 +35,11 @@ class SonarrAPI(object): return # Iteratively create a list of TVShow Objects from response json - tv_shows = [TVShow(**show) for show in get] + try: + tv_shows = [TVShow(**show) for show in get] + except TypeError as e: + logger.error('TypeError has occurred : %s', e) + return # Add show to missing list if file does not exist for show in tv_shows: diff --git a/varken/tautulli.py b/varken/tautulli.py index 5931179..ebd2573 100644 --- a/varken/tautulli.py +++ b/varken/tautulli.py @@ -7,8 +7,6 @@ from requests import Session, Request from varken.helpers import geo_lookup, hashit, connection_handler from varken.structures import TautulliStream -logger = logging.getLogger() - class TautulliAPI(object): def __init__(self, server, dbmanager): @@ -19,6 +17,7 @@ class TautulliAPI(object): self.session = Session() self.session.params = {'apikey': self.server.api_key, 'cmd': 'get_activity'} self.endpoint = '/api/v2' + self.logger = logging.getLogger() def __repr__(self): return "<tautulli-{}>".format(self.server.id) @@ -37,7 +36,7 @@ class TautulliAPI(object): try: sessions = [TautulliStream(**session) for session in get['sessions']] except TypeError as e: - logger.error('TypeError has occured : %s', e) + self.logger.error('TypeError has occurred : %s', e) return for session in sessions: From 59bfb4a567c78e67490fe277203bba291feef9f6 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 11:39:51 -0600 Subject: [PATCH 102/120] added logger to all subclasses, and added exception handling for structure creation --- varken/ombi.py | 2 ++ varken/radarr.py | 21 ++++++++++++++++++--- varken/sonarr.py | 16 +++++++++++++--- varken/tautulli.py | 4 ++-- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/varken/ombi.py b/varken/ombi.py index 6e0fc6e..dbc582b 100644 --- a/varken/ombi.py +++ b/varken/ombi.py @@ -1,3 +1,4 @@ +import logging from requests import Session, Request from datetime import datetime, timezone @@ -13,6 +14,7 @@ class OmbiAPI(object): # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = Session() self.session.headers = {'Apikey': self.server.api_key} + self.logger = logging.getLogger() def __repr__(self): return "<ombi-{}>".format(self.server.id) diff --git a/varken/radarr.py b/varken/radarr.py index 62ad2bf..ec4d723 100644 --- a/varken/radarr.py +++ b/varken/radarr.py @@ -1,3 +1,4 @@ +import logging from requests import Session, Request from datetime import datetime, timezone @@ -13,6 +14,7 @@ class RadarrAPI(object): # Create session to reduce server web thread load, and globally define pageSize for all requests self.session = Session() self.session.headers = {'X-Api-Key': self.server.api_key} + self.logger = logging.getLogger() def __repr__(self): return "<radarr-{}>".format(self.server.id) @@ -29,7 +31,11 @@ class RadarrAPI(object): if not get: return - movies = [Movie(**movie) for movie in get] + try: + movies = [Movie(**movie) for movie in get] + except TypeError as e: + self.logger.error('TypeError has occurred : %s', e) + return for movie in movies: if not movie.downloaded: @@ -74,8 +80,17 @@ class RadarrAPI(object): return for movie in get: - movie['movie'] = Movie(**movie['movie']) - download_queue = [Queue(**movie) for movie in get] + try: + movie['movie'] = Movie(**movie['movie']) + except TypeError as e: + self.logger.error('TypeError has occurred : %s', e) + return + + try: + download_queue = [Queue(**movie) for movie in get] + except TypeError as e: + self.logger.error('TypeError has occurred : %s', e) + return for queue_item in download_queue: name = '{} ({})'.format(queue_item.movie.title, queue_item.movie.year) diff --git a/varken/sonarr.py b/varken/sonarr.py index 1ff0263..65f2035 100644 --- a/varken/sonarr.py +++ b/varken/sonarr.py @@ -1,3 +1,4 @@ +import logging from requests import Session, Request from datetime import datetime, timezone, date, timedelta @@ -16,6 +17,7 @@ class SonarrAPI(object): self.session = Session() self.session.headers = {'X-Api-Key': self.server.api_key} self.session.params = {'pageSize': 1000} + self.logger = logging.getLogger() def __repr__(self): return "<sonarr-{}>".format(self.server.id) @@ -38,7 +40,7 @@ class SonarrAPI(object): try: tv_shows = [TVShow(**show) for show in get] except TypeError as e: - logger.error('TypeError has occurred : %s', e) + self.logger.error('TypeError has occurred : %s', e) return # Add show to missing list if file does not exist @@ -85,7 +87,11 @@ class SonarrAPI(object): if not get: return - tv_shows = [TVShow(**show) for show in get] + try: + tv_shows = [TVShow(**show) for show in get] + except TypeError as e: + self.logger.error('TypeError has occurred : %s', e) + return for show in tv_shows: sxe = 'S{:0>2}E{:0>2}'.format(show.seasonNumber, show.episodeNumber) @@ -131,7 +137,11 @@ class SonarrAPI(object): if not get: return - download_queue = [Queue(**show) for show in get] + try: + download_queue = [Queue(**show) for show in get] + except TypeError as e: + self.logger.error('TypeError has occurred : %s', e) + return for show in download_queue: sxe = 'S{:0>2}E{:0>2}'.format(show.episode['seasonNumber'], show.episode['episodeNumber']) diff --git a/varken/tautulli.py b/varken/tautulli.py index ebd2573..d4a5345 100644 --- a/varken/tautulli.py +++ b/varken/tautulli.py @@ -1,8 +1,7 @@ import logging - +from requests import Session, Request from datetime import datetime, timezone from geoip2.errors import AddressNotFoundError -from requests import Session, Request from varken.helpers import geo_lookup, hashit, connection_handler from varken.structures import TautulliStream @@ -33,6 +32,7 @@ class TautulliAPI(object): return get = g['response']['data'] + try: sessions = [TautulliStream(**session) for session in get['sessions']] except TypeError as e: From 743f35842e44bb14ebce47be16b2fbf002a8d544 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 12:04:02 -0600 Subject: [PATCH 103/120] made more verbose errors for TypeError --- varken/radarr.py | 15 ++++++++------- varken/sonarr.py | 6 +++--- varken/tautulli.py | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/varken/radarr.py b/varken/radarr.py index ec4d723..db4dd2e 100644 --- a/varken/radarr.py +++ b/varken/radarr.py @@ -34,7 +34,7 @@ class RadarrAPI(object): try: movies = [Movie(**movie) for movie in get] except TypeError as e: - self.logger.error('TypeError has occurred : %s', e) + self.logger.error('TypeError has occurred : %s while creating Movie structure', e) return for movie in movies: @@ -83,17 +83,18 @@ class RadarrAPI(object): try: movie['movie'] = Movie(**movie['movie']) except TypeError as e: - self.logger.error('TypeError has occurred : %s', e) + self.logger.error('TypeError has occurred : %s while creating Movie structure', e) return try: download_queue = [Queue(**movie) for movie in get] except TypeError as e: - self.logger.error('TypeError has occurred : %s', e) + self.logger.error('TypeError has occurred : %s while creating Queue structure', e) return for queue_item in download_queue: - name = '{} ({})'.format(queue_item.movie.title, queue_item.movie.year) + movie = queue_item.movie + name = '{} ({})'.format(movie.title, movie.year) if queue_item.protocol.upper() == 'USENET': protocol_id = 1 @@ -103,8 +104,8 @@ class RadarrAPI(object): queue.append((name, queue_item.quality['quality']['name'], queue_item.protocol.upper(), protocol_id, queue_item.id)) - for movie, quality, protocol, protocol_id, qid in queue: - hash_id = hashit('{}{}{}'.format(self.server.id, movie, quality)) + for name, quality, protocol, protocol_id, qid in queue: + hash_id = hashit('{}{}{}'.format(self.server.id, name, quality)) influx_payload.append( { "measurement": "Radarr", @@ -112,7 +113,7 @@ class RadarrAPI(object): "type": "Queue", "tmdbId": qid, "server": self.server.id, - "name": movie, + "name": name, "quality": quality, "protocol": protocol, "protocol_id": protocol_id diff --git a/varken/sonarr.py b/varken/sonarr.py index 65f2035..8f71817 100644 --- a/varken/sonarr.py +++ b/varken/sonarr.py @@ -40,7 +40,7 @@ class SonarrAPI(object): try: tv_shows = [TVShow(**show) for show in get] except TypeError as e: - self.logger.error('TypeError has occurred : %s', e) + self.logger.error('TypeError has occurred : %s while creating TVShow structure', e) return # Add show to missing list if file does not exist @@ -90,7 +90,7 @@ class SonarrAPI(object): try: tv_shows = [TVShow(**show) for show in get] except TypeError as e: - self.logger.error('TypeError has occurred : %s', e) + self.logger.error('TypeError has occurred : %s while creating TVShow structure', e) return for show in tv_shows: @@ -140,7 +140,7 @@ class SonarrAPI(object): try: download_queue = [Queue(**show) for show in get] except TypeError as e: - self.logger.error('TypeError has occurred : %s', e) + self.logger.error('TypeError has occurred : %s while creating Queue structure', e) return for show in download_queue: diff --git a/varken/tautulli.py b/varken/tautulli.py index d4a5345..5ce6773 100644 --- a/varken/tautulli.py +++ b/varken/tautulli.py @@ -36,7 +36,7 @@ class TautulliAPI(object): try: sessions = [TautulliStream(**session) for session in get['sessions']] except TypeError as e: - self.logger.error('TypeError has occurred : %s', e) + self.logger.error('TypeError has occurred : %s while creating TautulliStream structure', e) return for session in sessions: From 46ddea036be87457eccc387e51587f1555d26aaa Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 12:05:03 -0600 Subject: [PATCH 104/120] added missing key sceneAbsoluteEpisodeNumber --- varken/structures.py | 1 + 1 file changed, 1 insertion(+) diff --git a/varken/structures.py b/varken/structures.py index 4c5ca58..9684c40 100644 --- a/varken/structures.py +++ b/varken/structures.py @@ -282,6 +282,7 @@ class TVShow(NamedTuple): monitored: bool = None unverifiedSceneNumbering: bool = None absoluteEpisodeNumber: int = None + sceneAbsoluteEpisodeNumber: int = None series: dict = None id: int = None From 74021c8502f12886e4e4f2cc8c49f793691006c2 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 12:05:39 -0600 Subject: [PATCH 105/120] added missing key sceneEpisodeNumber --- varken/structures.py | 1 + 1 file changed, 1 insertion(+) diff --git a/varken/structures.py b/varken/structures.py index 9684c40..55b749d 100644 --- a/varken/structures.py +++ b/varken/structures.py @@ -283,6 +283,7 @@ class TVShow(NamedTuple): unverifiedSceneNumbering: bool = None absoluteEpisodeNumber: int = None sceneAbsoluteEpisodeNumber: int = None + sceneEpisodeNumber: int = None series: dict = None id: int = None From 33f947c57e49865afa4f4f1e1dfec4e870f63750 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 12:06:18 -0600 Subject: [PATCH 106/120] added missing key sceneSeasonNumber --- varken/structures.py | 1 + 1 file changed, 1 insertion(+) diff --git a/varken/structures.py b/varken/structures.py index 55b749d..d934ece 100644 --- a/varken/structures.py +++ b/varken/structures.py @@ -284,6 +284,7 @@ class TVShow(NamedTuple): absoluteEpisodeNumber: int = None sceneAbsoluteEpisodeNumber: int = None sceneEpisodeNumber: int = None + sceneSeasonNumber: int = None series: dict = None id: int = None From 03331bbad434bba886cee49dca069952ec86fa6d Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 12:16:17 -0600 Subject: [PATCH 107/120] version bump --- varken/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/varken/__init__.py b/varken/__init__.py index e69de29..cd46408 100644 --- a/varken/__init__.py +++ b/varken/__init__.py @@ -0,0 +1 @@ +VERSION = 0.2 From 6c44b230b795967aabe9c0e88444c07bcdf64631 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 12:25:33 -0600 Subject: [PATCH 108/120] added changelog. fixes #39 --- CHANGELOG.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2f2552f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,63 @@ +# Change Log + +## [Unreleased](https://github.com/Boerderij/Varken/tree/HEAD) + +[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.2...HEAD) + +**Implemented enhancements:** + +- Tautulli - multiple server support? [\#25](https://github.com/Boerderij/Varken/issues/25) + +**Closed issues:** + +- Create the DB if it does not exist. [\#38](https://github.com/Boerderij/Varken/issues/38) +- create systemd examples [\#37](https://github.com/Boerderij/Varken/issues/37) +- Create a GeoIP db downloader and refresher [\#36](https://github.com/Boerderij/Varken/issues/36) +- Create unique IDs for all scripts to prevent duplicate data [\#35](https://github.com/Boerderij/Varken/issues/35) +- use a config.ini instead of command-line flags [\#33](https://github.com/Boerderij/Varken/issues/33) +- Migrate crontab to python schedule package [\#31](https://github.com/Boerderij/Varken/issues/31) +- Consolidate missing and missing\_days in sonarr.py [\#30](https://github.com/Boerderij/Varken/issues/30) +- Ombi something new \[Request\] [\#26](https://github.com/Boerderij/Varken/issues/26) + +**Merged pull requests:** + +- varken to nightly [\#40](https://github.com/Boerderij/Varken/pull/40) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) + +## [v0.2](https://github.com/Boerderij/Varken/tree/v0.2) (2018-10-30) +[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.1...v0.2) + +**Closed issues:** + +- Support for Linux without ASA [\#21](https://github.com/Boerderij/Varken/issues/21) + +## [v0.1](https://github.com/Boerderij/Varken/tree/v0.1) (2018-10-20) +**Implemented enhancements:** + +- The address 172.17.0.1 is not in the database. [\#17](https://github.com/Boerderij/Varken/issues/17) +- Local streams aren't showing with Tautulli [\#16](https://github.com/Boerderij/Varken/issues/16) +- Worldmap panel [\#15](https://github.com/Boerderij/Varken/issues/15) + +**Closed issues:** + +- Tautulli.py not working. [\#18](https://github.com/Boerderij/Varken/issues/18) +- Issues with scripts [\#12](https://github.com/Boerderij/Varken/issues/12) +- issue with new tautulli.py [\#10](https://github.com/Boerderij/Varken/issues/10) +- ombi.py fails when attempting to update influxdb [\#9](https://github.com/Boerderij/Varken/issues/9) +- GeoIP Going to Break July 1st [\#8](https://github.com/Boerderij/Varken/issues/8) +- \[Request\] Documentation / How-to Guide [\#1](https://github.com/Boerderij/Varken/issues/1) + +**Merged pull requests:** + +- v0.1 [\#20](https://github.com/Boerderij/Varken/pull/20) ([samwiseg0](https://github.com/samwiseg0)) +- Added selfplug [\#19](https://github.com/Boerderij/Varken/pull/19) ([si0972](https://github.com/si0972)) +- Major rework of the scripts [\#14](https://github.com/Boerderij/Varken/pull/14) ([samwiseg0](https://github.com/samwiseg0)) +- fix worldmap after change to maxmind local db [\#11](https://github.com/Boerderij/Varken/pull/11) ([madbuda](https://github.com/madbuda)) +- Update sonarr.py [\#7](https://github.com/Boerderij/Varken/pull/7) ([ghost](https://github.com/ghost)) +- Create crontabs [\#6](https://github.com/Boerderij/Varken/pull/6) ([ghost](https://github.com/ghost)) +- update plex\_dashboard.json [\#5](https://github.com/Boerderij/Varken/pull/5) ([ghost](https://github.com/ghost)) +- Update README.md [\#4](https://github.com/Boerderij/Varken/pull/4) ([ghost](https://github.com/ghost)) +- added sickrage portion [\#3](https://github.com/Boerderij/Varken/pull/3) ([ghost](https://github.com/ghost)) + + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file From fdc5dfdf973544af73f34654757f787e91d97eae Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 12:36:39 -0600 Subject: [PATCH 109/120] Ok. Now i understand --- CHANGELOG.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f2552f..fd285f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ [Full Changelog](https://github.com/Boerderij/Varken/compare/v0.2...HEAD) +**Closed issues:** + +- Create Changelog for nightly release [\#39](https://github.com/Boerderij/Varken/issues/39) + +## [v0.2](https://github.com/Boerderij/Varken/tree/v0.2) (2018-12-06) +[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.1...v0.2) + **Implemented enhancements:** - Tautulli - multiple server support? [\#25](https://github.com/Boerderij/Varken/issues/25) @@ -18,18 +25,12 @@ - Migrate crontab to python schedule package [\#31](https://github.com/Boerderij/Varken/issues/31) - Consolidate missing and missing\_days in sonarr.py [\#30](https://github.com/Boerderij/Varken/issues/30) - Ombi something new \[Request\] [\#26](https://github.com/Boerderij/Varken/issues/26) +- Support for Linux without ASA [\#21](https://github.com/Boerderij/Varken/issues/21) **Merged pull requests:** - varken to nightly [\#40](https://github.com/Boerderij/Varken/pull/40) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) -## [v0.2](https://github.com/Boerderij/Varken/tree/v0.2) (2018-10-30) -[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.1...v0.2) - -**Closed issues:** - -- Support for Linux without ASA [\#21](https://github.com/Boerderij/Varken/issues/21) - ## [v0.1](https://github.com/Boerderij/Varken/tree/v0.1) (2018-10-20) **Implemented enhancements:** From 550e6fa853b2fb68ff63de2237bb4bd8d4768e72 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 14:18:50 -0600 Subject: [PATCH 110/120] Retagged nightly relase as pre-release with -nightly suffix. Fixes #39 --- CHANGELOG.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd285f8..63bf645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,7 @@ # Change Log -## [Unreleased](https://github.com/Boerderij/Varken/tree/HEAD) - -[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.2...HEAD) - -**Closed issues:** - -- Create Changelog for nightly release [\#39](https://github.com/Boerderij/Varken/issues/39) - -## [v0.2](https://github.com/Boerderij/Varken/tree/v0.2) (2018-12-06) -[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.1...v0.2) +## [v0.2-nightly](https://github.com/Boerderij/Varken/tree/v0.2-nightly) (2018-12-06) +[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.1...v0.2-nightly) **Implemented enhancements:** From 1dcb0ad724120e71ed7fc8ad4e58454b41934eff Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 14:37:27 -0600 Subject: [PATCH 111/120] closes #42 --- dashboard/beta_online_users_table.json | 316 ---- .../piechart_tautulli_device_types.json | 122 -- .../piechart_tautulli_device_types.png | Bin 35595 -> 0 bytes .../piechart_tautulli_stream_types.json | 109 -- .../piechart_tautulli_stream_types.png | Bin 30810 -> 0 bytes .../singlestat_ombi_requests_pending.json | 107 -- .../singlestat_ombi_requests_pending.png | Bin 3750 -> 0 bytes .../table_radarr_missing_avail_movies.json | 109 -- .../table_radarr_missing_avail_movies.png | Bin 6904 -> 0 bytes .../example_panels/table_radarr_queue.json | 161 -- .../example_panels/table_radarr_queue.png | Bin 5769 -> 0 bytes .../example_panels/table_sonarr_missing.json | 149 -- .../example_panels/table_sonarr_missing.png | Bin 14394 -> 0 bytes .../example_panels/table_sonarr_on_today.json | 163 -- .../example_panels/table_sonarr_on_today.png | Bin 49745 -> 0 bytes .../example_panels/table_sonarr_queue.json | 154 -- .../example_panels/table_sonarr_queue.png | Bin 7369 -> 0 bytes dashboard/panel_row_worldmap.json | 121 -- dashboard/panel_us_only_worldmap.json | 99 -- dashboard/plex_row_dashboard.json | 1363 ----------------- dashboard/plex_us_only_dashboard.json | 1351 ---------------- 21 files changed, 4324 deletions(-) delete mode 100644 dashboard/beta_online_users_table.json delete mode 100644 dashboard/example_panels/piechart_tautulli_device_types.json delete mode 100644 dashboard/example_panels/piechart_tautulli_device_types.png delete mode 100644 dashboard/example_panels/piechart_tautulli_stream_types.json delete mode 100644 dashboard/example_panels/piechart_tautulli_stream_types.png delete mode 100644 dashboard/example_panels/singlestat_ombi_requests_pending.json delete mode 100644 dashboard/example_panels/singlestat_ombi_requests_pending.png delete mode 100644 dashboard/example_panels/table_radarr_missing_avail_movies.json delete mode 100644 dashboard/example_panels/table_radarr_missing_avail_movies.png delete mode 100644 dashboard/example_panels/table_radarr_queue.json delete mode 100644 dashboard/example_panels/table_radarr_queue.png delete mode 100644 dashboard/example_panels/table_sonarr_missing.json delete mode 100644 dashboard/example_panels/table_sonarr_missing.png delete mode 100644 dashboard/example_panels/table_sonarr_on_today.json delete mode 100644 dashboard/example_panels/table_sonarr_on_today.png delete mode 100644 dashboard/example_panels/table_sonarr_queue.json delete mode 100644 dashboard/example_panels/table_sonarr_queue.png delete mode 100644 dashboard/panel_row_worldmap.json delete mode 100644 dashboard/panel_us_only_worldmap.json delete mode 100644 dashboard/plex_row_dashboard.json delete mode 100644 dashboard/plex_us_only_dashboard.json diff --git a/dashboard/beta_online_users_table.json b/dashboard/beta_online_users_table.json deleted file mode 100644 index 09bc070..0000000 --- a/dashboard/beta_online_users_table.json +++ /dev/null @@ -1,316 +0,0 @@ -{ - "columns": [], - "datasource": "plex", - "fontSize": "100%", - "gridPos": { - "h": 13, - "w": 16, - "x": 0, - "y": 16 - }, - "hideTimeOverride": true, - "id": 9, - "interval": "", - "links": [ - { - "targetBlank": true, - "title": "Tautulli", - "type": "absolute", - "url": "https://tautulli.domain.tld" - } - ], - "minSpan": 12, - "pageSize": 14, - "scroll": true, - "showHeader": true, - "sort": { - "col": 9, - "desc": true - }, - "styles": [ - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "MM/DD/YY h:mm:ss a", - "decimals": 2, - "link": false, - "pattern": "Time", - "thresholds": [], - "type": "hidden", - "unit": "short" - }, - { - "alias": "User", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "name", - "preserveFormat": false, - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Media", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "title", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Decision", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "video_decision", - "preserveFormat": false, - "sanitize": false, - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Quality", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "quality", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Limits", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "quality_profile", - "preserveFormat": true, - "sanitize": false, - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Version", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "pattern": "product_version", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Device", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "platform", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "distinct", - "thresholds": [], - "type": "hidden", - "unit": "short" - }, - { - "alias": "Location", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "location", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Player State", - "colorMode": "row", - "colors": [ - "rgba(50, 172, 45, 0.3)", - "rgba(14, 221, 229, 0.56)", - "rgba(214, 103, 28, 0.8)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "link": false, - "linkTargetBlank": false, - "linkTooltip": "", - "linkUrl": "", - "mappingType": 1, - "pattern": "player_state", - "thresholds": [ - "1", - "3" - ], - "type": "string", - "unit": "none", - "valueMaps": [ - { - "text": "Playing", - "value": "0" - }, - { - "text": "Paused", - "value": "1" - }, - { - "text": "Buffering", - "value": "3" - } - ] - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "name" - ], - "type": "tag" - }, - { - "params": [ - "title" - ], - "type": "tag" - }, - { - "params": [ - "quality" - ], - "type": "tag" - }, - { - "params": [ - "video_decision" - ], - "type": "tag" - }, - { - "params": [ - "quality_profile" - ], - "type": "tag" - }, - { - "params": [ - "platform" - ], - "type": "tag" - }, - { - "params": [ - "product_version" - ], - "type": "tag" - }, - { - "params": [ - "location" - ], - "type": "tag" - } - ], - "hide": false, - "limit": "", - "measurement": "Tautulli", - "orderByTime": "ASC", - "policy": "default", - "query": "SELECT distinct(\"session_key\") FROM \"Tautulli\" WHERE (\"type\" = 'Session') AND $timeFilter GROUP BY \"name\", \"title\", \"quality\", \"video_decision\", \"quality_profile\", \"platform\", \"product_version\", \"location\", \"player_state\"", - "rawQuery": true, - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "session_key" - ], - "type": "field" - }, - { - "params": [], - "type": "distinct" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Session" - } - ] - } - ], - "timeFrom": "1m", - "title": "Users Online", - "transform": "table", - "type": "table" -} diff --git a/dashboard/example_panels/piechart_tautulli_device_types.json b/dashboard/example_panels/piechart_tautulli_device_types.json deleted file mode 100644 index a4ec83b..0000000 --- a/dashboard/example_panels/piechart_tautulli_device_types.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "aliasColors": { - "Android": "#a4ca39", - "Chrome": "#db3236", - "Chromecast": "#039BE5", - "NetCast": "#c8135c", - "Playstation 4": "#003791", - "Plex Media Player": "#e67817", - "Roku": "#6d3c97", - "Samsung": "#034ea2", - "Tizen": "#034ea2", - "Xbox One": "#107c10", - "iOS": "#858487", - "tvOS": "#858487" - }, - "breakPoint": "50%", - "cacheTimeout": null, - "combine": { - "label": "Others", - "threshold": ".04" - }, - "datasource": "influxdb", - "decimals": 0, - "fontSize": "110%", - "format": "none", - "gridPos": { - "h": 10, - "w": 5, - "x": 0, - "y": 6 - }, - "id": 24, - "interval": null, - "legend": { - "percentage": true, - "percentageDecimals": 0, - "show": true, - "sort": "total", - "sortDesc": true, - "values": true - }, - "legendType": "On graph", - "links": [], - "maxDataPoints": 3, - "nullPointMode": "connected", - "pieType": "donut", - "strokeWidth": "7", - "targets": [ - { - "alias": "$tag_device_type", - "application": { - "filter": "" - }, - "functions": [], - "group": { - "filter": "" - }, - "groupBy": [ - { - "params": [ - "device_type" - ], - "type": "tag" - } - ], - "host": { - "filter": "" - }, - "item": { - "filter": "" - }, - "measurement": "Tautulli", - "mode": 0, - "options": { - "showDisabledItems": false, - "skipEmptyValues": false - }, - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "session_key" - ], - "type": "field" - }, - { - "params": [], - "type": "distinct" - }, - { - "params": [], - "type": "count" - } - ] - ], - "table": { - "skipEmptyValues": false - }, - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Session" - } - ], - "triggers": { - "acknowledged": 2, - "count": true, - "minSeverity": 3 - } - } - ], - "timeFrom": "1w", - "title": "Device Types", - "transparent": false, - "type": "grafana-piechart-panel", - "valueName": "total" -} diff --git a/dashboard/example_panels/piechart_tautulli_device_types.png b/dashboard/example_panels/piechart_tautulli_device_types.png deleted file mode 100644 index 1f08275c3981d6e38ee178f1de73a9ae716cd959..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35595 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 L_*1wFtb5GgwU`{x diff --git a/dashboard/example_panels/piechart_tautulli_stream_types.json b/dashboard/example_panels/piechart_tautulli_stream_types.json deleted file mode 100644 index 100281d..0000000 --- a/dashboard/example_panels/piechart_tautulli_stream_types.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "aliasColors": {}, - "breakPoint": "50%", - "cacheTimeout": null, - "combine": { - "label": "Others", - "threshold": "0" - }, - "datasource": "influxdb", - "decimals": 0, - "fontSize": "110%", - "format": "none", - "gridPos": { - "h": 10, - "w": 5, - "x": 5, - "y": 6 - }, - "id": 25, - "interval": null, - "legend": { - "percentage": true, - "percentageDecimals": 0, - "show": true, - "sort": "total", - "sortDesc": true, - "values": true - }, - "legendType": "On graph", - "links": [], - "maxDataPoints": 3, - "nullPointMode": "connected", - "pieType": "donut", - "strokeWidth": "7", - "targets": [ - { - "alias": "$tag_video_decision", - "application": { - "filter": "" - }, - "functions": [], - "group": { - "filter": "" - }, - "groupBy": [ - { - "params": [ - "video_decision" - ], - "type": "tag" - } - ], - "host": { - "filter": "" - }, - "item": { - "filter": "" - }, - "measurement": "Tautulli", - "mode": 0, - "options": { - "showDisabledItems": false, - "skipEmptyValues": false - }, - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "session_key" - ], - "type": "field" - }, - { - "params": [], - "type": "distinct" - }, - { - "params": [], - "type": "count" - } - ] - ], - "table": { - "skipEmptyValues": false - }, - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Session" - } - ], - "triggers": { - "acknowledged": 2, - "count": true, - "minSeverity": 3 - } - } - ], - "timeFrom": "1w", - "title": "Stream Types", - "transparent": false, - "type": "grafana-piechart-panel", - "valueName": "total" -} diff --git a/dashboard/example_panels/piechart_tautulli_stream_types.png b/dashboard/example_panels/piechart_tautulli_stream_types.png deleted file mode 100644 index 75c088aa81e2257ad37042c97dd053c6313f2bc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30810 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 diff --git a/dashboard/example_panels/singlestat_ombi_requests_pending.json b/dashboard/example_panels/singlestat_ombi_requests_pending.json deleted file mode 100644 index 0b09acf..0000000 --- a/dashboard/example_panels/singlestat_ombi_requests_pending.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "cacheTimeout": null, - "colorBackground": true, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "rgba(212, 74, 58, 0.58)" - ], - "datasource": "influxdb", - "format": "none", - "gauge": { - "maxValue": 40, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 2, - "x": 20, - "y": 0 - }, - "id": 8, - "interval": null, - "links": [ - { - "targetBlank": true, - "title": "Ombi", - "type": "absolute", - "url": "https://request.server.com/requests" - } - ], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Ombi", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "pending" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Request_Counts" - } - ] - } - ], - "thresholds": ".5,5", - "title": "Req. Pending", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" -} diff --git a/dashboard/example_panels/singlestat_ombi_requests_pending.png b/dashboard/example_panels/singlestat_ombi_requests_pending.png deleted file mode 100644 index e89bc700a630a07605c6b0997153d2f475b070a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3750 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 diff --git a/dashboard/example_panels/table_radarr_missing_avail_movies.json b/dashboard/example_panels/table_radarr_missing_avail_movies.json deleted file mode 100644 index 0ddeb4d..0000000 --- a/dashboard/example_panels/table_radarr_missing_avail_movies.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "columns": [], - "datasource": "influxdb", - "fontSize": "100%", - "gridPos": { - "h": 6, - "w": 4, - "x": 20, - "y": 3 - }, - "hideTimeOverride": true, - "id": 11, - "links": [ - { - "targetBlank": true, - "title": "Radarr", - "type": "absolute", - "url": "https://movies.server.com/wanted/missing" - } - ], - "minSpan": 6, - "pageSize": 4, - "scroll": true, - "showHeader": true, - "sort": { - "col": null, - "desc": false - }, - "styles": [ - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "Time", - "thresholds": [], - "type": "hidden", - "unit": "short" - }, - { - "alias": "Name", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "name", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Radarr", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - }, - { - "params": [], - "type": "distinct" - }, - { - "params": [ - "Movie" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Missing_Available" - }, - { - "condition": "AND", - "key": "server", - "operator": "=", - "value": "1" - } - ] - } - ], - "timeFrom": "1m", - "title": "Missing Available Movies", - "transform": "table", - "type": "table" -} diff --git a/dashboard/example_panels/table_radarr_missing_avail_movies.png b/dashboard/example_panels/table_radarr_missing_avail_movies.png deleted file mode 100644 index 932a66d4e99c9e6834756bdde0b5eb6e50b2bc48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6904 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 diff --git a/dashboard/example_panels/table_radarr_queue.json b/dashboard/example_panels/table_radarr_queue.json deleted file mode 100644 index 427b00e..0000000 --- a/dashboard/example_panels/table_radarr_queue.json +++ /dev/null @@ -1,161 +0,0 @@ -{ - "columns": [], - "datasource": "influxdb", - "fontSize": "90%", - "gridPos": { - "h": 5, - "w": 4, - "x": 20, - "y": 9 - }, - "hideTimeOverride": true, - "id": 20, - "interval": "32s", - "links": [ - { - "targetBlank": true, - "title": "Radarr", - "type": "absolute", - "url": "https://movies.server.com/wanted/missing" - } - ], - "minSpan": 6, - "pageSize": 3, - "scroll": true, - "showHeader": true, - "sort": { - "col": null, - "desc": false - }, - "styles": [ - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "Time", - "thresholds": [], - "type": "hidden", - "unit": "short" - }, - { - "alias": "Name", - "colorMode": "row", - "colors": [ - "#0a437c", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "name", - "preserveFormat": true, - "thresholds": [ - "20" - ], - "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": "row", - "colors": [ - "#0a437c", - "rgb(168, 147, 4)", - "#629e51" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "mappingType": 1, - "pattern": "T", - "thresholds": [ - "-1", - "1" - ], - "type": "string", - "unit": "none", - "valueMaps": [ - { - "text": "Tor", - "value": "0" - }, - { - "text": "Use", - "value": "1" - } - ] - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Radarr", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - }, - { - "params": [ - "Movie" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "quality" - ], - "type": "field" - }, - { - "params": [ - "Quality" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "protocol_id" - ], - "type": "field" - }, - { - "params": [ - "T" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Queue" - } - ] - } - ], - "timeFrom": "33s", - "title": "Movies in Queue", - "transform": "table", - "type": "table" -} diff --git a/dashboard/example_panels/table_radarr_queue.png b/dashboard/example_panels/table_radarr_queue.png deleted file mode 100644 index 58a853ba61760855f2d04b5da126b2480c39731f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5769 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 Xx|4(8$#tN&OroP<pkA%!@alg6SMrmQ diff --git a/dashboard/example_panels/table_sonarr_missing.json b/dashboard/example_panels/table_sonarr_missing.json deleted file mode 100644 index 018f181..0000000 --- a/dashboard/example_panels/table_sonarr_missing.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "columns": [], - "datasource": "influxdb", - "fontSize": "100%", - "gridPos": { - "h": 7, - "w": 4, - "x": 20, - "y": 14 - }, - "hideTimeOverride": true, - "id": 16, - "links": [ - { - "targetBlank": true, - "title": "Sonarr", - "type": "absolute", - "url": "https://tv.server.com/wanted/missing" - } - ], - "minSpan": 8, - "pageSize": 6, - "scroll": true, - "showHeader": true, - "sort": { - "col": null, - "desc": false - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "hidden" - }, - { - "alias": "Name", - "colorMode": "cell", - "colors": [ - "#e24d42", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "link": false, - "linkUrl": "", - "pattern": "name", - "preserveFormat": true, - "thresholds": [ - "20" - ], - "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": "row", - "colors": [ - "#e24d42", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "TV Show", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "mappingType": 1, - "pattern": "sonarrId", - "thresholds": [], - "type": "hidden", - "unit": "short" - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "sonarrId" - ], - "type": "tag" - } - ], - "measurement": "Sonarr", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - }, - { - "params": [ - "TV Show" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "sxe" - ], - "type": "field" - }, - { - "params": [ - "S/E" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Missing_Days" - } - ] - } - ], - "timeFrom": "33s", - "timeShift": null, - "title": "Missing TV Shows (Last 7 Days)", - "transform": "table", - "type": "table" -} diff --git a/dashboard/example_panels/table_sonarr_missing.png b/dashboard/example_panels/table_sonarr_missing.png deleted file mode 100644 index 52d89f3185c191ca31ec6df66a5571c84d498a98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14394 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 APyhe` diff --git a/dashboard/example_panels/table_sonarr_on_today.json b/dashboard/example_panels/table_sonarr_on_today.json deleted file mode 100644 index 29c210a..0000000 --- a/dashboard/example_panels/table_sonarr_on_today.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "columns": [], - "datasource": "influxdb", - "fontSize": "100%", - "gridPos": { - "h": 13, - "w": 4, - "x": 16, - "y": 16 - }, - "hideTimeOverride": true, - "id": 10, - "interval": "32s", - "links": [ - { - "targetBlank": true, - "title": "Sonarr", - "type": "absolute", - "url": "https://tv.server.com/wanted/missing" - } - ], - "minSpan": 8, - "pageSize": 14, - "scroll": true, - "showHeader": true, - "sort": { - "col": 3, - "desc": false - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "hidden" - }, - { - "alias": "Name", - "colorMode": "cell", - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "link": false, - "linkUrl": "", - "pattern": "name", - "preserveFormat": true, - "thresholds": [ - "20" - ], - "type": "string", - "unit": "short" - }, - { - "alias": "DL", - "colorMode": "row", - "colors": [ - "#0a50a1", - "rgba(237, 129, 40, 0.89)", - "#24c3ff" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "mappingType": 1, - "pattern": "downloaded", - "thresholds": [ - ".1", - "1" - ], - "type": "string", - "unit": "none", - "valueMaps": [ - { - "text": "No", - "value": "0" - }, - { - "text": "Yes", - "value": "1" - } - ] - }, - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "sonarrId", - "thresholds": [], - "type": "hidden", - "unit": "short" - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "limit": "", - "measurement": "Sonarr", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - }, - { - "params": [ - "TV Show" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "sxe" - ], - "type": "field" - }, - { - "params": [ - "S/E" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "downloaded" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Future" - } - ] - } - ], - "timeFrom": "33s", - "title": "TV Shows on Today", - "transform": "table", - "type": "table" -} diff --git a/dashboard/example_panels/table_sonarr_on_today.png b/dashboard/example_panels/table_sonarr_on_today.png deleted file mode 100644 index f88c6d19e1f953d2411cc4b7c7a8741ff4f5ea0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49745 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 Law4U|dcOYyR+5?R diff --git a/dashboard/example_panels/table_sonarr_queue.json b/dashboard/example_panels/table_sonarr_queue.json deleted file mode 100644 index a959a03..0000000 --- a/dashboard/example_panels/table_sonarr_queue.json +++ /dev/null @@ -1,154 +0,0 @@ -{ - "columns": [], - "datasource": "influxdb", - "fontSize": "100%", - "gridPos": { - "h": 8, - "w": 4, - "x": 20, - "y": 21 - }, - "hideTimeOverride": true, - "id": 19, - "interval": "30s", - "links": [ - { - "targetBlank": true, - "title": "Sonarr", - "type": "absolute", - "url": "https://tv.server.com/wanted/missing" - } - ], - "minSpan": 8, - "pageSize": 7, - "scroll": true, - "showHeader": true, - "sort": { - "col": null, - "desc": false - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "hidden" - }, - { - "alias": "Name", - "colorMode": "cell", - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "link": false, - "linkUrl": "", - "pattern": "name", - "preserveFormat": true, - "thresholds": [ - "20" - ], - "type": "string", - "unit": "short" - }, - { - "alias": "", - "colorMode": "row", - "colors": [ - "#0a437c", - "rgb(168, 147, 4)", - "#629e51" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 0, - "mappingType": 1, - "pattern": "T", - "thresholds": [ - "-1", - "1" - ], - "type": "string", - "unit": "short", - "valueMaps": [ - { - "text": "Tor", - "value": "0" - }, - { - "text": "Use", - "value": "1" - } - ] - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Sonarr", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - }, - { - "params": [ - "TV Show" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "sxe" - ], - "type": "field" - }, - { - "params": [ - "S/E" - ], - "type": "alias" - } - ], - [ - { - "params": [ - "protocol_id" - ], - "type": "field" - }, - { - "params": [ - "T" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Queue" - } - ] - } - ], - "timeFrom": "33s", - "timeShift": null, - "title": "TV Shows in Queue", - "transform": "table", - "type": "table" -} diff --git a/dashboard/example_panels/table_sonarr_queue.png b/dashboard/example_panels/table_sonarr_queue.png deleted file mode 100644 index a54b8018b1c116ac60d90e95a2705db0ca2f72d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7369 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 EFWa`!`2YX_ diff --git a/dashboard/panel_row_worldmap.json b/dashboard/panel_row_worldmap.json deleted file mode 100644 index 52a00e0..0000000 --- a/dashboard/panel_row_worldmap.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "circleMaxSize": "2", - "circleMinSize": "2", - "colors": [ - "#e67817", - "#6d3c97", - "#890f02" - ], - "datasource": "plex", - "decimals": 0, - "esGeoPoint": "geohash", - "esLocationName": "location", - "esMetric": "metric", - "gridPos": { - "h": 10, - "w": 10, - "x": 10, - "y": 6 - }, - "hideEmpty": false, - "hideTimeOverride": true, - "hideZero": false, - "id": 4, - "initialZoom": "4", - "interval": "", - "links": [], - "locationData": "table", - "mapCenter": "custom", - "mapCenterLatitude": "37.9", - "mapCenterLongitude": "-94.9", - "maxDataPoints": 1, - "minSpan": 8, - "mouseWheelZoom": false, - "showLegend": false, - "stickyLabels": false, - "tableQueryOptions": { - "geohashField": "geohash", - "labelField": "full_location", - "latitudeField": "latitude", - "longitudeField": "longitude", - "metricField": "metric", - "queryType": "coordinates" - }, - "targets": [ - { - "alias": "$tag_region_code", - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "latitude" - ], - "type": "tag" - }, - { - "params": [ - "longitude" - ], - "type": "tag" - }, - { - "params": [ - "full_location" - ], - "type": "tag" - }, - { - "params": [ - "name" - ], - "type": "tag" - } - ], - "measurement": "Tautulli", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "session_key" - ], - "type": "field" - }, - { - "params": [], - "type": "distinct" - }, - { - "params": [], - "type": "count" - }, - { - "params": [ - "metric" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Session" - } - ] - } - ], - "thresholds": "2,3", - "timeFrom": "1m", - "timeShift": null, - "title": "", - "type": "grafana-worldmap-panel", - "unitPlural": "Streams", - "unitSingle": "", - "unitSingular": "Stream", - "valueName": "current" -} diff --git a/dashboard/panel_us_only_worldmap.json b/dashboard/panel_us_only_worldmap.json deleted file mode 100644 index 99587c7..0000000 --- a/dashboard/panel_us_only_worldmap.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "circleMaxSize": 30, - "circleMinSize": "1", - "colors": [ - "#cca300", - "#c15c17", - "#890f02" - ], - "datasource": "influxdb", - "decimals": 0, - "esLocationName": "", - "esMetric": "$tag_counter", - "hideEmpty": false, - "hideZero": false, - "id": 41, - "initialZoom": "4", - "links": [], - "locationData": "states", - "mapCenter": "custom", - "mapCenterLatitude": "39.8283", - "mapCenterLongitude": "-98.5795", - "maxDataPoints": 1, - "minSpan": 8, - "showLegend": false, - "stickyLabels": false, - "targets": [ - { - "alias": "$tag_region_code", - "dsType": "influxdb", - "groupBy": [ - { - "type": "tag", - "params": [ - "region_code" - ] - } - ], - "measurement": "Tautulli", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "type": "field", - "params": [ - "session_key" - ] - }, - { - "type": "distinct", - "params": [] - }, - { - "type": "count", - "params": [] - }, - { - "type": "alias", - "params": [ - "metric" - ] - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Session" - } - ] - } - ], - "thresholds": "5,10", - "timeFrom": "1m", - "title": "", - "type": "grafana-worldmap-panel", - "unitPlural": "", - "unitSingle": "", - "unitSingular": "", - "valueName": "current", - "gridPos": { - "x": 16, - "y": 7, - "w": 8, - "h": 8 - }, - "mouseWheelZoom": false, - "tableQueryOptions": { - "queryType": "coordinates", - "geohashField": "geohash", - "latitudeField": "latitude", - "longitudeField": "longitude", - "metricField": "metric", - "labelField": "location" - } -} diff --git a/dashboard/plex_row_dashboard.json b/dashboard/plex_row_dashboard.json deleted file mode 100644 index 00588cb..0000000 --- a/dashboard/plex_row_dashboard.json +++ /dev/null @@ -1,1363 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_FIREWALL", - "label": "firewall", - "description": "", - "type": "datasource", - "pluginId": "influxdb", - "pluginName": "InfluxDB" - }, - { - "name": "DS_PLEX", - "label": "plex", - "description": "", - "type": "datasource", - "pluginId": "influxdb", - "pluginName": "InfluxDB" - }, - { - "name": "DS_STORAGE_SERVER", - "label": "storage_server", - "description": "", - "type": "datasource", - "pluginId": "influxdb", - "pluginName": "InfluxDB" - } - ], - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "5.2.2" - }, - { - "type": "panel", - "id": "grafana-worldmap-panel", - "name": "Worldmap Panel", - "version": "0.1.2" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "5.0.0" - }, - { - "type": "datasource", - "id": "influxdb", - "name": "InfluxDB", - "version": "5.0.0" - }, - { - "type": "panel", - "id": "singlestat", - "name": "Singlestat", - "version": "5.0.0" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "5.0.0" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": null, - "links": [], - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_FIREWALL}", - "fill": 1, - "gridPos": { - "h": 8, - "w": 16, - "x": 0, - "y": 0 - }, - "id": 1, - "legend": { - "avg": false, - "current": true, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "minSpan": 16, - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ - { - "alias": "Download", - "transform": "negative-Y" - } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "alias": "Download", - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "bandwidth", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "download_bitrate" - ], - "type": "field" - }, - { - "params": [], - "type": "last" - } - ] - ], - "tags": [ - { - "key": "interface", - "operator": "=", - "value": "outside" - } - ] - }, - { - "alias": "Upload", - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "interface" - ], - "type": "tag" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "bandwidth", - "orderByTime": "ASC", - "policy": "default", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "upload_bitrate" - ], - "type": "field" - }, - { - "params": [], - "type": "last" - } - ] - ], - "tags": [ - { - "key": "interface", - "operator": "=", - "value": "outside" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Bandwidth", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "bps", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "circleMaxSize": "5", - "circleMinSize": "1", - "colors": [ - "#cca300", - "#c15c17", - "#890f02" - ], - "datasource": "${DS_PLEX}", - "decimals": 0, - "esLocationName": "", - "esMetric": "$tag_counter", - "gridPos": { - "h": 8, - "w": 8, - "x": 16, - "y": 0 - }, - "hideEmpty": false, - "hideZero": false, - "id": 4, - "initialZoom": "4", - "links": [], - "locationData": "table", - "mapCenter": "custom", - "mapCenterLatitude": "39.8283", - "mapCenterLongitude": "-98.5795", - "maxDataPoints": 1, - "minSpan": 8, - "mouseWheelZoom": false, - "showLegend": false, - "stickyLabels": false, - "tableQueryOptions": { - "geohashField": "geohash", - "labelField": "location", - "latitudeField": "latitude", - "longitudeField": "longitude", - "metricField": "metric", - "queryType": "coordinates" - }, - "targets": [ - { - "alias": "$tag_region_code", - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "location" - ], - "type": "tag" - }, - { - "params": [ - "latitude" - ], - "type": "tag" - }, - { - "params": [ - "longitude" - ], - "type": "tag" - } - ], - "measurement": "Tautulli", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "location" - ], - "type": "field" - }, - { - "params": [], - "type": "count" - }, - { - "params": [ - "metric" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Session" - } - ] - } - ], - "thresholds": "5,10", - "timeFrom": "1m", - "title": "", - "type": "grafana-worldmap-panel", - "unitPlural": "", - "unitSingle": "", - "unitSingular": "", - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_FIREWALL}", - "decimals": 0, - "format": "bps", - "gauge": { - "maxValue": 800000000, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 0, - "y": 8 - }, - "id": 2, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "bandwidth", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "upload_bitrate" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "interface", - "operator": "=", - "value": "outside" - } - ] - } - ], - "thresholds": "300000000,700000000", - "title": "Upload", - "type": "singlestat", - "valueFontSize": "50%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_FIREWALL}", - "decimals": 0, - "format": "bps", - "gauge": { - "maxValue": 800000000, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 4, - "y": 8 - }, - "id": 3, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "bandwidth", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "download_bitrate" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "interface", - "operator": "=", - "value": "outside" - } - ] - } - ], - "thresholds": "300000000,700000000", - "title": "Download", - "type": "singlestat", - "valueFontSize": "50%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_PLEX}", - "format": "percent", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 8, - "y": 8 - }, - "id": 5, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "plex", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "CPU Utilization" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "server", - "operator": "=", - "value": "Plex" - } - ] - } - ], - "thresholds": "50,80", - "title": "Plex CPU Load", - "type": "singlestat", - "valueFontSize": "100%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_PLEX}", - "format": "none", - "gauge": { - "maxValue": 30, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 12, - "y": 8 - }, - "id": 6, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Tautulli", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "current_streams" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "stream_count" - } - ] - } - ], - "thresholds": "10,20", - "title": "Plex Current Streams", - "type": "singlestat", - "valueFontSize": "120%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_STORAGE_SERVER}", - "format": "percent", - "gauge": { - "maxValue": 30, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 16, - "y": 8 - }, - "id": 7, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Storage Servers", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "IO_Wait" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "server", - "operator": "=", - "value": "SAN3" - } - ] - } - ], - "thresholds": "5,15", - "title": "SAN IO_Wait", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_PLEX}", - "format": "none", - "gauge": { - "maxValue": 20, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 20, - "y": 8 - }, - "id": 8, - "interval": null, - "links": [ - { - "targetBlank": true, - "title": "Ombi", - "type": "absolute", - "url": "https://ombi.domain.tld/requests" - } - ], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Ombi", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "total" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Requests" - } - ] - } - ], - "thresholds": "1,10", - "title": "TV / Movie Requests in Queue", - "type": "singlestat", - "valueFontSize": "150%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "columns": [], - "datasource": "${DS_PLEX}", - "fontSize": "100%", - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 13 - }, - "hideTimeOverride": true, - "id": 9, - "links": [ - { - "targetBlank": true, - "title": "Tautulli", - "type": "absolute", - "url": "https://tautulli.domain.tld/home" - } - ], - "minSpan": 12, - "pageSize": 8, - "scroll": true, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "MM/DD/YY h:mm:ss a", - "decimals": 2, - "link": false, - "pattern": "Time", - "thresholds": [], - "type": "hidden", - "unit": "short" - }, - { - "alias": "User", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "name", - "preserveFormat": false, - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Movie / TV Show", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "title", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Transcode Decision", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "transcode_decision", - "preserveFormat": false, - "sanitize": false, - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Quality", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "quality", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Tautulli", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - } - ], - [ - { - "params": [ - "title" - ], - "type": "field" - } - ], - [ - { - "params": [ - "quality" - ], - "type": "field" - } - ], - [ - { - "params": [ - "transcode_decision" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Session" - } - ] - } - ], - "timeFrom": "1m", - "title": "Users Online", - "transform": "table", - "type": "table" - }, - { - "columns": [], - "datasource": "${DS_PLEX}", - "fontSize": "100%", - "gridPos": { - "h": 10, - "w": 6, - "x": 12, - "y": 13 - }, - "hideTimeOverride": true, - "id": 10, - "links": [ - { - "targetBlank": true, - "title": "Sonarr", - "type": "absolute", - "url": "https://sonarr.domain.tld/wanted/missing" - } - ], - "minSpan": 8, - "pageSize": 8, - "scroll": true, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "hidden" - }, - { - "alias": "Name", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "name", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Sonarr", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Missing" - } - ] - } - ], - "timeFrom": "30m", - "title": "Missing TV Shows", - "transform": "table", - "type": "table" - }, - { - "columns": [], - "datasource": "${DS_PLEX}", - "fontSize": "100%", - "gridPos": { - "h": 10, - "w": 6, - "x": 18, - "y": 13 - }, - "hideTimeOverride": true, - "id": 11, - "links": [ - { - "targetBlank": true, - "title": "Radarr", - "type": "absolute", - "url": "https://radarr.domain.tld/wanted/missing" - } - ], - "minSpan": 6, - "pageSize": 8, - "scroll": true, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "Time", - "thresholds": [], - "type": "hidden", - "unit": "short" - }, - { - "alias": "Name", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "name", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Radarr", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Missing" - } - ] - } - ], - "timeFrom": "31m", - "title": "Missing Movies", - "transform": "table", - "type": "table" - } - ], - "refresh": "30s", - "schemaVersion": 16, - "style": "dark", - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "browser", - "title": "Plex", - "uid": "iTbnha5mk", - "version": 1 -} diff --git a/dashboard/plex_us_only_dashboard.json b/dashboard/plex_us_only_dashboard.json deleted file mode 100644 index 9da3175..0000000 --- a/dashboard/plex_us_only_dashboard.json +++ /dev/null @@ -1,1351 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_FIREWALL", - "label": "firewall", - "description": "", - "type": "datasource", - "pluginId": "influxdb", - "pluginName": "InfluxDB" - }, - { - "name": "DS_PLEX", - "label": "plex", - "description": "", - "type": "datasource", - "pluginId": "influxdb", - "pluginName": "InfluxDB" - }, - { - "name": "DS_STORAGE_SERVER", - "label": "storage_server", - "description": "", - "type": "datasource", - "pluginId": "influxdb", - "pluginName": "InfluxDB" - } - ], - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "5.2.2" - }, - { - "type": "panel", - "id": "grafana-worldmap-panel", - "name": "Worldmap Panel", - "version": "0.1.2" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "5.0.0" - }, - { - "type": "datasource", - "id": "influxdb", - "name": "InfluxDB", - "version": "5.0.0" - }, - { - "type": "panel", - "id": "singlestat", - "name": "Singlestat", - "version": "5.0.0" - }, - { - "type": "panel", - "id": "table", - "name": "Table", - "version": "5.0.0" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": null, - "links": [], - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_FIREWALL}", - "fill": 1, - "gridPos": { - "h": 8, - "w": 16, - "x": 0, - "y": 0 - }, - "id": 1, - "legend": { - "avg": false, - "current": true, - "max": false, - "min": false, - "rightSide": false, - "show": false, - "total": false, - "values": true - }, - "lines": true, - "linewidth": 1, - "links": [], - "minSpan": 16, - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [ - { - "alias": "Download", - "transform": "negative-Y" - } - ], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "alias": "Download", - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "bandwidth", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "download_bitrate" - ], - "type": "field" - }, - { - "params": [], - "type": "last" - } - ] - ], - "tags": [ - { - "key": "interface", - "operator": "=", - "value": "outside" - } - ] - }, - { - "alias": "Upload", - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "$__interval" - ], - "type": "time" - }, - { - "params": [ - "interface" - ], - "type": "tag" - }, - { - "params": [ - "previous" - ], - "type": "fill" - } - ], - "measurement": "bandwidth", - "orderByTime": "ASC", - "policy": "default", - "refId": "B", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "upload_bitrate" - ], - "type": "field" - }, - { - "params": [], - "type": "last" - } - ] - ], - "tags": [ - { - "key": "interface", - "operator": "=", - "value": "outside" - } - ] - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Bandwidth", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "bps", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "circleMaxSize": 30, - "circleMinSize": "1", - "colors": [ - "#cca300", - "#c15c17", - "#890f02" - ], - "datasource": "${DS_PLEX}", - "decimals": 0, - "esLocationName": "", - "esMetric": "$tag_counter", - "gridPos": { - "h": 8, - "w": 8, - "x": 16, - "y": 0 - }, - "hideEmpty": false, - "hideZero": false, - "id": 4, - "initialZoom": "4", - "links": [], - "locationData": "states", - "mapCenter": "custom", - "mapCenterLatitude": "39.8283", - "mapCenterLongitude": "-98.5795", - "maxDataPoints": 1, - "minSpan": 8, - "mouseWheelZoom": false, - "showLegend": false, - "stickyLabels": false, - "tableQueryOptions": { - "geohashField": "geohash", - "labelField": "location", - "latitudeField": "latitude", - "longitudeField": "longitude", - "metricField": "metric", - "queryType": "coordinates" - }, - "targets": [ - { - "alias": "$tag_region_code", - "dsType": "influxdb", - "groupBy": [ - { - "params": [ - "region_code" - ], - "type": "tag" - } - ], - "measurement": "Tautulli", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "location" - ], - "type": "field" - }, - { - "params": [], - "type": "count" - }, - { - "params": [ - "metric" - ], - "type": "alias" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Session" - } - ] - } - ], - "thresholds": "5,10", - "timeFrom": "1m", - "title": "", - "type": "grafana-worldmap-panel", - "unitPlural": "", - "unitSingle": "", - "unitSingular": "", - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_FIREWALL}", - "decimals": 0, - "format": "bps", - "gauge": { - "maxValue": 800000000, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 0, - "y": 8 - }, - "id": 2, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "bandwidth", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "upload_bitrate" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "interface", - "operator": "=", - "value": "outside" - } - ] - } - ], - "thresholds": "300000000,700000000", - "title": "Upload", - "type": "singlestat", - "valueFontSize": "50%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_FIREWALL}", - "decimals": 0, - "format": "bps", - "gauge": { - "maxValue": 800000000, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 4, - "y": 8 - }, - "id": 3, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "bandwidth", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "download_bitrate" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "interface", - "operator": "=", - "value": "outside" - } - ] - } - ], - "thresholds": "300000000,700000000", - "title": "Download", - "type": "singlestat", - "valueFontSize": "50%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_PLEX}", - "format": "percent", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 8, - "y": 8 - }, - "id": 5, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "plex", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "CPU Utilization" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "server", - "operator": "=", - "value": "Plex" - } - ] - } - ], - "thresholds": "50,80", - "title": "Plex CPU Load", - "type": "singlestat", - "valueFontSize": "100%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "avg" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_PLEX}", - "format": "none", - "gauge": { - "maxValue": 30, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 12, - "y": 8 - }, - "id": 6, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Tautulli", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "current_streams" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "stream_count" - } - ] - } - ], - "thresholds": "10,20", - "title": "Plex Current Streams", - "type": "singlestat", - "valueFontSize": "120%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_STORAGE_SERVER}", - "format": "percent", - "gauge": { - "maxValue": 30, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 16, - "y": 8 - }, - "id": 7, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Storage Servers", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "IO_Wait" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "server", - "operator": "=", - "value": "SAN3" - } - ] - } - ], - "thresholds": "5,15", - "title": "SAN IO_Wait", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": true, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_PLEX}", - "format": "none", - "gauge": { - "maxValue": 20, - "minValue": 0, - "show": true, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 5, - "w": 4, - "x": 20, - "y": 8 - }, - "id": 8, - "interval": null, - "links": [ - { - "targetBlank": true, - "title": "Ombi", - "type": "absolute", - "url": "https://ombi.domain.tld/requests" - } - ], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Ombi", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "time_series", - "select": [ - [ - { - "params": [ - "total" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Requests" - } - ] - } - ], - "thresholds": "1,10", - "title": "TV / Movie Requests in Queue", - "type": "singlestat", - "valueFontSize": "150%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "columns": [], - "datasource": "${DS_PLEX}", - "fontSize": "100%", - "gridPos": { - "h": 10, - "w": 12, - "x": 0, - "y": 13 - }, - "hideTimeOverride": true, - "id": 9, - "links": [ - { - "targetBlank": true, - "title": "Tautulli", - "type": "absolute", - "url": "https://tautulli.domain.tld/home" - } - ], - "minSpan": 12, - "pageSize": 8, - "scroll": true, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "MM/DD/YY h:mm:ss a", - "decimals": 2, - "link": false, - "pattern": "Time", - "thresholds": [], - "type": "hidden", - "unit": "short" - }, - { - "alias": "User", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "name", - "preserveFormat": false, - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Movie / TV Show", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "title", - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Transcode Decision", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "transcode_decision", - "preserveFormat": false, - "sanitize": false, - "thresholds": [], - "type": "string", - "unit": "short" - }, - { - "alias": "Quality", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "quality", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Tautulli", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - } - ], - [ - { - "params": [ - "title" - ], - "type": "field" - } - ], - [ - { - "params": [ - "quality" - ], - "type": "field" - } - ], - [ - { - "params": [ - "transcode_decision" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Session" - } - ] - } - ], - "timeFrom": "1m", - "title": "Users Online", - "transform": "table", - "type": "table" - }, - { - "columns": [], - "datasource": "${DS_PLEX}", - "fontSize": "100%", - "gridPos": { - "h": 10, - "w": 6, - "x": 12, - "y": 13 - }, - "hideTimeOverride": true, - "id": 10, - "links": [ - { - "targetBlank": true, - "title": "Sonarr", - "type": "absolute", - "url": "https://sonarr.domain.tld/wanted/missing" - } - ], - "minSpan": 8, - "pageSize": 8, - "scroll": true, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "Time", - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "pattern": "Time", - "type": "hidden" - }, - { - "alias": "Name", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "name", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Sonarr", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Missing" - } - ] - } - ], - "timeFrom": "30m", - "title": "Missing TV Shows", - "transform": "table", - "type": "table" - }, - { - "columns": [], - "datasource": "${DS_PLEX}", - "fontSize": "100%", - "gridPos": { - "h": 10, - "w": 6, - "x": 18, - "y": 13 - }, - "hideTimeOverride": true, - "id": 11, - "links": [ - { - "targetBlank": true, - "title": "Radarr", - "type": "absolute", - "url": "https://radarr.domain.tld/wanted/missing" - } - ], - "minSpan": 6, - "pageSize": 8, - "scroll": true, - "showHeader": true, - "sort": { - "col": 0, - "desc": true - }, - "styles": [ - { - "alias": "", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "Time", - "thresholds": [], - "type": "hidden", - "unit": "short" - }, - { - "alias": "Name", - "colorMode": null, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(237, 129, 40, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "dateFormat": "YYYY-MM-DD HH:mm:ss", - "decimals": 2, - "pattern": "name", - "thresholds": [], - "type": "string", - "unit": "short" - } - ], - "targets": [ - { - "dsType": "influxdb", - "groupBy": [], - "measurement": "Radarr", - "orderByTime": "ASC", - "policy": "default", - "refId": "A", - "resultFormat": "table", - "select": [ - [ - { - "params": [ - "name" - ], - "type": "field" - } - ] - ], - "tags": [ - { - "key": "type", - "operator": "=", - "value": "Missing" - } - ] - } - ], - "timeFrom": "31m", - "title": "Missing Movies", - "transform": "table", - "type": "table" - } - ], - "refresh": "30s", - "schemaVersion": 16, - "style": "dark", - "tags": [], - "templating": { - "list": [] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "browser", - "title": "Plex", - "uid": "iTbnha5mk", - "version": 1 -} From 896ec10247d7623e74e33b322e7eab371ed9dde4 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Thu, 6 Dec 2018 14:39:04 -0600 Subject: [PATCH 112/120] Deleted out of scope scripts. Closes #41 --- Legacy/cisco_asa.py | 34 ---------------------------------- Legacy/raid_init.py | 35 ----------------------------------- Legacy/san.py | 36 ------------------------------------ 3 files changed, 105 deletions(-) delete mode 100644 Legacy/cisco_asa.py delete mode 100644 Legacy/raid_init.py delete mode 100644 Legacy/san.py diff --git a/Legacy/cisco_asa.py b/Legacy/cisco_asa.py deleted file mode 100644 index 33ebe44..0000000 --- a/Legacy/cisco_asa.py +++ /dev/null @@ -1,34 +0,0 @@ -# Do not edit this script. Edit configuration.py -import requests -from datetime import datetime, timezone -from influxdb import InfluxDBClient - -from Legacy import configuration - -current_time = datetime.now(timezone.utc).astimezone().isoformat() - -stats = { - 'token': requests.post('{}/api/tokenservices'.format(configuration.asa_url), - auth=(configuration.asa_username, configuration.asa_password), verify=False) -} -stats['headers'] = {'X-Auth-Token': stats['token'].headers['X-Auth-Token']} -stats['outside_interface'] = requests.get('{}/api/monitoring/device/interfaces/Outside'.format(configuration.asa_url), - headers=stats['headers'], verify=False).json() - -influx_payload = [ - { - "measurement": "bandwidth", - "tags": { - "interface": "outside" - }, - "time": current_time, - "fields": { - "upload_bitrate": stats['outside_interface']['outputBitRate'], - "download_bitrate": stats['outside_interface']['inputBitRate'] - } - } -] - -influx = InfluxDBClient(configuration.influxdb_url, configuration.influxdb_port, configuration.influxdb_username, - configuration.influxdb_password, configuration.asa_influxdb_db_name) -influx.write_points(influx_payload) diff --git a/Legacy/raid_init.py b/Legacy/raid_init.py deleted file mode 100644 index 4145bdd..0000000 --- a/Legacy/raid_init.py +++ /dev/null @@ -1,35 +0,0 @@ -import psutil -import mdstat -import platform -from datetime import datetime, timezone, timedelta -from influxdb import InfluxDBClient - -# Do not edit below this line # -influx_payload = [] -devices = { - 'md': mdstat.parse()['devices'], -} - -for array in devices['md']: - influx_payload.append( - { - "measurement": "Storage Servers", - "tags": { - "server": platform.uname()[1], - "mount_point": array, - "type": 'rebuild' - }, - "time": datetime.now(timezone.utc).astimezone().isoformat(), - "fields": { - "resync_progress": float(devices['md'][array]['resync']['progress'].replace('%', '')), - "resync_eta_mins": float(devices['md'][array]['resync']['finish'].replace('min', '')), - "resync_eta_date": '{:%A, %b %d %I:%M %p}'.format( - datetime.now() + timedelta(minutes=float(devices['md'][array]['resync']['finish'] - .replace('min', '')))), - "resync_speed_KiB/s": int(devices['md'][array]['resync']['speed'].replace('K/sec', '')), - } - } - ) - -influx = InfluxDBClient('grafana.domain.tld', 8086, 'root', 'root', 'storage_server') -influx.write_points(influx_payload) diff --git a/Legacy/san.py b/Legacy/san.py deleted file mode 100644 index bb7de3d..0000000 --- a/Legacy/san.py +++ /dev/null @@ -1,36 +0,0 @@ -import platform -import psutil -from datetime import datetime, timezone -from influxdb import InfluxDBClient - -mount_points = ['/mnt/raid6-a', '/mnt/raid6-b'] - -# Do not edit below this line # -influx_payload = [] -devices = { - 'mount_points': {} -} - -for mount in mount_points: - devices['mount_points'][mount] = { - 'usage': psutil.disk_usage(mount) - } - influx_payload.append( - { - "measurement": "Storage Servers", - "tags": { - "server": platform.uname()[1], - "mount_point": mount - }, - "time": datetime.now(timezone.utc).astimezone().isoformat(), - "fields": { - "bytes Used": devices['mount_points'][mount]['usage'].used, - "bytes Free": devices['mount_points'][mount]['usage'].free, - "bytes Total": devices['mount_points'][mount]['usage'].total, - "Utilization": devices['mount_points'][mount]['usage'].percent - } - } - ) - -influx = InfluxDBClient('grafana.domain.tld', 8086, 'root', 'root', 'storage_server') -influx.write_points(influx_payload) From 64b87a012e72f7c22443baf8ce6f1b45c9fa5e8e Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Fri, 7 Dec 2018 00:34:46 -0600 Subject: [PATCH 113/120] updated readme with badges, and docker update --- README.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5ddecfe..2e9a17f 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,23 @@ # Varken +[![Discord](https://img.shields.io/badge/Discord-Varken-7289DA.svg?logo=discord&style=flat-square)](https://discord.gg/AGTG44H) +[![BuyMeACoffee](https://img.shields.io/badge/BuyMeACoffee-Donate-ff813f.svg?logo=CoffeeScript&style=flat-square)](https://www.buymeacoffee.com/varken) +[![Docker Pulls](https://img.shields.io/docker/pulls/boerderij/varken.svg?style=flat-square)](https://hub.docker.com/r/boerderij/varken/) + Dutch for PIG. PIG is an Acronym for Plex/InfluxDB/Grafana varken is a standalone command-line utility to aggregate data from the Plex ecosystem into InfluxDB. Examples use Grafana for a frontend -Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), [Python3](https://www.python.org/downloads/), [InfluxDB](https://docs.influxdata.com/influxdb/v1.5/introduction/installation/) +Requirements: +* Python3.6+ +* Python3-pip <p align="center"> <img width="800" src="https://i.imgur.com/av8e0HP.png"> </p> -## Quick Setup (Varken Alpha) +## Quick Setup 1. Clone the repository `sudo git clone https://github.com/Boerderij/Varken.git /opt/Varken` 1. Follow the systemd install instructions located in `varken.systemd` 1. Create venv in project `cd /opt/Varken && /usr/bin/python3 -m venv varken-venv` @@ -23,21 +29,20 @@ Requirements /w install links: [Grafana](http://docs.grafana.org/installation/), 1. Make sure all the files have the appropriate permissions `sudo chown varken:varken -R /opt/Varken` 1. After completing the [getting started](http://docs.grafana.org/guides/getting_started/) portion of grafana, create your datasource for influxdb. 1. Install `grafana-cli plugins install grafana-worldmap-panel` -1. TODO:: Click the + on your menu and click import. Using the .json provided in this repo, paste it in and customize as you like. ### Docker -Repo is included in [si0972/grafana-scripts-docker](https://github.com/si0972/grafana-scripts-docker/tree/varken) +Repo is included in [Boerderij/docker-Varken](https://github.com/Boerderij/docker-Varken) <details><summary>Example</summary> <p> ``` -docker create \ - --name=grafana-scripts \ - -v <path to data>:/Scripts \ +docker run -d \ + --name=varken \ + -v <path to data>:/config \ -e PGID=<gid> -e PUID=<uid> \ - si0972/grafana-scripts:varken + boerderij/varken:nightly ``` </p> </details> From 3ee7a55d007a4c367c149e9f2c274a5a76aaf6fd Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Fri, 7 Dec 2018 00:40:37 -0600 Subject: [PATCH 114/120] typo fix for readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e9a17f..eb16bc3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Dutch for PIG. PIG is an Acronym for Plex/InfluxDB/Grafana -varken is a standalone command-line utility to aggregate data +Varken is a standalone command-line utility to aggregate data from the Plex ecosystem into InfluxDB. Examples use Grafana for a frontend From c345ed214dcbab428e94fb46fe5eefb5ca418954 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Fri, 7 Dec 2018 01:46:19 -0600 Subject: [PATCH 115/120] defined static method --- varken/iniparser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/varken/iniparser.py b/varken/iniparser.py index 7c12d2f..0c361f0 100644 --- a/varken/iniparser.py +++ b/varken/iniparser.py @@ -1,4 +1,3 @@ -import sys import configparser import logging from sys import exit @@ -42,7 +41,8 @@ class INIParser(object): sids = self.clean_check(global_server_ids, t) return sids - def clean_check(self, server_id_list, server_type=None): + @staticmethod + def clean_check(server_id_list, server_type=None): t = server_type sid_list = server_id_list cleaned_list = sid_list.replace(' ', '').split(',') From 211032993424156f8a0b9a38ae4b07371ec4b958 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Fri, 7 Dec 2018 01:55:16 -0600 Subject: [PATCH 116/120] Check for python3.6 or higher --- Varken.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Varken.py b/Varken.py index 1e1e94c..f904846 100644 --- a/Varken.py +++ b/Varken.py @@ -1,6 +1,11 @@ +import sys + +# Check for python3.6 or newer to resolve erroneous typing.NamedTuple issues +if sys.version_info < (3, 6): + exit('Varken requires python3.6 or newer') + import schedule import threading -import sys import platform import distro @@ -20,6 +25,7 @@ from varken.varkenlogger import VarkenLogger PLATFORM_LINUX_DISTRO = ' '.join(x for x in distro.linux_distribution() if x) + def threaded(job): thread = threading.Thread(target=job) thread.start() @@ -57,7 +63,6 @@ if __name__ == "__main__": )) vl.logger.info(u"Python {}".format(sys.version)) - CONFIG = INIParser(DATA_FOLDER) DBMANAGER = DBManager(CONFIG.influx_server) From 93069832ff2e2ed83bf6b2c5851a25ff7009c5c9 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Fri, 7 Dec 2018 02:01:23 -0600 Subject: [PATCH 117/120] v0.3-nightly --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63bf645..5c6d8ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log +## [v0.3-nightly](https://github.com/Boerderij/Varken/tree/v0.3-nightly) (2018-12-07) +[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.2-nightly...v0.3-nightly) + +**Implemented enhancements:** + +- Create Changelog for nightly release [\#39](https://github.com/Boerderij/Varken/issues/39) +- Create proper logging [\#34](https://github.com/Boerderij/Varken/issues/34) + +**Closed issues:** + +- Remove "dashboard" folder and subfolders [\#42](https://github.com/Boerderij/Varken/issues/42) +- Remove "Legacy" folder [\#41](https://github.com/Boerderij/Varken/issues/41) + ## [v0.2-nightly](https://github.com/Boerderij/Varken/tree/v0.2-nightly) (2018-12-06) [Full Changelog](https://github.com/Boerderij/Varken/compare/v0.1...v0.2-nightly) From 6c534c63b62ec9a2215b47164aefd02427d610d2 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 9 Dec 2018 18:00:39 -0600 Subject: [PATCH 118/120] added server ID to ombi Fixes #43 --- varken/ombi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/varken/ombi.py b/varken/ombi.py index dbc582b..f38ca49 100644 --- a/varken/ombi.py +++ b/varken/ombi.py @@ -39,7 +39,8 @@ class OmbiAPI(object): { "measurement": "Ombi", "tags": { - "type": "Request_Total" + "type": "Request_Total", + "server": self.server.id }, "time": self.now, "fields": { From ef7f471d58315311af61924c8af8a63b08fac554 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 9 Dec 2018 21:41:38 -0600 Subject: [PATCH 119/120] added asa module. fixes #44. Version Bump 1.0!!! --- Varken.py | 9 +++++- data/varken.example.ini | 7 +++-- requirements.txt | 1 + varken/__init__.py | 2 +- varken/cisco.py | 62 +++++++++++++++++++++++++++++++++++++++++ varken/helpers.py | 8 +++++- varken/iniparser.py | 37 ++++++++++++++---------- varken/structures.py | 8 ++++++ 8 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 varken/cisco.py diff --git a/Varken.py b/Varken.py index f904846..6fe4ec3 100644 --- a/Varken.py +++ b/Varken.py @@ -20,6 +20,7 @@ from varken.sonarr import SonarrAPI from varken.tautulli import TautulliAPI from varken.radarr import RadarrAPI from varken.ombi import OmbiAPI +from varken.cisco import CiscoAPI from varken.dbmanager import DBManager from varken.varkenlogger import VarkenLogger @@ -98,8 +99,14 @@ if __name__ == "__main__": if server.request_total_counts: schedule.every(server.request_total_run_seconds).seconds.do(threaded, OMBI.get_total_requests) + if CONFIG.ciscoasa_enabled: + for firewall in CONFIG.ciscoasa_firewalls: + ASA = CiscoAPI(firewall, DBMANAGER) + schedule.every(firewall.get_bandwidth_run_seconds).seconds.do(threaded, ASA.get_bandwidth) + # Run all on startup - SERVICES_ENABLED = [CONFIG.ombi_enabled, CONFIG.radarr_enabled, CONFIG.tautulli_enabled, CONFIG.sonarr_enabled] + SERVICES_ENABLED = [CONFIG.ombi_enabled, CONFIG.radarr_enabled, CONFIG.tautulli_enabled, + CONFIG.sonarr_enabled, CONFIG.ciscoasa_enabled] if not [enabled for enabled in SERVICES_ENABLED if enabled]: exit("All services disabled. Exiting") schedule.run_all() diff --git a/data/varken.example.ini b/data/varken.example.ini index f38217f..392ed80 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -10,7 +10,7 @@ sonarr_server_ids = 1,2 radarr_server_ids = 1,2 tautulli_server_ids = 1 ombi_server_ids = 1 -asa = false +ciscoasa_firewall_ids = false [influxdb] url = influxdb.domain.tld @@ -81,10 +81,11 @@ request_type_run_seconds = 300 get_request_total_counts = true request_total_run_seconds = 300 -[asa] +[ciscoasa-1] url = firewall.domain.tld username = cisco password = cisco -influx_db = asa +outside_interface = WAN ssl = false verify_ssl = true +get_bandwidth_run_seconds = 300 diff --git a/requirements.txt b/requirements.txt index 8fdd006..bab1e82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ geoip2>=2.9.0 influxdb>=5.2.0 schedule>=0.5.0 distro>=1.3.0 +urllib3>=1.22 \ No newline at end of file diff --git a/varken/__init__.py b/varken/__init__.py index cd46408..341988c 100644 --- a/varken/__init__.py +++ b/varken/__init__.py @@ -1 +1 @@ -VERSION = 0.2 +VERSION = 1.0 diff --git a/varken/cisco.py b/varken/cisco.py new file mode 100644 index 0000000..6ce3392 --- /dev/null +++ b/varken/cisco.py @@ -0,0 +1,62 @@ +import logging +from requests import Session, Request +from datetime import datetime, timezone + +from varken.helpers import connection_handler + + +class CiscoAPI(object): + def __init__(self, firewall, dbmanager): + self.now = datetime.now(timezone.utc).astimezone().isoformat() + self.dbmanager = dbmanager + self.firewall = firewall + # Create session to reduce server web thread load, and globally define pageSize for all requests + self.session = Session() + self.session.auth = (self.firewall.username, self.firewall.password) + self.logger = logging.getLogger() + + self.get_token() + + def __repr__(self): + return "<ciscoasa-{}>".format(self.firewall.id) + + def get_token(self): + endpoint = '/api/tokenservices' + + req = self.session.prepare_request(Request('POST', self.firewall.url + endpoint)) + post = connection_handler(self.session, req, self.firewall.verify_ssl) + + if not post: + return + + self.session.headers = {'X-Auth-Token': post} + + def get_bandwidth(self): + self.now = datetime.now(timezone.utc).astimezone().isoformat() + endpoint = '/api/monitoring/device/interfaces/' + self.firewall.outside_interface + + if not self.session.headers: + return + + req = self.session.prepare_request(Request('GET', self.firewall.url + endpoint)) + print(req.headers) + get = connection_handler(self.session, req, self.firewall.verify_ssl) + + if not get: + return + + influx_payload = [ + { + "measurement": "Cisco ASA", + "tags": { + "interface": self.firewall.outside_interface + }, + "time": self.now, + "fields": { + "upload_bitrate": get['outputBitRate'], + "download_bitrate": get['inputBitRate'] + } + } + ] + + self.dbmanager.write_points(influx_payload) diff --git a/varken/helpers.py b/varken/helpers.py index 00ca318..25f99d9 100644 --- a/varken/helpers.py +++ b/varken/helpers.py @@ -2,10 +2,10 @@ import os import time import tarfile import hashlib +import urllib3 import geoip2.database import logging -from functools import update_wrapper from json.decoder import JSONDecodeError from os.path import abspath, join from requests.exceptions import InvalidSchema, SSLError @@ -58,6 +58,8 @@ def connection_handler(session, request, verify): v = verify return_json = False + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + try: get = s.send(r, verify=v) if get.status_code == 401: @@ -69,6 +71,10 @@ def connection_handler(session, request, verify): return_json = get.json() except JSONDecodeError: logger.error('No JSON response... BORKED! Let us know in discord') + # 204 No Content is for ASA only + elif get.status_code == 204: + if get.headers['X-Auth-Token']: + return get.headers['X-Auth-Token'] except InvalidSchema: logger.error('You added http(s):// in the config file. Don\'t do that.') diff --git a/varken/iniparser.py b/varken/iniparser.py index 0c361f0..30629a0 100644 --- a/varken/iniparser.py +++ b/varken/iniparser.py @@ -2,14 +2,14 @@ import configparser import logging from sys import exit from os.path import join, exists -from varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer +from varken.structures import SonarrServer, RadarrServer, OmbiServer, TautulliServer, InfluxServer, CiscoASAFirewall logger = logging.getLogger() class INIParser(object): def __init__(self, data_folder): - self.config = configparser.ConfigParser() + self.config = configparser.ConfigParser(interpolation=None) self.data_folder = data_folder self.influx_server = InfluxServer() @@ -26,8 +26,8 @@ class INIParser(object): self.tautulli_enabled = False self.tautulli_servers = [] - self.asa_enabled = False - self.asa = None + self.ciscoasa_enabled = False + self.ciscoasa_firewalls = [] self.parse_opts() @@ -172,15 +172,22 @@ class INIParser(object): self.ombi_servers.append(server) # Parse ASA opts - if self.config.getboolean('global', 'asa'): - self.asa_enabled = True - url = self.config.get('asa', 'url') - username = self.config.get('asa', 'username') - password = self.config.get('asa', 'password') - scheme = 'https://' if self.config.getboolean('asa', 'ssl') else 'http://' - verify_ssl = self.config.getboolean('asa', 'verify_ssl') - if scheme != 'https://': - verify_ssl = False - db_name = self.config.get('asa', 'influx_db') + self.ciscoasa_enabled = self.enable_check('ciscoasa_firewall_ids') - self.asa = (scheme + url, username, password, verify_ssl, db_name) + if self.ciscoasa_enabled: + fids = self.config.get('global', 'ciscoasa_firewall_ids').strip(' ').split(',') + for firewall_id in fids: + ciscoasa_section = 'ciscoasa-' + firewall_id + url = self.config.get(ciscoasa_section, 'url') + username = self.config.get(ciscoasa_section, 'username') + password = self.config.get(ciscoasa_section, 'password') + scheme = 'https://' if self.config.getboolean(ciscoasa_section, 'ssl') else 'http://' + verify_ssl = self.config.getboolean(ciscoasa_section, 'verify_ssl') + if scheme != 'https://': + verify_ssl = False + outside_interface = self.config.get(ciscoasa_section, 'outside_interface') + get_bandwidth_run_seconds = self.config.getint(ciscoasa_section, 'get_bandwidth_run_seconds') + + firewall = CiscoASAFirewall(firewall_id, scheme + url, username, password, outside_interface, + verify_ssl, get_bandwidth_run_seconds) + self.ciscoasa_firewalls.append(firewall) diff --git a/varken/structures.py b/varken/structures.py index d934ece..44c202a 100644 --- a/varken/structures.py +++ b/varken/structures.py @@ -70,6 +70,14 @@ class InfluxServer(NamedTuple): username: str = 'root' password: str = 'root' +class CiscoASAFirewall(NamedTuple): + id: int = None + url: str = '192.168.1.1' + username: str = 'cisco' + password: str = 'cisco' + outside_interface: str = None + verify_ssl: bool = False + get_bandwidth_run_seconds: int = 30 class OmbiRequestCounts(NamedTuple): pending: int = 0 From 32b8884c1f60f6707479af14694a2627eb28b171 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" <nick@cajun.pro> Date: Sun, 9 Dec 2018 21:49:01 -0600 Subject: [PATCH 120/120] updated changelog for v1.0 --- CHANGELOG.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c6d8ff..d017bd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [v1.0](https://github.com/Boerderij/Varken/tree/v1.0) (2018-12-09) +[Full Changelog](https://github.com/Boerderij/Varken/compare/v0.3-nightly...v1.0) + +**Implemented enhancements:** + +- Add cisco asa from legacy [\#44](https://github.com/Boerderij/Varken/issues/44) +- Add server ID to ombi to differenciate [\#43](https://github.com/Boerderij/Varken/issues/43) + ## [v0.3-nightly](https://github.com/Boerderij/Varken/tree/v0.3-nightly) (2018-12-07) [Full Changelog](https://github.com/Boerderij/Varken/compare/v0.2-nightly...v0.3-nightly) @@ -63,7 +71,3 @@ - update plex\_dashboard.json [\#5](https://github.com/Boerderij/Varken/pull/5) ([ghost](https://github.com/ghost)) - Update README.md [\#4](https://github.com/Boerderij/Varken/pull/4) ([ghost](https://github.com/ghost)) - added sickrage portion [\#3](https://github.com/Boerderij/Varken/pull/3) ([ghost](https://github.com/ghost)) - - - -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file